redesign the homepage
Build & Deploy / Build & Push Docker Image (push) Failing after 47s
Build & Deploy / Deploy to VPS (push) Has been skipped
Test / API Unit Tests (push) Failing after 5m4s
Test / Marketplace Unit Tests (push) Failing after 4m55s
Test / Admin Unit Tests (push) Successful in 9m37s
Test / Dashboard Unit Tests (push) Successful in 9m37s
Test / API Integration Tests (push) Successful in 9m54s

This commit is contained in:
root
2026-06-26 16:27:21 -04:00
parent 256ff0814e
commit d7fb7b7a7b
1030 changed files with 107374 additions and 2657 deletions
@@ -0,0 +1,71 @@
.dialog {
max-inline-size: none;
max-block-size: none;
margin: auto;
padding: 0;
border: 0;
background: transparent;
color: var(--text-primary);
overflow: visible;
}
.dialog::backdrop {
background: var(--overlay-scrim);
}
.panel {
display: grid;
max-block-size: calc(100dvh - var(--space-8));
overflow: hidden;
border: 1px solid var(--border-standard);
border-radius: var(--radius-md);
background: var(--surface-elevated);
box-shadow: var(--shadow-overlay);
}
.modal .panel {
inline-size: min(44rem, calc(100vw - var(--space-8)));
}
.drawer {
margin-block: 0;
margin-inline-start: auto;
margin-inline-end: 0;
}
.drawer .panel {
inline-size: min(24rem, 88vw);
min-block-size: 100dvh;
border-radius: 0;
}
.header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: var(--space-4);
padding: var(--space-6);
border-block-end: 1px solid var(--border-standard);
}
.header h2 {
margin: 0;
font-size: var(--type-heading-3-size);
}
.header p {
margin-block: var(--space-2) 0;
color: var(--text-secondary);
}
.content {
min-block-size: 0;
padding: var(--space-6);
overflow-y: auto;
}
@media (max-width: 479px) {
.modal .panel {
inline-size: calc(100vw - var(--space-4));
max-block-size: calc(100dvh - var(--space-4));
}
.header,
.content {
padding: var(--space-4);
}
}
@media (forced-colors: active) {
.panel {
border: 2px solid CanvasText;
}
}
@@ -0,0 +1,99 @@
'use client';
import { Button } from '@/components/actions/Button';
import { useScrollLock } from '@/components/foundation/client-hooks';
import { classNames } from '@/lib/components/classNames';
import { useEffect, useId, useRef, type ReactNode, type RefObject } from 'react';
import styles from './Dialog.module.css';
interface DialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
title: string;
description?: string;
children: ReactNode;
closeLabel: string;
initialFocusRef?: RefObject<HTMLElement | null>;
returnFocusRef?: RefObject<HTMLElement | null>;
variant?: 'modal' | 'drawer';
dismissible?: boolean;
className?: string;
}
export function Dialog({
open,
onOpenChange,
title,
description,
children,
closeLabel,
initialFocusRef,
returnFocusRef,
variant = 'modal',
dismissible = true,
className,
}: DialogProps) {
const dialogRef = useRef<HTMLDialogElement>(null);
const closeRef = useRef<HTMLButtonElement>(null);
const titleId = useId();
const descriptionId = useId();
useScrollLock(open);
useEffect(() => {
const dialog = dialogRef.current;
if (!dialog) return;
if (open && !dialog.open) {
dialog.showModal();
window.requestAnimationFrame(() => (initialFocusRef?.current ?? closeRef.current)?.focus());
} else if (!open && dialog.open) {
dialog.close();
}
}, [initialFocusRef, open]);
const close = () => {
dialogRef.current?.close();
onOpenChange(false);
window.requestAnimationFrame(() => returnFocusRef?.current?.focus());
};
return (
<dialog
ref={dialogRef}
className={classNames(styles.dialog, styles[variant], className)}
aria-labelledby={titleId}
aria-describedby={description ? descriptionId : undefined}
onCancel={(event) => {
if (!dismissible) {
event.preventDefault();
return;
}
event.preventDefault();
close();
}}
onClose={() => onOpenChange(false)}
onClick={(event) => {
if (dismissible && event.target === dialogRef.current) close();
}}
>
<div className={styles.panel}>
<header className={styles.header}>
<div>
<h2 id={titleId}>{title}</h2>
{description ? <p id={descriptionId}>{description}</p> : null}
</div>
{dismissible ? (
<Button
ref={closeRef}
intent="ghost"
size="small"
icon="close"
aria-label={closeLabel}
onClick={close}
/>
) : null}
</header>
<div className={styles.content}>{children}</div>
</div>
</dialog>
);
}
@@ -0,0 +1,39 @@
.root {
position: relative;
display: inline-block;
}
.menu {
position: absolute;
z-index: var(--layer-overlay);
inset-block-start: calc(100% + var(--space-2));
inset-inline-end: 0;
display: grid;
min-inline-size: 13rem;
padding: var(--space-2);
border: 1px solid var(--border-standard);
border-radius: var(--radius-sm);
background: var(--surface-elevated);
box-shadow: var(--shadow-overlay);
}
.item {
min-block-size: 2.75rem;
padding-block: var(--space-2);
padding-inline: var(--space-3);
border: 0;
border-radius: calc(var(--radius-sm) - 2px);
background: transparent;
color: var(--text-primary);
cursor: pointer;
text-align: start;
}
.item:hover:not(:disabled),
.item:focus-visible {
background: var(--surface-muted);
}
.item:disabled {
cursor: not-allowed;
opacity: var(--opacity-disabled);
}
.destructive {
color: var(--status-error-text);
}
@@ -0,0 +1,90 @@
'use client';
import { Button } from '@/components/actions/Button';
import { useEscapeKey, useOutsideClick } from '@/components/foundation/client-hooks';
import { classNames } from '@/lib/components/classNames';
import { useRef, useState, type KeyboardEvent } from 'react';
import styles from './DropdownMenu.module.css';
export interface MenuItem {
id: string;
label: string;
onSelect: () => void;
disabled?: boolean;
destructive?: boolean;
}
interface DropdownMenuProps {
label: string;
items: MenuItem[];
className?: string;
}
export function DropdownMenu({ label, items, className }: DropdownMenuProps) {
const [open, setOpen] = useState(false);
const rootRef = useRef<HTMLDivElement>(null);
const itemRefs = useRef<Array<HTMLButtonElement | null>>([]);
useOutsideClick(rootRef, () => setOpen(false), open);
useEscapeKey(() => setOpen(false), open);
const focusItem = (index: number) => {
const enabled = items
.map((item, itemIndex) => ({ item, itemIndex }))
.filter(({ item }) => !item.disabled);
const target = enabled[(index + enabled.length) % enabled.length];
if (target) itemRefs.current[target.itemIndex]?.focus();
};
const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
const currentIndex = itemRefs.current.findIndex((node) => node === document.activeElement);
if (event.key === 'ArrowDown') {
event.preventDefault();
focusItem(currentIndex + 1);
} else if (event.key === 'ArrowUp') {
event.preventDefault();
focusItem(currentIndex - 1);
} else if (event.key === 'Home') {
event.preventDefault();
focusItem(0);
} else if (event.key === 'End') {
event.preventDefault();
focusItem(items.length - 1);
}
};
return (
<div ref={rootRef} className={classNames(styles.root, className)} onKeyDown={handleKeyDown}>
<Button
intent="tertiary"
trailingIcon="chevron-down"
aria-haspopup="menu"
aria-expanded={open}
onClick={() => setOpen((value) => !value)}
>
{label}
</Button>
{open ? (
<div className={styles.menu} role="menu">
{items.map((item, index) => (
<button
key={item.id}
ref={(node) => {
itemRefs.current[index] = node;
}}
type="button"
role="menuitem"
disabled={item.disabled}
className={classNames(styles.item, item.destructive && styles.destructive)}
onClick={() => {
item.onSelect();
setOpen(false);
}}
>
{item.label}
</button>
))}
</div>
) : null}
</div>
);
}
@@ -0,0 +1,41 @@
.root {
position: relative;
display: inline-flex;
}
.tooltip {
position: absolute;
z-index: var(--layer-overlay);
inset-block-end: calc(100% + var(--space-2));
inset-inline-start: 50%;
inline-size: max-content;
max-inline-size: 16rem;
padding-block: var(--space-2);
padding-inline: var(--space-3);
border-radius: var(--radius-sm);
background: var(--surface-strong);
color: var(--text-primary);
font-size: var(--type-caption-size);
opacity: 0;
pointer-events: none;
transform: translateX(-50%) translateY(var(--space-1));
transition:
opacity var(--duration-fast) var(--easing-productive),
transform var(--duration-fast) var(--easing-productive);
}
.root:hover .tooltip,
.root:focus-within .tooltip {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
html[dir='rtl'] .tooltip {
transform: translateX(50%) translateY(var(--space-1));
}
html[dir='rtl'] .root:hover .tooltip,
html[dir='rtl'] .root:focus-within .tooltip {
transform: translateX(50%) translateY(0);
}
@media (prefers-reduced-motion: reduce) {
.tooltip {
transition: none;
}
}
@@ -0,0 +1,22 @@
import { classNames } from '@/lib/components/classNames';
import type { ReactNode } from 'react';
import styles from './Tooltip.module.css';
export function Tooltip({
content,
children,
className,
}: {
content: string;
children: ReactNode;
className?: string;
}) {
return (
<span className={classNames(styles.root, className)}>
{children}
<span role="tooltip" className={styles.tooltip}>
{content}
</span>
</span>
);
}