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