This commit is contained in:
2023-10-12 09:13:06 +07:00
parent 3b46e71525
commit 35650a40f2
36 changed files with 444 additions and 1447 deletions

View File

@@ -1,122 +1,152 @@
import { NDKKind } from '@nostr-dev-kit/ndk';
import { useEffect, useState } from 'react';
import { Resolver, useForm } from 'react-hook-form';
import { useForm } from 'react-hook-form';
import { useNavigate } from 'react-router-dom';
import { useStorage } from '@libs/storage/provider';
import { EyeOffIcon, EyeOnIcon, LoaderIcon } from '@shared/icons';
import { AvatarUploader } from '@shared/avatarUploader';
import { BannerUploader } from '@shared/bannerUploader';
import { LoaderIcon } from '@shared/icons';
import { ArrowRightCircleIcon } from '@shared/icons/arrowRightCircle';
import { Image } from '@shared/image';
import { useOnboarding } from '@stores/onboarding';
import { useStronghold } from '@stores/stronghold';
import { WidgetKinds } from '@stores/widgets';
type FormValues = {
password: string;
};
const resolver: Resolver<FormValues> = async (values) => {
return {
values: values.password ? values : {},
errors: !values.password
? {
password: {
type: 'required',
message: 'This is required.',
},
}
: {},
};
};
import { useNostr } from '@utils/hooks/useNostr';
export function CreateStep2Screen() {
const navigate = useNavigate();
const setStep = useOnboarding((state) => state.setStep);
const pubkey = useOnboarding((state) => state.pubkey);
const privkey = useStronghold((state) => state.privkey);
const [passwordInput, setPasswordInput] = useState('password');
const [loading, setLoading] = useState(false);
const [picture, setPicture] = useState('https://void.cat/d/5VKmKyuHyxrNMf9bWSVPih');
const [banner, setBanner] = useState('');
const { db } = useStorage();
// toggle private key
const showPassword = () => {
if (passwordInput === 'password') {
setPasswordInput('text');
} else {
setPasswordInput('password');
}
};
const { publish } = useNostr();
const {
register,
setError,
handleSubmit,
formState: { errors, isDirty, isValid },
} = useForm<FormValues>({ resolver });
formState: { isDirty, isValid },
} = useForm();
const onSubmit = async (data: { [x: string]: string }) => {
const onSubmit = async (data: { name: string; about: string; website: string }) => {
setLoading(true);
if (data.password.length > 3) {
// save privkey to secure storage
await db.secureSave(pubkey, privkey);
try {
const profile = {
...data,
name: data.name,
display_name: data.name,
bio: data.about,
website: data.website,
};
// redirect to next step
navigate('/auth/create/step-3', { replace: true });
} else {
setLoading(false);
setError('password', {
type: 'custom',
message: 'Password is required and must be greater than 3',
const event = await publish({
content: JSON.stringify(profile),
kind: NDKKind.Metadata,
tags: [],
});
// create default widget
await db.createWidget(WidgetKinds.other.learnNostr, 'Learn Nostr', '');
if (event) {
navigate('/auth/onboarding', { replace: true });
}
} catch (e) {
console.log('error: ', e);
setLoading(false);
}
};
useEffect(() => {
// save current step, if user close app and reopen it
setStep('/auth/create/step-2');
setStep('/auth/create/step-3');
}, []);
return (
<div className="mx-auto w-full max-w-md">
<div className="mb-4 border-b border-white/10 pb-4">
<h1 className="mb-2 text-center text-2xl font-semibold text-white">
Set password to secure your key
Personalize your Nostr profile
</h1>
<p className="text-white/70">
Password is not related to your Nostr account. It is only used to secure your
keys stored on your local machine and to unlock the app (like unlocking your
phone with a passcode). When you move to other Nostr clients, you just need to
copy your private key.
Nostr profile is synchronous across all Nostr clients. If you create a profile
on Lume, it will also work well with other Nostr clients. If you update your
profile on another Nostr client, it will also sync to Lume.
</p>
</div>
<div className="flex flex-col gap-4">
<form onSubmit={handleSubmit(onSubmit)} className="flex flex-col gap-3">
<div className="flex flex-col gap-1">
<div className="relative">
<input
{...register('password', { required: true })}
type={passwordInput}
placeholder="Enter password"
className="relative h-12 w-full rounded-lg border-t border-white/10 bg-white/20 px-3.5 py-1 text-center tracking-widest text-white !outline-none backdrop-blur-xl placeholder:tracking-normal placeholder:text-white/70"
/>
<button
type="button"
onClick={() => showPassword()}
className="group absolute right-2 top-1/2 -translate-y-1/2 transform rounded p-1 backdrop-blur-xl hover:bg-white/20"
>
{passwordInput === 'password' ? (
<EyeOffIcon className="h-4 w-4 text-white/50 group-hover:text-white" />
) : (
<EyeOnIcon className="h-4 w-4 text-white/50 group-hover:text-white" />
)}
</button>
<div className="w-full overflow-hidden rounded-xl bg-white/10 backdrop-blur-xl">
<form onSubmit={handleSubmit(onSubmit)} className="mb-0 flex flex-col">
<input type={'hidden'} {...register('picture')} value={picture} />
<input type={'hidden'} {...register('banner')} value={banner} />
<div className="relative">
<div className="relative h-36 w-full bg-white/10 backdrop-blur-xl">
{banner ? (
<Image
src={banner}
alt="user's banner"
className="h-full w-full object-cover"
/>
) : (
<div className="h-full w-full bg-white/20" />
)}
<div className="absolute left-1/2 top-1/2 z-10 h-full w-full -translate-x-1/2 -translate-y-1/2 transform">
<BannerUploader setBanner={setBanner} />
</div>
</div>
<div className="mb-5 px-4">
<div className="relative z-10 -mt-8 h-16 w-16">
<Image
src={picture}
alt="user's avatar"
className="h-16 w-16 rounded-lg object-cover ring-2 ring-white/20"
/>
<div className="absolute left-1/2 top-1/2 z-10 h-full w-full -translate-x-1/2 -translate-y-1/2 transform">
<AvatarUploader setPicture={setPicture} />
</div>
</div>
</div>
<span className="text-sm text-red-400">
{errors.password && <p>{errors.password.message}</p>}
</span>
</div>
<div className="flex items-center justify-center">
<div className="flex flex-col gap-4 px-4 pb-4">
<div className="flex flex-col gap-1">
<label htmlFor="name" className="font-medium text-white">
Name *
</label>
<input
type={'text'}
{...register('name', {
required: true,
minLength: 1,
})}
spellCheck={false}
className="relative h-12 w-full rounded-lg bg-white/20 px-3 py-1 text-white !outline-none backdrop-blur-xl placeholder:text-white/70"
/>
</div>
<div className="flex flex-col gap-1">
<label htmlFor="about" className="font-medium text-white">
Bio
</label>
<textarea
{...register('about')}
spellCheck={false}
className="relative h-20 w-full resize-none rounded-lg bg-white/20 px-3 py-2 text-white !outline-none backdrop-blur-xl placeholder:text-white/70"
/>
</div>
<div className="flex flex-col gap-1">
<label htmlFor="website" className="font-medium text-white">
Website
</label>
<input
{...register('website', {
required: false,
})}
spellCheck={false}
className="relative h-12 w-full rounded-lg bg-white/20 px-3 py-1 text-white !outline-none backdrop-blur-xl placeholder:text-white/70"
/>
</div>
<button
type="submit"
disabled={!isDirty || !isValid}
@@ -125,7 +155,7 @@ export function CreateStep2Screen() {
{loading ? (
<>
<span className="w-5" />
<span>Securing your account...</span>
<span>Creating...</span>
<LoaderIcon className="h-5 w-5 animate-spin text-white" />
</>
) : (