diff --git a/.gitignore b/.gitignore index 00bba9b..326f0cd 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,5 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +.vscode diff --git a/next.config.mjs b/next.config.mjs index 5498aab..4de832e 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -18,6 +18,10 @@ const nextConfig = { protocol: "https", hostname: "cloudflare-ipfs.com", }, + { + protocol: "https", + hostname: "vwdgzbbojiqcjpodjyzo.supabase.co", + }, ], }, }; diff --git a/package-lock.json b/package-lock.json index abd6d0f..c030630 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,26 +9,34 @@ "version": "0.1.0", "dependencies": { "@nextui-org/accordion": "^2.0.36", + "@react-spring/web": "^9.7.4", + "@supabase/supabase-js": "^2.45.1", "bcryptjs": "^2.4.3", + "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "dayjs": "^1.11.11", - "faker": "^6.6.6", + "es-hangul": "^1.5.0", "mongoose": "^8.5.0", + "nanoid": "^5.0.7", "next": "14.2.4", "next-auth": "^5.0.0-beta.19", + "nodemailer": "^6.9.14", "react": "^18", "react-datepicker": "^7.2.0", "react-dom": "^18", "react-hot-toast": "^2.4.1", + "react-image-file-resizer": "^0.4.8", "react-quill": "^2.0.0", "react-select": "^5.8.0", "swiper": "^11.1.4", - "uuid": "^10.0.0" + "tailwind-merge": "^2.4.0", + "zustand": "^4.5.4", + "zustand-persist": "^0.4.0" }, "devDependencies": { - "@faker-js/faker": "^8.4.1", "@types/bcryptjs": "^2.4.6", "@types/node": "^20", + "@types/nodemailer": "^6.4.15", "@types/react": "^18", "@types/react-dom": "^18", "eslint": "^8", @@ -491,22 +499,6 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@faker-js/faker": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.4.1.tgz", - "integrity": "sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/fakerjs" - } - ], - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0", - "npm": ">=6.14.13" - } - }, "node_modules/@floating-ui/core": { "version": "1.6.3", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.3.tgz", @@ -1087,6 +1079,16 @@ "node": ">=6" } }, + "node_modules/@nextui-org/theme/node_modules/tailwind-merge": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.14.0.tgz", + "integrity": "sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/@nextui-org/use-aria-accordion": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@nextui-org/use-aria-accordion/-/use-aria-accordion-2.0.6.tgz", @@ -1308,6 +1310,72 @@ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" } }, + "node_modules/@react-spring/animated": { + "version": "9.7.4", + "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.7.4.tgz", + "integrity": "sha512-7As+8Pty2QlemJ9O5ecsuPKjmO0NKvmVkRR1n6mEotFgWar8FKuQt2xgxz3RTgxcccghpx1YdS1FCdElQNexmQ==", + "dependencies": { + "@react-spring/shared": "~9.7.4", + "@react-spring/types": "~9.7.4" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/core": { + "version": "9.7.4", + "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.7.4.tgz", + "integrity": "sha512-GzjA44niEJBFUe9jN3zubRDDDP2E4tBlhNlSIkTChiNf9p4ZQlgXBg50qbXfSXHQPHak/ExYxwhipKVsQ/sUTw==", + "dependencies": { + "@react-spring/animated": "~9.7.4", + "@react-spring/shared": "~9.7.4", + "@react-spring/types": "~9.7.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-spring/donate" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/rafz": { + "version": "9.7.4", + "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.7.4.tgz", + "integrity": "sha512-mqDI6rW0Ca8IdryOMiXRhMtVGiEGLIO89vIOyFQXRIwwIMX30HLya24g9z4olDvFyeDW3+kibiKwtZnA4xhldA==" + }, + "node_modules/@react-spring/shared": { + "version": "9.7.4", + "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.7.4.tgz", + "integrity": "sha512-bEPI7cQp94dOtCFSEYpxvLxj0+xQfB5r9Ru1h8OMycsIq7zFZon1G0sHrBLaLQIWeMCllc4tVDYRTLIRv70C8w==", + "dependencies": { + "@react-spring/rafz": "~9.7.4", + "@react-spring/types": "~9.7.4" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/types": { + "version": "9.7.4", + "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.7.4.tgz", + "integrity": "sha512-iQVztO09ZVfsletMiY+DpT/JRiBntdsdJ4uqk3UJFhrhS8mIC9ZOZbmfGSRs/kdbNPQkVyzucceDicQ/3Mlj9g==" + }, + "node_modules/@react-spring/web": { + "version": "9.7.4", + "resolved": "https://registry.npmjs.org/@react-spring/web/-/web-9.7.4.tgz", + "integrity": "sha512-UMvCZp7I5HCVIleSa4BwbNxynqvj+mJjG2m20VO2yPoi2pnCYANy58flvz9v/YcXTAvsmL655FV3pm5fbr6akA==", + "dependencies": { + "@react-spring/animated": "~9.7.4", + "@react-spring/core": "~9.7.4", + "@react-spring/shared": "~9.7.4", + "@react-spring/types": "~9.7.4" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/@react-stately/collections": { "version": "3.10.7", "resolved": "https://registry.npmjs.org/@react-stately/collections/-/collections-3.10.7.tgz", @@ -1444,6 +1512,92 @@ "integrity": "sha512-qC/xYId4NMebE6w/V33Fh9gWxLgURiNYgVNObbJl2LZv0GUUItCcCqC5axQSwRaAgaxl2mELq1rMzlswaQ0Zxg==", "dev": true }, + "node_modules/@supabase/auth-js": { + "version": "2.64.4", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.64.4.tgz", + "integrity": "sha512-9ITagy4WP4FLl+mke1rchapOH0RQpf++DI+WSG2sO1OFOZ0rW3cwAM0nCrMOxu+Zw4vJ4zObc08uvQrXx590Tg==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.4.1.tgz", + "integrity": "sha512-8sZ2ibwHlf+WkHDUZJUXqqmPvWQ3UHN0W30behOJngVh/qHHekhJLCFbh0AjkE9/FqqXtf9eoVvmYgfCLk5tNA==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/node-fetch": { + "version": "2.6.15", + "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz", + "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/@supabase/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/@supabase/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/@supabase/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/@supabase/postgrest-js": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.15.8.tgz", + "integrity": "sha512-YunjXpoQjQ0a0/7vGAvGZA2dlMABXFdVI/8TuVKtlePxyT71sl6ERl6ay1fmIeZcqxiuFQuZw/LXUuStUG9bbg==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.10.2.tgz", + "integrity": "sha512-qyCQaNg90HmJstsvr2aJNxK2zgoKh9ZZA8oqb7UT2LCh3mj9zpa3Iwu167AuyNxsxrUE8eEJ2yH6wLCij4EApA==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14", + "@types/phoenix": "^1.5.4", + "@types/ws": "^8.5.10", + "ws": "^8.14.2" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.6.0.tgz", + "integrity": "sha512-REAxr7myf+3utMkI2oOmZ6sdplMZZ71/2NEIEMBZHL9Fkmm3/JnaOZVSRqvG4LStYj2v5WhCruCzuMn6oD/Drw==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.45.1", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.45.1.tgz", + "integrity": "sha512-/PVe3lXmalazD8BGMIoI7+ttvT1mLXy13lNcoAPtjP1TDDY83g8csZbVR6l+0/RZtvJxl3LGXfTJT4bjWgC5Nw==", + "dependencies": { + "@supabase/auth-js": "2.64.4", + "@supabase/functions-js": "2.4.1", + "@supabase/node-fetch": "2.6.15", + "@supabase/postgrest-js": "1.15.8", + "@supabase/realtime-js": "2.10.2", + "@supabase/storage-js": "2.6.0" + } + }, "node_modules/@swc/counter": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", @@ -1479,16 +1633,29 @@ "version": "20.14.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.7.tgz", "integrity": "sha512-uTr2m2IbJJucF3KUxgnGOZvYbN0QgkGyWxG6973HCpMYFy2KfcgYuIwkJQMQkt1VbBMlvWRbpshFTLxnxCZjKQ==", - "dev": true, "dependencies": { "undici-types": "~5.26.4" } }, + "node_modules/@types/nodemailer": { + "version": "6.4.15", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.15.tgz", + "integrity": "sha512-0EBJxawVNjPkng1zm2vopRctuWVCxk34JcIlRuXSf54habUWdz1FB7wHDqOqvDa8Mtpt0Q3LTXQkAs2LNyK5jQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" }, + "node_modules/@types/phoenix": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.5.tgz", + "integrity": "sha512-xegpDuR+z0UqG9fwHqNoy3rI7JDlvaPh2TY47Fl80oq6g+hXT+c/LEuE43X48clZ6lOfANl5WrPur9fYO1RJ/w==" + }, "node_modules/@types/prop-types": { "version": "15.7.12", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", @@ -1541,6 +1708,14 @@ "@types/webidl-conversions": "*" } }, + "node_modules/@types/ws": { + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", + "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/parser": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz", @@ -2158,6 +2333,25 @@ "node": ">= 6" } }, + "node_modules/class-variance-authority": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.0.tgz", + "integrity": "sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==", + "dependencies": { + "clsx": "2.0.0" + }, + "funding": { + "url": "https://joebell.co.uk" + } + }, + "node_modules/class-variance-authority/node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -2642,6 +2836,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-hangul": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/es-hangul/-/es-hangul-1.5.0.tgz", + "integrity": "sha512-n8lAXAmLNDnJDazBMsO4x/dCmUFebreVuznT2m6QKwaYwpx8/FHIqruGicmXdsEm3sm6Kdk9/iyxBW3OUWtZdA==", + "workspaces": [ + ".", + "docs" + ] + }, "node_modules/es-iterator-helpers": { "version": "1.0.19", "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz", @@ -3152,11 +3355,6 @@ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, - "node_modules/faker": { - "version": "6.6.6", - "resolved": "https://registry.npmjs.org/faker/-/faker-6.6.6.tgz", - "integrity": "sha512-9tCqYEDHI5RYFQigXFwF1hnCwcWCOJl/hmll0lr5D2Ljjb0o4wphb69wikeJDz5qCEzXCoPvG6ss5SDP6IfOdg==" - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -4557,9 +4755,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.7.tgz", + "integrity": "sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==", "funding": [ { "type": "github", @@ -4567,10 +4765,10 @@ } ], "bin": { - "nanoid": "bin/nanoid.cjs" + "nanoid": "bin/nanoid.js" }, "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "node": "^18 || >=20" } }, "node_modules/natural-compare": { @@ -4654,6 +4852,23 @@ } } }, + "node_modules/next/node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -4681,6 +4896,14 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/nodemailer": { + "version": "6.9.14", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.14.tgz", + "integrity": "sha512-Dobp/ebDKBvz91sbtRKhcznLThrKxKt97GI2FAlAyy+fk19j73Uz3sBXolVtmcXjaorivqsbbbjDY+Jkt4/bQA==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -5168,6 +5391,23 @@ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, + "node_modules/postcss/node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/preact": { "version": "10.11.3", "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz", @@ -5357,6 +5597,11 @@ "react-dom": ">=16" } }, + "node_modules/react-image-file-resizer": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/react-image-file-resizer/-/react-image-file-resizer-0.4.8.tgz", + "integrity": "sha512-Ue7CfKnSlsfJ//SKzxNMz8avDgDSpWQDOnTKOp/GNRFJv4dO9L5YGHNEnj40peWkXXAK2OK0eRIoXhOYpUzUTQ==" + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -6077,10 +6322,9 @@ "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" }, "node_modules/tailwind-merge": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.14.0.tgz", - "integrity": "sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==", - "peer": true, + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.4.0.tgz", + "integrity": "sha512-49AwoOQNKdqKPd9CViyH5wJoSKsCDjUlzL8DxuGp3P1FsGY36NJDAa18jLZcaHAUUuTj+JB8IAo8zWgBNvBF7A==", "funding": { "type": "github", "url": "https://github.com/sponsors/dcastil" @@ -6102,6 +6346,16 @@ "tailwindcss": "*" } }, + "node_modules/tailwind-variants/node_modules/tailwind-merge": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.14.0.tgz", + "integrity": "sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "3.4.4", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.4.tgz", @@ -6364,8 +6618,7 @@ "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "node_modules/uri-js": { "version": "4.4.1", @@ -6389,23 +6642,19 @@ } } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, - "node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -6621,6 +6870,26 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/yaml": { "version": "2.4.5", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz", @@ -6643,6 +6912,42 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.4.tgz", + "integrity": "sha512-/BPMyLKJPtFEvVL0E9E9BTUM63MNyhPGlvxk1XjrfWTUlV+BR8jufjsovHzrtR6YNcBEcL7cMHovL1n9xHawEg==", + "dependencies": { + "use-sync-external-store": "1.2.0" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/zustand-persist": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/zustand-persist/-/zustand-persist-0.4.0.tgz", + "integrity": "sha512-u6bBIc4yZRpSKBKuTNhoqvoIb09gGHk2NkiPg4K7MPIWTYZg70PlpBn48QEDnKZwfNurnf58TaW5BuMGIMf5hw==", + "peerDependencies": { + "react": ">=16.8.0", + "zustand": ">=3.6.3" + } } } } diff --git a/package.json b/package.json index d42bb5b..7cb853f 100644 --- a/package.json +++ b/package.json @@ -10,26 +10,34 @@ }, "dependencies": { "@nextui-org/accordion": "^2.0.36", + "@react-spring/web": "^9.7.4", + "@supabase/supabase-js": "^2.45.1", "bcryptjs": "^2.4.3", + "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "dayjs": "^1.11.11", - "faker": "^6.6.6", + "es-hangul": "^1.5.0", "mongoose": "^8.5.0", + "nanoid": "^5.0.7", "next": "14.2.4", "next-auth": "^5.0.0-beta.19", + "nodemailer": "^6.9.14", "react": "^18", "react-datepicker": "^7.2.0", "react-dom": "^18", "react-hot-toast": "^2.4.1", + "react-image-file-resizer": "^0.4.8", "react-quill": "^2.0.0", "react-select": "^5.8.0", "swiper": "^11.1.4", - "uuid": "^10.0.0" + "tailwind-merge": "^2.4.0", + "zustand": "^4.5.4", + "zustand-persist": "^0.4.0" }, "devDependencies": { - "@faker-js/faker": "^8.4.1", "@types/bcryptjs": "^2.4.6", "@types/node": "^20", + "@types/nodemailer": "^6.4.15", "@types/react": "^18", "@types/react-dom": "^18", "eslint": "^8", diff --git a/public/icons/AlarmIcon.svg b/public/icons/AlarmIcon.svg index ac70931..e5ec567 100644 --- a/public/icons/AlarmIcon.svg +++ b/public/icons/AlarmIcon.svg @@ -1,3 +1,3 @@ - - + + diff --git a/public/icons/CreateStudyIcon.svg b/public/icons/CreateStudyIcon.svg index d6ac9a5..dc629a7 100644 --- a/public/icons/CreateStudyIcon.svg +++ b/public/icons/CreateStudyIcon.svg @@ -1,3 +1,3 @@ - + diff --git a/public/icons/Edit/Vector.svg b/public/icons/Edit/Vector.svg deleted file mode 100644 index 736b617..0000000 --- a/public/icons/Edit/Vector.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/public/icons/index.ts b/public/icons/index.ts index ea2a1fd..575b2f6 100644 --- a/public/icons/index.ts +++ b/public/icons/index.ts @@ -40,5 +40,6 @@ export { default as PeopleIcon } from "./details/people.svg"; export { default as ShareIcon } from "./share.svg"; export { default as ArrowIcon } from "./Arrow.svg"; +export { default as ImageCheckIcon } from "./whiteCheck.svg"; export { default as QandAIcon } from "./QandAIcon.svg"; diff --git a/public/icons/whiteCheck.svg b/public/icons/whiteCheck.svg new file mode 100644 index 0000000..7bb1797 --- /dev/null +++ b/public/icons/whiteCheck.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/app/(auth)/_components/FindAuthForm.tsx b/src/app/(auth)/_components/FindAuthForm.tsx new file mode 100644 index 0000000..93564f5 --- /dev/null +++ b/src/app/(auth)/_components/FindAuthForm.tsx @@ -0,0 +1,106 @@ +"use client"; + +import { ChangeEvent, FormEvent, useState } from "react"; +import { Input } from "./UserInput"; +import { findEmail, findPassword } from "@/lib/actions/authAction"; +import handleAlert from "@/common/Molecules/handleAlert"; +import LoadingContainer from "@/common/Layout/LoadingContainer"; +import findEmailStore from "@/store/findEmailStore"; +import Link from "next/link"; +import { formatPhoneNumber } from "@/utils/formatPhoneNumber"; + +export default function FindAuthForm({ title }: { title?: string }) { + const [sendEmail, setSendEmail] = useState(false); + const [loading, setLoading] = useState(false); + const [phoneData, setPhoneData] = useState(""); + const { setUserEmail } = findEmailStore(); + + async function handleSubmit(e: FormEvent) { + e.preventDefault(); + setLoading(true); + + const formData = new FormData(e.currentTarget); + + try { + let result; + if (title === "이메일") { + result = await findEmail(formData); + } else { + result = await findPassword(formData); + } + + if (result.state) { + setSendEmail(true); + setUserEmail(result.data); + handleAlert("success", result.message); + } else { + handleAlert("error", result.message); + } + } catch (error) { + console.log(error); + } + + setLoading(false); + } + + function handlePhoneInput(e: ChangeEvent) { + const formatData = formatPhoneNumber(e.target.value); + setPhoneData(formatData); + } + + return ( + <> +
+ {title === "이메일" ? ( + <> + + + + ) : ( + + )} +
+ + {loading ? ( +
+ +
+ ) : ( + + )} +
+ + 로그인 페이지로 가기 + + + + ); +} diff --git a/src/app/(auth)/_components/FindAuthWrap.tsx b/src/app/(auth)/_components/FindAuthWrap.tsx new file mode 100644 index 0000000..a1088e9 --- /dev/null +++ b/src/app/(auth)/_components/FindAuthWrap.tsx @@ -0,0 +1,19 @@ +import Image from "next/image"; +import { Logo } from "@public/icons"; +import AuthWrap from "../_components/AuthWrap"; +import FindAuthForm from "../_components/FindAuthForm"; + +export default function FindAuthWrap({ title }: { title: string }) { + return ( + + logo +

{title} 찾기

+

+ 가입한 정보를 입력하고 인증 요청할 Email을 입력하면
해당 Email로 + {title === "이메일" ? " 이메일 찾는" : " 비밀번호 변경"} 링크가 + 전송됩니다. +

+ +
+ ); +} diff --git a/src/app/(auth)/_components/FindNoData.tsx b/src/app/(auth)/_components/FindNoData.tsx new file mode 100644 index 0000000..9a99f9c --- /dev/null +++ b/src/app/(auth)/_components/FindNoData.tsx @@ -0,0 +1,21 @@ +"use client"; + +import Link from "next/link"; + +export default function FindNoData({ link }: { link: string }) { + return ( + <> +

+ 유효한 10분이 만료되어 +
다시 정보를 입력해 인증해 주세요. +

+
+ + {link === "find" + ? "이메일 찾기 페이지 이동" + : "비밀번호 찾기 페이지 이동"} + +
+ + ); +} diff --git a/src/app/(auth)/_components/LoginForm.tsx b/src/app/(auth)/_components/LoginForm.tsx index 0767568..e10efad 100644 --- a/src/app/(auth)/_components/LoginForm.tsx +++ b/src/app/(auth)/_components/LoginForm.tsx @@ -3,44 +3,45 @@ import { FormEvent, useState } from "react"; import Link from "next/link"; import { Input } from "./UserInput"; -import { signIn } from "next-auth/react"; import { useRouter } from "next/navigation"; -import handleAlert from "./ErrorAlert"; +import handleAlert from "@/common/Molecules/handleAlert"; +import Image from "next/image"; +import { login } from "@/lib/actions/authAction"; +import { Google, Kakao, Github } from "@public/icons"; +import { signIn } from "next-auth/react"; + +const socialLoginList = [ + { provider: "kakao", icon: Kakao }, + { provider: "google", icon: Google }, + { provider: "github", icon: Github }, +]; -export default function LoginForm() { +export function LoginForm() { const router = useRouter(); const [pwData, setPwData] = useState(""); - async function login(e: FormEvent) { + async function handleSubmit(e: FormEvent) { e.preventDefault(); const formData = new FormData(e.currentTarget); - const email = formData.get("email"); - const password = formData.get("password"); - - if (!email || !password) { - handleAlert("error", "입력한 정보를 다시 확인해 주세요."); - return; - } - const login = await signIn("credentials", { - redirect: false, - email, - password, - }); + try { + const result = await login(formData); - if (login?.error) { - handleAlert("error", "이메일 또는 비밀번호를 다시 확인해주세요."); - setPwData(""); - return; + if (result.state) { + router.replace("/"); + handleAlert("success", result.message); + } else { + handleAlert("error", result.message); + } + } catch (error) { + handleAlert("error", "로그인 중 문제가 발생했습니다."); } - - router.refresh(); } return ( <> -
+ 회원가입 @@ -70,3 +72,18 @@ export default function LoginForm() { ); } + +export function SocialLoginForm() { + return ( +
+ {socialLoginList.map(({ provider, icon }) => ( + + ))} +
+ ); +} diff --git a/src/app/(auth)/_components/LoginModal.tsx b/src/app/(auth)/_components/LoginModal.tsx deleted file mode 100644 index 0451c50..0000000 --- a/src/app/(auth)/_components/LoginModal.tsx +++ /dev/null @@ -1,85 +0,0 @@ -"use client"; - -import Link from "next/link"; -import Image from "next/image"; -import { SocialLogin, Google, Kakao, Logo, Github } from "@public/icons"; -import { useEffect, useState, useRef } from "react"; -import ModalBackdrop from "@/common/Molecules/ModalPortal/ModalBackdrop"; -import LoginForm from "./LoginForm"; -import AuthWrap from "./AuthWrap"; -import { loginGithub, loginGoogle, loginKakao } from "@/lib/action"; - -export default function LoginModal() { - const [open, setOpen] = useState(false); - const modalRef = useRef(null); - - useEffect(() => { - if (!open) { - modalRef.current?.close(); - } - - const modalKeyCloseHandler = (e: KeyboardEvent) => { - if (e.key === "Escape") { - modalRef.current?.close(); - setOpen(false); - } - }; - document.addEventListener("keydown", modalKeyCloseHandler); - - return () => { - document.removeEventListener("keydown", modalKeyCloseHandler); - }; - }, [open]); - - return ( - <> - - {open && ( - <> - setOpen(false)} /> - - - logo - -
- 이메일 찾기 - - 비밀번호 찾기 -
- <> - 간편 로그인 이미지 -
- - - -
- -
-
- -
-
- -
-
- - )} - - ); -} diff --git a/src/app/(auth)/_components/RegisterForm.tsx b/src/app/(auth)/_components/RegisterForm.tsx index b71896f..07755a2 100644 --- a/src/app/(auth)/_components/RegisterForm.tsx +++ b/src/app/(auth)/_components/RegisterForm.tsx @@ -1,36 +1,44 @@ "use client"; -import { Input } from "./UserInput"; import { ChangeEvent, FormEvent, useState } from "react"; +import { useRouter } from "next/navigation"; +import { Input } from "./UserInput"; import RegisterCheck from "./RegisterCheck"; -import handleAlert from "./ErrorAlert"; -import { authAction } from "@/lib/action"; +import handleAlert from "@/common/Molecules/handleAlert"; +import { register } from "@/lib/actions/authAction"; +import { formatPhoneNumber } from "@/utils/formatPhoneNumber"; export default function RegisterForm() { + const router = useRouter(); const [phoneData, setPhoneData] = useState(""); - async function register(e: FormEvent) { + async function handleSubmit(e: FormEvent) { e.preventDefault(); const formData = new FormData(e.currentTarget); try { - await authAction(formData); - handleAlert("success", "회원가입 완료되어 로그인 되었습니다."); - } catch (error: any) { - handleAlert("error", error.message); + const result = await register(formData); + + if (result.state) { + router.replace("/set-category"); + } else { + handleAlert("error", result.message); + } + } catch (error) { + console.log(error); } } function handlePhoneInput(e: ChangeEvent) { - const value = e.target.value.replace(/[^0-9]/g, ""); - setPhoneData(value); + const formatData = formatPhoneNumber(e.target.value); + setPhoneData(formatData); } return ( <>
diff --git a/src/app/(auth)/_components/UserInput.tsx b/src/app/(auth)/_components/UserInput.tsx index 4661511..5cc653f 100644 --- a/src/app/(auth)/_components/UserInput.tsx +++ b/src/app/(auth)/_components/UserInput.tsx @@ -7,6 +7,7 @@ import { ChangeEvent, useState } from "react"; type TUserInput = { id: string; type: string; + name?: string; title: string; placeholder: string; value?: string; @@ -16,6 +17,7 @@ type TUserInput = { export function Input({ id, type, + name, title, placeholder, value, @@ -34,10 +36,10 @@ export function Input({ + + logo +
+ {userEmail ? ( + <> +

해당 정보로 가입된 이메일 입니다.

+ {userEmail} +
+ + 로그인페이지 이동 + +
+ + ) : ( + + )} +
+
+ + ); +} diff --git a/src/app/(auth)/find/page.tsx b/src/app/(auth)/find/page.tsx index ac4c36a..e417324 100644 --- a/src/app/(auth)/find/page.tsx +++ b/src/app/(auth)/find/page.tsx @@ -1,45 +1,10 @@ -import Image from "next/image"; -import { Logo } from "@public/icons"; import AuthWrap from "../_components/AuthWrap"; -import { Input } from "../_components/UserInput"; +import FindAuthWrap from "../_components/FindAuthWrap"; export default function FindPage() { return ( - logo -

이메일 찾기

-
-
- - -
-
-
- -
- -
-
+
); } diff --git a/src/app/(auth)/find/password/page.tsx b/src/app/(auth)/find/password/page.tsx new file mode 100644 index 0000000..a2a2150 --- /dev/null +++ b/src/app/(auth)/find/password/page.tsx @@ -0,0 +1,81 @@ +"use client"; + +import AuthWrap from "../../_components/AuthWrap"; +import Image from "next/image"; +import { Logo } from "@public/icons"; +import { Input } from "../../_components/UserInput"; +import findEmailStore from "@/store/findEmailStore"; +import { updatePassword } from "@/lib/actions/authAction"; +import { FormEvent } from "react"; +import handleAlert from "@/common/Molecules/handleAlert"; +import { useRouter } from "next/navigation"; +import FindNoData from "../../_components/FindNoData"; + +export default function FindPassword() { + const { userEmail } = findEmailStore(); + const router = useRouter(); + + async function handleSubmit(e: FormEvent) { + e.preventDefault(); + + const formData = new FormData(e.currentTarget); + + if (!userEmail) { + handleAlert("error", "비밀번호를 변경할 유저의 이메일이 없습니다."); + return; + } + + try { + const result = await updatePassword(userEmail, formData); + + if (result?.state) { + router.replace("/login"); + handleAlert("success", result.message); + } else { + handleAlert("error", result.message); + } + } catch (error) { + console.log(error); + } + } + + return ( + <> + + logo + {userEmail ? ( + <> +

새로운 비밀번호로 변경해주세요.

+
+
+ +

+ ※ 영문 / 숫자 / 특수문자(!, @, #, *)중 1가지 포함 12자 이상 +

+
+ + +
+ + ) : ( + + )} +
+ + ); +} diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx new file mode 100644 index 0000000..5adfdf2 --- /dev/null +++ b/src/app/(auth)/login/page.tsx @@ -0,0 +1,23 @@ +import Link from "next/link"; +import Image from "next/image"; +import { SocialLogin, Logo } from "@public/icons"; +import { LoginForm, SocialLoginForm } from "../_components/LoginForm"; +import AuthWrap from "../_components/AuthWrap"; + +export default function Login() { + return ( + + logo + +
+ 이메일 찾기 + + 비밀번호 찾기 +
+ <> + 간편 로그인 이미지 + + +
+ ); +} diff --git a/src/app/(auth)/pw-reset/page.tsx b/src/app/(auth)/pw-reset/page.tsx index 38d6885..1f32f85 100644 --- a/src/app/(auth)/pw-reset/page.tsx +++ b/src/app/(auth)/pw-reset/page.tsx @@ -1,26 +1,10 @@ -import Image from "next/image"; -import { Logo } from "@public/icons"; import AuthWrap from "../_components/AuthWrap"; -import { Input } from "../_components/UserInput"; +import FindAuthWrap from "../_components/FindAuthWrap"; export default function PWresetPage() { return ( - logo -

비밀번호 찾기

-

- 가입한 이메일을 입력하면
이메일로 비밀번호 변경 링크가 - 전송됩니다. -

- - +
); } diff --git a/src/app/(route)/_components/DeletePostButton.tsx b/src/app/(route)/_components/DeletePostButton.tsx new file mode 100644 index 0000000..ae06cd9 --- /dev/null +++ b/src/app/(route)/_components/DeletePostButton.tsx @@ -0,0 +1,74 @@ +"use client"; + +import Button from "@/common/Atoms/Form/Button"; +import handleAlert from "@/common/Molecules/handleAlert"; +import useModal from "@/hooks/useModal"; +import { TProps } from "@/types/component/props"; +import { cfetch } from "@/utils/customFetch"; +import { useRouter } from "next/navigation"; +import { useState } from "react"; + +export default function DeletePostButton({ + postId, + children, +}: { postId: string } & TProps) { + const router = useRouter(); + const [disabled, setDisabled] = useState(false); + const { Modal, open, close } = useModal({ + children: ( +
+

글을 삭제하시겠습니까?

+
+ + +
+
+ ), + }); + function onDeleteCancel() { + close(); + } + async function onDelete() { + setDisabled(() => true); + const result = await cfetch("/api/community/" + postId, { + method: "DELETE", + }) + .then((res) => res.json()) + .then(({ data }) => data) + .catch((err) => { + // console.error(err); + return { state: false, message: "상태 업데이트에 실패했습니다." }; + }); + + if (!result?.success) { + setDisabled(() => false); + handleAlert("error", result.message); + return; + } + + handleAlert("success", result.message); + router.replace("/post"); + router.refresh(); + } + + return ( + <> + + {Modal} + + ); +} diff --git a/src/app/(route)/_components/LikeIconButton.tsx b/src/app/(route)/_components/LikeIconButton.tsx new file mode 100644 index 0000000..ae633b3 --- /dev/null +++ b/src/app/(route)/_components/LikeIconButton.tsx @@ -0,0 +1,80 @@ +"use client"; +import likePostStore from "@/store/likePostStore"; +import handleAlert from "@/common/Molecules/handleAlert"; +import Button from "@/common/Atoms/Form/Button"; +import { LikeThumbIcon } from "@/common/Atoms/Image/Icon"; +import { useEffect, useState } from "react"; +import { useSpring, animated } from "@react-spring/web"; +import { ONE_SEC_IN_MS } from "@/constants/times_unit"; +import clsx from "clsx"; + +type LikeIconButtonProps = { + count: number; + postId: string; + sessionId: string | undefined; +}; +export default function LikeIconButton(props: LikeIconButtonProps) { + const { count, postId, sessionId } = props; + const likedCount = count < 0 ? 0 : count; + + const { liked, fetchLiked, fetchLikeToggle } = likePostStore(); + const [init, setLike] = useState(false); + const checkLiked = async () => { + const result = await fetchLiked(postId); + setLike(result?.data ?? false); + console.log("init", init); + }; + useEffect(() => { + checkLiked(); + }, []); + + async function toggleLike() { + const result = await fetchLikeToggle(postId); + if (result?.state) { + handleAlert("success", result.message); + } else { + handleAlert("error", result?.message || "Error: 좋아요에 실패했습니다."); + } + } + + const { x } = useSpring({ + from: { x: 0 }, + x: liked ? 1 : 0, + config: { duration: ONE_SEC_IN_MS }, + }); + + // init과 liked 데이터가 다를 때 + // 화면에 보이는 liked count 값에 차이가 발생하는 정도 + const diff: number = + init === false && liked === true + ? 1 + : init === true && liked === false + ? -1 + : 0; + return ( +
+ + + + + + + {likedCount + diff} + +
+ ); +} diff --git a/src/app/(route)/_components/SearchInput.tsx b/src/app/(route)/_components/SearchInput.tsx index 48c0dbc..034f89d 100644 --- a/src/app/(route)/_components/SearchInput.tsx +++ b/src/app/(route)/_components/SearchInput.tsx @@ -18,32 +18,30 @@ export default function SearchInput({ origin = "study" }: { origin?: string }) { return (
- - - - - - - -
+ + + + + + ); } diff --git a/src/app/(route)/_components/ShareIconButton.tsx b/src/app/(route)/_components/ShareIconButton.tsx index 7912715..4ef95b2 100644 --- a/src/app/(route)/_components/ShareIconButton.tsx +++ b/src/app/(route)/_components/ShareIconButton.tsx @@ -2,9 +2,8 @@ import Button from "@/common/Atoms/Form/Button"; import Input from "@/common/Molecules/Form/Input"; import { usePathname } from "next/navigation"; -import toast, { Toast } from "react-hot-toast"; -import Notification from "@/common/Molecules/Notification"; import useModal from "@/hooks/useModal"; +import handleAlert from "@/common/Molecules/handleAlert"; export default function ShareIconButton({ width = "38", @@ -14,8 +13,7 @@ export default function ShareIconButton({ height?: string; }) { const pathname = usePathname(); - // TODO: 추후 도메인 수정 - const fullPathname = "https://chemeet.com" + pathname; + const fullPathname = process.env.NEXT_PUBLIC_BASE_URL + pathname; const { Modal, open } = useModal({ children: ( @@ -34,20 +32,15 @@ export default function ShareIconButton({ function copyPathname() { navigator.clipboard.writeText(fullPathname); - toast.custom((t: Toast) => ( - - )); + handleAlert( + "success", + "URL이 복사되었어요", + "원하는 곳에 붙여넣기 하여 케밋을 공유해보세요!" + ); } return ( <> - + {Modal} ); diff --git a/src/app/(route)/layout.tsx b/src/app/(route)/layout.tsx index 382abd6..6df0d22 100644 --- a/src/app/(route)/layout.tsx +++ b/src/app/(route)/layout.tsx @@ -5,7 +5,7 @@ export default function MainLayout({ children }: TProps) { return ( <>
-
+
{children}