mirror of
https://github.com/luminous-devs/lume.git
synced 2024-09-18 11:13:30 +00:00
Merge pull request #5 from reyamir/feat/improve-dx
Improved dev experience
This commit is contained in:
commit
42f4c8339e
@ -5,13 +5,13 @@
|
||||
"next/core-web-vitals",
|
||||
"plugin:react/recommended",
|
||||
"plugin:react/jsx-runtime",
|
||||
"plugin:react-hooks/recommended",
|
||||
"plugin:react-hooks/recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-unused-vars": "error",
|
||||
"@typescript-eslint/no-explicit-any": "error"
|
||||
"@typescript-eslint/no-unused-vars": "warn",
|
||||
"@typescript-eslint/no-explicit-any": "warn"
|
||||
},
|
||||
"ignorePatterns": ["dist", "**/*.js", "**/*.json", "node_modules"]
|
||||
}
|
||||
|
13
.prettierrc
13
.prettierrc
@ -3,12 +3,19 @@
|
||||
"trailingComma": "es5",
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"printWidth": 150,
|
||||
"printWidth": 120,
|
||||
"useTabs": false,
|
||||
"endOfLine": "lf",
|
||||
"bracketSpacing": true,
|
||||
"bracketSameLine": true,
|
||||
"importOrder": ["^@layouts/(.*)$", "^@pages/(.*)$", "^@components/(.*)$", "^@utils/(.*)$", "^@stores/(.*)$", "<THIRD_PARTY_MODULES>", "^[./]"],
|
||||
"bracketSameLine": false,
|
||||
"importOrder": [
|
||||
"^@layouts/(.*)$",
|
||||
"^@pages/(.*)$",
|
||||
"^@components/(.*)$",
|
||||
"^@utils/(.*)$",
|
||||
"<THIRD_PARTY_MODULES>",
|
||||
"^[./]"
|
||||
],
|
||||
"importOrderSeparation": true,
|
||||
"importOrderSortSpecifiers": true,
|
||||
"plugins": ["@trivago/prettier-plugin-sort-imports", "prettier-plugin-tailwindcss"],
|
||||
|
34
README.md
34
README.md
@ -51,7 +51,7 @@ Lume is "an ambitious nostr client", so I don't want to limit it be a part of yo
|
||||
|
||||
## Features
|
||||
|
||||
**Current**: v0.1.1-alpha
|
||||
**Current**: v0.1.2-alpha
|
||||
|
||||
- [x] create new key
|
||||
- [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] update profile
|
||||
- [x] cache profile to local database
|
||||
- [x] offline support
|
||||
- [x] implement newsfeed infinite loading
|
||||
- [x] native notification
|
||||
|
||||
**Next**:
|
||||
|
||||
- [ ] offline support
|
||||
- [ ] implement newsfeed infinite loading
|
||||
- [ ] handle tags (#[x]) in note
|
||||
- [ ] handle reply note
|
||||
- [ ] integrate webtorrent
|
||||
- [ ] native notification
|
||||
- [ ] publish a reply
|
||||
- [ ] direct message
|
||||
- [ ] 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
|
||||
|
||||
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
|
||||
```
|
||||
|
13
package.json
13
package.json
@ -23,15 +23,14 @@
|
||||
"boring-avatars": "^1.7.0",
|
||||
"framer-motion": "^9.1.7",
|
||||
"moment": "^2.29.4",
|
||||
"nanostores": "^0.7.4",
|
||||
"next": "^13.2.1",
|
||||
"next": "^13.2.3",
|
||||
"next-remove-imports": "^1.0.10",
|
||||
"nostr-relaypool": "^0.5.3",
|
||||
"nostr-tools": "^1.7.1",
|
||||
"nostr-tools": "^1.7.4",
|
||||
"qrcode.react": "^3.1.0",
|
||||
"react": "^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-player": "^2.11.2",
|
||||
"react-virtuoso": "^4.1.0",
|
||||
@ -43,7 +42,7 @@
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"@tauri-apps/cli": "^1.2.3",
|
||||
"@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-dom": "^18.0.11",
|
||||
"@typescript-eslint/eslint-plugin": "^5.54.0",
|
||||
@ -51,7 +50,7 @@
|
||||
"autoprefixer": "^10.4.13",
|
||||
"csstype": "^3.1.1",
|
||||
"eslint": "^8.35.0",
|
||||
"eslint-config-next": "^13.2.1",
|
||||
"eslint-config-next": "^13.2.3",
|
||||
"eslint-config-prettier": "^8.6.0",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
@ -59,7 +58,7 @@
|
||||
"lint-staged": "^13.1.2",
|
||||
"postcss": "^8.4.21",
|
||||
"prettier": "^2.8.4",
|
||||
"prettier-plugin-tailwindcss": "^0.2.3",
|
||||
"prettier-plugin-tailwindcss": "^0.2.4",
|
||||
"prop-types": "^15.8.1",
|
||||
"tailwindcss": "^3.2.7",
|
||||
"typescript": "^4.9.5"
|
||||
|
2170
pnpm-lock.yaml
2170
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -145,8 +145,8 @@
|
||||
.w-md-editor-toolbar-child {
|
||||
position: absolute;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 0 0 1px var(--color-border-default),
|
||||
0 0 0 var(--color-border-default), 0 1px 1px var(--color-border-default);
|
||||
box-shadow: 0 0 0 1px var(--color-border-default), 0 0 0 var(--color-border-default),
|
||||
0 1px 1px var(--color-border-default);
|
||||
background-color: var(--color-canvas-default);
|
||||
z-index: 1;
|
||||
display: none;
|
||||
|
@ -7,12 +7,7 @@ type ActiveLinkProps = LinkProps & {
|
||||
activeClassName: string;
|
||||
};
|
||||
|
||||
const ActiveLink = ({
|
||||
children,
|
||||
activeClassName,
|
||||
className,
|
||||
...props
|
||||
}: PropsWithChildren<ActiveLinkProps>) => {
|
||||
const ActiveLink = ({ children, activeClassName, className, ...props }: PropsWithChildren<ActiveLinkProps>) => {
|
||||
const { asPath, isReady } = useRouter();
|
||||
const [computedClassName, setComputedClassName] = useState(className);
|
||||
|
||||
@ -26,8 +21,7 @@ const ActiveLink = ({
|
||||
// Using URL().pathname to get rid of query and hash
|
||||
const activePathname = new URL(asPath, location.href).pathname;
|
||||
|
||||
const newClassName =
|
||||
linkPathname === activePathname ? `${className} ${activeClassName}`.trim() : className;
|
||||
const newClassName = linkPathname === activePathname ? `${className} ${activeClassName}`.trim() : className;
|
||||
|
||||
if (newClassName !== computedClassName) {
|
||||
setComputedClassName(newClassName);
|
||||
|
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import Image from 'next/image';
|
||||
import { memo } from 'react';
|
||||
|
||||
@ -14,7 +13,8 @@ export const Account = memo(function Account({ user, current }: { user: any; cur
|
||||
onClick={() => setCurrentUser()}
|
||||
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' : ''
|
||||
}`}>
|
||||
}`}
|
||||
>
|
||||
{userData?.picture !== undefined ? (
|
||||
<Image src={userData.picture} alt="user's avatar" fill={true} className="rounded-full object-cover" />
|
||||
) : (
|
@ -1,5 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { Account } from '@components/accountBar/account';
|
||||
import { Account } from '@components/columns/account/account';
|
||||
|
||||
import LumeSymbol from '@assets/icons/Lume';
|
||||
import { PlusIcon } from '@radix-ui/react-icons';
|
||||
@ -8,7 +7,7 @@ import Link from 'next/link';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import Database from 'tauri-plugin-sql-api';
|
||||
|
||||
export default function AccountBar() {
|
||||
export default function AccountColumn() {
|
||||
const [users, setUsers] = useState([]);
|
||||
const [currentUser]: any = useLocalStorage('current-user');
|
||||
|
||||
@ -31,7 +30,8 @@ export default function AccountBar() {
|
||||
))}
|
||||
<Link
|
||||
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" />
|
||||
</Link>
|
||||
</div>
|
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { RelayContext } from '@components/contexts/relay';
|
||||
|
||||
import { dateToUnix } from '@utils/getDate';
|
||||
@ -30,7 +29,9 @@ export default function CreatePost() {
|
||||
buttonProps: { className: 'cta-btn', 'aria-label': 'Post a message' },
|
||||
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">
|
||||
<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>
|
||||
</div>
|
||||
),
|
@ -1,12 +1,11 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import ActiveLink from '@components/activeLink';
|
||||
import CreatePost from '@components/navigatorBar/createPost';
|
||||
import { ProfileMenu } from '@components/navigatorBar/profileMenu';
|
||||
import CreatePost from '@components/columns/navigator/createPost';
|
||||
import { UserDropdownMenu } from '@components/columns/navigator/userDropdownMenu';
|
||||
|
||||
import { PlusIcon } from '@radix-ui/react-icons';
|
||||
import { useLocalStorage } from '@rehooks/local-storage';
|
||||
|
||||
export default function NavigatorBar() {
|
||||
export default function NavigatorColumn() {
|
||||
const [currentUser]: any = useLocalStorage('current-user');
|
||||
const profile = JSON.parse(currentUser.metadata);
|
||||
|
||||
@ -18,7 +17,7 @@ export default function NavigatorBar() {
|
||||
<div className="flex flex-col p-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<h5 className="font-semibold leading-tight text-zinc-100">{profile.display_name || ''}</h5>
|
||||
<ProfileMenu pubkey={currentUser.pubkey} />
|
||||
<UserDropdownMenu pubkey={currentUser.pubkey} />
|
||||
</div>
|
||||
<span className="text-sm leading-tight text-zinc-500">@{profile.username || ''}</span>
|
||||
</div>
|
||||
@ -30,22 +29,27 @@ export default function NavigatorBar() {
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center justify-between px-2">
|
||||
<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" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1 text-zinc-500">
|
||||
<ActiveLink
|
||||
href={`/feed/following`}
|
||||
href={`/newsfeed/following`}
|
||||
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>following</span>
|
||||
</ActiveLink>
|
||||
<ActiveLink
|
||||
href={`/feed/global`}
|
||||
href={`/newsfeed/global`}
|
||||
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>global</span>
|
||||
</ActiveLink>
|
||||
@ -55,7 +59,10 @@ export default function NavigatorBar() {
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center justify-between px-2">
|
||||
<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" />
|
||||
</button>
|
||||
</div>
|
@ -5,7 +5,7 @@ import { useRouter } from 'next/router';
|
||||
import { nip19 } from 'nostr-tools';
|
||||
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 viewProfile = () => {
|
||||
@ -31,20 +31,24 @@ export const ProfileMenu = memo(function ProfileMenu({ pubkey }: { pubkey: strin
|
||||
<DropdownMenu.Portal>
|
||||
<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"
|
||||
sideOffset={2}>
|
||||
sideOffset={2}
|
||||
>
|
||||
<DropdownMenu.Item
|
||||
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
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
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
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
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
|
||||
</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">
|
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { writeStorage } from '@rehooks/local-storage';
|
||||
import { createContext, useEffect, useState } from 'react';
|
||||
import Database from 'tauri-plugin-sql-api';
|
||||
|
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { RelayPool } from 'nostr-relaypool';
|
||||
import { createContext, useMemo } from 'react';
|
||||
|
||||
|
@ -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'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>
|
||||
);
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import Avatar from 'boring-avatars';
|
||||
import Image from 'next/image';
|
||||
import { memo, useEffect, useState } from 'react';
|
||||
@ -9,7 +8,7 @@ export const ImageWithFallback = memo(function ImageWithFallback({
|
||||
fill,
|
||||
className,
|
||||
}: {
|
||||
src: any;
|
||||
src: string;
|
||||
alt: string;
|
||||
fill: boolean;
|
||||
className: string;
|
||||
|
@ -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 <></>;
|
||||
}
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { RelayContext } from '@components/contexts/relay';
|
||||
|
||||
import { dateToUnix } from '@utils/getDate';
|
||||
@ -67,7 +66,11 @@ export default function Reaction({ eventID, eventPubkey }: { eventID: string; ev
|
||||
return (
|
||||
<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">
|
||||
{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>
|
||||
<span>{reaction}</span>
|
||||
</button>
|
||||
|
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { ChatBubbleIcon } from '@radix-ui/react-icons';
|
||||
import { useState } from 'react';
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { DatabaseContext } from '@components/contexts/database';
|
||||
import { ImageWithFallback } from '@components/imageWithFallback';
|
||||
|
||||
@ -17,7 +16,9 @@ export const User = memo(function User({ pubkey, time }: { pubkey: string; time:
|
||||
async (event) => {
|
||||
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);
|
||||
},
|
||||
[db, pubkey]
|
||||
@ -53,13 +54,20 @@ export const User = memo(function User({ pubkey, time }: { pubkey: string; time:
|
||||
{profile.picture ? (
|
||||
<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 className="flex w-full flex-1 items-start justify-between">
|
||||
<div className="flex w-full justify-between">
|
||||
<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>
|
||||
<Moment fromNow unix className="text-zinc-500">
|
||||
{time}
|
||||
|
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { truncate } from '@utils/truncate';
|
||||
|
||||
import { memo, useEffect, useState } from 'react';
|
||||
|
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { ImageWithFallback } from '@components/imageWithFallback';
|
||||
|
||||
import { truncate } from '@utils/truncate';
|
||||
@ -25,14 +24,23 @@ export const UserWithUsername = memo(function UserWithUsername({ pubkey }: { pub
|
||||
{profile.picture ? (
|
||||
<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 className="flex w-full flex-1 items-start justify-between">
|
||||
<div className="flex w-full justify-between">
|
||||
<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="text-zinc-500">{profile.username ? profile.username : truncate(pubkey, 16, ' .... ')}</span>
|
||||
<span className="font-bold leading-tight">
|
||||
{profile.name ? profile.name : truncate(pubkey, 16, ' .... ')}
|
||||
</span>
|
||||
<span className="text-zinc-500">
|
||||
{profile.username ? profile.username : truncate(pubkey, 16, ' .... ')}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<DotsHorizontalIcon className="h-4 w-4 text-zinc-500" />
|
||||
|
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { DatabaseContext } from '@components/contexts/database';
|
||||
import { RelayContext } from '@components/contexts/relay';
|
||||
|
||||
@ -40,7 +39,14 @@ export const NoteConnector = memo(function NoteConnector({
|
||||
`INSERT OR IGNORE INTO
|
||||
cache_notes
|
||||
(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]
|
||||
@ -85,7 +91,10 @@ export const NoteConnector = memo(function NoteConnector({
|
||||
<h3 className="text-sm font-semibold text-zinc-500"># following</h3>
|
||||
</div>
|
||||
<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" />
|
||||
</button>
|
||||
<div className="inline-flex items-center gap-1 rounded-full border border-zinc-700 bg-zinc-800 px-2.5 py-1">
|
||||
|
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import Reaction from '@components/note/atoms/reaction';
|
||||
import Reply from '@components/note/atoms/reply';
|
||||
import { User } from '@components/note/atoms/user';
|
||||
|
@ -7,7 +7,8 @@ export default function LinkCard({ data }: { data: object }) {
|
||||
<Link
|
||||
href={data['url']}
|
||||
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">
|
||||
<Image src={data['image']} alt="image preview" fill={true} className="object-cover" />
|
||||
</div>
|
||||
|
@ -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);
|
@ -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>
|
||||
);
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { RelayContext } from '@components/contexts/relay';
|
||||
import { UserRepost } from '@components/note/atoms/userRepost';
|
||||
import { Content } from '@components/note/content';
|
||||
|
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { Content } from '@components/note/content';
|
||||
|
||||
import { memo } from 'react';
|
||||
|
3
src/layouts/base.tsx
Normal file
3
src/layouts/base.tsx
Normal 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>;
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
@ -1,11 +1,8 @@
|
||||
export default function FullLayout({ children }: { children: React.ReactNode }) {
|
||||
export default function FullscreenLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="bg-gradient-radial-page relative h-full overflow-hidden">
|
||||
{/* dragging area */}
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className="absolute top-0 left-0 z-20 h-16 w-full bg-transparent"
|
||||
/>
|
||||
<div data-tauri-drag-region className="absolute top-0 left-0 z-20 h-16 w-full bg-transparent" />
|
||||
{/* end dragging area */}
|
||||
{/* content */}
|
||||
<div className="relative z-10 h-full">{children}</div>
|
@ -1,16 +1,16 @@
|
||||
import AccountBar from '@components/accountBar';
|
||||
import NavigatorBar from '@components/navigatorBar';
|
||||
import AccountColumn from '@components/columns/account';
|
||||
import NavigatorColumn from '@components/columns/navigator';
|
||||
|
||||
export default function NewsFeedLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="flex h-full w-full flex-row">
|
||||
<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" />
|
||||
<AccountBar />
|
||||
<AccountColumn />
|
||||
</div>
|
||||
<div className="grid grow grid-cols-4">
|
||||
<div className="col-span-1">
|
||||
<NavigatorBar />
|
||||
<NavigatorColumn />
|
||||
</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="h-full w-full rounded-lg">{children}</div>
|
@ -1,6 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import AccountBar from '@components/accountBar';
|
||||
import ActiveLink from '@components/activeLink';
|
||||
import AccountColumn from '@components/columns/account';
|
||||
|
||||
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="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" />
|
||||
<AccountBar />
|
||||
<AccountColumn />
|
||||
</div>
|
||||
<div className="grid grow grid-cols-4">
|
||||
<div className="col-span-1">
|
||||
@ -27,13 +26,15 @@ export default function UserLayout({ children }: { children: React.ReactNode })
|
||||
<ActiveLink
|
||||
href={`/profile/${currentUser.pubkey}`}
|
||||
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>
|
||||
</ActiveLink>
|
||||
<ActiveLink
|
||||
href={`/profile/update?pubkey=${currentUser.pubkey}`}
|
||||
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>
|
||||
</ActiveLink>
|
||||
</div>
|
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import DatabaseProvider from '@components/contexts/database';
|
||||
import RelayProvider from '@components/contexts/relay';
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import BaseLayout from '@layouts/baseLayout';
|
||||
import FullLayout from '@layouts/fullLayout';
|
||||
import BaseLayout from '@layouts/base';
|
||||
import FullscreenLayout from '@layouts/fullscreen';
|
||||
|
||||
import LumeSymbol from '@assets/icons/Lume';
|
||||
import { useLocalStorage } from '@rehooks/local-storage';
|
||||
@ -23,7 +22,7 @@ export default function Page() {
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
setLoading(false);
|
||||
router.push('/feed/following');
|
||||
router.push('/newsfeed/following');
|
||||
}, 1500);
|
||||
}
|
||||
}, [currentUser, router]);
|
||||
@ -36,12 +35,16 @@ export default function Page() {
|
||||
<LumeSymbol className="h-16 w-16 text-white" />
|
||||
</motion.div>
|
||||
<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
|
||||
</motion.h2>
|
||||
<motion.h1
|
||||
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
|
||||
</motion.h1>
|
||||
</div>
|
||||
@ -49,12 +52,18 @@ export default function Page() {
|
||||
<div className="flex items-center gap-2 pb-16">
|
||||
<div className="h-10">
|
||||
{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>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
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>
|
||||
) : (
|
||||
<></>
|
||||
@ -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)]">
|
||||
<svg
|
||||
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>
|
||||
<pattern id=":R11d6:" width="72" height="56" patternUnits="userSpaceOnUse" x="-12" y="4">
|
||||
<path d="M.5 56V.5H72" fill="none"></path>
|
||||
@ -86,11 +96,17 @@ export default function Page() {
|
||||
}
|
||||
|
||||
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 (
|
||||
<BaseLayout>
|
||||
<FullLayout>{page}</FullLayout>
|
||||
<FullscreenLayout>{page}</FullscreenLayout>
|
||||
</BaseLayout>
|
||||
);
|
||||
};
|
||||
|
28
src/pages/newsfeed/circle.tsx
Normal file
28
src/pages/newsfeed/circle.tsx
Normal 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>
|
||||
);
|
||||
};
|
@ -1,6 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import BaseLayout from '@layouts/baseLayout';
|
||||
import NewsFeedLayout from '@layouts/newsfeedLayout';
|
||||
import BaseLayout from '@layouts/base';
|
||||
import NewsFeedLayout from '@layouts/newsfeed';
|
||||
|
||||
import { DatabaseContext } from '@components/contexts/database';
|
||||
import { NoteConnector } from '@components/note/connector';
|
||||
@ -69,7 +68,9 @@ export default function Page() {
|
||||
useEffect(() => {
|
||||
const getData = async () => {
|
||||
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) {
|
||||
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">
|
||||
<button
|
||||
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" />
|
||||
<span className="drop-shadow-md">Load newest</span>
|
||||
</button>
|
||||
@ -125,7 +127,13 @@ export default function Page() {
|
||||
}
|
||||
|
||||
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 (
|
||||
<BaseLayout>
|
@ -1,5 +1,5 @@
|
||||
import BaseLayout from '@layouts/baseLayout';
|
||||
import NewsFeedLayout from '@layouts/newsfeedLayout';
|
||||
import BaseLayout from '@layouts/base';
|
||||
import NewsFeedLayout from '@layouts/newsfeed';
|
||||
|
||||
import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal } from 'react';
|
||||
|
||||
@ -12,7 +12,13 @@ export default function Page() {
|
||||
}
|
||||
|
||||
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 (
|
||||
<BaseLayout>
|
@ -1,6 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import BaseLayout from '@layouts/baseLayout';
|
||||
import OnboardingLayout from '@layouts/onboardingLayout';
|
||||
import BaseLayout from '@layouts/base';
|
||||
import OnboardingLayout from '@layouts/onboarding';
|
||||
|
||||
import { DatabaseContext } from '@components/contexts/database';
|
||||
import { RelayContext } from '@components/contexts/relay';
|
||||
@ -11,7 +10,16 @@ import { motion } from 'framer-motion';
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/router';
|
||||
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';
|
||||
|
||||
const config: Config = {
|
||||
@ -58,7 +66,9 @@ export default function Page() {
|
||||
|
||||
const insertDB = useCallback(async () => {
|
||||
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]);
|
||||
|
||||
@ -101,12 +111,15 @@ export default function Page() {
|
||||
<div>{/* spacer */}</div>
|
||||
<motion.div layoutId="form">
|
||||
<div className="mb-8 flex flex-col gap-3">
|
||||
<motion.h1 layoutId="title" className="bg-gradient-to-br from-zinc-200 to-zinc-400 bg-clip-text text-3xl font-medium text-transparent">
|
||||
<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
|
||||
</motion.h1>
|
||||
<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
|
||||
account or use other client
|
||||
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 account or use other client
|
||||
</motion.h2>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
@ -131,7 +144,8 @@ export default function Page() {
|
||||
/>
|
||||
<button
|
||||
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' ? (
|
||||
<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">
|
||||
<div className="flex h-10 items-center">
|
||||
{loading === true ? (
|
||||
<svg className="h-5 w-5 animate-spin text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<svg
|
||||
className="h-5 w-5 animate-spin text-white"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
) : (
|
||||
<div 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
|
||||
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>
|
||||
</button>
|
||||
</div>
|
||||
@ -193,7 +214,13 @@ export default function Page() {
|
||||
}
|
||||
|
||||
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 (
|
||||
<BaseLayout>
|
||||
|
@ -1,6 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import BaseLayout from '@layouts/baseLayout';
|
||||
import OnboardingLayout from '@layouts/onboardingLayout';
|
||||
import BaseLayout from '@layouts/base';
|
||||
import OnboardingLayout from '@layouts/onboarding';
|
||||
|
||||
import { DatabaseContext } from '@components/contexts/database';
|
||||
|
||||
@ -33,7 +32,9 @@ export default function Page() {
|
||||
|
||||
const insertDB = async () => {
|
||||
// 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.forEach(async (npub) => {
|
||||
const { data } = nip19.decode(npub);
|
||||
@ -57,11 +58,15 @@ export default function Page() {
|
||||
<div>{/* spacer */}</div>
|
||||
<motion.div layoutId="form" className="flex flex-col">
|
||||
<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
|
||||
</motion.h1>
|
||||
<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>
|
||||
</div>
|
||||
<div className="h-full w-full shrink">
|
||||
@ -73,7 +78,8 @@ export default function Page() {
|
||||
data-npub={item.npub}
|
||||
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' : ''
|
||||
}`}>
|
||||
}`}
|
||||
>
|
||||
<div className="relative h-10 w-10 flex-shrink-0">
|
||||
<Image className="rounded-full object-cover" src={item.avatar} alt={item.name} fill={true} />
|
||||
</div>
|
||||
@ -82,7 +88,9 @@ export default function Page() {
|
||||
<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>
|
||||
</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>
|
||||
))}
|
||||
@ -92,19 +100,26 @@ export default function Page() {
|
||||
<motion.div layoutId="action" className="pb-5">
|
||||
<div className="flex h-10 items-center">
|
||||
{loading === true ? (
|
||||
<svg className="h-5 w-5 animate-spin text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<svg
|
||||
className="h-5 w-5 animate-spin text-white"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
) : (
|
||||
<div 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
|
||||
onClick={() => createFollowing()}
|
||||
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>
|
||||
</button>
|
||||
</div>
|
||||
@ -116,7 +131,13 @@ export default function Page() {
|
||||
}
|
||||
|
||||
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 (
|
||||
<BaseLayout>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import BaseLayout from '@layouts/baseLayout';
|
||||
import OnboardingLayout from '@layouts/onboardingLayout';
|
||||
import BaseLayout from '@layouts/base';
|
||||
import OnboardingLayout from '@layouts/onboarding';
|
||||
|
||||
import { motion } from 'framer-motion';
|
||||
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>{/* spacer */}</div>
|
||||
<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
|
||||
<br />
|
||||
nostr use{' '}
|
||||
@ -19,19 +22,21 @@ export default function Page() {
|
||||
</span>
|
||||
</motion.h1>
|
||||
<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
|
||||
created by system.
|
||||
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 created by system.
|
||||
</motion.h2>
|
||||
<motion.div layoutId="form"></motion.div>
|
||||
<motion.div layoutId="action" className="mt-4 flex gap-2">
|
||||
<Link
|
||||
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
|
||||
</Link>
|
||||
<Link
|
||||
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
|
||||
</Link>
|
||||
</motion.div>
|
||||
@ -42,7 +47,13 @@ export default function Page() {
|
||||
}
|
||||
|
||||
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 (
|
||||
<BaseLayout>
|
||||
|
@ -1,6 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import BaseLayout from '@layouts/baseLayout';
|
||||
import OnboardingLayout from '@layouts/onboardingLayout';
|
||||
import BaseLayout from '@layouts/base';
|
||||
import OnboardingLayout from '@layouts/onboarding';
|
||||
|
||||
import { DatabaseContext } from '@components/contexts/database';
|
||||
import { RelayContext } from '@components/contexts/relay';
|
||||
@ -10,7 +9,16 @@ import { motion } from 'framer-motion';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
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() {
|
||||
const { db }: any = useContext(DatabaseContext);
|
||||
@ -44,7 +52,9 @@ export default function Page() {
|
||||
async (follows) => {
|
||||
follows.forEach(async (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>
|
||||
<motion.div layoutId="form">
|
||||
<div className="mb-8 flex flex-col gap-3">
|
||||
<motion.h1 layoutId="title" className="bg-gradient-to-br from-zinc-200 to-zinc-400 bg-clip-text text-3xl font-medium text-transparent">
|
||||
<motion.h1
|
||||
layoutId="title"
|
||||
className="bg-gradient-to-br from-zinc-200 to-zinc-400 bg-clip-text text-3xl font-medium text-transparent"
|
||||
>
|
||||
Fetching your profile...
|
||||
</motion.h1>
|
||||
<motion.h2 layoutId="subtitle" className="w-3/4 text-zinc-400">
|
||||
As long as you have private key, you alway can sync your profile 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>
|
||||
</div>
|
||||
</motion.div>
|
||||
<motion.div layoutId="action" className="pb-5">
|
||||
<div className="flex h-10 items-center">
|
||||
{loading === true ? (
|
||||
<svg className="h-5 w-5 animate-spin text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<svg
|
||||
className="h-5 w-5 animate-spin text-white"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
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>
|
||||
) : (
|
||||
<Link
|
||||
href="/"
|
||||
className="transform rounded-lg bg-[radial-gradient(ellipse_at_bottom_right,_var(--tw-gradient-stops))] from-gray-300 via-fuchsia-600 to-orange-600 px-3.5 py-2 font-medium active:translate-y-1 disabled:cursor-not-allowed disabled:opacity-30">
|
||||
className="transform rounded-lg bg-[radial-gradient(ellipse_at_bottom_right,_var(--tw-gradient-stops))] from-gray-300 via-fuchsia-600 to-orange-600 px-3.5 py-2 font-medium active:translate-y-1 disabled:cursor-not-allowed disabled:opacity-30"
|
||||
>
|
||||
<span className="drop-shadow-lg">Finish</span>
|
||||
</Link>
|
||||
)}
|
||||
@ -112,7 +133,13 @@ export default function Page() {
|
||||
}
|
||||
|
||||
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 (
|
||||
<BaseLayout>
|
||||
|
@ -1,6 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import BaseLayout from '@layouts/baseLayout';
|
||||
import OnboardingLayout from '@layouts/onboardingLayout';
|
||||
import BaseLayout from '@layouts/base';
|
||||
import OnboardingLayout from '@layouts/onboarding';
|
||||
|
||||
import { motion } from 'framer-motion';
|
||||
import { useRouter } from 'next/router';
|
||||
@ -60,12 +59,15 @@ export default function Page() {
|
||||
<div>{/* spacer */}</div>
|
||||
<motion.div layoutId="form">
|
||||
<div className="mb-8 flex flex-col gap-3">
|
||||
<motion.h1 layoutId="title" className="bg-gradient-to-br from-zinc-200 to-zinc-400 bg-clip-text text-3xl font-medium text-transparent">
|
||||
<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
|
||||
</motion.h1>
|
||||
<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
|
||||
connect by scan QR Code below
|
||||
You can import private key format as hex string or nsec. If you have installed Nostr Connect compality
|
||||
wallet in your mobile, you can connect by scan QR Code below
|
||||
</motion.h2>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
@ -83,18 +85,25 @@ export default function Page() {
|
||||
<motion.div layoutId="action" className="pb-5">
|
||||
<div className="flex h-10 items-center">
|
||||
{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>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
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>
|
||||
) : (
|
||||
<button
|
||||
type="submit"
|
||||
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>
|
||||
</button>
|
||||
)}
|
||||
@ -105,7 +114,13 @@ export default function Page() {
|
||||
}
|
||||
|
||||
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 (
|
||||
<BaseLayout>
|
||||
|
@ -1,5 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import BaseLayout from '@layouts/baseLayout';
|
||||
import BaseLayout from '@layouts/base';
|
||||
|
||||
import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal } from 'react';
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import BaseLayout from '@layouts/baseLayout';
|
||||
import UserLayout from '@layouts/userLayout';
|
||||
import BaseLayout from '@layouts/base';
|
||||
import UserLayout from '@layouts/user';
|
||||
|
||||
import { RelayContext } from '@components/contexts/relay';
|
||||
|
||||
@ -32,7 +31,8 @@ export default function Page() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
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 {
|
||||
register,
|
||||
@ -79,9 +79,12 @@ export default function Page() {
|
||||
return (
|
||||
<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">
|
||||
<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">
|
||||
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>
|
||||
</div>
|
||||
<fieldset className="flex flex-col gap-2">
|
||||
@ -179,18 +182,25 @@ export default function Page() {
|
||||
<div className="pb-5">
|
||||
<div className="flex h-10 items-center">
|
||||
{loading === true ? (
|
||||
<svg className="h-5 w-5 animate-spin text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<svg
|
||||
className="h-5 w-5 animate-spin text-white"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
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>
|
||||
) : (
|
||||
<button
|
||||
type="submit"
|
||||
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>
|
||||
</button>
|
||||
)}
|
||||
@ -201,7 +211,13 @@ export default function Page() {
|
||||
}
|
||||
|
||||
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 (
|
||||
<BaseLayout>
|
||||
|
@ -1,3 +1,4 @@
|
||||
// get X days ago with user provided date
|
||||
export const daysAgo = (numOfDays, date = new Date()) => {
|
||||
const daysAgo = new Date(date.getTime());
|
||||
daysAgo.setDate(date.getDate() - numOfDays);
|
||||
@ -5,6 +6,7 @@ export const daysAgo = (numOfDays, date = new Date()) => {
|
||||
return daysAgo;
|
||||
};
|
||||
|
||||
// get X hours ago with user provided date
|
||||
export const hoursAgo = (numOfHours, date = new Date()) => {
|
||||
const hoursAgo = new Date(date.getTime());
|
||||
hoursAgo.setHours(date.getHours() - numOfHours);
|
||||
@ -12,6 +14,7 @@ export const hoursAgo = (numOfHours, date = new Date()) => {
|
||||
return hoursAgo;
|
||||
};
|
||||
|
||||
// convert date to unix timestamp
|
||||
export const dateToUnix = (_date?: Date) => {
|
||||
const date = _date || new Date();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user