polish
This commit is contained in:
19
src/app.tsx
19
src/app.tsx
@@ -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() {
|
||||
|
||||
@@ -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>
|
||||
|
||||
7
src/app/settings/account.tsx
Normal file
7
src/app/settings/account.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
export function AccountSettingsScreen() {
|
||||
return (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<h1>Account</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
7
src/app/settings/general.tsx
Normal file
7
src/app/settings/general.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
export function GeneralSettingsScreen() {
|
||||
return (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<h1>General</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
7
src/app/settings/shortcuts.tsx
Normal file
7
src/app/settings/shortcuts.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
export function ShortcutsSettingsScreen() {
|
||||
return (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<h1>Shortcuts</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
7
src/app/settings/update.tsx
Normal file
7
src/app/settings/update.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
export function UpdateSettingsScreen() {
|
||||
return (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<h1>Update</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -39,4 +39,6 @@ export * from "./cmd";
|
||||
export * from "./verticalDots";
|
||||
export * from "./signal";
|
||||
export * from "./unverified";
|
||||
export * from "./settings";
|
||||
export * from "./logout";
|
||||
// @endindex
|
||||
|
||||
24
src/shared/icons/logout.tsx
Normal file
24
src/shared/icons/logout.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
29
src/shared/icons/settings.tsx
Normal file
29
src/shared/icons/settings.tsx
Normal 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
123
src/shared/logout.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
58
src/shared/notification.tsx
Normal file
58
src/shared/notification.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
74
src/shared/settingsLayout.tsx
Normal file
74
src/shared/settingsLayout.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user