feat(depot): update onboarding screen

This commit is contained in:
2023-12-19 15:43:32 +07:00
parent d9e8d05db7
commit a6ca2589ab
13 changed files with 397 additions and 579 deletions

View File

@@ -1,5 +1,4 @@
import { fetch } from '@tauri-apps/plugin-http';
import { nip19 } from 'nostr-tools';
import { RouterProvider, createBrowserRouter, defer, redirect } from 'react-router-dom';
import { ErrorScreen } from '@app/error';
import { useArk } from '@libs/ark';
@@ -13,17 +12,6 @@ import { SettingsLayout } from '@shared/layouts/settings';
export default function App() {
const ark = useArk();
const relayLoader = async ({ params }) => {
return defer({
relay: fetch(`https://${params.url}`, {
method: 'GET',
headers: {
Accept: 'application/nostr+json',
},
}).then((res) => res.json()),
});
};
const router = createBrowserRouter([
{
element: <AppLayout platform={ark.platform} />,
@@ -44,21 +32,6 @@ export default function App() {
return { Component: HomeScreen };
},
},
{
path: ':address',
loader: ({ params }) => {
const address = params.address;
const decode = nip19.decode(address);
if (decode.type === 'npub') return redirect(`/users/${decode.data}`);
if (decode.type === 'nprofile')
return redirect(`/users/${decode.data.pubkey}`);
if (decode.type === 'note') return redirect(`/events/${decode.data}`);
if (decode.type === 'nrelay') return redirect(`/relays/${decode.data}`);
if (decode.type === 'nevent')
return redirect(`/relays/${decode.data.id}`);
return null;
},
},
{
path: 'nwc',
async lazy() {
@@ -75,7 +48,16 @@ export default function App() {
},
{
path: 'relays/:url',
loader: relayLoader,
loader: async ({ params }) => {
return defer({
relay: fetch(`https://${params.url}`, {
method: 'GET',
headers: {
Accept: 'application/nostr+json',
},
}).then((res) => res.json()),
});
},
async lazy() {
const { RelayScreen } = await import('@app/relays/relay');
return { Component: RelayScreen };
@@ -83,10 +65,29 @@ export default function App() {
},
{
path: 'depot',
async lazy() {
const { DepotScreen } = await import('@app/depot');
return { Component: DepotScreen };
},
children: [
{
index: true,
loader: () => {
const depot = ark.checkDepot();
if (!depot) return redirect('/depot/onboarding/');
return null;
},
async lazy() {
const { DepotScreen } = await import('@app/depot');
return { Component: DepotScreen };
},
},
{
path: 'onboarding',
async lazy() {
const { DepotOnboardingScreen } = await import(
'@app/depot/onboarding'
);
return { Component: DepotOnboardingScreen };
},
},
],
},
{
path: 'new',

View File

@@ -73,7 +73,7 @@ export function CreateAccountScreen() {
await ark.createEvent({
kind: NDKKind.RelayList,
tags: [ark.relays],
tags: ark.relays.map((item) => ['r', item, '']),
});
setKeys({ npub: userNpub, nsec: userNsec });

View File

@@ -1,149 +1,96 @@
import { useEffect, useState } from 'react';
import { toast } from 'sonner';
import { useArk } from '@libs/ark';
import { LoaderIcon } from '@shared/icons';
import { delay } from '@utils/delay';
export function DepotScreen() {
const ark = useArk();
const [status, setStatus] = useState(false);
const [loading, setLoading] = useState(false);
const launch = async () => {
try {
setLoading(true);
await ark.launchDepot();
await ark.createSetting('depot', '1');
await delay(2000); // delay 2s to make sure depot is running
// default depot url: ws://localhost:6090
// #TODO: user can custom depot url
const connect = await ark.connectDepot();
if (connect) {
setStatus(true);
setLoading(false);
}
} catch (e) {
toast.error(e);
}
};
useEffect(() => {
const depotStatus = ark.checkDepot();
setStatus(depotStatus);
}, []);
return (
<div className="flex h-full w-full flex-col items-center justify-center">
{!status ? (
<div className="flex flex-col items-center gap-4">
<h1 className="mb-1 text-2xl font-semibold text-neutral-400 dark:text-neutral-600">
<span>Deploy Nostr Relay inside Lume</span>{' '}
<span className="text-neutral-900 dark:text-neutral-100">with Depot.</span>
</h1>
<button
type="button"
onClick={() => launch()}
className="inline-flex h-11 w-24 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" /> : 'Launch'}
</button>
</div>
) : (
<div className="mx-auto w-full max-w-md">
<div className="flex flex-col gap-10">
<div className="text-center">
<h1 className="mb-1 text-2xl font-semibold text-neutral-400 dark:text-neutral-600">
Your Depot is running
</h1>
<div className="flex items-center justify-center gap-2.5">
<span className="relative flex h-3 w-3">
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-teal-400 opacity-75"></span>
<span className="relative inline-flex h-3 w-3 rounded-full bg-teal-500"></span>
</span>
<p className="font-medium">ws://localhost:6090</p>
</div>
<div className="px-16 py-14">
<div className="mx-auto w-full max-w-md">
<div className="flex flex-col gap-10">
<div className="text-center">
<h1 className="mb-1 text-2xl font-semibold text-neutral-400 dark:text-neutral-600">
Your Depot is running
</h1>
<div className="flex items-center justify-center gap-2.5">
<span className="relative flex h-3 w-3">
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-teal-400 opacity-75"></span>
<span className="relative inline-flex h-3 w-3 rounded-full bg-teal-500"></span>
</span>
<p className="font-medium">ws://localhost:6090</p>
</div>
<div className="flex flex-col gap-6">
<div className="flex items-center justify-between gap-6">
<div>
<h3 className="text-lg font-semibold">Backup</h3>
<p className="text-sm text-neutral-600 dark:text-neutral-400">
Sync all your data to Depot.
</p>
</div>
<button
type="button"
className="inline-flex h-9 w-20 shrink-0 items-center justify-center rounded-lg bg-neutral-200 font-medium hover:bg-blue-500 hover:text-white dark:bg-neutral-800"
>
Sync
</button>
</div>
<div className="flex flex-col gap-6">
<div className="flex items-center justify-between gap-6">
<div>
<h3 className="text-lg font-semibold">Backup</h3>
<p className="text-sm text-neutral-600 dark:text-neutral-400">
Sync all your data to Depot.
</p>
</div>
<div className="flex items-center justify-between gap-6">
<div>
<h3 className="text-lg font-semibold">Expose</h3>
<p className="text-sm text-neutral-600 dark:text-neutral-400">
Help other users can see your depot on Internet. You also can do it by
yourself by using other service like ngrok or localtunnel.
</p>
</div>
<button
type="button"
className="inline-flex h-9 w-20 shrink-0 items-center justify-center rounded-lg bg-neutral-200 font-medium hover:bg-blue-500 hover:text-white dark:bg-neutral-800"
>
Start
</button>
<button
type="button"
className="inline-flex h-9 w-20 shrink-0 items-center justify-center rounded-lg bg-neutral-200 font-medium hover:bg-blue-500 hover:text-white dark:bg-neutral-800"
>
Sync
</button>
</div>
<div className="flex items-center justify-between gap-6">
<div>
<h3 className="text-lg font-semibold">Expose</h3>
<p className="text-sm text-neutral-600 dark:text-neutral-400">
Help other users can see your depot on Internet. You also can do it by
yourself by using other service like ngrok or localtunnel.
</p>
</div>
<div className="flex items-center justify-between gap-6">
<div>
<h3 className="text-lg font-semibold">Relay Hint</h3>
<p className="text-sm text-neutral-600 dark:text-neutral-400">
Instruct other Nostr client find your events in this depot.
</p>
</div>
<button
type="button"
className="inline-flex h-9 w-20 shrink-0 items-center justify-center rounded-lg bg-neutral-200 font-medium hover:bg-blue-500 hover:text-white dark:bg-neutral-800"
>
Add
</button>
<button
type="button"
className="inline-flex h-9 w-20 shrink-0 items-center justify-center rounded-lg bg-neutral-200 font-medium hover:bg-blue-500 hover:text-white dark:bg-neutral-800"
>
Start
</button>
</div>
<div className="flex items-center justify-between gap-6">
<div>
<h3 className="text-lg font-semibold">Relay Hint</h3>
<p className="text-sm text-neutral-600 dark:text-neutral-400">
Instruct other Nostr client find your events in this depot.
</p>
</div>
<div className="flex items-center justify-between gap-6">
<div>
<h3 className="text-lg font-semibold">Invite</h3>
<p className="text-sm text-neutral-600 dark:text-neutral-400">
By default, only you can write event to Depot, but you can invite
other user to your Depot.
</p>
</div>
<button
type="button"
className="inline-flex h-9 w-20 shrink-0 items-center justify-center rounded-lg bg-neutral-200 font-medium hover:bg-blue-500 hover:text-white dark:bg-neutral-800"
>
Invite
</button>
<button
type="button"
className="inline-flex h-9 w-20 shrink-0 items-center justify-center rounded-lg bg-neutral-200 font-medium hover:bg-blue-500 hover:text-white dark:bg-neutral-800"
>
Add
</button>
</div>
<div className="flex items-center justify-between gap-6">
<div>
<h3 className="text-lg font-semibold">Invite</h3>
<p className="text-sm text-neutral-600 dark:text-neutral-400">
By default, only you can write event to Depot, but you can invite other
user to your Depot.
</p>
</div>
<div className="flex items-center justify-between gap-6">
<div>
<h3 className="text-lg font-semibold">Customize</h3>
<p className="text-sm text-neutral-600 dark:text-neutral-400">
Depot also provide plenty config to customize your experiences.
</p>
</div>
<button
type="button"
className="inline-flex h-9 w-20 shrink-0 items-center justify-center rounded-lg bg-neutral-200 font-medium hover:bg-blue-500 hover:text-white dark:bg-neutral-800"
>
Config
</button>
<button
type="button"
className="inline-flex h-9 w-20 shrink-0 items-center justify-center rounded-lg bg-neutral-200 font-medium hover:bg-blue-500 hover:text-white dark:bg-neutral-800"
>
Invite
</button>
</div>
<div className="flex items-center justify-between gap-6">
<div>
<h3 className="text-lg font-semibold">Customize</h3>
<p className="text-sm text-neutral-600 dark:text-neutral-400">
Depot also provide plenty config to customize your experiences.
</p>
</div>
<button
type="button"
className="inline-flex h-9 w-20 shrink-0 items-center justify-center rounded-lg bg-neutral-200 font-medium hover:bg-blue-500 hover:text-white dark:bg-neutral-800"
>
Config
</button>
</div>
</div>
</div>
)}
</div>
</div>
);
}

View File

@@ -0,0 +1,95 @@
import { resolveResource } from '@tauri-apps/api/path';
import { readTextFile, writeTextFile } from '@tauri-apps/plugin-fs';
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { parse, stringify } from 'smol-toml';
import { toast } from 'sonner';
import { useArk } from '@libs/ark';
import { LoaderIcon } from '@shared/icons';
import { delay } from '@utils/delay';
export function DepotOnboardingScreen() {
const ark = useArk();
const navigate = useNavigate();
const [loading, setLoading] = useState(false);
const launchDepot = async () => {
try {
setLoading(true);
// get default config
const defaultConfig = await resolveResource('resources/config.toml');
const config = await readTextFile(defaultConfig);
const parsedConfig = parse(config);
// add current user to whitelist
parsedConfig.authorization['pubkey_whitelist'].push(ark.account.pubkey);
// update new config
const newConfig = stringify(parsedConfig);
await writeTextFile(defaultConfig, newConfig);
// launch depot
await ark.launchDepot();
await ark.createSetting('depot', '1');
await delay(2000); // delay 2s to make sure depot is running
// default depot url: ws://localhost:6090
// #TODO: user can custom depot url
const connect = await ark.connectDepot();
if (connect) {
toast.success('Your Depot is successfully launch.');
setLoading(false);
navigate('/depot/');
}
} catch (e) {
toast.error(e);
}
};
return (
<div className="flex h-full w-full flex-col items-center justify-center gap-10">
<div className="flex flex-col items-center gap-8">
<div className="text-center">
<h1 className="mb-1 text-3xl font-semibold text-neutral-400 dark:text-neutral-600">
Run your Personal Nostr Relay inside Lume
</h1>
<h2 className="text-4xl font-semibold">Your Relay, Your Control.</h2>
</div>
<div className="rounded-xl bg-blue-100 p-1.5 dark:bg-blue-900">
<button
type="button"
onClick={launchDepot}
className="inline-flex h-11 w-36 transform items-center justify-center gap-2 rounded-lg bg-blue-500 font-medium text-white active:translate-y-1"
>
{loading ? (
<>
<LoaderIcon className="h-5 w-5 animate-spin" />
Launching...
</>
) : (
<>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
className="h-5 w-5"
>
<path
fillRule="evenodd"
d="M12 2.25a.75.75 0 0 1 .75.75v9a.75.75 0 0 1-1.5 0V3a.75.75 0 0 1 .75-.75ZM6.166 5.106a.75.75 0 0 1 0 1.06 8.25 8.25 0 1 0 11.668 0 .75.75 0 1 1 1.06-1.06c3.808 3.807 3.808 9.98 0 13.788-3.807 3.808-9.98 3.808-13.788 0-3.808-3.807-3.808-9.98 0-13.788a.75.75 0 0 1 1.06 0Z"
clipRule="evenodd"
/>
</svg>
Launch
</>
)}
</button>
</div>
</div>
</div>
);
}

View File

@@ -121,7 +121,7 @@ export function NewPostScreen() {
}, []);
return (
<div className="flex flex-1 flex-col gap-4">
<div className="flex h-[500px] flex-1 flex-col gap-4">
<div className="flex-1 overflow-y-auto">
<div ref={containerRef} style={{ height: `${height}px` }}>
<EditorContent

View File

@@ -11,7 +11,7 @@ import NDK, {
NostrEvent,
} from '@nostr-dev-kit/ndk';
import { ndkAdapter } from '@nostr-fetch/adapter-ndk';
import { configDir, resolveResource } from '@tauri-apps/api/path';
import { appConfigDir, resolveResource } from '@tauri-apps/api/path';
import { invoke } from '@tauri-apps/api/primitives';
import { open } from '@tauri-apps/plugin-dialog';
import { readBinaryFile } from '@tauri-apps/plugin-fs';
@@ -28,12 +28,12 @@ import {
import { nip19 } from 'nostr-tools';
import { NDKCacheAdapterTauri } from '@libs/cache';
import {
Account,
NDKCacheUser,
NDKCacheUserProfile,
NDKEventWithReplies,
NIP05,
Widget,
type Account,
type NDKCacheUser,
type NDKCacheUserProfile,
type NDKEventWithReplies,
type NIP05,
type WidgetProps,
} from '@utils/types';
export class Ark {
@@ -69,7 +69,7 @@ export class Ark {
public async launchDepot() {
const configPath = await resolveResource('resources/config.toml');
const dataPath = await configDir();
const dataPath = await appConfigDir();
const command = Command.sidecar('bin/depot', ['-c', configPath, '-d', dataPath]);
this.#depot = await command.spawn();
@@ -77,11 +77,29 @@ export class Ark {
public async connectDepot() {
if (!this.#depot) return;
return this.ndk.addExplicitRelay(
new NDKRelay('ws://localhost:6090'),
undefined,
true
);
// connect
this.ndk.addExplicitRelay(new NDKRelay('ws://localhost:6090'), undefined, true);
const relayEvent = await this.ndk.fetchEvent({
kinds: [NDKKind.RelayList],
authors: [this.account.pubkey],
});
if (!relayEvent) {
// create new relay list
return await this.createEvent({
kind: NDKKind.RelayList,
tags: [['r', 'ws://localhost:6090', '']],
});
}
// update old relay list
relayEvent.tags.push(['r', 'ws://localhost:6090', '']);
return await this.createEvent({
kind: NDKKind.RelayList,
tags: relayEvent.tags,
});
}
public checkDepot() {
@@ -325,7 +343,7 @@ export class Ark {
}
public async getWidgets() {
const widgets: Array<Widget> = await this.#storage.select(
const widgets: Array<WidgetProps> = await this.#storage.select(
'SELECT * FROM widgets WHERE account_id = $1 ORDER BY created_at DESC;',
[this.account.id]
);
@@ -339,7 +357,7 @@ export class Ark {
);
if (insert) {
const widgets: Array<Widget> = await this.#storage.select(
const widgets: Array<WidgetProps> = await this.#storage.select(
'SELECT * FROM widgets ORDER BY id DESC LIMIT 1;'
);
if (widgets.length < 1) console.error('get created widget failed');

View File

@@ -18,7 +18,7 @@ export function ActiveAccount() {
encodeURIComponent(minidenticon(ark.account.pubkey, 90, 50));
return (
<div className="flex flex-col gap-1 rounded-lg bg-black/10 p-1 ring-1 ring-transparent hover:bg-black/20 hover:ring-blue-500 dark:bg-white/10 dark:hover:bg-white/20">
<div className="flex flex-col gap-1 rounded-xl bg-black/10 p-1 ring-1 ring-transparent hover:bg-black/20 hover:ring-blue-500 dark:bg-white/10 dark:hover:bg-white/20">
<Link to="/settings/" className="relative inline-block">
<Avatar.Root>
<Avatar.Image
@@ -27,13 +27,13 @@ export function ActiveAccount() {
loading="lazy"
decoding="async"
style={{ contentVisibility: 'auto' }}
className="aspect-square h-auto w-full rounded-md object-cover"
className="aspect-square h-auto w-full rounded-lg object-cover"
/>
<Avatar.Fallback delayMs={150}>
<img
src={svgURI}
alt={ark.account.pubkey}
className="aspect-square h-auto w-full rounded-md bg-black dark:bg-white"
className="aspect-square h-auto w-full rounded-lg bg-black dark:bg-white"
/>
</Avatar.Fallback>
</Avatar.Root>

View File

@@ -4,9 +4,7 @@ import { Navigation } from '@shared/navigation';
export function HomeLayout() {
return (
<div className="flex h-full w-full">
<div className="w-[68px] shrink-0">
<Navigation />
</div>
<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>

View File

@@ -12,7 +12,7 @@ import {
export function Navigation() {
return (
<div className="flex h-full w-full flex-col justify-between p-3">
<div className="flex h-full w-20 shrink-0 flex-col justify-between px-4 py-3">
<div className="flex flex-1 flex-col gap-5">
<NavLink
to="/"
@@ -33,8 +33,10 @@ export function Navigation() {
</div>
<div
className={twMerge(
'text-sm text-black dark:text-white',
isActive ? 'font-semibold' : 'font-medium'
'text-sm',
isActive
? 'font-semibold text-black dark:text-white'
: 'font-medium text-black/50 dark:text-white/50'
)}
>
Home
@@ -61,8 +63,10 @@ export function Navigation() {
</div>
<div
className={twMerge(
'text-sm text-black dark:text-white',
isActive ? 'font-semibold' : 'font-medium'
'text-sm',
isActive
? 'font-semibold text-black dark:text-white'
: 'font-medium text-black/50 dark:text-white/50'
)}
>
Relays
@@ -89,8 +93,10 @@ export function Navigation() {
</div>
<div
className={twMerge(
'text-sm text-black dark:text-white',
isActive ? 'font-semibold' : 'font-medium'
'text-sm',
isActive
? 'font-semibold text-black dark:text-white'
: 'font-medium text-black/50 dark:text-white/50'
)}
>
Depot
@@ -117,8 +123,10 @@ export function Navigation() {
</div>
<div
className={twMerge(
'text-sm text-black dark:text-white',
isActive ? 'font-semibold' : 'font-medium'
'text-sm',
isActive
? 'font-semibold text-black dark:text-white'
: 'font-medium text-black/50 dark:text-white/50'
)}
>
Wallet