feat(auth): migrate account security presentation
This commit is contained in:
@@ -12,7 +12,6 @@ import {
|
||||
ListItem,
|
||||
ListItemSecondaryAction,
|
||||
ListItemText,
|
||||
Paper,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
@@ -21,6 +20,7 @@ import { api, getApiErrorMessage } from "../api";
|
||||
import { useToast } from "../toast";
|
||||
import { useI18n } from "../i18n/I18nProvider";
|
||||
import { clearAuthClientState } from "../auth";
|
||||
import { PageSection } from "../layout/PageLayout";
|
||||
|
||||
type Session = {
|
||||
id: string;
|
||||
@@ -37,11 +37,14 @@ function apiErrorMessage(e: any, t: (k: any) => string) {
|
||||
|
||||
export default function SessionsSettingsCard() {
|
||||
const { toast } = useToast();
|
||||
const { t } = useI18n();
|
||||
const { t, language } = useI18n();
|
||||
const locale = language === "nb" ? "nb-NO" : "en";
|
||||
const [sessions, setSessions] = useState<Session[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [confirmRevokeOthers, setConfirmRevokeOthers] = useState(false);
|
||||
const [confirmError, setConfirmError] = useState<string | null>(null);
|
||||
const [revokingOthers, setRevokingOthers] = useState(false);
|
||||
|
||||
const loadSessions = useCallback(() => {
|
||||
setLoading(true);
|
||||
@@ -73,23 +76,29 @@ export default function SessionsSettingsCard() {
|
||||
}
|
||||
|
||||
async function revokeOthers() {
|
||||
setRevokingOthers(true);
|
||||
setConfirmError(null);
|
||||
try {
|
||||
await api.post("/auth/sessions/revoke-others");
|
||||
toast(t("sessionsRevokedOthers"), "success");
|
||||
setConfirmRevokeOthers(false);
|
||||
loadSessions();
|
||||
} catch (e: any) {
|
||||
setError(apiErrorMessage(e, t));
|
||||
setConfirmError(apiErrorMessage(e, t));
|
||||
} finally {
|
||||
setRevokingOthers(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Paper sx={{ mt: 2, p: 2.5, borderRadius: 4, border: "none", boxShadow: "0px 1px 2px 0px rgba(15,23,42,0.04), 0px 8px 24px -12px rgba(15,23,42,0.12)" }}>
|
||||
<Typography variant="h6" sx={{ mb: 1 }}>
|
||||
{t("sessionsSectionTitle")}
|
||||
</Typography>
|
||||
function closeConfirmation() {
|
||||
setConfirmRevokeOthers(false);
|
||||
setConfirmError(null);
|
||||
}
|
||||
|
||||
{error ? <Alert severity="error" role="alert" sx={{ mb: 1 }}>{error}</Alert> : null}
|
||||
return (
|
||||
<PageSection title={t("sessionsSectionTitle")} surface>
|
||||
{loading ? <Typography role="status" color="text.secondary">{t("loading")}</Typography> : null}
|
||||
{error ? <Alert severity="error" role="alert" sx={{ mb: 1 }} action={<Button color="inherit" onClick={loadSessions}>{t("retry")}</Button>}>{error}</Alert> : null}
|
||||
{!loading && sessions.length === 0 && !error ? (
|
||||
<Typography sx={{ color: "text.secondary", mb: 1 }}>{t("sessionsEmpty")}</Typography>
|
||||
) : null}
|
||||
@@ -99,6 +108,7 @@ export default function SessionsSettingsCard() {
|
||||
{sessions.map((s) => (
|
||||
<ListItem key={s.id} divider>
|
||||
<ListItemText
|
||||
sx={{ minWidth: 0, overflowWrap: "anywhere" }}
|
||||
primary={
|
||||
<>
|
||||
{s.deviceLabel || t("sessionsUnknownDevice")}
|
||||
@@ -110,8 +120,8 @@ export default function SessionsSettingsCard() {
|
||||
</>
|
||||
}
|
||||
secondary={t("sessionsMeta", {
|
||||
lastSeen: new Date(s.lastSeenAtUtc).toLocaleString(),
|
||||
expires: new Date(s.expiresAtUtc).toLocaleDateString(),
|
||||
lastSeen: new Date(s.lastSeenAtUtc).toLocaleString(locale),
|
||||
expires: new Date(s.expiresAtUtc).toLocaleDateString(locale),
|
||||
})}
|
||||
/>
|
||||
<ListItemSecondaryAction>
|
||||
@@ -125,23 +135,24 @@ export default function SessionsSettingsCard() {
|
||||
) : null}
|
||||
|
||||
{sessions.length > 1 ? (
|
||||
<Button variant="text" color="warning" size="small" sx={{ mt: 1 }} onClick={() => setConfirmRevokeOthers(true)}>
|
||||
<Button variant="text" color="warning" size="small" sx={{ mt: 1 }} onClick={() => { setConfirmError(null); setConfirmRevokeOthers(true); }}>
|
||||
{t("sessionsRevokeOthers")}
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
<Dialog open={confirmRevokeOthers} onClose={() => setConfirmRevokeOthers(false)} maxWidth="sm" fullWidth>
|
||||
<Dialog open={confirmRevokeOthers} onClose={revokingOthers ? undefined : closeConfirmation} maxWidth="sm" fullWidth>
|
||||
<DialogTitle>{t("sessionsRevokeOthersConfirmTitle")}</DialogTitle>
|
||||
<DialogContent>
|
||||
{confirmError ? <Alert severity="error" sx={{ mb: 2 }}>{confirmError}</Alert> : null}
|
||||
<Typography>{t("sessionsRevokeOthersConfirmBody")}</Typography>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button type="button" onClick={() => setConfirmRevokeOthers(false)}>{t("cancel")}</Button>
|
||||
<Button variant="contained" color="warning" onClick={revokeOthers}>
|
||||
<Button type="button" onClick={closeConfirmation} disabled={revokingOthers}>{t("cancel")}</Button>
|
||||
<Button variant="contained" color="warning" onClick={revokeOthers} disabled={revokingOthers}>
|
||||
{t("sessionsRevokeOthers")}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Paper>
|
||||
</PageSection>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user