{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "settings-activity-log",
  "title": "Settings Activity Log",
  "description": "View account activity and audit trail with search and filters.",
  "registryDependencies": [
    "badge",
    "button",
    "card",
    "field",
    "input-group",
    "select",
    "separator"
  ],
  "files": [
    {
      "path": "registry/new-york/blocks/settings/settings-activity-log.tsx",
      "content": "\"use client\";\n\nimport {\n  Calendar,\n  Download,\n  Filter,\n  Loader2,\n  MapPin,\n  Search,\n} from \"lucide-react\";\nimport { useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { Badge } from \"@/registry/new-york/ui/badge\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from \"@/registry/new-york/ui/card\";\nimport { FieldLabel } from \"@/registry/new-york/ui/field\";\nimport {\n  InputGroup,\n  InputGroupInput,\n} from \"@/registry/new-york/ui/input-group\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/registry/new-york/ui/select\";\nimport { Separator } from \"@/registry/new-york/ui/separator\";\n\nexport interface ActivityLogEntry {\n  id: string;\n  action: string;\n  type:\n    | \"login\"\n    | \"logout\"\n    | \"password_change\"\n    | \"profile_update\"\n    | \"settings_change\"\n    | \"export\"\n    | \"delete\"\n    | \"create\"\n    | \"update\";\n  description: string;\n  ipAddress: string;\n  location: string;\n  device: string;\n  timestamp: Date;\n  status: \"success\" | \"failed\";\n}\n\nexport interface SettingsActivityLogProps {\n  entries?: ActivityLogEntry[];\n  onExport?: (filters: {\n    dateRange?: { start: Date; end: Date };\n    type?: string;\n    search?: string;\n  }) => Promise<void>;\n  className?: string;\n}\n\nconst actionTypes = [\n  { value: \"all\", label: \"All Actions\" },\n  { value: \"login\", label: \"Logins\" },\n  { value: \"logout\", label: \"Logouts\" },\n  { value: \"password_change\", label: \"Password Changes\" },\n  { value: \"profile_update\", label: \"Profile Updates\" },\n  { value: \"settings_change\", label: \"Settings Changes\" },\n  { value: \"export\", label: \"Exports\" },\n  { value: \"delete\", label: \"Deletions\" },\n];\n\nfunction formatDate(date: Date): string {\n  return new Intl.DateTimeFormat(\"en-US\", {\n    month: \"short\",\n    day: \"numeric\",\n    year: \"numeric\",\n    hour: \"numeric\",\n    minute: \"2-digit\",\n  }).format(date);\n}\n\nfunction formatRelativeTime(date: Date): string {\n  const now = Date.now();\n  const diff = now - date.getTime();\n  const minutes = Math.floor(diff / 60_000);\n  const hours = Math.floor(minutes / 60);\n  const days = Math.floor(hours / 24);\n\n  if (minutes < 1) return \"Just now\";\n  if (minutes < 60) return `${minutes}m ago`;\n  if (hours < 24) return `${hours}h ago`;\n  if (days < 7) return `${days}d ago`;\n  return formatDate(date);\n}\n\nexport default function SettingsActivityLog({\n  entries = [],\n  onExport,\n  className,\n}: SettingsActivityLogProps) {\n  const [searchQuery, setSearchQuery] = useState(\"\");\n  const [selectedType, setSelectedType] = useState(\"all\");\n  const [dateRange, setDateRange] = useState({\n    start: \"\",\n    end: \"\",\n  });\n  const [isExporting, setIsExporting] = useState(false);\n\n  const filteredEntries = entries.filter((entry) => {\n    if (selectedType !== \"all\" && entry.type !== selectedType) return false;\n    if (\n      searchQuery &&\n      !entry.description.toLowerCase().includes(searchQuery.toLowerCase()) &&\n      !entry.ipAddress.includes(searchQuery) &&\n      !entry.location.toLowerCase().includes(searchQuery.toLowerCase())\n    )\n      return false;\n    return true;\n  });\n\n  const handleExport = async () => {\n    setIsExporting(true);\n    try {\n      const exportRange =\n        dateRange.start && dateRange.end\n          ? {\n              start: new Date(dateRange.start),\n              end: new Date(dateRange.end),\n            }\n          : undefined;\n\n      await onExport?.({\n        dateRange: exportRange,\n        type: selectedType !== \"all\" ? selectedType : undefined,\n        search: searchQuery || undefined,\n      });\n    } finally {\n      setIsExporting(false);\n    }\n  };\n\n  return (\n    <Card className={cn(\"w-full shadow-xs\", className)}>\n      <CardHeader>\n        <div className=\"flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between\">\n          <div className=\"flex min-w-0 flex-1 flex-col gap-2\">\n            <CardTitle className=\"wrap-break-word\">Activity Log</CardTitle>\n            <CardDescription className=\"wrap-break-word\">\n              View your account activity and audit trail\n            </CardDescription>\n          </div>\n        </div>\n      </CardHeader>\n      <CardContent>\n        <div className=\"flex flex-col gap-6\">\n          {/* Filters */}\n          <div className=\"grid gap-4 md:grid-cols-2\">\n            <div className=\"flex flex-col gap-4 rounded-lg border p-4\">\n              <div className=\"flex items-center gap-2\">\n                <div className=\"flex size-8 items-center justify-center rounded-lg bg-muted\">\n                  <Search className=\"size-4\" />\n                </div>\n                <FieldLabel className=\"mb-0\">Search</FieldLabel>\n              </div>\n              <InputGroup>\n                <InputGroupInput\n                  onChange={(e) => setSearchQuery(e.target.value)}\n                  placeholder=\"Search by description, IP, or location...\"\n                  value={searchQuery}\n                />\n              </InputGroup>\n            </div>\n\n            <div className=\"flex flex-col gap-4 rounded-lg border p-4\">\n              <div className=\"flex items-center gap-2\">\n                <div className=\"flex size-8 items-center justify-center rounded-lg bg-muted\">\n                  <Filter className=\"size-4\" />\n                </div>\n                <FieldLabel className=\"mb-0\" htmlFor=\"action-type\">\n                  Action Type\n                </FieldLabel>\n              </div>\n              <Select onValueChange={setSelectedType} value={selectedType}>\n                <SelectTrigger id=\"action-type\">\n                  <SelectValue />\n                </SelectTrigger>\n                <SelectContent>\n                  {actionTypes.map((type) => (\n                    <SelectItem key={type.value} value={type.value}>\n                      {type.label}\n                    </SelectItem>\n                  ))}\n                </SelectContent>\n              </Select>\n            </div>\n\n            <div className=\"flex flex-col gap-4 rounded-lg border p-4\">\n              <div className=\"flex items-center gap-2\">\n                <div className=\"flex size-8 items-center justify-center rounded-lg bg-muted\">\n                  <Calendar className=\"size-4\" />\n                </div>\n                <FieldLabel className=\"mb-0\" htmlFor=\"date-start\">\n                  Start Date\n                </FieldLabel>\n              </div>\n              <InputGroup>\n                <InputGroupInput\n                  id=\"date-start\"\n                  onChange={(e) =>\n                    setDateRange((prev) => ({ ...prev, start: e.target.value }))\n                  }\n                  type=\"date\"\n                  value={dateRange.start}\n                />\n              </InputGroup>\n            </div>\n\n            <div className=\"flex flex-col gap-4 rounded-lg border p-4\">\n              <div className=\"flex items-center gap-2\">\n                <div className=\"flex size-8 items-center justify-center rounded-lg bg-muted\">\n                  <Calendar className=\"size-4\" />\n                </div>\n                <FieldLabel className=\"mb-0\" htmlFor=\"date-end\">\n                  End Date\n                </FieldLabel>\n              </div>\n              <InputGroup>\n                <InputGroupInput\n                  id=\"date-end\"\n                  onChange={(e) =>\n                    setDateRange((prev) => ({ ...prev, end: e.target.value }))\n                  }\n                  type=\"date\"\n                  value={dateRange.end}\n                />\n              </InputGroup>\n            </div>\n          </div>\n\n          <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n            <p className=\"text-muted-foreground text-sm\">\n              {filteredEntries.length}{\" \"}\n              {filteredEntries.length === 1 ? \"entry\" : \"entries\"}\n            </p>\n            <Button\n              className=\"w-full sm:w-auto\"\n              disabled={isExporting}\n              onClick={handleExport}\n              type=\"button\"\n              variant=\"outline\"\n            >\n              {isExporting ? (\n                <>\n                  <Loader2 className=\"size-4 animate-spin\" />\n                  Exporting…\n                </>\n              ) : (\n                <>\n                  <Download className=\"size-4\" />\n                  Export Log\n                </>\n              )}\n            </Button>\n          </div>\n\n          <Separator />\n\n          {/* Activity Entries */}\n          {filteredEntries.length === 0 ? (\n            <div className=\"flex flex-col items-center justify-center gap-4 py-12 text-center\">\n              <div className=\"flex size-12 items-center justify-center rounded-full bg-muted\">\n                <Search className=\"size-6 text-muted-foreground\" />\n              </div>\n              <div className=\"flex flex-col gap-2\">\n                <p className=\"font-medium text-sm\">No activity found</p>\n                <p className=\"text-muted-foreground text-sm\">\n                  {searchQuery || selectedType !== \"all\"\n                    ? \"Try adjusting your filters\"\n                    : \"Your activity will appear here\"}\n                </p>\n              </div>\n            </div>\n          ) : (\n            <div className=\"flex flex-col gap-3\">\n              {filteredEntries.map((entry) => (\n                <div\n                  className=\"flex items-start gap-3 rounded-lg border p-4\"\n                  key={entry.id}\n                >\n                  <div className=\"flex size-8 shrink-0 items-center justify-center rounded-lg bg-muted\">\n                    <Calendar className=\"size-4\" />\n                  </div>\n                  <div className=\"flex min-w-0 flex-1 flex-col gap-2\">\n                    <div className=\"flex flex-wrap items-center gap-2\">\n                      <span className=\"font-medium text-sm\">\n                        {entry.description}\n                      </span>\n                      <Badge\n                        className=\"text-xs\"\n                        variant={\n                          entry.status === \"success\" ? \"default\" : \"destructive\"\n                        }\n                      >\n                        {entry.status}\n                      </Badge>\n                    </div>\n                    <div className=\"flex flex-wrap items-center gap-2 text-muted-foreground text-xs\">\n                      <span className=\"flex items-center gap-1\">\n                        <MapPin className=\"size-3\" />\n                        {entry.location}\n                      </span>\n                      <span>•</span>\n                      <span>{entry.ipAddress}</span>\n                      <span>•</span>\n                      <span>{entry.device}</span>\n                      <span>•</span>\n                      <span>{formatRelativeTime(entry.timestamp)}</span>\n                    </div>\n                  </div>\n                </div>\n              ))}\n            </div>\n          )}\n        </div>\n      </CardContent>\n    </Card>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "block",
    "settings"
  ],
  "type": "registry:ui"
}