update routing

This commit is contained in:
yzned
2025-07-20 18:09:45 +03:00
committed by yzned
parent 42b27b7ddc
commit bba27be7e2
10 changed files with 229 additions and 145 deletions

51
src/api/api.ts Normal file
View File

@@ -0,0 +1,51 @@
const _apiRoot = "https://ai-api.cytonic.com";
export class ApiError extends Error {
constructor(description: string, error: string, trace_id: string) {
super(error);
this.name = "ApiError";
this.message = description;
this.stack = trace_id;
this.cause = error;
}
}
async function request(
entrypoint: string,
method: "POST" | "GET" = "GET",
body?: object,
) {
const apiRoot = localStorage.getItem("API_ROOT") || _apiRoot;
const address =
apiRoot + (entrypoint[0] === "/" ? entrypoint : `/${entrypoint}`);
const res = await fetch(address, {
method,
body: JSON.stringify(body),
headers: {
...(body && { "Content-Type": "application/json" }),
},
});
if (!res.ok) {
const error = await res.json();
throw new ApiError(error.description, error.error, error.trace_id);
}
return await res.json();
}
const api = {
get(
entrypoint: string,
queryObj?: string | URLSearchParams | Record<string, string> | string[][],
) {
let queryStr = "";
if (queryObj) queryStr = `?${new URLSearchParams(queryObj).toString()}`;
return request(entrypoint + queryStr);
},
post(entrypoint: string, body?: object) {
return request(entrypoint, "POST", body);
},
};
export { api };

View File

@@ -2,77 +2,19 @@ import { observer } from "mobx-react-lite";
import { useRef, useState } from "react"; import { useRef, useState } from "react";
import Arrow from "@/icons/arrow.svg?react"; import Arrow from "@/icons/arrow.svg?react";
import Paperclip from "@/icons/paperclip.svg?react"; import Paperclip from "@/icons/paperclip.svg?react";
import Clock from "@/icons/clock.svg?react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { useNavigate } from "@tanstack/react-router"; import { useNavigate, useParams } from "@tanstack/react-router";
import { useAccountStore } from "@/contexts/AccountContext"; import { useAccountStore } from "@/contexts/AccountContext";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
import { useToast } from "@/lib/hooks/use-toast";
const Chat = observer(() => { const Chat = observer(() => {
const { id } = useParams({ from: "/interaction-panel/$id" });
return ( return (
<div className="flex-1 h-full flex items-center justify-center"> <div className="flex-1 h-full flex items-center justify-center">{id}</div>
<WelcomePanel />
</div>
); );
}); });
const WelcomePanel = () => {
const navigate = useNavigate();
const { userId } = useAccountStore();
const { toast } = useToast();
return (
<div className="max-w-[741px] flex flex-col gap-8">
<div className="flex flex-col gap-2 font-medium">
<span className="leading-[120%] text-black text-[24px]">
Hi!
<br /> I will help you create a crypto project on Cytonic
</span>
<span className="text-[#C0C0C0] text-[14px] flex gap-2 items-center">
<Clock />
<p>Estimated build time: 3 minutes</p>
</span>
</div>
<div className="flex flex-col gap-4">
<p className="text-[#C0C0C0] text-[14px] font-medium leading-[120%]">
Choose one of the most common prompts
<br /> below or use your own to begin
</p>
<div className="flex gap-4">
{[
{ emoji: "🐶", text: "Build a memecoin launchpad" },
{ emoji: "🏦", text: "Build a new stablecoin protocol" },
{ emoji: "🌐", text: "Create a RWA marketplace on Cytonic" },
{ emoji: "📊", text: "Ship a token dashboard" },
].map(({ emoji, text }) => (
<button
type="button"
key={emoji}
onClick={() => {
if (userId === undefined) {
navigate({
to: "/auth/mail",
viewTransition: { types: ["warp"] },
});
}
}}
className="text-left cursor-pointer hover:bg-fill-100 transition-all w-[173px] h-[123px] p-4 rounded-[16px] border border-[#E8E8E8] flex flex-col justify-between"
>
<p className="text-[14px] font-medium leading-[120%]">{text}</p>
<div className="text-[24px] leading-[100%]">{emoji}</div>
</button>
))}
</div>
<ChatInput />
</div>
</div>
);
};
const ChatInput = () => { const ChatInput = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const { userId } = useAccountStore(); const { userId } = useAccountStore();
@@ -156,4 +98,4 @@ const ChatInput = () => {
); );
}; };
export { Chat, WelcomePanel, ChatInput }; export { Chat, ChatInput };

