From 6d548520d0950dddaf280279bbbb7518a172c526 Mon Sep 17 00:00:00 2001 From: Samson Okunola Date: Sun, 12 Jul 2026 12:01:34 +0100 Subject: [PATCH 1/3] feat: add layouts and pages for authentication and member profile - Created DashboardLayout for the authentication section. - Implemented MemberLayout that requires active membership. - Added profile page for members with a welcome message and placeholder for future profile editing. - Enhanced join-community page with error handling and sign-in functionality. - Updated public page with PostHog analytics tracking for page views. - Introduced a welcome page guiding users on how to activate their membership through a pull request. - Refactored global state management with Zustand for sidebar collapse functionality. - Added persistent middleware for global state management. - Integrated PostHog for analytics tracking across the application. --- env.example | 16 +- package.json | 5 +- pnpm-lock.yaml | 227 +++++++++++------------ src/app/(auth)/layout.tsx | 15 ++ src/app/(member)/layout.tsx | 9 + src/app/(member)/profile/page.tsx | 19 ++ src/app/(public)/join-community/page.tsx | 38 +++- src/app/(public)/page.tsx | 10 +- src/app/(public)/welcome/page.tsx | 70 +++++++ src/app/layout.tsx | 19 +- src/components/providers/index.ts | 1 + src/components/providers/post-hog.tsx | 18 ++ src/components/shared/sidebar.tsx | 15 ++ src/features/auth/index.ts | 1 + src/features/auth/pending-status.tsx | 59 ++++++ src/features/auth/store.ts | 4 +- src/lib/index.ts | 3 +- src/lib/query.ts | 120 ------------ src/store/.gitkeep | 0 src/store/global.ts | 16 ++ src/store/index.ts | 1 + src/store/middleware/index.ts | 2 + src/store/middleware/persist.ts | 8 + src/store/middleware/report.ts | 21 +++ 24 files changed, 436 insertions(+), 261 deletions(-) create mode 100644 src/app/(auth)/layout.tsx create mode 100644 src/app/(member)/layout.tsx create mode 100644 src/app/(member)/profile/page.tsx create mode 100644 src/app/(public)/welcome/page.tsx create mode 100644 src/components/providers/post-hog.tsx create mode 100644 src/components/shared/sidebar.tsx create mode 100644 src/features/auth/pending-status.tsx delete mode 100644 src/lib/query.ts delete mode 100644 src/store/.gitkeep create mode 100644 src/store/global.ts create mode 100644 src/store/index.ts create mode 100644 src/store/middleware/index.ts create mode 100644 src/store/middleware/persist.ts create mode 100644 src/store/middleware/report.ts diff --git a/env.example b/env.example index a552315..f16fa20 100644 --- a/env.example +++ b/env.example @@ -1,5 +1,13 @@ -# Copy to .env.local and fill in real values. NEXT_PUBLIC_* vars are exposed to the browser. +# ============================================================================ +# Community Pro frontend — environment template +# Copy to `.env.local` and fill in real values. NEXT_PUBLIC_* vars are exposed +# to the browser — never put secrets in them. +# ============================================================================ + NEXT_PUBLIC_API_URL=http://localhost:5080 -NEXT_PUBLIC_POSTHOG_KEY= -NEXT_PUBLIC_GITHUB_ORG=https://github.com/CommunityPro -NEXT_PUBLIC_PRACTICE_REPO=https://github.com/CommunityPro/Practice-Contribution +NEXT_PUBLIC_GITHUB_ORG=CommunityPro +NEXT_PUBLIC_PRACTICE_REPO=Practice-Contribution +NEXT_PUBLIC_MEILISEARCH_HOST=http://localhost:7700 +REVALIDATE_SECRET= +NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN= +NEXT_PUBLIC_POSTHOG_HOST= diff --git a/package.json b/package.json index ae98e33..cf48cb1 100644 --- a/package.json +++ b/package.json @@ -15,12 +15,12 @@ }, "dependencies": { "@base-ui/react": "^1.5.0", + "@posthog/react": "^1.10.3", "@react-spring/three": "^10.1.2", "@react-three/drei": "^10.7.7", "@react-three/fiber": "^9.6.1", "@tanstack/react-query": "^5.100.14", "@tanstack/react-query-devtools": "^5.100.14", - "axios": "^1.16.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "framer-motion": "^12.42.2", @@ -28,12 +28,13 @@ "maath": "^0.10.8", "next": "16.2.6", "next-themes": "^0.4.6", + "posthog-js": "^1.399.2", + "posthog-node": "^5.41.0", "radix-ui": "^1.4.3", "react": "19.2.4", "react-dom": "19.2.4", "shadcn": "^4.8.0", "sonner": "^2.0.7", - "stunk": "^2.8.1", "tailwind-merge": "^3.6.0", "three": "^0.185.0", "tw-animate-css": "^1.4.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0095989..3217997 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@base-ui/react': specifier: ^1.5.0 version: 1.6.0(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@posthog/react': + specifier: ^1.10.3 + version: 1.10.3(@types/react@19.2.17)(posthog-js@1.399.2)(react@19.2.4) '@react-spring/three': specifier: ^10.1.2 version: 10.1.2(@react-three/fiber@9.6.1(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.185.1))(react@19.2.4)(three@0.185.1) @@ -26,9 +29,6 @@ importers: '@tanstack/react-query-devtools': specifier: ^5.100.14 version: 5.101.2(@tanstack/react-query@5.101.2(react@19.2.4))(react@19.2.4) - axios: - specifier: ^1.16.1 - version: 1.18.1 class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -50,6 +50,12 @@ importers: next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + posthog-js: + specifier: ^1.399.2 + version: 1.399.2 + posthog-node: + specifier: ^5.41.0 + version: 5.41.0 radix-ui: specifier: ^1.4.3 version: 1.6.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -65,9 +71,6 @@ importers: sonner: specifier: ^2.0.7 version: 2.0.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - stunk: - specifier: ^2.8.1 - version: 2.8.1(react@19.2.4) tailwind-merge: specifier: ^3.6.0 version: 3.6.0 @@ -632,6 +635,22 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} + '@posthog/core@1.40.2': + resolution: {integrity: sha512-H12j7O9iHGvpK9t2ko8W4pvfbV1pBDxrsWC1LA6yp2RhzwvC4T3sWhu+AekDQJSRSrJEWlB0t/Ueq9QhPSq7FQ==} + + '@posthog/react@1.10.3': + resolution: {integrity: sha512-Qu//fGQmVlX0B9kTA3LLg67e7AYLEmeuA0Bf1qSyUM0uUILcRQGjQezhNQPLYSTakOqvXEnl6fM2iQBF6Toxrw==} + peerDependencies: + '@types/react': '>=16.8.0' + posthog-js: '>=1.257.2' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + + '@posthog/types@1.393.0': + resolution: {integrity: sha512-vzWeEJZ7ERQhFRoQYaP5jzN1JvIu46UJyHXsuv+dTGW2r3sMgREOhNxXLZjmFHwZ8/FOHQoyqqQmXTCXZSfMSg==} + '@radix-ui/number@1.1.2': resolution: {integrity: sha512-ceTwaxc4I5IOi97DgCotl3pqiyRGvffcc0oOsE2dQYaJOFIDsDt4VWG6xEbg1QePv9QWausCEIppud/tJ1wNig==} @@ -1550,6 +1569,9 @@ packages: '@types/three@0.185.1': resolution: {integrity: sha512-db1xTb+EgYF2didW+eudSvVPtn75zo+fGsY8ShQrJY/B5ZBmC2Fiaykv3aImHAlCNEGuMPkPGXBJGLwzu5mC7A==} + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@types/validate-npm-package-name@4.0.2': resolution: {integrity: sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==} @@ -1747,10 +1769,6 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - agent-base@6.0.2: - resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} - engines: {node: '>= 6.0.0'} - ajv-formats@2.1.1: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} peerDependencies: @@ -1843,9 +1861,6 @@ packages: resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} engines: {node: '>= 0.4'} - asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - atomically@1.7.0: resolution: {integrity: sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==} engines: {node: '>=10.12.0'} @@ -1858,9 +1873,6 @@ packages: resolution: {integrity: sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA==} engines: {node: '>=4'} - axios@1.18.1: - resolution: {integrity: sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==} - axobject-query@4.1.0: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} @@ -1975,10 +1987,6 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} - commander@11.1.0: resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} engines: {node: '>=16'} @@ -2017,6 +2025,9 @@ packages: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} + core-js@3.49.0: + resolution: {integrity: sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==} + cors@2.8.6: resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} engines: {node: '>= 0.10'} @@ -2122,10 +2133,6 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} - delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} - depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} @@ -2148,6 +2155,9 @@ packages: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} + dompurify@3.4.12: + resolution: {integrity: sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg==} + dot-prop@6.0.1: resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==} engines: {node: '>=10'} @@ -2428,6 +2438,9 @@ packages: picomatch: optional: true + fflate@0.4.8: + resolution: {integrity: sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==} + fflate@0.6.10: resolution: {integrity: sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==} @@ -2465,23 +2478,10 @@ packages: flatted@3.4.2: resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} - follow-redirects@1.16.0: - resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - for-each@0.3.5: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} - form-data@4.0.6: - resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} - engines: {node: '>= 6'} - forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -2638,10 +2638,6 @@ packages: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} - https-proxy-agent@5.0.1: - resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} - engines: {node: '>= 6'} - human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -3110,18 +3106,10 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} - mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - mime-db@1.54.0: resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} engines: {node: '>= 0.6'} - mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - mime-types@3.0.2: resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} engines: {node: '>=18'} @@ -3384,6 +3372,18 @@ packages: resolution: {integrity: sha512-J7EF+8X+CzRPaJPOv9Ck2wNWJvGnnl3PcNPAdGg6GTLjyVpyQ0yATMSXRFRV01BviT/9Gwuc3rjEyJbDJG9a4w==} engines: {node: ^10 || ^12 || >=14} + posthog-js@1.399.2: + resolution: {integrity: sha512-xcvrGEgUYtIVcWPRlVfc/NkMo0IP9nwnM/dzJIAzjDOSewkdDk/9T4Vz1+gooEhXdQCPfG44jSK95mVJsoySqA==} + + posthog-node@5.41.0: + resolution: {integrity: sha512-jOkX6THOr5WD+FGUEaTxekas8c7NOC3TqJ2Byfe2KMimQdL/F/osz17uSbkNzR4V9WFZoe8YaGP3Xp0EUpPKGg==} + engines: {node: ^20.20.0 || >=22.22.0} + peerDependencies: + rxjs: ^7.0.0 + peerDependenciesMeta: + rxjs: + optional: true + potpack@1.0.2: resolution: {integrity: sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==} @@ -3391,6 +3391,14 @@ packages: resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} engines: {node: '>=20'} + preact@10.29.7: + resolution: {integrity: sha512-DCHYrK/B10yUD3ZjLfhZ3WIE/9Vf9VFUODcRE2dRomTYDpJk6z6L9wecSfhfE6M9ZTHUdyQkoC46arIDhEV84Q==} + peerDependencies: + preact-render-to-string: '>=5' + peerDependenciesMeta: + preact-render-to-string: + optional: true + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -3473,10 +3481,6 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} - proxy-from-env@2.1.0: - resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} - engines: {node: '>=10'} - punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -3485,6 +3489,9 @@ packages: resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} engines: {node: '>=0.6'} + query-selector-shadow-dom@1.0.1: + resolution: {integrity: sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw==} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -3796,17 +3803,6 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - stunk@2.8.1: - resolution: {integrity: sha512-NolnLmUXx/0D8na1lUjIhT4EdqCVNYCaFGKKIF+gUjmJcvXVj//SZ6Dvp7OSllW2XyfFembUi6BCUdhAb3dI7Q==} - peerDependencies: - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - vue: ^3.5.13 - peerDependenciesMeta: - react: - optional: true - vue: - optional: true - styled-jsx@5.1.6: resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} engines: {node: '>= 12.0.0'} @@ -4026,6 +4022,9 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + web-vitals@5.3.0: + resolution: {integrity: sha512-q6LWsLatGYZp5VGBIOvbTj6JBV2nOmC8KvWztXBmwJcfFAzhwKwbOxhUH306XY3CcaZDUlSmSuNPBsCn0bFu+g==} + webgl-constants@1.1.1: resolution: {integrity: sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg==} @@ -4672,6 +4671,19 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} + '@posthog/core@1.40.2': + dependencies: + '@posthog/types': 1.393.0 + + '@posthog/react@1.10.3(@types/react@19.2.17)(posthog-js@1.399.2)(react@19.2.4)': + dependencies: + posthog-js: 1.399.2 + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.17 + + '@posthog/types@1.393.0': {} + '@radix-ui/number@1.1.2': {} '@radix-ui/primitive@1.1.5': {} @@ -5648,6 +5660,9 @@ snapshots: fflate: 0.8.3 meshoptimizer: 1.1.1 + '@types/trusted-types@2.0.7': + optional: true + '@types/validate-npm-package-name@4.0.2': {} '@types/webxr@0.5.24': {} @@ -5831,12 +5846,6 @@ snapshots: acorn@8.17.0: {} - agent-base@6.0.2: - dependencies: - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - ajv-formats@2.1.1(ajv@8.20.0): optionalDependencies: ajv: 8.20.0 @@ -5952,8 +5961,6 @@ snapshots: async-function@1.0.0: {} - asynckit@0.4.0: {} - atomically@1.7.0: {} available-typed-arrays@1.0.7: @@ -5962,16 +5969,6 @@ snapshots: axe-core@4.12.1: {} - axios@1.18.1: - dependencies: - follow-redirects: 1.16.0 - form-data: 4.0.6 - https-proxy-agent: 5.0.1 - proxy-from-env: 2.1.0 - transitivePeerDependencies: - - debug - - supports-color - axobject-query@4.1.0: {} balanced-match@1.0.2: {} @@ -6086,10 +6083,6 @@ snapshots: color-name@1.1.4: {} - combined-stream@1.0.8: - dependencies: - delayed-stream: 1.0.0 - commander@11.1.0: {} commander@14.0.3: {} @@ -6121,6 +6114,8 @@ snapshots: cookie@0.7.2: {} + core-js@3.49.0: {} + cors@2.8.6: dependencies: object-assign: 4.1.1 @@ -6210,8 +6205,6 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 - delayed-stream@1.0.0: {} - depd@2.0.0: {} detect-gpu@5.0.70: @@ -6228,6 +6221,10 @@ snapshots: dependencies: esutils: 2.0.3 + dompurify@3.4.12: + optionalDependencies: + '@types/trusted-types': 2.0.7 + dot-prop@6.0.1: dependencies: is-obj: 2.0.0 @@ -6697,6 +6694,8 @@ snapshots: optionalDependencies: picomatch: 4.0.5 + fflate@0.4.8: {} + fflate@0.6.10: {} fflate@0.8.3: {} @@ -6740,20 +6739,10 @@ snapshots: flatted@3.4.2: {} - follow-redirects@1.16.0: {} - for-each@0.3.5: dependencies: is-callable: 1.2.7 - form-data@4.0.6: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - es-set-tostringtag: 2.1.0 - hasown: 2.0.4 - mime-types: 2.1.35 - forwarded@0.2.0: {} framer-motion@12.42.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4): @@ -6899,13 +6888,6 @@ snapshots: statuses: 2.0.2 toidentifier: 1.0.1 - https-proxy-agent@5.0.1: - dependencies: - agent-base: 6.0.2 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - human-signals@2.1.0: {} human-signals@8.0.1: {} @@ -7298,14 +7280,8 @@ snapshots: braces: 3.0.3 picomatch: 2.3.2 - mime-db@1.52.0: {} - mime-db@1.54.0: {} - mime-types@2.1.35: - dependencies: - mime-db: 1.52.0 - mime-types@3.0.2: dependencies: mime-db: 1.54.0 @@ -7569,10 +7545,29 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + posthog-js@1.399.2: + dependencies: + '@posthog/core': 1.40.2 + '@posthog/types': 1.393.0 + core-js: 3.49.0 + dompurify: 3.4.12 + fflate: 0.4.8 + preact: 10.29.7 + query-selector-shadow-dom: 1.0.1 + web-vitals: 5.3.0 + transitivePeerDependencies: + - preact-render-to-string + + posthog-node@5.41.0: + dependencies: + '@posthog/core': 1.40.2 + potpack@1.0.2: {} powershell-utils@0.1.0: {} + preact@10.29.7: {} + prelude-ls@1.2.1: {} prettier-plugin-tailwindcss@0.8.0(prettier@3.9.5): @@ -7606,8 +7601,6 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 - proxy-from-env@2.1.0: {} - punycode@2.3.1: {} qs@6.15.3: @@ -7615,6 +7608,8 @@ snapshots: es-define-property: 1.0.1 side-channel: 1.1.1 + query-selector-shadow-dom@1.0.1: {} + queue-microtask@1.2.3: {} radix-ui@1.6.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): @@ -8093,10 +8088,6 @@ snapshots: strip-json-comments@3.1.1: {} - stunk@2.8.1(react@19.2.4): - optionalDependencies: - react: 19.2.4 - styled-jsx@5.1.6(@babel/core@7.29.7)(react@19.2.4): dependencies: client-only: 0.0.1 @@ -8336,6 +8327,8 @@ snapshots: vary@1.1.2: {} + web-vitals@5.3.0: {} + webgl-constants@1.1.1: {} webgl-sdf-generator@1.1.1: {} diff --git a/src/app/(auth)/layout.tsx b/src/app/(auth)/layout.tsx new file mode 100644 index 0000000..2bbb63c --- /dev/null +++ b/src/app/(auth)/layout.tsx @@ -0,0 +1,15 @@ +import type React from "react"; + +interface Props { + children: React.ReactNode; +} + +const DashboardLayout = ({}: Props) => { + return ( +
+
+
+ ); +}; + +export default DashboardLayout; diff --git a/src/app/(member)/layout.tsx b/src/app/(member)/layout.tsx new file mode 100644 index 0000000..d08836b --- /dev/null +++ b/src/app/(member)/layout.tsx @@ -0,0 +1,9 @@ +import { RequireActiveMember } from "@/features/auth"; + +export default function MemberLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return {children}; +} diff --git a/src/app/(member)/profile/page.tsx b/src/app/(member)/profile/page.tsx new file mode 100644 index 0000000..60b71d3 --- /dev/null +++ b/src/app/(member)/profile/page.tsx @@ -0,0 +1,19 @@ +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Your profile | Community Pro", +}; + +export default function Page() { + return ( +
+

