mirror of
https://github.com/lumehq/lume.git
synced 2025-10-05 19:53:37 +02:00
feat: polish
This commit is contained in:
@@ -9,11 +9,12 @@ import NDK, {
|
|||||||
NDKPrivateKeySigner,
|
NDKPrivateKeySigner,
|
||||||
} from "@nostr-dev-kit/ndk";
|
} from "@nostr-dev-kit/ndk";
|
||||||
import * as Select from "@radix-ui/react-select";
|
import * as Select from "@radix-ui/react-select";
|
||||||
import { downloadDir } from "@tauri-apps/api/path";
|
import { desktopDir } from "@tauri-apps/api/path";
|
||||||
import { Window } from "@tauri-apps/api/window";
|
import { Window } from "@tauri-apps/api/window";
|
||||||
import { save } from "@tauri-apps/plugin-dialog";
|
import { save } from "@tauri-apps/plugin-dialog";
|
||||||
import { writeTextFile } from "@tauri-apps/plugin-fs";
|
import { writeTextFile } from "@tauri-apps/plugin-fs";
|
||||||
import { useSetAtom } from "jotai";
|
import { useSetAtom } from "jotai";
|
||||||
|
import { nanoid } from "nanoid";
|
||||||
import { getPublicKey, nip19 } from "nostr-tools";
|
import { getPublicKey, nip19 } from "nostr-tools";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
@@ -66,18 +67,20 @@ export function CreateAccountScreen() {
|
|||||||
|
|
||||||
ark.updateNostrSigner({ signer });
|
ark.updateNostrSigner({ signer });
|
||||||
|
|
||||||
const downloadPath = await downloadDir();
|
const downloadPath = await desktopDir();
|
||||||
const fileName = `nostr_keys_${new Date().getTime().toString(36)}.txt`;
|
const fileName = `nostr_keys_${nanoid(4)}.txt`;
|
||||||
const filePath = await save({
|
const filePath = await save({
|
||||||
defaultPath: `${downloadPath}/${fileName}`,
|
defaultPath: `${downloadPath}/${fileName}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (filePath) {
|
if (!filePath) {
|
||||||
await writeTextFile(
|
return toast.info("You need to save account keys before continue.");
|
||||||
filePath,
|
}
|
||||||
`Nostr account, generated by Lume (lume.nu)\nPublic key: ${npub}\nPrivate key: ${nsec}`,
|
|
||||||
);
|
await writeTextFile(
|
||||||
} // else { user cancel action }
|
filePath,
|
||||||
|
`Nostr Account\nGenerated by Lume (lume.nu)\n---\nPublic key: ${npub}\nPrivate key: ${nsec}`,
|
||||||
|
);
|
||||||
|
|
||||||
await storage.createAccount({
|
await storage.createAccount({
|
||||||
pubkey: pubkey,
|
pubkey: pubkey,
|
||||||
@@ -180,8 +183,8 @@ export function CreateAccountScreen() {
|
|||||||
Let's get you set up on Nostr.
|
Let's get you set up on Nostr.
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-lg font-medium leading-snug text-neutral-600 dark:text-neutral-500">
|
<p className="text-lg font-medium leading-snug text-neutral-600 dark:text-neutral-500">
|
||||||
With an account on Nostr, you'll be able to travel across all nostr
|
Get started with familiar way, but all data belong to you and you
|
||||||
clients, all your data are synced.
|
have ability controls everything.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{!services ? (
|
{!services ? (
|
||||||
@@ -221,7 +224,7 @@ export function CreateAccountScreen() {
|
|||||||
</Select.Icon>
|
</Select.Icon>
|
||||||
</Select.Trigger>
|
</Select.Trigger>
|
||||||
<Select.Portal>
|
<Select.Portal>
|
||||||
<Select.Content className="border rounded-lg bg-neutral-950 border-neutral-900">
|
<Select.Content className="rounded-lg border border-white/20 bg-white/10 backdrop-blur-xl">
|
||||||
<Select.Viewport className="p-3">
|
<Select.Viewport className="p-3">
|
||||||
<Select.Group>
|
<Select.Group>
|
||||||
<Select.Label className="mb-2 text-sm font-medium uppercase px-7 text-neutral-600">
|
<Select.Label className="mb-2 text-sm font-medium uppercase px-7 text-neutral-600">
|
||||||
@@ -254,28 +257,35 @@ export function CreateAccountScreen() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<div>
|
||||||
type="submit"
|
<button
|
||||||
disabled={!isValid}
|
type="submit"
|
||||||
className="inline-flex items-center justify-center w-full text-lg h-12 font-medium text-white bg-blue-500 rounded-xl hover:bg-blue-600 disabled:opacity-50"
|
disabled={!isValid}
|
||||||
>
|
className="inline-flex items-center justify-center w-full text-lg h-12 font-medium text-white bg-blue-500 rounded-xl hover:bg-blue-600 disabled:opacity-50"
|
||||||
{loading ? (
|
>
|
||||||
<LoaderIcon className="size-5 animate-spin" />
|
{loading ? (
|
||||||
) : (
|
<LoaderIcon className="size-5 animate-spin" />
|
||||||
"Create Account"
|
) : (
|
||||||
)}
|
"Create Account"
|
||||||
</button>
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div className="flex flex-col gap-6">
|
<div className="flex flex-col gap-6">
|
||||||
<div className="relative">
|
<div className="flex flex-col">
|
||||||
<div className="absolute inset-0 flex items-center">
|
<div className="relative">
|
||||||
<div className="w-full border-t border-neutral-900" />
|
<div className="absolute inset-0 flex items-center">
|
||||||
</div>
|
<div className="w-full border-t border-neutral-900" />
|
||||||
<div className="relative flex justify-center">
|
</div>
|
||||||
<span className="px-2 font-medium bg-black text-neutral-600">
|
<div className="relative flex justify-center">
|
||||||
Or
|
<span className="px-2 font-medium bg-black text-neutral-500">
|
||||||
</span>
|
Or
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<span className="mx-auto text-xs font-medium bg-black text-neutral-600">
|
||||||
|
More compatible with other Nostr clients
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
|
@@ -15,7 +15,7 @@ export function LoginScreen() {
|
|||||||
to="/auth/login-oauth"
|
to="/auth/login-oauth"
|
||||||
className="inline-flex items-center justify-center w-full h-12 text-lg font-medium text-white bg-blue-500 rounded-xl hover:bg-blue-600"
|
className="inline-flex items-center justify-center w-full h-12 text-lg font-medium text-white bg-blue-500 rounded-xl hover:bg-blue-600"
|
||||||
>
|
>
|
||||||
Login with address
|
Login with Address
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
to="/auth/login-nsecbunker"
|
to="/auth/login-nsecbunker"
|
||||||
@@ -31,7 +31,7 @@ export function LoginScreen() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="relative flex justify-center">
|
<div className="relative flex justify-center">
|
||||||
<span className="px-2 font-medium bg-black text-neutral-600">
|
<span className="px-2 font-medium bg-black text-neutral-600">
|
||||||
Or (Not recommend)
|
Or (Not recommended)
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -88,8 +88,8 @@ export function ProfileSettingScreen() {
|
|||||||
if (publish) {
|
if (publish) {
|
||||||
// invalid cache
|
// invalid cache
|
||||||
await storage.clearProfileCache(ark.account.pubkey);
|
await storage.clearProfileCache(ark.account.pubkey);
|
||||||
await queryClient.invalidateQueries({
|
await queryClient.setQueryData(["user", ark.account.pubkey], () => {
|
||||||
queryKey: ["user", ark.account.pubkey],
|
return content;
|
||||||
});
|
});
|
||||||
|
|
||||||
// reset state
|
// reset state
|
||||||
|
@@ -105,7 +105,11 @@ export class Ark {
|
|||||||
|
|
||||||
public getCleanPubkey(pubkey: string) {
|
public getCleanPubkey(pubkey: string) {
|
||||||
try {
|
try {
|
||||||
let hexstring = pubkey.replace("nostr:", "").split("'")[0].split(".")[0];
|
let hexstring = pubkey
|
||||||
|
.replace("nostr:", "")
|
||||||
|
.split("'")[0]
|
||||||
|
.split(".")[0]
|
||||||
|
.split("?")[0];
|
||||||
|
|
||||||
if (
|
if (
|
||||||
hexstring.startsWith("npub1") ||
|
hexstring.startsWith("npub1") ||
|
||||||
|
@@ -17,13 +17,10 @@ export function AvatarUploadButton({
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
const image = await ark.upload({ fileExts: [] });
|
const image = await ark.upload({ fileExts: [] });
|
||||||
|
|
||||||
if (image) {
|
if (image) {
|
||||||
setPicture(image);
|
setPicture(image);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
toast.error(e);
|
toast.error(e);
|
||||||
@@ -34,7 +31,7 @@ export function AvatarUploadButton({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => uploadAvatar()}
|
onClick={() => uploadAvatar()}
|
||||||
className="inline-flex items-center justify-center rounded-lg border border-blue-200 bg-blue-100 px-2 py-1.5 text-sm font-medium text-blue-500 hover:border-blue-300 hover:bg-blue-200 dark:border-blue-800 dark:bg-blue-900 dark:text-blue-500 dark:hover:border-blue-800 dark:hover:bg-blue-800"
|
className="inline-flex items-center justify-center rounded-lg border border-blue-200 bg-blue-100 w-32 px-2 py-1.5 text-sm font-medium text-blue-500 hover:border-blue-300 hover:bg-blue-200 dark:border-blue-800 dark:bg-blue-900 dark:text-blue-500 dark:hover:border-blue-800 dark:hover:bg-blue-800"
|
||||||
>
|
>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<LoaderIcon className="size-4 animate-spin" />
|
<LoaderIcon className="size-4 animate-spin" />
|
||||||
|
@@ -17,7 +17,7 @@ export function AuthLayout({ platform }: { platform: Platform }) {
|
|||||||
<div data-tauri-drag-region className="h-9 shrink-0" />
|
<div data-tauri-drag-region className="h-9 shrink-0" />
|
||||||
)}
|
)}
|
||||||
<div className="relative w-full h-full">
|
<div className="relative w-full h-full">
|
||||||
<div className="absolute top-0 z-10 flex items-center justify-between w-full px-9">
|
<div className="absolute top-8 z-10 flex items-center justify-between w-full px-9">
|
||||||
{canGoBack ? (
|
{canGoBack ? (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -29,9 +29,6 @@ export function AuthLayout({ platform }: { platform: Platform }) {
|
|||||||
) : (
|
) : (
|
||||||
<div />
|
<div />
|
||||||
)}
|
)}
|
||||||
<div className="inline-flex items-center justify-center rounded-lg size-10 bg-neutral-950 group hover:bg-neutral-900">
|
|
||||||
<SettingsIcon className="size-6 text-neutral-700 group-hover:text-neutral-500" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</div>
|
</div>
|
||||||
|
@@ -7,7 +7,6 @@ import { useSetAtom } from "jotai";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
export function OnboardingFinishScreen() {
|
export function OnboardingFinishScreen() {
|
||||||
const ark = useArk();
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const setOnboarding = useSetAtom(onboardingAtom);
|
const setOnboarding = useSetAtom(onboardingAtom);
|
||||||
|
|
||||||
@@ -19,10 +18,6 @@ export function OnboardingFinishScreen() {
|
|||||||
const queryCache = queryClient.getQueryCache();
|
const queryCache = queryClient.getQueryCache();
|
||||||
const queryKeys = queryCache.getAll().map((cache) => cache.queryKey);
|
const queryKeys = queryCache.getAll().map((cache) => cache.queryKey);
|
||||||
|
|
||||||
await queryClient.refetchQueries({
|
|
||||||
queryKey: ["user", ark.account.pubkey],
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const key of queryKeys) {
|
for (const key of queryKeys) {
|
||||||
await queryClient.refetchQueries({ queryKey: key });
|
await queryClient.refetchQueries({ queryKey: key });
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,7 @@ import { useArk } from "@lume/ark";
|
|||||||
import { ArrowLeftIcon, LoaderIcon } from "@lume/icons";
|
import { ArrowLeftIcon, LoaderIcon } from "@lume/icons";
|
||||||
import { useStorage } from "@lume/storage";
|
import { useStorage } from "@lume/storage";
|
||||||
import { NDKKind, NDKUserProfile } from "@nostr-dev-kit/ndk";
|
import { NDKKind, NDKUserProfile } from "@nostr-dev-kit/ndk";
|
||||||
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { minidenticon } from "minidenticons";
|
import { minidenticon } from "minidenticons";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
@@ -16,6 +17,7 @@ export function OnboardingProfileSettingsScreen() {
|
|||||||
|
|
||||||
const ark = useArk();
|
const ark = useArk();
|
||||||
const storage = useStorage();
|
const storage = useStorage();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const { register, handleSubmit } = useForm();
|
const { register, handleSubmit } = useForm();
|
||||||
@@ -52,6 +54,12 @@ export function OnboardingProfileSettingsScreen() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (publish) {
|
if (publish) {
|
||||||
|
// invalid cache
|
||||||
|
await storage.clearProfileCache(ark.account.pubkey);
|
||||||
|
await queryClient.setQueryData(["user", ark.account.pubkey], () => {
|
||||||
|
return profile;
|
||||||
|
});
|
||||||
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
navigate("/follow");
|
navigate("/follow");
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user