diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..6918b05 --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,93 @@ +# Design System Document: High-End Editorial Job Board + +## 1. Overview & Creative North Star +**Creative North Star: "The Architectural Curator"** + +This design system rejects the "commodity" look of traditional job boards. It is built on the principle of **Architectural Curation**—treating job listings not as database entries, but as editorial features. By utilizing high-contrast typography, intentional asymmetry, and deep tonal layering, we create a workspace that feels authoritative, premium, and calm. + +The system breaks the "template" aesthetic by favoring breathing room over density and using light as a functional tool rather than just an aesthetic choice. It is designed for the high-performing professional who values efficiency through visual clarity. + +--- + +## 2. Colors +Our palette is anchored in deep obsidian tones, punctuated by a high-energy primary yellow that acts as a beacon for action. + +* **Primary Palette:** + * `primary`: #ffdd73 (High-energy yellow for core actions) + * `on-primary`: #624e00 (Deep contrast for legibility on yellow) + * `background`: #0e0e0e (Deep charcoal/black foundation) +* **Surface Hierarchy:** + * `surface-container-low`: #131313 + * `surface-container`: #1a1a1a + * `surface-container-high`: #20201f +* **Status Indicators:** + * `Applied`: Blue (via `tertiary` logic) + * `Accepted`: Green + * `Error`: #ff7351 (`error`) + +### The "No-Line" Rule +Standard 1px borders are strictly prohibited for sectioning. Structural boundaries must be achieved through **Background Color Shifts**. For example, a `surface-container-high` card sits on a `surface-container-low` background. This creates a "shadow-less" depth that feels modern and integrated. + +### The "Glass & Gradient" Rule +To elevate the experience, floating headers or navigation bars should use **Glassmorphism**. Use semi-transparent surface colors with a `backdrop-blur(20px)`. Main CTAs should utilize a subtle linear gradient from `primary` to `primary-dim` to provide a tactile, "lit-from-within" feel. + +--- + +## 3. Typography +We use a dual-typeface system to balance editorial authority with functional utility. + +* **Display & Headlines (Manrope):** These are the "Editorial" voice. Use `display-lg` (3.5rem) for hero sections and `headline-md` (1.75rem) for section titles. Bold weights are mandatory for headers to maintain the high-contrast signature look. +* **Body & Labels (Inter):** These are the "Functional" voice. `body-md` (0.875rem) is the workhorse for job descriptions. Inter’s high x-height ensures maximum legibility against dark backgrounds. +* **Hierarchy as Identity:** Use `title-lg` (1.375rem) for job titles in lists. The scale jump between headlines and body text is intentional—it mimics luxury magazine layouts, guiding the eye to the most critical information first. + +--- + +## 4. Elevation & Depth +Depth in this system is a result of light physics, not artificial decoration. + +* **Tonal Layering:** Always stack from darkest to lightest. + * *Level 0:* `surface` (Main background) + * *Level 1:* `surface-container-low` (Content sections) + * *Level 2:* `surface-container-highest` (Interactive cards/modals) +* **Ambient Shadows:** If an element must "float" (like a Toast or Floating Action Button), use a shadow with a blur radius of at least `24px` and an opacity no higher than `8%`. Use a tinted shadow (blending `on-surface` with the background) to avoid a "dirty" grey appearance. +* **The "Ghost Border" Fallback:** If high-density data requires containment, use a **Ghost Border**. Apply `outline-variant` at 15% opacity. It should be felt, not seen. + +--- + +## 5. Components + +### Buttons +* **Primary:** Solid `primary` fill, `on-primary` text. Radius: `md` (0.75rem). Bold `label-md` text. +* **Secondary:** Ghost style. `outline` border (20% opacity) with `on-surface` text. +* **Glass Action:** Semi-transparent `surface-bright` with backdrop blur for secondary utility actions. + +### Chips (Status & Tags) +* **Status Chips:** Use a muted version of the status color for the background (e.g., 20% opacity) with a high-contrast and solid text. +* **Filter Chips:** Use `surface-variant` with a radius of `full` (9999px) for a soft, pill-shaped aesthetic. + +### Cards & Lists +* **Rule:** Forbid the use of divider lines. +* **Separation:** Use `spacing-8` (2rem) of vertical white space or shift the `surface-container` tier. +* **Interactive Cards:** On hover, a card should transition from `surface-container-low` to `surface-container-high`. + +### Input Fields +* **Style:** Minimalist. No bottom border. Use a `surface-container-highest` background with a subtle radius of `sm` (0.25rem). +* **Active State:** The border should only appear on focus, using a 1px `primary` line. + +### Job Specific: "Quick-Action" Bar +A specialized component for the job board—a sticky footer or side-rail for "Apply Now" and "Save" actions using the **Glassmorphism** rule to keep the user grounded in the content while providing immediate utility. + +--- + +## 6. Do's and Don'ts + +### Do: +* **Do** use asymmetrical spacing to create visual interest (e.g., more padding on the left than the right in editorial headers). +* **Do** use `primary` yellow sparingly. It is a "laser pointer," not a bucket of paint. +* **Do** ensure all text on dark backgrounds meets a minimum 4.5:1 contrast ratio for accessibility. + +### Don't: +* **Don't** use 100% opaque, high-contrast borders. They "trap" the content and break the fluid editorial feel. +* **Don't** use standard "Drop Shadows" (0, 2, 4, 0). They look dated and cheap. +* **Don't** use dividers between list items. Trust the white space and background shifts to do the work. +* **Don't** mix the font roles. Never use Manrope for body copy; it’s too "loud" for long-form reading. \ No newline at end of file diff --git a/frontend/.env.example b/frontend/.env.example new file mode 100644 index 0000000..6403b41 --- /dev/null +++ b/frontend/.env.example @@ -0,0 +1,11 @@ +MONGODB_URI= +MONGODB_DATABASE=default + +# Auth.js (NextAuth) +NEXTAUTH_SECRET= +NEXTAUTH_URL=http://localhost:3000 + +# OAuth providers +GOOGLE_CLIENT_ID= +GOOGLE_CLIENT_SECRET= + diff --git a/frontend/package-lock.json b/frontend/package-lock.json index dbf255c..9fd7831 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,7 @@ "name": "frontend", "version": "0.1.0", "dependencies": { + "@auth/mongodb-adapter": "^3.11.1", "@mantine/core": "^7.17.0", "@mantine/hooks": "^7.16.1", "@mantine/notifications": "^7.17.1", @@ -16,13 +17,14 @@ "@tailwindcss/typography": "^0.5.16", "@vercel/analytics": "^1.5.0", "@vercel/speed-insights": "^1.2.0", + "bcryptjs": "^3.0.3", "dompurify": "^3.2.3", "isomorphic-dompurify": "^2.22.0", "jsdom": "^26.0.0", "lru-cache": "^11.2.2", - "mongodb": "^6.14.2", "next": "15.1.7", + "next-auth": "^4.24.13", "pino": "^9.11.0", "pino-pretty": "^13.1.1", "react": "^19.0.0", @@ -76,6 +78,155 @@ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "license": "ISC" }, + "node_modules/@auth/core": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.34.3.tgz", + "integrity": "sha512-jMjY/S0doZnWYNV90x0jmU3B+UcrsfGYnukxYrRbj0CVvGI/MX3JbHsxSrx2d4mbnXaUsqJmAcDfoQWA6r0lOw==", + "license": "ISC", + "optional": true, + "peer": true, + "dependencies": { + "@panva/hkdf": "^1.1.1", + "@types/cookie": "0.6.0", + "cookie": "0.6.0", + "jose": "^5.1.3", + "oauth4webapi": "^2.10.4", + "preact": "10.11.3", + "preact-render-to-string": "5.2.3" + }, + "peerDependencies": { + "@simplewebauthn/browser": "^9.0.1", + "@simplewebauthn/server": "^9.0.2", + "nodemailer": "^7" + }, + "peerDependenciesMeta": { + "@simplewebauthn/browser": { + "optional": true + }, + "@simplewebauthn/server": { + "optional": true + }, + "nodemailer": { + "optional": true + } + } + }, + "node_modules/@auth/core/node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@auth/core/node_modules/jose": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz", + "integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==", + "license": "MIT", + "optional": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/@auth/core/node_modules/preact": { + "version": "10.11.3", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz", + "integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==", + "license": "MIT", + "optional": true, + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/@auth/core/node_modules/preact-render-to-string": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.3.tgz", + "integrity": "sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "pretty-format": "^3.8.0" + }, + "peerDependencies": { + "preact": ">=10" + } + }, + "node_modules/@auth/mongodb-adapter": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@auth/mongodb-adapter/-/mongodb-adapter-3.11.1.tgz", + "integrity": "sha512-xY+VUkC3CNXct8UwQgBAQqXASqolSlIARg6oAm1378CtRN2650tQUCOEnGLNLmroVefUeP73M6t+TpGXq72vwQ==", + "license": "ISC", + "dependencies": { + "@auth/core": "0.41.1" + }, + "peerDependencies": { + "mongodb": "^6" + } + }, + "node_modules/@auth/mongodb-adapter/node_modules/@auth/core": { + "version": "0.41.1", + "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.41.1.tgz", + "integrity": "sha512-t9cJ2zNYAdWMacGRMT6+r4xr1uybIdmYa49calBPeTqwgAFPV/88ac9TEvCR85pvATiSPt8VaNf+Gt24JIT/uw==", + "license": "ISC", + "dependencies": { + "@panva/hkdf": "^1.2.1", + "jose": "^6.0.6", + "oauth4webapi": "^3.3.0", + "preact": "10.24.3", + "preact-render-to-string": "6.5.11" + }, + "peerDependencies": { + "@simplewebauthn/browser": "^9.0.1", + "@simplewebauthn/server": "^9.0.2", + "nodemailer": "^7.0.7" + }, + "peerDependenciesMeta": { + "@simplewebauthn/browser": { + "optional": true + }, + "@simplewebauthn/server": { + "optional": true + }, + "nodemailer": { + "optional": true + } + } + }, + "node_modules/@auth/mongodb-adapter/node_modules/jose": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.2.tgz", + "integrity": "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/@auth/mongodb-adapter/node_modules/oauth4webapi": { + "version": "3.8.5", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.5.tgz", + "integrity": "sha512-A8jmyUckVhRJj5lspguklcl90Ydqk61H3dcU0oLhH3Yv13KpAliKTt5hknpGGPZSSfOwGyraNEFmofDYH+1kSg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/@auth/mongodb-adapter/node_modules/preact-render-to-string": { + "version": "6.5.11", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.5.11.tgz", + "integrity": "sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==", + "license": "MIT", + "peerDependencies": { + "preact": ">=10" + } + }, "node_modules/@babel/runtime": { "version": "7.26.7", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.7.tgz", @@ -1155,6 +1306,15 @@ "node": ">=12.4.0" } }, + "node_modules/@panva/hkdf": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz", + "integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -1248,6 +1408,14 @@ "node": ">=4" } }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -1979,6 +2147,15 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/bcryptjs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz", + "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==", + "license": "BSD-3-Clause", + "bin": { + "bcrypt": "bin/bcrypt" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -2268,6 +2445,15 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2593,18 +2779,6 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/es-abstract": { "version": "1.23.9", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", @@ -4369,6 +4543,15 @@ "jiti": "bin/jiti.js" } }, + "node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/joycon": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", @@ -4846,6 +5029,38 @@ } } }, + "node_modules/next-auth": { + "version": "4.24.13", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.13.tgz", + "integrity": "sha512-sgObCfcfL7BzIK76SS5TnQtc3yo2Oifp/yIpfv6fMfeBOiBJkDWF3A2y9+yqnmJ4JKc2C+nMjSjmgDeTwgN1rQ==", + "license": "ISC", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@panva/hkdf": "^1.0.2", + "cookie": "^0.7.0", + "jose": "^4.15.5", + "oauth": "^0.9.15", + "openid-client": "^5.4.0", + "preact": "^10.6.3", + "preact-render-to-string": "^5.1.19", + "uuid": "^8.3.2" + }, + "peerDependencies": { + "@auth/core": "0.34.3", + "next": "^12.2.5 || ^13 || ^14 || ^15 || ^16", + "nodemailer": "^7.0.7", + "react": "^17.0.2 || ^18 || ^19", + "react-dom": "^17.0.2 || ^18 || ^19" + }, + "peerDependenciesMeta": { + "@auth/core": { + "optional": true + }, + "nodemailer": { + "optional": true + } + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -4889,6 +5104,23 @@ "integrity": "sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==", "license": "MIT" }, + "node_modules/oauth": { + "version": "0.9.15", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", + "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==", + "license": "MIT" + }, + "node_modules/oauth4webapi": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-2.17.0.tgz", + "integrity": "sha512-lbC0Z7uzAFNFyzEYRIC+pkSVvDHJTbEW+dYlSBAlCYDe6RxUkJ26bClhk8ocBZip1wfI9uKTe0fm4Ib4RHn6uQ==", + "license": "MIT", + "optional": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -5019,6 +5251,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/oidc-token-hash": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.2.0.tgz", + "integrity": "sha512-6gj2m8cJZ+iSW8bm0FXdGF0YhIQbKrfP4yWTNzxc31U6MOjfEmB1rHvlYvxI1B7t7BCi1F2vYTT6YhtQRG4hxw==", + "license": "MIT", + "engines": { + "node": "^10.13.0 || >=12.0.0" + } + }, "node_modules/on-exit-leak-free": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", @@ -5037,6 +5278,42 @@ "wrappy": "1" } }, + "node_modules/openid-client": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.7.1.tgz", + "integrity": "sha512-jDBPgSVfTnkIh71Hg9pRvtJc6wTwqjRkN88+gCFtYWrlP4Yx2Dsrow8uPi3qLr/aeymPF3o2+dS+wOpglK04ew==", + "license": "MIT", + "dependencies": { + "jose": "^4.15.9", + "lru-cache": "^6.0.0", + "object-hash": "^2.2.0", + "oidc-token-hash": "^5.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/openid-client/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/openid-client/node_modules/object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -5499,6 +5776,28 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, + "node_modules/preact": { + "version": "10.24.3", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz", + "integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/preact-render-to-string": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz", + "integrity": "sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==", + "license": "MIT", + "dependencies": { + "pretty-format": "^3.8.0" + }, + "peerDependencies": { + "preact": ">=10" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -5525,6 +5824,12 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/pretty-format": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", + "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==", + "license": "MIT" + }, "node_modules/process-warning": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", @@ -7052,6 +7357,15 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", @@ -7350,6 +7664,12 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "license": "MIT" }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, "node_modules/yaml": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 13f22ab..d3fb225 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,6 +10,7 @@ "format": "npx prettier . --write" }, "dependencies": { + "@auth/mongodb-adapter": "^3.11.1", "@mantine/core": "^7.17.0", "@mantine/hooks": "^7.16.1", "@mantine/notifications": "^7.17.1", @@ -18,12 +19,14 @@ "@tailwindcss/typography": "^0.5.16", "@vercel/analytics": "^1.5.0", "@vercel/speed-insights": "^1.2.0", + "bcryptjs": "^3.0.3", "dompurify": "^3.2.3", "isomorphic-dompurify": "^2.22.0", "jsdom": "^26.0.0", "lru-cache": "^11.2.2", "mongodb": "^6.14.2", "next": "15.1.7", + "next-auth": "^4.24.13", "pino": "^9.11.0", "pino-pretty": "^13.1.1", "react": "^19.0.0", diff --git a/frontend/src/app/api/auth/[...nextauth]/route.ts b/frontend/src/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 0000000..6d4ddf6 --- /dev/null +++ b/frontend/src/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,7 @@ +import NextAuth from "next-auth"; +import { authOptions } from "@/lib/auth"; + +const handler = NextAuth(authOptions); + +export { handler as GET, handler as POST }; + diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index c40b579..fa79548 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -39,8 +39,7 @@ } html, -body, -main { +body { height: 100svh; margin: 0; padding: 0; diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index dbaf664..eec2cb2 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -22,6 +22,7 @@ import FeedbackButton from "@/components/ui/feedback-button"; import { Notifications } from "@mantine/notifications"; import FirstVisitNotification from "@/components/ui/first-visit-notification"; +import AuthSessionProvider from "@/components/auth/session-provider"; export const metadata: Metadata = { title: { @@ -53,22 +54,24 @@ export default function RootLayout({ children }: PropsWithChildren) {