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
1 change: 1 addition & 0 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

// Automatically generated code
"src/routeTree.gen.ts",
"src/types/kanae.gen.ts",
"src/hooks/use-mobile.ts",

// Shadcn/ui components
Expand Down
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default defineConfig([

// Automatically generated code
"src/routeTree.gen.ts",
"src/types/kanae.gen.ts",
"src/hooks/use-mobile.ts",

// Shadcn/ui components
Expand Down
10 changes: 10 additions & 0 deletions mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,13 @@ description = "Lints and formats the react codebase"
env._.path = "./node_modules/.bin"
dir = "{{ config_root }}"
run = [{"task" = "web:lint"}, { "task" = "web:format"}]

[tasks."web:generate:kanae"]
alias = ["web:generate:types"]
description = "Generates/regenerates the types from Kanae's openapi schema"
env._.path = "./node_modules/.bin"
dir = "{{ config_root }}"
usage = '''
arg "<file>" help="openapi schema to use"
'''
run = "openapi-ts --input ${usage_file}"
18 changes: 18 additions & 0 deletions openapi-ts.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { defineConfig, type UserConfig } from "@hey-api/openapi-ts";

// defineConfig requires us to have the input field. We don't use it
// so we'll get rid of it
const config: Omit<UserConfig, "input"> = {
output: {
path: "src/types",
clean: false,
entryFile: false,
fileName: { name: "kanae" },
postProcess: [
{ name: "oxfmt", command: "oxfmt", args: ["--write", "{{path}}/kanae.gen.ts"] }
]
},
plugins: ["@hey-api/typescript"]
};

export default defineConfig(config as UserConfig);
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"zod": "^4.4.3"
},
"devDependencies": {
"@hey-api/openapi-ts": "0.99.0",
"@rolldown/plugin-babel": "^0.2.3",
"@tailwindcss/vite": "^4.3.3",
"@tanstack/eslint-plugin-query": "^5.101.4",
Expand Down
1,255 changes: 738 additions & 517 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

12 changes: 4 additions & 8 deletions src/components/app/attendance-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,14 @@ import {
DialogTitle,
} from "@/components/ui/dialog";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import {
type AttendanceMember,
type FullEvent,
type KanaePage,
determineCheckIn,
fmtClock,
} from "@/lib/dashboard-events";
import { determineCheckIn, fmtClock } from "@/lib/dashboard-events";
import { type AttendanceMember, type FullEvents } from "@/types/kanae.gen";
import { type KanaePage } from "@/types/pages";

/// Types & interfaces

interface AttendanceDialogProps {
event: FullEvent;
event: FullEvents;
now: Date;
open: boolean;
onOpenChange: (open: boolean) => void;
Expand Down
7 changes: 3 additions & 4 deletions src/components/app/dashboard-events.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,9 @@ import {
import { Separator } from "@/components/ui/separator";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import {
type AttendanceMember,
type DashboardEvent,
type EventType,
type EventView,
type FullEvent,
EVENT_TYPE_CLASSES,
EVENT_TYPE_META,
EVENT_TYPES,
Expand All @@ -68,6 +66,7 @@ import {
monthDay,
} from "@/lib/dashboard-events";
import { cn } from "@/lib/utils";
import { type AttendanceMember, type FullEvents } from "@/types/kanae.gen";

/// Types & interfaces

Expand Down Expand Up @@ -235,7 +234,7 @@ function EventMeta({ icon: Icon, children }: Readonly<EventMetaProps>) {

/// DateBlock

function DateBlock({ event }: Readonly<{ event: FullEvent }>) {
function DateBlock({ event }: Readonly<{ event: FullEvents }>) {
const { mon, day } = monthDay(event.start_at, event.timezone);
return (
<div className="flex h-14 w-13 shrink-0 flex-col overflow-hidden rounded-xl border border-border bg-card text-center">
Expand All @@ -257,7 +256,7 @@ function DateBlock({ event }: Readonly<{ event: FullEvent }>) {
/// EventThumbnail

interface EventThumbnailProps {
event: FullEvent;
event: FullEvents;
className?: string;
}

Expand Down
2 changes: 1 addition & 1 deletion src/components/app/media-lightbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
} from "react";

import { Button } from "@/components/ui/button";
import { type MediaRecord } from "@/routes/dashboard/projects";
import { type MediaRecord } from "@/types/kanae.gen";

interface MediaLightboxProps {
items: MediaRecord[];
Expand Down
8 changes: 2 additions & 6 deletions src/components/app/sudo-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,8 @@ import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/components/ui/input-otp
import { Label } from "@/components/ui/label";
import { type Flow, ORY_URL, csrfToken, orySubmit } from "@/lib/ory";
import { cn } from "@/lib/utils";
import {
type ClientSession,
type Sudo,
meQueryOptions,
sudoQueryOptions,
} from "@/routes/dashboard/route";
import { type Sudo, meQueryOptions, sudoQueryOptions } from "@/routes/dashboard/route";
import { type ClientSession } from "@/types/kanae.gen";

/// Types and Interfaces

Expand Down
57 changes: 8 additions & 49 deletions src/lib/dashboard-events.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,16 @@
/// Types and Interfaces

import type { FullEvents } from "@/types/kanae.gen";

export type CheckInState = "ended" | "open" | "too_early";
export type EventView = "calendar" | "grid" | "list";
export type EventType = FullEvents["type"];

export type EventType =
| "general"
| "misc"
| "sig_ai"
| "sig_arch"
| "sig_cyber"
| "sig_data"
| "sig_graph"
| "sig_swe"
| "social";

export interface EventThumbnailData {
hash: string;
url: string;
}

export interface AttendanceMember {
id: string;
name: string;
planned?: boolean | null;
attended: boolean;
}

export interface FullEvent {
id: string;
name: string;
description: string;
start_at: string;
end_at: string;
location: string;
type: EventType;
timezone: string;
thumbnail?: EventThumbnailData | null;
tags?: string[] | null;
creator_id?: string | null;
}

export interface DashboardEvent extends FullEvent {
export interface DashboardEvent extends FullEvents {
planned: boolean;
attended: boolean;
}

// Kanae returns `data: null` for an empty page (e.g. a brand-new member's
// first dashboard load), so it could be both T[] or undefined
export interface KanaePage<T> {
data: T[] | undefined;
total: number;
}

interface EventTypeMeta {
label: string;
short: string;
Expand All @@ -70,7 +29,7 @@ interface EventTypeClasses {
/// Constants

const mergeCache = new WeakMap<
FullEvent,
FullEvents,
{ attended: boolean; merged: DashboardEvent; planned: boolean }
>();

Expand Down Expand Up @@ -210,7 +169,7 @@ const DAY_FMT_OPTIONS: Intl.DateTimeFormatOptions = {
/// Helpers

export function mergeDashboardEvent(
event: FullEvent,
event: FullEvents,
planned: boolean,
attended: boolean,
): DashboardEvent {
Expand Down Expand Up @@ -238,7 +197,7 @@ export function filterEvents(
}

// Check-in runs from 1h before start through end
export function determineCheckIn(event: FullEvent, now: Date): CheckInState {
export function determineCheckIn(event: FullEvents, now: Date): CheckInState {
const start = new Date(event.start_at).getTime();
const end = new Date(event.end_at).getTime();
const currentTime = now.getTime();
Expand All @@ -247,7 +206,7 @@ export function determineCheckIn(event: FullEvent, now: Date): CheckInState {
return "open";
}

export function isPastEvent(event: FullEvent, now: Date): boolean {
export function isPastEvent(event: FullEvents, now: Date): boolean {
return new Date(event.end_at).getTime() < now.getTime();
}

Expand Down
27 changes: 3 additions & 24 deletions src/routes/dashboard/admin/members.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,8 @@ import {
} from "@/components/ui/select";
import { Skeleton } from "@/components/ui/skeleton";
import { cn } from "@/lib/utils";
import {
type Role,
isSudoActive,
meQueryOptions,
sudoQueryOptions,
} from "@/routes/dashboard/route";
import { isSudoActive, meQueryOptions, sudoQueryOptions } from "@/routes/dashboard/route";
import { type MemberRoles, type Role, type SimpleMember } from "@/types/kanae.gen";

export const Route = createFileRoute("/dashboard/admin/members")({
component: MembersPage,
Expand All @@ -81,24 +77,7 @@ export const Route = createFileRoute("/dashboard/admin/members")({
type RowHandler = (event: MouseEvent<HTMLElement>) => void;
type RoleFilter = "admin" | "all" | "leads" | "manager" | "none";

interface SimpleMember {
id: string;
name: string;
display_name?: string | null;
email: string;
created_at: string;
}

interface AdminMember {
id: string;
name: string;
display_name?: string | null;
email: string;
created_at: string;
roles: Role[];
}

interface MemberRoles {
interface AdminMember extends SimpleMember {
roles: Role[];
}

Expand Down
13 changes: 5 additions & 8 deletions src/routes/dashboard/admin/overview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import { StatTile } from "@/components/app/dashboard-events";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { type ChartConfig } from "@/components/ui/chart";
import { Skeleton } from "@/components/ui/skeleton";
import { type FullEvent, type KanaePage, isPastEvent } from "@/lib/dashboard-events";
import { type Role } from "@/routes/dashboard/route";
import { isPastEvent } from "@/lib/dashboard-events";
import { type FullEvents, type MemberRoles, type Role } from "@/types/kanae.gen";
import { type KanaePage } from "@/types/pages";

export const Route = createFileRoute("/dashboard/admin/overview")({
component: OverviewPage,
Expand Down Expand Up @@ -38,10 +39,6 @@ interface OverviewProject {
active: boolean;
}

interface MemberRoles {
roles: Role[];
}

interface RoleBucket {
key: "admins" | "managers" | "leads" | "members";
label: string;
Expand Down Expand Up @@ -153,10 +150,10 @@ const overviewEventsQueryOptions = queryOptions({
queryKey: ["overview", "events"],
queryFn: async () => {
const now = new Date();
const events: FullEvent[] = [];
const events: FullEvents[] = [];
let more = true;
while (more) {
const { data } = await axios.get<KanaePage<FullEvent>>(`${API_BASE_URL}/events`, {
const { data } = await axios.get<KanaePage<FullEvents>>(`${API_BASE_URL}/events`, {
params: { page: events.length / EVENTS_PAGE_SIZE + 1, size: EVENTS_PAGE_SIZE },
});
events.push(...(data.data ?? []));
Expand Down
27 changes: 6 additions & 21 deletions src/routes/dashboard/admin/tags.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ import { Skeleton } from "@/components/ui/skeleton";
import { Textarea } from "@/components/ui/textarea";
import { cn } from "@/lib/utils";
import { isSudoActive, sudoQueryOptions } from "@/routes/dashboard/route";
import {
type AttachedTagEntry,
type AttachedTagResponse,
type FullTags,
type Tags as Tag,
} from "@/types/kanae.gen";

export const Route = createFileRoute("/dashboard/admin/tags")({
component: TagsPage,
Expand All @@ -71,27 +77,6 @@ export const Route = createFileRoute("/dashboard/admin/tags")({

type RowHandler = (event: MouseEvent<HTMLElement>) => void;

interface Tag {
id: number;
title: string;
description: string;
}

interface FullTags extends Tag {
in_use: boolean;
}

interface AttachedTagEntry {
id: string;
name: string;
type: "Project" | "Event";
}

interface AttachedTagResponse {
detail: string;
entries: AttachedTagEntry[];
}

interface Conflict {
title: string;
entries: AttachedTagEntry[];
Expand Down
4 changes: 2 additions & 2 deletions src/routes/dashboard/events.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import {
type DashboardEvent,
type EventType,
type EventView,
type FullEvent,
EVENT_TYPE_CLASSES,
EVENT_TYPE_META,
EVENT_TYPES,
Expand All @@ -33,6 +32,7 @@ import {
mergeDashboardEvent,
} from "@/lib/dashboard-events";
import { cn } from "@/lib/utils";
import { type FullEvents } from "@/types/kanae.gen";

export const Route = createFileRoute("/dashboard/events")({
component: DashboardEvents,
Expand Down Expand Up @@ -125,7 +125,7 @@ function DashboardEvents() {
joinMutate(event.id);
return;
}
queryClient.setQueryData<FullEvent[]>(plannedEventsQueryOptions.queryKey, (old) =>
queryClient.setQueryData<FullEvents[]>(plannedEventsQueryOptions.queryKey, (old) =>
(old ?? []).filter((item) => item.id !== event.id),
);
toast.success("RSVP cancelled.");
Expand Down
Loading