diff --git a/apps/desktop/index.html b/apps/desktop/index.html deleted file mode 100644 index 39bb9c2f..00000000 --- a/apps/desktop/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - Lume - - -
- - - diff --git a/apps/desktop/package.json b/apps/desktop/package.json deleted file mode 100644 index 8f022ba1..00000000 --- a/apps/desktop/package.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "name": "@lume/desktop", - "private": true, - "version": "3.0.0", - "scripts": { - "dev": "vite", - "build": "vite build" - }, - "dependencies": { - "@columns/antenas": "workspace:^", - "@columns/default": "workspace:^", - "@columns/foryou": "workspace:^", - "@columns/global": "workspace:^", - "@columns/group": "workspace:^", - "@columns/hashtag": "workspace:^", - "@columns/thread": "workspace:^", - "@columns/timeline": "workspace:^", - "@columns/trending-notes": "workspace:^", - "@columns/user": "workspace:^", - "@columns/waifu": "workspace:^", - "@getalby/sdk": "^3.2.3", - "@lume/ark": "workspace:^", - "@lume/icons": "workspace:^", - "@lume/storage": "workspace:^", - "@lume/ui": "workspace:^", - "@lume/utils": "workspace:^", - "@nostr-dev-kit/ndk": "^2.4.0", - "@radix-ui/react-accordion": "^1.1.2", - "@radix-ui/react-alert-dialog": "^1.0.5", - "@radix-ui/react-avatar": "^1.0.4", - "@radix-ui/react-checkbox": "^1.0.4", - "@radix-ui/react-collapsible": "^1.0.3", - "@radix-ui/react-dialog": "^1.0.5", - "@radix-ui/react-dropdown-menu": "^2.0.6", - "@radix-ui/react-hover-card": "^1.0.7", - "@radix-ui/react-popover": "^1.0.7", - "@radix-ui/react-select": "^2.0.0", - "@radix-ui/react-switch": "^1.0.3", - "@radix-ui/react-tooltip": "^1.0.7", - "@tanstack/react-query": "^5.18.1", - "framer-motion": "^11.0.3", - "i18next": "^23.8.2", - "i18next-resources-to-backend": "^1.2.0", - "jotai": "^2.6.4", - "minidenticons": "^4.2.0", - "nanoid": "^5.0.5", - "nostr-fetch": "^0.15.0", - "nostr-tools": "^1.17.0", - "react": "^18.2.0", - "react-currency-input-field": "^3.6.14", - "react-dom": "^18.2.0", - "react-hook-form": "^7.50.0", - "react-i18next": "^14.0.2", - "react-router-dom": "^6.22.0", - "smol-toml": "^1.1.4", - "sonner": "^1.4.0", - "virtua": "^0.23.3" - }, - "devDependencies": { - "@lume/tailwindcss": "workspace:^", - "@lume/tsconfig": "workspace:^", - "@lume/types": "workspace:^", - "@types/node": "^20.11.16", - "@types/react": "^18.2.52", - "@types/react-dom": "^18.2.18", - "@vitejs/plugin-react-swc": "^3.6.0", - "autoprefixer": "^10.4.17", - "cross-env": "^7.0.3", - "encoding": "^0.1.13", - "postcss": "^8.4.33", - "tailwindcss": "^3.4.1", - "typescript": "^5.3.3", - "vite": "^5.0.12", - "vite-plugin-top-level-await": "^1.4.1", - "vite-tsconfig-paths": "^4.3.1" - } -} diff --git a/apps/desktop/postcss.config.js b/apps/desktop/postcss.config.js deleted file mode 100644 index e873f1a4..00000000 --- a/apps/desktop/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; diff --git a/apps/desktop/public/anime.jpg b/apps/desktop/public/anime.jpg deleted file mode 100644 index f7f8eeab..00000000 Binary files a/apps/desktop/public/anime.jpg and /dev/null differ diff --git a/apps/desktop/public/art.jpg b/apps/desktop/public/art.jpg deleted file mode 100644 index efc36ab3..00000000 Binary files a/apps/desktop/public/art.jpg and /dev/null differ diff --git a/apps/desktop/public/clapping_hands.png b/apps/desktop/public/clapping_hands.png deleted file mode 100644 index 593056a4..00000000 Binary files a/apps/desktop/public/clapping_hands.png and /dev/null differ diff --git a/apps/desktop/public/clown_face.png b/apps/desktop/public/clown_face.png deleted file mode 100644 index 3c650616..00000000 Binary files a/apps/desktop/public/clown_face.png and /dev/null differ diff --git a/apps/desktop/public/columns/antenas.jpg b/apps/desktop/public/columns/antenas.jpg deleted file mode 100644 index 7be430dd..00000000 Binary files a/apps/desktop/public/columns/antenas.jpg and /dev/null differ diff --git a/apps/desktop/public/columns/antenas@2x.jpg b/apps/desktop/public/columns/antenas@2x.jpg deleted file mode 100644 index 4b38bb86..00000000 Binary files a/apps/desktop/public/columns/antenas@2x.jpg and /dev/null differ diff --git a/apps/desktop/public/columns/global.jpg b/apps/desktop/public/columns/global.jpg deleted file mode 100644 index 2288bfaf..00000000 Binary files a/apps/desktop/public/columns/global.jpg and /dev/null differ diff --git a/apps/desktop/public/columns/global@2x.jpg b/apps/desktop/public/columns/global@2x.jpg deleted file mode 100644 index e079bfe1..00000000 Binary files a/apps/desktop/public/columns/global@2x.jpg and /dev/null differ diff --git a/apps/desktop/public/columns/group.jpg b/apps/desktop/public/columns/group.jpg deleted file mode 100644 index 49a01657..00000000 Binary files a/apps/desktop/public/columns/group.jpg and /dev/null differ diff --git a/apps/desktop/public/columns/group@2x.jpg b/apps/desktop/public/columns/group@2x.jpg deleted file mode 100644 index 3718db40..00000000 Binary files a/apps/desktop/public/columns/group@2x.jpg and /dev/null differ diff --git a/apps/desktop/public/columns/trending-notes.jpg b/apps/desktop/public/columns/trending-notes.jpg deleted file mode 100644 index 0ae75021..00000000 Binary files a/apps/desktop/public/columns/trending-notes.jpg and /dev/null differ diff --git a/apps/desktop/public/columns/trending-notes@2x.jpg b/apps/desktop/public/columns/trending-notes@2x.jpg deleted file mode 100644 index b99e5849..00000000 Binary files a/apps/desktop/public/columns/trending-notes@2x.jpg and /dev/null differ diff --git a/apps/desktop/public/columns/waifu.jpg b/apps/desktop/public/columns/waifu.jpg deleted file mode 100644 index 3af7c137..00000000 Binary files a/apps/desktop/public/columns/waifu.jpg and /dev/null differ diff --git a/apps/desktop/public/columns/waifu@2x.jpg b/apps/desktop/public/columns/waifu@2x.jpg deleted file mode 100644 index 67b4556a..00000000 Binary files a/apps/desktop/public/columns/waifu@2x.jpg and /dev/null differ diff --git a/apps/desktop/public/crying_face.png b/apps/desktop/public/crying_face.png deleted file mode 100644 index 47bfc717..00000000 Binary files a/apps/desktop/public/crying_face.png and /dev/null differ diff --git a/apps/desktop/public/face_with_open_mouth.png b/apps/desktop/public/face_with_open_mouth.png deleted file mode 100644 index 4b6f5c57..00000000 Binary files a/apps/desktop/public/face_with_open_mouth.png and /dev/null differ diff --git a/apps/desktop/public/face_with_tongue.png b/apps/desktop/public/face_with_tongue.png deleted file mode 100644 index b6bf2b8e..00000000 Binary files a/apps/desktop/public/face_with_tongue.png and /dev/null differ diff --git a/apps/desktop/public/fallback-image.jpg b/apps/desktop/public/fallback-image.jpg deleted file mode 100644 index 1490c0b7..00000000 Binary files a/apps/desktop/public/fallback-image.jpg and /dev/null differ diff --git a/apps/desktop/public/gaming.jpg b/apps/desktop/public/gaming.jpg deleted file mode 100644 index de58aeeb..00000000 Binary files a/apps/desktop/public/gaming.jpg and /dev/null differ diff --git a/apps/desktop/public/ghost.png b/apps/desktop/public/ghost.png deleted file mode 100644 index 8cf4548c..00000000 Binary files a/apps/desktop/public/ghost.png and /dev/null differ diff --git a/apps/desktop/public/heading.png b/apps/desktop/public/heading.png deleted file mode 100644 index fb740945..00000000 Binary files a/apps/desktop/public/heading.png and /dev/null differ diff --git a/apps/desktop/public/heading@2x.png b/apps/desktop/public/heading@2x.png deleted file mode 100644 index 802c679b..00000000 Binary files a/apps/desktop/public/heading@2x.png and /dev/null differ diff --git a/apps/desktop/public/icon.png b/apps/desktop/public/icon.png deleted file mode 100644 index 0e62e01a..00000000 Binary files a/apps/desktop/public/icon.png and /dev/null differ diff --git a/apps/desktop/public/movie.jpg b/apps/desktop/public/movie.jpg deleted file mode 100644 index bcb36809..00000000 Binary files a/apps/desktop/public/movie.jpg and /dev/null differ diff --git a/apps/desktop/public/music.jpg b/apps/desktop/public/music.jpg deleted file mode 100644 index 1c533c15..00000000 Binary files a/apps/desktop/public/music.jpg and /dev/null differ diff --git a/apps/desktop/public/nsfw.jpg b/apps/desktop/public/nsfw.jpg deleted file mode 100644 index f9e183d2..00000000 Binary files a/apps/desktop/public/nsfw.jpg and /dev/null differ diff --git a/apps/desktop/public/photography.jpg b/apps/desktop/public/photography.jpg deleted file mode 100644 index 6f117830..00000000 Binary files a/apps/desktop/public/photography.jpg and /dev/null differ diff --git a/apps/desktop/public/technology.jpg b/apps/desktop/public/technology.jpg deleted file mode 100644 index cfd733f0..00000000 Binary files a/apps/desktop/public/technology.jpg and /dev/null differ diff --git a/apps/desktop/public/translate.jpg b/apps/desktop/public/translate.jpg deleted file mode 100644 index 2b43e2d7..00000000 Binary files a/apps/desktop/public/translate.jpg and /dev/null differ diff --git a/apps/desktop/public/translate@2x.jpg b/apps/desktop/public/translate@2x.jpg deleted file mode 100644 index 8cfde295..00000000 Binary files a/apps/desktop/public/translate@2x.jpg and /dev/null differ diff --git a/apps/desktop/public/tutorial-1.gif b/apps/desktop/public/tutorial-1.gif deleted file mode 100644 index a536c39b..00000000 Binary files a/apps/desktop/public/tutorial-1.gif and /dev/null differ diff --git a/apps/desktop/public/tutorial-2.gif b/apps/desktop/public/tutorial-2.gif deleted file mode 100644 index 16f74d34..00000000 Binary files a/apps/desktop/public/tutorial-2.gif and /dev/null differ diff --git a/apps/desktop/public/tutorial-3.gif b/apps/desktop/public/tutorial-3.gif deleted file mode 100644 index 37ba9f6e..00000000 Binary files a/apps/desktop/public/tutorial-3.gif and /dev/null differ diff --git a/apps/desktop/public/zap.png b/apps/desktop/public/zap.png deleted file mode 100644 index d0747151..00000000 Binary files a/apps/desktop/public/zap.png and /dev/null differ diff --git a/apps/desktop/src/app.css b/apps/desktop/src/app.css deleted file mode 100644 index 4c25b06a..00000000 --- a/apps/desktop/src/app.css +++ /dev/null @@ -1,48 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; - -@layer utilities { - .break-p { - word-break: break-word; - word-wrap: break-word; - overflow-wrap: break-word; - } - - .prose :where(iframe):not(:where([class~='not-prose'] *)) { - @apply w-full h-auto mx-auto aspect-video; - } - - .shadow-toolbar { - box-shadow: 0 0 #0000, 0 0 #0000, 0 8px 24px 0 rgba(0, 0, 0, .2), 0 2px 8px 0 rgba(0, 0, 0, .08), inset 0 0 0 1px rgba(0, 0, 0, .2), inset 0 0 0 2px hsla(0, 0%, 100%, .14) - } -} - -html { - font-size: 14px; -} - -a { - @apply cursor-default no-underline !important; -} - -button { - @apply cursor-default focus:outline-none; -} - -input::-ms-reveal, -input::-ms-clear { - display: none; -} - -::-webkit-input-placeholder { - line-height: normal; -} - -.border { - background-clip: padding-box; -} - -media-controller { - @apply w-full; -} \ No newline at end of file diff --git a/apps/desktop/src/app.tsx b/apps/desktop/src/app.tsx deleted file mode 100644 index 7783f54e..00000000 --- a/apps/desktop/src/app.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { ArkProvider } from "@lume/ark"; -import { StorageProvider } from "@lume/storage"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { I18nextProvider } from "react-i18next"; -import { Toaster } from "sonner"; -import i18n from "./i18n"; -import Router from "./router"; - -const queryClient = new QueryClient({ - defaultOptions: { - queries: { - retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 10000), // 10 seconds - }, - }, -}); - -export default function App() { - return ( - - - - - - - - - - - ); -} diff --git a/apps/desktop/src/i18n.ts b/apps/desktop/src/i18n.ts deleted file mode 100644 index 080f7339..00000000 --- a/apps/desktop/src/i18n.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { resolveResource } from "@tauri-apps/api/path"; -import { readTextFile } from "@tauri-apps/plugin-fs"; -import { locale } from "@tauri-apps/plugin-os"; -import i18n from "i18next"; -import resourcesToBackend from "i18next-resources-to-backend"; -import { initReactI18next } from "react-i18next"; - -const currentLocale = (await locale()).slice(0, 2); - -i18n - .use( - resourcesToBackend(async (language: string) => { - const file_path = await resolveResource(`locales/${language}.json`); - return JSON.parse(await readTextFile(file_path)); - }), - ) - .use(initReactI18next) - .init({ - lng: currentLocale, - fallbackLng: "en", - interpolation: { - escapeValue: false, - }, - }); - -export default i18n; diff --git a/apps/desktop/src/main.jsx b/apps/desktop/src/main.jsx deleted file mode 100644 index 01e6aed0..00000000 --- a/apps/desktop/src/main.jsx +++ /dev/null @@ -1,8 +0,0 @@ -import { createRoot } from "react-dom/client"; -import App from "./app"; -import "./app.css"; - -const container = document.getElementById("root"); -const root = createRoot(container); - -root.render(); diff --git a/apps/desktop/src/router.tsx b/apps/desktop/src/router.tsx deleted file mode 100644 index bdd9940e..00000000 --- a/apps/desktop/src/router.tsx +++ /dev/null @@ -1,290 +0,0 @@ -import { useArk } from "@lume/ark"; -import { LoaderIcon } from "@lume/icons"; -import { - RouterProvider, - createBrowserRouter, - defer, - redirect, -} from "react-router-dom"; -import { ErrorScreen } from "./routes/error"; - -export default function Router() { - const ark = useArk(); - - const router = createBrowserRouter([ - { - async lazy() { - const { AppLayout } = await import("@lume/ui"); - return { Component: AppLayout }; - }, - children: [ - { - path: "/", - errorElement: , - async lazy() { - const { HomeLayout } = await import("@lume/ui"); - return { Component: HomeLayout }; - }, - loader: async () => { - const signer = await ark.verify_signer(); - if (!signer) return redirect("auth"); - return null; - }, - children: [ - { - index: true, - async lazy() { - const { HomeScreen } = await import("./routes/home"); - return { Component: HomeScreen }; - }, - }, - ], - }, - { - path: "settings", - async lazy() { - const { SettingsLayout } = await import("@lume/ui"); - return { Component: SettingsLayout }; - }, - children: [ - { - index: true, - async lazy() { - const { GeneralSettingScreen } = await import( - "./routes/settings/general" - ); - return { Component: GeneralSettingScreen }; - }, - }, - { - path: "profile", - async lazy() { - const { ProfileSettingScreen } = await import( - "./routes/settings/profile" - ); - return { Component: ProfileSettingScreen }; - }, - }, - { - path: "backup", - async lazy() { - const { BackupSettingScreen } = await import( - "./routes/settings/backup" - ); - return { Component: BackupSettingScreen }; - }, - }, - { - path: "advanced", - async lazy() { - const { AdvancedSettingScreen } = await import( - "./routes/settings/advanced" - ); - return { Component: AdvancedSettingScreen }; - }, - }, - { - path: "nwc", - async lazy() { - const { NWCScreen } = await import("./routes/settings/nwc"); - return { Component: NWCScreen }; - }, - }, - { - path: "about", - async lazy() { - const { AboutScreen } = await import("./routes/settings/about"); - return { Component: AboutScreen }; - }, - }, - ], - }, - { - path: "activity", - async lazy() { - const { ActivityScreen } = await import("./routes/activty"); - return { Component: ActivityScreen }; - }, - children: [ - { - path: ":id", - async lazy() { - const { ActivityIdScreen } = await import( - "./routes/activty/id" - ); - return { Component: ActivityIdScreen }; - }, - }, - ], - }, - { - path: "relays", - async lazy() { - const { RelaysScreen } = await import("./routes/relays"); - return { Component: RelaysScreen }; - }, - children: [ - { - index: true, - async lazy() { - const { RelayGlobalScreen } = await import( - "./routes/relays/global" - ); - return { Component: RelayGlobalScreen }; - }, - }, - { - path: "follows", - async lazy() { - const { RelayFollowsScreen } = await import( - "./routes/relays/follows" - ); - return { Component: RelayFollowsScreen }; - }, - }, - { - path: ":url", - loader: async ({ request, params }) => { - return defer({ - relay: fetch(`https://${params.url}`, { - method: "GET", - headers: { - Accept: "application/nostr+json", - }, - signal: request.signal, - }).then((res) => res.json()), - }); - }, - async lazy() { - const { RelayUrlScreen } = await import("./routes/relays/url"); - return { Component: RelayUrlScreen }; - }, - }, - ], - }, - { - path: "depot", - children: [ - { - index: true, - async lazy() { - const { DepotScreen } = await import("./routes/depot"); - return { Component: DepotScreen }; - }, - }, - { - path: "onboarding", - async lazy() { - const { DepotOnboardingScreen } = await import( - "./routes/depot/onboarding" - ); - return { Component: DepotOnboardingScreen }; - }, - }, - ], - }, - ], - }, - { - path: "auth", - errorElement: , - async lazy() { - const { AuthLayout } = await import("@lume/ui"); - return { Component: AuthLayout }; - }, - children: [ - { - index: true, - async lazy() { - const { WelcomeScreen } = await import("./routes/auth/welcome"); - return { Component: WelcomeScreen }; - }, - }, - { - path: "create", - async lazy() { - const { CreateAccountScreen } = await import( - "./routes/auth/create" - ); - return { Component: CreateAccountScreen }; - }, - }, - { - path: "create-keys", - async lazy() { - const { CreateAccountKeys } = await import( - "./routes/auth/create-keys" - ); - return { Component: CreateAccountKeys }; - }, - }, - { - path: "create-address", - loader: async () => { - // return await ark.getOAuthServices(); - return null; - }, - async lazy() { - const { CreateAccountAddress } = await import( - "./routes/auth/create-address" - ); - return { Component: CreateAccountAddress }; - }, - }, - { - path: "login", - async lazy() { - const { LoginScreen } = await import("./routes/auth/login"); - return { Component: LoginScreen }; - }, - }, - { - path: "login-key", - async lazy() { - const { LoginWithKey } = await import("./routes/auth/login-key"); - return { Component: LoginWithKey }; - }, - }, - { - path: "login-nsecbunker", - async lazy() { - const { LoginWithNsecbunker } = await import( - "./routes/auth/login-nsecbunker" - ); - return { Component: LoginWithNsecbunker }; - }, - }, - { - path: "login-oauth", - async lazy() { - const { LoginWithOAuth } = await import( - "./routes/auth/login-oauth" - ); - return { Component: LoginWithOAuth }; - }, - }, - { - path: "onboarding", - async lazy() { - const { OnboardingScreen } = await import( - "./routes/auth/onboarding" - ); - return { Component: OnboardingScreen }; - }, - }, - ], - }, - ]); - - return ( - - - - } - future={{ v7_startTransition: true }} - /> - ); -} diff --git a/apps/desktop/src/routes/activty/components/activityRepost.tsx b/apps/desktop/src/routes/activty/components/activityRepost.tsx deleted file mode 100644 index cc577bd0..00000000 --- a/apps/desktop/src/routes/activty/components/activityRepost.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { User } from "@lume/ark"; -import { NDKEvent } from "@nostr-dev-kit/ndk"; -import { useTranslation } from "react-i18next"; -import { Link } from "react-router-dom"; - -export function ActivityRepost({ event }: { event: NDKEvent }) { - const { t } = useTranslation(); - - return ( - - - -
- -
- -

{t("activity.repost")}

-
-
- -
-
- - ); -} diff --git a/apps/desktop/src/routes/activty/components/activityText.tsx b/apps/desktop/src/routes/activty/components/activityText.tsx deleted file mode 100644 index 7a0e4cb3..00000000 --- a/apps/desktop/src/routes/activty/components/activityText.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { User } from "@lume/ark"; -import { NDKEvent } from "@nostr-dev-kit/ndk"; -import { useTranslation } from "react-i18next"; -import { Link } from "react-router-dom"; - -export function ActivityText({ event }: { event: NDKEvent }) { - const { t } = useTranslation(); - - return ( - - - -
- -
- -

{t("activity.mention")}

-
-
- -
-
- - ); -} diff --git a/apps/desktop/src/routes/activty/components/activityZap.tsx b/apps/desktop/src/routes/activty/components/activityZap.tsx deleted file mode 100644 index 2085900f..00000000 --- a/apps/desktop/src/routes/activty/components/activityZap.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { User } from "@lume/ark"; -import { compactNumber } from "@lume/utils"; -import { NDKEvent, zapInvoiceFromEvent } from "@nostr-dev-kit/ndk"; -import { useTranslation } from "react-i18next"; -import { Link } from "react-router-dom"; - -export function ActivityZap({ event }: { event: NDKEvent }) { - const { t } = useTranslation(); - const invoice = zapInvoiceFromEvent(event); - - return ( - - - -
- -
- -

- {t("activity.zap")} {compactNumber.format(invoice.amount)} sats -

-
-
- -
-
- - ); -} diff --git a/apps/desktop/src/routes/activty/components/list.tsx b/apps/desktop/src/routes/activty/components/list.tsx deleted file mode 100644 index 02d6ff2f..00000000 --- a/apps/desktop/src/routes/activty/components/list.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import { useArk } from "@lume/ark"; -import { ArrowRightCircleIcon, LoaderIcon } from "@lume/icons"; -import { FETCH_LIMIT } from "@lume/utils"; -import { NDKEvent, NDKKind } from "@nostr-dev-kit/ndk"; -import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query"; -import { useCallback, useMemo } from "react"; -import { useTranslation } from "react-i18next"; -import { ActivityRepost } from "./activityRepost"; -import { ActivityText } from "./activityText"; -import { ActivityZap } from "./activityZap"; - -export function ActivityList() { - const ark = useArk(); - const queryClient = useQueryClient(); - - const { t } = useTranslation(); - const { data, hasNextPage, isLoading, isFetchingNextPage, fetchNextPage } = - useInfiniteQuery({ - queryKey: ["activity"], - initialPageParam: 0, - queryFn: async ({ - signal, - pageParam, - }: { - signal: AbortSignal; - pageParam: number; - }) => { - const events = await ark.getInfiniteEvents({ - filter: { - kinds: [NDKKind.Text, NDKKind.Repost, NDKKind.Zap], - "#p": [ark.account.pubkey], - }, - limit: FETCH_LIMIT, - pageParam, - signal, - }); - - return events; - }, - getNextPageParam: (lastPage) => { - const lastEvent = lastPage.at(-1); - if (!lastEvent) return; - return lastEvent.created_at - 1; - }, - initialData: () => { - const queryCacheData = queryClient.getQueryState(["activity"]) - ?.data as NDKEvent[]; - if (queryCacheData) { - return { - pageParams: [undefined, 1], - pages: [queryCacheData], - }; - } - }, - staleTime: 360 * 1000, - refetchOnWindowFocus: false, - refetchOnMount: false, - }); - - const allEvents = useMemo( - () => (data ? data.pages.flatMap((page) => page) : []), - [data], - ); - - const renderEvenKind = useCallback( - (event: NDKEvent) => { - if (event.pubkey === ark.account.pubkey) return null; - switch (event.kind) { - case NDKKind.Text: - return ; - case NDKKind.Repost: - return ; - case NDKKind.Zap: - return ; - default: - return ; - } - }, - [data], - ); - - return ( -
- {isLoading ? ( -
- -
- ) : !allEvents.length ? ( -
-

🎉

-

{t("activity.empty")}

-
- ) : ( - allEvents.map((event) => renderEvenKind(event)) - )} -
- {hasNextPage ? ( - - ) : null} -
-
- ); -} diff --git a/apps/desktop/src/routes/activty/components/rootNote.tsx b/apps/desktop/src/routes/activty/components/rootNote.tsx deleted file mode 100644 index 5ba7e589..00000000 --- a/apps/desktop/src/routes/activty/components/rootNote.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { Note, useEvent } from "@lume/ark"; - -export function ActivityRootNote({ eventId }: { eventId: string }) { - const { isLoading, isError, data } = useEvent(eventId); - - if (isLoading) { - return ( -
-
-
-
-
- ); - } - - if (isError) { - return ( -
-
- Failed to fetch event -
-
- ); - } - - return ( - - -
- -
- -
- -
-
- - - ); -} diff --git a/apps/desktop/src/routes/activty/components/singleRepost.tsx b/apps/desktop/src/routes/activty/components/singleRepost.tsx deleted file mode 100644 index c97e876b..00000000 --- a/apps/desktop/src/routes/activty/components/singleRepost.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { User } from "@lume/ark"; -import { NDKEvent } from "@nostr-dev-kit/ndk"; -import { useTranslation } from "react-i18next"; -import { ActivityRootNote } from "./rootNote"; - -export function ActivitySingleRepost({ event }: { event: NDKEvent }) { - const { t } = useTranslation(); - const repostId = event.tags.find((el) => el[0] === "e")[1]; - - return ( -
-
-

- {t("activity.boost")} -

-

- {t("activity.boostSubtitle")} -

-
-
-
- - - - - -
-
-

{t("activity.repost")}

-
-
- -
-
-
- ); -} diff --git a/apps/desktop/src/routes/activty/components/singleText.tsx b/apps/desktop/src/routes/activty/components/singleText.tsx deleted file mode 100644 index d0c6d734..00000000 --- a/apps/desktop/src/routes/activty/components/singleText.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { Note, useArk } from "@lume/ark"; -import { NDKEvent } from "@nostr-dev-kit/ndk"; -import { useTranslation } from "react-i18next"; -import { ActivityRootNote } from "./rootNote"; - -export function ActivitySingleText({ event }: { event: NDKEvent }) { - const ark = useArk(); - const thread = ark.getEventThread({ - content: event.content, - tags: event.tags, - }); - - const { t } = useTranslation(); - - return ( -
-
-

- {t("activity.conversation")} -

-

- {t("activity.conversationSubtitle")} -

-
-
-
- {thread ? ( -
- {thread.rootEventId ? ( - - ) : null} - {thread.replyEventId ? ( - - ) : null} -
- ) : null} -
-
-

- {t("activity.newReply")} -

-
-
-
- - -
- -
- -
- -
-
- - -
-
-
-
- ); -} diff --git a/apps/desktop/src/routes/activty/components/singleZap.tsx b/apps/desktop/src/routes/activty/components/singleZap.tsx deleted file mode 100644 index 59e25c42..00000000 --- a/apps/desktop/src/routes/activty/components/singleZap.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { User } from "@lume/ark"; -import { compactNumber } from "@lume/utils"; -import { NDKEvent, zapInvoiceFromEvent } from "@nostr-dev-kit/ndk"; -import { ActivityRootNote } from "./rootNote"; - -export function ActivitySingleZap({ event }: { event: NDKEvent }) { - const zapEventId = event.tags.find((el) => el[0] === "e")[1]; - const invoice = zapInvoiceFromEvent(event); - - return ( -
-
-

- Conversation -

-

- @ Someone has replied to your note -

-
-
-
- - - - - -
-
-

- Zap you {compactNumber.format(invoice.amount)} sats for -

-
-
- -
-
-
- ); -} diff --git a/apps/desktop/src/routes/activty/id.tsx b/apps/desktop/src/routes/activty/id.tsx deleted file mode 100644 index 53350b55..00000000 --- a/apps/desktop/src/routes/activty/id.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { useEvent } from "@lume/ark"; -import { LoaderIcon } from "@lume/icons"; -import { NDKKind } from "@nostr-dev-kit/ndk"; -import { useParams } from "react-router-dom"; -import { ActivitySingleRepost } from "./components/singleRepost"; -import { ActivitySingleText } from "./components/singleText"; -import { ActivitySingleZap } from "./components/singleZap"; - -export function ActivityIdScreen() { - const { id } = useParams(); - const { isLoading, data } = useEvent(id); - - if (isLoading || !data) { - return ( -
- -
- ); - } - - if (data.kind === NDKKind.Text) return ; - if (data.kind === NDKKind.Zap) return ; - if (data.kind === NDKKind.Repost) - return ; - - return ; -} diff --git a/apps/desktop/src/routes/activty/index.tsx b/apps/desktop/src/routes/activty/index.tsx deleted file mode 100644 index 1dc3e54c..00000000 --- a/apps/desktop/src/routes/activty/index.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { activityUnreadAtom } from "@lume/utils"; -import { useSetAtom } from "jotai"; -import { useEffect } from "react"; -import { useTranslation } from "react-i18next"; -import { Outlet } from "react-router-dom"; -import { ActivityList } from "./components/list"; - -export function ActivityScreen() { - const { t } = useTranslation(); - const setUnreadActivity = useSetAtom(activityUnreadAtom); - - useEffect(() => { - setUnreadActivity(0); - }, []); - - return ( -
-
-
- {t("activity.title")} -
- -
-
- -
-
- ); -} diff --git a/apps/desktop/src/routes/auth/create-address.tsx b/apps/desktop/src/routes/auth/create-address.tsx deleted file mode 100644 index dcf8833a..00000000 --- a/apps/desktop/src/routes/auth/create-address.tsx +++ /dev/null @@ -1,264 +0,0 @@ -import { useArk } from "@lume/ark"; -import { CheckIcon, ChevronDownIcon, LoaderIcon } from "@lume/icons"; -import { useStorage } from "@lume/storage"; -import { onboardingAtom } from "@lume/utils"; -import NDK, { - NDKEvent, - NDKKind, - NDKNip46Signer, - NDKPrivateKeySigner, -} from "@nostr-dev-kit/ndk"; -import * as Select from "@radix-ui/react-select"; -import { UnlistenFn } from "@tauri-apps/api/event"; -import { Window } from "@tauri-apps/api/window"; -import { useSetAtom } from "jotai"; -import { useState } from "react"; -import { useForm } from "react-hook-form"; -import { useTranslation } from "react-i18next"; -import { useLoaderData, useNavigate } from "react-router-dom"; -import { toast } from "sonner"; - -const Item = ({ event }: { event: NDKEvent }) => { - const domain = JSON.parse(event.content).nip05.replace("_@", ""); - - return ( - - @{domain} - - - - - ); -}; - -export function CreateAccountAddress() { - const ark = useArk(); - const storage = useStorage(); - const services = useLoaderData() as NDKEvent[]; - const setOnboarding = useSetAtom(onboardingAtom); - const navigate = useNavigate(); - - const [serviceId, setServiceId] = useState(services?.[0]?.id); - const [loading, setIsLoading] = useState(false); - - const { t } = useTranslation(); - const { - register, - handleSubmit, - formState: { isValid }, - } = useForm(); - - const getDomainName = (id: string) => { - const event = services.find((ev) => ev.id === id); - return JSON.parse(event.content).nip05.replace("_@", "") as string; - }; - - const onSubmit = async (data: { username: string; email: string }) => { - try { - setIsLoading(true); - - const domain = getDomainName(serviceId); - const service = services.find((ev) => ev.id === serviceId); - - // generate ndk for nsecbunker - const localSigner = NDKPrivateKeySigner.generate(); - const bunker = new NDK({ - explicitRelayUrls: [ - "wss://relay.nsecbunker.com/", - "wss://nostr.vulpem.com/", - ], - }); - await bunker.connect(2000); - - // generate tmp remote singer for create account - const remoteSigner = new NDKNip46Signer( - bunker, - service.pubkey, - localSigner, - ); - - // handle auth url request - let unlisten: UnlistenFn; - let authWindow: Window; - let account: string = undefined; - - remoteSigner.addListener("authUrl", async (authUrl: string) => { - authWindow = new Window(`auth-${serviceId}`, { - url: authUrl, - title: domain, - titleBarStyle: "overlay", - width: 600, - height: 650, - center: true, - closable: false, - }); - unlisten = await authWindow.onCloseRequested(() => { - if (!account) { - setIsLoading(false); - unlisten(); - - return authWindow.close(); - } - }); - }); - - // create new account - account = await remoteSigner.createAccount( - data.username, - domain, - data.email, - ); - - if (!account) { - unlisten(); - setIsLoading(false); - - authWindow.close(); - - return toast.error("Failed to create new account, try again later"); - } - - unlisten(); - authWindow.close(); - - // add account to storage - await storage.createSetting("nsecbunker", "1"); - const newAccount = await storage.createAccount({ - pubkey: account, - privkey: localSigner.privateKey, - }); - ark.account = newAccount; - - // get final signer with newly created account - const finalSigner = new NDKNip46Signer(bunker, account, localSigner); - await finalSigner.blockUntilReady(); - - // update main ndk instance signer - ark.updateNostrSigner({ signer: finalSigner }); - - // remove default nsecbunker profile and contact list - // await ark.createEvent({ kind: NDKKind.Metadata, content: "", tags: [] }); - await ark.createEvent({ kind: NDKKind.Contacts, content: "", tags: [] }); - - setIsLoading(false); - setOnboarding({ open: true, newUser: true }); - - return navigate("/auth/onboarding", { replace: true }); - } catch (e) { - setIsLoading(false); - toast.error(String(e)); - } - }; - - return ( -
-
-
-

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

-
- {!services ? ( -
- -
- ) : ( -
-
-
- -
-
- - - - @{getDomainName(serviceId)} - - - - - - - - - - {t("signupWithProvider.chooseProvider")} - - {services.map((service) => ( - - ))} - - - - - -
- - {t("signupWithProvider.usernameFooter")} - -
-
-
-
- - -
- - {t("signupWithProvider.emailFooter")} - -
-
-
- -
-
- )} -
-
- ); -} diff --git a/apps/desktop/src/routes/auth/create-keys.tsx b/apps/desktop/src/routes/auth/create-keys.tsx deleted file mode 100644 index 84cbdd6c..00000000 --- a/apps/desktop/src/routes/auth/create-keys.tsx +++ /dev/null @@ -1,169 +0,0 @@ -import { CheckIcon, EyeOffIcon, EyeOnIcon, LoaderIcon } from "@lume/icons"; -import { Keys } from "@lume/types"; -import { onboardingAtom } from "@lume/utils"; -import * as Checkbox from "@radix-ui/react-checkbox"; -import { invoke } from "@tauri-apps/api/core"; -import { desktopDir } from "@tauri-apps/api/path"; -import { save } from "@tauri-apps/plugin-dialog"; -import { writeTextFile } from "@tauri-apps/plugin-fs"; -import { useSetAtom } from "jotai"; -import { nanoid } from "nanoid"; -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; -import { useNavigate } from "react-router-dom"; -import { toast } from "sonner"; - -export function CreateAccountKeys() { - const setOnboarding = useSetAtom(onboardingAtom); - const navigate = useNavigate(); - - const [t] = useTranslation(); - const [key, setKey] = useState(""); - const [loading, setLoading] = useState(false); - const [showKey, setShowKey] = useState(false); - const [confirm, setConfirm] = useState({ c1: false, c2: false, c3: false }); - - const submit = async () => { - try { - setLoading(true); - - // trigger save key - const save = await invoke("save_key", { nsec: key }); - - if (!save) { - setLoading(false); - toast.error("Save account keys failed, please try again later."); - } - - // update state - setLoading(false); - setOnboarding({ open: true, newUser: true }); - - // redirect to next step - return navigate("/auth/onboarding", { replace: true }); - } catch (e) { - setLoading(false); - toast.error(String(e)); - } - }; - - useEffect(() => { - async function createAccountKeys() { - const keys: Keys = await invoke("create_keys"); - setKey(keys.nsec); - } - createAccountKeys(); - }, []); - - return ( -
-
-
-

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

-

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

-
-
-
-
- - -
-
-
- - setConfirm((state) => ({ ...state, c1: !state.c1 })) - } - className="flex size-7 appearance-none items-center justify-center rounded-lg bg-neutral-900 outline-none" - id="confirm1" - > - - - - - -
-
- - setConfirm((state) => ({ ...state, c2: !state.c2 })) - } - className="flex size-7 appearance-none items-center justify-center rounded-lg bg-neutral-900 outline-none" - id="confirm2" - > - - - - - -
-
- - setConfirm((state) => ({ ...state, c3: !state.c3 })) - } - className="flex size-7 appearance-none items-center justify-center rounded-lg bg-neutral-900 outline-none" - id="confirm3" - > - - - - - -
-
-
- -
-
-
- ); -} diff --git a/apps/desktop/src/routes/auth/create.tsx b/apps/desktop/src/routes/auth/create.tsx deleted file mode 100644 index 5623022e..00000000 --- a/apps/desktop/src/routes/auth/create.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import { LoaderIcon } from "@lume/icons"; -import { cn } from "@lume/utils"; -import { useState } from "react"; -import { useTranslation } from "react-i18next"; -import { useNavigate } from "react-router-dom"; - -export function CreateAccountScreen() { - 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("/auth/create-keys"); - } else { - navigate("/auth/create-address"); - } - }; - - 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/desktop/src/routes/auth/login-key.tsx b/apps/desktop/src/routes/auth/login-key.tsx deleted file mode 100644 index 2f72380e..00000000 --- a/apps/desktop/src/routes/auth/login-key.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import { EyeOffIcon, EyeOnIcon, LoaderIcon } from "@lume/icons"; -import { useStorage } from "@lume/storage"; -import { invoke } from "@tauri-apps/api/core"; -import { useState } from "react"; -import { useForm } from "react-hook-form"; -import { Trans, useTranslation } from "react-i18next"; -import { useNavigate } from "react-router-dom"; -import { toast } from "sonner"; - -export function LoginWithKey() { - const storage = useStorage(); - const navigate = useNavigate(); - - const [showKey, setShowKey] = useState(false); - const [loading, setLoading] = useState(false); - - const { t } = useTranslation("loginWithPrivkey.subtitle"); - const { - register, - handleSubmit, - setError, - formState: { errors, isValid }, - } = useForm(); - - const onSubmit = async (data: { nsec: string }) => { - try { - if (!data.nsec.startsWith("nsec1")) - return toast.error("You need to enter a private key start with nsec1"); - - setLoading(true); - - // trigger save key - const save = await invoke("save_key", { nsec: data.nsec }); - - if (!save) { - setLoading(false); - toast.error("Save account keys failed, please try again later."); - } - - // redirect to next step - return navigate("/auth/onboarding", { replace: true }); - } catch (e) { - setLoading(false); - setError("nsec", { - type: "manual", - message: String(e), - }); - } - }; - - return ( -
-
-
-

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

-

- - Lume will put your private key to{" "} - - {storage.platform === "macos" - ? "Apple Keychain" - : storage.platform === "windows" - ? "Credential Manager" - : "Secret Service"} - - . It will be secured by your OS. - -

-
-
-
-
- - {errors.nsec && ( -

- {errors.nsec.message as string} -

- )} - -
- -
-
-
-
- ); -} diff --git a/apps/desktop/src/routes/auth/login-nsecbunker.tsx b/apps/desktop/src/routes/auth/login-nsecbunker.tsx deleted file mode 100644 index fb38b39d..00000000 --- a/apps/desktop/src/routes/auth/login-nsecbunker.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import { useArk } from "@lume/ark"; -import { LoaderIcon } from "@lume/icons"; -import { useStorage } from "@lume/storage"; -import NDK, { NDKNip46Signer, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk"; -import { nip19 } from "nostr-tools"; -import { useState } from "react"; -import { useForm } from "react-hook-form"; -import { useTranslation } from "react-i18next"; -import { useNavigate } from "react-router-dom"; -import { toast } from "sonner"; - -export function LoginWithNsecbunker() { - const ark = useArk(); - const storage = useStorage(); - const navigate = useNavigate(); - - const [loading, setLoading] = useState(false); - - const { t } = useTranslation(); - const { - register, - handleSubmit, - setError, - formState: { errors, isValid }, - } = useForm(); - - const onSubmit = async (data: { npub: string }) => { - try { - if (!data.npub.startsWith("npub1")) - return toast.info("You need to enter a token start with npub1"); - - if (!data.npub.includes("#")) - return toast.info("Token must include #secret"); - - setLoading(true); - - const bunker = new NDK({ - explicitRelayUrls: [ - "wss://relay.nsecbunker.com", - "wss://nostr.vulpem.com", - ], - }); - await bunker.connect(2000); - - const pubkey = nip19.decode(data.npub.split("#")[0]).data as string; - const localSigner = NDKPrivateKeySigner.generate(); - const remoteSigner = new NDKNip46Signer(bunker, data.npub, localSigner); - await remoteSigner.blockUntilReady(); - - ark.updateNostrSigner({ signer: remoteSigner }); - - await storage.createSetting("nsecbunker", "1"); - const account = await storage.createAccount({ - pubkey: pubkey, - privkey: localSigner.privateKey, - }); - ark.account = account; - - return navigate("/auth/onboarding", { replace: true }); - } catch (e) { - setLoading(false); - setError("npub", { - type: "manual", - message: String(e), - }); - } - }; - - return ( -
-
-
-

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

-
-
-
-
- - {errors.npub && ( -

- {errors.npub.message as string} -

- )} -
- -
-
-
-
- ); -} diff --git a/apps/desktop/src/routes/auth/login-oauth.tsx b/apps/desktop/src/routes/auth/login-oauth.tsx deleted file mode 100644 index 60981a82..00000000 --- a/apps/desktop/src/routes/auth/login-oauth.tsx +++ /dev/null @@ -1,176 +0,0 @@ -import { useArk } from "@lume/ark"; -import { LoaderIcon } from "@lume/icons"; -import { useStorage } from "@lume/storage"; -import { NIP05 } from "@lume/types"; -import NDK, { NDKNip46Signer, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk"; -import { Window } from "@tauri-apps/api/window"; -import { fetch } from "@tauri-apps/plugin-http"; -import { useState } from "react"; -import { useForm } from "react-hook-form"; -import { useTranslation } from "react-i18next"; -import { useNavigate } from "react-router-dom"; -import { toast } from "sonner"; - -const emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/; - -export function LoginWithOAuth() { - const ark = useArk(); - const storage = useStorage(); - const navigate = useNavigate(); - - const [loading, setLoading] = useState(false); - - const { t } = useTranslation(); - const { - register, - handleSubmit, - setError, - formState: { errors, isValid }, - } = useForm(); - - const onSubmit = async (data: { nip05: string }) => { - try { - setLoading(true); - - if (!emailRegex.test(data.nip05)) { - setLoading(false); - return toast.error( - "Cannot verify your NIP-05 address, please try again later.", - ); - } - - const localPath = data.nip05.split("@")[0]; - const service = data.nip05.split("@")[1]; - - const verifyURL = `https://${service}/.well-known/nostr.json?name=${localPath}`; - - const req = await fetch(verifyURL, { - method: "GET", - headers: { - "Content-Type": "application/json; charset=utf-8", - }, - }); - - if (!req.ok) { - setLoading(false); - return toast.error( - "Cannot verify your NIP-05 address, please try again later.", - ); - } - - const res: NIP05 = await req.json(); - - if (!res.names[localPath.toLowerCase()] || !res.names[localPath]) { - setLoading(false); - return toast.error( - "Cannot verify your NIP-05 address, please try again later.", - ); - } - - const pubkey = - (res.names[localPath] as string) || - (res.names[localPath.toLowerCase()] as string); - - if (!res.nip46[pubkey]) { - setLoading(false); - return toast.error("Cannot found NIP-46 with this address"); - } - - const nip46Relays = res.nip46[pubkey] as unknown as string[]; - - const bunker = new NDK({ - explicitRelayUrls: nip46Relays || [ - "wss://relay.nsecbunker.com", - "wss://nostr.vulpem.com", - ], - }); - await bunker.connect(2000); - - const localSigner = NDKPrivateKeySigner.generate(); - const remoteSigner = new NDKNip46Signer(bunker, pubkey, localSigner); - - // handle auth url request - let authWindow: Window; - remoteSigner.addListener("authUrl", (authUrl: string) => { - authWindow = new Window(`auth-${pubkey}`, { - url: authUrl, - title: "Login", - titleBarStyle: "overlay", - width: 415, - height: 600, - center: true, - closable: false, - }); - }); - - const remoteUser = await remoteSigner.blockUntilReady(); - - if (remoteUser) { - authWindow.close(); - - ark.updateNostrSigner({ signer: remoteSigner }); - - await storage.createSetting("nsecbunker", "1"); - const account = await storage.createAccount({ - pubkey, - privkey: localSigner.privateKey, - }); - ark.account = account; - - return navigate("/auth/onboarding", { replace: true }); - } - } catch (e) { - setLoading(false); - setError("nip05", { - type: "manual", - message: String(e), - }); - } - }; - - return ( -
-
-
-

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

-
-
-
-
- - {errors.nip05 && ( -

- {errors.nip05.message as string} -

- )} -
- -
-
-
-
- ); -} diff --git a/apps/desktop/src/routes/auth/login.tsx b/apps/desktop/src/routes/auth/login.tsx deleted file mode 100644 index a08c9c84..00000000 --- a/apps/desktop/src/routes/auth/login.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { useTranslation } from "react-i18next"; -import { Link } from "react-router-dom"; - -export function LoginScreen() { - const { t } = useTranslation(); - - return ( -
-
-
-

{t("login.title")}

-
-
-
- - {t("login.loginWithAddress")} - - - {t("login.loginWithBunker")} - -
-
-
-
-
-
-
- - {t("login.or")} - -
-
-
- - {t("login.loginWithPrivkey")} - -

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

-
-
-
-
-
- ); -} diff --git a/apps/desktop/src/routes/auth/onboarding.tsx b/apps/desktop/src/routes/auth/onboarding.tsx deleted file mode 100644 index 31765b2e..00000000 --- a/apps/desktop/src/routes/auth/onboarding.tsx +++ /dev/null @@ -1,198 +0,0 @@ -import { useArk } from "@lume/ark"; -import { InfoIcon, LoaderIcon } from "@lume/icons"; -import { useStorage } from "@lume/storage"; -import { TranslateRegisterModal } from "@lume/ui"; -import * as Switch from "@radix-ui/react-switch"; -import { - isPermissionGranted, - requestPermission, -} from "@tauri-apps/plugin-notification"; -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; -import { useNavigate } from "react-router-dom"; -import { toast } from "sonner"; - -export function OnboardingScreen() { - const ark = useArk(); - const storage = useStorage(); - const navigate = useNavigate(); - - const [t] = useTranslation(); - const [loading, setLoading] = useState(false); - const [apiKey, setAPIKey] = useState(""); - const [settings, setSettings] = useState({ - notification: false, - lowPower: false, - translation: false, - }); - - const toggleLowPower = async () => { - await storage.createSetting("lowPower", String(+!settings.lowPower)); - setSettings((state) => ({ ...state, lowPower: !settings.lowPower })); - }; - - const toggleTranslation = async () => { - await storage.createSetting("translation", String(+!settings.translation)); - setSettings((state) => ({ ...state, translation: !settings.translation })); - }; - - const toggleNofitication = async () => { - await requestPermission(); - setSettings((state) => ({ - ...state, - notification: !settings.notification, - })); - }; - - const completeAuth = async () => { - if (settings.translation) { - if (!apiKey.length) - return toast.warning( - "You need to provide Translate API if enable translation", - ); - - await storage.createSetting("translateApiKey", apiKey); - } - - setLoading(true); - - // get account contacts - await ark.getUserContacts(); - - navigate("/", { replace: true }); - }; - - useEffect(() => { - async function loadSettings() { - // get notification permission - const permissionGranted = await isPermissionGranted(); - setSettings((prev) => ({ ...prev, notification: permissionGranted })); - - // get other settings - const data = await storage.settings(); - for (const item of data) { - if (item.key === "lowPower") - setSettings((prev) => ({ - ...prev, - lowPower: !!parseInt(item.value), - })); - - if (item.key === "translation") - setSettings((prev) => ({ - ...prev, - translation: !!parseInt(item.value), - })); - } - } - - loadSettings(); - }, []); - - return ( -
-
-
-

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

-

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

-
-
-
- toggleNofitication()} - className="relative mt-1 h-7 w-12 shrink-0 cursor-default rounded-full outline-none data-[state=checked]:bg-blue-500 bg-neutral-800" - > - - -
-

- {t("onboardingSettings.notification.title")} -

-

- {t("onboardingSettings.notification.subtitle")} -

-
-
-
- toggleLowPower()} - className="relative mt-1 h-7 w-12 shrink-0 cursor-default rounded-full outline-none data-[state=checked]:bg-blue-500 bg-neutral-800" - > - - -
-