View File

@@ -1 +0,0 @@

View File

@@ -4,11 +4,12 @@ import Plus from "@/icons/plus.svg?react";
import Books from "@/icons/books.svg?react"; import Books from "@/icons/books.svg?react";
import Discord from "@/icons/discord.svg?react"; import Discord from "@/icons/discord.svg?react";
import Profile from "@/icons/profile.svg?react"; import Profile from "@/icons/profile.svg?react";
import ThreeDots from "@/icons/three-dots.svg?react";
import { LINKS } from "@/lib/constants"; import { LINKS } from "@/lib/constants";
import { useEffect, useRef, useState, type ReactNode } from "react"; import { useEffect, useRef, useState, type ReactNode } from "react";
import { Button, type ButtonVariantType } from "../ui/button"; import { Button, type ButtonVariantType } from "../ui/button";
import { Link } from "@tanstack/react-router"; import { Link, useMatchRoute, useParams } from "@tanstack/react-router";
import Logo from "@/icons/logo.svg?react"; import Logo from "@/icons/logo.svg?react";
import LogoWithText from "@/icons/logo-with-text.svg?react"; import LogoWithText from "@/icons/logo-with-text.svg?react";
@@ -41,32 +42,6 @@ const MENU_ITEMS: MenuItemType[] = [
}, },
]; ];
const MenuItem = ({ item }: { item: MenuItemType }) => {
const { isMainMenuOpen } = useAccountStore();
return (
<Link
to={item.path}
className="group flex items-center gap-3 w-full whitespace-nowrap"
viewTransition={{ types: ["warp"] }}
>
<Button
data-variant={item.variant}
variant={item.variant}
className="w-8 h-8 data-[variant=secondary]:group-hover:bg-fill-150 data-[variant=primary]:group-hover:bg-fill-700 transition-colors"
>
{item.icon}
</Button>
<div
data-open={isMainMenuOpen}
className="data-[open=true]:opacity-100 opacity-0 transition-opacity duration-200 font-[500] text-[14px]"
>
{item.name}
</div>
</Link>
);
};
const MainMenu = observer(() => { const MainMenu = observer(() => {
const { isMainMenuOpen } = useAccountStore(); const { isMainMenuOpen } = useAccountStore();
@@ -83,7 +58,6 @@ const MainMenu = observer(() => {
const MOCK_CHATS = Array.from({ length: 20 }, (_, i) => ({ const MOCK_CHATS = Array.from({ length: 20 }, (_, i) => ({
id: i, id: i,
title: `Chat ${i + 1}`, title: `Chat ${i + 1}`,
subtitle: `Last message preview...`,
})); }));
const MainMenuContent = observer(() => { const MainMenuContent = observer(() => {
@@ -109,8 +83,9 @@ const MainMenuContent = observer(() => {
return ( return (
<div <div
data-open={isMainMenuOpen}
ref={containerRef} ref={containerRef}
className="relative w-full h-full overflow-auto flex flex-col" className="relative w-full h-full overflow-x-hidden data-[open=true]:overflow-y-auto overflow-y-hidden flex flex-col"
> >
<header <header
data-scrolled={hasScrolled} data-scrolled={hasScrolled}
@@ -160,19 +135,12 @@ const MainMenuContent = observer(() => {
Recent Recent
</p> </p>
<ul className="space-y-2 pb-10"> <ul className=" pb-10">
{MOCK_CHATS.map((chat) => ( {MOCK_CHATS.map((chat) => (
<li <ChatCell
chat={{ id: chat.id.toString(), title: chat.title }}
key={chat.id} key={chat.id}
className="rounded-md px-3 py-2 hover:bg-fill-100 cursor-pointer transition-colors" />
>
<p className="text-sm font-medium text-text-light-900 truncate">
{chat.title}
</p>
<p className="text-xs text-text-light-400 truncate">
{chat.subtitle}
</p>
</li>
))} ))}
</ul> </ul>
</div> </div>
@@ -196,4 +164,63 @@ const MainMenuContent = observer(() => {
); );
}); });
const MenuItem = ({ item }: { item: MenuItemType }) => {
const { isMainMenuOpen } = useAccountStore();
return (
<Link
to={item.path}
className="group flex items-center gap-3 w-full whitespace-nowrap"
viewTransition={{ types: ["warp"] }}
>
<Button
data-variant={item.variant}
variant={item.variant}
className="w-8 h-8 data-[variant=secondary]:group-hover:bg-fill-150 data-[variant=primary]:group-hover:bg-fill-700 transition-colors"
>
{item.icon}
</Button>
<div
data-open={isMainMenuOpen}
className="data-[open=true]:opacity-100 opacity-0 transition-opacity duration-200 font-[500] text-[14px]"
>
{item.name}
</div>
</Link>
);
};
const ChatCell = ({ chat }: { chat: { id: string; title: string } }) => {
const matchRoute = useMatchRoute();
const match = matchRoute({ to: "/interaction-panel/$id", fuzzy: true });
const activeChatId = match ? match.id : undefined;
return (
<div
data-active={activeChatId === chat.id}
className="data-[active=true]:bg-fill-150 block rounded-md h-10 hover:bg-fill-100 cursor-pointer transition-colors group flex items-center"
>
<Link
to="/interaction-panel/$id"
params={{ id: chat.id }}
viewTransition={{ types: ["warp"] }}
key={chat.id}
className="flex-1 h-full items-center flex pl-3"
>
<div className="text-sm font-medium text-text-light-900 truncate">
{chat.title}
</div>
</Link>
<button
className="opacity-0 group-hover:opacity-100 duration-300 cursor-pointer flex items-center pr-3 justify-end w-5 h-full"
type="button"
>
<ThreeDots />
</button>
</div>
);
};
export { MainMenu, MenuItem, MENU_ITEMS, MainMenuContent }; export { MainMenu, MenuItem, MENU_ITEMS, MainMenuContent };

3
src/icons/three-dots.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg width="2" height="10" viewBox="0 0 2 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.75 5C1.75 5.14834 1.70601 5.29334 1.6236 5.41668C1.54119 5.54002 1.42406 5.63614 1.28701 5.69291C1.14997 5.74968 0.999168 5.76453 0.853682 5.73559C0.708197 5.70665 0.57456 5.63522 0.46967 5.53033C0.364781 5.42544 0.29335 5.2918 0.264411 5.14632C0.235472 5.00083 0.250325 4.85003 0.307091 4.71299C0.363856 4.57594 0.459986 4.45881 0.583323 4.3764C0.70666 4.29399 0.851664 4.25 1 4.25C1.19891 4.25 1.38968 4.32902 1.53033 4.46967C1.67098 4.61032 1.75 4.80109 1.75 5ZM1 1.5C1.14834 1.5 1.29334 1.45601 1.41668 1.3736C1.54001 1.29119 1.63614 1.17406 1.69291 1.03701C1.74968 0.899968 1.76453 0.749169 1.73559 0.603683C1.70665 0.458197 1.63522 0.32456 1.53033 0.21967C1.42544 0.114781 1.2918 0.0433503 1.14632 0.0144114C1.00083 -0.0145275 0.850032 0.000324965 0.712988 0.0570907C0.575943 0.113856 0.458809 0.209986 0.376398 0.333323C0.293987 0.45666 0.25 0.601664 0.25 0.750001C0.25 0.948913 0.329018 1.13968 0.46967 1.28033C0.610322 1.42098 0.801088 1.5 1 1.5ZM1 8.5C0.851664 8.5 0.70666 8.54399 0.583323 8.6264C0.459986 8.70881 0.363856 8.82594 0.307091 8.96299C0.250325 9.10003 0.235472 9.25083 0.264411 9.39632C0.29335 9.5418 0.364781 9.67544 0.46967 9.78033C0.57456 9.88522 0.708197 9.95665 0.853682 9.98559C0.999168 10.0145 1.14997 9.99968 1.28701 9.94291C1.42406 9.88614 1.54119 9.79002 1.6236 9.66668C1.70601 9.54334 1.75 9.39834 1.75 9.25C1.75 9.05109 1.67098 8.86032 1.53033 8.71967C1.38968 8.57902 1.19891 8.5 1 8.5Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -9,19 +9,14 @@
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { Route as rootRouteImport } from './routes/__root' import { Route as rootRouteImport } from './routes/__root'
import { Route as InteractionPanelRouteImport } from './routes/interaction-panel'
import { Route as AuthRouteImport } from './routes/auth' import { Route as AuthRouteImport } from './routes/auth'
import { Route as IndexRouteImport } from './routes/index' import { Route as IndexRouteImport } from './routes/index'
import { Route as MagicLinkMagicLinkRouteImport } from './routes/magic-link/$magic-link' import { Route as MagicLinkMagicLinkRouteImport } from './routes/magic-link/$magic-link'
import { Route as InteractionPanelIdRouteImport } from './routes/interaction-panel/$id'
import { Route as AuthUsernameRouteImport } from './routes/auth/username' import { Route as AuthUsernameRouteImport } from './routes/auth/username'
import { Route as AuthMailRouteImport } from './routes/auth/mail' import { Route as AuthMailRouteImport } from './routes/auth/mail'
import { Route as AuthCodeRouteImport } from './routes/auth/code' import { Route as AuthCodeRouteImport } from './routes/auth/code'
const InteractionPanelRoute = InteractionPanelRouteImport.update({
id: '/interaction-panel',
path: '/interaction-panel',
getParentRoute: () => rootRouteImport,
} as any)
const AuthRoute = AuthRouteImport.update({ const AuthRoute = AuthRouteImport.update({
id: '/auth', id: '/auth',
path: '/auth', path: '/auth',
@@ -37,6 +32,11 @@ const MagicLinkMagicLinkRoute = MagicLinkMagicLinkRouteImport.update({
path: '/magic-link/$magic-link', path: '/magic-link/$magic-link',
getParentRoute: () => rootRouteImport, getParentRoute: () => rootRouteImport,
} as any) } as any)
const InteractionPanelIdRoute = InteractionPanelIdRouteImport.update({
id: '/interaction-panel/$id',
path: '/interaction-panel/$id',
getParentRoute: () => rootRouteImport,
} as any)
const AuthUsernameRoute = AuthUsernameRouteImport.update({ const AuthUsernameRoute = AuthUsernameRouteImport.update({
id: '/username', id: '/username',
path: '/username', path: '/username',
@@ -56,29 +56,29 @@ const AuthCodeRoute = AuthCodeRouteImport.update({
export interface FileRoutesByFullPath { export interface FileRoutesByFullPath {
'/': typeof IndexRoute '/': typeof IndexRoute
'/auth': typeof AuthRouteWithChildren '/auth': typeof AuthRouteWithChildren
'/interaction-panel': typeof InteractionPanelRoute
'/auth/code': typeof AuthCodeRoute '/auth/code': typeof AuthCodeRoute
'/auth/mail': typeof AuthMailRoute '/auth/mail': typeof AuthMailRoute
'/auth/username': typeof AuthUsernameRoute '/auth/username': typeof AuthUsernameRoute
'/interaction-panel/$id': typeof InteractionPanelIdRoute
'/magic-link/$magic-link': typeof MagicLinkMagicLinkRoute '/magic-link/$magic-link': typeof MagicLinkMagicLinkRoute
} }
export interface FileRoutesByTo { export interface FileRoutesByTo {
'/': typeof IndexRoute '/': typeof IndexRoute
'/auth': typeof AuthRouteWithChildren '/auth': typeof AuthRouteWithChildren
'/interaction-panel': typeof InteractionPanelRoute
'/auth/code': typeof AuthCodeRoute '/auth/code': typeof AuthCodeRoute
'/auth/mail': typeof AuthMailRoute '/auth/mail': typeof AuthMailRoute
'/auth/username': typeof AuthUsernameRoute '/auth/username': typeof AuthUsernameRoute
'/interaction-panel/$id': typeof InteractionPanelIdRoute
'/magic-link/$magic-link': typeof MagicLinkMagicLinkRoute '/magic-link/$magic-link': typeof MagicLinkMagicLinkRoute
} }
export interface FileRoutesById { export interface FileRoutesById {
__root__: typeof rootRouteImport __root__: typeof rootRouteImport
'/': typeof IndexRoute '/': typeof IndexRoute
'/auth': typeof AuthRouteWithChildren '/auth': typeof AuthRouteWithChildren
'/interaction-panel': typeof InteractionPanelRoute
'/auth/code': typeof AuthCodeRoute '/auth/code': typeof AuthCodeRoute
'/auth/mail': typeof AuthMailRoute '/auth/mail': typeof AuthMailRoute
'/auth/username': typeof AuthUsernameRoute '/auth/username': typeof AuthUsernameRoute
'/interaction-panel/$id': typeof InteractionPanelIdRoute
'/magic-link/$magic-link': typeof MagicLinkMagicLinkRoute '/magic-link/$magic-link': typeof MagicLinkMagicLinkRoute
} }
export interface FileRouteTypes { export interface FileRouteTypes {
@@ -86,47 +86,40 @@ export interface FileRouteTypes {
fullPaths: fullPaths:
| '/' | '/'
| '/auth' | '/auth'
| '/interaction-panel'
| '/auth/code' | '/auth/code'
| '/auth/mail' | '/auth/mail'
| '/auth/username' | '/auth/username'
| '/interaction-panel/$id'
| '/magic-link/$magic-link' | '/magic-link/$magic-link'
fileRoutesByTo: FileRoutesByTo fileRoutesByTo: FileRoutesByTo
to: to:
| '/' | '/'
| '/auth' | '/auth'
| '/interaction-panel'
| '/auth/code' | '/auth/code'
| '/auth/mail' | '/auth/mail'
| '/auth/username' | '/auth/username'
| '/interaction-panel/$id'
| '/magic-link/$magic-link' | '/magic-link/$magic-link'
id: id:
| '__root__' | '__root__'
| '/' | '/'
| '/auth' | '/auth'
| '/interaction-panel'
| '/auth/code' | '/auth/code'
| '/auth/mail' | '/auth/mail'
| '/auth/username' | '/auth/username'
| '/interaction-panel/$id'
| '/magic-link/$magic-link' | '/magic-link/$magic-link'
fileRoutesById: FileRoutesById fileRoutesById: FileRoutesById
} }
export interface RootRouteChildren { export interface RootRouteChildren {
IndexRoute: typeof IndexRoute IndexRoute: typeof IndexRoute
AuthRoute: typeof AuthRouteWithChildren AuthRoute: typeof AuthRouteWithChildren
InteractionPanelRoute: typeof InteractionPanelRoute InteractionPanelIdRoute: typeof InteractionPanelIdRoute
MagicLinkMagicLinkRoute: typeof MagicLinkMagicLinkRoute MagicLinkMagicLinkRoute: typeof MagicLinkMagicLinkRoute
} }
declare module '@tanstack/react-router' { declare module '@tanstack/react-router' {
interface FileRoutesByPath { interface FileRoutesByPath {
'/interaction-panel': {
id: '/interaction-panel'
path: '/interaction-panel'
fullPath: '/interaction-panel'
preLoaderRoute: typeof InteractionPanelRouteImport
parentRoute: typeof rootRouteImport
}
'/auth': { '/auth': {
id: '/auth' id: '/auth'
path: '/auth' path: '/auth'
@@ -148,6 +141,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof MagicLinkMagicLinkRouteImport preLoaderRoute: typeof MagicLinkMagicLinkRouteImport
parentRoute: typeof rootRouteImport parentRoute: typeof rootRouteImport
} }
'/interaction-panel/$id': {
id: '/interaction-panel/$id'
path: '/interaction-panel/$id'
fullPath: '/interaction-panel/$id'
preLoaderRoute: typeof InteractionPanelIdRouteImport
parentRoute: typeof rootRouteImport
}
'/auth/username': { '/auth/username': {
id: '/auth/username' id: '/auth/username'
path: '/username' path: '/username'
@@ -189,7 +189,7 @@ const AuthRouteWithChildren = AuthRoute._addFileChildren(AuthRouteChildren)
const rootRouteChildren: RootRouteChildren = { const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute, IndexRoute: IndexRoute,
AuthRoute: AuthRouteWithChildren, AuthRoute: AuthRouteWithChildren,
InteractionPanelRoute: InteractionPanelRoute, InteractionPanelIdRoute: InteractionPanelIdRoute,
MagicLinkMagicLinkRoute: MagicLinkMagicLinkRoute, MagicLinkMagicLinkRoute: MagicLinkMagicLinkRoute,
} }
export const routeTree = rootRouteImport export const routeTree = rootRouteImport

View File

@@ -1,14 +1,13 @@
// src/routes/__root.tsx // src/routes/__root.tsx
import { Outlet, createRootRoute } from "@tanstack/react-router"; import { Outlet, createRootRoute } from "@tanstack/react-router";
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
import { useAccountStore } from "@/contexts/AccountContext"; import { useAccountStore } from "@/contexts/AccountContext";
import { MainMenu } from "@/components/main-menu/main-menu"; import { MainMenu } from "@/components/main-menu/main-menu";
import { observer } from "mobx-react-lite";
export const Route = createRootRoute({ export const Route = createRootRoute({
component: RootLayout, component: () => <RootLayout />,
}); });
const RootLayout = observer(() => {
function RootLayout() {
const { userId } = useAccountStore(); const { userId } = useAccountStore();
return ( return (
@@ -24,4 +23,4 @@ function RootLayout() {
{/* <TanStackRouterDevtools /> */} {/* <TanStackRouterDevtools /> */}
</div> </div>
); );
} });

View File

@@ -1,18 +1,23 @@
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { useAccountStore } from "@/contexts/AccountContext";
import { createFileRoute, useNavigate } from "@tanstack/react-router"; import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { observer } from "mobx-react-lite";
import { useState } from "react"; import { useState } from "react";
export const Route = createFileRoute("/auth/username")({ export const Route = createFileRoute("/auth/username")({
component: RouteComponent, component: () => <RouteComponent />,
}); });
function RouteComponent() { const RouteComponent = observer(() => {
const [username, setUsername] = useState(""); const [username, setUsername] = useState("");
const navigate = useNavigate(); const navigate = useNavigate();
const { setUserId } = useAccountStore();
const handleSubmit = () => { const handleSubmit = () => {
if (username.trim() === "") return; if (username.trim() === "") return;
setUserId("ID");
navigate({ navigate({
to: "/", to: "/",
viewTransition: { types: ["warp"] }, viewTransition: { types: ["warp"] },
@@ -67,4 +72,4 @@ function RouteComponent() {
</div> </div>
</div> </div>
); );
} });

View File

@@ -1,20 +1,22 @@
import { Chat } from "@/components/interaction-panel/chat"; import { createFileRoute, Link, useNavigate } from "@tanstack/react-router";
import { createFileRoute, Link } from "@tanstack/react-router";
import LogoWithText from "@/icons/logo-with-text.svg?react"; import LogoWithText from "@/icons/logo-with-text.svg?react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { useAccountStore } from "@/contexts/AccountContext"; import { useAccountStore } from "@/contexts/AccountContext";
import { observer } from "mobx-react-lite";
import Clock from "@/icons/clock.svg?react";
import { ChatInput } from "@/components/interaction-panel/chat";
export const Route = createFileRoute("/")({ export const Route = createFileRoute("/")({
component: App, component: () => <App />,
}); });
function App() { const App = observer(() => {
const { userId } = useAccountStore(); const { userId } = useAccountStore();
return ( return (
<div className="h-full pt-5"> <div className="h-full ">
{!userId && ( {!userId && (
<header className="px-10 h-[52px] flex items-center justify-between absolute w-full"> <header className=" pt-5 px-10 h-[52px] flex items-center justify-between absolute w-full">
<LogoWithText /> <LogoWithText />
<Link to="/auth/mail" viewTransition={{ types: ["warp"] }}> <Link to="/auth/mail" viewTransition={{ types: ["warp"] }}>
<Button className=" text-[18px] w-[106px] cursor-pointer"> <Button className=" text-[18px] w-[106px] cursor-pointer">
@@ -23,7 +25,64 @@ function App() {
</Link> </Link>
</header> </header>
)} )}
<Chat />
<div className="flex-1 h-full flex items-center justify-center">
<WelcomePanel />
</div>
</div> </div>
); );
});
const WelcomePanel = () => {
const navigate = useNavigate();
const { userId } = useAccountStore();
return (
<div className="max-w-[741px] flex flex-col gap-8">
<div className="flex flex-col gap-2 font-medium">
<span className="leading-[120%] text-black text-[24px]">
Hi!
<br /> I will help you create a crypto project on Cytonic
</span>
<span className="text-[#C0C0C0] text-[14px] flex gap-2 items-center">
<Clock />
<p>Estimated build time: 3 minutes</p>
</span>
</div>
<div className="flex flex-col gap-4">
<p className="text-[#C0C0C0] text-[14px] font-medium leading-[120%]">
Choose one of the most common prompts
<br /> below or use your own to begin
</p>
<div className="flex gap-4">
{[
{ emoji: "🐶", text: "Build a memecoin launchpad" },
{ emoji: "🏦", text: "Build a new stablecoin protocol" },
{ emoji: "🌐", text: "Create a RWA marketplace on Cytonic" },
{ emoji: "📊", text: "Ship a token dashboard" },
].map(({ emoji, text }) => (
<button
type="button"
key={emoji}
onClick={() => {
if (userId === undefined) {
navigate({
to: "/auth/mail",
viewTransition: { types: ["warp"] },
});
} }
}}
className="text-left cursor-pointer hover:bg-fill-100 transition-all w-[173px] h-[123px] p-4 rounded-[16px] border border-[#E8E8E8] flex flex-col justify-between"
>
<p className="text-[14px] font-medium leading-[120%]">{text}</p>
<div className="text-[24px] leading-[100%]">{emoji}</div>
</button>
))}
</div>
<ChatInput />
</div>
</div>
);
};

View File

@@ -1,13 +1,12 @@
// src/routes/chat.tsx
import { Chat } from "@/components/interaction-panel/chat"; import { Chat } from "@/components/interaction-panel/chat";
import { Sandbox } from "@/components/interaction-panel/sandbox"; import { Sandbox } from "@/components/interaction-panel/sandbox";
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute("/interaction-panel")({ export const Route = createFileRoute("/interaction-panel/$id")({
component: InteractionPanelRoute, component: RouteComponent,
}); });
function InteractionPanelRoute() { function RouteComponent() {
return ( return (
<div className="flex h-full"> <div className="flex h-full">
<Chat /> <Chat />