From b9bafc851e69f04af708cadcf46c702c383d338a Mon Sep 17 00:00:00 2001 From: Ren Amamiya <123083837+reyamir@users.noreply.github.com> Date: Sat, 22 Apr 2023 17:56:09 +0700 Subject: [PATCH] migrated to vite and vite-plugin-ssr --- .prettierrc | 1 + src-tauri/tauri.conf.json | 4 +- src/app/layout.tsx | 13 -- src/app/nostr/newsfeed/circle/page.tsx | 7 - src/app/nostr/newsfeed/note/page.tsx | 10 - src/app/nostr/user/page.tsx | 51 ------ src/app/onboarding/create/page.tsx | 169 ----------------- src/app/onboarding/login/page.tsx | 120 ------------ src/app/onboarding/login/step-2/page.tsx | 145 --------------- src/app/providers.tsx | 9 - src/components/activeLink.tsx | 25 --- src/components/appHeader/actions.tsx | 10 +- src/components/appHeader/index.tsx | 11 +- src/components/channels/channelList.tsx | 13 -- src/components/channels/channelListItem.tsx | 20 +- src/components/chats/chatList.tsx | 16 +- src/components/chats/chatListItem.tsx | 20 +- src/components/chats/chatModalUser.tsx | 14 +- src/components/chats/messageUser.tsx | 11 +- src/components/eventCollector.tsx | 2 - src/components/form/emojiPicker.tsx | 36 ---- src/components/imageWithFallback.tsx | 26 --- .../layouts/newsfeed.tsx} | 2 +- .../layouts/onboarding.tsx} | 0 src/components/link.tsx | 12 ++ .../multiAccounts/activeAccount.tsx | 14 +- .../multiAccounts/inactiveAccount.tsx | 9 +- src/components/multiAccounts/index.tsx | 13 +- src/components/navigation/channels.tsx | 2 - src/components/navigation/chats.tsx | 2 - src/components/navigation/newsfeed.tsx | 20 +- src/components/note/base.tsx | 7 +- src/components/note/meta/comment.tsx | 13 +- src/components/note/preview/image.tsx | 11 +- src/components/note/preview/link.tsx | 23 --- src/components/note/rootNote.tsx | 7 +- src/components/profile/followers.tsx | 2 - src/components/profile/follows.tsx | 2 - src/components/profile/metadata.tsx | 18 +- src/components/profile/notes.tsx | 2 - src/components/relaysProvider.tsx | 2 - src/components/user/base.tsx | 9 +- src/components/user/extend.tsx | 9 +- src/components/user/follow.tsx | 9 +- src/components/user/large.tsx | 7 +- src/components/user/mini.tsx | 9 +- src/components/user/quoteRepost.tsx | 9 +- .../page.tsx => pages/channel/index.page.tsx} | 25 +-- .../page.tsx => pages/chat/index.page.tsx} | 26 +-- src/{app/page.tsx => pages/index.page.tsx} | 55 +++--- src/pages/newsfeed/circle/index.page.tsx | 3 + .../newsfeed/following/index.page.tsx} | 53 +++--- src/pages/onboarding/create/index.page.tsx | 172 ++++++++++++++++++ .../onboarding/create/step-2/index.page.tsx} | 130 ++++++------- .../onboarding/index.page.tsx} | 93 +++++----- src/pages/onboarding/login/index.page.tsx | 121 ++++++++++++ .../onboarding/login/step-2/index.page.tsx | 144 +++++++++++++++ src/pages/user/index.page.tsx | 57 ++++++ src/renderer/_default.page.client.tsx | 20 ++ src/renderer/_default.page.server.tsx | 30 +++ src/renderer/_error.page.tsx | 17 ++ src/{ => renderer}/index.css | 0 src/renderer/shell.tsx | 17 ++ src/renderer/types.ts | 35 ++++ src/utils/hooks/usePageContext.tsx | 22 +++ tsconfig.json | 3 +- vite.config.ts | 2 +- 67 files changed, 911 insertions(+), 1060 deletions(-) delete mode 100644 src/app/layout.tsx delete mode 100644 src/app/nostr/newsfeed/circle/page.tsx delete mode 100644 src/app/nostr/newsfeed/note/page.tsx delete mode 100644 src/app/nostr/user/page.tsx delete mode 100644 src/app/onboarding/create/page.tsx delete mode 100644 src/app/onboarding/login/page.tsx delete mode 100644 src/app/onboarding/login/step-2/page.tsx delete mode 100644 src/app/providers.tsx delete mode 100644 src/components/activeLink.tsx delete mode 100644 src/components/form/emojiPicker.tsx delete mode 100644 src/components/imageWithFallback.tsx rename src/{app/nostr/layout.tsx => components/layouts/newsfeed.tsx} (95%) rename src/{app/onboarding/layout.tsx => components/layouts/onboarding.tsx} (100%) create mode 100644 src/components/link.tsx delete mode 100644 src/components/note/preview/link.tsx rename src/{app/nostr/channel/page.tsx => pages/channel/index.page.tsx} (82%) rename src/{app/nostr/chat/page.tsx => pages/chat/index.page.tsx} (73%) rename src/{app/page.tsx => pages/index.page.tsx} (86%) create mode 100644 src/pages/newsfeed/circle/index.page.tsx rename src/{app/nostr/newsfeed/following/page.tsx => pages/newsfeed/following/index.page.tsx} (71%) create mode 100644 src/pages/onboarding/create/index.page.tsx rename src/{app/onboarding/create/step-2/page.tsx => pages/onboarding/create/step-2/index.page.tsx} (61%) rename src/{app/onboarding/page.tsx => pages/onboarding/index.page.tsx} (61%) create mode 100644 src/pages/onboarding/login/index.page.tsx create mode 100644 src/pages/onboarding/login/step-2/index.page.tsx create mode 100644 src/pages/user/index.page.tsx create mode 100644 src/renderer/_default.page.client.tsx create mode 100644 src/renderer/_default.page.server.tsx create mode 100644 src/renderer/_error.page.tsx rename src/{ => renderer}/index.css (100%) create mode 100644 src/renderer/shell.tsx create mode 100644 src/renderer/types.ts create mode 100644 src/utils/hooks/usePageContext.tsx diff --git a/.prettierrc b/.prettierrc index 4ed3e6bf..b63ef5a4 100644 --- a/.prettierrc +++ b/.prettierrc @@ -15,6 +15,7 @@ "^@stores/(.*)$", "^@utils/(.*)$", "^@assets/(.*)$", + "^@renderer/(.*)$", "", "^[./]" ], diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 430b269c..44eda095 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -2,8 +2,8 @@ "build": { "beforeDevCommand": "pnpm dev", "beforeBuildCommand": "pnpm build", - "devPath": "http://localhost:1420", - "distDir": "../out", + "devPath": "http://localhost:3000", + "distDir": "../dist", "withGlobalTauri": true }, "package": { diff --git a/src/app/layout.tsx b/src/app/layout.tsx deleted file mode 100644 index 7a87fa9c..00000000 --- a/src/app/layout.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import '@assets/global.css'; - -import { Providers } from './providers'; - -export default function RootLayout({ children }: { children: React.ReactNode }) { - return ( - - - {children} - - - ); -} diff --git a/src/app/nostr/newsfeed/circle/page.tsx b/src/app/nostr/newsfeed/circle/page.tsx deleted file mode 100644 index a51233ce..00000000 --- a/src/app/nostr/newsfeed/circle/page.tsx +++ /dev/null @@ -1,7 +0,0 @@ -export default function Page() { - return ( -
-

Sorry, this feature under development, it will come in the next version

-
- ); -} diff --git a/src/app/nostr/newsfeed/note/page.tsx b/src/app/nostr/newsfeed/note/page.tsx deleted file mode 100644 index 276b0122..00000000 --- a/src/app/nostr/newsfeed/note/page.tsx +++ /dev/null @@ -1,10 +0,0 @@ -'use client'; - -import { useSearchParams } from 'next/navigation'; - -export default function Page() { - const searchParams = useSearchParams(); - const id = searchParams.get('event-id'); - - return
{id}
; -} diff --git a/src/app/nostr/user/page.tsx b/src/app/nostr/user/page.tsx deleted file mode 100644 index 31252c80..00000000 --- a/src/app/nostr/user/page.tsx +++ /dev/null @@ -1,51 +0,0 @@ -'use client'; - -import ProfileFollowers from '@components/profile/followers'; -import ProfileFollows from '@components/profile/follows'; -import ProfileMetadata from '@components/profile/metadata'; -import ProfileNotes from '@components/profile/notes'; - -import * as Tabs from '@radix-ui/react-tabs'; -import { useSearchParams } from 'next/navigation'; - -export default function Page() { - const searchParams = useSearchParams(); - const pubkey = searchParams.get('pubkey'); - - return ( -
- - - - - Notes - - - Followers - - - Following - - - - - - - - - - - - -
- ); -} diff --git a/src/app/onboarding/create/page.tsx b/src/app/onboarding/create/page.tsx deleted file mode 100644 index ee91734a..00000000 --- a/src/app/onboarding/create/page.tsx +++ /dev/null @@ -1,169 +0,0 @@ -'use client'; - -import { RelayContext } from '@components/relaysProvider'; - -import { createAccount } from '@utils/storage'; - -import { EyeClose, EyeEmpty } from 'iconoir-react'; -import Image from 'next/image'; -import { useRouter } from 'next/navigation'; -import { generatePrivateKey, getEventHash, getPublicKey, nip19, signEvent } from 'nostr-tools'; -import { useCallback, useContext, useMemo, useState } from 'react'; -import { Config, names, uniqueNamesGenerator } from 'unique-names-generator'; - -const config: Config = { - dictionaries: [names], -}; - -export default function Page() { - const router = useRouter(); - const [pool, relays]: any = useContext(RelayContext); - - const [type, setType] = useState('password'); - const [loading, setLoading] = useState(false); - - const privkey = useMemo(() => generatePrivateKey(), []); - const name = useMemo(() => uniqueNamesGenerator(config).toString(), []); - - const pubkey = getPublicKey(privkey); - const npub = nip19.npubEncode(pubkey); - const nsec = nip19.nsecEncode(privkey); - - // auto-generated profile metadata - const metadata: any = useMemo( - () => ({ - display_name: name, - name: name, - username: name.toLowerCase(), - picture: 'https://void.cat/d/KmypFh2fBdYCEvyJrPiN89.webp', - }), - [name] - ); - - // toggle privatek key - const showPrivateKey = () => { - if (type === 'password') { - setType('text'); - } else { - setType('password'); - } - }; - - // create account and broadcast to all relays - const submit = useCallback(async () => { - setLoading(true); - - // build event - const event: any = { - content: JSON.stringify(metadata), - created_at: Math.floor(Date.now() / 1000), - kind: 0, - pubkey: pubkey, - tags: [], - }; - event.id = getEventHash(event); - event.sig = signEvent(event, privkey); - // insert to database - createAccount(pubkey, privkey, metadata); - // broadcast - pool.publish(event, relays); - // redirect to next step - router.push(`/onboarding/create/step-2?pubkey=${pubkey}&privkey=${privkey}`, { forceOptimisticNavigation: true }); - }, [pool, pubkey, privkey, metadata, relays, router]); - - return ( -
-
-

- Create new account -

-
-
-
-
-
- -
- -
-
-
- -
- - -
-
-
- -
-
-
-
- -
-
-
-

{metadata.display_name}

-

@{metadata.username}

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {loading === true ? ( - - - - - ) : ( - - )} -
-
-
-
- ); -} diff --git a/src/app/onboarding/login/page.tsx b/src/app/onboarding/login/page.tsx deleted file mode 100644 index a0eeccfa..00000000 --- a/src/app/onboarding/login/page.tsx +++ /dev/null @@ -1,120 +0,0 @@ -'use client'; - -import { CableTag } from 'iconoir-react'; -import { useRouter } from 'next/navigation'; -import { getPublicKey, nip19 } from 'nostr-tools'; -import { Resolver, useForm } from 'react-hook-form'; - -type FormValues = { - key: string; -}; - -const resolver: Resolver = async (values) => { - return { - values: values.key ? values : {}, - errors: !values.key - ? { - key: { - type: 'required', - message: 'This is required.', - }, - } - : {}, - }; -}; - -export default function Page() { - const router = useRouter(); - const { - register, - setError, - handleSubmit, - formState: { errors, isDirty, isValid, isSubmitting }, - } = useForm({ resolver }); - - const onSubmit = async (data: any) => { - try { - let privkey = data['key']; - - if (privkey.substring(0, 4) === 'nsec') { - privkey = nip19.decode(privkey).data; - } - if (typeof getPublicKey(privkey) === 'string') { - router.push(`/onboarding/login/step-2?privkey=${privkey}`, { forceOptimisticNavigation: true }); - } - } catch (error) { - setError('key', { - type: 'custom', - message: 'Private Key is invalid, please check again', - }); - } - }; - - return ( -
-
-

- Login with Private Key -

-
-
-
-
-
- {/* #TODO: add function */} - -
-
-
-
-
-
- or -
-
-
-
- -
- {errors.key &&

{errors.key.message}

}
-
-
-
- {isSubmitting ? ( - - - - - ) : ( - - )} -
-
-
-
- ); -} diff --git a/src/app/onboarding/login/step-2/page.tsx b/src/app/onboarding/login/step-2/page.tsx deleted file mode 100644 index b9f23ee4..00000000 --- a/src/app/onboarding/login/step-2/page.tsx +++ /dev/null @@ -1,145 +0,0 @@ -'use client'; - -import { RelayContext } from '@components/relaysProvider'; - -import { DEFAULT_AVATAR } from '@stores/constants'; - -import { fetchProfileMetadata } from '@utils/hooks/useProfileMetadata'; -import { shortenKey } from '@utils/shortenKey'; -import { createAccount, createPleb, updateAccount } from '@utils/storage'; -import { nip02ToArray } from '@utils/transform'; - -import Image from 'next/image'; -import Link from 'next/link'; -import { useSearchParams } from 'next/navigation'; -import { getPublicKey } from 'nostr-tools'; -import { useCallback, useContext, useEffect, useRef, useState } from 'react'; - -export default function Page() { - const searchParams = useSearchParams(); - const privkey = searchParams.get('privkey'); - - const [pool, relays]: any = useContext(RelayContext); - const [profile, setProfile] = useState({ metadata: null }); - const [done, setDone] = useState(false); - const timeout = useRef(null); - - const pubkey = getPublicKey(privkey); - - const createPlebs = useCallback(async (tags: string[]) => { - for (const tag of tags) { - fetchProfileMetadata(tag[1]) - .then((res: any) => createPleb(tag[1], res.content)) - .catch(console.error); - } - }, []); - - useEffect(() => { - const unsubscribe = pool.subscribe( - [ - { - kinds: [0, 3], - authors: [pubkey], - }, - ], - relays, - (event: any) => { - if (event.kind === 0) { - // create account - createAccount(pubkey, privkey, event.content); - // update state - setProfile({ - metadata: JSON.parse(event.content), - }); - } else { - if (event.tags.length > 0) { - createPlebs(event.tags); - const arr = nip02ToArray(event.tags); - // update account's folllows with NIP-02 tag list - updateAccount('follows', arr, pubkey); - } - } - }, - undefined, - () => { - timeout.current = setTimeout(() => setDone(true), 5000); - }, - { - unsubscribeOnEose: true, - logAllEvents: false, - } - ); - - return () => { - unsubscribe(); - clearTimeout(timeout.current); - }; - }, [pool, relays, pubkey, privkey, createPlebs]); - - return ( -
-
-

- Bringing back your profile... -

-
-
-
-
-
-
-
- -
-
-
-

{profile.metadata?.display_name || profile.metadata?.name}

- · -

@{profile.metadata?.username || (pubkey && shortenKey(pubkey))}

-
-
-
-
-
-
-
-
-
-
-
-
-
- {done === false ? ( - - - - - ) : ( - - Done! Go to newsfeed - - )} -
-
-
-
- ); -} diff --git a/src/app/providers.tsx b/src/app/providers.tsx deleted file mode 100644 index 5d563bcb..00000000 --- a/src/app/providers.tsx +++ /dev/null @@ -1,9 +0,0 @@ -'use client'; - -import dynamic from 'next/dynamic'; - -const RelayProvider = dynamic(() => import('@components/relaysProvider'), { ssr: false }); - -export function Providers({ children }: { children: React.ReactNode }) { - return {children}; -} diff --git a/src/components/activeLink.tsx b/src/components/activeLink.tsx deleted file mode 100644 index 510b8dcb..00000000 --- a/src/components/activeLink.tsx +++ /dev/null @@ -1,25 +0,0 @@ -'use client'; - -import Link from 'next/link'; -import { useSelectedLayoutSegments } from 'next/navigation'; - -export const ActiveLink = ({ - href, - className, - activeClassName, - children, -}: { - href: string; - className: string; - activeClassName: string; - children: React.ReactNode; -}) => { - const segments = useSelectedLayoutSegments(); - const isActive = href.includes(segments[1]); - - return ( - - {children} - - ); -}; diff --git a/src/components/appHeader/actions.tsx b/src/components/appHeader/actions.tsx index b8c13ada..f5c4cf0d 100644 --- a/src/components/appHeader/actions.tsx +++ b/src/components/appHeader/actions.tsx @@ -1,24 +1,20 @@ -'use client'; - import { platform } from '@tauri-apps/api/os'; import { ArrowLeft, ArrowRight, Refresh } from 'iconoir-react'; -import { useRouter } from 'next/navigation'; import { useLayoutEffect, useState } from 'react'; export default function AppActions() { - const router = useRouter(); const [os, setOS] = useState(''); const goBack = () => { - router.back(); + window.history.back(); }; const goForward = () => { - router.forward(); + window.history.forward(); }; const reload = () => { - router.refresh(); + window.location.reload(); }; useLayoutEffect(() => { diff --git a/src/components/appHeader/index.tsx b/src/components/appHeader/index.tsx index 4c72fca8..7f10a490 100644 --- a/src/components/appHeader/index.tsx +++ b/src/components/appHeader/index.tsx @@ -1,12 +1,5 @@ -import dynamic from 'next/dynamic'; - -const AppActions = dynamic(() => import('@components/appHeader/actions'), { - ssr: false, -}); - -const EventCollector = dynamic(() => import('@components/eventCollector'), { - ssr: false, -}); +import AppActions from '@components/appHeader/actions'; +import EventCollector from '@components/eventCollector'; export default function AppHeader({ collector }: { collector: boolean }) { return ( diff --git a/src/components/channels/channelList.tsx b/src/components/channels/channelList.tsx index e6ecb109..cab853a6 100644 --- a/src/components/channels/channelList.tsx +++ b/src/components/channels/channelList.tsx @@ -10,19 +10,6 @@ export default function ChannelList() { return (
- {/* - -
- -
-
-
Browse channels
-
- - */} {list.map((item) => ( ))} diff --git a/src/components/channels/channelListItem.tsx b/src/components/channels/channelListItem.tsx index dbdae2cb..4b8368e9 100644 --- a/src/components/channels/channelListItem.tsx +++ b/src/components/channels/channelListItem.tsx @@ -1,31 +1,21 @@ -import { ImageWithFallback } from '@components/imageWithFallback'; - import { DEFAULT_AVATAR } from '@stores/constants'; import { useChannelMetadata } from '@utils/hooks/useChannelMetadata'; -import Link from 'next/link'; - export const ChannelListItem = ({ data }: { data: any }) => { const channel = useChannelMetadata(data.event_id, data.metadata); return ( - -
- +
+ {data.event_id}
{channel?.name.toLowerCase()}
- + ); }; diff --git a/src/components/chats/chatList.tsx b/src/components/chats/chatList.tsx index 726bdf47..65bbacf1 100644 --- a/src/components/chats/chatList.tsx +++ b/src/components/chats/chatList.tsx @@ -1,13 +1,11 @@ import { ChatListItem } from '@components/chats/chatListItem'; import { ChatModal } from '@components/chats/chatModal'; -import { ImageWithFallback } from '@components/imageWithFallback'; import { DEFAULT_AVATAR } from '@stores/constants'; import { getChats } from '@utils/storage'; import useLocalStorage from '@rehooks/local-storage'; -import Link from 'next/link'; import { useEffect, useState } from 'react'; export default function ChatList() { @@ -33,17 +31,15 @@ export default function ChatList() { return (
- -
- + {activeAccount.pubkey}
@@ -51,7 +47,7 @@ export default function ChatList() { {profile?.display_name || profile?.name} (you)
- + {list.map((item) => ( ))} diff --git a/src/components/chats/chatListItem.tsx b/src/components/chats/chatListItem.tsx index 1e58c7ce..32cdc1ab 100644 --- a/src/components/chats/chatListItem.tsx +++ b/src/components/chats/chatListItem.tsx @@ -1,34 +1,24 @@ -import { ImageWithFallback } from '@components/imageWithFallback'; - import { DEFAULT_AVATAR } from '@stores/constants'; import { useProfileMetadata } from '@utils/hooks/useProfileMetadata'; import { shortenKey } from '@utils/shortenKey'; -import Link from 'next/link'; - export const ChatListItem = ({ pubkey }: { pubkey: string }) => { const profile = useProfileMetadata(pubkey); return ( - -
- +
+ {pubkey}
{profile?.display_name || profile?.name || shortenKey(pubkey)}
- + ); }; diff --git a/src/components/chats/chatModalUser.tsx b/src/components/chats/chatModalUser.tsx index bf2cbbf9..c211c65e 100644 --- a/src/components/chats/chatModalUser.tsx +++ b/src/components/chats/chatModalUser.tsx @@ -1,28 +1,24 @@ -import { ImageWithFallback } from '@components/imageWithFallback'; - import { DEFAULT_AVATAR } from '@stores/constants'; import { shortenKey } from '@utils/shortenKey'; -import { useRouter } from 'next/navigation'; +import { navigate } from 'vite-plugin-ssr/client/router'; export const ChatModalUser = ({ data }: { data: any }) => { - const router = useRouter(); const profile = JSON.parse(data.metadata); const openNewChat = () => { - router.push(`/nostr/chat?pubkey=${data.pubkey}`, { forceOptimisticNavigation: true }); + navigate(`/chat?pubkey=${data.pubkey}`); }; return (
-
- + {data.pubkey}
diff --git a/src/components/chats/messageUser.tsx b/src/components/chats/messageUser.tsx index 4c632014..5b3a903a 100644 --- a/src/components/chats/messageUser.tsx +++ b/src/components/chats/messageUser.tsx @@ -1,5 +1,3 @@ -import { ImageWithFallback } from '@components/imageWithFallback'; - import { DEFAULT_AVATAR } from '@stores/constants'; import { useProfileMetadata } from '@utils/hooks/useProfileMetadata'; @@ -15,13 +13,8 @@ export const MessageUser = ({ pubkey, time }: { pubkey: string; time: number }) return (
-
- +
+ {pubkey}
diff --git a/src/components/eventCollector.tsx b/src/components/eventCollector.tsx index 54a833fa..e402d962 100644 --- a/src/components/eventCollector.tsx +++ b/src/components/eventCollector.tsx @@ -1,5 +1,3 @@ -'use client'; - import { NetworkStatusIndicator } from '@components/networkStatusIndicator'; import { RelayContext } from '@components/relaysProvider'; diff --git a/src/components/form/emojiPicker.tsx b/src/components/form/emojiPicker.tsx deleted file mode 100644 index e48802cc..00000000 --- a/src/components/form/emojiPicker.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { noteContentAtom } from '@stores/note'; - -import data from '@emoji-mart/data'; -import Picker from '@emoji-mart/react'; -import * as Popover from '@radix-ui/react-popover'; -import { Emoji } from 'iconoir-react'; -import { useAtom } from 'jotai'; - -export default function EmojiPicker() { - const [value, setValue] = useAtom(noteContentAtom); - - return ( - - - - - - - setValue(value + ' ' + res.native)} - /> - - - - - ); -} diff --git a/src/components/imageWithFallback.tsx b/src/components/imageWithFallback.tsx deleted file mode 100644 index dcf3ed97..00000000 --- a/src/components/imageWithFallback.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { DEFAULT_AVATAR } from '@stores/constants'; - -import Image from 'next/image'; -import { memo, useEffect, useState } from 'react'; - -export const ImageWithFallback = memo(function ImageWithFallback({ - src, - alt, - fill, - className, -}: { - src: string; - alt: string; - fill: boolean; - className: string; -}) { - const [error, setError] = useState(null); - - useEffect(() => { - setError(null); - }, [src]); - - return ( - {alt} - ); -}); diff --git a/src/app/nostr/layout.tsx b/src/components/layouts/newsfeed.tsx similarity index 95% rename from src/app/nostr/layout.tsx rename to src/components/layouts/newsfeed.tsx index 0da145e3..563fc421 100644 --- a/src/app/nostr/layout.tsx +++ b/src/components/layouts/newsfeed.tsx @@ -2,7 +2,7 @@ import AppHeader from '@components/appHeader'; import MultiAccounts from '@components/multiAccounts'; import Navigation from '@components/navigation'; -export default function NostrLayout({ children }: { children: React.ReactNode }) { +export default function NewsfeedLayout({ children }: { children: React.ReactNode }) { return (
diff --git a/src/app/onboarding/layout.tsx b/src/components/layouts/onboarding.tsx similarity index 100% rename from src/app/onboarding/layout.tsx rename to src/components/layouts/onboarding.tsx diff --git a/src/components/link.tsx b/src/components/link.tsx new file mode 100644 index 00000000..93b3181c --- /dev/null +++ b/src/components/link.tsx @@ -0,0 +1,12 @@ +import { usePageContext } from '@utils/hooks/usePageContext'; + +import { AnchorHTMLAttributes, ClassAttributes } from 'react'; + +export function Link( + props: JSX.IntrinsicAttributes & ClassAttributes & AnchorHTMLAttributes, + activeClass: string +) { + const pageContext = usePageContext(); + const className = [props.className, pageContext.urlPathname === props.href && activeClass].filter(Boolean).join(' '); + return ; +} diff --git a/src/components/multiAccounts/activeAccount.tsx b/src/components/multiAccounts/activeAccount.tsx index 90cdc165..a1aca932 100644 --- a/src/components/multiAccounts/activeAccount.tsx +++ b/src/components/multiAccounts/activeAccount.tsx @@ -1,20 +1,16 @@ -'use client'; - import { DEFAULT_AVATAR } from '@stores/constants'; import * as DropdownMenu from '@radix-ui/react-dropdown-menu'; import { writeText } from '@tauri-apps/api/clipboard'; import { LogOut, ProfileCircle, Settings } from 'iconoir-react'; -import Image from 'next/image'; -import { useRouter } from 'next/navigation'; import { nip19 } from 'nostr-tools'; +import { navigate } from 'vite-plugin-ssr/client/router'; export const ActiveAccount = ({ user }: { user: any }) => { - const router = useRouter(); const userData = JSON.parse(user.metadata); const openProfilePage = () => { - router.push(`/nostr/user?pubkey=${user.pubkey}`, { forceOptimisticNavigation: true }); + navigate(`/user?pubkey=${user.pubkey}`); }; const copyPublicKey = async () => { @@ -25,12 +21,10 @@ export const ActiveAccount = ({ user }: { user: any }) => { diff --git a/src/components/multiAccounts/inactiveAccount.tsx b/src/components/multiAccounts/inactiveAccount.tsx index b2d42c6e..ede7d61d 100644 --- a/src/components/multiAccounts/inactiveAccount.tsx +++ b/src/components/multiAccounts/inactiveAccount.tsx @@ -1,6 +1,5 @@ import { DEFAULT_AVATAR } from '@stores/constants'; -import Image from 'next/image'; import { memo } from 'react'; export const InactiveAccount = memo(function InactiveAccount({ user }: { user: any }) { @@ -12,13 +11,7 @@ export const InactiveAccount = memo(function InactiveAccount({ user }: { user: a return ( ); }); diff --git a/src/components/multiAccounts/index.tsx b/src/components/multiAccounts/index.tsx index 853b1623..734d258b 100644 --- a/src/components/multiAccounts/index.tsx +++ b/src/components/multiAccounts/index.tsx @@ -1,5 +1,3 @@ -'use client'; - import { ActiveAccount } from '@components/multiAccounts/activeAccount'; import { InactiveAccount } from '@components/multiAccounts/inactiveAccount'; @@ -11,7 +9,6 @@ import LumeSymbol from '@assets/icons/Lume'; import useLocalStorage from '@rehooks/local-storage'; import { Plus } from 'iconoir-react'; -import Link from 'next/link'; import { useCallback, useEffect, useState } from 'react'; export default function MultiAccounts() { @@ -38,21 +35,19 @@ export default function MultiAccounts() { return (
- - +
{users.map((user) => renderAccount(user))}
- - +
diff --git a/src/components/navigation/channels.tsx b/src/components/navigation/channels.tsx index e005c856..952c0cb5 100644 --- a/src/components/navigation/channels.tsx +++ b/src/components/navigation/channels.tsx @@ -1,5 +1,3 @@ -'use client'; - import ChannelList from '@components/channels/channelList'; import * as Collapsible from '@radix-ui/react-collapsible'; diff --git a/src/components/navigation/chats.tsx b/src/components/navigation/chats.tsx index db5f8c56..ae882c87 100644 --- a/src/components/navigation/chats.tsx +++ b/src/components/navigation/chats.tsx @@ -1,5 +1,3 @@ -'use client'; - import ChatList from '@components/chats/chatList'; import * as Collapsible from '@radix-ui/react-collapsible'; diff --git a/src/components/navigation/newsfeed.tsx b/src/components/navigation/newsfeed.tsx index 1327e511..1efb5d3b 100644 --- a/src/components/navigation/newsfeed.tsx +++ b/src/components/navigation/newsfeed.tsx @@ -1,7 +1,3 @@ -'use client'; - -import { ActiveLink } from '@components/activeLink'; - import * as Collapsible from '@radix-ui/react-collapsible'; import { Bonfire, NavArrowUp, PeopleTag } from 'iconoir-react'; import { useState } from 'react'; @@ -23,22 +19,22 @@ export default function Newsfeed() {

Newsfeed

- Following - - + Circle - +
diff --git a/src/components/note/base.tsx b/src/components/note/base.tsx index 42856031..3b33eae4 100644 --- a/src/components/note/base.tsx +++ b/src/components/note/base.tsx @@ -4,11 +4,10 @@ import { UserExtend } from '@components/user/extend'; import { contentParser } from '@utils/parser'; -import { useRouter } from 'next/navigation'; import { memo } from 'react'; +import { navigate } from 'vite-plugin-ssr/client/router'; export const NoteBase = memo(function NoteBase({ event }: { event: any }) { - const router = useRouter(); const content = contentParser(event.content, event.tags); const parentNote = () => { @@ -22,13 +21,13 @@ export const NoteBase = memo(function NoteBase({ event }: { event: any }) { const openUserPage = (e) => { e.stopPropagation(); - router.push(`/nostr/user?pubkey=${event.pubkey}`, { forceOptimisticNavigation: true }); + navigate(`/user?pubkey=${event.pubkey}`); }; const openThread = (e) => { const selection = window.getSelection(); if (selection.toString().length === 0) { - router.push(`/nostr/newsfeed/note?id=${event.parent_id}`, { forceOptimisticNavigation: true }); + navigate(`/newsfeed/note?id=${event.parent_id}`); } else { e.stopPropagation(); } diff --git a/src/components/note/meta/comment.tsx b/src/components/note/meta/comment.tsx index 8e5717c4..4e2a40d1 100644 --- a/src/components/note/meta/comment.tsx +++ b/src/components/note/meta/comment.tsx @@ -1,4 +1,3 @@ -import { ImageWithFallback } from '@components/imageWithFallback'; import { RelayContext } from '@components/relaysProvider'; import { UserExtend } from '@components/user/extend'; @@ -7,9 +6,9 @@ import { dateToUnix } from '@utils/getDate'; import * as Dialog from '@radix-ui/react-dialog'; import useLocalStorage from '@rehooks/local-storage'; import { ChatLines, OpenNewWindow } from 'iconoir-react'; -import { useRouter } from 'next/navigation'; import { getEventHash, signEvent } from 'nostr-tools'; import { useContext, useState } from 'react'; +import { navigate } from 'vite-plugin-ssr/client/router'; export const NoteComment = ({ count, @@ -24,7 +23,6 @@ export const NoteComment = ({ eventTime: number; eventContent: any; }) => { - const router = useRouter(); const [pool, relays]: any = useContext(RelayContext); const [open, setOpen] = useState(false); @@ -34,7 +32,7 @@ export const NoteComment = ({ const profile = activeAccount.metadata ? JSON.parse(activeAccount.metadata) : null; const openThread = () => { - router.push(`/nostr/newsfeed/note?id=${eventID}`, { forceOptimisticNavigation: true }); + navigate(`/newsfeed/note?id=${eventID}`); }; const submitEvent = () => { @@ -84,12 +82,7 @@ export const NoteComment = ({
- + user's avatar
diff --git a/src/components/note/preview/image.tsx b/src/components/note/preview/image.tsx index 0ec989d4..60327fd1 100644 --- a/src/components/note/preview/image.tsx +++ b/src/components/note/preview/image.tsx @@ -1,18 +1,9 @@ -import Image from 'next/image'; import { memo } from 'react'; export const ImagePreview = memo(function ImagePreview({ url, size }: { url: string; size: string }) { return (
- {url} + {url}
); }); diff --git a/src/components/note/preview/link.tsx b/src/components/note/preview/link.tsx deleted file mode 100644 index d1064231..00000000 --- a/src/components/note/preview/link.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import Image from 'next/image'; -import Link from 'next/link'; - -export default function LinkCard({ data }: { data: any }) { - return ( - -
- image preview -
-
-
-
{data['title']}
-

{data['description']}

-
- {data['url']} -
- - ); -} diff --git a/src/components/note/rootNote.tsx b/src/components/note/rootNote.tsx index a3678207..f09e5b18 100644 --- a/src/components/note/rootNote.tsx +++ b/src/components/note/rootNote.tsx @@ -4,11 +4,10 @@ import { UserExtend } from '@components/user/extend'; import { contentParser } from '@utils/parser'; -import { useRouter } from 'next/navigation'; import { memo, useCallback, useContext, useEffect, useState } from 'react'; +import { navigate } from 'vite-plugin-ssr/client/router'; export const RootNote = memo(function RootNote({ event }: { event: any }) { - const router = useRouter(); const [pool, relays]: any = useContext(RelayContext); const [data, setData] = useState(null); @@ -16,13 +15,13 @@ export const RootNote = memo(function RootNote({ event }: { event: any }) { const openUserPage = (e) => { e.stopPropagation(); - router.push(`/nostr/user?pubkey=${event.pubkey}`, { forceOptimisticNavigation: true }); + navigate(`/user?pubkey=${event.pubkey}`); }; const openThread = (e) => { const selection = window.getSelection(); if (selection.toString().length === 0) { - router.push(`/nostr/newsfeed/note?id=${event.parent_id}`, { forceOptimisticNavigation: true }); + navigate(`/newsfeed/note?id=${event.parent_id}`); } else { e.stopPropagation(); } diff --git a/src/components/profile/followers.tsx b/src/components/profile/followers.tsx index ef03f58f..5645b764 100644 --- a/src/components/profile/followers.tsx +++ b/src/components/profile/followers.tsx @@ -1,5 +1,3 @@ -'use client'; - import { RelayContext } from '@components/relaysProvider'; import { UserFollow } from '@components/user/follow'; diff --git a/src/components/profile/follows.tsx b/src/components/profile/follows.tsx index 7b807a03..14cda816 100644 --- a/src/components/profile/follows.tsx +++ b/src/components/profile/follows.tsx @@ -1,5 +1,3 @@ -'use client'; - import { RelayContext } from '@components/relaysProvider'; import { UserFollow } from '@components/user/follow'; diff --git a/src/components/profile/metadata.tsx b/src/components/profile/metadata.tsx index 3d5a1ea6..bc0e24e5 100644 --- a/src/components/profile/metadata.tsx +++ b/src/components/profile/metadata.tsx @@ -1,6 +1,3 @@ -'use client'; - -import { ImageWithFallback } from '@components/imageWithFallback'; import { RelayContext } from '@components/relaysProvider'; import { DEFAULT_AVATAR } from '@stores/constants'; @@ -8,7 +5,6 @@ import { DEFAULT_AVATAR } from '@stores/constants'; import { shortenKey } from '@utils/shortenKey'; import destr from 'destr'; -import Image from 'next/image'; import { Author } from 'nostr-relaypool'; import { useContext, useEffect, useState } from 'react'; @@ -27,21 +23,11 @@ export default function ProfileMetadata({ id }: { id: string }) { <>
- user's banner + user's banner
- + {id}
diff --git a/src/components/profile/notes.tsx b/src/components/profile/notes.tsx index 11ab6d06..d1a315c2 100644 --- a/src/components/profile/notes.tsx +++ b/src/components/profile/notes.tsx @@ -1,5 +1,3 @@ -'use client'; - import { NoteBase } from '@components/note/base'; import { RelayContext } from '@components/relaysProvider'; diff --git a/src/components/relaysProvider.tsx b/src/components/relaysProvider.tsx index 72c5d0cc..2ae54ff7 100644 --- a/src/components/relaysProvider.tsx +++ b/src/components/relaysProvider.tsx @@ -1,5 +1,3 @@ -'use client'; - import { DEFAULT_RELAYS } from '@stores/constants'; import { RelayPool } from 'nostr-relaypool'; diff --git a/src/components/user/base.tsx b/src/components/user/base.tsx index 0d363ee2..9464bec2 100644 --- a/src/components/user/base.tsx +++ b/src/components/user/base.tsx @@ -1,5 +1,3 @@ -import { ImageWithFallback } from '@components/imageWithFallback'; - import { DEFAULT_AVATAR } from '@stores/constants'; import { useProfileMetadata } from '@utils/hooks/useProfileMetadata'; @@ -13,12 +11,7 @@ export const UserBase = memo(function UserBase({ pubkey }: { pubkey: string }) { return (
- + {pubkey}
diff --git a/src/components/user/extend.tsx b/src/components/user/extend.tsx index 1beb204a..0311adcc 100644 --- a/src/components/user/extend.tsx +++ b/src/components/user/extend.tsx @@ -1,5 +1,3 @@ -import { ImageWithFallback } from '@components/imageWithFallback'; - import { DEFAULT_AVATAR } from '@stores/constants'; import { useProfileMetadata } from '@utils/hooks/useProfileMetadata'; @@ -16,12 +14,7 @@ export const UserExtend = ({ pubkey, time }: { pubkey: string; time: number }) = return (
- + {pubkey}
diff --git a/src/components/user/follow.tsx b/src/components/user/follow.tsx index 3942f561..0b4337be 100644 --- a/src/components/user/follow.tsx +++ b/src/components/user/follow.tsx @@ -1,5 +1,3 @@ -import { ImageWithFallback } from '@components/imageWithFallback'; - import { DEFAULT_AVATAR } from '@stores/constants'; import { useProfileMetadata } from '@utils/hooks/useProfileMetadata'; @@ -11,12 +9,7 @@ export const UserFollow = ({ pubkey }: { pubkey: string }) => { return (
- + {pubkey}
diff --git a/src/components/user/large.tsx b/src/components/user/large.tsx index c060578f..3fd0ac80 100644 --- a/src/components/user/large.tsx +++ b/src/components/user/large.tsx @@ -1,5 +1,3 @@ -import { ImageWithFallback } from '@components/imageWithFallback'; - import { DEFAULT_AVATAR } from '@stores/constants'; import { useProfileMetadata } from '@utils/hooks/useProfileMetadata'; @@ -17,11 +15,10 @@ export const UserLarge = ({ pubkey, time }: { pubkey: string; time: number }) => return (
-
diff --git a/src/components/user/mini.tsx b/src/components/user/mini.tsx index e4191755..5423f6a0 100644 --- a/src/components/user/mini.tsx +++ b/src/components/user/mini.tsx @@ -1,5 +1,3 @@ -import { ImageWithFallback } from '@components/imageWithFallback'; - import { DEFAULT_AVATAR } from '@stores/constants'; import { useProfileMetadata } from '@utils/hooks/useProfileMetadata'; @@ -11,12 +9,7 @@ export const UserMini = ({ pubkey }: { pubkey: string }) => { return (
- + {pubkey}
Replying to {profile?.name || shortenKey(pubkey)} diff --git a/src/components/user/quoteRepost.tsx b/src/components/user/quoteRepost.tsx index f240c7e4..956c7742 100644 --- a/src/components/user/quoteRepost.tsx +++ b/src/components/user/quoteRepost.tsx @@ -1,5 +1,3 @@ -import { ImageWithFallback } from '@components/imageWithFallback'; - import { DEFAULT_AVATAR } from '@stores/constants'; import { useProfileMetadata } from '@utils/hooks/useProfileMetadata'; @@ -16,12 +14,7 @@ export const UserQuoteRepost = ({ pubkey, time }: { pubkey: string; time: number return (
- + {pubkey}
diff --git a/src/app/nostr/channel/page.tsx b/src/pages/channel/index.page.tsx similarity index 82% rename from src/app/nostr/channel/page.tsx rename to src/pages/channel/index.page.tsx index ba83d9d8..f25ff292 100644 --- a/src/app/nostr/channel/page.tsx +++ b/src/pages/channel/index.page.tsx @@ -1,24 +1,25 @@ -'use client'; - import { ChannelMessages } from '@components/channels/messages'; import { FormChannel } from '@components/form/channel'; +import NewsfeedLayout from '@components/layouts/newsfeed'; import { RelayContext } from '@components/relaysProvider'; import { channelMessagesAtom, channelReplyAtom } from '@stores/channel'; import { FULL_RELAYS } from '@stores/constants'; import { dateToUnix, hoursAgo } from '@utils/getDate'; +import { usePageContext } from '@utils/hooks/usePageContext'; import useLocalStorage from '@rehooks/local-storage'; import { useSetAtom } from 'jotai'; import { useResetAtom } from 'jotai/utils'; -import { useSearchParams } from 'next/navigation'; import { useContext, useEffect, useRef } from 'react'; import useSWRSubscription from 'swr/subscription'; -export default function Page() { - const searchParams = useSearchParams(); - const id = searchParams.get('channel-id'); +export function Page() { + const pageContext = usePageContext(); + const searchParams: any = pageContext.urlParsed.search; + + const id = searchParams.id; const [pool]: any = useContext(RelayContext); const [activeAccount]: any = useLocalStorage('account', {}); @@ -84,11 +85,13 @@ export default function Page() { }, [resetChannelReply, resetChannelMessages]); return ( -
- -
- + +
+ +
+ +
-
+ ); } diff --git a/src/app/nostr/chat/page.tsx b/src/pages/chat/index.page.tsx similarity index 73% rename from src/app/nostr/chat/page.tsx rename to src/pages/chat/index.page.tsx index 57865643..30915591 100644 --- a/src/app/nostr/chat/page.tsx +++ b/src/pages/chat/index.page.tsx @@ -1,22 +1,24 @@ -'use client'; - import { MessageList } from '@components/chats/messageList'; import FormChat from '@components/form/chat'; +import NewsfeedLayout from '@components/layouts/newsfeed'; import { RelayContext } from '@components/relaysProvider'; import { chatMessagesAtom } from '@stores/chat'; import { FULL_RELAYS } from '@stores/constants'; +import { usePageContext } from '@utils/hooks/usePageContext'; + import useLocalStorage from '@rehooks/local-storage'; import { useSetAtom } from 'jotai'; import { useResetAtom } from 'jotai/utils'; -import { useSearchParams } from 'next/navigation'; import { useContext, useEffect } from 'react'; import useSWRSubscription from 'swr/subscription'; -export default function Page() { - const searchParams = useSearchParams(); - const pubkey = searchParams.get('pubkey'); +export function Page() { + const pageContext = usePageContext(); + const searchParams: any = pageContext.urlParsed.search; + + const pubkey = searchParams.pubkey; const [pool]: any = useContext(RelayContext); const [activeAccount]: any = useLocalStorage('account', {}); @@ -62,11 +64,13 @@ export default function Page() { }, [resetChatMessages]); return ( -
- -
- + +
+ +
+ +
-
+ ); } diff --git a/src/app/page.tsx b/src/pages/index.page.tsx similarity index 86% rename from src/app/page.tsx rename to src/pages/index.page.tsx index 7647f8b8..299aea41 100644 --- a/src/app/page.tsx +++ b/src/pages/index.page.tsx @@ -1,5 +1,3 @@ -'use client'; - import { RelayContext } from '@components/relaysProvider'; import { dateToUnix, hoursAgo } from '@utils/getDate'; @@ -19,11 +17,10 @@ import { getParentID } from '@utils/transform'; import LumeSymbol from '@assets/icons/Lume'; import { writeStorage } from '@rehooks/local-storage'; -import { useRouter } from 'next/navigation'; import { useCallback, useContext, useEffect, useRef } from 'react'; +import { navigate } from 'vite-plugin-ssr/client/router'; -export default function Page() { - const router = useRouter(); +export function Page() { const [pool, relays]: any = useContext(RelayContext); const now = useRef(new Date()); @@ -126,7 +123,7 @@ export default function Page() { () => { updateLastLogin(dateToUnix(now.current)); timeout.current = setTimeout(() => { - router.replace('/nostr/newsfeed/following', { forceOptimisticNavigation: true }); + navigate('/newsfeed/following', { overwriteLastHistoryEntry: true }); }, 5000); }, { @@ -139,39 +136,41 @@ export default function Page() { unsubscribe(); }; }, - [router, pool, relays] + [pool, relays] ); useEffect(() => { let ignore = false; - getPlebs() - .then((res) => { - if (res && !ignore) { - writeStorage('plebs', res); - } - }) - .catch(console.error); + if (!ignore) { + getActiveAccount() + .then((res: any) => { + if (res) { + const account = res; + // update local storage + writeStorage('account', account); + // fetch data + fetchData(account, account.follows); + } else { + navigate('/onboarding', { overwriteLastHistoryEntry: true }); + } + }) + .catch(console.error); - getActiveAccount() - .then((res: any) => { - if (res && !ignore) { - const account = res; - // update local storage - writeStorage('account', account); - // fetch data - fetchData(account, account.follows); - } else { - router.replace('/onboarding', { forceOptimisticNavigation: true }); - } - }) - .catch(console.error); + getPlebs() + .then((res) => { + if (res && !ignore) { + writeStorage('plebs', res); + } + }) + .catch(console.error); + } return () => { ignore = true; clearTimeout(timeout.current); }; - }, [fetchData, router]); + }, [fetchData]); return (
diff --git a/src/pages/newsfeed/circle/index.page.tsx b/src/pages/newsfeed/circle/index.page.tsx new file mode 100644 index 00000000..e7c8b9ac --- /dev/null +++ b/src/pages/newsfeed/circle/index.page.tsx @@ -0,0 +1,3 @@ +export function Page() { + return <>; +} diff --git a/src/app/nostr/newsfeed/following/page.tsx b/src/pages/newsfeed/following/index.page.tsx similarity index 71% rename from src/app/nostr/newsfeed/following/page.tsx rename to src/pages/newsfeed/following/index.page.tsx index 8f5ce5f9..ef48d400 100644 --- a/src/app/nostr/newsfeed/following/page.tsx +++ b/src/pages/newsfeed/following/index.page.tsx @@ -1,6 +1,5 @@ -'use client'; - import FormBase from '@components/form/base'; +import NewsfeedLayout from '@components/layouts/newsfeed'; import { NoteBase } from '@components/note/base'; import { Placeholder } from '@components/note/placeholder'; import { NoteQuoteRepost } from '@components/note/quoteRepost'; @@ -15,7 +14,7 @@ import { useAtom, useAtomValue, useSetAtom } from 'jotai'; import { useCallback, useEffect, useRef } from 'react'; import { Virtuoso } from 'react-virtuoso'; -export default function Page() { +export function Page() { const [hasNewerNote, setHasNewerNote] = useAtom(hasNewerNoteAtom); const setData = useSetAtom(notesAtom); const data = useAtomValue(filteredNotesAtom); @@ -82,29 +81,31 @@ export default function Page() { }, [initialData]); return ( -
- {hasNewerNote && ( -
- -
- )} - -
+ +
+ {hasNewerNote && ( +
+ +
+ )} + +
+
); } diff --git a/src/pages/onboarding/create/index.page.tsx b/src/pages/onboarding/create/index.page.tsx new file mode 100644 index 00000000..bf8f452b --- /dev/null +++ b/src/pages/onboarding/create/index.page.tsx @@ -0,0 +1,172 @@ +import OnboardingLayout from '@components/layouts/onboarding'; +import { RelayContext } from '@components/relaysProvider'; + +import { createAccount } from '@utils/storage'; + +import { EyeClose, EyeEmpty } from 'iconoir-react'; +import { generatePrivateKey, getEventHash, getPublicKey, nip19, signEvent } from 'nostr-tools'; +import { useCallback, useContext, useMemo, useState } from 'react'; +import { Config, names, uniqueNamesGenerator } from 'unique-names-generator'; +import { navigate } from 'vite-plugin-ssr/client/router'; + +const config: Config = { + dictionaries: [names], +}; + +export function Page() { + const [pool, relays]: any = useContext(RelayContext); + + const [type, setType] = useState('password'); + const [loading, setLoading] = useState(false); + + const privkey = useMemo(() => generatePrivateKey(), []); + const name = useMemo(() => uniqueNamesGenerator(config).toString(), []); + + const pubkey = getPublicKey(privkey); + const npub = nip19.npubEncode(pubkey); + const nsec = nip19.nsecEncode(privkey); + + // auto-generated profile metadata + const metadata: any = useMemo( + () => ({ + display_name: name, + name: name, + username: name.toLowerCase(), + picture: 'https://void.cat/d/KmypFh2fBdYCEvyJrPiN89.webp', + }), + [name] + ); + + // toggle privatek key + const showPrivateKey = () => { + if (type === 'password') { + setType('text'); + } else { + setType('password'); + } + }; + + // create account and broadcast to all relays + const submit = useCallback(async () => { + setLoading(true); + + // build event + const event: any = { + content: JSON.stringify(metadata), + created_at: Math.floor(Date.now() / 1000), + kind: 0, + pubkey: pubkey, + tags: [], + }; + event.id = getEventHash(event); + event.sig = signEvent(event, privkey); + // insert to database + createAccount(pubkey, privkey, metadata); + // broadcast + pool.publish(event, relays); + // redirect to next step + navigate(`/onboarding/create/step-2?pubkey=${pubkey}&privkey=${privkey}`); + }, [pool, pubkey, privkey, metadata, relays]); + + return ( + +
+
+

+ Create new account +

+
+
+
+
+
+ +
+ +
+
+
+ +
+ + +
+
+
+ +
+
+
+
+ default avatar +
+
+
+

{metadata.display_name}

+

@{metadata.username}

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {loading === true ? ( + + + + + ) : ( + + )} +
+
+
+
+
+ ); +} diff --git a/src/app/onboarding/create/step-2/page.tsx b/src/pages/onboarding/create/step-2/index.page.tsx similarity index 61% rename from src/app/onboarding/create/step-2/page.tsx rename to src/pages/onboarding/create/step-2/index.page.tsx index fd8b992d..d152353b 100644 --- a/src/app/onboarding/create/step-2/page.tsx +++ b/src/pages/onboarding/create/step-2/index.page.tsx @@ -1,17 +1,17 @@ -'use client'; - +import OnboardingLayout from '@components/layouts/onboarding'; import { RelayContext } from '@components/relaysProvider'; import { UserBase } from '@components/user/base'; +import { usePageContext } from '@utils/hooks/usePageContext'; import { fetchProfileMetadata } from '@utils/hooks/useProfileMetadata'; import { createPleb, updateAccount } from '@utils/storage'; import { arrayToNIP02 } from '@utils/transform'; import { createClient } from '@supabase/supabase-js'; import { CheckCircle } from 'iconoir-react'; -import { useRouter, useSearchParams } from 'next/navigation'; import { getEventHash, signEvent } from 'nostr-tools'; import { Key, useCallback, useContext, useEffect, useState } from 'react'; +import { navigate } from 'vite-plugin-ssr/client/router'; const supabase = createClient( 'https://niwaazauwnrwiwmnocnn.supabase.co', @@ -53,12 +53,12 @@ const initialList = [ { pubkey: 'ff04a0e6cd80c141b0b55825fed127d4532a6eecdb7e743a38a3c28bf9f44609' }, ]; -export default function Page() { - const router = useRouter(); - const searchParams = useSearchParams(); +export function Page() { + const pageContext = usePageContext(); + const searchParams: any = pageContext.urlParsed.search; - const pubkey = searchParams.get('pubkey'); - const privkey = searchParams.get('privkey'); + const pubkey = searchParams.pubkey; + const privkey = searchParams.privkey; const [pool, relays]: any = useContext(RelayContext); const [loading, setLoading] = useState(false); @@ -100,8 +100,8 @@ export default function Page() { // broadcast pool.publish(event, relays); // redirect to splashscreen - router.replace('/'); - }, [pubkey, privkey, follows, pool, relays, router]); + navigate('/'); + }, [pubkey, privkey, follows, pool, relays]); useEffect(() => { let ignore = false; @@ -122,65 +122,67 @@ export default function Page() { }, []); return ( -
-
-
-

- Personalized your newsfeed -

-

- Follow at least{' '} - - {follows.length}/10 - {' '} - plebs -

+ +
+
+
+

+ Personalized your newsfeed +

+

+ Follow at least{' '} + + {follows.length}/10 + {' '} + plebs +

+
-
-
-
- {list.map((item: { pubkey: string }, index: Key) => ( +
+
+ {list.map((item: { pubkey: string }, index: Key) => ( + + ))} +
+
+ {follows.length >= 10 && ( +
- ))} -
+
+ )}
- {follows.length >= 10 && ( -
- -
- )} -
+ ); } diff --git a/src/app/onboarding/page.tsx b/src/pages/onboarding/index.page.tsx similarity index 61% rename from src/app/onboarding/page.tsx rename to src/pages/onboarding/index.page.tsx index 7a84b156..22bc717d 100644 --- a/src/app/onboarding/page.tsx +++ b/src/pages/onboarding/index.page.tsx @@ -1,6 +1,6 @@ +import OnboardingLayout from '@components/layouts/onboarding'; + import { ArrowRight } from 'iconoir-react'; -import Image from 'next/image'; -import Link from 'next/link'; const PLEBS = [ 'https://133332.xyz/p.jpg', @@ -72,58 +72,47 @@ const InfiniteLoopSlider = ({ children, duration, reverse }: { children: any; du ); }; -export default function Page() { +export function Page() { return ( -
-
-
- {[...new Array(ROWS)].map((_, i) => ( - - {shuffle(PLEBS) - .slice(0, PLEBS_PER_ROW) - .map((tag) => ( -
- {tag} -
- ))} -
- ))} -
+ +
+
+
+ {[...new Array(ROWS)].map((_, i) => ( + + {shuffle(PLEBS) + .slice(0, PLEBS_PER_ROW) + .map((tag) => ( +
+ {tag} +
+ ))} +
+ ))} +
+
+
+
-
-

- Let's start! -

-
- - Create new key - - - - Login with private key - -
-
-
+
); } diff --git a/src/pages/onboarding/login/index.page.tsx b/src/pages/onboarding/login/index.page.tsx new file mode 100644 index 00000000..fdb975dd --- /dev/null +++ b/src/pages/onboarding/login/index.page.tsx @@ -0,0 +1,121 @@ +import OnboardingLayout from '@components/layouts/onboarding'; + +import { CableTag } from 'iconoir-react'; +import { getPublicKey, nip19 } from 'nostr-tools'; +import { Resolver, useForm } from 'react-hook-form'; +import { navigate } from 'vite-plugin-ssr/client/router'; + +type FormValues = { + key: string; +}; + +const resolver: Resolver = async (values) => { + return { + values: values.key ? values : {}, + errors: !values.key + ? { + key: { + type: 'required', + message: 'This is required.', + }, + } + : {}, + }; +}; + +export function Page() { + const { + register, + setError, + handleSubmit, + formState: { errors, isDirty, isValid, isSubmitting }, + } = useForm({ resolver }); + + const onSubmit = async (data: any) => { + try { + let privkey = data['key']; + + if (privkey.substring(0, 4) === 'nsec') { + privkey = nip19.decode(privkey).data; + } + if (typeof getPublicKey(privkey) === 'string') { + navigate(`/onboarding/login/step-2?privkey=${privkey}`); + } + } catch (error) { + setError('key', { + type: 'custom', + message: 'Private Key is invalid, please check again', + }); + } + }; + + return ( + +
+
+

+ Login with Private Key +

+
+
+
+
+
+ {/* #TODO: add function */} + +
+
+
+
+
+
+ or +
+
+
+
+ +
+ {errors.key &&

{errors.key.message}

}
+
+
+
+ {isSubmitting ? ( + + + + + ) : ( + + )} +
+
+
+
+
+ ); +} diff --git a/src/pages/onboarding/login/step-2/index.page.tsx b/src/pages/onboarding/login/step-2/index.page.tsx new file mode 100644 index 00000000..5e52b89d --- /dev/null +++ b/src/pages/onboarding/login/step-2/index.page.tsx @@ -0,0 +1,144 @@ +import OnboardingLayout from '@components/layouts/onboarding'; +import { RelayContext } from '@components/relaysProvider'; + +import { DEFAULT_AVATAR } from '@stores/constants'; + +import { usePageContext } from '@utils/hooks/usePageContext'; +import { fetchProfileMetadata } from '@utils/hooks/useProfileMetadata'; +import { shortenKey } from '@utils/shortenKey'; +import { createAccount, createPleb, updateAccount } from '@utils/storage'; +import { nip02ToArray } from '@utils/transform'; + +import { getPublicKey } from 'nostr-tools'; +import { useCallback, useContext, useEffect, useRef, useState } from 'react'; + +export function Page() { + const pageContext = usePageContext(); + const searchParams = pageContext.urlParsed.search; + + const privkey = searchParams.privkey; + const pubkey = getPublicKey(privkey); + + const [pool, relays]: any = useContext(RelayContext); + const [profile, setProfile] = useState({ metadata: null }); + const [done, setDone] = useState(false); + + const timeout = useRef(null); + + const createPlebs = useCallback(async (tags: string[]) => { + for (const tag of tags) { + fetchProfileMetadata(tag[1]) + .then((res: any) => createPleb(tag[1], res.content)) + .catch(console.error); + } + }, []); + + useEffect(() => { + const unsubscribe = pool.subscribe( + [ + { + kinds: [0, 3], + authors: [pubkey], + }, + ], + relays, + (event: any) => { + if (event.kind === 0) { + // create account + createAccount(pubkey, privkey, event.content); + // update state + setProfile({ + metadata: JSON.parse(event.content), + }); + } else { + if (event.tags.length > 0) { + createPlebs(event.tags); + const arr = nip02ToArray(event.tags); + // update account's folllows with NIP-02 tag list + updateAccount('follows', arr, pubkey); + } + } + }, + undefined, + () => { + timeout.current = setTimeout(() => setDone(true), 5000); + }, + { + unsubscribeOnEose: true, + logAllEvents: false, + } + ); + + return () => { + unsubscribe(); + clearTimeout(timeout.current); + }; + }, [pool, relays, pubkey, privkey, createPlebs]); + + return ( + +
+
+

+ Bringing back your profile... +

+
+
+
+
+
+
+
+ avatar +
+
+
+

{profile.metadata?.display_name || profile.metadata?.name}

+ · +

@{profile.metadata?.username || (pubkey && shortenKey(pubkey))}

+
+
+
+
+
+
+
+
+
+
+
+
+
+ {done === false ? ( + + + + + ) : ( + + Done! Go to newsfeed + + )} +
+
+
+
+
+ ); +} diff --git a/src/pages/user/index.page.tsx b/src/pages/user/index.page.tsx new file mode 100644 index 00000000..c35af46f --- /dev/null +++ b/src/pages/user/index.page.tsx @@ -0,0 +1,57 @@ +'use client'; + +import NewsfeedLayout from '@components/layouts/newsfeed'; +import ProfileFollowers from '@components/profile/followers'; +import ProfileFollows from '@components/profile/follows'; +import ProfileMetadata from '@components/profile/metadata'; +import ProfileNotes from '@components/profile/notes'; + +import { usePageContext } from '@utils/hooks/usePageContext'; + +import * as Tabs from '@radix-ui/react-tabs'; + +export function Page() { + const pageContext = usePageContext(); + const searchParams: any = pageContext.urlParsed.search; + + const pubkey = searchParams.pubkey; + + return ( + +
+ + + + + Notes + + + Followers + + + Following + + + + + + + + + + + + +
+
+ ); +} diff --git a/src/renderer/_default.page.client.tsx b/src/renderer/_default.page.client.tsx new file mode 100644 index 00000000..a82fd1b7 --- /dev/null +++ b/src/renderer/_default.page.client.tsx @@ -0,0 +1,20 @@ +import '@renderer/index.css'; +import { Shell } from '@renderer/shell'; +import { PageContextClient } from '@renderer/types'; + +import { hydrateRoot } from 'react-dom/client'; + +export const clientRouting = true; + +export async function render(pageContext: PageContextClient) { + const { Page, pageProps } = pageContext; + + if (!Page) throw new Error('Client-side render() hook expects pageContext.Page to be defined'); + + hydrateRoot( + document.getElementById('app')!, + + + + ); +} diff --git a/src/renderer/_default.page.server.tsx b/src/renderer/_default.page.server.tsx new file mode 100644 index 00000000..79018e03 --- /dev/null +++ b/src/renderer/_default.page.server.tsx @@ -0,0 +1,30 @@ +import { Shell } from '@renderer/shell'; +import { PageContextServer } from '@renderer/types'; + +import ReactDOMServer from 'react-dom/server'; +import { dangerouslySkipEscape, escapeInject } from 'vite-plugin-ssr/server'; + +export const passToClient = ['pageProps']; + +export function render(pageContext: PageContextServer) { + const { Page, pageProps } = pageContext; + + if (!Page) throw new Error('My render() hook expects pageContext.Page to be defined'); + + const pageHtml = ReactDOMServer.renderToString( + + + + ); + + return escapeInject` + + + + + + +
${dangerouslySkipEscape(pageHtml)}
+ + `; +} diff --git a/src/renderer/_error.page.tsx b/src/renderer/_error.page.tsx new file mode 100644 index 00000000..73711c64 --- /dev/null +++ b/src/renderer/_error.page.tsx @@ -0,0 +1,17 @@ +export function Page({ is404 }: { is404: boolean }) { + if (is404) { + return ( + <> +

404 Page Not Found

+

This page could not be found.

+ + ); + } else { + return ( + <> +

500 Internal Server Error

+

Something went wrong.

+ + ); + } +} diff --git a/src/index.css b/src/renderer/index.css similarity index 100% rename from src/index.css rename to src/renderer/index.css diff --git a/src/renderer/shell.tsx b/src/renderer/shell.tsx new file mode 100644 index 00000000..d16efe11 --- /dev/null +++ b/src/renderer/shell.tsx @@ -0,0 +1,17 @@ +import RelayProvider from '@components/relaysProvider'; + +import { PageContextProvider } from '@utils/hooks/usePageContext'; + +import { PageContext } from '@renderer/types'; + +import { StrictMode } from 'react'; + +export function Shell({ children, pageContext }: { children: React.ReactNode; pageContext: PageContext }) { + return ( + + + {children} + + + ); +} diff --git a/src/renderer/types.ts b/src/renderer/types.ts new file mode 100644 index 00000000..105b060b --- /dev/null +++ b/src/renderer/types.ts @@ -0,0 +1,35 @@ +import type { + PageContextBuiltIn, + /* +// When using Client Routing https://vite-plugin-ssr.com/clientRouting +PageContextBuiltInClientWithClientRouting as PageContextBuiltInClient +/*/ + // When using Server Routing + PageContextBuiltInClientWithServerRouting as PageContextBuiltInClient, //*/ +} from 'vite-plugin-ssr/types'; + +export type { PageContextServer }; +export type { PageContextClient }; +export type { PageContext }; +export type { PageProps }; + +type Page = (pageProps: PageProps) => React.ReactElement; +type PageProps = Record; + +export type PageContextCustom = { + Page: Page; + pageProps?: PageProps; + redirectTo?: string; + urlPathname: string; + exports: { + documentProps?: { + title?: string; + description?: string; + }; + }; +}; + +type PageContextServer = PageContextBuiltIn & PageContextCustom; +type PageContextClient = PageContextBuiltInClient & PageContextCustom; + +type PageContext = PageContextClient | PageContextServer; diff --git a/src/utils/hooks/usePageContext.tsx b/src/utils/hooks/usePageContext.tsx new file mode 100644 index 00000000..a1303f6b --- /dev/null +++ b/src/utils/hooks/usePageContext.tsx @@ -0,0 +1,22 @@ +// `usePageContext` allows us to access `pageContext` in any React component. +// See https://vite-plugin-ssr.com/pageContext-anywhere +import type { PageContext } from '@renderer/types'; + +import { createContext, useContext } from 'react'; + +const Context = createContext(undefined as any); + +export function PageContextProvider({ + pageContext, + children, +}: { + pageContext: PageContext; + children: React.ReactNode; +}) { + return {children}; +} + +export function usePageContext() { + const pageContext = useContext(Context); + return pageContext; +} diff --git a/tsconfig.json b/tsconfig.json index fc672f7d..95493d99 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,8 @@ "@components/*": ["src/components/*"], "@stores/*": ["src/stores/*"], "@utils/*": ["src/utils/*"], - "@assets/*": ["src/assets/*"] + "@assets/*": ["src/assets/*"], + "@renderer/*": ["src/renderer/*"] }, "target": "es2017", "lib": ["dom", "dom.iterable", "esnext"], diff --git a/vite.config.ts b/vite.config.ts index 27c24e7e..b78d40d1 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -4,5 +4,5 @@ import ssr from 'vite-plugin-ssr/plugin'; import viteTsconfigPaths from 'vite-tsconfig-paths'; export default defineConfig({ - plugins: [react(), ssr(), viteTsconfigPaths()], + plugins: [react(), ssr({ prerender: true }), viteTsconfigPaths()], });