From 6691cb2367e59cbbd3d0176c10fa3f57865ee39d Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Wed, 1 Jan 2025 13:21:45 +0800 Subject: [PATCH] feat: unlink account --- apps/mobile/package.json | 4 +- .../modules/profile/account-management.tsx | 80 ++++++---- locales/settings/en.json | 3 + packages/shared/package.json | 2 +- packages/shared/src/auth.ts | 1 + packages/shared/src/hono.ts | 143 +++++++++++++++++- pnpm-lock.yaml | 56 +++---- 7 files changed, 229 insertions(+), 60 deletions(-) diff --git a/apps/mobile/package.json b/apps/mobile/package.json index 357f14b05..3474a1c4d 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -14,7 +14,7 @@ "web": "expo start --web" }, "dependencies": { - "@better-auth/expo": "1.0.23-beta.2", + "@better-auth/expo": "1.1.8", "@expo/vector-icons": "^14.0.2", "@follow/components": "workspace:*", "@follow/constants": "workspace:*", @@ -30,7 +30,7 @@ "@react-navigation/native": "^7.0.0", "@shopify/flash-list": "1.7.1", "@tanstack/react-query": "5.62.3", - "better-auth": "1.1.3", + "better-auth": "1.1.8", "cookie-es": "^1.2.2", "dayjs": "1.11.13", "es-toolkit": "1.29.0", diff --git a/apps/renderer/src/modules/profile/account-management.tsx b/apps/renderer/src/modules/profile/account-management.tsx index e1933e11e..438e14642 100644 --- a/apps/renderer/src/modules/profile/account-management.tsx +++ b/apps/renderer/src/modules/profile/account-management.tsx @@ -1,47 +1,73 @@ import { Button } from "@follow/components/ui/button/index.js" import { authProvidersConfig } from "@follow/constants" -import { linkSocial } from "@follow/shared/auth" +import { linkSocial, unlinkAccount } from "@follow/shared/auth" import { IN_ELECTRON } from "@follow/shared/constants" import { cn } from "@follow/utils/utils" +import { useMutation } from "@tanstack/react-query" import { useTranslation } from "react-i18next" +import { toast } from "sonner" -import { useSocialAccounts } from "~/queries/auth" +import { auth, useSocialAccounts } from "~/queries/auth" import { useAuthProviders } from "~/queries/users" +function AuthProviderButton({ provider }: { provider: string }) { + const { t } = useTranslation("settings") + const { data: accounts } = useSocialAccounts() + const unlinkAccountMutation = useMutation({ + mutationFn: async () => { + const res = await unlinkAccount({ providerId: provider }) + if (res.error) throw new Error(res.error.message) + }, + onSuccess: () => { + toast.success(t("profile.link_social.unlink.success")) + auth.getAccounts().invalidate() + }, + onError: (error) => { + toast.error(error.message) + }, + }) + const account = accounts?.find((account) => account.provider === provider) + if (!account && IN_ELECTRON) return null + + return ( + + ) +} + export function AccountManagement() { const { t } = useTranslation("settings") - const { data: accounts } = useSocialAccounts() + const { data: providers } = useAuthProviders() return (
-

Auth Providers

+

{t("profile.link_social.auth_provider")}

{Object.keys(providers || {}) .filter((provider) => provider !== "credential") - .map((provider) => { - const account = accounts?.find((account) => account.provider === provider) - if (!account && IN_ELECTRON) return null - return ( - - ) - })} + .map((provider) => ( + + ))}

{t("profile.link_social.description")}

diff --git a/locales/settings/en.json b/locales/settings/en.json index 3bbbc4603..00d371b0d 100644 --- a/locales/settings/en.json +++ b/locales/settings/en.json @@ -278,7 +278,10 @@ "profile.email.verified": "Verified", "profile.handle.description": "Your unique identifier.", "profile.handle.label": "Handle", + "profile.link_social.auth_provider": "Auth Provider", "profile.link_social.description": "You can currently only connect social accounts with the same email.", + "profile.link_social.link": "Link", + "profile.link_social.unlink.success": "Social account unlinked.", "profile.name.description": "Your public display name.", "profile.name.label": "Display Name", "profile.new_password.label": "New Password", diff --git a/packages/shared/package.json b/packages/shared/package.json index 500269ae1..227641303 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -34,7 +34,7 @@ "@electron-toolkit/tsconfig": "^1.0.1", "@hono/node-server": "1.13.7", "@t3-oss/env-core": "^0.11.1", - "better-auth": "1.1.3", + "better-auth": "1.1.8", "drizzle-orm": "0.37.0", "hono": "4.6.13", "react": "^18.3.1", diff --git a/packages/shared/src/auth.ts b/packages/shared/src/auth.ts index 5c34753a9..bb8d23564 100644 --- a/packages/shared/src/auth.ts +++ b/packages/shared/src/auth.ts @@ -51,6 +51,7 @@ export const { signIn, signOut, signUp, + unlinkAccount, updateUser, } = authClient diff --git a/packages/shared/src/hono.ts b/packages/shared/src/hono.ts index 047b9a387..c7ece5908 100644 --- a/packages/shared/src/hono.ts +++ b/packages/shared/src/hono.ts @@ -6843,9 +6843,18 @@ declare const auth: { }>]>(...ctx: C): Promise; path: "/sign-up/email"; options: { @@ -7476,6 +7503,9 @@ declare const auth: { }>]>(...ctx: C): Promise; path: "/sign-in/email"; options: { @@ -7732,6 +7759,7 @@ declare const auth: { token: string; callbackURL?: string | undefined; }>; + use: better_call.Endpoint, better_call.EndpointOptions>[]; metadata: { openapi: { description: string; @@ -7762,6 +7790,18 @@ declare const auth: { asResponse: true; }] ? Response : void | { status: boolean; + user: { + id: any; + email: any; + name: any; + image: any; + emailVerified: any; + createdAt: any; + updatedAt: any; + }; + } | { + status: boolean; + user: null; }>; path: "/verify-email"; options: { @@ -7776,6 +7816,7 @@ declare const auth: { token: string; callbackURL?: string | undefined; }>; + use: better_call.Endpoint, better_call.EndpointOptions>[]; metadata: { openapi: { description: string; @@ -8148,6 +8189,15 @@ declare const auth: { asResponse: true; }] ? Response : { token: string | null; + user: { + id: string; + email: string; + name: string; + image: string | null | undefined; + emailVerified: boolean; + createdAt: Date; + updatedAt: Date; + }; }>; path: "/change-password"; options: { @@ -8603,6 +8653,7 @@ declare const auth: { }, { callbackURL: string; }>; + use: better_call.Endpoint, better_call.EndpointOptions>[]; metadata: { openapi: { description: string; @@ -8638,6 +8689,7 @@ declare const auth: { }, { callbackURL: string; }>; + use: better_call.Endpoint, better_call.EndpointOptions>[]; metadata: { openapi: { description: string; @@ -9338,6 +9390,10 @@ declare const auth: { }] ? Response : { id: string; provider: string; + createdAt: Date; + updatedAt: Date; + accountId: string; + scopes: string[]; }[]>; path: "/list-accounts"; options: { @@ -9410,6 +9466,7 @@ declare const auth: { token: string; callbackURL?: string | undefined; }>; + use: better_call.Endpoint, better_call.EndpointOptions>[]; }>]>(...ctx: C): Promise; + use: better_call.Endpoint, better_call.EndpointOptions>[]; + }; + method: better_call.Method | better_call.Method[]; + headers: Headers; + }; + unlinkAccount: { + ; + use: better_call.Endpoint & { + id: string; + createdAt: Date; + updatedAt: Date; + userId: string; + expiresAt: Date; + token: string; + ipAddress?: string | null | undefined; + userAgent?: string | null | undefined; + }; + user: Record & { + id: string; + email: string; + emailVerified: boolean; + name: string; + createdAt: Date; + updatedAt: Date; + image?: string | null | undefined; + }; + }; + }>, better_call.EndpointOptions>[]; + }>]>(...ctx: C): Promise; + path: "/unlink-account"; + options: { + method: "POST"; + body: zod.ZodObject<{ + providerId: zod.ZodString; + }, "strip", zod.ZodTypeAny, { + providerId: string; + }, { + providerId: string; + }>; + use: better_call.Endpoint & { + id: string; + createdAt: Date; + updatedAt: Date; + userId: string; + expiresAt: Date; + token: string; + ipAddress?: string | null | undefined; + userAgent?: string | null | undefined; + }; + user: Record & { + id: string; + email: string; + emailVerified: boolean; + name: string; + createdAt: Date; + updatedAt: Date; + image?: string | null | undefined; + }; + }; + }>, better_call.EndpointOptions>[]; }; method: better_call.Method | better_call.Method[]; headers: Headers; @@ -9878,6 +10011,8 @@ declare const auth: { EMAIL_CAN_NOT_BE_UPDATED: string; CREDENTIAL_ACCOUNT_NOT_FOUND: string; SESSION_EXPIRED: string; + FAILED_TO_UNLINK_LAST_ACCOUNT: string; + ACCOUNT_NOT_FOUND: string; }; }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f2722f17c..9544ad507 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -395,8 +395,8 @@ importers: apps/mobile: dependencies: '@better-auth/expo': - specifier: 1.0.23-beta.2 - version: 1.0.23-beta.2(better-auth@1.1.3(encoding@0.1.13)) + specifier: 1.1.8 + version: 1.1.8(better-auth@1.1.8(encoding@0.1.13)) '@expo/vector-icons': specifier: ^14.0.2 version: 14.0.4 @@ -443,8 +443,8 @@ importers: specifier: 5.62.3 version: 5.62.3(react@18.3.1) better-auth: - specifier: 1.1.3 - version: 1.1.3(encoding@0.1.13) + specifier: 1.1.8 + version: 1.1.8(encoding@0.1.13) cookie-es: specifier: ^1.2.2 version: 1.2.2 @@ -1361,8 +1361,8 @@ importers: specifier: ^0.11.1 version: 0.11.1(typescript@5.7.2)(zod@3.23.8) better-auth: - specifier: 1.1.3 - version: 1.1.3(encoding@0.1.13) + specifier: 1.1.8 + version: 1.1.8(encoding@0.1.13) drizzle-orm: specifier: 0.37.0 version: 0.37.0(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.14)(expo-sqlite@15.0.3(qc23ies2iywwyrf5w5kjvn5iki))(kysely@0.27.5)(react@18.3.1) @@ -2191,13 +2191,13 @@ packages: resolution: {integrity: sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==} engines: {node: '>=6.9.0'} - '@better-auth/expo@1.0.23-beta.2': - resolution: {integrity: sha512-K0gxAAwwB8aubVhuKYBk60xG6qmzg6ZCHirdCrzG30fGIrBsuTSg4f/BAWb6OaSqmM+ujWe74ZpUi/XXDgRKXw==} + '@better-auth/expo@1.1.8': + resolution: {integrity: sha512-7sHPYrliz/l4fAPveOdlI8UEEE2To3U0qc6mzMQCovMiIdy6Nh/SEktPULmRk3gbFytAceEce8BI3ZR773a0AA==} peerDependencies: - better-auth: 1.0.23-beta.2 + better-auth: 1.1.8 - '@better-auth/utils@0.2.2': - resolution: {integrity: sha512-YlOin3pX3sc8VPRdzCOWqz6uHKitwxm/k8p6/2I1JYI0hgMWmEqqismyvEjtOKoiSTnu1huwSq+yzcN7ehu59w==} + '@better-auth/utils@0.2.3': + resolution: {integrity: sha512-Ap1GaSmo6JYhJhxJOpUB0HobkKPTNzfta+bLV89HfpyCAHN7p8ntCrmNFHNAVD0F6v0mywFVEUg1FUhNCc81Rw==} '@better-fetch/fetch@1.1.12': resolution: {integrity: sha512-B3bfloI/2UBQWIATRN6qmlORrvx3Mp0kkNjmXLv0b+DtbtR+pP4/I5kQA/rDUv+OReLywCCldf6co4LdDmh8JA==} @@ -6500,14 +6500,14 @@ packages: before-after-hook@2.2.3: resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==} - better-auth@1.1.3: - resolution: {integrity: sha512-8zUOd5zGAQLBJXupDiwAUzreLf2Msu1HVtkRWQk09hvHURZZIiuH8g48WIRXraNNoeqIA+bW70uap5Fxnj8d1w==} + better-auth@1.1.8: + resolution: {integrity: sha512-8E9SH8PglrbMu2b5ue9c9/fQvvycIJJ+Z6+STtGJAAMljXtDWb4KUVbb/VNIAiGa0khMExgC3d3VSMreTsDB8Q==} better-call@0.2.15: resolution: {integrity: sha512-IVT8Mraf+eu6hyCJNMyFVMWhvsmNyHjM4K2h7pZg1LEwZUkTpsfjIghjTq6YiGaooMUmTLK1C9eCEYMM2obW9w==} - better-call@0.3.3-beta.4: - resolution: {integrity: sha512-txigUTFsjOlE4dfkfhDhu6Gk0I1Xo3S4Nye+nfMKJXknb+WsymcWtqoRUSUGdhOMumM3LT9R3QttTUxwny9lWQ==} + better-call@0.3.3: + resolution: {integrity: sha512-N4lDVm0NGmFfDJ0XMQ4O83Zm/3dPlvIQdxvwvgSLSkjFX5PM4GUYSVAuxNzXN27QZMHDkrJTWUqxBrm4tPC3eA==} better-opn@3.0.2: resolution: {integrity: sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==} @@ -13876,7 +13876,7 @@ packages: '@microsoft/api-extractor': ^7.36.0 '@swc/core': ^1 postcss: ^8.4.12 - typescript: 5.6.3 + typescript: '>=4.5.0' peerDependenciesMeta: '@microsoft/api-extractor': optional: true @@ -14822,6 +14822,9 @@ packages: zod@3.23.8: resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} + zod@3.24.1: + resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==} + zustand@5.0.2: resolution: {integrity: sha512-8qNdnJVJlHlrKXi50LDqqUNmUbuBjoKLrYQBnoChIbVph7vni+sY+YpvdjXG9YLd/Bxr6scMcR+rm5H3aSqPaw==} engines: {node: '>=12.20.0'} @@ -15801,13 +15804,13 @@ snapshots: '@babel/helper-string-parser': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 - '@better-auth/expo@1.0.23-beta.2(better-auth@1.1.3(encoding@0.1.13))': + '@better-auth/expo@1.1.8(better-auth@1.1.8(encoding@0.1.13))': dependencies: - better-auth: 1.1.3(encoding@0.1.13) + better-auth: 1.1.8(encoding@0.1.13) better-call: 0.2.15 zod: 3.23.8 - '@better-auth/utils@0.2.2': + '@better-auth/utils@0.2.3': dependencies: uncrypto: 0.1.3 @@ -21375,20 +21378,20 @@ snapshots: before-after-hook@2.2.3: {} - better-auth@1.1.3(encoding@0.1.13): + better-auth@1.1.8(encoding@0.1.13): dependencies: - '@better-auth/utils': 0.2.2 + '@better-auth/utils': 0.2.3 '@better-fetch/fetch': 1.1.12 '@noble/ciphers': 0.6.0 '@noble/hashes': 1.6.1 '@simplewebauthn/browser': 13.0.0 '@simplewebauthn/server': 13.0.0(encoding@0.1.13) - better-call: 0.3.3-beta.4 + better-call: 0.3.3 defu: 6.1.4 jose: 5.9.6 kysely: 0.27.5 uncrypto: 0.1.3 - zod: 3.23.8 + zod: 3.24.1 transitivePeerDependencies: - encoding @@ -21400,13 +21403,12 @@ snapshots: uncrypto: 0.1.3 zod: 3.23.8 - better-call@0.3.3-beta.4: + better-call@0.3.3: dependencies: '@better-fetch/fetch': 1.1.12 rou3: 0.5.1 - set-cookie-parser: 2.7.1 uncrypto: 0.1.3 - zod: 3.23.8 + zod: 3.24.1 better-opn@3.0.2: dependencies: @@ -31279,6 +31281,8 @@ snapshots: zod@3.23.8: {} + zod@3.24.1: {} + zustand@5.0.2(@types/react@18.3.14)(immer@10.1.1(patch_hash=og7mbnoo5vh43tjlw5rbmrdbvu))(react@18.3.1)(use-sync-external-store@1.4.0(react@18.3.1)): optionalDependencies: '@types/react': 18.3.14