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
98 changes: 60 additions & 38 deletions src/components/app/dashboard-events.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,13 @@ const STATE_OPEN: StateMeta = { label: "Open", icon: Ticket, className: STATUS_T
export const PILL_CLASS =
"h-auto gap-1.5 rounded-full px-2.5 py-1 text-[11.5px] font-bold [&>svg]:size-3.5!";

const ROW_ACTION_WIDTH = "w-20";

const TYPE_FILTER_OPTIONS: readonly { label: string; value: "all" | EventType }[] = [
{ value: "all", label: "All types" },
...EVENT_TYPES.map((key) => ({ value: key, label: EVENT_TYPE_META[key].label })),
];

const VIEW_META: Record<EventView, { icon: LucideIcon; label: string }> = {
calendar: { icon: Calendar, label: "Calendar" },
grid: { icon: LayoutGrid, label: "Gallery" },
Expand Down Expand Up @@ -343,9 +350,12 @@ function EventActions({ event, now, onRsvp, onCheckin, size = "sm" }: Readonly<E
[onRsvp, event],
);

return (
const showCheckin = state === "open" && !event.attended;
const showRsvp = !past && !event.attended && !event.planned;

return showCheckin || showRsvp ? (
<div className="relative z-2 flex items-center gap-1.5">
{state === "open" && !event.attended && (
{showCheckin && (
<Button
size={size}
onClick={handleCheckin}
Expand All @@ -355,12 +365,13 @@ function EventActions({ event, now, onRsvp, onCheckin, size = "sm" }: Readonly<E
Check in
</Button>
)}
{!past && !event.attended && !event.planned && (
{showRsvp && (
<Button
size={size}
variant={state === "open" ? "outline" : "default"}
onClick={handleRsvp}
className={cn(
ROW_ACTION_WIDTH,
"rounded-full font-bold",
state !== "open" && "bg-brand-teal text-primary hover:bg-brand-teal/85",
)}
Expand All @@ -370,7 +381,7 @@ function EventActions({ event, now, onRsvp, onCheckin, size = "sm" }: Readonly<E
</Button>
)}
</div>
);
) : undefined;
}

/// CancelRsvpButton
Expand Down Expand Up @@ -430,13 +441,14 @@ function GoingCancelPill({
className={cn(
PILL_CLASS,
STATUS_GREEN,
"group/going relative z-2 inline-flex w-30 items-center justify-center whitespace-nowrap transition-colors hover:bg-[#e13737]/12 hover:text-[#e13737] dark:hover:text-[#ff6b6b]",
ROW_ACTION_WIDTH,
"group/going relative z-2 inline-flex items-center justify-center whitespace-nowrap transition-colors hover:bg-[#e13737]/12 hover:text-[#e13737] dark:hover:text-[#ff6b6b]",
)}
>
<Check strokeWidth={2.4} className="group-hover/going:hidden" />
<X strokeWidth={2.4} className="hidden group-hover/going:inline" />
<span className="group-hover/going:hidden">Going</span>
<span className="hidden group-hover/going:inline">Cancel RSVP</span>
<span className="hidden group-hover/going:inline">Cancel</span>
</button>
);
}
Expand Down Expand Up @@ -621,45 +633,55 @@ export function EventToolbar({

return (
<div className="rounded-[20px] border border-border bg-card p-4 shadow-[0px_4px_14px_rgba(112,144,176,0.14)] dark:shadow-[0px_4px_14px_rgba(0,0,0,0.4)]">
<div className="flex flex-wrap items-center gap-3">
<div className="relative min-w-55 flex-1">
<div className="flex flex-col gap-3 lg:flex-row lg:flex-wrap lg:items-center">
<div className="relative w-full min-w-0 lg:min-w-55 lg:flex-1">
<Search className="absolute top-1/2 left-3 size-4.25 -translate-y-1/2 text-muted-foreground" />
<Input
value={query}
onChange={handleQueryChange}
placeholder={searchPlaceholder}
className="h-10 rounded-xl bg-muted pl-9.5"
className="h-10 rounded-xl border border-border bg-muted pl-9.5"
/>
</div>
<Select value={type} onValueChange={handleTypeChange}>
<SelectTrigger
aria-label="Filter events by type"
className="h-10 rounded-xl bg-muted font-bold"
>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All types</SelectItem>
{EVENT_TYPES.map((key) => (
<SelectItem key={key} value={key}>
{EVENT_TYPE_META[key].label}
</SelectItem>
))}
</SelectContent>
</Select>
<Tabs value={view} onValueChange={onViewChange}>
<TabsList className="h-10 rounded-xl">
{views.map((key) => {
const ViewIcon = VIEW_META[key].icon;
return (
<TabsTrigger key={key} value={key} className="gap-1.5 font-bold">
<ViewIcon className="size-4" />
<span className="hidden sm:inline">{VIEW_META[key].label}</span>
</TabsTrigger>
);
})}
</TabsList>
</Tabs>
<div className="flex items-center gap-3 lg:contents">
<Select value={type} onValueChange={handleTypeChange}>
<SelectTrigger
aria-label="Filter events by type"
className="h-10 min-w-0 flex-1 rounded-xl border border-border bg-muted font-bold lg:flex-none"
>
<SelectValue>
{(value: unknown) =>
TYPE_FILTER_OPTIONS.find((option) => option.value === value)?.label ?? ""
}
</SelectValue>
</SelectTrigger>
<SelectContent align="end" alignItemWithTrigger={false}>
{TYPE_FILTER_OPTIONS.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
<Tabs value={view} onValueChange={onViewChange}>
<TabsList className="h-10 shrink-0 rounded-xl">
{views.map((key) => {
const ViewIcon = VIEW_META[key].icon;
return (
<TabsTrigger
key={key}
value={key}
aria-label={VIEW_META[key].label}
className="gap-1.5 font-bold"
>
<ViewIcon className="size-4" />
<span className="hidden sm:inline">{VIEW_META[key].label}</span>
</TabsTrigger>
);
})}
</TabsList>
</Tabs>
</div>
</div>
</div>
);
Expand Down
4 changes: 4 additions & 0 deletions src/components/app/events-calendar-core.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ export interface ManageEventsCalendarProps {

/// Constants

// Fixes the positioning clipping issue
const DATE_PICKER_CONFIG = { teleportTo: document.body };
const PACIFIC_TZ = "America/Los_Angeles";

const PUBLIC_SHELL_CLASSES = cn(
"min-h-0 w-full flex-1 rounded-3xl border border-border bg-card",
"shadow-[0px_16px_40px_rgba(112,144,176,0.2)]",
Expand Down Expand Up @@ -122,6 +125,7 @@ function EventsCalendarCore({
dayBoundaries: { start: "08:00", end: "24:00" },
events: calendarEvents,
calendars,
datePicker: DATE_PICKER_CONFIG,
theme: "shadcn",
isDark: resolvedTheme === "dark",
timezone: PACIFIC_TZ,
Expand Down
14 changes: 9 additions & 5 deletions src/components/app/media-lightbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ interface MediaLightboxProps {
}

const LIGHTBOX_BUTTON_CLASS = "size-9 rounded-full text-white hover:bg-white/15";
const LIGHTBOX_MEDIA_CLASS =
"relative max-h-[86svh] w-auto max-w-full min-w-0 rounded-xl object-contain shadow-[0px_24px_64px_rgba(0,0,0,0.6)] xl:max-w-5xl";

export function MediaLightbox({
items,
Expand Down Expand Up @@ -88,11 +90,13 @@ export function MediaLightbox({
onKeyDown={onLightboxKey}
className="absolute inset-0 cursor-default bg-black/55 outline-hidden backdrop-blur-sm"
/>
<img
src={srcFor(current)}
alt=""
className="relative max-h-[86svh] max-w-5xl rounded-xl object-contain shadow-[0px_24px_64px_rgba(0,0,0,0.6)]"
/>
{current.kind === "video" ? (
<video src={srcFor(current)} controls autoPlay className={LIGHTBOX_MEDIA_CLASS}>
<track default kind="captions" srcLang="en" label="Captions unavailable" />
</video>
) : (
<img src={srcFor(current)} alt="" decoding="async" className={LIGHTBOX_MEDIA_CLASS} />
)}
<div className="absolute bottom-6 left-1/2 flex -translate-x-1/2 items-center gap-1 rounded-full border border-white/15 bg-black/50 p-1 backdrop-blur-sm">
{controls.map(({ title, onClick, icon }) => (
<Button
Expand Down
36 changes: 33 additions & 3 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -202,16 +202,46 @@
.sx__time-grid-event-inner .sx__time-grid-event-location::before {
content: ", ";
}
.sx__date-picker-popup.sx__date-picker-popup {
overflow: auto;
}
.sx__date-input-chevron-wrapper {
min-width: 24px;
min-height: 24px;
display: inline-flex;
align-items: center;
justify-content: center;
}
.sx__date-picker-popup.sx__date-picker-popup {
overflow: auto;
@apply max-h-[min(400px,70svh)] max-w-[calc(100vw-2.5rem)] rounded-xl border border-border shadow-lg;
}
:root .sx__date-picker-popup.is-teleported {
z-index: 40;
}

:is(.sx__view-container, .sx__list-wrapper) {
scrollbar-color: auto;
}
:is(.sx__view-container, .sx__list-wrapper)::-webkit-scrollbar {
@apply w-2.5;
}
:is(.sx__view-container, .sx__list-wrapper)::-webkit-scrollbar-button {
@apply hidden;
}
:is(.sx__view-container, .sx__list-wrapper)::-webkit-scrollbar-track {
@apply bg-transparent;
}
:is(.sx__view-container, .sx__list-wrapper)::-webkit-scrollbar-thumb {
@apply rounded-full border-3 border-transparent bg-muted-foreground/40 bg-clip-content;
}
:is(.sx__view-container, .sx__list-wrapper)::-webkit-scrollbar-thumb:hover {
@apply bg-muted-foreground/65;
}

@media (width < 40rem) {
.sx__date-picker-popup.is-teleported {
left: 50% !important;
transform: translateX(-50%);
}
}

@keyframes scroll-cue {
0% {
Expand Down
9 changes: 7 additions & 2 deletions src/routes/__root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Footer } from "@/components/app/footer";
import { Navbar } from "@/components/app/navigation-bar";
import { Toaster } from "@/components/ui/sonner";
import { ThemeProvider } from "@/components/ui/theme-provider";
import { cn } from "@/lib/utils";

export const Route = createRootRouteWithContext<{
queryClient: QueryClient;
Expand All @@ -30,8 +31,12 @@ function RootComponent() {
return (
<ThemeProvider defaultTheme="light" storageKey="vite-theme">
<Navbar />
{/* This only exists to prevent overflow issues on mobile */}
<div className="overflow-x-hidden">
<div
className={cn(
"flex flex-col overflow-x-hidden",
"min-h-[calc(100svh-4rem)] md:min-h-[calc(100svh-5.125rem)]",
)}
>
<main className="flex-1">
<Outlet />
</main>
Expand Down
4 changes: 2 additions & 2 deletions src/routes/about.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -282,8 +282,8 @@ function About() {
key={item.title}
value={String(index)}
className={cn(
"overflow-hidden rounded-3xl border-2 border-transparent bg-background",
"shadow-[0px_16px_40px_rgba(112,144,176,0.2)]",
"overflow-hidden rounded-3xl border-2 border-transparent not-last:border-b-2",
"bg-background shadow-[0px_16px_40px_rgba(112,144,176,0.2)]",
"data-open:border-brand-teal",
)}
>
Expand Down
16 changes: 8 additions & 8 deletions src/routes/dashboard/admin/members.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ const MEMBER_COLUMNS: ColumnDef<typeof dataTableFeatures, AdminMember>[] = [
.toUpperCase() ?? "··";
const tint = AVATAR_TINTS[name.length % AVATAR_TINTS.length];
return (
<div className="flex items-center gap-2.75">
<div className="flex max-w-44 items-center gap-2.75 @md:max-w-none">
<Avatar className={cn("size-9 shrink-0 border", tint)}>
<AvatarFallback className="bg-transparent text-[12px] font-extrabold">
{initials}
Expand Down Expand Up @@ -219,7 +219,7 @@ const MEMBER_COLUMNS: ColumnDef<typeof dataTableFeatures, AdminMember>[] = [
{
id: "actions",
header: () => <span className="sr-only">Actions</span>,
meta: { className: "text-right" },
meta: { className: "text-right pl-0 pr-1 @md:pr-2" },
cell: ({ row, table }) => {
const meta = table.options.meta?.members;
const isSelf = row.original.id === meta?.meId;
Expand Down Expand Up @@ -479,26 +479,26 @@ function MembersPage() {
return (
<div className="flex flex-col gap-5">
<div ref={topRef} className={cn(CARD_CLASS, "scroll-mt-4 p-4")}>
<div className="flex flex-wrap items-center justify-between gap-3">
<div className="flex flex-col gap-3 lg:flex-row lg:flex-wrap lg:items-center lg:justify-between">
<div className="text-sm font-bold text-brand-text-sub">
{allMembers.length} member{allMembers.length === 1 ? "" : "s"} · {withRoles} hold a
global role
</div>
<div className="flex flex-wrap items-center gap-3">
<div className="relative min-w-52 flex-1">
<div className="flex flex-col gap-3 lg:min-w-0 lg:flex-1 lg:flex-row lg:flex-wrap lg:items-center">
<div className="relative w-full min-w-0 lg:w-auto lg:min-w-52 lg:flex-1">
<Search className="absolute top-1/2 left-3 size-4.25 -translate-y-1/2 text-muted-foreground" />
<Input
value={search}
onChange={handleSearch}
placeholder="Search by name or email…"
className="h-10 rounded-xl bg-muted pl-9.5"
className="h-10 rounded-xl border border-border bg-muted pl-9.5"
/>
</div>
<Select value={filter} onValueChange={handleFilter}>
<SelectTrigger className="h-10 rounded-xl border border-border bg-muted font-bold">
<SelectTrigger className="h-10 w-full rounded-xl border border-border bg-muted font-bold lg:w-auto">
<SelectValue>{renderFilterLabel}</SelectValue>
</SelectTrigger>
<SelectContent>
<SelectContent align="end" alignItemWithTrigger={false}>
{FILTER_OPTIONS.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
Expand Down
10 changes: 5 additions & 5 deletions src/routes/dashboard/admin/tags.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -502,21 +502,21 @@ function TagsPage() {
return (
<div className="flex flex-col gap-5">
<div ref={topRef} className={cn(CARD_CLASS, "scroll-mt-4 p-4")}>
<div className="flex flex-wrap items-center gap-3">
<div className="relative min-w-52 flex-1">
<div className="flex flex-col gap-3 lg:flex-row lg:flex-wrap lg:items-center">
<div className="relative w-full min-w-0 lg:w-auto lg:min-w-52 lg:flex-1">
<Search className="absolute top-1/2 left-3 size-4.25 -translate-y-1/2 text-muted-foreground" />
<Input
value={search}
onChange={handleSearch}
placeholder="Search tags by title…"
className="h-10 rounded-xl bg-muted pl-9.5"
className="h-10 rounded-xl border border-border bg-muted pl-9.5"
/>
</div>
<Button className={cn("h-10", TEAL_BUTTON_CLASS)} onClick={openCreate}>
<Button className={cn("h-10 w-full lg:w-auto", TEAL_BUTTON_CLASS)} onClick={openCreate}>
<Plus />
Create tag
</Button>
<Button variant="outline" className="h-10 font-bold" onClick={openBulk}>
<Button variant="outline" className="h-10 w-full font-bold lg:w-auto" onClick={openBulk}>
<Upload />
Bulk create
<SudoLock active={sudoActive} />
Expand Down
15 changes: 11 additions & 4 deletions src/routes/dashboard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,9 @@ function DashboardHome() {
const handleHeroRsvp = useCallback(() => {
if (hero) handleRsvp(hero, true);
}, [hero, handleRsvp]);
const handleHeroDetails = useCallback(() => {
if (hero) setDetail(hero);
}, [hero]);

const role = ROLES_BY_RANK.find((item) => me?.roles.includes(item));

Expand Down Expand Up @@ -334,13 +337,17 @@ function DashboardHome() {
RSVP now
</Button>
)}
<Link
to="/dashboard/events"
className="inline-flex items-center justify-center gap-1.25 text-[13px] font-bold text-[#eaf6ff]/85 hover:text-[#eaf6ff]"
<Button
variant="ghost"
onClick={handleHeroDetails}
className={cn(
"h-auto justify-center gap-1.25 p-0 text-[13px] font-bold",
"text-[#eaf6ff]/85 hover:bg-transparent hover:text-[#eaf6ff]",
)}
>
View details
<ArrowRight className="size-3.75" />
</Link>
</Button>
</div>
</div>
</div>
Expand Down
Loading