update onboarding

This commit is contained in:
Ren Amamiya 2023-09-15 08:58:09 +07:00
parent 8f8617d8f9
commit d3db6492d9
8 changed files with 105 additions and 80 deletions

View File

@ -205,13 +205,6 @@ const router = createBrowserRouter([
return { Component: OnboardStep2Screen }; return { Component: OnboardStep2Screen };
}, },
}, },
{
path: 'step-3',
async lazy() {
const { OnboardStep3Screen } = await import('@app/auth/onboarding/step-3');
return { Component: OnboardStep3Screen };
},
},
], ],
}, },
{ {
@ -235,13 +228,6 @@ const router = createBrowserRouter([
return { Component: ResetScreen }; return { Component: ResetScreen };
}, },
}, },
{
path: 'hard-reset',
async lazy() {
const { HardResetScreen } = await import('@app/auth/hardReset');
return { Component: HardResetScreen };
},
},
], ],
}, },
{ {

View File

@ -11,7 +11,7 @@ export function User({ pubkey, fallback }: { pubkey: string; fallback?: string }
if (status === 'loading') { if (status === 'loading') {
return ( return (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="relative h-11 w-11 shrink-0 animate-pulse rounded-md bg-white/10 backdrop-blur-xl" /> <div className="relative h-14 w-14 shrink-0 animate-pulse rounded-md bg-white/10 backdrop-blur-xl" />
<div className="flex w-full flex-1 flex-col items-start gap-1 text-start"> <div className="flex w-full flex-1 flex-col items-start gap-1 text-start">
<span className="h-4 w-1/2 animate-pulse rounded bg-white/10 backdrop-blur-xl" /> <span className="h-4 w-1/2 animate-pulse rounded bg-white/10 backdrop-blur-xl" />
<span className="h-3 w-1/3 animate-pulse rounded bg-white/10 backdrop-blur-xl" /> <span className="h-3 w-1/3 animate-pulse rounded bg-white/10 backdrop-blur-xl" />
@ -21,30 +21,33 @@ export function User({ pubkey, fallback }: { pubkey: string; fallback?: string }
} }
return ( return (
<div className="flex items-start gap-2.5 py-2"> <div className="flex h-full w-full flex-col gap-2.5">
<Image <Image
src={user?.picture || user?.image} src={user?.picture || user?.image}
alt={pubkey} alt={pubkey}
className="h-11 w-11 shrink-0 rounded-lg object-cover" className="h-14 w-14 shrink-0 rounded-lg object-cover"
/> />
<div className="flex w-full flex-col items-start gap-1 text-start"> <div className="flex h-full flex-col items-start justify-between">
<div className="inline-flex items-center gap-2"> <div className="flex flex-col items-start gap-1 text-start">
<p className="max-w-[15rem] truncate font-semibold leading-none text-white"> <p className="max-w-[15rem] truncate text-lg font-semibold leading-none text-white">
{user?.name || user?.display_name} {user?.name || user?.display_name}
</p> </p>
<p className="line-clamp-6 break-all text-white/70">
{user?.about || user?.bio || 'No bio'}
</p>
</div>
<div className="flex flex-col gap-2">
{user?.website ? ( {user?.website ? (
<Link <Link
to={user.website} to={user?.website}
target="_blank" target="_blank"
className="border-l border-white/10 pl-2" className="inline-flex items-center gap-2 text-sm text-white/70"
> >
<WorldIcon className="h-4 w-4 text-white/70 hover:text-white" />{' '} <WorldIcon className="h-4 w-4" />
<p className="max-w-[10rem] truncate">{user.website}</p>
</Link> </Link>
) : null} ) : null}
</div> </div>
<div className="line-clamp-4 break-all text-sm text-white/70">
{user?.about || user?.bio || 'No bio'}
</div>
</div> </div>
</div> </div>
); );

View File

@ -1,7 +0,0 @@
export function HardResetScreen() {
return (
<div>
<p>hard reset</p>
</div>
);
}

View File

@ -51,7 +51,7 @@ export function ImportStep3Screen() {
<div className="mx-auto w-full max-w-md"> <div className="mx-auto w-full max-w-md">
<div className="mb-4 pb-4"> <div className="mb-4 pb-4">
<h1 className="text-center text-2xl font-semibold text-white"> <h1 className="text-center text-2xl font-semibold text-white">
{loading ? 'Prefetching data...' : 'Your Nostr profile'} {loading ? 'Downloading...' : 'Your Nostr profile'}
</h1> </h1>
</div> </div>
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">

View File

@ -68,8 +68,8 @@ export function OnboardStep1Screen() {
}, []); }, []);
return ( return (
<div className="mx-auto w-full max-w-md"> <div className="flex h-full w-full flex-col justify-center">
<div className="mb-4 border-b border-white/10 pb-4"> <div className="mx-auto mb-4 w-full max-w-md border-b border-white/10 pb-4">
<h1 className="mb-2 text-center text-2xl font-semibold text-white"> <h1 className="mb-2 text-center text-2xl font-semibold text-white">
{loading ? 'Prefetching data...' : 'Enrich your network'} {loading ? 'Prefetching data...' : 'Enrich your network'}
</h1> </h1>
@ -79,32 +79,30 @@ export function OnboardStep1Screen() {
add them later. add them later.
</p> </p>
</div> </div>
<div className="flex flex-col gap-4"> <div className="scrollbar-hide flex w-full flex-nowrap items-center gap-4 overflow-x-auto px-4">
<div className="scrollbar-hide flex h-[450px] w-full flex-col divide-y divide-white/5 overflow-y-auto rounded-xl bg-white/20 backdrop-blur-xl"> {status === 'loading' ? (
{status === 'loading' ? ( <div className="flex h-full w-full items-center justify-center">
<div className="flex h-full w-full items-center justify-center"> <LoaderIcon className="h-4 w-4 animate-spin text-white" />
<LoaderIcon className="h-4 w-4 animate-spin text-white" /> </div>
</div> ) : (
) : ( data?.profiles.map((item: { pubkey: string; profile: { content: string } }) => (
data?.profiles.map( <button
(item: { pubkey: string; profile: { content: string } }) => ( key={item.pubkey}
<button type="button"
key={item.pubkey} onClick={() => toggleFollow(item.pubkey)}
type="button" className="relative h-[300px] shrink-0 grow-0 basis-[250px] rounded-lg border-t border-white/10 bg-white/20 px-4 py-4 hover:bg-white/30"
onClick={() => toggleFollow(item.pubkey)} >
className="relative px-4 py-2 hover:bg-white/10" <User pubkey={item.pubkey} fallback={item.profile?.content} />
> {follows.includes(item.pubkey) && (
<User pubkey={item.pubkey} fallback={item.profile?.content} /> <div className="absolute right-2 top-2">
{follows.includes(item.pubkey) && ( <CheckCircleIcon className="h-4 w-4 text-green-400" />
<div className="absolute right-2 top-2"> </div>
<CheckCircleIcon className="h-4 w-4 text-green-400" /> )}
</div> </button>
)} ))
</button> )}
) </div>
) <div className="mx-auto mt-4 w-full max-w-md">
)}
</div>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<button <button
type="button" type="button"
@ -133,7 +131,12 @@ export function OnboardStep1Screen() {
> >
Skip, you can add later Skip, you can add later
</Link> </Link>
) : null} ) : (
<span className="text-center text-sm text-white/50">
By clicking &apos;Continue&apos;, Lume will download all events related to
your follows from the last 24 hours. It may take a bit
</span>
)}
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,6 +1,6 @@
import { message } from '@tauri-apps/api/dialog'; import { message } from '@tauri-apps/api/dialog';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { Link, useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { useStorage } from '@libs/storage/provider'; import { useStorage } from '@libs/storage/provider';
@ -34,8 +34,8 @@ const data = [
export function OnboardStep2Screen() { export function OnboardStep2Screen() {
const navigate = useNavigate(); const navigate = useNavigate();
const setStep = useOnboarding((state) => state.setStep);
const [setStep, clearStep] = useOnboarding((state) => [state.setStep, state.clearStep]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [tags, setTags] = useState(new Set<string>()); const [tags, setTags] = useState(new Set<string>());
@ -53,6 +53,16 @@ export function OnboardStep2Screen() {
} }
}; };
const skip = async () => {
// update last login
await db.updateLastLogin();
// clear local storage
clearStep();
navigate('/', { replace: true });
};
const submit = async () => { const submit = async () => {
try { try {
setLoading(true); setLoading(true);
@ -61,8 +71,15 @@ export function OnboardStep2Screen() {
await db.createWidget(WidgetKinds.global.hashtag, tag, tag.replace('#', '')); await db.createWidget(WidgetKinds.global.hashtag, tag, tag.replace('#', ''));
} }
navigate('/auth/onboarding/step-3', { replace: true }); // update last login
await db.updateLastLogin();
// clear local storage
clearStep();
navigate('/', { replace: true });
} catch (e) { } catch (e) {
setLoading(false);
await message(e, { title: 'Lume', type: 'error' }); await message(e, { title: 'Lume', type: 'error' });
} }
}; };
@ -123,12 +140,13 @@ export function OnboardStep2Screen() {
)} )}
</button> </button>
{!loading ? ( {!loading ? (
<Link <button
to="/auth/onboarding/step-3" type="button"
onClick={() => skip()}
className="inline-flex h-12 w-full items-center justify-center rounded-lg border-t border-white/10 bg-white/20 font-medium leading-none text-white backdrop-blur-xl hover:bg-white/30 focus:outline-none" className="inline-flex h-12 w-full items-center justify-center rounded-lg border-t border-white/10 bg-white/20 font-medium leading-none text-white backdrop-blur-xl hover:bg-white/30 focus:outline-none"
> >
Skip, you can add later Skip, you can add later
</Link> </button>
) : null} ) : null}
</div> </div>
</div> </div>

View File

@ -4,7 +4,7 @@ import { Resolver, useForm } from 'react-hook-form';
import { Link, useNavigate } from 'react-router-dom'; import { Link, useNavigate } from 'react-router-dom';
import { Stronghold } from 'tauri-plugin-stronghold-api'; import { Stronghold } from 'tauri-plugin-stronghold-api';
import { User } from '@app/auth/components/user'; import { UserImport } from '@app/auth/components/userImport';
import { useStorage } from '@libs/storage/provider'; import { useStorage } from '@libs/storage/provider';
@ -74,20 +74,22 @@ export function UnlockScreen() {
return ( return (
<div className="flex h-full w-full items-center justify-center"> <div className="flex h-full w-full items-center justify-center">
<div className="mx-auto w-full max-w-md"> <div className="mx-auto w-full max-w-md">
<div className="mb-6 text-center"> <div className="mb-4 pb-4">
<h1 className="text-2xl font-semibold text-white">Enter password to unlock</h1> <h1 className="text-center text-2xl font-semibold text-white">
Enter password to unlock
</h1>
</div> </div>
<form onSubmit={handleSubmit(onSubmit)} className="mb-0 flex flex-col"> <form onSubmit={handleSubmit(onSubmit)} className="mb-0 flex flex-col">
<div className="flex flex-col rounded-lg bg-white/5"> <div className="flex flex-col rounded-lg bg-white/5">
<div className="w-full rounded-t-lg border-b border-white/10 bg-white/5 p-4"> <div className="w-full rounded-t-lg border-b border-white/10 bg-white/5 p-4">
<User pubkey={db.account.pubkey} /> <UserImport pubkey={db.account.pubkey} />
</div> </div>
<div className="relative"> <div className="relative">
<input <input
{...register('password', { required: true, minLength: 4 })} {...register('password', { required: true, minLength: 4 })}
type={showPassword ? 'text' : 'password'} type={showPassword ? 'text' : 'password'}
placeholder="Password" placeholder="Password"
className="relative h-12 w-full rounded-b-lg bg-white/10 py-1 text-center text-white !outline-none backdrop-blur-xl placeholder:text-white/50" className="relative h-12 w-full rounded-b-lg bg-white/10 py-1 text-center tracking-widest text-white !outline-none backdrop-blur-xl placeholder:text-white/50"
/> />
<button <button
type="button" type="button"
@ -109,12 +111,12 @@ export function UnlockScreen() {
<button <button
type="submit" type="submit"
disabled={!isDirty || !isValid} disabled={!isDirty || !isValid}
className="inline-flex h-11 w-full items-center justify-between gap-2 rounded-lg bg-fuchsia-500 px-6 font-medium leading-none text-white hover:bg-fuchsia-600 focus:outline-none disabled:opacity-50" className="inline-flex h-12 w-full items-center justify-between gap-2 rounded-lg bg-fuchsia-500 px-6 font-medium leading-none text-white hover:bg-fuchsia-600 focus:outline-none disabled:opacity-50"
> >
{loading ? ( {loading ? (
<> <>
<span className="w-5" /> <span className="w-5" />
<span>Decryting...</span> <span>Unlocking...</span>
<LoaderIcon className="h-5 w-5 animate-spin text-white" /> <LoaderIcon className="h-5 w-5 animate-spin text-white" />
</> </>
) : ( ) : (
@ -127,7 +129,7 @@ export function UnlockScreen() {
</button> </button>
<Link <Link
to="/auth/reset" to="/auth/reset"
className="mt-1 inline-flex h-11 w-full items-center justify-center rounded-lg text-center text-white/50 hover:bg-white/10" className="mt-1 inline-flex h-12 w-full items-center justify-center rounded-lg text-center text-white/70 hover:bg-white/20"
> >
Reset password Reset password
</Link> </Link>

View File

@ -57,6 +57,24 @@ export function useNostr() {
const follows = new Set<string>(preFollows || []); const follows = new Set<string>(preFollows || []);
const lruNetwork = new LRUCache<string, string, void>({ max: 300 }); const lruNetwork = new LRUCache<string, string, void>({ max: 300 });
// fetch user's relays
const relayEvents = await ndk.fetchEvents({
kinds: [NDKKind.RelayList],
authors: [db.account.pubkey],
});
if (relayEvents) {
const latestRelayEvent = [...relayEvents].sort(
(a, b) => b.created_at - a.created_at
)[0];
if (latestRelayEvent) {
for (const item of latestRelayEvent.tags) {
await db.createRelay(item[1], item[2]);
}
}
}
// fetch user's follows // fetch user's follows
if (!preFollows) { if (!preFollows) {
const user = ndk.getUser({ hexpubkey: db.account.pubkey }); const user = ndk.getUser({ hexpubkey: db.account.pubkey });
@ -67,20 +85,22 @@ export function useNostr() {
} }
// build user's network // build user's network
const events = await ndk.fetchEvents({ const followEvents = await ndk.fetchEvents({
kinds: [3], kinds: [NDKKind.Contacts],
authors: [...follows], authors: [...follows],
limit: 300, limit: 300,
}); });
events.forEach((event: NDKEvent) => { followEvents.forEach((event: NDKEvent) => {
event.tags.forEach((tag) => { event.tags.forEach((tag) => {
if (tag[0] === 'p') lruNetwork.set(tag[1], tag[1]); if (tag[0] === 'p') lruNetwork.set(tag[1], tag[1]);
}); });
}); });
// get lru values
const network = [...lruNetwork.values()] as string[]; const network = [...lruNetwork.values()] as string[];
// update db
await db.updateAccount('follows', [...follows]); await db.updateAccount('follows', [...follows]);
await db.updateAccount('network', [...new Set([...follows, ...network])]); await db.updateAccount('network', [...new Set([...follows, ...network])]);