@ciromaciel/auth-react 1.5.0 → 1.7.0
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.
- package/README.md +34 -1
- package/dist/index.esm.js +1205 -673
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1202 -670
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.esm.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { create } from 'zustand';
|
|
2
|
-
import { useState, useCallback, useEffect, useMemo, createContext, useRef } from 'react';
|
|
2
|
+
import { useState, useCallback, useEffect, useMemo, createContext, useRef, useId } from 'react';
|
|
3
3
|
import { useShallow } from 'zustand/react/shallow';
|
|
4
4
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
5
5
|
import { Navigate, Outlet, useNavigate } from 'react-router-dom';
|
|
6
|
-
import { Modal, Stack, Text, Group, Image, Title, Paper, Anchor, NavLink, ActionIcon, Loader, Avatar, Button, Divider, TextInput, Alert, Center, Box,
|
|
6
|
+
import { Modal, Stack, Text, Group, Image, Title, Paper, Anchor, NavLink, ActionIcon, Loader, Avatar, Button, Divider, TextInput, Alert, Center, Box, UnstyledButton, FileButton } from '@mantine/core';
|
|
7
7
|
import { useForm } from '@mantine/form';
|
|
8
|
-
import { IconX, IconArrowRight, IconBrandGoogle, IconArrowLeft, IconRefresh, IconAlertCircle,
|
|
8
|
+
import { IconX, IconArrowRight, IconBrandGoogle, IconArrowLeft, IconRefresh, IconAlertCircle, IconDeviceTablet, IconDeviceMobile, IconDeviceLaptop, IconDeviceDesktop, IconMail, IconBrandGithub, IconLink, IconChevronDown, IconLogout, IconBuilding, IconUser, IconCreditCard } from '@tabler/icons-react';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* The accounts that already signed in on this browser.
|
|
@@ -3235,47 +3235,579 @@ function SignIn({
|
|
|
3235
3235
|
});
|
|
3236
3236
|
}
|
|
3237
3237
|
|
|
3238
|
+
/*
|
|
3239
|
+
* How a session reads on the account screen: which device, since when, and
|
|
3240
|
+
* how many there are.
|
|
3241
|
+
*
|
|
3242
|
+
* Pure functions, kept out of the component so they can be tested against real
|
|
3243
|
+
* user agents. The order of the checks is the point of this file:
|
|
3244
|
+
* - iPhone and iPad announce themselves as "like Mac OS X", so they must be
|
|
3245
|
+
* recognised BEFORE macOS — the old parser showed every phone as a Mac;
|
|
3246
|
+
* - Edge, Opera and Samsung Internet all carry "Chrome" in the string, and
|
|
3247
|
+
* Chrome on iOS carries "Safari", so each is checked before the engine it
|
|
3248
|
+
* imitates — the old parser never reached its Opera branch.
|
|
3249
|
+
*/
|
|
3250
|
+
|
|
3251
|
+
const BROWSERS = [['Edge', /\bEdg(?:e|A|iOS)?\//], ['Opera', /\b(?:OPR|Opera|OPT)\//], ['Samsung Internet', /\bSamsungBrowser\//], ['Firefox', /\b(?:Firefox|FxiOS)\//], ['Chrome', /\b(?:Chrome|CriOS)\//], ['Safari', /\bSafari\//]];
|
|
3252
|
+
|
|
3253
|
+
/**
|
|
3254
|
+
* @param {string} [userAgent]
|
|
3255
|
+
* @returns {{ browser: string|null, os: string|null, kind: 'desktop'|'phone'|'tablet' }}
|
|
3256
|
+
*/
|
|
3257
|
+
function describeDevice(userAgent) {
|
|
3258
|
+
const ua = userAgent || '';
|
|
3259
|
+
const browser = BROWSERS.find(([, pattern]) => pattern.test(ua))?.[0] || null;
|
|
3260
|
+
if (/\biPad\b/.test(ua)) return {
|
|
3261
|
+
browser,
|
|
3262
|
+
os: 'iPadOS',
|
|
3263
|
+
kind: 'tablet'
|
|
3264
|
+
};
|
|
3265
|
+
if (/\biPhone\b|\biPod\b/.test(ua)) return {
|
|
3266
|
+
browser,
|
|
3267
|
+
os: 'iOS',
|
|
3268
|
+
kind: 'phone'
|
|
3269
|
+
};
|
|
3270
|
+
if (/\bAndroid\b/.test(ua)) return {
|
|
3271
|
+
browser,
|
|
3272
|
+
os: 'Android',
|
|
3273
|
+
kind: /\bMobile\b/.test(ua) ? 'phone' : 'tablet'
|
|
3274
|
+
};
|
|
3275
|
+
if (/\bWindows\b/.test(ua)) return {
|
|
3276
|
+
browser,
|
|
3277
|
+
os: 'Windows',
|
|
3278
|
+
kind: 'desktop'
|
|
3279
|
+
};
|
|
3280
|
+
if (/\bCrOS\b/.test(ua)) return {
|
|
3281
|
+
browser,
|
|
3282
|
+
os: 'ChromeOS',
|
|
3283
|
+
kind: 'desktop'
|
|
3284
|
+
};
|
|
3285
|
+
if (/\bMac OS X\b|\bMacintosh\b/.test(ua)) return {
|
|
3286
|
+
browser,
|
|
3287
|
+
os: 'macOS',
|
|
3288
|
+
kind: 'desktop'
|
|
3289
|
+
};
|
|
3290
|
+
if (/\bLinux\b/.test(ua)) return {
|
|
3291
|
+
browser,
|
|
3292
|
+
os: 'Linux',
|
|
3293
|
+
kind: 'desktop'
|
|
3294
|
+
};
|
|
3295
|
+
return {
|
|
3296
|
+
browser,
|
|
3297
|
+
os: null,
|
|
3298
|
+
kind: 'desktop'
|
|
3299
|
+
};
|
|
3300
|
+
}
|
|
3301
|
+
|
|
3302
|
+
/** "Chrome no macOS", "Safari", "iOS", or "Aparelho desconhecido". */
|
|
3303
|
+
function deviceLabel({
|
|
3304
|
+
browser,
|
|
3305
|
+
os
|
|
3306
|
+
}) {
|
|
3307
|
+
if (browser && os) return `${browser} no ${os}`;
|
|
3308
|
+
return browser || os || 'Aparelho desconhecido';
|
|
3309
|
+
}
|
|
3310
|
+
const pad = n => String(n).padStart(2, '0');
|
|
3311
|
+
|
|
3312
|
+
/**
|
|
3313
|
+
* "hoje, 09:12", "ontem, 21:40", "23/09, 14:05" or "23/09/2025, 14:05".
|
|
3314
|
+
*
|
|
3315
|
+
* Calendar days in the viewer's own time zone: a session opened at 23:50 is
|
|
3316
|
+
* "ontem" ten minutes later, which is what the person remembers.
|
|
3317
|
+
*/
|
|
3318
|
+
function formatSessionStart(value, now = new Date()) {
|
|
3319
|
+
const date = new Date(value);
|
|
3320
|
+
if (Number.isNaN(date.getTime())) return null;
|
|
3321
|
+
const time = `${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
3322
|
+
const startOfDay = d => new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
|
|
3323
|
+
const days = Math.round((startOfDay(now) - startOfDay(date)) / 86_400_000);
|
|
3324
|
+
if (days === 0) return `hoje, ${time}`;
|
|
3325
|
+
if (days === 1) return `ontem, ${time}`;
|
|
3326
|
+
const day = `${pad(date.getDate())}/${pad(date.getMonth() + 1)}`;
|
|
3327
|
+
return date.getFullYear() === now.getFullYear() ? `${day}, ${time}` : `${day}/${date.getFullYear()}, ${time}`;
|
|
3328
|
+
}
|
|
3329
|
+
|
|
3330
|
+
/** "1 sessão aberta", "3 sessões abertas". */
|
|
3331
|
+
function countSessions(count) {
|
|
3332
|
+
return count === 1 ? '1 sessão aberta' : `${count} sessões abertas`;
|
|
3333
|
+
}
|
|
3334
|
+
|
|
3335
|
+
/**
|
|
3336
|
+
* The current session first, then the others from the newest.
|
|
3337
|
+
*
|
|
3338
|
+
* The API orders by creation only, so the device the person is holding could
|
|
3339
|
+
* land anywhere in the list; it is the one they look for first.
|
|
3340
|
+
*/
|
|
3341
|
+
function orderSessions(sessions, currentId) {
|
|
3342
|
+
return [...(sessions || [])].sort((a, b) => {
|
|
3343
|
+
if (a.id === currentId) return -1;
|
|
3344
|
+
if (b.id === currentId) return 1;
|
|
3345
|
+
return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
|
|
3346
|
+
});
|
|
3347
|
+
}
|
|
3348
|
+
|
|
3349
|
+
/*
|
|
3350
|
+
* Who the signed-in person is, as the account card and the account screen
|
|
3351
|
+
* show it. One rule for both, so the two never disagree about the same person.
|
|
3352
|
+
*
|
|
3353
|
+
* A name equal to the email's local part is not a name: it is what an account
|
|
3354
|
+
* born from a code gets by default, and showing it on top of the email repeats
|
|
3355
|
+
* the same fact. It counts as "no name", and the email becomes the title.
|
|
3356
|
+
*/
|
|
3357
|
+
function describeUser(user) {
|
|
3358
|
+
const name = (user?.fullName || user?.name || '').trim();
|
|
3359
|
+
const email = (user?.primaryEmailAddress || user?.email || '').trim();
|
|
3360
|
+
const hasRealName = Boolean(name) && name.toLowerCase() !== email.split('@')[0].toLowerCase() && name.toLowerCase() !== email.toLowerCase();
|
|
3361
|
+
const title = hasRealName ? name : email || name;
|
|
3362
|
+
const initials = hasRealName ? name.split(/\s+/).map(part => part[0]).join('').slice(0, 2).toUpperCase() : (email || name).charAt(0).toUpperCase();
|
|
3363
|
+
return {
|
|
3364
|
+
name,
|
|
3365
|
+
email,
|
|
3366
|
+
hasRealName,
|
|
3367
|
+
title,
|
|
3368
|
+
initials,
|
|
3369
|
+
image: user?.imageUrl || user?.image || null
|
|
3370
|
+
};
|
|
3371
|
+
}
|
|
3372
|
+
|
|
3373
|
+
const LABELS = {
|
|
3374
|
+
title: 'Conta',
|
|
3375
|
+
subtitle: 'Quem você é, como você entra e onde a sua sessão está aberta.',
|
|
3376
|
+
close: 'Fechar',
|
|
3377
|
+
profileSection: 'Perfil',
|
|
3378
|
+
avatar: 'Foto',
|
|
3379
|
+
name: 'Nome',
|
|
3380
|
+
email: 'E-mail',
|
|
3381
|
+
edit: 'Alterar',
|
|
3382
|
+
save: 'Salvar',
|
|
3383
|
+
cancel: 'Cancelar',
|
|
3384
|
+
remove: 'Remover',
|
|
3385
|
+
notDefined: 'Não definido',
|
|
3386
|
+
namePlaceholder: 'Seu nome',
|
|
3387
|
+
nameHint: 'Aparece no cartão da conta e para quem divide uma organização com você. Enter salva, Esc cancela.',
|
|
3388
|
+
nameRequired: 'Digite um nome.',
|
|
3389
|
+
emailHint: 'É para onde vai o código de acesso, por isso não muda por aqui.',
|
|
3390
|
+
avatarPrompt: 'Arraste uma imagem ou clique para escolher',
|
|
3391
|
+
avatarHint: 'JPG, PNG, GIF ou WebP, até {size}. Ela aparece em todos os painéis.',
|
|
3392
|
+
avatarInvalidType: 'Escolha uma imagem: JPG, PNG, GIF ou WebP.',
|
|
3393
|
+
avatarTooLarge: 'Imagem grande demais. O máximo é {size}.',
|
|
3394
|
+
signInSection: 'Formas de entrar',
|
|
3395
|
+
codeMethod: 'Código',
|
|
3396
|
+
codeByEmail: 'Por e-mail',
|
|
3397
|
+
alwaysOn: 'Sempre ativo',
|
|
3398
|
+
notConnected: 'Não conectado',
|
|
3399
|
+
connect: 'Conectar',
|
|
3400
|
+
disconnect: 'Desconectar',
|
|
3401
|
+
sessionsSection: 'Sessões',
|
|
3402
|
+
devices: 'Aparelhos',
|
|
3403
|
+
showSessions: 'Ver sessões',
|
|
3404
|
+
hideSessions: 'Ocultar',
|
|
3405
|
+
thisDevice: 'Este aparelho',
|
|
3406
|
+
end: 'Encerrar',
|
|
3407
|
+
since: 'desde',
|
|
3408
|
+
unknownIP: 'IP desconhecido',
|
|
3409
|
+
loadingSessions: 'Carregando as sessões…',
|
|
3410
|
+
noSessionsFound: 'Nenhuma sessão encontrada.',
|
|
3411
|
+
confirmEndBody: 'Quem estiver nesses aparelhos volta para a tela de entrar. Este aparelho continua conectado.',
|
|
3412
|
+
genericFailure: 'Não foi possível concluir. Tente de novo.'
|
|
3413
|
+
};
|
|
3414
|
+
const PROVIDER_MARKS = {
|
|
3415
|
+
google: IconBrandGoogle,
|
|
3416
|
+
github: IconBrandGithub
|
|
3417
|
+
};
|
|
3418
|
+
const DEVICE_MARKS = {
|
|
3419
|
+
desktop: IconDeviceLaptop,
|
|
3420
|
+
phone: IconDeviceMobile,
|
|
3421
|
+
tablet: IconDeviceTablet
|
|
3422
|
+
};
|
|
3423
|
+
const color = token => token === 'transparent' ? 'transparent' : `var(--mantine-color-${token.replace('.', '-')})`;
|
|
3424
|
+
|
|
3425
|
+
/*
|
|
3426
|
+
* The action buttons, as data. Hover and keyboard focus share the same look,
|
|
3427
|
+
* like the account card's rows. A destructive action keeps its red text on
|
|
3428
|
+
* hover and only gains a light red ground: turning the text black while the
|
|
3429
|
+
* border turned red read as a different button.
|
|
3430
|
+
*/
|
|
3431
|
+
const TONES = {
|
|
3432
|
+
default: {
|
|
3433
|
+
rest: {
|
|
3434
|
+
text: 'gray.9',
|
|
3435
|
+
border: 'gray.3',
|
|
3436
|
+
ground: 'transparent'
|
|
3437
|
+
},
|
|
3438
|
+
active: {
|
|
3439
|
+
border: 'gray.9'
|
|
3440
|
+
}
|
|
3441
|
+
},
|
|
3442
|
+
dark: {
|
|
3443
|
+
rest: {
|
|
3444
|
+
text: 'white',
|
|
3445
|
+
border: 'gray.9',
|
|
3446
|
+
ground: 'gray.9'
|
|
3447
|
+
},
|
|
3448
|
+
active: {
|
|
3449
|
+
border: 'gray.7',
|
|
3450
|
+
ground: 'gray.7'
|
|
3451
|
+
}
|
|
3452
|
+
},
|
|
3453
|
+
quiet: {
|
|
3454
|
+
rest: {
|
|
3455
|
+
text: 'gray.6',
|
|
3456
|
+
border: 'transparent',
|
|
3457
|
+
ground: 'transparent'
|
|
3458
|
+
},
|
|
3459
|
+
active: {
|
|
3460
|
+
text: 'gray.9',
|
|
3461
|
+
border: 'gray.3'
|
|
3462
|
+
}
|
|
3463
|
+
},
|
|
3464
|
+
danger: {
|
|
3465
|
+
rest: {
|
|
3466
|
+
text: 'red.8',
|
|
3467
|
+
border: 'transparent',
|
|
3468
|
+
ground: 'transparent'
|
|
3469
|
+
},
|
|
3470
|
+
active: {
|
|
3471
|
+
ground: 'red.0',
|
|
3472
|
+
border: 'red.2'
|
|
3473
|
+
}
|
|
3474
|
+
},
|
|
3475
|
+
dangerOutline: {
|
|
3476
|
+
rest: {
|
|
3477
|
+
text: 'red.8',
|
|
3478
|
+
border: 'gray.3',
|
|
3479
|
+
ground: 'transparent'
|
|
3480
|
+
},
|
|
3481
|
+
active: {
|
|
3482
|
+
ground: 'red.0',
|
|
3483
|
+
border: 'red.2'
|
|
3484
|
+
}
|
|
3485
|
+
},
|
|
3486
|
+
dangerFill: {
|
|
3487
|
+
rest: {
|
|
3488
|
+
text: 'white',
|
|
3489
|
+
border: 'red.8',
|
|
3490
|
+
ground: 'red.8'
|
|
3491
|
+
},
|
|
3492
|
+
active: {
|
|
3493
|
+
border: 'red.9',
|
|
3494
|
+
ground: 'red.9'
|
|
3495
|
+
}
|
|
3496
|
+
}
|
|
3497
|
+
};
|
|
3498
|
+
function ActionButton({
|
|
3499
|
+
tone = 'default',
|
|
3500
|
+
loading = false,
|
|
3501
|
+
disabled = false,
|
|
3502
|
+
children,
|
|
3503
|
+
style,
|
|
3504
|
+
...others
|
|
3505
|
+
}) {
|
|
3506
|
+
const [isActive, setIsActive] = useState(false);
|
|
3507
|
+
const isBlocked = disabled || loading;
|
|
3508
|
+
const look = {
|
|
3509
|
+
...TONES[tone].rest,
|
|
3510
|
+
...(isActive && !isBlocked ? TONES[tone].active : {})
|
|
3511
|
+
};
|
|
3512
|
+
return /*#__PURE__*/jsxs(UnstyledButton, {
|
|
3513
|
+
disabled: isBlocked,
|
|
3514
|
+
onMouseEnter: () => setIsActive(true),
|
|
3515
|
+
onMouseLeave: () => setIsActive(false),
|
|
3516
|
+
onFocus: () => setIsActive(true),
|
|
3517
|
+
onBlur: () => setIsActive(false),
|
|
3518
|
+
style: {
|
|
3519
|
+
display: 'inline-flex',
|
|
3520
|
+
alignItems: 'center',
|
|
3521
|
+
justifyContent: 'center',
|
|
3522
|
+
gap: 6,
|
|
3523
|
+
padding: '5px 12px',
|
|
3524
|
+
fontSize: 12,
|
|
3525
|
+
fontWeight: 700,
|
|
3526
|
+
lineHeight: 1.5,
|
|
3527
|
+
whiteSpace: 'nowrap',
|
|
3528
|
+
borderRadius: 0,
|
|
3529
|
+
color: color(look.text),
|
|
3530
|
+
background: color(look.ground),
|
|
3531
|
+
border: `1px solid ${color(look.border)}`,
|
|
3532
|
+
opacity: disabled ? 0.4 : 1,
|
|
3533
|
+
cursor: isBlocked ? 'default' : 'pointer',
|
|
3534
|
+
...style
|
|
3535
|
+
},
|
|
3536
|
+
...others,
|
|
3537
|
+
children: [loading && /*#__PURE__*/jsx(Loader, {
|
|
3538
|
+
size: 10,
|
|
3539
|
+
color: "currentColor"
|
|
3540
|
+
}), children]
|
|
3541
|
+
});
|
|
3542
|
+
}
|
|
3543
|
+
function SectionLabel({
|
|
3544
|
+
children,
|
|
3545
|
+
note
|
|
3546
|
+
}) {
|
|
3547
|
+
return /*#__PURE__*/jsxs(Group, {
|
|
3548
|
+
justify: "space-between",
|
|
3549
|
+
align: "baseline",
|
|
3550
|
+
gap: 8,
|
|
3551
|
+
mb: 4,
|
|
3552
|
+
children: [/*#__PURE__*/jsx(Text, {
|
|
3553
|
+
fz: 11,
|
|
3554
|
+
fw: 800,
|
|
3555
|
+
tt: "uppercase",
|
|
3556
|
+
lts: "1.5px",
|
|
3557
|
+
c: "gray.4",
|
|
3558
|
+
children: children
|
|
3559
|
+
}), note && /*#__PURE__*/jsx(Text, {
|
|
3560
|
+
fz: 12,
|
|
3561
|
+
fw: 500,
|
|
3562
|
+
c: "gray.5",
|
|
3563
|
+
children: note
|
|
3564
|
+
})]
|
|
3565
|
+
});
|
|
3566
|
+
}
|
|
3567
|
+
function Section({
|
|
3568
|
+
label,
|
|
3569
|
+
note,
|
|
3570
|
+
isFirst,
|
|
3571
|
+
children
|
|
3572
|
+
}) {
|
|
3573
|
+
return /*#__PURE__*/jsxs(Box, {
|
|
3574
|
+
px: 24,
|
|
3575
|
+
pt: 16,
|
|
3576
|
+
pb: 8,
|
|
3577
|
+
style: isFirst ? undefined : {
|
|
3578
|
+
borderTop: '1px solid var(--mantine-color-gray-2)'
|
|
3579
|
+
},
|
|
3580
|
+
children: [/*#__PURE__*/jsx(SectionLabel, {
|
|
3581
|
+
note: note,
|
|
3582
|
+
children: label
|
|
3583
|
+
}), children]
|
|
3584
|
+
});
|
|
3585
|
+
}
|
|
3586
|
+
const rowDivider = {
|
|
3587
|
+
borderTop: '1px solid var(--mantine-color-gray-2)'
|
|
3588
|
+
};
|
|
3589
|
+
|
|
3590
|
+
/** A label, a value and an optional action, on the same 88px label column. */
|
|
3591
|
+
function Row$1({
|
|
3592
|
+
label,
|
|
3593
|
+
children,
|
|
3594
|
+
action,
|
|
3595
|
+
isFirst
|
|
3596
|
+
}) {
|
|
3597
|
+
return /*#__PURE__*/jsxs(Box, {
|
|
3598
|
+
py: 10,
|
|
3599
|
+
mih: 52,
|
|
3600
|
+
style: {
|
|
3601
|
+
display: 'grid',
|
|
3602
|
+
gridTemplateColumns: label ? '88px 1fr auto' : '1fr auto',
|
|
3603
|
+
alignItems: 'center',
|
|
3604
|
+
gap: 12,
|
|
3605
|
+
...(isFirst ? {} : rowDivider)
|
|
3606
|
+
},
|
|
3607
|
+
children: [label && /*#__PURE__*/jsx(Text, {
|
|
3608
|
+
fz: 12,
|
|
3609
|
+
fw: 500,
|
|
3610
|
+
c: "gray.5",
|
|
3611
|
+
children: label
|
|
3612
|
+
}), /*#__PURE__*/jsx(Box, {
|
|
3613
|
+
fz: 13,
|
|
3614
|
+
fw: 500,
|
|
3615
|
+
c: "gray.9",
|
|
3616
|
+
style: {
|
|
3617
|
+
minWidth: 0,
|
|
3618
|
+
overflowWrap: 'anywhere'
|
|
3619
|
+
},
|
|
3620
|
+
children: children
|
|
3621
|
+
}), action || /*#__PURE__*/jsx("span", {})]
|
|
3622
|
+
});
|
|
3623
|
+
}
|
|
3624
|
+
function Hint({
|
|
3625
|
+
children,
|
|
3626
|
+
c = 'gray.5'
|
|
3627
|
+
}) {
|
|
3628
|
+
return /*#__PURE__*/jsx(Text, {
|
|
3629
|
+
fz: 12,
|
|
3630
|
+
fw: 500,
|
|
3631
|
+
c: c,
|
|
3632
|
+
mt: 2,
|
|
3633
|
+
children: children
|
|
3634
|
+
});
|
|
3635
|
+
}
|
|
3636
|
+
|
|
3637
|
+
/*
|
|
3638
|
+
* A failure is shown where it happened, on the screen itself. Five of the
|
|
3639
|
+
* seven panels passed no `onError`, and a rejected photo or a failed rename
|
|
3640
|
+
* vanished without a word.
|
|
3641
|
+
*/
|
|
3642
|
+
function FailureNote({
|
|
3643
|
+
failure,
|
|
3644
|
+
section
|
|
3645
|
+
}) {
|
|
3646
|
+
if (failure?.section !== section) return null;
|
|
3647
|
+
return /*#__PURE__*/jsx(Text, {
|
|
3648
|
+
role: "alert",
|
|
3649
|
+
fz: 12,
|
|
3650
|
+
fw: 600,
|
|
3651
|
+
c: "red.8",
|
|
3652
|
+
mt: 4,
|
|
3653
|
+
children: failure.message
|
|
3654
|
+
});
|
|
3655
|
+
}
|
|
3656
|
+
function Chip({
|
|
3657
|
+
children,
|
|
3658
|
+
tone = 'outline'
|
|
3659
|
+
}) {
|
|
3660
|
+
const looks = {
|
|
3661
|
+
outline: {
|
|
3662
|
+
c: 'gray.6',
|
|
3663
|
+
bg: 'transparent',
|
|
3664
|
+
border: 'gray.3'
|
|
3665
|
+
},
|
|
3666
|
+
dark: {
|
|
3667
|
+
c: 'white',
|
|
3668
|
+
bg: 'gray.9',
|
|
3669
|
+
border: 'gray.9'
|
|
3670
|
+
},
|
|
3671
|
+
good: {
|
|
3672
|
+
c: 'teal.9',
|
|
3673
|
+
bg: 'teal.0',
|
|
3674
|
+
border: 'teal.2'
|
|
3675
|
+
}
|
|
3676
|
+
};
|
|
3677
|
+
const look = looks[tone];
|
|
3678
|
+
return /*#__PURE__*/jsx(Text, {
|
|
3679
|
+
component: "span",
|
|
3680
|
+
fz: 10,
|
|
3681
|
+
fw: 800,
|
|
3682
|
+
tt: "uppercase",
|
|
3683
|
+
lts: "1.2px",
|
|
3684
|
+
lh: 1.6,
|
|
3685
|
+
px: 6,
|
|
3686
|
+
c: look.c,
|
|
3687
|
+
bg: look.bg,
|
|
3688
|
+
style: {
|
|
3689
|
+
border: `1px solid ${color(look.border)}`,
|
|
3690
|
+
whiteSpace: 'nowrap',
|
|
3691
|
+
display: 'inline-block'
|
|
3692
|
+
},
|
|
3693
|
+
children: children
|
|
3694
|
+
});
|
|
3695
|
+
}
|
|
3696
|
+
|
|
3697
|
+
/** An icon and a label, side by side, with an optional line under the label. */
|
|
3698
|
+
function WithIcon({
|
|
3699
|
+
icon: Icon,
|
|
3700
|
+
children,
|
|
3701
|
+
detail
|
|
3702
|
+
}) {
|
|
3703
|
+
return /*#__PURE__*/jsxs(Group, {
|
|
3704
|
+
gap: 10,
|
|
3705
|
+
wrap: "nowrap",
|
|
3706
|
+
align: detail ? 'flex-start' : 'center',
|
|
3707
|
+
children: [/*#__PURE__*/jsx(Icon, {
|
|
3708
|
+
size: 18,
|
|
3709
|
+
stroke: 1.5,
|
|
3710
|
+
style: {
|
|
3711
|
+
flex: 'none',
|
|
3712
|
+
color: 'var(--mantine-color-gray-5)',
|
|
3713
|
+
marginTop: detail ? 1 : 0
|
|
3714
|
+
}
|
|
3715
|
+
}), /*#__PURE__*/jsxs(Box, {
|
|
3716
|
+
style: {
|
|
3717
|
+
minWidth: 0
|
|
3718
|
+
},
|
|
3719
|
+
children: [children, detail && /*#__PURE__*/jsx(Hint, {
|
|
3720
|
+
children: detail
|
|
3721
|
+
})]
|
|
3722
|
+
})]
|
|
3723
|
+
});
|
|
3724
|
+
}
|
|
3725
|
+
function SquareAvatar({
|
|
3726
|
+
src,
|
|
3727
|
+
initials,
|
|
3728
|
+
size
|
|
3729
|
+
}) {
|
|
3730
|
+
return /*#__PURE__*/jsx(Avatar, {
|
|
3731
|
+
src: src || null,
|
|
3732
|
+
alt: "",
|
|
3733
|
+
size: size,
|
|
3734
|
+
radius: 0,
|
|
3735
|
+
color: "gray.9",
|
|
3736
|
+
variant: "filled",
|
|
3737
|
+
styles: {
|
|
3738
|
+
root: {
|
|
3739
|
+
borderRadius: 0,
|
|
3740
|
+
flex: 'none'
|
|
3741
|
+
},
|
|
3742
|
+
placeholder: {
|
|
3743
|
+
fontSize: Math.round(size / 3),
|
|
3744
|
+
fontWeight: 800
|
|
3745
|
+
}
|
|
3746
|
+
},
|
|
3747
|
+
children: initials
|
|
3748
|
+
});
|
|
3749
|
+
}
|
|
3750
|
+
const formatSize = bytes => `${Math.round(bytes / 1024)} KB`;
|
|
3751
|
+
|
|
3752
|
+
/**
|
|
3753
|
+
* @param {object} props
|
|
3754
|
+
* @param {'modal'|'card'} [props.variant='modal']
|
|
3755
|
+
* @param {boolean} [props.opened] - Visibility (modal only)
|
|
3756
|
+
* @param {Function} [props.onClose] - Closing (modal only)
|
|
3757
|
+
* @param {Function} [props.onProfileUpdate] - Receives `{ name }` or `{ image }`
|
|
3758
|
+
* @param {Function} [props.onSessionRevoked] - Receives the ended session's id
|
|
3759
|
+
* @param {Function} [props.onOtherSessionsRevoked]
|
|
3760
|
+
* @param {Function} [props.onProviderUnlinked] - Receives the provider id
|
|
3761
|
+
* @param {Function} [props.onError] - Receives the Error and `{ section, isShownOnScreen: true }`:
|
|
3762
|
+
* the screen already shows the message, so a panel should not toast it again
|
|
3763
|
+
* @param {boolean} [props.showAvatar=true]
|
|
3764
|
+
* @param {boolean} [props.showName=true]
|
|
3765
|
+
* @param {boolean} [props.showEmail=true]
|
|
3766
|
+
* @param {boolean} [props.showSignInMethods=true] - Drawn only when the application enabled a provider
|
|
3767
|
+
* @param {boolean} [props.showSessions=true]
|
|
3768
|
+
* @param {Partial<typeof LABELS>} [props.labels] - To change a word; the defaults are already Portuguese
|
|
3769
|
+
* @param {string} [props.title]
|
|
3770
|
+
* @param {string} [props.subtitle]
|
|
3771
|
+
* @param {string|import('react').ReactNode} [props.logo] - Card variant only
|
|
3772
|
+
* @param {number} [props.logoHeight=28]
|
|
3773
|
+
* @param {number} [props.width=520]
|
|
3774
|
+
* @param {number} [props.maxAvatarSize=512000] - In bytes
|
|
3775
|
+
* @param {import('react').ReactNode} [props.customSections] - Rendered after the built-in sections
|
|
3776
|
+
*/
|
|
3238
3777
|
function UserProfile({
|
|
3239
|
-
// Variant
|
|
3240
3778
|
variant = 'modal',
|
|
3241
3779
|
opened,
|
|
3242
3780
|
onClose,
|
|
3243
|
-
// Callbacks
|
|
3244
3781
|
onProfileUpdate,
|
|
3245
3782
|
onSessionRevoked,
|
|
3246
3783
|
onOtherSessionsRevoked,
|
|
3784
|
+
onProviderUnlinked,
|
|
3247
3785
|
onError,
|
|
3248
|
-
// Features toggle
|
|
3249
3786
|
showAvatar = true,
|
|
3250
3787
|
showName = true,
|
|
3251
3788
|
showEmail = true,
|
|
3789
|
+
showSignInMethods = true,
|
|
3252
3790
|
showSessions = true,
|
|
3253
|
-
// Customização
|
|
3254
3791
|
labels = {},
|
|
3255
|
-
title
|
|
3256
|
-
subtitle
|
|
3792
|
+
title,
|
|
3793
|
+
subtitle,
|
|
3257
3794
|
logo,
|
|
3258
3795
|
logoHeight = 28,
|
|
3259
|
-
width =
|
|
3260
|
-
// Avatar config
|
|
3796
|
+
width = 520,
|
|
3261
3797
|
maxAvatarSize = 500 * 1024,
|
|
3262
|
-
// 500KB
|
|
3263
|
-
|
|
3264
|
-
// Custom sections (React nodes to render after built-in sections)
|
|
3265
3798
|
customSections,
|
|
3266
3799
|
...containerProps
|
|
3267
3800
|
}) {
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3801
|
+
const t = {
|
|
3802
|
+
...LABELS,
|
|
3803
|
+
...labels
|
|
3804
|
+
};
|
|
3805
|
+
const isVisible = variant === 'card' || Boolean(opened);
|
|
3272
3806
|
const {
|
|
3273
3807
|
user,
|
|
3274
3808
|
updateProfile,
|
|
3275
3809
|
loadingUpdateProfile
|
|
3276
3810
|
} = useUser();
|
|
3277
|
-
|
|
3278
|
-
// Hook para sessions
|
|
3279
3811
|
const {
|
|
3280
3812
|
currentSession,
|
|
3281
3813
|
sessions,
|
|
@@ -3286,698 +3818,650 @@ function UserProfile({
|
|
|
3286
3818
|
loadingListSessions,
|
|
3287
3819
|
loadingRevokeSession
|
|
3288
3820
|
} = useSessions();
|
|
3821
|
+
const identity = describeUser(user);
|
|
3289
3822
|
|
|
3290
|
-
//
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
const
|
|
3301
|
-
|
|
3302
|
-
browser: 'Unknown Browser',
|
|
3303
|
-
os: 'Unknown OS'
|
|
3304
|
-
};
|
|
3305
|
-
let browser = 'Unknown Browser';
|
|
3306
|
-
let os = 'Unknown OS';
|
|
3307
|
-
|
|
3308
|
-
// Detect browser
|
|
3309
|
-
if (ua.includes('Chrome') && !ua.includes('Edg')) browser = 'Chrome';else if (ua.includes('Firefox')) browser = 'Firefox';else if (ua.includes('Safari') && !ua.includes('Chrome')) browser = 'Safari';else if (ua.includes('Edg')) browser = 'Edge';else if (ua.includes('Opera') || ua.includes('OPR')) browser = 'Opera';
|
|
3310
|
-
|
|
3311
|
-
// Detect OS
|
|
3312
|
-
if (ua.includes('Windows')) os = 'Windows';else if (ua.includes('Mac OS')) os = 'macOS';else if (ua.includes('Linux')) os = 'Linux';else if (ua.includes('Android')) os = 'Android';else if (ua.includes('iPhone') || ua.includes('iPad')) os = 'iOS';
|
|
3313
|
-
return {
|
|
3314
|
-
browser,
|
|
3315
|
-
os
|
|
3316
|
-
};
|
|
3317
|
-
};
|
|
3318
|
-
|
|
3319
|
-
// Session handlers
|
|
3320
|
-
const handleRevokeSession = async sessionId => {
|
|
3321
|
-
// Check if revoking current session
|
|
3322
|
-
const isCurrentSession = sessionId === currentSession?.id || sessions.length === 1;
|
|
3323
|
-
|
|
3324
|
-
// If revoking current session, handle logout immediately after revocation
|
|
3325
|
-
if (isCurrentSession) {
|
|
3326
|
-
try {
|
|
3327
|
-
await revokeSession(sessionId);
|
|
3328
|
-
onSessionRevoked?.(sessionId);
|
|
3329
|
-
} catch (error) {
|
|
3330
|
-
onError?.(error);
|
|
3331
|
-
// Even if it fails, we're revoking our own session, so just logout
|
|
3332
|
-
// The server already revoked our session
|
|
3333
|
-
}
|
|
3334
|
-
// Clear auth state and redirect
|
|
3335
|
-
localStorage.removeItem('auth:token');
|
|
3336
|
-
window.dispatchEvent(new CustomEvent('auth:session-revoked'));
|
|
3337
|
-
if (variant === 'modal') onClose?.();
|
|
3338
|
-
return;
|
|
3339
|
-
}
|
|
3340
|
-
|
|
3341
|
-
// Revoking another session
|
|
3342
|
-
try {
|
|
3343
|
-
await revokeSession(sessionId);
|
|
3344
|
-
onSessionRevoked?.(sessionId);
|
|
3345
|
-
} catch (error) {
|
|
3346
|
-
// Check for 401 error (our SDK uses error.res, axios uses error.response)
|
|
3347
|
-
const status = error.res?.status || error.response?.status;
|
|
3348
|
-
if (status === 401) {
|
|
3349
|
-
// This means our session was revoked, not the target one - do logout
|
|
3350
|
-
localStorage.removeItem('auth:token');
|
|
3351
|
-
window.dispatchEvent(new CustomEvent('auth:session-revoked'));
|
|
3352
|
-
if (variant === 'modal') onClose?.();
|
|
3353
|
-
return;
|
|
3354
|
-
}
|
|
3355
|
-
onError?.(error);
|
|
3356
|
-
}
|
|
3357
|
-
};
|
|
3358
|
-
const handleRevokeOtherSessions = async () => {
|
|
3359
|
-
try {
|
|
3360
|
-
await revokeOtherSessions();
|
|
3361
|
-
onOtherSessionsRevoked?.();
|
|
3362
|
-
} catch (error) {
|
|
3363
|
-
onError?.(error);
|
|
3364
|
-
}
|
|
3365
|
-
};
|
|
3366
|
-
|
|
3367
|
-
// Name form
|
|
3823
|
+
// One editor open at a time: 'name', 'avatar' or null.
|
|
3824
|
+
const [editing, setEditing] = useState(null);
|
|
3825
|
+
const [avatarPreview, setAvatarPreview] = useState(null);
|
|
3826
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
3827
|
+
const [areSessionsOpen, setAreSessionsOpen] = useState(false);
|
|
3828
|
+
const [isSessionsRowActive, setIsSessionsRowActive] = useState(false);
|
|
3829
|
+
const [isConfirmingEnd, setIsConfirmingEnd] = useState(false);
|
|
3830
|
+
const [providers, setProviders] = useState([]);
|
|
3831
|
+
const [linked, setLinked] = useState([]);
|
|
3832
|
+
const [pendingProvider, setPendingProvider] = useState(null);
|
|
3833
|
+
const [failure, setFailure] = useState(null);
|
|
3834
|
+
const sessionsListId = useId();
|
|
3368
3835
|
const nameForm = useForm({
|
|
3369
3836
|
initialValues: {
|
|
3370
3837
|
name: ''
|
|
3371
3838
|
},
|
|
3372
3839
|
validate: {
|
|
3373
|
-
name:
|
|
3840
|
+
name: value => value.trim() ? null : t.nameRequired
|
|
3374
3841
|
}
|
|
3375
3842
|
});
|
|
3376
3843
|
|
|
3377
|
-
//
|
|
3378
|
-
|
|
3379
|
-
|
|
3844
|
+
// Sessions and sign-in methods load when the screen is shown, not before.
|
|
3845
|
+
useEffect(() => {
|
|
3846
|
+
if (!isVisible) return;
|
|
3847
|
+
if (showSessions) {
|
|
3848
|
+
getSession().catch(error => console.warn('[AuthSDK] Failed to read the current session:', error.message));
|
|
3849
|
+
listSessions().catch(error => console.warn('[AuthSDK] Failed to list sessions:', error.message));
|
|
3850
|
+
}
|
|
3851
|
+
if (showSignInMethods) refreshSignInMethods();
|
|
3852
|
+
}, [isVisible, showSessions, showSignInMethods]);
|
|
3380
3853
|
|
|
3381
|
-
//
|
|
3382
|
-
|
|
3383
|
-
if (
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3854
|
+
// Closing the modal resets it: the next opening starts at rest.
|
|
3855
|
+
useEffect(() => {
|
|
3856
|
+
if (isVisible) return;
|
|
3857
|
+
closeEditor();
|
|
3858
|
+
setAreSessionsOpen(false);
|
|
3859
|
+
setIsConfirmingEnd(false);
|
|
3860
|
+
}, [isVisible]);
|
|
3861
|
+
async function refreshSignInMethods() {
|
|
3862
|
+
const available = await getSocialProviders();
|
|
3863
|
+
setProviders(available);
|
|
3864
|
+
if (available.length === 0) return;
|
|
3865
|
+
try {
|
|
3866
|
+
setLinked(await getLinkedProviders());
|
|
3867
|
+
} catch (error) {
|
|
3868
|
+
console.warn('[AuthSDK] Failed to list linked providers:', error.message);
|
|
3869
|
+
setLinked([]);
|
|
3387
3870
|
}
|
|
3871
|
+
}
|
|
3388
3872
|
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3873
|
+
/*
|
|
3874
|
+
* `onError` still fires, with `isShownOnScreen: true`: a panel that wants a
|
|
3875
|
+
* log or a metric has it, and knows not to show a second message.
|
|
3876
|
+
*/
|
|
3877
|
+
function fail(section, error) {
|
|
3878
|
+
setFailure({
|
|
3879
|
+
section,
|
|
3880
|
+
message: error?.message || t.genericFailure
|
|
3881
|
+
});
|
|
3882
|
+
onError?.(error, {
|
|
3883
|
+
section,
|
|
3884
|
+
isShownOnScreen: true
|
|
3885
|
+
});
|
|
3886
|
+
}
|
|
3887
|
+
function closeEditor() {
|
|
3888
|
+
setFailure(null);
|
|
3889
|
+
setEditing(null);
|
|
3890
|
+
setAvatarPreview(null);
|
|
3891
|
+
setIsDragging(false);
|
|
3892
|
+
nameForm.reset();
|
|
3893
|
+
}
|
|
3894
|
+
function openEditor(section) {
|
|
3895
|
+
closeEditor();
|
|
3896
|
+
setEditing(section);
|
|
3897
|
+
if (section === 'name') nameForm.setValues({
|
|
3898
|
+
name: identity.name
|
|
3899
|
+
});
|
|
3900
|
+
}
|
|
3901
|
+
async function handleSaveName(values) {
|
|
3902
|
+
const name = values.name.trim();
|
|
3903
|
+
try {
|
|
3904
|
+
await updateProfile({
|
|
3905
|
+
name
|
|
3906
|
+
});
|
|
3907
|
+
closeEditor();
|
|
3908
|
+
onProfileUpdate?.({
|
|
3909
|
+
name
|
|
3910
|
+
});
|
|
3911
|
+
} catch (error) {
|
|
3912
|
+
fail('name', error);
|
|
3913
|
+
}
|
|
3914
|
+
}
|
|
3915
|
+
function handleAvatarFile(file) {
|
|
3916
|
+
if (!file) return;
|
|
3917
|
+
setFailure(null);
|
|
3918
|
+
if (!file.type?.startsWith('image/')) {
|
|
3919
|
+
fail('avatar', new Error(t.avatarInvalidType));
|
|
3392
3920
|
return;
|
|
3393
3921
|
}
|
|
3394
|
-
|
|
3395
|
-
// Validate file size
|
|
3396
3922
|
if (file.size > maxAvatarSize) {
|
|
3397
|
-
|
|
3923
|
+
fail('avatar', new Error(t.avatarTooLarge.replace('{size}', formatSize(maxAvatarSize))));
|
|
3398
3924
|
return;
|
|
3399
3925
|
}
|
|
3400
|
-
setAvatarFile(file);
|
|
3401
|
-
|
|
3402
|
-
// Convert to base64
|
|
3403
3926
|
const reader = new FileReader();
|
|
3404
|
-
reader.onloadend = () =>
|
|
3405
|
-
setAvatarPreview(reader.result);
|
|
3406
|
-
};
|
|
3927
|
+
reader.onloadend = () => setAvatarPreview(reader.result);
|
|
3407
3928
|
reader.readAsDataURL(file);
|
|
3408
|
-
}
|
|
3409
|
-
|
|
3410
|
-
// Populate forms when user data is available or section opens
|
|
3411
|
-
useEffect(() => {
|
|
3412
|
-
if (editingSection === 'name' && user?.name) {
|
|
3413
|
-
nameForm.setValues({
|
|
3414
|
-
name: user.name
|
|
3415
|
-
});
|
|
3416
|
-
}
|
|
3417
|
-
if (editingSection === 'avatar' && user?.image) {
|
|
3418
|
-
setAvatarPreview(user.image);
|
|
3419
|
-
}
|
|
3420
|
-
}, [editingSection, user]);
|
|
3421
|
-
const handleToggleSection = section => {
|
|
3422
|
-
if (editingSection === section) {
|
|
3423
|
-
setEditingSection(null);
|
|
3424
|
-
nameForm.reset();
|
|
3425
|
-
setAvatarPreview(null);
|
|
3426
|
-
setAvatarFile(null);
|
|
3427
|
-
} else {
|
|
3428
|
-
setEditingSection(section);
|
|
3429
|
-
}
|
|
3430
|
-
};
|
|
3431
|
-
const handleChangeName = async values => {
|
|
3929
|
+
}
|
|
3930
|
+
async function saveAvatar(image) {
|
|
3432
3931
|
try {
|
|
3433
3932
|
await updateProfile({
|
|
3434
|
-
|
|
3933
|
+
image
|
|
3435
3934
|
});
|
|
3436
|
-
|
|
3437
|
-
setEditingSection(null);
|
|
3935
|
+
closeEditor();
|
|
3438
3936
|
onProfileUpdate?.({
|
|
3439
|
-
|
|
3937
|
+
image
|
|
3440
3938
|
});
|
|
3441
3939
|
} catch (error) {
|
|
3442
|
-
|
|
3940
|
+
fail('avatar', error);
|
|
3443
3941
|
}
|
|
3444
|
-
}
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3942
|
+
}
|
|
3943
|
+
async function handleUnlink(provider) {
|
|
3944
|
+
setPendingProvider(provider);
|
|
3945
|
+
setFailure(null);
|
|
3946
|
+
try {
|
|
3947
|
+
await unlinkSocialProvider(provider);
|
|
3948
|
+
setLinked(current => current.filter(item => item.provider !== provider));
|
|
3949
|
+
onProviderUnlinked?.(provider);
|
|
3950
|
+
} catch (error) {
|
|
3951
|
+
fail('signIn', error);
|
|
3952
|
+
} finally {
|
|
3953
|
+
setPendingProvider(null);
|
|
3454
3954
|
}
|
|
3955
|
+
}
|
|
3956
|
+
async function handleLink(provider) {
|
|
3957
|
+
setPendingProvider(provider);
|
|
3958
|
+
setFailure(null);
|
|
3455
3959
|
try {
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
});
|
|
3459
|
-
setAvatarPreview(null);
|
|
3460
|
-
setAvatarFile(null);
|
|
3461
|
-
setEditingSection(null);
|
|
3462
|
-
onProfileUpdate?.({
|
|
3463
|
-
image: avatarPreview
|
|
3464
|
-
});
|
|
3960
|
+
// Leaves for the provider's consent screen; the page navigates away.
|
|
3961
|
+
await startSocialLink(provider);
|
|
3465
3962
|
} catch (error) {
|
|
3466
|
-
|
|
3963
|
+
setPendingProvider(null);
|
|
3964
|
+
fail('signIn', error);
|
|
3467
3965
|
}
|
|
3468
|
-
}
|
|
3469
|
-
|
|
3966
|
+
}
|
|
3967
|
+
async function handleEndSession(sessionId) {
|
|
3968
|
+
setFailure(null);
|
|
3470
3969
|
try {
|
|
3471
|
-
await
|
|
3472
|
-
|
|
3473
|
-
});
|
|
3474
|
-
setAvatarPreview(null);
|
|
3475
|
-
setAvatarFile(null);
|
|
3476
|
-
setEditingSection(null);
|
|
3477
|
-
onProfileUpdate?.({
|
|
3478
|
-
image: ''
|
|
3479
|
-
});
|
|
3970
|
+
await revokeSession(sessionId);
|
|
3971
|
+
onSessionRevoked?.(sessionId);
|
|
3480
3972
|
} catch (error) {
|
|
3481
|
-
|
|
3973
|
+
fail('sessions', error);
|
|
3482
3974
|
}
|
|
3483
|
-
}
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3975
|
+
}
|
|
3976
|
+
async function handleEndOthers() {
|
|
3977
|
+
setFailure(null);
|
|
3978
|
+
try {
|
|
3979
|
+
await revokeOtherSessions();
|
|
3980
|
+
setIsConfirmingEnd(false);
|
|
3981
|
+
onOtherSessionsRevoked?.();
|
|
3982
|
+
} catch (error) {
|
|
3983
|
+
fail('sessions', error);
|
|
3984
|
+
}
|
|
3985
|
+
}
|
|
3986
|
+
if (!user) return null;
|
|
3987
|
+
const ordered = orderSessions(sessions, currentSession?.id);
|
|
3988
|
+
const current = ordered.find(item => item.id === currentSession?.id);
|
|
3989
|
+
const othersCount = ordered.filter(item => item.id !== currentSession?.id).length;
|
|
3990
|
+
const hasProfile = showAvatar || showName || showEmail;
|
|
3991
|
+
const hasSignInMethods = showSignInMethods && providers.length > 0;
|
|
3992
|
+
const sessionsSummary = current ? `${deviceLabel(describeDevice(current.userAgent))} (${t.thisDevice.toLowerCase()})${othersCount ? ` e mais ${othersCount}` : ''}` : null;
|
|
3993
|
+
const header = /*#__PURE__*/jsxs(Group, {
|
|
3994
|
+
align: "flex-start",
|
|
3995
|
+
wrap: "nowrap",
|
|
3996
|
+
gap: 12,
|
|
3997
|
+
px: 24,
|
|
3998
|
+
pt: 20,
|
|
3999
|
+
pb: 16,
|
|
4000
|
+
style: {
|
|
4001
|
+
borderBottom: '1px solid var(--mantine-color-gray-2)'
|
|
4002
|
+
},
|
|
4003
|
+
children: [variant === 'card' && logo && (typeof logo === 'string' ? /*#__PURE__*/jsx(Image, {
|
|
4004
|
+
src: logo,
|
|
4005
|
+
alt: "",
|
|
4006
|
+
h: logoHeight,
|
|
4007
|
+
w: "auto",
|
|
4008
|
+
fit: "contain"
|
|
4009
|
+
}) : logo), /*#__PURE__*/jsxs(Box, {
|
|
4010
|
+
style: {
|
|
4011
|
+
flex: 1,
|
|
4012
|
+
minWidth: 0
|
|
4013
|
+
},
|
|
4014
|
+
children: [/*#__PURE__*/jsx(Text, {
|
|
4015
|
+
component: variant === 'modal' ? Modal.Title : 'h2',
|
|
4016
|
+
m: 0,
|
|
4017
|
+
fz: 20,
|
|
4018
|
+
fw: 900,
|
|
4019
|
+
lts: "-0.03em",
|
|
4020
|
+
lh: 1.2,
|
|
4021
|
+
c: "gray.9",
|
|
4022
|
+
children: title || t.title
|
|
4023
|
+
}), /*#__PURE__*/jsx(Hint, {
|
|
4024
|
+
children: subtitle || t.subtitle
|
|
4025
|
+
})]
|
|
4026
|
+
}), variant === 'modal' && /*#__PURE__*/jsx(ActionButton, {
|
|
4027
|
+
"aria-label": t.close,
|
|
4028
|
+
onClick: onClose,
|
|
4029
|
+
style: {
|
|
4030
|
+
width: 32,
|
|
4031
|
+
height: 32,
|
|
4032
|
+
padding: 0,
|
|
4033
|
+
flex: 'none'
|
|
4034
|
+
},
|
|
4035
|
+
children: /*#__PURE__*/jsx(IconX, {
|
|
4036
|
+
size: 16,
|
|
3499
4037
|
stroke: 1.5
|
|
3500
4038
|
})
|
|
3501
|
-
})
|
|
3502
|
-
|
|
4039
|
+
})]
|
|
4040
|
+
});
|
|
4041
|
+
const identityStrip = /*#__PURE__*/jsxs(Group, {
|
|
4042
|
+
gap: 14,
|
|
4043
|
+
wrap: "nowrap",
|
|
4044
|
+
px: 24,
|
|
4045
|
+
py: 18,
|
|
4046
|
+
bg: "gray.1",
|
|
4047
|
+
style: {
|
|
4048
|
+
borderBottom: '1px solid var(--mantine-color-gray-2)'
|
|
4049
|
+
},
|
|
4050
|
+
children: [/*#__PURE__*/jsx(SquareAvatar, {
|
|
4051
|
+
src: identity.image,
|
|
4052
|
+
initials: identity.initials,
|
|
4053
|
+
size: 48
|
|
4054
|
+
}), /*#__PURE__*/jsxs(Box, {
|
|
4055
|
+
style: {
|
|
4056
|
+
minWidth: 0
|
|
4057
|
+
},
|
|
3503
4058
|
children: [/*#__PURE__*/jsx(Text, {
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
4059
|
+
fz: 15,
|
|
4060
|
+
fw: 800,
|
|
4061
|
+
c: "gray.9",
|
|
4062
|
+
lh: 1.3,
|
|
4063
|
+
truncate: "end",
|
|
4064
|
+
children: identity.title
|
|
4065
|
+
}), identity.hasRealName && identity.email && /*#__PURE__*/jsx(Text, {
|
|
4066
|
+
fz: 12,
|
|
4067
|
+
fw: 500,
|
|
4068
|
+
c: "gray.5",
|
|
4069
|
+
truncate: "end",
|
|
4070
|
+
children: identity.email
|
|
3511
4071
|
})]
|
|
3512
4072
|
})]
|
|
3513
4073
|
});
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
4074
|
+
const avatarEditor = /*#__PURE__*/jsxs(Stack, {
|
|
4075
|
+
gap: 10,
|
|
4076
|
+
py: 12,
|
|
4077
|
+
children: [/*#__PURE__*/jsx(Text, {
|
|
4078
|
+
fz: 12,
|
|
4079
|
+
fw: 700,
|
|
4080
|
+
c: "gray.9",
|
|
4081
|
+
children: t.avatar
|
|
4082
|
+
}), /*#__PURE__*/jsx(FileButton, {
|
|
4083
|
+
onChange: handleAvatarFile,
|
|
4084
|
+
accept: "image/png,image/jpeg,image/gif,image/webp",
|
|
4085
|
+
children: props => /*#__PURE__*/jsxs(UnstyledButton, {
|
|
4086
|
+
...props,
|
|
4087
|
+
onDragOver: event => {
|
|
4088
|
+
event.preventDefault();
|
|
4089
|
+
setIsDragging(true);
|
|
4090
|
+
},
|
|
4091
|
+
onDragLeave: () => setIsDragging(false),
|
|
4092
|
+
onDrop: event => {
|
|
4093
|
+
event.preventDefault();
|
|
4094
|
+
setIsDragging(false);
|
|
4095
|
+
handleAvatarFile(event.dataTransfer.files?.[0]);
|
|
4096
|
+
},
|
|
4097
|
+
style: {
|
|
4098
|
+
display: 'flex',
|
|
4099
|
+
alignItems: 'center',
|
|
4100
|
+
gap: 14,
|
|
4101
|
+
padding: 14,
|
|
4102
|
+
borderRadius: 0,
|
|
4103
|
+
background: isDragging ? 'var(--mantine-color-gray-2)' : 'var(--mantine-color-gray-1)',
|
|
4104
|
+
border: `1px dashed var(--mantine-color-${isDragging ? 'gray-9' : 'gray-4'})`
|
|
4105
|
+
},
|
|
4106
|
+
children: [/*#__PURE__*/jsx(SquareAvatar, {
|
|
4107
|
+
src: avatarPreview || identity.image,
|
|
4108
|
+
initials: identity.initials,
|
|
4109
|
+
size: 64
|
|
4110
|
+
}), /*#__PURE__*/jsxs(Box, {
|
|
4111
|
+
children: [/*#__PURE__*/jsx(Text, {
|
|
4112
|
+
fz: 13,
|
|
4113
|
+
fw: 700,
|
|
4114
|
+
c: "gray.9",
|
|
4115
|
+
children: t.avatarPrompt
|
|
4116
|
+
}), /*#__PURE__*/jsx(Hint, {
|
|
4117
|
+
children: t.avatarHint.replace('{size}', formatSize(maxAvatarSize))
|
|
4118
|
+
})]
|
|
4119
|
+
})]
|
|
4120
|
+
})
|
|
4121
|
+
}), /*#__PURE__*/jsx(FailureNote, {
|
|
4122
|
+
failure: failure,
|
|
4123
|
+
section: "avatar"
|
|
4124
|
+
}), /*#__PURE__*/jsxs(Group, {
|
|
4125
|
+
justify: "flex-end",
|
|
4126
|
+
gap: 8,
|
|
4127
|
+
children: [identity.image && !avatarPreview && /*#__PURE__*/jsx(ActionButton, {
|
|
4128
|
+
tone: "danger",
|
|
4129
|
+
loading: loadingUpdateProfile,
|
|
4130
|
+
onClick: () => saveAvatar(''),
|
|
4131
|
+
style: {
|
|
4132
|
+
marginRight: 'auto'
|
|
4133
|
+
},
|
|
4134
|
+
children: t.remove
|
|
4135
|
+
}), /*#__PURE__*/jsx(ActionButton, {
|
|
4136
|
+
tone: "quiet",
|
|
4137
|
+
onClick: closeEditor,
|
|
4138
|
+
children: t.cancel
|
|
4139
|
+
}), /*#__PURE__*/jsx(ActionButton, {
|
|
4140
|
+
tone: "dark",
|
|
4141
|
+
loading: loadingUpdateProfile,
|
|
4142
|
+
disabled: !avatarPreview,
|
|
4143
|
+
onClick: () => saveAvatar(avatarPreview),
|
|
4144
|
+
children: t.save
|
|
4145
|
+
})]
|
|
4146
|
+
})]
|
|
4147
|
+
});
|
|
4148
|
+
const nameEditor = /*#__PURE__*/jsx("form", {
|
|
4149
|
+
onSubmit: nameForm.onSubmit(handleSaveName),
|
|
4150
|
+
style: {
|
|
4151
|
+
...rowDivider,
|
|
4152
|
+
padding: '12px 0 14px'
|
|
4153
|
+
},
|
|
4154
|
+
children: /*#__PURE__*/jsxs(Stack, {
|
|
4155
|
+
gap: 10,
|
|
4156
|
+
children: [/*#__PURE__*/jsx(TextInput, {
|
|
4157
|
+
label: t.name,
|
|
4158
|
+
placeholder: t.namePlaceholder,
|
|
4159
|
+
autoComplete: "name",
|
|
4160
|
+
"data-autofocus": true,
|
|
4161
|
+
autoFocus: true,
|
|
4162
|
+
radius: 0,
|
|
4163
|
+
size: "sm",
|
|
4164
|
+
styles: {
|
|
4165
|
+
label: {
|
|
4166
|
+
fontSize: 12,
|
|
4167
|
+
fontWeight: 700,
|
|
4168
|
+
color: 'var(--mantine-color-gray-9)',
|
|
4169
|
+
marginBottom: 6
|
|
4170
|
+
}
|
|
4171
|
+
},
|
|
4172
|
+
onKeyDown: event => {
|
|
4173
|
+
if (event.key !== 'Escape') return;
|
|
4174
|
+
event.stopPropagation();
|
|
4175
|
+
closeEditor();
|
|
4176
|
+
},
|
|
4177
|
+
...nameForm.getInputProps('name')
|
|
4178
|
+
}), /*#__PURE__*/jsx(Hint, {
|
|
4179
|
+
children: t.nameHint
|
|
4180
|
+
}), /*#__PURE__*/jsx(FailureNote, {
|
|
4181
|
+
failure: failure,
|
|
4182
|
+
section: "name"
|
|
4183
|
+
}), /*#__PURE__*/jsxs(Group, {
|
|
4184
|
+
justify: "flex-end",
|
|
4185
|
+
gap: 8,
|
|
4186
|
+
children: [/*#__PURE__*/jsx(ActionButton, {
|
|
4187
|
+
tone: "quiet",
|
|
4188
|
+
onClick: closeEditor,
|
|
4189
|
+
children: t.cancel
|
|
4190
|
+
}), /*#__PURE__*/jsx(ActionButton, {
|
|
4191
|
+
tone: "dark",
|
|
4192
|
+
type: "submit",
|
|
4193
|
+
loading: loadingUpdateProfile,
|
|
4194
|
+
children: t.save
|
|
3541
4195
|
})]
|
|
3542
|
-
}), action && /*#__PURE__*/jsx(Tooltip, {
|
|
3543
|
-
label: actionLabel || action,
|
|
3544
|
-
position: "left",
|
|
3545
|
-
children: /*#__PURE__*/jsx(Anchor, {
|
|
3546
|
-
size: "xs",
|
|
3547
|
-
fw: 300,
|
|
3548
|
-
onClick: onClick,
|
|
3549
|
-
c: "gray",
|
|
3550
|
-
underline: "none",
|
|
3551
|
-
children: expanded ? labels.cancel || 'Cancel' : action
|
|
3552
|
-
})
|
|
3553
4196
|
})]
|
|
3554
4197
|
})
|
|
3555
4198
|
});
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
children: [
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
size: 48,
|
|
3580
|
-
radius: "xl"
|
|
3581
|
-
// color="initials"
|
|
3582
|
-
})
|
|
3583
|
-
})
|
|
3584
|
-
}), /*#__PURE__*/jsx(Collapse, {
|
|
3585
|
-
in: editingSection === 'avatar',
|
|
3586
|
-
children: /*#__PURE__*/jsx(Paper, {
|
|
3587
|
-
p: "sm",
|
|
3588
|
-
withBorder: true,
|
|
3589
|
-
radius: "sm",
|
|
3590
|
-
children: /*#__PURE__*/jsxs(Stack, {
|
|
3591
|
-
gap: "md",
|
|
3592
|
-
align: "center",
|
|
3593
|
-
children: [/*#__PURE__*/jsx(FileButton, {
|
|
3594
|
-
onChange: handleAvatarFileChange,
|
|
3595
|
-
accept: "image/*",
|
|
3596
|
-
children: props => /*#__PURE__*/jsx(Tooltip, {
|
|
3597
|
-
label: labels.clickToChange || 'Clique para alterar',
|
|
3598
|
-
position: "bottom",
|
|
3599
|
-
children: /*#__PURE__*/jsxs(Box, {
|
|
3600
|
-
...props,
|
|
3601
|
-
pos: "relative",
|
|
3602
|
-
style: {
|
|
3603
|
-
cursor: 'pointer'
|
|
3604
|
-
},
|
|
3605
|
-
children: [/*#__PURE__*/jsx(Avatar, {
|
|
3606
|
-
src: avatarPreview || user?.image,
|
|
3607
|
-
name: user?.name || user?.email,
|
|
3608
|
-
size: 80,
|
|
3609
|
-
radius: 80,
|
|
3610
|
-
color: "gray"
|
|
3611
|
-
}), /*#__PURE__*/jsx(ThemeIcon, {
|
|
3612
|
-
size: 26,
|
|
3613
|
-
radius: "xl",
|
|
3614
|
-
color: "gray",
|
|
3615
|
-
pos: "absolute",
|
|
3616
|
-
bottom: 0,
|
|
3617
|
-
right: 0,
|
|
3618
|
-
bd: "2px solid body",
|
|
3619
|
-
children: /*#__PURE__*/jsx(IconPhoto, {
|
|
3620
|
-
size: 14,
|
|
3621
|
-
stroke: 1.5
|
|
3622
|
-
})
|
|
3623
|
-
})]
|
|
3624
|
-
})
|
|
3625
|
-
})
|
|
3626
|
-
}), /*#__PURE__*/jsx(Text, {
|
|
3627
|
-
size: "xs",
|
|
3628
|
-
c: "dimmed",
|
|
3629
|
-
ta: "center",
|
|
3630
|
-
children: labels.avatarHint || `Máximo ${Math.round(maxAvatarSize / 1024)}KB • JPG, PNG, GIF, WebP`
|
|
3631
|
-
}), /*#__PURE__*/jsxs(Group, {
|
|
3632
|
-
justify: "center",
|
|
3633
|
-
gap: "xs",
|
|
3634
|
-
children: [user?.image && /*#__PURE__*/jsx(Button, {
|
|
3635
|
-
variant: "subtle",
|
|
3636
|
-
color: "gray",
|
|
3637
|
-
size: "xs",
|
|
3638
|
-
onClick: handleRemoveAvatar,
|
|
3639
|
-
loading: loadingUpdateProfile,
|
|
3640
|
-
loaderProps: {
|
|
3641
|
-
size: 12
|
|
3642
|
-
},
|
|
3643
|
-
leftSection: /*#__PURE__*/jsx(IconTrash, {
|
|
3644
|
-
size: 14,
|
|
3645
|
-
stroke: 1.5
|
|
3646
|
-
}),
|
|
3647
|
-
children: labels.remove || 'Remover'
|
|
3648
|
-
}), /*#__PURE__*/jsx(Button, {
|
|
3649
|
-
variant: "default",
|
|
3650
|
-
size: "xs",
|
|
3651
|
-
onClick: () => handleToggleSection('avatar'),
|
|
3652
|
-
children: labels.cancel || 'Cancelar'
|
|
3653
|
-
}), /*#__PURE__*/jsx(Button, {
|
|
3654
|
-
size: "xs",
|
|
3655
|
-
loading: loadingUpdateProfile,
|
|
3656
|
-
loaderProps: {
|
|
3657
|
-
size: 12
|
|
3658
|
-
},
|
|
3659
|
-
leftSection: /*#__PURE__*/jsx(IconCheck, {
|
|
3660
|
-
size: 14,
|
|
3661
|
-
stroke: 1.5
|
|
3662
|
-
}),
|
|
3663
|
-
onClick: handleChangeAvatar,
|
|
3664
|
-
disabled: !avatarPreview || avatarPreview === user?.image,
|
|
3665
|
-
children: labels.save || 'Salvar'
|
|
3666
|
-
})]
|
|
3667
|
-
})]
|
|
3668
|
-
})
|
|
3669
|
-
})
|
|
3670
|
-
})]
|
|
3671
|
-
}), showName && /*#__PURE__*/jsxs(Fragment, {
|
|
3672
|
-
children: [/*#__PURE__*/jsx(SettingRow, {
|
|
3673
|
-
label: labels.name || 'Nome',
|
|
3674
|
-
action: labels.change || 'Change',
|
|
3675
|
-
actionLabel: labels.changeName || 'Change your display name',
|
|
3676
|
-
onClick: () => handleToggleSection('name'),
|
|
3677
|
-
expanded: editingSection === 'name',
|
|
3678
|
-
children: /*#__PURE__*/jsxs(Group, {
|
|
3679
|
-
gap: "xs",
|
|
3680
|
-
children: [/*#__PURE__*/jsx(IconPencil, {
|
|
3681
|
-
size: 18,
|
|
3682
|
-
stroke: 1.5,
|
|
3683
|
-
color: "var(--mantine-color-dimmed)"
|
|
3684
|
-
}), /*#__PURE__*/jsx(Text, {
|
|
3685
|
-
size: "sm",
|
|
3686
|
-
children: user?.name || labels.notDefined || 'Não definido'
|
|
3687
|
-
})]
|
|
3688
|
-
})
|
|
3689
|
-
}), /*#__PURE__*/jsx(Collapse, {
|
|
3690
|
-
in: editingSection === 'name',
|
|
3691
|
-
children: /*#__PURE__*/jsx(Paper, {
|
|
3692
|
-
p: "sm",
|
|
3693
|
-
withBorder: true,
|
|
3694
|
-
radius: "sm",
|
|
3695
|
-
children: /*#__PURE__*/jsx("form", {
|
|
3696
|
-
onSubmit: nameForm.onSubmit(handleChangeName),
|
|
3697
|
-
children: /*#__PURE__*/jsxs(Stack, {
|
|
3698
|
-
gap: "sm",
|
|
3699
|
-
children: [/*#__PURE__*/jsx(TextInput, {
|
|
3700
|
-
label: labels.name || 'Nome',
|
|
3701
|
-
placeholder: labels.namePlaceholder || 'Digite seu nome',
|
|
3702
|
-
leftSection: /*#__PURE__*/jsx(IconUser, {
|
|
3703
|
-
size: 16,
|
|
3704
|
-
stroke: 1.5
|
|
3705
|
-
}),
|
|
3706
|
-
...nameForm.getInputProps('name')
|
|
3707
|
-
}), /*#__PURE__*/jsxs(Group, {
|
|
3708
|
-
justify: "flex-end",
|
|
3709
|
-
gap: "xs",
|
|
3710
|
-
children: [/*#__PURE__*/jsx(Button, {
|
|
3711
|
-
variant: "default",
|
|
3712
|
-
size: "xs",
|
|
3713
|
-
onClick: () => handleToggleSection('name'),
|
|
3714
|
-
children: labels.cancel || 'Cancelar'
|
|
3715
|
-
}), /*#__PURE__*/jsx(Button, {
|
|
3716
|
-
type: "submit",
|
|
3717
|
-
size: "xs",
|
|
3718
|
-
loading: loadingUpdateProfile,
|
|
3719
|
-
loaderProps: {
|
|
3720
|
-
size: 12
|
|
3721
|
-
},
|
|
3722
|
-
leftSection: /*#__PURE__*/jsx(IconCheck, {
|
|
3723
|
-
size: 14,
|
|
3724
|
-
stroke: 1.5
|
|
3725
|
-
}),
|
|
3726
|
-
children: labels.save || 'Salvar'
|
|
3727
|
-
})]
|
|
3728
|
-
})]
|
|
3729
|
-
})
|
|
3730
|
-
})
|
|
3731
|
-
})
|
|
3732
|
-
})]
|
|
3733
|
-
}), showEmail && /*#__PURE__*/jsx(SettingRow, {
|
|
3734
|
-
label: labels.email || 'Email',
|
|
4199
|
+
const sessionsList = /*#__PURE__*/jsxs(Box, {
|
|
4200
|
+
id: sessionsListId,
|
|
4201
|
+
mb: 10,
|
|
4202
|
+
children: [loadingListSessions && ordered.length === 0 ? /*#__PURE__*/jsx(Hint, {
|
|
4203
|
+
children: t.loadingSessions
|
|
4204
|
+
}) : ordered.length === 0 ? /*#__PURE__*/jsx(Hint, {
|
|
4205
|
+
children: t.noSessionsFound
|
|
4206
|
+
}) : ordered.map((item, index) => {
|
|
4207
|
+
const device = describeDevice(item.userAgent);
|
|
4208
|
+
const isCurrent = item.id === currentSession?.id;
|
|
4209
|
+
const since = formatSessionStart(item.createdAt);
|
|
4210
|
+
return /*#__PURE__*/jsx(Row$1, {
|
|
4211
|
+
isFirst: index === 0,
|
|
4212
|
+
action: isCurrent ? null : /*#__PURE__*/jsx(ActionButton, {
|
|
4213
|
+
tone: "danger",
|
|
4214
|
+
loading: loadingRevokeSession === item.id,
|
|
4215
|
+
onClick: () => handleEndSession(item.id),
|
|
4216
|
+
"aria-label": `${t.end} ${deviceLabel(device)}`,
|
|
4217
|
+
children: t.end
|
|
4218
|
+
}),
|
|
4219
|
+
children: /*#__PURE__*/jsx(WithIcon, {
|
|
4220
|
+
icon: DEVICE_MARKS[device.kind] || IconDeviceDesktop,
|
|
4221
|
+
detail: `${item.ipAddress ? `IP ${item.ipAddress}` : t.unknownIP}${since ? ` · ${t.since} ${since}` : ''}`,
|
|
3735
4222
|
children: /*#__PURE__*/jsxs(Group, {
|
|
3736
|
-
gap:
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
children: user?.email || 'email@exemplo.com'
|
|
4223
|
+
gap: 6,
|
|
4224
|
+
wrap: "wrap",
|
|
4225
|
+
children: [/*#__PURE__*/jsx("span", {
|
|
4226
|
+
children: deviceLabel(device)
|
|
4227
|
+
}), isCurrent && /*#__PURE__*/jsx(Chip, {
|
|
4228
|
+
tone: "dark",
|
|
4229
|
+
children: t.thisDevice
|
|
3744
4230
|
})]
|
|
3745
4231
|
})
|
|
4232
|
+
})
|
|
4233
|
+
}, item.id);
|
|
4234
|
+
}), /*#__PURE__*/jsx(FailureNote, {
|
|
4235
|
+
failure: failure,
|
|
4236
|
+
section: "sessions"
|
|
4237
|
+
}), othersCount > 0 && current && (isConfirmingEnd ? /*#__PURE__*/jsxs(Stack, {
|
|
4238
|
+
role: "alertdialog",
|
|
4239
|
+
"aria-label": endOthersQuestion(othersCount),
|
|
4240
|
+
gap: 10,
|
|
4241
|
+
p: 12,
|
|
4242
|
+
mt: 4,
|
|
4243
|
+
bg: "red.0",
|
|
4244
|
+
style: {
|
|
4245
|
+
border: '1px solid var(--mantine-color-red-2)'
|
|
4246
|
+
},
|
|
4247
|
+
children: [/*#__PURE__*/jsxs(Text, {
|
|
4248
|
+
fz: 12,
|
|
4249
|
+
fw: 500,
|
|
4250
|
+
c: "gray.7",
|
|
4251
|
+
children: [/*#__PURE__*/jsx(Text, {
|
|
4252
|
+
span: true,
|
|
4253
|
+
inherit: true,
|
|
4254
|
+
fw: 800,
|
|
4255
|
+
c: "gray.9",
|
|
4256
|
+
children: endOthersQuestion(othersCount)
|
|
4257
|
+
}), ' ', t.confirmEndBody]
|
|
4258
|
+
}), /*#__PURE__*/jsxs(Group, {
|
|
4259
|
+
justify: "flex-end",
|
|
4260
|
+
gap: 8,
|
|
4261
|
+
children: [/*#__PURE__*/jsx(ActionButton, {
|
|
4262
|
+
tone: "quiet",
|
|
4263
|
+
onClick: () => setIsConfirmingEnd(false),
|
|
4264
|
+
children: t.cancel
|
|
4265
|
+
}), /*#__PURE__*/jsx(ActionButton, {
|
|
4266
|
+
tone: "dangerFill",
|
|
4267
|
+
loading: loadingRevokeSession === 'all',
|
|
4268
|
+
onClick: handleEndOthers,
|
|
4269
|
+
children: endOthersConfirm(othersCount)
|
|
3746
4270
|
})]
|
|
3747
4271
|
})]
|
|
3748
|
-
})
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
align: "center",
|
|
3809
|
-
children: [/*#__PURE__*/jsxs(Group, {
|
|
3810
|
-
gap: "sm",
|
|
3811
|
-
wrap: "nowrap",
|
|
3812
|
-
flex: 1,
|
|
3813
|
-
children: [/*#__PURE__*/jsx(ThemeIcon, {
|
|
3814
|
-
size: 32,
|
|
3815
|
-
variant: "subtle",
|
|
3816
|
-
color: "gray",
|
|
3817
|
-
children: /*#__PURE__*/jsx(IconDeviceMobile, {
|
|
3818
|
-
size: 18,
|
|
3819
|
-
stroke: 1.5
|
|
3820
|
-
})
|
|
3821
|
-
}), /*#__PURE__*/jsxs(Box, {
|
|
3822
|
-
flex: 1,
|
|
3823
|
-
children: [/*#__PURE__*/jsxs(Group, {
|
|
3824
|
-
gap: "xs",
|
|
3825
|
-
children: [/*#__PURE__*/jsx(Text, {
|
|
3826
|
-
size: "xs",
|
|
3827
|
-
fw: 600,
|
|
3828
|
-
children: deviceInfo.browser
|
|
3829
|
-
}), isCurrentSession && /*#__PURE__*/jsx(Badge, {
|
|
3830
|
-
size: "xs",
|
|
3831
|
-
variant: "light",
|
|
3832
|
-
color: "gray",
|
|
3833
|
-
children: labels.thisDevice || 'Este dispositivo'
|
|
3834
|
-
})]
|
|
3835
|
-
}), /*#__PURE__*/jsxs(Text, {
|
|
3836
|
-
size: "xs",
|
|
3837
|
-
c: "dimmed",
|
|
3838
|
-
children: [deviceInfo.os, " \u2022 ", sessionItem.ipAddress || labels.unknownIP || 'IP desconhecido']
|
|
3839
|
-
}), /*#__PURE__*/jsxs(Text, {
|
|
3840
|
-
size: "xs",
|
|
3841
|
-
c: "dimmed",
|
|
3842
|
-
children: [labels.createdAt || 'Criada em', " ", createdDate.toLocaleDateString('pt-BR'), " ", labels.at || 'às', ' ', createdDate.toLocaleTimeString('pt-BR', {
|
|
3843
|
-
hour: '2-digit',
|
|
3844
|
-
minute: '2-digit'
|
|
3845
|
-
})]
|
|
3846
|
-
})]
|
|
3847
|
-
})]
|
|
3848
|
-
}), /*#__PURE__*/jsx(Tooltip, {
|
|
3849
|
-
label: isCurrentSession ? labels.signOutAndEnd || 'Encerrar e sair' : labels.endSession || 'Encerrar sessão',
|
|
3850
|
-
children: /*#__PURE__*/jsx(Button, {
|
|
3851
|
-
variant: "subtle",
|
|
3852
|
-
color: "gray",
|
|
3853
|
-
size: "xs",
|
|
3854
|
-
onClick: () => handleRevokeSession(sessionItem.id),
|
|
3855
|
-
loading: loadingRevokeSession === sessionItem.id,
|
|
3856
|
-
loaderProps: {
|
|
3857
|
-
size: 12
|
|
3858
|
-
},
|
|
3859
|
-
leftSection: /*#__PURE__*/jsx(IconLogout, {
|
|
3860
|
-
size: 14,
|
|
3861
|
-
stroke: 1.5
|
|
3862
|
-
}),
|
|
3863
|
-
children: labels.end || 'Encerrar'
|
|
3864
|
-
})
|
|
3865
|
-
})]
|
|
3866
|
-
})
|
|
3867
|
-
}, sessionItem.id);
|
|
3868
|
-
}), sessions.length > 1 && /*#__PURE__*/jsx(Group, {
|
|
3869
|
-
justify: "flex-end",
|
|
3870
|
-
mt: "xs",
|
|
3871
|
-
children: /*#__PURE__*/jsx(Button, {
|
|
3872
|
-
variant: "subtle",
|
|
3873
|
-
color: "gray",
|
|
3874
|
-
size: "xs",
|
|
3875
|
-
onClick: handleRevokeOtherSessions,
|
|
3876
|
-
loading: loadingRevokeSession === 'all',
|
|
3877
|
-
loaderProps: {
|
|
3878
|
-
size: 12
|
|
3879
|
-
},
|
|
3880
|
-
leftSection: /*#__PURE__*/jsx(IconLogout, {
|
|
3881
|
-
size: 14,
|
|
3882
|
-
stroke: 1.5
|
|
3883
|
-
}),
|
|
3884
|
-
children: labels.endOtherSessions || 'Encerrar todas as outras sessões'
|
|
3885
|
-
})
|
|
3886
|
-
})]
|
|
3887
|
-
})
|
|
3888
|
-
})
|
|
3889
|
-
})
|
|
3890
|
-
})]
|
|
4272
|
+
}) : /*#__PURE__*/jsx(Group, {
|
|
4273
|
+
justify: "flex-end",
|
|
4274
|
+
pt: 4,
|
|
4275
|
+
pb: 4,
|
|
4276
|
+
children: /*#__PURE__*/jsx(ActionButton, {
|
|
4277
|
+
tone: "dangerOutline",
|
|
4278
|
+
onClick: () => setIsConfirmingEnd(true),
|
|
4279
|
+
children: endOthersAction(othersCount)
|
|
4280
|
+
})
|
|
4281
|
+
}))]
|
|
4282
|
+
});
|
|
4283
|
+
const content = /*#__PURE__*/jsxs(Fragment, {
|
|
4284
|
+
children: [header, identityStrip, hasProfile && /*#__PURE__*/jsxs(Section, {
|
|
4285
|
+
label: t.profileSection,
|
|
4286
|
+
isFirst: true,
|
|
4287
|
+
children: [showAvatar && (editing === 'avatar' ? avatarEditor : /*#__PURE__*/jsx(Row$1, {
|
|
4288
|
+
label: t.avatar,
|
|
4289
|
+
isFirst: true,
|
|
4290
|
+
action: /*#__PURE__*/jsx(ActionButton, {
|
|
4291
|
+
onClick: () => openEditor('avatar'),
|
|
4292
|
+
children: t.edit
|
|
4293
|
+
}),
|
|
4294
|
+
children: /*#__PURE__*/jsx(SquareAvatar, {
|
|
4295
|
+
src: identity.image,
|
|
4296
|
+
initials: identity.initials,
|
|
4297
|
+
size: 32
|
|
4298
|
+
})
|
|
4299
|
+
})), showName && (editing === 'name' ? nameEditor : /*#__PURE__*/jsx(Row$1, {
|
|
4300
|
+
label: t.name,
|
|
4301
|
+
isFirst: !showAvatar,
|
|
4302
|
+
action: /*#__PURE__*/jsx(ActionButton, {
|
|
4303
|
+
onClick: () => openEditor('name'),
|
|
4304
|
+
children: t.edit
|
|
4305
|
+
}),
|
|
4306
|
+
children: identity.name || /*#__PURE__*/jsx(Text, {
|
|
4307
|
+
span: true,
|
|
4308
|
+
inherit: true,
|
|
4309
|
+
c: "gray.5",
|
|
4310
|
+
children: t.notDefined
|
|
4311
|
+
})
|
|
4312
|
+
})), showEmail && /*#__PURE__*/jsxs(Row$1, {
|
|
4313
|
+
label: t.email,
|
|
4314
|
+
isFirst: !showAvatar && !showName,
|
|
4315
|
+
children: [identity.email, /*#__PURE__*/jsx(Hint, {
|
|
4316
|
+
children: t.emailHint
|
|
4317
|
+
})]
|
|
4318
|
+
})]
|
|
4319
|
+
}), hasSignInMethods && /*#__PURE__*/jsxs(Section, {
|
|
4320
|
+
label: t.signInSection,
|
|
4321
|
+
isFirst: !hasProfile,
|
|
4322
|
+
children: [/*#__PURE__*/jsx(Row$1, {
|
|
4323
|
+
label: t.codeMethod,
|
|
4324
|
+
isFirst: true,
|
|
4325
|
+
action: /*#__PURE__*/jsx(Chip, {
|
|
4326
|
+
tone: "good",
|
|
4327
|
+
children: t.alwaysOn
|
|
4328
|
+
}),
|
|
4329
|
+
children: /*#__PURE__*/jsx(WithIcon, {
|
|
4330
|
+
icon: IconMail,
|
|
4331
|
+
children: t.codeByEmail
|
|
3891
4332
|
})
|
|
4333
|
+
}), providers.map(item => {
|
|
4334
|
+
const link = linked.find(entry => entry.provider === item.provider);
|
|
4335
|
+
return /*#__PURE__*/jsx(Row$1, {
|
|
4336
|
+
label: item.name,
|
|
4337
|
+
action: link ? /*#__PURE__*/jsx(ActionButton, {
|
|
4338
|
+
tone: "quiet",
|
|
4339
|
+
loading: pendingProvider === item.provider,
|
|
4340
|
+
onClick: () => handleUnlink(item.provider),
|
|
4341
|
+
"aria-label": `${t.disconnect} ${item.name}`,
|
|
4342
|
+
children: t.disconnect
|
|
4343
|
+
}) : /*#__PURE__*/jsx(ActionButton, {
|
|
4344
|
+
loading: pendingProvider === item.provider,
|
|
4345
|
+
onClick: () => handleLink(item.provider),
|
|
4346
|
+
"aria-label": `${t.connect} ${item.name}`,
|
|
4347
|
+
children: t.connect
|
|
4348
|
+
}),
|
|
4349
|
+
children: /*#__PURE__*/jsx(WithIcon, {
|
|
4350
|
+
icon: PROVIDER_MARKS[item.provider] || IconLink,
|
|
4351
|
+
children: link ? link.email || item.name : /*#__PURE__*/jsx(Text, {
|
|
4352
|
+
span: true,
|
|
4353
|
+
inherit: true,
|
|
4354
|
+
c: "gray.5",
|
|
4355
|
+
children: t.notConnected
|
|
4356
|
+
})
|
|
4357
|
+
})
|
|
4358
|
+
}, item.provider);
|
|
4359
|
+
}), /*#__PURE__*/jsx(FailureNote, {
|
|
4360
|
+
failure: failure,
|
|
4361
|
+
section: "signIn"
|
|
3892
4362
|
})]
|
|
4363
|
+
}), showSessions && /*#__PURE__*/jsxs(Section, {
|
|
4364
|
+
label: t.sessionsSection,
|
|
4365
|
+
isFirst: !hasProfile && !hasSignInMethods,
|
|
4366
|
+
children: [/*#__PURE__*/jsx(UnstyledButton, {
|
|
4367
|
+
"aria-expanded": areSessionsOpen,
|
|
4368
|
+
"aria-controls": sessionsListId,
|
|
4369
|
+
onClick: () => {
|
|
4370
|
+
setAreSessionsOpen(isOpen => !isOpen);
|
|
4371
|
+
setIsConfirmingEnd(false);
|
|
4372
|
+
},
|
|
4373
|
+
onMouseEnter: () => setIsSessionsRowActive(true),
|
|
4374
|
+
onMouseLeave: () => setIsSessionsRowActive(false),
|
|
4375
|
+
onFocus: () => setIsSessionsRowActive(true),
|
|
4376
|
+
onBlur: () => setIsSessionsRowActive(false),
|
|
4377
|
+
w: "100%",
|
|
4378
|
+
style: {
|
|
4379
|
+
display: 'block',
|
|
4380
|
+
borderRadius: 0
|
|
4381
|
+
},
|
|
4382
|
+
children: /*#__PURE__*/jsx(Row$1, {
|
|
4383
|
+
label: t.devices,
|
|
4384
|
+
isFirst: true,
|
|
4385
|
+
action: /*#__PURE__*/jsxs(Box, {
|
|
4386
|
+
component: "span",
|
|
4387
|
+
style: {
|
|
4388
|
+
display: 'inline-flex',
|
|
4389
|
+
alignItems: 'center',
|
|
4390
|
+
gap: 4,
|
|
4391
|
+
padding: '5px 12px',
|
|
4392
|
+
fontSize: 12,
|
|
4393
|
+
fontWeight: 700,
|
|
4394
|
+
color: 'var(--mantine-color-gray-9)',
|
|
4395
|
+
// The whole line is the button; the pill only shows it.
|
|
4396
|
+
border: `1px solid var(--mantine-color-${isSessionsRowActive ? 'gray-9' : 'gray-3'})`,
|
|
4397
|
+
whiteSpace: 'nowrap'
|
|
4398
|
+
},
|
|
4399
|
+
children: [areSessionsOpen ? t.hideSessions : t.showSessions, /*#__PURE__*/jsx(IconChevronDown, {
|
|
4400
|
+
size: 14,
|
|
4401
|
+
stroke: 1.5,
|
|
4402
|
+
style: {
|
|
4403
|
+
transform: areSessionsOpen ? 'rotate(180deg)' : 'none',
|
|
4404
|
+
transition: 'transform 150ms ease'
|
|
4405
|
+
}
|
|
4406
|
+
})]
|
|
4407
|
+
}),
|
|
4408
|
+
children: /*#__PURE__*/jsx(WithIcon, {
|
|
4409
|
+
icon: IconDeviceLaptop,
|
|
4410
|
+
detail: sessionsSummary,
|
|
4411
|
+
children: loadingListSessions && ordered.length === 0 ? t.loadingSessions : countSessions(ordered.length)
|
|
4412
|
+
})
|
|
4413
|
+
})
|
|
4414
|
+
}), areSessionsOpen && sessionsList]
|
|
3893
4415
|
}), customSections]
|
|
3894
4416
|
});
|
|
3895
|
-
|
|
3896
|
-
// Renderizar como Modal
|
|
3897
4417
|
if (variant === 'modal') {
|
|
3898
|
-
return /*#__PURE__*/jsxs(Modal, {
|
|
3899
|
-
opened: opened,
|
|
4418
|
+
return /*#__PURE__*/jsxs(Modal.Root, {
|
|
4419
|
+
opened: Boolean(opened),
|
|
3900
4420
|
onClose: onClose,
|
|
3901
|
-
size: width
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
4421
|
+
size: width
|
|
4422
|
+
// With an editor open, Esc belongs to the editor: it cancels the
|
|
4423
|
+
// edit and leaves the screen open. Mantine listens for Esc on its
|
|
4424
|
+
// own, so stopping the key in the field is not enough.
|
|
4425
|
+
,
|
|
4426
|
+
closeOnEscape: !editing && !isConfirmingEnd,
|
|
4427
|
+
...containerProps,
|
|
4428
|
+
children: [/*#__PURE__*/jsx(Modal.Overlay, {
|
|
3905
4429
|
backgroundOpacity: 0.5,
|
|
3906
4430
|
blur: 4
|
|
3907
|
-
},
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
children:
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
})
|
|
3918
|
-
}), /*#__PURE__*/jsxs(Stack, {
|
|
3919
|
-
gap: 0,
|
|
3920
|
-
children: [/*#__PURE__*/jsx(Title, {
|
|
3921
|
-
order: 5,
|
|
3922
|
-
fw: 600,
|
|
3923
|
-
children: title
|
|
3924
|
-
}), /*#__PURE__*/jsx(Text, {
|
|
3925
|
-
size: "xs",
|
|
3926
|
-
c: "dimmed",
|
|
3927
|
-
children: subtitle
|
|
3928
|
-
})]
|
|
3929
|
-
})]
|
|
3930
|
-
}),
|
|
3931
|
-
...containerProps,
|
|
3932
|
-
children: [/*#__PURE__*/jsx(Divider, {
|
|
3933
|
-
mb: "md"
|
|
3934
|
-
}), profileContent]
|
|
4431
|
+
}), /*#__PURE__*/jsx(Modal.Content, {
|
|
4432
|
+
radius: 0,
|
|
4433
|
+
style: {
|
|
4434
|
+
border: '1px solid var(--mantine-color-gray-3)'
|
|
4435
|
+
},
|
|
4436
|
+
children: /*#__PURE__*/jsx(Modal.Body, {
|
|
4437
|
+
p: 0,
|
|
4438
|
+
children: content
|
|
4439
|
+
})
|
|
4440
|
+
})]
|
|
3935
4441
|
});
|
|
3936
4442
|
}
|
|
3937
|
-
|
|
3938
|
-
// Renderizar como Card
|
|
3939
4443
|
return /*#__PURE__*/jsx(Paper, {
|
|
3940
|
-
withBorder: true
|
|
3941
|
-
|
|
3942
|
-
,
|
|
3943
|
-
p: "md",
|
|
4444
|
+
withBorder: true,
|
|
4445
|
+
radius: 0,
|
|
4446
|
+
p: 0,
|
|
3944
4447
|
w: width,
|
|
3945
|
-
|
|
4448
|
+
maw: "100%",
|
|
3946
4449
|
...containerProps,
|
|
3947
|
-
children:
|
|
3948
|
-
gap: "sm",
|
|
3949
|
-
children: [/*#__PURE__*/jsxs(Group, {
|
|
3950
|
-
gap: "sm",
|
|
3951
|
-
children: [logo ? /*#__PURE__*/jsx(Image, {
|
|
3952
|
-
src: logo,
|
|
3953
|
-
alt: "Auth",
|
|
3954
|
-
h: logoHeight,
|
|
3955
|
-
fit: "contain"
|
|
3956
|
-
}) : /*#__PURE__*/jsx(ThemeIcon, {
|
|
3957
|
-
size: 32,
|
|
3958
|
-
variant: "subtle",
|
|
3959
|
-
color: "gray",
|
|
3960
|
-
children: /*#__PURE__*/jsx(IconUserCircle, {
|
|
3961
|
-
size: 24,
|
|
3962
|
-
stroke: 1.5
|
|
3963
|
-
})
|
|
3964
|
-
}), /*#__PURE__*/jsxs(Stack, {
|
|
3965
|
-
gap: 0,
|
|
3966
|
-
children: [/*#__PURE__*/jsx(Title, {
|
|
3967
|
-
order: 5,
|
|
3968
|
-
fw: 600,
|
|
3969
|
-
children: title
|
|
3970
|
-
}), /*#__PURE__*/jsx(Text, {
|
|
3971
|
-
size: "xs",
|
|
3972
|
-
c: "dimmed",
|
|
3973
|
-
children: subtitle
|
|
3974
|
-
})]
|
|
3975
|
-
})]
|
|
3976
|
-
}), /*#__PURE__*/jsx(Divider, {}), profileContent]
|
|
3977
|
-
})
|
|
4450
|
+
children: content
|
|
3978
4451
|
});
|
|
3979
4452
|
}
|
|
3980
4453
|
|
|
4454
|
+
/* The only texts that change with the count, so they are functions, not labels. */
|
|
4455
|
+
function endOthersAction(count) {
|
|
4456
|
+
return count === 1 ? 'Encerrar a outra' : `Encerrar as outras ${count}`;
|
|
4457
|
+
}
|
|
4458
|
+
function endOthersQuestion(count) {
|
|
4459
|
+
return count === 1 ? 'Encerrar 1 sessão?' : `Encerrar ${count} sessões?`;
|
|
4460
|
+
}
|
|
4461
|
+
function endOthersConfirm(count) {
|
|
4462
|
+
return count === 1 ? 'Encerrar 1 sessão' : `Encerrar ${count} sessões`;
|
|
4463
|
+
}
|
|
4464
|
+
|
|
3981
4465
|
const ROLE_LABELS = {
|
|
3982
4466
|
owner: 'Dono',
|
|
3983
4467
|
admin: 'Administrador',
|
|
@@ -3996,6 +4480,8 @@ const UPGRADE_THRESHOLD = 0.8;
|
|
|
3996
4480
|
* @property {string} [id] - Stable key
|
|
3997
4481
|
* @property {string} label - The row's text
|
|
3998
4482
|
* @property {Function} [icon] - A Tabler icon component
|
|
4483
|
+
* @property {string} [description] - One line under the label, saying what the action does
|
|
4484
|
+
* @property {'danger'} [tone] - For what cannot be undone: red label and icon
|
|
3999
4485
|
* @property {Function} onClick
|
|
4000
4486
|
*
|
|
4001
4487
|
* @typedef {Object} UserInformationPlan
|
|
@@ -4022,6 +4508,7 @@ const UPGRADE_THRESHOLD = 0.8;
|
|
|
4022
4508
|
* @param {Function} [props.onAccountClick]
|
|
4023
4509
|
* @param {Function} [props.onBillingClick]
|
|
4024
4510
|
* @param {UserInformationItem[]} [props.items] - The panel's own rows, shown in their own group
|
|
4511
|
+
* @param {string} [props.itemsLabel] - A title for that group ("Seus dados"); without it, none
|
|
4025
4512
|
* @param {UserInformationPlan} [props.plan] - The plan strip; omitted, the strip is not drawn
|
|
4026
4513
|
* @param {UserInformationOrganization} [props.organization] - Where the person is acting
|
|
4027
4514
|
* @param {boolean} [props.branded=true] - The footer: "Protegido por Auth" and the terms link
|
|
@@ -4037,6 +4524,7 @@ function UserInformation({
|
|
|
4037
4524
|
onAccountClick,
|
|
4038
4525
|
onBillingClick,
|
|
4039
4526
|
items = [],
|
|
4527
|
+
itemsLabel,
|
|
4040
4528
|
plan,
|
|
4041
4529
|
organization,
|
|
4042
4530
|
branded = true,
|
|
@@ -4054,17 +4542,13 @@ function UserInformation({
|
|
|
4054
4542
|
...others
|
|
4055
4543
|
}) {
|
|
4056
4544
|
if (!user) return null;
|
|
4057
|
-
const
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
*/
|
|
4065
|
-
const hasRealName = Boolean(name) && name.toLowerCase() !== email.split('@')[0].toLowerCase() && name.toLowerCase() !== email.toLowerCase();
|
|
4066
|
-
const title = hasRealName ? name : email || name;
|
|
4067
|
-
const initials = hasRealName ? name.split(/\s+/).map(part => part[0]).join('').slice(0, 2).toUpperCase() : (email || name).charAt(0).toUpperCase();
|
|
4545
|
+
const {
|
|
4546
|
+
email,
|
|
4547
|
+
hasRealName,
|
|
4548
|
+
title,
|
|
4549
|
+
initials,
|
|
4550
|
+
image
|
|
4551
|
+
} = describeUser(user);
|
|
4068
4552
|
const baseRows = [onAccountClick && {
|
|
4069
4553
|
id: 'account',
|
|
4070
4554
|
label: accountLabel,
|
|
@@ -4088,7 +4572,7 @@ function UserInformation({
|
|
|
4088
4572
|
px: padded ? 16 : 0,
|
|
4089
4573
|
py: 14,
|
|
4090
4574
|
children: [/*#__PURE__*/jsx(Avatar, {
|
|
4091
|
-
src:
|
|
4575
|
+
src: image,
|
|
4092
4576
|
alt: "",
|
|
4093
4577
|
size: 32,
|
|
4094
4578
|
radius: 0,
|
|
@@ -4138,6 +4622,7 @@ function UserInformation({
|
|
|
4138
4622
|
hasDivider: !organization?.name && !plan
|
|
4139
4623
|
}), panelRows.length > 0 && /*#__PURE__*/jsx(RowGroup, {
|
|
4140
4624
|
rows: panelRows,
|
|
4625
|
+
label: itemsLabel,
|
|
4141
4626
|
hasDivider: true
|
|
4142
4627
|
}), signOut && /*#__PURE__*/jsx(RowGroup, {
|
|
4143
4628
|
rows: [{
|
|
@@ -4309,24 +4794,59 @@ function ContextStrip({
|
|
|
4309
4794
|
}
|
|
4310
4795
|
function RowGroup({
|
|
4311
4796
|
rows,
|
|
4797
|
+
label,
|
|
4312
4798
|
hasDivider
|
|
4313
4799
|
}) {
|
|
4314
|
-
return /*#__PURE__*/
|
|
4800
|
+
return /*#__PURE__*/jsxs(Stack, {
|
|
4315
4801
|
gap: 0,
|
|
4316
4802
|
p: 6,
|
|
4803
|
+
role: label ? 'group' : undefined,
|
|
4804
|
+
"aria-label": label || undefined,
|
|
4317
4805
|
style: hasDivider ? {
|
|
4318
4806
|
borderTop: '1px solid var(--mantine-color-gray-2)'
|
|
4319
4807
|
} : undefined,
|
|
4320
|
-
children:
|
|
4808
|
+
children: [label && /*#__PURE__*/jsx(Text, {
|
|
4809
|
+
"aria-hidden": true,
|
|
4810
|
+
fz: 11,
|
|
4811
|
+
fw: 800,
|
|
4812
|
+
tt: "uppercase",
|
|
4813
|
+
lts: "1.5px",
|
|
4814
|
+
c: "gray.4",
|
|
4815
|
+
px: 10,
|
|
4816
|
+
pt: 6,
|
|
4817
|
+
pb: 2,
|
|
4818
|
+
children: label
|
|
4819
|
+
}), rows.map(row => /*#__PURE__*/jsx(Row, {
|
|
4321
4820
|
...row
|
|
4322
|
-
}, row.id || row.label))
|
|
4821
|
+
}, row.id || row.label))]
|
|
4323
4822
|
});
|
|
4324
4823
|
}
|
|
4824
|
+
|
|
4825
|
+
/*
|
|
4826
|
+
* A panel's row may carry a line saying what it does and, for what cannot be
|
|
4827
|
+
* undone, the danger tone — still as data, so every card keeps one shape. The
|
|
4828
|
+
* History needed both: "Apagar tudo" looked exactly like "Exportar".
|
|
4829
|
+
*/
|
|
4830
|
+
const ROW_TONES = {
|
|
4831
|
+
default: {
|
|
4832
|
+
text: 'gray.9',
|
|
4833
|
+
icon: 'var(--mantine-color-gray-5)',
|
|
4834
|
+
hover: 'var(--mantine-color-gray-1)'
|
|
4835
|
+
},
|
|
4836
|
+
danger: {
|
|
4837
|
+
text: 'red.8',
|
|
4838
|
+
icon: 'var(--mantine-color-red-8)',
|
|
4839
|
+
hover: 'var(--mantine-color-red-0)'
|
|
4840
|
+
}
|
|
4841
|
+
};
|
|
4325
4842
|
function Row({
|
|
4326
4843
|
label,
|
|
4844
|
+
description,
|
|
4845
|
+
tone,
|
|
4327
4846
|
icon: Icon,
|
|
4328
4847
|
onClick
|
|
4329
4848
|
}) {
|
|
4849
|
+
const look = ROW_TONES[tone] || ROW_TONES.default;
|
|
4330
4850
|
return /*#__PURE__*/jsxs(UnstyledButton, {
|
|
4331
4851
|
role: "menuitem",
|
|
4332
4852
|
onClick: onClick,
|
|
@@ -4335,7 +4855,7 @@ function Row({
|
|
|
4335
4855
|
w: "100%",
|
|
4336
4856
|
style: {
|
|
4337
4857
|
display: 'flex',
|
|
4338
|
-
alignItems: 'center',
|
|
4858
|
+
alignItems: description ? 'flex-start' : 'center',
|
|
4339
4859
|
gap: 10,
|
|
4340
4860
|
borderRadius: 0
|
|
4341
4861
|
}
|
|
@@ -4343,23 +4863,35 @@ function Row({
|
|
|
4343
4863
|
* Hover and keyboard focus share the same surface: a row reached
|
|
4344
4864
|
* with the arrows must look exactly like the one under the mouse.
|
|
4345
4865
|
*/,
|
|
4346
|
-
onMouseEnter: event => event.currentTarget.style.background =
|
|
4866
|
+
onMouseEnter: event => event.currentTarget.style.background = look.hover,
|
|
4347
4867
|
onMouseLeave: event => event.currentTarget.style.background = 'transparent',
|
|
4348
|
-
onFocus: event => event.currentTarget.style.background =
|
|
4868
|
+
onFocus: event => event.currentTarget.style.background = look.hover,
|
|
4349
4869
|
onBlur: event => event.currentTarget.style.background = 'transparent',
|
|
4350
4870
|
children: [Icon && /*#__PURE__*/jsx(Icon, {
|
|
4351
4871
|
size: 16,
|
|
4352
4872
|
stroke: 1.5,
|
|
4353
4873
|
style: {
|
|
4354
4874
|
flex: 'none',
|
|
4355
|
-
color:
|
|
4875
|
+
color: look.icon,
|
|
4876
|
+
marginTop: description ? 1 : 0
|
|
4356
4877
|
}
|
|
4357
|
-
}), /*#__PURE__*/
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4878
|
+
}), /*#__PURE__*/jsxs(Box, {
|
|
4879
|
+
style: {
|
|
4880
|
+
minWidth: 0
|
|
4881
|
+
},
|
|
4882
|
+
children: [/*#__PURE__*/jsx(Text, {
|
|
4883
|
+
fz: 12,
|
|
4884
|
+
fw: 500,
|
|
4885
|
+
c: look.text,
|
|
4886
|
+
truncate: "end",
|
|
4887
|
+
children: label
|
|
4888
|
+
}), description && /*#__PURE__*/jsx(Text, {
|
|
4889
|
+
fz: 12,
|
|
4890
|
+
fw: 500,
|
|
4891
|
+
c: "gray.5",
|
|
4892
|
+
lh: 1.4,
|
|
4893
|
+
children: description
|
|
4894
|
+
})]
|
|
4363
4895
|
})]
|
|
4364
4896
|
});
|
|
4365
4897
|
}
|