- Frontend React (SKEEN Brand) con Vite, TypeScript, Tailwind - Frontend Homenest (versión alternativa) - Módulos Odoo 17 custom (citas, pacientes, monedero, pagos, ventas, inventario, whatsapp) - WACRM fork (Next.js 16 + Supabase) - Hermes + Bridge + Skills (Qwen3.6 via Nan Builders) - Scripts de migración y operación - Documentación extensiva en docs/
110 lines
3.1 KiB
TypeScript
110 lines
3.1 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { toast } from 'sonner';
|
|
import { Loader2, LogOut } from 'lucide-react';
|
|
|
|
import { createClient } from '@/lib/supabase/client';
|
|
import { Button } from '@/components/ui/button';
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardHeader,
|
|
CardTitle,
|
|
CardDescription,
|
|
} from '@/components/ui/card';
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
} from '@/components/ui/dialog';
|
|
|
|
export function SessionsCard() {
|
|
const supabase = createClient();
|
|
const [open, setOpen] = useState(false);
|
|
const [signingOut, setSigningOut] = useState(false);
|
|
|
|
const onConfirm = async () => {
|
|
setSigningOut(true);
|
|
try {
|
|
// scope: 'global' revokes every refresh token for this user
|
|
// across all devices; the next auth-state change on this tab
|
|
// triggers the usual redirect.
|
|
const { error } = await supabase.auth.signOut({ scope: 'global' });
|
|
if (error) {
|
|
toast.error(`Sign-out failed: ${error.message}`);
|
|
return;
|
|
}
|
|
window.location.href = '/login';
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : 'Unknown error';
|
|
toast.error(msg);
|
|
} finally {
|
|
setSigningOut(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2 text-foreground">
|
|
<LogOut className="size-4 text-primary" />
|
|
Active sessions
|
|
</CardTitle>
|
|
<CardDescription className="text-muted-foreground">
|
|
Sign out of every device where you're logged in — including
|
|
this one. Useful if you lost a laptop or shared your password.
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
onClick={() => setOpen(true)}
|
|
>
|
|
<LogOut className="size-4" />
|
|
Sign out of all devices
|
|
</Button>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Dialog open={open} onOpenChange={setOpen}>
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>Sign out everywhere?</DialogTitle>
|
|
<DialogDescription>
|
|
Every device logged into this account will be signed out and
|
|
will need to log in again. You will be redirected to the login
|
|
page.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<DialogFooter>
|
|
<Button
|
|
type="button"
|
|
variant="ghost"
|
|
onClick={() => setOpen(false)}
|
|
disabled={signingOut}
|
|
>
|
|
Cancel
|
|
</Button>
|
|
<Button type="button" onClick={onConfirm} disabled={signingOut}>
|
|
{signingOut ? (
|
|
<>
|
|
<Loader2 className="size-4 animate-spin" />
|
|
Signing out…
|
|
</>
|
|
) : (
|
|
'Sign out everywhere'
|
|
)}
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</>
|
|
);
|
|
}
|