mirror of
https://github.com/luminous-devs/lume.git
synced 2024-09-19 11:43:30 +00:00
add complete screen
This commit is contained in:
parent
d3db6492d9
commit
c6a0636e8c
@ -18,7 +18,6 @@
|
|||||||
"**/*.{ts, tsx, css, md, html, json}": "prettier --cache --write"
|
"**/*.{ts, tsx, css, md, html, json}": "prettier --cache --write"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ctrl/magnet-link": "^3.1.2",
|
|
||||||
"@getalby/sdk": "^2.4.0",
|
"@getalby/sdk": "^2.4.0",
|
||||||
"@nostr-dev-kit/ndk": "^1.0.0",
|
"@nostr-dev-kit/ndk": "^1.0.0",
|
||||||
"@nostr-fetch/adapter-ndk": "^0.12.2",
|
"@nostr-fetch/adapter-ndk": "^0.12.2",
|
||||||
@ -38,10 +37,8 @@
|
|||||||
"@tiptap/react": "^2.1.8",
|
"@tiptap/react": "^2.1.8",
|
||||||
"@tiptap/starter-kit": "^2.1.8",
|
"@tiptap/starter-kit": "^2.1.8",
|
||||||
"@tiptap/suggestion": "^2.1.8",
|
"@tiptap/suggestion": "^2.1.8",
|
||||||
"@void-cat/api": "^1.0.7",
|
|
||||||
"dayjs": "^1.11.9",
|
"dayjs": "^1.11.9",
|
||||||
"destr": "^2.0.1",
|
"destr": "^2.0.1",
|
||||||
"framer-motion": "^10.16.4",
|
|
||||||
"get-urls": "^12.1.0",
|
"get-urls": "^12.1.0",
|
||||||
"html-to-text": "^9.0.5",
|
"html-to-text": "^9.0.5",
|
||||||
"light-bolt11-decoder": "^3.0.0",
|
"light-bolt11-decoder": "^3.0.0",
|
||||||
@ -54,7 +51,6 @@
|
|||||||
"react-currency-input-field": "^3.6.11",
|
"react-currency-input-field": "^3.6.11",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-hook-form": "^7.46.1",
|
"react-hook-form": "^7.46.1",
|
||||||
"react-hotkeys-hook": "^4.4.1",
|
|
||||||
"react-markdown": "^8.0.7",
|
"react-markdown": "^8.0.7",
|
||||||
"react-player": "^2.13.0",
|
"react-player": "^2.13.0",
|
||||||
"react-router-dom": "^6.15.0",
|
"react-router-dom": "^6.15.0",
|
||||||
@ -65,7 +61,6 @@
|
|||||||
"tauri-plugin-store-api": "github:tauri-apps/tauri-plugin-store#v1",
|
"tauri-plugin-store-api": "github:tauri-apps/tauri-plugin-store#v1",
|
||||||
"tauri-plugin-stronghold-api": "github:tauri-apps/tauri-plugin-stronghold#v1",
|
"tauri-plugin-stronghold-api": "github:tauri-apps/tauri-plugin-stronghold#v1",
|
||||||
"tauri-plugin-upload-api": "github:tauri-apps/tauri-plugin-upload#v1",
|
"tauri-plugin-upload-api": "github:tauri-apps/tauri-plugin-upload#v1",
|
||||||
"tippy.js": "^6.3.7",
|
|
||||||
"zustand": "^4.4.1"
|
"zustand": "^4.4.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -207,6 +207,13 @@ const router = createBrowserRouter([
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'complete',
|
||||||
|
async lazy() {
|
||||||
|
const { CompleteScreen } = await import('@app/auth/complete');
|
||||||
|
return { Component: CompleteScreen };
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'unlock',
|
path: 'unlock',
|
||||||
async lazy() {
|
async lazy() {
|
||||||
|
43
src/app/auth/complete.tsx
Normal file
43
src/app/auth/complete.tsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
export function CompleteScreen() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [count, setCount] = useState(3);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let counter: NodeJS.Timeout;
|
||||||
|
|
||||||
|
if (count > 0) {
|
||||||
|
counter = setTimeout(() => setCount(count - 1), 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count === 0) {
|
||||||
|
navigate('/', { replace: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(counter);
|
||||||
|
};
|
||||||
|
}, [count]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative flex h-full w-full flex-col items-center justify-center">
|
||||||
|
<div className="mx-auto flex max-w-xl flex-col gap-1.5 text-center">
|
||||||
|
<h1 className="text-2xl font-light leading-none text-white">
|
||||||
|
<span className="font-semibold">You're ready</span>, redirecting in {count}
|
||||||
|
...
|
||||||
|
</h1>
|
||||||
|
<p className="text-white/70">
|
||||||
|
Thank you for using Lume. Lume doesn't use telemetry. If you encounter any
|
||||||
|
problems, please submit a report via the "Report Issue" button.
|
||||||
|
<br />
|
||||||
|
You can find it while using the application.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="absolute bottom-6 left-1/2 flex -translate-x-1/2 transform items-center justify-center">
|
||||||
|
<img src="/lume.png" alt="lume" className="h-auto w-1/5" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -112,7 +112,7 @@ export function CreateStep3Screen() {
|
|||||||
type={'text'}
|
type={'text'}
|
||||||
{...register('name', {
|
{...register('name', {
|
||||||
required: true,
|
required: true,
|
||||||
minLength: 4,
|
minLength: 1,
|
||||||
})}
|
})}
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
className="relative h-12 w-full rounded-lg bg-white/20 px-3 py-1 text-white !outline-none backdrop-blur-xl placeholder:text-white/70"
|
className="relative h-12 w-full rounded-lg bg-white/20 px-3 py-1 text-white !outline-none backdrop-blur-xl placeholder:text-white/70"
|
||||||
|
@ -5,7 +5,7 @@ import { useNavigate } from 'react-router-dom';
|
|||||||
|
|
||||||
import { useStorage } from '@libs/storage/provider';
|
import { useStorage } from '@libs/storage/provider';
|
||||||
|
|
||||||
import { LoaderIcon } from '@shared/icons';
|
import { EyeOffIcon, EyeOnIcon, LoaderIcon } from '@shared/icons';
|
||||||
import { ArrowRightCircleIcon } from '@shared/icons/arrowRightCircle';
|
import { ArrowRightCircleIcon } from '@shared/icons/arrowRightCircle';
|
||||||
|
|
||||||
import { useOnboarding } from '@stores/onboarding';
|
import { useOnboarding } from '@stores/onboarding';
|
||||||
@ -37,6 +37,7 @@ export function ImportStep1Screen() {
|
|||||||
const setStep = useOnboarding((state) => state.setStep);
|
const setStep = useOnboarding((state) => state.setStep);
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [passwordInput, setPasswordInput] = useState('password');
|
||||||
|
|
||||||
const { db } = useStorage();
|
const { db } = useStorage();
|
||||||
const {
|
const {
|
||||||
@ -78,6 +79,15 @@ export function ImportStep1Screen() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// toggle private key
|
||||||
|
const showPassword = () => {
|
||||||
|
if (passwordInput === 'password') {
|
||||||
|
setPasswordInput('text');
|
||||||
|
} else {
|
||||||
|
setPasswordInput('password');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// save current step, if user close app and reopen it
|
// save current step, if user close app and reopen it
|
||||||
setStep('/auth/import');
|
setStep('/auth/import');
|
||||||
@ -96,12 +106,25 @@ export function ImportStep1Screen() {
|
|||||||
<label htmlFor="privkey" className="font-medium text-white">
|
<label htmlFor="privkey" className="font-medium text-white">
|
||||||
Insert your nostr private key, in nsec or hex format
|
Insert your nostr private key, in nsec or hex format
|
||||||
</label>
|
</label>
|
||||||
<input
|
<div className="relative">
|
||||||
{...register('privkey', { required: true, minLength: 32 })}
|
<input
|
||||||
type={'password'}
|
{...register('privkey', { required: true, minLength: 32 })}
|
||||||
placeholder="nsec1..."
|
type={passwordInput}
|
||||||
className="relative h-12 w-full rounded-lg border-t border-white/10 bg-white/20 px-3 py-1 text-white backdrop-blur-xl placeholder:text-white/70 focus:outline-none"
|
placeholder="nsec1..."
|
||||||
/>
|
className="relative h-12 w-full rounded-lg border-t border-white/10 bg-white/20 px-3 py-1 text-white backdrop-blur-xl placeholder:text-white/70 focus:outline-none"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => showPassword()}
|
||||||
|
className="group absolute right-2 top-1/2 -translate-y-1/2 transform rounded p-1 backdrop-blur-xl hover:bg-white/20"
|
||||||
|
>
|
||||||
|
{passwordInput === 'password' ? (
|
||||||
|
<EyeOffIcon className="h-4 w-4 text-white/50 group-hover:text-white" />
|
||||||
|
) : (
|
||||||
|
<EyeOnIcon className="h-4 w-4 text-white/50 group-hover:text-white" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<span className="text-sm text-red-500">
|
<span className="text-sm text-red-500">
|
||||||
{errors.privkey && <p>{errors.privkey.message}</p>}
|
{errors.privkey && <p>{errors.privkey.message}</p>}
|
||||||
</span>
|
</span>
|
||||||
|
@ -60,7 +60,7 @@ export function OnboardStep2Screen() {
|
|||||||
// clear local storage
|
// clear local storage
|
||||||
clearStep();
|
clearStep();
|
||||||
|
|
||||||
navigate('/', { replace: true });
|
navigate('/auth/complete', { replace: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
@ -77,7 +77,7 @@ export function OnboardStep2Screen() {
|
|||||||
// clear local storage
|
// clear local storage
|
||||||
clearStep();
|
clearStep();
|
||||||
|
|
||||||
navigate('/', { replace: true });
|
navigate('/auth/complete', { replace: true });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
await message(e, { title: 'Lume', type: 'error' });
|
await message(e, { title: 'Lume', type: 'error' });
|
||||||
|
@ -2,7 +2,6 @@ import { LogicalSize, getCurrent } from '@tauri-apps/api/window';
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import { Frame } from '@shared/frame';
|
|
||||||
import { ArrowRightCircleIcon } from '@shared/icons/arrowRightCircle';
|
import { ArrowRightCircleIcon } from '@shared/icons/arrowRightCircle';
|
||||||
|
|
||||||
export function WelcomeScreen() {
|
export function WelcomeScreen() {
|
||||||
@ -29,7 +28,7 @@ export function WelcomeScreen() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Frame className="flex h-screen w-full flex-col justify-between">
|
<div className="flex h-screen w-full flex-col justify-between">
|
||||||
<div className="flex flex-col gap-10 pt-16">
|
<div className="flex flex-col gap-10 pt-16">
|
||||||
<div className="flex flex-col gap-1.5 text-center">
|
<div className="flex flex-col gap-1.5 text-center">
|
||||||
<h1 className="text-3xl font-semibold text-white">Welcome to Lume</h1>
|
<h1 className="text-3xl font-semibold text-white">Welcome to Lume</h1>
|
||||||
@ -58,6 +57,6 @@ export function WelcomeScreen() {
|
|||||||
<div className="flex flex-1 items-end justify-center pb-6">
|
<div className="flex flex-1 items-end justify-center pb-6">
|
||||||
<img src="/lume.png" alt="lume" className="h-auto w-1/4" />
|
<img src="/lume.png" alt="lume" className="h-auto w-1/4" />
|
||||||
</div>
|
</div>
|
||||||
</Frame>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,90 +0,0 @@
|
|||||||
import { magnetDecode } from '@ctrl/magnet-link';
|
|
||||||
import { open } from '@tauri-apps/api/dialog';
|
|
||||||
import { VoidApi } from '@void-cat/api';
|
|
||||||
|
|
||||||
import { createBlobFromFile } from '@utils/createBlobFromFile';
|
|
||||||
import { useNostr } from '@utils/hooks/useNostr';
|
|
||||||
|
|
||||||
export function useImageUploader() {
|
|
||||||
const { publish } = useNostr();
|
|
||||||
|
|
||||||
const upload = async (file: null | string, nip94?: boolean) => {
|
|
||||||
const voidcat = new VoidApi('https://void.cat');
|
|
||||||
|
|
||||||
let filepath = file;
|
|
||||||
if (!file) {
|
|
||||||
const selected = await open({
|
|
||||||
multiple: false,
|
|
||||||
filters: [
|
|
||||||
{
|
|
||||||
name: 'Image',
|
|
||||||
extensions: [
|
|
||||||
'png',
|
|
||||||
'jpeg',
|
|
||||||
'jpg',
|
|
||||||
'gif',
|
|
||||||
'mp4',
|
|
||||||
'mp3',
|
|
||||||
'webm',
|
|
||||||
'mkv',
|
|
||||||
'avi',
|
|
||||||
'mov',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
if (Array.isArray(selected)) {
|
|
||||||
// user selected multiple files
|
|
||||||
} else if (selected === null) {
|
|
||||||
// user cancelled the selection
|
|
||||||
} else {
|
|
||||||
filepath = selected;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const filename = filepath.split('/').pop();
|
|
||||||
const filetype = filename.split('.').pop();
|
|
||||||
|
|
||||||
const blob = await createBlobFromFile(filepath);
|
|
||||||
const uploader = voidcat.getUploader(blob);
|
|
||||||
|
|
||||||
// upload file
|
|
||||||
const res = await uploader.upload();
|
|
||||||
|
|
||||||
if (res.ok) {
|
|
||||||
const url =
|
|
||||||
res.file?.metadata?.url ?? `https://void.cat/d/${res.file?.id}.${filetype}`;
|
|
||||||
|
|
||||||
if (nip94) {
|
|
||||||
const tags = [
|
|
||||||
['url', url],
|
|
||||||
['x', res.file?.metadata?.digest ?? ''],
|
|
||||||
['m', res.file?.metadata?.mimeType ?? 'application/octet-stream'],
|
|
||||||
['size', res.file?.metadata?.size.toString() ?? '0'],
|
|
||||||
];
|
|
||||||
|
|
||||||
if (res.file?.metadata?.magnetLink) {
|
|
||||||
tags.push(['magnet', res.file.metadata.magnetLink]);
|
|
||||||
const parsedMagnet = magnetDecode(res.file.metadata.magnetLink);
|
|
||||||
if (parsedMagnet?.infoHash) {
|
|
||||||
tags.push(['i', parsedMagnet?.infoHash]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await publish({ content: '', kind: 1063, tags: tags });
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
url: url,
|
|
||||||
error: null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
url: null,
|
|
||||||
error: 'Upload failed',
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
return upload;
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user