feat(router): restructure

This commit is contained in:
reya 2023-12-14 13:22:03 +07:00
parent d9ab7893e0
commit 2fcc4dead1
13 changed files with 404 additions and 643 deletions

View File

@ -78,7 +78,7 @@
"react-dom": "^18.2.0",
"react-hook-form": "^7.49.0",
"react-hotkeys-hook": "^4.4.1",
"react-router-dom": "^6.20.1",
"react-router-dom": "^6.21.0",
"react-string-replace": "^1.1.1",
"sonner": "^1.2.4",
"tippy.js": "^6.3.7",

View File

@ -186,8 +186,8 @@ dependencies:
specifier: ^4.4.1
version: 4.4.1(react-dom@18.2.0)(react@18.2.0)
react-router-dom:
specifier: ^6.20.1
version: 6.20.1(react-dom@18.2.0)(react@18.2.0)
specifier: ^6.21.0
version: 6.21.0(react-dom@18.2.0)(react@18.2.0)
react-string-replace:
specifier: ^1.1.1
version: 1.1.1
@ -1774,8 +1774,8 @@ packages:
type-fest: 2.19.0
dev: false
/@remix-run/router@1.13.1:
resolution: {integrity: sha512-so+DHzZKsoOcoXrILB4rqDkMDy7NLMErRdOxvzvOKb507YINKUP4Di+shbTZDhSE/pBZ+vr7XGIpcOO0VLSA+Q==}
/@remix-run/router@1.14.0:
resolution: {integrity: sha512-WOHih+ClN7N8oHk9N4JUiMxQJmRVaOxcg8w7F/oHUXzJt920ekASLI/7cYX8XkntDWRhLZtsk6LbGrkgOAvi5A==}
engines: {node: '>=14.0.0'}
dev: false
@ -5272,26 +5272,26 @@ packages:
use-sidecar: 1.1.2(@types/react@18.2.43)(react@18.2.0)
dev: false
/react-router-dom@6.20.1(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-npzfPWcxfQN35psS7rJgi/EW0Gx6EsNjfdJSAk73U/HqMEJZ2k/8puxfwHFgDQhBGmS3+sjnGbMdMSV45axPQw==}
/react-router-dom@6.21.0(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-1dUdVj3cwc1npzJaf23gulB562ESNvxf7E4x8upNJycqyUm5BRRZ6dd3LrlzhtLaMrwOCO8R0zoiYxdaJx4LlQ==}
engines: {node: '>=14.0.0'}
peerDependencies:
react: '>=16.8'
react-dom: '>=16.8'
dependencies:
'@remix-run/router': 1.13.1
'@remix-run/router': 1.14.0
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
react-router: 6.20.1(react@18.2.0)
react-router: 6.21.0(react@18.2.0)
dev: false
/react-router@6.20.1(react@18.2.0):
resolution: {integrity: sha512-ccvLrB4QeT5DlaxSFFYi/KR8UMQ4fcD8zBcR71Zp1kaYTC5oJKYAp1cbavzGrogwxca+ubjkd7XjFZKBW8CxPA==}
/react-router@6.21.0(react@18.2.0):
resolution: {integrity: sha512-hGZ0HXbwz3zw52pLZV3j3+ec+m/PQ9cTpBvqjFQmy2XVUWGn5MD+31oXHb6dVTxYzmAeaiUBYjkoNz66n3RGCg==}
engines: {node: '>=14.0.0'}
peerDependencies:
react: '>=16.8'
dependencies:
'@remix-run/router': 1.13.1
'@remix-run/router': 1.14.0
react: 18.2.0
dev: false

View File

