diff --git a/.cursor/debug-ce85d7.log b/.cursor/debug-ce85d7.log new file mode 100644 index 0000000..95bd0a2 --- /dev/null +++ b/.cursor/debug-ce85d7.log @@ -0,0 +1,8 @@ +{"sessionId":"ce85d7","runId":"pre-fix","hypothesisId":"C","location":"schedule.tsx:mount","message":"schedule page data loaded","data":{"modules":0,"jobs":0,"loading":true,"modulesError":false,"jobsError":false},"timestamp":1783613834659} +{"sessionId":"ce85d7","runId":"pre-fix","hypothesisId":"C","location":"schedule.tsx:mount","message":"schedule page data loaded","data":{"modules":4,"jobs":0,"loading":false,"modulesError":false,"jobsError":false},"timestamp":1783613834740} +{"sessionId":"ce85d7","runId":"pre-fix","hypothesisId":"C","location":"schedule.tsx:mount","message":"schedule page data loaded","data":{"modules":0,"jobs":0,"loading":true,"modulesError":false,"jobsError":false},"timestamp":1783613854903} +{"sessionId":"ce85d7","runId":"pre-fix","hypothesisId":"C","location":"schedule.tsx:mount","message":"schedule page data loaded","data":{"modules":4,"jobs":0,"loading":false,"modulesError":false,"jobsError":false},"timestamp":1783613854967} +{"sessionId":"ce85d7","runId":"pre-fix","hypothesisId":"C","location":"schedule.tsx:mount","message":"schedule page data loaded","data":{"modules":0,"jobs":0,"loading":true,"modulesError":false,"jobsError":false},"timestamp":1783613897698} +{"sessionId":"ce85d7","runId":"pre-fix","hypothesisId":"C","location":"schedule.tsx:mount","message":"schedule page data loaded","data":{"modules":4,"jobs":0,"loading":false,"modulesError":false,"jobsError":false},"timestamp":1783613897769} +{"sessionId":"ce85d7","runId":"pre-fix","hypothesisId":"C","location":"schedule.tsx:mount","message":"schedule page data loaded","data":{"modules":4,"jobs":0,"loading":false,"modulesError":false,"jobsError":false},"timestamp":1783614164222} +{"sessionId":"ce85d7","runId":"pre-fix","hypothesisId":"C","location":"schedule.tsx:mount","message":"schedule page data loaded","data":{"modules":4,"jobs":0,"loading":false,"modulesError":false,"jobsError":false},"timestamp":1783614181455} diff --git a/apps/web/src/components/analytics/analytics-kpi-row.tsx b/apps/web/src/components/analytics/analytics-kpi-row.tsx index 0a0d936..4a44722 100644 --- a/apps/web/src/components/analytics/analytics-kpi-row.tsx +++ b/apps/web/src/components/analytics/analytics-kpi-row.tsx @@ -1,5 +1,5 @@ -import { Minus, TrendingDown, TrendingUp } from 'lucide-react' - +import { KpiStatGrid, type KpiStatItem } from '@/components/kpi-stat-grid' +import { Badge } from '@/components/reui/badge' import { cn } from '@evobgp/ui/lib/utils' export type AnalyticsKpiItem = { @@ -12,39 +12,42 @@ export type AnalyticsKpiItem = { } } -const TONE_CLASS = { - success: 'text-success', - warning: 'text-warning', - destructive: 'text-destructive', - muted: 'text-muted-foreground', -} as const - -function DeltaIcon({ direction }: { direction: AnalyticsKpiItem['delta'] extends infer D ? D extends { direction: infer Dir } ? Dir : never : never }) { - if (direction === 'up') return - if (direction === 'down') return - return +function deltaBadgeVariant( + tone?: 'success' | 'warning' | 'destructive' | 'muted', +): 'success-light' | 'warning-light' | 'destructive-light' | 'outline' { + if (tone === 'success') return 'success-light' + if (tone === 'warning') return 'warning-light' + if (tone === 'destructive') return 'destructive-light' + return 'outline' } -export function AnalyticsKpiRow({ items, className }: { items: AnalyticsKpiItem[]; className?: string }) { +function toKpiItem(item: AnalyticsKpiItem): KpiStatItem { + return { + id: item.label, + value: item.value, + label: item.label, + footer: item.delta ? ( + + {item.delta.label} + + ) : undefined, + } +} + +/** Compact KPI row inside analytics panels (stats-12 embedded tiles). */ +export function AnalyticsKpiRow({ + items, + className, +}: { + items: AnalyticsKpiItem[] + className?: string +}) { return ( -
- {items.map((item) => ( -
-

{item.label}

-

{item.value}

- {item.delta ? ( -

- - {item.delta.label} -

- ) : null} -
- ))} -
+ ) } diff --git a/apps/web/src/components/badge-tabs.tsx b/apps/web/src/components/badge-tabs.tsx index 208ef0e..4a6f673 100644 --- a/apps/web/src/components/badge-tabs.tsx +++ b/apps/web/src/components/badge-tabs.tsx @@ -40,14 +40,12 @@ export function BadgeTabs({ value={value} defaultValue={defaultValue} onValueChange={onValueChange} + orientation="horizontal" className={cn('w-full', className)} > {items.map((item) => ( diff --git a/apps/web/src/components/blocks/settings-7/components/account-settings.tsx b/apps/web/src/components/blocks/settings-7/components/account-settings.tsx new file mode 100644 index 0000000..83c2039 --- /dev/null +++ b/apps/web/src/components/blocks/settings-7/components/account-settings.tsx @@ -0,0 +1,116 @@ +"use client" + +import { useState, type ComponentType } from "react" + +import { useIsMobile } from "@/hooks/use-mobile" +import { cn } from "@evobgp/ui/lib/utils" +import { + Tabs, + TabsContent, + TabsList, + TabsTrigger, +} from "@evobgp/ui/components/tabs" + +import { BillingTab } from "./billing-tab" +import { SETTINGS_TAB_ITEMS } from "./data" +import { NotificationsTab } from "./notifications-tab" +import { ProfileTab } from "./profile-tab" +import { SecurityTab } from "./security-tab" + +const TAB_COMPONENTS: Record = { + profile: ProfileTab, + security: SecurityTab, + notifications: NotificationsTab, + billing: BillingTab, +} + +// ── Settings Navigation ── + +function SettingsNavigation({ + isMobile, + activeValue, +}: { + isMobile: boolean + activeValue: string +}) { + return ( +
+ {isMobile ? ( +
+ + {SETTINGS_TAB_ITEMS.map((tab) => ( + + {tab.icon} + {tab.label} + + ))} + +
+ ) : ( + + {SETTINGS_TAB_ITEMS.map((tab) => ( + + {tab.icon} + {tab.label} + + ))} + + )} +
+ ) +} + +export function AccountSettings() { + const isMobile = useIsMobile() + const [activeTab, setActiveTab] = useState("profile") + + return ( +
+ {/* Header */} +
+

+ Account Settings +

+

+ Update your profile, access, notifications, and billing preferences. +

+
+ + {/* Tabs */} + + + +
+ {SETTINGS_TAB_ITEMS.map((tab) => { + const TabComponent = TAB_COMPONENTS[tab.value] + + return ( + + + + ) + })} +
+
+
+ ) +} \ No newline at end of file diff --git a/apps/web/src/components/blocks/settings-7/components/billing-tab.tsx b/apps/web/src/components/blocks/settings-7/components/billing-tab.tsx new file mode 100644 index 0000000..604dfa8 --- /dev/null +++ b/apps/web/src/components/blocks/settings-7/components/billing-tab.tsx @@ -0,0 +1,180 @@ +import { useState } from "react" +import { Badge } from "@/components/reui/badge" + +import { Button } from "@evobgp/ui/components/button" +import { + Field, + FieldDescription, + FieldGroup, + FieldLabel, + FieldLegend, + FieldSet, +} from "@evobgp/ui/components/field" +import { Input } from "@evobgp/ui/components/input" +import { + InputGroup, + InputGroupAddon, + InputGroupInput, + InputGroupText, +} from "@evobgp/ui/components/input-group" +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectTrigger, + SelectValue, +} from "@evobgp/ui/components/select" + +import { BILLING_PLANS, COUNTRIES } from "./data" +import { SettingRow } from "./setting-row" +import { SettingsCard } from "./settings-card" +import { SettingsFieldGroup } from "./settings-field-group" +import { createSelectValueHandler, getOptionLabel } from "./utils" + +export function BillingTab() { + const [country, setCountry] = useState("us") + const handleCountryChange = createSelectValueHandler(setCountry) + + return ( +
+ {/* Card */} + + + {BILLING_PLANS.map((plan, index) => ( + + Current + + ) : null + } + description={ + <> + + {plan.price} + {" "} + / {plan.period} · {plan.features.join(", ")} + + } + last={index === BILLING_PLANS.length - 1} + > + + + ))} + + + + Update billing} + > + + + + + + +
+ Invoice profile + + Company identity used for invoices and tax documents. + + + + + + Company name + + + + + + + Country + + + + +
+
+ + + + + VAT + + + + + + +
+ + Visa ending in 4242 + + +
+
+
+
+
+ ) +} \ No newline at end of file diff --git a/apps/web/src/components/blocks/settings-7/components/data.tsx b/apps/web/src/components/blocks/settings-7/components/data.tsx new file mode 100644 index 0000000..f2ef237 --- /dev/null +++ b/apps/web/src/components/blocks/settings-7/components/data.tsx @@ -0,0 +1,187 @@ +"use client" + +import { type ReactNode } from "react" +import { UserIcon, ShieldIcon, BellIcon, CreditCardIcon, MonitorIcon, SmartphoneIcon } from "lucide-react" + +// ── Types ── + +export type SelectOption = { + value: string + label: string +} + +export type BillingPlan = { + id: string + name: string + price: string + period: string + current: boolean + features: string[] +} + +export type Session = { + id: string + device: string + browser: string + location: string + lastActive: string + current: boolean + icon: ReactNode +} + +export type SettingsTabItem = { + value: string + label: string + icon: ReactNode +} + +// ── Data ── + +export const SETTINGS_TAB_ITEMS: SettingsTabItem[] = [ + { + value: "profile", + label: "My Profile", + icon: ( +