diff --git a/.env.example b/.env.example index 4104fdd..eb00a90 100644 --- a/.env.example +++ b/.env.example @@ -3,5 +3,10 @@ DATABASE_URL= # github GITHUB_AUTH_TOKEN= +# https://github.com/settings/developers +# To have an Oauth configuration for dev and prod environments, you'll need to register a second Oauth app OAUTH_CLIENT_ID= OAUTH_CLIENT_SECRET= + +# A random value that encrypts tokens and email verification hashes. https://authjs.dev/getting-started/installation?framework=next.js#setup-environment +AUTH_SECRET= diff --git a/.github/workflows/lighthouse.yml b/.github/workflows/lighthouse.yml index cbe48f4..4f98a08 100644 --- a/.github/workflows/lighthouse.yml +++ b/.github/workflows/lighthouse.yml @@ -56,6 +56,7 @@ jobs: ${{ steps.vercel_preview_url.outputs.url }} ${{ steps.vercel_preview_url.outputs.url }}/blog ${{ steps.vercel_preview_url.outputs.url }}/projects + ${{ steps.vercel_preview_url.outputs.url }}/guestbook ${{ steps.vercel_preview_url.outputs.url }}/about uploadArtifacts: true # save results as a action artifacts temporaryPublicStorage: true # upload lighthouse report to the temporary storage diff --git a/messages/en.json b/messages/en.json index 418e386..63180a1 100644 --- a/messages/en.json +++ b/messages/en.json @@ -14,7 +14,8 @@ "backToHome": "Back to Home", "postViews": " views", "notFound": "Sorry, the page you are looking for does not exist.", - "chooseLanguage": "chooseLanguage" + "chooseLanguage": "chooseLanguage", + "guestbook": "Guestbook" }, "homePage": { "description": "👋 Hi, I'm Tom Jin, a frontend developer based in Taiwan who specializes in React and Next.js. I'm here to share learning and projects. Please feel free to read and discover.", diff --git a/messages/zh-TW.json b/messages/zh-TW.json index 2b7cfdc..830f853 100644 --- a/messages/zh-TW.json +++ b/messages/zh-TW.json @@ -14,7 +14,8 @@ "backToHome": "返回首頁", "postViews": "瀏覽次數: 次", "notFound": "對不起,您正在尋找的頁面不存在。", - "chooseLanguage": "選擇語言" + "chooseLanguage": "選擇語言", + "guestbook": "留言板" }, "homePage": { "description": "👋 嗨,我是Tom Jin,我是一位前端工程師。專注於使用 React 和 Next.js,我在這裡分享我的學習筆記和專案實作,歡迎閱讀與探索。", diff --git a/next.config.mjs b/next.config.mjs index 229820c..7a6eac6 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -4,6 +4,13 @@ import withNextIntl from 'next-intl/plugin'; const nextConfig = { swcMinify: true, i18n: null, + images: { + remotePatterns: [ + { + hostname: 'avatars.githubusercontent.com', + }, + ], + }, }; export default withNextIntl('./src/i18n.ts')(nextConfig); diff --git a/package.json b/package.json index 216f099..853144e 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "clsx": "^1.2.1", "framer-motion": "^10.12.12", "next": "^14.0.1", + "next-auth": "5.0.0-beta.19", "next-intl": "^3.2.4", "octokit": "^3.1.2", "react": "18.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3b1e0d8..e609e68 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,9 @@ importers: next: specifier: ^14.0.1 version: 14.0.1(@babel/core@7.24.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + next-auth: + specifier: 5.0.0-beta.19 + version: 5.0.0-beta.19(next@14.0.1(@babel/core@7.24.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0) next-intl: specifier: ^3.2.4 version: 3.9.5(next@14.0.1(@babel/core@7.24.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0) @@ -162,6 +165,20 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} + '@auth/core@0.32.0': + resolution: {integrity: sha512-3+ssTScBd+1fd0/fscAyQN1tSygXzuhysuVVzB942ggU4mdfiTbv36P0ccVnExKWYJKvu3E2r3/zxXCCAmTOrg==} + peerDependencies: + '@simplewebauthn/browser': ^9.0.1 + '@simplewebauthn/server': ^9.0.2 + nodemailer: ^6.8.0 + peerDependenciesMeta: + '@simplewebauthn/browser': + optional: true + '@simplewebauthn/server': + optional: true + nodemailer: + optional: true + '@babel/code-frame@7.24.2': resolution: {integrity: sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==} engines: {node: '>=6.9.0'} @@ -993,6 +1010,9 @@ packages: resolution: {integrity: sha512-+nGS3ReCByF6m+nbNB59x7Aa3CNjCCGuBLFzfkiJP1O3uVKKuJbkP4uO4t46YqH26nlugmOhqjT7nx5D0VPtdA==} engines: {node: '>= 18'} + '@panva/hkdf@1.2.1': + resolution: {integrity: sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -1378,6 +1398,9 @@ packages: '@types/btoa-lite@1.0.2': resolution: {integrity: sha512-ZYbcE2x7yrvNFJiU7xJGrpF/ihpkM7zKgw8bha3LNJSesvTtUNxbpzaT7WXBIryf6jovisrxTBvymxMeLLj1Mg==} + '@types/cookie@0.6.0': + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} @@ -1888,6 +1911,10 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie@0.6.0: + resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} + engines: {node: '>= 0.6'} + create-jest@29.7.0: resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2934,6 +2961,9 @@ packages: resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==} hasBin: true + jose@5.6.3: + resolution: {integrity: sha512-1Jh//hEEwMhNYPDDLwXHa2ePWgWiFNNUadVmguAAw2IJ6sj9mNxV5tGXJNqlMkJAybF6Lgw1mISDxTePP/187g==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -3418,6 +3448,22 @@ packages: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} + next-auth@5.0.0-beta.19: + resolution: {integrity: sha512-YHu1igcAxZPh8ZB7GIM93dqgY6gcAzq66FOhQFheAdOx1raxNcApt05nNyNCSB6NegSiyJ4XOPsaNow4pfDmsg==} + peerDependencies: + '@simplewebauthn/browser': ^9.0.1 + '@simplewebauthn/server': ^9.0.2 + next: ^14 || ^15.0.0-0 + nodemailer: ^6.6.5 + react: ^18.2.0 || ^19.0.0-0 + peerDependenciesMeta: + '@simplewebauthn/browser': + optional: true + '@simplewebauthn/server': + optional: true + nodemailer: + optional: true + next-intl@3.9.5: resolution: {integrity: sha512-tsp4N433WgTAbbyZdMlcsLGHFM88wv2a7ZpF/od8X9+qAlO1TrajZrNrGBpIg6nA9EGZyMbQPzZD7XZrqYIv7g==} peerDependencies: @@ -3467,6 +3513,9 @@ packages: nwsapi@2.2.10: resolution: {integrity: sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ==} + oauth4webapi@2.11.1: + resolution: {integrity: sha512-aNzOnL98bL6izG97zgnZs1PFEyO4WDVRhz2Pd066NPak44w5ESLRCYmJIyey8avSBPOMtBjhF3ZDDm7bIb7UOg==} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -3654,6 +3703,14 @@ packages: resolution: {integrity: sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==} engines: {node: ^10 || ^12 || >=14} + preact-render-to-string@5.2.3: + resolution: {integrity: sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==} + peerDependencies: + preact: '>=10' + + preact@10.11.3: + resolution: {integrity: sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==} + prebuild-install@7.1.2: resolution: {integrity: sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==} engines: {node: '>=10'} @@ -3676,6 +3733,9 @@ packages: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + pretty-format@3.8.0: + resolution: {integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==} + prisma@4.16.2: resolution: {integrity: sha512-SYCsBvDf0/7XSJyf2cHTLjLeTLVXYfqp7pG5eEVafFLeT0u/hLFz/9W196nDRGUOo1JfPatAEb+uEnTQImQC1g==} engines: {node: '>=14.17'} @@ -4491,6 +4551,16 @@ snapshots: '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 + '@auth/core@0.32.0': + dependencies: + '@panva/hkdf': 1.2.1 + '@types/cookie': 0.6.0 + cookie: 0.6.0 + jose: 5.6.3 + oauth4webapi: 2.11.1 + preact: 10.11.3 + preact-render-to-string: 5.2.3(preact@10.11.3) + '@babel/code-frame@7.24.2': dependencies: '@babel/highlight': 7.24.2 @@ -5438,6 +5508,8 @@ snapshots: '@octokit/webhooks-types': 7.3.2 aggregate-error: 3.1.0 + '@panva/hkdf@1.2.1': {} + '@pkgjs/parseargs@0.11.0': optional: true @@ -5826,6 +5898,8 @@ snapshots: '@types/btoa-lite@1.0.2': {} + '@types/cookie@0.6.0': {} + '@types/debug@4.1.12': dependencies: '@types/ms': 0.7.34 @@ -6392,6 +6466,8 @@ snapshots: convert-source-map@2.0.0: {} + cookie@0.6.0: {} + create-jest@29.7.0(@types/node@20.2.1): dependencies: '@jest/types': 29.6.3 @@ -6753,8 +6829,8 @@ snapshots: '@typescript-eslint/parser': 5.62.0(eslint@8.41.0)(typescript@5.0.4) eslint: 8.41.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@5.62.0(eslint@8.41.0)(typescript@5.0.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.41.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.41.0)(typescript@5.0.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.41.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@5.62.0(eslint@8.41.0)(typescript@5.0.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.41.0)(typescript@5.0.4))(eslint@8.41.0))(eslint@8.41.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.41.0)(typescript@5.0.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.62.0(eslint@8.41.0)(typescript@5.0.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.41.0)(typescript@5.0.4))(eslint@8.41.0))(eslint@8.41.0))(eslint@8.41.0) eslint-plugin-jsx-a11y: 6.8.0(eslint@8.41.0) eslint-plugin-react: 7.34.1(eslint@8.41.0) eslint-plugin-react-hooks: 4.6.0(eslint@8.41.0) @@ -6772,13 +6848,13 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.62.0(eslint@8.41.0)(typescript@5.0.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.41.0): + eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.62.0(eslint@8.41.0)(typescript@5.0.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.41.0)(typescript@5.0.4))(eslint@8.41.0))(eslint@8.41.0): dependencies: debug: 4.3.4 enhanced-resolve: 5.16.0 eslint: 8.41.0 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@5.62.0(eslint@8.41.0)(typescript@5.0.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.62.0(eslint@8.41.0)(typescript@5.0.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.41.0))(eslint@8.41.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.41.0)(typescript@5.0.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.41.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@5.62.0(eslint@8.41.0)(typescript@5.0.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.62.0(eslint@8.41.0)(typescript@5.0.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.41.0)(typescript@5.0.4))(eslint@8.41.0))(eslint@8.41.0))(eslint@8.41.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.41.0)(typescript@5.0.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.62.0(eslint@8.41.0)(typescript@5.0.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.41.0)(typescript@5.0.4))(eslint@8.41.0))(eslint@8.41.0))(eslint@8.41.0) fast-glob: 3.3.2 get-tsconfig: 4.7.3 is-core-module: 2.13.1 @@ -6789,18 +6865,18 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@5.62.0(eslint@8.41.0)(typescript@5.0.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.62.0(eslint@8.41.0)(typescript@5.0.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.41.0))(eslint@8.41.0): + eslint-module-utils@2.8.1(@typescript-eslint/parser@5.62.0(eslint@8.41.0)(typescript@5.0.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.62.0(eslint@8.41.0)(typescript@5.0.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.41.0)(typescript@5.0.4))(eslint@8.41.0))(eslint@8.41.0))(eslint@8.41.0): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 5.62.0(eslint@8.41.0)(typescript@5.0.4) eslint: 8.41.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@5.62.0(eslint@8.41.0)(typescript@5.0.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.41.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@5.62.0(eslint@8.41.0)(typescript@5.0.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.41.0)(typescript@5.0.4))(eslint@8.41.0))(eslint@8.41.0) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.41.0)(typescript@5.0.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.41.0): + eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.41.0)(typescript@5.0.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.62.0(eslint@8.41.0)(typescript@5.0.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.41.0)(typescript@5.0.4))(eslint@8.41.0))(eslint@8.41.0))(eslint@8.41.0): dependencies: array-includes: 3.1.7 array.prototype.findlastindex: 1.2.4 @@ -6810,7 +6886,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.41.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@5.62.0(eslint@8.41.0)(typescript@5.0.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.62.0(eslint@8.41.0)(typescript@5.0.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.41.0))(eslint@8.41.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@5.62.0(eslint@8.41.0)(typescript@5.0.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.62.0(eslint@8.41.0)(typescript@5.0.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.41.0)(typescript@5.0.4))(eslint@8.41.0))(eslint@8.41.0))(eslint@8.41.0) hasown: 2.0.2 is-core-module: 2.13.1 is-glob: 4.0.3 @@ -7885,6 +7961,8 @@ snapshots: jiti@1.21.0: {} + jose@5.6.3: {} + js-tokens@4.0.0: {} js-yaml@3.14.1: @@ -8716,6 +8794,12 @@ snapshots: negotiator@0.6.3: {} + next-auth@5.0.0-beta.19(next@14.0.1(@babel/core@7.24.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0): + dependencies: + '@auth/core': 0.32.0 + next: 14.0.1(@babel/core@7.24.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + react: 18.2.0 + next-intl@3.9.5(next@14.0.1(@babel/core@7.24.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0): dependencies: '@formatjs/intl-localematcher': 0.2.32 @@ -8769,6 +8853,8 @@ snapshots: nwsapi@2.2.10: {} + oauth4webapi@2.11.1: {} + object-assign@4.1.1: {} object-hash@3.0.0: {} @@ -8974,6 +9060,13 @@ snapshots: picocolors: 1.0.0 source-map-js: 1.0.2 + preact-render-to-string@5.2.3(preact@10.11.3): + dependencies: + preact: 10.11.3 + pretty-format: 3.8.0 + + preact@10.11.3: {} + prebuild-install@7.1.2: dependencies: detect-libc: 2.0.2 @@ -9005,6 +9098,8 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.3.1 + pretty-format@3.8.0: {} + prisma@4.16.2: dependencies: '@prisma/engines': 4.16.2 diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 2913aa4..180b4f1 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -11,3 +11,10 @@ model Post { slug String @unique views Int @default(0) } + +model Guestbook { + id Int @id @default(autoincrement()) + email String @db.VarChar(255) + body String + createdAt DateTime @db.Timestamp(0) +} diff --git a/src/app/[locale]/guestbook/page.tsx b/src/app/[locale]/guestbook/page.tsx new file mode 100644 index 0000000..72a3ff6 --- /dev/null +++ b/src/app/[locale]/guestbook/page.tsx @@ -0,0 +1,39 @@ +import { signIn, signOut , auth } from '@/lib/auth'; +import Image from 'next/image'; + +const GuestbookPage = async () => { + const session = await auth(); + + if (!session?.user) { + return ( + <> +
{ + 'use server'; + await signIn('github'); + }} + > + +
+ + ); + } + + return ( + <> +

Welcome {session.user.name}

+ avatar +
{ + 'use server'; + await signOut(); + }} + > + +
+ + + ); +}; + +export default GuestbookPage; diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 0000000..5ef28c1 --- /dev/null +++ b/src/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,3 @@ +import { handlers } from '@/lib/auth'; + +export const { GET, POST } = handlers; diff --git a/src/app/api/guestbook/route.ts b/src/app/api/guestbook/route.ts new file mode 100644 index 0000000..b0d598c --- /dev/null +++ b/src/app/api/guestbook/route.ts @@ -0,0 +1,5 @@ +import { NextResponse } from 'next/server'; + +export const GET = async () => { + return NextResponse.json({}); +}; diff --git a/src/components/header.tsx b/src/components/header.tsx index f1c8f21..584d404 100644 --- a/src/components/header.tsx +++ b/src/components/header.tsx @@ -47,11 +47,11 @@ const Header = () => { return (
-
+
{ {navLinks.map((link) => ( diff --git a/src/config/nav-links.tsx b/src/config/nav-links.tsx index 5dcaf4e..3b097aa 100644 --- a/src/config/nav-links.tsx +++ b/src/config/nav-links.tsx @@ -1,4 +1,4 @@ -import { RiGitRepositoryFill, RiQuillPenFill, RiUser3Fill } from 'react-icons/ri'; +import { RiGitRepositoryFill, RiQuillPenFill, RiUser3Fill, RiUser6Line } from 'react-icons/ri'; const navLinks = [ { @@ -16,6 +16,11 @@ const navLinks = [ href: '/about', icon: , }, + { + title: 'guestbook', + href: '/guestbook', + icon: , + }, ]; export default navLinks; diff --git a/src/lib/auth.ts b/src/lib/auth.ts new file mode 100644 index 0000000..1b521a1 --- /dev/null +++ b/src/lib/auth.ts @@ -0,0 +1,11 @@ +import NextAuth from 'next-auth'; +import Github from 'next-auth/providers/github'; + +export const { handlers, signIn, signOut, auth } = NextAuth({ + providers: [ + Github({ + clientId: process.env.OAUTH_CLIENT_ID, + clientSecret: process.env.OAUTH_CLIENT_SECRET, + }), + ], +});