wip: new design

This commit is contained in:
2024-02-14 08:29:24 +07:00
parent 4292def206
commit 60fd09000b
131 changed files with 2026 additions and 5853 deletions

View File

@@ -1,16 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Lume Desktop</title>
</head>
<body class="relative w-screen h-screen overflow-hidden font-sans antialiased cursor-default select-none">
<div class="fixed top-0 left-0 z-50 w-full h-9" data-tauri-drag-region></div>
<div id="root"></div>
<script type="module" src="/src/app.tsx"></script>
</body>
</html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Lume Desktop</title>
</head>
<body
class="relative h-screen w-screen cursor-default select-none overflow-hidden bg-white font-sans text-black antialiased dark:bg-black dark:text-white"
>
<div id="root" class="h-full w-full"></div>
<script type="module" src="/src/app.tsx"></script>
</body>
</html>

View File

@@ -14,6 +14,7 @@
"@lume/storage": "workspace:^",
"@lume/ui": "workspace:^",
"@lume/utils": "workspace:^",
"@radix-ui/react-checkbox": "^1.0.4",
"@tanstack/react-query": "^5.18.1",
"@tanstack/react-router": "^1.16.0",
"i18next": "^23.8.2",
@@ -21,7 +22,8 @@
"jotai": "^2.6.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-i18next": "^14.0.2"
"react-i18next": "^14.0.2",
"sonner": "^1.4.0"
},
"devDependencies": {
"@lume/tailwindcss": "workspace:^",

View File

@@ -1,20 +1,89 @@
import { Navigation } from "@lume/ui";
import {
BellFilledIcon,
BellIcon,
HomeFilledIcon,
HomeIcon,
SpaceFilledIcon,
SpaceIcon,
} from "@lume/icons";
import { useStorage } from "@lume/storage";
import { Link } from "@tanstack/react-router";
import { Outlet, createFileRoute } from "@tanstack/react-router";
import { cn } from "@lume/utils";
export const Route = createFileRoute("/app")({
component: App,
component: App,
});
function App() {
return (
<div className="flex flex-col w-screen h-screen bg-gradient-to-tl from-gray-1 to-gray-2 dark:from-graydark-1 dark:to-graydark-2">
<div data-tauri-drag-region className="h-9 shrink-0" />
<div className="flex w-full h-full min-h-0">
<Navigation />
<div className="flex-1 h-full pb-2 pl-1 pr-2">
<Outlet />
</div>
</div>
</div>
);
const storage = useStorage();
return (
<div className="flex h-screen w-screen flex-col bg-gradient-to-tr from-neutral-200 to-neutral-100 dark:from-neutral-800 dark:to-neutral-900">
<div
data-tauri-drag-region
className={cn(
"flex h-11 shrink-0 items-center gap-2",
storage.platform === "macos" ? "pl-24" : "",
)}
>
<Link to="/app/home">
{({ isActive }) => (
<div
className={cn(
"inline-flex h-9 w-max items-center justify-center gap-2 rounded-lg px-3 hover:bg-black/10 dark:hover:bg-white/10",
isActive ? "bg-white shadow dark:bg-black" : "",
)}
>
{isActive ? (
<HomeFilledIcon className="size-5" />
) : (
<HomeIcon className="size-5" />
)}
<span className="text-sm font-medium">Home</span>
</div>
)}
</Link>
<Link to="/app/space">
{({ isActive }) => (
<div
className={cn(
"inline-flex h-9 w-max items-center justify-center gap-2 rounded-lg px-3 hover:bg-black/10 dark:hover:bg-white/10",
isActive ? "bg-white shadow dark:bg-black" : "",
)}
>
{isActive ? (
<SpaceFilledIcon className="size-5" />
) : (
<SpaceIcon className="size-5" />
)}
<span className="text-sm font-medium">Space</span>
</div>
)}
</Link>
<Link to="/app/activity">
{({ isActive }) => (
<div
className={cn(
"inline-flex h-9 w-max items-center justify-center gap-2 rounded-lg px-3 hover:bg-black/10 dark:hover:bg-white/10",
isActive ? "bg-white shadow dark:bg-black" : "",
)}
>
{isActive ? (
<BellFilledIcon className="size-5" />
) : (
<BellIcon className="size-5" />
)}
<span className="text-sm font-medium">Activity</span>
</div>
)}
</Link>
</div>
<div className="flex h-full min-h-0 w-full">
<div className="h-full w-full flex-1 px-2 pb-2">
<Outlet />
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,13 @@
import { createLazyFileRoute } from "@tanstack/react-router";
export const Route = createLazyFileRoute("/app/activity")({
component: Activity,
});
function Activity() {
return (
<div className="h-full w-full overflow-hidden rounded-xl bg-white shadow-[rgba(50,_50,_105,_0.15)_0px_2px_5px_0px,_rgba(0,_0,_0,_0.05)_0px_1px_1px_0px] dark:bg-black dark:shadow-none dark:ring-1 dark:ring-white/5">
<p>Activity</p>
</div>
);
}

View File

@@ -0,0 +1,13 @@
import { createLazyFileRoute } from "@tanstack/react-router";
export const Route = createLazyFileRoute("/app/home")({
component: Home,
});
function Home() {
return (
<div className="h-full w-full overflow-hidden rounded-xl bg-white shadow-[rgba(50,_50,_105,_0.15)_0px_2px_5px_0px,_rgba(0,_0,_0,_0.05)_0px_1px_1px_0px] dark:bg-black dark:shadow-none dark:ring-1 dark:ring-white/5">
<p>Home</p>
</div>
);
}

View File

@@ -1,13 +1,13 @@
import { createLazyFileRoute } from "@tanstack/react-router";
export const Route = createLazyFileRoute("/app/space")({
component: Space,
component: Space,
});
function Space() {
return (
<div className="h-full w-full rounded-xl overflow-hidden bg-white shadow-[rgba(50,_50,_105,_0.15)_0px_2px_5px_0px,_rgba(0,_0,_0,_0.05)_0px_1px_1px_0px] dark:bg-black dark:shadow-none dark:ring-1 dark:ring-white/5">
<p>Hello</p>
</div>
);
return (
<div className="h-full w-full overflow-hidden rounded-xl bg-white shadow-[rgba(50,_50,_105,_0.15)_0px_2px_5px_0px,_rgba(0,_0,_0,_0.05)_0px_1px_1px_0px] dark:bg-black dark:shadow-none dark:ring-1 dark:ring-white/5">
<p>Space</p>
</div>
);
}

View File

@@ -1,52 +0,0 @@
import { useArk } from "@lume/ark";
import { Keys } from "@lume/types";
import { createLazyFileRoute, useNavigate } from "@tanstack/react-router";
import { invoke } from "@tauri-apps/api/core";
import { useEffect, useState } from "react";
export const Route = createLazyFileRoute("/auth/create")({
component: Create,
});
function Create() {
const ark = useArk();
const navigate = useNavigate();
const [keys, setKeys] = useState<Keys>(null);
const submit = async () => {
const save = await ark.save_account(keys);
if (save) {
navigate({ to: "/" });
} else {
console.log("create failed");
}
};
useEffect(() => {
async function genKeys() {
const cmd: Keys = await invoke("create_keys");
setKeys(cmd);
}
genKeys();
}, []);
return (
<div className="flex flex-col items-center justify-center w-screen h-screen">
<div>
<h3>Backup your key</h3>
<div className="flex flex-col gap-2">
{keys ? <input name="nsec" readOnly value={keys.nsec} /> : null}
<button
type="button"
onClick={submit}
className="w-full h-11 bg-gray-3 hover:bg-gray-4"
>
Submit
</button>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,108 @@
import { LoaderIcon } from "@lume/icons";
import { cn } from "@lume/utils";
import { createLazyFileRoute, useNavigate } from "@tanstack/react-router";
import { useState } from "react";
import { useTranslation } from "react-i18next";
export const Route = createLazyFileRoute("/auth/create/")({
component: Screen,
});
function Screen() {
const navigate = useNavigate();
const [t] = useTranslation();
const [method, setMethod] = useState<"self" | "managed">("self");
const [loading, setLoading] = useState(false);
const next = () => {
setLoading(true);
if (method === "self") {
navigate({ to: "/auth/create/self" });
} else {
navigate({ to: "/auth/create/managed" });
}
};
return (
<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-2 text-center">
<h1 className="text-2xl font-semibold">{t("signup.title")}</h1>
<p className="text-lg leading-snug text-neutral-600 dark:text-neutral-500">
{t("signup.subtitle")}
</p>
</div>
<div className="flex flex-col gap-4">
<button
type="button"
onClick={() => setMethod("self")}
className={cn(
"flex flex-col items-start rounded-xl bg-neutral-100 px-4 py-3.5 hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800",
method === "self"
? "ring-1 ring-blue-500 ring-offset-2 ring-offset-white dark:ring-offset-black"
: "",
)}
>
<p className="font-semibold">{t("signup.selfManageMethod")}</p>
<p className="text-sm text-neutral-600 dark:text-neutral-500">
{t("signup.selfManageMethodDescription")}
</p>
</button>
<button
type="button"
onClick={() => setMethod("managed")}
className={cn(
"flex flex-col items-start rounded-xl bg-neutral-100 px-4 py-3.5 hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800",
method === "managed"
? "ring-1 ring-blue-500 ring-offset-2 ring-offset-white dark:ring-offset-black"
: "",
)}
>
<p className="font-semibold">{t("signup.providerMethod")}</p>
<p className="text-sm text-neutral-600 dark:text-neutral-500">
{t("signup.providerMethodDescription")}
</p>
</button>
<div className="flex flex-col gap-3">
<button
type="button"
onClick={next}
className="inline-flex h-11 w-full items-center justify-center rounded-xl bg-blue-500 text-lg font-medium text-white hover:bg-blue-600"
>
{loading ? (
<LoaderIcon className="size-5 animate-spin" />
) : (
t("global.continue")
)}
</button>
{method === "managed" ? (
<div className="flex flex-col gap-1.5 rounded-xl border border-red-200 bg-red-100 p-2 text-sm text-red-800 dark:border-red-800 dark:bg-red-900 dark:text-red-200">
<p className="font-semibold text-red-900 dark:text-red-100">
Attention:
</p>
<p>
You're chosing Managed by Provider, this feature still in
"Beta".
</p>
<p>
Some functions still missing or not work as expected, you
shouldn't create your main account with this method
</p>
<a
href="https://github.com/kind-0/nsecbunkerd/blob/master/OAUTH-LIKE-FLOW.md"
target="_blank"
rel="noreferrer"
className="text-blue-500"
>
Learn more
</a>
</div>
) : null}
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,5 @@
import { createLazyFileRoute } from '@tanstack/react-router'
export const Route = createLazyFileRoute('/auth/create/managed')({
component: () => <div>Hello /auth/create/managed!</div>
})

View File

@@ -0,0 +1,164 @@
import { useArk } from "@lume/ark";
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 { createLazyFileRoute, useNavigate } from "@tanstack/react-router";
import { useSetAtom } from "jotai";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
export const Route = createLazyFileRoute("/auth/create/self")({
component: Create,
});
function Create() {
const ark = useArk();
const setOnboarding = useSetAtom(onboardingAtom);
const navigate = useNavigate();
const [t] = useTranslation();
const [loading, setLoading] = useState(false);
const [showKey, setShowKey] = useState(false);
const [confirm, setConfirm] = useState({ c1: false, c2: false, c3: false });
const [keys, setKeys] = useState<Keys>(null);
const submit = async () => {
const save = await ark.save_account(keys);
if (!save) {
setLoading(false);
toast.error("Save account keys failed, please try again later.");
}
// update state
setLoading(false);
setOnboarding({ open: true, newUser: true });
// next step
navigate({ to: "/app/space", replace: true });
};
useEffect(() => {
async function genKeys() {
const res = await ark.create_keys();
setKeys(res);
}
genKeys();
}, []);
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-2 text-center">
<h1 className="text-2xl font-semibold">
{t("signupWithSelfManage.title")}
</h1>
<p className="text-lg leading-snug text-neutral-600 dark:text-neutral-500">
{t("signupWithSelfManage.subtitle")}
</p>
</div>
<div className="mb-0 flex flex-col gap-6">
<div className="flex flex-col gap-6">
<div className="relative">
{keys ? (
<input
readOnly
value={keys.nsec}
type={showKey ? "text" : "password"}
className="h-11 w-full resize-none rounded-xl border-transparent bg-neutral-100 pl-3 pr-10 placeholder:text-neutral-600 focus:border-blue-500 focus:ring focus:ring-blue-100 dark:bg-neutral-900 dark:focus:ring-blue-900"
/>
) : null}
<button
type="button"
onClick={() => setShowKey((state) => !state)}
className="absolute right-2 top-2 inline-flex size-7 items-center justify-center rounded-lg bg-neutral-200 hover:bg-neutral-300 dark:bg-neutral-800 dark:hover:bg-neutral-700"
>
{showKey ? (
<EyeOnIcon className="size-4" />
) : (
<EyeOffIcon className="size-4" />
)}
</button>
</div>
<div className="flex flex-col gap-3">
<div className="flex items-center gap-2">
<Checkbox.Root
checked={confirm.c1}
onCheckedChange={() =>
setConfirm((state) => ({ ...state, c1: !state.c1 }))
}
className="flex size-7 appearance-none items-center justify-center rounded-lg bg-neutral-100 outline-none dark:bg-neutral-900"
id="confirm1"
>
<Checkbox.Indicator className="text-blue-500">
<CheckIcon className="size-4" />
</Checkbox.Indicator>
</Checkbox.Root>
<label
className="text-sm text-neutral-700 dark:text-neutral-400"
htmlFor="confirm1"
>
{t("signupWithSelfManage.confirm1")}
</label>
</div>
<div className="flex items-center gap-2">
<Checkbox.Root
checked={confirm.c2}
onCheckedChange={() =>
setConfirm((state) => ({ ...state, c2: !state.c2 }))
}
className="flex size-7 appearance-none items-center justify-center rounded-lg bg-neutral-100 outline-none dark:bg-neutral-900"
id="confirm2"
>
<Checkbox.Indicator className="text-blue-500">
<CheckIcon className="size-4" />
</Checkbox.Indicator>
</Checkbox.Root>
<label
className="text-sm text-neutral-700 dark:text-neutral-400"
htmlFor="confirm2"
>
{t("signupWithSelfManage.confirm2")}
</label>
</div>
<div className="flex items-center gap-2">
<Checkbox.Root
checked={confirm.c3}
onCheckedChange={() =>
setConfirm((state) => ({ ...state, c3: !state.c3 }))
}
className="flex size-7 appearance-none items-center justify-center rounded-lg bg-neutral-100 outline-none dark:bg-neutral-900"
id="confirm3"
>
<Checkbox.Indicator className="text-blue-500">
<CheckIcon className="size-4" />
</Checkbox.Indicator>
</Checkbox.Root>
<label
className="text-sm text-neutral-700 dark:text-neutral-400"
htmlFor="confirm3"
>
{t("signupWithSelfManage.confirm3")}
</label>
</div>
</div>
</div>
<button
type="button"
onClick={submit}
disabled={!confirm.c1 || !confirm.c2 || !confirm.c3}
className="inline-flex h-11 w-full items-center justify-center rounded-xl bg-blue-500 text-lg font-medium text-white hover:bg-blue-600 disabled:opacity-50"
>
{loading ? (
<LoaderIcon className="size-5 animate-spin" />
) : (
t("signupWithSelfManage.button")
)}
</button>
</div>
</div>
</div>
);
}

View File

@@ -3,15 +3,15 @@ import { Link, createFileRoute } from "@tanstack/react-router";
import { useTranslation } from "react-i18next";
export const Route = createFileRoute("/landing/")({
component: Index,
component: Screen,
});
function Index() {
function Screen() {
const storage = useStorage();
const { t } = useTranslation();
return (
<div className="flex flex-col w-screen h-screen bg-black">
<div className="flex w-screen h-screen bg-black">
<div className="flex flex-col items-center justify-between w-full h-full">
<div />
<div className="flex flex-col items-center w-full max-w-4xl gap-10 mx-auto">
@@ -22,27 +22,27 @@ function Index() {
alt="lume"
className="w-2/3"
/>
<p className="mt-5 text-lg font-medium leading-snug whitespace-pre-line text-gray-7">
<p className="mt-5 text-lg font-medium leading-snug whitespace-pre-line text-neutral-700">
{t("welcome.title")}
</p>
</div>
<div className="flex flex-col w-full max-w-xs gap-2 mx-auto">
<div className="flex flex-col w-full max-w-sm gap-2 mx-auto">
<Link
to="/auth/create"
className="inline-flex items-center justify-center w-full h-12 text-lg font-medium text-white bg-blue-10 rounded-xl hover:bg-blue-11"
className="inline-flex items-center justify-center w-full text-lg font-medium text-white bg-blue-500 h-11 rounded-xl hover:bg-blue-600"
>
{t("welcome.signup")}
</Link>
<Link
to="/auth/import"
className="inline-flex items-center justify-center w-full h-12 text-lg font-medium text-white rounded-xl bg-gray-12 hover:bg-gray-11"
className="inline-flex items-center justify-center w-full text-lg font-medium text-white h-11 rounded-xl bg-neutral-950 hover:bg-neutral-900"
>
{t("welcome.login")}
</Link>
</div>
</div>
<div className="flex items-center justify-center h-11">
<p className="text-gray-8">
<p className="text-neutral-800">
{t("welcome.footer")}{" "}
<Link
to="https://nostr.com"