diff --git a/apps/core/src/atoms/event.ts b/apps/core/src/atoms/event.ts new file mode 100644 index 0000000000..68f1db9609 --- /dev/null +++ b/apps/core/src/atoms/event.ts @@ -0,0 +1,25 @@ +import { atom, useAtom } from 'jotai'; +import { useCallback } from 'react'; + +export type OnceSignedInEvent = () => void; + +export const onceSignedInEventsAtom = atom([]); + +export const setOnceSignedInEventAtom = atom( + null, + (get, set, event: OnceSignedInEvent) => { + set(onceSignedInEventsAtom, [...get(onceSignedInEventsAtom), event]); + } +); + +export const useOnceSignedInEvents = () => { + const [events, setEvents] = useAtom(onceSignedInEventsAtom); + return useCallback(async () => { + try { + await Promise.all(events.map(event => event())); + } catch (err) { + console.error('Error executing one of the events:', err); + } + setEvents([]); + }, [events, setEvents]); +}; diff --git a/apps/core/src/atoms/index.ts b/apps/core/src/atoms/index.ts index 9a4ef22831..b2f2b86910 100644 --- a/apps/core/src/atoms/index.ts +++ b/apps/core/src/atoms/index.ts @@ -27,8 +27,6 @@ export type AuthAtom = { state: AuthProps['state']; email?: string; emailType?: AuthProps['emailType']; - // Only used for sign in page callback, after called, it will be set to undefined - onceSignedIn?: () => void; }; export const authAtom = atom({ diff --git a/apps/core/src/components/affine/enable-affine-cloud-modal/index.tsx b/apps/core/src/components/affine/enable-affine-cloud-modal/index.tsx index 8c8f1b8595..4f81ad6d2d 100644 --- a/apps/core/src/components/affine/enable-affine-cloud-modal/index.tsx +++ b/apps/core/src/components/affine/enable-affine-cloud-modal/index.tsx @@ -2,7 +2,12 @@ import { Modal, ModalWrapper } from '@affine/component'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { CloseIcon } from '@blocksuite/icons'; import { Button, IconButton } from '@toeverything/components/button'; +import { useSetAtom } from 'jotai'; +import { useCallback } from 'react'; +import { authAtom } from '../../../atoms'; +import { setOnceSignedInEventAtom } from '../../../atoms/event'; +import { useCurrentLoginStatus } from '../../../hooks/affine/use-current-login-status'; import { ButtonContainer, Content, Header, StyleTips, Title } from './style'; interface EnableAffineCloudModalProps { @@ -12,11 +17,31 @@ interface EnableAffineCloudModalProps { } export const EnableAffineCloudModal = ({ - onConfirm, + onConfirm: propsOnConfirm, open, onClose, }: EnableAffineCloudModalProps) => { const t = useAFFiNEI18N(); + const loginStatus = useCurrentLoginStatus(); + const setAuthAtom = useSetAtom(authAtom); + const setOnceSignedInEvent = useSetAtom(setOnceSignedInEventAtom); + + const confirm = useCallback(async () => { + return propsOnConfirm(); + }, [propsOnConfirm]); + + const onConfirm = useCallback(() => { + if (loginStatus === 'unauthenticated') { + setAuthAtom(prev => ({ + ...prev, + openModal: true, + })); + setOnceSignedInEvent(confirm); + } + if (loginStatus === 'authenticated') { + return propsOnConfirm(); + } + }, [confirm, loginStatus, propsOnConfirm, setAuthAtom, setOnceSignedInEvent]); return ( @@ -42,7 +67,9 @@ export const EnableAffineCloudModal = ({ block onClick={onConfirm} > - {t['Sign in and Enable']()} + {loginStatus === 'authenticated' + ? t['Enable']() + : t['Sign in and Enable']()} diff --git a/apps/core/src/hooks/root/use-on-transform-workspace.ts b/apps/core/src/hooks/root/use-on-transform-workspace.ts index 5ec1a5bce0..6810cc5294 100644 --- a/apps/core/src/hooks/root/use-on-transform-workspace.ts +++ b/apps/core/src/hooks/root/use-on-transform-workspace.ts @@ -1,6 +1,8 @@ +import { pushNotificationAtom } from '@affine/component/notification-center'; import type { WorkspaceRegistry } from '@affine/env/workspace'; import type { WorkspaceFlavour } from '@affine/env/workspace'; import { WorkspaceSubPath } from '@affine/env/workspace'; +import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { rootWorkspacesMetadataAtom, workspaceAdaptersAtom, @@ -14,11 +16,14 @@ import { openSettingModalAtom } from '../../atoms'; import { useNavigateHelper } from '../use-navigate-helper'; export function useOnTransformWorkspace() { + const t = useAFFiNEI18N(); const setSettingModal = useSetAtom(openSettingModalAtom); const WorkspaceAdapters = useAtomValue(workspaceAdaptersAtom); const setMetadata = useSetAtom(rootWorkspacesMetadataAtom); const { openPage } = useNavigateHelper(); const currentPageId = useAtomValue(currentPageIdAtom); + const pushNotification = useSetAtom(pushNotificationAtom); + return useCallback( async ( from: From, @@ -55,8 +60,20 @@ export function useOnTransformWorkspace() { }) ); openPage(newId, currentPageId ?? WorkspaceSubPath.ALL); + pushNotification({ + title: t['Successfully enabled AFFiNE Cloud'](), + type: 'success', + }); }, - [WorkspaceAdapters, setMetadata, setSettingModal, openPage, currentPageId] + [ + WorkspaceAdapters, + setMetadata, + setSettingModal, + openPage, + currentPageId, + pushNotification, + t, + ] ); } diff --git a/apps/core/src/pages/invite.tsx b/apps/core/src/pages/invite.tsx index 9322b80059..59e3223a25 100644 --- a/apps/core/src/pages/invite.tsx +++ b/apps/core/src/pages/invite.tsx @@ -6,11 +6,12 @@ import { getInviteInfoQuery, } from '@affine/graphql'; import { fetcher } from '@affine/workspace/affine/gql'; -import { useAtom } from 'jotai'; +import { useSetAtom } from 'jotai'; import { useCallback, useEffect } from 'react'; import { type LoaderFunction, redirect, useLoaderData } from 'react-router-dom'; import { authAtom } from '../atoms'; +import { setOnceSignedInEventAtom } from '../atoms/event'; import { useCurrentLoginStatus } from '../hooks/affine/use-current-login-status'; import { RouteLogic, useNavigateHelper } from '../hooks/use-navigate-helper'; import { useAppHelper } from '../hooks/use-workspaces'; @@ -51,16 +52,14 @@ export const Component = () => { const { addCloudWorkspace } = useAppHelper(); const { jumpToSubPath } = useNavigateHelper(); - const [, setAuthAtom] = useAtom(authAtom); + const setOnceSignedInEvent = useSetAtom(setOnceSignedInEventAtom); + + const setAuthAtom = useSetAtom(authAtom); const { inviteInfo } = useLoaderData() as { inviteId: string; inviteInfo: GetInviteInfoQuery['getInviteInfo']; }; - const loadWorkspaceAfterSignIn = useCallback(() => { - addCloudWorkspace(inviteInfo.workspace.id); - }, [addCloudWorkspace, inviteInfo.workspace.id]); - const openWorkspace = useCallback(() => { addCloudWorkspace(inviteInfo.workspace.id); jumpToSubPath( @@ -73,10 +72,7 @@ export const Component = () => { useEffect(() => { if (loginStatus === 'unauthenticated') { // We can not pass function to navigate state, so we need to save it in atom - setAuthAtom(prev => ({ - ...prev, - onceSignedIn: loadWorkspaceAfterSignIn, - })); + setOnceSignedInEvent(openWorkspace); jumpToSignIn(RouteLogic.REPLACE, { state: { callbackURL: `/workspace/${inviteInfo.workspace.id}/all`, @@ -86,9 +82,10 @@ export const Component = () => { }, [ inviteInfo.workspace.id, jumpToSignIn, - loadWorkspaceAfterSignIn, loginStatus, + openWorkspace, setAuthAtom, + setOnceSignedInEvent, ]); if (loginStatus === 'authenticated') { diff --git a/apps/core/src/pages/sign-in.tsx b/apps/core/src/pages/sign-in.tsx index 38a2107ab1..b759891822 100644 --- a/apps/core/src/pages/sign-in.tsx +++ b/apps/core/src/pages/sign-in.tsx @@ -7,6 +7,7 @@ import { useLocation, useNavigate } from 'react-router-dom'; import { authAtom } from '../atoms'; import { AuthPanel } from '../components/affine/auth'; import { useCurrentLoginStatus } from '../hooks/affine/use-current-login-status'; +import { RouteLogic, useNavigateHelper } from '../hooks/use-navigate-helper'; interface LocationState { state?: { @@ -14,36 +15,28 @@ interface LocationState { }; } export const Component = () => { - const [ - { state, email = '', emailType = 'changePassword', onceSignedIn }, - setAuthAtom, - ] = useAtom(authAtom); + const [{ state, email = '', emailType = 'changePassword' }, setAuthAtom] = + useAtom(authAtom); const loginStatus = useCurrentLoginStatus(); const location = useLocation() as LocationState; const navigate = useNavigate(); + const { jumpToIndex } = useNavigateHelper(); useEffect(() => { - const afterSignedIn = async () => { - if (loginStatus === 'authenticated') { - if (onceSignedIn) { - await onceSignedIn(); - setAuthAtom(prev => ({ ...prev, onceSignedIn: undefined })); - } - if (location.state?.callbackURL) { - navigate(location.state.callbackURL, { - replace: true, - }); - } + if (loginStatus === 'authenticated') { + if (location.state?.callbackURL) { + navigate(location.state.callbackURL, { + replace: true, + }); + } else { + jumpToIndex(RouteLogic.REPLACE); } - }; - afterSignedIn().catch(err => { - console.error(err); - }); + } }, [ + jumpToIndex, location.state?.callbackURL, loginStatus, navigate, - onceSignedIn, setAuthAtom, ]); diff --git a/apps/core/src/providers/modal-provider.tsx b/apps/core/src/providers/modal-provider.tsx index 3d3c9a09a6..ed1108d65d 100644 --- a/apps/core/src/providers/modal-provider.tsx +++ b/apps/core/src/providers/modal-provider.tsx @@ -101,6 +101,7 @@ export const AuthModal = (): ReactElement => { { openModal, state, email = '', emailType = 'changePassword' }, setAuthAtom, ] = useAtom(authAtom); + return ( { const session = useSession(); const prevSession = useRef>(); const pushNotification = useSetAtom(pushNotificationAtom); const refreshMetadata = useSetAtom(refreshRootMetadataAtom); + const onceSignedInEvents = useOnceSignedInEvents(); const t = useAFFiNEI18N(); if (prevSession.current !== session && session.status !== 'loading') { @@ -23,7 +26,9 @@ const SessionReporter = () => { session.status === 'authenticated' ) { startTransition(() => { - refreshMetadata(); + onceSignedInEvents().then(() => { + refreshMetadata(); + }); }); pushNotification({ title: t['com.affine.auth.has.signed'](), diff --git a/packages/i18n/src/resources/en.json b/packages/i18n/src/resources/en.json index 2498654142..757aa81874 100644 --- a/packages/i18n/src/resources/en.json +++ b/packages/i18n/src/resources/en.json @@ -569,5 +569,6 @@ "Workspace Settings": "Workspace Settings", "Workspace Settings with name": "{{name}}'s Settings", "Workspace Type": "Workspace Type", - "You cannot delete the last workspace": "You cannot delete the last workspace" + "You cannot delete the last workspace": "You cannot delete the last workspace", + "Successfully enabled AFFiNE Cloud": "Successfully enabled AFFiNE Cloud" }