feat: add interest screen to onboarding

This commit is contained in:
reya 2024-01-19 14:53:26 +07:00
parent f65175f11e
commit a3460418f6
9 changed files with 372 additions and 161 deletions

View File

@ -1,7 +1,6 @@
import { useArk } from "@lume/ark"; import { useArk } from "@lume/ark";
import { CheckIcon, ChevronDownIcon, LoaderIcon } from "@lume/icons"; import { CheckIcon, ChevronDownIcon, LoaderIcon } from "@lume/icons";
import { useStorage } from "@lume/storage"; import { useStorage } from "@lume/storage";
import { onboardingAtom } from "@lume/utils";
import NDK, { import NDK, {
NDKEvent, NDKEvent,
NDKKind, NDKKind,
@ -9,11 +8,11 @@ import NDK, {
NDKPrivateKeySigner, NDKPrivateKeySigner,
} from "@nostr-dev-kit/ndk"; } from "@nostr-dev-kit/ndk";
import * as Select from "@radix-ui/react-select"; import * as Select from "@radix-ui/react-select";
import { UnlistenFn } from "@tauri-apps/api/event";
import { desktopDir } from "@tauri-apps/api/path"; import { desktopDir } from "@tauri-apps/api/path";
import { Window } from "@tauri-apps/api/window"; import { Window } from "@tauri-apps/api/window";
import { save } from "@tauri-apps/plugin-dialog"; import { save } from "@tauri-apps/plugin-dialog";
import { writeTextFile } from "@tauri-apps/plugin-fs"; import { writeTextFile } from "@tauri-apps/plugin-fs";
import { useSetAtom } from "jotai";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { getPublicKey, nip19 } from "nostr-tools"; import { getPublicKey, nip19 } from "nostr-tools";
import { useState } from "react"; import { useState } from "react";
@ -42,7 +41,6 @@ export function CreateAccountScreen() {
const storage = useStorage(); const storage = useStorage();
const services = useLoaderData() as NDKEvent[]; const services = useLoaderData() as NDKEvent[];
const navigate = useNavigate(); const navigate = useNavigate();
const setOnboarding = useSetAtom(onboardingAtom);
const [serviceId, setServiceId] = useState(services?.[0]?.id); const [serviceId, setServiceId] = useState(services?.[0]?.id);
const [loading, setIsLoading] = useState(false); const [loading, setIsLoading] = useState(false);
@ -87,8 +85,6 @@ export function CreateAccountScreen() {
privkey: signer.privateKey, privkey: signer.privateKey,
}); });
setOnboarding(true);
return navigate("/auth/onboarding", { replace: true }); return navigate("/auth/onboarding", { replace: true });
}; };
@ -117,33 +113,47 @@ export function CreateAccountScreen() {
); );
// handle auth url request // handle auth url request
let unlisten: UnlistenFn;
let authWindow: Window; let authWindow: Window;
remoteSigner.addListener("authUrl", (authUrl: string) => { let account: string = undefined;
remoteSigner.addListener("authUrl", async (authUrl: string) => {
authWindow = new Window(`auth-${serviceId}`, { authWindow = new Window(`auth-${serviceId}`, {
url: authUrl, url: authUrl,
title: domain, title: domain,
titleBarStyle: "overlay", titleBarStyle: "overlay",
width: 415, width: 600,
height: 600, height: 650,
center: true, center: true,
closable: false, closable: false,
}); });
unlisten = await authWindow.onCloseRequested(() => {
if (!account) {
setIsLoading(false);
unlisten();
return authWindow.close();
}
});
}); });
// create new account // create new account
const account = await remoteSigner.createAccount( account = await remoteSigner.createAccount(
data.username, data.username,
domain, domain,
data.email, data.email,
); );
if (!account) { if (!account) {
authWindow.close(); unlisten();
setIsLoading(false); setIsLoading(false);
authWindow.close();
return toast.error("Failed to create new account, try again later"); return toast.error("Failed to create new account, try again later");
} }
unlisten();
authWindow.close(); authWindow.close();
// add account to storage // add account to storage
@ -165,7 +175,6 @@ export function CreateAccountScreen() {
// await ark.createEvent({ kind: NDKKind.Metadata, content: "", tags: [] }); // await ark.createEvent({ kind: NDKKind.Metadata, content: "", tags: [] });
await ark.createEvent({ kind: NDKKind.Contacts, content: "", tags: [] }); await ark.createEvent({ kind: NDKKind.Contacts, content: "", tags: [] });
setOnboarding(true);
setIsLoading(false); setIsLoading(false);
return navigate("/auth/onboarding", { replace: true }); return navigate("/auth/onboarding", { replace: true });
@ -213,7 +222,10 @@ export function CreateAccountScreen() {
minLength: 1, minLength: 1,
})} })}
spellCheck={false} spellCheck={false}
placeholder="satoshi" autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
placeholder="lume"
className="flex-1 min-w-0 text-xl bg-transparent border-transparent outline-none focus:outline-none focus:ring-0 focus:border-none h-14 ring-0 placeholder:text-neutral-600" className="flex-1 min-w-0 text-xl bg-transparent border-transparent outline-none focus:outline-none focus:ring-0 focus:border-none h-14 ring-0 placeholder:text-neutral-600"
/> />
<Select.Root value={serviceId} onValueChange={setServiceId}> <Select.Root value={serviceId} onValueChange={setServiceId}>

