This commit is contained in:
Ren Amamiya
2023-07-02 15:12:07 +07:00
parent 8a92813211
commit d35c64e28d
20 changed files with 589 additions and 88 deletions

View File

@@ -13,12 +13,17 @@ import { ChannelScreen } from "@app/channel";
import { ChatScreen } from "@app/chat";
import { ErrorScreen } from "@app/error";
import { Root } from "@app/root";
import { AccountSettingsScreen } from "@app/settings/account";
import { GeneralSettingsScreen } from "@app/settings/general";
import { ShortcutsSettingsScreen } from "@app/settings/shortcuts";
import { UpdateSettingsScreen } from "@app/settings/update";
import { SpaceScreen } from "@app/space";
import { TrendingScreen } from "@app/trending";
import { UserScreen } from "@app/user";
import { AppLayout } from "@shared/appLayout";
import { AuthLayout } from "@shared/authLayout";
import { Protected } from "@shared/protected";
import { SettingsLayout } from "@shared/settingsLayout";
import { RouterProvider, createBrowserRouter } from "react-router-dom";
const router = createBrowserRouter([
@@ -72,6 +77,20 @@ const router = createBrowserRouter([
{ path: "channel/:id", element: <ChannelScreen /> },
],
},
{
path: "/settings",
element: (
<Protected>
<SettingsLayout />
</Protected>
),
children: [
{ path: "general", element: <GeneralSettingsScreen /> },
{ path: "shortcuts", element: <ShortcutsSettingsScreen /> },
{ path: "account", element: <AccountSettingsScreen /> },
{ path: "update", element: <UpdateSettingsScreen /> },
],
},
]);
export default function App() {

View File

@@ -12,9 +12,7 @@ export function ChatsListItem({ data }: { data: any }) {
return (
<div className="inline-flex h-9 items-center gap-2.5 rounded-md px-2.5">
<div className="relative h-6 w-6 shrink-0 animate-pulse rounded bg-zinc-800" />
<div>
<div className="h-2.5 w-2/3 animate-pulse truncate rounded bg-zinc-800 text-base font-medium" />
</div>
<div className="h-2.5 w-2/3 animate-pulse rounded bg-zinc-800" />
</div>
);
}
@@ -40,10 +38,10 @@ export function ChatsListItem({ data }: { data: any }) {
</div>
<div className="w-full inline-flex items-center justify-between">
<div className="inline-flex items-baseline gap-1">
<h5 className="max-w-[9rem] truncate font-medium text-zinc-200">
<h5 className="max-w-[10rem] truncate font-medium text-zinc-200">
{user?.nip05 ||
user?.displayName ||
user?.name ||
user?.displayName ||
shortenKey(data.sender_pubkey)}
</h5>
</div>

View File

@@ -0,0 +1,7 @@
export function AccountSettingsScreen() {
return (
<div className="w-full h-full flex items-center justify-center">
<h1>Account</h1>
</div>
);
}

View File

@@ -0,0 +1,7 @@
export function GeneralSettingsScreen() {
return (
<div className="w-full h-full flex items-center justify-center">
<h1>General</h1>
</div>
);
}

View File

@@ -0,0 +1,7 @@
export function ShortcutsSettingsScreen() {
return (
<div className="w-full h-full flex items-center justify-center">
<h1>Shortcuts</h1>
</div>
);
}

View File

@@ -0,0 +1,7 @@
export function UpdateSettingsScreen() {
return (
<div className="w-full h-full flex items-center justify-center">
<h1>Update</h1>
</div>
);
}

View File

@@ -1,19 +1,32 @@
import { getNotesByPubkey } from "@libs/storage";
import { NDKFilter } from "@nostr-dev-kit/ndk";
import { Note } from "@shared/notes/note";
import { RelayContext } from "@shared/relayProvider";
import { useQuery } from "@tanstack/react-query";
import { dateToUnix, getHourAgo } from "@utils/date";
import { LumeEvent } from "@utils/types";
import { useContext } from "react";
export function UserFeed({ pubkey }: { pubkey: string }) {
const ndk = useContext(RelayContext);
const { status, data } = useQuery(["user-feed", pubkey], async () => {
return await getNotesByPubkey(pubkey);
const now = new Date();
const filter: NDKFilter = {
kinds: [1],
authors: [pubkey],
since: dateToUnix(getHourAgo(48, now)),
};
const events = await ndk.fetchEvents(filter);
return [...events];
});
return (
<div className="w-full max-w-[400px] px-2">
<div className="w-full max-w-[400px] px-2 pb-10">
{status === "loading" ? (
<p>Loading...</p>
<div className="px-3">
<p>Loading...</p>
</div>
) : (
data.map((note: LumeEvent) => <Note key={note.event_id} event={note} />)
data.map((note: LumeEvent) => <Note key={note.id} event={note} />)
)}
</div>
);

View File

@@ -145,7 +145,7 @@ export function UserScreen() {
} font-medium inline-flex items-center gap-2 h-10 border-t`}
>
<ThreadsIcon className="w-4 h-4" />
Posts
Activities from 48 hours ago
</button>
)}
</Tab>

View File

@@ -400,7 +400,21 @@ export async function createBlock(kind: number, title: string, content: any) {
);
}
// remove block
export async function removeBlock(id: string) {
const db = await connect();
return await db.execute(`DELETE FROM blocks WHERE id = "${id}";`);
}
// logout
export async function removeAll() {
const db = await connect();
await db.execute(`UPDATE settings SET value = "0" WHERE key = "last_login";`);
await db.execute("DELETE FROM replies;");
await db.execute("DELETE FROM notes;");
await db.execute("DELETE FROM blacklist;");
await db.execute("DELETE FROM blocks;");
await db.execute("DELETE FROM chats;");
await db.execute("DELETE FROM accounts;");
return true;
}

View File

@@ -39,4 +39,6 @@ export * from "./cmd";
export * from "./verticalDots";
export * from "./signal";
export * from "./unverified";
export * from "./settings";
export * from "./logout";
// @endindex

View File

@@ -0,0 +1,24 @@
import { SVGProps } from "react";
export function LogoutIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
{...props}
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
d="M20.25 12H9m11.25 0l-4.5 4.5m4.5-4.5l-4.5-4.5m-4.5 12.75h-6.5a1 1 0 01-1-1V4.75a1 1 0 011-1h6.5"
/>
</svg>
);
}

View File

@@ -0,0 +1,29 @@
import { SVGProps } from "react";
export function SettingsIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
{...props}
>
<path
stroke="currentColor"
strokeLinejoin="round"
strokeWidth="1.5"
d="M8.552 5.37l-1.793-.414a1 1 0 00-.932.267l-.604.604a1 1 0 00-.267.932l.414 1.793a1 1 0 01-.42 1.056l-1.755 1.17a1 1 0 00-.445.832v.78a1 1 0 00.445.832l1.755 1.17a1 1 0 01.42 1.056l-.414 1.793a1 1 0 00.267.932l.604.604a1 1 0 00.932.267l1.793-.414a1 1 0 011.056.42l1.17 1.755a1 1 0 00.832.445h.78a1 1 0 00.832-.445l1.17-1.755a1 1 0 011.056-.42l1.793.414a1 1 0 00.932-.267l.604-.604a1 1 0 00.267-.932l-.414-1.793a1 1 0 01.42-1.056l1.755-1.17a1 1 0 00.445-.832v-.78a1 1 0 00-.445-.832l-1.755-1.17a1 1 0 01-.42-1.056l.414-1.793a1 1 0 00-.267-.932l-.604-.604a1 1 0 00-.932-.267l-1.793.414a1 1 0 01-1.056-.42l-1.17-1.755a1 1 0 00-.832-.445h-.78a1 1 0 00-.832.445L9.608 4.95a1 1 0 01-1.056.42z"
/>
<path
stroke="currentColor"
strokeLinejoin="round"
strokeWidth="1.5"
d="M14.75 12a2.75 2.75 0 11-5.5 0 2.75 2.75 0 015.5 0z"
/>
</svg>
);
}

123
src/shared/logout.tsx Normal file
View File

@@ -0,0 +1,123 @@
import { Dialog, Transition } from "@headlessui/react";
import { removeAll } from "@libs/storage";
import { CancelIcon, LogoutIcon } from "@shared/icons";
import { useQueryClient } from "@tanstack/react-query";
import { relaunch } from "@tauri-apps/api/process";
import { Fragment, useState } from "react";
export function Logout() {
const queryClient = useQueryClient();
const [isOpen, setIsOpen] = useState(false);
const closeModal = () => {
setIsOpen(false);
};
const openModal = () => {
setIsOpen(true);
};
const logout = async () => {
// reset database
await removeAll();
// reset react query
queryClient.clear();
// navigate
await relaunch();
};
return (
<>
<button
type="button"
onClick={() => openModal()}
aria-label="Logout"
className="inline-flex items-center justify-center w-9 h-9 rounded-md border-t bg-zinc-800 border-zinc-700/50 transform active:translate-y-1"
>
<LogoutIcon className="w-4 h-4 text-zinc-400" />
</button>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={closeModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 z-50 bg-black bg-opacity-30 backdrop-blur-md" />
</Transition.Child>
<div className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="relative flex h-min w-full max-w-lg flex-col rounded-xl border-t border-zinc-800/50 bg-zinc-900">
<div className="h-min w-full shrink-0 border-b border-zinc-800 px-5 py-6">
<div className="flex flex-col gap-2">
<div className="flex items-center justify-between">
<Dialog.Title
as="h3"
className="text-lg font-semibold leading-none text-zinc-100"
>
Are you sure!
</Dialog.Title>
<button
type="button"
onClick={closeModal}
className="inline-flex h-5 w-5 items-center justify-center rounded hover:bg-zinc-900"
>
<CancelIcon
width={20}
height={20}
className="text-zinc-300"
/>
</button>
</div>
<Dialog.Description className="text-sm leading-tight text-zinc-400">
<p className="mb-2">
When logout, all local data will be wiped, and restart
app then you need to start onboarding process again when
you log in.
</p>
<p>
In the next version, Lume will support multi account,
then you can switch between all account s instead of
logout
</p>
</Dialog.Description>
</div>
</div>
<div className="flex h-full w-full flex-col items-end justify-center overflow-y-auto px-5 py-2.5">
<div className="flex items-center gap-2">
<button
type="button"
onClick={closeModal}
className="inline-flex h-9 items-center justify-center rounded-md px-3 text-sm font-medium text-zinc-400 hover:bg-zinc-800 hover:text-zinc-100"
>
Cancel
</button>
<button
type="button"
onClick={() => logout()}
className="inline-flex h-9 items-center justify-center rounded-md bg-red-500 px-3 text-sm font-medium text-zinc-100 hover:bg-red-600"
>
Confirm
</button>
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</Dialog>
</Transition>
</>
);
}

View File

@@ -1,68 +1,30 @@
import { Transition } from "@headlessui/react";
import { getActiveAccount } from "@libs/storage";
import { ActiveAccount } from "@shared/accounts/active";
import { VerticalDotsIcon } from "@shared/icons";
import { RelayManager } from "@shared/relayManager";
import { useQuery } from "@tanstack/react-query";
import { useState } from "react";
import { SettingsIcon } from "@shared/icons";
import { Logout } from "@shared/logout";
import { Notification } from "@shared/notification";
import { useAccount } from "@utils/hooks/useAccount";
import { Link } from "react-router-dom";
export function LumeBar() {
const { status, data: activeAccount } = useQuery(
["activeAccount"],
async () => {
return await getActiveAccount();
},
);
const [open, setOpen] = useState(false);
const toggleMenu = () => {
setOpen((isOpen) => !isOpen);
};
const { status, account } = useAccount();
return (
<div className="flex flex-col gap-2 rounded-xl p-2 border-t border-zinc-800/50 bg-zinc-900/80 backdrop-blur-md">
<div className="rounded-xl p-2 border-t border-zinc-800/50 bg-zinc-900/80 backdrop-blur-md">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
{status === "loading" ? (
<div className="group relative flex h-9 w-9 shrink animate-pulse items-center justify-center rounded-md bg-zinc-900" />
) : (
<ActiveAccount data={activeAccount} />
)}
<RelayManager />
</div>
<button
type="button"
onClick={() => toggleMenu()}
className="inline-flex items-center justify-center w-5 h-5 rounded hover:bg-zinc-800"
{status === "loading" ? (
<div className="group relative flex h-9 w-9 shrink animate-pulse items-center justify-center rounded-md bg-zinc-900" />
) : (
<ActiveAccount data={account} />
)}
<Notification />
<Link
to="/settings/general"
className="inline-flex items-center justify-center w-9 h-9 rounded-md border-t bg-zinc-800 border-zinc-700/50 transform active:translate-y-1"
>
<VerticalDotsIcon className="w-4 h-4 text-zinc-100" />
</button>
<SettingsIcon className="w-4 h-4 text-zinc-400" />
</Link>
<Logout />
</div>
<Transition
show={open}
enter="transition-transform ease-in-out duration-75"
enterFrom="translate-y-16"
enterTo="translate-y-0"
leave="transition-transform ease-in-out duration-150"
leaveFrom="translate-y-0"
leaveTo="translate-y-16"
className="flex flex-col items-start justify-start gap-1 pt-1.5 border-t border-zinc-800 transform"
>
<Link
to="/app/settings"
className="w-full py-2 px-2 rounded hover:bg-zinc-800 text-zinc-100 text-start text-sm"
>
Settings
</Link>
<Link
to="/app/logout"
className="w-full py-2 px-2 rounded hover:bg-zinc-800 text-zinc-100 text-start text-sm"
>
Logout
</Link>
</Transition>
</div>
);
}

View File

@@ -7,14 +7,10 @@ import { LumeBar } from "@shared/lumeBar";
import { NavLink } from "react-router-dom";
import { twMerge } from "tailwind-merge";
export function Navigation({ reverse = false }: { reverse?: boolean }) {
export function Navigation() {
return (
<div
className={`relative flex w-[232px] flex-col gap-3 ${
reverse ? "border-l" : "border-r"
} border-zinc-900`}
>
<AppHeader reverse={reverse} />
<div className="relative flex w-[232px] flex-col gap-3 border-r border-zinc-900">
<AppHeader />
<div className="pb-20 flex flex-col gap-5 overflow-y-auto scrollbar-hide">
<div className="inlin-lflex h-8 px-3.5">
<Composer />
@@ -117,7 +113,7 @@ export function Navigation({ reverse = false }: { reverse?: boolean }) {
)}
</Disclosure>
</div>
<div className="absolute bottom-3 left-0 px-8 w-full">
<div className="absolute bottom-3 left-0 px-10 w-full">
<LumeBar />
</div>
</div>

View File

@@ -0,0 +1,58 @@
import { Dialog, Transition } from "@headlessui/react";
import { BellIcon } from "@shared/icons";
import { Fragment, useState } from "react";
export function Notification() {
const [isOpen, setIsOpen] = useState(false);
const closeModal = () => {
setIsOpen(false);
};
const openModal = () => {
setIsOpen(true);
};
return (
<>
<button
type="button"
onClick={() => openModal()}
aria-label="Notification"
className="inline-flex items-center justify-center w-9 h-9 rounded-md border-t bg-zinc-800 border-zinc-700/50 transform active:translate-y-1"
>
<BellIcon className="w-4 h-4 text-zinc-400" />
</button>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={closeModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 z-50 bg-black bg-opacity-30 backdrop-blur-md" />
</Transition.Child>
<div className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="relative flex h-min w-full max-w-lg flex-col gap-2 rounded-lg border-t border-zinc-800/50 bg-zinc-900">
<p>OK</p>
</Dialog.Panel>
</Transition.Child>
</div>
</Dialog>
</Transition>
</>
);
}

View File

@@ -1,13 +0,0 @@
import { SignalIcon } from "@shared/icons";
export function RelayManager() {
return (
<button
type="button"
aria-label="Relay manager"
className="inline-flex items-center justify-center w-9 h-9 rounded-md border-t bg-zinc-800 border-zinc-700/50 transform active:translate-y-1"
>
<SignalIcon className="w-4 h-4 text-zinc-400" />
</button>
);
}

View File

@@ -0,0 +1,74 @@
import { AppHeader } from "@shared/appHeader";
import { NavLink, Outlet, ScrollRestoration } from "react-router-dom";
import { twMerge } from "tailwind-merge";
export function SettingsLayout() {
return (
<div className="flex w-screen h-screen">
<div className="relative flex flex-row shrink-0">
<div className="relative flex w-[232px] flex-col gap-3 border-r border-zinc-900">
<AppHeader />
<div className="pb-20 flex flex-col gap-5 overflow-y-auto scrollbar-hide">
<div className="flex flex-col gap-0.5 px-1.5">
<div className="px-2.5">
<h3 className="text-[11px] font-bold uppercase tracking-widest text-zinc-600">
Settings
</h3>
</div>
<div className="flex flex-col">
<NavLink
to="/settings/general"
className={({ isActive }) =>
twMerge(
"flex h-9 items-center gap-2.5 rounded-md px-2.5 text-zinc-200",
isActive ? "bg-zinc-900/50" : "",
)
}
>
<span className="font-medium">General</span>
</NavLink>
<NavLink
to="/settings/shortcuts"
className={({ isActive }) =>
twMerge(
"flex h-9 items-center gap-2.5 rounded-md px-2.5 text-zinc-200",
isActive ? "bg-zinc-900/50" : "",
)
}
>
<span className="font-medium">Shortcuts</span>
</NavLink>
<NavLink
to="/settings/account"
className={({ isActive }) =>
twMerge(
"flex h-9 items-center gap-2.5 rounded-md px-2.5 text-zinc-200",
isActive ? "bg-zinc-900/50" : "",
)
}
>
<span className="font-medium">Account</span>
</NavLink>
<NavLink
to="/settings/update"
className={({ isActive }) =>
twMerge(
"flex h-9 items-center gap-2.5 rounded-md px-2.5 text-zinc-200",
isActive ? "bg-zinc-900/50" : "",
)
}
>
<span className="font-medium">Update</span>
</NavLink>
</div>
</div>
</div>
</div>
</div>
<div className="w-full h-full">
<Outlet />
<ScrollRestoration />
</div>
</div>
);
}