mirror of
https://github.com/lumehq/lume.git
synced 2025-04-08 11:58:22 +02:00
update onboarding
This commit is contained in:
parent
8f8617d8f9
commit
d3db6492d9
14
src/app.tsx
14
src/app.tsx
@ -205,13 +205,6 @@ const router = createBrowserRouter([
|
||||
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 };
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'hard-reset',
|
||||
async lazy() {
|
||||
const { HardResetScreen } = await import('@app/auth/hardReset');
|
||||
return { Component: HardResetScreen };
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@ -11,7 +11,7 @@ export function User({ pubkey, fallback }: { pubkey: string; fallback?: string }
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
<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">
|
||||
<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" />
|
||||
@ -21,30 +21,33 @@ export function User({ pubkey, fallback }: { pubkey: string; fallback?: string }
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-start gap-2.5 py-2">
|
||||
<div className="flex h-full w-full flex-col gap-2.5">
|
||||
<Image
|
||||
src={user?.picture || user?.image}
|
||||
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="inline-flex items-center gap-2">
|
||||
<p className="max-w-[15rem] truncate font-semibold leading-none text-white">
|
||||
<div className="flex h-full flex-col items-start justify-between">
|
||||
<div className="flex flex-col items-start gap-1 text-start">
|
||||
<p className="max-w-[15rem] truncate text-lg font-semibold leading-none text-white">
|
||||
{user?.name || user?.display_name}
|
||||
</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 ? (
|
||||
<Link
|
||||
to={user.website}
|
||||
to={user?.website}
|
||||
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>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="line-clamp-4 break-all text-sm text-white/70">
|
||||
{user?.about || user?.bio || 'No bio'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,7 +0,0 @@
|
||||
export function HardResetScreen() {
|
||||
return (
|
||||
<div>
|
||||
<p>hard reset</p>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -51,7 +51,7 @@ export function ImportStep3Screen() {
|
||||
<div className="mx-auto w-full max-w-md">
|
||||
<div className="mb-4 pb-4">
|
||||
<h1 className="text-center text-2xl font-semibold text-white">
|
||||
{loading ? 'Prefetching data...' : 'Your Nostr profile'}
|
||||
{loading ? 'Downloading...' : 'Your Nostr profile'}
|
||||
</h1>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
|
@ -68,8 +68,8 @@ export function OnboardStep1Screen() {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-md">
|
||||
<div className="mb-4 border-b border-white/10 pb-4">
|
||||
<div className="flex h-full w-full flex-col justify-center">
|
||||
<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">
|
||||
{loading ? 'Prefetching data...' : 'Enrich your network'}
|
||||
</h1>
|
||||
@ -79,32 +79,30 @@ export function OnboardStep1Screen() {
|
||||
add them later.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-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' ? (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<LoaderIcon className="h-4 w-4 animate-spin text-white" />
|
||||
</div>
|
||||
) : (
|
||||
data?.profiles.map(
|
||||
(item: { pubkey: string; profile: { content: string } }) => (
|
||||
<button
|
||||
key={item.pubkey}
|
||||
type="button"
|
||||
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) && (
|
||||
<div className="absolute right-2 top-2">
|
||||
<CheckCircleIcon className="h-4 w-4 text-green-400" />
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
<div className="scrollbar-hide flex w-full flex-nowrap items-center gap-4 overflow-x-auto px-4">
|
||||
{status === 'loading' ? (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<LoaderIcon className="h-4 w-4 animate-spin text-white" />
|
||||
</div>
|
||||
) : (
|
||||
data?.profiles.map((item: { pubkey: string; profile: { content: string } }) => (
|
||||
<button
|
||||
key={item.pubkey}
|
||||
type="button"
|
||||
onClick={() => toggleFollow(item.pubkey)}
|
||||
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"
|
||||
>
|
||||
<User pubkey={item.pubkey} fallback={item.profile?.content} />
|
||||
{follows.includes(item.pubkey) && (
|
||||
<div className="absolute right-2 top-2">
|
||||
<CheckCircleIcon className="h-4 w-4 text-green-400" />
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
<div className="mx-auto mt-4 w-full max-w-md">
|
||||
<div className="flex flex-col gap-2">
|
||||
<button
|
||||
type="button"
|
||||
@ -133,7 +131,12 @@ export function OnboardStep1Screen() {
|
||||
>
|
||||
Skip, you can add later
|
||||
</Link>
|
||||
) : null}
|
||||
) : (
|
||||
<span className="text-center text-sm text-white/50">
|
||||
By clicking 'Continue', Lume will download all events related to
|
||||
your follows from the last 24 hours. It may take a bit
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { message } from '@tauri-apps/api/dialog';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { useStorage } from '@libs/storage/provider';
|
||||
|
||||
@ -34,8 +34,8 @@ const data = [
|
||||
|
||||
export function OnboardStep2Screen() {
|
||||
const navigate = useNavigate();
|
||||
const setStep = useOnboarding((state) => state.setStep);
|
||||
|
||||
const [setStep, clearStep] = useOnboarding((state) => [state.setStep, state.clearStep]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
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 () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
@ -61,8 +71,15 @@ export function OnboardStep2Screen() {
|
||||
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) {
|
||||
setLoading(false);
|
||||
await message(e, { title: 'Lume', type: 'error' });
|
||||
}
|
||||
};
|
||||
@ -123,12 +140,13 @@ export function OnboardStep2Screen() {
|
||||
)}
|
||||
</button>
|
||||
{!loading ? (
|
||||
<Link
|
||||
to="/auth/onboarding/step-3"
|
||||
<button
|
||||
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"
|
||||
>
|
||||
Skip, you can add later
|
||||
</Link>
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -4,7 +4,7 @@ import { Resolver, useForm } from 'react-hook-form';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
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';
|
||||
|
||||
@ -74,20 +74,22 @@ export function UnlockScreen() {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<div className="mx-auto w-full max-w-md">
|
||||
<div className="mb-6 text-center">
|
||||
<h1 className="text-2xl font-semibold text-white">Enter password to unlock</h1>
|
||||
<div className="mb-4 pb-4">
|
||||
<h1 className="text-center text-2xl font-semibold text-white">
|
||||
Enter password to unlock
|
||||
</h1>
|
||||
</div>
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="mb-0 flex flex-col">
|
||||
<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">
|
||||
<User pubkey={db.account.pubkey} />
|
||||
<UserImport pubkey={db.account.pubkey} />
|
||||
</div>
|
||||
<div className="relative">
|
||||
<input
|
||||
{...register('password', { required: true, minLength: 4 })}
|
||||
type={showPassword ? 'text' : '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
|
||||
type="button"
|
||||
@ -109,12 +111,12 @@ export function UnlockScreen() {
|
||||
<button
|
||||
type="submit"
|
||||
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 ? (
|
||||
<>
|
||||
<span className="w-5" />
|
||||
<span>Decryting...</span>
|
||||
<span>Unlocking...</span>
|
||||
<LoaderIcon className="h-5 w-5 animate-spin text-white" />
|
||||
</>
|
||||
) : (
|
||||
@ -127,7 +129,7 @@ export function UnlockScreen() {
|
||||
</button>
|
||||
<Link
|
||||
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
|
||||
</Link>
|
||||
|
@ -57,6 +57,24 @@ export function useNostr() {
|
||||
const follows = new Set<string>(preFollows || []);
|
||||
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
|
||||
if (!preFollows) {
|
||||
const user = ndk.getUser({ hexpubkey: db.account.pubkey });
|
||||
@ -67,20 +85,22 @@ export function useNostr() {
|
||||
}
|
||||
|
||||
// build user's network
|
||||
const events = await ndk.fetchEvents({
|
||||
kinds: [3],
|
||||
const followEvents = await ndk.fetchEvents({
|
||||
kinds: [NDKKind.Contacts],
|
||||
authors: [...follows],
|
||||
limit: 300,
|
||||
});
|
||||
|
||||
events.forEach((event: NDKEvent) => {
|
||||
followEvents.forEach((event: NDKEvent) => {
|
||||
event.tags.forEach((tag) => {
|
||||
if (tag[0] === 'p') lruNetwork.set(tag[1], tag[1]);
|
||||
});
|
||||
});
|
||||
|
||||
// get lru values
|
||||
const network = [...lruNetwork.values()] as string[];
|
||||
|
||||
// update db
|
||||
await db.updateAccount('follows', [...follows]);
|
||||
await db.updateAccount('network', [...new Set([...follows, ...network])]);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user