@stokr/components-library 3.0.68 → 3.0.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,7 +14,10 @@ import YtLogo from "../../static/images/social/youtube_social_circle_red.png.js"
14
14
  import TelegramLogo from "../../static/images/social/Telegram-Logo.png.js";
15
15
  import IsoLogo from "../../static/images/iso27001-black.svg.js";
16
16
  import { SocialLinksContainer } from "../Header/Header.styles.js";
17
- const CollapseWrapper = ({ children, collapse = false, isOpened = false }) => collapse ? /* @__PURE__ */ jsx(Collapse, { isOpened, children }) : /* @__PURE__ */ jsx(Fragment$1, { children });
17
+ const CollapseWrapper = ({ children, collapse = false, isOpened = false }) => collapse ? (
18
+ // inert while collapsed so the hidden links leave the tab order / a11y tree.
19
+ /* @__PURE__ */ jsx(Collapse, { isOpened, children: /* @__PURE__ */ jsx("div", { ...isOpened ? {} : { inert: true }, children }) })
20
+ ) : /* @__PURE__ */ jsx(Fragment$1, { children });
18
21
  CollapseWrapper.propTypes = {
19
22
  children: PropTypes.node.isRequired,
20
23
  collapse: PropTypes.bool,
@@ -25,31 +25,7 @@ import { sizes } from "../../styles/rwd.js";
25
25
  import { track } from "../../analytics/index.js";
26
26
  import { navigateToHref } from "../../routing/navigate-app.js";
27
27
  import { Breakdown } from "../breakdown/Breakdown.js";
28
- const UserAvatarComponent = ({ avatar }) => {
29
- const [photo, setphoto] = useState(avatarPlaceholder);
30
- const prevAvatar = usePrevious(avatar);
31
- const checkUserPhoto = (_avatar) => {
32
- try {
33
- const http = new XMLHttpRequest();
34
- http.open("HEAD", _avatar, true);
35
- http.send();
36
- if (http.status !== 404) {
37
- setphoto(_avatar);
38
- }
39
- } catch (error) {
40
- console.log("error: ", error);
41
- }
42
- };
43
- useEffect(() => {
44
- checkUserPhoto(avatar);
45
- }, [avatar]);
46
- useEffect(() => {
47
- if (avatar != prevAvatar) {
48
- checkUserPhoto(avatar);
49
- }
50
- }, [avatar]);
51
- return /* @__PURE__ */ jsx(UserAvatar, { src: photo });
52
- };
28
+ const UserAvatarComponent = ({ avatar }) => /* @__PURE__ */ jsx(UserAvatar, { src: avatar || avatarPlaceholder });
53
29
  const RenderSubMenu = ({
54
30
  title,
55
31
  isActive,
@@ -189,7 +165,7 @@ function HeaderView({
189
165
  withSidebar && /* @__PURE__ */ jsx(SidebarToggle, { isSidebarExpanded, onClick: sidebarHandler, children: /* @__PURE__ */ jsx(HamburgerIcon, {}) }),
190
166
  /* @__PURE__ */ jsxs(HeaderInner, { withSidebar, children: [
191
167
  /* @__PURE__ */ jsxs(MainNavWrap, { hasProgress: progress, children: [
192
- /* @__PURE__ */ jsx(Logo, { isHighlight: currentActiveMenu === "main", children: /* @__PURE__ */ jsx(stdin_default$1, { to: useRelativePathForMenu ? "/" : platformURL, "data-cy": "logo-nav-link", children: /* @__PURE__ */ jsx(stdin_default$2, {}) }) }),
168
+ /* @__PURE__ */ jsx(Logo, { isHighlight: currentActiveMenu === "main", children: /* @__PURE__ */ jsx(stdin_default$1, { to: useRelativePathForMenu ? "/" : platformURL, "data-cy": "logo-nav-link", "aria-label": "STOKR home", children: /* @__PURE__ */ jsx(stdin_default$2, {}) }) }),
193
169
  !progress && /* @__PURE__ */ jsx(HeaderMainNav, { children: /* @__PURE__ */ jsx(MenuNav, { children: /* @__PURE__ */ jsxs("ul", { children: [
194
170
  /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
195
171
  stdin_default$1,
@@ -248,6 +224,9 @@ function HeaderView({
248
224
  isActive: currentActiveMenu === "main",
249
225
  onClick: () => toggleMenu("main"),
250
226
  withSidebar,
227
+ type: "button",
228
+ "aria-label": "Toggle navigation menu",
229
+ "aria-expanded": currentActiveMenu === "main",
251
230
  children: [
252
231
  /* @__PURE__ */ jsx("span", {}),
253
232
  /* @__PURE__ */ jsx("span", {}),
@@ -1,12 +1,10 @@
1
1
  import Cookies from "js-cookie";
2
- import axios from "axios";
3
2
  import axiosInstance from "../model/axios.js";
4
3
  import axiosInstance$1 from "../model/axiosPublic.js";
5
4
  import getCookieDomain from "../utils/get-cookie-domain.js";
6
5
  import { getLastFirebaseConfig } from "../config.js";
7
6
  import { initFirebase, getFirebaseAuth } from "../firebase-config.js";
8
7
  import { updatePassword, updateProfile, signInWithCustomToken, signInWithEmailAndPassword, createUserWithEmailAndPassword, applyActionCode, verifyPasswordResetCode, confirmPasswordReset, onAuthStateChanged } from "firebase/auth";
9
- import { getConfig } from "../runtime-config.js";
10
8
  function normalizeCustomToken(token) {
11
9
  if (token == null) return "";
12
10
  if (typeof token === "object" && token !== null) {
@@ -372,15 +370,32 @@ class Auth {
372
370
  });
373
371
  }
374
372
  /**
375
- * Upload Photo
376
- * @param data
373
+ * Get the authenticated user's avatar from service-media.
374
+ * Returns the image Blob, or null when the user has no picture (404).
377
375
  */
378
- static uploadPhoto(data) {
376
+ static getAvatar() {
379
377
  return new Promise((resolve, reject) => {
380
- axios.post(`${getConfig("photoApiUrl")}/media/picture/create`, data, {
381
- headers: {
382
- "Content-Type": "multipart/form-data"
378
+ axiosInstance.post(`media/user/avatar/get`, null, { responseType: "blob" }).then((response) => {
379
+ resolve(response.data);
380
+ }).catch((err) => {
381
+ if (err?.response?.status === 404) {
382
+ resolve(null);
383
+ return;
383
384
  }
385
+ reject(err);
386
+ });
387
+ });
388
+ }
389
+ /**
390
+ * Upload the authenticated user's avatar to service-media.
391
+ * Responds 201 with the processed (resized) image, so we read it back as a Blob.
392
+ * @param data FormData with a `file` field (jpeg/png, ≤10mb)
393
+ */
394
+ static uploadPhoto(data) {
395
+ return new Promise((resolve, reject) => {
396
+ axiosInstance.post(`media/user/avatar/upload`, data, {
397
+ responseType: "blob",
398
+ headers: { "Content-Type": "multipart/form-data" }
384
399
  }).then((response) => {
385
400
  resolve(response.data);
386
401
  }).catch((err) => {
@@ -389,16 +404,11 @@ class Auth {
389
404
  });
390
405
  }
391
406
  /**
392
- * Delete Photo
393
- * @param data
407
+ * Delete the authenticated user's avatar from service-media.
394
408
  */
395
- static deletePhoto(data) {
409
+ static deletePhoto() {
396
410
  return new Promise((resolve, reject) => {
397
- axios.post(`${getConfig("photoApiUrl")}/media/picture/delete`, data, {
398
- headers: {
399
- "Content-Type": "multipart/form-data"
400
- }
401
- }).then((response) => {
411
+ axiosInstance.post(`media/user/avatar/delete`).then((response) => {
402
412
  resolve(response.data);
403
413
  }).catch((err) => {
404
414
  reject(err);
@@ -102,6 +102,7 @@ class AuthProviderClass extends Component {
102
102
  INACTIVITY_EVENTS.forEach((event) => window.removeEventListener(event, this.resetInactivityTimer));
103
103
  this.clearInactivityTimer();
104
104
  this.clearCookieExpiryTimer();
105
+ if (this.avatarObjectUrl) URL.revokeObjectURL(this.avatarObjectUrl);
105
106
  }
106
107
  checkTokenIsValid = (redirect = false) => {
107
108
  const accessToken = Auth.getAccessToken();
@@ -398,6 +399,10 @@ class AuthProviderClass extends Component {
398
399
  delete axiosInstance.defaults.headers.common.Authorization;
399
400
  reset();
400
401
  this.userRef.current = null;
402
+ if (this.avatarObjectUrl) {
403
+ URL.revokeObjectURL(this.avatarObjectUrl);
404
+ this.avatarObjectUrl = void 0;
405
+ }
401
406
  this.setState({
402
407
  user: null,
403
408
  firebaseUser: null,
@@ -446,20 +451,27 @@ class AuthProviderClass extends Component {
446
451
  merged.active = merged.emailVerified;
447
452
  return merged;
448
453
  };
449
- checkUserPhoto = (avatar) => {
454
+ // Fetch the authenticated user's avatar from service-media as a Blob and expose it as an
455
+ // object URL. Falls back to the placeholder on 404/error. The endpoint requires the Bearer
456
+ // token, so it can't be used directly as an <img src> — hence the blob round-trip.
457
+ loadAvatar = async () => {
450
458
  try {
451
- const http = new XMLHttpRequest();
452
- http.open("HEAD", avatar, true);
453
- http.onload = () => {
454
- if (http.status === 200) {
455
- this.setState({ avatar });
456
- }
457
- };
458
- http.send();
459
+ const blob = await Auth.getAvatar();
460
+ this.setAvatarObjectUrl(blob ? URL.createObjectURL(blob) : avatarPlaceholder);
459
461
  } catch (error) {
460
- console.log("error: ", error);
462
+ console.log("error loading avatar: ", error);
463
+ this.setAvatarObjectUrl(avatarPlaceholder);
464
+ }
465
+ };
466
+ setAvatarObjectUrl = (nextAvatar) => {
467
+ if (this.avatarObjectUrl && this.avatarObjectUrl !== nextAvatar) {
468
+ URL.revokeObjectURL(this.avatarObjectUrl);
461
469
  }
470
+ this.avatarObjectUrl = nextAvatar?.startsWith("blob:") ? nextAvatar : void 0;
471
+ this.setState({ avatar: nextAvatar });
462
472
  };
473
+ // Kept for backward compatibility; refetches the current user's avatar.
474
+ checkUserPhoto = () => this.loadAvatar();
463
475
  getUser = async (accessToken = Auth.getAccessToken(), firebaseUserOverride) => {
464
476
  if (!accessToken) {
465
477
  this.setState({
@@ -533,11 +545,7 @@ class AuthProviderClass extends Component {
533
545
  const user = this.patchUserObject(result.user, firebaseUser);
534
546
  this.checkUserIsValid(user);
535
547
  customValidateGetUser?.(user);
536
- const userIdForMedia = user._id ?? user.id;
537
- if (userIdForMedia) {
538
- const userAvatar = `${getConfig("photoApiUrl")}/media/picture/view/${userIdForMedia}`;
539
- this.checkUserPhoto(userAvatar);
540
- }
548
+ this.loadAvatar();
541
549
  this.setUser(user);
542
550
  this.setState({
543
551
  isFetchingUser: false
@@ -617,13 +625,10 @@ class AuthProviderClass extends Component {
617
625
  return;
618
626
  }
619
627
  const formData = new FormData();
620
- formData.append("userId", user._id);
621
628
  formData.append("file", file);
622
- const result = await Auth.uploadPhoto(formData);
623
- this.setState({
624
- avatar: `${getConfig("photoApiUrl")}/media/picture/view/${user._id}?ignore=${Date.now()}`
625
- });
626
- return result;
629
+ const blob = await Auth.uploadPhoto(formData);
630
+ this.setAvatarObjectUrl(blob ? URL.createObjectURL(blob) : avatarPlaceholder);
631
+ return blob;
627
632
  } catch (error) {
628
633
  console.log(`Error: ${error}`);
629
634
  throw error;
@@ -634,12 +639,8 @@ class AuthProviderClass extends Component {
634
639
  if (!user) {
635
640
  return;
636
641
  }
637
- const formData = new FormData();
638
- formData.append("userId", user._id);
639
- await Auth.deletePhoto(formData);
640
- this.setState({
641
- avatar: void 0
642
- });
642
+ await Auth.deletePhoto();
643
+ this.setAvatarObjectUrl(avatarPlaceholder);
643
644
  };
644
645
  replaceLocationPathName = () => {
645
646
  const pathname = this.props.location?.pathname ?? "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stokr/components-library",
3
- "version": "3.0.68",
3
+ "version": "3.0.70",
4
4
  "description": "STOKR - Components Library",
5
5
  "author": "Bilal Hodzic <bilal@stokr.io>",
6
6
  "license": "MIT",