All components

Auth Session Manager

Auth Session Manager is a React and Tailwind CSS component from HextaUI, licensed MIT. Copy it and paste it into your project.

HextaUI MIT other

What it is

Auth Session Manager is a React and Tailwind CSS component from HextaUI, licensed MIT. Copy it and paste it into your project.

How to use it

Open it in the catalogue, press copy, and paste it into your project. The prompt you copy carries the code and the rules that tell your agent to reproduce it without changing anything.

Open in the catalogue

Five free copies a month. No card.

No packages beyond the project baseline

Licence

This component comes from HextaUI and is redistributed under the MIT licence, which permits commercial use. The copyright notice travels with the code.

Auth Session Manager

registry/new-york/blocks/auth/auth-session-manager.tsx
"use client";

import {
  AlertTriangle,
  Globe,
  Loader2,
  LogOut,
  Monitor,
  Smartphone,
  Tablet,
} from "lucide-react";
import { useCallback, useState } from "react";
import { cn } from "@/lib/utils";
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@/registry/new-york/ui/alert-dialog";
import { Badge } from "@/registry/new-york/ui/badge";
import { Button } from "@/registry/new-york/ui/button";
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/registry/new-york/ui/card";
import { Separator } from "@/registry/new-york/ui/separator";

export type DeviceType = "desktop" | "mobile" | "tablet" | "unknown";

export interface Session {
  id: string;
  deviceName: string;
  deviceType: DeviceType;
  browser?: string;
  os?: string;
  ipAddress?: string;
  location?: string;
  lastActive: Date;
  isCurrent: boolean;
}

export interface AuthSessionManagerProps {
  sessions?: Session[];
  onRevoke?: (sessionId: string) => void;
  onRevokeAll?: () => void;
  className?: string;
  isLoading?: boolean;
  errors?: {
    general?: string;
  };
}

const DEVICE_ICONS: Record<
  DeviceType,
  React.ComponentType<{ className?: string }>
> = {
  desktop: Monitor,
  mobile: Smartphone,
  tablet: Tablet,
  unknown: Globe,
};

function formatDate(date: Date): string {
  const year = date.getFullYear();
  const month = date.toLocaleString("en-US", { month: "short" });
  const day = date.getDate();
  return `${month} ${day}, ${year}`;
}

function formatLastActive(date: Date): string {
  const now = new Date();
  const diff = now.getTime() - date.getTime();
  const minutes = Math.floor(diff / 60_000);
  const hours = Math.floor(diff / 3_600_000);
  const days = Math.floor(diff / 86_400_000);

  if (minutes < 1) return "Just now";
  if (minutes < 60) return `${minutes}m ago`;
  if (hours < 24) return `${hours}h ago`;
  if (days === 1) return "Yesterday";
  if (days < 7) return `${days}d ago`;
  return formatDate(date);
}

interface ErrorAlertProps {
  message: string;
}

function ErrorAlert({ message }: ErrorAlertProps) {
  return (
    <div
      aria-live="polite"
      className="flex items-start gap-2 rounded-lg border border-destructive/50 bg-destructive/10 p-3 text-destructive text-sm"
      role="alert"
    >
      <AlertTriangle aria-hidden="true" className="size-4 shrink-0" />
      <span>{message}</span>
    </div>
  );
}

interface EmptyStateProps {
  message?: string;
}

function EmptyState({ message = "No active sessions found" }: EmptyStateProps) {
  return (
    <div className="flex flex-col items-center justify-center py-8 text-center">
      <p className="text-muted-foreground text-sm">{message}</p>
    </div>
  );
}

interface RevokeSessionDialogProps {
  deviceName: string;
  isLoading: boolean;
  onRevoke: (sessionId: string) => void;
  sessionId: string;
}

function RevokeSessionDialog({
  deviceName,
  isLoading,
  onRevoke,
  sessionId,
}: RevokeSessionDialogProps) {
  return (
    <AlertDialog>
      <AlertDialogTrigger asChild>
        <Button
          aria-label={`Revoke session on ${deviceName}`}
          className="min-h-[32px] min-w-[32px] touch-manipulation"
          disabled={isLoading}
          size="icon"
          type="button"
          variant="ghost"
        >
          <LogOut aria-hidden="true" className="size-4" />
        </Button>
      </AlertDialogTrigger>
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogTitle>Revoke session?</AlertDialogTitle>
          <AlertDialogDescription>
            This will sign out the session on {deviceName}. The user will need
            to sign in again to access their account from this device.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Cancel</AlertDialogCancel>
          <AlertDialogAction
            className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
            onClick={() => onRevoke(sessionId)}
          >
            Revoke session
          </AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  );
}

interface SessionItemProps {
  isLoading: boolean;
  onRevoke?: (sessionId: string) => void;
  session: Session;
}

