From 4fc3cc8a80a7cd3489eb876f07fb82738d79b1d6 Mon Sep 17 00:00:00 2001 From: reya Date: Thu, 28 Dec 2023 11:31:47 +0700 Subject: [PATCH] feat(column): add thread and user columns --- apps/desktop/package.json | 2 + apps/desktop/src/routes/home/index.tsx | 7 +- packages/@columns/thread/package.json | 26 +++ packages/@columns/thread/src/event.tsx | 28 +++ packages/@columns/thread/src/home.tsx | 13 ++ packages/@columns/thread/src/index.tsx | 18 ++ packages/@columns/thread/src/user.tsx | 213 ++++++++++++++++++ packages/@columns/thread/tailwind.config.js | 8 + packages/@columns/thread/tsconfig.json | 8 + packages/@columns/user/package.json | 26 +++ packages/@columns/user/src/event.tsx | 28 +++ packages/@columns/user/src/home.tsx | 202 +++++++++++++++++ packages/@columns/user/src/index.tsx | 23 ++ packages/@columns/user/src/user.tsx | 213 ++++++++++++++++++ packages/@columns/user/tailwind.config.js | 8 + packages/@columns/user/tsconfig.json | 8 + .../ark/src/components/note/preview/image.tsx | 2 +- .../ark/src/components/note/preview/link.tsx | 4 +- .../ark/src/components/note/preview/video.tsx | 30 +-- packages/ark/src/components/note/provider.tsx | 2 +- pnpm-lock.yaml | 74 +++++- 21 files changed, 921 insertions(+), 22 deletions(-) create mode 100644 packages/@columns/thread/package.json create mode 100644 packages/@columns/thread/src/event.tsx create mode 100644 packages/@columns/thread/src/home.tsx create mode 100644 packages/@columns/thread/src/index.tsx create mode 100644 packages/@columns/thread/src/user.tsx create mode 100644 packages/@columns/thread/tailwind.config.js create mode 100644 packages/@columns/thread/tsconfig.json create mode 100644 packages/@columns/user/package.json create mode 100644 packages/@columns/user/src/event.tsx create mode 100644 packages/@columns/user/src/home.tsx create mode 100644 packages/@columns/user/src/index.tsx create mode 100644 packages/@columns/user/src/user.tsx create mode 100644 packages/@columns/user/tailwind.config.js create mode 100644 packages/@columns/user/tsconfig.json diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 50b74f54..2e526e49 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -9,7 +9,9 @@ }, "dependencies": { "@columns/notification": "workspace:^", + "@columns/thread": "workspace:^", "@columns/timeline": "workspace:^", + "@columns/user": "workspace:^", "@getalby/sdk": "^3.2.1", "@lume/ark": "workspace:^", "@lume/icons": "workspace:^", diff --git a/apps/desktop/src/routes/home/index.tsx b/apps/desktop/src/routes/home/index.tsx index 9b49aa91..81ae7732 100644 --- a/apps/desktop/src/routes/home/index.tsx +++ b/apps/desktop/src/routes/home/index.tsx @@ -1,5 +1,6 @@ -import { NotificationColumn } from "@columns/notification"; +import { Thread } from "@columns/thread"; import { Timeline } from "@columns/timeline"; +import { User } from "@columns/user"; import { useStorage } from "@lume/ark"; import { LoaderIcon } from "@lume/icons"; import { WidgetProps } from "@lume/types"; @@ -39,6 +40,10 @@ export function HomeScreen() { switch (widget.kind) { case WIDGET_KIND.newsfeed: return ; + case WIDGET_KIND.thread: + return ; + case WIDGET_KIND.user: + return ; default: return ; } diff --git a/packages/@columns/thread/package.json b/packages/@columns/thread/package.json new file mode 100644 index 00000000..44b26cf3 --- /dev/null +++ b/packages/@columns/thread/package.json @@ -0,0 +1,26 @@ +{ + "name": "@columns/thread", + "version": "0.0.0", + "private": true, + "main": "./src/index.tsx", + "dependencies": { + "@lume/ark": "workspace:^", + "@lume/icons": "workspace:^", + "@lume/ui": "workspace:^", + "@lume/utils": "workspace:^", + "@nostr-dev-kit/ndk": "^2.3.1", + "@tanstack/react-query": "^5.14.2", + "react": "^18.2.0", + "react-router-dom": "^6.21.0", + "sonner": "^1.2.4", + "virtua": "^0.18.0" + }, + "devDependencies": { + "@lume/tailwindcss": "workspace:^", + "@lume/tsconfig": "workspace:^", + "@lume/types": "workspace:^", + "@types/react": "^18.2.45", + "tailwind": "^4.0.0", + "typescript": "^5.3.3" + } +} diff --git a/packages/@columns/thread/src/event.tsx b/packages/@columns/thread/src/event.tsx new file mode 100644 index 00000000..2aa10da8 --- /dev/null +++ b/packages/@columns/thread/src/event.tsx @@ -0,0 +1,28 @@ +import { Note, ThreadNote } from "@lume/ark"; +import { ArrowLeftIcon } from "@lume/icons"; +import { useNavigate, useParams } from "react-router-dom"; +import { WVList } from "virtua"; + +export function EventRoute() { + const { id } = useParams(); + const navigate = useNavigate(); + + return ( + +
+ +
+
+ + +
+
+ ); +} diff --git a/packages/@columns/thread/src/home.tsx b/packages/@columns/thread/src/home.tsx new file mode 100644 index 00000000..47f381d3 --- /dev/null +++ b/packages/@columns/thread/src/home.tsx @@ -0,0 +1,13 @@ +import { Note, ThreadNote } from "@lume/ark"; +import { WVList } from "virtua"; + +export function HomeRoute({ id }: { id: string }) { + return ( + +
+ + +
+
+ ); +} diff --git a/packages/@columns/thread/src/index.tsx b/packages/@columns/thread/src/index.tsx new file mode 100644 index 00000000..a7e0b415 --- /dev/null +++ b/packages/@columns/thread/src/index.tsx @@ -0,0 +1,18 @@ +import { Column } from "@lume/ark"; +import { WidgetProps } from "@lume/types"; +import { EventRoute } from "./event"; +import { HomeRoute } from "./home"; +import { UserRoute } from "./user"; + +export function Thread({ thread }: { thread: WidgetProps }) { + return ( + + + + } /> + } /> + } /> + + + ); +} diff --git a/packages/@columns/thread/src/user.tsx b/packages/@columns/thread/src/user.tsx new file mode 100644 index 00000000..8d46f957 --- /dev/null +++ b/packages/@columns/thread/src/user.tsx @@ -0,0 +1,213 @@ +import { + RepostNote, + TextNote, + useArk, + useProfile, + useStorage, +} from "@lume/ark"; +import { ArrowLeftIcon, ArrowRightCircleIcon, LoaderIcon } from "@lume/icons"; +import { NIP05 } from "@lume/ui"; +import { FETCH_LIMIT, displayNpub } from "@lume/utils"; +import { NDKEvent, NDKKind } from "@nostr-dev-kit/ndk"; +import { useInfiniteQuery } from "@tanstack/react-query"; +import { useEffect, useMemo, useState } from "react"; +import { Link, useNavigate, useParams } from "react-router-dom"; +import { toast } from "sonner"; +import { WVList } from "virtua"; + +export function UserRoute() { + const ark = useArk(); + const storage = useStorage(); + const navigate = useNavigate(); + + const { id } = useParams(); + const { user } = useProfile(id); + const { data, hasNextPage, isLoading, isFetchingNextPage, fetchNextPage } = + useInfiniteQuery({ + queryKey: ["user-posts", id], + initialPageParam: 0, + queryFn: async ({ + signal, + pageParam, + }: { + signal: AbortSignal; + pageParam: number; + }) => { + const events = await ark.getInfiniteEvents({ + filter: { + kinds: [NDKKind.Text, NDKKind.Repost], + authors: [id], + }, + limit: FETCH_LIMIT, + pageParam, + signal, + }); + + return events; + }, + getNextPageParam: (lastPage) => { + const lastEvent = lastPage.at(-1); + if (!lastEvent) return; + return lastEvent.created_at - 1; + }, + refetchOnWindowFocus: false, + }); + + const [followed, setFollowed] = useState(false); + + const allEvents = useMemo( + () => (data ? data.pages.flatMap((page) => page) : []), + [data], + ); + + const follow = async (pubkey: string) => { + try { + const add = await ark.createContact({ pubkey }); + if (add) { + setFollowed(true); + } else { + toast.success("You already follow this user"); + } + } catch (error) { + console.log(error); + } + }; + + const unfollow = async (pubkey: string) => { + try { + const remove = await ark.deleteContact({ pubkey }); + if (remove) { + setFollowed(false); + } + } catch (error) { + console.log(error); + } + }; + + const renderItem = (event: NDKEvent) => { + switch (event.kind) { + case NDKKind.Text: + return ; + case NDKKind.Repost: + return ; + default: + return ; + } + }; + + useEffect(() => { + if (storage.account.contacts.includes(id)) { + setFollowed(true); + } + }, []); + + return ( + +
+ +
+
+
+
+ {id} +
+ {followed ? ( + + ) : ( + + )} + + Message + +
+
+
+
+
+ {user?.name || + user?.display_name || + user?.displayName || + "Anon"} +
+ {user?.nip05 ? ( + + ) : ( + + {displayNpub(id, 16)} + + )} +
+
+ {user?.about} +
+
+
+
+

+ Latest posts +

+
+ {isLoading ? ( +
+ +
+ ) : ( + allEvents.map((item) => renderItem(item)) + )} +
+ {hasNextPage ? ( + + ) : null} +
+
+
+
+
+ ); +} diff --git a/packages/@columns/thread/tailwind.config.js b/packages/@columns/thread/tailwind.config.js new file mode 100644 index 00000000..49c48c7a --- /dev/null +++ b/packages/@columns/thread/tailwind.config.js @@ -0,0 +1,8 @@ +import sharedConfig from "@lume/tailwindcss"; + +const config = { + content: ["./src/**/*.{js,ts,jsx,tsx}"], + presets: [sharedConfig], +}; + +export default config; diff --git a/packages/@columns/thread/tsconfig.json b/packages/@columns/thread/tsconfig.json new file mode 100644 index 00000000..34a32891 --- /dev/null +++ b/packages/@columns/thread/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@lume/tsconfig/base.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": ["src"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/@columns/user/package.json b/packages/@columns/user/package.json new file mode 100644 index 00000000..bb5d61fc --- /dev/null +++ b/packages/@columns/user/package.json @@ -0,0 +1,26 @@ +{ + "name": "@columns/user", + "version": "0.0.0", + "private": true, + "main": "./src/index.tsx", + "dependencies": { + "@lume/ark": "workspace:^", + "@lume/icons": "workspace:^", + "@lume/ui": "workspace:^", + "@lume/utils": "workspace:^", + "@nostr-dev-kit/ndk": "^2.3.1", + "@tanstack/react-query": "^5.14.2", + "react": "^18.2.0", + "react-router-dom": "^6.21.0", + "sonner": "^1.2.4", + "virtua": "^0.18.0" + }, + "devDependencies": { + "@lume/tailwindcss": "workspace:^", + "@lume/tsconfig": "workspace:^", + "@lume/types": "workspace:^", + "@types/react": "^18.2.45", + "tailwind": "^4.0.0", + "typescript": "^5.3.3" + } +} diff --git a/packages/@columns/user/src/event.tsx b/packages/@columns/user/src/event.tsx new file mode 100644 index 00000000..2aa10da8 --- /dev/null +++ b/packages/@columns/user/src/event.tsx @@ -0,0 +1,28 @@ +import { Note, ThreadNote } from "@lume/ark"; +import { ArrowLeftIcon } from "@lume/icons"; +import { useNavigate, useParams } from "react-router-dom"; +import { WVList } from "virtua"; + +export function EventRoute() { + const { id } = useParams(); + const navigate = useNavigate(); + + return ( + +
+ +
+
+ + +
+
+ ); +} diff --git a/packages/@columns/user/src/home.tsx b/packages/@columns/user/src/home.tsx new file mode 100644 index 00000000..30f960e9 --- /dev/null +++ b/packages/@columns/user/src/home.tsx @@ -0,0 +1,202 @@ +import { + RepostNote, + TextNote, + useArk, + useProfile, + useStorage, +} from "@lume/ark"; +import { ArrowLeftIcon, ArrowRightCircleIcon, LoaderIcon } from "@lume/icons"; +import { NIP05 } from "@lume/ui"; +import { FETCH_LIMIT, displayNpub } from "@lume/utils"; +import { NDKEvent, NDKKind } from "@nostr-dev-kit/ndk"; +import { useInfiniteQuery } from "@tanstack/react-query"; +import { useEffect, useMemo, useState } from "react"; +import { Link, useNavigate, useParams } from "react-router-dom"; +import { toast } from "sonner"; +import { WVList } from "virtua"; + +export function HomeRoute({ id }: { id: string }) { + const ark = useArk(); + const storage = useStorage(); + const navigate = useNavigate(); + + const { user } = useProfile(id); + const { data, hasNextPage, isLoading, isFetchingNextPage, fetchNextPage } = + useInfiniteQuery({ + queryKey: ["user-posts", id], + initialPageParam: 0, + queryFn: async ({ + signal, + pageParam, + }: { + signal: AbortSignal; + pageParam: number; + }) => { + const events = await ark.getInfiniteEvents({ + filter: { + kinds: [NDKKind.Text, NDKKind.Repost], + authors: [id], + }, + limit: FETCH_LIMIT, + pageParam, + signal, + }); + + return events; + }, + getNextPageParam: (lastPage) => { + const lastEvent = lastPage.at(-1); + if (!lastEvent) return; + return lastEvent.created_at - 1; + }, + refetchOnWindowFocus: false, + }); + + const [followed, setFollowed] = useState(false); + + const allEvents = useMemo( + () => (data ? data.pages.flatMap((page) => page) : []), + [data], + ); + + const follow = async (pubkey: string) => { + try { + const add = await ark.createContact({ pubkey }); + if (add) { + setFollowed(true); + } else { + toast.success("You already follow this user"); + } + } catch (error) { + console.log(error); + } + }; + + const unfollow = async (pubkey: string) => { + try { + const remove = await ark.deleteContact({ pubkey }); + if (remove) { + setFollowed(false); + } + } catch (error) { + console.log(error); + } + }; + + const renderItem = (event: NDKEvent) => { + switch (event.kind) { + case NDKKind.Text: + return ; + case NDKKind.Repost: + return ; + default: + return ; + } + }; + + useEffect(() => { + if (storage.account.contacts.includes(id)) { + setFollowed(true); + } + }, []); + + return ( + +
+
+
+ {id} +
+ {followed ? ( + + ) : ( + + )} + + Message + +
+
+
+
+
+ {user?.name || + user?.display_name || + user?.displayName || + "Anon"} +
+ {user?.nip05 ? ( + + ) : ( + + {displayNpub(id, 16)} + + )} +
+
+ {user?.about} +
+
+
+
+

+ Latest posts +

+
+ {isLoading ? ( +
+ +
+ ) : ( + allEvents.map((item) => renderItem(item)) + )} +
+ {hasNextPage ? ( + + ) : null} +
+
+
+
+
+ ); +} diff --git a/packages/@columns/user/src/index.tsx b/packages/@columns/user/src/index.tsx new file mode 100644 index 00000000..e34c21cd --- /dev/null +++ b/packages/@columns/user/src/index.tsx @@ -0,0 +1,23 @@ +import { Column } from "@lume/ark"; +import { UserIcon } from "@lume/icons"; +import { WidgetProps } from "@lume/types"; +import { EventRoute } from "./event"; +import { HomeRoute } from "./home"; +import { UserRoute } from "./user"; + +export function User({ user }: { user: WidgetProps }) { + return ( + + } + /> + + } /> + } /> + } /> + + + ); +} diff --git a/packages/@columns/user/src/user.tsx b/packages/@columns/user/src/user.tsx new file mode 100644 index 00000000..8d46f957 --- /dev/null +++ b/packages/@columns/user/src/user.tsx @@ -0,0 +1,213 @@ +import { + RepostNote, + TextNote, + useArk, + useProfile, + useStorage, +} from "@lume/ark"; +import { ArrowLeftIcon, ArrowRightCircleIcon, LoaderIcon } from "@lume/icons"; +import { NIP05 } from "@lume/ui"; +import { FETCH_LIMIT, displayNpub } from "@lume/utils"; +import { NDKEvent, NDKKind } from "@nostr-dev-kit/ndk"; +import { useInfiniteQuery } from "@tanstack/react-query"; +import { useEffect, useMemo, useState } from "react"; +import { Link, useNavigate, useParams } from "react-router-dom"; +import { toast } from "sonner"; +import { WVList } from "virtua"; + +export function UserRoute() { + const ark = useArk(); + const storage = useStorage(); + const navigate = useNavigate(); + + const { id } = useParams(); + const { user } = useProfile(id); + const { data, hasNextPage, isLoading, isFetchingNextPage, fetchNextPage } = + useInfiniteQuery({ + queryKey: ["user-posts", id], + initialPageParam: 0, + queryFn: async ({ + signal, + pageParam, + }: { + signal: AbortSignal; + pageParam: number; + }) => { + const events = await ark.getInfiniteEvents({ + filter: { + kinds: [NDKKind.Text, NDKKind.Repost], + authors: [id], + }, + limit: FETCH_LIMIT, + pageParam, + signal, + }); + + return events; + }, + getNextPageParam: (lastPage) => { + const lastEvent = lastPage.at(-1); + if (!lastEvent) return; + return lastEvent.created_at - 1; + }, + refetchOnWindowFocus: false, + }); + + const [followed, setFollowed] = useState(false); + + const allEvents = useMemo( + () => (data ? data.pages.flatMap((page) => page) : []), + [data], + ); + + const follow = async (pubkey: string) => { + try { + const add = await ark.createContact({ pubkey }); + if (add) { + setFollowed(true); + } else { + toast.success("You already follow this user"); + } + } catch (error) { + console.log(error); + } + }; + + const unfollow = async (pubkey: string) => { + try { + const remove = await ark.deleteContact({ pubkey }); + if (remove) { + setFollowed(false); + } + } catch (error) { + console.log(error); + } + }; + + const renderItem = (event: NDKEvent) => { + switch (event.kind) { + case NDKKind.Text: + return ; + case NDKKind.Repost: + return ; + default: + return ; + } + }; + + useEffect(() => { + if (storage.account.contacts.includes(id)) { + setFollowed(true); + } + }, []); + + return ( + +
+ +
+
+
+
+ {id} +
+ {followed ? ( + + ) : ( + + )} + + Message + +
+
+
+
+
+ {user?.name || + user?.display_name || + user?.displayName || + "Anon"} +
+ {user?.nip05 ? ( + + ) : ( + + {displayNpub(id, 16)} + + )} +
+
+ {user?.about} +
+
+
+
+

+ Latest posts +

+
+ {isLoading ? ( +
+ +
+ ) : ( + allEvents.map((item) => renderItem(item)) + )} +
+ {hasNextPage ? ( + + ) : null} +
+
+
+
+
+ ); +} diff --git a/packages/@columns/user/tailwind.config.js b/packages/@columns/user/tailwind.config.js new file mode 100644 index 00000000..49c48c7a --- /dev/null +++ b/packages/@columns/user/tailwind.config.js @@ -0,0 +1,8 @@ +import sharedConfig from "@lume/tailwindcss"; + +const config = { + content: ["./src/**/*.{js,ts,jsx,tsx}"], + presets: [sharedConfig], +}; + +export default config; diff --git a/packages/@columns/user/tsconfig.json b/packages/@columns/user/tsconfig.json new file mode 100644 index 00000000..34a32891 --- /dev/null +++ b/packages/@columns/user/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@lume/tsconfig/base.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": ["src"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/ark/src/components/note/preview/image.tsx b/packages/ark/src/components/note/preview/image.tsx index bf90af2e..2743b7ff 100644 --- a/packages/ark/src/components/note/preview/image.tsx +++ b/packages/ark/src/components/note/preview/image.tsx @@ -31,7 +31,7 @@ export function ImagePreview({ url }: { url: string }) { return ( // biome-ignore lint/a11y/useKeyWithClickEvents: -
+
{url} +
@@ -42,7 +42,7 @@ export function LinkPreview({ url }: { url: string }) { to={url} target="_blank" rel="noreferrer" - className="my-2 flex w-full flex-col rounded-lg bg-neutral-100 dark:bg-neutral-900" + className="flex w-full flex-col rounded-lg bg-neutral-100 dark:bg-neutral-900" > {isImage(data.image) ? ( - - - - ); + return ( + + + + + ); } diff --git a/packages/ark/src/components/note/provider.tsx b/packages/ark/src/components/note/provider.tsx index d5ee6920..c2cf4d45 100644 --- a/packages/ark/src/components/note/provider.tsx +++ b/packages/ark/src/components/note/provider.tsx @@ -14,7 +14,7 @@ export function NoteProvider({ export function useNoteContext() { const context = useContext(EventContext); - if (context === undefined) { + if (!context) { throw new Error("Please import Note Provider to use useNoteContext() hook"); } return context; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d2413c3f..d42f5031 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,9 +23,15 @@ importers: '@columns/notification': specifier: workspace:^ version: link:../../packages/@columns/notification + '@columns/thread': + specifier: workspace:^ + version: link:../../packages/@columns/thread '@columns/timeline': specifier: workspace:^ version: link:../../packages/@columns/timeline + '@columns/user': + specifier: workspace:^ + version: link:../../packages/@columns/user '@getalby/sdk': specifier: ^3.2.1 version: 3.2.1(typescript@5.3.3) @@ -283,7 +289,7 @@ importers: specifier: ^4.2.2 version: 4.2.2(typescript@5.3.3)(vite@4.5.1) - packages/@columns/newsfeed: + packages/@columns/notification: dependencies: '@lume/ark': specifier: workspace:^ @@ -323,7 +329,7 @@ importers: specifier: ^5.3.3 version: 5.3.3 - packages/@columns/notification: + packages/@columns/thread: dependencies: '@lume/ark': specifier: workspace:^ @@ -331,6 +337,9 @@ importers: '@lume/icons': specifier: workspace:^ version: link:../../icons + '@lume/ui': + specifier: workspace:^ + version: link:../../ui '@lume/utils': specifier: workspace:^ version: link:../../utils @@ -343,6 +352,12 @@ importers: react: specifier: ^18.2.0 version: 18.2.0 + react-router-dom: + specifier: ^6.21.0 + version: 6.21.0(react-dom@18.2.0)(react@18.2.0) + sonner: + specifier: ^1.2.4 + version: 1.2.4(react-dom@18.2.0)(react@18.2.0) virtua: specifier: ^0.18.0 version: 0.18.0(react-dom@18.2.0)(react@18.2.0) @@ -353,6 +368,9 @@ importers: '@lume/tsconfig': specifier: workspace:^ version: link:../../tsconfig + '@lume/types': + specifier: workspace:^ + version: link:../../types '@types/react': specifier: ^18.2.45 version: 18.2.45 @@ -412,6 +430,58 @@ importers: specifier: ^5.3.3 version: 5.3.3 + packages/@columns/user: + dependencies: + '@lume/ark': + specifier: workspace:^ + version: link:../../ark + '@lume/icons': + specifier: workspace:^ + version: link:../../icons + '@lume/ui': + specifier: workspace:^ + version: link:../../ui + '@lume/utils': + specifier: workspace:^ + version: link:../../utils + '@nostr-dev-kit/ndk': + specifier: ^2.3.1 + version: 2.3.1(typescript@5.3.3) + '@tanstack/react-query': + specifier: ^5.14.2 + version: 5.14.2(react@18.2.0) + react: + specifier: ^18.2.0 + version: 18.2.0 + react-router-dom: + specifier: ^6.21.0 + version: 6.21.0(react-dom@18.2.0)(react@18.2.0) + sonner: + specifier: ^1.2.4 + version: 1.2.4(react-dom@18.2.0)(react@18.2.0) + virtua: + specifier: ^0.18.0 + version: 0.18.0(react-dom@18.2.0)(react@18.2.0) + devDependencies: + '@lume/tailwindcss': + specifier: workspace:^ + version: link:../../tailwindcss + '@lume/tsconfig': + specifier: workspace:^ + version: link:../../tsconfig + '@lume/types': + specifier: workspace:^ + version: link:../../types + '@types/react': + specifier: ^18.2.45 + version: 18.2.45 + tailwind: + specifier: ^4.0.0 + version: 4.0.0 + typescript: + specifier: ^5.3.3 + version: 5.3.3 + packages/ark: dependencies: '@getalby/sdk':