View File

@ -22,7 +22,10 @@ export function Navigation() {
useHotkeys("meta+n", () => setIsEditorOpen((state) => !state), []); useHotkeys("meta+n", () => setIsEditorOpen((state) => !state), []);
return ( return (
<div className="flex flex-col justify-between w-20 h-full px-4 py-3 shrink-0"> <div
data-tauri-drag-region
className="flex flex-col justify-between w-20 h-full px-4 py-3 shrink-0"
>
<div className="flex flex-col flex-1"> <div className="flex flex-col flex-1">
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
<ActiveAccount /> <ActiveAccount />

View File

@ -27,7 +27,7 @@ export function OnboardingHomeScreen() {
<div className="mt-4 flex flex-col gap-2 items-center"> <div className="mt-4 flex flex-col gap-2 items-center">
<button <button
type="button" type="button"
onClick={() => navigate("/profile-settings")} onClick={() => navigate("/profile")}
className="inline-flex items-center justify-center gap-2 w-44 font-medium h-11 rounded-xl bg-blue-100 text-blue-500 hover:bg-blue-200 dark:bg-blue-900 dark:text-blue-500 dark:hover:bg-blue-800" className="inline-flex items-center justify-center gap-2 w-44 font-medium h-11 rounded-xl bg-blue-100 text-blue-500 hover:bg-blue-200 dark:bg-blue-900 dark:text-blue-500 dark:hover:bg-blue-800"
> >
Profile Settings Profile Settings

View File

@ -0,0 +1,123 @@
import { ArrowLeftIcon, LoaderIcon } from "@lume/icons";
import { useStorage } from "@lume/storage";
import { TOPICS, cn } from "@lume/utils";
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { toast } from "sonner";
export function OnboardingInterestScreen() {
const storage = useStorage();
const navigate = useNavigate();
const [loading, setLoading] = useState(false);
const [hashtags, setHashtags] = useState([]);
const toggleHashtag = (item: string) => {
const arr = hashtags.includes(item)
? hashtags.filter((i) => i !== item)
: [...hashtags, item];
setHashtags(arr);
};
const toggleAll = (item: string[]) => {
const sets = new Set([...hashtags, ...item]);
setHashtags([...sets]);
};
const submit = async () => {
try {
setLoading(true);
if (!hashtags.length) return navigate("/finish");
const save = await storage.createSetting(
"interests",
JSON.stringify({ hashtags }),
);
setLoading(false);
if (save) return navigate("/finish");
} catch (e) {
setLoading(false);
toast.error(String(e));
}
};
return (
<div className="w-full h-full flex flex-col">
<div className="h-16 shrink-0 px-8 border-b border-neutral-100 dark:border-neutral-900 flex w-full items-center justify-between">
<div className="flex flex-col">
<h3 className="font-semibold">Interests</h3>
<p className="text-sm font-medium text-neutral-700 dark:text-neutral-300">
Pick things you'd like to see in your home feed.
</p>
</div>
</div>
<div className="w-full flex-1 min-h-0 flex flex-col justify-between">
<div className="flex-1 min-h-0 overflow-y-auto px-8 py-8">
<div className="flex flex-col gap-8">
{TOPICS.map((topic, index) => (
<div key={topic.title + index} className="flex flex-col gap-4">
<div className="w-full flex items-center justify-between">
<div className="inline-flex items-center gap-2.5">
<img
src={topic.icon}
alt={topic.title}
className="size-8 object-cover rounded-lg"
/>
<h3 className="text-lg font-semibold">{topic.title}</h3>
</div>
<button
type="button"
onClick={() => toggleAll(topic.content)}
className="text-sm font-medium text-blue-500"
>
Follow All
</button>
</div>
<div className="flex flex-wrap items-center gap-3">
{topic.content.map((hashtag) => (
<button
type="button"
onClick={() => toggleHashtag(hashtag)}
className={cn(
"inline-flex items-center rounded-full bg-neutral-100 dark:bg-neutral-900 border border-transparent px-2 py-1 text-sm font-medium",
hashtags.includes(hashtag)
? "border-blue-500 text-blue-500"
: "",
)}
>
{hashtag}
</button>
))}
</div>
</div>
))}
</div>
</div>
<div className="h-16 shrink-0 w-full flex items-center px-8 justify-center gap-2 border-t border-neutral-100 dark:border-neutral-900 bg-neutral-50 dark:bg-neutral-950">
<button
type="button"
onClick={() => navigate(-1)}
className="inline-flex h-9 flex-1 gap-2 shrink-0 items-center justify-center rounded-lg bg-neutral-100 font-medium dark:bg-neutral-900 dark:hover:bg-neutral-800 hover:bg-blue-200"
>
<ArrowLeftIcon className="size-4" />
Back
</button>
<button
type="button"
onClick={submit}
className="inline-flex h-9 flex-1 shrink-0 items-center justify-center rounded-lg bg-blue-500 font-semibold text-white hover:bg-blue-600 disabled:opacity-50"
>
{loading ? (
<LoaderIcon className="size-4 animate-spin" />
) : (
"Continue"
)}
</button>
</div>
</div>
</div>
);
}

View File

@ -11,7 +11,11 @@ export function OnboardingModal() {
<Dialog.Portal> <Dialog.Portal>
<Dialog.Overlay className="fixed inset-0 z-50 bg-black/10 backdrop-blur-sm dark:bg-white/10" /> <Dialog.Overlay className="fixed inset-0 z-50 bg-black/10 backdrop-blur-sm dark:bg-white/10" />
<Dialog.Content className="fixed inset-0 z-50 flex items-center justify-center min-h-full"> <Dialog.Content className="fixed inset-0 z-50 flex items-center justify-center min-h-full">
<div className="relative w-full max-w-lg bg-white h-[500px] rounded-xl dark:bg-black overflow-hidden"> <div
data-tauri-drag-region
className="h-20 absolute top-0 left-0 w-full"
/>
<div className="relative w-full max-w-xl xl:max-w-2xl bg-white h-[600px] xl:h-[700px] rounded-xl dark:bg-black overflow-hidden">
<OnboardingRouter /> <OnboardingRouter />
</div> </div>
</Dialog.Content> </Dialog.Content>

View File

@ -11,7 +11,7 @@ import { useNavigate } from "react-router-dom";
import { toast } from "sonner"; import { toast } from "sonner";
import { AvatarUploadButton } from "../avatarUploadButton"; import { AvatarUploadButton } from "../avatarUploadButton";
export function OnboardingProfileSettingsScreen() { export function OnboardingProfileScreen() {
const [picture, setPicture] = useState(""); const [picture, setPicture] = useState("");
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@ -32,7 +32,7 @@ export function OnboardingProfileSettingsScreen() {
if (!data.name.length && !data.about.length) { if (!data.name.length && !data.about.length) {
setLoading(false); setLoading(false);
navigate("/follow"); navigate("/interests");
} }
const oldProfile = await ark.getUserProfile(); const oldProfile = await ark.getUserProfile();
@ -41,7 +41,6 @@ export function OnboardingProfileSettingsScreen() {
...data, ...data,
lud16: "", // temporary remove lud16 lud16: "", // temporary remove lud16
nip05: oldProfile?.nip05 || "", nip05: oldProfile?.nip05 || "",
display_name: data.name,
bio: data.about, bio: data.about,
image: picture, image: picture,
picture: picture, picture: picture,
@ -62,7 +61,7 @@ export function OnboardingProfileSettingsScreen() {
); );
setLoading(false); setLoading(false);
navigate("/follow"); navigate("/interests");
} }
} catch (e) { } catch (e) {
setLoading(false); setLoading(false);
@ -72,8 +71,13 @@ export function OnboardingProfileSettingsScreen() {
return ( return (
<div className="w-full h-full flex flex-col gap-4"> <div className="w-full h-full flex flex-col gap-4">
<div className="h-12 shrink-0 px-8 border-b border-neutral-100 dark:border-neutral-900 flex font-medium text-neutral-700 dark:text-neutral-600 w-full items-center"> <div className="h-16 shrink-0 px-8 border-b border-neutral-100 dark:border-neutral-900 flex w-full items-center justify-between">
Profile Settings <div className="flex flex-col">
<h3 className="font-semibold">About you</h3>
<p className="text-sm font-medium text-neutral-700 dark:text-neutral-300">
Tell Lume about yourself to start building your home feed.
</p>
</div>
</div> </div>
<form <form
onSubmit={handleSubmit(onSubmit)} onSubmit={handleSubmit(onSubmit)}
@ -111,9 +115,10 @@ export function OnboardingProfileSettingsScreen() {
</label> </label>
<input <input
type={"text"} type={"text"}
{...register("name")} {...register("name", { required: true, minLength: 1 })}
placeholder="e.g. Alice"
spellCheck={false} spellCheck={false}
className="h-11 rounded-lg border-transparent bg-neutral-100 px-3 placeholder:text-neutral-500 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:bg-neutral-950 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800" className="h-11 rounded-lg border-transparent bg-neutral-100 px-3 placeholder:text-neutral-600 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:bg-neutral-950 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800"
/> />
</div> </div>
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
@ -122,8 +127,21 @@ export function OnboardingProfileSettingsScreen() {
</label> </label>
<textarea <textarea
{...register("about")} {...register("about")}
placeholder="e.g. Artist, anime-lover, and k-pop fan"
spellCheck={false} spellCheck={false}
className="relative h-24 w-full resize-none rounded-lg border-transparent bg-neutral-100 px-3 py-2 !outline-none placeholder:text-neutral-500 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:bg-neutral-950 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800" className="relative h-24 w-full resize-none rounded-lg border-transparent bg-neutral-100 px-3 py-2 !outline-none placeholder:text-neutral-600 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:bg-neutral-950 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800"
/>
</div>
<div className="flex flex-col gap-1">
<label htmlFor="website" className="font-medium">
Website
</label>
<input
type="url"
{...register("website")}
placeholder="e.g. https://alice.me"
spellCheck={false}
className="h-11 rounded-lg border-transparent bg-neutral-100 px-3 placeholder:text-neutral-500 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:bg-neutral-950 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800"
/> />
</div> </div>
</motion.div> </motion.div>

View File

@ -6,9 +6,9 @@ import {
UNSAFE_LocationContext, UNSAFE_LocationContext,
} from "react-router-dom"; } from "react-router-dom";
import { OnboardingFinishScreen } from "./finish"; import { OnboardingFinishScreen } from "./finish";
import { OnboardingFollowScreen } from "./follow";
import { OnboardingHomeScreen } from "./home"; import { OnboardingHomeScreen } from "./home";
import { OnboardingProfileSettingsScreen } from "./profileSettings"; import { OnboardingInterestScreen } from "./interest";
import { OnboardingProfileScreen } from "./profile";
export function OnboardingRouter() { export function OnboardingRouter() {
return ( return (
@ -17,11 +17,8 @@ export function OnboardingRouter() {
<AnimatePresence> <AnimatePresence>
<Routes> <Routes>
<Route path="/" element={<OnboardingHomeScreen />} /> <Route path="/" element={<OnboardingHomeScreen />} />
<Route <Route path="/profile" element={<OnboardingProfileScreen />} />
path="/profile-settings" <Route path="/interests" element={<OnboardingInterestScreen />} />
element={<OnboardingProfileSettingsScreen />}
/>
<Route path="/follow" element={<OnboardingFollowScreen />} />
<Route path="/finish" element={<OnboardingFinishScreen />} /> <Route path="/finish" element={<OnboardingFinishScreen />} />
</Routes> </Routes>
</AnimatePresence> </AnimatePresence>

View File

@ -86,50 +86,95 @@ export const COL_TYPES = {
topic: 6, topic: 6,
trendingNotes: 9000, trendingNotes: 9000,
trendingAccounts: 9001, trendingAccounts: 9001,
foryou: 9998,
newsfeed: 9999, newsfeed: 9999,
}; };
export const TOPICS = [ export const TOPICS = [
{ {
icon: "/anime.jpg",
title: "Anime & Manga",
content: [
"#animestr",
"#anime",
"#manga",
"#otaku",
"#frieren",
"#fate",
"#aot",
"#AttackOnTitan",
"#JujutsuKaisen",
"#OnePiece",
"#KimetsuNoYaiba",
"#Overlord",
"#Evangelion",
"#DemonSlayer",
"#JoJo",
"#SPYxFAMILY",
"#MatoSeiheinoSlave",
"#ghibli",
"#ChainsawMan",
"#Gintama",
"#animeart",
"#animegirl",
"#cosplay",
"#weeb",
"#animeworld",
"#fanart",
"#vocaloid",
"#vtuber",
"#hololive",
"#hololivemeet",
"#pixiv",
"#waifu",
],
},
{
icon: "/gaming.jpg",
title: "Gaming", title: "Gaming",
content: [ content: [
"#gamestr", "#gamestr",
"#gaming", "#GenshinImpact",
"#gamer", "#HonkaiStarRail",
"#ps", "#HonkaiImpact",
"#playstation",
"#videogames",
"#game",
"#xbox",
"#games",
"#twitch",
"#fortnite",
"#pc",
"#pcgaming",
"#gamers",
"#gamingcommunity",
"#switch",
"#gamergirl",
"#nintendo",
"#gta",
"#callofduty",
"#pubg",
"#videogame",
"#esports",
"#genshinimpact",
"#honkaiimpact",
"#warthunder",
"#hoyoverse",
"#arknights",
"#soullike",
"#eldenring",
"#steam", "#steam",
"#pubg", "#pubg",
"#cs2", "#cs2",
"#Cyberpunk",
"#Skyrim",
"#GTA",
"#GTA6",
"#CallofDuty",
"#Pokemon",
"#apexlegends", "#apexlegends",
"#baldurgate3", "#baldurgate",
"#starfield", "#starfield",
"#gta6", "#thefinals",
"#palworld",
"#famitsu",
"#jrpg",
"#ffxiv",
"#gacha",
"#warthunder",
"#hoyoverse",
"#arknights",
"#soulslike",
"#eldenring",
"#persona",
"#playstation",
"#steamdeck",
"#xbox",
"#xbot",
"#consolewars",
"#game",
"#games",
"#twitch",
"#fortnite",
"#pcgaming",
"#nintendo",
"#switch",
"#pubg",
"#esports",
"#gameoftheyear", "#gameoftheyear",
"#darksoul", "#darksoul",
"#batterfield", "#batterfield",
@ -144,18 +189,35 @@ export const TOPICS = [
], ],
}, },
{ {
title: "Music", icon: "/music.jpg",
title: "Music & Entertainment",
content: [ content: [
"#audiostr", "#audiostr",
"#musicstr", "#musicstr",
"#music", "#music",
"#love", "#movie",
"#hiphop", "#BLACKPINK",
"#Lisa",
"#Jennie",
"#Taylor",
"#BTC",
"#Twice",
"#TikTok",
"#KPOP",
"#Marvel",
"#DC",
"#Woke",
"#fan",
"#StarWars",
"#Podcast",
"#JoeRogan",
"#Ariana",
"#SONTUNGMTP",
"#rap", "#rap",
"#metal",
"#vinyl",
"#art", "#art",
"#musician",
"#artist", "#artist",
"#musica",
"#singer", "#singer",
"#dj", "#dj",
"#rock", "#rock",
@ -164,77 +226,28 @@ export const TOPICS = [
"#song", "#song",
"#newmusic", "#newmusic",
"#producer", "#producer",
"#life",
"#rapper", "#rapper",
"#party", "#party",
"#fashion", "#fashion",
"#explorepage",
"#viral", "#viral",
"#beats", "#beats",
"#dvd", "#dvd",
"#amass", "#amass",
"#bluray", "#bluray",
"#Blu_Ray",
"#taylor",
], ],
}, },
{ {
title: "Photography", icon: "/movie.jpg",
content: [ title: "Television",
"#photography",
"#photooftheday",
"#love",
"#photo",
"#nature",
"#picoftheday",
"#photographer",
"#beautiful",
"#fashion",
"#travel",
"#photoshoot",
"#naturephotography",
"#model",
"#me",
"#smile",
"#style",
"#happy",
"#likes",
"#myself",
],
},
{
title: "Art",
content: [
"#nostrdesign",
"#artstr",
"#art",
"#artist",
"#drawing",
"#artwork",
"#painting",
"#fashion",
"#beautiful",
"#illustration",
"#digitalart",
"#design",
"#nature",
"#photo",
"#sketch",
"#style",
"#arte",
"#happy",
"#cute",
"#draw",
"#artoftheday",
],
},
{
title: "Movie",
content: [ content: [
"#filmstr", "#filmstr",
"#moviestr", "#moviestr",
"#movies", "#movies",
"#movie", "#movie",
"#HBO",
"#BreakingBad",
"#Wednesday",
"#Disney+",
"#film", "#film",
"#cinema", "#cinema",
"#films", "#films",
@ -249,24 +262,28 @@ export const TOPICS = [
"#horror", "#horror",
"#bollywood", "#bollywood",
"#movienight", "#movienight",
"#photography",
"#comedy", "#comedy",
"#cinephile",
"#cine", "#cine",
"#tv",
"#director",
"#horrormovies", "#horrormovies",
"#drama", "#drama",
"#filmmaker", "#kdrama",
], ],
}, },
{ {
icon: "/technology.jpg",
title: "Technology", title: "Technology",
content: [ content: [
"#apple", "#Apple",
"#xiaomi", "#Tesla",
"#huawei", "#AMD",
"#Intel",
"#Xiaomi",
"#Huawei",
"#OpenAI",
"#BigTech",
"#ai", "#ai",
"#IOS",
"#Android",
"#oppo", "#oppo",
"#nostr", "#nostr",
"#technology", "#technology",
@ -278,8 +295,6 @@ export const TOPICS = [
"#technews", "#technews",
"#science", "#science",
"#gadgets", "#gadgets",
"#electronics",
"#android",
"#software", "#software",
"#programming", "#programming",
"#smartphone", "#smartphone",
@ -289,51 +304,90 @@ export const TOPICS = [
"#security", "#security",
"#gadget", "#gadget",
"#mobile", "#mobile",
"#technologynews",
"#opensource", "#opensource",
"#tor", "#tor",
"#bitcoin",
"#lightning",
], ],
}, },
{ {
title: "Anime", icon: "/photography.jpg",
title: "Photography",
content: [ content: [
"#animestr", "#photostr",
"#anime", "#NewProfilePic",
"#manga", "#photography",
"#otaku", "#photooftheday",
"#animeart", "#foot",
"#animegirl", "#love",
"#cosplay", "#photo",
"#weeb", "#nature",
"#onepiece", "#picoftheday",
"#demonslayer", "#photographer",
"#animeworld", "#beautiful",
"#aot", "#fashion",
"#fanart", "#travel",
"#vocaloid", "#photoshoot",
"#vtuber", "#nature",
"#fate", "#naturephotography",
"#hololive", "#smile",
"#hololivemeet", "#style",
"#pixiv", "#happy",
"#waifu", "#likes",
"#myself",
], ],
}, },
{ {
icon: "/art.jpg",
title: "Art & Design",
content: [
"#nostrdesign",
"#artstr",
"#art",
"#design",
"#ui",
"#ux",
"#MidJourney",
"#Dall-E",
"#aiart",
"#retro",
"#webdesign",
"#artist",
"#pixelart",
"#pixel",
"#3D",
"#drawing",
"#artwork",
"#painting",
"#fashion",
"#beautiful",
"#illustration",
"#digitalart",
"#nature",
"#photo",
"#sketch",
"#style",
"#draw",
"#artoftheday",
],
},
{
icon: "/nsfw.jpg",
title: "NSFW", title: "NSFW",
content: [ content: [
"#pornstr", "#pornstr",
"#porn", "#porn",
"#Lesbian",
"#ntr",
"#yuri",
"#BigCock",
"#Anal",
"#BDSM",
"#pornhub",
"#nsfw", "#nsfw",
"#bdsm", "#nude",
"#lewd",
"#kink",
"#sexy", "#sexy",
"#loli", "#loli",
"#hentai", "#hentai",
"#ntr", "#69",
], ],
}, },
]; ];

View File

@ -11,7 +11,7 @@ export const editorValueAtom = atom([
]); ]);
// Onboarding // Onboarding
export const onboardingAtom = atom(false); export const onboardingAtom = atom(true);
// Activity // Activity
export const activityAtom = atom(false); export const activityAtom = atom(false);