diff --git a/apps/renderer/src/modules/app-layout/feed-column/index.mobile.tsx b/apps/renderer/src/modules/app-layout/feed-column/index.mobile.tsx index 0e3a2529b..43db8498b 100644 --- a/apps/renderer/src/modules/app-layout/feed-column/index.mobile.tsx +++ b/apps/renderer/src/modules/app-layout/feed-column/index.mobile.tsx @@ -1,9 +1,8 @@ import { RootPortal } from "@follow/components/ui/portal/index.js" +import { PresentSheet } from "@follow/components/ui/sheet/Sheet.js" import { Outlet } from "react-router" import { useLoginModalShow, useWhoami } from "~/atoms/user" -import { PlainModal } from "~/components/ui/modal/stacked/custom-modal" -import { DeclarativeModal } from "~/components/ui/modal/stacked/declarative-modal" import { useDailyTask } from "~/hooks/biz/useDailyTask" import { LoginModalContent } from "~/modules/auth/LoginModalContent" @@ -20,17 +19,13 @@ export const MobileRootLayout = () => { {isAuthFail && !user && ( - - - + hideHeader + content={} + /> )} diff --git a/apps/renderer/src/modules/auth/LoginModalContent.tsx b/apps/renderer/src/modules/auth/LoginModalContent.tsx index 541ddc9df..7c30b673f 100644 --- a/apps/renderer/src/modules/auth/LoginModalContent.tsx +++ b/apps/renderer/src/modules/auth/LoginModalContent.tsx @@ -1,23 +1,41 @@ -import { FollowIcon } from "@follow/components/icons/follow.jsx" +import { useMobile } from "@follow/components/hooks/useMobile.js" +import { Logo } from "@follow/components/icons/logo.js" +import { AutoResizeHeight } from "@follow/components/ui/auto-resize-height/index.js" import { MotionButtonBase } from "@follow/components/ui/button/index.js" import { LoadingCircle } from "@follow/components/ui/loading/index.jsx" import { authProvidersConfig } from "@follow/constants" import type { LoginRuntime } from "@follow/shared/auth" import { loginHandler } from "@follow/shared/auth" -import { stopPropagation } from "@follow/utils/dom" import clsx from "clsx" import { AnimatePresence, m } from "framer-motion" -import { useEffect, useRef, useState } from "react" +import type { FC } from "react" +import { Fragment, useEffect, useMemo, useRef, useState } from "react" import { useTranslation } from "react-i18next" -import { modalMontionConfig } from "~/components/ui/modal/stacked/constants" import { useCurrentModal } from "~/components/ui/modal/stacked/hooks" +import type { AuthProvider } from "~/queries/users" import { useAuthProviders } from "~/queries/users" interface LoginModalContentProps { runtime?: LoginRuntime canClose?: boolean } + +const defaultProviders = { + google: { + id: "google", + name: "Google", + color: "#3b82f6", + icon: '', + }, + github: { + id: "github", + name: "GitHub", + color: "#000000", + icon: '', + }, +} + export const LoginModalContent = (props: LoginModalContentProps) => { const modal = useCurrentModal() @@ -48,45 +66,78 @@ export const LoginModalContent = (props: LoginModalContentProps) => { }, []) const disabled = !!loadingLockSet + + const filteredDefaultProviders = useMemo(() => { + if (!authProviders) return Object.values(defaultProviders) + return Object.entries(defaultProviders) + .filter(([key]) => authProviders[key]) + .map(([_, provider]) => provider) + }, [authProviders]) + + const extraProviders = useMemo(() => { + if (!authProviders) return [] + return Object.entries(authProviders) + .filter(([key]) => !defaultProviders[key]) + .map(([_, provider]) => provider) + }, [authProviders]) + + const isMobile = useMobile() + + const Inner = ( + <> +
+ +
+
+ + {t("signin.sign_in_to")} {APP_NAME} + +
+ +
+ {filteredDefaultProviders.map((provider) => ( + { + loginHandler(provider.id, runtime) + setLoadingLockSet(provider.id) + window.analytics?.capture("login", { + type: provider.id, + }) + }} + > + + {" "} + {t("signin.continue_with", { provider: provider.name })} + + + ))} + + +
+ + ) + if (isMobile) { + return Inner + } + return (
-
- {t("signin.sign_in_to")} - - - {APP_NAME} - -
-
- {Object.entries(authProviders || []).map(([key, provider]) => ( - { - loginHandler(key, runtime) - setLoadingLockSet(key) - window.analytics?.capture("login", { - type: key, - }) - }} - > - - {" "} - {t("signin.continue_with", { provider: provider.name })} - - - ))} +
+ {Inner}
@@ -133,3 +184,50 @@ const LoginButtonContent = (props: { children: React.ReactNode; isLoading: boole ) } + +export const AuthProvidersRender: FC<{ + providers: AuthProvider[] + runtime?: LoginRuntime +}> = ({ providers, runtime }) => { + const [authProcessingLockSet, setAuthProcessingLockSet] = useState(() => new Set()) + return ( + + {providers.length > 0 && ( +
    + {providers.map((provider) => ( +
  • + { + if (authProcessingLockSet.has(provider.id)) return + loginHandler(provider.id, runtime) + + setAuthProcessingLockSet((prev) => { + prev.add(provider.id) + return new Set(prev) + }) + }} + > +
    + {!authProcessingLockSet.has(provider.id) ? ( + + + + ) : ( +
    + +
    + )} +
    +
    +
  • + ))} +
+ )} +
+ ) +} diff --git a/apps/renderer/src/queries/users.ts b/apps/renderer/src/queries/users.ts index fd1408349..243a8e96e 100644 --- a/apps/renderer/src/queries/users.ts +++ b/apps/renderer/src/queries/users.ts @@ -14,19 +14,15 @@ export const users = { }), } +export interface AuthProvider { + name: string + id: string + color: string + icon: string +} export const useAuthProviders = () => { return useQuery({ queryKey: ["providers"], - queryFn: async () => (await getProviders()).data, - placeholderData: { - google: { - id: "google", - name: "Google", - }, - github: { - id: "github", - name: "GitHub", - }, - }, + queryFn: async () => (await getProviders()).data as Record, }) } diff --git a/packages/components/src/ui/sheet/Sheet.tsx b/packages/components/src/ui/sheet/Sheet.tsx index cc0b0659e..e30ea2a82 100644 --- a/packages/components/src/ui/sheet/Sheet.tsx +++ b/packages/components/src/ui/sheet/Sheet.tsx @@ -13,6 +13,7 @@ export interface PresentSheetProps { open?: boolean onOpenChange?: (value: boolean) => void title?: ReactNode + hideHeader?: boolean zIndex?: number dismissible?: boolean defaultOpen?: boolean @@ -35,6 +36,7 @@ export const PresentSheet = forwardRef + {title} ) : ( diff --git a/packages/shared/src/hono.ts b/packages/shared/src/hono.ts index a8ef01ca3..661393250 100644 --- a/packages/shared/src/hono.ts +++ b/packages/shared/src/hono.ts @@ -24,12 +24,7 @@ declare const authPlugins: ({ method: "GET"; }> | undefined)?]>(...ctx: C): Promise; + }] ? Response : any>; path: "/get-providers"; options: { method: "GET"; @@ -4735,10 +4730,10 @@ declare const user: drizzle_orm_pg_core.PgTableWithColumns<{ emailVerified: drizzle_orm_pg_core.PgColumn<{ name: "emailVerified"; tableName: "user"; - dataType: "date"; - columnType: "PgTimestamp"; - data: Date; - driverParam: string; + dataType: "boolean"; + columnType: "PgBoolean"; + data: boolean; + driverParam: boolean; notNull: false; hasDefault: false; isPrimaryKey: false; @@ -4878,10 +4873,10 @@ declare const users: drizzle_orm_pg_core.PgTableWithColumns<{ emailVerified: drizzle_orm_pg_core.PgColumn<{ name: "emailVerified"; tableName: "user"; - dataType: "date"; - columnType: "PgTimestamp"; - data: Date; - driverParam: string; + dataType: "boolean"; + columnType: "PgBoolean"; + data: boolean; + driverParam: boolean; notNull: false; hasDefault: false; isPrimaryKey: false; @@ -4968,7 +4963,7 @@ declare const usersOpenApiSchema: z.ZodObject; email: z.ZodString; - emailVerified: z.ZodNullable; + emailVerified: z.ZodNullable; image: z.ZodNullable; handle: z.ZodNullable; createdAt: z.ZodDate; @@ -4976,7 +4971,7 @@ declare const usersOpenApiSchema: z.ZodObject, z.UnknownKeysParam, z.ZodTypeAny, { name: string | null; id: string; - emailVerified: string | null; + emailVerified: boolean | null; image: string | null; handle: string | null; createdAt: Date; @@ -4984,7 +4979,7 @@ declare const usersOpenApiSchema: z.ZodObject; + disableRefresh: zod.ZodOptional; }, "strip", zod.ZodTypeAny, { disableCookieCache?: boolean | undefined; + disableRefresh?: boolean | undefined; }, { disableCookieCache?: boolean | undefined; + disableRefresh?: boolean | undefined; }>>; requireHeaders: true; metadata: { @@ -6599,12 +6597,7 @@ declare const auth: { method: "GET"; }> | undefined)?]>(...ctx: C): Promise; + }] ? Response : any>; path: "/get-providers"; options: { method: "GET"; @@ -6722,6 +6715,28 @@ declare const auth: { }>]>(...ctx: C): Promise; @@ -7989,20 +8004,20 @@ declare const auth: { ; - image: zod.ZodOptional; - }, zod.UnknownKeysParam, zod.ZodTypeAny, { - name?: string | undefined; - image?: string | undefined; - }, { - name?: string | undefined; - image?: string | undefined; - }> & zod.ZodObject<{ handle: zod.ZodString; }, zod.UnknownKeysParam, zod.ZodTypeAny, { handle: string; }, { handle: string; + }> & zod.ZodObject<{ + name: zod.ZodOptional; + image: zod.ZodOptional; + }, zod.UnknownKeysParam, zod.ZodTypeAny, { + name?: string | undefined; + image?: string | null | undefined; + }, { + name?: string | undefined; + image?: string | null | undefined; }>; use: better_call.Endpoint; - image: zod.ZodOptional; - }, zod.UnknownKeysParam, zod.ZodTypeAny, { - name?: string | undefined; - image?: string | undefined; - }, { - name?: string | undefined; - image?: string | undefined; - }> & zod.ZodObject<{ handle: zod.ZodString; }, zod.UnknownKeysParam, zod.ZodTypeAny, { handle: string; }, { handle: string; + }> & zod.ZodObject<{ + name: zod.ZodOptional; + image: zod.ZodOptional; + }, zod.UnknownKeysParam, zod.ZodTypeAny, { + name?: string | undefined; + image?: string | null | undefined; + }, { + name?: string | undefined; + image?: string | null | undefined; }>; use: better_call.Endpoint; use: better_call.Endpoint & { @@ -8210,19 +8218,15 @@ declare const auth: { }; }; }; - }>]>(...ctx: C): Promise | undefined)?]>(...ctx: C): Promise; + }] ? Response : { + success: boolean; + message: string; + }>; path: "/delete-user"; options: { method: "POST"; - body: zod.ZodObject<{ - password: zod.ZodString; - }, "strip", zod.ZodTypeAny, { - password: string; - }, { - password: string; - }>; use: better_call.Endpoint & { @@ -9071,6 +9075,36 @@ declare const auth: { method: better_call.Method | better_call.Method[]; headers: Headers; }; + deleteUserCallback: { + ; + }>]>(...ctx: C): Promise; + path: "/delete-user/callback"; + options: { + method: "GET"; + query: zod.ZodObject<{ + token: zod.ZodString; + }, "strip", zod.ZodTypeAny, { + token: string; + }, { + token: string; + }>; + }; + method: better_call.Method | better_call.Method[]; + headers: Headers; + }; }; options: { appName: string; @@ -9116,7 +9150,6 @@ declare const auth: { }): Promise; options: better_auth_adapters_drizzle.DrizzleAdapterConfig; }; - baseUrl: string | undefined; basePath: string; trustedOrigins: string[]; user: { @@ -9126,25 +9159,29 @@ declare const auth: { }; }; }; - accounts: { + account: { accountLinking: { - enabled: boolean; - trustedProviders: string[]; + enabled: true; + trustedProviders: ("github" | "apple" | "google")[]; }; }; socialProviders: { google: { clientId: string; clientSecret: string; + redirectURI: string; }; github: { clientId: string; clientSecret: string; + redirectURI: string; }; apple: { + enabled: boolean; clientId: string; clientSecret: string; - } | undefined; + redirectURI: string; + }; }; plugins: ({ id: "custom-session"; @@ -9207,12 +9244,7 @@ declare const auth: { method: "GET"; }> | undefined)?]>(...ctx: C): Promise; + }] ? Response : any>; path: "/get-providers"; options: { method: "GET"; @@ -9726,7 +9758,7 @@ declare const _routes: hono_hono_base.HonoBase