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