mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2026-05-07 06:43:29 -04:00
[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:
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'
|
||||
|
||||
110
interface/app/onboarding/login.tsx
Normal file
110
interface/app/onboarding/login.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
46
interface/components/LoginButton.tsx
Normal file
46
interface/components/LoginButton.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user