File size: 3,075 Bytes
dc47d57 134ecb7 dc47d57 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | import { useI18n } from '../i18n'
import type { MessageKey } from '../i18n/en'
import { isWebAuthnSupported } from '../auth/passkey'
import { Callout } from './ui'
type T = (key: MessageKey, vars?: Record<string, string | number>) => string
/** True when the app is rendered inside another frame — passkeys need the top-level origin. */
export function inEmbeddedFrame(): boolean {
try {
return window.self !== window.top
} catch {
return true
}
}
export function statusLabel(t: T, status: string): string {
const key = `d2.status.${status}` as MessageKey
const label = t(key)
return label === key ? status : label
}
export function roleLabel(t: T, role: string): string {
const key = `d2.role.${role}` as MessageKey
const label = t(key)
return label === key ? role : label
}
/** Shows unsupported-browser and embedded-frame warnings for passkey flows. */
export function PasskeyEnvWarnings() {
const { t } = useI18n()
return (
<>
{!isWebAuthnSupported() && <Callout tone="warn">{t('d2.unsupported')}</Callout>}
{inEmbeddedFrame() && <Callout tone="warn">{t('d2.iframeWarn')}</Callout>}
</>
)
}
/**
* When the app is embedded in another site's iframe (e.g. the huggingface.co Spaces page),
* browsers may block the session cookie and passkeys. Offer a one-tap break-out to the app's own
* first-party tab, where everything works. Renders nothing when not embedded.
*/
export function OpenInNewTabBanner() {
const { t } = useI18n()
if (!inEmbeddedFrame()) return null
const href = typeof window !== 'undefined' ? window.location.href : '#'
return (
<div className="callout tone-warn" role="note">
<strong>{t('d2.openNewTab.title')}</strong>
<p className="hint">{t('d2.openNewTab.body')}</p>
<a className="btn" href={href} target="_blank" rel="noopener noreferrer">
{t('d2.openNewTab.button')} ↗
</a>
</div>
)
}
/** Accessible destructive-action confirmation. Focus lands on Cancel; Esc cancels. */
export function ConfirmDialog({
message, confirmLabel, danger = true, busy = false, onConfirm, onCancel,
}: {
message: string
confirmLabel: string
danger?: boolean
busy?: boolean
onConfirm: () => void
onCancel: () => void
}) {
const { t } = useI18n()
return (
<div className="d2-modal-backdrop" onClick={onCancel}>
<div
className="d2-modal card"
role="alertdialog"
aria-modal="true"
aria-label={message}
onClick={(e) => e.stopPropagation()}
onKeyDown={(e) => {
if (e.key === 'Escape') onCancel()
}}
>
<p>{message}</p>
<div className="row">
<button type="button" className="btn" autoFocus onClick={onCancel} disabled={busy}>
{t('d2.cancel')}
</button>
<button
type="button"
className={danger ? 'btn danger' : 'btn'}
onClick={onConfirm}
disabled={busy}
>
{confirmLabel}
</button>
</div>
</div>
</div>
)
}
|