function SessionItem({ session, onRevoke, isLoading }: SessionItemProps) {
  const DeviceIcon = DEVICE_ICONS[session.deviceType] || DEVICE_ICONS.unknown;

  return (
    <div className="flex flex-col gap-3 rounded-lg border bg-card p-4 sm:flex-row sm:items-start sm:gap-4">
      <div className="flex size-10 shrink-0 items-center justify-center rounded-lg bg-muted">
        <DeviceIcon
          aria-hidden="true"
          className="size-5 text-muted-foreground"
        />
      </div>
      <div className="flex min-w-0 flex-1 flex-col gap-2">
        <div className="flex flex-wrap items-center gap-2">
          <h4 className="font-medium text-sm">{session.deviceName}</h4>
          {session.isCurrent && (
            <Badge className="text-xs" variant="secondary">
              Current session
            </Badge>
          )}
        </div>
        <div className="flex flex-wrap items-center gap-2 text-muted-foreground text-xs">
          {session.browser && (
            <>
              <span>{session.browser}</span>
              {session.os && <span aria-hidden="true">•</span>}
            </>
          )}
          {session.os && <span>{session.os}</span>}
          {(session.browser || session.os) && session.location && (
            <span aria-hidden="true">•</span>
          )}
          {session.location && <span>{session.location}</span>}
        </div>
        <div className="flex flex-wrap items-center gap-2 text-muted-foreground text-xs">
          <span>Last active: {formatLastActive(session.lastActive)}</span>
          {session.ipAddress && (
            <>
              <span aria-hidden="true">•</span>
              <span>IP: {session.ipAddress}</span>
            </>
          )}
        </div>
      </div>
      {!session.isCurrent && onRevoke && (
        <RevokeSessionDialog
          deviceName={session.deviceName}
          isLoading={isLoading}
          onRevoke={onRevoke}
          sessionId={session.id}
        />
      )}
    </div>
  );
}

interface RevokeAllDialogProps {
  isLoading: boolean;
  onRevokeAll: () => void;
  revokingAll: boolean;
}

function RevokeAllDialog({
  isLoading,
  onRevokeAll,
  revokingAll,
}: RevokeAllDialogProps) {
  return (
    <AlertDialog>
      <AlertDialogTrigger asChild>
        <Button
          aria-label="Revoke all other sessions"
          className="min-h-[44px] w-full touch-manipulation sm:w-auto"
          disabled={isLoading || revokingAll}
          type="button"
          variant="outline"
        >
          {revokingAll ? (
            <>
              <Loader2 aria-hidden="true" className="size-4 animate-spin" />
              Revoking…
            </>
          ) : (
            <>
              <LogOut aria-hidden="true" className="size-4" />
              Revoke all
            </>
          )}
        </Button>
      </AlertDialogTrigger>
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogTitle>Revoke all other sessions?</AlertDialogTitle>
          <AlertDialogDescription>
            This will sign out all other devices except this one. You will
            remain signed in on this device, but all other active sessions will
            be terminated.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Cancel</AlertDialogCancel>
          <AlertDialogAction
            className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
            onClick={onRevokeAll}
          >
            Revoke all sessions
          </AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  );
}

interface CurrentSessionSectionProps {
  isLoading: boolean;
  onRevoke?: (sessionId: string) => void;
  session: Session;
}

function CurrentSessionSection({
  session,
  onRevoke,
  isLoading,
}: CurrentSessionSectionProps) {
  return (
    <div className="flex flex-col gap-2">
      <h3 className="font-medium text-sm">Current session</h3>
      <SessionItem
        isLoading={isLoading}
        onRevoke={onRevoke}
        session={session}
      />
    </div>
  );
}

interface OtherSessionsSectionProps {
  isLoading: boolean;
  onRevoke?: (sessionId: string) => void;
  sessions: Session[];
}

function OtherSessionsSection({
  sessions,
  onRevoke,
  isLoading,
}: OtherSessionsSectionProps) {
  if (sessions.length === 0) return null;

  return (
    <div className="flex flex-col gap-2">
      <h3 className="font-medium text-sm">
        Other active sessions ({sessions.length})
      </h3>
      <div className="flex flex-col gap-3">
        {sessions.map((session) => (
          <SessionItem
            isLoading={isLoading}
            key={session.id}
            onRevoke={onRevoke}
            session={session}
          />
        ))}
      </div>
    </div>
  );
}

export default function AuthSessionManager({
  sessions = [],
  onRevoke,
  onRevokeAll,
  className,
  isLoading = false,
  errors,
}: AuthSessionManagerProps) {
  const [revokingAll, setRevokingAll] = useState(false);

  const handleRevokeAll = useCallback(async () => {
    setRevokingAll(true);
    try {
      await onRevokeAll?.();
    } finally {
      setRevokingAll(false);
    }
  }, [onRevokeAll]);

  const otherSessions = sessions.filter((s) => !s.isCurrent);
  const currentSession = sessions.find((s) => s.isCurrent);

  return (
    <Card className={cn("w-full max-w-sm shadow-xs", className)}>
      <CardHeader>
        <div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
          <div className="flex flex-col gap-2">
            <CardTitle>Active sessions</CardTitle>
            <CardDescription>
              Manage devices that are signed in to your account
            </CardDescription>
          </div>
          {otherSessions.length > 0 && onRevokeAll && (
            <RevokeAllDialog
              isLoading={isLoading}
              onRevokeAll={handleRevokeAll}
              revokingAll={revokingAll}
            />
          )}
        </div>
      </CardHeader>
      <CardContent>
        <div className="flex flex-col gap-6">
          {errors?.general && <ErrorAlert message={errors.general} />}

          {sessions.length === 0 ? (
            <EmptyState />
          ) : (
            <div className="flex flex-col gap-4">
              {currentSession && (
                <>
                  <CurrentSessionSection
                    isLoading={isLoading}
                    onRevoke={onRevoke}
                    session={currentSession}
                  />
                  {otherSessions.length > 0 && <Separator />}
                </>
              )}

              <OtherSessionsSection
                isLoading={isLoading}
                onRevoke={onRevoke}
                sessions={otherSessions}
              />
            </div>
          )}
        </div>
      </CardContent>
    </Card>
  );
}

Similar components