diff --git a/CODE_STYLE.md b/CODE_STYLE.md index adac833..c78e7c8 100644 --- a/CODE_STYLE.md +++ b/CODE_STYLE.md @@ -117,10 +117,10 @@ import { z } from "zod"; import DEFAULT_AVATAR from "$assets/images/default-avatar.png"; // Components -import Avatar from "$components/avatar"; -import Button from "$components/button"; -import Modal from "$components/modal"; -import Spinner from "$components/spinner"; +import { Avatar } from "$components/avatar"; +import { Button } from "$components/button"; +import { Modal } from "$components/modal"; +import { Spinner } from "$components/spinner"; // Data import { USER_DEFAULTS } from "$data/constants"; @@ -155,9 +155,9 @@ import { useMemo, useState } from "react"; import HERO_IMAGE from "$assets/images/events-hero.jpg"; // Components -import EventCard from "$components/event-card"; -import FilterBar from "$components/filter-bar"; -import Pagination from "$components/pagination"; +import { EventCard } from "$components/event-card"; +import { FilterBar } from "$components/filter-bar"; +import { Pagination } from "$components/pagination"; // Data import { EVENT_CATEGORIES } from "$data/categories"; @@ -209,7 +209,7 @@ import { useState } from "react"; import ICON from "$assets/icons/star.svg"; -import Button from "$components/button"; +import { Button } from "$components/button"; import { api } from "$lib/api"; @@ -223,7 +223,7 @@ import { useQuery, useSuspenseQuery } from "@tanstack/react-query"; import { api, auth, cache } from "$lib/api"; // Avoid - Mixed categories without separation -import Button from "$components/button"; +import { Button } from "$components/button"; import styles from "$styles/app.module.scss"; import { useState } from "react"; import type { Props } from "./types"; diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index ae47312..fccf1a2 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -13,8 +13,8 @@ The project uses [pnpm](https://pnpm.io/) for package management and scripts. Ma ## Prerequisites -- `pnpm` - >v9.0.0 -- `node` - >v20.0.0 +- `pnpm` - >v10.0.0 +- `node` - >=v22.0.0 and v27.0.0 ## Environment Variables diff --git a/biome.json b/biome.json deleted file mode 100644 index 40015ec..0000000 --- a/biome.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "$schema": "https://biomejs.dev/schemas/2.3.14/schema.json", - "vcs": { - "enabled": false, - "clientKind": "git", - "useIgnoreFile": false - }, - "files": { - "ignoreUnknown": false, - "includes": [ - "**/src/**/*", - "**/.vscode/**/*", - "**/index.html", - "**/vite.config.js", - "!**/src/routeTree.gen.ts", - "!**/src/styles.css" - ] - }, - "formatter": { - "enabled": true, - "indentStyle": "space", - "indentWidth": 2, - "attributePosition": "multiline" - }, - "assist": { - "actions": { - "source": { - "organizeImports": { - "level": "on", - "options": { - "groups": [ - { "type": true, "source": ["**"] }, - ":BLANK_LINE:", - [":URL:", ":PACKAGE_WITH_PROTOCOL:"], - ":BLANK_LINE:", - ":NODE:", - ":BUN:", - ":PACKAGE:", - ":BLANK_LINE:", - "$assets/**", - ":BLANK_LINE:", - "$components/**", - ":BLANK_LINE:", - "$data/**", - ":BLANK_LINE:", - "$hooks/**", - ":BLANK_LINE:", - "$lib/**", - ":BLANK_LINE:", - "$styles/**" - ] - } - } - } - } - }, - "linter": { - "enabled": true, - "rules": { - "recommended": true, - "correctness": { - "noUnusedImports": "error" - } - } - }, - "javascript": { - "formatter": { - "quoteStyle": "double", - "arrowParentheses": "asNeeded" - } - } -} diff --git a/package.json b/package.json index 51d5e6c..c36bc65 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,13 @@ { - "name": "acm-website-tanstack", + "name": "@pec-css/website", + "version": "0.1.0", "private": true, + "homepage": "https://pecacm.in", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/Savy011/acm-website-tanstack.git" + }, "type": "module", "scripts": { "container:up": "docker compose up -d", @@ -20,24 +27,28 @@ "email:dev": "email dev --dir=./src/emails --port=2999" }, "dependencies": { + "@base-ui/react": "^1.3.0", "@bogeychan/elysia-logger": "^0.1.10", "@elysiajs/eden": "^1.4.8", "@elysiajs/openapi": "^1.4.14", "@fontsource/space-grotesk": "^5.2.10", + "@logtape/elysia": "^2.0.5", + "@logtape/logtape": "^2.0.5", + "@logtape/pretty": "^2.0.5", "@netlify/vite-plugin-tanstack-start": "^1.3.2", "@react-email/components": "1.0.10", "@t3-oss/env-core": "^0.13.11", - "@tailwindcss/vite": "^4.1.18", + "@tabler/icons-react": "^3.41.1", + "@tailwindcss/vite": "^4.2.2", "@tanstack/react-devtools": "^0.10.0", "@tanstack/react-form": "^1.28.5", "@tanstack/react-form-start": "^1.28.0", "@tanstack/react-query": "^5.95.2", - "@tanstack/react-query-devtools": "^5.95.2", "@tanstack/react-router": "^1.168.3", - "@tanstack/react-router-devtools": "^1.166.11", "@tanstack/react-router-ssr-query": "^1.166.10", "@tanstack/react-start": "^1.167.5", "@tanstack/router-plugin": "^1.167.4", + "clsx": "^2.1.1", "concurrently": "^9.2.1", "dotenv": "^17.2.4", "elysia": "^1.4.28", @@ -46,14 +57,16 @@ "pino-pretty": "^13.1.3", "react": "^19.2.4", "react-dom": "^19.2.4", - "tailwindcss": "^4.1.18", - "vite-tsconfig-paths": "^5.1.4", + "tailwind-merge": "^3.5.0", + "tailwindcss": "^4.2.2", "zod": "^4.3.6" }, "devDependencies": { "@react-email/preview-server": "5.2.10", "@rollup/plugin-yaml": "^4.1.2", "@tanstack/devtools-vite": "^0.6.0", + "@tanstack/react-query-devtools": "^5.84.2", + "@tanstack/react-router-devtools": "^1.132.0", "@testing-library/dom": "^10.4.0", "@testing-library/react": "^16.3.2", "@types/node": "^22.10.2", @@ -63,6 +76,7 @@ "@typescript/native-preview": "7.0.0-dev.20260324.1", "@vitejs/plugin-react": "^5.2.0", "babel-plugin-react-compiler": "^1.0.0", + "concurrently": "^9.2.1", "husky": "^9.1.7", "jsdom": "^29.0.1", "lint-staged": "^16.4.0", @@ -79,5 +93,10 @@ "lint-staged": { "*.{js,jsx,ts,tsx,mjs,cjs}": "pnpm run lint", "*": "oxfmt --no-error-on-unmatched-pattern" - } + }, + "engines": { + "node": ">=22.0.0 <23.0.0", + "pnpm": ">=10.0.0" + }, + "engineStrict": true } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 48dddec..8d43c67 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@base-ui/react': + specifier: ^1.3.0 + version: 1.3.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@bogeychan/elysia-logger': specifier: ^0.1.10 version: 0.1.10(elysia@1.4.28(@sinclair/typebox@0.34.41)(exact-mirror@0.2.5(@sinclair/typebox@0.34.41))(file-type@21.1.1)(openapi-types@12.1.3)(typescript@6.0.2))(pino@10.3.1) @@ -20,6 +23,15 @@ importers: '@fontsource/space-grotesk': specifier: ^5.2.10 version: 5.2.10 + '@logtape/elysia': + specifier: ^2.0.5 + version: 2.0.5(@logtape/logtape@2.0.5)(elysia@1.4.28(@sinclair/typebox@0.34.41)(exact-mirror@0.2.5(@sinclair/typebox@0.34.41))(file-type@21.1.1)(openapi-types@12.1.3)(typescript@6.0.2)) + '@logtape/logtape': + specifier: ^2.0.5 + version: 2.0.5 + '@logtape/pretty': + specifier: ^2.0.5 + version: 2.0.5(@logtape/logtape@2.0.5) '@netlify/vite-plugin-tanstack-start': specifier: ^1.3.2 version: 1.3.2(@tanstack/react-start@1.167.14(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.7.1)(@types/node@22.19.1)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.94.0)(tsx@4.20.6)(yaml@2.8.3)))(rollup@4.53.2)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.7.1)(@types/node@22.19.1)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.94.0)(tsx@4.20.6)(yaml@2.8.3)) @@ -29,8 +41,11 @@ importers: '@t3-oss/env-core': specifier: ^0.13.11 version: 0.13.11(typescript@6.0.2)(zod@4.3.6) + '@tabler/icons-react': + specifier: ^3.41.1 + version: 3.41.1(react@19.2.4) '@tailwindcss/vite': - specifier: ^4.1.18 + specifier: ^4.2.2 version: 4.2.2(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.7.1)(@types/node@22.19.1)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.94.0)(tsx@4.20.6)(yaml@2.8.3)) '@tanstack/react-devtools': specifier: ^0.10.0 @@ -44,15 +59,9 @@ importers: '@tanstack/react-query': specifier: ^5.95.2 version: 5.95.2(react@19.2.4) - '@tanstack/react-query-devtools': - specifier: ^5.95.2 - version: 5.95.2(@tanstack/react-query@5.95.2(react@19.2.4))(react@19.2.4) '@tanstack/react-router': specifier: ^1.168.3 version: 1.168.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@tanstack/react-router-devtools': - specifier: ^1.166.11 - version: 1.166.11(@tanstack/react-router@1.168.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.168.7)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tanstack/react-router-ssr-query': specifier: ^1.166.10 version: 1.166.10(@tanstack/query-core@5.95.2)(@tanstack/react-query@5.95.2(react@19.2.4))(@tanstack/react-router@1.168.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.168.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -62,6 +71,9 @@ importers: '@tanstack/router-plugin': specifier: ^1.167.4 version: 1.167.10(@tanstack/react-router@1.168.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.7.1)(@types/node@22.19.1)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.94.0)(tsx@4.20.6)(yaml@2.8.3)) + clsx: + specifier: ^2.1.1 + version: 2.1.1 concurrently: specifier: ^9.2.1 version: 9.2.1 @@ -86,12 +98,12 @@ importers: react-dom: specifier: ^19.2.4 version: 19.2.4(react@19.2.4) + tailwind-merge: + specifier: ^3.5.0 + version: 3.5.0 tailwindcss: - specifier: ^4.1.18 - version: 4.1.18 - vite-tsconfig-paths: - specifier: ^5.1.4 - version: 5.1.4(typescript@6.0.2)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.7.1)(@types/node@22.19.1)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.94.0)(tsx@4.20.6)(yaml@2.8.3)) + specifier: ^4.2.2 + version: 4.2.2 zod: specifier: ^4.3.6 version: 4.3.6 @@ -105,6 +117,12 @@ importers: '@tanstack/devtools-vite': specifier: ^0.6.0 version: 0.6.0(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.7.1)(@types/node@22.19.1)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.94.0)(tsx@4.20.6)(yaml@2.8.3)) + '@tanstack/react-query-devtools': + specifier: ^5.84.2 + version: 5.95.2(@tanstack/react-query@5.95.2(react@19.2.4))(react@19.2.4) + '@tanstack/react-router-devtools': + specifier: ^1.132.0 + version: 1.166.11(@tanstack/react-router@1.168.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.168.7)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@testing-library/dom': specifier: ^10.4.0 version: 10.4.1 @@ -313,6 +331,10 @@ packages: resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} engines: {node: '>=6.9.0'} + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + engines: {node: '>=6.9.0'} + '@babel/template@7.27.2': resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} @@ -341,6 +363,27 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} + '@base-ui/react@1.3.0': + resolution: {integrity: sha512-FwpKqZbPz14AITp1CVgf4AjhKPe1OeeVKSBMdgD10zbFlj3QSWelmtCMLi2+/PFZZcIm3l87G7rwtCZJwHyXWA==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@types/react': ^17 || ^18 || ^19 + react: ^17 || ^18 || ^19 + react-dom: ^17 || ^18 || ^19 + peerDependenciesMeta: + '@types/react': + optional: true + + '@base-ui/utils@0.2.6': + resolution: {integrity: sha512-yQ+qeuqohwhsNpoYDqqXaLllYAkPCP4vYdDrVo8FQXaAPfHWm1pG/Vm+jmGTA5JFS0BAIjookyapuJFY8F9PIw==} + peerDependencies: + '@types/react': ^17 || ^18 || ^19 + react: ^17 || ^18 || ^19 + react-dom: ^17 || ^18 || ^19 + peerDependenciesMeta: + '@types/react': + optional: true + '@bogeychan/elysia-logger@0.1.10': resolution: {integrity: sha512-wFp3KUCNIkCV4zcbo70gifHH99ch7e4LNrP5Xa5e2MRO2MDyPgM92SWQmrzkng6PsSZk13+UKJskGNQ+ZuDZkQ==} peerDependencies: @@ -754,6 +797,21 @@ packages: '@fastify/busboy@3.2.0': resolution: {integrity: sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA==} + '@floating-ui/core@1.7.5': + resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} + + '@floating-ui/dom@1.7.6': + resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} + + '@floating-ui/react-dom@2.1.8': + resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.11': + resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} + '@fontsource/space-grotesk@5.2.10': resolution: {integrity: sha512-XNXEbT74OIITPqw2H6HXwPDp85fy43uxfBwFR5PU+9sLnjuLj12KlhVM9nZVN6q6dlKjkuN8JisW/OBxwxgUew==} @@ -794,89 +852,105 @@ packages: resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-arm@1.2.4': resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.2.4': resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-riscv64@1.2.4': resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-s390x@1.2.4': resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-x64@1.2.4': resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.2.4': resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.2.4': resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-linux-arm64@0.34.5': resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-linux-arm@0.34.5': resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-linux-ppc64@0.34.5': resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-linux-riscv64@0.34.5': resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-linux-s390x@0.34.5': resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-linux-x64@0.34.5': resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-linuxmusl-arm64@0.34.5': resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-linuxmusl-x64@0.34.5': resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-wasm32@0.34.5': resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} @@ -928,6 +1002,20 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@logtape/elysia@2.0.5': + resolution: {integrity: sha512-H9hz0JtWbuGIQP0YWRNRfnvlM9UnRfvTE6rpCLnTlOd8wIgdiNaAHT2wPsg9bvXZ/Sf3omUo1qdt2HPUHGanWw==} + peerDependencies: + '@logtape/logtape': ^2.0.5 + elysia: ^1.4.0 + + '@logtape/logtape@2.0.5': + resolution: {integrity: sha512-UizDkh20ZPJVOddRxG1F77WhHdlNl/sbQgoO8T534R7XvUBMAJ9En9f35u+meW2tRsNLvjz6R87Zanwf53tspQ==} + + '@logtape/pretty@2.0.5': + resolution: {integrity: sha512-jU5pYL0CW0tFmxBS5umMF5VEMq1vXLvkqKrj7KRHnSlb5SrBOSCYl0w4q7FmPPFVADmgTmzVVr6IcIWwK/2Nig==} + peerDependencies: + '@logtape/logtape': ^2.0.5 + '@mapbox/node-pre-gyp@2.0.0': resolution: {integrity: sha512-llMXd39jtP0HpQLVI37Bf1m2ADlEb35GYSh1SDSLsBhR+5iCxiNGlT31yqbNtVHygHAtMy6dWFERpU2JgufhPg==} engines: {node: '>=18'} @@ -1090,24 +1178,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@next/swc-linux-arm64-musl@16.1.7': resolution: {integrity: sha512-uufcze7LYv0FQg9GnNeZ3/whYfo+1Q3HnQpm16o6Uyi0OVzLlk2ZWoY7j07KADZFY8qwDbsmFnMQP3p3+Ftprw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@next/swc-linux-x64-gnu@16.1.7': resolution: {integrity: sha512-KWVf2gxYvHtvuT+c4MBOGxuse5TD7DsMFYSxVxRBnOzok/xryNeQSjXgxSv9QpIVlaGzEn/pIuI6Koosx8CGWA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@next/swc-linux-x64-musl@16.1.7': resolution: {integrity: sha512-HguhaGwsGr1YAGs68uRKc4aGWxLET+NevJskOcCAwXbwj0fYX0RgZW2gsOCzr9S11CSQPIkxmoSbuVaBp4Z3dA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@next/swc-win32-arm64-msvc@16.1.7': resolution: {integrity: sha512-S0n3KrDJokKTeFyM/vGGGR8+pCmXYrjNTk2ZozOL1C/JFdfUIL9O1ATaJOl5r2POe56iRChbsszrjMAdWSv7kQ==} @@ -1259,48 +1351,56 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@oxfmt/binding-linux-arm64-musl@0.40.0': resolution: {integrity: sha512-tGmWhLD/0YMotCdfezlT6tC/MJG/wKpo4vnQ3Cq+4eBk/BwNv7EmkD0VkD5F/dYkT3b8FNU01X2e8vvJuWoM1w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@oxfmt/binding-linux-ppc64-gnu@0.40.0': resolution: {integrity: sha512-rVbFyM3e7YhkVnp0IVYjaSHfrBWcTRWb60LEcdNAJcE2mbhTpbqKufx0FrhWfoxOrW/+7UJonAOShoFFLigDqQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@oxfmt/binding-linux-riscv64-gnu@0.40.0': resolution: {integrity: sha512-3ZqBw14JtWeEoLiioJcXSJz8RQyPE+3jLARnYM1HdPzZG4vk+Ua8CUupt2+d+vSAvMyaQBTN2dZK+kbBS/j5mA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@oxfmt/binding-linux-riscv64-musl@0.40.0': resolution: {integrity: sha512-JJ4PPSdcbGBjPvb+O7xYm2FmAsKCyuEMYhqatBAHMp/6TA6rVlf9Z/sYPa4/3Bommb+8nndm15SPFRHEPU5qFA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [musl] '@oxfmt/binding-linux-s390x-gnu@0.40.0': resolution: {integrity: sha512-Kp0zNJoX9Ik77wUya2tpBY3W9f40VUoMQLWVaob5SgCrblH/t2xr/9B2bWHfs0WCefuGmqXcB+t0Lq77sbBmZw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + libc: [glibc] '@oxfmt/binding-linux-x64-gnu@0.40.0': resolution: {integrity: sha512-7YTCNzleWTaQTqNGUNQ66qVjpoV6DjbCOea+RnpMBly2bpzrI/uu7Rr+2zcgRfNxyjXaFTVQKaRKjqVdeUfeVA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@oxfmt/binding-linux-x64-musl@0.40.0': resolution: {integrity: sha512-hWnSzJ0oegeOwfOEeejYXfBqmnRGHusgtHfCPzmvJvHTwy1s3Neo59UKc1CmpE3zxvrCzJoVHos0rr97GHMNPw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@oxfmt/binding-openharmony-arm64@0.40.0': resolution: {integrity: sha512-28sJC1lR4qtBJGzSRRbPnSW3GxU2+4YyQFE6rCmsUYqZ5XYH8jg0/w+CvEzQ8TuAQz5zLkcA25nFQGwoU0PT3Q==} @@ -1403,48 +1503,56 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@oxlint/binding-linux-arm64-musl@1.57.0': resolution: {integrity: sha512-i66WyEPVEvq9bxRUCJ/MP5EBfnTDN3nhwEdFZFTO5MmLLvzngfWEG3NSdXQzTT3vk5B9i6C2XSIYBh+aG6uqyg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@oxlint/binding-linux-ppc64-gnu@1.57.0': resolution: {integrity: sha512-oMZDCwz4NobclZU3pH+V1/upVlJZiZvne4jQP+zhJwt+lmio4XXr4qG47CehvrW1Lx2YZiIHuxM2D4YpkG3KVA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@oxlint/binding-linux-riscv64-gnu@1.57.0': resolution: {integrity: sha512-uoBnjJ3MMEBbfnWC1jSFr7/nSCkcQYa72NYoNtLl1imshDnWSolYCjzb8LVCwYCCfLJXD+0gBLD7fyC14c0+0g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@oxlint/binding-linux-riscv64-musl@1.57.0': resolution: {integrity: sha512-BdrwD7haPZ8a9KrZhKJRSj6jwCor+Z8tHFZ3PT89Y3Jq5v3LfMfEePeAmD0LOTWpiTmzSzdmyw9ijneapiVHKQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [musl] '@oxlint/binding-linux-s390x-gnu@1.57.0': resolution: {integrity: sha512-BNs+7ZNsRstVg2tpNxAXfMX/Iv5oZh204dVyb8Z37+/gCh+yZqNTlg6YwCLIMPSk5wLWIGOaQjT0GUOahKYImw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + libc: [glibc] '@oxlint/binding-linux-x64-gnu@1.57.0': resolution: {integrity: sha512-AghS18w+XcENcAX0+BQGLiqjpqpaxKJa4cWWP0OWNLacs27vHBxu7TYkv9LUSGe5w8lOJHeMxcYfZNOAPqw2bg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@oxlint/binding-linux-x64-musl@1.57.0': resolution: {integrity: sha512-E/FV3GB8phu/Rpkhz5T96hAiJlGzn91qX5yj5gU754P5cmVGXY1Jw/VSjDSlZBCY3VHjsVLdzgdkJaomEmcNOg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@oxlint/binding-openharmony-arm64@1.57.0': resolution: {integrity: sha512-xvZ2yZt0nUVfU14iuGv3V25jpr9pov5N0Wr28RXnHFxHCRxNDMtYPHV61gGLhN9IlXM96gI4pyYpLSJC5ClLCQ==} @@ -1499,36 +1607,42 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.1': resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.1': resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.1': resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.1': resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.1': resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [musl] '@parcel/watcher-wasm@2.5.1': resolution: {integrity: sha512-RJxlQQLkaMMIuWRozy+z2vEqbaQlCuaCgVZIUCzQLYggY22LZbP5Y1+ia+FD724Ids9e+XIyOLXLrLgQSHIthw==} @@ -1762,36 +1876,42 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-arm64-musl@1.0.0-rc.12': resolution: {integrity: sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12': resolution: {integrity: sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12': resolution: {integrity: sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-gnu@1.0.0-rc.12': resolution: {integrity: sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-musl@1.0.0-rc.12': resolution: {integrity: sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@rolldown/binding-openharmony-arm64@1.0.0-rc.12': resolution: {integrity: sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==} @@ -1877,56 +1997,67 @@ packages: resolution: {integrity: sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.53.2': resolution: {integrity: sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.53.2': resolution: {integrity: sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.53.2': resolution: {integrity: sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.53.2': resolution: {integrity: sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-gnu@4.53.2': resolution: {integrity: sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.53.2': resolution: {integrity: sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.53.2': resolution: {integrity: sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.53.2': resolution: {integrity: sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.53.2': resolution: {integrity: sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.53.2': resolution: {integrity: sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openharmony-arm64@4.53.2': resolution: {integrity: sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A==} @@ -2023,6 +2154,14 @@ packages: zod: optional: true + '@tabler/icons-react@3.41.1': + resolution: {integrity: sha512-kUgweE+DJtAlMZVIns1FTDdcbpRVnkK7ZpUOXmoxy3JAF0rSHj0TcP4VHF14+gMJGnF+psH2Zt26BLT6owetBA==} + peerDependencies: + react: '>= 16' + + '@tabler/icons@3.41.1': + resolution: {integrity: sha512-OaRnVbRmH2nHtFeg+RmMJ/7m2oBIF9XCJAUD5gQnMrpK9f05ydj8MZrAf3NZQqOXyxGN1UBL0D5IKLLEUfr74Q==} + '@tailwindcss/node@4.2.2': resolution: {integrity: sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==} @@ -2061,24 +2200,28 @@ packages: engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.2.2': resolution: {integrity: sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.2.2': resolution: {integrity: sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.2.2': resolution: {integrity: sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.2.2': resolution: {integrity: sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==} @@ -3474,9 +3617,6 @@ packages: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} - globrex@0.1.2: - resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} - gonzales-pe@4.3.0: resolution: {integrity: sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==} engines: {node: '>=0.6.0'} @@ -3568,8 +3708,8 @@ packages: resolution: {integrity: sha512-xQjs+2vrxLnAjCq+omuNkd5UQTld9/bP8+YT0LyYTlKfuSQtgUBvqhUwGugzSAh6sCdN+LnROMuLswn5hZ9Fhg==} engines: {node: '>=20.0.0'} - immutable@5.1.4: - resolution: {integrity: sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==} + immutable@5.1.5: + resolution: {integrity: sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==} import-in-the-middle@1.15.0: resolution: {integrity: sha512-bpQy+CrsRmYmoPMAE/0G33iwRqwW4ouqdRg8jgbH3aKuCtOc8lxgmYXg2dMM92CRiGP660EtBcymH/eVUpCSaA==} @@ -3838,24 +3978,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.32.0: resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.32.0: resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} @@ -4509,6 +4653,9 @@ packages: require-package-name@2.0.1: resolution: {integrity: sha512-uuoJ1hU/k6M0779t3VMVIYpb2VMJk05cehCaABFhXaibcbvfgR8wKiozLjVFSzJPmQMRqIcO0HMyTFqfV09V6Q==} + reselect@5.1.1: + resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} + resolve-from@5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} @@ -4824,10 +4971,16 @@ packages: resolution: {integrity: sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA==} engines: {node: '>=18'} + tabbable@6.4.0: + resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==} + tagged-tag@1.0.0: resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} engines: {node: '>=20'} + tailwind-merge@3.5.0: + resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==} + tailwindcss@4.1.18: resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==} @@ -4939,16 +5092,6 @@ packages: peerDependencies: typescript: '>=4.8.4' - tsconfck@3.1.6: - resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} - engines: {node: ^18 || >=20} - hasBin: true - peerDependencies: - typescript: ^5.0.0 - peerDependenciesMeta: - typescript: - optional: true - tsconfig-paths@4.2.0: resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} engines: {node: '>=6'} @@ -5128,14 +5271,6 @@ packages: resolution: {integrity: sha512-FwjApRNZyN+RucPW9Z9kf0dyzyi3r3zlDfrTnzHXNaYpmT3pZ5w//d6QkApy1iypbDm+3fq+Gwfv+PYA4j4uYw==} engines: {node: '>=20.0.0'} - vite-tsconfig-paths@5.1.4: - resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==} - peerDependencies: - vite: '*' - peerDependenciesMeta: - vite: - optional: true - vite@8.0.3: resolution: {integrity: sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -5587,6 +5722,8 @@ snapshots: '@babel/runtime@7.28.4': {} + '@babel/runtime@7.29.2': {} + '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 @@ -5645,6 +5782,30 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@base-ui/react@1.3.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@babel/runtime': 7.29.2 + '@base-ui/utils': 0.2.6(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@floating-ui/react-dom': 2.1.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@floating-ui/utils': 0.2.11 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + tabbable: 6.4.0 + use-sync-external-store: 1.6.0(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + + '@base-ui/utils@0.2.6(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@babel/runtime': 7.29.2 + '@floating-ui/utils': 0.2.11 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + reselect: 5.1.1 + use-sync-external-store: 1.6.0(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@bogeychan/elysia-logger@0.1.10(elysia@1.4.28(@sinclair/typebox@0.34.41)(exact-mirror@0.2.5(@sinclair/typebox@0.34.41))(file-type@21.1.1)(openapi-types@12.1.3)(typescript@6.0.2))(pino@10.3.1)': dependencies: elysia: 1.4.28(@sinclair/typebox@0.34.41)(exact-mirror@0.2.5(@sinclair/typebox@0.34.41))(file-type@21.1.1)(openapi-types@12.1.3)(typescript@6.0.2) @@ -5886,6 +6047,23 @@ snapshots: '@fastify/busboy@3.2.0': {} + '@floating-ui/core@1.7.5': + dependencies: + '@floating-ui/utils': 0.2.11 + + '@floating-ui/dom@1.7.6': + dependencies: + '@floating-ui/core': 1.7.5 + '@floating-ui/utils': 0.2.11 + + '@floating-ui/react-dom@2.1.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@floating-ui/dom': 1.7.6 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@floating-ui/utils@0.2.11': {} + '@fontsource/space-grotesk@5.2.10': {} '@humanwhocodes/momoa@2.0.4': {} @@ -6022,6 +6200,17 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@logtape/elysia@2.0.5(@logtape/logtape@2.0.5)(elysia@1.4.28(@sinclair/typebox@0.34.41)(exact-mirror@0.2.5(@sinclair/typebox@0.34.41))(file-type@21.1.1)(openapi-types@12.1.3)(typescript@6.0.2))': + dependencies: + '@logtape/logtape': 2.0.5 + elysia: 1.4.28(@sinclair/typebox@0.34.41)(exact-mirror@0.2.5(@sinclair/typebox@0.34.41))(file-type@21.1.1)(openapi-types@12.1.3)(typescript@6.0.2) + + '@logtape/logtape@2.0.5': {} + + '@logtape/pretty@2.0.5(@logtape/logtape@2.0.5)': + dependencies: + '@logtape/logtape': 2.0.5 + '@mapbox/node-pre-gyp@2.0.0': dependencies: consola: 3.4.2 @@ -7071,6 +7260,13 @@ snapshots: typescript: 6.0.2 zod: 4.3.6 + '@tabler/icons-react@3.41.1(react@19.2.4)': + dependencies: + '@tabler/icons': 3.41.1 + react: 19.2.4 + + '@tabler/icons@3.41.1': {} + '@tailwindcss/node@4.2.2': dependencies: '@jridgewell/remapping': 2.3.5 @@ -8658,8 +8854,6 @@ snapshots: globals@11.12.0: {} - globrex@0.1.2: {} - gonzales-pe@4.3.0: dependencies: minimist: 1.2.8 @@ -8752,7 +8946,7 @@ snapshots: imagetools-core@9.1.0: {} - immutable@5.1.4: + immutable@5.1.5: optional: true import-in-the-middle@1.15.0: @@ -9760,6 +9954,8 @@ snapshots: require-package-name@2.0.1: {} + reselect@5.1.1: {} + resolve-from@5.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -9861,7 +10057,7 @@ snapshots: sass@1.94.0: dependencies: chokidar: 4.0.3 - immutable: 5.1.4 + immutable: 5.1.5 source-map-js: 1.2.1 optionalDependencies: '@parcel/watcher': 2.5.1 @@ -10130,8 +10326,12 @@ snapshots: system-architecture@0.1.0: {} + tabbable@6.4.0: {} + tagged-tag@1.0.0: {} + tailwind-merge@3.5.0: {} + tailwindcss@4.1.18: {} tailwindcss@4.2.2: {} @@ -10236,10 +10436,6 @@ snapshots: dependencies: typescript: 5.9.3 - tsconfck@3.1.6(typescript@6.0.2): - optionalDependencies: - typescript: 6.0.2 - tsconfig-paths@4.2.0: dependencies: json5: 2.2.3 @@ -10350,17 +10546,6 @@ snapshots: transitivePeerDependencies: - rollup - vite-tsconfig-paths@5.1.4(typescript@6.0.2)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.7.1)(@types/node@22.19.1)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.94.0)(tsx@4.20.6)(yaml@2.8.3)): - dependencies: - debug: 4.4.3 - globrex: 0.1.2 - tsconfck: 3.1.6(typescript@6.0.2) - optionalDependencies: - vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.7.1)(@types/node@22.19.1)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.94.0)(tsx@4.20.6)(yaml@2.8.3) - transitivePeerDependencies: - - supports-color - - typescript - vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.7.1)(@types/node@22.19.1)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.94.0)(tsx@4.20.6)(yaml@2.8.3): dependencies: lightningcss: 1.32.0 diff --git a/src/components/footer.tsx b/src/components/footer.tsx new file mode 100644 index 0000000..b4d84fc --- /dev/null +++ b/src/components/footer.tsx @@ -0,0 +1,39 @@ +import { SOCIALS_DATA } from "$data"; + +import ACM_LOGO from "../lib/assets/pec-acm-logo.png"; +import { SocialIcon } from "./social-icon"; + +interface IFooterProps {} + +export function Footer(_props: IFooterProps) { + return ( +
+
+
+
+ + +
+ PEC ACM CSS + © {new Date().getFullYear()} +
+
+ + +
+
+
+ ); +} diff --git a/src/components/navbar/index.tsx b/src/components/navbar/index.tsx new file mode 100644 index 0000000..00e009a --- /dev/null +++ b/src/components/navbar/index.tsx @@ -0,0 +1,131 @@ +import { Separator } from "@base-ui/react"; +import { Drawer } from "@base-ui/react/drawer"; +import { + IconX, + IconGitBranch, + IconLogin, + IconUsersGroup, + IconCalendarWeek, + IconMenu2, +} from "@tabler/icons-react"; +import { Link, useLocation } from "@tanstack/react-router"; + +import { SOCIALS_DATA } from "$data"; + +import { cn } from "$lib/utils"; +import ACM_LOGO from "../../lib/assets/pec-acm-logo.png"; +import { SocialIcon } from "../social-icon"; +import { MobileDrawer } from "./mobile-sheet"; + +const NAVBAR_LINKS = [ + { label: "Team", href: "/team", Icon: IconUsersGroup }, + { label: "Branches", href: "/branches", Icon: IconGitBranch }, + { label: "Events", href: "/events", Icon: IconCalendarWeek }, +]; + +interface INavbarProps {} + +export function Navbar(_props: INavbarProps) { + const { pathname } = useLocation(); + + const drawerHandle = Drawer.createHandle(); + + return ( + <> +
+
+
+ + + + + +
+ + +
+
+ +
+ + + + + + {drawerHandle.isOpen ? : } + +
+ + +
+
+ +
    + {NAVBAR_LINKS.map(link => ( +
  • + + {link.label} + +
  • + ))} +
+ + + + + + + + +
+
+ + ); +} diff --git a/src/components/navbar/mobile-sheet.tsx b/src/components/navbar/mobile-sheet.tsx new file mode 100644 index 0000000..2289e74 --- /dev/null +++ b/src/components/navbar/mobile-sheet.tsx @@ -0,0 +1,24 @@ +import { Drawer } from "@base-ui/react/drawer"; + +interface IMobileDrawerProps { + handle: Drawer.Handle; + children: React.ReactNode; +} + +export function MobileDrawer({ handle, children }: IMobileDrawerProps) { + return ( + + + + + + {children} + + + + + ); +} diff --git a/src/components/social-icon.tsx b/src/components/social-icon.tsx new file mode 100644 index 0000000..7ef4cbc --- /dev/null +++ b/src/components/social-icon.tsx @@ -0,0 +1,30 @@ +import type { TSocialsData } from "$data"; + +import { PLATFORM_ICONS } from "$lib/constants/platforms"; +import { capitalize } from "$lib/utils"; + +interface TSocialIconProps { + link: TSocialsData[number]; +} + +export function SocialIcon({ link }: TSocialIconProps) { + const Icon = PLATFORM_ICONS[link.platform]; + + return ( +
  • + + + +
  • + ); +} diff --git a/src/data/devs.yaml b/src/data/devs.yaml index 4936e82..e88d09a 100644 --- a/src/data/devs.yaml +++ b/src/data/devs.yaml @@ -6,41 +6,41 @@ - Frontend - Documentation links: - - platform: LinkedIn + - platform: linkedin url: "https://www.linkedin.com/in/harmanjeet-sahota/" - - platform: GitHub + - platform: github url: "https://github.com/Savy011" - name: Vidhi Sethi roles: - Co-Lead - Backend links: - - platform: LinkedIn + - platform: linkedin url: "https://www.linkedin.com/in/vidhi-sethi-013962319/" - - platform: GitHub + - platform: github url: "https://github.com/vidhipec27" - name: Keshav Dham avatar: "devs/keshav.jpg" roles: - Documentation links: - - platform: LinkedIn + - platform: linkedin url: "https://www.linkedin.com/in/keshavdham/" - - platform: GitHub + - platform: github url: https://github.com/KD9605 - name: Mithas Janbade roles: - Frontend links: - - platform: LinkedIn + - platform: linkedin url: "https://www.linkedin.com/in/mithas-janbade-glorytogod/" - - platform: GitHub + - platform: github url: "https://github.com/mjisblessed" - name: Matisha Kansal roles: - Frontend links: - - platform: LinkedIn + - platform: linkedin url: "https://www.linkedin.com/in/matisha-kansal/" - - platform: GitHub + - platform: github url: "https://github.com/MatishaKansal" diff --git a/src/data/index.ts b/src/data/index.ts index edf6838..30b9413 100644 --- a/src/data/index.ts +++ b/src/data/index.ts @@ -10,13 +10,14 @@ * */ import z from "zod"; +import { PLATFORMS } from "$lib/constants/platforms"; import { resolveStaticImg, slugify } from "$lib/utils"; import DEVS from "./devs.yaml"; import SOCIALS from "./socials.yaml"; import TEAM from "./team.yaml"; const LinkSchema = z.object({ - platform: z.string(), + platform: z.enum(PLATFORMS), url: z.url(), }); @@ -45,12 +46,7 @@ const TeamSchema = z.object({ groups: z.array(GroupSchema), }); -const SocialsSchema = z.array( - z.object({ - platform: z.string(), - url: z.url(), - }), -); +const SocialsSchema = z.array(LinkSchema); const RoleEnum = z.enum(["Project Lead", "Co-Lead", "Backend", "Frontend", "Documentation"]); diff --git a/src/data/socials.yaml b/src/data/socials.yaml index ff83233..6faefb5 100644 --- a/src/data/socials.yaml +++ b/src/data/socials.yaml @@ -1,14 +1,14 @@ -- platform: Discord +- platform: discord url: https://discord.gg/D68nVDjczA -- platform: Github +- platform: github url: https://github.com/PEC-CSS/ -- platform: Instagram +- platform: instagram url: https://www.instagram.com/pecacm -- platform: Youtube +- platform: youtube url: https://www.youtube.com/c/PECACMStudentChapter -- platform: LinkedIn +- platform: linkedin url: https://www.linkedin.com/company/pec-acm-student-chapter diff --git a/src/data/team.yaml b/src/data/team.yaml index 427ab32..44dd9f2 100644 --- a/src/data/team.yaml +++ b/src/data/team.yaml @@ -3,41 +3,41 @@ core: title: Chairperson avatar: "leads/gurmehar.jpg" links: - - platform: LinkedIn + - platform: linkedin url: "https://www.linkedin.com/in/gurmehar-singh-87636425a/" - - platform: GitHub + - platform: github url: "https://github.com/GURMEHAR-PEC" - - platform: E-Mail + - platform: email url: "mailto:gurmeharsingh.bt22cse@pec.edu.in" - name: Anubhav Pandey title: Vice Chairperson avatar: "leads/anubhav.jpg" links: - - platform: LinkedIn + - platform: linkedin url: "https://www.linkedin.com/in/anubhav3p/" - - platform: GitHub + - platform: github url: "https://github.com/Anubhavpandey27" - - platform: E-Mail + - platform: email url: "mailto:anubhavpandey.bt22cseds@pec.edu.in" - name: Gaurav Gupta title: Treasurer avatar: "leads/gaurav.jpg" links: - - platform: LinkedIn + - platform: linkedin url: "https://www.linkedin.com/in/gaurav-gupta-7801a625b/" - - platform: GitHub + - platform: github url: "https://github.com/GauravGupta993" - - platform: E-Mail + - platform: email url: "mailto:gauravgupta.bt22cse@pec.edu.in" - name: Harshul Arora title: Webmaster avatar: "leads/harshul.jpg" links: - - platform: LinkedIn + - platform: linkedin url: "https://www.linkedin.com/in/harshul-arora-7a264b250/" - - platform: GitHub + - platform: github url: "https://github.com/harshxll" - - platform: E-Mail + - platform: email url: "mailto:harshul.bt22cse@pec.edu.in" groups: @@ -46,16 +46,16 @@ groups: - name: Sambhav Jain avatar: "leads/sambhav.jpg" links: - - platform: LinkedIn + - platform: linkedin url: "https://www.linkedin.com/in/sambhav2j/" - - platform: GitHub + - platform: github url: "https://github.com/SambhavJain74" - name: Chetanya Mahana avatar: "leads/chetanya.jpg" links: - - platform: LinkedIn + - platform: linkedin url: "https://www.linkedin.com/in/chetanya-mahana-212a95258/" - - platform: GitHub + - platform: github url: "https://github.com/chetanyamahana" - name: Artificial Intelligence @@ -63,21 +63,21 @@ groups: - name: Arnav Vikas Garg avatar: "leads/arnav.jpg" links: - - platform: LinkedIn + - platform: linkedin url: "https://www.linkedin.com/in/arnav-vikas-g-6079a2218/" - - platform: GitHub + - platform: github url: "https://github.com/ArnavVikasGarg" - name: Aaditya Sharma avatar: "leads/aaditya.jpg" links: - - platform: LinkedIn + - platform: linkedin url: "https://www.linkedin.com/in/aaditya-sharma-255133257/" - - platform: GitHub + - platform: github url: "https://github.com/aadityasharma911" - name: Hardik Ahuja avatar: "leads/hardik.jpg" links: - - platform: LinkedIn + - platform: linkedin url: "https://www.linkedin.com/in/hardik-ahuja-b4bb07252/" - name: Development @@ -85,16 +85,16 @@ groups: - name: Kritika Jari avatar: "leads/kritika.jpg" links: - - platform: LinkedIn + - platform: linkedin url: "https://www.linkedin.com/in/krittika-jari-676897268/" - - platform: GitHub + - platform: github url: "https://github.com/krittika2004" - name: Harshit Minhas avatar: "leads/harshit.jpg" links: - - platform: LinkedIn + - platform: linkedin url: "https://www.linkedin.com/in/harshit-minhas-879026253/" - - platform: GitHub + - platform: github url: "https://github.com/harshit-m007" - name: Open Source @@ -102,9 +102,9 @@ groups: - name: Raghav Sharma avatar: "leads/raghav.jpg" links: - - platform: LinkedIn + - platform: linkedin url: "https://www.linkedin.com/in/raghav-sharma-0b8785270/" - - platform: GitHub + - platform: github url: "https://github.com/RAGHAVS1304" - name: Cyber-Security @@ -112,16 +112,16 @@ groups: - name: Aryan avatar: "leads/aryan.jpg" links: - - platform: LinkedIn + - platform: linkedin url: "https://www.linkedin.com/in/aryan1006/" - - platform: GitHub + - platform: github url: "https://github.com/ary-a-n" - name: Keshav Arora avatar: "leads/keshav.jpg" links: - - platform: LinkedIn + - platform: linkedin url: "https://www.linkedin.com/in/keshavarorapec/" - - platform: GitHub + - platform: github url: "https://github.com/CoderOMaster" - name: Socials @@ -129,16 +129,16 @@ groups: - name: Prasuk Jain avatar: "leads/prasuk.jpg" links: - - platform: LinkedIn + - platform: linkedin url: "https://www.linkedin.com/in/prasuk-jain-1053b5244/" - - platform: GitHub + - platform: github url: "https://github.com/prasukjain07" - name: Shreya Pahuja avatar: "leads/shreya.jpg" links: - - platform: LinkedIn + - platform: linkedin url: "https://www.linkedin.com/in/sp49/" - - platform: GitHub + - platform: github url: "https://github.com/sp9324" - name: Women In Tech @@ -146,12 +146,12 @@ groups: - name: Akanksha Narula avatar: "leads/akanksha.jpg" links: - - platform: LinkedIn + - platform: linkedin url: "https://www.linkedin.com/in/akanksha-narula-b6ba26249/" - - platform: GitHub + - platform: github url: "https://github.com/AkankshaNarula" - name: Vanshika Garg avatar: "leads/vanshika.jpg" links: - - platform: LinkedIn + - platform: linkedin url: "https://www.linkedin.com/in/vanshika-330333271/" diff --git a/src/env.d.ts b/src/env.d.ts new file mode 100644 index 0000000..73e015a --- /dev/null +++ b/src/env.d.ts @@ -0,0 +1,8 @@ +interface ImportMetaEnv { + readonly COMMIT_HASH: string; + readonly PKG_VERSION: string; +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +} diff --git a/src/lib/api/handlers/auth.ts b/src/lib/api/handlers/auth.ts index a5e3699..d116d8e 100644 --- a/src/lib/api/handlers/auth.ts +++ b/src/lib/api/handlers/auth.ts @@ -9,7 +9,7 @@ import { API_URL, cookieBase } from "$lib/constants/api"; import { BRANCH_NAMES } from "$lib/constants/org"; import { decodeJWTPayload } from "$lib/utils"; import { isErrorResponse } from "../types"; -import { APILoginResponseSchema, AuthenticatedUserSchema, UserSchema } from "../types/schemas"; +import { AuthenticatedUserSchema, UpstreamLoginResponseSchema, UserSchema } from "../types/schemas"; import { createErrorResponse, createResponseSchema, toSuccessResponse } from "../utils/helpers"; import { requireAuth } from "../utils/macros"; @@ -42,8 +42,9 @@ export const api = new Elysia({ prefix: "/auth" }) }); } - const validated = APILoginResponseSchema.parse(response); + const validated = UpstreamLoginResponseSchema.parse(response); const claims = decodeJWTPayload(validated.jwtToken); + if (!claims) return status(500, { success: false, diff --git a/src/lib/api/handlers/health.ts b/src/lib/api/handlers/health.ts new file mode 100644 index 0000000..b4f782a --- /dev/null +++ b/src/lib/api/handlers/health.ts @@ -0,0 +1,119 @@ +import Elysia from "elysia"; +import z from "zod"; + +import { API_URL } from "$lib/constants/api"; + +const ResponseSchema = z + .object({ + status: z.enum(["ok", "degraded"]).meta({ + title: "Status", + description: "Overall health status of the service", + }), + version: z.string().meta({ + title: "Version", + description: "Application version", + examples: ["1.0.0", "2.1.5"], + }), + commit: z.string().meta({ + title: "Commit", + description: "Git commit hash of the current deployment", + examples: ["abc123def456", "f7e9d8c6b5a4"], + }), + timestamp: z.iso.datetime().meta({ + title: "Timestamp", + description: "ISO 8601 timestamp of the health check", + examples: ["2026-04-03T10:30:00Z", "2026-04-03T14:45:30.123Z"], + }), + uptime: z.string().meta({ + title: "Uptime", + description: "Service uptime in minutes", + examples: ["120mins", "1440mins"], + }), + environment: z.enum(["production", "development"]).meta({ + title: "Environment", + description: "Deployment environment", + }), + upstream: z + .object({ + status: z.enum(["ok", "unreachable"]).meta({ + title: "Status", + description: "Upstream service status", + examples: ["ok", "unreachable"], + }), + latency: z + .string() + .nullable() + .meta({ + title: "Latency", + description: "Upstream response latency in milliseconds", + examples: ["45ms", "120ms", null], + }), + }) + .meta({ + title: "Upstream", + description: "Upstream service health details", + }), + }) + .meta({ + $id: "HealthResponse", + title: "Health Response", + description: "Health check response containing service status and upstream server information", + }); + +const base = () => ({ + version: import.meta.env.PKG_VERSION, + commit: import.meta.env.COMMIT_HASH, + timestamp: new Date().toISOString(), + uptime: `${Math.floor(process.uptime() / 60)}mins`, + environment: import.meta.env.DEV ? ("development" as const) : ("production" as const), +}); + +export const api = new Elysia({ prefix: "/health" }) + .model({ + Health: ResponseSchema, + }) + .get( + "/", + async ({ status }) => { + const start = Date.now(); + + try { + const res = await fetch(API_URL.HEALTH.CHECK); + const upstreamLatency = Date.now() - start; + + if (!res.ok) throw new Error("upstream returned non-ok status"); + + return { + ...base(), + status: "ok", + upstream: { + status: "ok", + latency: `${upstreamLatency}ms`, + }, + } as const; + } catch { + return status(503, { + ...base(), + status: "degraded", + upstream: { + status: "unreachable", + latency: null, + }, + } as const); + } + }, + { + response: { 200: "Health", 503: "Health" }, + detail: { + summary: "Health check", + description: `Checks the health status of the service and its upstream dependencies. + +Returns the current service status, version information, and upstream service connectivity. + +**Response Codes:** +- **200**: Service is healthy or degraded but operational +- **503**: Service is unavailable`, + tags: ["Health"], + }, + }, + ); diff --git a/src/lib/api/handlers/user.ts b/src/lib/api/handlers/user.ts index dad0838..f068e1e 100644 --- a/src/lib/api/handlers/user.ts +++ b/src/lib/api/handlers/user.ts @@ -41,7 +41,7 @@ export const api = new Elysia({ prefix: "/user" }) return toSuccessResponse(response); }, { - requireAuth: true, + requireAuth: "Admin", body: z.object({ email: z.email("Please provide a valid email"), newRole: z.string().min(1, "Role is required"), diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts index e37728a..8efca64 100644 --- a/src/lib/api/index.ts +++ b/src/lib/api/index.ts @@ -1,3 +1,4 @@ export { api as auth } from "./handlers/auth"; export { api as events } from "./handlers/events"; +export { api as health } from "./handlers/health"; export { api as user } from "./handlers/user"; diff --git a/src/lib/api/types/schemas.ts b/src/lib/api/types/schemas.ts index a94ec8c..040525e 100644 --- a/src/lib/api/types/schemas.ts +++ b/src/lib/api/types/schemas.ts @@ -1,6 +1,6 @@ import z from "zod"; -import { BranchEnum, DesignationEnum } from "$lib/types"; +import { AcmBranchEnum, DesignationEnum } from "$lib/types"; export const BaseUserSchema = z.object({ id: z.number(), @@ -17,7 +17,7 @@ export const UserSchema = BaseUserSchema.extend({ experience: z.number(), }); -export const APIUserSchema = BaseUserSchema.extend({ +export const UpstreamUserSchema = BaseUserSchema.extend({ dp: z.string().nullable(), password: z.string(), xp: z.number(), @@ -28,16 +28,16 @@ export const AuthenticatedUserSchema = z.object({ user: UserSchema, }); -export const APILoginResponseSchema = z.object({ +export const UpstreamLoginResponseSchema = z.object({ jwtToken: z.string(), - user: APIUserSchema, + user: UpstreamUserSchema, }); // everything can be made optional to support => export const BaseEventSchema = z.object({ id: z.number().optional(), title: z.string().optional(), - branch: BranchEnum.optional(), + branch: AcmBranchEnum.optional(), startDate: z.string().optional(), endDate: z.string().optional(), description: z.string().optional(), diff --git a/src/lib/api/utils/macros.ts b/src/lib/api/utils/macros.ts index 7c07ff1..995075d 100644 --- a/src/lib/api/utils/macros.ts +++ b/src/lib/api/utils/macros.ts @@ -1,10 +1,10 @@ import type { Designation } from "$lib/types"; -import type { SessionUser } from "../types"; import { Elysia, status } from "elysia"; import { hasRole } from "$lib/types"; import { decodeJWTPayload } from "$lib/utils"; +import { UserSchema } from "../types/schemas"; const unauthorized = (message: string) => status(401, { @@ -39,24 +39,24 @@ export const requireAuth = new Elysia({ name: "require-auth" }).macro({ return unauthorized("Your session has expired. Please sign in again."); } - const userData = userCookie.value as SessionUser | undefined; + const { data: parsedUser, success } = UserSchema.safeParse(userCookie.value); - if (!userData) { + if (!success) { session.remove(); return unauthorized("Session data missing. Please sign in again."); } if (enabled !== true) { - if (!hasRole(userData.designation, enabled)) { + if (!hasRole(parsedUser.designation, enabled)) { return forbidden( - `This action requires the '${enabled}' role or above. Your current role is '${userData.designation}'.`, + `This action requires the '${enabled}' role or above. Your current role is '${parsedUser.designation}'.`, ); } } return { token, - user: userData, + user: parsedUser, }; }, }), diff --git a/src/lib/api/utils/plugins.ts b/src/lib/api/utils/plugins.ts new file mode 100644 index 0000000..5a2f737 --- /dev/null +++ b/src/lib/api/utils/plugins.ts @@ -0,0 +1,79 @@ +import { openapi as createOpenAPI } from "@elysiajs/openapi"; +import { elysiaLogger } from "@logtape/elysia"; +import { configure, getConsoleSink } from "@logtape/logtape"; +import { prettyFormatter } from "@logtape/pretty"; +import * as z from "zod"; + +let loggerConfigured = false; + +export const configureLogger = async () => { + if (loggerConfigured) return; + await configure({ + reset: import.meta.env.DEV, + sinks: { + console: getConsoleSink({ + formatter: prettyFormatter, + }), + }, + loggers: [ + { + category: ["logtape", "meta"], + sinks: ["console"], + lowestLevel: "warning", + }, + { category: "elysia", sinks: ["console"], lowestLevel: "info" }, + ], + }); + + loggerConfigured = true; +}; + +export const logger = () => elysiaLogger(); + +export const openapi = createOpenAPI({ + path: "/docs", + documentation: { + info: { + title: "PEC ACM Community Management & Events Platform API", + description: `A REST API for managing community members, events, and user engagement for ACM at Punjab Engineering College. +## Authentication +Most endpoints require JWT authentication. Obtain a token via the \`POST /v1/user/login\` endpoint and include it in the \`Authorization\` header as a Bearer token. + +## Response Format +All responses follow a consistent JSON structure with appropriate HTTP status codes and error messages.`, + version: "1.0.0", + license: { + name: "MIT", + url: "https://github.com/PEC-CSS/acm-website-backend/blob/main/LICENSE", + }, + contact: { + name: "PEC ACM Webmaster", + email: "bruh@example.com", + }, + }, + components: { + securitySchemes: { + bearerAuth: { + type: "http", + scheme: "Bearer", + bearerFormat: "JWT", + }, + }, + }, + tags: [ + { + name: "Authentication", + description: + "Endpoints for user authentication, token verification, and session management. Includes user login, email verification, and ~~password reset~~ operations.", + }, + { + name: "Health", + description: + "Endpoints for monitoring service health and status. Includes health checks, uptime information, and upstream dependency status.", + }, + ], + }, + mapJsonSchema: { + zod: z.toJSONSchema, + }, +}); diff --git a/src/lib/assets/pec-acm-logo.png b/src/lib/assets/pec-acm-logo.png new file mode 100644 index 0000000..47be392 Binary files /dev/null and b/src/lib/assets/pec-acm-logo.png differ diff --git a/src/lib/constants/org.ts b/src/lib/constants/org.ts index 141326e..5a3684e 100644 --- a/src/lib/constants/org.ts +++ b/src/lib/constants/org.ts @@ -1,5 +1,3 @@ -// TODO: Rethink filename. - export const BRANCH_NAMES = [ "CSE", "ECE", diff --git a/src/lib/constants/platforms.ts b/src/lib/constants/platforms.ts new file mode 100644 index 0000000..8dfe424 --- /dev/null +++ b/src/lib/constants/platforms.ts @@ -0,0 +1,30 @@ +import type { TablerIcon } from "@tabler/icons-react"; + +import { + IconMailFilled, + IconBrandLinkedinFilled, + IconBrandDiscordFilled, + IconBrandGithubFilled, + IconBrandInstagramFilled, + IconBrandYoutubeFilled, +} from "@tabler/icons-react"; + +export const PLATFORMS = [ + "discord", + "github", + "instagram", + "youtube", + "linkedin", + "email", +] as const; + +export type TPlatform = (typeof PLATFORMS)[number]; + +export const PLATFORM_ICONS: Record = { + email: IconMailFilled, + linkedin: IconBrandLinkedinFilled, + discord: IconBrandDiscordFilled, + github: IconBrandGithubFilled, + instagram: IconBrandInstagramFilled, + youtube: IconBrandYoutubeFilled, +}; diff --git a/src/lib/env/client.ts b/src/lib/env/client.ts index 76c11ca..232f6b5 100644 --- a/src/lib/env/client.ts +++ b/src/lib/env/client.ts @@ -1,5 +1,4 @@ import { createEnv } from "@t3-oss/env-core"; -// import { z } from "zod"; export const env = createEnv({ clientPrefix: "VITE_", diff --git a/src/lib/fns/auth.ts b/src/lib/fns/auth.ts new file mode 100644 index 0000000..9f73606 --- /dev/null +++ b/src/lib/fns/auth.ts @@ -0,0 +1,20 @@ +import { getTreaty } from "$lib/treaty"; + +export const signinFn = async (email: string, password: string) => { + const api = getTreaty(); + const result = await api.auth["sign-in"].post( + { email, password }, + { fetch: { credentials: "include" } }, + ); + + if (result.error) return { error: result.error.value.message }; + + return { success: true }; +}; + +export const signoutFn = async () => { + const api = getTreaty(); + await api.auth["sign-out"].post({}, { fetch: { credentials: "include" } }); + + return { success: true }; +}; diff --git a/src/lib/server/fns.ts b/src/lib/server/fns.ts index 3302489..e425d2f 100644 --- a/src/lib/server/fns.ts +++ b/src/lib/server/fns.ts @@ -1,11 +1,7 @@ import type { SessionUser } from "$lib/api/types"; import { createServerFn } from "@tanstack/react-start"; -import { deleteCookie, setCookie } from "@tanstack/react-start/server"; -import { cookieBase } from "$lib/constants/api"; -import { decodeJWTPayload } from "$lib/utils"; -import { getTreaty } from "../../routes/api.$"; import { readSession } from "./session"; export const fetchUserFn = createServerFn({ method: "GET" }).handler( @@ -15,35 +11,3 @@ export const fetchUserFn = createServerFn({ method: "GET" }).handler( return session.user; }, ); - -export const signinFn = createServerFn({ method: "POST" }) - .inputValidator((data: { email: string; password: string }) => data) - .handler(async ({ data }) => { - const api = getTreaty(); - - const { data: res, error } = await api.auth["sign-in"].post({ - email: data.email, - password: data.password, - }); - - if (error) return { error: error.value.message }; - - const claims = decodeJWTPayload(res.data.token); - - if (!claims) return { error: "Invalid token received from server" }; - - setCookie("session", res.data.token, cookieBase(claims.exp)); - setCookie("user", JSON.stringify(res.data.user), cookieBase(claims.exp)); - - return { success: true }; - }); - -export const signoutFn = createServerFn({ method: "POST" }).handler(async () => { - const api = getTreaty(); - - await api.auth["sign-out"].post(); - deleteCookie("session"); - deleteCookie("user"); - - return { success: true }; -}); diff --git a/src/lib/treaty.ts b/src/lib/treaty.ts new file mode 100644 index 0000000..4d0ce5f --- /dev/null +++ b/src/lib/treaty.ts @@ -0,0 +1,10 @@ +// src/lib/treaty.ts +import type { App } from "~/routes/api.$"; + +import { treaty } from "@elysiajs/eden"; +import { createIsomorphicFn } from "@tanstack/react-start"; +import { app } from "~/routes/api.$"; + +export const getTreaty = createIsomorphicFn() + .server(() => treaty(app).api) + .client(() => treaty("localhost:3000").api); diff --git a/src/lib/types.ts b/src/lib/types.ts index 8567e14..02dc863 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -2,7 +2,17 @@ import z from "zod"; import { ACM_BRANCHES, DESIGNATIONS } from "./constants/org"; -export const BranchEnum = z.enum(ACM_BRANCHES); +export const AcmBranchEnum = z.enum(ACM_BRANCHES).meta({ + title: "ACM Branch Name", + "x-enumDescriptions": { + DEVELOPMENT: "Development", + AI: "Artificial Intelligence", + CP: "Competitive Programming", + CYBER: "Cyber-Security", + SOCIALS: "Social", + WIT: "Women In Tech", + }, +}); export type Designation = (typeof DESIGNATIONS)[number]; diff --git a/src/lib/utils.ts b/src/lib/utils.ts index bc29a45..7518e4c 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,3 +1,38 @@ +import type { ClassValue } from "clsx"; + +import { clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; + +/** + * Capitalizes the first character of a string. + * Returns the original value if the string is empty. + */ +export function capitalize(str: string) { + if (!str) return str; + return str.charAt(0).toUpperCase() + str.slice(1); +} + +/** + * Converts a string into a URL-friendly slug. + * Lowercases, normalizes unicode (café → cafe), removes special characters, + * and collapses spaces, underscores, and hyphens into a single hyphen. + * + * @example + * slugify("Hello World!") // "hello-world" + * slugify("café latte") // "cafe-latte" + * slugify("hello - world") // "hello-world" + */ +export function slugify(str: string) { + return str + .toLowerCase() + .normalize("NFD") + .replace(/[\u0300-\u036f]/g, "") + .trim() + .replace(/[^\w\s-]/g, "") + .replace(/[\s_-]+/g, "-") + .replace(/^-+|-+$/g, ""); +} + interface JWTPayload { sub: string; exp: number; @@ -13,15 +48,6 @@ export function decodeJWTPayload(token: string): JWTPayload | null { } } -export function slugify(str: string) { - return str - .toLowerCase() - .trim() - .replace(/[^\w\s-]/g, "") - .replace(/[\s_]+/g, "-") - .replace(/^-+|-+$/g, ""); -} - interface IImageMetadata { format: string; width: number; @@ -49,6 +75,16 @@ const allImages = import.meta.glob("/src/lib/assets/images/**/*", { }, }) as Record; +/** + * Resolves a static image asset path and returns its metadata. + * + * Accepts three path formats: + * - `$assets/images/foo.jpg` — alias prefix + * - `/src/lib/assets/images/foo.jpg` — absolute src path + * - `foo.jpg` — bare filename, resolved relative to the images directory + * + * @throws {Error} If the image asset cannot be found in the glob map + */ export function resolveStaticImg(path: string) { const fullPath = path.startsWith("$assets/images/") ? path.replace("$assets/images/", "src/lib/assets/images/") @@ -69,3 +105,18 @@ export function resolveStaticImg(path: string) { src: resolved.src, }; } + +/** + * Merges class names using `clsx` and resolves Tailwind CSS conflicts using `tailwind-merge`. + * + * @param inputs - Class values to merge. Accepts strings, objects, arrays, and falsy values. + * @returns A single merged class name string with Tailwind conflicts resolved. + * + * @example + * cn("p-4", "p-2") // "p-2" (conflict resolved) + * cn("flex", { hidden: false, block: true }) // "flex block" + * cn(undefined, null, "text-sm") // "text-sm" + */ +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts index bfc476c..201cf87 100644 --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -9,20 +9,24 @@ // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. import { Route as rootRouteImport } from './routes/__root' -import { Route as AuthedRouteImport } from './routes/_authed' -import { Route as IndexRouteImport } from './routes/index' +import { Route as appRouteRouteImport } from './routes/(app)/route' +import { Route as appIndexRouteImport } from './routes/(app)/index' import { Route as ApiSplatRouteImport } from './routes/api.$' import { Route as authSignInRouteImport } from './routes/(auth)/sign-in' -import { Route as AuthedDashboardIndexRouteImport } from './routes/_authed/dashboard/index' +import { Route as appTeamRouteImport } from './routes/(app)/team' +import { Route as appEventsRouteImport } from './routes/(app)/events' +import { Route as appBranchesRouteImport } from './routes/(app)/branches' +import { Route as appAuthedRouteRouteImport } from './routes/(app)/_authed/route' +import { Route as appAuthedDashboardIndexRouteImport } from './routes/(app)/_authed/dashboard/index' -const AuthedRoute = AuthedRouteImport.update({ - id: '/_authed', +const appRouteRoute = appRouteRouteImport.update({ + id: '/(app)', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ +const appIndexRoute = appIndexRouteImport.update({ id: '/', path: '/', - getParentRoute: () => rootRouteImport, + getParentRoute: () => appRouteRoute, } as any) const ApiSplatRoute = ApiSplatRouteImport.update({ id: '/api/$', @@ -34,68 +38,114 @@ const authSignInRoute = authSignInRouteImport.update({ path: '/sign-in', getParentRoute: () => rootRouteImport, } as any) -const AuthedDashboardIndexRoute = AuthedDashboardIndexRouteImport.update({ +const appTeamRoute = appTeamRouteImport.update({ + id: '/team', + path: '/team', + getParentRoute: () => appRouteRoute, +} as any) +const appEventsRoute = appEventsRouteImport.update({ + id: '/events', + path: '/events', + getParentRoute: () => appRouteRoute, +} as any) +const appBranchesRoute = appBranchesRouteImport.update({ + id: '/branches', + path: '/branches', + getParentRoute: () => appRouteRoute, +} as any) +const appAuthedRouteRoute = appAuthedRouteRouteImport.update({ + id: '/_authed', + getParentRoute: () => appRouteRoute, +} as any) +const appAuthedDashboardIndexRoute = appAuthedDashboardIndexRouteImport.update({ id: '/dashboard/', path: '/dashboard/', - getParentRoute: () => AuthedRoute, + getParentRoute: () => appAuthedRouteRoute, } as any) export interface FileRoutesByFullPath { - '/': typeof IndexRoute + '/branches': typeof appBranchesRoute + '/events': typeof appEventsRoute + '/team': typeof appTeamRoute '/sign-in': typeof authSignInRoute '/api/$': typeof ApiSplatRoute - '/dashboard': typeof AuthedDashboardIndexRoute + '/': typeof appIndexRoute + '/dashboard/': typeof appAuthedDashboardIndexRoute } export interface FileRoutesByTo { - '/': typeof IndexRoute + '/branches': typeof appBranchesRoute + '/events': typeof appEventsRoute + '/team': typeof appTeamRoute '/sign-in': typeof authSignInRoute '/api/$': typeof ApiSplatRoute - '/dashboard': typeof AuthedDashboardIndexRoute + '/': typeof appIndexRoute + '/dashboard': typeof appAuthedDashboardIndexRoute } export interface FileRoutesById { __root__: typeof rootRouteImport - '/': typeof IndexRoute - '/_authed': typeof AuthedRouteWithChildren + '/(app)': typeof appRouteRouteWithChildren + '/(app)/_authed': typeof appAuthedRouteRouteWithChildren + '/(app)/branches': typeof appBranchesRoute + '/(app)/events': typeof appEventsRoute + '/(app)/team': typeof appTeamRoute '/(auth)/sign-in': typeof authSignInRoute '/api/$': typeof ApiSplatRoute - '/_authed/dashboard/': typeof AuthedDashboardIndexRoute + '/(app)/': typeof appIndexRoute + '/(app)/_authed/dashboard/': typeof appAuthedDashboardIndexRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/' | '/sign-in' | '/api/$' | '/dashboard' + fullPaths: + | '/branches' + | '/events' + | '/team' + | '/sign-in' + | '/api/$' + | '/' + | '/dashboard/' fileRoutesByTo: FileRoutesByTo - to: '/' | '/sign-in' | '/api/$' | '/dashboard' + to: + | '/branches' + | '/events' + | '/team' + | '/sign-in' + | '/api/$' + | '/' + | '/dashboard' id: | '__root__' - | '/' - | '/_authed' + | '/(app)' + | '/(app)/_authed' + | '/(app)/branches' + | '/(app)/events' + | '/(app)/team' | '/(auth)/sign-in' | '/api/$' - | '/_authed/dashboard/' + | '/(app)/' + | '/(app)/_authed/dashboard/' fileRoutesById: FileRoutesById } export interface RootRouteChildren { - IndexRoute: typeof IndexRoute - AuthedRoute: typeof AuthedRouteWithChildren + appRouteRoute: typeof appRouteRouteWithChildren authSignInRoute: typeof authSignInRoute ApiSplatRoute: typeof ApiSplatRoute } declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/_authed': { - id: '/_authed' + '/(app)': { + id: '/(app)' path: '' fullPath: '' - preLoaderRoute: typeof AuthedRouteImport + preLoaderRoute: typeof appRouteRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' + '/(app)/': { + id: '/(app)/' path: '/' fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport + preLoaderRoute: typeof appIndexRouteImport + parentRoute: typeof appRouteRoute } '/api/$': { id: '/api/$' @@ -111,30 +161,78 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof authSignInRouteImport parentRoute: typeof rootRouteImport } - '/_authed/dashboard/': { - id: '/_authed/dashboard/' + '/(app)/team': { + id: '/(app)/team' + path: '/team' + fullPath: '/team' + preLoaderRoute: typeof appTeamRouteImport + parentRoute: typeof appRouteRoute + } + '/(app)/events': { + id: '/(app)/events' + path: '/events' + fullPath: '/events' + preLoaderRoute: typeof appEventsRouteImport + parentRoute: typeof appRouteRoute + } + '/(app)/branches': { + id: '/(app)/branches' + path: '/branches' + fullPath: '/branches' + preLoaderRoute: typeof appBranchesRouteImport + parentRoute: typeof appRouteRoute + } + '/(app)/_authed': { + id: '/(app)/_authed' + path: '' + fullPath: '' + preLoaderRoute: typeof appAuthedRouteRouteImport + parentRoute: typeof appRouteRoute + } + '/(app)/_authed/dashboard/': { + id: '/(app)/_authed/dashboard/' path: '/dashboard' - fullPath: '/dashboard' - preLoaderRoute: typeof AuthedDashboardIndexRouteImport - parentRoute: typeof AuthedRoute + fullPath: '/dashboard/' + preLoaderRoute: typeof appAuthedDashboardIndexRouteImport + parentRoute: typeof appAuthedRouteRoute } } } -interface AuthedRouteChildren { - AuthedDashboardIndexRoute: typeof AuthedDashboardIndexRoute +interface appAuthedRouteRouteChildren { + appAuthedDashboardIndexRoute: typeof appAuthedDashboardIndexRoute +} + +const appAuthedRouteRouteChildren: appAuthedRouteRouteChildren = { + appAuthedDashboardIndexRoute: appAuthedDashboardIndexRoute, +} + +const appAuthedRouteRouteWithChildren = appAuthedRouteRoute._addFileChildren( + appAuthedRouteRouteChildren, +) + +interface appRouteRouteChildren { + appAuthedRouteRoute: typeof appAuthedRouteRouteWithChildren + appBranchesRoute: typeof appBranchesRoute + appEventsRoute: typeof appEventsRoute + appTeamRoute: typeof appTeamRoute + appIndexRoute: typeof appIndexRoute } -const AuthedRouteChildren: AuthedRouteChildren = { - AuthedDashboardIndexRoute: AuthedDashboardIndexRoute, +const appRouteRouteChildren: appRouteRouteChildren = { + appAuthedRouteRoute: appAuthedRouteRouteWithChildren, + appBranchesRoute: appBranchesRoute, + appEventsRoute: appEventsRoute, + appTeamRoute: appTeamRoute, + appIndexRoute: appIndexRoute, } -const AuthedRouteWithChildren = - AuthedRoute._addFileChildren(AuthedRouteChildren) +const appRouteRouteWithChildren = appRouteRoute._addFileChildren( + appRouteRouteChildren, +) const rootRouteChildren: RootRouteChildren = { - IndexRoute: IndexRoute, - AuthedRoute: AuthedRouteWithChildren, + appRouteRoute: appRouteRouteWithChildren, authSignInRoute: authSignInRoute, ApiSplatRoute: ApiSplatRoute, } diff --git a/src/routes/_authed/dashboard/index.tsx b/src/routes/(app)/_authed/dashboard/index.tsx similarity index 82% rename from src/routes/_authed/dashboard/index.tsx rename to src/routes/(app)/_authed/dashboard/index.tsx index 1a973c6..af71d75 100644 --- a/src/routes/_authed/dashboard/index.tsx +++ b/src/routes/(app)/_authed/dashboard/index.tsx @@ -1,8 +1,8 @@ import { createFileRoute, useRouter } from "@tanstack/react-router"; -import { signoutFn } from "$lib/server/fns"; +import { signoutFn } from "$lib/fns/auth"; -export const Route = createFileRoute("/_authed/dashboard/")({ +export const Route = createFileRoute("/(app)/_authed/dashboard/")({ component: RouteComponent, }); diff --git a/src/routes/_authed.tsx b/src/routes/(app)/_authed/route.tsx similarity index 85% rename from src/routes/_authed.tsx rename to src/routes/(app)/_authed/route.tsx index 9f602ef..54ee694 100644 --- a/src/routes/_authed.tsx +++ b/src/routes/(app)/_authed/route.tsx @@ -1,6 +1,6 @@ import { createFileRoute, Outlet, redirect } from "@tanstack/react-router"; -export const Route = createFileRoute("/_authed")({ +export const Route = createFileRoute("/(app)/_authed")({ beforeLoad: ({ context, location }) => { if (!context.user) { throw redirect({ diff --git a/src/routes/(app)/branches.tsx b/src/routes/(app)/branches.tsx new file mode 100644 index 0000000..79c0b83 --- /dev/null +++ b/src/routes/(app)/branches.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from "@tanstack/react-router"; + +export const Route = createFileRoute("/(app)/branches")({ + component: RouteComponent, +}); + +function RouteComponent() { + return
    Hello "/branches"!
    ; +} diff --git a/src/routes/(app)/events.tsx b/src/routes/(app)/events.tsx new file mode 100644 index 0000000..e6fc79b --- /dev/null +++ b/src/routes/(app)/events.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from "@tanstack/react-router"; + +export const Route = createFileRoute("/(app)/events")({ + component: RouteComponent, +}); + +function RouteComponent() { + return
    Hello "/events"!
    ; +} diff --git a/src/routes/index.tsx b/src/routes/(app)/index.tsx similarity index 78% rename from src/routes/index.tsx rename to src/routes/(app)/index.tsx index 85b51f4..fa17063 100644 --- a/src/routes/index.tsx +++ b/src/routes/(app)/index.tsx @@ -1,6 +1,6 @@ import { createFileRoute, Link } from "@tanstack/react-router"; -export const Route = createFileRoute("/")({ component: App }); +export const Route = createFileRoute("/(app)/")({ component: App }); function App() { return ( diff --git a/src/routes/(app)/route.tsx b/src/routes/(app)/route.tsx new file mode 100644 index 0000000..2252e3b --- /dev/null +++ b/src/routes/(app)/route.tsx @@ -0,0 +1,22 @@ +import { createFileRoute, Outlet } from "@tanstack/react-router"; + +import { Footer } from "$components/footer"; +import { Navbar } from "$components/navbar"; + +export const Route = createFileRoute("/(app)")({ + component: RouteComponent, +}); + +function RouteComponent() { + return ( + <> + +
    +
    + +
    +
    +