Merge pull request #5 from reyamir/feat/improve-dx

Improved dev experience
This commit is contained in:
Ren Amamiya
2023-03-05 09:42:28 +07:00
committed by GitHub
48 changed files with 1828 additions and 1116 deletions

View File

@@ -5,13 +5,13 @@
"next/core-web-vitals", "next/core-web-vitals",
"plugin:react/recommended", "plugin:react/recommended",
"plugin:react/jsx-runtime", "plugin:react/jsx-runtime",
"plugin:react-hooks/recommended", "plugin:react-hooks/recommended",
"plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended",
"prettier" "prettier"
], ],
"rules": { "rules": {
"@typescript-eslint/no-unused-vars": "error", "@typescript-eslint/no-unused-vars": "warn",
"@typescript-eslint/no-explicit-any": "error" "@typescript-eslint/no-explicit-any": "warn"
}, },
"ignorePatterns": ["dist", "**/*.js", "**/*.json", "node_modules"] "ignorePatterns": ["dist", "**/*.js", "**/*.json", "node_modules"]
} }

View File

@@ -3,12 +3,19 @@
"trailingComma": "es5", "trailingComma": "es5",
"singleQuote": true, "singleQuote": true,
"tabWidth": 2, "tabWidth": 2,
"printWidth": 150, "printWidth": 120,
"useTabs": false, "useTabs": false,
"endOfLine": "lf", "endOfLine": "lf",
"bracketSpacing": true, "bracketSpacing": true,
"bracketSameLine": true, "bracketSameLine": false,
"importOrder": ["^@layouts/(.*)$", "^@pages/(.*)$", "^@components/(.*)$", "^@utils/(.*)$", "^@stores/(.*)$", "<THIRD_PARTY_MODULES>", "^[./]"], "importOrder": [
"^@layouts/(.*)$",
"^@pages/(.*)$",
"^@components/(.*)$",
"^@utils/(.*)$",
"<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"],

View File

