diff --git a/.env.template b/.env.template index f0fe2dc812..d1eda676f4 100644 --- a/.env.template +++ b/.env.template @@ -3,15 +3,5 @@ NEXT_PUBLIC_GATEWAY_URL=http://127.0.0.1:2333/ NEXT_PUBLIC_API_URL=https://innei.ren/api/v2 NEXT_PUBLIC_GATEWAY_URL=https://api.innei.ren -NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_aaaaaaaaaaaaaaaaaxxxxxx - -## Clerk -CLERK_SECRET_KEY=sk_test_ - -NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in -NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up -NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/ -NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/ - TMDB_API_KEY= GH_TOKEN= diff --git a/Dockerfile b/Dockerfile index cdcf719a5b..43cc43e155 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,6 @@ WORKDIR /app COPY . . - RUN npm install -g pnpm RUN pnpm install @@ -19,15 +18,13 @@ FROM base AS builder RUN apk update && apk add --no-cache git - WORKDIR /app COPY --from=deps /app/ . RUN npm install -g pnpm ENV NODE_ENV production ARG BASE_URL -ARG NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY -ARG CLERK_SECRET_KEY + ARG S3_ACCESS_KEY ARG S3_SECRET_KEY ARG WEBHOOK_SECRET @@ -36,8 +33,7 @@ ARG GH_TOKEN ENV BASE_URL=${BASE_URL} ENV NEXT_PUBLIC_API_URL=${BASE_URL}/api/v2 ENV NEXT_PUBLIC_GATEWAY_URL=${BASE_URL} -ENV NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=${NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY} -ENV CLERK_SECRET_KEY=${CLERK_SECRET_KEY} + ENV S3_ACCESS_KEY=${S3_ACCESS_KEY} ENV S3_SECRET_KEY=${S3_SECRET_KEY} ENV TMDB_API_KEY=${TMDB_API_KEY} @@ -61,4 +57,4 @@ EXPOSE 2323 ENV PORT 2323 ENV NEXT_SHARP_PATH=/usr/local/lib/node_modules/sharp -CMD echo "Mix Space Web [Shiro] Image." && node server.js; \ No newline at end of file +CMD echo "Mix Space Web [Shiro] Image." && node server.js diff --git a/package.json b/package.json index 7bc823e0b9..b1b5e762fe 100644 --- a/package.json +++ b/package.json @@ -23,8 +23,6 @@ }, "dependencies": { "@aws-sdk/client-s3": "3.649.0", - "@clerk/nextjs": "5.5.1", - "@clerk/themes": "2.1.29", "@excalidraw/excalidraw": "0.17.6", "@floating-ui/react-dom": "2.1.1", "@milkdown/core": "7.5.0", @@ -42,7 +40,9 @@ "@milkdown/utils": "7.5.0", "@mx-space/api-client": "1.15.0", "@prosemirror-adapter/react": "0.2.6", + "@radix-ui/react-avatar": "1.1.0", "@radix-ui/react-dialog": "1.1.1", + "@radix-ui/react-dropdown-menu": "2.1.1", "@radix-ui/react-label": "2.1.0", "@radix-ui/react-scroll-area": "1.1.0", "@radix-ui/react-select": "2.1.1", @@ -85,6 +85,7 @@ "mermaid": "11.2.0", "nanoid": "^5.0.7", "next": "14.2.9", + "next-auth": "4.24.7", "next-runtime-env": "3.2.2", "next-themes": "0.3.0", "ofetch": "1.3.4", @@ -155,6 +156,7 @@ "tailwind-scrollbar": "3.1.0", "tailwind-variants": "0.2.1", "tailwindcss": "^3.4.10", + "tailwindcss-animate": "1.0.7", "tailwindcss-animated": "1.1.2", "typescript": "5.6.2", "zx": "8.1.6" @@ -202,4 +204,4 @@ "browserslist": [ "defaults and fully supports es6-module" ] -} +} \ No newline at end of file diff --git a/packages/fetch/src/fetch.server.ts b/packages/fetch/src/fetch.server.ts index 175aea99d8..426efea0fd 100644 --- a/packages/fetch/src/fetch.server.ts +++ b/packages/fetch/src/fetch.server.ts @@ -6,12 +6,7 @@ import { createFetch } from 'ofetch' import PKG from '~/../package.json' -import { - ClerkCookieKey, - createApiClient, - createFetchAdapter, - TokenKey, -} from './shared' +import { createApiClient, createFetchAdapter, TokenKey } from './shared' const isDev = process.env.NODE_ENV === 'development' export const $fetch = createFetch({ @@ -20,9 +15,8 @@ export const $fetch = createFetch({ onRequest(context) { const cookie = cookies() - const clerkJwt = cookie.get(ClerkCookieKey)?.value - const token = cookie.get(TokenKey)?.value || clerkJwt + const token = cookie.get(TokenKey)?.value const headers: any = context.options.headers ?? {} if (token) { diff --git a/packages/fetch/src/shared.ts b/packages/fetch/src/shared.ts index b0e3b233d3..b787fd49fc 100644 --- a/packages/fetch/src/shared.ts +++ b/packages/fetch/src/shared.ts @@ -62,15 +62,10 @@ export const createApiClient = ( export const TokenKey = 'mx-token' -export const ClerkCookieKey = '__session' -export const AuthKeyNames = [TokenKey, ClerkCookieKey] +export const AuthKeyNames = [TokenKey] export function getToken(): string | null { - // FUCK clerk constants not export, and mark it internal and can not custom - // packages/backend/src/constants.ts - const clerkJwt = Cookies.get(ClerkCookieKey) - - const token = Cookies.get(TokenKey) || clerkJwt + const token = Cookies.get(TokenKey) return token || null } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 66b44fce76..293d427ae9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,12 +26,6 @@ importers: '@aws-sdk/client-s3': specifier: 3.649.0 version: 3.649.0 - '@clerk/nextjs': - specifier: 5.5.1 - version: 5.5.1(next@14.2.9(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@clerk/themes': - specifier: 2.1.29 - version: 2.1.29 '@excalidraw/excalidraw': specifier: 0.17.6 version: 0.17.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -83,9 +77,15 @@ importers: '@prosemirror-adapter/react': specifier: 0.2.6 version: 0.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-avatar': + specifier: 1.1.0 + version: 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-dialog': specifier: 1.1.1 version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-dropdown-menu': + specifier: 2.1.1 + version: 2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-label': specifier: 2.1.0 version: 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -212,6 +212,9 @@ importers: next: specifier: 14.2.9 version: 14.2.9(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next-auth: + specifier: 4.24.7 + version: 4.24.7(next@14.2.9(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-runtime-env: specifier: 3.2.2 version: 3.2.2(next@14.2.9(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) @@ -417,6 +420,9 @@ importers: tailwindcss: specifier: ^3.4.10 version: 3.4.10 + tailwindcss-animate: + specifier: 1.0.7 + version: 1.0.7(tailwindcss@3.4.10) tailwindcss-animated: specifier: 1.1.2 version: 1.1.2(tailwindcss@3.4.10) @@ -771,45 +777,6 @@ packages: '@chevrotain/utils@11.0.3': resolution: {integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==} - '@clerk/backend@1.11.0': - resolution: {integrity: sha512-JIJwuTgPFq21MHDrdoXmWCcuyAAXBArAOCpgRd84vN/5LMomJKzs1SL21UGEhDiFCEW4L0OTfvLzWAaEr5t39g==} - engines: {node: '>=18.17.0'} - - '@clerk/clerk-react@5.8.1': - resolution: {integrity: sha512-n9zo6XNrK0xkp9xwkdpC+7e1d9zKWpNrZNJlr5xsWIbVkZcrLVd/R+K9LxTidbV8s7MR2ZQjw+u4i29/9iYTDg==} - engines: {node: '>=18.17.0'} - peerDependencies: - react: '>=18 || >=19.0.0-beta' - react-dom: '>=18 || >=19.0.0-beta' - - '@clerk/nextjs@5.5.1': - resolution: {integrity: sha512-op987JSWA13GiYpsgPYc8ZknbM8p3M6S+hMPUnl/yUJrfvOfNEZHyxdEgE4vx0jtNVseqf2oIIPvKnzliTcDWA==} - engines: {node: '>=18.17.0'} - peerDependencies: - next: ^13.5.4 || ^14.0.3 || >=15.0.0-rc - react: '>=18 || >=19.0.0-beta' - react-dom: '>=18 || >=19.0.0-beta' - - '@clerk/shared@2.7.1': - resolution: {integrity: sha512-cEhLSCP/bzrEKh6j/NjEcNlXexYvJy2CjD2pPyYLnZLGKmP0vBGDO6dqGeJPJm356lspi57O97GA7QEGgn0gRA==} - engines: {node: '>=18.17.0'} - peerDependencies: - react: '>=18 || >=19.0.0-beta' - react-dom: '>=18 || >=19.0.0-beta' - peerDependenciesMeta: - react: - optional: true - react-dom: - optional: true - - '@clerk/themes@2.1.29': - resolution: {integrity: sha512-uwhVLw6jLTQQLnPhwJmncEElbGit/55+IHsLSgaUm6shB01fSK7lfoHFaWpopRsNvGZKZ6bv6v6pgP8bB2ZokQ==} - engines: {node: '>=18.17.0'} - - '@clerk/types@4.20.1': - resolution: {integrity: sha512-s2v3wFgLsB+d0Ot5yN+5IjRNKWl63AAeEczTZDZYSWuNkGihvEXYjS2NtnYuhROBRgWEHEsm0JOp0rQkfTMkBw==} - engines: {node: '>=18.17.0'} - '@crossbell/ipfs-fetch@0.0.21': resolution: {integrity: sha512-wPNLptEPy64/7WMz7bhS5QVYfjproCr3ACgeQoJzmNKvH2FNDlF6FjMB9XNwV0spAczGb/SueNzW8p6BBLXHnw==} @@ -1544,6 +1511,9 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@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'} @@ -1583,6 +1553,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-avatar@1.1.0': + resolution: {integrity: sha512-Q/PbuSMk/vyAd/UoIShVGZ7StHHeRFYU7wXmi5GV+8cLXflZAEpHL/F697H1klrzxKXNtZ97vWiC0q3RKUH8UA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-collection@1.1.0': resolution: {integrity: sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==} peerDependencies: @@ -1658,6 +1641,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-dropdown-menu@2.1.1': + resolution: {integrity: sha512-y8E+x9fBq9qvteD2Zwa4397pUVhYsh9iq44b5RD5qu1GMJWBCBuVg1hMyItbc6+zH00TxGRqd9Iot4wzf3OoBQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-focus-guards@1.1.0': resolution: {integrity: sha512-w6XZNUPVv6xCpZUqb/yN9DL6auvpGX3C/ee6Hdi16v2UUy25HV2Q5bcflsiDyT/g5RwbPQ/GIT1vLkeRb+ITBw==} peerDependencies: @@ -1702,6 +1698,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-menu@2.1.1': + resolution: {integrity: sha512-oa3mXRRVjHi6DZu/ghuzdylyjaMXLymx83irM7hTxutQbD+7IhPKdMdRHD26Rm+kHRrWcrUkkRPv5pd47a2xFQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-popper@1.2.0': resolution: {integrity: sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==} peerDependencies: @@ -2907,9 +2916,6 @@ packages: cssfilter@0.0.10: resolution: {integrity: sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==} - csstype@3.1.1: - resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==} - csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} @@ -3174,9 +3180,6 @@ packages: dompurify@3.1.3: resolution: {integrity: sha512-5sOWYSNPaxz6o2MUPvtyxTTqR4D3L77pr5rUQoWgD5ROQtVIZQgJkXbo1DLlK3vj11YGw5+LnF4SYti4gZmwng==} - dot-case@3.0.4: - resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} - dotenv@16.4.5: resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} engines: {node: '>=12'} @@ -3660,9 +3663,6 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - glob-to-regexp@0.4.1: - resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} - glob@11.0.0: resolution: {integrity: sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==} engines: {node: 20 || >=22} @@ -3929,6 +3929,9 @@ packages: jotai: ^2.0.0 react: ^18.0.0 + jose@4.15.9: + resolution: {integrity: sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==} + jotai@2.9.3: resolution: {integrity: sha512-IqMWKoXuEzWSShjd9UhalNsRGbdju5G2FrqNLQJT+Ih6p41VNYe2sav5hnwQx4HJr25jq9wRqvGSWGviGG6Gjw==} engines: {node: '>=12.20.0'} @@ -4105,9 +4108,6 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true - lower-case@2.0.2: - resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} - lru-cache@10.2.2: resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==} engines: {node: 14 || >=16.14} @@ -4119,13 +4119,13 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + magic-string@0.30.11: resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} - map-obj@4.3.0: - resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} - engines: {node: '>=8'} - markdown-escape@2.0.0: resolution: {integrity: sha512-Trz4v0+XWlwy68LJIyw3bLbsJiC8XAbRCKF9DbEtZjyndKOGVx6n+wNB0VfoRmY2LKboQLeniap3xrb6LGSJ8A==} @@ -4470,6 +4470,17 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + next-auth@4.24.7: + resolution: {integrity: sha512-iChjE8ov/1K/z98gdKbn2Jw+2vLgJtVV39X+rCP5SGnVQuco7QOr19FRNGMIrD8d3LYhHWV9j9sKLzq1aDWWQQ==} + peerDependencies: + next: ^12.2.5 || ^13 || ^14 + nodemailer: ^6.6.5 + react: ^17.0.2 || ^18 + react-dom: ^17.0.2 || ^18 + peerDependenciesMeta: + nodemailer: + optional: true + next-runtime-env@3.2.2: resolution: {integrity: sha512-S5S6NxIf3XeaVc9fLBN2L5Jzu+6dLYCXeOaPQa1RzKRYlG2BBayxXOj6A4VsciocyNkJMazW1VAibtbb1/ZjAw==} peerDependencies: @@ -4500,9 +4511,6 @@ packages: sass: optional: true - no-case@3.0.4: - resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} - node-domexception@1.0.0: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'} @@ -4548,10 +4556,17 @@ packages: resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + oauth@0.9.15: + resolution: {integrity: sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} + object-hash@2.2.0: + resolution: {integrity: sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==} + engines: {node: '>= 6'} + object-hash@3.0.0: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} @@ -4569,6 +4584,10 @@ packages: ofetch@1.3.4: resolution: {integrity: sha512-KLIET85ik3vhEfS+3fDlc/BAZiAp+43QEC/yCo5zkNoY2YaKvNkOaFr/6wCFgFH1kuYQM5pMNi0Tg8koiIemtw==} + oidc-token-hash@5.0.3: + resolution: {integrity: sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==} + engines: {node: ^10.13.0 || >=12.0.0} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -4596,6 +4615,9 @@ packages: resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} hasBin: true + openid-client@5.7.0: + resolution: {integrity: sha512-4GCCGZt1i2kTHpwvaC/sCpTpQqDnBzDzuJcJMbH+y1Q5qI8U8RBvoSh28svarXszZHR5BAMXbJPX1PGPRE3VOA==} + optimist@0.6.1: resolution: {integrity: sha512-snN4O4TkigujZphWLN0E//nQmm7790RYaE53DdL7ZYwee2D8DDo9/EyYiKUfN3rneWUjhJnueija3G9I2i0h3g==} @@ -4681,9 +4703,6 @@ packages: resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} engines: {node: 20 || >=22} - path-to-regexp@6.2.2: - resolution: {integrity: sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==} - path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -4981,6 +5000,14 @@ packages: postgres-range@1.1.4: resolution: {integrity: sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==} + preact-render-to-string@5.2.6: + resolution: {integrity: sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==} + peerDependencies: + preact: '>=10' + + preact@10.23.2: + resolution: {integrity: sha512-kKYfePf9rzKnxOAKDpsWhg/ysrHPqT+yQ7UW4JjdnqjFIeNUnNcEJvhuA8fDenxAGWzUqtd51DfVg7xp/8T9NA==} + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -5058,6 +5085,9 @@ packages: engines: {node: '>=14'} hasBin: true + pretty-format@3.8.0: + resolution: {integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==} + prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -5415,13 +5445,6 @@ packages: resolution: {integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==} engines: {node: '>=18'} - snake-case@3.0.4: - resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} - - snakecase-keys@5.4.4: - resolution: {integrity: sha512-YTywJG93yxwHLgrYLZjlC75moVEX04LZM4FHfihjHe1FCXm+QaLOFfSf535aXOAd0ArVQMWUAe8ZPm4VtWyXaA==} - engines: {node: '>=12'} - socket.io-client@4.7.5: resolution: {integrity: sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==} engines: {node: '>=10.0.0'} @@ -5462,9 +5485,6 @@ packages: stable-hash@0.0.4: resolution: {integrity: sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==} - std-env@3.7.0: - resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} - streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} @@ -5575,6 +5595,11 @@ packages: peerDependencies: tailwindcss: '*' + tailwindcss-animate@1.0.7: + resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders' + tailwindcss-animated@1.1.2: resolution: {integrity: sha512-SI4owS5ojserhgEYIZA/uFVdNjU2GMB2P3sjtjmFA52VxoUi+Hht6oR5+RdT+CxrX9cNNYEa+vbTWHvN9zbj3w==} peerDependencies: @@ -5643,9 +5668,6 @@ packages: ts-pattern@5.3.1: resolution: {integrity: sha512-1RUMKa8jYQdNfmnK4jyzBK3/PS/tnjcZ1CW0v1vWDeYe5RBklc/nquw03MEoB66hVBm4BnlCfmOqDVxHyT1DpA==} - tslib@2.4.1: - resolution: {integrity: sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==} - tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} @@ -5669,10 +5691,6 @@ packages: resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} engines: {node: '>=8'} - type-fest@2.19.0: - resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} - engines: {node: '>=12.20'} - type-fest@4.24.0: resolution: {integrity: sha512-spAaHzc6qre0TlZQQ2aA/nGMe+2Z/wyGk5Z+Ru2VUfdNwT6kWO6TjevOlpebsATEG1EIQ2sOiDszud3lO5mt/Q==} engines: {node: '>=16'} @@ -5781,6 +5799,10 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + uuid@9.0.1: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} hasBin: true @@ -5961,6 +5983,9 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yaml@1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} @@ -6705,59 +6730,6 @@ snapshots: '@chevrotain/utils@11.0.3': {} - '@clerk/backend@1.11.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@clerk/shared': 2.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@clerk/types': 4.20.1 - cookie: 0.5.0 - snakecase-keys: 5.4.4 - tslib: 2.4.1 - transitivePeerDependencies: - - react - - react-dom - - '@clerk/clerk-react@5.8.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@clerk/shared': 2.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@clerk/types': 4.20.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - tslib: 2.4.1 - - '@clerk/nextjs@5.5.1(next@14.2.9(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@clerk/backend': 1.11.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@clerk/clerk-react': 5.8.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@clerk/shared': 2.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@clerk/types': 4.20.1 - crypto-js: 4.2.0 - next: 14.2.9(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - path-to-regexp: 6.2.2 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - server-only: 0.0.1 - tslib: 2.4.1 - - '@clerk/shared@2.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@clerk/types': 4.20.1 - glob-to-regexp: 0.4.1 - js-cookie: 3.0.5 - std-env: 3.7.0 - swr: 2.2.5(react@18.3.1) - optionalDependencies: - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - '@clerk/themes@2.1.29': - dependencies: - '@clerk/types': 4.20.1 - tslib: 2.4.1 - - '@clerk/types@4.20.1': - dependencies: - csstype: 3.1.1 - '@crossbell/ipfs-fetch@0.0.21': {} '@csstools/cascade-layer-name-parser@2.0.1(@csstools/css-parser-algorithms@3.0.1(@csstools/css-tokenizer@3.0.1))(@csstools/css-tokenizer@3.0.1)': @@ -7581,6 +7553,8 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.15.0 + '@panva/hkdf@1.2.1': {} + '@pkgjs/parseargs@0.11.0': optional: true @@ -7613,6 +7587,18 @@ snapshots: '@types/react': 18.3.5 '@types/react-dom': 18.3.0 + '@radix-ui/react-avatar@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-context': 1.1.0(@types/react@18.3.5)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.5)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.5)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.5 + '@types/react-dom': 18.3.0 + '@radix-ui/react-collection@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.5)(react@18.3.1) @@ -7685,6 +7671,21 @@ snapshots: '@types/react': 18.3.5 '@types/react-dom': 18.3.0 + '@radix-ui/react-dropdown-menu@2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.5)(react@18.3.1) + '@radix-ui/react-context': 1.1.0(@types/react@18.3.5)(react@18.3.1) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.5)(react@18.3.1) + '@radix-ui/react-menu': 2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.5)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.5 + '@types/react-dom': 18.3.0 + '@radix-ui/react-focus-guards@1.1.0(@types/react@18.3.5)(react@18.3.1)': dependencies: react: 18.3.1 @@ -7718,6 +7719,32 @@ snapshots: '@types/react': 18.3.5 '@types/react-dom': 18.3.0 + '@radix-ui/react-menu@2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-collection': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.5)(react@18.3.1) + '@radix-ui/react-context': 1.1.0(@types/react@18.3.5)(react@18.3.1) + '@radix-ui/react-direction': 1.1.0(@types/react@18.3.5)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.0(@types/react@18.3.5)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.5)(react@18.3.1) + '@radix-ui/react-popper': 1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.5)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.5)(react@18.3.1) + aria-hidden: 1.2.3 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.5.7(@types/react@18.3.5)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.5 + '@types/react-dom': 18.3.0 + '@radix-ui/react-popper@1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@floating-ui/react-dom': 2.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -9107,8 +9134,6 @@ snapshots: cssfilter@0.0.10: {} - csstype@3.1.1: {} - csstype@3.1.3: {} culori@3.3.0: {} @@ -9374,11 +9399,6 @@ snapshots: dompurify@3.1.3: {} - dot-case@3.0.4: - dependencies: - no-case: 3.0.4 - tslib: 2.7.0 - dotenv@16.4.5: {} duplexer@0.1.2: {} @@ -10003,8 +10023,6 @@ snapshots: dependencies: is-glob: 4.0.3 - glob-to-regexp@0.4.1: {} - glob@11.0.0: dependencies: foreground-child: 3.1.1 @@ -10259,6 +10277,8 @@ snapshots: jotai: 2.9.3(@types/react@18.3.5)(react@18.3.1) react: 18.3.1 + jose@4.15.9: {} + jotai@2.9.3(@types/react@18.3.5)(react@18.3.1): optionalDependencies: '@types/react': 18.3.5 @@ -10426,10 +10446,6 @@ snapshots: dependencies: js-tokens: 4.0.0 - lower-case@2.0.2: - dependencies: - tslib: 2.7.0 - lru-cache@10.2.2: {} lru-cache@11.0.0: {} @@ -10438,12 +10454,14 @@ snapshots: dependencies: yallist: 3.1.1 + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + magic-string@0.30.11: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 - map-obj@4.3.0: {} - markdown-escape@2.0.0: {} markdown-table@3.0.3: {} @@ -11085,6 +11103,21 @@ snapshots: natural-compare@1.4.0: {} + next-auth@4.24.7(next@14.2.9(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.24.1 + '@panva/hkdf': 1.2.1 + cookie: 0.5.0 + jose: 4.15.9 + next: 14.2.9(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + oauth: 0.9.15 + openid-client: 5.7.0 + preact: 10.23.2 + preact-render-to-string: 5.2.6(preact@10.23.2) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + uuid: 8.3.2 + next-runtime-env@3.2.2(next@14.2.9(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1): dependencies: next: 14.2.9(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -11120,11 +11153,6 @@ snapshots: - '@babel/core' - babel-plugin-macros - no-case@3.0.4: - dependencies: - lower-case: 2.0.2 - tslib: 2.7.0 - node-domexception@1.0.0: {} node-fetch-native@1.6.4: {} @@ -11160,8 +11188,12 @@ snapshots: dependencies: path-key: 4.0.0 + oauth@0.9.15: {} + object-assign@4.1.1: {} + object-hash@2.2.0: {} + object-hash@3.0.0: {} object-inspect@1.13.2: {} @@ -11176,6 +11208,8 @@ snapshots: node-fetch-native: 1.6.4 ufo: 1.5.3 + oidc-token-hash@5.0.3: {} + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -11208,6 +11242,13 @@ snapshots: opener@1.5.2: {} + openid-client@5.7.0: + dependencies: + jose: 4.15.9 + lru-cache: 6.0.0 + object-hash: 2.2.0 + oidc-token-hash: 5.0.3 + optimist@0.6.1: dependencies: minimist: 0.0.10 @@ -11299,8 +11340,6 @@ snapshots: lru-cache: 11.0.0 minipass: 7.1.2 - path-to-regexp@6.2.2: {} - path-type@4.0.0: {} pathe@1.1.2: {} @@ -11639,6 +11678,13 @@ snapshots: postgres-range@1.1.4: {} + preact-render-to-string@5.2.6(preact@10.23.2): + dependencies: + preact: 10.23.2 + pretty-format: 3.8.0 + + preact@10.23.2: {} + prelude-ls@1.2.1: {} prettier-package-json@2.8.0: @@ -11672,6 +11718,8 @@ snapshots: prettier@3.3.3: {} + pretty-format@3.8.0: {} + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -12096,17 +12144,6 @@ snapshots: ansi-styles: 6.2.1 is-fullwidth-code-point: 5.0.0 - snake-case@3.0.4: - dependencies: - dot-case: 3.0.4 - tslib: 2.7.0 - - snakecase-keys@5.4.4: - dependencies: - map-obj: 4.3.0 - snake-case: 3.0.4 - type-fest: 2.19.0 - socket.io-client@4.7.5(bufferutil@4.0.8): dependencies: '@socket.io/component-emitter': 3.1.0 @@ -12158,8 +12195,6 @@ snapshots: stable-hash@0.0.4: {} - std-env@3.7.0: {} - streamsearch@1.1.0: {} string-argv@0.3.2: {} @@ -12262,6 +12297,10 @@ snapshots: tailwind-merge: 2.5.2 tailwindcss: 3.4.10 + tailwindcss-animate@1.0.7(tailwindcss@3.4.10): + dependencies: + tailwindcss: 3.4.10 + tailwindcss-animated@1.1.2(tailwindcss@3.4.10): dependencies: tailwindcss: 3.4.10 @@ -12336,8 +12375,6 @@ snapshots: ts-pattern@5.3.1: {} - tslib@2.4.1: {} - tslib@2.6.2: {} tslib@2.7.0: {} @@ -12357,8 +12394,6 @@ snapshots: type-fest@0.8.1: {} - type-fest@2.19.0: {} - type-fest@4.24.0: {} typescript-eslint@8.4.0(eslint@9.10.0(jiti@1.21.0))(typescript@5.6.2): @@ -12486,6 +12521,8 @@ snapshots: util-deprecate@1.0.2: {} + uuid@8.3.2: {} + uuid@9.0.1: {} uvu@0.5.6: @@ -12674,6 +12711,8 @@ snapshots: yallist@3.1.1: {} + yallist@4.0.0: {} + yaml@1.10.2: {} yaml@2.5.0: {} diff --git a/src/app/(app)/layout.tsx b/src/app/(app)/layout.tsx index 43d27167a4..024c2ac58c 100644 --- a/src/app/(app)/layout.tsx +++ b/src/app/(app)/layout.tsx @@ -1,7 +1,7 @@ /* eslint-disable no-console */ -import { ClerkProvider } from '@clerk/nextjs' + import type { Metadata, Viewport } from 'next' -import { env, PublicEnvScript } from 'next-runtime-env' +import { PublicEnvScript } from 'next-runtime-env' import type { PropsWithChildren } from 'react' import { ToastContainer } from 'react-toastify' @@ -139,7 +139,7 @@ export default async function RootLayout(props: PropsWithChildren) { -
+
初始数据的获取失败,请检查 API 服务器是否正常运行。接口请求错误信息:
@@ -153,54 +153,52 @@ export default async function RootLayout(props: PropsWithChildren) { const themeConfig = data.theme return ( - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - - - -
- {children} -
- - - - - - - -
- - - - - +
+ {children} +
+ + + + + + + +
+ + + + ) } diff --git a/src/app/(app)/sign-in/[[...sign-in]]/page.tsx b/src/app/(app)/sign-in/[[...sign-in]]/page.tsx deleted file mode 100644 index 951c88a8a7..0000000000 --- a/src/app/(app)/sign-in/[[...sign-in]]/page.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { SignIn } from '@clerk/nextjs' -import React from 'react' - -export default function Page() { - return -} diff --git a/src/app/(app)/sign-up/[[...sign-up]]/page.tsx b/src/app/(app)/sign-up/[[...sign-up]]/page.tsx deleted file mode 100644 index 94b2a89c63..0000000000 --- a/src/app/(app)/sign-up/[[...sign-up]]/page.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { SignUp } from '@clerk/nextjs' -import React from 'react' - -export default function Page() { - return -} diff --git a/src/atoms/hooks/reader.ts b/src/atoms/hooks/reader.ts new file mode 100644 index 0000000000..734935c558 --- /dev/null +++ b/src/atoms/hooks/reader.ts @@ -0,0 +1,25 @@ +import type { AuthUser } from '@mx-space/api-client' +import { createAtomHooks } from 'jojoo/react' +import { atom } from 'jotai' + +import { createAtomSelector } from '~/lib/atom' +import type { SessionReader } from '~/models/session' + +export const [, , useSessionReader, , getSessionReader, setSessionReader] = + createAtomHooks(atom(null)) + +const [authReaderAtom, , , , getAuthReaders, _setAuthReaders] = createAtomHooks( + atom>({}), +) +export { getAuthReaders } + +export const setAuthReaders = (readers: Record) => { + _setAuthReaders({ + ...getAuthReaders(), + ...readers, + }) +} +const useAuthReaderSelector = createAtomSelector(authReaderAtom) +export const useAuthReader = (id: string): AuthUser | undefined => { + return useAuthReaderSelector((readers) => readers[id], [id]) +} diff --git a/src/atoms/hooks/socket.ts b/src/atoms/hooks/socket.ts index 7b69752174..419b85a6c3 100644 --- a/src/atoms/hooks/socket.ts +++ b/src/atoms/hooks/socket.ts @@ -1,4 +1,3 @@ -import { useUser } from '@clerk/nextjs' import { useAtomValue } from 'jotai' import { customAlphabet } from 'nanoid' import { useMemo } from 'react' @@ -8,6 +7,7 @@ import { buildNSKey } from '~/lib/ns' import { socketIsConnectAtom } from '../socket' import { useIsLogged, useOwner } from './owner' +import { useSessionReader } from './reader' const alphabet = `1234567890abcdefghijklmnopqrstuvwxyz` @@ -26,7 +26,7 @@ export const getSocketWebSessionId = () => { } export const useSocketSessionId = () => { - const user = useUser() + const sessionReader = useSessionReader() const owner = useOwner() const ownerIsLogin = useIsLogged() @@ -35,13 +35,11 @@ export const useSocketSessionId = () => { if (ownerIsLogin) { if (!owner) return fallbackSid return `owner_${owner.id}` - } else if (user && user.isSignedIn) { - return user.user.id.toLowerCase() + } else if (sessionReader) { + return sessionReader.id.toLowerCase() } return fallbackSid - }, [owner, ownerIsLogin, user]) + }, [owner, ownerIsLogin, sessionReader]) } -export const useSocketIsConnect = () => { - return useAtomValue(socketIsConnectAtom) -} +export const useSocketIsConnect = () => useAtomValue(socketIsConnectAtom) diff --git a/src/components/common/ClientOnly.tsx b/src/components/common/ClientOnly.tsx index f9c070cd57..dbc312e700 100644 --- a/src/components/common/ClientOnly.tsx +++ b/src/components/common/ClientOnly.tsx @@ -1,9 +1,21 @@ 'use client' +import type { ReactNode } from 'react' + import { useIsClient } from '~/hooks/common/use-is-client' -export const ClientOnly: Component = (props) => { +export const ClientOnly: Component<{ + fallback?: ReactNode +}> = (props) => { const isClient = useIsClient() - if (!isClient) return null + if (!isClient) return props.fallback ?? null return <>{props.children} } + +export const withClientOnly = +

(Component: Component

) => + (props: P) => ( + + + + ) diff --git a/src/components/layout/header/internal/Activity.tsx b/src/components/layout/header/internal/Activity.tsx index 65edc2d202..f6c2e7e9a0 100644 --- a/src/components/layout/header/internal/Activity.tsx +++ b/src/components/layout/header/internal/Activity.tsx @@ -33,12 +33,11 @@ const ActivityIconContext = createContext<{ }>(null!) const CND_DOMAIN = 'https://fastly.jsdelivr.net/gh/Innei/reporter-assets@main' -const fetchJsonData = () => { - return Promise.all([ +const fetchJsonData = () => + Promise.all([ fetch(`${CND_DOMAIN}/app-icon.json`).then((res) => res.json() as object), fetch(`${CND_DOMAIN}/app-desc.json`).then((res) => res.json() as object), ]) -} export const Activity = () => { const shouldShowMeta = useHeaderMetaShouldShow() @@ -68,8 +67,8 @@ const ActivityIcon = memo(() => { const isPageActive = usePageIsActive() const { data } = useQuery({ queryKey: ['activity'], - queryFn: async () => { - return await apiClient + queryFn: async () => + await apiClient .proxy(endpoint) .post<{ processName: string @@ -85,14 +84,11 @@ const ActivityIcon = memo(() => { } }>() .then((res) => res) - .catch(() => { - return { - processName: '', - processInfo: undefined, - mediaInfo: undefined, - } - }) - }, + .catch(() => ({ + processName: '', + processInfo: undefined, + mediaInfo: undefined, + })), refetchInterval: 1000 * 5 * 60, refetchOnMount: 'always', retry: false, @@ -139,7 +135,7 @@ const ActivityIcon = memo(() => { <> {!!media && ( -

+
{ const isDesktop = useViewport(($) => $.lg && $.w !== 0) + const isClient = useIsClient() + if (!isClient) return null + if (isDesktop) return ( <> diff --git a/src/components/layout/header/internal/BluredBackground.tsx b/src/components/layout/header/internal/BluredBackground.tsx index 4f351294a5..18faa766c5 100644 --- a/src/components/layout/header/internal/BluredBackground.tsx +++ b/src/components/layout/header/internal/BluredBackground.tsx @@ -11,6 +11,7 @@ export const BluredBackground = () => { className={clsx( 'absolute inset-0 transform-gpu [-webkit-backdrop-filter:saturate(180%)_blur(20px)] [backdrop-filter:saturate(180%)_blur(20px)] [backface-visibility:hidden]', 'bg-themed-bg_opacity [border-bottom:1px_solid_rgb(187_187_187_/_20%)]', + // "before:bg-accent/5 before:content-[''] before:absolute before:inset-0 before:z-0", )} style={{ opacity: headerOpacity, diff --git a/src/components/layout/header/internal/HeaderActionButton.tsx b/src/components/layout/header/internal/HeaderActionButton.tsx index e90735d9bb..0681c031eb 100644 --- a/src/components/layout/header/internal/HeaderActionButton.tsx +++ b/src/components/layout/header/internal/HeaderActionButton.tsx @@ -5,24 +5,22 @@ import { forwardRef } from 'react' export const HeaderActionButton = forwardRef< HTMLDivElement, JSX.IntrinsicElements['div'] ->(({ children, ...rest }, ref) => { - return ( -
(({ children, ...rest }, ref) => ( +
- {children} -
- ) -}) + 'center flex', + )} + {...rest} + ref={ref} + aria-label="Header Action" + > + {children} +
+)) HeaderActionButton.displayName = 'HeaderActionButton' diff --git a/src/components/layout/header/internal/HeaderArea.tsx b/src/components/layout/header/internal/HeaderArea.tsx index 18552b0b3c..51d99a8b11 100644 --- a/src/components/layout/header/internal/HeaderArea.tsx +++ b/src/components/layout/header/internal/HeaderArea.tsx @@ -5,40 +5,32 @@ import { clsxm } from '~/lib/helper' import styles from './grid.module.css' -export const HeaderLogoArea: Component = ({ children }) => { - return ( -
-
- {children} -
-
- ) -} - -export const HeaderLeftButtonArea: Component = ({ children }) => { - return ( +export const HeaderLogoArea: Component = ({ children }) => ( +
{children}
- ) -} +
+) -export const HeaderCenterArea: Component = ({ children }) => { - return ( - -
-
- {children} -
+export const HeaderLeftButtonArea: Component = ({ children }) => ( +
+ {children} +
+) + +export const HeaderCenterArea: Component = ({ children }) => ( + +
+
+ {children}
- - ) -} +
+
+) diff --git a/src/components/layout/header/internal/HeaderDrawerButton.tsx b/src/components/layout/header/internal/HeaderDrawerButton.tsx index 2004821b90..6be67a26e0 100644 --- a/src/components/layout/header/internal/HeaderDrawerButton.tsx +++ b/src/components/layout/header/internal/HeaderDrawerButton.tsx @@ -9,7 +9,7 @@ import { HeaderDrawerContent } from './HeaderDrawerContent' export const HeaderDrawerButton = () => { const isClient = useIsClient() const ButtonElement = ( - + ) diff --git a/src/components/layout/header/internal/HeaderDrawerContent.tsx b/src/components/layout/header/internal/HeaderDrawerContent.tsx index ad60db69f5..20bcadf1c1 100644 --- a/src/components/layout/header/internal/HeaderDrawerContent.tsx +++ b/src/components/layout/header/internal/HeaderDrawerContent.tsx @@ -15,6 +15,8 @@ export const HeaderDrawerContent = () => { return (
{config.map((section, index) => { + const href = section.path + return ( { ...reboundPreset, delay: index * 0.08, }} - key={section.path} + key={href} > - + {section.icon}

{section.title}

@@ -34,18 +36,13 @@ export const HeaderDrawerContent = () => { {section.subMenu && (
    - {section.subMenu.map((sub) => { - return ( -
  • - - {sub.title} - -
  • - ) - })} + {section.subMenu.map((sub) => ( +
  • + + {sub.title} + +
  • + ))}
)}
diff --git a/src/components/layout/header/internal/Logo.tsx b/src/components/layout/header/internal/Logo.tsx index 94bd48f83c..3fc7618559 100644 --- a/src/components/layout/header/internal/Logo.tsx +++ b/src/components/layout/header/internal/Logo.tsx @@ -2,37 +2,35 @@ import type { SVGProps } from 'react' import { clsxm } from '~/lib/helper' -export const Logo = (props: SVGProps) => { - return ( - - - - - - - +export const Logo = (props: SVGProps) => ( + + + + + + - - ) -} + + +) diff --git a/src/components/layout/header/internal/MenuPopover.tsx b/src/components/layout/header/internal/MenuPopover.tsx index 67e99f92a8..1dfc1caab4 100644 --- a/src/components/layout/header/internal/MenuPopover.tsx +++ b/src/components/layout/header/internal/MenuPopover.tsx @@ -29,10 +29,7 @@ export const MenuPopover: Component<{ ])} triggerElement={<>{children}} > - {subMenu.length > 0 && - subMenu.map((m) => { - return - })} + {subMenu.length > 0 && subMenu.map((m) => )} ) }) diff --git a/src/components/layout/header/internal/SiteOwnerAvatar.tsx b/src/components/layout/header/internal/SiteOwnerAvatar.tsx index a77dc662ad..8a27845a66 100644 --- a/src/components/layout/header/internal/SiteOwnerAvatar.tsx +++ b/src/components/layout/header/internal/SiteOwnerAvatar.tsx @@ -26,9 +26,7 @@ export const SiteOwnerAvatar: Component = ({ className }) => { }) .then((res) => res.json()) .catch(() => null), - select: useCallback((data: any) => { - return !!data - }, []), + select: useCallback((data: any) => !!data, []), refetchInterval: 1000 * 60, enabled: !!liveId, meta: { @@ -41,22 +39,25 @@ export const SiteOwnerAvatar: Component = ({ className }) => {
- Site Owner Avatar + > + Site Owner Avatar +
{isLiving && ( <>

diff --git a/src/components/layout/header/internal/UserAuth.tsx b/src/components/layout/header/internal/UserAuth.tsx index 5ad728b976..8061c077c7 100644 --- a/src/components/layout/header/internal/UserAuth.tsx +++ b/src/components/layout/header/internal/UserAuth.tsx @@ -1,48 +1,37 @@ 'use client' -import { dark } from '@clerk/themes/dist/themes/src/themes/dark' import { AnimatePresence } from 'framer-motion' -import dynamic from 'next/dynamic' import Image from 'next/image' -import { usePathname } from 'next/navigation' +import { Fragment } from 'react' import { useIsLogged } from '~/atoms/hooks' +import { useSessionReader } from '~/atoms/hooks/reader' import { UserArrowLeftIcon } from '~/components/icons/user-arrow-left' -import { MotionButtonBase } from '~/components/ui/button' -import { useIsDark } from '~/hooks/common/use-is-dark' -import { urlBuilder } from '~/lib/url-builder' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuPortal, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from '~/components/ui/dropdown-menu' +import { EllipsisHorizontalTextWithTooltip } from '~/components/ui/typography' +import { useIsClient } from '~/hooks/common/use-is-client' +import { signOut } from '~/lib/authjs' +import { getToken, removeToken } from '~/lib/cookie' +import { apiClient } from '~/lib/request' import { useAggregationSelector } from '~/providers/root/aggregation-data-provider' +import { useHasProviders, useOauthLoginModal } from '~/queries/hooks/authjs' import { HeaderActionButton } from './HeaderActionButton' - -const SignedIn = dynamic(() => - import('@clerk/nextjs').then((mod) => mod.SignedIn), -) - -const UserAuthFromIcon = dynamic(() => - import('./UserAuthFromIcon').then((mod) => mod.UserAuthFromIcon), -) -const SignedOut = dynamic(() => - import('@clerk/nextjs').then((mod) => mod.SignedOut), -) -const UserButton = dynamic(() => - import('@clerk/nextjs').then((mod) => mod.UserButton), -) -const SignInButton = dynamic(() => - import('@clerk/nextjs').then((mod) => mod.SignInButton), -) +import { UserAuthFromIcon } from './UserAuthFromIcon' const OwnerAvatar = () => { const ownerAvatar = useAggregationSelector((s) => s.user.avatar)! return ( - { - window.open('/dashboard', '_blank') - }} - className="pointer-events-auto relative flex items-center justify-center" - > - Go to dashboard +

{ src={ownerAvatar} alt="site owner" /> - - + +
) } - export function UserAuth() { - const pathname = usePathname() - const isLogged = useIsLogged() + const isOwner = useIsLogged() + const isClient = useIsClient() + const session = useSessionReader() - const isDark = useIsDark() + const hasProviders = useHasProviders() - if (isLogged) { - return - } + const presentOauthModal = useOauthLoginModal() + if (!isClient) return null return ( - -
-
- - -
-
-
+ + + {isOwner ? ( + + ) : session ? ( + + ) : ( + hasProviders && ( + { + presentOauthModal() + }} + aria-label="Reader Login" + > + + + ) + )} + - - - + {(session || isOwner) && ( + + + {session && ( + + + Account + + +
+ +
+
{session.name}
+ + {session?.handle + ? `@${session.handle}` + : session?.email} + +
+
+
+ +
+ )} + + {isOwner && ( + + { + window.open('/dashboard', '_blank') + }} + icon={ + + } + > + Dashboard + + + + )} + { + Promise.allSettled([ + getToken() && apiClient.user.proxy('logout').post(), + signOut(), + ]) + removeToken() + }} + icon={} + > + Sign out + +
+
+ )} +
) } -const TriggerComponent = () => { - const pathname = usePathname() +const ReaderAvatar = () => { + const session = useSessionReader()! return ( - - - - - +
+ {session.name} + +
) } diff --git a/src/components/layout/header/internal/UserAuthFromIcon.tsx b/src/components/layout/header/internal/UserAuthFromIcon.tsx index 69d345e5e9..0e058d2eef 100644 --- a/src/components/layout/header/internal/UserAuthFromIcon.tsx +++ b/src/components/layout/header/internal/UserAuthFromIcon.tsx @@ -1,25 +1,19 @@ 'use client' -import { useUser } from '@clerk/nextjs' import React from 'react' +import { useSessionReader } from '~/atoms/hooks/reader' import { getStrategyIconComponent } from '~/components/ui/user/UserAuthStrategyIcon' import { clsxm } from '~/lib/helper' export const UserAuthFromIcon: Component = ({ className }) => { - const { user } = useUser() - const StrategyIcon = React.useMemo(() => { - const strategy = user?.primaryEmailAddress?.verification.strategy - if (!strategy) { - return null - } - return getStrategyIconComponent(strategy) - }, [user?.primaryEmailAddress?.verification.strategy]) + const session = useSessionReader() + const provider = session?.provider + const StrategyIcon = provider && getStrategyIconComponent(provider) if (!StrategyIcon) { return null } - return ( { export const useMenuVisibility = () => useMenuOpacity() > 0 export const useHeaderBgOpacity = () => { - const threshold = 50 + const threshold = 84 + 63 + 50 + const distance = 50 const isMobile = useIsMobile() const headerShouldShowBg = useHeaderShouldShowBg() || isMobile return usePageScrollLocationSelector( - (y) => - headerShouldShowBg - ? y >= threshold + (y) => { + if (y < threshold) return 0 + return headerShouldShowBg + ? y >= distance + threshold ? 1 - : Math.floor((y / threshold) * 100) / 100 - : 0, + : Math.floor(((y - threshold) / distance) * 100) / 100 + : 0 + }, [headerShouldShowBg], ) } @@ -48,13 +51,14 @@ export const useHeaderMetaShouldShow = () => { return useAtomValue(headerMetaShouldShowAtom) && !v } export const useSetHeaderMetaInfo = () => { - useEffect(() => { - return () => { + useEffect( + () => () => { jotaiStore.set(headerMetaTitleAtom, '') jotaiStore.set(headerMetaDescriptionAtom, '') jotaiStore.set(headerMetaSlugAtom, '') - } - }, []) + }, + [], + ) return ({ title, description, @@ -70,13 +74,11 @@ export const useSetHeaderMetaInfo = () => { } } -export const useHeaderMetaInfo = () => { - return { - title: useAtomValue(headerMetaTitleAtom), - description: useAtomValue(headerMetaDescriptionAtom), - slug: useAtomValue(headerMetaSlugAtom), - } -} +export const useHeaderMetaInfo = () => ({ + title: useAtomValue(headerMetaTitleAtom), + description: useAtomValue(headerMetaDescriptionAtom), + slug: useAtomValue(headerMetaSlugAtom), +}) const headerHasMetaInfoAtom = atom((get) => { const title = get(headerMetaTitleAtom) @@ -84,6 +86,4 @@ const headerHasMetaInfoAtom = atom((get) => { return title !== '' && description !== '' }) -export const useHeaderHasMetaInfo = () => { - return useAtomValue(headerHasMetaInfoAtom) -} +export const useHeaderHasMetaInfo = () => useAtomValue(headerHasMetaInfoAtom) diff --git a/src/components/modules/comment/Comment.tsx b/src/components/modules/comment/Comment.tsx index e16a761fca..dc9cd497c1 100644 --- a/src/components/modules/comment/Comment.tsx +++ b/src/components/modules/comment/Comment.tsx @@ -2,6 +2,7 @@ import type { CommentModel } from '@mx-space/api-client' import clsx from 'clsx' import { m } from 'framer-motion' import { atom, useAtomValue } from 'jotai' +import type { BuiltInProviderType } from 'next-auth/providers/index' import type { PropsWithChildren } from 'react' import { createContext, @@ -14,6 +15,7 @@ import { import { createPortal } from 'react-dom' import { Avatar } from '~/components/ui/avatar' +import { BlockLinkRenderer } from '~/components/ui/markdown/renderers/LinkRenderer' import { RelativeTime } from '~/components/ui/relative-time' import { getStrategyIconComponent, @@ -32,6 +34,14 @@ export const Comment: Component<{ }> = memo(function Comment(props) { const { comment, className } = props const elAtom = useMemo(() => atom(null), []) + const isSingleLinkContent = useMemo(() => { + const trimmedContent = comment?.text + return ( + trimmedContent.startsWith('http') && + trimmedContent.split('\n').length === 1 + ) + }, [comment?.text]) + // FIXME 兜一下后端给的脏数据 if (typeof comment === 'string') return null const { @@ -50,16 +60,33 @@ export const Comment: Component<{ const authorElement = url ? ( {author} ) : ( - {author} + {author} ) + const CommentNormalContent = ( +
+ {text} + + +
+ ) return ( <> @@ -84,20 +111,27 @@ export const Comment: Component<{ className={clsx('relative my-2', className)} >
-
+
- {source && !!getStrategyIconComponent(source) && ( -
- -
- )} + {source && + !!getStrategyIconComponent(source as BuiltInProviderType) && ( +
+ +
+ )}
{/* Header */} @@ -110,12 +144,13 @@ export const Comment: Component<{ - + {authorElement} - + @@ -137,17 +172,20 @@ export const Comment: Component<{ {/* Content */} -
- {text} - -
+ {isSingleLinkContent ? ( +
+ + +
+ ) : ( + CommentNormalContent + )}
diff --git a/src/components/modules/comment/CommentBox/AuthedInput.tsx b/src/components/modules/comment/CommentBox/AuthedInput.tsx index 8274d2dc9b..018a344054 100644 --- a/src/components/modules/comment/CommentBox/AuthedInput.tsx +++ b/src/components/modules/comment/CommentBox/AuthedInput.tsx @@ -1,65 +1,67 @@ 'use client' -import { useUser } from '@clerk/nextjs' +import * as Avatar from '@radix-ui/react-avatar' import clsx from 'clsx' import { useEffect } from 'react' +import { useSessionReader } from '~/atoms/hooks/reader' +import { UserAuthFromIcon } from '~/components/layout/header/internal/UserAuthFromIcon' +import { getUserUrl } from '~/lib/authjs' + import { CommentBoxActionBar } from './ActionBar' -import { CommentBoxAuthedInputSkeleton } from './AuthedInputSkeleton' import { useSetCommentBoxValues } from './hooks' import { UniversalTextArea } from './UniversalTextArea' export const CommentBoxAuthedInput = () => { - const { user } = useUser() const setter = useSetCommentBoxValues() - const displayName = user - ? user.fullName || - `${user.firstName || ''} ${user.lastName || ''}`.trim() || - user.username || - 'Anonymous' - : '' - useEffect(() => { - if (!user) return - setter('author', displayName) - setter('avatar', user.imageUrl) - setter('mail', user.primaryEmailAddress?.emailAddress || '') + const reader = useSessionReader() - for (const account of user.externalAccounts) { - if (account.provider === 'github') { - account.username && - setter('url', `https://github.com/${account.username}`) - break - } + const displayName = reader ? reader.name || 'Anonymous' : '' - if (account.provider === 'twitter') { - account.username && setter('url', `https://x.com/${account.username}`) - break - } - } + useEffect(() => { + if (!reader) return + setter('author', displayName) + setter('avatar', reader.image) + setter('mail', reader.email) - const strategy = user.primaryEmailAddress?.verification.strategy + reader.handle && + setter( + 'url', + getUserUrl({ + provider: reader.provider, + handle: reader.handle, + }) || '', + ) + setter('source', reader.provider) + }, [displayName, reader, setter]) - strategy && setter('source', strategy) - }, [displayName, setter, user]) + if (!reader) return null - if (!user) return return (
- {`${displayName}'s + + + + + +
diff --git a/src/components/modules/comment/CommentBox/CommentBoxLegacyForm.tsx b/src/components/modules/comment/CommentBox/CommentBoxLegacyForm.tsx index 1fedbb3976..8b4df20ced 100644 --- a/src/components/modules/comment/CommentBox/CommentBoxLegacyForm.tsx +++ b/src/components/modules/comment/CommentBox/CommentBoxLegacyForm.tsx @@ -3,6 +3,7 @@ import { useAtom } from 'jotai' import Image from 'next/image' import { useIsLogged } from '~/atoms/hooks' +import { UserAuthFromIcon } from '~/components/layout/header/internal/UserAuthFromIcon' import { Form, FormInput as FInput } from '~/components/ui/form' import { useAggregationSelector } from '~/providers/root/aggregation-data-provider' @@ -56,25 +57,20 @@ const FormInput = (props: { fieldKey: FormKey; required?: boolean }) => { /> ) } -const FormWithUserInfo = () => { - return ( -
-
- - - -
-
- -
+const FormWithUserInfo = () => ( + +
+ + + +
+
+ +
- - - ) -} + + +) const LoggedForm = () => { const user = useAggregationSelector((v) => v.user)! @@ -83,9 +79,9 @@ const LoggedForm = () => {
{ width={48} height={48} /> +
diff --git a/src/components/modules/comment/CommentBox/Root.tsx b/src/components/modules/comment/CommentBox/Root.tsx index b794518047..1a7bacd479 100644 --- a/src/components/modules/comment/CommentBox/Root.tsx +++ b/src/components/modules/comment/CommentBox/Root.tsx @@ -1,9 +1,9 @@ 'use client' -import { SignedIn, SignedOut } from '@clerk/nextjs' import { useEffect } from 'react' import { useIsLogged } from '~/atoms/hooks' +import { useSessionReader } from '~/atoms/hooks/reader' import { ErrorBoundary } from '~/components/common/ErrorBoundary' import { AutoResizeHeight } from '~/components/modules/shared/AutoResizeHeight' import { clsxm } from '~/lib/helper' @@ -22,9 +22,14 @@ export const CommentBoxRoot: Component = (props) => { const mode = useCommentMode() const isLogged = useIsLogged() + + const sessionReader = useSessionReader() + useEffect(() => { - if (isLogged) setCommentMode(CommentBoxMode['legacy']) - }, [isLogged]) + if (sessionReader) { + setCommentMode(CommentBoxMode['with-auth']) + } + }, [sessionReader]) return ( @@ -40,7 +45,9 @@ export const CommentBoxRoot: Component = (props) => {
- {mode === CommentBoxMode.legacy ? ( + {isLogged ? ( + + ) : mode === CommentBoxMode.legacy ? ( ) : ( @@ -52,24 +59,22 @@ export const CommentBoxRoot: Component = (props) => { ) } -const CommentBoxLegacy = () => { - return ( - - - - ) -} +const CommentBoxLegacy = () => ( + + + +) const CommentBoxWithAuth = () => { + const isReaderLogin = !!useSessionReader() + return ( - + {!isReaderLogin ? ( - - - + ) : ( - + )} ) } diff --git a/src/components/modules/comment/CommentBox/SignedOutContent.tsx b/src/components/modules/comment/CommentBox/SignedOutContent.tsx index 212be28ba9..712f09b4ac 100644 --- a/src/components/modules/comment/CommentBox/SignedOutContent.tsx +++ b/src/components/modules/comment/CommentBox/SignedOutContent.tsx @@ -1,22 +1,22 @@ 'use client' -import { SignInButton } from '@clerk/nextjs' -import { usePathname } from 'next/navigation' - -import { UserArrowLeftIcon } from '~/components/icons/user-arrow-left' +import { useSessionReader } from '~/atoms/hooks/reader' import { StyledButton } from '~/components/ui/button' -import { useModalStack } from '~/components/ui/modal' -import { urlBuilder } from '~/lib/url-builder' +import { AuthProvidersRender } from '~/queries/hooks/authjs' import { CommentBoxMode, setCommentMode } from './hooks' export function CommentBoxSignedOutContent() { - const pathname = usePathname() - const { dismissAll } = useModalStack() + const isReaderLogin = !!useSessionReader() + if (isReaderLogin) return null return ( -
+
+

使用社交账号登录

+ + { @@ -25,21 +25,6 @@ export function CommentBoxSignedOutContent() { > 免登录评论 - - { - dismissAll() - }} - variant="primary" - type="button" - > - - 登录后才可以留言噢 - -
) } diff --git a/src/components/modules/comment/CommentBox/SwitchCommentMode.tsx b/src/components/modules/comment/CommentBox/SwitchCommentMode.tsx index 5a3512f576..b92f47a52f 100644 --- a/src/components/modules/comment/CommentBox/SwitchCommentMode.tsx +++ b/src/components/modules/comment/CommentBox/SwitchCommentMode.tsx @@ -1,11 +1,9 @@ 'use client' -import { useUser } from '@clerk/nextjs' import clsx from 'clsx' -import type { FC } from 'react' -import { useRef } from 'react' import { useIsLogged } from '~/atoms/hooks' +import { useSessionReader } from '~/atoms/hooks/reader' import { MotionButtonBase } from '~/components/ui/button' import { FloatPopover } from '~/components/ui/float-popover' @@ -16,29 +14,34 @@ import { useCommentMode, } from './hooks' +const copyMap = { + [CommentBoxMode.legacy]: '新版评论', + [CommentBoxMode['with-auth']]: '旧版评论', +} +const SwitchCommentModeButton = () => { + const mode = useCommentMode() + const copy = `转换到${copyMap[mode]}` + return ( + <> + + {copy} + + ) +} export const SwitchCommentMode = () => { const mode = useCommentMode() - const copy = `转换到${mode === CommentBoxMode.legacy ? '新' : '旧'}版评论` - const hasText = useCommentBoxHasText() + const copy = `转换到${copyMap[mode]}` - const notLogged = !!useUser() - - const TriggerComponent = useRef(function SwitchCommentModeButton() { - const mode = useCommentMode() + const hasText = useCommentBoxHasText() - return ( - <> - - {copy} - - ) - }).current + // TODO + const notLogged = !useSessionReader() const isOwnerLogged = useIsLogged() if (isOwnerLogged) return null @@ -48,7 +51,7 @@ export const SwitchCommentMode = () => { 'absolute left-0 top-0 z-10 rounded-full text-sm', 'size-6 border border-slate-200 dark:border-neutral-800', 'bg-slate-100 dark:bg-neutral-900', - 'flex cursor-pointer center', + 'center flex cursor-pointer', 'text-base-content/50', 'opacity-0 transition-opacity duration-200 group-[:hover]:opacity-100', mode === CommentBoxMode['legacy'] && 'bottom-0 top-auto', @@ -65,7 +68,9 @@ export const SwitchCommentMode = () => { ) }} > - {copy} + + {copy} + ) } diff --git a/src/components/modules/comment/CommentBox/UniversalTextArea.tsx b/src/components/modules/comment/CommentBox/UniversalTextArea.tsx index 24a6987248..ca83da8db8 100644 --- a/src/components/modules/comment/CommentBox/UniversalTextArea.tsx +++ b/src/components/modules/comment/CommentBox/UniversalTextArea.tsx @@ -37,9 +37,9 @@ export const UniversalTextArea: Component = ({ className }) => { const start = $ta.selectionStart const end = $ta.selectionEnd - $ta.value = `${$ta.value.substring( + $ta.value = `${$ta.value.slice( 0, - start, + Math.max(0, start), )} ${emoji} ${$ta.value.substring(end, $ta.value.length)}` setter('text', $ta.value) @@ -62,8 +62,8 @@ export const UniversalTextArea: Component = ({ className }) => { if ($ta) { const start = $ta.selectionStart const end = $ta.selectionEnd - const textBefore = $ta.value.substring(0, start) - const textAfter = $ta.value.substring(end) + const textBefore = $ta.value.slice(0, Math.max(0, start)) + const textAfter = $ta.value.slice(Math.max(0, end)) $ta.value = `${textBefore}\n\n${textAfter}` setter('text', $ta.value) @@ -120,33 +120,28 @@ export const UniversalTextArea: Component = ({ className }) => { }} > - <> - {!isMobile && ( - - - - )} - + {!isMobile && ( + + + 表情 +
+ } + headless + popoverWrapperClassNames="z-[999]" + popoverClassNames="pointer-events-auto" + > + + + )} ) } - -const EmojiButton = () => { - return ( -
- - 表情 -
- ) -} diff --git a/src/components/modules/comment/CommentBox/hooks.tsx b/src/components/modules/comment/CommentBox/hooks.tsx index aeb0ce2da4..ba5154d5b0 100644 --- a/src/components/modules/comment/CommentBox/hooks.tsx +++ b/src/components/modules/comment/CommentBox/hooks.tsx @@ -12,7 +12,7 @@ import { produce } from 'immer' import type { ExtractAtomValue } from 'jotai' import { atom, useAtomValue } from 'jotai' import { atomWithStorage, selectAtom } from 'jotai/utils' -import type React from 'react' +import type { PropsWithChildren } from 'react' import { useCallback, useContext } from 'react' import { useIsLogged } from '~/atoms/hooks' @@ -48,18 +48,16 @@ export const useCommentBoxTextValue = () => export const useCommentBoxRefIdValue = () => useAtomValue(useContext(CommentBoxContext).refId) -export const useGetCommentBoxAtomValues = () => { - return useContext(CommentBoxContext) -} +export const useGetCommentBoxAtomValues = () => useContext(CommentBoxContext) export const useCommentBoxLifeCycle = () => useContext(CommentBoxLifeCycleContext) // ReactNode 导致 tsx 无法推断,过于复杂 -const commentActionLeftSlotAtom = atom(null as React.JSX.Element | null) +const commentActionLeftSlotAtom = atom(null as PropsWithChildren['children']) export const useCommentActionLeftSlot = () => useAtomValue(commentActionLeftSlotAtom) -export const setCommentActionLeftSlot = (slot: React.JSX.Element | null) => +export const setCommentActionLeftSlot = (slot: PropsWithChildren['children']) => jotaiStore.set(commentActionLeftSlotAtom, slot) export const useCommentBoxHasText = () => diff --git a/src/components/modules/comment/CommentBox/providers.tsx b/src/components/modules/comment/CommentBox/providers.tsx index c290a111b5..817295c590 100644 --- a/src/components/modules/comment/CommentBox/providers.tsx +++ b/src/components/modules/comment/CommentBox/providers.tsx @@ -94,22 +94,18 @@ export const CommentIsReplyProvider = ( ) } -export const CommentBoxSlotPortal = memo( - (props: { children: React.JSX.Element }) => { - const { children } = props - useEffect(() => { - setCommentActionLeftSlot(children) - return () => { - setCommentActionLeftSlot(null) - } - }, [children]) - return null - }, -) - -export const CommentBoxSlotProvider: FC = memo(() => { - return useCommentActionLeftSlot() +export const CommentBoxSlotPortal = memo((props: PropsWithChildren) => { + const { children } = props + useEffect(() => { + setCommentActionLeftSlot(children) + return () => { + setCommentActionLeftSlot(null) + } + }, [children]) + return null }) +export const CommentBoxSlotProvider: FC = memo(() => useCommentActionLeftSlot()) + CommentBoxSlotProvider.displayName = 'CommentBoxSlotProvider' CommentBoxSlotPortal.displayName = 'CommentBoxSlotPortal' diff --git a/src/components/modules/comment/CommentMarkdown.tsx b/src/components/modules/comment/CommentMarkdown.tsx index e43309e075..c04a7b85bc 100644 --- a/src/components/modules/comment/CommentMarkdown.tsx +++ b/src/components/modules/comment/CommentMarkdown.tsx @@ -14,13 +14,11 @@ const disabledTypes = [ export const CommentMarkdown: FC<{ children: string -}> = ({ children }) => { - return ( - - ) -} +}> = ({ children }) => ( + +) diff --git a/src/components/modules/comment/CommentPinButton.tsx b/src/components/modules/comment/CommentPinButton.tsx index c550447043..8380278b81 100644 --- a/src/components/modules/comment/CommentPinButton.tsx +++ b/src/components/modules/comment/CommentPinButton.tsx @@ -21,8 +21,8 @@ export const CommentPinButton = ({ comment }: { comment: CommentModel }) => { onPinChange={async (nextPin) => { queryClient.setQueryData>>( buildCommentsQueryKey(refId), - (old) => { - return produce(old, (draft) => { + (old) => + produce(old, (draft) => { if (!draft) return draft let draftComment: Draft = null draft.pages.forEach((page) => @@ -34,8 +34,7 @@ export const CommentPinButton = ({ comment }: { comment: CommentModel }) => { if (!draftComment) return draft ;(draftComment as any as CommentModel).pin = nextPin return draft - }) - }, + }), ) await apiClient.comment.proxy(comment.id).patch({ data: { diff --git a/src/components/modules/comment/CommentReplyButton.tsx b/src/components/modules/comment/CommentReplyButton.tsx index 82f4f396fd..04bb4ca1a1 100644 --- a/src/components/modules/comment/CommentReplyButton.tsx +++ b/src/components/modules/comment/CommentReplyButton.tsx @@ -11,7 +11,8 @@ import { CommentIsReplyProvider } from './CommentBox/providers' export const CommentReplyButton: FC<{ commentId: string -}> = ({ commentId }) => { + className?: string +}> = ({ commentId, className }) => { const [replyFormOpen, setReplyFormOpen] = useState(false) const originalRefId = useCommentBoxRefIdValue() const onReplyCompleted = useCallback(() => { @@ -24,10 +25,11 @@ export const CommentReplyButton: FC<{ className={clsx( 'absolute bottom-0 right-0 translate-x-2/3 translate-y-1/4 text-xs', 'aspect-square rounded-full', - 'box-content flex size-6 p-[2px] center', + 'center box-content flex size-6 p-[2px]', 'border border-slate-200 bg-zinc-100 dark:border-neutral-700 dark:bg-gray-800', 'invisible cursor-pointer opacity-0', 'group-[:hover]:visible group-[:hover]:opacity-70', + className, )} onClick={() => { setReplyFormOpen((o) => !o) diff --git a/src/components/modules/comment/CommentRootLazy.tsx b/src/components/modules/comment/CommentRootLazy.tsx index b343e360c2..bf327cdf9a 100644 --- a/src/components/modules/comment/CommentRootLazy.tsx +++ b/src/components/modules/comment/CommentRootLazy.tsx @@ -4,10 +4,8 @@ import { ErrorBoundary } from '~/components/common/ErrorBoundary' import { CommentAreaRoot } from './CommentRoot' -export const CommentAreaRootLazy: typeof CommentAreaRoot = (props) => { - return ( - - - - ) -} +export const CommentAreaRootLazy: typeof CommentAreaRoot = (props) => ( + + + +) diff --git a/src/components/modules/comment/CommentSkeleton.tsx b/src/components/modules/comment/CommentSkeleton.tsx index 3493feb3d2..1046f5951e 100644 --- a/src/components/modules/comment/CommentSkeleton.tsx +++ b/src/components/modules/comment/CommentSkeleton.tsx @@ -1,5 +1,5 @@ const CommentSkeletonSingle = ( -
  • +
  • @@ -27,14 +27,12 @@ const CommentSkeletonSingle = (
  • ) -export const CommentSkeleton: Component = () => { - return ( -
    - {CommentSkeletonSingle} - {CommentSkeletonSingle} - {CommentSkeletonSingle} - {CommentSkeletonSingle} - {CommentSkeletonSingle} -
    - ) -} +export const CommentSkeleton: Component = () => ( +
    + {CommentSkeletonSingle} + {CommentSkeletonSingle} + {CommentSkeletonSingle} + {CommentSkeletonSingle} + {CommentSkeletonSingle} +
    +) diff --git a/src/components/modules/comment/Comments.tsx b/src/components/modules/comment/Comments.tsx index 7ff59d60f7..fd9177ba37 100644 --- a/src/components/modules/comment/Comments.tsx +++ b/src/components/modules/comment/Comments.tsx @@ -75,28 +75,24 @@ export const Comments: FC = ({ refId }) => { } if (!data || data.pages.length === 0 || data.pages[0].data.length === 0) return ( -
    +
    ) return (
      - {data?.pages.map((data, index) => { - return ( - - {data.data.map((comment) => { - return ( - - ) - })} - - ) - })} + {data?.pages.map((data, index) => ( + + {data.data.map((comment) => ( + + ))} + + ))}
    {hasNextPage && ( diff --git a/src/components/ui/dropdown-menu/index.tsx b/src/components/ui/dropdown-menu/index.tsx new file mode 100644 index 0000000000..cdcb023448 --- /dev/null +++ b/src/components/ui/dropdown-menu/index.tsx @@ -0,0 +1,145 @@ +'use client' +import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu' +import * as React from 'react' + +import { clsxm as cn } from '~/lib/helper' + +const DropdownMenu = DropdownMenuPrimitive.Root + +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger + +const DropdownMenuGroup = DropdownMenuPrimitive.Group + +const DropdownMenuPortal = DropdownMenuPrimitive.Portal + +const DropdownMenuSub = DropdownMenuPrimitive.Sub + +const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup + +const DropdownMenuSubTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, children, ...props }, ref) => ( + + {children} + + +)) +DropdownMenuSubTrigger.displayName = + DropdownMenuPrimitive.SubTrigger.displayName + +const DropdownMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)) +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName + +const DropdownMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + icon?: React.ReactNode + } +>(({ className, inset, ...props }, ref) => ( + + {props.icon && ( + + {props.icon} + + )} + {props.children} + {/* Justify Fill */} + {props.icon && } + +)) +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName + +const DropdownMenuLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + +)) +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName + +const DropdownMenuSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName + +const DropdownMenuShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => ( + +) +DropdownMenuShortcut.displayName = 'DropdownMenuShortcut' + +export { + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuPortal, + DropdownMenuRadioGroup, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuSub, + DropdownMenuSubTrigger, + DropdownMenuTrigger, +} diff --git a/src/components/ui/markdown/renderers/LinkRenderer.tsx b/src/components/ui/markdown/renderers/LinkRenderer.tsx index e7ea79f08c..b64f579a03 100644 --- a/src/components/ui/markdown/renderers/LinkRenderer.tsx +++ b/src/components/ui/markdown/renderers/LinkRenderer.tsx @@ -1,8 +1,11 @@ import dynamic from 'next/dynamic' +import type React from 'react' import type { FC, PropsWithChildren, ReactNode } from 'react' -import React, { useMemo } from 'react' +import { Suspense, useMemo } from 'react' +import { ClientOnly } from '~/components/common/ClientOnly' import { GitHubBrandIcon } from '~/components/icons/platform/GitHubBrandIcon' +import { BlockLoading } from '~/components/modules/shared/BlockLoading' import { getTweetId, isBilibiliVideoUrl, @@ -39,7 +42,8 @@ const Tweet = dynamic(() => import('~/components/modules/shared/Tweet'), { export const BlockLinkRenderer = ({ href, children, -}: PropsWithChildren<{ href: string }>) => { + fallback, +}: PropsWithChildren<{ href: string; fallback?: ReactNode }>) => { const url = useMemo(() => { try { return new URL(href) @@ -49,12 +53,13 @@ export const BlockLinkRenderer = ({ }, [href]) const fallbackElement = useMemo( - () => ( -

    - {children ?? {href}} -

    - ), - [children, href], + () => + fallback ?? ( +

    + {children ?? {href}} +

    + ), + [children, fallback, href], ) const tmdbEnabled = useFeatureEnabled('tmdb') @@ -73,10 +78,15 @@ export const BlockLinkRenderer = ({ /> ) } + case isTweetUrl(url): { const id = getTweetId(url) - return + return ( + + + + ) } case isYoutubeUrl(url): { @@ -118,6 +128,7 @@ export const BlockLinkRenderer = ({ /> ) } + case isTMDBUrl(url): { if (tmdbEnabled) return ( @@ -145,15 +156,23 @@ export const BlockLinkRenderer = ({ const { id } = parseBilibiliVideoUrl(url) return ( -
    +
    -