mirror of
https://github.com/luminous-devs/lume.git
synced 2024-09-18 11:13: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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ctrl/magnet-link": "^3.1.2",
|
||||
"@getalby/sdk": "^2.4.0",
|
||||
"@nostr-dev-kit/ndk": "^1.0.0",
|
||||
"@nostr-fetch/adapter-ndk": "^0.12.2",
|
||||
@ -38,10 +37,8 @@
|
||||
"@tiptap/react": "^2.1.8",
|
||||
"@tiptap/starter-kit": "^2.1.8",
|
||||
"@tiptap/suggestion": "^2.1.8",
|
||||
"@void-cat/api": "^1.0.7",
|
||||
"dayjs": "^1.11.9",
|
||||
"destr": "^2.0.1",
|
||||
"framer-motion": "^10.16.4",
|
||||
"get-urls": "^12.1.0",
|
||||
"html-to-text": "^9.0.5",
|
||||
"light-bolt11-decoder": "^3.0.0",
|
||||
@ -54,7 +51,6 @@
|
||||
"react-currency-input-field": "^3.6.11",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hook-form": "^7.46.1",
|
||||
"react-hotkeys-hook": "^4.4.1",
|
||||
"react-markdown": "^8.0.7",
|
||||
"react-player": "^2.13.0",
|
||||
"react-router-dom": "^6.15.0",
|
||||
@ -65,7 +61,6 @@
|
||||
"tauri-plugin-store-api": "github:tauri-apps/tauri-plugin-store#v1",
|
||||
"tauri-plugin-stronghold-api": "github:tauri-apps/tauri-plugin-stronghold#v1",
|
||||
"tauri-plugin-upload-api": "github:tauri-apps/tauri-plugin-upload#v1",
|
||||
"tippy.js": "^6.3.7",
|
||||
"zustand": "^4.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -207,6 +207,13 @@ const router = createBrowserRouter([
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'complete',
|
||||
async lazy() {
|
||||
const { CompleteScreen } = await import('@app/auth/complete');
|
||||
return { Component: CompleteScreen };
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'unlock',
|
||||
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'}
|
||||
{...register('name', {
|
||||
required: true,
|
||||
minLength: 4,
|
||||
minLength: 1,
|
||||
})}
|
||||
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"
|
||||
|
@ -5,7 +5,7 @@ import { useNavigate } from 'react-router-dom';
|
||||
|
||||
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 { useOnboarding } from '@stores/onboarding';
|
||||
@ -37,6 +37,7 @@ export function ImportStep1Screen() {
|
||||
const setStep = useOnboarding((state) => state.setStep);
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [passwordInput, setPasswordInput] = useState('password');
|
||||
|
||||
const { db } = useStorage();
|
||||
const {
|
||||
@ -78,6 +79,15 @@ export function ImportStep1Screen() {
|
||||
}
|
||||
};
|
||||
|
||||
// toggle private key
|
||||
const showPassword = () => {
|
||||
if (passwordInput === 'password') {
|
||||
setPasswordInput('text');
|
||||
} else {
|
||||
setPasswordInput('password');
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// save current step, if user close app and reopen it
|
||||
setStep('/auth/import');
|
||||
@ -96,12 +106,25 @@ export function ImportStep1Screen() {
|
||||
<label htmlFor="privkey" className="font-medium text-white">
|
||||
Insert your nostr private key, in nsec or hex format
|
||||
</label>
|
||||
<input
|
||||
{...register('privkey', { required: true, minLength: 32 })}
|
||||
type={'password'}
|
||||
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"
|
||||
/>
|
||||
<div className="relative">
|
||||
<input
|
||||
{...register('privkey', { required: true, minLength: 32 })}
|
||||
type={passwordInput}
|
||||
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">
|
||||
{errors.privkey && <p>{errors.privkey.message}</p>}
|
||||
</span>
|
||||
|
@ -60,7 +60,7 @@ export function OnboardStep2Screen() {
|
||||
// clear local storage
|
||||
clearStep();
|
||||
|
||||
navigate('/', { replace: true });
|
||||
navigate('/auth/complete', { replace: true });
|
||||
};
|
||||
|
||||
const submit = async () => {
|
||||
@ -77,7 +77,7 @@ export function OnboardStep2Screen() {
|
||||
// clear local storage
|
||||
clearStep();
|
||||
|
||||
navigate('/', { replace: true });
|
||||
navigate('/auth/complete', { replace: true });
|
||||
} catch (e) {
|
||||
setLoading(false);
|
||||
await message(e, { title: 'Lume', type: 'error' });
|
||||
|
@ -2,7 +2,6 @@ import { LogicalSize, getCurrent } from '@tauri-apps/api/window';
|
||||
import { useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { Frame } from '@shared/frame';
|
||||
import { ArrowRightCircleIcon } from '@shared/icons/arrowRightCircle';
|
||||
|
||||
export function WelcomeScreen() {
|
||||
@ -29,7 +28,7 @@ export function WelcomeScreen() {
|
||||
}, []);
|
||||
|
||||
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-1.5 text-center">
|
||||
<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">
|
||||
<img src="/lume.png" alt="lume" className="h-auto w-1/4" />
|
||||
</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