@nextelco/common-ui 2.1.102 → 2.1.103
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.
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useEffect } from "react";
|
|
2
|
+
import { useRef, useState, useEffect } from "react";
|
|
3
3
|
import * as MsalReact from "@azure/msal-react";
|
|
4
4
|
import UserAvatar from "../UserAvatar/UserAvatar.js";
|
|
5
5
|
const GRAPH = "https://graph.microsoft.com/v1.0";
|
|
@@ -43,50 +43,52 @@ const profileCache = /* @__PURE__ */ new Map();
|
|
|
43
43
|
function PhotoAvatar({ oid, name, email, className = "", showCard = false, getToken }) {
|
|
44
44
|
const { instance, accounts } = MsalReact.useMsal();
|
|
45
45
|
const account = instance.getActiveAccount() ?? accounts[0];
|
|
46
|
-
const
|
|
47
|
-
const
|
|
46
|
+
const accountKey = account?.homeAccountId ?? null;
|
|
47
|
+
const getTokenRef = useRef(getToken);
|
|
48
|
+
getTokenRef.current = getToken;
|
|
49
|
+
const accountRef = useRef(account);
|
|
50
|
+
accountRef.current = account;
|
|
51
|
+
const temGetToken = Boolean(getToken);
|
|
52
|
+
const cacheKey = temGetToken ? "self" : oid ?? accountKey ?? "me";
|
|
53
|
+
const [url, setUrl] = useState(() => photoCache.get(cacheKey) ?? null);
|
|
54
|
+
const [profile, setProfile] = useState(() => profileCache.get(cacheKey) ?? null);
|
|
48
55
|
useEffect(() => {
|
|
49
|
-
if (!
|
|
56
|
+
if (!temGetToken && !accountKey) return void 0;
|
|
50
57
|
let cancelled = false;
|
|
51
|
-
let objectUrl = null;
|
|
52
|
-
const cacheKey = getToken ? "self" : oid ?? account?.homeAccountId ?? "me";
|
|
53
|
-
const apply = (blob) => {
|
|
54
|
-
if (cancelled || !blob) return;
|
|
55
|
-
objectUrl = URL.createObjectURL(blob);
|
|
56
|
-
setUrl(objectUrl);
|
|
57
|
-
};
|
|
58
|
-
const resolve = async () => {
|
|
59
|
-
if (getToken) return { token: await getToken(), base: "/me" };
|
|
60
|
-
const token = await getGraphToken(instance, account);
|
|
61
|
-
const selfOid = account.idTokenClaims?.oid;
|
|
62
|
-
const base = !oid || selfOid && selfOid === oid ? "/me" : `/users/${oid}`;
|
|
63
|
-
return { token, base };
|
|
64
|
-
};
|
|
65
58
|
const cachedPhoto = photoCache.get(cacheKey);
|
|
66
59
|
const cachedProfile = profileCache.get(cacheKey);
|
|
67
60
|
if (cachedPhoto !== void 0 && (!showCard || cachedProfile !== void 0)) {
|
|
68
|
-
|
|
61
|
+
setUrl(cachedPhoto);
|
|
69
62
|
if (cachedProfile) setProfile(cachedProfile);
|
|
70
|
-
|
|
71
|
-
resolve().then(async ({ token, base }) => {
|
|
72
|
-
const blob = await fetchPhotoBlob(token, base);
|
|
73
|
-
photoCache.set(cacheKey, blob);
|
|
74
|
-
apply(blob);
|
|
75
|
-
if (showCard) {
|
|
76
|
-
const prof = await fetchProfile(token, base);
|
|
77
|
-
profileCache.set(cacheKey, prof);
|
|
78
|
-
if (!cancelled && prof) setProfile(prof);
|
|
79
|
-
}
|
|
80
|
-
}).catch(() => {
|
|
81
|
-
photoCache.set(cacheKey, null);
|
|
82
|
-
if (showCard) profileCache.set(cacheKey, null);
|
|
83
|
-
});
|
|
63
|
+
return void 0;
|
|
84
64
|
}
|
|
65
|
+
const resolve = async () => {
|
|
66
|
+
const fornecer = getTokenRef.current;
|
|
67
|
+
if (fornecer) return { token: await fornecer(), base: "/me" };
|
|
68
|
+
const conta = accountRef.current;
|
|
69
|
+
const token = await getGraphToken(instance, conta);
|
|
70
|
+
const selfOid = conta.idTokenClaims?.oid;
|
|
71
|
+
const base = !oid || selfOid && selfOid === oid ? "/me" : `/users/${oid}`;
|
|
72
|
+
return { token, base };
|
|
73
|
+
};
|
|
74
|
+
resolve().then(async ({ token, base }) => {
|
|
75
|
+
const blob = await fetchPhotoBlob(token, base);
|
|
76
|
+
const objectUrl = blob ? URL.createObjectURL(blob) : null;
|
|
77
|
+
photoCache.set(cacheKey, objectUrl);
|
|
78
|
+
if (!cancelled) setUrl(objectUrl);
|
|
79
|
+
if (showCard) {
|
|
80
|
+
const prof = await fetchProfile(token, base);
|
|
81
|
+
profileCache.set(cacheKey, prof);
|
|
82
|
+
if (!cancelled && prof) setProfile(prof);
|
|
83
|
+
}
|
|
84
|
+
}).catch(() => {
|
|
85
|
+
photoCache.set(cacheKey, null);
|
|
86
|
+
if (showCard) profileCache.set(cacheKey, null);
|
|
87
|
+
});
|
|
85
88
|
return () => {
|
|
86
89
|
cancelled = true;
|
|
87
|
-
if (objectUrl) URL.revokeObjectURL(objectUrl);
|
|
88
90
|
};
|
|
89
|
-
}, [
|
|
91
|
+
}, [cacheKey, accountKey, temGetToken, showCard, oid, instance]);
|
|
90
92
|
return /* @__PURE__ */ jsx(
|
|
91
93
|
UserAvatar,
|
|
92
94
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PhotoAvatar.js","sources":["../../../../../../src/legacy/components/molecules/PhotoAvatar/PhotoAvatar.tsx"],"sourcesContent":["import React, { useEffect, useState } from 'react'\r\n// O MSAL é peer opcional: numa app sem ele, o bundler troca-o por um módulo vazio, e\r\n// imports com nome (useMsal, InteractionRequiredAuthError) partiam o build dessa app.\r\nimport * as MsalReact from '@azure/msal-react'\r\nimport type { IPublicClientApplication, AccountInfo } from '@azure/msal-browser'\r\nimport UserAvatar from '../UserAvatar/UserAvatar'\r\n\r\nconst GRAPH = 'https://graph.microsoft.com/v1.0'\r\n\r\nasync function getGraphToken(instance: IPublicClientApplication, account: AccountInfo): Promise<string> {\r\n try {\r\n const res = await instance.acquireTokenSilent({ scopes: ['User.Read'], account })\r\n return res.accessToken\r\n } catch (err) {\r\n if ((err as { name?: string } | null)?.name === 'InteractionRequiredAuthError') {\r\n const res = await instance.acquireTokenPopup({ scopes: ['User.Read'] })\r\n return res.accessToken\r\n }\r\n throw err\r\n }\r\n}\r\n\r\n/** Busca a foto (240×240, fallback /photo). null se 404/erro. */\r\nasync function fetchPhotoBlob(token: string, base: string): Promise<Blob | null> {\r\n for (const seg of ['/photos/240x240/$value', '/photo/$value']) {\r\n const resp = await fetch(`${GRAPH}${base}${seg}`, { headers: { Authorization: `Bearer ${token}` } })\r\n if (resp.ok) return resp.blob()\r\n if (resp.status === 404) continue\r\n return null\r\n }\r\n return null\r\n}\r\n\r\nexport interface GraphProfile {\r\n name?: string | null\r\n email?: string | null\r\n company?: string | null\r\n department?: string | null\r\n jobTitle?: string | null\r\n}\r\n\r\n/** Perfil p/ o cartão de hover (mesmo token User.Read). null se erro. */\r\nasync function fetchProfile(token: string, base: string): Promise<GraphProfile | null> {\r\n const resp = await fetch(`${GRAPH}${base}?$select=displayName,mail,userPrincipalName,jobTitle,department,companyName`, {\r\n headers: { Authorization: `Bearer ${token}` },\r\n })\r\n if (!resp.ok) return null\r\n const d = (await resp.json()) as Record<string, string | null>\r\n return {\r\n name: d.displayName,\r\n email: d.mail ?? d.userPrincipalName,\r\n company: d.companyName,\r\n department: d.department,\r\n jobTitle: d.jobTitle,\r\n }\r\n}\r\n\r\ninterface Props {\r\n oid?: string | null\r\n name?: string | null\r\n email?: string | null\r\n className?: string\r\n /** Passa para UserAvatar: cartão de detalhes em hover. Default: off. */\r\n showCard?: boolean\r\n /**\r\n * Fornece um token do Graph (scope `User.Read`) usando o MSAL DA APP.\r\n * PREFERIR isto: evita o `useMsal` interno, que pode ler um `MsalContext`\r\n * diferente quando o bundler duplica o `@azure/msal-react` (→ account null →\r\n * sem foto). Com `getToken`, a foto do próprio (/me) resolve sempre.\r\n * Ex.: `getToken={() => msalInstance.acquireTokenSilent({scopes:['User.Read'],account}).then(r=>r.accessToken)}`\r\n */\r\n getToken?: () => Promise<string>\r\n}\r\n\r\n// Cache em memória (evita refetch entre montagens).\r\nconst photoCache = new Map<string, Blob | null>()\r\nconst profileCache = new Map<string, GraphProfile | null>()\r\n\r\n/**\r\n * Avatar com a foto real do MS Graph. Fallback: iniciais (UserAvatar).\r\n * Token: via `getToken` (recomendado) ou, em alternativa, via `useMsal` interno.\r\n * Peers: @azure/msal-react, @azure/msal-browser.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport default function PhotoAvatar({ oid, name, email, className = '', showCard = false, getToken }: Props) {\r\n const { instance, accounts } = MsalReact.useMsal()\r\n const account = instance.getActiveAccount() ?? accounts[0]\r\n const [url, setUrl] = useState<string | null>(null)\r\n const [profile, setProfile] = useState<GraphProfile | null>(null)\r\n\r\n useEffect(() => {\r\n if (!getToken && !account) return\r\n let cancelled = false\r\n let objectUrl: string | null = null\r\n const cacheKey = getToken ? 'self' : oid ?? account?.homeAccountId ?? 'me'\r\n\r\n const apply = (blob: Blob | null) => {\r\n if (cancelled || !blob) return\r\n objectUrl = URL.createObjectURL(blob)\r\n setUrl(objectUrl)\r\n }\r\n\r\n // Token + base do Graph resolvidos 1x, reutilizados p/ foto e perfil.\r\n const resolve = async (): Promise<{ token: string; base: string }> => {\r\n if (getToken) return { token: await getToken(), base: '/me' }\r\n const token = await getGraphToken(instance, account!)\r\n const selfOid = (account!.idTokenClaims as { oid?: string } | undefined)?.oid\r\n const base = !oid || (selfOid && selfOid === oid) ? '/me' : `/users/${oid}`\r\n return { token, base }\r\n }\r\n\r\n const cachedPhoto = photoCache.get(cacheKey)\r\n const cachedProfile = profileCache.get(cacheKey)\r\n if (cachedPhoto !== undefined && (!showCard || cachedProfile !== undefined)) {\r\n apply(cachedPhoto)\r\n if (cachedProfile) setProfile(cachedProfile)\r\n } else {\r\n resolve()\r\n .then(async ({ token, base }) => {\r\n const blob = await fetchPhotoBlob(token, base)\r\n photoCache.set(cacheKey, blob)\r\n apply(blob)\r\n // Perfil só quando há cartão de hover (evita chamada Graph extra).\r\n if (showCard) {\r\n const prof = await fetchProfile(token, base)\r\n profileCache.set(cacheKey, prof)\r\n if (!cancelled && prof) setProfile(prof)\r\n }\r\n })\r\n .catch(() => {\r\n photoCache.set(cacheKey, null)\r\n if (showCard) profileCache.set(cacheKey, null)\r\n })\r\n }\r\n\r\n return () => {\r\n cancelled = true\r\n if (objectUrl) URL.revokeObjectURL(objectUrl)\r\n }\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [oid, account, instance, getToken, showCard])\r\n\r\n // Redondo por default (a app pode sobrepor a forma no className, ex: rounded-none).\r\n return (\r\n <UserAvatar\r\n name={name ?? profile?.name}\r\n email={email ?? profile?.email}\r\n company={profile?.company}\r\n department={profile?.department}\r\n jobTitle={profile?.jobTitle}\r\n photo={url}\r\n className={`rounded-full ${className}`}\r\n showCard={showCard}\r\n />\r\n )\r\n}\r\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PhotoAvatar.js","sources":["../../../../../../src/legacy/components/molecules/PhotoAvatar/PhotoAvatar.tsx"],"sourcesContent":["import React, { useEffect, useRef, useState } from 'react'\r\n// O MSAL é peer opcional: numa app sem ele, o bundler troca-o por um módulo vazio, e\r\n// imports com nome (useMsal, InteractionRequiredAuthError) partiam o build dessa app.\r\nimport * as MsalReact from '@azure/msal-react'\r\nimport type { IPublicClientApplication, AccountInfo } from '@azure/msal-browser'\r\nimport UserAvatar from '../UserAvatar/UserAvatar'\r\n\r\nconst GRAPH = 'https://graph.microsoft.com/v1.0'\r\n\r\nasync function getGraphToken(instance: IPublicClientApplication, account: AccountInfo): Promise<string> {\r\n try {\r\n const res = await instance.acquireTokenSilent({ scopes: ['User.Read'], account })\r\n return res.accessToken\r\n } catch (err) {\r\n if ((err as { name?: string } | null)?.name === 'InteractionRequiredAuthError') {\r\n const res = await instance.acquireTokenPopup({ scopes: ['User.Read'] })\r\n return res.accessToken\r\n }\r\n throw err\r\n }\r\n}\r\n\r\n/** Busca a foto (240×240, fallback /photo). null se 404/erro. */\r\nasync function fetchPhotoBlob(token: string, base: string): Promise<Blob | null> {\r\n for (const seg of ['/photos/240x240/$value', '/photo/$value']) {\r\n const resp = await fetch(`${GRAPH}${base}${seg}`, { headers: { Authorization: `Bearer ${token}` } })\r\n if (resp.ok) return resp.blob()\r\n if (resp.status === 404) continue\r\n return null\r\n }\r\n return null\r\n}\r\n\r\nexport interface GraphProfile {\r\n name?: string | null\r\n email?: string | null\r\n company?: string | null\r\n department?: string | null\r\n jobTitle?: string | null\r\n}\r\n\r\n/** Perfil p/ o cartão de hover (mesmo token User.Read). null se erro. */\r\nasync function fetchProfile(token: string, base: string): Promise<GraphProfile | null> {\r\n const resp = await fetch(`${GRAPH}${base}?$select=displayName,mail,userPrincipalName,jobTitle,department,companyName`, {\r\n headers: { Authorization: `Bearer ${token}` },\r\n })\r\n if (!resp.ok) return null\r\n const d = (await resp.json()) as Record<string, string | null>\r\n return {\r\n name: d.displayName,\r\n email: d.mail ?? d.userPrincipalName,\r\n company: d.companyName,\r\n department: d.department,\r\n jobTitle: d.jobTitle,\r\n }\r\n}\r\n\r\ninterface Props {\r\n oid?: string | null\r\n name?: string | null\r\n email?: string | null\r\n className?: string\r\n /** Passa para UserAvatar: cartão de detalhes em hover. Default: off. */\r\n showCard?: boolean\r\n /**\r\n * Fornece um token do Graph (scope `User.Read`) usando o MSAL DA APP.\r\n * PREFERIR isto: evita o `useMsal` interno, que pode ler um `MsalContext`\r\n * diferente quando o bundler duplica o `@azure/msal-react` (→ account null →\r\n * sem foto). Com `getToken`, a foto do próprio (/me) resolve sempre.\r\n * Ex.: `getToken={() => msalInstance.acquireTokenSilent({scopes:['User.Read'],account}).then(r=>r.accessToken)}`\r\n */\r\n getToken?: () => Promise<string>\r\n}\r\n\r\n// Cache em memória (evita refetch entre montagens).\r\n//\r\n// Guarda o OBJECT URL já criado, não o Blob. Com o Blob, cada passagem pelo\r\n// efeito criava um URL novo, o que mudava o estado e disparava outro render —\r\n// e outra passagem. Era um ciclo infinito, visível como a foto a alternar com\r\n// as iniciais.\r\n//\r\n// O URL nunca é revogado: é uma imagem para toda a sessão, partilhada por\r\n// todas as montagens com a mesma chave. Revogá-la ao desmontar partia as que\r\n// ficavam a usá-la.\r\nconst photoCache = new Map<string, string | null>()\r\nconst profileCache = new Map<string, GraphProfile | null>()\r\n\r\n/**\r\n * Avatar com a foto real do MS Graph. Fallback: iniciais (UserAvatar).\r\n * Token: via `getToken` (recomendado) ou, em alternativa, via `useMsal` interno.\r\n * Peers: @azure/msal-react, @azure/msal-browser.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport default function PhotoAvatar({ oid, name, email, className = '', showCard = false, getToken }: Props) {\r\n const { instance, accounts } = MsalReact.useMsal()\r\n const account = instance.getActiveAccount() ?? accounts[0]\r\n\r\n // O MSAL reconstrói o objeto da conta a cada chamada, por isso ele nunca\r\n // serve como dependência do efeito: fazia-o correr em todos os renders. A\r\n // identidade estável é o homeAccountId.\r\n const accountKey = account?.homeAccountId ?? null\r\n\r\n // O getToken é quase sempre uma arrow inline na app, logo também muda a cada\r\n // render. Fica numa ref, fora das dependências; o que importa é se existe.\r\n const getTokenRef = useRef(getToken)\r\n getTokenRef.current = getToken\r\n const accountRef = useRef(account)\r\n accountRef.current = account\r\n const temGetToken = Boolean(getToken)\r\n\r\n const cacheKey = temGetToken ? 'self' : oid ?? accountKey ?? 'me'\r\n\r\n // Arranca já com o que estiver em cache, para não piscar as iniciais ao\r\n // mudar de página.\r\n const [url, setUrl] = useState<string | null>(() => photoCache.get(cacheKey) ?? null)\r\n const [profile, setProfile] = useState<GraphProfile | null>(() => profileCache.get(cacheKey) ?? null)\r\n\r\n useEffect(() => {\r\n if (!temGetToken && !accountKey) return undefined\r\n let cancelled = false\r\n\r\n const cachedPhoto = photoCache.get(cacheKey)\r\n const cachedProfile = profileCache.get(cacheKey)\r\n if (cachedPhoto !== undefined && (!showCard || cachedProfile !== undefined)) {\r\n setUrl(cachedPhoto)\r\n if (cachedProfile) setProfile(cachedProfile)\r\n return undefined\r\n }\r\n\r\n // Token + base do Graph resolvidos 1x, reutilizados p/ foto e perfil.\r\n const resolve = async (): Promise<{ token: string; base: string }> => {\r\n const fornecer = getTokenRef.current\r\n if (fornecer) return { token: await fornecer(), base: '/me' }\r\n const conta = accountRef.current!\r\n const token = await getGraphToken(instance, conta)\r\n const selfOid = (conta.idTokenClaims as { oid?: string } | undefined)?.oid\r\n const base = !oid || (selfOid && selfOid === oid) ? '/me' : `/users/${oid}`\r\n return { token, base }\r\n }\r\n\r\n resolve()\r\n .then(async ({ token, base }) => {\r\n const blob = await fetchPhotoBlob(token, base)\r\n const objectUrl = blob ? URL.createObjectURL(blob) : null\r\n photoCache.set(cacheKey, objectUrl)\r\n if (!cancelled) setUrl(objectUrl)\r\n // Perfil só quando há cartão de hover (evita chamada Graph extra).\r\n if (showCard) {\r\n const prof = await fetchProfile(token, base)\r\n profileCache.set(cacheKey, prof)\r\n if (!cancelled && prof) setProfile(prof)\r\n }\r\n })\r\n .catch(() => {\r\n photoCache.set(cacheKey, null)\r\n if (showCard) profileCache.set(cacheKey, null)\r\n })\r\n\r\n return () => {\r\n cancelled = true\r\n }\r\n }, [cacheKey, accountKey, temGetToken, showCard, oid, instance])\r\n\r\n // Redondo por default (a app pode sobrepor a forma no className, ex: rounded-none).\r\n return (\r\n <UserAvatar\r\n name={name ?? profile?.name}\r\n email={email ?? profile?.email}\r\n company={profile?.company}\r\n department={profile?.department}\r\n jobTitle={profile?.jobTitle}\r\n photo={url}\r\n className={`rounded-full ${className}`}\r\n showCard={showCard}\r\n />\r\n )\r\n}\r\n"],"names":[],"mappings":"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"}
|