* feat: implement recent impersonations list with localStorage storage - Add RecentImpersonationsList component with quick action buttons - Store up to 5 recent impersonations in localStorage using existing webstorage utility - Integrate component into admin impersonation page above existing form - Add translation keys for recent impersonations UI elements - Update impersonation flow to save successful impersonations locally - Follow existing List component patterns and button styling Co-Authored-By: sean@cal.com <Sean@brydon.io> * refactor: optimize RecentImpersonationsList to use lazy initial state - Replace useEffect with useState lazy initializer for better performance - Removes unnecessary re-render on component mount - Remove unused setRecentImpersonations variable to fix ESLint warning - Follows React best practices for initial state computation Co-Authored-By: sean@cal.com <Sean@brydon.io> * Add styling to match v3 and newer designs * Update packages/lib/recentImpersonations.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
80 lines
2.6 KiB
TypeScript
80 lines
2.6 KiB
TypeScript
"use client";
|
|
|
|
import { signIn } from "next-auth/react";
|
|
import { useState } from "react";
|
|
|
|
import { WEBAPP_URL } from "@calcom/lib/constants";
|
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
|
import { getRecentImpersonations, type RecentImpersonation } from "@calcom/lib/recentImpersonations";
|
|
import { Button } from "@calcom/ui/components/button";
|
|
import { PanelCard } from "@calcom/ui/components/card";
|
|
import {
|
|
TableNew,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow,
|
|
} from "@calcom/ui/components/table";
|
|
|
|
interface RecentImpersonationsListProps {
|
|
onImpersonate?: (username: string) => void;
|
|
}
|
|
|
|
export default function RecentImpersonationsList({ onImpersonate }: RecentImpersonationsListProps) {
|
|
const { t } = useLocale();
|
|
const [recentImpersonations] = useState<RecentImpersonation[]>(() => getRecentImpersonations());
|
|
|
|
const handleQuickImpersonate = (username: string) => {
|
|
if (onImpersonate) {
|
|
onImpersonate(username);
|
|
} else {
|
|
signIn("impersonation-auth", {
|
|
username: username.toLowerCase(),
|
|
callbackUrl: `${WEBAPP_URL}/event-types`,
|
|
});
|
|
}
|
|
};
|
|
|
|
if (recentImpersonations.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<PanelCard title={t("recent_impersonations")}>
|
|
<div className="overflow-x-auto">
|
|
<TableNew className="border-0">
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead className="w-[40%]">User</TableHead>
|
|
<TableHead>Impersonated At</TableHead>
|
|
<TableHead className="text-right">Actions</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{recentImpersonations.map((impersonation) => (
|
|
<TableRow key={impersonation.username}>
|
|
<TableCell className="font-medium">{impersonation.username}</TableCell>
|
|
<TableCell className="text-muted-foreground">
|
|
{new Date(impersonation.timestamp).toLocaleDateString()}{" "}
|
|
{new Date(impersonation.timestamp).toLocaleTimeString()}
|
|
</TableCell>
|
|
<TableCell className="text-right">
|
|
<Button
|
|
type="button"
|
|
color="secondary"
|
|
size="sm"
|
|
onClick={() => handleQuickImpersonate(impersonation.username)}
|
|
data-testid={`quick-impersonate-${impersonation.username}`}>
|
|
{t("quick_impersonate")}
|
|
</Button>
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</TableNew>
|
|
</div>
|
|
</PanelCard>
|
|
);
|
|
}
|