mirror of
https://github.com/luminous-devs/lume.git
synced 2024-09-18 11:13:30 +00:00
refactor initial database and state management
This commit is contained in:
parent
964343ccc8
commit
458f826958
11
.prettierrc
11
.prettierrc
@ -8,16 +8,7 @@
|
|||||||
"endOfLine": "lf",
|
"endOfLine": "lf",
|
||||||
"bracketSpacing": true,
|
"bracketSpacing": true,
|
||||||
"bracketSameLine": true,
|
"bracketSameLine": true,
|
||||||
"importOrder": [
|
"importOrder": ["^@layouts/(.*)$", "^@pages/(.*)$", "^@components/(.*)$", "^@utils/(.*)$", "^@stores/(.*)$", "<THIRD_PARTY_MODULES>", "^[./]"],
|
||||||
"^@layouts/(.*)$",
|
|
||||||
"^@pages/(.*)$",
|
|
||||||
"^@components/(.*)$",
|
|
||||||
"^@utils/(.*)$",
|
|
||||||
"^@stores/(.*)$",
|
|
||||||
"^@assets/(.*)$",
|
|
||||||
"<THIRD_PARTY_MODULES>",
|
|
||||||
"^[./]"
|
|
||||||
],
|
|
||||||
"importOrderSeparation": true,
|
"importOrderSeparation": true,
|
||||||
"importOrderSortSpecifiers": true,
|
"importOrderSortSpecifiers": true,
|
||||||
"plugins": ["@trivago/prettier-plugin-sort-imports", "prettier-plugin-tailwindcss"],
|
"plugins": ["@trivago/prettier-plugin-sort-imports", "prettier-plugin-tailwindcss"],
|
||||||
|
@ -12,11 +12,10 @@
|
|||||||
"**/*": "prettier --write --ignore-unknown"
|
"**/*": "prettier --write --ignore-unknown"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nanostores/persistent": "^0.7.0",
|
|
||||||
"@nanostores/react": "^0.4.1",
|
|
||||||
"@radix-ui/react-dialog": "^1.0.2",
|
"@radix-ui/react-dialog": "^1.0.2",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.0.3",
|
"@radix-ui/react-dropdown-menu": "^2.0.3",
|
||||||
"@radix-ui/react-icons": "^1.2.0",
|
"@radix-ui/react-icons": "^1.2.0",
|
||||||
|
"@rehooks/local-storage": "^2.4.4",
|
||||||
"@tauri-apps/api": "^1.2.0",
|
"@tauri-apps/api": "^1.2.0",
|
||||||
"@uiw/react-markdown-preview": "^4.1.9",
|
"@uiw/react-markdown-preview": "^4.1.9",
|
||||||
"@uiw/react-md-editor": "^3.20.5",
|
"@uiw/react-md-editor": "^3.20.5",
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
lockfileVersion: 5.4
|
lockfileVersion: 5.4
|
||||||
|
|
||||||
specifiers:
|
specifiers:
|
||||||
'@nanostores/persistent': ^0.7.0
|
|
||||||
'@nanostores/react': ^0.4.1
|
|
||||||
'@radix-ui/react-dialog': ^1.0.2
|
'@radix-ui/react-dialog': ^1.0.2
|
||||||
'@radix-ui/react-dropdown-menu': ^2.0.3
|
'@radix-ui/react-dropdown-menu': ^2.0.3
|
||||||
'@radix-ui/react-icons': ^1.2.0
|
'@radix-ui/react-icons': ^1.2.0
|
||||||
|
'@rehooks/local-storage': ^2.4.4
|
||||||
'@tailwindcss/typography': ^0.5.9
|
'@tailwindcss/typography': ^0.5.9
|
||||||
'@tauri-apps/api': ^1.2.0
|
'@tauri-apps/api': ^1.2.0
|
||||||
'@tauri-apps/cli': ^1.2.3
|
'@tauri-apps/cli': ^1.2.3
|
||||||
@ -54,11 +53,10 @@ specifiers:
|
|||||||
ws: ^8.12.1
|
ws: ^8.12.1
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@nanostores/persistent': 0.7.0_nanostores@0.7.4
|
|
||||||
'@nanostores/react': 0.4.1_nkfnbc2tpc77iht7asm3uqwau4
|
|
||||||
'@radix-ui/react-dialog': 1.0.2_zula6vjvt3wdocc4mwcxqa6nzi
|
'@radix-ui/react-dialog': 1.0.2_zula6vjvt3wdocc4mwcxqa6nzi
|
||||||
'@radix-ui/react-dropdown-menu': 2.0.3_zula6vjvt3wdocc4mwcxqa6nzi
|
'@radix-ui/react-dropdown-menu': 2.0.3_zula6vjvt3wdocc4mwcxqa6nzi
|
||||||
'@radix-ui/react-icons': 1.2.0_react@18.2.0
|
'@radix-ui/react-icons': 1.2.0_react@18.2.0
|
||||||
|
'@rehooks/local-storage': 2.4.4_react@18.2.0
|
||||||
'@tauri-apps/api': 1.2.0
|
'@tauri-apps/api': 1.2.0
|
||||||
'@uiw/react-markdown-preview': 4.1.9_zula6vjvt3wdocc4mwcxqa6nzi
|
'@uiw/react-markdown-preview': 4.1.9_zula6vjvt3wdocc4mwcxqa6nzi
|
||||||
'@uiw/react-md-editor': 3.20.5_zula6vjvt3wdocc4mwcxqa6nzi
|
'@uiw/react-md-editor': 3.20.5_zula6vjvt3wdocc4mwcxqa6nzi
|
||||||
@ -467,27 +465,6 @@ packages:
|
|||||||
'@jridgewell/sourcemap-codec': 1.4.14
|
'@jridgewell/sourcemap-codec': 1.4.14
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@nanostores/persistent/0.7.0_nanostores@0.7.4:
|
|
||||||
resolution: { integrity: sha512-4PAInL/T1hbftZUJ0cmgdFHBMalUoq7BUXFBy7QfyMv/8X3LPTYNh/yxspL7+J+XM3UNvVI7IFRMMs6FBasjhQ== }
|
|
||||||
engines: { node: ^14.0.0 || ^16.0.0 || >=18.0.0 }
|
|
||||||
peerDependencies:
|
|
||||||
nanostores: ^0.7.0
|
|
||||||
dependencies:
|
|
||||||
nanostores: 0.7.4
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@nanostores/react/0.4.1_nkfnbc2tpc77iht7asm3uqwau4:
|
|
||||||
resolution: { integrity: sha512-lsv0CYrMxczbXtoV/mxFVEoL/uVjEjseoP89srO/5yNAOkJka+dSFS7LYyWEbuvCPO7EgbtkvRpO5V+OztKQOw== }
|
|
||||||
engines: { node: ^14.0.0 || ^16.0.0 || >=18.0.0 }
|
|
||||||
peerDependencies:
|
|
||||||
nanostores: ^0.7.0
|
|
||||||
react: '>=18.0.0'
|
|
||||||
dependencies:
|
|
||||||
nanostores: 0.7.4
|
|
||||||
react: 18.2.0
|
|
||||||
use-sync-external-store: 1.2.0_react@18.2.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@next/env/13.2.1:
|
/@next/env/13.2.1:
|
||||||
resolution: { integrity: sha512-Hq+6QZ6kgmloCg8Kgrix+4F0HtvLqVK3FZAnlAoS0eonaDemHe1Km4kwjSWRE3JNpJNcKxFHF+jsZrYo0SxWoQ== }
|
resolution: { integrity: sha512-Hq+6QZ6kgmloCg8Kgrix+4F0HtvLqVK3FZAnlAoS0eonaDemHe1Km4kwjSWRE3JNpJNcKxFHF+jsZrYo0SxWoQ== }
|
||||||
dev: false
|
dev: false
|
||||||
@ -1009,6 +986,14 @@ packages:
|
|||||||
'@babel/runtime': 7.21.0
|
'@babel/runtime': 7.21.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@rehooks/local-storage/2.4.4_react@18.2.0:
|
||||||
|
resolution: { integrity: sha512-zE+kfOkG59n/1UTxdmbwktIosclr67Nlbf2MzUJ9mNtCSypVscNHeD1qT6JCSo5Pjj8DO893IKWNLJqKKzDL/Q== }
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16.8.0'
|
||||||
|
dependencies:
|
||||||
|
react: 18.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@rushstack/eslint-patch/1.2.0:
|
/@rushstack/eslint-patch/1.2.0:
|
||||||
resolution: { integrity: sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg== }
|
resolution: { integrity: sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg== }
|
||||||
dev: true
|
dev: true
|
||||||
@ -5374,14 +5359,6 @@ packages:
|
|||||||
tslib: 2.5.0
|
tslib: 2.5.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/use-sync-external-store/1.2.0_react@18.2.0:
|
|
||||||
resolution: { integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== }
|
|
||||||
peerDependencies:
|
|
||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
|
||||||
dependencies:
|
|
||||||
react: 18.2.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/util-deprecate/1.0.2:
|
/util-deprecate/1.0.2:
|
||||||
resolution: { integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== }
|
resolution: { integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== }
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -1,4 +1,28 @@
|
|||||||
-- Add migration script here
|
-- Add migration script here
|
||||||
|
-- create relays
|
||||||
|
CREATE TABLE
|
||||||
|
relays (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
relay_url TEXT NOT NULL,
|
||||||
|
relay_status INTEGER NOT NULL DEFAULT 1,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO
|
||||||
|
relays (relay_url, relay_status)
|
||||||
|
VALUES
|
||||||
|
("wss://relay.damus.io", "1"),
|
||||||
|
("wss://relay.uselume.xyz", "0"),
|
||||||
|
("wss://nostr-pub.wellorder.net", "1"),
|
||||||
|
("wss://nostr.bongbong.com", "1"),
|
||||||
|
("wss://nostr.zebedee.cloud", "1"),
|
||||||
|
("wss://nostr.fmt.wiz.biz", "1"),
|
||||||
|
("wss://nostr.walletofsatoshi.com", "1"),
|
||||||
|
("wss://relay.snort.social", "1"),
|
||||||
|
("wss://offchain.pub", "1"),
|
||||||
|
("wss://nos.lol", "1");
|
||||||
|
|
||||||
-- create accounts
|
-- create accounts
|
||||||
CREATE TABLE
|
CREATE TABLE
|
||||||
accounts (
|
accounts (
|
||||||
@ -6,6 +30,7 @@ CREATE TABLE
|
|||||||
privkey TEXT NOT NULL,
|
privkey TEXT NOT NULL,
|
||||||
npub TEXT NOT NULL,
|
npub TEXT NOT NULL,
|
||||||
nsec TEXT NOT NULL,
|
nsec TEXT NOT NULL,
|
||||||
|
is_active INTEGER NOT NULL DEFAULT 0,
|
||||||
metadata JSON
|
metadata JSON
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -40,5 +65,6 @@ CREATE TABLE
|
|||||||
kind INTEGER NOT NULL DEFAULT 1,
|
kind INTEGER NOT NULL DEFAULT 1,
|
||||||
tags TEXT NOT NULL,
|
tags TEXT NOT NULL,
|
||||||
content TEXT NOT NULL,
|
content TEXT NOT NULL,
|
||||||
|
relay TEXT,
|
||||||
is_multi BOOLEAN DEFAULT 0
|
is_multi BOOLEAN DEFAULT 0
|
||||||
);
|
);
|
@ -1,19 +1,16 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import { Account } from '@components/accountBar/account';
|
import { Account } from '@components/accountBar/account';
|
||||||
|
|
||||||
import { currentUser } from '@stores/currentUser';
|
|
||||||
|
|
||||||
import LumeSymbol from '@assets/icons/Lume';
|
import LumeSymbol from '@assets/icons/Lume';
|
||||||
|
|
||||||
import { useStore } from '@nanostores/react';
|
|
||||||
import { PlusIcon } from '@radix-ui/react-icons';
|
import { PlusIcon } from '@radix-ui/react-icons';
|
||||||
|
import { useLocalStorage } from '@rehooks/local-storage';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import Database from 'tauri-plugin-sql-api';
|
import Database from 'tauri-plugin-sql-api';
|
||||||
|
|
||||||
export default function AccountBar() {
|
export default function AccountBar() {
|
||||||
const [users, setUsers] = useState([]);
|
const [users, setUsers] = useState([]);
|
||||||
const $currentUser: any = useStore(currentUser);
|
const [currentUser]: any = useLocalStorage('current-user');
|
||||||
|
|
||||||
const getAccounts = useCallback(async () => {
|
const getAccounts = useCallback(async () => {
|
||||||
const db = await Database.load('sqlite:lume.db');
|
const db = await Database.load('sqlite:lume.db');
|
||||||
@ -30,7 +27,7 @@ export default function AccountBar() {
|
|||||||
<div className="flex h-full flex-col items-center justify-between px-2 pt-12 pb-4">
|
<div className="flex h-full flex-col items-center justify-between px-2 pt-12 pb-4">
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
{users.map((user, index) => (
|
{users.map((user, index) => (
|
||||||
<Account key={index} user={user} current={$currentUser.pubkey} />
|
<Account key={index} user={user} current={currentUser.pubkey} />
|
||||||
))}
|
))}
|
||||||
<Link
|
<Link
|
||||||
href="/onboarding"
|
href="/onboarding"
|
||||||
|
@ -4,10 +4,7 @@ import { RelayContext } from '@components/contexts/relay';
|
|||||||
|
|
||||||
import { dateToUnix, hoursAgo } from '@utils/getDate';
|
import { dateToUnix, hoursAgo } from '@utils/getDate';
|
||||||
|
|
||||||
import { follows } from '@stores/follows';
|
import { useLocalStorage } from '@rehooks/local-storage';
|
||||||
import { relays } from '@stores/relays';
|
|
||||||
|
|
||||||
import { useStore } from '@nanostores/react';
|
|
||||||
import { memo, useCallback, useContext, useRef } from 'react';
|
import { memo, useCallback, useContext, useRef } from 'react';
|
||||||
|
|
||||||
export const NoteConnector = memo(function NoteConnector() {
|
export const NoteConnector = memo(function NoteConnector() {
|
||||||
@ -16,8 +13,8 @@ export const NoteConnector = memo(function NoteConnector() {
|
|||||||
|
|
||||||
const now = useRef(new Date());
|
const now = useRef(new Date());
|
||||||
|
|
||||||
const $follows = useStore(follows);
|
const [follows]: any = useLocalStorage('follows');
|
||||||
const $relays = useStore(relays);
|
const [relays]: any = useLocalStorage('relays');
|
||||||
|
|
||||||
const insertDB = useCallback(
|
const insertDB = useCallback(
|
||||||
async (event: any) => {
|
async (event: any) => {
|
||||||
@ -35,11 +32,11 @@ export const NoteConnector = memo(function NoteConnector() {
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
kinds: [1],
|
kinds: [1],
|
||||||
authors: $follows,
|
authors: follows,
|
||||||
since: dateToUnix(hoursAgo(12, now.current)),
|
since: dateToUnix(hoursAgo(12, now.current)),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
$relays,
|
relays,
|
||||||
(event: any) => {
|
(event: any) => {
|
||||||
insertDB(event).catch(console.error);
|
insertDB(event).catch(console.error);
|
||||||
},
|
},
|
||||||
|
@ -1,13 +1,56 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import { createContext } from 'react';
|
import { writeStorage } from '@rehooks/local-storage';
|
||||||
|
import { createContext, useEffect, useMemo } from 'react';
|
||||||
import Database from 'tauri-plugin-sql-api';
|
import Database from 'tauri-plugin-sql-api';
|
||||||
|
|
||||||
export const DatabaseContext = createContext({});
|
export const DatabaseContext = createContext({});
|
||||||
|
|
||||||
const db = typeof window !== 'undefined' ? await Database.load('sqlite:lume.db') : null;
|
const initDB = typeof window !== 'undefined' ? await Database.load('sqlite:lume.db') : null;
|
||||||
|
|
||||||
export default function DatabaseProvider({ children }: { children: React.ReactNode }) {
|
export default function DatabaseProvider({ children }: { children: React.ReactNode }) {
|
||||||
const value = db;
|
const db = useMemo(() => initDB, []);
|
||||||
|
|
||||||
return <DatabaseContext.Provider value={value}>{children}</DatabaseContext.Provider>;
|
useEffect(() => {
|
||||||
|
const getRelays = async () => {
|
||||||
|
const arr = [];
|
||||||
|
const result: any[] = await db.select('SELECT relay_url FROM relays WHERE relay_status = "1"');
|
||||||
|
|
||||||
|
result.forEach((item: { relay_url: string }) => {
|
||||||
|
arr.push(item.relay_url);
|
||||||
|
});
|
||||||
|
|
||||||
|
writeStorage('relays', arr);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAccount = async () => {
|
||||||
|
const result = await db.select(`SELECT * FROM accounts LIMIT 1`);
|
||||||
|
writeStorage('current-user', result[0]);
|
||||||
|
|
||||||
|
return result[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFollows = async (id: string) => {
|
||||||
|
const arr = [];
|
||||||
|
const result: any[] = await db.select(`SELECT pubkey FROM follows WHERE account = "${id}"`);
|
||||||
|
|
||||||
|
result.forEach((item: { pubkey: string }) => {
|
||||||
|
arr.push(item.pubkey);
|
||||||
|
});
|
||||||
|
|
||||||
|
writeStorage('follows', arr);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (db !== null) {
|
||||||
|
getRelays().catch(console.error);
|
||||||
|
getAccount()
|
||||||
|
.then((res) => {
|
||||||
|
if (res) {
|
||||||
|
getFollows(res.id).catch(console.error);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
}
|
||||||
|
}, [db]);
|
||||||
|
|
||||||
|
return <DatabaseContext.Provider value={{ db }}>{children}</DatabaseContext.Provider>;
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import { currentUser } from '@stores/currentUser';
|
|
||||||
|
|
||||||
import { useStore } from '@nanostores/react';
|
|
||||||
import * as Dialog from '@radix-ui/react-dialog';
|
import * as Dialog from '@radix-ui/react-dialog';
|
||||||
|
import { useLocalStorage } from '@rehooks/local-storage';
|
||||||
import * as commands from '@uiw/react-md-editor/lib/commands';
|
import * as commands from '@uiw/react-md-editor/lib/commands';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import { dateToUnix, useNostr } from 'nostr-react';
|
import { dateToUnix, useNostr } from 'nostr-react';
|
||||||
@ -17,9 +15,9 @@ export default function CreatePost() {
|
|||||||
const { publish } = useNostr();
|
const { publish } = useNostr();
|
||||||
const [value, setValue] = useState('');
|
const [value, setValue] = useState('');
|
||||||
|
|
||||||
const $currentUser: any = useStore(currentUser);
|
const [currentUser]: any = useLocalStorage('current-user');
|
||||||
const pubkey = $currentUser.pubkey;
|
const pubkey = currentUser.pubkey;
|
||||||
const privkey = $currentUser.privkey;
|
const privkey = currentUser.privkey;
|
||||||
|
|
||||||
const postButton = {
|
const postButton = {
|
||||||
name: 'post',
|
name: 'post',
|
||||||
@ -27,9 +25,7 @@ export default function CreatePost() {
|
|||||||
buttonProps: { className: 'cta-btn', 'aria-label': 'Post a message' },
|
buttonProps: { className: 'cta-btn', 'aria-label': 'Post a message' },
|
||||||
icon: (
|
icon: (
|
||||||
<div className="relative inline-flex h-10 w-16 transform cursor-pointer overflow-hidden rounded bg-zinc-900 px-2.5 ring-zinc-500/50 ring-offset-zinc-900 will-change-transform focus:outline-none focus:ring-1 focus:ring-offset-2 active:translate-y-1">
|
<div className="relative inline-flex h-10 w-16 transform cursor-pointer overflow-hidden rounded bg-zinc-900 px-2.5 ring-zinc-500/50 ring-offset-zinc-900 will-change-transform focus:outline-none focus:ring-1 focus:ring-offset-2 active:translate-y-1">
|
||||||
<span className="absolute inset-px z-10 inline-flex items-center justify-center rounded bg-zinc-900 text-zinc-200">
|
<span className="absolute inset-px z-10 inline-flex items-center justify-center rounded bg-zinc-900 text-zinc-200">Post</span>
|
||||||
Post
|
|
||||||
</span>
|
|
||||||
<span className="absolute inset-0 z-0 scale-x-[2.0] blur before:absolute before:inset-0 before:top-1/2 before:aspect-square before:animate-disco before:bg-gradient-conic before:from-gray-300 before:via-fuchsia-600 before:to-orange-600"></span>
|
<span className="absolute inset-0 z-0 scale-x-[2.0] blur before:absolute before:inset-0 before:top-1/2 before:aspect-square before:animate-disco before:bg-gradient-conic before:from-gray-300 before:via-fuchsia-600 before:to-orange-600"></span>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
|
@ -4,14 +4,12 @@ import { NoteConnector } from '@components/connectors/note';
|
|||||||
import CreatePost from '@components/navigatorBar/createPost';
|
import CreatePost from '@components/navigatorBar/createPost';
|
||||||
import { ProfileMenu } from '@components/navigatorBar/profileMenu';
|
import { ProfileMenu } from '@components/navigatorBar/profileMenu';
|
||||||
|
|
||||||
import { currentUser } from '@stores/currentUser';
|
|
||||||
|
|
||||||
import { useStore } from '@nanostores/react';
|
|
||||||
import { PlusIcon } from '@radix-ui/react-icons';
|
import { PlusIcon } from '@radix-ui/react-icons';
|
||||||
|
import { useLocalStorage } from '@rehooks/local-storage';
|
||||||
|
|
||||||
export default function NavigatorBar() {
|
export default function NavigatorBar() {
|
||||||
const $currentUser: any = useStore(currentUser);
|
const [currentUser]: any = useLocalStorage('current-user');
|
||||||
const profile = $currentUser.metadata !== undefined ? JSON.parse($currentUser.metadata) : { display_name: null, username: null };
|
const profile = currentUser.metadata !== undefined ? JSON.parse(currentUser.metadata) : { display_name: null, username: null };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full flex-col flex-wrap justify-between overflow-hidden px-2 pt-3 pb-4">
|
<div className="flex h-full flex-col flex-wrap justify-between overflow-hidden px-2 pt-3 pb-4">
|
||||||
@ -25,7 +23,7 @@ export default function NavigatorBar() {
|
|||||||
<div className="flex flex-col p-2">
|
<div className="flex flex-col p-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h5 className="font-semibold leading-tight text-zinc-100">{profile.display_name || ''}</h5>
|
<h5 className="font-semibold leading-tight text-zinc-100">{profile.display_name || ''}</h5>
|
||||||
<ProfileMenu pubkey={$currentUser.pubkey} />
|
<ProfileMenu pubkey={currentUser.pubkey} />
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm leading-tight text-zinc-500">@{profile.username || ''}</span>
|
<span className="text-sm leading-tight text-zinc-500">@{profile.username || ''}</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,26 +1,18 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import { currentUser } from '@stores/currentUser';
|
|
||||||
|
|
||||||
import { useStore } from '@nanostores/react';
|
|
||||||
import { HeartFilledIcon, HeartIcon } from '@radix-ui/react-icons';
|
import { HeartFilledIcon, HeartIcon } from '@radix-ui/react-icons';
|
||||||
|
import { useLocalStorage } from '@rehooks/local-storage';
|
||||||
import { dateToUnix, useNostr, useNostrEvents } from 'nostr-react';
|
import { dateToUnix, useNostr, useNostrEvents } from 'nostr-react';
|
||||||
import { getEventHash, signEvent } from 'nostr-tools';
|
import { getEventHash, signEvent } from 'nostr-tools';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
export default function Reaction({
|
export default function Reaction({ eventID, eventPubkey }: { eventID: string; eventPubkey: string }) {
|
||||||
eventID,
|
|
||||||
eventPubkey,
|
|
||||||
}: {
|
|
||||||
eventID: string;
|
|
||||||
eventPubkey: string;
|
|
||||||
}) {
|
|
||||||
const { publish } = useNostr();
|
const { publish } = useNostr();
|
||||||
const [reaction, setReaction] = useState(0);
|
const [reaction, setReaction] = useState(0);
|
||||||
const [isReact, setIsReact] = useState(false);
|
const [isReact, setIsReact] = useState(false);
|
||||||
|
|
||||||
const $currentUser: any = useStore(currentUser);
|
const [currentUser]: any = useLocalStorage('current-user');
|
||||||
const pubkey = $currentUser.pubkey;
|
const pubkey = currentUser.pubkey;
|
||||||
const privkey = $currentUser.privkey;
|
const privkey = currentUser.privkey;
|
||||||
|
|
||||||
const { onEvent } = useNostrEvents({
|
const { onEvent } = useNostrEvents({
|
||||||
filter: {
|
filter: {
|
||||||
@ -65,15 +57,9 @@ export default function Reaction({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button onClick={(e) => handleReaction(e)} className="group flex w-16 items-center gap-1.5 text-sm text-zinc-500">
|
||||||
onClick={(e) => handleReaction(e)}
|
|
||||||
className="group flex w-16 items-center gap-1.5 text-sm text-zinc-500">
|
|
||||||
<div className="rounded-lg p-1 group-hover:bg-zinc-600">
|
<div className="rounded-lg p-1 group-hover:bg-zinc-600">
|
||||||
{isReact ? (
|
{isReact ? <HeartFilledIcon className="h-4 w-4 group-hover:text-red-400" /> : <HeartIcon className="h-4 w-4 text-zinc-500" />}
|
||||||
<HeartFilledIcon className="h-4 w-4 group-hover:text-red-400" />
|
|
||||||
) : (
|
|
||||||
<HeartIcon className="h-4 w-4 text-zinc-500" />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<span>{reaction}</span>
|
<span>{reaction}</span>
|
||||||
</button>
|
</button>
|
||||||
|
@ -2,12 +2,10 @@
|
|||||||
import AccountBar from '@components/accountBar';
|
import AccountBar from '@components/accountBar';
|
||||||
import ActiveLink from '@components/activeLink';
|
import ActiveLink from '@components/activeLink';
|
||||||
|
|
||||||
import { currentUser } from '@stores/currentUser';
|
import { useLocalStorage } from '@rehooks/local-storage';
|
||||||
|
|
||||||
import { useStore } from '@nanostores/react';
|
|
||||||
|
|
||||||
export default function UserLayout({ children }: { children: React.ReactNode }) {
|
export default function UserLayout({ children }: { children: React.ReactNode }) {
|
||||||
const $currentUser: any = useStore(currentUser);
|
const [currentUser]: any = useLocalStorage('current-user');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full flex-row">
|
<div className="flex h-full w-full flex-row">
|
||||||
@ -27,13 +25,13 @@ export default function UserLayout({ children }: { children: React.ReactNode })
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-1 text-zinc-500">
|
<div className="flex flex-col gap-1 text-zinc-500">
|
||||||
<ActiveLink
|
<ActiveLink
|
||||||
href={`/profile/${$currentUser.pubkey}`}
|
href={`/profile/${currentUser.pubkey}`}
|
||||||
activeClassName="ring-1 ring-white/10 dark:bg-zinc-900 dark:text-white"
|
activeClassName="ring-1 ring-white/10 dark:bg-zinc-900 dark:text-white"
|
||||||
className="flex h-10 items-center gap-1 rounded-lg px-2.5 text-sm font-medium hover:bg-zinc-900">
|
className="flex h-10 items-center gap-1 rounded-lg px-2.5 text-sm font-medium hover:bg-zinc-900">
|
||||||
<span>Personal Page</span>
|
<span>Personal Page</span>
|
||||||
</ActiveLink>
|
</ActiveLink>
|
||||||
<ActiveLink
|
<ActiveLink
|
||||||
href={`/profile/update?pubkey=${$currentUser.pubkey}`}
|
href={`/profile/update?pubkey=${currentUser.pubkey}`}
|
||||||
activeClassName="ring-1 ring-white/10 dark:bg-zinc-900 dark:text-white"
|
activeClassName="ring-1 ring-white/10 dark:bg-zinc-900 dark:text-white"
|
||||||
className="flex h-10 items-center gap-1 rounded-lg px-2.5 text-sm font-medium hover:bg-zinc-900">
|
className="flex h-10 items-center gap-1 rounded-lg px-2.5 text-sm font-medium hover:bg-zinc-900">
|
||||||
<span>Update Profile</span>
|
<span>Update Profile</span>
|
||||||
|
@ -2,9 +2,7 @@
|
|||||||
import DatabaseProvider from '@components/contexts/database';
|
import DatabaseProvider from '@components/contexts/database';
|
||||||
import RelayProvider from '@components/contexts/relay';
|
import RelayProvider from '@components/contexts/relay';
|
||||||
|
|
||||||
import { relays } from '@stores/relays';
|
import { useLocalStorage } from '@rehooks/local-storage';
|
||||||
|
|
||||||
import { useStore } from '@nanostores/react';
|
|
||||||
import type { NextPage } from 'next';
|
import type { NextPage } from 'next';
|
||||||
import type { AppProps } from 'next/app';
|
import type { AppProps } from 'next/app';
|
||||||
import { ReactElement, ReactNode } from 'react';
|
import { ReactElement, ReactNode } from 'react';
|
||||||
@ -23,12 +21,12 @@ type AppPropsWithLayout = AppProps & {
|
|||||||
export default function MyApp({ Component, pageProps }: AppPropsWithLayout) {
|
export default function MyApp({ Component, pageProps }: AppPropsWithLayout) {
|
||||||
// Use the layout defined at the page level, if available
|
// Use the layout defined at the page level, if available
|
||||||
const getLayout = Component.getLayout ?? ((page) => page);
|
const getLayout = Component.getLayout ?? ((page) => page);
|
||||||
// Get all relays
|
// Get relays from localstorage
|
||||||
const $relays = useStore(relays);
|
const [relays] = useLocalStorage('relays');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DatabaseProvider>
|
<DatabaseProvider>
|
||||||
<RelayProvider relays={$relays}>{getLayout(<Component {...pageProps} />)}</RelayProvider>
|
<RelayProvider relays={relays}>{getLayout(<Component {...pageProps} />)}</RelayProvider>
|
||||||
</DatabaseProvider>
|
</DatabaseProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,90 +2,32 @@
|
|||||||
import BaseLayout from '@layouts/baseLayout';
|
import BaseLayout from '@layouts/baseLayout';
|
||||||
import FullLayout from '@layouts/fullLayout';
|
import FullLayout from '@layouts/fullLayout';
|
||||||
|
|
||||||
import { DatabaseContext } from '@components/contexts/database';
|
|
||||||
|
|
||||||
import { currentUser } from '@stores/currentUser';
|
|
||||||
import { follows } from '@stores/follows';
|
|
||||||
|
|
||||||
import LumeSymbol from '@assets/icons/Lume';
|
import LumeSymbol from '@assets/icons/Lume';
|
||||||
|
import { useLocalStorage } from '@rehooks/local-storage';
|
||||||
import { isPermissionGranted, requestPermission, sendNotification } from '@tauri-apps/api/notification';
|
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useCallback, useContext, useEffect, useState } from 'react';
|
import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useEffect, useState } from 'react';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const db: any = useContext(DatabaseContext);
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const [currentUser]: any = useLocalStorage('current-user');
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
const requestNotification = useCallback(async () => {
|
|
||||||
// NOTE: notification don't work in dev mode (only affect MacOS)
|
|
||||||
// ref: https://github.com/tauri-apps/tauri/issues/4965
|
|
||||||
let permissionGranted = await isPermissionGranted();
|
|
||||||
if (!permissionGranted) {
|
|
||||||
const permission = await requestPermission();
|
|
||||||
permissionGranted = permission === 'granted';
|
|
||||||
}
|
|
||||||
if (permissionGranted) {
|
|
||||||
sendNotification({ title: 'Lume', body: 'Nostr is awesome' });
|
|
||||||
}
|
|
||||||
|
|
||||||
return permissionGranted;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const getAccount = useCallback(async () => {
|
|
||||||
const result = await db.select(`SELECT * FROM accounts ASC LIMIT 1`);
|
|
||||||
return result;
|
|
||||||
}, [db]);
|
|
||||||
|
|
||||||
const getFollows = useCallback(
|
|
||||||
async (account: { id: string }) => {
|
|
||||||
const arr = [];
|
|
||||||
const result: any = await db.select(`SELECT pubkey FROM follows WHERE account = "${account.id}"`);
|
|
||||||
|
|
||||||
result.forEach((item: { pubkey: string }) => {
|
|
||||||
arr.push(item.pubkey);
|
|
||||||
});
|
|
||||||
|
|
||||||
return arr;
|
|
||||||
},
|
|
||||||
[db]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Explain:
|
|
||||||
// Step 1: request allow notification from system
|
|
||||||
// Step 2: get first account. #TODO: get last used account instead (part of multi account feature)
|
|
||||||
// Step 3: get follows by account
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
requestNotification().then(() => {
|
console.log(currentUser);
|
||||||
getAccount()
|
if (!currentUser) {
|
||||||
.then((res: any) => {
|
setTimeout(() => {
|
||||||
if (res.length === 0) {
|
setLoading(false);
|
||||||
setTimeout(() => {
|
router.push('/onboarding');
|
||||||
setLoading(false);
|
}, 1500);
|
||||||
router.push('/onboarding');
|
} else {
|
||||||
}, 1500);
|
setTimeout(() => {
|
||||||
} else {
|
setLoading(false);
|
||||||
// store current user in localstorage
|
router.push('/feed/following');
|
||||||
currentUser.set(res[0]);
|
}, 1500);
|
||||||
getFollows(res[0])
|
}
|
||||||
.then(async (res) => {
|
}, [currentUser, router]);
|
||||||
// store follows in localstorage
|
|
||||||
follows.set(res);
|
|
||||||
// redirect to newsfeed
|
|
||||||
setTimeout(() => {
|
|
||||||
setLoading(false);
|
|
||||||
router.push('/feed/following');
|
|
||||||
}, 1500);
|
|
||||||
})
|
|
||||||
.catch(console.error);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(console.error);
|
|
||||||
});
|
|
||||||
}, [requestNotification, getAccount, getFollows, router]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex h-full flex-col items-center justify-between">
|
<div className="relative flex h-full flex-col items-center justify-between">
|
||||||
|
@ -5,16 +5,13 @@ import OnboardingLayout from '@layouts/onboardingLayout';
|
|||||||
import { DatabaseContext } from '@components/contexts/database';
|
import { DatabaseContext } from '@components/contexts/database';
|
||||||
import { RelayContext } from '@components/contexts/relay';
|
import { RelayContext } from '@components/contexts/relay';
|
||||||
|
|
||||||
import { currentUser } from '@stores/currentUser';
|
|
||||||
import { relays } from '@stores/relays';
|
|
||||||
|
|
||||||
import { useStore } from '@nanostores/react';
|
|
||||||
import { EyeClosedIcon, EyeOpenIcon } from '@radix-ui/react-icons';
|
import { EyeClosedIcon, EyeOpenIcon } from '@radix-ui/react-icons';
|
||||||
|
import { useLocalStorage, writeStorage } from '@rehooks/local-storage';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { generatePrivateKey, getEventHash, getPublicKey, nip19, signEvent } from 'nostr-tools';
|
import { generatePrivateKey, getEventHash, getPublicKey, nip19, signEvent } from 'nostr-tools';
|
||||||
import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useContext, useState } from 'react';
|
import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useCallback, useContext, useMemo, useState } from 'react';
|
||||||
import { Config, names, uniqueNamesGenerator } from 'unique-names-generator';
|
import { Config, names, uniqueNamesGenerator } from 'unique-names-generator';
|
||||||
|
|
||||||
const config: Config = {
|
const config: Config = {
|
||||||
@ -22,11 +19,12 @@ const config: Config = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const db: any = useContext(DatabaseContext);
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const { db }: any = useContext(DatabaseContext);
|
||||||
const relayPool: any = useContext(RelayContext);
|
const relayPool: any = useContext(RelayContext);
|
||||||
const $relays = useStore(relays);
|
|
||||||
|
const [relays] = useLocalStorage('relays');
|
||||||
|
|
||||||
const [type, setType] = useState('password');
|
const [type, setType] = useState('password');
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@ -47,13 +45,22 @@ export default function Page() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// auto-generated profile
|
// auto-generated profile
|
||||||
const data = {
|
const data = useMemo(
|
||||||
display_name: name,
|
() => ({
|
||||||
name: name,
|
display_name: name,
|
||||||
username: name.toLowerCase(),
|
name: name,
|
||||||
picture: 'https://bafybeidfsbrzqbvontmucteomoz2rkrxugu462l5hyhh6uioslkfzzs4oq.ipfs.w3s.link/avatar-11.png',
|
username: name.toLowerCase(),
|
||||||
banner: 'https://bafybeiacwit7hjmdefqggxqtgh6ht5dhth7ndptwn2msl5kpkodudsr7py.ipfs.w3s.link/banner-1.jpg',
|
picture: 'https://bafybeidfsbrzqbvontmucteomoz2rkrxugu462l5hyhh6uioslkfzzs4oq.ipfs.w3s.link/avatar-11.png',
|
||||||
};
|
banner: 'https://bafybeiacwit7hjmdefqggxqtgh6ht5dhth7ndptwn2msl5kpkodudsr7py.ipfs.w3s.link/banner-1.jpg',
|
||||||
|
}),
|
||||||
|
[name]
|
||||||
|
);
|
||||||
|
|
||||||
|
const insertDB = useCallback(async () => {
|
||||||
|
await db.execute(
|
||||||
|
`INSERT INTO accounts (id, privkey, npub, nsec, metadata) VALUES ("${pubKey}", "${privKey}", "${npub}", "${nsec}", '${JSON.stringify(data)}')`
|
||||||
|
);
|
||||||
|
}, [data, db, npub, nsec, privKey, pubKey]);
|
||||||
|
|
||||||
const createAccount = async () => {
|
const createAccount = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@ -68,27 +75,25 @@ export default function Page() {
|
|||||||
};
|
};
|
||||||
event.id = getEventHash(event);
|
event.id = getEventHash(event);
|
||||||
event.sig = signEvent(event, privKey);
|
event.sig = signEvent(event, privKey);
|
||||||
// publish to relays
|
|
||||||
relayPool.publish(event, $relays);
|
|
||||||
|
|
||||||
// save account to database
|
insertDB()
|
||||||
await db.execute(
|
.then(() => {
|
||||||
`INSERT INTO accounts (id, privkey, npub, nsec, metadata) VALUES ("${pubKey}", "${privKey}", "${npub}", "${nsec}", '${JSON.stringify(data)}')`
|
// publish to relays
|
||||||
);
|
relayPool.publish(event, relays);
|
||||||
|
// set currentUser in global state
|
||||||
// set currentUser in global state
|
writeStorage('current-user', {
|
||||||
currentUser.set({
|
metadata: JSON.stringify(data),
|
||||||
metadata: JSON.stringify(data),
|
npub: npub,
|
||||||
npub: npub,
|
privkey: privKey,
|
||||||
privkey: privKey,
|
pubkey: pubKey,
|
||||||
pubkey: pubKey,
|
});
|
||||||
});
|
// redirect to pre-follow
|
||||||
|
setTimeout(() => {
|
||||||
// redirect to pre-follow
|
setLoading(false);
|
||||||
setTimeout(() => {
|
router.push('/onboarding/create/pre-follows');
|
||||||
setLoading(false);
|
}, 1500);
|
||||||
router.push('/onboarding/following');
|
})
|
||||||
}, 1500);
|
.catch(console.error);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
@ -6,28 +6,25 @@ import { DatabaseContext } from '@components/contexts/database';
|
|||||||
|
|
||||||
import { truncate } from '@utils/truncate';
|
import { truncate } from '@utils/truncate';
|
||||||
|
|
||||||
import { currentUser } from '@stores/currentUser';
|
|
||||||
|
|
||||||
import data from '@assets/directory.json';
|
import data from '@assets/directory.json';
|
||||||
|
|
||||||
import { useStore } from '@nanostores/react';
|
|
||||||
import { CheckCircledIcon } from '@radix-ui/react-icons';
|
import { CheckCircledIcon } from '@radix-ui/react-icons';
|
||||||
|
import { useLocalStorage } from '@rehooks/local-storage';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { nip19 } from 'nostr-tools';
|
import { nip19 } from 'nostr-tools';
|
||||||
import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useContext, useState } from 'react';
|
import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useContext, useState } from 'react';
|
||||||
|
|
||||||
|
const shuffle = (arr: { name: string; avatar: string; npub: string }[]) => [...arr].sort(() => Math.random() - 0.5);
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const db: any = useContext(DatabaseContext);
|
const db: any = useContext(DatabaseContext);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const shuffle = (arr) => [...arr].sort(() => Math.random() - 0.5);
|
|
||||||
|
|
||||||
const [follow, setFollow] = useState([]);
|
const [follow, setFollow] = useState([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [list] = useState(shuffle(data));
|
const [list] = useState(shuffle(data));
|
||||||
const $currentUser: any = useStore(currentUser);
|
const [currentUser]: any = useLocalStorage('current-user');
|
||||||
|
|
||||||
const followUser = (e) => {
|
const followUser = (e) => {
|
||||||
const npub = e.currentTarget.getAttribute('data-npub');
|
const npub = e.currentTarget.getAttribute('data-npub');
|
||||||
@ -36,11 +33,11 @@ export default function Page() {
|
|||||||
|
|
||||||
const insertDB = async () => {
|
const insertDB = async () => {
|
||||||
// self follow
|
// self follow
|
||||||
await db.execute(`INSERT INTO follows (pubkey, account, kind) VALUES ("${$currentUser.pubkey}", "${$currentUser.pubkey}", "0")`);
|
await db.execute(`INSERT INTO follows (pubkey, account, kind) VALUES ("${currentUser.pubkey}", "${currentUser.pubkey}", "0")`);
|
||||||
// follow selected
|
// follow selected
|
||||||
follow.forEach(async (npub) => {
|
follow.forEach(async (npub) => {
|
||||||
const { data } = nip19.decode(npub);
|
const { data } = nip19.decode(npub);
|
||||||
await db.execute(`INSERT INTO follows (pubkey, account, kind) VALUES ("${data}", "${$currentUser.pubkey}", "0")`);
|
await db.execute(`INSERT INTO follows (pubkey, account, kind) VALUES ("${data}", "${currentUser.pubkey}", "0")`);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
@ -1,116 +0,0 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
import BaseLayout from '@layouts/baseLayout';
|
|
||||||
import OnboardingLayout from '@layouts/onboardingLayout';
|
|
||||||
|
|
||||||
import { DatabaseContext } from '@components/contexts/database';
|
|
||||||
import { RelayContext } from '@components/contexts/relay';
|
|
||||||
|
|
||||||
import { relays } from '@stores/relays';
|
|
||||||
|
|
||||||
import { useStore } from '@nanostores/react';
|
|
||||||
import { motion } from 'framer-motion';
|
|
||||||
import { useRouter } from 'next/router';
|
|
||||||
import { getPublicKey, nip19 } from 'nostr-tools';
|
|
||||||
import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useCallback, useContext, useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
export default function Page() {
|
|
||||||
const db: any = useContext(DatabaseContext);
|
|
||||||
const relayPool: any = useContext(RelayContext);
|
|
||||||
const $relays = useStore(relays);
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
const { privkey }: any = router.query;
|
|
||||||
|
|
||||||
const [account, setAccount] = useState(null);
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
|
|
||||||
const pubkey = privkey ? getPublicKey(privkey) : null;
|
|
||||||
const npub = privkey ? nip19.npubEncode(pubkey) : null;
|
|
||||||
const nsec = privkey ? nip19.nsecEncode(privkey) : null;
|
|
||||||
|
|
||||||
relayPool.subscribe(
|
|
||||||
[
|
|
||||||
{
|
|
||||||
authors: [pubkey],
|
|
||||||
kinds: [0],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
$relays,
|
|
||||||
(event: any) => {
|
|
||||||
const metadata = JSON.parse(event.content);
|
|
||||||
setAccount(metadata);
|
|
||||||
},
|
|
||||||
undefined,
|
|
||||||
(events: any, relayURL: any) => {
|
|
||||||
console.log(events, relayURL);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const insertDB = useCallback(async () => {
|
|
||||||
// save account to database
|
|
||||||
const metadata = JSON.stringify(account);
|
|
||||||
await db.execute(
|
|
||||||
`INSERT INTO accounts (id, privkey, npub, nsec, metadata) VALUES ("${pubkey}", "${privkey}", "${npub}", "${nsec}", '${metadata}')`
|
|
||||||
);
|
|
||||||
await db.close();
|
|
||||||
}, [account, db, npub, nsec, privkey, pubkey]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
if (account !== null) {
|
|
||||||
insertDB()
|
|
||||||
.then(() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
setLoading(false);
|
|
||||||
router.push({
|
|
||||||
pathname: '/onboarding/fetch-follows',
|
|
||||||
query: { pubkey: pubkey },
|
|
||||||
});
|
|
||||||
}, 1500);
|
|
||||||
})
|
|
||||||
.catch(console.error);
|
|
||||||
}
|
|
||||||
}, [account, insertDB, npub, nsec, privkey, pubkey, router]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex h-full flex-col justify-between px-8">
|
|
||||||
<div>{/* spacer */}</div>
|
|
||||||
<motion.div layoutId="form">
|
|
||||||
<div className="mb-8 flex flex-col gap-3">
|
|
||||||
<motion.h1 layoutId="title" className="bg-gradient-to-br from-zinc-200 to-zinc-400 bg-clip-text text-3xl font-medium text-transparent">
|
|
||||||
Fetching your profile...
|
|
||||||
</motion.h1>
|
|
||||||
<motion.h2 layoutId="subtitle" className="w-3/4 text-zinc-400">
|
|
||||||
As long as you have private key, you alway can sync your profile on every nostr client, so please keep your key safely
|
|
||||||
</motion.h2>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
<motion.div layoutId="action" className="pb-5">
|
|
||||||
<div className="flex h-10 items-center">
|
|
||||||
{loading === true ? (
|
|
||||||
<svg className="h-5 w-5 animate-spin text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
||||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
|
||||||
<path
|
|
||||||
className="opacity-75"
|
|
||||||
fill="currentColor"
|
|
||||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
||||||
</svg>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Page.getLayout = function getLayout(
|
|
||||||
page: string | number | boolean | ReactElement<unknown, string | JSXElementConstructor<unknown>> | ReactFragment | ReactPortal
|
|
||||||
) {
|
|
||||||
return (
|
|
||||||
<BaseLayout>
|
|
||||||
<OnboardingLayout>{page}</OnboardingLayout>
|
|
||||||
</BaseLayout>
|
|
||||||
);
|
|
||||||
};
|
|
@ -10,9 +10,7 @@ export default function Page() {
|
|||||||
<div className="flex h-full flex-col justify-between px-8">
|
<div className="flex h-full flex-col justify-between px-8">
|
||||||
<div>{/* spacer */}</div>
|
<div>{/* spacer */}</div>
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
<motion.h1
|
<motion.h1 layoutId="title" className="bg-gradient-to-br from-zinc-200 to-zinc-400 bg-clip-text text-3xl font-medium text-transparent">
|
||||||
layoutId="title"
|
|
||||||
className="bg-gradient-to-br from-zinc-200 to-zinc-400 bg-clip-text text-3xl font-medium text-transparent">
|
|
||||||
Other social network require email/password
|
Other social network require email/password
|
||||||
<br />
|
<br />
|
||||||
nostr use{' '}
|
nostr use{' '}
|
||||||
@ -21,8 +19,8 @@ export default function Page() {
|
|||||||
</span>
|
</span>
|
||||||
</motion.h1>
|
</motion.h1>
|
||||||
<motion.h2 layoutId="subtitle" className="w-3/4 text-zinc-400">
|
<motion.h2 layoutId="subtitle" className="w-3/4 text-zinc-400">
|
||||||
If you have used nostr before, you can import your own private key. Otherwise, you can
|
If you have used nostr before, you can import your own private key. Otherwise, you can create a new key or use auto-generated account
|
||||||
create a new key or use auto-generated account created by system.
|
created by system.
|
||||||
</motion.h2>
|
</motion.h2>
|
||||||
<motion.div layoutId="form"></motion.div>
|
<motion.div layoutId="form"></motion.div>
|
||||||
<motion.div layoutId="action" className="mt-4 flex gap-2">
|
<motion.div layoutId="action" className="mt-4 flex gap-2">
|
||||||
@ -32,7 +30,7 @@ export default function Page() {
|
|||||||
Create new key
|
Create new key
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="/onboarding/import"
|
href="/onboarding/login"
|
||||||
className="hover:bg-zinc-900/2.5 transform rounded-lg border border-black/5 bg-zinc-800 px-3.5 py-2 font-medium ring-1 ring-inset ring-zinc-900/10 hover:text-zinc-900 active:translate-y-1 dark:text-zinc-300 dark:ring-white/10 dark:hover:bg-zinc-700 dark:hover:text-white">
|
className="hover:bg-zinc-900/2.5 transform rounded-lg border border-black/5 bg-zinc-800 px-3.5 py-2 font-medium ring-1 ring-inset ring-zinc-900/10 hover:text-zinc-900 active:translate-y-1 dark:text-zinc-300 dark:ring-white/10 dark:hover:bg-zinc-700 dark:hover:text-white">
|
||||||
Login with private key
|
Login with private key
|
||||||
</Link>
|
</Link>
|
||||||
@ -44,13 +42,7 @@ export default function Page() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Page.getLayout = function getLayout(
|
Page.getLayout = function getLayout(
|
||||||
page:
|
page: string | number | boolean | ReactElement<unknown, string | JSXElementConstructor<unknown>> | ReactFragment | ReactPortal
|
||||||
| string
|
|
||||||
| number
|
|
||||||
| boolean
|
|
||||||
| ReactElement<unknown, string | JSXElementConstructor<unknown>>
|
|
||||||
| ReactFragment
|
|
||||||
| ReactPortal
|
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<BaseLayout>
|
<BaseLayout>
|
||||||
|
@ -5,35 +5,69 @@ import OnboardingLayout from '@layouts/onboardingLayout';
|
|||||||
import { DatabaseContext } from '@components/contexts/database';
|
import { DatabaseContext } from '@components/contexts/database';
|
||||||
import { RelayContext } from '@components/contexts/relay';
|
import { RelayContext } from '@components/contexts/relay';
|
||||||
|
|
||||||
import { relays } from '@stores/relays';
|
import { useLocalStorage } from '@rehooks/local-storage';
|
||||||
|
|
||||||
import { useStore } from '@nanostores/react';
|
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
|
import Link from 'next/link';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useContext, useEffect, useState } from 'react';
|
import { getPublicKey, nip19 } from 'nostr-tools';
|
||||||
|
import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useCallback, useContext, useMemo, useState } from 'react';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const db: any = useContext(DatabaseContext);
|
const { db }: any = useContext(DatabaseContext);
|
||||||
const relayPool: any = useContext(RelayContext);
|
const relayPool: any = useContext(RelayContext);
|
||||||
const $relays = useStore(relays);
|
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [relays] = useLocalStorage('relays');
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { pubkey }: any = router.query;
|
const { privkey }: any = router.query;
|
||||||
|
|
||||||
const [follows, setFollows] = useState([null]);
|
const pubkey = useMemo(() => (privkey ? getPublicKey(privkey) : null), [privkey]);
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
|
// save account to database
|
||||||
|
const insertAccount = useCallback(
|
||||||
|
async (metadata) => {
|
||||||
|
if (loading === false) {
|
||||||
|
const npub = privkey ? nip19.npubEncode(pubkey) : null;
|
||||||
|
const nsec = privkey ? nip19.nsecEncode(privkey) : null;
|
||||||
|
await db.execute(
|
||||||
|
`INSERT OR IGNORE INTO accounts (id, privkey, npub, nsec, metadata) VALUES ("${pubkey}", "${privkey}", "${npub}", "${nsec}", '${metadata}')`
|
||||||
|
);
|
||||||
|
setLoading(true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[db, privkey, pubkey, loading]
|
||||||
|
);
|
||||||
|
|
||||||
|
// save follows to database
|
||||||
|
const insertFollows = useCallback(
|
||||||
|
async (follows) => {
|
||||||
|
follows.forEach(async (item) => {
|
||||||
|
if (item) {
|
||||||
|
await db.execute(`INSERT OR IGNORE INTO follows (pubkey, account, kind) VALUES ("${item[1]}", "${pubkey}", "0")`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[db, pubkey]
|
||||||
|
);
|
||||||
|
|
||||||
relayPool.subscribe(
|
relayPool.subscribe(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
authors: [pubkey],
|
authors: [pubkey],
|
||||||
kinds: [0],
|
kinds: [0, 3],
|
||||||
since: 0,
|
since: 0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
$relays,
|
relays,
|
||||||
(event: any) => {
|
(event: any) => {
|
||||||
setFollows(event.tags);
|
if (event.kind === 0) {
|
||||||
|
insertAccount(event.content);
|
||||||
|
} else {
|
||||||
|
if (event.tags.length > 0) {
|
||||||
|
insertFollows(event.tags);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
(events: any, relayURL: any) => {
|
(events: any, relayURL: any) => {
|
||||||
@ -41,39 +75,16 @@ export default function Page() {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
const insertDB = async () => {
|
|
||||||
follows.forEach(async (item) => {
|
|
||||||
if (item) {
|
|
||||||
await db.execute(`INSERT OR IGNORE INTO follows (pubkey, account, kind) VALUES ("${item[1]}", "${pubkey}", "0")`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (follows !== null && follows.length > 0) {
|
|
||||||
insertDB()
|
|
||||||
.then(() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
setLoading(false);
|
|
||||||
router.push('/');
|
|
||||||
}, 1500);
|
|
||||||
})
|
|
||||||
.catch(console.error);
|
|
||||||
}
|
|
||||||
}, [db, follows, pubkey, router]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full flex-col justify-between px-8">
|
<div className="flex h-full flex-col justify-between px-8">
|
||||||
<div>{/* spacer */}</div>
|
<div>{/* spacer */}</div>
|
||||||
<motion.div layoutId="form">
|
<motion.div layoutId="form">
|
||||||
<div className="mb-8 flex flex-col gap-3">
|
<div className="mb-8 flex flex-col gap-3">
|
||||||
<motion.h1 layoutId="title" className="bg-gradient-to-br from-zinc-200 to-zinc-400 bg-clip-text text-3xl font-medium text-transparent">
|
<motion.h1 layoutId="title" className="bg-gradient-to-br from-zinc-200 to-zinc-400 bg-clip-text text-3xl font-medium text-transparent">
|
||||||
Fetching your follows...
|
Fetching your profile...
|
||||||
</motion.h1>
|
</motion.h1>
|
||||||
<motion.h2 layoutId="subtitle" className="w-3/4 text-zinc-400">
|
<motion.h2 layoutId="subtitle" className="w-3/4 text-zinc-400">
|
||||||
Not only profile, every nostr client can sync your follows list when you move to a new client, so please keep your key safely (again)
|
As long as you have private key, you alway can sync your profile and follows list on every nostr client, so please keep your key safely
|
||||||
</motion.h2>
|
</motion.h2>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@ -88,7 +99,11 @@ export default function Page() {
|
|||||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<Link
|
||||||
|
href="/"
|
||||||
|
className="transform rounded-lg bg-[radial-gradient(ellipse_at_bottom_right,_var(--tw-gradient-stops))] from-gray-300 via-fuchsia-600 to-orange-600 px-3.5 py-2 font-medium active:translate-y-1 disabled:cursor-not-allowed disabled:opacity-30">
|
||||||
|
<span className="drop-shadow-lg">Finish</span>
|
||||||
|
</Link>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
@ -44,7 +44,7 @@ export default function Page() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
router.push({
|
router.push({
|
||||||
pathname: '/onboarding/fetch-profile',
|
pathname: '/onboarding/login/fetch',
|
||||||
query: { privkey: privkey },
|
query: { privkey: privkey },
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
@ -2,9 +2,7 @@
|
|||||||
import BaseLayout from '@layouts/baseLayout';
|
import BaseLayout from '@layouts/baseLayout';
|
||||||
import UserLayout from '@layouts/userLayout';
|
import UserLayout from '@layouts/userLayout';
|
||||||
|
|
||||||
import { currentUser } from '@stores/currentUser';
|
import { useLocalStorage } from '@rehooks/local-storage';
|
||||||
|
|
||||||
import { useStore } from '@nanostores/react';
|
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { dateToUnix, useNostr } from 'nostr-react';
|
import { dateToUnix, useNostr } from 'nostr-react';
|
||||||
import { getEventHash, signEvent } from 'nostr-tools';
|
import { getEventHash, signEvent } from 'nostr-tools';
|
||||||
@ -28,11 +26,8 @@ export default function Page() {
|
|||||||
const { publish } = useNostr();
|
const { publish } = useNostr();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const $currentUser: any = useStore(currentUser);
|
const [currentUser]: any = useLocalStorage('current-user');
|
||||||
const profile =
|
const profile = currentUser.metadata !== undefined ? JSON.parse(currentUser.metadata) : { display_name: null, username: null };
|
||||||
$currentUser.metadata !== undefined
|
|
||||||
? JSON.parse($currentUser.metadata)
|
|
||||||
: { display_name: null, username: null };
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
@ -48,28 +43,24 @@ export default function Page() {
|
|||||||
content: JSON.stringify(data),
|
content: JSON.stringify(data),
|
||||||
created_at: dateToUnix(),
|
created_at: dateToUnix(),
|
||||||
kind: 0,
|
kind: 0,
|
||||||
pubkey: $currentUser.pubkey,
|
pubkey: currentUser.pubkey,
|
||||||
tags: [],
|
tags: [],
|
||||||
};
|
};
|
||||||
event.id = getEventHash(event);
|
event.id = getEventHash(event);
|
||||||
event.sig = signEvent(event, $currentUser.privkey);
|
event.sig = signEvent(event, currentUser.privkey);
|
||||||
publish(event);
|
publish(event);
|
||||||
|
|
||||||
// save account to database
|
// save account to database
|
||||||
const db = await Database.load('sqlite:lume.db');
|
const db = await Database.load('sqlite:lume.db');
|
||||||
await db.execute(
|
await db.execute(`UPDATE accounts SET metadata = '${JSON.stringify(data)}' WHERE pubkey = "${currentUser.pubkey}"`);
|
||||||
`UPDATE accounts SET metadata = '${JSON.stringify(data)}' WHERE pubkey = "${
|
|
||||||
$currentUser.pubkey
|
|
||||||
}"`
|
|
||||||
);
|
|
||||||
await db.close();
|
await db.close();
|
||||||
|
|
||||||
// set currentUser in global state
|
// set currentUser in global state
|
||||||
currentUser.set({
|
currentUser.set({
|
||||||
metadata: JSON.stringify(data),
|
metadata: JSON.stringify(data),
|
||||||
npub: $currentUser.npub,
|
npub: currentUser.npub,
|
||||||
privkey: $currentUser.privkey,
|
privkey: currentUser.privkey,
|
||||||
pubkey: $currentUser.pubkey,
|
pubkey: currentUser.pubkey,
|
||||||
});
|
});
|
||||||
|
|
||||||
// redirect to newsfeed
|
// redirect to newsfeed
|
||||||
@ -80,16 +71,11 @@ export default function Page() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form onSubmit={handleSubmit(onSubmit)} className="flex h-full w-full flex-col justify-between px-6">
|
||||||
onSubmit={handleSubmit(onSubmit)}
|
|
||||||
className="flex h-full w-full flex-col justify-between px-6">
|
|
||||||
<div className="mb-8 flex flex-col gap-3 pt-8">
|
<div className="mb-8 flex flex-col gap-3 pt-8">
|
||||||
<h1 className="bg-gradient-to-br from-zinc-200 to-zinc-400 bg-clip-text text-3xl font-medium text-transparent">
|
<h1 className="bg-gradient-to-br from-zinc-200 to-zinc-400 bg-clip-text text-3xl font-medium text-transparent">Update profile</h1>
|
||||||
Update profile
|
|
||||||
</h1>
|
|
||||||
<h2 className="w-3/4 text-zinc-400">
|
<h2 className="w-3/4 text-zinc-400">
|
||||||
Your profile will be published to all relays, as long as you have the private key, you
|
Your profile will be published to all relays, as long as you have the private key, you always can recover your profile in any client
|
||||||
always can recover your profile in any client
|
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<fieldset className="flex flex-col gap-2">
|
<fieldset className="flex flex-col gap-2">
|
||||||
@ -105,9 +91,7 @@ export default function Page() {
|
|||||||
className="relative w-full rounded-lg border border-black/5 px-3.5 py-2 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-500"
|
className="relative w-full rounded-lg border border-black/5 px-3.5 py-2 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm text-red-400">
|
<span className="text-sm text-red-400">{errors.display_name && <p>{errors.display_name.message}</p>}</span>
|
||||||
{errors.display_name && <p>{errors.display_name.message}</p>}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-4">
|
<div className="grid grid-cols-4">
|
||||||
@ -122,9 +106,7 @@ export default function Page() {
|
|||||||
className="relative w-full rounded-lg border border-black/5 px-3.5 py-2 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-500"
|
className="relative w-full rounded-lg border border-black/5 px-3.5 py-2 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm text-red-400">
|
<span className="text-sm text-red-400">{errors.name && <p>{errors.name.message}</p>}</span>
|
||||||
{errors.name && <p>{errors.name.message}</p>}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-4">
|
<div className="grid grid-cols-4">
|
||||||
@ -139,9 +121,7 @@ export default function Page() {
|
|||||||
className="relative w-full rounded-lg border border-black/5 px-3.5 py-2 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-500"
|
className="relative w-full rounded-lg border border-black/5 px-3.5 py-2 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm text-red-400">
|
<span className="text-sm text-red-400">{errors.username && <p>{errors.username.message}</p>}</span>
|
||||||
{errors.username && <p>{errors.username.message}</p>}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-4">
|
<div className="grid grid-cols-4">
|
||||||
@ -156,9 +136,7 @@ export default function Page() {
|
|||||||
className="relative w-full rounded-lg border border-black/5 px-3.5 py-2 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-500"
|
className="relative w-full rounded-lg border border-black/5 px-3.5 py-2 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm text-red-400">
|
<span className="text-sm text-red-400">{errors.picture && <p>{errors.picture.message}</p>}</span>
|
||||||
{errors.picture && <p>{errors.picture.message}</p>}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-4">
|
<div className="grid grid-cols-4">
|
||||||
@ -173,9 +151,7 @@ export default function Page() {
|
|||||||
className="relative w-full rounded-lg border border-black/5 px-3.5 py-2 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-500"
|
className="relative w-full rounded-lg border border-black/5 px-3.5 py-2 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm text-red-400">
|
<span className="text-sm text-red-400">{errors.banner && <p>{errors.banner.message}</p>}</span>
|
||||||
{errors.banner && <p>{errors.banner.message}</p>}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-4">
|
<div className="grid grid-cols-4">
|
||||||
@ -190,27 +166,15 @@ export default function Page() {
|
|||||||
className="relative h-24 w-full resize-none rounded-lg border border-black/5 px-3.5 py-2 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-500"
|
className="relative h-24 w-full resize-none rounded-lg border border-black/5 px-3.5 py-2 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm text-red-400">
|
<span className="text-sm text-red-400">{errors.about && <p>{errors.about.message}</p>}</span>
|
||||||
{errors.about && <p>{errors.about.message}</p>}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<div className="pb-5">
|
<div className="pb-5">
|
||||||
<div className="flex h-10 items-center">
|
<div className="flex h-10 items-center">
|
||||||
{loading === true ? (
|
{loading === true ? (
|
||||||
<svg
|
<svg className="h-5 w-5 animate-spin text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
className="h-5 w-5 animate-spin text-white"
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24">
|
|
||||||
<circle
|
|
||||||
className="opacity-25"
|
|
||||||
cx="12"
|
|
||||||
cy="12"
|
|
||||||
r="10"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth="4"></circle>
|
|
||||||
<path
|
<path
|
||||||
className="opacity-75"
|
className="opacity-75"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
@ -231,13 +195,7 @@ export default function Page() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Page.getLayout = function getLayout(
|
Page.getLayout = function getLayout(
|
||||||
page:
|
page: string | number | boolean | ReactElement<unknown, string | JSXElementConstructor<unknown>> | ReactFragment | ReactPortal
|
||||||
| string
|
|
||||||
| number
|
|
||||||
| boolean
|
|
||||||
| ReactElement<unknown, string | JSXElementConstructor<unknown>>
|
|
||||||
| ReactFragment
|
|
||||||
| ReactPortal
|
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<BaseLayout>
|
<BaseLayout>
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
import { persistentAtom } from '@nanostores/persistent';
|
|
||||||
|
|
||||||
export const currentUser = persistentAtom(
|
|
||||||
'currentUser',
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
encode: JSON.stringify,
|
|
||||||
decode: JSON.parse,
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,10 +0,0 @@
|
|||||||
import { persistentAtom } from '@nanostores/persistent';
|
|
||||||
|
|
||||||
export const follows = persistentAtom('follows', [], {
|
|
||||||
encode(value) {
|
|
||||||
return JSON.stringify(value);
|
|
||||||
},
|
|
||||||
decode(value) {
|
|
||||||
return JSON.parse(value);
|
|
||||||
},
|
|
||||||
});
|
|
@ -1,25 +0,0 @@
|
|||||||
import { persistentAtom } from '@nanostores/persistent';
|
|
||||||
|
|
||||||
export const relays = persistentAtom(
|
|
||||||
'relays',
|
|
||||||
[
|
|
||||||
'wss://relay.uselume.xyz',
|
|
||||||
'wss://nostr-pub.wellorder.net',
|
|
||||||
'wss://nostr.bongbong.com',
|
|
||||||
'wss://nostr.zebedee.cloud',
|
|
||||||
'wss://nostr.fmt.wiz.biz',
|
|
||||||
'wss://nostr.walletofsatoshi.com',
|
|
||||||
'wss://relay.snort.social',
|
|
||||||
'wss://offchain.pub',
|
|
||||||
'wss://nos.lol',
|
|
||||||
'wss://relay.damus.io',
|
|
||||||
],
|
|
||||||
{
|
|
||||||
encode(value) {
|
|
||||||
return JSON.stringify(value);
|
|
||||||
},
|
|
||||||
decode(value) {
|
|
||||||
return JSON.parse(value);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
@ -6,7 +6,6 @@
|
|||||||
"@layouts/*": ["src/layouts/*"],
|
"@layouts/*": ["src/layouts/*"],
|
||||||
"@components/*": ["src/components/*"],
|
"@components/*": ["src/components/*"],
|
||||||
"@utils/*": ["src/utils/*"],
|
"@utils/*": ["src/utils/*"],
|
||||||
"@stores/*": ["src/stores/*"],
|
|
||||||
"@assets/*": ["src/assets/*"]
|
"@assets/*": ["src/assets/*"]
|
||||||
},
|
},
|
||||||
"target": "es2017",
|
"target": "es2017",
|
||||||
|
Loading…
Reference in New Issue
Block a user