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
123 changes: 123 additions & 0 deletions apps/web/src/components/app-page-shell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import type { ReactNode } from "react";

import { Badge } from "@open-learn/ui/components/badge";
import { cn } from "@open-learn/ui/lib/utils";

export function AppPage({ children, className }: { children: ReactNode; className?: string }) {
return <div className={cn("flex w-full min-w-0 flex-col gap-6", className)}>{children}</div>;
}

export function AppPageHeader({
eyebrow,
title,
description,
actions,
children,
className,
}: {
eyebrow?: string;
title: string;
description?: string;
actions?: ReactNode;
children?: ReactNode;
className?: string;
}) {
return (
<section
className={cn(
"flex flex-col gap-4 border-b border-border/70 pb-4 lg:flex-row lg:items-end lg:justify-between",
className,
)}
>
<div className="min-w-0 flex-1">
{eyebrow ? (
<p className="text-xs font-medium uppercase tracking-[0.18em] text-muted-foreground">
{eyebrow}
</p>
) : null}
<h1 className="mt-1 text-2xl font-semibold tracking-tight">{title}</h1>
{description ? (
<p className="mt-1 max-w-2xl text-sm text-muted-foreground">{description}</p>
) : null}
{children ? <div className="mt-3">{children}</div> : null}
</div>

{actions ? (
<div className="flex shrink-0 flex-wrap items-center gap-2 lg:justify-end">{actions}</div>
) : null}
</section>
);
}

export function AppPageHeaderMeta({
items,
className,
}: {
items: Array<{ label: string; value: string }>;
className?: string;
}) {
return (
<div className={cn("flex flex-wrap items-center gap-2", className)}>
{items.map((item) => (
<Badge
key={item.label}
variant="outline"
className="rounded-none border-border/70 px-2 py-1 text-[10px] uppercase tracking-[0.18em] text-muted-foreground"
>
{item.label}
<span className="ml-2 text-foreground">{item.value}</span>
</Badge>
))}
</div>
);
}

export function AppSurface({ children, className }: { children: ReactNode; className?: string }) {
return (
<section
className={cn(
"overflow-hidden rounded-none border border-border/60 bg-background ring-1 ring-foreground/10",
className,
)}
>
{children}
</section>
);
}

export function AppSurfaceHeader({
label,
title,
description,
actions,
className,
}: {
label?: string;
title?: string;
description?: string;
actions?: ReactNode;
className?: string;
}) {
return (
<div
className={cn(
"flex flex-col gap-3 border-b border-border/60 bg-muted/15 px-4 py-3 lg:flex-row lg:items-start lg:justify-between",
className,
)}
>
<div className="min-w-0">
{label ? (
<p className="text-xs font-medium uppercase tracking-[0.18em] text-muted-foreground">
{label}
</p>
) : null}
{title ? <p className="mt-0.5 text-sm font-medium">{title}</p> : null}
{description ? <p className="mt-1 text-xs text-muted-foreground">{description}</p> : null}
</div>

{actions ? (
<div className="flex shrink-0 flex-wrap items-center gap-2 lg:justify-end">{actions}</div>
) : null}
</div>
);
}
18 changes: 18 additions & 0 deletions apps/web/src/components/archived-badge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { cn } from "@open-learn/ui/lib/utils";

interface ArchivedBadgeProps {
className?: string;
}

export function ArchivedBadge({ className }: ArchivedBadgeProps) {
return (
<span
className={cn(
"rounded-none border border-border/70 bg-muted px-1.5 py-0.5 text-xs text-muted-foreground",
className,
)}
>
archived
</span>
);
}
94 changes: 94 additions & 0 deletions apps/web/src/components/row-actions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import type { ReactNode } from "react";

import { ArchiveIcon, ArchiveRestoreIcon, MoreVerticalIcon, Trash2Icon } from "lucide-react";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@open-learn/ui/components/alert-dialog";
import { Button } from "@open-learn/ui/components/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@open-learn/ui/components/dropdown-menu";

interface RowActionsProps {
isArchived: boolean;
onToggleArchive: () => void;
isArchivePending: boolean;
onDelete: () => void;
deleteTitle: string;
deleteDescription: ReactNode;
}

export function RowActions({
isArchived,
onToggleArchive,
isArchivePending,
onDelete,
deleteTitle,
deleteDescription,
}: RowActionsProps) {
return (
<AlertDialog>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="h-8 w-8">
<MoreVerticalIcon className="h-4 w-4" />
<span className="sr-only">More options</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={onToggleArchive} disabled={isArchivePending}>
{isArchived ? (
<>
<ArchiveRestoreIcon className="mr-2 h-4 w-4" />
Unarchive
</>
) : (
<>
<ArchiveIcon className="mr-2 h-4 w-4" />
Archive
</>
)}
</DropdownMenuItem>
<DropdownMenuSeparator />
<AlertDialogTrigger asChild>
<DropdownMenuItem
className="text-destructive focus:text-destructive"
onSelect={(e) => e.preventDefault()}
>
<Trash2Icon className="mr-2 h-4 w-4" />
Delete
</DropdownMenuItem>
</AlertDialogTrigger>
</DropdownMenuContent>
</DropdownMenu>

<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{deleteTitle}</AlertDialogTitle>
<AlertDialogDescription>{deleteDescription}</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={onDelete}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
Delete
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
}
3 changes: 2 additions & 1 deletion apps/web/src/features/auth/components/oauth-buttons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,10 @@ export default function OAuthButtons({ invitationId }: { invitationId?: string }
}

return (
<div className="flex gap-2 justify-center items-center my-4 mx-auto">
<div className="flex gap-2 justify-center items-center my-4 ">
<Button
type="button"
size="lg"
variant="outline"
className="w-full"
disabled={loadingProvider !== null}
Expand Down
Loading
Loading