Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"@tanstack/react-query-devtools": "^5.100.9",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
"lucide-react": "^1.14.0",
"radix-ui": "^1.4.3",
"react": "^19.2.5",
Expand Down
34 changes: 34 additions & 0 deletions client/src/components/Avatar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { cn } from "@/lib/utils";
import { colorIndex, initials } from "@/lib/avatar";

const VARIANTS = [
"bg-avatar-1-bg text-avatar-1-fg",
"bg-avatar-2-bg text-avatar-2-fg",
"bg-avatar-3-bg text-avatar-3-fg",
"bg-avatar-4-bg text-avatar-4-fg",
"bg-avatar-5-bg text-avatar-5-fg",
] as const;

export function Avatar({
name,
size = "md",
className,
}: {
name: string;
size?: "md" | "lg";
className?: string;
}) {
return (
<div
className={cn(
"inline-flex shrink-0 items-center justify-center rounded-full font-medium",
VARIANTS[colorIndex(name)],
size === "lg" ? "h-12 w-12 text-name" : "h-9 w-9 text-meta",
className,
)}
aria-hidden
>
{initials(name)}
</div>
);
}
32 changes: 32 additions & 0 deletions client/src/components/EmptyState.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { type LucideIcon } from "lucide-react";
import { cn } from "@/lib/utils";

export function EmptyState({
icon: Icon,
headline,
subtext,
cta,
className,
}: {
icon: LucideIcon;
headline: string;
subtext?: string;
cta?: React.ReactNode;
className?: string;
}) {
return (
<div
className={cn(
"flex flex-col items-center justify-center gap-3 py-12 text-center",
className,
)}
>
<Icon className="h-8 w-8 text-muted-foreground" aria-hidden />
<p className="text-title font-medium">{headline}</p>
{subtext && (
<p className="text-meta text-muted-foreground">{subtext}</p>
)}
{cta && <div className="mt-2">{cta}</div>}
</div>
);
}
20 changes: 20 additions & 0 deletions client/src/components/EventBadge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { cn } from "@/lib/utils";

export function EventBadge({
label,
className,
}: {
label: string;
className?: string;
}) {
return (
<span
className={cn(
"inline-block whitespace-nowrap rounded-full bg-event-bg px-2 py-0.5 text-label text-event-fg",
className,
)}
>
{label}
</span>
);
}
37 changes: 37 additions & 0 deletions client/src/components/RingBadge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { cn } from "@/lib/utils";

type Ring = "inner_circle" | "network" | "community" | "acquaintances";

const LABELS: Record<Ring, string> = {
inner_circle: "Inner circle",
network: "Network",
community: "Community",
acquaintances: "Acquaintances",
};

const VARIANTS: Record<Ring, string> = {
inner_circle: "bg-tier-inner-bg text-tier-inner-fg",
network: "bg-tier-network-bg text-tier-network-fg",
community: "bg-tier-community-bg text-tier-community-fg",
acquaintances: "bg-tieracquaintances-bg text-tier-acquaintances-fg",
};

export function RingBadge({
ring,
className,
}: {
ring: Ring;
className?: string;
}) {
return (
<span
className={cn(
"inline-block whitespace-nowrap rounded-full px-2 py-0.5 text-label",
VARIANTS[ring],
className,
)}
>
{LABELS[ring]}
</span>
);
}
199 changes: 199 additions & 0 deletions client/src/components/ui/alert-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
"use client"

import * as React from "react"
import { AlertDialog as AlertDialogPrimitive } from "radix-ui"

import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"

function AlertDialog({
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />
}

function AlertDialogTrigger({
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
return (
<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
)
}

function AlertDialogPortal({
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
return (
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
)
}

function AlertDialogOverlay({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) {
return (
<AlertDialogPrimitive.Overlay
data-slot="alert-dialog-overlay"
className={cn(
"fixed inset-0 z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
className
)}
{...props}
/>
)
}

function AlertDialogContent({
className,
size = "default",
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Content> & {
size?: "default" | "sm"
}) {
return (
<AlertDialogPortal>
<AlertDialogOverlay />
<AlertDialogPrimitive.Content
data-slot="alert-dialog-content"
data-size={size}
className={cn(
"group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
className
)}
{...props}
/>
</AlertDialogPortal>
)
}

function AlertDialogHeader({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-dialog-header"
className={cn(
"grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]",
className
)}
{...props}
/>
)
}

function AlertDialogFooter({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-dialog-footer"
className={cn(
"-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end",
className
)}
{...props}
/>
)
}

function AlertDialogMedia({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-dialog-media"
className={cn(
"mb-2 inline-flex size-10 items-center justify-center rounded-md bg-muted sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-6",
className
)}
{...props}
/>
)
}

function AlertDialogTitle({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
return (
<AlertDialogPrimitive.Title
data-slot="alert-dialog-title"
className={cn(
"font-heading text-base font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",
className
)}
{...props}
/>
)
}

function AlertDialogDescription({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
return (
<AlertDialogPrimitive.Description
data-slot="alert-dialog-description"
className={cn(
"text-sm text-balance text-muted-foreground md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
className
)}
{...props}
/>
)
}

function AlertDialogAction({
className,
variant = "default",
size = "default",
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Action> &
Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
return (
<Button variant={variant} size={size} asChild>
<AlertDialogPrimitive.Action
data-slot="alert-dialog-action"
className={cn(className)}
{...props}
/>
</Button>
)
}

function AlertDialogCancel({
className,
variant = "outline",
size = "default",
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel> &
Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
return (
<Button variant={variant} size={size} asChild>
<AlertDialogPrimitive.Cancel
data-slot="alert-dialog-cancel"
className={cn(className)}
{...props}
/>
</Button>
)
}

export {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogMedia,
AlertDialogOverlay,
AlertDialogPortal,
AlertDialogTitle,
AlertDialogTrigger,
}
18 changes: 18 additions & 0 deletions client/src/components/ui/textarea.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as React from "react"

import { cn } from "@/lib/utils"

function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
return (
<textarea
data-slot="textarea"
className={cn(
"flex field-sizing-content min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-base transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
className
)}
{...props}
/>
)
}

export { Textarea }
Loading
Loading