[ENG-1178] Add optional auth step to onboarding & update profile section (#1426)

* Add login step to onboarding

* only show auth loader on first fetch

---------

Co-authored-by: Brendan Allan <brendonovich@outlook.com>
This commit is contained in:
nikec
2023-10-04 11:05:50 +02:00
committed by GitHub
parent e33a4cd999
commit ddf3995032
5 changed files with 190 additions and 68 deletions

View File

@@ -1,44 +1,7 @@
import { useQueryClient } from '@tanstack/react-query';
import { useRef, useState } from 'react';
import { useBridgeMutation, useBridgeQuery, useBridgeSubscription } from '@sd/client';
import { Button, Card } from '@sd/ui';
import { usePlatform } from '~/util/Platform';
type State = { status: 'Idle' } | { status: 'LoggingIn' };
export function LoginButton({ onLogin }: { onLogin: () => void }) {
const [state, setState] = useState<State>({ status: 'Idle' });
const platform = usePlatform();
const ret = useRef(null);
useBridgeSubscription(['auth.loginSession'], {
enabled: state.status === 'LoggingIn',
onData(data) {
if (data === 'Complete') {
onLogin();
platform.auth.finish?.(ret.current);
} else if (data === 'Error') setState({ status: 'Idle' });
else {
ret.current = platform.auth.start(data.Start.verification_url_complete);
}
},
onError() {
setState({ status: 'Idle' });
}
});
return (
<Button
variant="accent"
disabled={state.status !== 'Idle'}
onClick={() => setState({ status: 'LoggingIn' })}
>
{state.status === 'Idle' ? 'Login' : 'Logging In...'}
</Button>
);
}
import { useBridgeMutation, useBridgeQuery } from '@sd/client';
import { Button, Card, Loader } from '@sd/ui';
import { LoginButton } from '~/components/LoginButton';
export function SpacedriveAccount() {
const user = useBridgeQuery(['auth.me'], {
@@ -51,36 +14,34 @@ export function SpacedriveAccount() {
const queryClient = useQueryClient();
return (
<Card className="px-5">
<div className="my-2 flex w-full flex-col">
<div className="flex flex-row items-center justify-between">
<span className="font-semibold">Spacedrive Account</span>
{user.isFetchedAfterMount ? (
user.data ? (
<Button
variant="outline"
onClick={async () => {
await logout.mutateAsync(undefined);
// this sucks but oh well :)
queryClient.setQueryData(['auth.me'], null);
}}
disabled={logout.isLoading}
>
Logout
</Button>
) : (
<LoginButton onLogin={user.refetch} />
)
<Card className="relative overflow-hidden px-5">
{!user.data && (
<div className="absolute inset-0 z-50 flex items-center justify-center bg-app/75 backdrop-blur-lg">
{!user.isFetchedAfterMount ? (
<Loader />
) : (
'Loading...'
<LoginButton onLogin={user.refetch} />
)}
</div>
<hr className="mb-4 mt-2 flex w-full border-app-line" />
{user.data ? (
<>Loggged in as email {user.data.email}</>
) : (
"Login to Spacedrive bc it's cool idk"
)}
)}
<div className="my-2 flex w-full flex-col">
<div className="flex items-center justify-between">
<span className="font-semibold">Spacedrive Account</span>
<Button
variant="gray"
onClick={async () => {
await logout.mutateAsync(undefined);
// this sucks but oh well :)
queryClient.setQueryData(['auth.me'], null);
}}
disabled={logout.isLoading || !user.data}
>
Logout
</Button>
</div>
<hr className="mb-4 mt-2 w-full border-app-line" />
<span>Logged in as {user.data?.email}</span>
</div>
</Card>
);

View File

@@ -40,7 +40,7 @@ export default function OnboardingAlpha() {
<Discord className="h-4 w-4 fill-ink" />
Join Discord
</Button>
<ButtonLink to="../new-library" replace variant="accent">
<ButtonLink to="../login" replace variant="accent">
Continue
</ButtonLink>
</div>

View File

@@ -4,6 +4,7 @@ import { getOnboardingStore } from '@sd/client';
import Alpha from './alpha';
import { useOnboardingContext } from './context';
import CreatingLibrary from './creating-library';
import Login from './login';
import NewLibrary from './new-library';
import Privacy from './privacy';
@@ -23,6 +24,10 @@ export default [
element: <Index />
},
{ path: 'alpha', element: <Alpha /> },
{
element: <Login />,
path: 'login'
},
{
element: <NewLibrary />,
path: 'new-library'

View File

@@ -0,0 +1,110 @@
import { AppLogo } from '@sd/assets/images';
import { useQueryClient } from '@tanstack/react-query';
import { useNavigate } from 'react-router';
import { useBridgeMutation, useBridgeQuery } from '@sd/client';
import { Button, ButtonLink, Loader } from '@sd/ui';
import { LoginButton } from '~/components/LoginButton';
import { OnboardingContainer } from './Layout';
export default function OnboardingLogin() {
const navigate = useNavigate();
const queryClient = useQueryClient();
const user = useBridgeQuery(['auth.me'], {
// If the backend returns un unauthorized error we don't want to retry
retry: false
});
const logout = useBridgeMutation(['auth.logout']);
return (
<OnboardingContainer>
{user.isLoading && !user.isFetchedAfterMount ? (
<Loader />
) : user.data ? (
<>
<div className="flex flex-col items-center justify-center">
<img
src={AppLogo}
alt="Spacedrive logo"
width={50}
height={50}
draggable={false}
className="mb-3"
/>
<h1 className="text-lg text-ink">
Logged in as <b>{user.data.email}</b>
</h1>
</div>
<div className="mt-10 flex w-[250px] flex-col gap-3">
<ButtonLink
to="../new-library"
replace
variant="accent"
size="md"
className="text-center"
>
Continue
</ButtonLink>
<div className="space-x-2 text-center text-sm">
<span>Not you?</span>
<Button
onClick={async () => {
await logout.mutateAsync(undefined);
queryClient.setQueryData(['auth.me'], null);
}}
disabled={logout.isLoading}
variant="bare"
size="md"
className="border-none !p-0 font-normal text-accent-deep hover:underline"
>
Log out
</Button>
</div>
</div>
</>
) : (
<>
<div className="flex flex-col items-center justify-center">
<img
src={AppLogo}
alt="Spacedrive logo"
width={50}
height={50}
draggable={false}
className="mb-3"
/>
<h1 className="text-lg text-ink">
Log in to <b>Spacedrive</b>
</h1>
</div>
<div className="mt-10 flex w-[250px] flex-col gap-3">
<LoginButton
size="md"
onLogin={() => navigate('../new-library', { replace: true })}
>
Log in with browser
</LoginButton>
<div className="space-x-2 text-center text-sm">
<span>Want to do this later?</span>
<ButtonLink
to="../new-library"
variant="bare"
size="md"
className="border-none !p-0 font-normal text-accent-deep hover:underline"
replace
>
Skip login
</ButtonLink>
</div>
</div>
</>
)}
</OnboardingContainer>
);
}

View File

@@ -0,0 +1,46 @@
import { useRef, useState } from 'react';
import { useBridgeSubscription } from '@sd/client';
import { Button, ButtonProps } from '@sd/ui';
import { usePlatform } from '..';
type State = { status: 'Idle' } | { status: 'LoggingIn' };
interface Props extends ButtonProps {
onLogin?(): void;
}
export function LoginButton({ children, onLogin, ...props }: Props) {
const [state, setState] = useState<State>({ status: 'Idle' });
const platform = usePlatform();
const ret = useRef(null);
useBridgeSubscription(['auth.loginSession'], {
enabled: state.status === 'LoggingIn',
onData(data) {
if (data === 'Complete') {
onLogin?.();
platform.auth.finish?.(ret.current);
} else if (data === 'Error') setState({ status: 'Idle' });
else {
ret.current = platform.auth.start(data.Start.verification_url_complete);
}
},
onError() {
setState({ status: 'Idle' });
}
});
return (
<Button
variant="accent"
disabled={state.status !== 'Idle'}
onClick={() => setState({ status: 'LoggingIn' })}
{...props}
>
{state.status === 'Idle' ? children || 'Log in' : 'Logging In...'}
</Button>
);
}