diff --git a/apps/web/package.json b/apps/web/package.json index af7c2a8..0a64450 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -12,33 +12,34 @@ "preview": "cd dist && nix-shell -p python3 --run 'python3 -m http.server 3000' && cd .." }, "dependencies": { - "@tensamin/call": "workspace:*", - "@tensamin/chat": "workspace:*", - "@tensamin/crypto": "workspace:*", - "@tensamin/ttp": "workspace:*", - "@tensamin/storage": "workspace:*", - "@tensamin/user": "workspace:*", - "@tensamin/shared": "workspace:*", - "@tensamin/ui": "workspace:*", + "@fontsource-variable/inter": "^5.2.8", "@noble/curves": "^2.0.1", "@tailwindcss/vite": "^4.2.1", "@tanstack/react-router": "^1.0.0", "@tanstack/react-virtual": "^3.0.0", + "@tensamin/call": "workspace:*", + "@tensamin/chat": "workspace:*", + "@tensamin/crypto": "workspace:*", + "@tensamin/shared": "workspace:*", + "@tensamin/storage": "workspace:*", + "@tensamin/ttp": "workspace:*", + "@tensamin/ui": "workspace:*", + "@tensamin/user": "workspace:*", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "comlink": "^4.4.2", + "framer-motion": "^12.38.0", "lucide-react": "^0.564.0", "react": "^19.2.0", "react-dom": "^19.2.0", + "shadcn": "^4.0.7", "sonner": "^1.0.0", "tailwind-merge": "^3.5.0", "tailwind-scrollbar-hide": "^4.0.0", "tailwindcss": "^4.2.1", "tw-animate-css": "^1.4.0", "vite-tsconfig-paths": "^6.1.1", - "zod": "^4.3.6", - "@fontsource-variable/inter": "^5.2.8", - "shadcn": "^4.0.7" + "zod": "^4.3.6" }, "devDependencies": { "@types/react": "^19.2.2", diff --git a/apps/web/src/components/navbar.tsx b/apps/web/src/components/navbar.tsx index 5b7b23c..8b646e8 100644 --- a/apps/web/src/components/navbar.tsx +++ b/apps/web/src/components/navbar.tsx @@ -21,7 +21,7 @@ export default function Navbar() { const { pathname } = useLocation(); const { id } = useSearch({ strict: false }); - const currentCalls = id ? conversations[id]?.calls || [] : []; + const currentCalls = id ? conversations?.[id]?.calls || [] : []; return (
diff --git a/apps/web/src/features/conversation/context.tsx b/apps/web/src/features/conversation/context.tsx index 66b75ee..3968a67 100644 --- a/apps/web/src/features/conversation/context.tsx +++ b/apps/web/src/features/conversation/context.tsx @@ -8,8 +8,8 @@ import type { } from "@tensamin/shared/features/conversation/schema"; interface contextValue { - conversations: Conversation[]; - communities: Community[]; + conversations: Conversation[] | null; + communities: Community[] | null; } const ConversationContext = React.createContext( @@ -24,8 +24,12 @@ const ConversationContext = React.createContext( export default function ConversationProvider(props: { children: React.ReactNode; }) { - const [conversations, setConversations] = React.useState([]); - const [communities, setCommunities] = React.useState([]); + const [conversations, setConversations] = React.useState< + Conversation[] | null + >(null); + const [communities, setCommunities] = React.useState( + null, + ); const { send } = useSocket(); @@ -39,6 +43,12 @@ export default function ConversationProvider(props: { } }) .catch(() => { + setConversations([ + { + last_message_at: 0, + user_id: 0, + }, + ]); toast("error", "Failed to load conversations"); }); @@ -49,6 +59,13 @@ export default function ConversationProvider(props: { } }) .catch(() => { + setCommunities([ + { + community_address: "none", + community_title: "Failed", + position: "", + }, + ]); toast("error", "Failed to load communities"); }); @@ -57,13 +74,13 @@ export default function ConversationProvider(props: { }; }, [send]); - const value = React.useMemo( - () => ({ conversations, communities }), - [communities, conversations], - ); - return ( - + {props.children} ); diff --git a/apps/web/src/features/conversation/list/body.tsx b/apps/web/src/features/conversation/list/body.tsx index 53ce98a..1084c52 100644 --- a/apps/web/src/features/conversation/list/body.tsx +++ b/apps/web/src/features/conversation/list/body.tsx @@ -5,6 +5,7 @@ import { useConversation } from "../context"; import Switch from "./switch"; import ConversationModal from "../modal/conversation"; import CommunityModal from "../modal/community"; +import { Loader2 } from "lucide-react"; /** * Executes List. @@ -22,7 +23,9 @@ export default function List() { const virtualizer = useVirtualizer({ count: - category === "conversations" ? conversations.length : communities.length, + category === "conversations" + ? conversations?.length || 0 + : communities?.length || 0, estimateSize: () => 60, getScrollElement: () => scrollRef.current, }); @@ -41,32 +44,45 @@ export default function List() { height: `${virtualizer.getTotalSize()}px`, }} > - {virtualizer.getVirtualItems().map((virtualItem) => { - const itemKey = `${category}-${virtualItem.index}`; + {communities === null || conversations === null ? ( +
+

+ + Loading... +

+
+ ) : null} - return ( -
- {category === "conversations" ? ( - conversations[virtualItem.index] ? ( - { + const itemKey = `${category}-${virtualItem.index}`; + + return ( +
+ {category === "conversations" ? ( + conversations[virtualItem.index] ? ( + + ) : null + ) : communities[virtualItem.index] ? ( + - ) : null - ) : communities[virtualItem.index] ? ( - - ) : null} -
- ); - })} + ) : null} +
+ ); + })}
diff --git a/apps/web/src/index.css b/apps/web/src/index.css index 30f037c..406e9e1 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -79,7 +79,7 @@ @apply border-border outline-ring/50; } body { - @apply bg-background text-foreground; + @apply bg-black text-foreground; } html { @apply font-sans; diff --git a/apps/web/src/routes/app/layout.tsx b/apps/web/src/routes/app/layout.tsx index 470568c..4251d75 100644 --- a/apps/web/src/routes/app/layout.tsx +++ b/apps/web/src/routes/app/layout.tsx @@ -5,6 +5,8 @@ import Sidebar from "@/components/sidebar"; import Navbar from "@/components/navbar"; import { useStorage } from "@tensamin/storage/context"; +import { motion } from "framer-motion"; + /** * Executes Layout. * @param props Parameter props. @@ -46,7 +48,20 @@ export default function Layout(props: { children: ReactNode }) { } return ( -
+
@@ -54,6 +69,11 @@ export default function Layout(props: { children: ReactNode }) { {props.children}
- + ); } + +const animationTiming = { + first: 0.18, + second: 0.1, +}; diff --git a/bun.lock b/bun.lock index 91dea86..3abbc6e 100644 --- a/bun.lock +++ b/bun.lock @@ -49,6 +49,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "comlink": "^4.4.2", + "framer-motion": "^12.38.0", "lucide-react": "^0.564.0", "react": "^19.2.0", "react-dom": "^19.2.0", @@ -911,6 +912,8 @@ "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], + "framer-motion": ["framer-motion@12.38.0", "", { "dependencies": { "motion-dom": "^12.38.0", "motion-utils": "^12.36.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g=="], + "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], "fs-extra": ["fs-extra@11.3.4", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA=="], @@ -1107,6 +1110,10 @@ "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + "motion-dom": ["motion-dom@12.38.0", "", { "dependencies": { "motion-utils": "^12.36.0" } }, "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA=="], + + "motion-utils": ["motion-utils@12.36.0", "", {}, "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg=="], + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "msw": ["msw@2.12.10", "", { "dependencies": { "@inquirer/confirm": "^5.0.0", "@mswjs/interceptors": "^0.41.2", "@open-draft/deferred-promise": "^2.2.0", "@types/statuses": "^2.0.6", "cookie": "^1.0.2", "graphql": "^16.12.0", "headers-polyfill": "^4.0.2", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "path-to-regexp": "^6.3.0", "picocolors": "^1.1.1", "rettime": "^0.10.1", "statuses": "^2.0.2", "strict-event-emitter": "^0.5.1", "tough-cookie": "^6.0.0", "type-fest": "^5.2.0", "until-async": "^3.0.2", "yargs": "^17.7.2" }, "peerDependencies": { "typescript": ">= 4.8.x" }, "optionalPeers": ["typescript"], "bin": { "msw": "cli/index.js" } }, "sha512-G3VUymSE0/iegFnuipujpwyTM2GuZAKXNeerUSrG2+Eg391wW63xFs5ixWsK9MWzr1AGoSkYGmyAzNgbR3+urw=="], diff --git a/packages/chat/src/screen.tsx b/packages/chat/src/screen.tsx index 76fd5d7..6e9a18c 100644 --- a/packages/chat/src/screen.tsx +++ b/packages/chat/src/screen.tsx @@ -6,7 +6,7 @@ import { useChat } from "./context"; import InputComponent from "./components/input"; import Message from "./components/message"; -const PAGE_SIZE = 50; +import { PAGE_SIZE } from "./values"; /** * Renders the chat screen with virtualized history and live message updates. @@ -179,8 +179,8 @@ export default function Screen() { if (!hasValidChatUser) { return ( -
- Select a conversation to start chatting. +
+ Invalid user
); } diff --git a/packages/chat/src/values.ts b/packages/chat/src/values.ts index 692737b..9bfc328 100644 --- a/packages/chat/src/values.ts +++ b/packages/chat/src/values.ts @@ -11,3 +11,5 @@ export type LiveMessage = RawMessage & { failed?: boolean; localId: string; }; + +export const PAGE_SIZE = 25; diff --git a/packages/shared/src/data.ts b/packages/shared/src/data.ts index 9634269..e3b4d49 100644 --- a/packages/shared/src/data.ts +++ b/packages/shared/src/data.ts @@ -22,7 +22,7 @@ const message = z.object({ }); export const failedUser = { - display: "Failed to load user", + display: "Failed", iota_id: 0, omikron_connections: [], online_status: "user_borked",