@@ -51,7 +51,7 @@ Lume is "an ambitious nostr client", so I don't want to limit it be a part of yo
## Features ## Features
**Current**: v0.1.1-alpha **Current**: v0.1.2-alpha
- [x] create new key - [x] create new key
- [x] import private key (hex/nsec) - [x] import private key (hex/nsec)
@@ -64,15 +64,15 @@ Lume is "an ambitious nostr client", so I don't want to limit it be a part of yo
- [x] publish a note (support markdown) - [x] publish a note (support markdown)
- [x] update profile - [x] update profile
- [x] cache profile to local database - [x] cache profile to local database
- [x] offline support
- [x] implement newsfeed infinite loading
- [x] native notification
**Next**: **Next**:
- [ ] offline support
- [ ] implement newsfeed infinite loading
- [ ] handle tags (#[x]) in note - [ ] handle tags (#[x]) in note
- [ ] handle reply note - [ ] handle reply note
- [ ] integrate webtorrent - [ ] integrate webtorrent
- [ ] native notification
- [ ] publish a reply - [ ] publish a reply
- [ ] direct message - [ ] direct message
- [ ] multi accounts - [ ] multi accounts
@@ -83,3 +83,29 @@ Lume is "an ambitious nostr client", so I don't want to limit it be a part of yo
## Roadmap ## Roadmap
Public roadmap will be released when Lume reach v0.3.0 Public roadmap will be released when Lume reach v0.3.0
## Running dev build
Prerequisites:
- Nodejs >= 18.0.0
- Install pnpm: [docs](https://pnpm.io/)
- Setup Tauri: [docs](https://tauri.app/v1/guides/getting-started/prerequisites)
Clone repo:
```
git clone https://github.com/reyamir/lume-desktop.git
```
Install dependencies
```
pnpm install
```
Run development window
```
pnpm tauri dev
```

View File

@@ -23,15 +23,14 @@
"boring-avatars": "^1.7.0", "boring-avatars": "^1.7.0",
"framer-motion": "^9.1.7", "framer-motion": "^9.1.7",
"moment": "^2.29.4", "moment": "^2.29.4",
"nanostores": "^0.7.4", "next": "^13.2.3",
"next": "^13.2.1",
"next-remove-imports": "^1.0.10", "next-remove-imports": "^1.0.10",
"nostr-relaypool": "^0.5.3", "nostr-relaypool": "^0.5.3",
"nostr-tools": "^1.7.1", "nostr-tools": "^1.7.4",
"qrcode.react": "^3.1.0", "qrcode.react": "^3.1.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-hook-form": "^7.43.2", "react-hook-form": "^7.43.4",
"react-moment": "^1.1.3", "react-moment": "^1.1.3",
"react-player": "^2.11.2", "react-player": "^2.11.2",
"react-virtuoso": "^4.1.0", "react-virtuoso": "^4.1.0",
@@ -43,7 +42,7 @@
"@tailwindcss/typography": "^0.5.9", "@tailwindcss/typography": "^0.5.9",
"@tauri-apps/cli": "^1.2.3", "@tauri-apps/cli": "^1.2.3",
"@trivago/prettier-plugin-sort-imports": "^4.1.1", "@trivago/prettier-plugin-sort-imports": "^4.1.1",
"@types/node": "^18.14.2", "@types/node": "^18.14.6",
"@types/react": "^18.0.28", "@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"@typescript-eslint/eslint-plugin": "^5.54.0", "@typescript-eslint/eslint-plugin": "^5.54.0",
@@ -51,7 +50,7 @@
"autoprefixer": "^10.4.13", "autoprefixer": "^10.4.13",
"csstype": "^3.1.1", "csstype": "^3.1.1",
"eslint": "^8.35.0", "eslint": "^8.35.0",
"eslint-config-next": "^13.2.1", "eslint-config-next": "^13.2.3",
"eslint-config-prettier": "^8.6.0", "eslint-config-prettier": "^8.6.0",
"eslint-plugin-react": "^7.32.2", "eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
@@ -59,7 +58,7 @@
"lint-staged": "^13.1.2", "lint-staged": "^13.1.2",
"postcss": "^8.4.21", "postcss": "^8.4.21",
"prettier": "^2.8.4", "prettier": "^2.8.4",
"prettier-plugin-tailwindcss": "^0.2.3", "prettier-plugin-tailwindcss": "^0.2.4",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"tailwindcss": "^3.2.7", "tailwindcss": "^3.2.7",
"typescript": "^4.9.5" "typescript": "^4.9.5"

2170
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -145,8 +145,8 @@
.w-md-editor-toolbar-child { .w-md-editor-toolbar-child {
position: absolute; position: absolute;
border-radius: 3px; border-radius: 3px;
box-shadow: 0 0 0 1px var(--color-border-default), box-shadow: 0 0 0 1px var(--color-border-default), 0 0 0 var(--color-border-default),
0 0 0 var(--color-border-default), 0 1px 1px var(--color-border-default); 0 1px 1px var(--color-border-default);
background-color: var(--color-canvas-default); background-color: var(--color-canvas-default);
z-index: 1; z-index: 1;
display: none; display: none;

View File

@@ -7,12 +7,7 @@ type ActiveLinkProps = LinkProps & {
activeClassName: string; activeClassName: string;
}; };
const ActiveLink = ({ const ActiveLink = ({ children, activeClassName, className, ...props }: PropsWithChildren<ActiveLinkProps>) => {
children,
activeClassName,
className,
...props
}: PropsWithChildren<ActiveLinkProps>) => {
const { asPath, isReady } = useRouter(); const { asPath, isReady } = useRouter();
const [computedClassName, setComputedClassName] = useState(className); const [computedClassName, setComputedClassName] = useState(className);
@@ -26,8 +21,7 @@ const ActiveLink = ({
// Using URL().pathname to get rid of query and hash // Using URL().pathname to get rid of query and hash
const activePathname = new URL(asPath, location.href).pathname; const activePathname = new URL(asPath, location.href).pathname;
const newClassName = const newClassName = linkPathname === activePathname ? `${className} ${activeClassName}`.trim() : className;
linkPathname === activePathname ? `${className} ${activeClassName}`.trim() : className;
if (newClassName !== computedClassName) { if (newClassName !== computedClassName) {
setComputedClassName(newClassName); setComputedClassName(newClassName);

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import Image from 'next/image'; import Image from 'next/image';
import { memo } from 'react'; import { memo } from 'react';
@@ -14,7 +13,8 @@ export const Account = memo(function Account({ user, current }: { user: any; cur
onClick={() => setCurrentUser()} onClick={() => setCurrentUser()}
className={`relative h-11 w-11 shrink overflow-hidden rounded-full ${ className={`relative h-11 w-11 shrink overflow-hidden rounded-full ${
current === user.pubkey ? 'ring-1 ring-fuchsia-500 ring-offset-4 ring-offset-black' : '' current === user.pubkey ? 'ring-1 ring-fuchsia-500 ring-offset-4 ring-offset-black' : ''
}`}> }`}
>
{userData?.picture !== undefined ? ( {userData?.picture !== undefined ? (
<Image src={userData.picture} alt="user's avatar" fill={true} className="rounded-full object-cover" /> <Image src={userData.picture} alt="user's avatar" fill={true} className="rounded-full object-cover" />
) : ( ) : (

View File

@@ -1,5 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ import { Account } from '@components/columns/account/account';
import { Account } from '@components/accountBar/account';
import LumeSymbol from '@assets/icons/Lume'; import LumeSymbol from '@assets/icons/Lume';
import { PlusIcon } from '@radix-ui/react-icons'; import { PlusIcon } from '@radix-ui/react-icons';
@@ -8,7 +7,7 @@ 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 AccountColumn() {
const [users, setUsers] = useState([]); const [users, setUsers] = useState([]);
const [currentUser]: any = useLocalStorage('current-user'); const [currentUser]: any = useLocalStorage('current-user');
@@ -31,7 +30,8 @@ export default function AccountBar() {
))} ))}
<Link <Link
href="/onboarding" href="/onboarding"
className="group relative flex h-11 w-11 shrink cursor-pointer items-center justify-center overflow-hidden rounded-full border-2 border-dashed border-zinc-600 hover:border-zinc-400"> className="group relative flex h-11 w-11 shrink cursor-pointer items-center justify-center overflow-hidden rounded-full border-2 border-dashed border-zinc-600 hover:border-zinc-400"
>
<PlusIcon className="h-4 w-4 text-zinc-400 group-hover:text-zinc-200" /> <PlusIcon className="h-4 w-4 text-zinc-400 group-hover:text-zinc-200" />
</Link> </Link>
</div> </div>

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { RelayContext } from '@components/contexts/relay'; import { RelayContext } from '@components/contexts/relay';
import { dateToUnix } from '@utils/getDate'; import { dateToUnix } from '@utils/getDate';
@@ -30,7 +29,9 @@ 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">Post</span> <span className="absolute inset-px z-10 inline-flex items-center justify-center rounded bg-zinc-900 text-zinc-200">
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>
), ),

View File

@@ -1,12 +1,11 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ActiveLink from '@components/activeLink'; import ActiveLink from '@components/activeLink';
import CreatePost from '@components/navigatorBar/createPost'; import CreatePost from '@components/columns/navigator/createPost';
import { ProfileMenu } from '@components/navigatorBar/profileMenu'; import { UserDropdownMenu } from '@components/columns/navigator/userDropdownMenu';
import { PlusIcon } from '@radix-ui/react-icons'; import { PlusIcon } from '@radix-ui/react-icons';
import { useLocalStorage } from '@rehooks/local-storage'; import { useLocalStorage } from '@rehooks/local-storage';
export default function NavigatorBar() { export default function NavigatorColumn() {
const [currentUser]: any = useLocalStorage('current-user'); const [currentUser]: any = useLocalStorage('current-user');
const profile = JSON.parse(currentUser.metadata); const profile = JSON.parse(currentUser.metadata);
@@ -18,7 +17,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} /> <UserDropdownMenu 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>
@@ -30,22 +29,27 @@ export default function NavigatorBar() {
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div className="flex items-center justify-between px-2"> <div className="flex items-center justify-between px-2">
<h3 className="text-sm font-bold text-zinc-400">Newsfeed</h3> <h3 className="text-sm font-bold text-zinc-400">Newsfeed</h3>
<button type="button" className="group flex h-6 w-6 items-center justify-center rounded-full hover:bg-zinc-900"> <button
type="button"
className="group flex h-6 w-6 items-center justify-center rounded-full hover:bg-zinc-900"
>
<PlusIcon className="h-3 w-3 text-zinc-400 group-hover:text-zinc-100" /> <PlusIcon className="h-3 w-3 text-zinc-400 group-hover:text-zinc-100" />
</button> </button>
</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={`/feed/following`} href={`/newsfeed/following`}
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>#</span> <span>#</span>
<span>following</span> <span>following</span>
</ActiveLink> </ActiveLink>
<ActiveLink <ActiveLink
href={`/feed/global`} href={`/newsfeed/global`}
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>#</span> <span>#</span>
<span>global</span> <span>global</span>
</ActiveLink> </ActiveLink>
@@ -55,7 +59,10 @@ export default function NavigatorBar() {
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div className="flex items-center justify-between px-2"> <div className="flex items-center justify-between px-2">
<h3 className="text-sm font-bold text-zinc-400">Direct Messages</h3> <h3 className="text-sm font-bold text-zinc-400">Direct Messages</h3>
<button type="button" className="group flex h-6 w-6 items-center justify-center rounded-full hover:bg-zinc-900"> <button
type="button"
className="group flex h-6 w-6 items-center justify-center rounded-full hover:bg-zinc-900"
>
<PlusIcon className="h-3 w-3 text-zinc-400 group-hover:text-zinc-100" /> <PlusIcon className="h-3 w-3 text-zinc-400 group-hover:text-zinc-100" />
</button> </button>
</div> </div>

View File

@@ -5,7 +5,7 @@ import { useRouter } from 'next/router';
import { nip19 } from 'nostr-tools'; import { nip19 } from 'nostr-tools';
import { memo } from 'react'; import { memo } from 'react';
export const ProfileMenu = memo(function ProfileMenu({ pubkey }: { pubkey: string }) { export const UserDropdownMenu = memo(function ProfileMenu({ pubkey }: { pubkey: string }) {
const router = useRouter(); const router = useRouter();
const viewProfile = () => { const viewProfile = () => {
@@ -31,20 +31,24 @@ export const ProfileMenu = memo(function ProfileMenu({ pubkey }: { pubkey: strin
<DropdownMenu.Portal> <DropdownMenu.Portal>
<DropdownMenu.Content <DropdownMenu.Content
className="min-w-[220px] rounded-md border border-white/20 bg-zinc-800 p-1 shadow-lg shadow-black/30 will-change-[opacity,transform] data-[side=top]:animate-slideDownAndFade data-[side=right]:animate-slideLeftAndFade data-[side=bottom]:animate-slideUpAndFade data-[side=left]:animate-slideRightAndFade" className="min-w-[220px] rounded-md border border-white/20 bg-zinc-800 p-1 shadow-lg shadow-black/30 will-change-[opacity,transform] data-[side=top]:animate-slideDownAndFade data-[side=right]:animate-slideLeftAndFade data-[side=bottom]:animate-slideUpAndFade data-[side=left]:animate-slideRightAndFade"
sideOffset={2}> sideOffset={2}
>
<DropdownMenu.Item <DropdownMenu.Item
onClick={() => viewProfile()} onClick={() => viewProfile()}
className="group relative flex h-[30px] select-none items-center rounded px-1 pl-6 text-sm leading-none text-zinc-100 outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-zinc-700 data-[highlighted]:text-fuchsia-400 data-[disabled]:text-zinc-400"> className="group relative flex h-[30px] select-none items-center rounded px-1 pl-6 text-sm leading-none text-zinc-100 outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-zinc-700 data-[highlighted]:text-fuchsia-400 data-[disabled]:text-zinc-400"
>
View profile View profile
</DropdownMenu.Item> </DropdownMenu.Item>
<DropdownMenu.Item <DropdownMenu.Item
onClick={() => updateProfile()} onClick={() => updateProfile()}
className="group relative flex h-[30px] select-none items-center rounded px-1 pl-6 text-sm leading-none text-zinc-100 outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-zinc-700 data-[highlighted]:text-fuchsia-400 data-[disabled]:text-zinc-400"> className="group relative flex h-[30px] select-none items-center rounded px-1 pl-6 text-sm leading-none text-zinc-100 outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-zinc-700 data-[highlighted]:text-fuchsia-400 data-[disabled]:text-zinc-400"
>
Update profile Update profile
</DropdownMenu.Item> </DropdownMenu.Item>
<DropdownMenu.Item <DropdownMenu.Item
onClick={() => copyPubkey()} onClick={() => copyPubkey()}
className="group relative flex h-[30px] select-none items-center rounded px-1 pl-6 text-sm leading-none text-zinc-100 outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-zinc-700 data-[highlighted]:text-fuchsia-400 data-[disabled]:text-zinc-400"> className="group relative flex h-[30px] select-none items-center rounded px-1 pl-6 text-sm leading-none text-zinc-100 outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-zinc-700 data-[highlighted]:text-fuchsia-400 data-[disabled]:text-zinc-400"
>
Copy public key Copy public key
</DropdownMenu.Item> </DropdownMenu.Item>
<DropdownMenu.Item className="group relative flex h-[30px] select-none items-center rounded px-1 pl-6 text-sm leading-none text-zinc-100 outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-zinc-700 data-[highlighted]:text-fuchsia-400 data-[disabled]:text-zinc-400"> <DropdownMenu.Item className="group relative flex h-[30px] select-none items-center rounded px-1 pl-6 text-sm leading-none text-zinc-100 outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-zinc-700 data-[highlighted]:text-fuchsia-400 data-[disabled]:text-zinc-400">

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { writeStorage } from '@rehooks/local-storage'; import { writeStorage } from '@rehooks/local-storage';
import { createContext, useEffect, useState } from 'react'; import { createContext, useEffect, useState } from 'react';
import Database from 'tauri-plugin-sql-api'; import Database from 'tauri-plugin-sql-api';

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { RelayPool } from 'nostr-relaypool'; import { RelayPool } from 'nostr-relaypool';
import { createContext, useMemo } from 'react'; import { createContext, useMemo } from 'react';

View File

@@ -1,100 +0,0 @@
import { EnvelopeClosedIcon, PlusIcon, UpdateIcon } from '@radix-ui/react-icons';
import Image from 'next/image';
const sampleData = [
{
name: 'Dick Whitman (🌎/21M)',
role: 'dickwhitman@nostrplebs.com',
imageUrl: 'https://pbs.twimg.com/profile_images/1594930968325984256/TjMXaXBE_400x400.jpg',
},
{
name: 'Jack',
role: 'npub1sg6plzptd64u62a878hep2kev88swjh3tw00gjsfl8f237lmu63q0uf63m',
imageUrl: 'https://pbs.twimg.com/profile_images/1115644092329758721/AFjOr-K8_400x400.jpg',
},
{
name: 'Sats Symbol',
role: 'npub1mqngkfwfyv2ckv7hshck9pqucpz08tktde2jukr3hheatup2y2tqnzc32w',
imageUrl: 'https://pbs.twimg.com/profile_images/1563388888860594177/7evrI1uB_400x400.jpg',
},
];
export default function Empty() {
return (
<div className="mx-auto max-w-lg pt-8">
<div>
<div className="text-center">
<svg
className="mx-auto h-12 w-12 text-zinc-300"
fill="none"
stroke="currentColor"
viewBox="0 0 48 48"
aria-hidden="true">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M34 40h10v-4a6 6 0 00-10.712-3.714M34 40H14m20 0v-4a9.971 9.971 0 00-.712-3.714M14 40H4v-4a6 6 0 0110.713-3.714M14 40v-4c0-1.313.253-2.566.713-3.714m0 0A10.003 10.003 0 0124 26c4.21 0 7.813 2.602 9.288 6.286M30 14a6 6 0 11-12 0 6 6 0 0112 0zm12 6a4 4 0 11-8 0 4 4 0 018 0zm-28 0a4 4 0 11-8 0 4 4 0 018 0z"></path>
</svg>
<h2 className="mt-2 text-lg font-medium text-zinc-100">
You haven&apos;t followed anyone yet
</h2>
<p className="mt-1 text-sm text-zinc-500">
You can send invite via email to your friend and lume will onboard them into nostr or
follow some people in suggested below
</p>
</div>
<form action="#" className="relative mt-6">
<input
type="email"
name="email"
id="email"
className="block h-11 w-full rounded-lg border-none px-4 shadow-md ring-1 ring-white/10 placeholder:text-zinc-500 dark:bg-zinc-800 dark:text-zinc-200"
placeholder="Enter an email"
/>
<button className="absolute right-0.5 top-1/2 inline-flex h-10 -translate-y-1/2 transform items-center gap-1 rounded-md border border-zinc-600 bg-zinc-700 px-4 text-sm font-medium text-zinc-200 shadow-md">
<EnvelopeClosedIcon className="h-4 w-4" />
Invite
</button>
</form>
</div>
<div className="mt-10 flex flex-col items-start gap-4">
<div className="flex w-full items-center justify-between">
<h3 className="text-sm font-medium text-zinc-500">Suggestions</h3>
<UpdateIcon className="h-4 w-4 text-zinc-600" />
</div>
<ul className="w-full divide-y divide-zinc-800 border-t border-b border-zinc-800">
{sampleData.map((person, index) => (
<li key={index} className="flex items-center justify-between space-x-3 py-4">
<div className="flex min-w-0 flex-1 items-center space-x-3">
<div className="relative h-10 w-10 flex-shrink-0">
<Image
className="rounded-full object-cover"
src={person.imageUrl}
alt={person.name}
fill={true}
/>
</div>
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-medium text-zinc-200">{person.name}</p>
<p className="w-56 truncate text-sm font-medium text-zinc-500">{person.role}</p>
</div>
</div>
<div className="flex-shrink-0">
<button
type="button"
className="inline-flex items-center rounded-full border border-zinc-700 bg-zinc-800 px-3 py-1 text-xs font-medium text-zinc-400 shadow-sm hover:bg-zinc-800 focus:outline-none focus:ring-2 focus:ring-fuchsia-600 focus:ring-offset-2">
<PlusIcon className="-ml-1 h-5 w-5" />
<span className="text-sm font-medium text-zinc-300">Follow</span>
</button>
</div>
</li>
))}
</ul>
<button className="bg-[radial-gradient(ellipse_at_bottom_right,_var(--tw-gradient-stops))] from-gray-300 via-fuchsia-600 to-orange-600 bg-clip-text text-sm font-bold text-transparent">
Explore more
</button>
</div>
</div>
);
}

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import Avatar from 'boring-avatars'; import Avatar from 'boring-avatars';
import Image from 'next/image'; import Image from 'next/image';
import { memo, useEffect, useState } from 'react'; import { memo, useEffect, useState } from 'react';
@@ -9,7 +8,7 @@ export const ImageWithFallback = memo(function ImageWithFallback({
fill, fill,
className, className,
}: { }: {
src: any; src: string;
alt: string; alt: string;
fill: boolean; fill: boolean;
className: string; className: string;

View File

@@ -1,18 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
export function IncomingList({ data }: { data: any }) {
const list: any = Array.from(new Set(data.map((item: any) => item.pubkey)));
if (list.length > 0) {
return (
<>
{list.map((item, index) => (
<div key={index}>
<p>{item}</p>
</div>
))}
</>
);
} else {
return <></>;
}
}

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { RelayContext } from '@components/contexts/relay'; import { RelayContext } from '@components/contexts/relay';
import { dateToUnix } from '@utils/getDate'; import { dateToUnix } from '@utils/getDate';
@@ -67,7 +66,11 @@ export default function Reaction({ eventID, eventPubkey }: { eventID: string; ev
return ( return (
<button onClick={(e) => handleReaction(e)} className="group flex w-16 items-center gap-1.5 text-sm text-zinc-500"> <button 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 ? <HeartFilledIcon className="h-4 w-4 group-hover:text-red-400" /> : <HeartIcon className="h-4 w-4 text-zinc-500" />} {isReact ? (
<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>

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { ChatBubbleIcon } from '@radix-ui/react-icons'; import { ChatBubbleIcon } from '@radix-ui/react-icons';
import { useState } from 'react'; import { useState } from 'react';

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { DatabaseContext } from '@components/contexts/database'; import { DatabaseContext } from '@components/contexts/database';
import { ImageWithFallback } from '@components/imageWithFallback'; import { ImageWithFallback } from '@components/imageWithFallback';
@@ -17,7 +16,9 @@ export const User = memo(function User({ pubkey, time }: { pubkey: string; time:
async (event) => { async (event) => {
const metadata: any = JSON.parse(event.content); const metadata: any = JSON.parse(event.content);
await db.execute(`INSERT OR IGNORE INTO cache_profiles (id, metadata) VALUES ("${pubkey}", '${JSON.stringify(metadata)}')`); await db.execute(
`INSERT OR IGNORE INTO cache_profiles (id, metadata) VALUES ("${pubkey}", '${JSON.stringify(metadata)}')`
);
setProfile(metadata); setProfile(metadata);
}, },
[db, pubkey] [db, pubkey]
@@ -53,13 +54,20 @@ export const User = memo(function User({ pubkey, time }: { pubkey: string; time:
{profile.picture ? ( {profile.picture ? (
<ImageWithFallback src={profile.picture} alt={pubkey} fill={true} className="rounded-full object-cover" /> <ImageWithFallback src={profile.picture} alt={pubkey} fill={true} className="rounded-full object-cover" />
) : ( ) : (
<Avatar size={44} name={pubkey} variant="beam" colors={['#FEE2E2', '#FEF3C7', '#F59E0B', '#EC4899', '#D946EF', '#8B5CF6']} /> <Avatar
size={44}
name={pubkey}
variant="beam"
colors={['#FEE2E2', '#FEF3C7', '#F59E0B', '#EC4899', '#D946EF', '#8B5CF6']}
/>
)} )}
</div> </div>
<div className="flex w-full flex-1 items-start justify-between"> <div className="flex w-full flex-1 items-start justify-between">
<div className="flex w-full justify-between"> <div className="flex w-full justify-between">
<div className="flex items-baseline gap-2 text-sm"> <div className="flex items-baseline gap-2 text-sm">
<span className="font-bold leading-tight">{profile.name ? profile.name : truncate(pubkey, 16, ' .... ')}</span> <span className="font-bold leading-tight">
{profile.name ? profile.name : truncate(pubkey, 16, ' .... ')}
</span>
<span className="leading-tight text-zinc-500">·</span> <span className="leading-tight text-zinc-500">·</span>
<Moment fromNow unix className="text-zinc-500"> <Moment fromNow unix className="text-zinc-500">
{time} {time}

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { truncate } from '@utils/truncate'; import { truncate } from '@utils/truncate';
import { memo, useEffect, useState } from 'react'; import { memo, useEffect, useState } from 'react';

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { ImageWithFallback } from '@components/imageWithFallback'; import { ImageWithFallback } from '@components/imageWithFallback';
import { truncate } from '@utils/truncate'; import { truncate } from '@utils/truncate';
@@ -25,14 +24,23 @@ export const UserWithUsername = memo(function UserWithUsername({ pubkey }: { pub
{profile.picture ? ( {profile.picture ? (
<ImageWithFallback src={profile.picture} alt={pubkey} fill={true} className="rounded-full object-cover" /> <ImageWithFallback src={profile.picture} alt={pubkey} fill={true} className="rounded-full object-cover" />
) : ( ) : (
<Avatar size={44} name={pubkey} variant="beam" colors={['#FEE2E2', '#FEF3C7', '#F59E0B', '#EC4899', '#D946EF', '#8B5CF6']} /> <Avatar
size={44}
name={pubkey}
variant="beam"
colors={['#FEE2E2', '#FEF3C7', '#F59E0B', '#EC4899', '#D946EF', '#8B5CF6']}
/>
)} )}
</div> </div>
<div className="flex w-full flex-1 items-start justify-between"> <div className="flex w-full flex-1 items-start justify-between">
<div className="flex w-full justify-between"> <div className="flex w-full justify-between">
<div className="flex flex-col gap-1 text-sm"> <div className="flex flex-col gap-1 text-sm">
<span className="font-bold leading-tight">{profile.name ? profile.name : truncate(pubkey, 16, ' .... ')}</span> <span className="font-bold leading-tight">
<span className="text-zinc-500">{profile.username ? profile.username : truncate(pubkey, 16, ' .... ')}</span> {profile.name ? profile.name : truncate(pubkey, 16, ' .... ')}
</span>
<span className="text-zinc-500">
{profile.username ? profile.username : truncate(pubkey, 16, ' .... ')}
</span>
</div> </div>
<div> <div>
<DotsHorizontalIcon className="h-4 w-4 text-zinc-500" /> <DotsHorizontalIcon className="h-4 w-4 text-zinc-500" />

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { DatabaseContext } from '@components/contexts/database'; import { DatabaseContext } from '@components/contexts/database';
import { RelayContext } from '@components/contexts/relay'; import { RelayContext } from '@components/contexts/relay';
@@ -40,7 +39,14 @@ export const NoteConnector = memo(function NoteConnector({
`INSERT OR IGNORE INTO `INSERT OR IGNORE INTO
cache_notes cache_notes
(id, pubkey, created_at, kind, tags, content) VALUES (id, pubkey, created_at, kind, tags, content) VALUES
("${event.id}", "${event.pubkey}", "${event.created_at}", "${event.kind}", '${JSON.stringify(event.tags)}', "${event.content}");` (
"${event.id}",
"${event.pubkey}",
"${event.created_at}",
"${event.kind}",
'${JSON.stringify(event.tags)}',
"${event.content}"
);`
); );
}, },
[db] [db]
@@ -85,7 +91,10 @@ export const NoteConnector = memo(function NoteConnector({
<h3 className="text-sm font-semibold text-zinc-500"># following</h3> <h3 className="text-sm font-semibold text-zinc-500"># following</h3>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<button onClick={() => reloadNewsfeed()} className={`${reload ? 'animate-spin' : ''} rounded-full p-1 hover:bg-zinc-800`}> <button
onClick={() => reloadNewsfeed()}
className={`${reload ? 'animate-spin' : ''} rounded-full p-1 hover:bg-zinc-800`}
>
<ReloadIcon className="h-3.5 w-3.5 text-zinc-500" /> <ReloadIcon className="h-3.5 w-3.5 text-zinc-500" />
</button> </button>
<div className="inline-flex items-center gap-1 rounded-full border border-zinc-700 bg-zinc-800 px-2.5 py-1"> <div className="inline-flex items-center gap-1 rounded-full border border-zinc-700 bg-zinc-800 px-2.5 py-1">

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import Reaction from '@components/note/atoms/reaction'; import Reaction from '@components/note/atoms/reaction';
import Reply from '@components/note/atoms/reply'; import Reply from '@components/note/atoms/reply';
import { User } from '@components/note/atoms/user'; import { User } from '@components/note/atoms/user';

View File

@@ -7,7 +7,8 @@ export default function LinkCard({ data }: { data: object }) {
<Link <Link
href={data['url']} href={data['url']}
target={'_blank'} target={'_blank'}
className="relative mt-2 flex flex-col overflow-hidden rounded-xl border border-zinc-700"> className="relative mt-2 flex flex-col overflow-hidden rounded-xl border border-zinc-700"
>
<div className="relative aspect-video h-auto w-full"> <div className="relative aspect-video h-auto w-full">
<Image src={data['image']} alt="image preview" fill={true} className="object-cover" /> <Image src={data['image']} alt="image preview" fill={true} className="object-cover" />
</div> </div>

View File

@@ -1,53 +0,0 @@
import { RelayContext } from '@components/contexts/relay';
import { Content } from '@components/note/content';
import NoteReply from '@components/note/modal/noteReply';
import useLocalStorage from '@rehooks/local-storage';
import { memo, useContext, useState } from 'react';
/* eslint-disable @typescript-eslint/no-explicit-any */
const Modal = ({ event }: { event: any }) => {
const relayPool: any = useContext(RelayContext);
const [relays]: any = useLocalStorage('relays');
const [events, setEvents] = useState([]);
relayPool.subscribe(
[
{
'#e': [event.id],
since: event.created_at,
kinds: [1],
},
],
relays,
(event: any) => {
setEvents((events) => [event, ...events]);
},
undefined,
(events: any, relayURL: any) => {
console.log(events, relayURL);
}
);
return (
<div className="flex min-h-full items-center justify-center p-4">
<div className="relative h-[90vh] w-full max-w-3xl transform overflow-hidden rounded-lg text-zinc-100 shadow-modal transition-all">
<div className="absolute top-0 left-0 h-full w-full bg-black bg-opacity-20 backdrop-blur-lg"></div>
<div className="relative z-10 h-full p-4">
<div className="relative h-full overflow-auto rounded-lg border-[0.5px] border-white/30 bg-zinc-800 p-4 shadow-inner">
<div className="flex flex-col gap-4">
<Content data={event} />
</div>
<div className="flex flex-col gap-2 divide-y divide-zinc-700">
{events.map((item) => (
<NoteReply key={item.id} event={item} />
))}
</div>
</div>
</div>
</div>
</div>
);
};
export default memo(Modal);

View File

@@ -1,10 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Content } from '@components/note/content';
export default function NoteReply({ event }: { event: any }) {
return (
<div className="flex h-min min-h-min w-full select-text flex-col border-b border-zinc-800 py-4 hover:bg-zinc-800">
<Content data={event} />
</div>
);
}

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { RelayContext } from '@components/contexts/relay'; import { RelayContext } from '@components/contexts/relay';
import { UserRepost } from '@components/note/atoms/userRepost'; import { UserRepost } from '@components/note/atoms/userRepost';
import { Content } from '@components/note/content'; import { Content } from '@components/note/content';

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Content } from '@components/note/content'; import { Content } from '@components/note/content';
import { memo } from 'react'; import { memo } from 'react';

3
src/layouts/base.tsx Normal file
View File

@@ -0,0 +1,3 @@
export default function BaseLayout({ children }: { children: React.ReactNode }) {
return <div className="h-screen w-screen bg-white text-zinc-900 dark:bg-near-black dark:text-white">{children}</div>;
}

View File

@@ -1,7 +0,0 @@
export default function BaseLayout({ children }: { children: React.ReactNode }) {
return (
<div className="h-screen w-screen bg-white text-zinc-900 dark:bg-near-black dark:text-white">
{children}
</div>
);
}

View File

@@ -1,11 +1,8 @@
export default function FullLayout({ children }: { children: React.ReactNode }) { export default function FullscreenLayout({ children }: { children: React.ReactNode }) {
return ( return (
<div className="bg-gradient-radial-page relative h-full overflow-hidden"> <div className="bg-gradient-radial-page relative h-full overflow-hidden">
{/* dragging area */} {/* dragging area */}
<div <div data-tauri-drag-region className="absolute top-0 left-0 z-20 h-16 w-full bg-transparent" />
data-tauri-drag-region
className="absolute top-0 left-0 z-20 h-16 w-full bg-transparent"
/>
{/* end dragging area */} {/* end dragging area */}
{/* content */} {/* content */}
<div className="relative z-10 h-full">{children}</div> <div className="relative z-10 h-full">{children}</div>

View File

@@ -1,16 +1,16 @@
import AccountBar from '@components/accountBar'; import AccountColumn from '@components/columns/account';
import NavigatorBar from '@components/navigatorBar'; import NavigatorColumn from '@components/columns/navigator';
export default function NewsFeedLayout({ children }: { children: React.ReactNode }) { export default function NewsFeedLayout({ children }: { children: React.ReactNode }) {
return ( return (
<div className="flex h-full w-full flex-row"> <div className="flex h-full w-full flex-row">
<div className="relative h-full w-[70px] shrink-0 border-r border-zinc-900"> <div className="relative h-full w-[70px] shrink-0 border-r border-zinc-900">
<div data-tauri-drag-region className="absolute top-0 left-0 h-12 w-full" /> <div data-tauri-drag-region className="absolute top-0 left-0 h-12 w-full" />
<AccountBar /> <AccountColumn />
</div> </div>
<div className="grid grow grid-cols-4"> <div className="grid grow grid-cols-4">
<div className="col-span-1"> <div className="col-span-1">
<NavigatorBar /> <NavigatorColumn />
</div> </div>
<div className="col-span-3 m-3 ml-0 overflow-hidden rounded-lg border border-zinc-800 bg-zinc-900 shadow-input shadow-black/20"> <div className="col-span-3 m-3 ml-0 overflow-hidden rounded-lg border border-zinc-800 bg-zinc-900 shadow-input shadow-black/20">
<div className="h-full w-full rounded-lg">{children}</div> <div className="h-full w-full rounded-lg">{children}</div>

View File

@@ -1,6 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import AccountBar from '@components/accountBar';
import ActiveLink from '@components/activeLink'; import ActiveLink from '@components/activeLink';
import AccountColumn from '@components/columns/account';
import { useLocalStorage } from '@rehooks/local-storage'; import { useLocalStorage } from '@rehooks/local-storage';
@@ -11,7 +10,7 @@ export default function UserLayout({ children }: { children: React.ReactNode })
<div className="flex h-full w-full flex-row"> <div className="flex h-full w-full flex-row">
<div className="relative h-full w-[70px] shrink-0 border-r border-zinc-900"> <div className="relative h-full w-[70px] shrink-0 border-r border-zinc-900">
<div data-tauri-drag-region className="absolute top-0 left-0 h-12 w-full" /> <div data-tauri-drag-region className="absolute top-0 left-0 h-12 w-full" />
<AccountBar /> <AccountColumn />
</div> </div>
<div className="grid grow grid-cols-4"> <div className="grid grow grid-cols-4">
<div className="col-span-1"> <div className="col-span-1">
@@ -27,13 +26,15 @@ export default function UserLayout({ children }: { children: React.ReactNode })
<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>
</ActiveLink> </ActiveLink>
</div> </div>

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import DatabaseProvider from '@components/contexts/database'; import DatabaseProvider from '@components/contexts/database';
import RelayProvider from '@components/contexts/relay'; import RelayProvider from '@components/contexts/relay';

View File

@@ -1,6 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ import BaseLayout from '@layouts/base';
import BaseLayout from '@layouts/baseLayout'; import FullscreenLayout from '@layouts/fullscreen';
import FullLayout from '@layouts/fullLayout';
import LumeSymbol from '@assets/icons/Lume'; import LumeSymbol from '@assets/icons/Lume';
import { useLocalStorage } from '@rehooks/local-storage'; import { useLocalStorage } from '@rehooks/local-storage';
@@ -23,7 +22,7 @@ export default function Page() {
} else { } else {
setTimeout(() => { setTimeout(() => {
setLoading(false); setLoading(false);
router.push('/feed/following'); router.push('/newsfeed/following');
}, 1500); }, 1500);
} }
}, [currentUser, router]); }, [currentUser, router]);
@@ -36,12 +35,16 @@ export default function Page() {
<LumeSymbol className="h-16 w-16 text-white" /> <LumeSymbol className="h-16 w-16 text-white" />
</motion.div> </motion.div>
<div className="flex flex-col items-center gap-0.5"> <div className="flex flex-col items-center gap-0.5">
<motion.h2 layoutId="subtitle" className="bg-gradient-to-br from-zinc-200 to-zinc-400 bg-clip-text text-4xl font-medium text-transparent"> <motion.h2
layoutId="subtitle"
className="bg-gradient-to-br from-zinc-200 to-zinc-400 bg-clip-text text-4xl font-medium text-transparent"
>
A censorship-resistant social network A censorship-resistant social network
</motion.h2> </motion.h2>
<motion.h1 <motion.h1
layoutId="title" layoutId="title"
className="bg-[radial-gradient(ellipse_at_bottom_right,_var(--tw-gradient-stops))] from-gray-300 via-fuchsia-600 to-orange-600 bg-clip-text text-5xl font-bold text-transparent"> className="bg-[radial-gradient(ellipse_at_bottom_right,_var(--tw-gradient-stops))] from-gray-300 via-fuchsia-600 to-orange-600 bg-clip-text text-5xl font-bold text-transparent"
>
built on nostr built on nostr
</motion.h1> </motion.h1>
</div> </div>
@@ -49,12 +52,18 @@ export default function Page() {
<div className="flex items-center gap-2 pb-16"> <div className="flex items-center gap-2 pb-16">
<div className="h-10"> <div className="h-10">
{loading ? ( {loading ? (
<svg className="h-5 w-5 animate-spin text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> <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> <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"
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>
) : ( ) : (
<></> <></>
@@ -65,7 +74,8 @@ export default function Page() {
<div className="absolute inset-0 bg-gradient-to-r from-fuchsia-400/10 to-orange-100/10 opacity-100 [mask-image:radial-gradient(farthest-side_at_top,white,transparent)]"> <div className="absolute inset-0 bg-gradient-to-r from-fuchsia-400/10 to-orange-100/10 opacity-100 [mask-image:radial-gradient(farthest-side_at_top,white,transparent)]">
<svg <svg
aria-hidden="true" aria-hidden="true"
className="dark:fill-white/2.5 absolute inset-x-0 inset-y-[-50%] h-[200%] w-full skew-y-[-18deg] fill-black/40 stroke-black/50 mix-blend-overlay dark:stroke-white/5"> className="dark:fill-white/2.5 absolute inset-x-0 inset-y-[-50%] h-[200%] w-full skew-y-[-18deg] fill-black/40 stroke-black/50 mix-blend-overlay dark:stroke-white/5"
>
<defs> <defs>
<pattern id=":R11d6:" width="72" height="56" patternUnits="userSpaceOnUse" x="-12" y="4"> <pattern id=":R11d6:" width="72" height="56" patternUnits="userSpaceOnUse" x="-12" y="4">
<path d="M.5 56V.5H72" fill="none"></path> <path d="M.5 56V.5H72" fill="none"></path>
@@ -86,11 +96,17 @@ export default function Page() {
} }
Page.getLayout = function getLayout( Page.getLayout = function getLayout(
page: string | number | boolean | ReactElement<unknown, string | JSXElementConstructor<unknown>> | ReactFragment | ReactPortal page:
| string
| number
| boolean
| ReactElement<unknown, string | JSXElementConstructor<unknown>>
| ReactFragment
| ReactPortal
) { ) {
return ( return (
<BaseLayout> <BaseLayout>
<FullLayout>{page}</FullLayout> <FullscreenLayout>{page}</FullscreenLayout>
</BaseLayout> </BaseLayout>
); );
}; };

View File

@@ -0,0 +1,28 @@
import BaseLayout from '@layouts/base';
import NewsFeedLayout from '@layouts/newsfeed';
import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal } from 'react';
export default function Page() {
return (
<div className="h-full w-full">
<p>Global</p>
</div>
);
}
Page.getLayout = function getLayout(
page:
| string
| number
| boolean
| ReactElement<unknown, string | JSXElementConstructor<unknown>>
| ReactFragment
| ReactPortal
) {
return (
<BaseLayout>
<NewsFeedLayout>{page}</NewsFeedLayout>
</BaseLayout>
);
};

View File

@@ -1,6 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ import BaseLayout from '@layouts/base';
import BaseLayout from '@layouts/baseLayout'; import NewsFeedLayout from '@layouts/newsfeed';
import NewsFeedLayout from '@layouts/newsfeedLayout';
import { DatabaseContext } from '@components/contexts/database'; import { DatabaseContext } from '@components/contexts/database';
import { NoteConnector } from '@components/note/connector'; import { NoteConnector } from '@components/note/connector';
@@ -69,7 +68,9 @@ export default function Page() {
useEffect(() => { useEffect(() => {
const getData = async () => { const getData = async () => {
const result = await db.select( const result = await db.select(
`SELECT * FROM cache_notes WHERE created_at <= ${dateToUnix(now.current)} ORDER BY created_at DESC LIMIT ${limit.current}` `SELECT * FROM cache_notes WHERE created_at <= ${dateToUnix(now.current)} ORDER BY created_at DESC LIMIT ${
limit.current
}`
); );
if (result) { if (result) {
setData(result); setData(result);
@@ -94,7 +95,8 @@ export default function Page() {
<div className="absolute top-16 left-1/2 z-50 -translate-x-1/2 transform"> <div className="absolute top-16 left-1/2 z-50 -translate-x-1/2 transform">
<button <button
onClick={() => loadNewest()} onClick={() => loadNewest()}
className="inline-flex h-8 transform items-center justify-center gap-1 rounded-full bg-[radial-gradient(ellipse_at_bottom_right,_var(--tw-gradient-stops))] from-gray-300 via-fuchsia-600 to-orange-600 pl-3 pr-3.5 text-sm shadow-lg active:translate-y-1"> className="inline-flex h-8 transform items-center justify-center gap-1 rounded-full bg-[radial-gradient(ellipse_at_bottom_right,_var(--tw-gradient-stops))] from-gray-300 via-fuchsia-600 to-orange-600 pl-3 pr-3.5 text-sm shadow-lg active:translate-y-1"
>
<ArrowUpIcon className="h-4 w-4" /> <ArrowUpIcon className="h-4 w-4" />
<span className="drop-shadow-md">Load newest</span> <span className="drop-shadow-md">Load newest</span>
</button> </button>
@@ -125,7 +127,13 @@ export default function Page() {
} }
Page.getLayout = function getLayout( Page.getLayout = function getLayout(
page: string | number | boolean | ReactElement<unknown, string | JSXElementConstructor<unknown>> | ReactFragment | ReactPortal page:
| string
| number
| boolean
| ReactElement<unknown, string | JSXElementConstructor<unknown>>
| ReactFragment
| ReactPortal
) { ) {
return ( return (
<BaseLayout> <BaseLayout>

View File

@@ -1,5 +1,5 @@
import BaseLayout from '@layouts/baseLayout'; import BaseLayout from '@layouts/base';
import NewsFeedLayout from '@layouts/newsfeedLayout'; import NewsFeedLayout from '@layouts/newsfeed';
import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal } from 'react'; import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal } from 'react';
@@ -12,7 +12,13 @@ export default function Page() {
} }
Page.getLayout = function getLayout( Page.getLayout = function getLayout(
page: string | number | boolean | ReactElement<unknown, string | JSXElementConstructor<unknown>> | ReactFragment | ReactPortal page:
| string
| number
| boolean
| ReactElement<unknown, string | JSXElementConstructor<unknown>>
| ReactFragment
| ReactPortal
) { ) {
return ( return (
<BaseLayout> <BaseLayout>

View File

@@ -1,6 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ import BaseLayout from '@layouts/base';
import BaseLayout from '@layouts/baseLayout'; import OnboardingLayout from '@layouts/onboarding';
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';
@@ -11,7 +10,16 @@ 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, useCallback, useContext, useMemo, 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 = {
@@ -58,7 +66,9 @@ export default function Page() {
const insertDB = useCallback(async () => { const insertDB = useCallback(async () => {
await db.execute( await db.execute(
`INSERT INTO accounts (id, privkey, npub, nsec, metadata) VALUES ("${pubKey}", "${privKey}", "${npub}", "${nsec}", '${JSON.stringify(data)}')` `INSERT INTO accounts (id, privkey, npub, nsec, metadata) VALUES ("${pubKey}", "${privKey}", "${npub}", "${nsec}", '${JSON.stringify(
data
)}')`
); );
}, [data, db, npub, nsec, privKey, pubKey]); }, [data, db, npub, nsec, privKey, pubKey]);
@@ -101,12 +111,15 @@ export default function Page() {
<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"
>
Create new key Create new key
</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">
Lume will generate key with default profile for you, you can edit it later, and please store your key safely so you can restore your Lume will generate key with default profile for you, you can edit it later, and please store your key safely
account or use other client so you can restore your account or use other client
</motion.h2> </motion.h2>
</div> </div>
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
@@ -131,7 +144,8 @@ export default function Page() {
/> />
<button <button
onClick={() => showPrivateKey()} onClick={() => showPrivateKey()}
className="group absolute right-2 top-1/2 -translate-y-1/2 transform rounded p-1 hover:bg-zinc-700"> className="group absolute right-2 top-1/2 -translate-y-1/2 transform rounded p-1 hover:bg-zinc-700"
>
{type === 'password' ? ( {type === 'password' ? (
<EyeClosedIcon className="h-5 w-5 text-zinc-500 group-hover:text-zinc-200" /> <EyeClosedIcon className="h-5 w-5 text-zinc-500 group-hover:text-zinc-200" />
) : ( ) : (
@@ -170,18 +184,25 @@ export default function Page() {
<motion.div layoutId="action" className="pb-5"> <motion.div layoutId="action" className="pb-5">
<div className="flex h-10 items-center"> <div className="flex h-10 items-center">
{loading === true ? ( {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"> <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> <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"
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>
) : ( ) : (
<div className="relative shrink-0 before:pointer-events-none before:absolute before:-inset-1 before:rounded-[11px] before:border before:border-blue-500 before:opacity-0 before:ring-2 before:ring-blue-500/20 before:transition after:pointer-events-none after:absolute after:inset-px after:rounded-[7px] after:shadow-highlight after:shadow-white/5 after:transition focus-within:before:opacity-100 focus-within:after:shadow-blue-500/100 dark:focus-within:after:shadow-blue-500/20"> <div className="relative shrink-0 before:pointer-events-none before:absolute before:-inset-1 before:rounded-[11px] before:border before:border-blue-500 before:opacity-0 before:ring-2 before:ring-blue-500/20 before:transition after:pointer-events-none after:absolute after:inset-px after:rounded-[7px] after:shadow-highlight after:shadow-white/5 after:transition focus-within:before:opacity-100 focus-within:after:shadow-blue-500/100 dark:focus-within:after:shadow-blue-500/20">
<button <button
onClick={() => createAccount()} onClick={() => createAccount()}
className="transform rounded-lg border border-white/10 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 shadow-input shadow-black/5 active:translate-y-1 disabled:cursor-not-allowed disabled:opacity-50 dark:shadow-black/10"> className="transform rounded-lg border border-white/10 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 shadow-input shadow-black/5 active:translate-y-1 disabled:cursor-not-allowed disabled:opacity-50 dark:shadow-black/10"
>
<span className="drop-shadow-lg">Continue </span> <span className="drop-shadow-lg">Continue </span>
</button> </button>
</div> </div>
@@ -193,7 +214,13 @@ export default function Page() {
} }
Page.getLayout = function getLayout( Page.getLayout = function getLayout(
page: string | number | boolean | ReactElement<unknown, string | JSXElementConstructor<unknown>> | ReactFragment | ReactPortal page:
| string
| number
| boolean
| ReactElement<unknown, string | JSXElementConstructor<unknown>>
| ReactFragment
| ReactPortal
) { ) {
return ( return (
<BaseLayout> <BaseLayout>

View File

@@ -1,6 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ import BaseLayout from '@layouts/base';
import BaseLayout from '@layouts/baseLayout'; import OnboardingLayout from '@layouts/onboarding';
import OnboardingLayout from '@layouts/onboardingLayout';
import { DatabaseContext } from '@components/contexts/database'; import { DatabaseContext } from '@components/contexts/database';
@@ -33,7 +32,9 @@ 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);
@@ -57,11 +58,15 @@ export default function Page() {
<div>{/* spacer */}</div> <div>{/* spacer */}</div>
<motion.div layoutId="form" className="flex flex-col"> <motion.div layoutId="form" className="flex flex-col">
<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"
>
Choose 10 people you want to following Choose 10 people you want to following
</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">
For better experiences, you should follow the people you care about to personalize your newsfeed, otherwise you will be very bored For better experiences, you should follow the people you care about to personalize your newsfeed, otherwise
you will be very bored
</motion.h2> </motion.h2>
</div> </div>
<div className="h-full w-full shrink"> <div className="h-full w-full shrink">
@@ -73,7 +78,8 @@ export default function Page() {
data-npub={item.npub} data-npub={item.npub}
className={`col-span-1 inline-flex cursor-pointer items-center gap-3 rounded-lg p-2 hover:bg-zinc-700 ${ className={`col-span-1 inline-flex cursor-pointer items-center gap-3 rounded-lg p-2 hover:bg-zinc-700 ${
follow.includes(item.npub) ? 'bg-zinc-800' : '' follow.includes(item.npub) ? 'bg-zinc-800' : ''
}`}> }`}
>
<div className="relative h-10 w-10 flex-shrink-0"> <div className="relative h-10 w-10 flex-shrink-0">
<Image className="rounded-full object-cover" src={item.avatar} alt={item.name} fill={true} /> <Image className="rounded-full object-cover" src={item.avatar} alt={item.name} fill={true} />
</div> </div>
@@ -82,7 +88,9 @@ export default function Page() {
<p className="truncate text-sm font-medium text-zinc-200">{item.name}</p> <p className="truncate text-sm font-medium text-zinc-200">{item.name}</p>
<p className="text-sm leading-tight text-zinc-500">{truncate(item.npub, 16, ' .... ')}</p> <p className="text-sm leading-tight text-zinc-500">{truncate(item.npub, 16, ' .... ')}</p>
</div> </div>
<div>{follow.includes(item.npub) ? <CheckCircledIcon className="h-4 w-4 text-green-500" /> : <></>}</div> <div>
{follow.includes(item.npub) ? <CheckCircledIcon className="h-4 w-4 text-green-500" /> : <></>}
</div>
</div> </div>
</div> </div>
))} ))}
@@ -92,19 +100,26 @@ export default function Page() {
<motion.div layoutId="action" className="pb-5"> <motion.div layoutId="action" className="pb-5">
<div className="flex h-10 items-center"> <div className="flex h-10 items-center">
{loading === true ? ( {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"> <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> <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"
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>
) : ( ) : (
<div className="relative shrink-0 before:pointer-events-none before:absolute before:-inset-1 before:rounded-[11px] before:border before:border-blue-500 before:opacity-0 before:ring-2 before:ring-blue-500/20 before:transition after:pointer-events-none after:absolute after:inset-px after:rounded-[7px] after:shadow-highlight after:shadow-white/5 after:transition focus-within:before:opacity-100 focus-within:after:shadow-blue-500/100 dark:focus-within:after:shadow-blue-500/20"> <div className="relative shrink-0 before:pointer-events-none before:absolute before:-inset-1 before:rounded-[11px] before:border before:border-blue-500 before:opacity-0 before:ring-2 before:ring-blue-500/20 before:transition after:pointer-events-none after:absolute after:inset-px after:rounded-[7px] after:shadow-highlight after:shadow-white/5 after:transition focus-within:before:opacity-100 focus-within:after:shadow-blue-500/100 dark:focus-within:after:shadow-blue-500/20">
<button <button
onClick={() => createFollowing()} onClick={() => createFollowing()}
disabled={follow.length < 10 ? true : false} disabled={follow.length < 10 ? true : false}
className="transform rounded-lg border border-white/10 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 shadow-input shadow-black/5 active:translate-y-1 disabled:cursor-not-allowed disabled:opacity-50 dark:shadow-black/10"> className="transform rounded-lg border border-white/10 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 shadow-input shadow-black/5 active:translate-y-1 disabled:cursor-not-allowed disabled:opacity-50 dark:shadow-black/10"
>
<span className="drop-shadow-lg">Finish </span> <span className="drop-shadow-lg">Finish </span>
</button> </button>
</div> </div>
@@ -116,7 +131,13 @@ export default function Page() {
} }
Page.getLayout = function getLayout( Page.getLayout = function getLayout(
page: string | number | boolean | ReactElement<unknown, string | JSXElementConstructor<unknown>> | ReactFragment | ReactPortal page:
| string
| number
| boolean
| ReactElement<unknown, string | JSXElementConstructor<unknown>>
| ReactFragment
| ReactPortal
) { ) {
return ( return (
<BaseLayout> <BaseLayout>

View File

@@ -1,5 +1,5 @@
import BaseLayout from '@layouts/baseLayout'; import BaseLayout from '@layouts/base';
import OnboardingLayout from '@layouts/onboardingLayout'; import OnboardingLayout from '@layouts/onboarding';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import Link from 'next/link'; import Link from 'next/link';
@@ -10,7 +10,10 @@ 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 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"
>
Other social network require email/password Other social network require email/password
<br /> <br />
nostr use{' '} nostr use{' '}
@@ -19,19 +22,21 @@ 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 create a new key or use auto-generated account If you have used nostr before, you can import your own private key. Otherwise, you can create a new key or use
created by system. auto-generated account 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">
<Link <Link
href="/onboarding/create" href="/onboarding/create"
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"
>
Create new key Create new key
</Link> </Link>
<Link <Link
href="/onboarding/login" 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>
</motion.div> </motion.div>
@@ -42,7 +47,13 @@ export default function Page() {
} }
Page.getLayout = function getLayout( Page.getLayout = function getLayout(
page: string | number | boolean | ReactElement<unknown, string | JSXElementConstructor<unknown>> | ReactFragment | ReactPortal page:
| string
| number
| boolean
| ReactElement<unknown, string | JSXElementConstructor<unknown>>
| ReactFragment
| ReactPortal
) { ) {
return ( return (
<BaseLayout> <BaseLayout>

View File

@@ -1,6 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ import BaseLayout from '@layouts/base';
import BaseLayout from '@layouts/baseLayout'; import OnboardingLayout from '@layouts/onboarding';
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';
@@ -10,7 +9,16 @@ import { motion } from 'framer-motion';
import Link from 'next/link'; import Link from 'next/link';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { getPublicKey, nip19 } from 'nostr-tools'; import { getPublicKey, nip19 } from 'nostr-tools';
import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useCallback, useContext, useMemo, useState } from 'react'; 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);
@@ -44,7 +52,9 @@ export default function Page() {
async (follows) => { async (follows) => {
follows.forEach(async (item) => { follows.forEach(async (item) => {
if (item) { if (item) {
await db.execute(`INSERT OR IGNORE INTO follows (pubkey, account, kind) VALUES ("${item[1]}", "${pubkey}", "0")`); await db.execute(
`INSERT OR IGNORE INTO follows (pubkey, account, kind) VALUES ("${item[1]}", "${pubkey}", "0")`
);
} }
}); });
}, },
@@ -80,28 +90,39 @@ export default function Page() {
<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 profile... 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">
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 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>
<motion.div layoutId="action" className="pb-5"> <motion.div layoutId="action" className="pb-5">
<div className="flex h-10 items-center"> <div className="flex h-10 items-center">
{loading === true ? ( {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"> <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> <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"
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 <Link
href="/" 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"> 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> <span className="drop-shadow-lg">Finish</span>
</Link> </Link>
)} )}
@@ -112,7 +133,13 @@ export default function Page() {
} }
Page.getLayout = function getLayout( Page.getLayout = function getLayout(
page: string | number | boolean | ReactElement<unknown, string | JSXElementConstructor<unknown>> | ReactFragment | ReactPortal page:
| string
| number
| boolean
| ReactElement<unknown, string | JSXElementConstructor<unknown>>
| ReactFragment
| ReactPortal
) { ) {
return ( return (
<BaseLayout> <BaseLayout>

View File

@@ -1,6 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ import BaseLayout from '@layouts/base';
import BaseLayout from '@layouts/baseLayout'; import OnboardingLayout from '@layouts/onboarding';
import OnboardingLayout from '@layouts/onboardingLayout';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
@@ -60,12 +59,15 @@ export default function Page() {
<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"
>
Import your private key Import your private key
</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">
You can import private key format as hex string or nsec. If you have installed Nostr Connect compality wallet in your mobile, you can You can import private key format as hex string or nsec. If you have installed Nostr Connect compality
connect by scan QR Code below wallet in your mobile, you can connect by scan QR Code below
</motion.h2> </motion.h2>
</div> </div>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
@@ -83,18 +85,25 @@ export default function Page() {
<motion.div layoutId="action" className="pb-5"> <motion.div layoutId="action" className="pb-5">
<div className="flex h-10 items-center"> <div className="flex h-10 items-center">
{isSubmitting ? ( {isSubmitting ? (
<svg className="h-5 w-5 animate-spin text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> <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> <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"
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>
) : ( ) : (
<button <button
type="submit" type="submit"
disabled={!isDirty || !isValid} disabled={!isDirty || !isValid}
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"> 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">Continue </span> <span className="drop-shadow-lg">Continue </span>
</button> </button>
)} )}
@@ -105,7 +114,13 @@ export default function Page() {
} }
Page.getLayout = function getLayout( Page.getLayout = function getLayout(
page: string | number | boolean | ReactElement<unknown, string | JSXElementConstructor<unknown>> | ReactFragment | ReactPortal page:
| string
| number
| boolean
| ReactElement<unknown, string | JSXElementConstructor<unknown>>
| ReactFragment
| ReactPortal
) { ) {
return ( return (
<BaseLayout> <BaseLayout>

View File

@@ -1,5 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ import BaseLayout from '@layouts/base';
import BaseLayout from '@layouts/baseLayout';
import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal } from 'react'; import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal } from 'react';

View File

@@ -1,6 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ import BaseLayout from '@layouts/base';
import BaseLayout from '@layouts/baseLayout'; import UserLayout from '@layouts/user';
import UserLayout from '@layouts/userLayout';
import { RelayContext } from '@components/contexts/relay'; import { RelayContext } from '@components/contexts/relay';
@@ -32,7 +31,8 @@ export default function Page() {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [currentUser]: any = useLocalStorage('current-user'); 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 };
const { const {
register, register,
@@ -79,9 +79,12 @@ export default function Page() {
return ( return (
<form onSubmit={handleSubmit(onSubmit)} className="flex h-full w-full flex-col justify-between px-6"> <form 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">Update profile</h1> <h1 className="bg-gradient-to-br from-zinc-200 to-zinc-400 bg-clip-text text-3xl font-medium text-transparent">
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 always can recover your profile in any client 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
</h2> </h2>
</div> </div>
<fieldset className="flex flex-col gap-2"> <fieldset className="flex flex-col gap-2">
@@ -179,18 +182,25 @@ export default function Page() {
<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 className="h-5 w-5 animate-spin text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> <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> <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"
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>
) : ( ) : (
<button <button
type="submit" type="submit"
disabled={!isDirty || !isValid} disabled={!isDirty || !isValid}
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"> 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">Update</span> <span className="drop-shadow-lg">Update</span>
</button> </button>
)} )}
@@ -201,7 +211,13 @@ export default function Page() {
} }
Page.getLayout = function getLayout( Page.getLayout = function getLayout(
page: string | number | boolean | ReactElement<unknown, string | JSXElementConstructor<unknown>> | ReactFragment | ReactPortal page:
| string
| number
| boolean
| ReactElement<unknown, string | JSXElementConstructor<unknown>>
| ReactFragment
| ReactPortal
) { ) {
return ( return (
<BaseLayout> <BaseLayout>

View File

@@ -1,3 +1,4 @@
// get X days ago with user provided date
export const daysAgo = (numOfDays, date = new Date()) => { export const daysAgo = (numOfDays, date = new Date()) => {
const daysAgo = new Date(date.getTime()); const daysAgo = new Date(date.getTime());
daysAgo.setDate(date.getDate() - numOfDays); daysAgo.setDate(date.getDate() - numOfDays);
@@ -5,6 +6,7 @@ export const daysAgo = (numOfDays, date = new Date()) => {
return daysAgo; return daysAgo;
}; };
// get X hours ago with user provided date
export const hoursAgo = (numOfHours, date = new Date()) => { export const hoursAgo = (numOfHours, date = new Date()) => {
const hoursAgo = new Date(date.getTime()); const hoursAgo = new Date(date.getTime());
hoursAgo.setHours(date.getHours() - numOfHours); hoursAgo.setHours(date.getHours() - numOfHours);
@@ -12,6 +14,7 @@ export const hoursAgo = (numOfHours, date = new Date()) => {
return hoursAgo; return hoursAgo;
}; };
// convert date to unix timestamp
export const dateToUnix = (_date?: Date) => { export const dateToUnix = (_date?: Date) => {
const date = _date || new Date(); const date = _date || new Date();