diff --git a/.env.example b/.env.example index 3c75490..bb898d3 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,3 @@ DATABASE_URI=postgres://postgres:@127.0.0.1:5432/your-database-name PAYLOAD_SECRET=YOUR_SECRET_HERE +PORT=3000 diff --git a/README.md b/README.md index 4a2dde6..398bd01 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,11 @@ -# Payload Blank Template - -This template comes configured with the bare minimum to get started on anything you need. - -## Quick start - -This template can be deployed directly from our Cloud hosting and it will setup MongoDB and cloud S3 object storage for media. - -## Quick Start - local setup - -To spin up this template locally, follow these steps: - -### Clone - -After you click the `Deploy` button above, you'll want to have standalone copy of this repo on your machine. If you've already cloned this repo, skip to [Development](#development). +# Payload Personal Portfolio ### Development -1. First [clone the repo](#clone) if you have not done so already -2. `cd my-project && cp .env.example .env` to copy the example environment variables. You'll need to add the `MONGODB_URI` from your Cloud project to your `.env` if you want to use S3 storage and the MongoDB database that was created for you. +1. `cp .env.example .env` to copy the example environment variables. +2. `npm install && npm run dev` to install dependencies and start the dev server +3. open `http://localhost:{process.env.PORT}` to open the app in your browser -3. `pnpm install && pnpm dev` to install dependencies and start the dev server -4. open `http://localhost:3000` to open the app in your browser - -That's it! Changes made in `./src` will be reflected in your app. Follow the on-screen instructions to login and create your first admin user. Then check out [Production](#production) once you're ready to build and serve your app, and [Deployment](#deployment) when you're ready to go live. - -#### Docker (Optional) - -If you prefer to use Docker for local development instead of a local MongoDB instance, the provided docker-compose.yml file can be used. - -To do so, follow these steps: - -- Modify the `MONGODB_URI` in your `.env` file to `mongodb://127.0.0.1/` -- Modify the `docker-compose.yml` file's `MONGODB_URI` to match the above `` -- Run `docker-compose up` to start the database, optionally pass `-d` to run in the background. ## How it works @@ -62,6 +35,4 @@ Alternatively, you can use [Docker](https://www.docker.com) to spin up this temp That's it! The Docker instance will help you get up and running quickly while also standardizing the development environment across your teams. -## Questions -If you have any issues or questions, reach out to us on [Discord](https://discord.com/invite/payload) or start a [GitHub discussion](https://github.com/payloadcms/payload/discussions). diff --git a/components.json b/components.json new file mode 100644 index 0000000..664d9b9 --- /dev/null +++ b/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "src/app/globals.css", + "baseColor": "stone", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 3aba7cc..8de77a7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,32 +1,30 @@ -version: '3' - services: payload: image: node:18-alpine ports: - - '3000:3000' + - "${PORT}:${PORT}" volumes: - .:/home/node/app - node_modules:/home/node/app/node_modules working_dir: /home/node/app/ command: sh -c "corepack enable && corepack prepare pnpm@latest --activate && pnpm install && pnpm dev" - depends_on: - - mongo + # depends_on: + # - mongo # - postgres env_file: - .env # Ensure your DATABASE_URI uses 'mongo' as the hostname ie. mongodb://mongo/my-db-name - mongo: - image: mongo:latest - ports: - - '27017:27017' - command: - - --storageEngine=wiredTiger - volumes: - - data:/data/db - logging: - driver: none + # mongo: + # image: mongo:latest + # ports: + # - '27017:27017' + # command: + # - --storageEngine=wiredTiger + # volumes: + # - data:/data/db + # logging: + # driver: none # Uncomment the following to use postgres # postgres: diff --git a/eslint.config.mjs b/eslint.config.mjs index 37d29ea..80f037d 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -15,7 +15,7 @@ const eslintConfig = [ rules: { '@typescript-eslint/ban-ts-comment': 'warn', '@typescript-eslint/no-empty-object-type': 'warn', - '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-unused-vars': [ 'warn', { diff --git a/package-lock.json b/package-lock.json index 6360124..d2e5199 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,22 +13,38 @@ "@payloadcms/next": "3.33.0", "@payloadcms/payload-cloud": "3.33.0", "@payloadcms/richtext-lexical": "3.33.0", + "@radix-ui/react-avatar": "^1.1.7", + "@radix-ui/react-icons": "^1.3.2", + "@radix-ui/react-separator": "^1.1.4", + "@radix-ui/react-tooltip": "^1.2.4", + "@tailwindcss/postcss": "^4.1.4", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", "cross-env": "^7.0.3", "graphql": "^16.8.1", + "lucide-react": "^0.503.0", + "motion": "^12.7.4", "next": "15.3.0", + "next-themes": "^0.4.6", "payload": "3.33.0", "react": "19.1.0", "react-dom": "19.1.0", - "sharp": "0.32.6" + "react-markdown": "^10.1.0", + "sharp": "0.32.6", + "tailwind-merge": "^3.2.0", + "tw-animate-css": "^1.2.8" }, "devDependencies": { "@eslint/eslintrc": "^3.2.0", "@types/node": "^22.5.4", "@types/react": "19.1.0", "@types/react-dom": "19.1.2", + "autoprefixer": "^10.4.21", "eslint": "^9.16.0", "eslint-config-next": "15.3.0", + "postcss": "^8.5.3", "prettier": "^3.4.2", + "tailwindcss": "^4.1.4", "typescript": "5.7.3" }, "engines": { @@ -36,6 +52,18 @@ "pnpm": "^9 || ^10" } }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@apidevtools/json-schema-ref-parser": { "version": "11.9.3", "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.9.3.tgz", @@ -1240,7 +1268,6 @@ "version": "1.4.3", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz", "integrity": "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -1262,7 +1289,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.2.tgz", "integrity": "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -3238,7 +3264,6 @@ "version": "0.2.9", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.9.tgz", "integrity": "sha512-OKRBiajrrxB9ATokgEQoG87Z25c67pCpYcCwmXYX8PBftC9pBfN18gnm/fh1wurSLEKIAt+QRFLFCQISrb66Jg==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -3660,6 +3685,492 @@ "react-dom": "^19.0.0 || ^19.0.0-rc-65a56d0e-20241020" } }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz", + "integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.4.tgz", + "integrity": "sha512-qz+fxrqgNxG0dYew5l7qR3c7wdgRu1XVUHGnGYX7rg5HM4p9SWaRmJwfgR3J0SgyUKayLmzQIun+N6rWRgiRKw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-avatar": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.7.tgz", + "integrity": "sha512-V7ODUt4mUoJTe3VUxZw6nfURxaPALVqmDQh501YmaQsk3D8AZQrOPRnfKn4H7JGDLBc0KqLhT94H79nV88ppNg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.0", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-is-hydrated": "0.1.0", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.7.tgz", + "integrity": "sha512-j5+WBUdhccJsmH5/H0K6RncjDtoALSEr6jbkaZu+bjw6hOPOhHycr6vEUujl+HBK8kjUfWcoCJXxP6e4lUlMZw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.0", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-icons": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.2.tgz", + "integrity": "sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==", + "license": "MIT", + "peerDependencies": { + "react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.4.tgz", + "integrity": "sha512-3p2Rgm/a1cK0r/UVkx5F/K9v/EplfjAeIFCGOPYPO4lZ0jtg4iSQXt/YGTSLWaf4x7NG6Z4+uKFcylcTZjeqDA==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.0", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.6.tgz", + "integrity": "sha512-XmsIl2z1n/TsYFLIdYam2rmFwf9OC/Sh2avkbmVMDuBZIe7hSpM0cYnWPAo7nHOVx8zTuwDZGByfcqLdnzp3Vw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.0", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.4.tgz", + "integrity": "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.0.tgz", + "integrity": "sha512-/J/FhLdK0zVcILOwt5g+dH4KnkonCtkVJsa2G6JmvbbtZfBEI1gMsO3QMjseL4F/SwfAMt1Vc/0XKYKq+xJ1sw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.4.tgz", + "integrity": "sha512-2fTm6PSiUm8YPq9W0E4reYuv01EE3aFSzt8edBiXqPHshF8N9+Kymt/k0/R+F3dkY5lQyB/zPtrP82phskLi7w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.0.tgz", + "integrity": "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.4.tgz", + "integrity": "sha512-DyW8VVeeMSSLFvAmnVnCwvI3H+1tpJFHT50r+tdOoMse9XqYDBCcyux8u3G2y+LOpt7fPQ6KKH0mhs+ce1+Z5w==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.4", + "@radix-ui/react-portal": "1.1.6", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.0", + "@radix-ui/react-slot": "1.2.0", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-visually-hidden": "1.2.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-is-hydrated": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz", + "integrity": "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.5.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.0.tgz", + "integrity": "sha512-rQj0aAWOpCdCMRbI6pLQm8r7S2BM3YhTa0SzOYD55k+hJA8oo9J+H+9wLM9oMlZWOX/wJWPTzfDfmZkf7LvCfg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", + "license": "MIT" + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -4419,6 +4930,259 @@ "tslib": "^2.8.0" } }, + "node_modules/@tailwindcss/node": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.4.tgz", + "integrity": "sha512-MT5118zaiO6x6hNA04OWInuAiP1YISXql8Z+/Y8iisV5nuhM8VXlyhRuqc2PEviPszcXI66W44bCIk500Oolhw==", + "license": "MIT", + "dependencies": { + "enhanced-resolve": "^5.18.1", + "jiti": "^2.4.2", + "lightningcss": "1.29.2", + "tailwindcss": "4.1.4" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.4.tgz", + "integrity": "sha512-p5wOpXyOJx7mKh5MXh5oKk+kqcz8T+bA3z/5VWWeQwFrmuBItGwz8Y2CHk/sJ+dNb9B0nYFfn0rj/cKHZyjahQ==", + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.4", + "@tailwindcss/oxide-darwin-arm64": "4.1.4", + "@tailwindcss/oxide-darwin-x64": "4.1.4", + "@tailwindcss/oxide-freebsd-x64": "4.1.4", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.4", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.4", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.4", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.4", + "@tailwindcss/oxide-linux-x64-musl": "4.1.4", + "@tailwindcss/oxide-wasm32-wasi": "4.1.4", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.4", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.4" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.4.tgz", + "integrity": "sha512-xMMAe/SaCN/vHfQYui3fqaBDEXMu22BVwQ33veLc8ep+DNy7CWN52L+TTG9y1K397w9nkzv+Mw+mZWISiqhmlA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.4.tgz", + "integrity": "sha512-JGRj0SYFuDuAGilWFBlshcexev2hOKfNkoX+0QTksKYq2zgF9VY/vVMq9m8IObYnLna0Xlg+ytCi2FN2rOL0Sg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.4.tgz", + "integrity": "sha512-sdDeLNvs3cYeWsEJ4H1DvjOzaGios4QbBTNLVLVs0XQ0V95bffT3+scptzYGPMjm7xv4+qMhCDrkHwhnUySEzA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.4.tgz", + "integrity": "sha512-VHxAqxqdghM83HslPhRsNhHo91McsxRJaEnShJOMu8mHmEj9Ig7ToHJtDukkuLWLzLboh2XSjq/0zO6wgvykNA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.4.tgz", + "integrity": "sha512-OTU/m/eV4gQKxy9r5acuesqaymyeSCnsx1cFto/I1WhPmi5HDxX1nkzb8KYBiwkHIGg7CTfo/AcGzoXAJBxLfg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.4.tgz", + "integrity": "sha512-hKlLNvbmUC6z5g/J4H+Zx7f7w15whSVImokLPmP6ff1QqTVE+TxUM9PGuNsjHvkvlHUtGTdDnOvGNSEUiXI1Ww==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.4.tgz", + "integrity": "sha512-X3As2xhtgPTY/m5edUtddmZ8rCruvBvtxYLMw9OsZdH01L2gS2icsHRwxdU0dMItNfVmrBezueXZCHxVeeb7Aw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.4.tgz", + "integrity": "sha512-2VG4DqhGaDSmYIu6C4ua2vSLXnJsb/C9liej7TuSO04NK+JJJgJucDUgmX6sn7Gw3Cs5ZJ9ZLrnI0QRDOjLfNQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.4.tgz", + "integrity": "sha512-v+mxVgH2kmur/X5Mdrz9m7TsoVjbdYQT0b4Z+dr+I4RvreCNXyCFELZL/DO0M1RsidZTrm6O1eMnV6zlgEzTMQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.4.tgz", + "integrity": "sha512-2TLe9ir+9esCf6Wm+lLWTMbgklIjiF0pbmDnwmhR9MksVOq+e8aP3TSsXySnBDDvTTVd/vKu1aNttEGj3P6l8Q==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.0", + "@emnapi/runtime": "^1.4.0", + "@emnapi/wasi-threads": "^1.0.1", + "@napi-rs/wasm-runtime": "^0.2.8", + "@tybys/wasm-util": "^0.9.0", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.4.tgz", + "integrity": "sha512-VlnhfilPlO0ltxW9/BgfLI5547PYzqBMPIzRrk4W7uupgCt8z6Trw/tAj6QUtF2om+1MH281Pg+HHUJoLesmng==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.4.tgz", + "integrity": "sha512-+7S63t5zhYjslUGb8NcgLpFXD+Kq1F/zt5Xv5qTv7HaFTG/DHyHD9GA6ieNAxhgyA4IcKa/zy7Xx4Oad2/wuhw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.4.tgz", + "integrity": "sha512-bjV6sqycCEa+AQSt2Kr7wpGF1bOZJ5wsqnLEkqSbM/JEHxx/yhMH8wHmdkPyApF9xhHeMSwnnkDUUMMM/hYnXw==", + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.4", + "@tailwindcss/oxide": "4.1.4", + "postcss": "^8.4.41", + "tailwindcss": "4.1.4" + } + }, "node_modules/@tokenizer/token": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", @@ -4429,7 +5193,6 @@ "version": "0.9.0", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -4824,6 +5587,12 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, "node_modules/@unrs/resolver-binding-darwin-arm64": { "version": "1.6.4", "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.6.4.tgz", @@ -5377,6 +6146,44 @@ "node": ">=8.0.0" } }, + "node_modules/autoprefixer": { + "version": "10.4.21", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -5434,6 +6241,16 @@ "npm": ">=6" } }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -5591,6 +6408,39 @@ "node": ">=8" } }, + "node_modules/browserslist": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/bson-objectid": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/bson-objectid/-/bson-objectid-2.0.4.tgz", @@ -5845,6 +6695,18 @@ "node": ">=8" } }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, "node_modules/classnames": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", @@ -5913,6 +6775,16 @@ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "license": "MIT" }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -6429,6 +7301,13 @@ "node": ">= 0.4" } }, + "node_modules/electron-to-chromium": { + "version": "1.5.140", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.140.tgz", + "integrity": "sha512-o82Rj+ONp4Ip7Cl1r7lrqx/pXhbp/lh9DpKcMNscFJdh8ebyRofnc7Sh01B4jx403RI0oqTBvlZ7OBIZLMr2+Q==", + "dev": true, + "license": "ISC" + }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -6445,6 +7324,19 @@ "once": "^1.4.0" } }, + "node_modules/enhanced-resolve": { + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", + "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -6678,6 +7570,16 @@ "esbuild": ">=0.12 <1" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -7177,6 +8079,12 @@ "node": ">=6" } }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, "node_modules/fast-base64-decode": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fast-base64-decode/-/fast-base64-decode-1.0.0.tgz", @@ -7433,6 +8341,47 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/framer-motion": { + "version": "12.7.4", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.7.4.tgz", + "integrity": "sha512-jX0bPsTmU0oPZTYz/dVyD0dmOyEOEJvdn0TaZBE5I8g2GvVnnQnW9f65cJnoVfUkY3WZWNXGXnPbVA9YnaIfVA==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.7.4", + "motion-utils": "^12.7.2", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -7624,6 +8573,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -7772,6 +8727,46 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/help-me": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", @@ -7787,6 +8782,16 @@ "react-is": "^16.7.0" } }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/http-status": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/http-status/-/http-status-2.1.0.tgz", @@ -7885,6 +8890,12 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "license": "ISC" }, + "node_modules/inline-style-parser": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", + "license": "MIT" + }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -8207,6 +9218,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -8393,6 +9416,15 @@ "node": ">= 0.4" } }, + "node_modules/jiti": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, "node_modules/jose": { "version": "5.9.6", "resolved": "https://registry.npmjs.org/jose/-/jose-5.9.6.tgz", @@ -8594,6 +9626,234 @@ "integrity": "sha512-dLE3O1PZg0TlZxRQo9YDpjCjDUj8zluGyBO9MHdjo21qZmMUNrxQPeCRt8fn2s5l4HKYFQ1YNgl7k1pOJB/vZQ==", "license": "MIT" }, + "node_modules/lightningcss": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.2.tgz", + "integrity": "sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-darwin-arm64": "1.29.2", + "lightningcss-darwin-x64": "1.29.2", + "lightningcss-freebsd-x64": "1.29.2", + "lightningcss-linux-arm-gnueabihf": "1.29.2", + "lightningcss-linux-arm64-gnu": "1.29.2", + "lightningcss-linux-arm64-musl": "1.29.2", + "lightningcss-linux-x64-gnu": "1.29.2", + "lightningcss-linux-x64-musl": "1.29.2", + "lightningcss-win32-arm64-msvc": "1.29.2", + "lightningcss-win32-x64-msvc": "1.29.2" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.29.2.tgz", + "integrity": "sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.29.2.tgz", + "integrity": "sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.29.2.tgz", + "integrity": "sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.29.2.tgz", + "integrity": "sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.29.2.tgz", + "integrity": "sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.29.2.tgz", + "integrity": "sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.29.2.tgz", + "integrity": "sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.29.2.tgz", + "integrity": "sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.29.2.tgz", + "integrity": "sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.29.2.tgz", + "integrity": "sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -8651,6 +9911,15 @@ "loose-envify": "cli.js" } }, + "node_modules/lucide-react": { + "version": "0.503.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.503.0.tgz", + "integrity": "sha512-HGGkdlPWQ0vTF8jJ5TdIqhQXZi6uh3LnNgfZ8MHiuxFfX3RZeA79r2MW2tHAZKlAVfoNE8esm3p+O6VkIvpj6w==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -8696,6 +9965,24 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdast-util-mdx-jsx": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.1.3.tgz", @@ -8720,6 +10007,24 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdast-util-phrasing": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", @@ -8734,6 +10039,27 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdast-util-to-markdown": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", @@ -9355,6 +10681,47 @@ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "license": "MIT" }, + "node_modules/motion": { + "version": "12.7.4", + "resolved": "https://registry.npmjs.org/motion/-/motion-12.7.4.tgz", + "integrity": "sha512-MBGrMbYageHw4iZJn+pGTr7abq5n53jCxYkhFC1It3vYukQPRWg5zij46MnwYGpLR8KG465MLHSASXot9edYOw==", + "license": "MIT", + "dependencies": { + "framer-motion": "^12.7.4", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/motion-dom": { + "version": "12.7.4", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.7.4.tgz", + "integrity": "sha512-1ZUHAoSUMMxP6jPqyxlk9XUfb6NxMsnWPnH2YGhrOhTURLcXWbETi6eemoKb60Pe32NVJYduL4B62VQSO5Jq8Q==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.7.2" + } + }, + "node_modules/motion-utils": { + "version": "12.7.2", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.7.2.tgz", + "integrity": "sha512-XhZwqctxyJs89oX00zn3OGCuIIpVevbTa+u82usWBC6pSHUd2AoNWiYa7Du8tJxJy9TFbZ82pcn5t7NOm1PHAw==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -9462,6 +10829,44 @@ } } }, + "node_modules/next-themes": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", + "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/next/node_modules/sharp": { "version": "0.34.1", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.1.tgz", @@ -9541,6 +10946,13 @@ } } }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, "node_modules/nodemailer": { "version": "6.9.16", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.16.tgz", @@ -9559,6 +10971,16 @@ "node": ">=0.10.0" } }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -10229,9 +11651,9 @@ } }, "node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", "funding": [ { "type": "opencollective", @@ -10248,14 +11670,21 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, "node_modules/postgres-array": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.4.tgz", @@ -10429,6 +11858,16 @@ "react-is": "^16.13.1" } }, + "node_modules/property-information": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.0.0.tgz", + "integrity": "sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/pump": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", @@ -10620,6 +12059,33 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, "node_modules/react-select": { "version": "5.9.0", "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.9.0.tgz", @@ -10742,6 +12208,39 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -11261,6 +12760,16 @@ "node": ">=0.10.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/split2": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", @@ -11501,6 +13010,24 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/style-to-js": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.16.tgz", + "integrity": "sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.8" + } + }, + "node_modules/style-to-object": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz", + "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.4" + } + }, "node_modules/styled-jsx": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", @@ -11561,6 +13088,31 @@ "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", "license": "MIT" }, + "node_modules/tailwind-merge": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.2.0.tgz", + "integrity": "sha512-FQT/OVqCD+7edmmJpsgCsY820RTD5AkBryuG5IUqR5YQZSdj5xlH5nLgH7YPths7WsLPSpSBNneJdM8aS8aeFA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.4.tgz", + "integrity": "sha512-1ZIUqtPITFbv/DxRmDr5/agPqJwF69d24m9qmM1939TJehgY539CtzeZRjbLt5G6fSy/7YqqYsfvoTEw9xUI2A==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/tar-fs": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.8.tgz", @@ -11691,6 +13243,26 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "license": "MIT" }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/truncate-utf8-bytes": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", @@ -12184,6 +13756,15 @@ "node": "*" } }, + "node_modules/tw-animate-css": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.2.8.tgz", + "integrity": "sha512-AxSnYRvyFnAiZCUndS3zQZhNfV/B77ZhJ+O7d3K6wfg/jKJY+yv6ahuyXwnyaYA9UdLqnpCwhTRv9pPTBnPR2g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Wombosvideo" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -12332,6 +13913,25 @@ "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==", "license": "MIT" }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/unist-util-is": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", @@ -12345,6 +13945,19 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/unist-util-position-from-estree": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", @@ -12432,6 +14045,37 @@ "@unrs/resolver-binding-win32-x64-msvc": "1.6.4" } }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -12466,6 +14110,15 @@ } } }, + "node_modules/use-sync-external-store": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/utf8-byte-length": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", @@ -12491,6 +14144,20 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/vfile-message": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", diff --git a/package.json b/package.json index 7f98890..15f1e4c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ysandler-work", "version": "1.0.0", - "description": "A blank template to get started with Payload 3.0", + "description": "Personal Portfolio Site", "license": "MIT", "type": "module", "scripts": { @@ -15,26 +15,42 @@ "start": "cross-env NODE_OPTIONS=--no-deprecation next start" }, "dependencies": { + "@payloadcms/db-postgres": "3.33.0", "@payloadcms/next": "3.33.0", "@payloadcms/payload-cloud": "3.33.0", "@payloadcms/richtext-lexical": "3.33.0", + "@radix-ui/react-avatar": "^1.1.7", + "@radix-ui/react-icons": "^1.3.2", + "@radix-ui/react-separator": "^1.1.4", + "@radix-ui/react-tooltip": "^1.2.4", + "@tailwindcss/postcss": "^4.1.4", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", "cross-env": "^7.0.3", "graphql": "^16.8.1", + "lucide-react": "^0.503.0", + "motion": "^12.7.4", "next": "15.3.0", + "next-themes": "^0.4.6", "payload": "3.33.0", "react": "19.1.0", "react-dom": "19.1.0", + "react-markdown": "^10.1.0", "sharp": "0.32.6", - "@payloadcms/db-postgres": "3.33.0" + "tailwind-merge": "^3.2.0", + "tw-animate-css": "^1.2.8" }, "devDependencies": { "@eslint/eslintrc": "^3.2.0", "@types/node": "^22.5.4", "@types/react": "19.1.0", "@types/react-dom": "19.1.2", + "autoprefixer": "^10.4.21", "eslint": "^9.16.0", "eslint-config-next": "15.3.0", + "postcss": "^8.5.3", "prettier": "^3.4.2", + "tailwindcss": "^4.1.4", "typescript": "5.7.3" }, "engines": { diff --git a/postcss.config.mjs b/postcss.config.mjs new file mode 100644 index 0000000..317d71c --- /dev/null +++ b/postcss.config.mjs @@ -0,0 +1,6 @@ +const config = { + plugins: { + '@tailwindcss/postcss': {}, + }, +} +export default config diff --git a/src/app/(frontend)/[slug]/page.client.tsx b/src/app/(frontend)/[slug]/page.client.tsx new file mode 100644 index 0000000..ccfa286 --- /dev/null +++ b/src/app/(frontend)/[slug]/page.client.tsx @@ -0,0 +1,8 @@ +'use client' +import React from 'react' + +const PageClient: React.FC = () => { + return +} + +export default PageClient diff --git a/src/app/(frontend)/[slug]/page.tsx b/src/app/(frontend)/[slug]/page.tsx new file mode 100644 index 0000000..9934a2d --- /dev/null +++ b/src/app/(frontend)/[slug]/page.tsx @@ -0,0 +1,87 @@ +import configPromise from '@payload-config' +import { getPayload, type RequiredDataFromCollectionSlug } from 'payload' +import { draftMode } from "next/headers"; +import { cache } from "react"; +import { RenderBlocks } from "@/blocks/RenderBlocks"; +import PageClient from "./page.client"; + + +export async function generateStaticParams() { + const payload = await getPayload({ config: configPromise }) + const pages = await payload.find({ + collection: 'pages', + draft: false, + limit: 1000, + overrideAccess: false, + pagination: false, + select: { + slug: true, + }, + }) + + const params = pages.docs + ?.filter((doc) => { + return doc.slug !== 'home' + }) + .map(({ slug }) => { + return { slug } + }) + + return params +} + +const queryPageBySlug = cache(async ({ slug }: { slug: string }) => { + const { isEnabled: draft } = await draftMode() + + const payload = await getPayload({ config: configPromise }) + + const result = await payload.find({ + collection: 'pages', + draft, + limit: 1, + pagination: false, + overrideAccess: draft, + where: { + slug: { + equals: slug, + }, + }, + }) + + return result.docs?.[0] || null +}) + +type Args = { + params: Promise<{ + slug?: string + }> +} + +export default async function Page({ params: paramsPromise }: Args) { + const { slug = 'home' } = await paramsPromise + + //const { isEnabled: draft } = await draftMode() + //const url = '/' + slug + + const page: RequiredDataFromCollectionSlug<'pages'> | null = await queryPageBySlug({ + slug, + }) || null + + if (!page) { + return
// return + } + + const { layout } = page + + return ( +
+ + {/* Allows redirects for valid pages too */} + {/**/} + {/*draft && */} + + +
+ ) +} + diff --git a/src/app/(frontend)/layout.tsx b/src/app/(frontend)/layout.tsx index e7681f7..ccde032 100644 --- a/src/app/(frontend)/layout.tsx +++ b/src/app/(frontend)/layout.tsx @@ -1,18 +1,82 @@ -import React from 'react' -import './styles.css' +import { ThemeProvider } from '@/components/theme-provider' +import { TooltipProvider } from '@/components/ui/tooltip' +import { cn } from '@/lib/utils' +import type { Metadata } from 'next' +import { Inter as FontSans } from 'next/font/google' +import { getPayload } from 'payload' +import configPromise from '@payload-config' +import Navbar from '@/globals/Nav/component' -export const metadata = { - description: 'A blank template using Payload in a Next.js app.', - title: 'Payload Blank Template', +const fontSans = FontSans({ + subsets: ['latin'], + variable: '--font-sans', +}) + +const payload = await getPayload({ config: configPromise }) + +const metaProps = await payload.findGlobal({ + slug: 'meta', +}) + +const navProps = await payload.findGlobal({ + slug: 'nav', +}) + +export const metadata: Metadata = { + metadataBase: new URL(metaProps.url || ''), + title: { + default: metaProps.name || '', + template: `%s | ${metaProps.name}`, + }, + description: metaProps.description, + openGraph: { + title: `${metaProps.name}`, + description: metaProps.description || '', + url: metaProps.url || '', + siteName: `${metaProps.name}`, + locale: 'en_US', + type: 'website', + }, + robots: { + index: true, + follow: true, + googleBot: { + index: true, + follow: true, + 'max-video-preview': -1, + 'max-image-preview': 'large', + 'max-snippet': -1, + }, + }, + twitter: { + title: `${metaProps.name}`, + card: 'summary_large_image', + }, + verification: { + google: `${metaProps.googleVerification}`, + yandex: '', + }, } -export default async function RootLayout(props: { children: React.ReactNode }) { - const { children } = props - +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode +}>) { return ( - - -
{children}
+ + + + +
{children}
+ +
+
) diff --git a/src/app/(frontend)/page.tsx b/src/app/(frontend)/page.tsx index d0e8d64..87d789e 100644 --- a/src/app/(frontend)/page.tsx +++ b/src/app/(frontend)/page.tsx @@ -1,59 +1,3 @@ -import { headers as getHeaders } from 'next/headers.js' -import Image from 'next/image' -import { getPayload } from 'payload' -import React from 'react' -import { fileURLToPath } from 'url' +import PageTemplate from './[slug]/page' -import config from '@/payload.config' -import './styles.css' - -export default async function HomePage() { - const headers = await getHeaders() - const payloadConfig = await config - const payload = await getPayload({ config: payloadConfig }) - const { user } = await payload.auth({ headers }) - - const fileURL = `vscode://file/${fileURLToPath(import.meta.url)}` - - return ( -
-
- - - Payload Logo - - {!user &&

Welcome to your new project.

} - {user &&

Welcome back, {user.email}

} - -
-
-

Update this page by editing

- - app/(frontend)/page.tsx - -
-
- ) -} +export default PageTemplate diff --git a/src/app/(frontend)/styles.css b/src/app/(frontend)/styles.css index d1fb941..8b13789 100644 --- a/src/app/(frontend)/styles.css +++ b/src/app/(frontend)/styles.css @@ -1,164 +1 @@ -:root { - --font-mono: 'Roboto Mono', monospace; -} -* { - box-sizing: border-box; -} - -html { - font-size: 18px; - line-height: 32px; - - background: rgb(0, 0, 0); - -webkit-font-smoothing: antialiased; -} - -html, -body, -#app { - height: 100%; -} - -body { - font-family: system-ui; - font-size: 18px; - line-height: 32px; - - margin: 0; - color: rgb(1000, 1000, 1000); - - @media (max-width: 1024px) { - font-size: 15px; - line-height: 24px; - } -} - -img { - max-width: 100%; - height: auto; - display: block; -} - -h1 { - margin: 40px 0; - font-size: 64px; - line-height: 70px; - font-weight: bold; - - @media (max-width: 1024px) { - margin: 24px 0; - font-size: 42px; - line-height: 42px; - } - - @media (max-width: 768px) { - font-size: 38px; - line-height: 38px; - } - - @media (max-width: 400px) { - font-size: 32px; - line-height: 32px; - } -} - -p { - margin: 24px 0; - - @media (max-width: 1024px) { - margin: calc(var(--base) * 0.75) 0; - } -} - -a { - color: currentColor; - - &:focus { - opacity: 0.8; - outline: none; - } - - &:active { - opacity: 0.7; - outline: none; - } -} - -svg { - vertical-align: middle; -} - -.home { - display: flex; - flex-direction: column; - justify-content: space-between; - align-items: center; - height: 100vh; - padding: 45px; - max-width: 1024px; - margin: 0 auto; - overflow: hidden; - - @media (max-width: 400px) { - padding: 24px; - } - - .content { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - flex-grow: 1; - - h1 { - text-align: center; - } - } - - .links { - display: flex; - align-items: center; - gap: 12px; - - a { - text-decoration: none; - padding: 0.25rem 0.5rem; - border-radius: 4px; - } - - .admin { - color: rgb(0, 0, 0); - background: rgb(1000, 1000, 1000); - border: 1px solid rgb(0, 0, 0); - } - - .docs { - color: rgb(1000, 1000, 1000); - background: rgb(0, 0, 0); - border: 1px solid rgb(1000, 1000, 1000); - } - } - - .footer { - display: flex; - align-items: center; - gap: 8px; - - @media (max-width: 1024px) { - flex-direction: column; - gap: 6px; - } - - p { - margin: 0; - } - - .codeLink { - text-decoration: none; - padding: 0 0.5rem; - background: rgb(60, 60, 60); - border-radius: 4px; - } - } -} diff --git a/src/app/(payload)/admin/importMap.js b/src/app/(payload)/admin/importMap.js index 8ef7021..03758d4 100644 --- a/src/app/(payload)/admin/importMap.js +++ b/src/app/(payload)/admin/importMap.js @@ -1 +1,37 @@ -export const importMap = {} +import { RscEntryLexicalCell as RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc' +import { RscEntryLexicalField as RscEntryLexicalField_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc' +import { LexicalDiffComponent as LexicalDiffComponent_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc' +import { BlocksFeatureClient as BlocksFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' +import { LinkFeatureClient as LinkFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' +import { HeadingFeatureClient as HeadingFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' +import { InlineCodeFeatureClient as InlineCodeFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' +import { IndentFeatureClient as IndentFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' +import { OrderedListFeatureClient as OrderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' +import { UnorderedListFeatureClient as UnorderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' +import { BlockquoteFeatureClient as BlockquoteFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' +import { AlignFeatureClient as AlignFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' +import { BoldFeatureClient as BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' +import { ItalicFeatureClient as ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' +import { UnderlineFeatureClient as UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' +import { ParagraphFeatureClient as ParagraphFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' +import { FixedToolbarFeatureClient as FixedToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' + +export const importMap = { + "@payloadcms/richtext-lexical/rsc#RscEntryLexicalCell": RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e, + "@payloadcms/richtext-lexical/rsc#RscEntryLexicalField": RscEntryLexicalField_44fe37237e0ebf4470c9990d8cb7b07e, + "@payloadcms/richtext-lexical/rsc#LexicalDiffComponent": LexicalDiffComponent_44fe37237e0ebf4470c9990d8cb7b07e, + "@payloadcms/richtext-lexical/client#BlocksFeatureClient": BlocksFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, + "@payloadcms/richtext-lexical/client#LinkFeatureClient": LinkFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, + "@payloadcms/richtext-lexical/client#HeadingFeatureClient": HeadingFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, + "@payloadcms/richtext-lexical/client#InlineCodeFeatureClient": InlineCodeFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, + "@payloadcms/richtext-lexical/client#IndentFeatureClient": IndentFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, + "@payloadcms/richtext-lexical/client#OrderedListFeatureClient": OrderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, + "@payloadcms/richtext-lexical/client#UnorderedListFeatureClient": UnorderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, + "@payloadcms/richtext-lexical/client#BlockquoteFeatureClient": BlockquoteFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, + "@payloadcms/richtext-lexical/client#AlignFeatureClient": AlignFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, + "@payloadcms/richtext-lexical/client#BoldFeatureClient": BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, + "@payloadcms/richtext-lexical/client#ItalicFeatureClient": ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, + "@payloadcms/richtext-lexical/client#UnderlineFeatureClient": UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, + "@payloadcms/richtext-lexical/client#ParagraphFeatureClient": ParagraphFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, + "@payloadcms/richtext-lexical/client#FixedToolbarFeatureClient": FixedToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 +} diff --git a/src/app/(payload)/layout.tsx b/src/app/(payload)/layout.tsx index 8df141a..57dd4e7 100644 --- a/src/app/(payload)/layout.tsx +++ b/src/app/(payload)/layout.tsx @@ -23,9 +23,13 @@ const serverFunction: ServerFunctionClient = async function (args) { } const Layout = ({ children }: Args) => ( + + {children} + + ) export default Layout diff --git a/src/app/globals.css b/src/app/globals.css new file mode 100644 index 0000000..83bc82f --- /dev/null +++ b/src/app/globals.css @@ -0,0 +1,120 @@ +@import "tailwindcss"; +@import "tw-animate-css"; + +@custom-variant dark (&:is(.dark *)); + +@theme inline { + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); +} + +:root { + --radius: 0.625rem; + --background: oklch(1 0 0); + --foreground: oklch(0.147 0.004 49.25); + --card: oklch(1 0 0); + --card-foreground: oklch(0.147 0.004 49.25); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.147 0.004 49.25); + --primary: oklch(0.216 0.006 56.043); + --primary-foreground: oklch(0.985 0.001 106.423); + --secondary: oklch(0.97 0.001 106.424); + --secondary-foreground: oklch(0.216 0.006 56.043); + --muted: oklch(0.97 0.001 106.424); + --muted-foreground: oklch(0.553 0.013 58.071); + --accent: oklch(0.97 0.001 106.424); + --accent-foreground: oklch(0.216 0.006 56.043); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.923 0.003 48.717); + --input: oklch(0.923 0.003 48.717); + --ring: oklch(0.709 0.01 56.259); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.985 0.001 106.423); + --sidebar-foreground: oklch(0.147 0.004 49.25); + --sidebar-primary: oklch(0.216 0.006 56.043); + --sidebar-primary-foreground: oklch(0.985 0.001 106.423); + --sidebar-accent: oklch(0.97 0.001 106.424); + --sidebar-accent-foreground: oklch(0.216 0.006 56.043); + --sidebar-border: oklch(0.923 0.003 48.717); + --sidebar-ring: oklch(0.709 0.01 56.259); +} + +.dark { + --background: oklch(0.147 0.004 49.25); + --foreground: oklch(0.985 0.001 106.423); + --card: oklch(0.216 0.006 56.043); + --card-foreground: oklch(0.985 0.001 106.423); + --popover: oklch(0.216 0.006 56.043); + --popover-foreground: oklch(0.985 0.001 106.423); + --primary: oklch(0.923 0.003 48.717); + --primary-foreground: oklch(0.216 0.006 56.043); + --secondary: oklch(0.268 0.007 34.298); + --secondary-foreground: oklch(0.985 0.001 106.423); + --muted: oklch(0.268 0.007 34.298); + --muted-foreground: oklch(0.709 0.01 56.259); + --accent: oklch(0.268 0.007 34.298); + --accent-foreground: oklch(0.985 0.001 106.423); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.553 0.013 58.071); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.216 0.006 56.043); + --sidebar-foreground: oklch(0.985 0.001 106.423); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0.001 106.423); + --sidebar-accent: oklch(0.268 0.007 34.298); + --sidebar-accent-foreground: oklch(0.985 0.001 106.423); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.553 0.013 58.071); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx new file mode 100644 index 0000000..5610aa8 --- /dev/null +++ b/src/app/layout.tsx @@ -0,0 +1,12 @@ +import { ReactNode } from 'react' +import './globals.css' + +type LayoutProps = { + children: ReactNode +} + +const Layout = ({ children }: LayoutProps) => { + return children +} + +export default Layout diff --git a/src/blocks/BadgeList/component.tsx b/src/blocks/BadgeList/component.tsx new file mode 100644 index 0000000..705ba6f --- /dev/null +++ b/src/blocks/BadgeList/component.tsx @@ -0,0 +1,33 @@ +import { Badge } from '@/components/ui/badge' +import BlurFade from '@/components/ui/blur-fade' +import { BadgeListBlock as BadgeListProps } from '@/payload-types' +import clsx from 'clsx' + +const BLUR_FADE_DELAY = 0.04 + +type Props = { className?: string } & BadgeListProps +export const BadgeList = (props: Props) => { + return ( +
+
+ {!props.shouldHideListHeader && ( + +

{props.listHeader}

+
+ )} +
+ {props.badges?.map((b, id) => ( + + + {b.value} + + + ))} +
+
+
+ ) +} diff --git a/src/blocks/BadgeList/config.ts b/src/blocks/BadgeList/config.ts new file mode 100644 index 0000000..b163f8b --- /dev/null +++ b/src/blocks/BadgeList/config.ts @@ -0,0 +1,72 @@ +import { Block } from "payload"; + +export const BadgeList: Block = { + slug: 'badgeList', + interfaceName: 'BadgeListBlock', + fields: [ + { + name: 'listHeader', + type: 'text', + }, + { + name: 'shouldHideListHeader', + type: 'checkbox', + }, + { + name: 'defaultColor', + type: 'text', + }, + { + name: 'defaultTextColor', + type: 'text', + }, + { + name: 'textSize', + type: 'text', + admin: { + description: 'A valid CSS text size string', + }, + }, + { + name: 'listVariant', + type: 'select', + options: [ + 'default', + 'secondary', + 'destructive', + 'outline', + ], + defaultValue: 'default', + }, + { + name: 'badges', + type: 'array', + fields: [ + { + name: 'value', + type: 'text', + }, + { + name: 'color', + type: 'text', + }, + { + name: 'textColor', + type: 'text', + admin: { + description: 'String of a valid CSS color', + } + }, + { + name: 'icon', + type: 'relationship', + relationTo: 'media' + }, + { + name: 'href', + type: 'text', + } + ], + }, + ], +} diff --git a/src/blocks/ProfileBrief/component.tsx b/src/blocks/ProfileBrief/component.tsx new file mode 100644 index 0000000..0a1b549 --- /dev/null +++ b/src/blocks/ProfileBrief/component.tsx @@ -0,0 +1,41 @@ +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' +import BlurFade from '@/components/ui/blur-fade' +import BlurFadeText from '@/components/ui/blur-fade-text' +import type { Media, ProfileBriefBlock as ProfileBriefProps } from '@/payload-types' + +const BLUR_FADE_DELAY = 0.04 +const INITITALS = 'YS' + +type Props = { + className?: string +} & ProfileBriefProps +export const ProfileBrief = (props: Props) => { + const avatar = (props.avatar as Media) || undefined + return ( +
+
+
+
+ + +
+ + + + {INITITALS} + + +
+
+
+ ) +} diff --git a/src/blocks/ProfileBrief/config.ts b/src/blocks/ProfileBrief/config.ts new file mode 100644 index 0000000..c89ce80 --- /dev/null +++ b/src/blocks/ProfileBrief/config.ts @@ -0,0 +1,21 @@ +import { Block } from "payload"; + +export const ProfileBrief: Block = { + slug: 'profileBrief', + interfaceName: 'ProfileBriefBlock', + fields: [ + { + name: 'heading', + type: 'text', + }, + { + name: 'subheading', + type: 'text', + }, + { + name: 'avatar', + type: 'relationship', + relationTo: 'media' + }, + ], +} diff --git a/src/blocks/RenderBlocks.tsx b/src/blocks/RenderBlocks.tsx new file mode 100644 index 0000000..98b733c --- /dev/null +++ b/src/blocks/RenderBlocks.tsx @@ -0,0 +1,46 @@ +import React, { Fragment } from 'react' + +import type { Page } from '@/payload-types' +import { SimpleBrief } from './SimpleBrief/component' +import { SimpleList } from './SimpleList/component' +import { Showcase } from './Showcase/component' +import { ProfileBrief } from './ProfileBrief/component' +import { BadgeList } from './BadgeList/component' + +const blockComponents = { + simpleBrief: SimpleBrief, + simpleList: SimpleList, + showcase: Showcase, + profileBrief: ProfileBrief, + badgeList: BadgeList, +} + +export const RenderBlocks: React.FC<{ + blocks: Page['layout'] +}> = (props) => { + const blocks = Array.from(props.blocks?.values() || []) + const hasBlocks = blocks && Array.isArray(blocks) && blocks.length > 0 + if (!hasBlocks) return null + + return ( + + {blocks.map((block, index) => { + const { blockType } = block + + if (blockType && blockType in blockComponents) { + const Block = blockComponents[blockType] + + if (Block) { + return ( +
+ {/* @ts-expect-error there may be some mismatch between the expected types here */} + +
+ ) + } + } + return null + })} +
+ ) +} diff --git a/src/blocks/Showcase/component.tsx b/src/blocks/Showcase/component.tsx new file mode 100644 index 0000000..d7ebb42 --- /dev/null +++ b/src/blocks/Showcase/component.tsx @@ -0,0 +1,56 @@ +import { ProjectCard } from '@/components/project-card' +import BlurFade from '@/components/ui/blur-fade' +import { BadgeListBlock, Media, ShowcaseBlock as ShowcaseProps } from '@/payload-types' +import { ShowcaseCardBlock as ShowcaseCardProps } from '@/payload-types' +import { DefaultTypedEditorState } from '@payloadcms/richtext-lexical' + +const BLUR_FADE_DELAY = 0.04 + +type CardProps = { className?: string; index: number } & ShowcaseCardProps +export const ShowcaseCard = (props: CardProps) => { + const realtedImage = props.image as Media | undefined + const realtedVideo = props.video as Media | undefined + const description = props.description as DefaultTypedEditorState | undefined + const tags = (props.tags as BadgeListBlock[]) || [] + const links = (props.links as BadgeListBlock[]) || [] + return ( + + + + ) +} + +type ShowcaseComponentProps = { className: string } & ShowcaseProps +export const Showcase = (props: ShowcaseComponentProps) => { + return ( +
+
+ +
+
+

+ {props.listName} +

+

{props.heading}

+

+ {props.subheading} +

+
+
+
+
+ {props.lineItems?.map((item, i) => )} +
+
+
+ ) +} diff --git a/src/blocks/Showcase/config.ts b/src/blocks/Showcase/config.ts new file mode 100644 index 0000000..5525129 --- /dev/null +++ b/src/blocks/Showcase/config.ts @@ -0,0 +1,71 @@ +import { Block } from "payload"; +import { BadgeList } from "../BadgeList/config"; + +export const ShowcaseCard: Block = { + slug: 'showcaseCard', + interfaceName: 'ShowcaseCardBlock', + fields: [ + { + name: 'image', + type: 'relationship', + relationTo: 'media' + }, + { + name: 'video', + type: 'relationship', + relationTo: 'media' + }, + { + name: 'mainLink', + type: 'text', + }, + { + name: 'title', + type: 'text', + }, + { + name: 'unstructuredDate', + type: 'text', + }, + { + name: 'description', + type: 'richText' + }, + { + name: 'tags', + label: 'Add Tag Section', + type: 'blocks', + blocks: [BadgeList], + }, + { + name: 'links', + label: 'Add Link Section', + type: 'blocks', + blocks: [BadgeList], + }, + ] +} + +export const Showcase: Block = { + slug: 'showcase', + interfaceName: 'ShowcaseBlock', + fields: [ + { + name: 'listName', + type: 'text', + }, + { + name: 'heading', + type: 'text', + }, + { + name: 'subheading', + type: 'text', + }, + { + name: 'lineItems', + type: 'blocks', + blocks: [ShowcaseCard] + }, + ], +} diff --git a/src/blocks/SimpleBrief/component.tsx b/src/blocks/SimpleBrief/component.tsx new file mode 100644 index 0000000..054113f --- /dev/null +++ b/src/blocks/SimpleBrief/component.tsx @@ -0,0 +1,23 @@ +import { RichText } from '@/components/RichText' +import BlurFade from '@/components/ui/blur-fade' +import { SimpleBriefBlock } from '@/payload-types' +import { DefaultTypedEditorState } from '@payloadcms/richtext-lexical' + +const BLUR_FADE_DELAY = 0.04 + +type Props = { className: string } & SimpleBriefBlock +export const SimpleBrief = (props: Props) => { + return ( +
+ +

{props.title}

+
+ + + +
+ ) +} diff --git a/src/blocks/SimpleBrief/config.ts b/src/blocks/SimpleBrief/config.ts new file mode 100644 index 0000000..b66caf6 --- /dev/null +++ b/src/blocks/SimpleBrief/config.ts @@ -0,0 +1,16 @@ +import { Block } from "payload"; + +export const SimpleBrief: Block = { + slug: 'simpleBrief', + interfaceName: 'simpleBriefBlock', + fields: [ + { + name: 'title', + type: 'text', + }, + { + name: 'content', + type: 'richText' + } + ], +} diff --git a/src/blocks/SimpleList/component.tsx b/src/blocks/SimpleList/component.tsx new file mode 100644 index 0000000..aa26f9e --- /dev/null +++ b/src/blocks/SimpleList/component.tsx @@ -0,0 +1,39 @@ +import { ResumeCard } from '@/components/resume-card' +import BlurFade from '@/components/ui/blur-fade' +import { Media, SimpleListBlock } from '@/payload-types' +import { DefaultTypedEditorState } from '@payloadcms/richtext-lexical' + +const BLUR_FADE_DELAY = 0.04 + +type Props = { + className: string +} & SimpleListBlock +export const SimpleList = (props: Props) => { + return ( +
+
+ +

{props.listHeader}

+
+ {props.lineItems?.map((item, id) => { + const avatar = item.avatar as Media | undefined + + return ( + + + + ) + })} +
+
+ ) +} diff --git a/src/blocks/SimpleList/config.ts b/src/blocks/SimpleList/config.ts new file mode 100644 index 0000000..46283f9 --- /dev/null +++ b/src/blocks/SimpleList/config.ts @@ -0,0 +1,51 @@ +import { Block } from "payload"; + +export const SimpleList: Block = { + slug: 'simpleList', + interfaceName: 'SimpleListBlock', + fields: [ + { + name: 'listHeader', + type: 'text', + }, + { + name: 'lineItems', + type: 'array', + fields: [ + { + name: 'title', + type: 'text', + }, + { + name: 'subtitle', + type: 'text', + }, + { + name: 'description', + type: 'richText', + }, + { + name: 'extraDetail', + type: 'text', + }, + { + name: 'avatar', + type: 'relationship', + relationTo: 'media', + }, + { + name: 'initials', + type: 'text', + }, + { + name: 'link', + type: 'text', + }, + { + name: 'isInitiallyHidden', + type: 'checkbox', + }, + ], + }, + ], +} diff --git a/src/collections/Pages.ts b/src/collections/Pages.ts new file mode 100644 index 0000000..d853643 --- /dev/null +++ b/src/collections/Pages.ts @@ -0,0 +1,27 @@ +import { BadgeList } from "@/blocks/BadgeList/config"; +import { ProfileBrief } from "@/blocks/ProfileBrief/config"; +import { Showcase } from "@/blocks/Showcase/config"; +import { SimpleBrief } from "@/blocks/SimpleBrief/config"; +import { SimpleList } from "@/blocks/SimpleList/config"; +import { CollectionConfig } from "payload"; + +export const Pages: CollectionConfig = { + slug: 'pages', + admin: { + useAsTitle: 'slug', + }, + access: { + read: () => true + }, + fields: [ + { + name: 'slug', + type: 'text', + }, + { + name: 'layout', + type: 'blocks', + blocks: [SimpleList, Showcase, ProfileBrief, SimpleBrief, BadgeList], + }, + ] +} diff --git a/src/components/RichText.tsx b/src/components/RichText.tsx new file mode 100644 index 0000000..64bb75c --- /dev/null +++ b/src/components/RichText.tsx @@ -0,0 +1,60 @@ +import { cn } from '@/lib/utils' +import { + DefaultNodeTypes, + SerializedBlockNode, + SerializedLinkNode, + type DefaultTypedEditorState, +} from '@payloadcms/richtext-lexical' +import { + JSXConvertersFunction, + LinkJSXConverter, + RichText as ConvertRichText, +} from '@payloadcms/richtext-lexical/react' +import { BadgeList } from '@/blocks/BadgeList/component' +import { BadgeListBlock } from '@/payload-types' + +type NodeTypes = DefaultNodeTypes | SerializedBlockNode + +const internalDocToHref = ({ linkNode }: { linkNode: SerializedLinkNode }) => { + const { value, relationTo } = linkNode.fields.doc! + if (typeof value !== 'object') { + throw new Error('Expected value to be an object') + } + const slug = value.slug + return relationTo === 'posts' ? `/posts/${slug}` : `/${slug}` +} + +const jsxConverters: JSXConvertersFunction = ({ defaultConverters }) => { + return { + ...defaultConverters, + ...LinkJSXConverter({ internalDocToHref }), + blocks: { + badgeList: ({ node }) => , + }, + } +} + +type Props = { + data: DefaultTypedEditorState + enableGutter?: boolean + enableProse?: boolean +} & React.HTMLAttributes + +export const RichText = (props: Props) => { + const { className, enableProse = true, enableGutter = true, ...rest } = props + return ( + + ) +} diff --git a/src/components/hackathon-card.tsx b/src/components/hackathon-card.tsx new file mode 100644 index 0000000..d444240 --- /dev/null +++ b/src/components/hackathon-card.tsx @@ -0,0 +1,62 @@ +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +import { Badge } from "@/components/ui/badge"; +import Link from "next/link"; + +interface Props { + title: string; + description: string; + dates: string; + location: string; + image?: string; + links?: readonly { + icon: React.ReactNode; + title: string; + href: string; + }[]; +} + +export function HackathonCard({ + title, + description, + dates, + location, + image, + links, +}: Props) { + return ( +
  • +
    + + + {title[0]} + +
    +
    + {dates && ( + + )} +

    {title}

    + {location && ( +

    {location}

    + )} + {description && ( + + {description} + + )} +
    + {links && links.length > 0 && ( +
    + {links?.map((link, idx) => ( + + + {link.icon} + {link.title} + + + ))} +
    + )} +
  • + ); +} diff --git a/src/components/icons.tsx b/src/components/icons.tsx new file mode 100644 index 0000000..c4984e8 --- /dev/null +++ b/src/components/icons.tsx @@ -0,0 +1,223 @@ +import { GlobeIcon, MailIcon } from "lucide-react"; + +export type IconProps = React.HTMLAttributes; + +export const Icons = { + globe: (props: IconProps) => , + email: (props: IconProps) => , + linkedin: (props: IconProps) => ( + + LinkedIn + + + ), + x: (props: IconProps) => ( + + X + + + ), + youtube: (props: IconProps) => ( + + youtube + + + ), + nextjs: (props: IconProps) => ( + + Next.js + + + ), + framermotion: (props: IconProps) => ( + + Framer Motion + + + + + ), + tailwindcss: (props: IconProps) => ( + + Tailwind CSS + + + ), + typescript: (props: IconProps) => ( + + + + ), + react: (props: IconProps) => ( + + React + + + ), + github: (props: IconProps) => ( + + + + ), + notion: (props: IconProps) => ( + + + + + ), + openai: (props: IconProps) => ( + + + + ), + googleDrive: (props: IconProps) => ( + + + + + + + + + ), + whatsapp: (props: IconProps) => ( + + + + + + + + + + + + + + + + + ), +}; diff --git a/src/components/mode-toggle.tsx b/src/components/mode-toggle.tsx new file mode 100644 index 0000000..7eb0a65 --- /dev/null +++ b/src/components/mode-toggle.tsx @@ -0,0 +1,22 @@ +"use client"; + +import { Button } from "@/components/ui/button"; +import { MoonIcon, SunIcon } from "@radix-ui/react-icons"; +import { useTheme } from "next-themes"; + +export function ModeToggle() { + const { theme, setTheme } = useTheme(); + + return ( + + ); +} diff --git a/src/components/project-card.tsx b/src/components/project-card.tsx new file mode 100644 index 0000000..ed1cc3e --- /dev/null +++ b/src/components/project-card.tsx @@ -0,0 +1,123 @@ +import { Badge } from '@/components/ui/badge' +import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/components/ui/card' +import { cn } from '@/lib/utils' +import { BadgeListBlock, Media } from '@/payload-types' +import { DefaultTypedEditorState } from '@payloadcms/richtext-lexical' +import Image from 'next/image' +import Link from 'next/link' +import { RichText } from './RichText' +import clsx from 'clsx' + +type Props = { + title: string + href?: string + description?: DefaultTypedEditorState + dates: string + tags?: BadgeListBlock[] + link?: string + image?: Media + video?: Media + links?: BadgeListBlock[] + className?: string +} + +export function ProjectCard({ + title, + href, + description, + dates, + link, + image, + video, + className, + ...rest +}: Props) { + const tags = rest?.tags?.length ? rest.tags[0] : null + const links = rest?.links?.length ? rest.links[0] : null + + return ( + + + {video && ( + + ) +} diff --git a/src/components/resume-card.tsx b/src/components/resume-card.tsx new file mode 100644 index 0000000..fa3a660 --- /dev/null +++ b/src/components/resume-card.tsx @@ -0,0 +1,112 @@ +'use client' + +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' +import { Badge } from '@/components/ui/badge' +import { Card, CardHeader } from '@/components/ui/card' +import { cn } from '@/lib/utils' +import { DefaultTypedEditorState } from '@payloadcms/richtext-lexical' +import { motion } from 'framer-motion' +import { ChevronRightIcon } from 'lucide-react' +import Link from 'next/link' +import React from 'react' +import { RichText } from './RichText' + +type ResumeCardProps = { + logoUrl: string + altText: string + title: string + subtitle?: string + href?: string + badges?: string[] + period: string + description?: DefaultTypedEditorState +} +export const ResumeCard = ({ + logoUrl, + altText, + title, + subtitle, + href, + badges, + period, + description, +}: ResumeCardProps) => { + const [isExpanded, setIsExpanded] = React.useState(false) + + const handleClick = (e: React.MouseEvent) => { + if (description) { + e.preventDefault() + setIsExpanded(!isExpanded) + } + } + + const handleDoubleClick = (e: React.MouseEvent) => { + e.preventDefault() + if (description) setIsExpanded(true) + + if (href && window) window.open(href, '_blank') + } + + return ( + + +
    + + + {altText[0]} + +
    +
    + +
    +

    + {title} + {badges && ( + + {badges.map((badge, index) => ( + + {badge} + + ))} + + )} + +

    +
    + {period} +
    +
    + {subtitle &&
    {subtitle}
    } +
    + {description && ( + + + + )} +
    +
    + + ) +} diff --git a/src/components/theme-provider.tsx b/src/components/theme-provider.tsx new file mode 100644 index 0000000..f0e58dd --- /dev/null +++ b/src/components/theme-provider.tsx @@ -0,0 +1,7 @@ +'use client' + +import { ThemeProvider as NextThemesProvider, ThemeProviderProps } from 'next-themes' + +export function ThemeProvider({ children, ...props }: ThemeProviderProps) { + return {children} +} diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx new file mode 100644 index 0000000..51e507b --- /dev/null +++ b/src/components/ui/avatar.tsx @@ -0,0 +1,50 @@ +"use client" + +import * as React from "react" +import * as AvatarPrimitive from "@radix-ui/react-avatar" + +import { cn } from "@/lib/utils" + +const Avatar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +Avatar.displayName = AvatarPrimitive.Root.displayName + +const AvatarImage = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarImage.displayName = AvatarPrimitive.Image.displayName + +const AvatarFallback = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName + +export { Avatar, AvatarImage, AvatarFallback } diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx new file mode 100644 index 0000000..e87d62b --- /dev/null +++ b/src/components/ui/badge.tsx @@ -0,0 +1,36 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const badgeVariants = cva( + "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
    + ) +} + +export { Badge, badgeVariants } diff --git a/src/components/ui/blur-fade-text.tsx b/src/components/ui/blur-fade-text.tsx new file mode 100644 index 0000000..8c03838 --- /dev/null +++ b/src/components/ui/blur-fade-text.tsx @@ -0,0 +1,85 @@ +"use client"; + +import { cn } from "@/lib/utils"; +import { AnimatePresence, motion, Variants } from "framer-motion"; +import { useMemo } from "react"; + +interface BlurFadeTextProps { + text: string; + className?: string; + variant?: { + hidden: { y: number }; + visible: { y: number }; + }; + duration?: number; + characterDelay?: number; + delay?: number; + yOffset?: number; + animateByCharacter?: boolean; +} +const BlurFadeText = ({ + text, + className, + variant, + characterDelay = 0.03, + delay = 0, + yOffset = 8, + animateByCharacter = false, +}: BlurFadeTextProps) => { + const defaultVariants: Variants = { + hidden: { y: yOffset, opacity: 0, filter: "blur(8px)" }, + visible: { y: -yOffset, opacity: 1, filter: "blur(0px)" }, + }; + const combinedVariants = variant || defaultVariants; + const characters = useMemo(() => Array.from(text), [text]); + + if (animateByCharacter) { + return ( +
    + + {characters.map((char, i) => ( + + {char} + + ))} + +
    + ); + } + + return ( +
    + + + {text} + + +
    + ); +}; + +export default BlurFadeText; diff --git a/src/components/ui/blur-fade.tsx b/src/components/ui/blur-fade.tsx new file mode 100644 index 0000000..e5323f1 --- /dev/null +++ b/src/components/ui/blur-fade.tsx @@ -0,0 +1,61 @@ +'use client' + +import { AnimatePresence, motion, useInView, Variants } from 'framer-motion' +import { useRef } from 'react' + +interface BlurFadeProps { + children: React.ReactNode + className?: string + variant?: { + hidden: { y: number } + visible: { y: number } + } + duration?: number + delay?: number + yOffset?: number + inView?: boolean + inViewMargin?: string + blur?: string +} +const BlurFade = ({ + children, + className, + variant, + duration = 0.4, + delay = 0, + yOffset = 6, + inView = false, + inViewMargin = '-50px', + blur = '6px', +}: BlurFadeProps) => { + const ref = useRef(null) + // @ts-ignore + const inViewResult = useInView(ref, { once: true, margin: inViewMargin }) + const isInView = !inView || inViewResult + const defaultVariants: Variants = { + hidden: { y: yOffset, opacity: 0, filter: `blur(${blur})` }, + visible: { y: -yOffset, opacity: 1, filter: `blur(0px)` }, + } + const combinedVariants = variant || defaultVariants + return ( + + + {children} + + + ) +} + +export default BlurFade diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx new file mode 100644 index 0000000..9ea2d23 --- /dev/null +++ b/src/components/ui/button.tsx @@ -0,0 +1,57 @@ +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const buttonVariants = cva( + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: + "bg-primary text-primary-foreground shadow hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", + outline: + "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-9 px-4 py-2", + sm: "h-8 rounded-md px-3 text-xs", + lg: "h-10 rounded-md px-8", + icon: "h-9 w-9 rounded-full", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +); + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button"; + return ( + + ); + } +); +Button.displayName = "Button"; + +export { Button, buttonVariants }; diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx new file mode 100644 index 0000000..1174478 --- /dev/null +++ b/src/components/ui/card.tsx @@ -0,0 +1,86 @@ +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
    +)); +Card.displayName = "Card"; + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
    +)); +CardHeader.displayName = "CardHeader"; + +const CardTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

    +)); +CardTitle.displayName = "CardTitle"; + +const CardDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

    +)); +CardDescription.displayName = "CardDescription"; + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

    +)); +CardContent.displayName = "CardContent"; + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
    +)); +CardFooter.displayName = "CardFooter"; + +export { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +}; diff --git a/src/components/ui/dock.tsx b/src/components/ui/dock.tsx new file mode 100644 index 0000000..32cc662 --- /dev/null +++ b/src/components/ui/dock.tsx @@ -0,0 +1,112 @@ +'use client' + +import { cn } from '@/lib/utils' +import { cva, type VariantProps } from 'class-variance-authority' +import { motion, useMotionValue, useSpring, useTransform } from 'framer-motion' +import React, { PropsWithChildren, useRef } from 'react' + +export interface DockProps extends VariantProps { + className?: string + magnification?: number + distance?: number + children: React.ReactNode +} + +const DEFAULT_MAGNIFICATION = 60 +const DEFAULT_DISTANCE = 140 + +const dockVariants = cva('mx-auto w-max h-full p-2 flex items-end rounded-full border') + +const Dock = React.forwardRef( + ( + { + className, + children, + magnification = DEFAULT_MAGNIFICATION, + distance = DEFAULT_DISTANCE, + ...props + }, + ref, + ) => { + const mousex = useMotionValue(Infinity) + + const renderChildren = () => { + return React.Children.map(children, (child: React.ReactNode) => { + if (React.isValidElement(child)) { + return React.cloneElement(child, { + mousex, + magnification, + distance, + } as DockIconProps) + } + return child + }) + } + + return ( + mousex.set(e.pageX)} + onMouseLeave={() => mousex.set(Infinity)} + {...props} + className={cn(dockVariants({ className }))} + > + {renderChildren()} + + ) + }, +) + +Dock.displayName = 'Dock' + +export interface DockIconProps { + size?: number + magnification?: number + distance?: number + mousex?: any + className?: string + children?: React.ReactNode + props?: PropsWithChildren +} + +const DockIcon = ({ + magnification = DEFAULT_MAGNIFICATION, + distance = DEFAULT_DISTANCE, + mousex, + className, + children, + ...props +}: DockIconProps) => { + const ref = useRef(null) + + const distanceCalc = useTransform(mousex, (val: number) => { + const bounds = ref.current?.getBoundingClientRect() ?? { x: 0, width: 0 } + return val - bounds.x - bounds.width / 2 + }) + + const widthSync = useTransform(distanceCalc, [-distance, 0, distance], [40, magnification, 40]) + + const width = useSpring(widthSync, { + mass: 0.1, + stiffness: 150, + damping: 12, + }) + + return ( + + {children} + + ) +} + +DockIcon.displayName = 'DockIcon' + +export { Dock, DockIcon, dockVariants } diff --git a/src/components/ui/separator.tsx b/src/components/ui/separator.tsx new file mode 100644 index 0000000..12d81c4 --- /dev/null +++ b/src/components/ui/separator.tsx @@ -0,0 +1,31 @@ +"use client" + +import * as React from "react" +import * as SeparatorPrimitive from "@radix-ui/react-separator" + +import { cn } from "@/lib/utils" + +const Separator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>( + ( + { className, orientation = "horizontal", decorative = true, ...props }, + ref + ) => ( + + ) +) +Separator.displayName = SeparatorPrimitive.Root.displayName + +export { Separator } diff --git a/src/components/ui/terminal.tsx b/src/components/ui/terminal.tsx new file mode 100644 index 0000000..a32083d --- /dev/null +++ b/src/components/ui/terminal.tsx @@ -0,0 +1,119 @@ +"use client"; + +import { cn } from "@/lib/utils"; +import { motion, MotionProps } from "motion/react"; +import { useEffect, useRef, useState } from "react"; + +interface AnimatedSpanProps extends MotionProps { + children: React.ReactNode; + delay?: number; + className?: string; +} + +export const AnimatedSpan = ({ + children, + delay = 0, + className, + ...props +}: AnimatedSpanProps) => ( + + {children} + +); + +interface TypingAnimationProps extends MotionProps { + children: string; + className?: string; + duration?: number; + delay?: number; + as?: React.ElementType; +} + +export const TypingAnimation = ({ + children, + className, + duration = 60, + delay = 0, + as: Component = "span", + ...props +}: TypingAnimationProps) => { + if (typeof children !== "string") { + throw new Error("TypingAnimation: children must be a string. Received:"); + } + + const MotionComponent = motion.create(Component, { + forwardMotionProps: true, + }); + + const [displayedText, setDisplayedText] = useState(""); + const [started, setStarted] = useState(false); + const elementRef = useRef(null); + + useEffect(() => { + const startTimeout = setTimeout(() => { + setStarted(true); + }, delay); + return () => clearTimeout(startTimeout); + }, [delay]); + + useEffect(() => { + if (!started) return; + + let i = 0; + const typingEffect = setInterval(() => { + if (i < children.length) { + setDisplayedText(children.substring(0, i + 1)); + i++; + } else { + clearInterval(typingEffect); + } + }, duration); + + return () => { + clearInterval(typingEffect); + }; + }, [children, duration, started]); + + return ( + + {displayedText} + + ); +}; + +interface TerminalProps { + children: React.ReactNode; + className?: string; +} + +export const Terminal = ({ children, className }: TerminalProps) => { + return ( +
    +
    +
    +
    +
    +
    +
    +
    +
    +        {children}
    +      
    +
    + ); +}; diff --git a/src/components/ui/tooltip.tsx b/src/components/ui/tooltip.tsx new file mode 100644 index 0000000..6672b37 --- /dev/null +++ b/src/components/ui/tooltip.tsx @@ -0,0 +1,30 @@ +"use client"; + +import * as TooltipPrimitive from "@radix-ui/react-tooltip"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const TooltipProvider = TooltipPrimitive.Provider; + +const Tooltip = TooltipPrimitive.Root; + +const TooltipTrigger = TooltipPrimitive.Trigger; + +const TooltipContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + +)); +TooltipContent.displayName = TooltipPrimitive.Content.displayName; + +export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger }; diff --git a/src/fields/defaultLexical.ts b/src/fields/defaultLexical.ts new file mode 100644 index 0000000..57707ab --- /dev/null +++ b/src/fields/defaultLexical.ts @@ -0,0 +1,68 @@ +import type { TextFieldSingleValidation } from 'payload' +import { + BoldFeature, + ItalicFeature, + LinkFeature, + ParagraphFeature, + lexicalEditor, + UnderlineFeature, + type LinkFields, + UnorderedListFeature, + AlignFeature, + BlockquoteFeature, + InlineCodeFeature, + HeadingFeature, + OrderedListFeature, + IndentFeature, + FixedToolbarFeature, + BlocksFeature, +} from '@payloadcms/richtext-lexical' +import { BadgeList } from '@/blocks/BadgeList/config' + +export const defaultLexical = lexicalEditor({ + features: [ + FixedToolbarFeature(), + ParagraphFeature(), + UnderlineFeature(), + BoldFeature(), + ItalicFeature(), + AlignFeature(), + BlockquoteFeature(), + UnorderedListFeature(), + OrderedListFeature(), + IndentFeature(), + InlineCodeFeature(), + HeadingFeature(), + LinkFeature({ + enabledCollections: ['pages',], + fields: ({ defaultFields }) => { + const defaultFieldsWithoutUrl = defaultFields.filter((field) => { + if ('name' in field && field.name === 'url') return false + return true + }) + + return [ + ...defaultFieldsWithoutUrl, + { + name: 'url', + type: 'text', + admin: { + condition: (_data, siblingData) => siblingData?.linkType !== 'internal', + }, + label: ({ t }) => t('fields:enterURL'), + required: true, + validate: ((value, options) => { + if ((options?.siblingData as LinkFields)?.linkType === 'internal') { + return true // no validation needed, as no url should exist for internal links + } + return value ? true : 'URL is required' + }) as TextFieldSingleValidation, + }, + ] + }, + }), + BlocksFeature({ + blocks: [BadgeList], + }), + ], +}) diff --git a/src/globals/Meta/config.ts b/src/globals/Meta/config.ts new file mode 100644 index 0000000..0b49ae9 --- /dev/null +++ b/src/globals/Meta/config.ts @@ -0,0 +1,23 @@ +import { GlobalConfig } from "payload"; + +export const MetaGlobal: GlobalConfig = { + slug: 'meta', + fields: [ + { + name: 'name', + type: 'text', + }, + { + name: 'url', + type: 'text', + }, + { + name: 'description', + type: 'textarea', + }, + { + name: 'googleVerification', + type: 'text', + }, + ], +} diff --git a/src/globals/Nav/component.tsx b/src/globals/Nav/component.tsx new file mode 100644 index 0000000..256492c --- /dev/null +++ b/src/globals/Nav/component.tsx @@ -0,0 +1,79 @@ +import { Dock, DockIcon } from '@/components/ui/dock' +import { ModeToggle } from '@/components/mode-toggle' +import { buttonVariants } from '@/components/ui/button' +import { Separator } from '@/components/ui/separator' +import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' +import { cn } from '@/lib/utils' +import Link from 'next/link' +import { Nav } from '@/payload-types' +import Image from 'next/image' + +type Props = { className?: string } & Nav + +export default function Navbar(props: Props) { + return ( +
    +
    + + {props.internalLinks?.map((item) => ( + + + + + {''} + + + +

    {item.label}

    +
    +
    +
    + ))} + + {props.contact?.map((c) => ( + + + + + {''} + + + +

    {c.label}

    +
    +
    +
    + ))} + + + + + + + +

    Theme

    +
    +
    +
    +
    +
    + ) +} diff --git a/src/globals/Nav/config.ts b/src/globals/Nav/config.ts new file mode 100644 index 0000000..a567476 --- /dev/null +++ b/src/globals/Nav/config.ts @@ -0,0 +1,43 @@ +import { GlobalConfig } from "payload"; + +export const NavGlobal: GlobalConfig = { + slug: 'nav', + fields: [ + { + name: 'internalLinks', + type: 'array', + fields: [ + { + name: 'href', + type: 'text', + }, + { + name: 'iconSrc', + type: 'text', + }, + { + name: 'label', + type: 'text', + }, + ], + }, + { + name: 'contact', + type: 'array', + fields: [ + { + name: 'href', + type: 'text', + }, + { + name: 'iconSrc', + type: 'text', + }, + { + name: 'label', + type: 'text', + }, + ], + } + ], +} diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..ae5dc16 --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,37 @@ +import { clsx, type ClassValue } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} + +export function formatDate(date: string) { + const currentDate = new Date().getTime(); + if (!date.includes("T")) { + date = `${date}T00:00:00`; + } + const targetDate = new Date(date).getTime(); + const timeDifference = Math.abs(currentDate - targetDate); + const daysAgo = Math.floor(timeDifference / (1000 * 60 * 60 * 24)); + + const fullDate = new Date(date).toLocaleString("en-us", { + month: "long", + day: "numeric", + year: "numeric", + }); + + if (daysAgo < 1) { + return "Today"; + } else if (daysAgo < 7) { + return `${fullDate} (${daysAgo}d ago)`; + } else if (daysAgo < 30) { + const weeksAgo = Math.floor(daysAgo / 7); + return `${fullDate} (${weeksAgo}w ago)`; + } else if (daysAgo < 365) { + const monthsAgo = Math.floor(daysAgo / 30); + return `${fullDate} (${monthsAgo}mo ago)`; + } else { + const yearsAgo = Math.floor(daysAgo / 365); + return `${fullDate} (${yearsAgo}y ago)`; + } +} diff --git a/src/payload-types.ts b/src/payload-types.ts index d98da0d..34a4253 100644 --- a/src/payload-types.ts +++ b/src/payload-types.ts @@ -6,24 +6,102 @@ * and re-run `payload generate:types` to regenerate this file. */ +/** + * Supported timezones in IANA format. + * + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "supportedTimezones". + */ +export type SupportedTimezones = + | 'Pacific/Midway' + | 'Pacific/Niue' + | 'Pacific/Honolulu' + | 'Pacific/Rarotonga' + | 'America/Anchorage' + | 'Pacific/Gambier' + | 'America/Los_Angeles' + | 'America/Tijuana' + | 'America/Denver' + | 'America/Phoenix' + | 'America/Chicago' + | 'America/Guatemala' + | 'America/New_York' + | 'America/Bogota' + | 'America/Caracas' + | 'America/Santiago' + | 'America/Buenos_Aires' + | 'America/Sao_Paulo' + | 'Atlantic/South_Georgia' + | 'Atlantic/Azores' + | 'Atlantic/Cape_Verde' + | 'Europe/London' + | 'Europe/Berlin' + | 'Africa/Lagos' + | 'Europe/Athens' + | 'Africa/Cairo' + | 'Europe/Moscow' + | 'Asia/Riyadh' + | 'Asia/Dubai' + | 'Asia/Baku' + | 'Asia/Karachi' + | 'Asia/Tashkent' + | 'Asia/Calcutta' + | 'Asia/Dhaka' + | 'Asia/Almaty' + | 'Asia/Jakarta' + | 'Asia/Bangkok' + | 'Asia/Shanghai' + | 'Asia/Singapore' + | 'Asia/Tokyo' + | 'Asia/Seoul' + | 'Australia/Brisbane' + | 'Australia/Sydney' + | 'Pacific/Guam' + | 'Pacific/Noumea' + | 'Pacific/Auckland' + | 'Pacific/Fiji'; + export interface Config { auth: { users: UserAuthOperations; }; + blocks: {}; collections: { users: User; media: Media; + pages: Page; + 'payload-locked-documents': PayloadLockedDocument; 'payload-preferences': PayloadPreference; 'payload-migrations': PayloadMigration; }; - db: { - defaultIDType: string; + collectionsJoins: {}; + collectionsSelect: { + users: UsersSelect | UsersSelect; + media: MediaSelect | MediaSelect; + pages: PagesSelect | PagesSelect; + 'payload-locked-documents': PayloadLockedDocumentsSelect | PayloadLockedDocumentsSelect; + 'payload-preferences': PayloadPreferencesSelect | PayloadPreferencesSelect; + 'payload-migrations': PayloadMigrationsSelect | PayloadMigrationsSelect; + }; + db: { + defaultIDType: number; + }; + globals: { + meta: Meta; + nav: Nav; + }; + globalsSelect: { + meta: MetaSelect | MetaSelect; + nav: NavSelect | NavSelect; }; - globals: {}; locale: null; user: User & { collection: 'users'; }; + jobs: { + tasks: unknown; + workflows: unknown; + }; } export interface UserAuthOperations { forgotPassword: { @@ -48,7 +126,7 @@ export interface UserAuthOperations { * via the `definition` "users". */ export interface User { - id: string; + id: number; updatedAt: string; createdAt: string; email: string; @@ -65,7 +143,7 @@ export interface User { * via the `definition` "media". */ export interface Media { - id: string; + id: number; alt: string; updatedAt: string; createdAt: string; @@ -79,15 +157,202 @@ export interface Media { focalX?: number | null; focalY?: number | null; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "pages". + */ +export interface Page { + id: number; + slug?: string | null; + layout?: (SimpleListBlock | ShowcaseBlock | ProfileBriefBlock | SimpleBriefBlock | BadgeListBlock)[] | null; + updatedAt: string; + createdAt: string; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "SimpleListBlock". + */ +export interface SimpleListBlock { + listHeader?: string | null; + lineItems?: + | { + title?: string | null; + subtitle?: string | null; + description?: { + root: { + type: string; + children: { + type: string; + version: number; + [k: string]: unknown; + }[]; + direction: ('ltr' | 'rtl') | null; + format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | ''; + indent: number; + version: number; + }; + [k: string]: unknown; + } | null; + extraDetail?: string | null; + avatar?: (number | null) | Media; + initials?: string | null; + link?: string | null; + isInitiallyHidden?: boolean | null; + id?: string | null; + }[] + | null; + id?: string | null; + blockName?: string | null; + blockType: 'simpleList'; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "ShowcaseBlock". + */ +export interface ShowcaseBlock { + listName?: string | null; + heading?: string | null; + subheading?: string | null; + lineItems?: ShowcaseCardBlock[] | null; + id?: string | null; + blockName?: string | null; + blockType: 'showcase'; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "ShowcaseCardBlock". + */ +export interface ShowcaseCardBlock { + image?: (number | null) | Media; + video?: (number | null) | Media; + mainLink?: string | null; + title?: string | null; + unstructuredDate?: string | null; + description?: { + root: { + type: string; + children: { + type: string; + version: number; + [k: string]: unknown; + }[]; + direction: ('ltr' | 'rtl') | null; + format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | ''; + indent: number; + version: number; + }; + [k: string]: unknown; + } | null; + tags?: BadgeListBlock[] | null; + links?: BadgeListBlock[] | null; + id?: string | null; + blockName?: string | null; + blockType: 'showcaseCard'; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "BadgeListBlock". + */ +export interface BadgeListBlock { + listHeader?: string | null; + shouldHideListHeader?: boolean | null; + defaultColor?: string | null; + defaultTextColor?: string | null; + /** + * A valid CSS text size string + */ + textSize?: string | null; + listVariant?: ('default' | 'secondary' | 'destructive' | 'outline') | null; + badges?: + | { + value?: string | null; + color?: string | null; + /** + * String of a valid CSS color + */ + textColor?: string | null; + icon?: (number | null) | Media; + href?: string | null; + id?: string | null; + }[] + | null; + id?: string | null; + blockName?: string | null; + blockType: 'badgeList'; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "ProfileBriefBlock". + */ +export interface ProfileBriefBlock { + heading?: string | null; + subheading?: string | null; + avatar?: (number | null) | Media; + id?: string | null; + blockName?: string | null; + blockType: 'profileBrief'; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "simpleBriefBlock". + */ +export interface SimpleBriefBlock { + title?: string | null; + content?: { + root: { + type: string; + children: { + type: string; + version: number; + [k: string]: unknown; + }[]; + direction: ('ltr' | 'rtl') | null; + format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | ''; + indent: number; + version: number; + }; + [k: string]: unknown; + } | null; + id?: string | null; + blockName?: string | null; + blockType: 'simpleBrief'; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "payload-locked-documents". + */ +export interface PayloadLockedDocument { + id: number; + document?: + | ({ + relationTo: 'users'; + value: number | User; + } | null) + | ({ + relationTo: 'media'; + value: number | Media; + } | null) + | ({ + relationTo: 'pages'; + value: number | Page; + } | null); + globalSlug?: string | null; + user: { + relationTo: 'users'; + value: number | User; + }; + updatedAt: string; + createdAt: string; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "payload-preferences". */ export interface PayloadPreference { - id: string; + id: number; user: { relationTo: 'users'; - value: string | User; + value: number | User; }; key?: string | null; value?: @@ -107,12 +372,278 @@ export interface PayloadPreference { * via the `definition` "payload-migrations". */ export interface PayloadMigration { - id: string; + id: number; name?: string | null; batch?: number | null; updatedAt: string; createdAt: string; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "users_select". + */ +export interface UsersSelect { + updatedAt?: T; + createdAt?: T; + email?: T; + resetPasswordToken?: T; + resetPasswordExpiration?: T; + salt?: T; + hash?: T; + loginAttempts?: T; + lockUntil?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "media_select". + */ +export interface MediaSelect { + alt?: T; + updatedAt?: T; + createdAt?: T; + url?: T; + thumbnailURL?: T; + filename?: T; + mimeType?: T; + filesize?: T; + width?: T; + height?: T; + focalX?: T; + focalY?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "pages_select". + */ +export interface PagesSelect { + slug?: T; + layout?: + | T + | { + simpleList?: T | SimpleListBlockSelect; + showcase?: T | ShowcaseBlockSelect; + profileBrief?: T | ProfileBriefBlockSelect; + simpleBrief?: T | SimpleBriefBlockSelect; + badgeList?: T | BadgeListBlockSelect; + }; + updatedAt?: T; + createdAt?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "SimpleListBlock_select". + */ +export interface SimpleListBlockSelect { + listHeader?: T; + lineItems?: + | T + | { + title?: T; + subtitle?: T; + description?: T; + extraDetail?: T; + avatar?: T; + initials?: T; + link?: T; + isInitiallyHidden?: T; + id?: T; + }; + id?: T; + blockName?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "ShowcaseBlock_select". + */ +export interface ShowcaseBlockSelect { + listName?: T; + heading?: T; + subheading?: T; + lineItems?: + | T + | { + showcaseCard?: T | ShowcaseCardBlockSelect; + }; + id?: T; + blockName?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "ShowcaseCardBlock_select". + */ +export interface ShowcaseCardBlockSelect { + image?: T; + video?: T; + mainLink?: T; + title?: T; + unstructuredDate?: T; + description?: T; + tags?: + | T + | { + badgeList?: T | BadgeListBlockSelect; + }; + links?: + | T + | { + badgeList?: T | BadgeListBlockSelect; + }; + id?: T; + blockName?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "BadgeListBlock_select". + */ +export interface BadgeListBlockSelect { + listHeader?: T; + shouldHideListHeader?: T; + defaultColor?: T; + defaultTextColor?: T; + textSize?: T; + listVariant?: T; + badges?: + | T + | { + value?: T; + color?: T; + textColor?: T; + icon?: T; + href?: T; + id?: T; + }; + id?: T; + blockName?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "ProfileBriefBlock_select". + */ +export interface ProfileBriefBlockSelect { + heading?: T; + subheading?: T; + avatar?: T; + id?: T; + blockName?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "simpleBriefBlock_select". + */ +export interface SimpleBriefBlockSelect { + title?: T; + content?: T; + id?: T; + blockName?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "payload-locked-documents_select". + */ +export interface PayloadLockedDocumentsSelect { + document?: T; + globalSlug?: T; + user?: T; + updatedAt?: T; + createdAt?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "payload-preferences_select". + */ +export interface PayloadPreferencesSelect { + user?: T; + key?: T; + value?: T; + updatedAt?: T; + createdAt?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "payload-migrations_select". + */ +export interface PayloadMigrationsSelect { + name?: T; + batch?: T; + updatedAt?: T; + createdAt?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "meta". + */ +export interface Meta { + id: number; + name?: string | null; + url?: string | null; + description?: string | null; + googleVerification?: string | null; + updatedAt?: string | null; + createdAt?: string | null; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "nav". + */ +export interface Nav { + id: number; + internalLinks?: + | { + href?: string | null; + iconSrc?: string | null; + label?: string | null; + id?: string | null; + }[] + | null; + contact?: + | { + href?: string | null; + iconSrc?: string | null; + label?: string | null; + id?: string | null; + }[] + | null; + updatedAt?: string | null; + createdAt?: string | null; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "meta_select". + */ +export interface MetaSelect { + name?: T; + url?: T; + description?: T; + googleVerification?: T; + updatedAt?: T; + createdAt?: T; + globalType?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "nav_select". + */ +export interface NavSelect { + internalLinks?: + | T + | { + href?: T; + iconSrc?: T; + label?: T; + id?: T; + }; + contact?: + | T + | { + href?: T; + iconSrc?: T; + label?: T; + id?: T; + }; + updatedAt?: T; + createdAt?: T; + globalType?: T; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "auth". diff --git a/src/payload.config.ts b/src/payload.config.ts index 5f2c20c..36f6619 100644 --- a/src/payload.config.ts +++ b/src/payload.config.ts @@ -1,7 +1,6 @@ // storage-adapter-import-placeholder import { postgresAdapter } from '@payloadcms/db-postgres' import { payloadCloudPlugin } from '@payloadcms/payload-cloud' -import { lexicalEditor } from '@payloadcms/richtext-lexical' import path from 'path' import { buildConfig } from 'payload' import { fileURLToPath } from 'url' @@ -9,6 +8,10 @@ import sharp from 'sharp' import { Users } from './collections/Users' import { Media } from './collections/Media' +import { Pages } from './collections/Pages' +import { defaultLexical } from './fields/defaultLexical' +import { NavGlobal } from './globals/Nav/config' +import { MetaGlobal } from './globals/Meta/config' const filename = fileURLToPath(import.meta.url) const dirname = path.dirname(filename) @@ -20,8 +23,9 @@ export default buildConfig({ baseDir: path.resolve(dirname), }, }, - collections: [Users, Media], - editor: lexicalEditor(), + collections: [Users, Media, Pages], + globals: [MetaGlobal, NavGlobal], + editor: defaultLexical, secret: process.env.PAYLOAD_SECRET || '', typescript: { outputFile: path.resolve(dirname, 'payload-types.ts'), diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..f0a9a20 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,13 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: ['./src /**/*.{jsx,tsx + }' + ], // tell tailwind where to look + darkMode: ['selector', '[data-theme="dark" + ]', '.dark' + ], + theme: { + extend: {}, + }, + plugins: [], +}