You're in

+

Welcome to Community Pro

+ {/* why: placeholder until Phase 2 ships profile editing — keeps the post-activation + redirect from landing on a 404. */} +

+ Profile setup is on its way. Soon you'll add your skills, portfolio link, and avatar here. +

+
+ ); +} diff --git a/src/app/(public)/join-community/page.tsx b/src/app/(public)/join-community/page.tsx index f431a64..42a16b1 100644 --- a/src/app/(public)/join-community/page.tsx +++ b/src/app/(public)/join-community/page.tsx @@ -1,9 +1,33 @@ -const Page = () => { - return ( -
-

Join Community

-
- ); +import type { Metadata } from "next"; + +import { SignInButton } from "@/features/auth"; + +export const metadata: Metadata = { + title: "Join the community | Community Pro", + description: "Sign in with GitHub, raise one pull request, and earn your membership.", }; -export default Page; +const errorMessages: Record = { + deactivated: "This account has been deactivated. Reach out to an admin if you think that's a mistake.", + oauth_failed: "GitHub sign-in didn't complete. Give it another try.", + status_unavailable: "We couldn't confirm your membership status. Please sign in again.", +}; + +export default async function Page({ searchParams }: { searchParams: Promise<{ error?: string }> }) { + const { error } = await searchParams; + const errorMessage = error ? errorMessages[error] : undefined; + + return ( +
+
+

Join Community Pro

+

+ Membership is earned with a single merged pull request. Sign in with GitHub to get started — we'll walk + you through the rest. +

+
+ {errorMessage ?

{errorMessage}

: null} + +
+ ); +} diff --git a/src/app/(public)/page.tsx b/src/app/(public)/page.tsx index 6eca10f..5e7e566 100644 --- a/src/app/(public)/page.tsx +++ b/src/app/(public)/page.tsx @@ -1,15 +1,17 @@ "use client"; import { motion, useMotionValue, useSpring } from "framer-motion"; -import { useRef } from "react"; +import { usePostHog } from "@posthog/react"; +import { useEffect, useRef } from "react"; import Link from "next/link"; import { Avatar, AvatarGroup, AvatarImage } from "@/components/ui/avatar"; -// import { ScrollLock } from "@/components/shared"; import { Button } from "@/components/ui/button"; import { AVATARS, METRICS } from "@/constants"; const Page = () => { + const posthong = usePostHog() + const ref = useRef(null); const rotateX = useMotionValue(0); const rotateY = useMotionValue(0); @@ -31,6 +33,10 @@ const Page = () => { rotateY.set(0); }; + useEffect(() => { + posthong.capture('page_view') + },[posthong]) + return (
+
+

Almost there

+

One PR away from membership

+

+ Community Pro membership is earned, not granted. Show us a merged pull request in our practice repo and + you're in. +

+
+ +
    + {steps.map(({ icon: Icon, title, body }, index) => ( +
  1. + + + +

    + {index + 1}. {title} +

    +

    {body}

    +
  2. + ))} +
+ + + + ); +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index ccebaa1..c00db02 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -3,7 +3,9 @@ import { ThemeProvider } from "next-themes"; import type { Metadata } from "next"; import "./globals.css"; -import { AppProvider, ErrorBoundary, QueryProvider } from "@/components/providers"; +import { AppProvider, ErrorBoundary, PostHogProvider, QueryProvider } from "@/components/providers"; +import { Toaster } from "@/components/ui/sonner"; +import { AuthProvider } from "@/features/auth"; const space_grotesk = Space_Grotesk({ style: ["normal"], subsets: ["latin"], variable: "--font-space-grotesk" }); const raleway = Raleway({ style: ["italic", "normal"], subsets: ["latin"], variable: "--font-raleway" }); @@ -28,11 +30,16 @@ export default function RootLayout({ - - - {children} - - + + + + + {children} + + + + + diff --git a/src/components/providers/index.ts b/src/components/providers/index.ts index 7d45e82..5b91b0c 100644 --- a/src/components/providers/index.ts +++ b/src/components/providers/index.ts @@ -1,3 +1,4 @@ export * from "./app-context"; export * from "./error-boundary"; +export * from "./post-hog"; export * from "./query-provider"; diff --git a/src/components/providers/post-hog.tsx b/src/components/providers/post-hog.tsx new file mode 100644 index 0000000..4747273 --- /dev/null +++ b/src/components/providers/post-hog.tsx @@ -0,0 +1,18 @@ +"use client"; + +// import { usePathname, useSearchParams } from "next/navigation" +import { useEffect } from "react"; + +import posthog from "posthog-js"; +import { PostHogProvider as PHProvider } from "@posthog/react"; + +export function PostHogProvider({ children }: { children: React.ReactNode }) { + useEffect(() => { + posthog.init(process.env.NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN as string, { + api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST, + defaults: "2026-05-30", + }); + }, []); + + return {children}; +} diff --git a/src/components/shared/sidebar.tsx b/src/components/shared/sidebar.tsx new file mode 100644 index 0000000..190a5bc --- /dev/null +++ b/src/components/shared/sidebar.tsx @@ -0,0 +1,15 @@ +"use client"; + +import { useGlobalStore } from "@/store"; +import { cn } from "@/lib"; + +export const Sidebar = () => { + const { isCollapsed } = useGlobalStore(); + + return ( + + ); +}; diff --git a/src/features/auth/index.ts b/src/features/auth/index.ts index 7b5a29f..f65ad3f 100644 --- a/src/features/auth/index.ts +++ b/src/features/auth/index.ts @@ -1,6 +1,7 @@ export * from "./api"; export * from "./auth-provider"; export * from "./guards"; +export * from "./pending-status"; export * from "./session"; export * from "./sign-in-button"; export * from "./store"; diff --git a/src/features/auth/pending-status.tsx b/src/features/auth/pending-status.tsx new file mode 100644 index 0000000..d193999 --- /dev/null +++ b/src/features/auth/pending-status.tsx @@ -0,0 +1,59 @@ +"use client"; + +import { RefreshCw } from "lucide-react"; +import { useRouter } from "next/navigation"; +import { useEffect, useState } from "react"; +import { toast } from "sonner"; + +import { Button } from "@/components/ui/button"; + +import { useAuthStore } from "./store"; + +/** + * Interactive footer of the `/welcome` pending screen: redirects visitors who don't + * belong here (anonymous → join page, already-active → profile) and offers a + * "Check my status" action that re-resolves `GET /me` after their practice PR merges. + * + * @returns The status-check button with contextual redirects wired in. + * @example + * + */ +export const PendingStatusActions = () => { + const router = useRouter(); + const status = useAuthStore((state) => state.status); + const user = useAuthStore((state) => state.user); + const hydrate = useAuthStore((state) => state.hydrate); + const [checking, setChecking] = useState(false); + + useEffect(() => { + if (status === "loading") return; + if (status === "anonymous" || user === null) { + router.replace("/join-community"); + return; + } + if (user.status === "active") router.replace("/profile"); + }, [router, status, user]); + + const checkStatus = async () => { + setChecking(true); + try { + await hydrate(); + const fresh = useAuthStore.getState().user; + if (fresh?.status === "active") { + toast.success("Your PR is merged — welcome aboard!"); + router.replace("/profile"); + } else { + toast.info("Not merged yet. We'll activate you the moment it lands."); + } + } finally { + setChecking(false); + } + }; + + return ( + + ); +}; diff --git a/src/features/auth/store.ts b/src/features/auth/store.ts index bf4f9fc..dc3b391 100644 --- a/src/features/auth/store.ts +++ b/src/features/auth/store.ts @@ -35,9 +35,11 @@ type AuthState = { * const status = useAuthStore((state) => state.status); * if (status === "anonymous") return ; */ +// why: starting at "loading" (not "anonymous") keeps guards from redirecting during the +// window between first render and AuthProvider's hydrate effect settling the real status. export const useAuthStore = create((set) => ({ user: null, - status: "anonymous", + status: "loading", hydrate: async () => { set({ status: "loading" }); try { diff --git a/src/lib/index.ts b/src/lib/index.ts index 735ce07..68155ba 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -1,3 +1,2 @@ export * from "./utils"; -export * from "./client"; -export * from "./query"; +export * from "./env"; diff --git a/src/lib/query.ts b/src/lib/query.ts deleted file mode 100644 index 028e374..0000000 --- a/src/lib/query.ts +++ /dev/null @@ -1,120 +0,0 @@ -"use client"; - -import { QueryClient, useMutation, useQuery } from "@tanstack/react-query"; -import { useState } from "react"; - -import { http } from "./client"; -import type { - MutationConfig, - MutationReturn, - PaginatedQueryConfig, - PaginatedQueryReturn, - PaginatedResponse, - QueryConfig, - QueryReturn, -} from "@/types"; -import { removeNullOrUndefined } from "./utils"; - -export function makeQueryClient() { - return new QueryClient({ - defaultOptions: { - queries: { - refetchOnWindowFocus: false, - retry: 3, - retryDelay: (attempt) => Math.min(1_000 * 2 ** attempt, 30_000), - staleTime: 30_000, - }, - mutations: { - retry: 0, - }, - }, - }); -} - -const fetcher = async ( - url: string, - params?: TParams, - transform?: (raw: unknown) => TData, -): Promise => { - const raw = await http.get(url, { params }); - return transform ? transform(raw) : (raw as TData); -}; - -const mutator = async ( - method: "POST" | "PUT" | "DELETE" | "PATCH", - url: string, - variables: TVariables, - transform?: (raw: unknown) => TData, -): Promise => { - const raw = await http[method.toLowerCase() as "post" | "put" | "delete" | "patch"](url, variables); - return transform ? transform(raw) : (raw as TData); -}; - -export const useApiMutation = ( - config: MutationConfig, -): MutationReturn => { - const queryClient = makeQueryClient(); - - const result = useMutation({ - mutationFn: (variables: TVariables) => - mutator(config.method, config.url, variables, config.transform), - onMutate: async (variables) => { - if (!config.optimistic || !config.optimisticQueryKey) return; - await queryClient.cancelQueries({ queryKey: config.optimisticQueryKey }); - const snapshot = queryClient.getQueryData(config.optimisticQueryKey); - queryClient.setQueryData(config.optimisticQueryKey, (current) => - config.optimistic!.updater(current, variables), - ); - return snapshot; - }, - onError: (_, variables, context) => { - if (config.optimistic && config.optimisticQueryKey) { - const ctx = context as { snapshot: TCache } | undefined; - queryClient.setQueryData( - config.optimisticQueryKey, - config.optimistic.rollback - ? (current) => config.optimistic!.rollback!(current, variables) - : () => ctx?.snapshot, - ); - } - }, - onSuccess: (data) => { - const keys = typeof config.invalidates === "function" ? config.invalidates(data) : (config.invalidates ?? []); - keys.forEach((key) => queryClient.invalidateQueries({ queryKey: key as readonly unknown[] })); - }, - }); - - return { ...result, kind: "mutation" }; -}; - -export const useApiQuery = ( - config: QueryConfig, -): QueryReturn => { - const _params = removeNullOrUndefined(config.params || {}); - - const result = useQuery({ - queryKey: [config.url, JSON.stringify(_params)], - queryFn: () => fetcher(config.url, { params: { ..._params } }), - enabled: config.enabled || true, - staleTime: 30_000, - }); - - return { ...result, kind: "query" }; -}; - -export const useApiPaginatedQuery = ( - config: PaginatedQueryConfig, -): PaginatedQueryReturn => { - const [pageSize, setPageSize] = useState(config.pageSize || 10); - const [page, setPage] = useState(config.initialPage || 1); - const _params = removeNullOrUndefined(config.params || {}); - - const result = useQuery, Error>({ - queryKey: [config.url, JSON.stringify(_params), { page, pageSize }], - queryFn: () => http.get>(config.url, { params: { ..._params, page, pageSize } }), - staleTime: 30_000, - enabled: config.enabled || true, - }); - - return { ...result, kind: "paginated", page, pageSize, setPage, setPageSize }; -}; diff --git a/src/store/.gitkeep b/src/store/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/store/global.ts b/src/store/global.ts new file mode 100644 index 0000000..64f042d --- /dev/null +++ b/src/store/global.ts @@ -0,0 +1,16 @@ +import { createPersistMiddleware } from "./middleware"; + +interface GlobalState { + isCollapsed: boolean; + onCollapsedChange: () => void; +} + +const initialState: GlobalState = { + isCollapsed: false, + onCollapsedChange: () => {}, +}; + +export const useGlobalStore = createPersistMiddleware("CP_GLOBAL", (set) => ({ + ...initialState, + onCollapsedChange: () => set((state) => ({ isCollapsed: !state.isCollapsed })), +})); diff --git a/src/store/index.ts b/src/store/index.ts new file mode 100644 index 0000000..0937023 --- /dev/null +++ b/src/store/index.ts @@ -0,0 +1 @@ +export * from "./global"; diff --git a/src/store/middleware/index.ts b/src/store/middleware/index.ts new file mode 100644 index 0000000..8bf097f --- /dev/null +++ b/src/store/middleware/index.ts @@ -0,0 +1,2 @@ +export * from "./persist"; +export * from "./report"; diff --git a/src/store/middleware/persist.ts b/src/store/middleware/persist.ts new file mode 100644 index 0000000..c5910b4 --- /dev/null +++ b/src/store/middleware/persist.ts @@ -0,0 +1,8 @@ +import type { StateCreator } from "zustand/vanilla"; +import { persist } from "zustand/middleware"; +import { create } from "zustand/react"; + +import { reportException } from "./report"; + +export const createPersistMiddleware = (name: string, storeCreator: StateCreator) => + create(reportException(persist(storeCreator, { name: name || "z:root" }) as StateCreator)); diff --git a/src/store/middleware/report.ts b/src/store/middleware/report.ts new file mode 100644 index 0000000..70d6035 --- /dev/null +++ b/src/store/middleware/report.ts @@ -0,0 +1,21 @@ +import type { StateCreator } from "zustand/vanilla"; +import { create } from "zustand/react"; + +export const reportException = + (stateCreator: StateCreator): StateCreator => + (set, get, api) => + stateCreator( + (args) => { + try { + set(args); + } catch (error: unknown) { + console.error("Error :(", error); + throw error; + } + }, + get, + api, + ); + +export const createReportableStore = (storeCreator: StateCreator) => + create()(reportException(storeCreator)); From 97b51609f91ccd3c33906aca48c50b218683a138 Mon Sep 17 00:00:00 2001 From: Samson Okunola Date: Sun, 12 Jul 2026 12:05:20 +0100 Subject: [PATCH 2/3] chore: fix formatting and consistency in page.tsx --- src/app/(public)/page.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/(public)/page.tsx b/src/app/(public)/page.tsx index 5e7e566..aad6801 100644 --- a/src/app/(public)/page.tsx +++ b/src/app/(public)/page.tsx @@ -10,7 +10,7 @@ import { Button } from "@/components/ui/button"; import { AVATARS, METRICS } from "@/constants"; const Page = () => { - const posthong = usePostHog() + const posthong = usePostHog(); const ref = useRef(null); const rotateX = useMotionValue(0); @@ -34,8 +34,8 @@ const Page = () => { }; useEffect(() => { - posthong.capture('page_view') - },[posthong]) + posthong.capture("page_view"); + }, [posthong]); return (
From a0b924ddb2b20fd539d1066c171cbcf059e50360 Mon Sep 17 00:00:00 2001 From: Samson Okunola Date: Sun, 12 Jul 2026 12:07:03 +0100 Subject: [PATCH 3/3] chore: add axios dependency to package.json and update pnpm-lock.yaml --- package.json | 1 + pnpm-lock.yaml | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) diff --git a/package.json b/package.json index cf48cb1..0358b0f 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@react-three/fiber": "^9.6.1", "@tanstack/react-query": "^5.100.14", "@tanstack/react-query-devtools": "^5.100.14", + "axios": "^1.18.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "framer-motion": "^12.42.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3217997..dd5e8ad 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,6 +29,9 @@ importers: '@tanstack/react-query-devtools': specifier: ^5.100.14 version: 5.101.2(@tanstack/react-query@5.101.2(react@19.2.4))(react@19.2.4) + axios: + specifier: ^1.18.1 + version: 1.18.1 class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -1769,6 +1772,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + ajv-formats@2.1.1: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} peerDependencies: @@ -1861,6 +1868,9 @@ packages: resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} engines: {node: '>= 0.4'} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + atomically@1.7.0: resolution: {integrity: sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==} engines: {node: '>=10.12.0'} @@ -1873,6 +1883,9 @@ packages: resolution: {integrity: sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA==} engines: {node: '>=4'} + axios@1.18.1: + resolution: {integrity: sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==} + axobject-query@4.1.0: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} @@ -1987,6 +2000,10 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + commander@11.1.0: resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} engines: {node: '>=16'} @@ -2133,6 +2150,10 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} @@ -2478,10 +2499,23 @@ packages: flatted@3.4.2: resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + follow-redirects@1.16.0: + resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + for-each@0.3.5: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} + form-data@4.0.6: + resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} + engines: {node: '>= 6'} + forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -2638,6 +2672,10 @@ packages: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -3106,10 +3144,18 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + mime-db@1.54.0: resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} engines: {node: '>= 0.6'} + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + mime-types@3.0.2: resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} engines: {node: '>=18'} @@ -3481,6 +3527,10 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} + proxy-from-env@2.1.0: + resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} + engines: {node: '>=10'} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -5846,6 +5896,12 @@ snapshots: acorn@8.17.0: {} + agent-base@6.0.2: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + ajv-formats@2.1.1(ajv@8.20.0): optionalDependencies: ajv: 8.20.0 @@ -5961,6 +6017,8 @@ snapshots: async-function@1.0.0: {} + asynckit@0.4.0: {} + atomically@1.7.0: {} available-typed-arrays@1.0.7: @@ -5969,6 +6027,16 @@ snapshots: axe-core@4.12.1: {} + axios@1.18.1: + dependencies: + follow-redirects: 1.16.0 + form-data: 4.0.6 + https-proxy-agent: 5.0.1 + proxy-from-env: 2.1.0 + transitivePeerDependencies: + - debug + - supports-color + axobject-query@4.1.0: {} balanced-match@1.0.2: {} @@ -6083,6 +6151,10 @@ snapshots: color-name@1.1.4: {} + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + commander@11.1.0: {} commander@14.0.3: {} @@ -6205,6 +6277,8 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 + delayed-stream@1.0.0: {} + depd@2.0.0: {} detect-gpu@5.0.70: @@ -6739,10 +6813,20 @@ snapshots: flatted@3.4.2: {} + follow-redirects@1.16.0: {} + for-each@0.3.5: dependencies: is-callable: 1.2.7 + form-data@4.0.6: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.4 + mime-types: 2.1.35 + forwarded@0.2.0: {} framer-motion@12.42.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4): @@ -6888,6 +6972,13 @@ snapshots: statuses: 2.0.2 toidentifier: 1.0.1 + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + human-signals@2.1.0: {} human-signals@8.0.1: {} @@ -7280,8 +7371,14 @@ snapshots: braces: 3.0.3 picomatch: 2.3.2 + mime-db@1.52.0: {} + mime-db@1.54.0: {} + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + mime-types@3.0.2: dependencies: mime-db: 1.54.0 @@ -7601,6 +7698,8 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 + proxy-from-env@2.1.0: {} + punycode@2.3.1: {} qs@6.15.3: