diff --git a/apps/desktop2/index.html b/apps/desktop2/index.html index 9f289e37..6bad771c 100644 --- a/apps/desktop2/index.html +++ b/apps/desktop2/index.html @@ -6,7 +6,7 @@ Lume Desktop
diff --git a/apps/desktop2/package.json b/apps/desktop2/package.json index fb531acf..a2a85e92 100644 --- a/apps/desktop2/package.json +++ b/apps/desktop2/package.json @@ -18,37 +18,43 @@ "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-popover": "^1.0.7", - "@tanstack/query-sync-storage-persister": "^5.24.1", - "@tanstack/react-query": "^5.24.1", - "@tanstack/react-query-persist-client": "^5.24.1", - "@tanstack/react-router": "^1.18.1", - "i18next": "^23.10.0", + "@radix-ui/react-switch": "^1.0.3", + "@tanstack/query-sync-storage-persister": "^5.29.0", + "@tanstack/react-query": "^5.29.0", + "@tanstack/react-query-persist-client": "^5.29.0", + "@tanstack/react-router": "^1.26.18", + "i18next": "^23.11.1", "i18next-resources-to-backend": "^1.2.0", - "nostr-tools": "^2.3.1", + "minidenticons": "^4.2.1", + "nanoid": "^5.0.7", + "nostr-tools": "^2.4.0", "react": "^18.2.0", "react-currency-input-field": "^3.8.0", "react-dom": "^18.2.0", - "react-i18next": "^14.0.5", - "slate": "^0.101.5", - "slate-react": "^0.101.6", - "sonner": "^1.4.3", - "virtua": "^0.27.5" + "react-hook-form": "^7.51.2", + "react-hotkeys-hook": "^4.5.0", + "react-i18next": "^14.1.0", + "slate": "^0.102.0", + "slate-react": "^0.102.0", + "sonner": "^1.4.41", + "use-debounce": "^10.0.0", + "virtua": "^0.29.2" }, "devDependencies": { "@lume/tailwindcss": "workspace:^", "@lume/tsconfig": "workspace:^", "@lume/types": "workspace:^", - "@tanstack/router-devtools": "^1.18.1", - "@tanstack/router-vite-plugin": "^1.18.1", - "@types/react": "^18.2.61", - "@types/react-dom": "^18.2.19", + "@tanstack/router-devtools": "^1.26.18", + "@tanstack/router-vite-plugin": "^1.26.16", + "@types/react": "^18.2.75", + "@types/react-dom": "^18.2.24", "@vitejs/plugin-react-swc": "^3.6.0", - "autoprefixer": "^10.4.18", - "postcss": "^8.4.35", - "tailwindcss": "^3.4.1", - "typescript": "^5.3.3", - "vite": "^5.1.4", + "autoprefixer": "^10.4.19", + "postcss": "^8.4.38", + "tailwindcss": "^3.4.3", + "typescript": "^5.4.4", + "vite": "^5.2.8", "vite-plugin-top-level-await": "^1.4.1", - "vite-tsconfig-paths": "^4.3.1" + "vite-tsconfig-paths": "^4.3.2" } } diff --git a/apps/desktop2/public/anime.jpg b/apps/desktop2/public/anime.jpg new file mode 100644 index 00000000..f7f8eeab Binary files /dev/null and b/apps/desktop2/public/anime.jpg differ diff --git a/apps/desktop2/public/antenas.png b/apps/desktop2/public/antenas.png new file mode 100644 index 00000000..a55b08c2 Binary files /dev/null and b/apps/desktop2/public/antenas.png differ diff --git a/apps/desktop2/public/antenas@2x.png b/apps/desktop2/public/antenas@2x.png new file mode 100644 index 00000000..1b4f705e Binary files /dev/null and b/apps/desktop2/public/antenas@2x.png differ diff --git a/apps/desktop2/public/art.jpg b/apps/desktop2/public/art.jpg new file mode 100644 index 00000000..efc36ab3 Binary files /dev/null and b/apps/desktop2/public/art.jpg differ diff --git a/apps/desktop2/public/foryou.png b/apps/desktop2/public/foryou.png new file mode 100644 index 00000000..275c23e2 Binary files /dev/null and b/apps/desktop2/public/foryou.png differ diff --git a/apps/desktop2/public/foryou@2x.png b/apps/desktop2/public/foryou@2x.png new file mode 100644 index 00000000..0e1bd40d Binary files /dev/null and b/apps/desktop2/public/foryou@2x.png differ diff --git a/apps/desktop2/public/gaming.jpg b/apps/desktop2/public/gaming.jpg new file mode 100644 index 00000000..de58aeeb Binary files /dev/null and b/apps/desktop2/public/gaming.jpg differ diff --git a/apps/desktop2/public/global.png b/apps/desktop2/public/global.png new file mode 100644 index 00000000..3ec2cc39 Binary files /dev/null and b/apps/desktop2/public/global.png differ diff --git a/apps/desktop2/public/global@2x.png b/apps/desktop2/public/global@2x.png new file mode 100644 index 00000000..f5d7a9d6 Binary files /dev/null and b/apps/desktop2/public/global@2x.png differ diff --git a/apps/desktop2/public/group.png b/apps/desktop2/public/group.png new file mode 100644 index 00000000..0fba6730 Binary files /dev/null and b/apps/desktop2/public/group.png differ diff --git a/apps/desktop2/public/group@2x.png b/apps/desktop2/public/group@2x.png new file mode 100644 index 00000000..7698d451 Binary files /dev/null and b/apps/desktop2/public/group@2x.png differ diff --git a/apps/desktop2/public/movie.jpg b/apps/desktop2/public/movie.jpg new file mode 100644 index 00000000..bcb36809 Binary files /dev/null and b/apps/desktop2/public/movie.jpg differ diff --git a/apps/desktop2/public/music.jpg b/apps/desktop2/public/music.jpg new file mode 100644 index 00000000..1c533c15 Binary files /dev/null and b/apps/desktop2/public/music.jpg differ diff --git a/apps/desktop2/public/nsfw.jpg b/apps/desktop2/public/nsfw.jpg new file mode 100644 index 00000000..f9e183d2 Binary files /dev/null and b/apps/desktop2/public/nsfw.jpg differ diff --git a/apps/desktop2/public/photography.jpg b/apps/desktop2/public/photography.jpg new file mode 100644 index 00000000..6f117830 Binary files /dev/null and b/apps/desktop2/public/photography.jpg differ diff --git a/apps/desktop2/public/technology.jpg b/apps/desktop2/public/technology.jpg new file mode 100644 index 00000000..cfd733f0 Binary files /dev/null and b/apps/desktop2/public/technology.jpg differ diff --git a/apps/desktop2/public/trending.png b/apps/desktop2/public/trending.png new file mode 100644 index 00000000..1b036c02 Binary files /dev/null and b/apps/desktop2/public/trending.png differ diff --git a/apps/desktop2/public/trending@2x.png b/apps/desktop2/public/trending@2x.png new file mode 100644 index 00000000..5e124f31 Binary files /dev/null and b/apps/desktop2/public/trending@2x.png differ diff --git a/apps/desktop2/public/waifu.png b/apps/desktop2/public/waifu.png new file mode 100644 index 00000000..833949c3 Binary files /dev/null and b/apps/desktop2/public/waifu.png differ diff --git a/apps/desktop2/public/waifu@2x.png b/apps/desktop2/public/waifu@2x.png new file mode 100644 index 00000000..484ef2c9 Binary files /dev/null and b/apps/desktop2/public/waifu@2x.png differ diff --git a/apps/desktop2/src/app.css b/apps/desktop2/src/app.css index 0ad09a56..a9df0c78 100644 --- a/apps/desktop2/src/app.css +++ b/apps/desktop2/src/app.css @@ -7,13 +7,39 @@ } *::-webkit-scrollbar-track { - @apply bg-white dark:bg-black; + @apply bg-transparent; } *::-webkit-scrollbar-thumb { - @apply bg-black dark:bg-white rounded; + @apply rounded bg-black dark:bg-white; } +@layer utilities { + .content-break { + word-break: break-word; + word-wrap: break-word; + overflow-wrap: break-word; + } + + .shadow-toolbar { + box-shadow: + 0 0 #0000, + 0 0 #0000, + 0 8px 24px 0 rgba(0, 0, 0, 0.2), + 0 2px 8px 0 rgba(0, 0, 0, 0.08), + inset 0 0 0 1px rgba(0, 0, 0, 0.2), + inset 0 0 0 2px hsla(0, 0%, 100%, 0.14); + } + + .shadow-primary { + box-shadow: 0px 0px 4px rgba(66, 65, 73, 0.14); + } +} + +/* + Overide some default styles +*/ + html { font-size: 14px; } @@ -34,25 +60,3 @@ input::-ms-clear { ::-webkit-input-placeholder { line-height: normal; } - -media-controller { - @apply w-full overflow-hidden rounded-xl; -} - -@layer utilities { - .content-break { - word-break: break-word; - word-wrap: break-word; - overflow-wrap: break-word; - } - - .shadow-toolbar { - box-shadow: - 0 0 #0000, - 0 0 #0000, - 0 8px 24px 0 rgba(0, 0, 0, 0.2), - 0 2px 8px 0 rgba(0, 0, 0, 0.08), - inset 0 0 0 1px rgba(0, 0, 0, 0.2), - inset 0 0 0 2px hsla(0, 0%, 100%, 0.14); - } -} diff --git a/apps/desktop2/src/app.tsx b/apps/desktop2/src/app.tsx index e288989d..b5007e46 100644 --- a/apps/desktop2/src/app.tsx +++ b/apps/desktop2/src/app.tsx @@ -1,5 +1,3 @@ -import { useArk } from "@lume/ark"; -import { ArkProvider } from "./ark"; import { QueryClient } from "@tanstack/react-query"; import { RouterProvider, createRouter } from "@tanstack/react-router"; import React, { StrictMode } from "react"; @@ -8,11 +6,11 @@ import { I18nextProvider } from "react-i18next"; import "./app.css"; import i18n from "./locale"; import { Toaster } from "sonner"; -import { locale, platform } from "@tauri-apps/plugin-os"; import { PersistQueryClientProvider } from "@tanstack/react-query-persist-client"; import { createSyncStoragePersister } from "@tanstack/query-sync-storage-persister"; import { routeTree } from "./router.gen"; // auto generated file import { CancelCircleIcon, CheckCircleIcon, InfoCircleIcon } from "@lume/icons"; +import { Ark } from "@lume/ark"; const queryClient = new QueryClient({ defaultOptions: { @@ -27,16 +25,13 @@ const persister = createSyncStoragePersister({ storage: window.localStorage, }); -const platformName = await platform(); -const osLocale = (await locale()).slice(0, 2); +const ark = new Ark(); // Set up a Router instance const router = createRouter({ routeTree, context: { - ark: undefined!, - platform: platformName, - locale: osLocale, + ark, queryClient, }, }); @@ -48,17 +43,8 @@ declare module "@tanstack/react-router" { } } -function InnerApp() { - const ark = useArk(); - return ; -} - function App() { - return ( - - - - ); + return ; } // biome-ignore lint/style/noNonNullAssertion: idk diff --git a/apps/desktop2/src/ark.tsx b/apps/desktop2/src/ark.tsx deleted file mode 100644 index bb844efb..00000000 --- a/apps/desktop2/src/ark.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { Ark, ArkContext } from "@lume/ark"; -import { PropsWithChildren, useMemo } from "react"; - -export const ArkProvider = ({ children }: PropsWithChildren) => { - const ark = useMemo(() => new Ark(), []); - return {children}; -}; diff --git a/apps/desktop2/src/components/accounts.tsx b/apps/desktop2/src/components/accounts.tsx index c9f2e382..01919b59 100644 --- a/apps/desktop2/src/components/accounts.tsx +++ b/apps/desktop2/src/components/accounts.tsx @@ -1,15 +1,14 @@ -import { useArk } from "@lume/ark"; import { Account } from "@lume/types"; import { User } from "@lume/ui"; -import { useNavigate, useParams, useSearch } from "@tanstack/react-router"; +import { + useNavigate, + useParams, + useRouteContext, +} from "@tanstack/react-router"; import { useEffect, useState } from "react"; -import * as Popover from "@radix-ui/react-popover"; -import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; -import { BackupDialog } from "./backup"; -import { LoginDialog } from "./login"; export function Accounts() { - const ark = useArk(); + const { ark } = useRouteContext({ strict: false }); const params = useParams({ strict: false }); const [accounts, setAccounts] = useState(null); @@ -24,7 +23,7 @@ export function Accounts() { }, []); return ( -
+
{accounts ? accounts.map((account) => // @ts-ignore, useless @@ -40,13 +39,12 @@ export function Accounts() { } function Inactive({ pubkey }: { pubkey: string }) { - const ark = useArk(); + const { ark } = useRouteContext({ strict: false }); const navigate = useNavigate(); const changeAccount = async (npub: string) => { const select = await ark.load_selected_account(npub); - if (select) - navigate({ to: "/$account/home/local", params: { account: npub } }); + if (select) navigate({ to: "/$account/home", params: { account: npub } }); }; return ( @@ -61,91 +59,11 @@ function Inactive({ pubkey }: { pubkey: string }) { } function Active({ pubkey }: { pubkey: string }) { - const ark = useArk(); - const navigate = useNavigate(); - - // @ts-ignore, magic !!! - const { guest } = useSearch({ strict: false }); - // @ts-ignore, magic !!! - const { account } = useParams({ strict: false }); - - if (guest) { - return ( - - - - - - -
-

- You're using random account -

-

- You can continue by claim and backup this account, or you can - import your own account. -

-
-
- - -
- -
-
-
- ); - } - return ( - - - - - - - - - - - - Add account - - ark.open_profile(account)} - className="group relative flex h-9 select-none items-center rounded-md px-3 text-sm font-medium leading-none outline-none hover:bg-neutral-900 dark:hover:bg-neutral-100" - > - Profile -
- ⌘+Shift+P -
-
- navigate({ to: "/", search: { manually: true } })} - className="group relative flex h-9 select-none items-center rounded-md px-3 text-sm font-medium leading-none outline-none hover:bg-neutral-900 dark:hover:bg-neutral-100" - > - Logout -
- ⌘+Shift+L -
-
- -
-
-
+ + + + + ); } diff --git a/apps/desktop2/src/components/avatarUploader.tsx b/apps/desktop2/src/components/avatarUploader.tsx new file mode 100644 index 00000000..cffb22e3 --- /dev/null +++ b/apps/desktop2/src/components/avatarUploader.tsx @@ -0,0 +1,42 @@ +import { LoaderIcon } from "@lume/icons"; +import { cn } from "@lume/utils"; +import { useRouteContext } from "@tanstack/react-router"; +import { Dispatch, ReactNode, SetStateAction, useState } from "react"; +import { toast } from "sonner"; + +export function AvatarUploader({ + setPicture, + children, + className, +}: { + setPicture: Dispatch>; + children: ReactNode; + className?: string; +}) { + const { ark } = useRouteContext({ strict: false }); + const [loading, setLoading] = useState(false); + + const uploadAvatar = async () => { + // start loading + setLoading(true); + try { + const image = await ark.upload(); + setPicture(image); + } catch (e) { + toast.error(String(e)); + } + + // stop loading + setLoading(false); + }; + + return ( + + ); +} diff --git a/apps/desktop2/src/components/backup.tsx b/apps/desktop2/src/components/backup.tsx deleted file mode 100644 index 1f2fa91c..00000000 --- a/apps/desktop2/src/components/backup.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import { ArrowRightIcon, CancelIcon } from "@lume/icons"; -import * as Dialog from "@radix-ui/react-dialog"; -import { Link, useParams } from "@tanstack/react-router"; -import { invoke } from "@tauri-apps/api/core"; -import { useState } from "react"; -import { toast } from "sonner"; - -export function BackupDialog() { - // @ts-ignore, magic!!! - const { account } = useParams({ strict: false }); - - const [key, setKey] = useState(null); - const [passphase, setPassphase] = useState(""); - const [loading, setLoading] = useState(false); - - const encryptKey = async () => { - try { - setLoading(true); - - const encrypted: string = await invoke("get_encrypted_key", { - npub: account, - password: passphase, - }); - - if (encrypted) { - setKey(encrypted); - } - - setLoading(false); - } catch (e) { - setLoading(false); - toast.error(String(e)); - } - }; - - return ( - - - - - - - - - - - Esc - - -
-
-

- This is your account key -

-

- It's use for login to Lume or other Nostr clients. You will lost - access to your account if you lose this key. -

-
-
-
- -
- setPassphase(e.target.value)} - className="h-11 w-full resize-none rounded-lg border-transparent bg-neutral-100 placeholder:text-neutral-600 focus:border-blue-500 focus:ring focus:ring-blue-100 dark:bg-neutral-900 dark:focus:ring-blue-900" - /> -
-
- {key ? ( -
- - -
- ) : null} -
-
- {!key ? ( - - ) : ( - - I've safely store my account key - - )} -
-
-
-
-
- ); -} diff --git a/apps/desktop2/src/components/balance.tsx b/apps/desktop2/src/components/balance.tsx index 5be1f4ec..f31adaba 100644 --- a/apps/desktop2/src/components/balance.tsx +++ b/apps/desktop2/src/components/balance.tsx @@ -1,20 +1,11 @@ -import { useArk } from "@lume/ark"; import { User } from "@lume/ui"; import { getBitcoinDisplayValues } from "@lume/utils"; +import { useRouteContext } from "@tanstack/react-router"; import { useEffect, useMemo, useState } from "react"; -import { useTranslation } from "react-i18next"; -export function Balance({ - recipient, - account, -}: { - recipient: string; - account: string; -}) { - const [t] = useTranslation(); +export function Balance({ account }: { account: string }) { + const { ark } = useRouteContext({ strict: false }); const [balance, setBalance] = useState(0); - - const ark = useArk(); const value = useMemo(() => getBitcoinDisplayValues(balance), [balance]); useEffect(() => { diff --git a/apps/desktop2/src/components/col.tsx b/apps/desktop2/src/components/col.tsx new file mode 100644 index 00000000..a6c474a4 --- /dev/null +++ b/apps/desktop2/src/components/col.tsx @@ -0,0 +1,77 @@ +import { useEffect, useRef } from "react"; +import { LumeColumn } from "@lume/types"; +import { invoke } from "@tauri-apps/api/core"; +import { LoaderIcon } from "@lume/icons"; + +export function Col({ + column, + account, + isScroll, +}: { + column: LumeColumn; + account: string; + isScroll: boolean; +}) { + const webview = useRef(undefined); + const container = useRef(null); + + const repositionWebview = async () => { + if (webview.current && webview.current.length > 1) { + const newRect = container.current.getBoundingClientRect(); + await invoke("reposition_column", { + label: webview.current, + x: newRect.x, + y: newRect.y, + }); + } + }; + + useEffect(() => { + if (isScroll) { + repositionWebview(); + } + }, [isScroll]); + + useEffect(() => { + (async () => { + const rect = container.current.getBoundingClientRect(); + const windowLabel = `column-${column.label}`; + const url = + column.content + + `?account=${account}&label=${column.label}&name=${column.name}`; + + // create new webview + webview.current = await invoke("create_column", { + label: windowLabel, + x: rect.x, + y: rect.y, + width: rect.width, + height: rect.height, + url, + }); + })(); + + // close webview when unmounted + return () => { + if (webview.current && webview.current.length > 1) { + invoke("close_column", { + label: webview.current, + }).then(() => { + webview.current = undefined; + }); + } + }; + }, []); + + return ( +
+ {column.label !== "open" ? ( +
+ +
+ ) : null} +
+ ); +} diff --git a/apps/desktop2/src/components/login.tsx b/apps/desktop2/src/components/login.tsx deleted file mode 100644 index 9f245eeb..00000000 --- a/apps/desktop2/src/components/login.tsx +++ /dev/null @@ -1,125 +0,0 @@ -import { useArk } from "@lume/ark"; -import { ArrowRightIcon, CancelIcon } from "@lume/icons"; -import * as Dialog from "@radix-ui/react-dialog"; -import { useNavigate } from "@tanstack/react-router"; -import { useState } from "react"; -import { toast } from "sonner"; - -export function LoginDialog() { - const ark = useArk(); - const navigate = useNavigate(); - - const [nsec, setNsec] = useState(""); - const [passphase, setPassphase] = useState(""); - - const login = async () => { - try { - if (!nsec.length) { - return toast.info("You must enter a valid nsec or ncrypto"); - } - - if (nsec.startsWith("ncrypto") && !passphase.length) { - return toast.warning("You must provide a passphase for ncrypto key"); - } - - const save = await ark.save_account(nsec, passphase); - - if (save) { - navigate({ to: "/", search: { guest: false } }); - } - } catch (e) { - toast.error(String(e)); - } - }; - - return ( - - - - - - - - - - - Esc - - -
-
-

Add new account with

-
- - - -
-
-
-
- - setNsec(e.target.value)} - className="h-11 w-full resize-none rounded-lg border-transparent bg-neutral-100 placeholder:text-neutral-600 focus:border-blue-500 focus:ring focus:ring-blue-100 dark:bg-neutral-900 dark:focus:ring-blue-900" - /> -
-
- - setPassphase(e.target.value)} - className="h-11 w-full resize-none rounded-lg border-transparent bg-neutral-100 placeholder:text-neutral-600 focus:border-blue-500 focus:ring focus:ring-blue-100 dark:bg-neutral-900 dark:focus:ring-blue-900" - /> -
-
-
- -
-
-
-
-
- ); -} diff --git a/apps/desktop2/src/components/repost.tsx b/apps/desktop2/src/components/repost.tsx index cca86d0f..97ee0ac7 100644 --- a/apps/desktop2/src/components/repost.tsx +++ b/apps/desktop2/src/components/repost.tsx @@ -3,8 +3,8 @@ import { Event } from "@lume/types"; import { cn } from "@lume/utils"; import { useQuery } from "@tanstack/react-query"; import { useTranslation } from "react-i18next"; -import { useArk } from "@lume/ark"; import { Note, User } from "@lume/ui"; +import { useRouteContext } from "@tanstack/react-router"; export function RepostNote({ event, @@ -13,8 +13,7 @@ export function RepostNote({ event: Event; className?: string; }) { - const ark = useArk(); - + const { ark } = useRouteContext({ strict: false }); const { t } = useTranslation(); const { isLoading, @@ -44,7 +43,12 @@ export function RepostNote({ if (isError || !repostEvent) { return ( - +
@@ -71,7 +75,7 @@ export function RepostNote({ return ( diff --git a/apps/desktop2/src/components/text.tsx b/apps/desktop2/src/components/text.tsx index fab9e334..bd02f43e 100644 --- a/apps/desktop2/src/components/text.tsx +++ b/apps/desktop2/src/components/text.tsx @@ -13,7 +13,7 @@ export function TextNote({ diff --git a/apps/desktop2/src/components/toolbar.tsx b/apps/desktop2/src/components/toolbar.tsx new file mode 100644 index 00000000..117d572c --- /dev/null +++ b/apps/desktop2/src/components/toolbar.tsx @@ -0,0 +1,15 @@ +import { ReactNode } from "@tanstack/react-router"; +import { useLayoutEffect, useState } from "react"; +import { createPortal } from "react-dom"; + +export function Toolbar({ children }: { children: ReactNode }) { + const [domReady, setDomReady] = useState(false); + + useLayoutEffect(() => { + setDomReady(true); + }, []); + + return domReady + ? createPortal(children, document.getElementById("toolbar")) + : null; +} diff --git a/apps/desktop2/src/routes/$account.home.tsx b/apps/desktop2/src/routes/$account.home.tsx new file mode 100644 index 00000000..37d9bb59 --- /dev/null +++ b/apps/desktop2/src/routes/$account.home.tsx @@ -0,0 +1,155 @@ +import { Col } from "@/components/col"; +import { Toolbar } from "@/components/toolbar"; +import { ArrowLeftIcon, ArrowRightIcon, LoaderIcon } from "@lume/icons"; +import { EventColumns, LumeColumn } from "@lume/types"; +import { createFileRoute } from "@tanstack/react-router"; +import { listen } from "@tauri-apps/api/event"; +import { resolveResource } from "@tauri-apps/api/path"; +import { readTextFile } from "@tauri-apps/plugin-fs"; +import { nanoid } from "nanoid"; +import { useEffect, useRef, useState } from "react"; +import { useDebouncedCallback } from "use-debounce"; +import { VList, VListHandle } from "virtua"; + +export const Route = createFileRoute("/$account/home")({ + component: Screen, + pendingComponent: Pending, + beforeLoad: async ({ context }) => { + const ark = context.ark; + const resourcePath = await resolveResource("resources/system_columns.json"); + const systemColumns: LumeColumn[] = JSON.parse( + await readTextFile(resourcePath), + ); + const userColumns = await ark.get_columns(); + + return { + storedColumns: !userColumns.length ? systemColumns : userColumns, + }; + }, +}); + +function Screen() { + const { account } = Route.useParams(); + const { ark, storedColumns } = Route.useRouteContext(); + + const [selectedIndex, setSelectedIndex] = useState(-1); + const [isScroll, setIsScroll] = useState(false); + const [columns, setColumns] = useState(storedColumns); + + const vlistRef = useRef(null); + + const goLeft = () => { + const prevIndex = Math.max(selectedIndex - 1, 0); + setSelectedIndex(prevIndex); + vlistRef.current.scrollToIndex(prevIndex, { + align: "center", + }); + }; + + const goRight = () => { + const nextIndex = Math.min(selectedIndex + 1, columns.length - 1); + setSelectedIndex(nextIndex); + vlistRef.current.scrollToIndex(nextIndex, { + align: "center", + }); + }; + + const add = useDebouncedCallback((column: LumeColumn) => { + column["label"] = column.label + "-" + nanoid(); + + setColumns((state) => [...state, column]); + setSelectedIndex(columns.length + 1); + + // scroll to the last column + vlistRef.current.scrollToIndex(columns.length + 1, { + align: "end", + }); + }, 150); + + const remove = useDebouncedCallback((label: string) => { + setColumns((state) => state.filter((t) => t.label !== label)); + setSelectedIndex(columns.length - 1); + + // scroll to the first column + vlistRef.current.scrollToIndex(0, { + align: "start", + }); + }, 150); + + useEffect(() => { + // save state + ark.set_columns(columns); + }, [columns]); + + useEffect(() => { + let unlisten: Awaited> | undefined = undefined; + + (async () => { + if (unlisten) return; + unlisten = await listen("columns", (data) => { + if (data.payload.type === "add") add(data.payload.column); + if (data.payload.type === "remove") remove(data.payload.label); + }); + })(); + + return () => { + if (unlisten) unlisten(); + }; + }, []); + + return ( +
+ { + setIsScroll(true); + }} + onScrollEnd={() => { + setIsScroll(false); + }} + className="scrollbar-none h-full w-full overflow-x-auto focus:outline-none" + > + {columns.map((column, index) => ( + + ))} + + +
+ + +
+
+
+ ); +} + +function Pending() { + return ( +
+ +
+ ); +} diff --git a/apps/desktop2/src/routes/$account.tsx b/apps/desktop2/src/routes/$account.tsx index 9e00558b..402f2db5 100644 --- a/apps/desktop2/src/routes/$account.tsx +++ b/apps/desktop2/src/routes/$account.tsx @@ -1,131 +1,55 @@ -import { - BellFilledIcon, - BellIcon, - ComposeFilledIcon, - HomeFilledIcon, - HomeIcon, - HorizontalDotsIcon, - SettingsIcon, - SpaceFilledIcon, - SpaceIcon, -} from "@lume/icons"; -import { Link } from "@tanstack/react-router"; -import { Outlet, createFileRoute } from "@tanstack/react-router"; +import { ComposeFilledIcon, PlusIcon } from "@lume/icons"; +import { Outlet, createFileRoute, useNavigate } from "@tanstack/react-router"; import { cn } from "@lume/utils"; import { Accounts } from "@/components/accounts"; -import { useArk } from "@lume/ark"; -import { Box } from "@lume/ui"; +import { platform } from "@tauri-apps/plugin-os"; export const Route = createFileRoute("/$account")({ component: App, + beforeLoad: async () => { + const platformName = await platform(); + return { platform: platformName }; + }, }); function App() { - const ark = useArk(); - const context = Route.useRouteContext(); + const navigate = useNavigate(); + const { ark, platform } = Route.useRouteContext(); return ( -
+
-
+ +
+
- +
- +
- -
- ); -} - -function Navigation() { - // @ts-ignore, useless - const { account } = Route.useParams(); - - return ( -
- - {({ isActive }) => ( -
- {isActive ? ( - - ) : ( - - )} - Home -
- )} - - - {({ isActive }) => ( -
- {isActive ? ( - - ) : ( - - )} - Space -
- )} - - - {({ isActive }) => ( -
- {isActive ? ( - - ) : ( - - )} - Activity -
- )} - +
); } diff --git a/apps/desktop2/src/routes/$account/activity.lazy.tsx b/apps/desktop2/src/routes/$account/activity.lazy.tsx deleted file mode 100644 index 9ae4e2cf..00000000 --- a/apps/desktop2/src/routes/$account/activity.lazy.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { createLazyFileRoute } from "@tanstack/react-router"; - -export const Route = createLazyFileRoute("/$account/activity")({ - component: Activity, -}); - -function Activity() { - return ( -
-

Activity

-
- ); -} diff --git a/apps/desktop2/src/routes/$account/home.tsx b/apps/desktop2/src/routes/$account/home.tsx deleted file mode 100644 index 34cdf3ad..00000000 --- a/apps/desktop2/src/routes/$account/home.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { GlobalIcon, LoaderIcon, LocalIcon, RefreshIcon } from "@lume/icons"; -import { cn } from "@lume/utils"; -import { useQueryClient } from "@tanstack/react-query"; -import { Link } from "@tanstack/react-router"; -import { Outlet, createFileRoute } from "@tanstack/react-router"; - -export const Route = createFileRoute("/$account/home")({ - component: Screen, -}); - -function Screen() { - const queryClient = useQueryClient(); - const { account } = Route.useParams(); - - const refresh = async () => { - const queryKey = `${window.location.pathname.split("/").at(-1)}_newsfeed`; - await queryClient.refetchQueries({ queryKey: [queryKey, account] }); - }; - - return ( -
-
-
- - {({ isActive }) => ( -
- - Local -
- )} - - - {({ isActive }) => ( -
- - Global -
- )} - -
-
- -
-
- -
- ); -} diff --git a/apps/desktop2/src/routes/$account/home/global.lazy.tsx b/apps/desktop2/src/routes/$account/home/global.lazy.tsx deleted file mode 100644 index bd8c6772..00000000 --- a/apps/desktop2/src/routes/$account/home/global.lazy.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { RepostNote } from "@/components/repost"; -import { Suggest } from "@/components/suggest"; -import { TextNote } from "@/components/text"; -import { useArk } from "@lume/ark"; -import { LoaderIcon, ArrowRightCircleIcon, InfoIcon } from "@lume/icons"; -import { Event, Kind } from "@lume/types"; -import { FETCH_LIMIT } from "@lume/utils"; -import { useInfiniteQuery } from "@tanstack/react-query"; -import { createLazyFileRoute } from "@tanstack/react-router"; -import { Virtualizer } from "virtua"; - -export const Route = createLazyFileRoute("/$account/home/global")({ - component: Screen, -}); - -function Screen() { - const ark = useArk(); - const { account } = Route.useParams(); - const { - data, - hasNextPage, - isLoading, - isRefetching, - isFetchingNextPage, - fetchNextPage, - } = useInfiniteQuery({ - queryKey: ["global_newsfeed", account], - initialPageParam: 0, - queryFn: async ({ pageParam }: { pageParam: number }) => { - const events = await ark.get_events( - "global", - FETCH_LIMIT, - pageParam, - true, - ); - return events; - }, - getNextPageParam: (lastPage) => { - const lastEvent = lastPage?.at(-1); - if (!lastEvent) return; - return lastEvent.created_at - 1; - }, - select: (data) => data?.pages.flatMap((page) => page), - refetchOnWindowFocus: false, - }); - - const renderItem = (event: Event) => { - if (!event) return; - switch (event.kind) { - case Kind.Repost: - return ; - default: - return ; - } - }; - - return ( -
-
- {isLoading || isRefetching ? ( -
- -
- ) : ( - - {data.map((item) => renderItem(item))} - - )} -
- {hasNextPage ? ( - - ) : null} -
-
-
- ); -} diff --git a/apps/desktop2/src/routes/$account/space.lazy.tsx b/apps/desktop2/src/routes/$account/space.lazy.tsx deleted file mode 100644 index 857a601e..00000000 --- a/apps/desktop2/src/routes/$account/space.lazy.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { createLazyFileRoute } from "@tanstack/react-router"; - -export const Route = createLazyFileRoute("/$account/space")({ - component: Space, -}); - -function Space() { - return ( -
-

Space

-
- ); -} diff --git a/apps/desktop2/src/routes/__root.tsx b/apps/desktop2/src/routes/__root.tsx index 649e9ad0..e9e739b6 100644 --- a/apps/desktop2/src/routes/__root.tsx +++ b/apps/desktop2/src/routes/__root.tsx @@ -1,27 +1,22 @@ import { LoaderIcon } from "@lume/icons"; -import { - Outlet, - ScrollRestoration, - createRootRouteWithContext, -} from "@tanstack/react-router"; +import { Outlet, createRootRouteWithContext } from "@tanstack/react-router"; import { type Ark } from "@lume/ark"; import { type QueryClient } from "@tanstack/react-query"; import { type Platform } from "@tauri-apps/plugin-os"; +import { Account, Interests, Settings } from "@lume/types"; interface RouterContext { ark: Ark; queryClient: QueryClient; - platform: Platform; - locale: string; + platform?: Platform; + locale?: string; + settings?: Settings; + interests?: Interests; + accounts?: Account[]; } export const Route = createRootRouteWithContext()({ - component: () => ( - <> - - - - ), + component: () => , pendingComponent: Pending, wrapInSuspense: true, }); @@ -29,7 +24,9 @@ export const Route = createRootRouteWithContext()({ function Pending() { return (
- +
); } diff --git a/apps/desktop2/src/routes/$account/home/local.lazy.tsx b/apps/desktop2/src/routes/antenas.tsx similarity index 56% rename from apps/desktop2/src/routes/$account/home/local.lazy.tsx rename to apps/desktop2/src/routes/antenas.tsx index b815cf3c..365b6cfd 100644 --- a/apps/desktop2/src/routes/$account/home/local.lazy.tsx +++ b/apps/desktop2/src/routes/antenas.tsx @@ -1,49 +1,44 @@ import { RepostNote } from "@/components/repost"; import { Suggest } from "@/components/suggest"; import { TextNote } from "@/components/text"; -import { useArk } from "@lume/ark"; import { LoaderIcon, ArrowRightCircleIcon, InfoIcon } from "@lume/icons"; -import { Event, Kind } from "@lume/types"; -import { FETCH_LIMIT } from "@lume/utils"; +import { ColumnRouteSearch, Event, Kind } from "@lume/types"; +import { Column } from "@lume/ui"; import { useInfiniteQuery } from "@tanstack/react-query"; -import { Link } from "@tanstack/react-router"; -import { createLazyFileRoute } from "@tanstack/react-router"; +import { createFileRoute } from "@tanstack/react-router"; +import { useTranslation } from "react-i18next"; import { Virtualizer } from "virtua"; -export const Route = createLazyFileRoute("/$account/home/local")({ +export const Route = createFileRoute("/antenas")({ + validateSearch: (search: Record): ColumnRouteSearch => { + return { + account: search.account, + label: search.label, + name: search.name, + }; + }, component: Screen, }); -function Screen() { - const ark = useArk(); - const { account } = Route.useParams(); - const { - data, - hasNextPage, - isLoading, - isRefetching, - isFetchingNextPage, - fetchNextPage, - } = useInfiniteQuery({ - queryKey: ["local_newsfeed", account], - initialPageParam: 0, - queryFn: async ({ pageParam }: { pageParam: number }) => { - const events = await ark.get_events( - "local", - FETCH_LIMIT, - pageParam, - true, - ); - return events; - }, - getNextPageParam: (lastPage) => { - const lastEvent = lastPage?.at(-1); - if (!lastEvent) return; - return lastEvent.created_at - 1; - }, - select: (data) => data?.pages.flatMap((page) => page), - refetchOnWindowFocus: false, - }); +export function Screen() { + const { label, name, account } = Route.useSearch(); + const { ark } = Route.useRouteContext(); + const { t } = useTranslation(); + const { data, isLoading, isFetchingNextPage, hasNextPage, fetchNextPage } = + useInfiniteQuery({ + queryKey: [name, account], + initialPageParam: 0, + queryFn: async ({ pageParam }: { pageParam: number }) => { + const events = await ark.get_events(20, pageParam); + return events; + }, + getNextPageParam: (lastPage) => { + const lastEvent = lastPage?.at(-1); + return lastEvent ? lastEvent.created_at - 1 : null; + }, + select: (data) => data?.pages.flatMap((page) => page), + refetchOnWindowFocus: false, + }); const renderItem = (event: Event) => { if (!event) return; @@ -56,9 +51,10 @@ function Screen() { }; return ( -
-
- {isLoading || isRefetching ? ( + + + + {isLoading ? (
@@ -66,15 +62,10 @@ function Screen() {
-

- Empty newsfeed. Or you view the{" "} - - Global Newsfeed - -

+
+

{t("emptyFeedTitle")}

+

{t("emptyFeedSubtitle")}

+
@@ -102,7 +93,7 @@ function Screen() { ) : null}
-
-
+ + ); } diff --git a/apps/desktop2/src/routes/auth.lazy.tsx b/apps/desktop2/src/routes/auth.lazy.tsx new file mode 100644 index 00000000..d7a0cd4e --- /dev/null +++ b/apps/desktop2/src/routes/auth.lazy.tsx @@ -0,0 +1,16 @@ +import { Box, Container } from "@lume/ui"; +import { Outlet, createLazyFileRoute } from "@tanstack/react-router"; + +export const Route = createLazyFileRoute("/auth")({ + component: Screen, +}); + +function Screen() { + return ( + + + + + + ); +} diff --git a/apps/desktop2/src/routes/auth/create/index.lazy.tsx b/apps/desktop2/src/routes/auth/create/index.lazy.tsx deleted file mode 100644 index fed2597e..00000000 --- a/apps/desktop2/src/routes/auth/create/index.lazy.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import { LoaderIcon } from "@lume/icons"; -import { cn } from "@lume/utils"; -import { createLazyFileRoute, useNavigate } from "@tanstack/react-router"; -import { useState } from "react"; -import { useTranslation } from "react-i18next"; - -export const Route = createLazyFileRoute("/auth/create/")({ - component: Screen, -}); - -function Screen() { - const navigate = useNavigate(); - - const [t] = useTranslation(); - const [method, setMethod] = useState<"self" | "managed">("self"); - const [loading, setLoading] = useState(false); - - const next = () => { - setLoading(true); - - if (method === "self") { - navigate({ to: "/auth/create/self" }); - } else { - navigate({ to: "/auth/create/managed" }); - } - }; - - return ( -
-
-
-

{t("signup.title")}

-

- {t("signup.subtitle")} -

-
-
- - -
- - {method === "managed" ? ( -
-

- Attention: -

-

- You're chosing Managed by Provider, this feature still in - "Beta". -

-

- Some functions still missing or not work as expected, you - shouldn't create your main account with this method -

- - Learn more - -
- ) : null} -
-
-
-
- ); -} diff --git a/apps/desktop2/src/routes/auth/create/managed.lazy.tsx b/apps/desktop2/src/routes/auth/create/managed.lazy.tsx deleted file mode 100644 index 9258c283..00000000 --- a/apps/desktop2/src/routes/auth/create/managed.lazy.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { createLazyFileRoute } from '@tanstack/react-router' - -export const Route = createLazyFileRoute('/auth/create/managed')({ - component: () =>
Hello /auth/create/managed!
-}) \ No newline at end of file diff --git a/apps/desktop2/src/routes/auth/create/self.lazy.tsx b/apps/desktop2/src/routes/auth/create/self.lazy.tsx deleted file mode 100644 index 547045f1..00000000 --- a/apps/desktop2/src/routes/auth/create/self.lazy.tsx +++ /dev/null @@ -1,161 +0,0 @@ -import { useArk } from "@lume/ark"; -import { CheckIcon, EyeOffIcon, EyeOnIcon, LoaderIcon } from "@lume/icons"; -import { Keys } from "@lume/types"; -import * as Checkbox from "@radix-ui/react-checkbox"; -import { createLazyFileRoute, useNavigate } from "@tanstack/react-router"; -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; -import { toast } from "sonner"; - -export const Route = createLazyFileRoute("/auth/create/self")({ - component: Create, -}); - -function Create() { - const ark = useArk(); - const navigate = useNavigate(); - - const [t] = useTranslation(); - const [loading, setLoading] = useState(false); - const [showKey, setShowKey] = useState(false); - const [confirm, setConfirm] = useState({ c1: false, c2: false, c3: false }); - const [keys, setKeys] = useState(null); - - const submit = async () => { - setLoading(true); - try { - await ark.save_account(keys); - navigate({ - to: "/$account/home/local", - params: { account: keys.npub }, - search: { onboarding: true }, - replace: true, - }); - } catch (e) { - setLoading(false); - toast.error(e); - } - }; - - useEffect(() => { - async function genKeys() { - const res = await ark.create_keys(); - setKeys(res); - } - genKeys(); - }, []); - - return ( -
-
-
-

- {t("signupWithSelfManage.title")} -

-

- {t("signupWithSelfManage.subtitle")} -

-
-
-
-
- {keys ? ( - - ) : null} - -
-
-
- - setConfirm((state) => ({ ...state, c1: !state.c1 })) - } - className="flex size-7 appearance-none items-center justify-center rounded-lg bg-neutral-100 outline-none dark:bg-neutral-900" - id="confirm1" - > - - - - - -
-
- - setConfirm((state) => ({ ...state, c3: !state.c3 })) - } - className="flex size-7 appearance-none items-center justify-center rounded-lg bg-neutral-100 outline-none dark:bg-neutral-900" - id="confirm3" - > - - - - - -
-
- - setConfirm((state) => ({ ...state, c2: !state.c2 })) - } - className="flex size-7 appearance-none items-center justify-center rounded-lg bg-neutral-100 outline-none dark:bg-neutral-900" - id="confirm2" - > - - - - - -
-
-
- -
-
-
- ); -} diff --git a/apps/desktop2/src/routes/auth/import.lazy.tsx b/apps/desktop2/src/routes/auth/import.lazy.tsx deleted file mode 100644 index 3de95d31..00000000 --- a/apps/desktop2/src/routes/auth/import.lazy.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { useArk } from "@lume/ark"; -import { LoaderIcon } from "@lume/icons"; -import { createLazyFileRoute, useNavigate } from "@tanstack/react-router"; -import { invoke } from "@tauri-apps/api/core"; -import { useState } from "react"; -import { useTranslation } from "react-i18next"; -import { toast } from "sonner"; - -export const Route = createLazyFileRoute("/auth/import")({ - component: Import, -}); - -function Import() { - const ark = useArk(); - const navigate = useNavigate(); - - const [t] = useTranslation(); - const [key, setKey] = useState(""); - const [password, setPassword] = useState(""); - const [loading, setLoading] = useState(false); - - const submit = async () => { - if (!key.startsWith("nsec1")) return; - if (key.length < 30) return; - - setLoading(true); - - try { - const npub: string = await invoke("get_public_key", { nsec: key }); - await ark.save_account({ - npub, - nsec: key, - }); - navigate({ - to: "/$account/home/local", - params: { account: npub }, - search: { onboarding: true }, - replace: true, - }); - } catch (e) { - setLoading(false); - toast.error(e); - } - }; - - const isNip05 = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/.test(key); - const isNip49 = key.startsWith("ncryptsec"); - - return ( -
-
-
-

{t("login.title")}

-

- {t("login.subtitle")} -

-
-
-
-
- setKey(e.target.value)} - className="h-12 w-full resize-none rounded-xl border-transparent bg-neutral-100 pl-3 pr-10 placeholder:text-neutral-600 focus:border-blue-500 focus:ring focus:ring-blue-100 dark:bg-neutral-900 dark:focus:ring-blue-900" - /> -
- {isNip05 || isNip49 ? ( -
- - setPassword(e.target.value)} - className="h-12 w-full resize-none rounded-xl border-transparent bg-neutral-100 pl-3 pr-10 placeholder:text-neutral-600 focus:border-blue-500 focus:ring focus:ring-blue-100 dark:bg-neutral-900 dark:focus:ring-blue-900" - /> -
- ) : null} -
- -
-
-
- ); -} diff --git a/apps/desktop2/src/routes/auth/new/backup.tsx b/apps/desktop2/src/routes/auth/new/backup.tsx new file mode 100644 index 00000000..e90f21e4 --- /dev/null +++ b/apps/desktop2/src/routes/auth/new/backup.tsx @@ -0,0 +1,186 @@ +import { displayNsec } from "@lume/utils"; +import { createFileRoute, useNavigate } from "@tanstack/react-router"; +import { invoke } from "@tauri-apps/api/core"; +import { writeText } from "@tauri-apps/plugin-clipboard-manager"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { toast } from "sonner"; +import * as Checkbox from "@radix-ui/react-checkbox"; +import { CheckIcon } from "@lume/icons"; + +export const Route = createFileRoute("/auth/new/backup")({ + component: Screen, +}); + +function Screen() { + // @ts-ignore, magic!!! + const { account } = Route.useSearch(); + const { t } = useTranslation(); + + const [key, setKey] = useState(null); + const [passphase, setPassphase] = useState(""); + const [copied, setCopied] = useState(false); + const [confirm, setConfirm] = useState({ c1: false, c2: false, c3: false }); + + const navigate = useNavigate(); + + const submit = async () => { + try { + if (key) { + if (!confirm.c1 || !confirm.c2 || !confirm.c3) { + return toast.warning("You need to confirm before continue"); + } else { + return navigate({ + to: "/auth/settings", + search: { account, new: true }, + }); + } + } + + const encrypted: string = await invoke("get_encrypted_key", { + npub: account, + password: passphase, + }); + + setKey(encrypted); + } catch (e) { + toast.error(String(e)); + } + }; + + const copyKey = async () => { + try { + await writeText(key); + setCopied(true); + } catch (e) { + toast.error(e); + } + }; + + return ( +
+
+

Backup your sign in keys

+

+ It's use for login to Lume or other Nostr clients. You will lost + access to your account if you lose this key. +

+
+
+
+ +
+ setPassphase(e.target.value)} + className="h-11 w-full resize-none rounded-lg border-transparent bg-neutral-100 placeholder:text-neutral-600 focus:border-blue-500 focus:ring focus:ring-blue-100 dark:bg-neutral-900 dark:focus:ring-blue-900" + /> +
+
+ {key ? ( + <> +
+ +
+ + +
+
+
+
Before you continue:
+
+
+ + setConfirm((state) => ({ ...state, c1: !state.c1 })) + } + className="flex size-6 appearance-none items-center justify-center rounded-md bg-neutral-100 outline-none dark:bg-neutral-900" + id="confirm1" + > + + + + + +
+
+ + setConfirm((state) => ({ ...state, c2: !state.c2 })) + } + className="flex size-6 appearance-none items-center justify-center rounded-md bg-neutral-100 outline-none dark:bg-neutral-900" + id="confirm2" + > + + + + + +
+
+ + setConfirm((state) => ({ ...state, c3: !state.c3 })) + } + className="flex size-6 appearance-none items-center justify-center rounded-md bg-neutral-100 outline-none dark:bg-neutral-900" + id="confirm3" + > + + + + + +
+
+
+ + ) : null} +
+ +
+
+
+ ); +} diff --git a/apps/desktop2/src/routes/auth/new/profile.tsx b/apps/desktop2/src/routes/auth/new/profile.tsx new file mode 100644 index 00000000..57170983 --- /dev/null +++ b/apps/desktop2/src/routes/auth/new/profile.tsx @@ -0,0 +1,146 @@ +import { AvatarUploader } from "@/components/avatarUploader"; +import { LoaderIcon, PlusIcon } from "@lume/icons"; +import { Metadata } from "@lume/types"; +import { createFileRoute, useNavigate } from "@tanstack/react-router"; +import { useState } from "react"; +import { useForm } from "react-hook-form"; +import { useTranslation } from "react-i18next"; +import { toast } from "sonner"; + +export const Route = createFileRoute("/auth/new/profile")({ + component: Screen, + loader: ({ context }) => { + return context.ark.create_keys(); + }, +}); + +function Screen() { + const keys = Route.useLoaderData(); + const navigate = useNavigate(); + + const { t } = useTranslation(); + const { ark } = Route.useRouteContext(); + const { register, handleSubmit } = useForm(); + + const [picture, setPicture] = useState(""); + const [loading, setLoading] = useState(false); + + const onSubmit = async (data: { + name: string; + about: string; + website: string; + }) => { + setLoading(true); + + try { + // Save account keys + const save = await ark.save_account(keys.nsec); + + // Then create profile + if (save) { + const profile: Metadata = { ...data, picture }; + const eventId = await ark.create_profile(profile); + + if (eventId) { + navigate({ + to: "/auth/new/backup", + search: { account: keys.npub }, + replace: true, + }); + } + } + } catch (e) { + setLoading(false); + toast.error(String(e)); + } + }; + + return ( +
+
+

Let's set up your profile.

+
+
+
+ {picture ? ( + avatar + ) : null} + + + +
+
+
+
+ + +
+
+ + +
+
+ +