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

@ -16,6 +16,7 @@
"@floating-ui/react": "^0.23.1",
"@headlessui/react": "^1.7.15",
"@nostr-dev-kit/ndk": "0.6.0",
"@radix-ui/react-popover": "^1.0.6",
"@radix-ui/react-tooltip": "^1.0.6",
"@tanstack/react-query": "^4.29.19",
"@tanstack/react-virtual": "3.0.0-beta.54",

View File

@ -10,6 +10,9 @@ dependencies:
'@nostr-dev-kit/ndk':
specifier: 0.6.0
version: 0.6.0(typescript@4.9.5)
'@radix-ui/react-popover':
specifier: ^1.0.6
version: 1.0.6(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-tooltip':
specifier: ^1.0.6
version: 1.0.6(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
@ -714,6 +717,43 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-focus-guards@1.0.1(@types/react@18.2.14)(react@18.2.0):
resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@babel/runtime': 7.22.5
'@types/react': 18.2.14
react: 18.2.0
dev: false
/@radix-ui/react-focus-scope@1.0.3(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-upXdPfqI4islj2CslyfUBNlaJCPybbqRHAi1KER7Isel9Q2AtSJ0zRBZv8mWQiFXD2nyAJ4BhC3yXgZ6kMBSrQ==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
dependencies:
'@babel/runtime': 7.22.5
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.14)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.14)(react@18.2.0)
'@types/react': 18.2.14
'@types/react-dom': 18.2.6
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-id@1.0.1(@types/react@18.2.14)(react@18.2.0):
resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==}
peerDependencies:
@ -729,6 +769,41 @@ packages:
react: 18.2.0
dev: false
/@radix-ui/react-popover@1.0.6(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-cZ4defGpkZ0qTRtlIBzJLSzL6ht7ofhhW4i1+pkemjV1IKXm0wgCRnee154qlV6r9Ttunmh2TNZhMfV2bavUyA==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
dependencies:
'@babel/runtime': 7.22.5
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.14)(react@18.2.0)
'@radix-ui/react-context': 1.0.1(@types/react@18.2.14)(react@18.2.0)
'@radix-ui/react-dismissable-layer': 1.0.4(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-focus-guards': 1.0.1(@types/react@18.2.14)(react@18.2.0)
'@radix-ui/react-focus-scope': 1.0.3(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-id': 1.0.1(@types/react@18.2.14)(react@18.2.0)
'@radix-ui/react-popper': 1.1.2(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-portal': 1.0.3(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-slot': 1.0.2(@types/react@18.2.14)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.14)(react@18.2.0)
'@types/react': 18.2.14
'@types/react-dom': 18.2.6
aria-hidden: 1.2.3
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
react-remove-scroll: 2.5.5(@types/react@18.2.14)(react@18.2.0)
dev: false
/@radix-ui/react-popper@1.1.2(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-1CnGGfFi/bbqtJZZ0P/NQY20xdG3E0LALJaLUEoKwPLwl6PPPfbeiCqMVQnhoFRAxjJj4RpBRJzDmUgsex2tSg==}
peerDependencies:
@ -2158,6 +2233,10 @@ packages:
resolution: {integrity: sha512-lrbCJwD9saUQrqUfXvl6qoM+QN3W7tLV5pAOs+OqOmopCCz/JkE05MHedJR1xfk4IAnZuJXPVuN5+7jNA2ZCiA==}
dev: false
/detect-node-es@1.1.0:
resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
dev: false
/didyoumean@1.2.2:
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
dev: true
@ -2827,6 +2906,11 @@ packages:
has-symbols: 1.0.3
dev: false
/get-nonce@1.0.1:
resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
engines: {node: '>=6'}
dev: false
/get-stream@6.0.1:
resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
engines: {node: '>=10'}
@ -3118,6 +3202,12 @@ packages:
side-channel: 1.0.4
dev: false
/invariant@2.2.4:
resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==}
dependencies:
loose-envify: 1.4.0
dev: false
/ip-regex@4.3.0:
resolution: {integrity: sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==}
engines: {node: '>=8'}
@ -4297,6 +4387,41 @@ packages:
react-fast-compare: 3.2.2
dev: false
/react-remove-scroll-bar@2.3.4(@types/react@18.2.14)(react@18.2.0):
resolution: {integrity: sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==}
engines: {node: '>=10'}
peerDependencies:
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
react: ^16.8.0 || ^17.0.0 || ^18.0.0
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@types/react': 18.2.14
react: 18.2.0
react-style-singleton: 2.2.1(@types/react@18.2.14)(react@18.2.0)
tslib: 2.6.0
dev: false
/react-remove-scroll@2.5.5(@types/react@18.2.14)(react@18.2.0):
resolution: {integrity: sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==}
engines: {node: '>=10'}
peerDependencies:
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
react: ^16.8.0 || ^17.0.0 || ^18.0.0
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@types/react': 18.2.14
react: 18.2.0
react-remove-scroll-bar: 2.3.4(@types/react@18.2.14)(react@18.2.0)
react-style-singleton: 2.2.1(@types/react@18.2.14)(react@18.2.0)
tslib: 2.6.0
use-callback-ref: 1.3.0(@types/react@18.2.14)(react@18.2.0)
use-sidecar: 1.1.2(@types/react@18.2.14)(react@18.2.0)
dev: false
/react-router-dom@6.14.0(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-YEwlApKwzMMMbGbhh+Q7MsloTldcwMgHxUY/1g0uA62+B1hZo2jsybCWIDCL8zvIDB1FA0pBKY9chHbZHt+2dQ==}
engines: {node: '>=14'}
@ -4325,6 +4450,23 @@ packages:
engines: {node: '>=0.12.0'}
dev: false
/react-style-singleton@2.2.1(@types/react@18.2.14)(react@18.2.0):
resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==}
engines: {node: '>=10'}
peerDependencies:
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
react: ^16.8.0 || ^17.0.0 || ^18.0.0
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@types/react': 18.2.14
get-nonce: 1.0.1
invariant: 2.2.4
react: 18.2.0
tslib: 2.6.0
dev: false
/react-virtuoso@4.3.11(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-0YrCvQ5GsIKRcN34GxrzhSJGuMNI+hGxWci5cTVuPQ8QWTEsrKfCyqm7YNBMmV3pu7onG1YVUBo86CyCXdejXg==}
engines: {node: '>=10'}
@ -5117,6 +5259,37 @@ packages:
tlds: 1.240.0
dev: false
/use-callback-ref@1.3.0(@types/react@18.2.14)(react@18.2.0):
resolution: {integrity: sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==}
engines: {node: '>=10'}
peerDependencies:
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
react: ^16.8.0 || ^17.0.0 || ^18.0.0
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@types/react': 18.2.14
react: 18.2.0
tslib: 2.6.0
dev: false
/use-sidecar@1.1.2(@types/react@18.2.14)(react@18.2.0):
resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==}
engines: {node: '>=10'}
peerDependencies:
'@types/react': ^16.9.0 || ^17.0.0 || ^18.0.0
react: ^16.8.0 || ^17.0.0 || ^18.0.0
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@types/react': 18.2.14
detect-node-es: 1.1.0
react: 18.2.0
tslib: 2.6.0
dev: false
/use-sync-external-store@1.2.0(react@18.2.0):
resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
peerDependencies:

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>
);
}