mirror of
https://github.com/luminous-devs/lume.git
synced 2024-09-30 00:41:00 +00:00
feat: add login dialog
This commit is contained in:
parent
86183d799a
commit
8eaf47f6d2
@ -15,6 +15,7 @@
|
|||||||
"@lume/utils": "workspace:^",
|
"@lume/utils": "workspace:^",
|
||||||
"@radix-ui/react-checkbox": "^1.0.4",
|
"@radix-ui/react-checkbox": "^1.0.4",
|
||||||
"@radix-ui/react-collapsible": "^1.0.3",
|
"@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-dropdown-menu": "^2.0.6",
|
||||||
"@radix-ui/react-popover": "^1.0.7",
|
"@radix-ui/react-popover": "^1.0.7",
|
||||||
"@tanstack/query-sync-storage-persister": "^5.24.1",
|
"@tanstack/query-sync-storage-persister": "^5.24.1",
|
||||||
|
@ -4,9 +4,9 @@ import { User } from "@lume/ui";
|
|||||||
import { useNavigate, useParams, useSearch } from "@tanstack/react-router";
|
import { useNavigate, useParams, useSearch } from "@tanstack/react-router";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import * as Popover from "@radix-ui/react-popover";
|
import * as Popover from "@radix-ui/react-popover";
|
||||||
import { Link } from "@tanstack/react-router";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
||||||
|
import { BackupDialog } from "./backup";
|
||||||
|
import { LoginDialog } from "./login";
|
||||||
|
|
||||||
export function Accounts() {
|
export function Accounts() {
|
||||||
const ark = useArk();
|
const ark = useArk();
|
||||||
@ -63,7 +63,6 @@ function Active({ pubkey }: { pubkey: string }) {
|
|||||||
const [open, setOpen] = useState(true);
|
const [open, setOpen] = useState(true);
|
||||||
// @ts-ignore, magic !!!
|
// @ts-ignore, magic !!!
|
||||||
const { guest } = useSearch({ strict: false });
|
const { guest } = useSearch({ strict: false });
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
if (guest) {
|
if (guest) {
|
||||||
return (
|
return (
|
||||||
@ -84,25 +83,17 @@ function Active({ pubkey }: { pubkey: string }) {
|
|||||||
side="bottom"
|
side="bottom"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="mb-1 font-semibold">You're using guest account</h1>
|
<h1 className="mb-1 font-semibold">
|
||||||
|
You're using random account
|
||||||
|
</h1>
|
||||||
<p className="text-sm text-neutral-500 dark:text-neutral-600">
|
<p className="text-sm text-neutral-500 dark:text-neutral-600">
|
||||||
You can continue by claim and backup this account, or you can
|
You can continue by claim and backup this account, or you can
|
||||||
import your own account key.
|
import your own account.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<Link
|
<BackupDialog />
|
||||||
to="/backup"
|
<LoginDialog />
|
||||||
className="inline-flex h-9 w-full items-center justify-center rounded-lg bg-white text-sm font-medium leading-tight text-neutral-900 hover:bg-neutral-100"
|
|
||||||
>
|
|
||||||
Claim & Backup
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
to="/login"
|
|
||||||
className="inline-flex h-9 w-full items-center justify-center rounded-lg bg-neutral-900 text-sm font-medium leading-tight text-neutral-100 hover:bg-neutral-800"
|
|
||||||
>
|
|
||||||
{t("welcome.login")}
|
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
<Popover.Arrow className="fill-black dark:fill-white" />
|
<Popover.Arrow className="fill-black dark:fill-white" />
|
||||||
</Popover.Content>
|
</Popover.Content>
|
||||||
|
85
apps/desktop2/src/components/backup.tsx
Normal file
85
apps/desktop2/src/components/backup.tsx
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import { CancelIcon } from "@lume/icons";
|
||||||
|
import * as Dialog from "@radix-ui/react-dialog";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
export function BackupDialog() {
|
||||||
|
const [key, setKey] = useState("");
|
||||||
|
const [passphase, setPassphase] = useState("");
|
||||||
|
|
||||||
|
const encryptKey = async () => {
|
||||||
|
console.log("****");
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog.Root>
|
||||||
|
<Dialog.Trigger asChild>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="inline-flex h-9 w-full items-center justify-center rounded-lg bg-neutral-900 text-sm font-medium leading-tight text-neutral-100 hover:bg-neutral-800"
|
||||||
|
>
|
||||||
|
Claim & Backup
|
||||||
|
</button>
|
||||||
|
</Dialog.Trigger>
|
||||||
|
<Dialog.Portal>
|
||||||
|
<Dialog.Overlay className="fixed inset-0 z-50 bg-black/30 backdrop-blur dark:bg-white/30" />
|
||||||
|
<Dialog.Content className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
|
||||||
|
<Dialog.Close className="absolute right-5 top-5 flex w-12 flex-col items-center justify-center gap-1 text-white">
|
||||||
|
<CancelIcon className="size-8" />
|
||||||
|
<span className="text-sm font-medium uppercase text-neutral-400 dark:text-neutral-600">
|
||||||
|
Esc
|
||||||
|
</span>
|
||||||
|
</Dialog.Close>
|
||||||
|
<div className="relative flex h-min w-full max-w-xl flex-col gap-8 rounded-xl bg-white p-5 dark:bg-black">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<h3 className="text-lg font-semibold">
|
||||||
|
This is your account key
|
||||||
|
</h3>
|
||||||
|
<p>
|
||||||
|
It's use for login to Lume or other Nostr clients. You will lost
|
||||||
|
access to your account if you lose this key.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-5">
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label htmlFor="nsec">
|
||||||
|
Copy this key and keep it in safe place
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
name="nsec"
|
||||||
|
type="text"
|
||||||
|
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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label htmlFor="nsec">
|
||||||
|
<span className="font-semibold">(Recommend)</span> Set a
|
||||||
|
passphase to secure your key
|
||||||
|
</label>
|
||||||
|
<div className="relative">
|
||||||
|
<input
|
||||||
|
name="passphase"
|
||||||
|
type="password"
|
||||||
|
value={passphase}
|
||||||
|
onChange={(e) => 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"
|
||||||
|
/>
|
||||||
|
{passphase.length ? (
|
||||||
|
<div className="absolute right-2 top-0 h-11 py-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={encryptKey}
|
||||||
|
className="inline-flex h-full items-center justify-center rounded-md bg-blue-500 px-3 text-sm font-medium text-white hover:bg-blue-600"
|
||||||
|
>
|
||||||
|
Update
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog.Content>
|
||||||
|
</Dialog.Portal>
|
||||||
|
</Dialog.Root>
|
||||||
|
);
|
||||||
|
}
|
120
apps/desktop2/src/components/login.tsx
Normal file
120
apps/desktop2/src/components/login.tsx
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
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 [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const login = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
const save = await ark.save_account(nsec, passphase);
|
||||||
|
|
||||||
|
if (save) {
|
||||||
|
navigate({ to: "/", search: { guest: false } });
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
setLoading(false);
|
||||||
|
toast.error(String(e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog.Root>
|
||||||
|
<Dialog.Trigger asChild>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="inline-flex h-9 w-full items-center justify-center rounded-lg bg-neutral-900 text-sm font-medium leading-tight text-neutral-100 hover:bg-neutral-800"
|
||||||
|
>
|
||||||
|
Add account
|
||||||
|
</button>
|
||||||
|
</Dialog.Trigger>
|
||||||
|
<Dialog.Portal>
|
||||||
|
<Dialog.Overlay className="fixed inset-0 z-50 bg-black/30 backdrop-blur dark:bg-white/30" />
|
||||||
|
<Dialog.Content className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
|
||||||
|
<Dialog.Close className="absolute right-5 top-5 flex w-12 flex-col items-center justify-center gap-1 text-white">
|
||||||
|
<CancelIcon className="size-8" />
|
||||||
|
<span className="text-sm font-medium uppercase text-neutral-400 dark:text-neutral-600">
|
||||||
|
Esc
|
||||||
|
</span>
|
||||||
|
</Dialog.Close>
|
||||||
|
<div className="relative flex h-min w-full max-w-xl flex-col gap-8 rounded-xl bg-white p-5 dark:bg-black">
|
||||||
|
<div className="flex flex-col gap-1.5">
|
||||||
|
<h3 className="text-lg font-semibold">Add new account with</h3>
|
||||||
|
<div className="flex h-11 items-center overflow-hidden rounded-lg bg-neutral-100 p-1 dark:bg-neutral-900">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="h-full flex-1 rounded-md bg-white text-sm font-medium dark:bg-black"
|
||||||
|
>
|
||||||
|
nsec
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="flex h-full flex-1 flex-col items-center justify-center rounded-md text-sm font-medium"
|
||||||
|
>
|
||||||
|
<span className="leading-tight">nsecBunker</span>
|
||||||
|
<span className="text-xs font-normal leading-tight text-neutral-700 dark:text-neutral-300">
|
||||||
|
coming soon
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="flex h-full flex-1 flex-col items-center justify-center rounded-md text-sm font-medium"
|
||||||
|
>
|
||||||
|
<span className="leading-tight">Address</span>
|
||||||
|
<span className="text-xs font-normal leading-tight text-neutral-700 dark:text-neutral-300">
|
||||||
|
coming soon
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-5">
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label htmlFor="nsec">
|
||||||
|
Enter sign in key start with nsec or ncrypto
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
name="nsec"
|
||||||
|
type="text"
|
||||||
|
value={nsec}
|
||||||
|
onChange={(e) => 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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label htmlFor="nsec">Passphase (optional)</label>
|
||||||
|
<input
|
||||||
|
name="passphase"
|
||||||
|
type="password"
|
||||||
|
value={passphase}
|
||||||
|
onChange={(e) => 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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={login}
|
||||||
|
className="inline-flex h-11 w-full items-center justify-between gap-1.5 rounded-lg bg-blue-500 px-5 font-medium text-white hover:bg-blue-600"
|
||||||
|
>
|
||||||
|
<div className="size-5" />
|
||||||
|
<div>Add account</div>
|
||||||
|
<ArrowRightIcon className="size-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog.Content>
|
||||||
|
</Dialog.Portal>
|
||||||
|
</Dialog.Root>
|
||||||
|
);
|
||||||
|
}
|
@ -5,6 +5,7 @@ import {
|
|||||||
HomeFilledIcon,
|
HomeFilledIcon,
|
||||||
HomeIcon,
|
HomeIcon,
|
||||||
HorizontalDotsIcon,
|
HorizontalDotsIcon,
|
||||||
|
SettingsIcon,
|
||||||
SpaceFilledIcon,
|
SpaceFilledIcon,
|
||||||
SpaceIcon,
|
SpaceIcon,
|
||||||
} from "@lume/icons";
|
} from "@lume/icons";
|
||||||
@ -43,6 +44,12 @@ function App() {
|
|||||||
<ComposeFilledIcon className="size-4" />
|
<ComposeFilledIcon className="size-4" />
|
||||||
New post
|
New post
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="inline-flex size-8 items-center justify-center rounded-full bg-neutral-200 text-neutral-800 hover:bg-neutral-400 dark:bg-neutral-800 dark:text-neutral-200 dark:hover:bg-neutral-600"
|
||||||
|
>
|
||||||
|
<HorizontalDotsIcon className="size-5" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Box>
|
<Box>
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
export const Route = createLazyFileRoute("/backup")({
|
|
||||||
component: Screen,
|
|
||||||
});
|
|
||||||
|
|
||||||
function Screen() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
|
||||||
<div className="mx-auto flex w-full max-w-md flex-col gap-8">
|
|
||||||
<div className="flex flex-col items-center gap-1 text-center">
|
|
||||||
<h1 className="text-2xl font-semibold">{t("backup.title")}</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -6,7 +6,7 @@ import { createFileRoute, redirect, useNavigate } from "@tanstack/react-router";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
export const Route = createFileRoute("/")({
|
export const Route = createFileRoute("/")({
|
||||||
beforeLoad: async ({ location, context }) => {
|
beforeLoad: async ({ context }) => {
|
||||||
const ark = context.ark;
|
const ark = context.ark;
|
||||||
const accounts = await ark.get_all_accounts();
|
const accounts = await ark.get_all_accounts();
|
||||||
|
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
import { Outlet, createFileRoute } from "@tanstack/react-router";
|
|
||||||
|
|
||||||
export const Route = createFileRoute("/login")({
|
|
||||||
component: Screen,
|
|
||||||
});
|
|
||||||
|
|
||||||
function Screen() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h1>Login</h1>
|
|
||||||
<Outlet />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -31,7 +31,6 @@ export class Ark {
|
|||||||
this.accounts = accounts;
|
this.accounts = accounts;
|
||||||
return accounts;
|
return accounts;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -45,19 +44,18 @@ export class Ark {
|
|||||||
|
|
||||||
return cmd;
|
return cmd;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
throw new Error(String(e));
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async create_guest_account() {
|
public async create_guest_account() {
|
||||||
try {
|
try {
|
||||||
const keys = await this.create_keys();
|
const keys = await this.create_keys();
|
||||||
await this.save_account(keys);
|
await this.save_account(keys.nsec, "");
|
||||||
|
|
||||||
return keys.npub;
|
return keys.npub;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
throw new Error(String(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,17 +68,20 @@ export class Ark {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async save_account(keys: Keys) {
|
public async save_account(nsec: string, password: string = "") {
|
||||||
try {
|
try {
|
||||||
const cmd: boolean = await invoke("save_key", { nsec: keys.nsec });
|
const cmd: boolean = await invoke("save_key", {
|
||||||
|
nsec,
|
||||||
|
password,
|
||||||
|
});
|
||||||
|
|
||||||
if (cmd) {
|
if (cmd) {
|
||||||
await invoke("update_signer", { nsec: keys.nsec });
|
await invoke("update_signer", { nsec });
|
||||||
}
|
}
|
||||||
|
|
||||||
return cmd;
|
return cmd;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(String(e));
|
throw new Error(String(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,7 +93,7 @@ export class Ark {
|
|||||||
});
|
});
|
||||||
return cmd;
|
return cmd;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(String(e));
|
throw new Error(String(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,7 +107,7 @@ export class Ark {
|
|||||||
const event: Event = JSON.parse(cmd);
|
const event: Event = JSON.parse(cmd);
|
||||||
return event;
|
return event;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return null;
|
throw new Error(String(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,8 +211,7 @@ export class Ark {
|
|||||||
|
|
||||||
return cmd;
|
return cmd;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(String(e));
|
throw new Error(String(e));
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,7 +220,7 @@ export class Ark {
|
|||||||
const cmd: string = await invoke("reply_to", { content, tags });
|
const cmd: string = await invoke("reply_to", { content, tags });
|
||||||
return cmd;
|
return cmd;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(String(e));
|
throw new Error(String(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,7 +229,7 @@ export class Ark {
|
|||||||
const cmd: string = await invoke("repost", { id, pubkey: author });
|
const cmd: string = await invoke("repost", { id, pubkey: author });
|
||||||
return cmd;
|
return cmd;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(String(e));
|
throw new Error(String(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,7 +238,7 @@ export class Ark {
|
|||||||
const cmd: string = await invoke("upvote", { id, pubkey: author });
|
const cmd: string = await invoke("upvote", { id, pubkey: author });
|
||||||
return cmd;
|
return cmd;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(String(e));
|
throw new Error(String(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,7 +247,7 @@ export class Ark {
|
|||||||
const cmd: string = await invoke("downvote", { id, pubkey: author });
|
const cmd: string = await invoke("downvote", { id, pubkey: author });
|
||||||
return cmd;
|
return cmd;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(String(e));
|
throw new Error(String(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -366,8 +366,7 @@ export class Ark {
|
|||||||
const cmd: string = await invoke("follow", { id, alias });
|
const cmd: string = await invoke("follow", { id, alias });
|
||||||
return cmd;
|
return cmd;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
throw new Error(String(e));
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -376,8 +375,7 @@ export class Ark {
|
|||||||
const cmd: string = await invoke("unfollow", { id });
|
const cmd: string = await invoke("unfollow", { id });
|
||||||
return cmd;
|
return cmd;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
throw new Error(String(e));
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -389,7 +387,7 @@ export class Ark {
|
|||||||
});
|
});
|
||||||
return cmd;
|
return cmd;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(String(e));
|
throw new Error(String(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,18 +1,13 @@
|
|||||||
export function ArrowRightIcon(props: JSX.IntrinsicElements['svg']) {
|
export function ArrowRightIcon(props: JSX.IntrinsicElements["svg"]) {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" {...props}>
|
||||||
{...props}
|
<path
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
>
|
d="m14 6 6 6-6 6m5-6H4"
|
||||||
<path d="M15.17 6a30.23 30.23 0 0 1 5.62 5.406c.14.174.21.384.21.594m-5.83 6a30.232 30.232 0 0 0 5.62-5.406A.949.949 0 0 0 21 12m0 0H3" />
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,12 @@
|
|||||||
export function CancelIcon(props: JSX.IntrinsicElements['svg']) {
|
export function CancelIcon(props: JSX.IntrinsicElements["svg"]) {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" {...props}>
|
||||||
{...props}
|
<path
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
strokeLinecap="round"
|
stroke-linecap="round"
|
||||||
strokeLinejoin="round"
|
stroke-width="2"
|
||||||
strokeWidth="2"
|
d="m5 5 14 14m0-14L5 19"
|
||||||
>
|
/>
|
||||||
<path d="m6 18 6-6m0 0 6-6m-6 6L6 6m6 6 6 6" />
|
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -7,14 +7,16 @@ export function SettingsIcon(
|
|||||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" {...props}>
|
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" {...props}>
|
||||||
<path
|
<path
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
stroke-linejoin="round"
|
strokeLinecap="square"
|
||||||
stroke-width="2"
|
strokeLinejoin="round"
|
||||||
d="m7.99 5.398-.685-.158A1.722 1.722 0 0 0 5.24 7.305l.158.684a1.946 1.946 0 0 1-.817 2.057l-.832.555a1.682 1.682 0 0 0 0 2.798l.832.555c.673.449.999 1.268.817 2.057l-.158.684a1.722 1.722 0 0 0 2.065 2.065l.684-.158a1.946 1.946 0 0 1 2.057.817l.555.832a1.682 1.682 0 0 0 2.798 0l.555-.832a1.946 1.946 0 0 1 2.057-.817l.684.158a1.722 1.722 0 0 0 2.065-2.065l-.158-.684a1.946 1.946 0 0 1 .817-2.057l.832-.555a1.682 1.682 0 0 0 0-2.798l-.832-.555a1.946 1.946 0 0 1-.817-2.057l.158-.684a1.722 1.722 0 0 0-2.065-2.065l-.684.158a1.946 1.946 0 0 1-2.057-.817l-.555-.832a1.682 1.682 0 0 0-2.798 0l-.555.832a1.946 1.946 0 0 1-2.057.817Z"
|
strokeWidth="2"
|
||||||
|
d="M11.02 3.552a2 2 0 0 1 1.96 0l6 3.374A2 2 0 0 1 20 8.67v6.66a2 2 0 0 1-1.02 1.743l-6 3.375a2 2 0 0 1-1.96 0l-6-3.374A2 2 0 0 1 4 15.33V8.67a2 2 0 0 1 1.02-1.744l6-3.374Z"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
stroke-linejoin="round"
|
strokeLinecap="square"
|
||||||
stroke-width="2"
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
|
d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -78,6 +78,9 @@ importers:
|
|||||||
'@radix-ui/react-collapsible':
|
'@radix-ui/react-collapsible':
|
||||||
specifier: ^1.0.3
|
specifier: ^1.0.3
|
||||||
version: 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0)
|
version: 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@radix-ui/react-dialog':
|
||||||
|
specifier: ^1.0.5
|
||||||
|
version: 1.0.5(@types/react-dom@18.2.19)(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0)
|
||||||
'@radix-ui/react-dropdown-menu':
|
'@radix-ui/react-dropdown-menu':
|
||||||
specifier: ^2.0.6
|
specifier: ^2.0.6
|
||||||
version: 2.0.6(@types/react-dom@18.2.19)(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0)
|
version: 2.0.6(@types/react-dom@18.2.19)(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0)
|
||||||
@ -226,7 +229,7 @@ importers:
|
|||||||
version: 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0)
|
version: 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0)
|
||||||
'@radix-ui/react-dialog':
|
'@radix-ui/react-dialog':
|
||||||
specifier: ^1.0.5
|
specifier: ^1.0.5
|
||||||
version: 1.0.5(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0)
|
version: 1.0.5(@types/react-dom@18.2.19)(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0)
|
||||||
'@radix-ui/react-dropdown-menu':
|
'@radix-ui/react-dropdown-menu':
|
||||||
specifier: ^2.0.6
|
specifier: ^2.0.6
|
||||||
version: 2.0.6(@types/react-dom@18.2.19)(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0)
|
version: 2.0.6(@types/react-dom@18.2.19)(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0)
|
||||||
@ -874,7 +877,7 @@ importers:
|
|||||||
version: 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0)
|
version: 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0)
|
||||||
'@radix-ui/react-dialog':
|
'@radix-ui/react-dialog':
|
||||||
specifier: ^1.0.5
|
specifier: ^1.0.5
|
||||||
version: 1.0.5(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0)
|
version: 1.0.5(@types/react-dom@18.2.19)(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0)
|
||||||
'@radix-ui/react-dropdown-menu':
|
'@radix-ui/react-dropdown-menu':
|
||||||
specifier: ^2.0.6
|
specifier: ^2.0.6
|
||||||
version: 2.0.6(@types/react-dom@18.2.19)(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0)
|
version: 2.0.6(@types/react-dom@18.2.19)(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0)
|
||||||
@ -1912,7 +1915,7 @@ packages:
|
|||||||
'@radix-ui/primitive': 1.0.1
|
'@radix-ui/primitive': 1.0.1
|
||||||
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.61)(react@18.2.0)
|
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.61)(react@18.2.0)
|
||||||
'@radix-ui/react-context': 1.0.1(@types/react@18.2.61)(react@18.2.0)
|
'@radix-ui/react-context': 1.0.1(@types/react@18.2.61)(react@18.2.0)
|
||||||
'@radix-ui/react-dialog': 1.0.5(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0)
|
'@radix-ui/react-dialog': 1.0.5(@types/react-dom@18.2.19)(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0)
|
||||||
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0)
|
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0)
|
||||||
'@radix-ui/react-slot': 1.0.2(@types/react@18.2.61)(react@18.2.0)
|
'@radix-ui/react-slot': 1.0.2(@types/react@18.2.61)(react@18.2.0)
|
||||||
'@types/react': 18.2.61
|
'@types/react': 18.2.61
|
||||||
@ -2072,7 +2075,7 @@ packages:
|
|||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@radix-ui/react-dialog@1.0.5(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0):
|
/@radix-ui/react-dialog@1.0.5(@types/react-dom@18.2.19)(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==}
|
resolution: {integrity: sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': '*'
|
||||||
@ -2099,6 +2102,7 @@ packages:
|
|||||||
'@radix-ui/react-slot': 1.0.2(@types/react@18.2.61)(react@18.2.0)
|
'@radix-ui/react-slot': 1.0.2(@types/react@18.2.61)(react@18.2.0)
|
||||||
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.61)(react@18.2.0)
|
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.61)(react@18.2.0)
|
||||||
'@types/react': 18.2.61
|
'@types/react': 18.2.61
|
||||||
|
'@types/react-dom': 18.2.19
|
||||||
aria-hidden: 1.2.3
|
aria-hidden: 1.2.3
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
|
@ -89,7 +89,6 @@ fn main() {
|
|||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
nostr::keys::create_keys,
|
nostr::keys::create_keys,
|
||||||
nostr::keys::save_key,
|
nostr::keys::save_key,
|
||||||
nostr::keys::get_public_key,
|
|
||||||
nostr::keys::update_signer,
|
nostr::keys::update_signer,
|
||||||
nostr::keys::verify_signer,
|
nostr::keys::verify_signer,
|
||||||
nostr::keys::load_selected_account,
|
nostr::keys::load_selected_account,
|
||||||
|
@ -30,11 +30,28 @@ pub fn create_keys() -> Result<CreateKeysResponse, ()> {
|
|||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn save_key(
|
pub async fn save_key(
|
||||||
nsec: &str,
|
nsec: &str,
|
||||||
|
password: &str,
|
||||||
app_handle: tauri::AppHandle,
|
app_handle: tauri::AppHandle,
|
||||||
state: State<'_, Nostr>,
|
state: State<'_, Nostr>,
|
||||||
) -> Result<bool, ()> {
|
) -> Result<bool, String> {
|
||||||
if let Ok(nostr_secret_key) = SecretKey::from_bech32(nsec) {
|
let secret_key: Result<SecretKey, String>;
|
||||||
let nostr_keys = Keys::new(nostr_secret_key);
|
|
||||||
|
if nsec.starts_with("ncrypto") {
|
||||||
|
let encrypted_key = EncryptedSecretKey::from_bech32(nsec).unwrap();
|
||||||
|
secret_key = match encrypted_key.to_secret_key(password) {
|
||||||
|
Ok(val) => Ok(val),
|
||||||
|
Err(_) => Err("Wrong passphase".into()),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
secret_key = match SecretKey::from_bech32(nsec) {
|
||||||
|
Ok(val) => Ok(val),
|
||||||
|
Err(_) => Err("nsec is not valid".into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match secret_key {
|
||||||
|
Ok(val) => {
|
||||||
|
let nostr_keys = Keys::new(val);
|
||||||
let nostr_npub = nostr_keys.public_key().to_bech32().unwrap();
|
let nostr_npub = nostr_keys.public_key().to_bech32().unwrap();
|
||||||
let signer = NostrSigner::Keys(nostr_keys);
|
let signer = NostrSigner::Keys(nostr_keys);
|
||||||
|
|
||||||
@ -43,16 +60,15 @@ pub async fn save_key(
|
|||||||
client.set_signer(Some(signer)).await;
|
client.set_signer(Some(signer)).await;
|
||||||
|
|
||||||
let keyring_entry = Entry::new("Lume Secret Storage", "AppKey").unwrap();
|
let keyring_entry = Entry::new("Lume Secret Storage", "AppKey").unwrap();
|
||||||
let secret_key = keyring_entry.get_password().unwrap();
|
let master_key = keyring_entry.get_password().unwrap();
|
||||||
let app_key = age::x25519::Identity::from_str(&secret_key).unwrap();
|
let app_key = age::x25519::Identity::from_str(&master_key).unwrap();
|
||||||
let app_pubkey = app_key.to_public();
|
let app_pubkey = app_key.to_public();
|
||||||
|
|
||||||
let config_dir = app_handle.path().app_config_dir().unwrap();
|
let config_dir = app_handle.path().app_config_dir().unwrap();
|
||||||
let encryptor =
|
let encryptor = age::Encryptor::with_recipients(vec![Box::new(app_pubkey)])
|
||||||
age::Encryptor::with_recipients(vec![Box::new(app_pubkey)]).expect("we provided a recipient");
|
.expect("we provided a recipient");
|
||||||
|
|
||||||
let file_ext = ".nsec".to_owned();
|
let file_path = nostr_npub + ".nsec";
|
||||||
let file_path = nostr_npub + &file_ext;
|
|
||||||
let mut file = File::create(config_dir.join(file_path)).unwrap();
|
let mut file = File::create(config_dir.join(file_path)).unwrap();
|
||||||
let mut writer = encryptor
|
let mut writer = encryptor
|
||||||
.wrap_output(&mut file)
|
.wrap_output(&mut file)
|
||||||
@ -63,21 +79,9 @@ pub async fn save_key(
|
|||||||
writer.finish().expect("Save nsec failed");
|
writer.finish().expect("Save nsec failed");
|
||||||
|
|
||||||
Ok(true)
|
Ok(true)
|
||||||
} else {
|
|
||||||
Ok(false)
|
|
||||||
}
|
}
|
||||||
}
|
Err(msg) => Err(msg.into()),
|
||||||
|
}
|
||||||
#[tauri::command]
|
|
||||||
pub fn get_public_key(nsec: &str) -> Result<String, ()> {
|
|
||||||
let secret_key = SecretKey::from_bech32(nsec).unwrap();
|
|
||||||
let keys = Keys::new(secret_key);
|
|
||||||
Ok(
|
|
||||||
keys
|
|
||||||
.public_key()
|
|
||||||
.to_bech32()
|
|
||||||
.expect("get public key failed"),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
@ -1,22 +1,12 @@
|
|||||||
use tauri::{tray::ClickType, Manager, Runtime};
|
use tauri::{tray::ClickType, Manager, Runtime};
|
||||||
|
|
||||||
pub fn create_tray<R: Runtime>(app: &tauri::AppHandle<R>) -> tauri::Result<()> {
|
pub fn create_tray<R: Runtime>(app: &tauri::AppHandle<R>) -> tauri::Result<()> {
|
||||||
|
let tray = app.tray().unwrap();
|
||||||
let menu = tauri::menu::MenuBuilder::new(app)
|
let menu = tauri::menu::MenuBuilder::new(app)
|
||||||
.item(&tauri::menu::MenuItem::with_id(app, "quit", "Quit", true, None::<&str>).unwrap())
|
.item(&tauri::menu::MenuItem::with_id(app, "quit", "Quit", true, None::<&str>).unwrap())
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
let _ = tray.set_menu(Some(menu));
|
||||||
let tray = tauri::tray::TrayIconBuilder::with_id("main_tray")
|
|
||||||
.tooltip("Lume")
|
|
||||||
.icon(tauri::Icon::Rgba {
|
|
||||||
rgba: include_bytes!("../icons/icon.png").to_vec(),
|
|
||||||
width: 500,
|
|
||||||
height: 500,
|
|
||||||
})
|
|
||||||
.icon_as_template(true)
|
|
||||||
.menu(&menu)
|
|
||||||
.build(app)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
tray.on_menu_event(move |app, event| match event.id.0.as_str() {
|
tray.on_menu_event(move |app, event| match event.id.0.as_str() {
|
||||||
"quit" => {
|
"quit" => {
|
||||||
|
@ -12,6 +12,11 @@
|
|||||||
"app": {
|
"app": {
|
||||||
"macOSPrivateApi": true,
|
"macOSPrivateApi": true,
|
||||||
"withGlobalTauri": true,
|
"withGlobalTauri": true,
|
||||||
|
"trayIcon": {
|
||||||
|
"id": "main_tray",
|
||||||
|
"iconPath": "./icons/tray.png",
|
||||||
|
"iconAsTemplate": true
|
||||||
|
},
|
||||||
"security": {
|
"security": {
|
||||||
"assetProtocol": {
|
"assetProtocol": {
|
||||||
"enable": true,
|
"enable": true,
|
||||||
@ -92,7 +97,15 @@
|
|||||||
"type": "downloadBootstrapper"
|
"type": "downloadBootstrapper"
|
||||||
},
|
},
|
||||||
"wix": null
|
"wix": null
|
||||||
|
},
|
||||||
|
"fileAssociations": [
|
||||||
|
{
|
||||||
|
"name": "bech32",
|
||||||
|
"description": "Nostr Bech32",
|
||||||
|
"ext": ["nsec", "nprofile", "nevent", "naddr", "nrelay"],
|
||||||
|
"role": "Viewer"
|
||||||
}
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"updater": {
|
"updater": {
|
||||||
|
Loading…
Reference in New Issue
Block a user