mirror of
https://github.com/luminous-devs/lume.git
synced 2024-09-19 19:46:34 +00:00
updated onboarding, include UI & UX
This commit is contained in:
parent
c77c08675a
commit
297cc2f018
@ -1,102 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"name": "jb55",
|
|
||||||
"avatar": "https://pbs.twimg.com/profile_images/1362882895669436423/Jzsp1Ikr.jpg",
|
|
||||||
"npub": "npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "jack",
|
|
||||||
"avatar": "https://pbs.twimg.com/profile_images/1115644092329758721/AFjOr-K8.jpg",
|
|
||||||
"npub": "npub1sg6plzptd64u62a878hep2kev88swjh3tw00gjsfl8f237lmu63q0uf63m"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "derekmoss",
|
|
||||||
"avatar": "https://pbs.twimg.com/profile_images/1609534946435076096/Gl1xeTPP.jpg",
|
|
||||||
"npub": "npub18ams6ewn5aj2n3wt2qawzglx9mr4nzksxhvrdc4gzrecw7n5tvjqctp424"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ODELL",
|
|
||||||
"avatar": "https://pbs.twimg.com/profile_images/1421584695746338819/Z_7ZfAeP.jpg",
|
|
||||||
"npub": "npub1qny3tkh0acurzla8x3zy4nhrjz5zd8l9sy9jys09umwng00manysew95gx"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "yeg0rpetrov",
|
|
||||||
"avatar": "https://pbs.twimg.com/profile_images/1593772940126035968/D_LQYRd9.jpg",
|
|
||||||
"npub": "npub1z4m7gkva6yxgvdyclc7zp0vz4ta0s2d9jh8g83w03tp5vdf3kzdsxana6p"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "PrestonPysh",
|
|
||||||
"avatar": "https://pbs.twimg.com/profile_images/1408783276081299462/f4Ye5n7-.jpg",
|
|
||||||
"npub": "npub1s5yq6wadwrxde4lhfs56gn64hwzuhnfa6r9mj476r5s4hkunzgzqrs6q7z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "fiatjaf",
|
|
||||||
"avatar": "https://pbs.twimg.com/profile_images/539211568035004416/sBMjPR9q.jpeg",
|
|
||||||
"npub": "npub180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsyjh6w6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "dergigi",
|
|
||||||
"avatar": "https://pbs.twimg.com/profile_images/1566370176446119943/UeuACt-4.jpg",
|
|
||||||
"npub": "npub1dergggklka99wwrs92yz8wdjs952h2ux2ha2ed598ngwu9w7a6fsh9xzpc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "hodlonaut",
|
|
||||||
"avatar": "https://pbs.twimg.com/profile_images/1570910274755911682/z8DJsufc.jpg",
|
|
||||||
"npub": "npub1cjw49ftnxene9wdxujz3tp7zspp0kf862cjud4nm3j2usag6eg2smwj2rh"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "DylanLeClair_",
|
|
||||||
"avatar": "https://pbs.twimg.com/profile_images/1599858581611941922/XxvPPWAt.jpg",
|
|
||||||
"npub": "npub1pyp9fqq60689ppds9ec3vghsm7s6s4grfya0y342g2hs3a0y6t0segc0qq"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ShadowOfNakadai",
|
|
||||||
"avatar": "https://pbs.twimg.com/profile_images/1620811984374464514/V7GJo1ak.jpg",
|
|
||||||
"npub": "npub1sqaxzwvh5fhgw9q3d7v658ucapvfeds3dcd2587fcwyesn7dnwuqt2r45v"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "jackmallers",
|
|
||||||
"avatar": "https://pbs.twimg.com/profile_images/1599778945699909632/O0qc9ykA.jpg",
|
|
||||||
"npub": "npub1cn4t4cd78nm900qc2hhqte5aa8c9njm6qkfzw95tszufwcwtcnsq7g3vle"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "remroya",
|
|
||||||
"avatar": "https://pbs.twimg.com/profile_images/1616979727515881478/5ABZzBYO.jpg",
|
|
||||||
"npub": "npub1csamkk8zu67zl9z4wkp90a462v53q775aqn5q6xzjdkxnkvcpd7srtz4x9"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "TakumiHisoka",
|
|
||||||
"avatar": "https://pbs.twimg.com/profile_images/1623286991944302594/cXSJ04BF.jpg",
|
|
||||||
"npub": "npub1yc8jxnzkzm2esndrqdae6lza6qlwzxpcz9drpy699j9k7xetrpkqgvkwe9"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "EvelinSchallert",
|
|
||||||
"avatar": "https://pbs.twimg.com/profile_images/1448008447983763457/7k07LJxQ.jpg",
|
|
||||||
"npub": "npub1l2gvp9wxajsl6wqnh6eulvz5sdk05gtajjwjn2yn45s9yvfru2kqf3r0gm"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "peer",
|
|
||||||
"avatar": "https://pbs.twimg.com/profile_images/1623291991709700097/aBL_VpMC.jpg",
|
|
||||||
"npub": "npub18zx8lw3947pghsgzqv2t0x8pe767sscag5djgj5afr755xkqd97qt530pr"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "francispouliot_",
|
|
||||||
"avatar": "https://pbs.twimg.com/profile_images/1524789480439283719/5Q_XBKGb.jpg",
|
|
||||||
"npub": "npub1t289s8ck5qfwynf2vsq49t2kypvvkpj7rhegayrur0ag9s2sezaqgunkzs"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "lanyihou",
|
|
||||||
"avatar": "https://pbs.twimg.com/profile_images/1603653816175689729/Ctj5GXPt.jpg",
|
|
||||||
"npub": "npub18hywyhcnn5rqhlgu80yxeyf57fyhghlrc54dzaqyd9vtts949u9s24rtva"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "marttimalmi",
|
|
||||||
"avatar": "https://pbs.twimg.com/profile_images/1125299725828272129/n8NDo1LN.png",
|
|
||||||
"npub": "npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Snowden",
|
|
||||||
"avatar": "https://pbs.twimg.com/profile_images/648888480974508032/66_cUYfj.jpg",
|
|
||||||
"npub": "npub1sn0wdenkukak0d9dfczzeacvhkrgz92ak56egt7vdgzn8pv2wfqqhrjdv9"
|
|
||||||
}
|
|
||||||
]
|
|
@ -1,3 +1,3 @@
|
|||||||
export default function BaseLayout({ children }: { children: React.ReactNode }) {
|
export default function BaseLayout({ children }: { children: React.ReactNode }) {
|
||||||
return <div className="h-screen w-screen bg-white text-zinc-900 dark:bg-near-black dark:text-white">{children}</div>;
|
return <div className="h-screen w-screen bg-zinc-50 text-zinc-900 dark:bg-black dark:text-white">{children}</div>;
|
||||||
}
|
}
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
export default function FullscreenLayout({ children }: { children: React.ReactNode }) {
|
|
||||||
return (
|
|
||||||
<div className="bg-gradient-radial-page relative h-full overflow-hidden">
|
|
||||||
{/* dragging area */}
|
|
||||||
<div data-tauri-drag-region className="absolute top-0 left-0 z-20 h-16 w-full bg-transparent" />
|
|
||||||
{/* end dragging area */}
|
|
||||||
{/* content */}
|
|
||||||
<div className="relative z-10 h-full">{children}</div>
|
|
||||||
{/* end content */}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
export default function OnboardingLayout({ children }: { children: React.ReactNode }) {
|
|
||||||
return (
|
|
||||||
<div className="flex h-full w-full flex-row">
|
|
||||||
<div className="relative h-full w-[70px] shrink-0 border-r border-zinc-900">
|
|
||||||
<div data-tauri-drag-region className="absolute top-0 left-0 h-12 w-full" />
|
|
||||||
</div>
|
|
||||||
<div className="grid grow grid-cols-4">
|
|
||||||
<div className="col-span-1"></div>
|
|
||||||
<div className="col-span-3 m-3 ml-0 overflow-hidden rounded-lg bg-zinc-900 shadow-md ring-1 ring-inset dark:shadow-black/10 dark:ring-white/10">
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,16 +1,14 @@
|
|||||||
import BaseLayout from '@layouts/base';
|
import BaseLayout from '@layouts/base';
|
||||||
import FullscreenLayout from '@layouts/fullscreen';
|
|
||||||
|
|
||||||
import LumeSymbol from '@assets/icons/Lume';
|
import LumeSymbol from '@assets/icons/Lume';
|
||||||
import { useLocalStorage } from '@rehooks/local-storage';
|
import { useLocalStorage } from '@rehooks/local-storage';
|
||||||
import { motion } from 'framer-motion';
|
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useEffect, useRef, useState } from 'react';
|
import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const [currentUser]: any = useLocalStorage('current-user');
|
const [currentUser]: object[] = useLocalStorage('current-user');
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const timer = useRef(null);
|
const timer = useRef(null);
|
||||||
|
|
||||||
@ -18,7 +16,7 @@ export default function Page() {
|
|||||||
if (currentUser) {
|
if (currentUser) {
|
||||||
timer.current = setTimeout(() => {
|
timer.current = setTimeout(() => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
router.push('/newsfeed/following');
|
router.push('/newsfeed/circle');
|
||||||
}, 1000);
|
}, 1000);
|
||||||
} else {
|
} else {
|
||||||
timer.current = setTimeout(() => {
|
timer.current = setTimeout(() => {
|
||||||
@ -34,32 +32,24 @@ export default function Page() {
|
|||||||
}, [currentUser, router]);
|
}, [currentUser, router]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex h-full flex-col items-center justify-between">
|
<div className="relative h-full overflow-hidden">
|
||||||
<div>{/* spacer */}</div>
|
{/* dragging area */}
|
||||||
<div className="flex flex-col items-center gap-4">
|
<div data-tauri-drag-region className="absolute top-0 left-0 z-20 h-16 w-full bg-transparent" />
|
||||||
<motion.div layoutId="logo" className="relative">
|
{/* end dragging area */}
|
||||||
<LumeSymbol className="h-16 w-16 text-white" />
|
<div className="relative flex h-full flex-col items-center justify-center">
|
||||||
</motion.div>
|
<div className="flex flex-col items-center gap-2">
|
||||||
<div className="flex flex-col items-center gap-0.5">
|
<LumeSymbol className="h-16 w-16 text-black dark:text-white" />
|
||||||
<motion.h2
|
<div className="text-center">
|
||||||
layoutId="subtitle"
|
<h3 className="text-lg font-semibold text-zinc-900 dark:text-zinc-100">Did you know?</h3>
|
||||||
className="bg-gradient-to-br from-zinc-200 to-zinc-400 bg-clip-text text-4xl font-medium text-transparent"
|
<p className="font-medium text-zinc-300 dark:text-zinc-600">
|
||||||
>
|
no one can't stop you use bitcoin and nostr
|
||||||
A censorship-resistant social network
|
</p>
|
||||||
</motion.h2>
|
</div>
|
||||||
<motion.h1
|
|
||||||
layoutId="title"
|
|
||||||
className="bg-[radial-gradient(ellipse_at_bottom_right,_var(--tw-gradient-stops))] from-gray-300 via-fuchsia-600 to-orange-600 bg-clip-text text-5xl font-bold text-transparent"
|
|
||||||
>
|
|
||||||
built on nostr
|
|
||||||
</motion.h1>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="absolute bottom-16 left-1/2 -translate-x-1/2 transform">
|
||||||
<div className="flex items-center gap-2 pb-16">
|
{loading && (
|
||||||
<div className="h-10">
|
|
||||||
{loading ? (
|
|
||||||
<svg
|
<svg
|
||||||
className="h-5 w-5 animate-spin text-white"
|
className="h-5 w-5 animate-spin text-black dark:text-white"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
fill="none"
|
fill="none"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
@ -71,32 +61,9 @@ export default function Page() {
|
|||||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||||
></path>
|
></path>
|
||||||
</svg>
|
</svg>
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* background */}
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-r from-fuchsia-400/10 to-orange-100/10 opacity-100 [mask-image:radial-gradient(farthest-side_at_top,white,transparent)]">
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
className="dark:fill-white/2.5 absolute inset-x-0 inset-y-[-50%] h-[200%] w-full skew-y-[-18deg] fill-black/40 stroke-black/50 mix-blend-overlay dark:stroke-white/5"
|
|
||||||
>
|
|
||||||
<defs>
|
|
||||||
<pattern id=":R11d6:" width="72" height="56" patternUnits="userSpaceOnUse" x="-12" y="4">
|
|
||||||
<path d="M.5 56V.5H72" fill="none"></path>
|
|
||||||
</pattern>
|
|
||||||
</defs>
|
|
||||||
<rect width="100%" height="100%" strokeWidth="0" fill="url(#:R11d6:)"></rect>
|
|
||||||
<svg x="-12" y="4" className="overflow-visible">
|
|
||||||
<rect strokeWidth="0" width="73" height="57" x="288" y="168"></rect>
|
|
||||||
<rect strokeWidth="0" width="73" height="57" x="144" y="56"></rect>
|
|
||||||
<rect strokeWidth="0" width="73" height="57" x="504" y="168"></rect>
|
|
||||||
<rect strokeWidth="0" width="73" height="57" x="720" y="336"></rect>
|
|
||||||
</svg>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
{/* end background */}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -110,9 +77,5 @@ Page.getLayout = function getLayout(
|
|||||||
| ReactFragment
|
| ReactFragment
|
||||||
| ReactPortal
|
| ReactPortal
|
||||||
) {
|
) {
|
||||||
return (
|
return <BaseLayout>{page}</BaseLayout>;
|
||||||
<BaseLayout>
|
|
||||||
<FullscreenLayout>{page}</FullscreenLayout>
|
|
||||||
</BaseLayout>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
@ -6,7 +6,7 @@ import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal } from
|
|||||||
export default function Page() {
|
export default function Page() {
|
||||||
return (
|
return (
|
||||||
<div className="h-full w-full">
|
<div className="h-full w-full">
|
||||||
<p>Global</p>
|
<p>Circle Newsfeed</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
import BaseLayout from '@layouts/base';
|
|
||||||
import NewsFeedLayout from '@layouts/newsfeed';
|
|
||||||
|
|
||||||
import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal } from 'react';
|
|
||||||
|
|
||||||
export default function Page() {
|
|
||||||
return (
|
|
||||||
<div className="h-full w-full">
|
|
||||||
<p>Global</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Page.getLayout = function getLayout(
|
|
||||||
page:
|
|
||||||
| string
|
|
||||||
| number
|
|
||||||
| boolean
|
|
||||||
| ReactElement<unknown, string | JSXElementConstructor<unknown>>
|
|
||||||
| ReactFragment
|
|
||||||
| ReactPortal
|
|
||||||
) {
|
|
||||||
return (
|
|
||||||
<BaseLayout>
|
|
||||||
<NewsFeedLayout>{page}</NewsFeedLayout>
|
|
||||||
</BaseLayout>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,12 +1,10 @@
|
|||||||
import BaseLayout from '@layouts/base';
|
import BaseLayout from '@layouts/base';
|
||||||
import OnboardingLayout from '@layouts/onboarding';
|
|
||||||
|
|
||||||
import { DatabaseContext } from '@components/contexts/database';
|
import { DatabaseContext } from '@components/contexts/database';
|
||||||
import { RelayContext } from '@components/contexts/relay';
|
import { RelayContext } from '@components/contexts/relay';
|
||||||
|
|
||||||
import { EyeClosedIcon, EyeOpenIcon } from '@radix-ui/react-icons';
|
import { EyeClosedIcon, EyeOpenIcon } from '@radix-ui/react-icons';
|
||||||
import { useLocalStorage, writeStorage } from '@rehooks/local-storage';
|
import { useLocalStorage, writeStorage } from '@rehooks/local-storage';
|
||||||
import { motion } from 'framer-motion';
|
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { generatePrivateKey, getEventHash, getPublicKey, nip19, signEvent } from 'nostr-tools';
|
import { generatePrivateKey, getEventHash, getPublicKey, nip19, signEvent } from 'nostr-tools';
|
||||||
@ -100,115 +98,107 @@ export default function Page() {
|
|||||||
// redirect to pre-follow
|
// redirect to pre-follow
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
router.push('/onboarding/create/pre-follows');
|
router.push('/');
|
||||||
}, 1500);
|
}, 1500);
|
||||||
})
|
})
|
||||||
.catch(console.error);
|
.catch(console.error);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full flex-col justify-between px-8">
|
<div className="grid h-full w-full grid-rows-5">
|
||||||
<div>{/* spacer */}</div>
|
<div className="row-span-1 flex items-center justify-center">
|
||||||
<motion.div layoutId="form">
|
|
||||||
<div className="mb-8 flex flex-col gap-3">
|
<div className="mb-8 flex flex-col gap-3">
|
||||||
<motion.h1
|
<h1 className="bg-gradient-to-br from-zinc-200 to-zinc-400 bg-clip-text text-3xl font-medium text-transparent">
|
||||||
layoutId="title"
|
Create new account
|
||||||
className="bg-gradient-to-br from-zinc-200 to-zinc-400 bg-clip-text text-3xl font-medium text-transparent"
|
</h1>
|
||||||
>
|
|
||||||
Create new key
|
|
||||||
</motion.h1>
|
|
||||||
<motion.h2 layoutId="subtitle" className="w-3/4 text-zinc-400">
|
|
||||||
Lume will generate key with default profile for you, you can edit it later, and please store your key safely
|
|
||||||
so you can restore your account or use other client
|
|
||||||
</motion.h2>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-4">
|
</div>
|
||||||
<div className="flex flex-col gap-1">
|
<div className="row-span-4">
|
||||||
<label className="text-sm font-semibold text-zinc-400">Public Key</label>
|
<div className="mx-auto w-full max-w-md">
|
||||||
<div className="relative shrink-0 before:pointer-events-none before:absolute before:-inset-1 before:rounded-[11px] before:border before:border-blue-500 before:opacity-0 before:ring-2 before:ring-blue-500/20 before:transition after:pointer-events-none after:absolute after:inset-px after:rounded-[7px] after:shadow-highlight after:shadow-white/5 after:transition focus-within:before:opacity-100 focus-within:after:shadow-blue-500/100 dark:focus-within:after:shadow-blue-500/20">
|
<div className="mb-8 flex flex-col gap-4">
|
||||||
<input
|
<div className="flex flex-col gap-1">
|
||||||
readOnly
|
<label className="text-sm font-semibold text-zinc-400">Public Key</label>
|
||||||
value={npub}
|
<div className="relative shrink-0 before:pointer-events-none before:absolute before:-inset-1 before:rounded-[11px] before:border before:border-blue-500 before:opacity-0 before:ring-2 before:ring-blue-500/20 before:transition after:pointer-events-none after:absolute after:inset-px after:rounded-[7px] after:shadow-highlight after:shadow-white/5 after:transition focus-within:before:opacity-100 focus-within:after:shadow-blue-500/100 dark:focus-within:after:shadow-blue-500/20">
|
||||||
className="relative w-full rounded-lg border border-black/5 px-3.5 py-2 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-600"
|
<input
|
||||||
/>
|
readOnly
|
||||||
|
value={npub}
|
||||||
|
className="relative w-full rounded-lg border border-black/5 px-3.5 py-2.5 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-600"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="flex flex-col gap-1">
|
||||||
<div className="flex flex-col gap-1">
|
<label className="text-sm font-semibold text-zinc-400">Private Key</label>
|
||||||
<label className="text-sm font-semibold text-zinc-400">Private Key</label>
|
<div className="relative shrink-0 before:pointer-events-none before:absolute before:-inset-1 before:rounded-[11px] before:border before:border-blue-500 before:opacity-0 before:ring-2 before:ring-blue-500/20 before:transition after:pointer-events-none after:absolute after:inset-px after:rounded-[7px] after:shadow-highlight after:shadow-white/5 after:transition focus-within:before:opacity-100 focus-within:after:shadow-blue-500/100 dark:focus-within:after:shadow-blue-500/20">
|
||||||
<div className="relative shrink-0 before:pointer-events-none before:absolute before:-inset-1 before:rounded-[11px] before:border before:border-blue-500 before:opacity-0 before:ring-2 before:ring-blue-500/20 before:transition after:pointer-events-none after:absolute after:inset-px after:rounded-[7px] after:shadow-highlight after:shadow-white/5 after:transition focus-within:before:opacity-100 focus-within:after:shadow-blue-500/100 dark:focus-within:after:shadow-blue-500/20">
|
<input
|
||||||
<input
|
readOnly
|
||||||
readOnly
|
type={type}
|
||||||
type={type}
|
value={nsec}
|
||||||
value={nsec}
|
className="relative w-full rounded-lg border border-black/5 px-3.5 py-2.5 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-600"
|
||||||
className="relative w-full rounded-lg border border-black/5 px-3.5 py-2 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-600"
|
/>
|
||||||
/>
|
<button
|
||||||
<button
|
onClick={() => showPrivateKey()}
|
||||||
onClick={() => showPrivateKey()}
|
className="group absolute right-2 top-1/2 -translate-y-1/2 transform rounded p-1 hover:bg-zinc-700"
|
||||||
className="group absolute right-2 top-1/2 -translate-y-1/2 transform rounded p-1 hover:bg-zinc-700"
|
>
|
||||||
>
|
{type === 'password' ? (
|
||||||
{type === 'password' ? (
|
<EyeClosedIcon className="h-5 w-5 text-zinc-500 group-hover:text-zinc-200" />
|
||||||
<EyeClosedIcon className="h-5 w-5 text-zinc-500 group-hover:text-zinc-200" />
|
) : (
|
||||||
) : (
|
<EyeOpenIcon className="h-5 w-5 text-zinc-500 group-hover:text-zinc-200" />
|
||||||
<EyeOpenIcon className="h-5 w-5 text-zinc-500 group-hover:text-zinc-200" />
|
)}
|
||||||
)}
|
</button>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="flex flex-col gap-1">
|
||||||
<div className="flex flex-col gap-1">
|
<label className="text-sm font-semibold text-zinc-400">Default Profile (you can change it later)</label>
|
||||||
<label className="text-sm font-semibold text-zinc-400">Default Profile (you can change it later)</label>
|
<div className="relative w-full shrink-0 before:pointer-events-none before:absolute before:-inset-1 before:rounded-[11px] before:border before:border-blue-500 before:opacity-0 before:ring-2 before:ring-blue-500/20 before:transition after:pointer-events-none after:absolute after:inset-px after:rounded-[7px] after:shadow-highlight after:shadow-white/5 after:transition focus-within:before:opacity-100 focus-within:after:shadow-blue-500/100 dark:focus-within:after:shadow-blue-500/20">
|
||||||
<div className="relative max-w-sm shrink-0 before:pointer-events-none before:absolute before:-inset-1 before:rounded-[11px] before:border before:border-blue-500 before:opacity-0 before:ring-2 before:ring-blue-500/20 before:transition after:pointer-events-none after:absolute after:inset-px after:rounded-[7px] after:shadow-highlight after:shadow-white/5 after:transition focus-within:before:opacity-100 focus-within:after:shadow-blue-500/100 dark:focus-within:after:shadow-blue-500/20">
|
<div className="relative w-full rounded-lg border border-black/5 px-3.5 py-4 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-600">
|
||||||
<div className="relative max-w-sm rounded-lg border border-black/5 px-3.5 py-4 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-600">
|
<div className="flex space-x-4">
|
||||||
<div className="flex space-x-4">
|
<div className="relative h-10 w-10 rounded-full">
|
||||||
<div className="relative h-10 w-10 rounded-full">
|
<Image className="inline-block rounded-full" src={data.picture} alt="" fill={true} />
|
||||||
<Image className="inline-block rounded-full" src={data.picture} alt="" fill={true} />
|
|
||||||
</div>
|
|
||||||
<div className="flex-1 space-y-4 py-1">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<p className="font-semibold">{data.display_name}</p>
|
|
||||||
<p className="text-zinc-400">@{data.username}</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-3">
|
<div className="flex-1 space-y-4 py-1">
|
||||||
<div className="grid grid-cols-3 gap-4">
|
<div className="flex items-center gap-2">
|
||||||
<div className="col-span-2 h-2 rounded bg-zinc-700"></div>
|
<p className="font-semibold">{data.display_name}</p>
|
||||||
<div className="col-span-1 h-2 rounded bg-zinc-700"></div>
|
<p className="text-zinc-400">@{data.username}</p>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="grid grid-cols-3 gap-4">
|
||||||
|
<div className="col-span-2 h-2 rounded bg-zinc-700"></div>
|
||||||
|
<div className="col-span-1 h-2 rounded bg-zinc-700"></div>
|
||||||
|
</div>
|
||||||
|
<div className="h-2 rounded bg-zinc-700"></div>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-2 rounded bg-zinc-700"></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="flex h-10 items-center justify-center">
|
||||||
</motion.div>
|
{loading === true ? (
|
||||||
<motion.div layoutId="action" className="pb-5">
|
<svg
|
||||||
<div className="flex h-10 items-center">
|
className="h-5 w-5 animate-spin text-white"
|
||||||
{loading === true ? (
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
<svg
|
fill="none"
|
||||||
className="h-5 w-5 animate-spin text-white"
|
viewBox="0 0 24 24"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
>
|
||||||
fill="none"
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||||
viewBox="0 0 24 24"
|
<path
|
||||||
>
|
className="opacity-75"
|
||||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
fill="currentColor"
|
||||||
<path
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||||
className="opacity-75"
|
></path>
|
||||||
fill="currentColor"
|
</svg>
|
||||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
) : (
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
) : (
|
|
||||||
<div className="relative shrink-0 before:pointer-events-none before:absolute before:-inset-1 before:rounded-[11px] before:border before:border-blue-500 before:opacity-0 before:ring-2 before:ring-blue-500/20 before:transition after:pointer-events-none after:absolute after:inset-px after:rounded-[7px] after:shadow-highlight after:shadow-white/5 after:transition focus-within:before:opacity-100 focus-within:after:shadow-blue-500/100 dark:focus-within:after:shadow-blue-500/20">
|
|
||||||
<button
|
<button
|
||||||
onClick={() => createAccount()}
|
onClick={() => createAccount()}
|
||||||
className="transform rounded-lg border border-white/10 bg-[radial-gradient(ellipse_at_bottom_right,_var(--tw-gradient-stops))] from-gray-300 via-fuchsia-600 to-orange-600 px-3.5 py-2 font-medium shadow-input shadow-black/5 active:translate-y-1 disabled:cursor-not-allowed disabled:opacity-50 dark:shadow-black/10"
|
className="w-full transform rounded-lg bg-gradient-to-r from-fuchsia-300 via-orange-100 to-amber-300 px-3.5 py-2.5 font-medium text-zinc-800 active:translate-y-1 disabled:cursor-not-allowed disabled:opacity-30"
|
||||||
>
|
>
|
||||||
<span className="drop-shadow-lg">Continue →</span>
|
<span className="drop-shadow-lg">Continue →</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
)}
|
||||||
)}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -222,9 +212,5 @@ Page.getLayout = function getLayout(
|
|||||||
| ReactFragment
|
| ReactFragment
|
||||||
| ReactPortal
|
| ReactPortal
|
||||||
) {
|
) {
|
||||||
return (
|
return <BaseLayout>{page}</BaseLayout>;
|
||||||
<BaseLayout>
|
|
||||||
<OnboardingLayout>{page}</OnboardingLayout>
|
|
||||||
</BaseLayout>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
@ -1,147 +0,0 @@
|
|||||||
import BaseLayout from '@layouts/base';
|
|
||||||
import OnboardingLayout from '@layouts/onboarding';
|
|
||||||
|
|
||||||
import { DatabaseContext } from '@components/contexts/database';
|
|
||||||
|
|
||||||
import { truncate } from '@utils/truncate';
|
|
||||||
|
|
||||||
import data from '@assets/directory.json';
|
|
||||||
import { CheckCircledIcon } from '@radix-ui/react-icons';
|
|
||||||
import { useLocalStorage } from '@rehooks/local-storage';
|
|
||||||
import { motion } from 'framer-motion';
|
|
||||||
import Image from 'next/image';
|
|
||||||
import { useRouter } from 'next/router';
|
|
||||||
import { nip19 } from 'nostr-tools';
|
|
||||||
import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useContext, useState } from 'react';
|
|
||||||
|
|
||||||
const shuffle = (arr: { name: string; avatar: string; npub: string }[]) => [...arr].sort(() => Math.random() - 0.5);
|
|
||||||
|
|
||||||
export default function Page() {
|
|
||||||
const { db }: any = useContext(DatabaseContext);
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const [follow, setFollow] = useState([]);
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [list] = useState(shuffle(data));
|
|
||||||
const [currentUser]: any = useLocalStorage('current-user');
|
|
||||||
|
|
||||||
const followUser = (e) => {
|
|
||||||
const npub = e.currentTarget.getAttribute('data-npub');
|
|
||||||
setFollow((arr) => [...arr, npub]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const insertDB = async () => {
|
|
||||||
// self follow
|
|
||||||
await db.execute(
|
|
||||||
`INSERT INTO follows (pubkey, account, kind) VALUES ("${currentUser.id}", "${currentUser.id}", "0")`
|
|
||||||
);
|
|
||||||
// follow selected
|
|
||||||
follow.forEach(async (npub) => {
|
|
||||||
const { data } = nip19.decode(npub);
|
|
||||||
await db.execute(`INSERT INTO follows (pubkey, account, kind) VALUES ("${data}", "${currentUser.id}", "0")`);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const createFollowing = async () => {
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
insertDB().then(() =>
|
|
||||||
setTimeout(() => {
|
|
||||||
setLoading(false);
|
|
||||||
router.push('/');
|
|
||||||
}, 1500)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex h-full flex-col justify-between px-8">
|
|
||||||
<div>{/* spacer */}</div>
|
|
||||||
<motion.div layoutId="form" className="flex flex-col">
|
|
||||||
<div className="mb-8 flex flex-col gap-3">
|
|
||||||
<motion.h1
|
|
||||||
layoutId="title"
|
|
||||||
className="bg-gradient-to-br from-zinc-200 to-zinc-400 bg-clip-text text-3xl font-medium text-transparent"
|
|
||||||
>
|
|
||||||
Choose 10 people you want to following
|
|
||||||
</motion.h1>
|
|
||||||
<motion.h2 layoutId="subtitle" className="w-3/4 text-zinc-400">
|
|
||||||
For better experiences, you should follow the people you care about to personalize your newsfeed, otherwise
|
|
||||||
you will be very bored
|
|
||||||
</motion.h2>
|
|
||||||
</div>
|
|
||||||
<div className="h-full w-full shrink">
|
|
||||||
<div className="scrollbar-hide grid grid-cols-3 gap-4 overflow-y-auto">
|
|
||||||
{list.map((item, index) => (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
onClick={(e) => followUser(e)}
|
|
||||||
data-npub={item.npub}
|
|
||||||
className={`col-span-1 inline-flex cursor-pointer items-center gap-3 rounded-lg p-2 hover:bg-zinc-700 ${
|
|
||||||
follow.includes(item.npub) ? 'bg-zinc-800' : ''
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div className="relative h-10 w-10 flex-shrink-0">
|
|
||||||
<Image className="rounded-full object-cover" src={item.avatar} alt={item.name} fill={true} />
|
|
||||||
</div>
|
|
||||||
<div className="inline-flex flex-1 items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<p className="truncate text-sm font-medium text-zinc-200">{item.name}</p>
|
|
||||||
<p className="text-sm leading-tight text-zinc-500">{truncate(item.npub, 16, ' .... ')}</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{follow.includes(item.npub) ? <CheckCircledIcon className="h-4 w-4 text-green-500" /> : <></>}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
<motion.div layoutId="action" className="pb-5">
|
|
||||||
<div className="flex h-10 items-center">
|
|
||||||
{loading === true ? (
|
|
||||||
<svg
|
|
||||||
className="h-5 w-5 animate-spin text-white"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
|
||||||
<path
|
|
||||||
className="opacity-75"
|
|
||||||
fill="currentColor"
|
|
||||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
) : (
|
|
||||||
<div className="relative shrink-0 before:pointer-events-none before:absolute before:-inset-1 before:rounded-[11px] before:border before:border-blue-500 before:opacity-0 before:ring-2 before:ring-blue-500/20 before:transition after:pointer-events-none after:absolute after:inset-px after:rounded-[7px] after:shadow-highlight after:shadow-white/5 after:transition focus-within:before:opacity-100 focus-within:after:shadow-blue-500/100 dark:focus-within:after:shadow-blue-500/20">
|
|
||||||
<button
|
|
||||||
onClick={() => createFollowing()}
|
|
||||||
disabled={follow.length < 10 ? true : false}
|
|
||||||
className="transform rounded-lg border border-white/10 bg-[radial-gradient(ellipse_at_bottom_right,_var(--tw-gradient-stops))] from-gray-300 via-fuchsia-600 to-orange-600 px-3.5 py-2 font-medium shadow-input shadow-black/5 active:translate-y-1 disabled:cursor-not-allowed disabled:opacity-50 dark:shadow-black/10"
|
|
||||||
>
|
|
||||||
<span className="drop-shadow-lg">Finish →</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Page.getLayout = function getLayout(
|
|
||||||
page:
|
|
||||||
| string
|
|
||||||
| number
|
|
||||||
| boolean
|
|
||||||
| ReactElement<unknown, string | JSXElementConstructor<unknown>>
|
|
||||||
| ReactFragment
|
|
||||||
| ReactPortal
|
|
||||||
) {
|
|
||||||
return (
|
|
||||||
<BaseLayout>
|
|
||||||
<OnboardingLayout>{page}</OnboardingLayout>
|
|
||||||
</BaseLayout>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,47 +1,33 @@
|
|||||||
import BaseLayout from '@layouts/base';
|
import BaseLayout from '@layouts/base';
|
||||||
import OnboardingLayout from '@layouts/onboarding';
|
|
||||||
|
|
||||||
import { motion } from 'framer-motion';
|
import { ArrowRightIcon } from '@radix-ui/react-icons';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal } from 'react';
|
import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal } from 'react';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full flex-col justify-between px-8">
|
<div className="grid h-full w-full grid-rows-5">
|
||||||
<div>{/* spacer */}</div>
|
<div className="row-span-3 overflow-hidden p-4"></div>
|
||||||
<div className="flex flex-col gap-3">
|
<div className="row-span-2 flex w-full flex-col items-center gap-8 overflow-hidden pt-10">
|
||||||
<motion.h1
|
<h1 className="animate-moveBg bg-gradient-to-r from-fuchsia-300 via-orange-100 to-amber-300 bg-clip-text text-5xl font-bold leading-none text-transparent">
|
||||||
layoutId="title"
|
Let's start!
|
||||||
className="bg-gradient-to-br from-zinc-200 to-zinc-400 bg-clip-text text-3xl font-medium text-transparent"
|
</h1>
|
||||||
>
|
<div className="mt-4 flex flex-col items-center gap-1.5">
|
||||||
Other social network require email/password
|
|
||||||
<br />
|
|
||||||
nostr use{' '}
|
|
||||||
<span className="bg-[radial-gradient(ellipse_at_bottom_right,_var(--tw-gradient-stops))] from-gray-300 via-fuchsia-600 to-orange-600 bg-clip-text text-transparent">
|
|
||||||
public/private key instead
|
|
||||||
</span>
|
|
||||||
</motion.h1>
|
|
||||||
<motion.h2 layoutId="subtitle" className="w-3/4 text-zinc-400">
|
|
||||||
If you have used nostr before, you can import your own private key. Otherwise, you can create a new key or use
|
|
||||||
auto-generated account created by system.
|
|
||||||
</motion.h2>
|
|
||||||
<motion.div layoutId="form"></motion.div>
|
|
||||||
<motion.div layoutId="action" className="mt-4 flex gap-2">
|
|
||||||
<Link
|
<Link
|
||||||
href="/onboarding/create"
|
href="/onboarding/create"
|
||||||
className="hover:bg-zinc-900/2.5 transform rounded-lg border border-black/5 bg-zinc-800 px-3.5 py-2 font-medium ring-1 ring-inset ring-zinc-900/10 hover:text-zinc-900 active:translate-y-1 dark:text-zinc-300 dark:ring-white/10 dark:hover:bg-zinc-700 dark:hover:text-white"
|
className="relative inline-flex h-14 w-64 items-center justify-center gap-2 rounded-full bg-zinc-900 px-6 text-lg font-medium ring-1 ring-zinc-800 hover:bg-zinc-800"
|
||||||
>
|
>
|
||||||
Create new key
|
Create new key
|
||||||
|
<ArrowRightIcon className="h-5 w-5" />
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="/onboarding/login"
|
href="/onboarding/login"
|
||||||
className="hover:bg-zinc-900/2.5 transform rounded-lg border border-black/5 bg-zinc-800 px-3.5 py-2 font-medium ring-1 ring-inset ring-zinc-900/10 hover:text-zinc-900 active:translate-y-1 dark:text-zinc-300 dark:ring-white/10 dark:hover:bg-zinc-700 dark:hover:text-white"
|
className="inline-flex h-14 w-64 items-center justify-center gap-2 rounded-full px-6 text-base font-medium text-zinc-300 hover:bg-zinc-800"
|
||||||
>
|
>
|
||||||
Login with private key
|
Login with private key
|
||||||
</Link>
|
</Link>
|
||||||
</motion.div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>{/* spacer */}</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -55,9 +41,5 @@ Page.getLayout = function getLayout(
|
|||||||
| ReactFragment
|
| ReactFragment
|
||||||
| ReactPortal
|
| ReactPortal
|
||||||
) {
|
) {
|
||||||
return (
|
return <BaseLayout>{page}</BaseLayout>;
|
||||||
<BaseLayout>
|
|
||||||
<OnboardingLayout>{page}</OnboardingLayout>
|
|
||||||
</BaseLayout>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
@ -1,149 +0,0 @@
|
|||||||
import BaseLayout from '@layouts/base';
|
|
||||||
import OnboardingLayout from '@layouts/onboarding';
|
|
||||||
|
|
||||||
import { DatabaseContext } from '@components/contexts/database';
|
|
||||||
import { RelayContext } from '@components/contexts/relay';
|
|
||||||
|
|
||||||
import { useLocalStorage } from '@rehooks/local-storage';
|
|
||||||
import { motion } from 'framer-motion';
|
|
||||||
import Link from 'next/link';
|
|
||||||
import { useRouter } from 'next/router';
|
|
||||||
import { getPublicKey, nip19 } from 'nostr-tools';
|
|
||||||
import {
|
|
||||||
JSXElementConstructor,
|
|
||||||
ReactElement,
|
|
||||||
ReactFragment,
|
|
||||||
ReactPortal,
|
|
||||||
useCallback,
|
|
||||||
useContext,
|
|
||||||
useMemo,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
|
|
||||||
export default function Page() {
|
|
||||||
const { db }: any = useContext(DatabaseContext);
|
|
||||||
const relayPool: any = useContext(RelayContext);
|
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [relays] = useLocalStorage('relays');
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
const { privkey }: any = router.query;
|
|
||||||
|
|
||||||
const pubkey = useMemo(() => (privkey ? getPublicKey(privkey) : null), [privkey]);
|
|
||||||
|
|
||||||
// save account to database
|
|
||||||
const insertAccount = useCallback(
|
|
||||||
async (metadata) => {
|
|
||||||
if (loading === false) {
|
|
||||||
const npub = privkey ? nip19.npubEncode(pubkey) : null;
|
|
||||||
const nsec = privkey ? nip19.nsecEncode(privkey) : null;
|
|
||||||
await db.execute(
|
|
||||||
`INSERT OR IGNORE INTO accounts (id, privkey, npub, nsec, metadata) VALUES ("${pubkey}", "${privkey}", "${npub}", "${nsec}", '${metadata}')`
|
|
||||||
);
|
|
||||||
setLoading(true);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[db, privkey, pubkey, loading]
|
|
||||||
);
|
|
||||||
|
|
||||||
// save follows to database
|
|
||||||
const insertFollows = useCallback(
|
|
||||||
async (follows) => {
|
|
||||||
follows.forEach(async (item) => {
|
|
||||||
if (item) {
|
|
||||||
await db.execute(
|
|
||||||
`INSERT OR IGNORE INTO follows (pubkey, account, kind) VALUES ("${item[1]}", "${pubkey}", "0")`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[db, pubkey]
|
|
||||||
);
|
|
||||||
|
|
||||||
relayPool.subscribe(
|
|
||||||
[
|
|
||||||
{
|
|
||||||
authors: [pubkey],
|
|
||||||
kinds: [0, 3],
|
|
||||||
since: 0,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
relays,
|
|
||||||
(event: any) => {
|
|
||||||
if (event.kind === 0) {
|
|
||||||
insertAccount(event.content);
|
|
||||||
} else {
|
|
||||||
if (event.tags.length > 0) {
|
|
||||||
insertFollows(event.tags);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
undefined,
|
|
||||||
(events: any, relayURL: any) => {
|
|
||||||
console.log(events, relayURL);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex h-full flex-col justify-between px-8">
|
|
||||||
<div>{/* spacer */}</div>
|
|
||||||
<motion.div layoutId="form">
|
|
||||||
<div className="mb-8 flex flex-col gap-3">
|
|
||||||
<motion.h1
|
|
||||||
layoutId="title"
|
|
||||||
className="bg-gradient-to-br from-zinc-200 to-zinc-400 bg-clip-text text-3xl font-medium text-transparent"
|
|
||||||
>
|
|
||||||
Fetching your profile...
|
|
||||||
</motion.h1>
|
|
||||||
<motion.h2 layoutId="subtitle" className="w-3/4 text-zinc-400">
|
|
||||||
As long as you have private key, you alway can sync your profile and follows list on every nostr client, so
|
|
||||||
please keep your key safely
|
|
||||||
</motion.h2>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
<motion.div layoutId="action" className="pb-5">
|
|
||||||
<div className="flex h-10 items-center">
|
|
||||||
{loading === true ? (
|
|
||||||
<svg
|
|
||||||
className="h-5 w-5 animate-spin text-white"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
|
||||||
<path
|
|
||||||
className="opacity-75"
|
|
||||||
fill="currentColor"
|
|
||||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
) : (
|
|
||||||
<Link
|
|
||||||
href="/"
|
|
||||||
className="transform rounded-lg bg-[radial-gradient(ellipse_at_bottom_right,_var(--tw-gradient-stops))] from-gray-300 via-fuchsia-600 to-orange-600 px-3.5 py-2 font-medium active:translate-y-1 disabled:cursor-not-allowed disabled:opacity-30"
|
|
||||||
>
|
|
||||||
<span className="drop-shadow-lg">Finish</span>
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Page.getLayout = function getLayout(
|
|
||||||
page:
|
|
||||||
| string
|
|
||||||
| number
|
|
||||||
| boolean
|
|
||||||
| ReactElement<unknown, string | JSXElementConstructor<unknown>>
|
|
||||||
| ReactFragment
|
|
||||||
| ReactPortal
|
|
||||||
) {
|
|
||||||
return (
|
|
||||||
<BaseLayout>
|
|
||||||
<OnboardingLayout>{page}</OnboardingLayout>
|
|
||||||
</BaseLayout>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,7 +1,6 @@
|
|||||||
import BaseLayout from '@layouts/base';
|
import BaseLayout from '@layouts/base';
|
||||||
import OnboardingLayout from '@layouts/onboarding';
|
|
||||||
|
|
||||||
import { motion } from 'framer-motion';
|
import { LightningBoltIcon } from '@radix-ui/react-icons';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { nip19 } from 'nostr-tools';
|
import { nip19 } from 'nostr-tools';
|
||||||
import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal } from 'react';
|
import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal } from 'react';
|
||||||
@ -43,7 +42,7 @@ export default function Page() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
router.push({
|
router.push({
|
||||||
pathname: '/onboarding/login/fetch',
|
pathname: '/onboarding/login/step-2',
|
||||||
query: { privkey: privkey },
|
query: { privkey: privkey },
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -55,60 +54,70 @@ export default function Page() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(onSubmit)} className="flex h-full flex-col justify-between px-8">
|
<form onSubmit={handleSubmit(onSubmit)} className="grid h-full w-full grid-rows-5">
|
||||||
<div>{/* spacer */}</div>
|
<div className="row-span-1 flex items-center justify-center">
|
||||||
<motion.div layoutId="form">
|
<h1 className="bg-gradient-to-br from-zinc-200 via-white to-zinc-300 bg-clip-text text-3xl font-semibold text-transparent">
|
||||||
<div className="mb-8 flex flex-col gap-3">
|
Login with Private Key
|
||||||
<motion.h1
|
</h1>
|
||||||
layoutId="title"
|
</div>
|
||||||
className="bg-gradient-to-br from-zinc-200 to-zinc-400 bg-clip-text text-3xl font-medium text-transparent"
|
<div className="row-span-4">
|
||||||
>
|
<div className="mx-auto w-full max-w-md">
|
||||||
Import your private key
|
<div className="flex flex-col gap-4">
|
||||||
</motion.h1>
|
<div>
|
||||||
<motion.h2 layoutId="subtitle" className="w-3/4 text-zinc-400">
|
{/* #TODO: add function */}
|
||||||
You can import private key format as hex string or nsec. If you have installed Nostr Connect compality
|
<button className="inline-flex w-full transform items-center justify-center gap-1.5 rounded-lg bg-zinc-700 px-3.5 py-2.5 font-medium text-zinc-200 shadow-input ring-1 ring-zinc-600 active:translate-y-1">
|
||||||
wallet in your mobile, you can connect by scan QR Code below
|
{/* #TODO: change to nostr connect logo */}
|
||||||
</motion.h2>
|
<LightningBoltIcon className="h-5 w-5 text-fuchsia-500" />
|
||||||
</div>
|
<span>Continue with Nostr Connect</span>
|
||||||
<div className="flex flex-col gap-2">
|
</button>
|
||||||
<div className="relative shrink-0 before:pointer-events-none before:absolute before:-inset-1 before:rounded-[11px] before:border before:border-blue-500 before:opacity-0 before:ring-2 before:ring-blue-500/20 before:transition after:pointer-events-none after:absolute after:inset-px after:rounded-[7px] after:shadow-highlight after:shadow-white/5 after:transition focus-within:before:opacity-100 focus-within:after:shadow-blue-500/100 dark:focus-within:after:shadow-blue-500/20">
|
</div>
|
||||||
<input
|
<div className="relative">
|
||||||
{...register('key', { required: true, minLength: 32 })}
|
<div className="absolute inset-0 flex items-center">
|
||||||
type={'password'}
|
<div className="w-full border-t border-zinc-800"></div>
|
||||||
placeholder="Paste nsec or hex key here..."
|
</div>
|
||||||
className="relative w-full rounded-lg border border-black/5 px-3.5 py-2 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-500"
|
<div className="relative flex justify-center">
|
||||||
/>
|
<span className="bg-black px-2 text-sm text-zinc-500">or</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<div className="relative shrink-0 before:pointer-events-none before:absolute before:-inset-1 before:rounded-[11px] before:border before:border-blue-500 before:opacity-0 before:ring-2 before:ring-blue-500/20 before:transition after:pointer-events-none after:absolute after:inset-px after:rounded-[7px] after:shadow-highlight after:shadow-white/5 after:transition focus-within:before:opacity-100 focus-within:after:shadow-blue-500/100 dark:focus-within:after:shadow-blue-500/20">
|
||||||
|
<input
|
||||||
|
{...register('key', { required: true, minLength: 32 })}
|
||||||
|
type={'password'}
|
||||||
|
placeholder="Paste private key here..."
|
||||||
|
className="relative w-full rounded-lg border border-black/5 px-3.5 py-2.5 text-center shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="text-sm text-red-400">{errors.key && <p>{errors.key.message}</p>}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-1 flex h-10 items-center justify-center">
|
||||||
|
{isSubmitting ? (
|
||||||
|
<svg
|
||||||
|
className="h-5 w-5 animate-spin text-white"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||||
|
<path
|
||||||
|
className="opacity-75"
|
||||||
|
fill="currentColor"
|
||||||
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={!isDirty || !isValid}
|
||||||
|
className="w-full transform rounded-lg bg-gradient-to-r from-fuchsia-300 via-orange-100 to-amber-300 px-3.5 py-2.5 font-medium text-zinc-800 active:translate-y-1 disabled:cursor-not-allowed disabled:opacity-30"
|
||||||
|
>
|
||||||
|
<span className="drop-shadow-lg">Continue →</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm text-red-400">{errors.key && <p>{errors.key.message}</p>}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</div>
|
||||||
<motion.div layoutId="action" className="pb-5">
|
|
||||||
<div className="flex h-10 items-center">
|
|
||||||
{isSubmitting ? (
|
|
||||||
<svg
|
|
||||||
className="h-5 w-5 animate-spin text-white"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
|
||||||
<path
|
|
||||||
className="opacity-75"
|
|
||||||
fill="currentColor"
|
|
||||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
) : (
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
disabled={!isDirty || !isValid}
|
|
||||||
className="transform rounded-lg bg-[radial-gradient(ellipse_at_bottom_right,_var(--tw-gradient-stops))] from-gray-300 via-fuchsia-600 to-orange-600 px-3.5 py-2 font-medium active:translate-y-1 disabled:cursor-not-allowed disabled:opacity-30"
|
|
||||||
>
|
|
||||||
<span className="drop-shadow-lg">Continue →</span>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -122,9 +131,5 @@ Page.getLayout = function getLayout(
|
|||||||
| ReactFragment
|
| ReactFragment
|
||||||
| ReactPortal
|
| ReactPortal
|
||||||
) {
|
) {
|
||||||
return (
|
return <BaseLayout>{page}</BaseLayout>;
|
||||||
<BaseLayout>
|
|
||||||
<OnboardingLayout>{page}</OnboardingLayout>
|
|
||||||
</BaseLayout>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
147
src/pages/onboarding/login/step-2.tsx
Normal file
147
src/pages/onboarding/login/step-2.tsx
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
import BaseLayout from '@layouts/base';
|
||||||
|
|
||||||
|
import { DatabaseContext } from '@components/contexts/database';
|
||||||
|
import { RelayContext } from '@components/contexts/relay';
|
||||||
|
|
||||||
|
import { useLocalStorage } from '@rehooks/local-storage';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import { getPublicKey, nip19 } from 'nostr-tools';
|
||||||
|
import {
|
||||||
|
JSXElementConstructor,
|
||||||
|
ReactElement,
|
||||||
|
ReactFragment,
|
||||||
|
ReactPortal,
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
const { db }: any = useContext(DatabaseContext);
|
||||||
|
const relayPool: any = useContext(RelayContext);
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const { privkey }: any = router.query;
|
||||||
|
|
||||||
|
const [relays] = useLocalStorage('relays');
|
||||||
|
const [profile, setProfile] = useState({ picture: '', display_name: '', username: '' });
|
||||||
|
|
||||||
|
const pubkey = useMemo(() => (privkey ? getPublicKey(privkey) : null), [privkey]);
|
||||||
|
|
||||||
|
// save account to database
|
||||||
|
const insertAccount = useCallback(
|
||||||
|
async (metadata) => {
|
||||||
|
const npub = privkey ? nip19.npubEncode(pubkey) : null;
|
||||||
|
const nsec = privkey ? nip19.nsecEncode(privkey) : null;
|
||||||
|
// insert to database
|
||||||
|
await db.execute(
|
||||||
|
`INSERT OR IGNORE INTO accounts (id, privkey, npub, nsec, metadata) VALUES ("${pubkey}", "${privkey}", "${npub}", "${nsec}", '${metadata}')`
|
||||||
|
);
|
||||||
|
// update state
|
||||||
|
setProfile(JSON.parse(metadata));
|
||||||
|
},
|
||||||
|
[db, privkey, pubkey]
|
||||||
|
);
|
||||||
|
|
||||||
|
// save follows to database
|
||||||
|
const insertFollows = useCallback(
|
||||||
|
async (follows) => {
|
||||||
|
follows.forEach(async (item) => {
|
||||||
|
if (item) {
|
||||||
|
// insert to database
|
||||||
|
await db.execute(
|
||||||
|
`INSERT OR IGNORE INTO follows (pubkey, account, kind) VALUES ("${item[1]}", "${pubkey}", "0")`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[db, pubkey]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
relayPool.subscribe(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
authors: [pubkey],
|
||||||
|
kinds: [0, 3],
|
||||||
|
since: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
relays,
|
||||||
|
(event: any) => {
|
||||||
|
if (event.kind === 0) {
|
||||||
|
insertAccount(event.content);
|
||||||
|
} else {
|
||||||
|
if (event.tags.length > 0) {
|
||||||
|
insertFollows(event.tags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
(events: any, relayURL: any) => {
|
||||||
|
console.log(events, relayURL);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}, [insertAccount, insertFollows, pubkey, relayPool, relays]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="grid h-full w-full grid-rows-5">
|
||||||
|
<div className="row-span-1 flex items-center justify-center">
|
||||||
|
<div className="mb-8 flex flex-col gap-3">
|
||||||
|
<h1 className="bg-gradient-to-br from-zinc-200 to-zinc-400 bg-clip-text text-3xl font-medium text-transparent">
|
||||||
|
Bringing back your profile...
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="row-span-4 flex flex-col gap-8">
|
||||||
|
<div className="mx-auto w-full max-w-md">
|
||||||
|
<div className="mb-4 flex flex-col gap-2">
|
||||||
|
<div className="w-full rounded-lg bg-zinc-900 p-4 shadow-input ring-1 ring-zinc-800">
|
||||||
|
<div className="flex space-x-4">
|
||||||
|
<div className="relative h-10 w-10 rounded-full">
|
||||||
|
<Image className="inline-block rounded-full" src={profile.picture} alt="" fill={true} />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 space-y-4 py-1">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<p className="font-semibold">{profile.display_name}</p>
|
||||||
|
<span className="leading-tight text-zinc-500">·</span>
|
||||||
|
<p className="text-zinc-500">@{profile.username}</p>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="grid grid-cols-3 gap-4">
|
||||||
|
<div className="col-span-2 h-2 rounded bg-zinc-700"></div>
|
||||||
|
<div className="col-span-1 h-2 rounded bg-zinc-700"></div>
|
||||||
|
</div>
|
||||||
|
<div className="h-2 rounded bg-zinc-700"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Link
|
||||||
|
href="/"
|
||||||
|
className="inline-flex w-full transform items-center justify-center rounded-lg bg-gradient-to-r from-fuchsia-300 via-orange-100 to-amber-300 px-3.5 py-2.5 font-medium text-zinc-800 active:translate-y-1 disabled:cursor-not-allowed disabled:opacity-30"
|
||||||
|
>
|
||||||
|
<span className="drop-shadow-lg">Done →</span>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Page.getLayout = function getLayout(
|
||||||
|
page:
|
||||||
|
| string
|
||||||
|
| number
|
||||||
|
| boolean
|
||||||
|
| ReactElement<unknown, string | JSXElementConstructor<unknown>>
|
||||||
|
| ReactFragment
|
||||||
|
| ReactPortal
|
||||||
|
) {
|
||||||
|
return <BaseLayout>{page}</BaseLayout>;
|
||||||
|
};
|
@ -65,6 +65,14 @@ module.exports = {
|
|||||||
from: { opacity: 0, transform: 'translateX(2px)' },
|
from: { opacity: 0, transform: 'translateX(2px)' },
|
||||||
to: { opacity: 1, transform: 'translateX(0)' },
|
to: { opacity: 1, transform: 'translateX(0)' },
|
||||||
},
|
},
|
||||||
|
moveBg: {
|
||||||
|
'0%': { backgroundPosition: '50px' },
|
||||||
|
'20%': { backgroundPosition: '150px' },
|
||||||
|
'40%': { backgroundPosition: '250px' },
|
||||||
|
'60%': { backgroundPosition: '350px' },
|
||||||
|
'80%': { backgroundPosition: '450px' },
|
||||||
|
'100%': { backgroundPosition: '550px' },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
animation: {
|
animation: {
|
||||||
disco: 'disco 1.5s linear infinite',
|
disco: 'disco 1.5s linear infinite',
|
||||||
@ -74,6 +82,7 @@ module.exports = {
|
|||||||
slideLeftAndFade: 'slideLeftAndFade 400ms cubic-bezier(0.16, 1, 0.3, 1)',
|
slideLeftAndFade: 'slideLeftAndFade 400ms cubic-bezier(0.16, 1, 0.3, 1)',
|
||||||
slideUpAndFade: 'slideUpAndFade 400ms cubic-bezier(0.16, 1, 0.3, 1)',
|
slideUpAndFade: 'slideUpAndFade 400ms cubic-bezier(0.16, 1, 0.3, 1)',
|
||||||
slideRightAndFade: 'slideRightAndFade 400ms cubic-bezier(0.16, 1, 0.3, 1)',
|
slideRightAndFade: 'slideRightAndFade 400ms cubic-bezier(0.16, 1, 0.3, 1)',
|
||||||
|
moveBg: 'moveBg 3s ease-in-out infinite alternate running forwards',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user