- {t("onboardingSettings.lowPower.title")} -

-

- {t("onboardingSettings.lowPower.subtitle")} -

-
-
-
- toggleTranslation()} - className="relative mt-1 h-7 w-12 shrink-0 cursor-default rounded-full outline-none data-[state=checked]:bg-blue-500 bg-neutral-800" - > - - -
-

- {t("onboardingSettings.translation.title")} -

-

- {t("onboardingSettings.translation.subtitle")} -

-
-
- {settings.translation ? ( -
-

Translate API Key

- setAPIKey(e.target.value)} - className="w-full text-xl border-transparent outline-none focus:outline-none focus:ring-0 focus:border-none h-11 rounded-lg ring-0 placeholder:text-neutral-600 bg-neutral-900" - /> -
-
-
-
-
-
- - Don't have an API key? - -
-
- -
-
- ) : null} -
- -

{t("onboardingSettings.footer")}

-
- -
-
-
- ); -} diff --git a/apps/desktop/src/routes/auth/welcome.tsx b/apps/desktop/src/routes/auth/welcome.tsx deleted file mode 100644 index 2ca0437a..00000000 --- a/apps/desktop/src/routes/auth/welcome.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { useTranslation } from "react-i18next"; -import { Link } from "react-router-dom"; - -export function WelcomeScreen() { - const { t } = useTranslation(); - - return ( -
-
-
-
- lume -

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

-
-
- - {t("welcome.signup")} - - - {t("welcome.login")} - -
-
-
-

- {t("welcome.footer")}{" "} - - here - -

-
-
- ); -} diff --git a/apps/desktop/src/routes/depot/components/contact.tsx b/apps/desktop/src/routes/depot/components/contact.tsx deleted file mode 100644 index eee12604..00000000 --- a/apps/desktop/src/routes/depot/components/contact.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { useArk } from "@lume/ark"; -import { LoaderIcon, RunIcon } from "@lume/icons"; -import { useStorage } from "@lume/storage"; -import { User } from "@lume/ui"; -import { NDKKind } from "@nostr-dev-kit/ndk"; -import { useState } from "react"; -import { toast } from "sonner"; - -export function DepotContactCard() { - const ark = useArk(); - const storage = useStorage(); - - const [status, setStatus] = useState(false); - - const backupContact = async () => { - try { - setStatus(true); - - const event = await ark.getEventByFilter({ - filter: { - authors: [ark.account.pubkey], - kinds: [NDKKind.Contacts], - }, - }); - - // broadcast to depot - const publish = await event.publish(); - - if (publish) { - setStatus(false); - toast.success("Backup contact list successfully."); - } - } catch (e) { - setStatus(false); - toast.error(String(e)); - } - }; - - return ( -
-
-
- {ark.account.contacts?.slice(0, 8).map((item) => ( - - ))} - {ark.account.contacts?.length > 8 ? ( -
- - +{ark.account.contacts?.length - 8} - -
- ) : null} -
-
-
-
Contacts
- -
-
- ); -} diff --git a/apps/desktop/src/routes/depot/components/members.tsx b/apps/desktop/src/routes/depot/components/members.tsx deleted file mode 100644 index d09802d8..00000000 --- a/apps/desktop/src/routes/depot/components/members.tsx +++ /dev/null @@ -1,151 +0,0 @@ -import { CancelIcon, PlusIcon, UserAddIcon, UserRemoveIcon } from "@lume/icons"; -import { User } from "@lume/ui"; -import * as Dialog from "@radix-ui/react-dialog"; -import { resolveResource, resolve } from "@tauri-apps/api/path"; -import { readTextFile, writeTextFile } from "@tauri-apps/plugin-fs"; -import { nip19 } from "nostr-tools"; -import { useEffect, useState } from "react"; -import { parse, stringify } from "smol-toml"; -import { toast } from "sonner"; -import { VITE_FLATPAK_RESOURCE } from "@lume/utils"; - -export function DepotMembers() { - const [members, setMembers] = useState>(null); - const [tmpMembers, setTmpMembers] = useState>([]); - const [newMember, setNewMember] = useState(""); - - const addMember = async () => { - if (!newMember.startsWith("npub1")) - return toast.error("You need to enter a valid npub"); - - try { - const pubkey = nip19.decode(newMember).data as string; - setTmpMembers((prev) => [...prev, pubkey]); - } catch (e) { - console.error(e); - } - }; - - const removeMember = (member: string) => { - setTmpMembers((prev) => prev.filter((item) => item !== member)); - }; - - const updateMembers = async () => { - setMembers(new Set(tmpMembers)); - - const defaultConfig = VITE_FLATPAK_RESOURCE !== null ? await resolve("/",VITE_FLATPAK_RESOURCE) : await resolveResource("resources/config.toml"); - const config = await readTextFile(defaultConfig); - const configContent = parse(config); - - // biome-ignore lint/complexity/useLiteralKeys: - configContent.authorization["pubkey_whitelist"] = [...members]; - - const newConfig = stringify(configContent); - - return await writeTextFile(defaultConfig, newConfig); - }; - - useEffect(() => { - async function loadConfig() { - const defaultConfig = VITE_FLATPAK_RESOURCE !== null ? await resolve("/",VITE_FLATPAK_RESOURCE) : await resolveResource("resources/config.toml"); - const config = await readTextFile(defaultConfig); - const configContent = parse(config); - setTmpMembers( - // biome-ignore lint/complexity/useLiteralKeys: - Array.from(configContent.authorization["pubkey_whitelist"]), - ); - } - - loadConfig(); - }, []); - - return ( - -
-
-

Members

-

- Only allowed users can publish event to your Depot -

-
-
-
- {tmpMembers.slice(0, 5).map((item) => ( - - ))} - {tmpMembers.length > 5 ? ( -
- - +{tmpMembers.length} - -
- ) : null} -
- - - Manage - -
-
- - - -
-
- - Manage member - -
- - - - -
-
-
-
- setNewMember(e.target.value)} - placeholder="npub1..." - className="h-11 w-full rounded-lg border-transparent bg-neutral-100 pl-3 pr-20 placeholder:text-neutral-500 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:bg-neutral-900 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800" - /> - -
- {tmpMembers.map((member) => ( -
- - -
- ))} -
-
-
-
-
- ); -} diff --git a/apps/desktop/src/routes/depot/components/profile.tsx b/apps/desktop/src/routes/depot/components/profile.tsx deleted file mode 100644 index a44c478a..00000000 --- a/apps/desktop/src/routes/depot/components/profile.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { useArk } from "@lume/ark"; -import { LoaderIcon, RunIcon } from "@lume/icons"; -import { useStorage } from "@lume/storage"; -import { User } from "@lume/ui"; -import { NDKKind } from "@nostr-dev-kit/ndk"; -import { useState } from "react"; -import { toast } from "sonner"; - -export function DepotProfileCard() { - const ark = useArk(); - const storage = useStorage(); - - const [status, setStatus] = useState(false); - - const backupProfile = async () => { - try { - setStatus(true); - - const event = await ark.getEventByFilter({ - filter: { - authors: [ark.account.pubkey], - kinds: [NDKKind.Metadata], - }, - }); - - // broadcast to depot - const publish = await event.publish(); - - if (publish) { - setStatus(false); - toast.success("Backup profile successfully."); - } - } catch (e) { - setStatus(false); - toast.error(JSON.stringify(e)); - } - }; - - return ( -
-
- -
-
-
Profile
- -
-
- ); -} diff --git a/apps/desktop/src/routes/depot/components/relays.tsx b/apps/desktop/src/routes/depot/components/relays.tsx deleted file mode 100644 index 0ef3cdee..00000000 --- a/apps/desktop/src/routes/depot/components/relays.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { useArk } from "@lume/ark"; -import { LoaderIcon, RunIcon } from "@lume/icons"; -import { useStorage } from "@lume/storage"; -import { NDKKind } from "@nostr-dev-kit/ndk"; -import { useEffect, useState } from "react"; -import { toast } from "sonner"; - -export function DepotRelaysCard() { - const ark = useArk(); - const storage = useStorage(); - - const [status, setStatus] = useState(false); - const [relaySize, setRelaySize] = useState(0); - - const backupRelays = async () => { - try { - setStatus(true); - - const event = await ark.getEventByFilter({ - filter: { - authors: [ark.account.pubkey], - kinds: [NDKKind.RelayList], - }, - }); - - // broadcast to depot - const publish = await event.publish(); - - if (publish) { - setStatus(false); - toast.success("Backup profile successfully."); - } - } catch (e) { - setStatus(false); - toast.error(JSON.stringify(e)); - } - }; - - useEffect(() => { - async function loadRelays() { - const event = await ark.getEventByFilter({ - filter: { - authors: [ark.account.pubkey], - kinds: [NDKKind.RelayList], - }, - }); - if (event) setRelaySize(event.tags.length); - } - - loadRelays(); - }, []); - - return ( -
-
-

{relaySize} relays

-
-
-
Relay List
- -
-
- ); -} diff --git a/apps/desktop/src/routes/depot/index.tsx b/apps/desktop/src/routes/depot/index.tsx deleted file mode 100644 index c9dac648..00000000 --- a/apps/desktop/src/routes/depot/index.tsx +++ /dev/null @@ -1,222 +0,0 @@ -import { useArk } from "@lume/ark"; -import { ChevronDownIcon, DepotIcon, GossipIcon } from "@lume/icons"; -import { useStorage } from "@lume/storage"; -import { NDKKind } from "@nostr-dev-kit/ndk"; -import * as Collapsible from "@radix-ui/react-collapsible"; -import { invoke } from "@tauri-apps/api/core"; -import { appConfigDir } from "@tauri-apps/api/path"; -import { useEffect, useState } from "react"; -import { toast } from "sonner"; -import { DepotContactCard } from "./components/contact"; -import { DepotMembers } from "./components/members"; -import { DepotProfileCard } from "./components/profile"; -import { DepotRelaysCard } from "./components/relays"; - -export function DepotScreen() { - const ark = useArk(); - const storage = useStorage(); - - const [dataPath, setDataPath] = useState(""); - const [tunnelUrl, setTunnelUrl] = useState(""); - - const openFolder = async () => { - await invoke("show_in_folder", { - path: `${dataPath}/nostr.db`, - }); - }; - - const updateRelayList = async () => { - try { - if (tunnelUrl.length < 1) - return toast.info("Please enter a valid relay url"); - if (!tunnelUrl.startsWith("ws")) - return toast.info("Please enter a valid relay url"); - - const relayUrl = new URL(tunnelUrl.replace(/\s/g, "")); - if (!/^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/.test(relayUrl.host)) return; - - const relayEvent = await ark.getEventByFilter({ - filter: { - authors: [ark.account.pubkey], - kinds: [NDKKind.RelayList], - }, - }); - - let publish: { id: string; seens: string[] }; - - if (!relayEvent) { - publish = await ark.createEvent({ - kind: NDKKind.RelayList, - tags: [["r", tunnelUrl, ""]], - }); - } - - const newTags = relayEvent.tags ?? []; - newTags.push(["r", tunnelUrl, ""]); - - publish = await ark.createEvent({ - kind: NDKKind.RelayList, - tags: newTags, - }); - - if (publish) { - await storage.createSetting("tunnel_url", tunnelUrl); - toast.success("Update relay list successfully."); - - setTunnelUrl(""); - } - } catch (e) { - console.error(e); - toast.error("Error"); - } - }; - - useEffect(() => { - async function loadConfig() { - const appDir = await appConfigDir(); - setDataPath(appDir); - } - - loadConfig(); - }, []); - - return ( -
-
-
-
-
- -
-
-

Depot is running

-
-
-
-
Relay URL
-
- ws://localhost:6090 -
-
-
-
Database
-
-

nostr.db (SQLite)

- -
-
-
-
-
-
-

- Actions -

-
-
- - -
-

Expose

-

- Make your Depot visible in the Internet, everyone can connect - into it. -

-
- -
- -
-
-

ngrok

- -
-
-

Cloudflare Tunnel

- -
-
-

Local Tunnel

- -
-
-
- -

- Support Gossip Model (Recommended) -

-
- -
-

- By adding to Relay List, other Nostr Client which support - Gossip Model will automatically connect to your Depot and - improve the discoverability. -

-
- setTunnelUrl(e.target.value)} - spellCheck={false} - placeholder="wss://" - className="h-10 flex-1 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-900 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800" - /> - -
-
-
-
-
-
- - -
-

Backup (Recommended)

-

- Backup all your data to Depot, it always live on your machine. -

-
- -
- -
- - - -
-
-
- -
-
-
- ); -} diff --git a/apps/desktop/src/routes/depot/onboarding.tsx b/apps/desktop/src/routes/depot/onboarding.tsx deleted file mode 100644 index 95030d25..00000000 --- a/apps/desktop/src/routes/depot/onboarding.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { useArk } from "@lume/ark"; -import { LoaderIcon } from "@lume/icons"; -import { delay, VITE_FLATPAK_RESOURCE } from "@lume/utils"; -import { resolve, resolveResource } from "@tauri-apps/api/path"; -import { useStorage } from "@lume/storage"; -import { readTextFile, writeTextFile } from "@tauri-apps/plugin-fs"; -import { useState } from "react"; -import { useNavigate } from "react-router-dom"; -import { parse, stringify } from "smol-toml"; -import { toast } from "sonner"; - -export function DepotOnboardingScreen() { - const ark = useArk(); - const storage = useStorage(); - const navigate = useNavigate(); - - const [loading, setLoading] = useState(false); - - const launchDepot = async () => { - try { - setLoading(true); - - // get default config - const defaultConfig = - VITE_FLATPAK_RESOURCE !== null - ? await resolve("/", VITE_FLATPAK_RESOURCE) - : await resolveResource("resources/config.toml"); - const config = await readTextFile(defaultConfig); - const parsedConfig = parse(config); - - // add current user to whitelist - // biome-ignore lint/complexity/useLiteralKeys: - parsedConfig.authorization["pubkey_whitelist"].push(ark.account.pubkey); - - // update new config - const newConfig = stringify(parsedConfig); - await writeTextFile(defaultConfig, newConfig); - - // launch depot - await storage.launchDepot(); - await storage.createSetting("depot", "1"); - await delay(2000); // delay 2s to make sure depot is running - - // default depot url: ws://localhost:6090 - // #TODO: user can custom depot url - const connect = await ark.connectDepot(); - - if (connect) { - toast.success("Your Depot is successfully launch."); - setLoading(false); - - navigate("/depot/"); - } - } catch (e) { - toast.error(String(e)); - } - }; - - return ( -
-
-
-

- Run your Personal Nostr Relay inside Lume -

-

Your Relay, Your Control.

-
-
- -
-
-
- ); -} diff --git a/apps/desktop/src/routes/error.tsx b/apps/desktop/src/routes/error.tsx deleted file mode 100644 index 8ea4e36f..00000000 --- a/apps/desktop/src/routes/error.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import { downloadDir } from "@tauri-apps/api/path"; -import { message, save } from "@tauri-apps/plugin-dialog"; -import { relaunch } from "@tauri-apps/plugin-process"; -import { useRouteError } from "react-router-dom"; - -interface RouteError { - statusText: string; - message: string; -} - -export function ErrorScreen() { - const error = useRouteError() as RouteError; - - const restart = async () => { - await relaunch(); - }; - - const download = async () => { - try { - const downloadPath = await downloadDir(); - const fileName = `nostr_keys_${new Date().toISOString()}.txt`; - const filePath = await save({ - defaultPath: `${downloadPath}/${fileName}`, - }); - /* - const nsec = await storage.loadPrivkey(ark.account.pubkey); - - if (filePath) { - if (nsec) { - await writeTextFile( - filePath, - `Nostr account, generated by Lume (lume.nu)\nPublic key: ${ark.account.id}\nPrivate key: ${nsec}`, - ); - } else { - await writeTextFile( - filePath, - `Nostr account, generated by Lume (lume.nu)\nPublic key: ${ark.account.id}`, - ); - } - } // else { user cancel action } - */ - } catch (e) { - await message(e, { - title: "Cannot download account keys", - type: "error", - }); - } - }; - - return ( -
-
-
-

- Sorry, an unexpected error has occurred. -

-

- Don't panic, your account is safe. -
- Here are what things you can do: -

-
-
-
-
- 1. Try to close and re-open the app -
- -
-
-
- 2. Backup Nostr account -
- -
-
-
-
-
- 3. Report this issue to Lume -
- - Report - -
-
-

- {error.statusText || error.message} -

-
-
-
-
-
-
- 4. Use another Nostr client -
-
-

- While waiting for Lume release the bug fixes, you always can - use other Nostr clients with your account: -

- -
-
-
-
-
-
- ); -} diff --git a/apps/desktop/src/routes/home/index.tsx b/apps/desktop/src/routes/home/index.tsx deleted file mode 100644 index 1eedf47a..00000000 --- a/apps/desktop/src/routes/home/index.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { Timeline } from "@columns/timeline"; -import { - ArrowLeftIcon, - ArrowRightIcon, - PlusIcon, - PlusSquareIcon, -} from "@lume/icons"; -import { useColumn } from "@lume/storage"; -import * as Tooltip from "@radix-ui/react-tooltip"; -import { t } from "i18next"; -import { VList } from "virtua"; - -export function HomeScreen() { - const { vlistRef } = useColumn(); - - return ( -
- - -
- -
-
- -
-
- - - - - - - {t("global.moveLeft")} - - - - - - - - - - - {t("global.moveRight")} - - - - - - - - - - - {t("global.newColumn")} - - - - -
-
-
- -
- ); -} diff --git a/apps/desktop/src/routes/relays/components/relayEventList.tsx b/apps/desktop/src/routes/relays/components/relayEventList.tsx deleted file mode 100644 index bc637764..00000000 --- a/apps/desktop/src/routes/relays/components/relayEventList.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { NoteSkeleton, RepostNote, TextNote, useArk } from "@lume/ark"; -import { ArrowRightCircleIcon, LoaderIcon } from "@lume/icons"; -import { FETCH_LIMIT } from "@lume/utils"; -import { NDKEvent, NDKKind } from "@nostr-dev-kit/ndk"; -import { useInfiniteQuery } from "@tanstack/react-query"; -import { useCallback, useMemo } from "react"; -import { useTranslation } from "react-i18next"; -import { VList } from "virtua"; - -export function RelayEventList({ relayUrl }: { relayUrl: string }) { - const ark = useArk(); - - const { t } = useTranslation(); - const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } = - useInfiniteQuery({ - queryKey: ["relay-events", relayUrl], - initialPageParam: 0, - queryFn: async ({ - signal, - pageParam, - }: { - signal: AbortSignal; - pageParam: number; - }) => { - const url = `wss://${relayUrl}`; - const events = await ark.getRelayEvents({ - relayUrl: url, - filter: { - kinds: [NDKKind.Text, NDKKind.Repost], - }, - limit: FETCH_LIMIT, - pageParam, - signal, - }); - - 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 = useCallback( - (event: NDKEvent) => { - switch (event.kind) { - case NDKKind.Text: - return ; - case NDKKind.Repost: - return ; - default: - return ; - } - }, - [data], - ); - - return ( - - {status === "pending" ? ( - - ) : ( - data.map((item) => renderItem(item)) - )} -
- {hasNextPage ? ( - - ) : null} -
-
- ); -} diff --git a/apps/desktop/src/routes/relays/components/relayForm.tsx b/apps/desktop/src/routes/relays/components/relayForm.tsx deleted file mode 100644 index 27808861..00000000 --- a/apps/desktop/src/routes/relays/components/relayForm.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { useRelaylist } from "@lume/ark"; -import { PlusIcon } from "@lume/icons"; -import { normalizeRelayUrl } from "nostr-fetch"; -import { useState } from "react"; -import { toast } from "sonner"; - -export function RelayForm() { - const { connectRelay } = useRelaylist(); - - const [relay, setRelay] = useState<{ - url: WebSocket["url"]; - purpose: "read" | "write" | undefined; - }>({ url: "", purpose: undefined }); - - const create = () => { - if (relay.url.length < 1) return toast.info("Please enter relay url"); - try { - const relayUrl = new URL(relay.url.replace(/\s/g, "")); - - if (relayUrl.protocol === "wss:" || relayUrl.protocol === "ws:") { - connectRelay.mutate(normalizeRelayUrl(relay.url)); - setRelay({ url: "", purpose: undefined }); - } else { - return toast.error( - "URL is invalid, a relay must use websocket protocol (start with wss:// or ws://). Please check again", - ); - } - } catch { - return toast.error("Relay URL is not valid. Please check again"); - } - }; - - return ( -
- setRelay((prev) => ({ ...prev, url: e.target.value }))} - /> - -
- ); -} diff --git a/apps/desktop/src/routes/relays/components/relayItem.tsx b/apps/desktop/src/routes/relays/components/relayItem.tsx deleted file mode 100644 index 001c73a2..00000000 --- a/apps/desktop/src/routes/relays/components/relayItem.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { User, useRelaylist } from "@lume/ark"; -import { PlusIcon, SearchIcon } from "@lume/icons"; -import { normalizeRelayUrl } from "nostr-fetch"; -import { useTranslation } from "react-i18next"; -import { Link } from "react-router-dom"; - -export function RelayItem({ url, users }: { url: string; users?: string[] }) { - const domain = new URL(url).hostname; - - const { t } = useTranslation(); - const { connectRelay } = useRelaylist(); - - return ( -
-
- - {t("global.relay")}:{" "} - - - {url} - -
-
- {users ? ( -
- {users.slice(0, 4).map((item) => ( - - - - - - ))} - {users.length > 4 ? ( -
- +{users.length - 4} -
- ) : null} -
- ) : null} - - - {t("global.inspect")} - - -
-
- ); -} diff --git a/apps/desktop/src/routes/relays/components/sidebar.tsx b/apps/desktop/src/routes/relays/components/sidebar.tsx deleted file mode 100644 index b92c2381..00000000 --- a/apps/desktop/src/routes/relays/components/sidebar.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import { useArk, useRelaylist } from "@lume/ark"; -import { CancelIcon, LoaderIcon, RefreshIcon } from "@lume/icons"; -import { cn } from "@lume/utils"; -import { NDKKind, NDKSubscriptionCacheUsage } from "@nostr-dev-kit/ndk"; -import { useQuery } from "@tanstack/react-query"; -import { useTranslation } from "react-i18next"; -import { RelayForm } from "./relayForm"; - -export function RelaySidebar({ className }: { className?: string }) { - const ark = useArk(); - - const { t } = useTranslation(); - const { removeRelay } = useRelaylist(); - const { status, data, isRefetching, refetch } = useQuery({ - queryKey: ["relay-personal"], - queryFn: async () => { - const event = await ark.getEventByFilter({ - filter: { - kinds: [NDKKind.RelayList], - authors: [ark.account.pubkey], - }, - cache: NDKSubscriptionCacheUsage.ONLY_RELAY, - }); - if (!event) return []; - return event.tags.filter((tag) => tag[0] === "r"); - }, - refetchOnWindowFocus: false, - refetchOnMount: false, - refetchOnReconnect: false, - staleTime: Infinity, - }); - - const currentRelays = new Set( - ark.ndk.pool.connectedRelays().map((item) => item.url), - ); - - return ( -
-
-

{t("relays.sidebar.title")}

- -
-
- {status === "pending" ? ( -
- -
- ) : !data.length ? ( -
-

{t("relays.sidebar.empty")}

-
- ) : ( - data.map((item) => ( -
-
- {currentRelays.has(item[1]) ? ( - - - - - ) : ( - - - - - )} -

- {item[1] - .replace("wss://", "") - .replace("ws://", "") - .replace("/", "")} -

-
-
- {item[2]?.length ? ( -
- {item[2]} -
- ) : null} - -
-
- )) - )} - -
-
- ); -} diff --git a/apps/desktop/src/routes/relays/follows.tsx b/apps/desktop/src/routes/relays/follows.tsx deleted file mode 100644 index 6b1a31e4..00000000 --- a/apps/desktop/src/routes/relays/follows.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { useArk } from "@lume/ark"; -import { LoaderIcon } from "@lume/icons"; -import { useQuery } from "@tanstack/react-query"; -import { VList } from "virtua"; -import { RelayItem } from "./components/relayItem"; - -export function RelayFollowsScreen() { - const ark = useArk(); - const { - isLoading, - isError, - data: relays, - } = useQuery({ - queryKey: ["relay-follows"], - queryFn: async ({ signal }: { signal: AbortSignal }) => { - const data = await ark.getAllRelaysFromContacts({ signal }); - if (!data) throw new Error("Failed to get relay list from contacts"); - return data; - }, - refetchOnMount: false, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - }); - - if (isLoading) { - return ( -
- -
- ); - } - - if (isError || !relays) { - return ( -
-

Error

-
- ); - } - - return ( - - {[...relays].map(([key, value]) => ( - - ))} - - ); -} diff --git a/apps/desktop/src/routes/relays/global.tsx b/apps/desktop/src/routes/relays/global.tsx deleted file mode 100644 index dba51189..00000000 --- a/apps/desktop/src/routes/relays/global.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { LoaderIcon } from "@lume/icons"; -import { useQuery } from "@tanstack/react-query"; -import { fetch } from "@tauri-apps/plugin-http"; -import { VList } from "virtua"; -import { RelayItem } from "./components/relayItem"; - -export function RelayGlobalScreen() { - const { isLoading, data: relays } = useQuery({ - queryKey: ["relay-global"], - queryFn: async ({ signal }: { signal: AbortSignal }) => { - const res = await fetch("https://api.nostr.watch/v1/online", { signal }); - if (!res.ok) throw new Error("Failed to get online relays"); - return (await res.json()) as string[]; - }, - refetchOnMount: false, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - }); - - if (isLoading) { - return ( -
- -
- ); - } - - return ( - - {relays.map((item: string) => ( - - ))} - - ); -} diff --git a/apps/desktop/src/routes/relays/index.tsx b/apps/desktop/src/routes/relays/index.tsx deleted file mode 100644 index 7e9a7af1..00000000 --- a/apps/desktop/src/routes/relays/index.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { cn } from "@lume/utils"; -import { useTranslation } from "react-i18next"; -import { NavLink, Outlet } from "react-router-dom"; -import { RelaySidebar } from "./components/sidebar"; - -export function RelaysScreen() { - const { t } = useTranslation(); - - return ( -
- -
-
- - cn( - "h-9 w-24 rounded-lg inline-flex items-center justify-center font-medium", - isActive - ? "bg-neutral-100 hover:bg-neutral-200 dark:bg-neutral-950 dark:hover:bg-neutral-900" - : "", - ) - } - > - {t("relays.global")} - - - cn( - "h-9 w-24 rounded-lg inline-flex items-center justify-center font-medium", - isActive - ? "bg-neutral-100 hover:bg-neutral-200 dark:bg-neutral-950 dark:hover:bg-neutral-900" - : "", - ) - } - > - {t("relays.follows")} - -
-
- -
-
-
- ); -} diff --git a/apps/desktop/src/routes/relays/url.tsx b/apps/desktop/src/routes/relays/url.tsx deleted file mode 100644 index 7c5a7ee0..00000000 --- a/apps/desktop/src/routes/relays/url.tsx +++ /dev/null @@ -1,160 +0,0 @@ -import { LoaderIcon } from "@lume/icons"; -import { NIP11 } from "@lume/types"; -import { User } from "@lume/ui"; -import { Suspense } from "react"; -import { useTranslation } from "react-i18next"; -import { Await, useLoaderData, useParams } from "react-router-dom"; -import { RelayEventList } from "./components/relayEventList"; - -export function RelayUrlScreen() { - const { t } = useTranslation(); - const { url } = useParams(); - - const data: { relay?: { [key: string]: string } } = useLoaderData(); - - const getSoftwareName = (url: string) => { - const filename = url.substring(url.lastIndexOf("/") + 1); - return filename.replace(".git", ""); - }; - - const titleCase = (s: string) => { - return s - .replace(/^[-_]*(.)/, (_, c) => c.toUpperCase()) - .replace(/[-_]+(.)/g, (_, c) => ` ${c.toUpperCase()}`); - }; - - return ( -
-
- -
-
- - - {t("global.loading")} -
- } - > - -

{t("relays.relayView.empty")}

-
- } - > - {(resolvedRelay: NIP11) => ( -
-
-

{resolvedRelay.name}

-

- {resolvedRelay.description} -

-
- {resolvedRelay.pubkey ? ( -
-
- {t("relays.relayView.owner")}: -
-
- -
-
- ) : null} - {resolvedRelay.contact ? ( -
-
- {t("relays.relayView.contact")}: -
- - {resolvedRelay.contact} - -
- ) : null} - -
-
- {t("relays.relayView.nips")}: -
-
- {resolvedRelay.supported_nips.map((item) => ( - - {item} - - ))} -
-
- {resolvedRelay.limitation ? ( -
-
- {t("relays.relayView.limit")} -
-
- {Object.keys(resolvedRelay.limitation).map((key) => { - return ( -
-

- {titleCase(key)}: -

-

- {resolvedRelay.limitation[key].toString()} -

-
- ); - })} -
-
- ) : null} - {resolvedRelay.payments_url ? ( -
- - {t("relays.relayView.payment")} - - - {t("relays.relayView.paymentNote")} - -
- ) : null} -
- )} - - -
-
- ); -} diff --git a/apps/desktop/src/routes/settings/about.tsx b/apps/desktop/src/routes/settings/about.tsx deleted file mode 100644 index 5cbb23f5..00000000 --- a/apps/desktop/src/routes/settings/about.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { getVersion } from "@tauri-apps/api/app"; -import { relaunch } from "@tauri-apps/plugin-process"; -import { Update, check } from "@tauri-apps/plugin-updater"; -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; -import { Link } from "react-router-dom"; -import { toast } from "sonner"; - -export function AboutScreen() { - const [t] = useTranslation(); - const [version, setVersion] = useState(""); - const [newUpdate, setNewUpdate] = useState(null); - - const checkUpdate = async () => { - const update = await check(); - if (!update) toast.info("There is no update available"); - setNewUpdate(update); - }; - - const installUpdate = async () => { - await newUpdate.downloadAndInstall(); - await relaunch(); - }; - - useEffect(() => { - async function loadVersion() { - const appVersion = await getVersion(); - setVersion(appVersion); - } - - loadVersion(); - }, []); - - return ( -
-
-

Lume

-

- {t("settings.about.version")} {version} -

-
-
- {!newUpdate ? ( - - ) : ( - - )} - - Website - - - Report a issue - -
-
- ); -} diff --git a/apps/desktop/src/routes/settings/advanced.tsx b/apps/desktop/src/routes/settings/advanced.tsx deleted file mode 100644 index 710189e4..00000000 --- a/apps/desktop/src/routes/settings/advanced.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { useStorage } from "@lume/storage"; -import { useTranslation } from "react-i18next"; - -export function AdvancedSettingScreen() { - const storage = useStorage(); - const { t } = useTranslation(); - - const clearCache = async () => { - await storage.clearCache(); - }; - - return ( -
-
-
-
-
- {t("settings.advanced.cache.title")} -
-
- {t("settings.advanced.cache.subtitle")} -
-
- -
-
-
- ); -} diff --git a/apps/desktop/src/routes/settings/backup.tsx b/apps/desktop/src/routes/settings/backup.tsx deleted file mode 100644 index aa095b44..00000000 --- a/apps/desktop/src/routes/settings/backup.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { useArk } from "@lume/ark"; -import { EyeOffIcon } from "@lume/icons"; -import { useStorage } from "@lume/storage"; -import { nip19 } from "nostr-tools"; -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; - -export function BackupSettingScreen() { - const ark = useArk(); - const storage = useStorage(); - - const [t] = useTranslation(); - const [privkey, setPrivkey] = useState(null); - const [showPassword, setShowPassword] = useState(false); - - const removePrivkey = async () => { - await storage.removePrivkey(ark.account.pubkey); - }; - - useEffect(() => { - async function loadPrivkey() { - const key = await storage.loadPrivkey(ark.account.pubkey); - if (key) setPrivkey(key); - } - - loadPrivkey(); - }, []); - - return ( -
-
- {privkey ? ( -
-
- {t("settings.backup.privkey.title")} -
-
- - -
- -
- ) : null} -
-
- ); -} diff --git a/apps/desktop/src/routes/settings/components/avatarUpload.tsx b/apps/desktop/src/routes/settings/components/avatarUpload.tsx deleted file mode 100644 index 601f43f6..00000000 --- a/apps/desktop/src/routes/settings/components/avatarUpload.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { useArk } from "@lume/ark"; -import { LoaderIcon } from "@lume/icons"; -import { useState } from "react"; -import { useTranslation } from "react-i18next"; -import { toast } from "sonner"; - -export function AvatarUpload({ setPicture }) { - const ark = useArk(); - - const [t] = useTranslation(); - const [loading, setLoading] = useState(false); - - const upload = async () => { - try { - setLoading(true); - - // upload image to nostr.build server - // #TODO: support multiple server - const image = await ark.upload({ fileExts: [] }); - - if (!image) - toast.error("Failed to upload image, please try again later."); - - setPicture(image); - setLoading(false); - } catch (e) { - setLoading(false); - toast.error("Failed to upload image, please try again later."); - } - }; - - return ( - - ); -} diff --git a/apps/desktop/src/routes/settings/components/coverUpload.tsx b/apps/desktop/src/routes/settings/components/coverUpload.tsx deleted file mode 100644 index bdeb7868..00000000 --- a/apps/desktop/src/routes/settings/components/coverUpload.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { useArk } from "@lume/ark"; -import { LoaderIcon } from "@lume/icons"; -import { useState } from "react"; -import { useTranslation } from "react-i18next"; -import { toast } from "sonner"; - -export function CoverUpload({ setBanner }) { - const ark = useArk(); - - const [t] = useTranslation(); - const [loading, setLoading] = useState(false); - - const upload = async () => { - try { - setLoading(true); - - // upload image to nostr.build server - // #TODO: support multiple server - const image = await ark.upload({ fileExts: [] }); - - if (!image) - toast.error("Failed to upload image, please try again later."); - - setBanner(image); - setLoading(false); - } catch (e) { - setLoading(false); - toast.error("Failed to upload image, please try again later."); - } - }; - - return ( - - ); -} diff --git a/apps/desktop/src/routes/settings/general.tsx b/apps/desktop/src/routes/settings/general.tsx deleted file mode 100644 index c9b4d699..00000000 --- a/apps/desktop/src/routes/settings/general.tsx +++ /dev/null @@ -1,320 +0,0 @@ -import { DarkIcon, LightIcon, SystemModeIcon } from "@lume/icons"; -import { useStorage } from "@lume/storage"; -import { cn } from "@lume/utils"; -import * as Switch from "@radix-ui/react-switch"; -import { invoke } from "@tauri-apps/api/core"; -import { getCurrent } from "@tauri-apps/api/window"; -import { disable, enable, isEnabled } from "@tauri-apps/plugin-autostart"; -import { - isPermissionGranted, - requestPermission, -} from "@tauri-apps/plugin-notification"; -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; - -export function GeneralSettingScreen() { - const storage = useStorage(); - - const [t] = useTranslation(); - const [apiKey, setAPIKey] = useState(""); - const [settings, setSettings] = useState({ - ...storage.settings, - notification: false, - autolaunch: false, - appearance: "system", - }); - - const changeTheme = async (theme: "light" | "dark" | "auto") => { - await invoke("plugin:theme|set_theme", { theme }); - // update state - setSettings((prev) => ({ ...prev, appearance: theme })); - }; - - const toggleLowPower = async () => { - await storage.createSetting("lowPower", String(+!settings.lowPower)); - setSettings((state) => ({ ...state, lowPower: !settings.lowPower })); - }; - - const toggleAutolaunch = async () => { - if (!settings.autolaunch) { - await enable(); - // update state - setSettings((prev) => ({ ...prev, autolaunch: true })); - } else { - await disable(); - // update state - setSettings((prev) => ({ ...prev, autolaunch: false })); - } - }; - - const toggleMedia = async () => { - await storage.createSetting("media", String(+!settings.media)); - storage.settings.media = !settings.media; - // update state - setSettings((prev) => ({ ...prev, media: !settings.media })); - }; - - const toggleHashtag = async () => { - await storage.createSetting("hashtag", String(+!settings.hashtag)); - storage.settings.hashtag = !settings.hashtag; - // update state - setSettings((prev) => ({ ...prev, hashtag: !settings.hashtag })); - }; - - const toggleAutoupdate = async () => { - await storage.createSetting("autoupdate", String(+!settings.autoupdate)); - storage.settings.autoupdate = !settings.autoupdate; - // update state - setSettings((prev) => ({ ...prev, autoupdate: !settings.autoupdate })); - }; - - const toggleNofitication = async () => { - if (settings.notification) return; - - await requestPermission(); - // update state - setSettings((prev) => ({ ...prev, notification: !settings.notification })); - }; - - const toggleTranslation = async () => { - await storage.createSetting("translation", String(+!settings.translation)); - storage.settings.translation = !settings.translation; - // update state - setSettings((prev) => ({ ...prev, translation: !settings.translation })); - }; - - const saveApi = async () => { - await storage.createSetting("translateApiKey", apiKey); - }; - - useEffect(() => { - async function loadSettings() { - const theme = await getCurrent().theme(); - setSettings((prev) => ({ ...prev, appearance: theme })); - - const autostart = await isEnabled(); - setSettings((prev) => ({ ...prev, autolaunch: autostart })); - - const permissionGranted = await isPermissionGranted(); - setSettings((prev) => ({ ...prev, notification: permissionGranted })); - } - - loadSettings(); - }, []); - - return ( -
-
-
-
-
- {t("settings.general.update.title")} -
-
- {t("settings.general.update.subtitle")} -
-
- toggleAutoupdate()} - className="relative h-7 w-12 cursor-default rounded-full bg-neutral-200 outline-none data-[state=checked]:bg-blue-500 dark:bg-neutral-800" - > - - -
-
-
-
- {t("settings.general.lowPower.title")} -
-
- {t("settings.general.lowPower.subtitle")} -
-
- toggleLowPower()} - className="relative h-7 w-12 cursor-default rounded-full bg-neutral-200 outline-none data-[state=checked]:bg-blue-500 dark:bg-neutral-800" - > - - -
-
-
-
- {t("settings.general.startup.title")} -
-
- {t("settings.general.startup.subtitle")} -
-
- toggleAutolaunch()} - className="relative h-7 w-12 cursor-default rounded-full bg-neutral-200 outline-none data-[state=checked]:bg-blue-500 dark:bg-neutral-800" - > - - -
-
-
-
- {t("settings.general.media.title")} -
-
- {t("settings.general.media.subtitle")} -
-
- toggleMedia()} - className="relative h-7 w-12 cursor-default rounded-full bg-neutral-200 outline-none data-[state=checked]:bg-blue-500 dark:bg-neutral-800" - > - - -
-
-
-
- {t("settings.general.hashtag.title")} -
-
- {t("settings.general.hashtag.subtitle")} -
-
- toggleHashtag()} - className="relative h-7 w-12 cursor-default rounded-full bg-neutral-200 outline-none data-[state=checked]:bg-blue-500 dark:bg-neutral-800" - > - - -
-
-
-
- {t("settings.general.notification.title")} -
-
- {t("settings.general.notification.subtitle")} -
-
- toggleNofitication()} - className="relative h-7 w-12 cursor-default rounded-full bg-neutral-200 outline-none data-[state=checked]:bg-blue-500 dark:bg-neutral-800" - > - - -
-
-
-
- {t("settings.general.translation.title")} -
-
- {t("settings.general.translation.subtitle")} -
-
- toggleTranslation()} - className="relative h-7 w-12 cursor-default rounded-full bg-neutral-200 outline-none data-[state=checked]:bg-blue-500 dark:bg-neutral-800" - > - - -
- {settings.translation ? ( -
-
- {t("global.apiKey")} -
-
- setAPIKey(e.target.value)} - className="w-full border-transparent outline-none focus:outline-none focus:ring-0 focus:border-none h-9 rounded-lg ring-0 placeholder:text-neutral-600 bg-neutral-100 dark:bg-neutral-900" - /> -
- -
-
-
- ) : null} -
-
- {t("settings.general.appearance.title")} -
-
- - - -
-
-
-
- ); -} diff --git a/apps/desktop/src/routes/settings/nwc.tsx b/apps/desktop/src/routes/settings/nwc.tsx deleted file mode 100644 index 114e8644..00000000 --- a/apps/desktop/src/routes/settings/nwc.tsx +++ /dev/null @@ -1,157 +0,0 @@ -import { useArk } from "@lume/ark"; -import { useStorage } from "@lume/storage"; -import * as Switch from "@radix-ui/react-switch"; -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; -import { toast } from "sonner"; - -export function NWCScreen() { - const ark = useArk(); - const storage = useStorage(); - - const [t] = useTranslation(); - const [settings, setSettings] = useState({ - nwc: false, - instantZap: storage.settings.instantZap, - }); - const [walletConnectURL, setWalletConnectURL] = useState(null); - const [amount, setAmount] = useState("21"); - - const saveNWC = async () => { - try { - if (!walletConnectURL.startsWith("nostr+walletconnect:")) { - return toast.error( - "Connect URI is required and must start with format nostr+walletconnect:, please check again", - ); - } - - const uriObj = new URL(walletConnectURL); - const params = new URLSearchParams(uriObj.search); - - if (params.has("relay") && params.has("secret")) { - await storage.createPrivkey( - `${ark.account.pubkey}.nwc`, - walletConnectURL, - ); - - storage.nwc = walletConnectURL; - - setWalletConnectURL(walletConnectURL); - setSettings((state) => ({ ...state, nwc: true })); - } else { - return toast.error("Connect URI is not valid, please check again"); - } - } catch (e) { - toast.error(String(e)); - } - }; - - const toggleInstantZap = async () => { - await storage.createSetting("instantZap", String(+!settings.instantZap)); - setSettings((state) => ({ ...state, instantZap: !settings.instantZap })); - }; - - const saveAmount = async () => { - await storage.createSetting("zapAmount", amount); - }; - - const remove = async () => { - await storage.removePrivkey(`${ark.account.pubkey}.nwc`); - - setWalletConnectURL(""); - setSettings((state) => ({ ...state, nwc: false })); - storage.nwc = null; - }; - - useEffect(() => { - if (storage.nwc) { - setSettings((state) => ({ ...state, nwc: true })); - setWalletConnectURL(storage.nwc); - } - }, []); - - return ( -
-
-
-
-
- {t("settings.zap.nwc")} -
-
-