@ -1,24 +1,17 @@
import { fetch } from '@tauri-apps/plugin-http';
import { RouterProvider, createBrowserRouter, defer, redirect } from 'react-router-dom';
import { ChatsScreen } from '@app/chats';
import { ErrorScreen } from '@app/error';
import { useArk } from '@libs/ark';
import { LoaderIcon } from '@shared/icons';
import { AppLayout } from '@shared/layouts/app';
import { AuthLayout } from '@shared/layouts/auth';
import { NewLayout } from '@shared/layouts/new';
import { NoteLayout } from '@shared/layouts/note';
import { ComposerLayout } from '@shared/layouts/composer';
import { HomeLayout } from '@shared/layouts/home';
import { SettingsLayout } from '@shared/layouts/settings';
import './app.css';
export default function App() {
const ark = useArk();
const accountLoader = async () => {
if (!ark.account) return redirect('/auth/welcome');
return null;
};
const relayLoader = async ({ params }) => {
return defer({
relay: fetch(`https://${params.url}`, {
@ -32,249 +25,234 @@ export default function App() {
const router = createBrowserRouter([
{
path: '/',
element: <AppLayout />,
errorElement: <ErrorScreen />,
loader: accountLoader,
element: <AppLayout platform={ark.platform} />,
children: [
{
path: '',
async lazy() {
const { HomeScreen } = await import('@app/home');
return { Component: HomeScreen };
path: '/',
element: <HomeLayout />,
errorElement: <ErrorScreen />,
loader: async () => {
if (!ark.account) return redirect('auth/welcome');
return null;
},
children: [
{
index: true,
async lazy() {
const { HomeScreen } = await import('@app/home');
return { Component: HomeScreen };
},
},
{
path: 'nwc',
async lazy() {
const { NWCScreen } = await import('@app/nwc');
return { Component: NWCScreen };
},
},
{
path: 'relays',
async lazy() {
const { RelaysScreen } = await import('@app/relays');
return { Component: RelaysScreen };
},
},
{
path: 'relays/:url',
loader: relayLoader,
async lazy() {
const { RelayScreen } = await import('@app/relays/relay');
return { Component: RelayScreen };
},
},
{
path: 'users/:pubkey',
async lazy() {
const { UserScreen } = await import('@app/users');
return { Component: UserScreen };
},
},
{
path: 'events/:id',
async lazy() {
const { TextNoteScreen } = await import('@app/notes/text');
return { Component: TextNoteScreen };
},
},
{
path: 'new',
element: <ComposerLayout />,
children: [
{
index: true,
async lazy() {
const { NewPostScreen } = await import('@app/new/post');
return { Component: NewPostScreen };
},
},
{
path: 'article',
async lazy() {
const { NewArticleScreen } = await import('@app/new/article');
return { Component: NewArticleScreen };
},
},
{
path: 'file',
async lazy() {
const { NewFileScreen } = await import('@app/new/file');
return { Component: NewFileScreen };
},
},
{
path: 'privkey',
async lazy() {
const { NewPrivkeyScreen } = await import('@app/new/privkey');
return { Component: NewPrivkeyScreen };
},
},
],
},
],
},
{
path: 'users/:pubkey',
async lazy() {
const { UserScreen } = await import('@app/users');
return { Component: UserScreen };
},
},
{
path: 'nwc',
async lazy() {
const { NWCScreen } = await import('@app/nwc');
return { Component: NWCScreen };
},
},
{
path: 'relays',
async lazy() {
const { RelaysScreen } = await import('@app/relays');
return { Component: RelaysScreen };
},
},
{
path: 'relays/:url',
loader: relayLoader,
async lazy() {
const { RelayScreen } = await import('@app/relays/relay');
return { Component: RelayScreen };
},
},
{
path: 'chats',
element: <ChatsScreen />,
path: 'auth',
element: <AuthLayout />,
errorElement: <ErrorScreen />,
children: [
{
path: 'chat/:pubkey',
path: 'welcome',
async lazy() {
const { ChatScreen } = await import('@app/chats/chat');
return { Component: ChatScreen };
const { WelcomeScreen } = await import('@app/auth/welcome');
return { Component: WelcomeScreen };
},
},
{
path: 'create',
async lazy() {
const { CreateAccountScreen } = await import('@app/auth/create');
return { Component: CreateAccountScreen };
},
},
{
path: 'import',
async lazy() {
const { ImportAccountScreen } = await import('@app/auth/import');
return { Component: ImportAccountScreen };
},
},
{
path: 'onboarding',
async lazy() {
const { OnboardingScreen } = await import('@app/auth/onboarding');
return { Component: OnboardingScreen };
},
},
{
path: 'follow',
async lazy() {
const { FollowScreen } = await import('@app/auth/follow');
return { Component: FollowScreen };
},
},
{
path: 'finish',
async lazy() {
const { FinishScreen } = await import('@app/auth/finish');
return { Component: FinishScreen };
},
},
{
path: 'tutorials/note',
async lazy() {
const { TutorialNoteScreen } = await import('@app/auth/tutorials/note');
return { Component: TutorialNoteScreen };
},
},
{
path: 'tutorials/widget',
async lazy() {
const { TutorialWidgetScreen } = await import(
'@app/auth/tutorials/widget'
);
return { Component: TutorialWidgetScreen };
},
},
{
path: 'tutorials/posting',
async lazy() {
const { TutorialPostingScreen } = await import(
'@app/auth/tutorials/posting'
);
return { Component: TutorialPostingScreen };
},
},
{
path: 'tutorials/finish',
async lazy() {
const { TutorialFinishScreen } = await import(
'@app/auth/tutorials/finish'
);
return { Component: TutorialFinishScreen };
},
},
],
},
],
},
{
path: '/new',
element: <NewLayout />,
errorElement: <ErrorScreen />,
children: [
{
path: '',
async lazy() {
const { NewPostScreen } = await import('@app/new/post');
return { Component: NewPostScreen };
},
},
{
path: 'article',
async lazy() {
const { NewArticleScreen } = await import('@app/new/article');
return { Component: NewArticleScreen };
},
},
{
path: 'file',
async lazy() {
const { NewFileScreen } = await import('@app/new/file');
return { Component: NewFileScreen };
},
},
{
path: 'privkey',
async lazy() {
const { NewPrivkeyScreen } = await import('@app/new/privkey');
return { Component: NewPrivkeyScreen };
},
},
],
},
{
path: '/notes',
element: <NoteLayout />,
errorElement: <ErrorScreen />,
children: [
{
path: 'text/:id',
async lazy() {
const { TextNoteScreen } = await import('@app/notes/text');
return { Component: TextNoteScreen };
},
},
{
path: 'article/:id',
async lazy() {
const { ArticleNoteScreen } = await import('@app/notes/article');
return { Component: ArticleNoteScreen };
},
},
],
},
{
path: '/auth',
element: <AuthLayout />,
errorElement: <ErrorScreen />,
children: [
{
path: 'welcome',
async lazy() {
const { WelcomeScreen } = await import('@app/auth/welcome');
return { Component: WelcomeScreen };
},
},
{
path: 'create',
async lazy() {
const { CreateAccountScreen } = await import('@app/auth/create');
return { Component: CreateAccountScreen };
},
},
{
path: 'import',
async lazy() {
const { ImportAccountScreen } = await import('@app/auth/import');
return { Component: ImportAccountScreen };
},
},
{
path: 'onboarding',
async lazy() {
const { OnboardingScreen } = await import('@app/auth/onboarding');
return { Component: OnboardingScreen };
},
},
{
path: 'follow',
async lazy() {
const { FollowScreen } = await import('@app/auth/follow');
return { Component: FollowScreen };
},
},
{
path: 'finish',
async lazy() {
const { FinishScreen } = await import('@app/auth/finish');
return { Component: FinishScreen };
},
},
{
path: 'tutorials/note',
async lazy() {
const { TutorialNoteScreen } = await import('@app/auth/tutorials/note');
return { Component: TutorialNoteScreen };
},
},
{
path: 'tutorials/widget',
async lazy() {
const { TutorialWidgetScreen } = await import('@app/auth/tutorials/widget');
return { Component: TutorialWidgetScreen };
},
},
{
path: 'tutorials/posting',
async lazy() {
const { TutorialPostingScreen } = await import('@app/auth/tutorials/posting');
return { Component: TutorialPostingScreen };
},
},
{
path: 'tutorials/finish',
async lazy() {
const { TutorialFinishScreen } = await import('@app/auth/tutorials/finish');
return { Component: TutorialFinishScreen };
},
},
],
},
{
path: '/settings',
element: <SettingsLayout />,
errorElement: <ErrorScreen />,
children: [
{
path: '',
async lazy() {
const { UserSettingScreen } = await import('@app/settings');
return { Component: UserSettingScreen };
},
},
{
path: 'edit-profile',
async lazy() {
const { EditProfileScreen } = await import('@app/settings/editProfile');
return { Component: EditProfileScreen };
},
},
{
path: 'edit-contact',
async lazy() {
const { EditContactScreen } = await import('@app/settings/editContact');
return { Component: EditContactScreen };
},
},
{
path: 'general',
async lazy() {
const { GeneralSettingScreen } = await import('@app/settings/general');
return { Component: GeneralSettingScreen };
},
},
{
path: 'backup',
async lazy() {
const { BackupSettingScreen } = await import('@app/settings/backup');
return { Component: BackupSettingScreen };
},
},
{
path: 'advanced',
async lazy() {
const { AdvancedSettingScreen } = await import('@app/settings/advanced');
return { Component: AdvancedSettingScreen };
},
},
{
path: 'about',
async lazy() {
const { AboutScreen } = await import('@app/settings/about');
return { Component: AboutScreen };
},
path: 'settings',
element: <SettingsLayout />,
errorElement: <ErrorScreen />,
children: [
{
index: true,
async lazy() {
const { UserSettingScreen } = await import('@app/settings');
return { Component: UserSettingScreen };
},
},
{
path: 'edit-profile',
async lazy() {
const { EditProfileScreen } = await import('@app/settings/editProfile');
return { Component: EditProfileScreen };
},
},
{
path: 'edit-contact',
async lazy() {
const { EditContactScreen } = await import('@app/settings/editContact');
return { Component: EditContactScreen };
},
},
{
path: 'general',
async lazy() {
const { GeneralSettingScreen } = await import('@app/settings/general');
return { Component: GeneralSettingScreen };
},
},
{
path: 'backup',
async lazy() {
const { BackupSettingScreen } = await import('@app/settings/backup');
return { Component: BackupSettingScreen };
},
},
{
path: 'advanced',
async lazy() {
const { AdvancedSettingScreen } = await import('@app/settings/advanced');
return { Component: AdvancedSettingScreen };
},
},
{
path: 'about',
async lazy() {
const { AboutScreen } = await import('@app/settings/about');
return { Component: AboutScreen };
},
},
],
},
],
},

View File

@ -1,78 +1,6 @@
import { NDKKind } from '@nostr-dev-kit/ndk';
import { useQueryClient } from '@tanstack/react-query';
import { useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { useArk } from '@libs/ark';
import { LoaderIcon } from '@shared/icons';
import { FETCH_LIMIT } from '@utils/constants';
import { Link } from 'react-router-dom';
export function FinishScreen() {
const ark = useArk();
const [loading, setLoading] = useState(false);
const queryClient = useQueryClient();
const navigate = useNavigate();
const prefetch = async () => {
if (!ark.account.contacts.length) return navigate('/');
try {
setLoading(true);
// prefetch newsfeed
await queryClient.prefetchInfiniteQuery({
queryKey: ['newsfeed'],
initialPageParam: 0,
queryFn: async ({
signal,
pageParam,
}: {
signal: AbortSignal;
pageParam: number;
}) => {
return await ark.getInfiniteEvents({
filter: {
kinds: [NDKKind.Text, NDKKind.Repost],
authors: !ark.account.contacts.length
? [ark.account.pubkey]
: ark.account.contacts,
},
limit: FETCH_LIMIT,
pageParam,
signal,
});
},
});
// prefetch notification
await queryClient.prefetchInfiniteQuery({
queryKey: ['notification'],
initialPageParam: 0,
queryFn: async ({
signal,
pageParam,
}: {
signal: AbortSignal;
pageParam: number;
}) => {
return await ark.getInfiniteEvents({
filter: {
kinds: [NDKKind.Text, NDKKind.Repost, NDKKind.Reaction, NDKKind.Zap],
'#p': [ark.account.pubkey],
},
limit: FETCH_LIMIT,
pageParam,
signal,
});
},
});
navigate('/');
} catch (e) {
console.error(e);
}
};
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-10">
@ -89,13 +17,12 @@ export function FinishScreen() {
>
Start tutorial
</Link>
<button
type="button"
onClick={prefetch}
<Link
to="/"
className="inline-flex h-11 w-full items-center justify-center rounded-lg bg-neutral-100 font-medium hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800"
>
{loading ? <LoaderIcon className="h-4 w-4 animate-spin" /> : 'Skip'}
</button>
Skip
</Link>
<p className="text-center text-sm font-medium text-neutral-500 dark:text-neutral-600">
You need to restart app to make changes in previous step take effect or you
can continue with Lume default settings

View File

@ -1,78 +1,6 @@
import { NDKKind } from '@nostr-dev-kit/ndk';
import { useQueryClient } from '@tanstack/react-query';
import { useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { useArk } from '@libs/ark';
import { LoaderIcon } from '@shared/icons';
import { FETCH_LIMIT } from '@utils/constants';
import { Link } from 'react-router-dom';
export function TutorialFinishScreen() {
const ark = useArk();
const [loading, setLoading] = useState(false);
const queryClient = useQueryClient();
const navigate = useNavigate();
const prefetch = async () => {
if (!ark.account.contacts.length) return navigate('/');
try {
setLoading(true);
// prefetch newsfeed
await queryClient.prefetchInfiniteQuery({
queryKey: ['newsfeed'],
initialPageParam: 0,
queryFn: async ({
signal,
pageParam,
}: {
signal: AbortSignal;
pageParam: number;
}) => {
return await ark.getInfiniteEvents({
filter: {
kinds: [NDKKind.Text, NDKKind.Repost],
authors: !ark.account.contacts.length
? [ark.account.pubkey]
: ark.account.contacts,
},
limit: FETCH_LIMIT,
pageParam,
signal,
});
},
});
// prefetch notification
await queryClient.prefetchInfiniteQuery({
queryKey: ['notification'],
initialPageParam: 0,
queryFn: async ({
signal,
pageParam,
}: {
signal: AbortSignal;
pageParam: number;
}) => {
return await ark.getInfiniteEvents({
filter: {
kinds: [NDKKind.Text, NDKKind.Repost, NDKKind.Reaction, NDKKind.Zap],
'#p': [ark.account.pubkey],
},
limit: FETCH_LIMIT,
pageParam,
signal,
});
},
});
navigate('/');
} catch (e) {
console.error(e);
}
};
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-10">
@ -83,17 +11,12 @@ export function TutorialFinishScreen() {
</h1>
</div>
<div className="flex flex-col gap-2">
<button
type="button"
onClick={prefetch}
<Link
to="/"
className="inline-flex h-11 w-full items-center justify-center rounded-lg bg-blue-500 font-medium text-white hover:bg-blue-600"
>
{loading ? (
<LoaderIcon className="h-4 w-4 animate-spin" />
) : (
'Start using Lume'
)}
</button>
Start using Lume
</Link>
<Link
to="https://nostr.how/"
target="_blank"

View File

@ -4,6 +4,7 @@ import { createRoot } from 'react-dom/client';
import { Toaster } from 'sonner';
import { ArkProvider } from '@libs/ark/provider';
import App from './app';
import './app.css';
const queryClient = new QueryClient({
defaultOptions: {
@ -18,7 +19,7 @@ const root = createRoot(container);
root.render(
<QueryClientProvider client={queryClient}>
<ReactQueryDevtools initialIsOpen={false} />
<ReactQueryDevtools initialIsOpen={false} buttonPosition="top-right" />
<Toaster position="top-center" theme="system" closeButton />
<ArkProvider>
<App />

View File

@ -1,38 +1,24 @@
import { type Platform } from '@tauri-apps/plugin-os';
import { Outlet, ScrollRestoration } from 'react-router-dom';
import { twMerge } from 'tailwind-merge';
import { useArk } from '@libs/ark';
import { Navigation } from '@shared/navigation';
import { WindowTitleBar } from '@shared/titlebar';
export function AppLayout() {
const ark = useArk();
export function AppLayout({ platform }: { platform: Platform }) {
return (
<div
className={twMerge(
'flex h-screen w-screen flex-col',
ark.platform !== 'macos' ? 'bg-neutral-50 dark:bg-neutral-950' : ''
platform !== 'macos' ? 'bg-neutral-50 dark:bg-neutral-950' : ''
)}
>
{ark.platform !== 'macos' ? (
<WindowTitleBar platform={ark.platform} />
{platform !== 'macos' ? (
<WindowTitleBar platform={platform} />
) : (
<div data-tauri-drag-region className="h-9" />
<div data-tauri-drag-region className="h-9 shrink-0" />
)}
<div className="flex h-full min-h-0 w-full">
<div
data-tauri-drag-region
className={twMerge(
'h-full w-[64px] shrink-0',
ark.platform !== 'macos' ? 'pt-2' : 'pt-0'
)}
>
<Navigation />
</div>
<div className="min-h-0 flex-1 rounded-tl-lg 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-[inset_0_0_0.5px_1px_hsla(0,0%,100%,0.075),0_0_0_1px_hsla(0,0%,0%,0.05),0_0.3px_0.4px_hsla(0,0%,0%,0.02),0_0.9px_1.5px_hsla(0,0%,0%,0.045),0_3.5px_6px_hsla(0,0%,0%,0.09)]">
<Outlet />
<ScrollRestoration />
</div>
<div className="h-full w-full">
<Outlet />
<ScrollRestoration />
</div>
</div>
);

View File

@ -1,22 +1,10 @@
import { Outlet, ScrollRestoration } from 'react-router-dom';
import { useArk } from '@libs/ark';
import { WindowTitleBar } from '@shared/titlebar';
import { Outlet } from 'react-router-dom';
export function AuthLayout() {
const ark = useArk();
return (
<div className="flex h-screen w-screen flex-col">
{ark.platform !== 'macos' ? (
<WindowTitleBar platform={ark.platform} />
) : (
<div data-tauri-drag-region className="h-9" />
)}
<div className="h-full w-full px-2.5 pb-2.5 pt-1">
<div className="flex h-full min-h-0 w-full rounded-lg bg-white p-3 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-[inset_0_0_0.5px_1px_hsla(0,0%,100%,0.075),0_0_0_1px_hsla(0,0%,0%,0.05),0_0.3px_0.4px_hsla(0,0%,0%,0.02),0_0.9px_1.5px_hsla(0,0%,0%,0.045),0_3.5px_6px_hsla(0,0%,0%,0.09)]">
<Outlet />
<ScrollRestoration />
</div>
<div className="h-full w-full px-2.5 pb-2.5 pt-1">
<div className="flex h-full min-h-0 w-full rounded-lg bg-white p-3 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-[inset_0_0_0.5px_1px_hsla(0,0%,100%,0.075),0_0_0_1px_hsla(0,0%,0%,0.05),0_0.3px_0.4px_hsla(0,0%,0%,0.02),0_0.9px_1.5px_hsla(0,0%,0%,0.045),0_3.5px_6px_hsla(0,0%,0%,0.09)]">
<Outlet />
</div>
</div>
);

View File

@ -0,0 +1,52 @@
import { NavLink, Outlet, useLocation } from 'react-router-dom';
import { twMerge } from 'tailwind-merge';
export function ComposerLayout() {
const location = useLocation();
return (
<div className="container mx-auto h-full px-8 pt-8">
<div className="mb-8 flex h-10 shrink-0 items-center gap-3">
{location.pathname !== '/new/privkey' ? (
<div className="flex h-10 items-center gap-2 rounded-lg bg-neutral-100 px-0.5 dark:bg-neutral-800">
<NavLink
to="/new/"
end
className={({ isActive }) =>
twMerge(
'inline-flex h-9 w-20 items-center justify-center rounded-lg text-sm font-medium',
isActive ? 'bg-white shadow dark:bg-black' : 'bg-transparent'
)
}
>
Post
</NavLink>
<NavLink
to="/new/article"
className={({ isActive }) =>
twMerge(
'inline-flex h-9 w-20 items-center justify-center rounded-lg text-sm font-medium',
isActive ? 'bg-white shadow dark:bg-black' : 'bg-transparent'
)
}
>
Article
</NavLink>
<NavLink
to="/new/file"
className={({ isActive }) =>
twMerge(
'inline-flex h-9 w-28 items-center justify-center rounded-lg text-sm font-medium',
isActive ? 'bg-white shadow dark:bg-black' : 'bg-transparent'
)
}
>
File Sharing
</NavLink>
</div>
) : null}
</div>
<Outlet />
</div>
);
}

View File

@ -0,0 +1,13 @@
import { Outlet } from 'react-router-dom';
import { Navigation } from '@shared/navigation';
export function HomeLayout() {
return (
<div className="flex h-full w-full">
<Navigation />
<div className="min-h-0 flex-1 rounded-tl-lg 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-[inset_0_0_0.5px_1px_hsla(0,0%,100%,0.075),0_0_0_1px_hsla(0,0%,0%,0.05),0_0.3px_0.4px_hsla(0,0%,0%,0.02),0_0.9px_1.5px_hsla(0,0%,0%,0.045),0_3.5px_6px_hsla(0,0%,0%,0.09)]">
<Outlet />
</div>
</div>
);
}

View File

@ -1,75 +0,0 @@
import { Link, NavLink, Outlet, useLocation } from 'react-router-dom';
import { twMerge } from 'tailwind-merge';
import { useArk } from '@libs/ark';
import { ArrowLeftIcon } from '@shared/icons';
import { WindowTitleBar } from '@shared/titlebar';
export function NewLayout() {
const ark = useArk();
const location = useLocation();
return (
<div className="flex h-screen w-screen flex-col bg-neutral-50 dark:bg-neutral-950">
{ark.platform !== 'macos' ? (
<WindowTitleBar platform={ark.platform} />
) : (
<div data-tauri-drag-region className="h-9 shrink-0" />
)}
<div data-tauri-drag-region className="h-4 shrink-0" />
<div className="container mx-auto grid flex-1 grid-cols-8 px-4">
<div className="col-span-1">
<Link
to="/"
className="inline-flex h-10 w-10 items-center justify-center rounded-lg bg-neutral-100 hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800"
>
<ArrowLeftIcon className="h-5 w-5" />
</Link>
</div>
<div className="col-span-6 flex flex-col">
<div className="mb-8 flex h-10 shrink-0 items-center gap-3">
{location.pathname !== '/new/privkey' ? (
<div className="flex h-10 items-center gap-2 rounded-lg bg-neutral-100 px-0.5 dark:bg-neutral-800">
<NavLink
to="/new/"
end
className={({ isActive }) =>
twMerge(
'inline-flex h-9 w-20 items-center justify-center rounded-lg text-sm font-medium',
isActive ? 'bg-white shadow dark:bg-black' : 'bg-transparent'
)
}
>
Post
</NavLink>
<NavLink
to="/new/article"
className={({ isActive }) =>
twMerge(
'inline-flex h-9 w-20 items-center justify-center rounded-lg text-sm font-medium',
isActive ? 'bg-white shadow dark:bg-black' : 'bg-transparent'
)
}
>
Article
</NavLink>
<NavLink
to="/new/file"
className={({ isActive }) =>
twMerge(
'inline-flex h-9 w-28 items-center justify-center rounded-lg text-sm font-medium',
isActive ? 'bg-white shadow dark:bg-black' : 'bg-transparent'
)
}
>
File Sharing
</NavLink>
</div>
) : null}
</div>
<Outlet />
</div>
<div className="col-span-1" />
</div>
</div>
);
}

View File

@ -1,21 +0,0 @@
import { Outlet, ScrollRestoration } from 'react-router-dom';
import { useArk } from '@libs/ark';
import { WindowTitleBar } from '@shared/titlebar';
export function NoteLayout() {
const ark = useArk();
return (
<div className="flex h-screen w-screen flex-col bg-neutral-50 dark:bg-neutral-950">
{ark.platform !== 'macos' ? (
<WindowTitleBar platform={ark.platform} />
) : (
<div data-tauri-drag-region className="h-9" />
)}
<div className="flex h-full min-h-0 w-full">
<Outlet />
<ScrollRestoration />
</div>
</div>
);
}

View File

@ -1,6 +1,5 @@
import { NavLink, Outlet, ScrollRestoration, useNavigate } from 'react-router-dom';
import { NavLink, Outlet, useNavigate } from 'react-router-dom';
import { twMerge } from 'tailwind-merge';
import { useArk } from '@libs/ark';
import {
AdvancedSettingsIcon,
ArrowLeftIcon,
@ -9,108 +8,98 @@ import {
SettingsIcon,
UserIcon,
} from '@shared/icons';
import { WindowTitleBar } from '@shared/titlebar';
export function SettingsLayout() {
const ark = useArk();
const navigate = useNavigate();
return (
<div className="flex h-screen w-screen flex-col bg-neutral-50 dark:bg-neutral-950">
{ark.platform !== 'macos' ? (
<WindowTitleBar platform={ark.platform} />
) : (
<div data-tauri-drag-region className="h-9" />
)}
<div className="flex h-full min-h-0 w-full flex-col gap-8 overflow-y-auto pb-10">
<div className="flex h-20 w-full items-center justify-between border-b border-neutral-200 px-2 pb-2 dark:border-neutral-900">
<div>
<button
type="button"
onClick={() => navigate(-1)}
className="inline-flex h-12 w-12 items-center justify-center rounded-xl bg-neutral-100 dark:bg-neutral-900"
>
<ArrowLeftIcon className="h-5 w-5" />
</button>
</div>
<div className="flex items-center gap-0.5">
<NavLink
to="/settings/"
end
className={({ isActive }) =>
twMerge(
'flex w-20 shrink-0 flex-col items-center justify-center rounded-lg px-2 py-2 text-neutral-700 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-900',
isActive
? 'bg-neutral-100 text-blue-500 hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800'
: ''
)
}
>
<UserIcon className="h-6 w-6" />
<p className="text-sm font-medium">User</p>
</NavLink>
<NavLink
to="/settings/general"
className={({ isActive }) =>
twMerge(
'flex w-20 shrink-0 flex-col items-center justify-center rounded-lg px-2 py-2 text-neutral-700 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-900',
isActive
? 'bg-neutral-100 text-blue-500 hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800'
: ''
)
}
>
<SettingsIcon className="h-6 w-6" />
<p className="text-sm font-medium">General</p>
</NavLink>
<NavLink
to="/settings/backup"
className={({ isActive }) =>
twMerge(
'flex w-20 shrink-0 flex-col items-center justify-center rounded-lg px-2 py-2 text-neutral-700 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-900',
isActive
? 'bg-neutral-100 text-blue-500 hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800'
: ''
)
}
>
<SecureIcon className="h-6 w-6" />
<p className="text-sm font-medium">Backup</p>
</NavLink>
<NavLink
to="/settings/advanced"
className={({ isActive }) =>
twMerge(
'flex w-20 shrink-0 flex-col items-center justify-center rounded-lg px-2 py-2 text-neutral-700 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-900',
isActive
? 'bg-neutral-100 text-blue-500 hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800'
: ''
)
}
>
<AdvancedSettingsIcon className="h-6 w-6" />
<p className="text-sm font-medium">Advanced</p>
</NavLink>
<NavLink
to="/settings/about"
className={({ isActive }) =>
twMerge(
'flex w-20 shrink-0 flex-col items-center justify-center rounded-lg px-2 py-2 text-neutral-700 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-900',
isActive
? 'bg-neutral-100 text-blue-500 hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800'
: ''
)
}
>
<InfoIcon className="h-6 w-6" />
<p className="text-sm font-medium">About</p>
</NavLink>
</div>
<div />
<div className="flex h-full min-h-0 w-full flex-col gap-8 overflow-y-auto">
<div className="flex h-20 w-full items-center justify-between border-b border-neutral-200 px-2 pb-2 dark:border-neutral-900">
<div>
<button
type="button"
onClick={() => navigate(-1)}
className="inline-flex h-12 w-12 items-center justify-center rounded-xl"
>
<ArrowLeftIcon className="h-5 w-5" />
</button>
</div>
<Outlet />
<ScrollRestoration />
<div className="flex items-center gap-0.5">
<NavLink
to="/settings/"
end
className={({ isActive }) =>
twMerge(
'flex w-20 shrink-0 flex-col items-center justify-center rounded-lg px-2 py-2 text-neutral-700 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-900',
isActive
? 'bg-neutral-50 text-blue-500 hover:bg-neutral-100 dark:bg-neutral-950 dark:hover:bg-neutral-900'
: ''
)
}
>
<UserIcon className="h-6 w-6" />
<p className="text-sm font-medium">User</p>
</NavLink>
<NavLink
to="/settings/general"
className={({ isActive }) =>
twMerge(
'flex w-20 shrink-0 flex-col items-center justify-center rounded-lg px-2 py-2 text-neutral-700 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-900',
isActive
? 'bg-neutral-50 text-blue-500 hover:bg-neutral-100 dark:bg-neutral-950 dark:hover:bg-neutral-900'
: ''
)
}
>
<SettingsIcon className="h-6 w-6" />
<p className="text-sm font-medium">General</p>
</NavLink>
<NavLink
to="/settings/backup"
className={({ isActive }) =>
twMerge(
'flex w-20 shrink-0 flex-col items-center justify-center rounded-lg px-2 py-2 text-neutral-700 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-900',
isActive
? 'bg-neutral-50 text-blue-500 hover:bg-neutral-100 dark:bg-neutral-950 dark:hover:bg-neutral-900'
: ''
)
}
>
<SecureIcon className="h-6 w-6" />
<p className="text-sm font-medium">Backup</p>
</NavLink>
<NavLink
to="/settings/advanced"
className={({ isActive }) =>
twMerge(
'flex w-20 shrink-0 flex-col items-center justify-center rounded-lg px-2 py-2 text-neutral-700 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-900',
isActive
? 'bg-neutral-50 text-blue-500 hover:bg-neutral-100 dark:bg-neutral-950 dark:hover:bg-neutral-900'
: ''
)
}
>
<AdvancedSettingsIcon className="h-6 w-6" />
<p className="text-sm font-medium">Advanced</p>
</NavLink>
<NavLink
to="/settings/about"
className={({ isActive }) =>
twMerge(
'flex w-20 shrink-0 flex-col items-center justify-center rounded-lg px-2 py-2 text-neutral-700 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-900',
isActive
? 'bg-neutral-50 text-blue-500 hover:bg-neutral-100 dark:bg-neutral-950 dark:hover:bg-neutral-900'
: ''
)
}
>
<InfoIcon className="h-6 w-6" />
<p className="text-sm font-medium">About</p>
</NavLink>
</div>
<div />
</div>
<Outlet />
</div>
);
}