rome -> eslint + prettier
This commit is contained in:
@@ -1,44 +1,43 @@
|
||||
import { Image } from "@shared/image";
|
||||
import { DEFAULT_AVATAR } from "@stores/constants";
|
||||
import { useProfile } from "@utils/hooks/useProfile";
|
||||
import { shortenKey } from "@utils/shortenKey";
|
||||
import { Image } from '@shared/image';
|
||||
|
||||
export function User({
|
||||
pubkey,
|
||||
fallback,
|
||||
}: { pubkey: string; fallback?: string }) {
|
||||
const { status, user } = useProfile(pubkey, fallback);
|
||||
import { DEFAULT_AVATAR } from '@stores/constants';
|
||||
|
||||
if (status === "loading") {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative h-10 w-10 shrink-0 rounded-md bg-zinc-800 animate-pulse" />
|
||||
<div className="flex w-full flex-1 flex-col items-start gap-1 text-start">
|
||||
<span className="w-1/2 h-4 rounded bg-zinc-800 animate-pulse" />
|
||||
<span className="w-1/3 h-3 rounded bg-zinc-800 animate-pulse" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
import { useProfile } from '@utils/hooks/useProfile';
|
||||
import { shortenKey } from '@utils/shortenKey';
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative h-10 w-10 shrink rounded-md">
|
||||
<Image
|
||||
src={user.picture || user.image}
|
||||
fallback={DEFAULT_AVATAR}
|
||||
alt={pubkey}
|
||||
className="h-10 w-10 rounded-md object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex w-full flex-1 flex-col items-start text-start">
|
||||
<span className="truncate font-medium leading-tight text-zinc-100">
|
||||
{user.name || user.displayName || user.display_name}
|
||||
</span>
|
||||
<span className="text-base leading-tight text-zinc-400">
|
||||
{user.nip05?.toLowerCase() || shortenKey(pubkey)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
export function User({ pubkey, fallback }: { pubkey: string; fallback?: string }) {
|
||||
const { status, user } = useProfile(pubkey, fallback);
|
||||
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative h-10 w-10 shrink-0 animate-pulse rounded-md bg-zinc-800" />
|
||||
<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-zinc-800" />
|
||||
<span className="h-3 w-1/3 animate-pulse rounded bg-zinc-800" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative h-10 w-10 shrink rounded-md">
|
||||
<Image
|
||||
src={user.picture || user.image}
|
||||
fallback={DEFAULT_AVATAR}
|
||||
alt={pubkey}
|
||||
className="h-10 w-10 rounded-md object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex w-full flex-1 flex-col items-start text-start">
|
||||
<span className="truncate font-medium leading-tight text-zinc-100">
|
||||
{user.name || user.displayName || user.display_name}
|
||||
</span>
|
||||
<span className="text-base leading-tight text-zinc-400">
|
||||
{user.nip05?.toLowerCase() || shortenKey(pubkey)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Outlet } from "react-router-dom";
|
||||
import { Outlet } from 'react-router-dom';
|
||||
|
||||
export function AuthCreateScreen() {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Outlet />
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Outlet />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,114 +1,118 @@
|
||||
import { createAccount, createBlock } from "@libs/storage";
|
||||
import { Button } from "@shared/button";
|
||||
import { EyeOffIcon, EyeOnIcon, LoaderIcon } from "@shared/icons";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { generatePrivateKey, getPublicKey, nip19 } from "nostr-tools";
|
||||
import { useMemo, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { generatePrivateKey, getPublicKey, nip19 } from 'nostr-tools';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { createAccount } from '@libs/storage';
|
||||
|
||||
import { Button } from '@shared/button';
|
||||
import { EyeOffIcon, EyeOnIcon, LoaderIcon } from '@shared/icons';
|
||||
|
||||
export function CreateStep1Screen() {
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const [type, setType] = useState("password");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [type, setType] = useState('password');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const privkey = useMemo(() => generatePrivateKey(), []);
|
||||
const pubkey = getPublicKey(privkey);
|
||||
const npub = nip19.npubEncode(pubkey);
|
||||
const nsec = nip19.nsecEncode(privkey);
|
||||
const privkey = useMemo(() => generatePrivateKey(), []);
|
||||
const pubkey = getPublicKey(privkey);
|
||||
const npub = nip19.npubEncode(pubkey);
|
||||
const nsec = nip19.nsecEncode(privkey);
|
||||
|
||||
// toggle private key
|
||||
const showPrivateKey = () => {
|
||||
if (type === "password") {
|
||||
setType("text");
|
||||
} else {
|
||||
setType("password");
|
||||
}
|
||||
};
|
||||
// toggle private key
|
||||
const showPrivateKey = () => {
|
||||
if (type === 'password') {
|
||||
setType('text');
|
||||
} else {
|
||||
setType('password');
|
||||
}
|
||||
};
|
||||
|
||||
const account = useMutation({
|
||||
mutationFn: (data: any) => {
|
||||
return createAccount(data.npub, data.pubkey, data.privkey, null, 1);
|
||||
},
|
||||
onSuccess: (data: any) => {
|
||||
queryClient.setQueryData(["currentAccount"], data);
|
||||
},
|
||||
});
|
||||
const account = useMutation({
|
||||
mutationFn: (data: {
|
||||
npub: string;
|
||||
pubkey: string;
|
||||
privkey: string;
|
||||
follows: null | string[][];
|
||||
is_active: number;
|
||||
}) => {
|
||||
return createAccount(data.npub, data.pubkey, data.privkey, null, 1);
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
queryClient.setQueryData(['currentAccount'], data);
|
||||
},
|
||||
});
|
||||
|
||||
const submit = () => {
|
||||
setLoading(true);
|
||||
const submit = () => {
|
||||
setLoading(true);
|
||||
|
||||
account.mutate({
|
||||
npub,
|
||||
pubkey,
|
||||
privkey,
|
||||
follows: null,
|
||||
is_active: 1,
|
||||
});
|
||||
account.mutate({
|
||||
npub,
|
||||
pubkey,
|
||||
privkey,
|
||||
follows: null,
|
||||
is_active: 1,
|
||||
});
|
||||
|
||||
// redirect to next step
|
||||
setTimeout(() => navigate("/auth/create/step-2", { replace: true }), 1200);
|
||||
};
|
||||
// redirect to next step
|
||||
setTimeout(() => navigate('/auth/create/step-2', { replace: true }), 1200);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-md">
|
||||
<div className="mb-8 text-center">
|
||||
<h1 className="text-xl font-semibold text-zinc-100">
|
||||
Lume is auto-generated key for you
|
||||
</h1>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-base font-semibold text-zinc-400">
|
||||
Public Key
|
||||
</label>
|
||||
<input
|
||||
readOnly
|
||||
value={npub}
|
||||
className="relative w-full rounded-lg py-3 pl-3.5 pr-11 !outline-none placeholder:text-zinc-400 bg-zinc-800 text-zinc-100"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-base font-semibold text-zinc-400">
|
||||
Private Key
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
readOnly
|
||||
type={type}
|
||||
value={nsec}
|
||||
className="relative w-full rounded-lg py-3 pl-3.5 pr-11 !outline-none placeholder:text-zinc-400 bg-zinc-800 text-zinc-100"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => showPrivateKey()}
|
||||
className="group absolute right-2 top-1/2 -translate-y-1/2 transform rounded p-1 hover:bg-zinc-700"
|
||||
>
|
||||
{type === "password" ? (
|
||||
<EyeOffIcon
|
||||
width={20}
|
||||
height={20}
|
||||
className="text-zinc-500 group-hover:text-zinc-100"
|
||||
/>
|
||||
) : (
|
||||
<EyeOnIcon
|
||||
width={20}
|
||||
height={20}
|
||||
className="text-zinc-500 group-hover:text-zinc-100"
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<Button preset="large" onClick={() => submit()}>
|
||||
{loading ? (
|
||||
<LoaderIcon className="h-4 w-4 animate-spin text-black dark:text-zinc-100" />
|
||||
) : (
|
||||
"Continue →"
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-md">
|
||||
<div className="mb-8 text-center">
|
||||
<h1 className="text-xl font-semibold text-zinc-100">
|
||||
Lume is auto-generated key for you
|
||||
</h1>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="text-base font-semibold text-zinc-400">Public Key</span>
|
||||
<input
|
||||
readOnly
|
||||
value={npub}
|
||||
className="relative w-full rounded-lg bg-zinc-800 py-3 pl-3.5 pr-11 text-zinc-100 !outline-none placeholder:text-zinc-400"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="text-base font-semibold text-zinc-400">Private Key</span>
|
||||
<div className="relative">
|
||||
<input
|
||||
readOnly
|
||||
type={type}
|
||||
value={nsec}
|
||||
className="relative w-full rounded-lg bg-zinc-800 py-3 pl-3.5 pr-11 text-zinc-100 !outline-none placeholder:text-zinc-400"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => showPrivateKey()}
|
||||
className="group absolute right-2 top-1/2 -translate-y-1/2 transform rounded p-1 hover:bg-zinc-700"
|
||||
>
|
||||
{type === 'password' ? (
|
||||
<EyeOffIcon
|
||||
width={20}
|
||||
height={20}
|
||||
className="text-zinc-500 group-hover:text-zinc-100"
|
||||
/>
|
||||
) : (
|
||||
<EyeOnIcon
|
||||
width={20}
|
||||
height={20}
|
||||
className="text-zinc-500 group-hover:text-zinc-100"
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<Button preset="large" onClick={() => submit()}>
|
||||
{loading ? (
|
||||
<LoaderIcon className="h-4 w-4 animate-spin text-black dark:text-zinc-100" />
|
||||
) : (
|
||||
'Continue →'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,146 +1,152 @@
|
||||
import { AvatarUploader } from "@shared/avatarUploader";
|
||||
import { BannerUploader } from "@shared/bannerUploader";
|
||||
import { LoaderIcon } from "@shared/icons";
|
||||
import { Image } from "@shared/image";
|
||||
import { DEFAULT_AVATAR } from "@stores/constants";
|
||||
import { useOnboarding } from "@stores/onboarding";
|
||||
import { useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { AvatarUploader } from '@shared/avatarUploader';
|
||||
import { BannerUploader } from '@shared/bannerUploader';
|
||||
import { LoaderIcon } from '@shared/icons';
|
||||
import { Image } from '@shared/image';
|
||||
|
||||
import { DEFAULT_AVATAR } from '@stores/constants';
|
||||
import { useOnboarding } from '@stores/onboarding';
|
||||
|
||||
export function CreateStep2Screen() {
|
||||
const navigate = useNavigate();
|
||||
const createProfile = useOnboarding((state: any) => state.createProfile);
|
||||
const navigate = useNavigate();
|
||||
const createProfile = useOnboarding((state: any) => state.createProfile);
|
||||
|
||||
const [picture, setPicture] = useState(DEFAULT_AVATAR);
|
||||
const [banner, setBanner] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [picture, setPicture] = useState(DEFAULT_AVATAR);
|
||||
const [banner, setBanner] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { isDirty, isValid },
|
||||
} = useForm();
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { isDirty, isValid },
|
||||
} = useForm();
|
||||
|
||||
const onSubmit = (data: any) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const profile = {
|
||||
...data,
|
||||
username: data.name,
|
||||
display_name: data.name,
|
||||
bio: data.about,
|
||||
};
|
||||
createProfile(profile);
|
||||
// redirect to next step
|
||||
setTimeout(
|
||||
() => navigate("/auth/create/step-3", { replace: true }),
|
||||
1200,
|
||||
);
|
||||
} catch {
|
||||
console.log("error");
|
||||
}
|
||||
};
|
||||
const onSubmit = (data: any) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const profile = {
|
||||
...data,
|
||||
username: data.name,
|
||||
display_name: data.name,
|
||||
bio: data.about,
|
||||
};
|
||||
createProfile(profile);
|
||||
// redirect to next step
|
||||
setTimeout(() => navigate('/auth/create/step-3', { replace: true }), 1200);
|
||||
} catch {
|
||||
console.log('error');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-md">
|
||||
<div className="mb-8 text-center">
|
||||
<h1 className="text-xl font-semibold text-zinc-100">
|
||||
Create your profile
|
||||
</h1>
|
||||
</div>
|
||||
<div className="w-full rounded-xl border-t border-zinc-800/50 bg-zinc-900 overflow-hidden">
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="flex flex-col mb-0">
|
||||
<input
|
||||
type={"hidden"}
|
||||
{...register("picture")}
|
||||
value={picture}
|
||||
className="relative h-10 w-full rounded-lg border border-black/5 px-3 py-2 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-100 dark:shadow-black/10 dark:placeholder:text-zinc-500"
|
||||
/>
|
||||
<input
|
||||
type={"hidden"}
|
||||
{...register("banner")}
|
||||
value={banner}
|
||||
className="relative h-10 w-full rounded-lg border border-black/5 px-3 py-2 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-100 dark:shadow-black/10 dark:placeholder:text-zinc-500"
|
||||
/>
|
||||
<div className="relative">
|
||||
<div className="relative w-full h-44 bg-zinc-800">
|
||||
<Image
|
||||
src={banner}
|
||||
fallback="https://void.cat/d/QY1myro5tkHVs2nY7dy74b.jpg"
|
||||
alt="user's banner"
|
||||
className="h-full w-full object-cover"
|
||||
/>
|
||||
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-10 w-full h-full">
|
||||
<BannerUploader setBanner={setBanner} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-4 mb-5">
|
||||
<div className="z-10 relative h-14 w-14 -mt-7">
|
||||
<Image
|
||||
src={picture}
|
||||
fallback={DEFAULT_AVATAR}
|
||||
alt="user's avatar"
|
||||
className="h-14 w-14 object-cover ring-2 ring-zinc-900 rounded-lg"
|
||||
/>
|
||||
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-10 w-full h-full">
|
||||
<AvatarUploader setPicture={setPicture} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 px-4 pb-4">
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-sm font-semibold uppercase tracking-wider text-zinc-400">
|
||||
Name *
|
||||
</label>
|
||||
<input
|
||||
type={"text"}
|
||||
{...register("name", {
|
||||
required: true,
|
||||
minLength: 4,
|
||||
})}
|
||||
spellCheck={false}
|
||||
className="relative h-10 w-full rounded-lg px-3 py-2 !outline-none bg-zinc-800 text-zinc-100 placeholder:text-zinc-500"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-sm font-semibold uppercase tracking-wider text-zinc-400">
|
||||
Bio
|
||||
</label>
|
||||
<textarea
|
||||
{...register("about")}
|
||||
spellCheck={false}
|
||||
className="resize-none relative h-20 w-full rounded-lg px-3 py-2 !outline-none bg-zinc-800 text-zinc-100 placeholder:text-zinc-500"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-sm font-semibold uppercase tracking-wider text-zinc-400">
|
||||
Website
|
||||
</label>
|
||||
<input
|
||||
type={"text"}
|
||||
{...register("website", {
|
||||
required: false,
|
||||
})}
|
||||
spellCheck={false}
|
||||
className="relative h-10 w-full rounded-lg px-3 py-2 !outline-none bg-zinc-800 text-zinc-100 placeholder:text-zinc-500"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!isDirty || !isValid}
|
||||
className="inline-flex items-center justify-center h-11 w-full bg-fuchsia-500 rounded-md font-medium text-zinc-100 hover:bg-fuchsia-600"
|
||||
>
|
||||
{loading ? (
|
||||
<LoaderIcon className="h-4 w-4 animate-spin text-black dark:text-zinc-100" />
|
||||
) : (
|
||||
"Continue →"
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-md">
|
||||
<div className="mb-8 text-center">
|
||||
<h1 className="text-xl font-semibold text-zinc-100">Create your profile</h1>
|
||||
</div>
|
||||
<div className="w-full overflow-hidden rounded-xl border-t border-zinc-800/50 bg-zinc-900">
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="mb-0 flex flex-col">
|
||||
<input
|
||||
type={'hidden'}
|
||||
{...register('picture')}
|
||||
value={picture}
|
||||
className="shadow-input relative h-10 w-full rounded-lg border border-black/5 px-3 py-2 shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-100 dark:shadow-black/10 dark:placeholder:text-zinc-500"
|
||||
/>
|
||||
<input
|
||||
type={'hidden'}
|
||||
{...register('banner')}
|
||||
value={banner}
|
||||
className="shadow-input relative h-10 w-full rounded-lg border border-black/5 px-3 py-2 shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-100 dark:shadow-black/10 dark:placeholder:text-zinc-500"
|
||||
/>
|
||||
<div className="relative">
|
||||
<div className="relative h-44 w-full bg-zinc-800">
|
||||
<Image
|
||||
src={banner}
|
||||
fallback="https://void.cat/d/QY1myro5tkHVs2nY7dy74b.jpg"
|
||||
alt="user's banner"
|
||||
className="h-full w-full object-cover"
|
||||
/>
|
||||
<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-7 h-14 w-14">
|
||||
<Image
|
||||
src={picture}
|
||||
fallback={DEFAULT_AVATAR}
|
||||
alt="user's avatar"
|
||||
className="h-14 w-14 rounded-lg object-cover ring-2 ring-zinc-900"
|
||||
/>
|
||||
<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>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 px-4 pb-4">
|
||||
<div className="flex flex-col gap-1">
|
||||
<label
|
||||
htmlFor="name"
|
||||
className="text-sm font-semibold uppercase tracking-wider text-zinc-400"
|
||||
>
|
||||
Name *
|
||||
</label>
|
||||
<input
|
||||
type={'text'}
|
||||
{...register('name', {
|
||||
required: true,
|
||||
minLength: 4,
|
||||
})}
|
||||
spellCheck={false}
|
||||
className="relative h-10 w-full rounded-lg bg-zinc-800 px-3 py-2 text-zinc-100 !outline-none placeholder:text-zinc-500"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label
|
||||
htmlFor="about"
|
||||
className="text-sm font-semibold uppercase tracking-wider text-zinc-400"
|
||||
>
|
||||
Bio
|
||||
</label>
|
||||
<textarea
|
||||
{...register('about')}
|
||||
spellCheck={false}
|
||||
className="relative h-20 w-full resize-none rounded-lg bg-zinc-800 px-3 py-2 text-zinc-100 !outline-none placeholder:text-zinc-500"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label
|
||||
htmlFor="website"
|
||||
className="text-sm font-semibold uppercase tracking-wider text-zinc-400"
|
||||
>
|
||||
Website
|
||||
</label>
|
||||
<input
|
||||
type={'text'}
|
||||
{...register('website', {
|
||||
required: false,
|
||||
})}
|
||||
spellCheck={false}
|
||||
className="relative h-10 w-full rounded-lg bg-zinc-800 px-3 py-2 text-zinc-100 !outline-none placeholder:text-zinc-500"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!isDirty || !isValid}
|
||||
className="inline-flex h-11 w-full items-center justify-center rounded-md bg-fuchsia-500 font-medium text-zinc-100 hover:bg-fuchsia-600"
|
||||
>
|
||||
{loading ? (
|
||||
<LoaderIcon className="h-4 w-4 animate-spin text-black dark:text-zinc-100" />
|
||||
) : (
|
||||
'Continue →'
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,99 +1,98 @@
|
||||
import { NDKEvent, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
|
||||
import { Button } from "@shared/button";
|
||||
import { LoaderIcon } from "@shared/icons";
|
||||
import { RelayContext } from "@shared/relayProvider";
|
||||
import { useOnboarding } from "@stores/onboarding";
|
||||
import { Body, fetch } from "@tauri-apps/api/http";
|
||||
import { useAccount } from "@utils/hooks/useAccount";
|
||||
import { useContext, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { NDKEvent, NDKPrivateKeySigner } from '@nostr-dev-kit/ndk';
|
||||
import { Body, fetch } from '@tauri-apps/api/http';
|
||||
import { useContext, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { Button } from '@shared/button';
|
||||
import { LoaderIcon } from '@shared/icons';
|
||||
import { RelayContext } from '@shared/relayProvider';
|
||||
|
||||
import { useOnboarding } from '@stores/onboarding';
|
||||
|
||||
import { useAccount } from '@utils/hooks/useAccount';
|
||||
|
||||
export function CreateStep3Screen() {
|
||||
const ndk = useContext(RelayContext);
|
||||
const profile = useOnboarding((state: any) => state.profile);
|
||||
const navigate = useNavigate();
|
||||
const ndk = useContext(RelayContext);
|
||||
const profile = useOnboarding((state: any) => state.profile);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { account } = useAccount();
|
||||
const { account } = useAccount();
|
||||
|
||||
const [username, setUsername] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [username, setUsername] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const createNIP05 = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const createNIP05 = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
const response = await fetch("https://lume.nu/api/user-create", {
|
||||
method: "POST",
|
||||
timeout: 30,
|
||||
headers: {
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
body: Body.json({
|
||||
username: username,
|
||||
pubkey: account.pubkey,
|
||||
lightningAddress: "",
|
||||
}),
|
||||
});
|
||||
const response = await fetch('https://lume.nu/api/user-create', {
|
||||
method: 'POST',
|
||||
timeout: 30,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
},
|
||||
body: Body.json({
|
||||
username: username,
|
||||
pubkey: account.pubkey,
|
||||
lightningAddress: '',
|
||||
}),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = { ...profile, nip05: `${username}@lume.nu` };
|
||||
if (response.ok) {
|
||||
const data = { ...profile, nip05: `${username}@lume.nu` };
|
||||
|
||||
const signer = new NDKPrivateKeySigner(account.privkey);
|
||||
ndk.signer = signer;
|
||||
const signer = new NDKPrivateKeySigner(account.privkey);
|
||||
ndk.signer = signer;
|
||||
|
||||
const event = new NDKEvent(ndk);
|
||||
// build event
|
||||
event.content = JSON.stringify(data);
|
||||
event.kind = 0;
|
||||
event.pubkey = account.pubkey;
|
||||
event.tags = [];
|
||||
// publish event
|
||||
event.publish();
|
||||
const event = new NDKEvent(ndk);
|
||||
// build event
|
||||
event.content = JSON.stringify(data);
|
||||
event.kind = 0;
|
||||
event.pubkey = account.pubkey;
|
||||
event.tags = [];
|
||||
// publish event
|
||||
event.publish();
|
||||
|
||||
// redirect to step 4
|
||||
navigate("/auth/create/step-4", { replace: true });
|
||||
}
|
||||
} catch (error) {
|
||||
setLoading(false);
|
||||
console.error("Error:", error);
|
||||
}
|
||||
};
|
||||
// redirect to step 4
|
||||
navigate('/auth/create/step-4', { replace: true });
|
||||
}
|
||||
} catch (error) {
|
||||
setLoading(false);
|
||||
console.error('Error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-md">
|
||||
<div className="mb-8 text-center">
|
||||
<h1 className="text-xl font-semibold text-zinc-100">
|
||||
Create your Lume ID
|
||||
</h1>
|
||||
</div>
|
||||
<div className="w-full flex flex-col justify-center items-center gap-4">
|
||||
<div className="w-full inline-flex items-center justify-center gap-2 rounded-lg bg-zinc-800">
|
||||
<input
|
||||
type="text"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
autoCapitalize="false"
|
||||
autoCorrect="none"
|
||||
spellCheck="false"
|
||||
placeholder="satoshi"
|
||||
className="relative w-full py-3 pl-3.5 !outline-none placeholder:text-zinc-500 bg-transparent text-zinc-100"
|
||||
/>
|
||||
<span className="text-fuchsia-500 font-semibold pr-3.5">
|
||||
@lume.nu
|
||||
</span>
|
||||
</div>
|
||||
<Button
|
||||
preset="large"
|
||||
onClick={() => createNIP05()}
|
||||
disabled={username.length === 0}
|
||||
>
|
||||
{loading ? (
|
||||
<LoaderIcon className="h-4 w-4 animate-spin text-black dark:text-zinc-100" />
|
||||
) : (
|
||||
"Continue →"
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-md">
|
||||
<div className="mb-8 text-center">
|
||||
<h1 className="text-xl font-semibold text-zinc-100">Create your Lume ID</h1>
|
||||
</div>
|
||||
<div className="flex w-full flex-col items-center justify-center gap-4">
|
||||
<div className="inline-flex w-full items-center justify-center gap-2 rounded-lg bg-zinc-800">
|
||||
<input
|
||||
type="text"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
autoCapitalize="false"
|
||||
autoCorrect="none"
|
||||
spellCheck="false"
|
||||
placeholder="satoshi"
|
||||
className="relative w-full bg-transparent py-3 pl-3.5 text-zinc-100 !outline-none placeholder:text-zinc-500"
|
||||
/>
|
||||
<span className="pr-3.5 font-semibold text-fuchsia-500">@lume.nu</span>
|
||||
</div>
|
||||
<Button
|
||||
preset="large"
|
||||
onClick={() => createNIP05()}
|
||||
disabled={username.length === 0}
|
||||
>
|
||||
{loading ? (
|
||||
<LoaderIcon className="h-4 w-4 animate-spin text-black dark:text-zinc-100" />
|
||||
) : (
|
||||
'Continue →'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,236 +1,235 @@
|
||||
import { User } from "@app/auth/components/user";
|
||||
import { updateAccount } from "@libs/storage";
|
||||
import { NDKEvent, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
|
||||
import { CheckCircleIcon, LoaderIcon } from "@shared/icons";
|
||||
import { RelayContext } from "@shared/relayProvider";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { useAccount } from "@utils/hooks/useAccount";
|
||||
import { arrayToNIP02 } from "@utils/transform";
|
||||
import { useContext, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { NDKEvent, NDKPrivateKeySigner } from '@nostr-dev-kit/ndk';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useContext, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { User } from '@app/auth/components/user';
|
||||
|
||||
import { updateAccount } from '@libs/storage';
|
||||
|
||||
import { CheckCircleIcon, LoaderIcon } from '@shared/icons';
|
||||
import { RelayContext } from '@shared/relayProvider';
|
||||
|
||||
import { useAccount } from '@utils/hooks/useAccount';
|
||||
import { arrayToNIP02 } from '@utils/transform';
|
||||
|
||||
const INITIAL_LIST = [
|
||||
{
|
||||
pubkey: "82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2",
|
||||
},
|
||||
{
|
||||
pubkey: "a341f45ff9758f570a21b000c17d4e53a3a497c8397f26c0e6d61e5acffc7a98",
|
||||
},
|
||||
{
|
||||
pubkey: "04c915daefee38317fa734444acee390a8269fe5810b2241e5e6dd343dfbecc9",
|
||||
},
|
||||
{
|
||||
pubkey: "c4eabae1be3cf657bc1855ee05e69de9f059cb7a059227168b80b89761cbc4e0",
|
||||
},
|
||||
{
|
||||
pubkey: "6e468422dfb74a5738702a8823b9b28168abab8655faacb6853cd0ee15deee93",
|
||||
},
|
||||
{
|
||||
pubkey: "e88a691e98d9987c964521dff60025f60700378a4879180dcbbb4a5027850411",
|
||||
},
|
||||
{
|
||||
pubkey: "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d",
|
||||
},
|
||||
{
|
||||
pubkey: "c49d52a573366792b9a6e4851587c28042fb24fa5625c6d67b8c95c8751aca15",
|
||||
},
|
||||
{
|
||||
pubkey: "e33fe65f1fde44c6dc17eeb38fdad0fceaf1cae8722084332ed1e32496291d42",
|
||||
},
|
||||
{
|
||||
pubkey: "84dee6e676e5bb67b4ad4e042cf70cbd8681155db535942fcc6a0533858a7240",
|
||||
},
|
||||
{
|
||||
pubkey: "703e26b4f8bc0fa57f99d815dbb75b086012acc24fc557befa310f5aa08d1898",
|
||||
},
|
||||
{
|
||||
pubkey: "bf2376e17ba4ec269d10fcc996a4746b451152be9031fa48e74553dde5526bce",
|
||||
},
|
||||
{
|
||||
pubkey: "4523be58d395b1b196a9b8c82b038b6895cb02b683d0c253a955068dba1facd0",
|
||||
},
|
||||
{
|
||||
pubkey: "c9b19ffcd43e6a5f23b3d27106ce19e4ad2df89ba1031dd4617f1b591e108965",
|
||||
},
|
||||
{
|
||||
pubkey: "c7dccba4fe4426a7b1ea239a5637ba40fab9862c8c86b3330fe65e9f667435f6",
|
||||
},
|
||||
{
|
||||
pubkey: "6e1534f56fc9e937e06237c8ba4b5662bcacc4e1a3cfab9c16d89390bec4fca3",
|
||||
},
|
||||
{
|
||||
pubkey: "50d94fc2d8580c682b071a542f8b1e31a200b0508bab95a33bef0855df281d63",
|
||||
},
|
||||
{
|
||||
pubkey: "3d2e51508699f98f0f2bdbe7a45b673c687fe6420f466dc296d90b908d51d594",
|
||||
},
|
||||
{
|
||||
pubkey: "6e3f51664e19e082df5217fd4492bb96907405a0b27028671dd7f297b688608c",
|
||||
},
|
||||
{
|
||||
pubkey: "2edbcea694d164629854a52583458fd6d965b161e3c48b57d3aff01940558884",
|
||||
},
|
||||
{
|
||||
pubkey: "3f770d65d3a764a9c5cb503ae123e62ec7598ad035d836e2a810f3877a745b24",
|
||||
},
|
||||
{
|
||||
pubkey: "eab0e756d32b80bcd464f3d844b8040303075a13eabc3599a762c9ac7ab91f4f",
|
||||
},
|
||||
{
|
||||
pubkey: "be1d89794bf92de5dd64c1e60f6a2c70c140abac9932418fee30c5c637fe9479",
|
||||
},
|
||||
{
|
||||
pubkey: "a5e93aef8e820cbc7ab7b6205f854b87aed4b48c5f6b30fbbeba5c99e40dcf3f",
|
||||
},
|
||||
{
|
||||
pubkey: "1989034e56b8f606c724f45a12ce84a11841621aaf7182a1f6564380b9c4276b",
|
||||
},
|
||||
{
|
||||
pubkey: "c48b5cced5ada74db078df6b00fa53fc1139d73bf0ed16de325d52220211dbd5",
|
||||
},
|
||||
{
|
||||
pubkey: "460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c",
|
||||
},
|
||||
{
|
||||
pubkey: "7f3b464b9ff3623630485060cbda3a7790131c5339a7803bde8feb79a5e1b06a",
|
||||
},
|
||||
{
|
||||
pubkey: "b99dbca0184a32ce55904cb267b22e434823c97f418f36daf5d2dff0dd7b5c27",
|
||||
},
|
||||
{
|
||||
pubkey: "e9e4276490374a0daf7759fd5f475deff6ffb9b0fc5fa98c902b5f4b2fe3bba2",
|
||||
},
|
||||
{
|
||||
pubkey: "ea2e3c814d08a378f8a5b8faecb2884d05855975c5ca4b5c25e2d6f936286f14",
|
||||
},
|
||||
{
|
||||
pubkey: "ff04a0e6cd80c141b0b55825fed127d4532a6eecdb7e743a38a3c28bf9f44609",
|
||||
},
|
||||
{
|
||||
pubkey: '82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2',
|
||||
},
|
||||
{
|
||||
pubkey: 'a341f45ff9758f570a21b000c17d4e53a3a497c8397f26c0e6d61e5acffc7a98',
|
||||
},
|
||||
{
|
||||
pubkey: '04c915daefee38317fa734444acee390a8269fe5810b2241e5e6dd343dfbecc9',
|
||||
},
|
||||
{
|
||||
pubkey: 'c4eabae1be3cf657bc1855ee05e69de9f059cb7a059227168b80b89761cbc4e0',
|
||||
},
|
||||
{
|
||||
pubkey: '6e468422dfb74a5738702a8823b9b28168abab8655faacb6853cd0ee15deee93',
|
||||
},
|
||||
{
|
||||
pubkey: 'e88a691e98d9987c964521dff60025f60700378a4879180dcbbb4a5027850411',
|
||||
},
|
||||
{
|
||||
pubkey: '3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d',
|
||||
},
|
||||
{
|
||||
pubkey: 'c49d52a573366792b9a6e4851587c28042fb24fa5625c6d67b8c95c8751aca15',
|
||||
},
|
||||
{
|
||||
pubkey: 'e33fe65f1fde44c6dc17eeb38fdad0fceaf1cae8722084332ed1e32496291d42',
|
||||
},
|
||||
{
|
||||
pubkey: '84dee6e676e5bb67b4ad4e042cf70cbd8681155db535942fcc6a0533858a7240',
|
||||
},
|
||||
{
|
||||
pubkey: '703e26b4f8bc0fa57f99d815dbb75b086012acc24fc557befa310f5aa08d1898',
|
||||
},
|
||||
{
|
||||
pubkey: 'bf2376e17ba4ec269d10fcc996a4746b451152be9031fa48e74553dde5526bce',
|
||||
},
|
||||
{
|
||||
pubkey: '4523be58d395b1b196a9b8c82b038b6895cb02b683d0c253a955068dba1facd0',
|
||||
},
|
||||
{
|
||||
pubkey: 'c9b19ffcd43e6a5f23b3d27106ce19e4ad2df89ba1031dd4617f1b591e108965',
|
||||
},
|
||||
{
|
||||
pubkey: 'c7dccba4fe4426a7b1ea239a5637ba40fab9862c8c86b3330fe65e9f667435f6',
|
||||
},
|
||||
{
|
||||
pubkey: '6e1534f56fc9e937e06237c8ba4b5662bcacc4e1a3cfab9c16d89390bec4fca3',
|
||||
},
|
||||
{
|
||||
pubkey: '50d94fc2d8580c682b071a542f8b1e31a200b0508bab95a33bef0855df281d63',
|
||||
},
|
||||
{
|
||||
pubkey: '3d2e51508699f98f0f2bdbe7a45b673c687fe6420f466dc296d90b908d51d594',
|
||||
},
|
||||
{
|
||||
pubkey: '6e3f51664e19e082df5217fd4492bb96907405a0b27028671dd7f297b688608c',
|
||||
},
|
||||
{
|
||||
pubkey: '2edbcea694d164629854a52583458fd6d965b161e3c48b57d3aff01940558884',
|
||||
},
|
||||
{
|
||||
pubkey: '3f770d65d3a764a9c5cb503ae123e62ec7598ad035d836e2a810f3877a745b24',
|
||||
},
|
||||
{
|
||||
pubkey: 'eab0e756d32b80bcd464f3d844b8040303075a13eabc3599a762c9ac7ab91f4f',
|
||||
},
|
||||
{
|
||||
pubkey: 'be1d89794bf92de5dd64c1e60f6a2c70c140abac9932418fee30c5c637fe9479',
|
||||
},
|
||||
{
|
||||
pubkey: 'a5e93aef8e820cbc7ab7b6205f854b87aed4b48c5f6b30fbbeba5c99e40dcf3f',
|
||||
},
|
||||
{
|
||||
pubkey: '1989034e56b8f606c724f45a12ce84a11841621aaf7182a1f6564380b9c4276b',
|
||||
},
|
||||
{
|
||||
pubkey: 'c48b5cced5ada74db078df6b00fa53fc1139d73bf0ed16de325d52220211dbd5',
|
||||
},
|
||||
{
|
||||
pubkey: '460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c',
|
||||
},
|
||||
{
|
||||
pubkey: '7f3b464b9ff3623630485060cbda3a7790131c5339a7803bde8feb79a5e1b06a',
|
||||
},
|
||||
{
|
||||
pubkey: 'b99dbca0184a32ce55904cb267b22e434823c97f418f36daf5d2dff0dd7b5c27',
|
||||
},
|
||||
{
|
||||
pubkey: 'e9e4276490374a0daf7759fd5f475deff6ffb9b0fc5fa98c902b5f4b2fe3bba2',
|
||||
},
|
||||
{
|
||||
pubkey: 'ea2e3c814d08a378f8a5b8faecb2884d05855975c5ca4b5c25e2d6f936286f14',
|
||||
},
|
||||
{
|
||||
pubkey: 'ff04a0e6cd80c141b0b55825fed127d4532a6eecdb7e743a38a3c28bf9f44609',
|
||||
},
|
||||
];
|
||||
|
||||
export function CreateStep4Screen() {
|
||||
const ndk = useContext(RelayContext);
|
||||
const queryClient = useQueryClient();
|
||||
const navigate = useNavigate();
|
||||
const ndk = useContext(RelayContext);
|
||||
const queryClient = useQueryClient();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [follows, setFollows] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [follows, setFollows] = useState([]);
|
||||
|
||||
const { account } = useAccount();
|
||||
const { status, data } = useQuery(["trending-profiles"], async () => {
|
||||
const res = await fetch("https://api.nostr.band/v0/trending/profiles");
|
||||
if (!res.ok) {
|
||||
throw new Error("Error");
|
||||
}
|
||||
return res.json();
|
||||
});
|
||||
const { account } = useAccount();
|
||||
const { status, data } = useQuery(['trending-profiles'], async () => {
|
||||
const res = await fetch('https://api.nostr.band/v0/trending/profiles');
|
||||
if (!res.ok) {
|
||||
throw new Error('Error');
|
||||
}
|
||||
return res.json();
|
||||
});
|
||||
|
||||
// toggle follow state
|
||||
const toggleFollow = (pubkey: string) => {
|
||||
const arr = follows.includes(pubkey)
|
||||
? follows.filter((i) => i !== pubkey)
|
||||
: [...follows, pubkey];
|
||||
setFollows(arr);
|
||||
};
|
||||
// toggle follow state
|
||||
const toggleFollow = (pubkey: string) => {
|
||||
const arr = follows.includes(pubkey)
|
||||
? follows.filter((i) => i !== pubkey)
|
||||
: [...follows, pubkey];
|
||||
setFollows(arr);
|
||||
};
|
||||
|
||||
const update = useMutation({
|
||||
mutationFn: (follows: any) => {
|
||||
return updateAccount("follows", follows, account.pubkey);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["currentAccount"] });
|
||||
},
|
||||
});
|
||||
const update = useMutation({
|
||||
mutationFn: (follows: any) => {
|
||||
return updateAccount('follows', follows, account.pubkey);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['currentAccount'] });
|
||||
},
|
||||
});
|
||||
|
||||
// save follows to database then broadcast
|
||||
const submit = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
// save follows to database then broadcast
|
||||
const submit = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
const tags = arrayToNIP02([...follows, account.pubkey]);
|
||||
const signer = new NDKPrivateKeySigner(account.privkey);
|
||||
ndk.signer = signer;
|
||||
const tags = arrayToNIP02([...follows, account.pubkey]);
|
||||
const signer = new NDKPrivateKeySigner(account.privkey);
|
||||
ndk.signer = signer;
|
||||
|
||||
const event = new NDKEvent(ndk);
|
||||
// build event
|
||||
event.content = "";
|
||||
event.kind = 3;
|
||||
event.pubkey = account.pubkey;
|
||||
event.tags = tags;
|
||||
// publish event
|
||||
event.publish();
|
||||
const event = new NDKEvent(ndk);
|
||||
// build event
|
||||
event.content = '';
|
||||
event.kind = 3;
|
||||
event.pubkey = account.pubkey;
|
||||
event.tags = tags;
|
||||
// publish event
|
||||
event.publish();
|
||||
|
||||
// update
|
||||
update.mutate([...follows, account.pubkey]);
|
||||
// update
|
||||
update.mutate([...follows, account.pubkey]);
|
||||
|
||||
// redirect to next step
|
||||
setTimeout(() => navigate("/auth/onboarding", { replace: true }), 1200);
|
||||
} catch {
|
||||
console.log("error");
|
||||
}
|
||||
};
|
||||
// redirect to next step
|
||||
setTimeout(() => navigate('/auth/onboarding', { replace: true }), 1200);
|
||||
} catch {
|
||||
console.log('error');
|
||||
}
|
||||
};
|
||||
|
||||
const list = data ? data.profiles.concat(INITIAL_LIST) : [];
|
||||
const list = data ? data.profiles.concat(INITIAL_LIST) : [];
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-md">
|
||||
<div className="mb-8 text-center">
|
||||
<h1 className="text-xl font-semibold text-zinc-100">
|
||||
Personalized your newsfeed
|
||||
</h1>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="w-full rounded-xl border-t border-zinc-800/50 bg-zinc-900 overflow-hidden">
|
||||
<div className="inline-flex h-10 w-full items-center gap-1 border-b border-zinc-800 px-4 text-base font-medium text-zinc-400">
|
||||
Follow at least
|
||||
<span className="text-fuchsia-500 font-semibold">
|
||||
{follows.length}/10
|
||||
</span>{" "}
|
||||
plebs
|
||||
</div>
|
||||
{status === "loading" ? (
|
||||
<div className="py-2 px-4 w-full h-11 inline-flex items-center justify-center">
|
||||
<LoaderIcon className="h-4 w-4 animate-spin text-black dark:text-zinc-100" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="scrollbar-hide flex h-96 flex-col overflow-y-auto py-2">
|
||||
{list.map(
|
||||
(item: { pubkey: string; profile: { content: string } }) => (
|
||||
<button
|
||||
key={item.pubkey}
|
||||
type="button"
|
||||
onClick={() => toggleFollow(item.pubkey)}
|
||||
className="inline-flex transform items-center justify-between bg-zinc-900 px-4 py-2 hover:bg-zinc-800 active:translate-y-1"
|
||||
>
|
||||
<User
|
||||
pubkey={item.pubkey}
|
||||
fallback={item.profile?.content}
|
||||
/>
|
||||
{follows.includes(item.pubkey) && (
|
||||
<div>
|
||||
<CheckCircleIcon className="w-4 h-4 text-green-400" />
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{follows.length >= 10 && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => submit()}
|
||||
className="inline-flex items-center justify-center h-11 w-full bg-fuchsia-500 rounded-md font-medium text-zinc-100 hover:bg-fuchsia-600"
|
||||
>
|
||||
{loading ? (
|
||||
<LoaderIcon className="h-4 w-4 animate-spin text-black dark:text-zinc-100" />
|
||||
) : (
|
||||
"Finish →"
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-md">
|
||||
<div className="mb-8 text-center">
|
||||
<h1 className="text-xl font-semibold text-zinc-100">
|
||||
Personalized your newsfeed
|
||||
</h1>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="w-full overflow-hidden rounded-xl border-t border-zinc-800/50 bg-zinc-900">
|
||||
<div className="inline-flex h-10 w-full items-center gap-1 border-b border-zinc-800 px-4 text-base font-medium text-zinc-400">
|
||||
Follow at least
|
||||
<span className="font-semibold text-fuchsia-500">
|
||||
{follows.length}/10
|
||||
</span>{' '}
|
||||
plebs
|
||||
</div>
|
||||
{status === 'loading' ? (
|
||||
<div className="inline-flex h-11 w-full items-center justify-center px-4 py-2">
|
||||
<LoaderIcon className="h-4 w-4 animate-spin text-black dark:text-zinc-100" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="scrollbar-hide flex h-96 flex-col overflow-y-auto py-2">
|
||||
{list.map((item: { pubkey: string; profile: { content: string } }) => (
|
||||
<button
|
||||
key={item.pubkey}
|
||||
type="button"
|
||||
onClick={() => toggleFollow(item.pubkey)}
|
||||
className="inline-flex transform items-center justify-between bg-zinc-900 px-4 py-2 hover:bg-zinc-800 active:translate-y-1"
|
||||
>
|
||||
<User pubkey={item.pubkey} fallback={item.profile?.content} />
|
||||
{follows.includes(item.pubkey) && (
|
||||
<div>
|
||||
<CheckCircleIcon className="h-4 w-4 text-green-400" />
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{follows.length >= 10 && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => submit()}
|
||||
className="inline-flex h-11 w-full items-center justify-center rounded-md bg-fuchsia-500 font-medium text-zinc-100 hover:bg-fuchsia-600"
|
||||
>
|
||||
{loading ? (
|
||||
<LoaderIcon className="h-4 w-4 animate-spin text-black dark:text-zinc-100" />
|
||||
) : (
|
||||
'Finish →'
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Outlet } from "react-router-dom";
|
||||
import { Outlet } from 'react-router-dom';
|
||||
|
||||
export function AuthImportScreen() {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Outlet />
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Outlet />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,120 +1,119 @@
|
||||
import { createAccount, createBlock } from "@libs/storage";
|
||||
import { LoaderIcon } from "@shared/icons";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { getPublicKey, nip19 } from "nostr-tools";
|
||||
import { useState } from "react";
|
||||
import { Resolver, useForm } from "react-hook-form";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { getPublicKey, nip19 } from 'nostr-tools';
|
||||
import { useState } from 'react';
|
||||
import { Resolver, useForm } from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { createAccount } from '@libs/storage';
|
||||
|
||||
import { LoaderIcon } from '@shared/icons';
|
||||
|
||||
type FormValues = {
|
||||
key: string;
|
||||
key: string;
|
||||
};
|
||||
|
||||
const resolver: Resolver<FormValues> = async (values) => {
|
||||
return {
|
||||
values: values.key ? values : {},
|
||||
errors: !values.key
|
||||
? {
|
||||
key: {
|
||||
type: "required",
|
||||
message: "This is required.",
|
||||
},
|
||||
}
|
||||
: {},
|
||||
};
|
||||
return {
|
||||
values: values.key ? values : {},
|
||||
errors: !values.key
|
||||
? {
|
||||
key: {
|
||||
type: 'required',
|
||||
message: 'This is required.',
|
||||
},
|
||||
}
|
||||
: {},
|
||||
};
|
||||
};
|
||||
|
||||
export function ImportStep1Screen() {
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const account = useMutation({
|
||||
mutationFn: (data: any) => {
|
||||
return createAccount(data.npub, data.pubkey, data.privkey, null, 1);
|
||||
},
|
||||
onSuccess: (data: any) => {
|
||||
queryClient.setQueryData(["currentAccount"], data);
|
||||
},
|
||||
});
|
||||
const account = useMutation({
|
||||
mutationFn: (data: any) => {
|
||||
return createAccount(data.npub, data.pubkey, data.privkey, null, 1);
|
||||
},
|
||||
onSuccess: (data: any) => {
|
||||
queryClient.setQueryData(['currentAccount'], data);
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
register,
|
||||
setError,
|
||||
handleSubmit,
|
||||
formState: { errors, isDirty, isValid },
|
||||
} = useForm<FormValues>({ resolver });
|
||||
const {
|
||||
register,
|
||||
setError,
|
||||
handleSubmit,
|
||||
formState: { errors, isDirty, isValid },
|
||||
} = useForm<FormValues>({ resolver });
|
||||
|
||||
const onSubmit = async (data: any) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const onSubmit = async (data: any) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
let privkey = data["key"];
|
||||
if (privkey.substring(0, 4) === "nsec") {
|
||||
privkey = nip19.decode(privkey).data;
|
||||
}
|
||||
let privkey = data['key'];
|
||||
if (privkey.substring(0, 4) === 'nsec') {
|
||||
privkey = nip19.decode(privkey).data;
|
||||
}
|
||||
|
||||
if (typeof getPublicKey(privkey) === "string") {
|
||||
const pubkey = getPublicKey(privkey);
|
||||
const npub = nip19.npubEncode(pubkey);
|
||||
if (typeof getPublicKey(privkey) === 'string') {
|
||||
const pubkey = getPublicKey(privkey);
|
||||
const npub = nip19.npubEncode(pubkey);
|
||||
|
||||
// update
|
||||
account.mutate({
|
||||
npub,
|
||||
pubkey,
|
||||
privkey,
|
||||
follows: null,
|
||||
is_active: 1,
|
||||
});
|
||||
// update
|
||||
account.mutate({
|
||||
npub,
|
||||
pubkey,
|
||||
privkey,
|
||||
follows: null,
|
||||
is_active: 1,
|
||||
});
|
||||
|
||||
// redirect to step 2
|
||||
setTimeout(
|
||||
() => navigate("/auth/import/step-2", { replace: true }),
|
||||
1200,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
setError("key", {
|
||||
type: "custom",
|
||||
message: "Private Key is invalid, please check again",
|
||||
});
|
||||
}
|
||||
};
|
||||
// redirect to step 2
|
||||
setTimeout(() => navigate('/auth/import/step-2', { replace: true }), 1200);
|
||||
}
|
||||
} catch (error) {
|
||||
setError('key', {
|
||||
type: 'custom',
|
||||
message: 'Private Key is invalid, please check again',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-md">
|
||||
<div className="mb-8 text-center">
|
||||
<h1 className="text-xl font-semibold text-zinc-100">Import your key</h1>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="flex flex-col gap-3">
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<input
|
||||
{...register("key", { required: true, minLength: 32 })}
|
||||
type={"password"}
|
||||
placeholder="Paste private key here..."
|
||||
className="relative w-full rounded-lg px-3 py-3 !outline-none bg-zinc-800 text-zinc-100 placeholder:text-zinc-500"
|
||||
/>
|
||||
<span className="text-base text-red-400">
|
||||
{errors.key && <p>{errors.key.message}</p>}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-center">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!isDirty || !isValid}
|
||||
className="inline-flex items-center justify-center h-11 w-full bg-fuchsia-500 rounded-md font-medium text-zinc-100 hover:bg-fuchsia-600"
|
||||
>
|
||||
{loading ? (
|
||||
<LoaderIcon className="h-4 w-4 animate-spin text-black dark:text-zinc-100" />
|
||||
) : (
|
||||
"Continue →"
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-md">
|
||||
<div className="mb-8 text-center">
|
||||
<h1 className="text-xl font-semibold text-zinc-100">Import your key</h1>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="flex flex-col gap-3">
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<input
|
||||
{...register('key', { required: true, minLength: 32 })}
|
||||
type={'password'}
|
||||
placeholder="Paste private key here..."
|
||||
className="relative w-full rounded-lg bg-zinc-800 px-3 py-3 text-zinc-100 !outline-none placeholder:text-zinc-500"
|
||||
/>
|
||||
<span className="text-base text-red-400">
|
||||
{errors.key && <p>{errors.key.message}</p>}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-center">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!isDirty || !isValid}
|
||||
className="inline-flex h-11 w-full items-center justify-center rounded-md bg-fuchsia-500 font-medium text-zinc-100 hover:bg-fuchsia-600"
|
||||
>
|
||||
{loading ? (
|
||||
<LoaderIcon className="h-4 w-4 animate-spin text-black dark:text-zinc-100" />
|
||||
) : (
|
||||
'Continue →'
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,83 +1,87 @@
|
||||
import { User } from "@app/auth/components/user";
|
||||
import { updateAccount } from "@libs/storage";
|
||||
import { Button } from "@shared/button";
|
||||
import { LoaderIcon } from "@shared/icons";
|
||||
import { RelayContext } from "@shared/relayProvider";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { useAccount } from "@utils/hooks/useAccount";
|
||||
import { setToArray } from "@utils/transform";
|
||||
import { useContext, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useContext, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { User } from '@app/auth/components/user';
|
||||
|
||||
import { updateAccount } from '@libs/storage';
|
||||
|
||||
import { Button } from '@shared/button';
|
||||
import { LoaderIcon } from '@shared/icons';
|
||||
import { RelayContext } from '@shared/relayProvider';
|
||||
|
||||
import { useAccount } from '@utils/hooks/useAccount';
|
||||
import { setToArray } from '@utils/transform';
|
||||
|
||||
export function ImportStep2Screen() {
|
||||
const ndk = useContext(RelayContext);
|
||||
const queryClient = useQueryClient();
|
||||
const navigate = useNavigate();
|
||||
const ndk = useContext(RelayContext);
|
||||
const queryClient = useQueryClient();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { status, account } = useAccount();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { status, account } = useAccount();
|
||||
|
||||
const update = useMutation({
|
||||
mutationFn: (follows: any) => {
|
||||
return updateAccount("follows", follows, account.pubkey);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["currentAccount"] });
|
||||
},
|
||||
});
|
||||
const update = useMutation({
|
||||
mutationFn: (follows: any) => {
|
||||
return updateAccount('follows', follows, account.pubkey);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['currentAccount'] });
|
||||
},
|
||||
});
|
||||
|
||||
const submit = async () => {
|
||||
try {
|
||||
// show loading indicator
|
||||
setLoading(true);
|
||||
const submit = async () => {
|
||||
try {
|
||||
// show loading indicator
|
||||
setLoading(true);
|
||||
|
||||
const user = ndk.getUser({ hexpubkey: account.pubkey });
|
||||
const follows = await user.follows();
|
||||
const user = ndk.getUser({ hexpubkey: account.pubkey });
|
||||
const follows = await user.follows();
|
||||
|
||||
// follows as list
|
||||
const followsList = setToArray(follows);
|
||||
// follows as list
|
||||
const followsList = setToArray(follows);
|
||||
|
||||
// update
|
||||
update.mutate([...followsList, account.pubkey]);
|
||||
// update
|
||||
update.mutate([...followsList, account.pubkey]);
|
||||
|
||||
// redirect to next step
|
||||
setTimeout(() => navigate("/auth/onboarding", { replace: true }), 1200);
|
||||
} catch {
|
||||
console.log("error");
|
||||
}
|
||||
};
|
||||
// redirect to next step
|
||||
setTimeout(() => navigate('/auth/onboarding', { replace: true }), 1200);
|
||||
} catch {
|
||||
console.log('error');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-md">
|
||||
<div className="mb-8 text-center">
|
||||
<h1 className="text-xl font-semibold">
|
||||
{loading ? "Creating..." : "Continue with"}
|
||||
</h1>
|
||||
</div>
|
||||
<div className="w-full rounded-xl border-t border-zinc-800/50 bg-zinc-900 p-4">
|
||||
{status === "loading" ? (
|
||||
<div className="w-full">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="h-11 w-11 animate-pulse rounded-lg bg-zinc-800" />
|
||||
<div>
|
||||
<h3 className="mb-1 h-4 w-16 animate-pulse rounded bg-zinc-800" />
|
||||
<p className="h-3 w-36 animate-pulse rounded bg-zinc-800" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-3">
|
||||
<User pubkey={account.pubkey} />
|
||||
<Button preset="large" onClick={() => submit()}>
|
||||
{loading ? (
|
||||
<LoaderIcon className="h-4 w-4 animate-spin text-black dark:text-zinc-100" />
|
||||
) : (
|
||||
"Continue →"
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-md">
|
||||
<div className="mb-8 text-center">
|
||||
<h1 className="text-xl font-semibold">
|
||||
{loading ? 'Creating...' : 'Continue with'}
|
||||
</h1>
|
||||
</div>
|
||||
<div className="w-full rounded-xl border-t border-zinc-800/50 bg-zinc-900 p-4">
|
||||
{status === 'loading' ? (
|
||||
<div className="w-full">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="h-11 w-11 animate-pulse rounded-lg bg-zinc-800" />
|
||||
<div>
|
||||
<div className="mb-1 h-4 w-16 animate-pulse rounded bg-zinc-800" />
|
||||
<div className="h-3 w-36 animate-pulse rounded bg-zinc-800" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-3">
|
||||
<User pubkey={account.pubkey} />
|
||||
<Button preset="large" onClick={() => submit()}>
|
||||
{loading ? (
|
||||
<LoaderIcon className="h-4 w-4 animate-spin text-black dark:text-zinc-100" />
|
||||
) : (
|
||||
'Continue →'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,103 +1,103 @@
|
||||
import { usePublish } from "@libs/ndk";
|
||||
import { LoaderIcon } from "@shared/icons";
|
||||
import { ArrowRightCircleIcon } from "@shared/icons/arrowRightCircle";
|
||||
import { User } from "@shared/user";
|
||||
import { useAccount } from "@utils/hooks/useAccount";
|
||||
import { useState } from "react";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import { useState } from 'react';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
|
||||
import { usePublish } from '@libs/ndk';
|
||||
|
||||
import { LoaderIcon } from '@shared/icons';
|
||||
import { ArrowRightCircleIcon } from '@shared/icons/arrowRightCircle';
|
||||
import { User } from '@shared/user';
|
||||
|
||||
import { useAccount } from '@utils/hooks/useAccount';
|
||||
|
||||
export function OnboardingScreen() {
|
||||
const publish = usePublish();
|
||||
const navigate = useNavigate();
|
||||
const publish = usePublish();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { status, account } = useAccount();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { status, account } = useAccount();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const submit = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const submit = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// publish event
|
||||
publish({
|
||||
content:
|
||||
"Running Lume, fighting for better future, join us here: https://lume.nu",
|
||||
kind: 1,
|
||||
tags: [],
|
||||
});
|
||||
// publish event
|
||||
publish({
|
||||
content:
|
||||
'Running Lume, fighting for better future, join us here: https://lume.nu',
|
||||
kind: 1,
|
||||
tags: [],
|
||||
});
|
||||
|
||||
// redirect to home
|
||||
setTimeout(() => navigate("/", { replace: true }), 1200);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
// redirect to home
|
||||
setTimeout(() => navigate('/', { replace: true }), 1200);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<div className="mx-auto w-full max-w-md">
|
||||
<div className="mb-8 text-center">
|
||||
<h1 className="mb-2 text-xl font-semibold text-zinc-100">
|
||||
👋 Hello, welcome you to Lume
|
||||
</h1>
|
||||
<p className="text-sm text-zinc-300">
|
||||
You're a part of better future that we're fighting
|
||||
</p>
|
||||
<p className="text-sm text-zinc-300">
|
||||
If Lume gets your attention, please help us spread via button below
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-full border-t border-zinc-800/50 bg-zinc-900 rounded-xl">
|
||||
<div className="h-min w-full px-5 py-3">
|
||||
{status === "success" && (
|
||||
<User
|
||||
pubkey={account.pubkey}
|
||||
time={Math.floor(Date.now() / 1000)}
|
||||
/>
|
||||
)}
|
||||
<div className="-mt-6 pl-[49px] select-text whitespace-pre-line break-words text-base text-zinc-100">
|
||||
<p>Running Lume, fighting for better future</p>
|
||||
<p>
|
||||
join us here:{" "}
|
||||
<a
|
||||
href="https://lume.nu"
|
||||
className="text-fuchsia-500 hover:text-fuchsia-600 no-underline font-normal"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
https://lume.nu
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 w-full flex flex-col gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => submit()}
|
||||
className="inline-flex h-12 w-full items-center justify-between gap-2 rounded-lg px-6 font-medium text-zinc-100 bg-fuchsia-500 hover:bg-fuchsia-600"
|
||||
>
|
||||
{loading ? (
|
||||
<>
|
||||
<span className="w-5" />
|
||||
<LoaderIcon className="h-4 w-4 animate-spin text-black dark:text-zinc-100" />
|
||||
<span className="w-5" />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="w-5" />
|
||||
<span>Publish</span>
|
||||
<ArrowRightCircleIcon className="w-5 h-5" />
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
<Link
|
||||
to="/"
|
||||
className="inline-flex h-10 w-full items-center justify-center gap-2 rounded-lg px-6 text-sm font-medium text-zinc-200"
|
||||
>
|
||||
Skip for now
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<div className="mx-auto w-full max-w-md">
|
||||
<div className="mb-8 text-center">
|
||||
<h1 className="mb-2 text-xl font-semibold text-zinc-100">
|
||||
👋 Hello, welcome you to Lume
|
||||
</h1>
|
||||
<p className="text-sm text-zinc-300">
|
||||
You're a part of better future that we're fighting
|
||||
</p>
|
||||
<p className="text-sm text-zinc-300">
|
||||
If Lume gets your attention, please help us spread via button below
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-full rounded-xl border-t border-zinc-800/50 bg-zinc-900">
|
||||
<div className="h-min w-full px-5 py-3">
|
||||
{status === 'success' && (
|
||||
<User pubkey={account.pubkey} time={Math.floor(Date.now() / 1000)} />
|
||||
)}
|
||||
<div className="-mt-6 select-text whitespace-pre-line break-words pl-[49px] text-base text-zinc-100">
|
||||
<p>Running Lume, fighting for better future</p>
|
||||
<p>
|
||||
join us here:{' '}
|
||||
<a
|
||||
href="https://lume.nu"
|
||||
className="font-normal text-fuchsia-500 no-underline hover:text-fuchsia-600"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
https://lume.nu
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 flex w-full flex-col gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => submit()}
|
||||
className="inline-flex h-12 w-full items-center justify-between gap-2 rounded-lg bg-fuchsia-500 px-6 font-medium text-zinc-100 hover:bg-fuchsia-600"
|
||||
>
|
||||
{loading ? (
|
||||
<>
|
||||
<span className="w-5" />
|
||||
<LoaderIcon className="h-4 w-4 animate-spin text-black dark:text-zinc-100" />
|
||||
<span className="w-5" />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="w-5" />
|
||||
<span>Publish</span>
|
||||
<ArrowRightCircleIcon className="h-5 w-5" />
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
<Link
|
||||
to="/"
|
||||
className="inline-flex h-10 w-full items-center justify-center gap-2 rounded-lg px-6 text-sm font-medium text-zinc-200"
|
||||
>
|
||||
Skip for now
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,53 +1,54 @@
|
||||
import { ArrowRightCircleIcon } from "@shared/icons/arrowRightCircle";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { ArrowRightCircleIcon } from '@shared/icons/arrowRightCircle';
|
||||
|
||||
export function WelcomeScreen() {
|
||||
return (
|
||||
<div className="w-full h-full grid grid-cols-12 gap-4 px-4 py-4">
|
||||
<div className="col-span-5 border-t border-zinc-800/50 bg-zinc-900 rounded-xl flex flex-col">
|
||||
<div className="w-full h-full flex flex-col justify-center px-4 py-4 gap-2">
|
||||
<h1 className="text-zinc-700 text-4xl font-bold leading-none text-transparent">
|
||||
Preserve your <span className="text-fuchsia-300">freedom</span>
|
||||
</h1>
|
||||
<h2 className="text-zinc-700 text-4xl font-bold leading-none text-transparent">
|
||||
Protect your <span className="text-red-300">future</span>
|
||||
</h2>
|
||||
<h3 className="text-zinc-700 text-4xl font-bold leading-none text-transparent">
|
||||
Stack <span className="text-orange-300">bitcoin</span>
|
||||
</h3>
|
||||
<h3 className="text-zinc-700 text-4xl font-bold leading-none text-transparent">
|
||||
Use <span className="text-purple-300">nostr</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div className="mt-auto w-full flex flex-col gap-2 px-4 py-4">
|
||||
<Link
|
||||
to="/auth/import"
|
||||
className="inline-flex h-12 w-full items-center justify-between gap-2 rounded-lg px-6 font-medium text-zinc-100 bg-fuchsia-500 hover:bg-fuchsia-600"
|
||||
>
|
||||
<span className="w-5" />
|
||||
<span>Login with private key</span>
|
||||
<ArrowRightCircleIcon className="w-5 h-5" />
|
||||
</Link>
|
||||
<Link
|
||||
to="/auth/create"
|
||||
className="inline-flex h-12 w-full items-center justify-center gap-2 rounded-lg px-6 font-medium text-zinc-200 bg-zinc-800 hover:bg-zinc-700"
|
||||
>
|
||||
Create new key
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="col-span-5 bg-zinc-900 rounded-xl bg-cover bg-center"
|
||||
style={{
|
||||
backgroundImage: `url("https://void.cat/d/Ps1b36vu5pdkEA2w75usuB")`,
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
className="col-span-2 bg-zinc-900 rounded-xl bg-cover bg-center"
|
||||
style={{
|
||||
backgroundImage: `url("https://void.cat/d/5FdJcBP5ZXKAjYqV8hpcp3")`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="grid h-full w-full grid-cols-12 gap-4 px-4 py-4">
|
||||
<div className="col-span-5 flex flex-col rounded-xl border-t border-zinc-800/50 bg-zinc-900">
|
||||
<div className="flex h-full w-full flex-col justify-center gap-2 px-4 py-4">
|
||||
<h1 className="text-4xl font-bold leading-none text-transparent text-zinc-700">
|
||||
Preserve your <span className="text-fuchsia-300">freedom</span>
|
||||
</h1>
|
||||
<h2 className="text-4xl font-bold leading-none text-transparent text-zinc-700">
|
||||
Protect your <span className="text-red-300">future</span>
|
||||
</h2>
|
||||
<h3 className="text-4xl font-bold leading-none text-transparent text-zinc-700">
|
||||
Stack <span className="text-orange-300">bitcoin</span>
|
||||
</h3>
|
||||
<h3 className="text-4xl font-bold leading-none text-transparent text-zinc-700">
|
||||
Use <span className="text-purple-300">nostr</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div className="mt-auto flex w-full flex-col gap-2 px-4 py-4">
|
||||
<Link
|
||||
to="/auth/import"
|
||||
className="inline-flex h-12 w-full items-center justify-between gap-2 rounded-lg bg-fuchsia-500 px-6 font-medium text-zinc-100 hover:bg-fuchsia-600"
|
||||
>
|
||||
<span className="w-5" />
|
||||
<span>Login with private key</span>
|
||||
<ArrowRightCircleIcon className="h-5 w-5" />
|
||||
</Link>
|
||||
<Link
|
||||
to="/auth/create"
|
||||
className="inline-flex h-12 w-full items-center justify-center gap-2 rounded-lg bg-zinc-800 px-6 font-medium text-zinc-200 hover:bg-zinc-700"
|
||||
>
|
||||
Create new key
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="col-span-5 rounded-xl bg-zinc-900 bg-cover bg-center"
|
||||
style={{
|
||||
backgroundImage: `url("https://void.cat/d/Ps1b36vu5pdkEA2w75usuB")`,
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
className="col-span-2 rounded-xl bg-zinc-900 bg-cover bg-center"
|
||||
style={{
|
||||
backgroundImage: `url("https://void.cat/d/5FdJcBP5ZXKAjYqV8hpcp3")`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user