From 50a6451bea4bf762d0144b8224bd323ea2f3daff Mon Sep 17 00:00:00 2001 From: blelian Date: Sun, 5 Oct 2025 20:49:12 +0200 Subject: [PATCH 01/22] =?UTF-8?q?=E2=9C=85=20Fixed=20login=20flow,=20cooki?= =?UTF-8?q?es=20saved,=20and=20sign=20up=20forms=20working=20for=20custome?= =?UTF-8?q?r=20&=20seller?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- handcrafted-haven/package.json | 4 + handcrafted-haven/pnpm-lock.yaml | 305 ++++++++++++++++++ handcrafted-haven/prisma/schema.prisma | 61 ++++ handcrafted-haven/src/app/api/login/route.ts | 39 +++ handcrafted-haven/src/app/api/signup/route.ts | 59 ++++ handcrafted-haven/src/app/login/page.tsx | 24 +- handcrafted-haven/src/app/logout/page.tsx | 25 ++ handcrafted-haven/src/app/middleware.ts | 34 ++ .../src/app/seller/dashboard/page.tsx | 25 ++ .../src/app/signup/customer/page.tsx | 128 +++++--- handcrafted-haven/src/app/signup/page.tsx | 5 +- .../src/app/signup/seller/page.tsx | 175 ++++++---- handcrafted-haven/src/app/ui/login-form.tsx | 142 ++++---- handcrafted-haven/src/app/ui/side-links.tsx | 89 ++++- handcrafted-haven/src/app/ui/sidenav.tsx | 35 +- handcrafted-haven/src/app/ui/signup-form.tsx | 118 +++++++ .../src/customer/dashboard/page.tsx | 22 ++ 17 files changed, 1083 insertions(+), 207 deletions(-) create mode 100644 handcrafted-haven/prisma/schema.prisma create mode 100644 handcrafted-haven/src/app/api/login/route.ts create mode 100644 handcrafted-haven/src/app/api/signup/route.ts create mode 100644 handcrafted-haven/src/app/logout/page.tsx create mode 100644 handcrafted-haven/src/app/middleware.ts create mode 100644 handcrafted-haven/src/app/seller/dashboard/page.tsx create mode 100644 handcrafted-haven/src/app/ui/signup-form.tsx create mode 100644 handcrafted-haven/src/customer/dashboard/page.tsx diff --git a/handcrafted-haven/package.json b/handcrafted-haven/package.json index 28c0d53..199ad28 100644 --- a/handcrafted-haven/package.json +++ b/handcrafted-haven/package.json @@ -10,10 +10,14 @@ }, "dependencies": { "@heroicons/react": "^2.2.0", + "@prisma/client": "^6.16.3", "@tailwindcss/forms": "^0.5.10", + "bcrypt": "^6.0.0", "clsx": "^2.1.1", + "js-cookie": "^3.0.5", "lucide-react": "^0.544.0", "next": "15.5.3", + "prisma": "^6.16.3", "react": "19.1.0", "react-dom": "19.1.0" }, diff --git a/handcrafted-haven/pnpm-lock.yaml b/handcrafted-haven/pnpm-lock.yaml index ad29c90..d2a48be 100644 --- a/handcrafted-haven/pnpm-lock.yaml +++ b/handcrafted-haven/pnpm-lock.yaml @@ -11,18 +11,30 @@ importers: '@heroicons/react': specifier: ^2.2.0 version: 2.2.0(react@19.1.0) + '@prisma/client': + specifier: ^6.16.3 + version: 6.16.3(prisma@6.16.3(typescript@5.9.2))(typescript@5.9.2) '@tailwindcss/forms': specifier: ^0.5.10 version: 0.5.10(tailwindcss@4.1.13) + bcrypt: + specifier: ^6.0.0 + version: 6.0.0 clsx: specifier: ^2.1.1 version: 2.1.1 + js-cookie: + specifier: ^3.0.5 + version: 3.0.5 lucide-react: specifier: ^0.544.0 version: 0.544.0(react@19.1.0) next: specifier: 15.5.3 version: 15.5.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + prisma: + specifier: ^6.16.3 + version: 6.16.3(typescript@5.9.2) react: specifier: 19.1.0 version: 19.1.0 @@ -357,12 +369,45 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} + '@prisma/client@6.16.3': + resolution: {integrity: sha512-JfNfAtXG+/lIopsvoZlZiH2k5yNx87mcTS4t9/S5oufM1nKdXYxOvpDC1XoTCFBa5cQh7uXnbMPsmZrwZY80xw==} + engines: {node: '>=18.18'} + peerDependencies: + prisma: '*' + typescript: '>=5.1.0' + peerDependenciesMeta: + prisma: + optional: true + typescript: + optional: true + + '@prisma/config@6.16.3': + resolution: {integrity: sha512-VlsLnG4oOuKGGMToEeVaRhoTBZu5H3q51jTQXb/diRags3WV0+BQK5MolJTtP6G7COlzoXmWeS11rNBtvg+qFQ==} + + '@prisma/debug@6.16.3': + resolution: {integrity: sha512-89DdqWtdKd7qoc9/qJCKLTazj3W3zPEiz0hc7HfZdpjzm21c7orOUB5oHWJsG+4KbV4cWU5pefq3CuDVYF9vgA==} + + '@prisma/engines-version@6.16.1-1.bb420e667c1820a8c05a38023385f6cc7ef8e83a': + resolution: {integrity: sha512-fftRmosBex48Ph1v2ll1FrPpirwtPZpNkE5CDCY1Lw2SD2ctyrLlVlHiuxDAAlALwWBOkPbAll4+EaqdGuMhJw==} + + '@prisma/engines@6.16.3': + resolution: {integrity: sha512-b+Rl4nzQDcoqe6RIpSHv8f5lLnwdDGvXhHjGDiokObguAAv/O1KaX1Oc69mBW/GFWKQpCkOraobLjU6s1h8HGg==} + + '@prisma/fetch-engine@6.16.3': + resolution: {integrity: sha512-bUoRIkVaI+CCaVGrSfcKev0/Mk4ateubqWqGZvQ9uCqFv2ENwWIR3OeNuGin96nZn5+SkebcD7RGgKr/+mJelw==} + + '@prisma/get-platform@6.16.3': + resolution: {integrity: sha512-X1LxiFXinJ4iQehrodGp0f66Dv6cDL0GbRlcCoLtSu6f4Wi+hgo7eND/afIs5029GQLgNWKZ46vn8hjyXTsHLA==} + '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} '@rushstack/eslint-patch@1.12.0': resolution: {integrity: sha512-5EwMtOqvJMMa3HbmxLlF74e+3/HhwBTMcvt3nqVJgGCozO6hzIPOBlwm8mGVNR9SN2IJpxSnlxczyDjcn7qIyw==} + '@standard-schema/spec@1.0.0': + resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} @@ -725,6 +770,10 @@ packages: resolution: {integrity: sha512-wrH5NNqren/QMtKUEEJf7z86YjfqW/2uw3IL3/xpqZUC95SSVIFXYQeeGjL6FT/X68IROu6RMehZQS5foy2BXw==} hasBin: true + bcrypt@6.0.0: + resolution: {integrity: sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==} + engines: {node: '>= 18'} + brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -740,6 +789,14 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + c12@3.1.0: + resolution: {integrity: sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==} + peerDependencies: + magicast: ^0.3.5 + peerDependenciesMeta: + magicast: + optional: true + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -763,10 +820,17 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + chownr@3.0.0: resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} engines: {node: '>=18'} + citty@0.1.6: + resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} + client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} @@ -784,6 +848,13 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + confbox@0.2.2: + resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -826,6 +897,10 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + deepmerge-ts@7.1.5: + resolution: {integrity: sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==} + engines: {node: '>=16.0.0'} + define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} @@ -834,6 +909,12 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + detect-libc@2.1.0: resolution: {integrity: sha512-vEtk+OcP7VBRtQZ1EJ3bdgzSfBjgnEalLTp5zjJrS+2Z1w2KZly4SBdac/WDU3hhsNAZ9E8SC96ME4Ey8MZ7cg==} engines: {node: '>=8'} @@ -842,16 +923,27 @@ packages: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + effect@3.16.12: + resolution: {integrity: sha512-N39iBk0K71F9nb442TLbTkjl24FLUzuvx2i1I2RsEAQsdAdUTuUoW0vlfUXgkMTUOnYqKnWcFfqw4hK4Pw27hg==} + electron-to-chromium@1.5.223: resolution: {integrity: sha512-qKm55ic6nbEmagFlTFczML33rF90aU+WtrJ9MdTCThrcvDNdUHN4p6QfVN78U06ZmguqXIyMPyYhw2TrbDUwPQ==} emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + empathic@2.0.0: + resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} + engines: {node: '>=14'} + enhanced-resolve@5.18.3: resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} engines: {node: '>=10.13.0'} @@ -1012,6 +1104,13 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + exsolve@1.0.7: + resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==} + + fast-check@3.23.2: + resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} + engines: {node: '>=8.0.0'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1092,6 +1191,10 @@ packages: get-tsconfig@4.10.1: resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} + giget@2.0.0: + resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} + hasBin: true + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -1282,6 +1385,10 @@ packages: resolution: {integrity: sha512-VXe6RjJkBPj0ohtqaO8vSWP3ZhAKo66fKrFNCll4BTcwljPLz03pCbaNKfzGP5MbrCYcbJ7v0nOYYwUzTEIdXQ==} hasBin: true + js-cookie@3.0.5: + resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} + engines: {node: '>=14'} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -1474,6 +1581,17 @@ packages: sass: optional: true + node-addon-api@8.5.0: + resolution: {integrity: sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==} + engines: {node: ^18 || ^20 || >= 21} + + node-fetch-native@1.6.7: + resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} + + node-gyp-build@4.8.4: + resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} + hasBin: true + node-releases@2.0.21: resolution: {integrity: sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==} @@ -1481,6 +1599,11 @@ packages: resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} engines: {node: '>=0.10.0'} + nypm@0.6.2: + resolution: {integrity: sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==} + engines: {node: ^14.16.0 || >=16.10.0} + hasBin: true + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -1513,6 +1636,9 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -1544,6 +1670,12 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -1555,6 +1687,9 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + pkg-types@2.3.0: + resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -1574,6 +1709,16 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + prisma@6.16.3: + resolution: {integrity: sha512-4tJq3KB9WRshH5+QmzOLV54YMkNlKOtLKaSdvraI5kC/axF47HuOw6zDM8xrxJ6s9o2WodY654On4XKkrobQdQ==} + engines: {node: '>=18.18'} + hasBin: true + peerDependencies: + typescript: '>=5.1.0' + peerDependenciesMeta: + typescript: + optional: true + prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -1581,9 +1726,15 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + rc9@2.1.2: + resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} + react-dom@19.1.0: resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==} peerDependencies: @@ -1596,6 +1747,10 @@ packages: resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} engines: {node: '>=0.10.0'} + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + reflect.getprototypeof@1.0.10: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} @@ -1765,6 +1920,9 @@ packages: resolution: {integrity: sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==} engines: {node: '>=18'} + tinyexec@1.0.1: + resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==} + tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} @@ -2104,10 +2262,47 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} + '@prisma/client@6.16.3(prisma@6.16.3(typescript@5.9.2))(typescript@5.9.2)': + optionalDependencies: + prisma: 6.16.3(typescript@5.9.2) + typescript: 5.9.2 + + '@prisma/config@6.16.3': + dependencies: + c12: 3.1.0 + deepmerge-ts: 7.1.5 + effect: 3.16.12 + empathic: 2.0.0 + transitivePeerDependencies: + - magicast + + '@prisma/debug@6.16.3': {} + + '@prisma/engines-version@6.16.1-1.bb420e667c1820a8c05a38023385f6cc7ef8e83a': {} + + '@prisma/engines@6.16.3': + dependencies: + '@prisma/debug': 6.16.3 + '@prisma/engines-version': 6.16.1-1.bb420e667c1820a8c05a38023385f6cc7ef8e83a + '@prisma/fetch-engine': 6.16.3 + '@prisma/get-platform': 6.16.3 + + '@prisma/fetch-engine@6.16.3': + dependencies: + '@prisma/debug': 6.16.3 + '@prisma/engines-version': 6.16.1-1.bb420e667c1820a8c05a38023385f6cc7ef8e83a + '@prisma/get-platform': 6.16.3 + + '@prisma/get-platform@6.16.3': + dependencies: + '@prisma/debug': 6.16.3 + '@rtsao/scc@1.1.0': {} '@rushstack/eslint-patch@1.12.0': {} + '@standard-schema/spec@1.0.0': {} + '@swc/helpers@0.5.15': dependencies: tslib: 2.8.1 @@ -2478,6 +2673,11 @@ snapshots: baseline-browser-mapping@2.8.6: {} + bcrypt@6.0.0: + dependencies: + node-addon-api: 8.5.0 + node-gyp-build: 4.8.4 + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -2499,6 +2699,21 @@ snapshots: node-releases: 2.0.21 update-browserslist-db: 1.1.3(browserslist@4.26.2) + c12@3.1.0: + dependencies: + chokidar: 4.0.3 + confbox: 0.2.2 + defu: 6.1.4 + dotenv: 16.6.1 + exsolve: 1.0.7 + giget: 2.0.0 + jiti: 2.6.0 + ohash: 2.0.11 + pathe: 2.0.3 + perfect-debounce: 1.0.0 + pkg-types: 2.3.0 + rc9: 2.1.2 + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -2525,8 +2740,16 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + chownr@3.0.0: {} + citty@0.1.6: + dependencies: + consola: 3.4.2 + client-only@0.0.1: {} clsx@2.1.1: {} @@ -2539,6 +2762,10 @@ snapshots: concat-map@0.0.1: {} + confbox@0.2.2: {} + + consola@3.4.2: {} + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -2577,6 +2804,8 @@ snapshots: deep-is@0.1.4: {} + deepmerge-ts@7.1.5: {} + define-data-property@1.1.4: dependencies: es-define-property: 1.0.1 @@ -2589,22 +2818,35 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 + defu@6.1.4: {} + + destr@2.0.5: {} + detect-libc@2.1.0: {} doctrine@2.1.0: dependencies: esutils: 2.0.3 + dotenv@16.6.1: {} + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 es-errors: 1.3.0 gopd: 1.2.0 + effect@3.16.12: + dependencies: + '@standard-schema/spec': 1.0.0 + fast-check: 3.23.2 + electron-to-chromium@1.5.223: {} emoji-regex@9.2.2: {} + empathic@2.0.0: {} + enhanced-resolve@5.18.3: dependencies: graceful-fs: 4.2.11 @@ -2912,6 +3154,12 @@ snapshots: esutils@2.0.3: {} + exsolve@1.0.7: {} + + fast-check@3.23.2: + dependencies: + pure-rand: 6.1.0 + fast-deep-equal@3.1.3: {} fast-glob@3.3.1: @@ -3009,6 +3257,15 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + giget@2.0.0: + dependencies: + citty: 0.1.6 + consola: 3.4.2 + defu: 6.1.4 + node-fetch-native: 1.6.7 + nypm: 0.6.2 + pathe: 2.0.3 + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -3195,6 +3452,8 @@ snapshots: jiti@2.6.0: {} + js-cookie@3.0.5: {} + js-tokens@4.0.0: {} js-yaml@4.1.0: @@ -3354,10 +3613,24 @@ snapshots: - '@babel/core' - babel-plugin-macros + node-addon-api@8.5.0: {} + + node-fetch-native@1.6.7: {} + + node-gyp-build@4.8.4: {} + node-releases@2.0.21: {} normalize-range@0.1.2: {} + nypm@0.6.2: + dependencies: + citty: 0.1.6 + consola: 3.4.2 + pathe: 2.0.3 + pkg-types: 2.3.0 + tinyexec: 1.0.1 + object-assign@4.1.1: {} object-inspect@1.13.4: {} @@ -3400,6 +3673,8 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + ohash@2.0.11: {} + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -3433,12 +3708,22 @@ snapshots: path-parse@1.0.7: {} + pathe@2.0.3: {} + + perfect-debounce@1.0.0: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} picomatch@4.0.3: {} + pkg-types@2.3.0: + dependencies: + confbox: 0.2.2 + exsolve: 1.0.7 + pathe: 2.0.3 + possible-typed-array-names@1.1.0: {} postcss-value-parser@4.2.0: {} @@ -3457,6 +3742,15 @@ snapshots: prelude-ls@1.2.1: {} + prisma@6.16.3(typescript@5.9.2): + dependencies: + '@prisma/config': 6.16.3 + '@prisma/engines': 6.16.3 + optionalDependencies: + typescript: 5.9.2 + transitivePeerDependencies: + - magicast + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -3465,8 +3759,15 @@ snapshots: punycode@2.3.1: {} + pure-rand@6.1.0: {} + queue-microtask@1.2.3: {} + rc9@2.1.2: + dependencies: + defu: 6.1.4 + destr: 2.0.5 + react-dom@19.1.0(react@19.1.0): dependencies: react: 19.1.0 @@ -3476,6 +3777,8 @@ snapshots: react@19.1.0: {} + readdirp@4.1.2: {} + reflect.getprototypeof@1.0.10: dependencies: call-bind: 1.0.8 @@ -3715,6 +4018,8 @@ snapshots: minizlib: 3.1.0 yallist: 5.0.0 + tinyexec@1.0.1: {} + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.3) diff --git a/handcrafted-haven/prisma/schema.prisma b/handcrafted-haven/prisma/schema.prisma new file mode 100644 index 0000000..638e29d --- /dev/null +++ b/handcrafted-haven/prisma/schema.prisma @@ -0,0 +1,61 @@ +// prisma/schema.prisma + +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model User { + id String @id @default(cuid()) + name String + email String @unique + password String + role Role @default(CUSTOMER) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + sellerProfile Seller? + customerProfile Customer? +} + +model Seller { + id String @id @default(cuid()) + shopName String + bio String? + logo String? + banner String? + user User @relation(fields: [userId], references: [id]) + userId String @unique + products Product[] + createdAt DateTime @default(now()) +} + +model Customer { + id String @id @default(cuid()) + user User @relation(fields: [userId], references: [id]) + userId String @unique + address String? + createdAt DateTime @default(now()) +} + +model Product { + id String @id @default(cuid()) + name String + price Float + description String? + image String? + category String? + seller Seller @relation(fields: [sellerId], references: [id]) + sellerId String + createdAt DateTime @default(now()) +} + +enum Role { + CUSTOMER + SELLER + ADMIN +} diff --git a/handcrafted-haven/src/app/api/login/route.ts b/handcrafted-haven/src/app/api/login/route.ts new file mode 100644 index 0000000..74e00ea --- /dev/null +++ b/handcrafted-haven/src/app/api/login/route.ts @@ -0,0 +1,39 @@ +import { NextResponse } from "next/server"; +import { PrismaClient } from "@prisma/client"; +import bcrypt from "bcrypt"; + +const prisma = new PrismaClient(); + +export async function POST(req: Request) { + try { + const { email, password } = await req.json(); + + if (!email || !password) { + return NextResponse.json({ error: "Missing email or password" }, { status: 400 }); + } + + const user = await prisma.user.findUnique({ where: { email } }); + if (!user) { + return NextResponse.json({ error: "Invalid credentials" }, { status: 401 }); + } + + const valid = await bcrypt.compare(password, user.password); + if (!valid) { + return NextResponse.json({ error: "Invalid credentials" }, { status: 401 }); + } + + // ✅ Optional: add JWT or session later + return NextResponse.json({ + message: "Login successful", + user: { + id: user.id, + name: user.name, + email: user.email, + role: user.role, + }, + }); + } catch (error) { + console.error("Login error:", error); + return NextResponse.json({ error: "Server error" }, { status: 500 }); + } +} diff --git a/handcrafted-haven/src/app/api/signup/route.ts b/handcrafted-haven/src/app/api/signup/route.ts new file mode 100644 index 0000000..3664340 --- /dev/null +++ b/handcrafted-haven/src/app/api/signup/route.ts @@ -0,0 +1,59 @@ +import { NextResponse } from "next/server"; +import { PrismaClient } from "@prisma/client"; +import bcrypt from "bcrypt"; + +const prisma = new PrismaClient(); + +export async function POST(req: Request) { + try { + const { name, email, password, role, shopName, bio } = await req.json(); + + if (!name || !email || !password) { + return NextResponse.json({ error: "Missing required fields" }, { status: 400 }); + } + + // Check if user already exists + const existingUser = await prisma.user.findUnique({ where: { email } }); + if (existingUser) { + return NextResponse.json({ error: "Email already registered" }, { status: 409 }); + } + + // Hash password + const hashedPassword = await bcrypt.hash(password, 10); + + // Create user + const user = await prisma.user.create({ + data: { + name, + email, + password: hashedPassword, + role: role === "SELLER" ? "SELLER" : "CUSTOMER", + }, + }); + + // If seller, create seller profile + if (role === "SELLER") { + await prisma.seller.create({ + data: { + shopName: shopName || `${name}'s Shop`, + bio: bio || "", + userId: user.id, + }, + }); + } + + // If customer, create customer profile + if (role === "CUSTOMER") { + await prisma.customer.create({ + data: { + userId: user.id, + }, + }); + } + + return NextResponse.json({ message: "User created successfully", user }, { status: 201 }); + } catch (error) { + console.error("Signup error:", error); + return NextResponse.json({ error: "Server error" }, { status: 500 }); + } +} diff --git a/handcrafted-haven/src/app/login/page.tsx b/handcrafted-haven/src/app/login/page.tsx index 63db442..df9315f 100644 --- a/handcrafted-haven/src/app/login/page.tsx +++ b/handcrafted-haven/src/app/login/page.tsx @@ -1,22 +1,24 @@ "use client"; import { Suspense } from "react"; -import Image from "next/image"; // optional, AuthHeader handles this import LoginForm from "@/app/ui/login-form"; import AuthHeader from "@/app/ui/auth-header"; +import NavigationBar from "@/app/ui/sidenav"; // ✅ import navbar export default function LoginPage() { return ( -
-
- {/* Use AuthHeader instead of hardcoded div */} - - Loading login form...
}> - - +
+ {/* ✅ Add navbar */} +
+
+ + Loading login form...
}> + + +
); diff --git a/handcrafted-haven/src/app/logout/page.tsx b/handcrafted-haven/src/app/logout/page.tsx new file mode 100644 index 0000000..7f8c716 --- /dev/null +++ b/handcrafted-haven/src/app/logout/page.tsx @@ -0,0 +1,25 @@ +"use client"; + +import { useEffect } from "react"; +import { useRouter } from "next/navigation"; +import Cookies from "js-cookie"; + +export default function LogoutPage() { + const router = useRouter(); + + useEffect(() => { + // Clear cookies + Cookies.remove("token"); + Cookies.remove("role"); + Cookies.remove("name"); + + // Redirect to homepage + router.push("/"); + }, [router]); + + return ( +
+

Logging out...

+
+ ); +} diff --git a/handcrafted-haven/src/app/middleware.ts b/handcrafted-haven/src/app/middleware.ts new file mode 100644 index 0000000..253a21b --- /dev/null +++ b/handcrafted-haven/src/app/middleware.ts @@ -0,0 +1,34 @@ +import { NextResponse } from "next/server"; +import type { NextRequest } from "next/server"; + +export function middleware(req: NextRequest) { + const url = req.nextUrl.clone(); + const token = req.cookies.get("token")?.value; + const role = req.cookies.get("role")?.value; + + const protectedCustomer = ["/signup/customer"]; + const protectedSeller = ["/signup/seller"]; + + if (!token) { + // Redirect all protected pages to login + if (protectedCustomer.includes(url.pathname) || protectedSeller.includes(url.pathname)) { + url.pathname = "/login"; + return NextResponse.redirect(url); + } + } else { + if (protectedCustomer.includes(url.pathname) && role !== "CUSTOMER") { + url.pathname = "/login"; + return NextResponse.redirect(url); + } + if (protectedSeller.includes(url.pathname) && role !== "SELLER") { + url.pathname = "/login"; + return NextResponse.redirect(url); + } + } + + return NextResponse.next(); +} + +export const config = { + matcher: ["/signup/customer", "/signup/seller"], +}; diff --git a/handcrafted-haven/src/app/seller/dashboard/page.tsx b/handcrafted-haven/src/app/seller/dashboard/page.tsx new file mode 100644 index 0000000..ea7c2bd --- /dev/null +++ b/handcrafted-haven/src/app/seller/dashboard/page.tsx @@ -0,0 +1,25 @@ +import Header from '@/app/ui/landing-page/header'; +import Footer from '@/app/ui/footer'; +import Link from 'next/link'; + +export default function SellerDashboard() { + return ( +
+
+
+

Seller Dashboard

+

Welcome! Manage your products and shop here.

+ +
+ + + + + + +
+
+
+
+ ); +} diff --git a/handcrafted-haven/src/app/signup/customer/page.tsx b/handcrafted-haven/src/app/signup/customer/page.tsx index 34c9179..a067647 100644 --- a/handcrafted-haven/src/app/signup/customer/page.tsx +++ b/handcrafted-haven/src/app/signup/customer/page.tsx @@ -1,66 +1,108 @@ "use client"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import PasswordInput from "@/app/ui/password-input"; import AuthHeader from "@/app/ui/auth-header"; +import NavigationBar from "@/app/ui/sidenav"; +import Cookies from "js-cookie"; +import { useRouter } from "next/navigation"; export default function SignUpCustomerPage() { const [form, setForm] = useState({ name: "", email: "", password: "" }); + const [loading, setLoading] = useState(false); + const [message, setMessage] = useState(""); + const router = useRouter(); + + // ✅ Redirect if already logged in + useEffect(() => { + const token = Cookies.get("token"); + const role = Cookies.get("role"); + + if (token && role) { + if (role === "CUSTOMER") router.push("/customer/dashboard"); + else if (role === "SELLER") router.push("/seller/dashboard"); + } + }, [router]); const handleChange = (e: React.ChangeEvent) => { setForm({ ...form, [e.target.name]: e.target.value }); }; - const handleSubmit = (e: React.FormEvent) => { + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - console.log("Customer Sign Up:", form); + setLoading(true); + setMessage(""); + + try { + const res = await fetch("/api/signup", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ ...form, role: "CUSTOMER" }), + }); + const data = await res.json(); + + if (!res.ok) { + setMessage(data.error || "Signup failed"); + } else { + setMessage("✅ Account created! Redirecting..."); + // Optional: redirect after signup + router.push("/customer/dashboard"); + } + } catch (err) { + setMessage("⚠️ Network or server error"); + } finally { + setLoading(false); + } }; return ( -
-
- +
+ +
+ + + + - + - + - + - - + {message &&

{message}

} + +
); } diff --git a/handcrafted-haven/src/app/signup/page.tsx b/handcrafted-haven/src/app/signup/page.tsx index 738def8..60026f9 100644 --- a/handcrafted-haven/src/app/signup/page.tsx +++ b/handcrafted-haven/src/app/signup/page.tsx @@ -6,7 +6,6 @@ import AuthHeader from "@/app/ui/auth-header"; export default function SignUpPage() { return (
- {/* Logo + Welcome */} Sign up as Seller + + ← Already have an account? Login +
); } - \ No newline at end of file diff --git a/handcrafted-haven/src/app/signup/seller/page.tsx b/handcrafted-haven/src/app/signup/seller/page.tsx index 8ffac3f..b12d57a 100644 --- a/handcrafted-haven/src/app/signup/seller/page.tsx +++ b/handcrafted-haven/src/app/signup/seller/page.tsx @@ -1,81 +1,126 @@ "use client"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import PasswordInput from "@/app/ui/password-input"; import AuthHeader from "@/app/ui/auth-header"; +import NavigationBar from "@/app/ui/sidenav"; +import Cookies from "js-cookie"; +import { useRouter } from "next/navigation"; export default function SignUpSellerPage() { - const [form, setForm] = useState({ - name: "", - email: "", - password: "", - shopName: "", - }); - - const handleChange = (e: React.ChangeEvent) => { + const [form, setForm] = useState({ name: "", email: "", password: "", shopName: "", bio: "" }); + const [loading, setLoading] = useState(false); + const [message, setMessage] = useState(""); + const router = useRouter(); + + // ✅ Redirect if already logged in + useEffect(() => { + const token = Cookies.get("token"); + const role = Cookies.get("role"); + + if (token && role) { + if (role === "CUSTOMER") router.push("/customer/dashboard"); + else if (role === "SELLER") router.push("/seller/dashboard"); + } + }, [router]); + + const handleChange = (e: React.ChangeEvent) => { setForm({ ...form, [e.target.name]: e.target.value }); }; - const handleSubmit = (e: React.FormEvent) => { + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - console.log("Seller Sign Up:", form); + setLoading(true); + setMessage(""); + + try { + const res = await fetch("/api/signup", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ ...form, role: "SELLER" }), + }); + const data = await res.json(); + + if (!res.ok) { + setMessage(data.error || "Signup failed"); + } else { + setMessage("✅ Seller account created! Redirecting..."); + // Optional: redirect after signup + router.push("/seller/dashboard"); + } + } catch (err) { + setMessage("⚠️ Network or server error"); + } finally { + setLoading(false); + } }; return ( -
-
- - - - - - - - - - - - +
+ +
+
+ + + + + + + + + + +