feat(web): add login and customer chat demos
Add a responsive login screen using the shared appearance controls and a browser-only customer-service simulator for the chat workspace. Register both routes and include mock media resolution, message sending, and earlier-history loading for interactive verification.
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
import type React from "react"
|
||||
|
||||
type Props = Omit<React.ComponentProps<"svg">, "role" | "viewBox" | "xmlns">
|
||||
|
||||
export function Logo(props: Props) {
|
||||
return (
|
||||
<svg
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="#7AB55C"
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<title>Shopify</title>
|
||||
<path d="M15.337 23.979l7.216-1.561s-2.604-17.613-2.625-17.73c-.018-.116-.114-.192-.211-.192s-1.929-.136-1.929-.136-1.275-1.274-1.439-1.411c-.045-.037-.075-.057-.121-.074l-.914 21.104h.023zM11.71 11.305s-.81-.424-1.774-.424c-1.447 0-1.504.906-1.504 1.141 0 1.232 3.24 1.715 3.24 4.629 0 2.295-1.44 3.76-3.406 3.76-2.354 0-3.54-1.465-3.54-1.465l.646-2.086s1.245 1.066 2.28 1.066c.675 0 .975-.545.975-.932 0-1.619-2.654-1.694-2.654-4.359-.034-2.237 1.571-4.416 4.827-4.416 1.257 0 1.875.361 1.875.361l-.945 2.715-.02.01zM11.17.83c.136 0 .271.038.405.135-.984.465-2.064 1.639-2.508 3.992-.656.213-1.293.405-1.889.578C7.697 3.75 8.951.84 11.17.84V.83zm1.235 2.949v.135c-.754.232-1.583.484-2.394.736.466-1.777 1.333-2.645 2.085-2.971.193.501.309 1.176.309 2.1zm.539-2.234c.694.074 1.141.867 1.429 1.755-.349.114-.735.231-1.158.366v-.252c0-.752-.096-1.371-.271-1.871v.002zm2.992 1.289c-.02 0-.06.021-.078.021s-.289.075-.714.21c-.423-1.233-1.176-2.37-2.508-2.37h-.115C12.135.209 11.669 0 11.265 0 8.159 0 6.675 3.877 6.21 5.846c-1.194.365-2.063.636-2.16.674-.675.213-.694.232-.772.87-.075.462-1.83 14.063-1.83 14.063L15.009 24l.927-21.166z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
import * as React from "react"
|
||||
import {
|
||||
ChatWorkspace,
|
||||
type ChatConversation,
|
||||
type ChatMessage,
|
||||
} from "@workspace/blocks/chats"
|
||||
|
||||
const mockMediaUrls: Record<string, string> = {
|
||||
"campaign-banner":
|
||||
"https://images.unsplash.com/photo-1556742049-0cfed4f6a45d?auto=format&fit=crop&w=960&q=80",
|
||||
"order-card-thumbnail":
|
||||
"https://images.unsplash.com/photo-1556740749-887f6717d7e4?auto=format&fit=crop&w=320&q=80",
|
||||
}
|
||||
|
||||
const initialConversations: readonly ChatConversation[] = [
|
||||
{
|
||||
avatarUrl:
|
||||
"https://pub-c5e31b5cdafb419fb247a8ac2e78df7a.r2.dev/public/assets/images/mock/avatar/avatar-1.webp",
|
||||
description: "微信小程序客服会话",
|
||||
id: "customer-service",
|
||||
lastActivityLabel: "刚刚",
|
||||
lastMessage: "我的订单",
|
||||
messages: [
|
||||
{
|
||||
author: { id: "customer-zhang", name: "张晓敏" },
|
||||
createdAt: "2026-07-31T09:30:00+08:00",
|
||||
direction: "incoming",
|
||||
id: "welcome",
|
||||
msgtype: "text",
|
||||
text: { content: "你好,我想了解一下订单的配送进度。" },
|
||||
},
|
||||
{
|
||||
createdAt: "2026-07-31T09:31:00+08:00",
|
||||
direction: "outgoing",
|
||||
id: "campaign-image",
|
||||
image: { media_id: "campaign-banner" },
|
||||
msgtype: "image",
|
||||
},
|
||||
{
|
||||
createdAt: "2026-07-31T09:32:00+08:00",
|
||||
direction: "outgoing",
|
||||
id: "help-link",
|
||||
link: {
|
||||
description: "查看商品、订单与售后服务说明。",
|
||||
thumb_url:
|
||||
"https://images.unsplash.com/photo-1556740758-90de374c12ad?auto=format&fit=crop&w=640&q=80",
|
||||
title: "帮助中心",
|
||||
url: "https://example.com/help",
|
||||
},
|
||||
msgtype: "link",
|
||||
},
|
||||
{
|
||||
createdAt: "2026-07-31T09:33:00+08:00",
|
||||
direction: "outgoing",
|
||||
id: "orders-card",
|
||||
miniprogrampage: {
|
||||
pagepath: "pages/orders/index",
|
||||
thumb_media_id: "order-card-thumbnail",
|
||||
title: "我的订单",
|
||||
},
|
||||
msgtype: "miniprogrampage",
|
||||
},
|
||||
],
|
||||
title: "张晓敏",
|
||||
unreadCount: 3,
|
||||
},
|
||||
{
|
||||
avatarUrl:
|
||||
"https://pub-c5e31b5cdafb419fb247a8ac2e78df7a.r2.dev/public/assets/images/mock/avatar/avatar-4.webp",
|
||||
description: "微信小程序客服会话",
|
||||
id: "shipping",
|
||||
lastActivityLabel: "昨天",
|
||||
lastMessage: "请问我的包裹什么时候可以送到?",
|
||||
messages: [
|
||||
{
|
||||
author: { id: "customer-li", name: "李雨晴" },
|
||||
createdAt: "2026-07-30T16:20:00+08:00",
|
||||
direction: "incoming",
|
||||
id: "shipping-status",
|
||||
msgtype: "text",
|
||||
text: { content: "请问我的包裹什么时候可以送到?" },
|
||||
},
|
||||
],
|
||||
title: "李雨晴",
|
||||
},
|
||||
]
|
||||
|
||||
export function ChatsDemoPage() {
|
||||
const [activeConversationId, setActiveConversationId] = React.useState(
|
||||
initialConversations[0]!.id
|
||||
)
|
||||
const [conversations, setConversations] = React.useState(initialConversations)
|
||||
const [hasMoreHistory, setHasMoreHistory] = React.useState(true)
|
||||
const [mediaUrls, setMediaUrls] = React.useState(mockMediaUrls)
|
||||
const objectUrlsRef = React.useRef(new Set<string>())
|
||||
|
||||
React.useEffect(
|
||||
() => () => {
|
||||
for (const url of objectUrlsRef.current) URL.revokeObjectURL(url)
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
const appendMessages = React.useCallback(
|
||||
(conversationId: string, messages: readonly ChatMessage[]) => {
|
||||
if (messages.length === 0) return
|
||||
|
||||
const lastMessage = messages.at(-1)!
|
||||
setConversations((current) =>
|
||||
current.map((conversation) =>
|
||||
conversation.id === conversationId
|
||||
? {
|
||||
...conversation,
|
||||
lastActivityLabel: "刚刚",
|
||||
lastMessage: getMessagePreview(lastMessage),
|
||||
messages: [...conversation.messages, ...messages],
|
||||
unreadCount: 0,
|
||||
}
|
||||
: conversation
|
||||
)
|
||||
)
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
const handleSend = React.useCallback(
|
||||
async ({
|
||||
conversation,
|
||||
files,
|
||||
text,
|
||||
}: Parameters<
|
||||
NonNullable<React.ComponentProps<typeof ChatWorkspace>["onSend"]>
|
||||
>[0]) => {
|
||||
const createdAt = new Date().toISOString()
|
||||
const prefix = `${Date.now()}`
|
||||
const messages: ChatMessage[] = []
|
||||
|
||||
if (text) {
|
||||
messages.push({
|
||||
createdAt,
|
||||
direction: "outgoing",
|
||||
id: `${prefix}:text`,
|
||||
msgtype: "text",
|
||||
status: "sent",
|
||||
text: { content: text },
|
||||
})
|
||||
}
|
||||
|
||||
const newMediaUrls: Record<string, string> = {}
|
||||
for (const [index, file] of files.entries()) {
|
||||
const url = URL.createObjectURL(file)
|
||||
objectUrlsRef.current.add(url)
|
||||
|
||||
if (file.type.startsWith("image/")) {
|
||||
const mediaId = `${prefix}:image:${index}`
|
||||
newMediaUrls[mediaId] = url
|
||||
messages.push({
|
||||
createdAt,
|
||||
direction: "outgoing",
|
||||
id: mediaId,
|
||||
image: { media_id: mediaId },
|
||||
msgtype: "image",
|
||||
status: "sent",
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
messages.push({
|
||||
createdAt,
|
||||
direction: "outgoing",
|
||||
id: `${prefix}:link:${index}`,
|
||||
link: {
|
||||
description: "本地选择的文件(仅用于模拟)。",
|
||||
thumb_url: "",
|
||||
title: file.name,
|
||||
url,
|
||||
},
|
||||
msgtype: "link",
|
||||
status: "sent",
|
||||
})
|
||||
}
|
||||
|
||||
if (Object.keys(newMediaUrls).length > 0) {
|
||||
setMediaUrls((current) => ({ ...current, ...newMediaUrls }))
|
||||
}
|
||||
appendMessages(conversation.id, messages)
|
||||
},
|
||||
[appendMessages]
|
||||
)
|
||||
|
||||
const handleLoadEarlierMessages = React.useCallback(
|
||||
(conversation: ChatConversation) => {
|
||||
setHasMoreHistory(false)
|
||||
setConversations((current) =>
|
||||
current.map((currentConversation) =>
|
||||
currentConversation.id === conversation.id
|
||||
? {
|
||||
...currentConversation,
|
||||
messages: [
|
||||
{
|
||||
author: { id: "customer-zhang", name: "张晓敏" },
|
||||
createdAt: "2026-07-31T09:20:00+08:00",
|
||||
direction: "incoming",
|
||||
id: `${conversation.id}:earlier-message`,
|
||||
msgtype: "text",
|
||||
text: { content: "我昨天已经联系过客服了。" },
|
||||
},
|
||||
...currentConversation.messages,
|
||||
],
|
||||
}
|
||||
: currentConversation
|
||||
)
|
||||
)
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="flex min-w-0 flex-col gap-6">
|
||||
<div>
|
||||
<h1 className="text-xl font-medium">聊天模拟器</h1>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
所有数据仅保存在浏览器内,可模拟微信小程序客服的文本、图片、图文链接和小程序卡片消息。
|
||||
</p>
|
||||
</div>
|
||||
<ChatWorkspace
|
||||
activeConversationId={activeConversationId}
|
||||
conversations={conversations}
|
||||
hasMoreMessages={
|
||||
hasMoreHistory && activeConversationId === "customer-service"
|
||||
}
|
||||
maxHeight="calc(100svh - 15rem)"
|
||||
minHeight="min(36rem, calc(100svh - 15rem))"
|
||||
onActiveConversationChange={(conversation) =>
|
||||
setActiveConversationId(conversation.id)
|
||||
}
|
||||
onLoadEarlierMessages={handleLoadEarlierMessages}
|
||||
onSend={handleSend}
|
||||
resolveMediaUrl={(mediaId) => mediaUrls[mediaId]}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function getMessagePreview(message: ChatMessage) {
|
||||
switch (message.msgtype) {
|
||||
case "text":
|
||||
return message.text.content
|
||||
case "image":
|
||||
return "[图片]"
|
||||
case "link":
|
||||
return message.link.title
|
||||
case "miniprogrampage":
|
||||
return message.miniprogrampage.title
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
import React, { useState } from "react"
|
||||
import { useMutation } from "@tanstack/react-query"
|
||||
import { Button } from "@workspace/ui/components/button"
|
||||
import { Input } from "@workspace/ui/components/input"
|
||||
import { EyeIcon, EyeOffIcon } from "lucide-react"
|
||||
import { cn } from "@workspace/ui/lib/utils"
|
||||
import { ThemeToggleButton } from "@workspace/blocks/blocks/appearance"
|
||||
import { Logo } from "@/components/logo"
|
||||
|
||||
export function LoginPage() {
|
||||
return (
|
||||
<div className="isolate h-svh min-h-148 w-svw lg:min-h-164.5">
|
||||
<header className="relative z-10 h-0">
|
||||
<div className="flex h-14 items-center justify-between px-6">
|
||||
<div className="size-6 opacity-0 lg:opacity-100">
|
||||
<Logo />
|
||||
</div>
|
||||
<div>
|
||||
<ThemeToggleButton />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<main className="relative isolate flex size-full before:pointer-events-none before:absolute before:inset-0 before:-z-1 before:bg-[url(https://pub-c5e31b5cdafb419fb247a8ac2e78df7a.r2.dev/public/assets/background/background-3-blur.webp)] before:bg-cover before:bg-center before:bg-no-repeat before:opacity-20">
|
||||
<div className="pointer-events-none hidden w-120 flex-col justify-center gap-16 bg-linear-to-r from-secondary/10 to-muted/20 p-6 pt-18 select-none lg:flex">
|
||||
<div className="text-center">
|
||||
<h2 className="text-3xl font-bold tracking-tight text-foreground">
|
||||
欢迎回来 👋🏻
|
||||
</h2>
|
||||
<p className="mt-4 text-muted-foreground">
|
||||
让商品、订单与运营管理更简单高效。
|
||||
</p>
|
||||
</div>
|
||||
<img
|
||||
src="https://pub-c5e31b5cdafb419fb247a8ac2e78df7a.r2.dev/public/assets/illustrations/illustration-dashboard.webp"
|
||||
alt=""
|
||||
className="aspect-4/3 w-full object-cover object-center"
|
||||
/>
|
||||
<Features />
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col items-start gap-7 bg-sidebar/80 px-6 py-20 lg:justify-center lg:px-8">
|
||||
<div className="mx-auto mb-10 w-full max-w-md lg:hidden">
|
||||
<Logo className="size-12" />
|
||||
</div>
|
||||
<LoginForm />
|
||||
<div className="lg:h-24" />
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const features = [
|
||||
{
|
||||
label: "Jwt",
|
||||
img: "https://pub-c5e31b5cdafb419fb247a8ac2e78df7a.r2.dev/public/assets/icons/platforms/ic-jwt.svg",
|
||||
},
|
||||
{
|
||||
label: "Firebase",
|
||||
img: "https://pub-c5e31b5cdafb419fb247a8ac2e78df7a.r2.dev/public/assets/icons/platforms/ic-firebase.svg",
|
||||
},
|
||||
{
|
||||
label: "Amplify",
|
||||
img: "https://pub-c5e31b5cdafb419fb247a8ac2e78df7a.r2.dev/public/assets/icons/platforms/ic-amplify.svg",
|
||||
},
|
||||
{
|
||||
label: "Auth0",
|
||||
img: "https://pub-c5e31b5cdafb419fb247a8ac2e78df7a.r2.dev/public/assets/icons/platforms/ic-auth0.svg",
|
||||
},
|
||||
{
|
||||
label: "Supabase",
|
||||
img: "https://pub-c5e31b5cdafb419fb247a8ac2e78df7a.r2.dev/public/assets/icons/platforms/ic-supabase.svg",
|
||||
},
|
||||
]
|
||||
|
||||
function Features() {
|
||||
const [index, setIndex] = React.useState(0)
|
||||
|
||||
React.useEffect(() => {
|
||||
const timerId = setInterval(() => {
|
||||
setIndex((currentIndex) => (currentIndex + 1) % features.length)
|
||||
}, 1000)
|
||||
|
||||
return () => {
|
||||
clearInterval(timerId)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<ul className="flex items-center justify-center gap-4">
|
||||
{features.map((f, i) => (
|
||||
<li
|
||||
key={f.label}
|
||||
className={cn(
|
||||
"transition-[filter] duration-1000 ease-linear",
|
||||
i === index ? "grayscale-0" : "grayscale-100"
|
||||
)}
|
||||
>
|
||||
<img className="size-8" alt={f.label} src={f.img} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
function LoginForm() {
|
||||
// const navigate = useNavigate()
|
||||
const [username, setUsername] = useState("")
|
||||
const [password, setPassword] = useState("")
|
||||
|
||||
const mutation = useMutation({
|
||||
// mutationFn: () => {
|
||||
// return api.post<AdminAuthResponse>("/api/v1/admin/auth/login", {
|
||||
// username,
|
||||
// password,
|
||||
// })
|
||||
// },
|
||||
// onSuccess: () => {
|
||||
// login()
|
||||
// Toast("登录成功", "success")
|
||||
// navigate("/", { replace: true })
|
||||
// },
|
||||
// onError: (err: Error) => toast(err.message || "登录失败", "error"),
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-md">
|
||||
<h5 className="mb-3 text-2xl font-bold tracking-tight text-foreground">
|
||||
账号登录
|
||||
</h5>
|
||||
<p className="lg:text-md text-sm text-muted-foreground">
|
||||
<span className="text-muted-foreground">
|
||||
请输入您的帐户信息以开始管理您的系统
|
||||
</span>
|
||||
</p>
|
||||
<form
|
||||
className="mt-8 space-y-8"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault()
|
||||
mutation.mutate()
|
||||
}}
|
||||
>
|
||||
<Field
|
||||
label="用户名"
|
||||
inputId="username"
|
||||
Component={UsernameInput}
|
||||
value={username}
|
||||
setValue={setUsername}
|
||||
/>
|
||||
<Field
|
||||
label="密码"
|
||||
inputId="password"
|
||||
Component={PasswordInput}
|
||||
value={password}
|
||||
setValue={setPassword}
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
className="mt-2 h-12 w-full rounded-lg"
|
||||
disabled={mutation.isPending}
|
||||
>
|
||||
{mutation.isPending ? "登录中..." : "登录"}
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
type FieldInputProps = {
|
||||
inputId: string
|
||||
className?: string
|
||||
value: string
|
||||
setValue: React.Dispatch<React.SetStateAction<string>>
|
||||
}
|
||||
|
||||
type FieldInputComponent = React.ComponentType<FieldInputProps>
|
||||
|
||||
function Field({
|
||||
label,
|
||||
Component,
|
||||
...props
|
||||
}: Omit<FieldInputProps, "className"> & {
|
||||
label: string
|
||||
Component: FieldInputComponent
|
||||
}) {
|
||||
return (
|
||||
<fieldset className="relative flex h-12 items-center rounded-lg border-2 border-border p-0 focus-within:border-primary focus-within:[&_label]:opacity-100">
|
||||
<legend className="ml-2 flex h-0 px-0">
|
||||
<label
|
||||
className="h-fit -translate-y-1/2 px-1 text-sm opacity-50"
|
||||
htmlFor={props.inputId}
|
||||
>
|
||||
{label}
|
||||
</label>
|
||||
</legend>
|
||||
<Component
|
||||
className="h-full rounded-none border-none bg-transparent outline-none autofill:bg-transparent focus-visible:border-none focus-visible:ring-0 [&:-webkit-autofill]:[-webkit-background-clip:text] [&:-webkit-autofill]:[-webkit-text-fill-color:var(--foreground)] [&:-webkit-autofill]:[transition:background-color_9999s_ease-out]"
|
||||
{...props}
|
||||
/>
|
||||
</fieldset>
|
||||
)
|
||||
}
|
||||
|
||||
function UsernameInput({
|
||||
inputId,
|
||||
className,
|
||||
value,
|
||||
setValue,
|
||||
}: FieldInputProps) {
|
||||
return (
|
||||
<Input
|
||||
id={inputId}
|
||||
value={value}
|
||||
className={className}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
placeholder="请输入用户名"
|
||||
required
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function PasswordInput({
|
||||
inputId,
|
||||
className,
|
||||
value,
|
||||
setValue,
|
||||
}: FieldInputProps) {
|
||||
const [isVisible, setVisile] = React.useState(false)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Input
|
||||
id={inputId}
|
||||
type={isVisible ? "text" : "password"}
|
||||
value={value}
|
||||
className={cn("relative z-1 pe-24", className)}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
placeholder="请输入登录密码"
|
||||
required
|
||||
/>
|
||||
<div className="absolute top-0 right-0 z-2 flex h-11 w-11 items-center justify-center">
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
setVisile((v) => !v)
|
||||
}}
|
||||
className="rounded-sm"
|
||||
>
|
||||
{isVisible ? <EyeIcon /> : <EyeOffIcon />}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -10,13 +10,20 @@
|
||||
|
||||
import { Route as rootRouteImport } from './routes/__root'
|
||||
import { Route as AppRouteImport } from './routes/_app'
|
||||
import { Route as LoginRouteImport } from './routes/login'
|
||||
import { Route as AppIndexRouteImport } from './routes/_app.index'
|
||||
import { Route as AppSplatRouteImport } from './routes/_app.$'
|
||||
import { Route as AppChatsRouteImport } from './routes/_app.chats'
|
||||
|
||||
const AppRoute = AppRouteImport.update({
|
||||
id: '/_app',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const LoginRoute = LoginRouteImport.update({
|
||||
id: '/login',
|
||||
path: '/login',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const AppIndexRoute = AppIndexRouteImport.update({
|
||||
id: '/',
|
||||
path: '/',
|
||||
@@ -27,31 +34,43 @@ const AppSplatRoute = AppSplatRouteImport.update({
|
||||
path: '/$',
|
||||
getParentRoute: () => AppRoute,
|
||||
} as any)
|
||||
const AppChatsRoute = AppChatsRouteImport.update({
|
||||
id: '/chats',
|
||||
path: '/chats',
|
||||
getParentRoute: () => AppRoute,
|
||||
} as any)
|
||||
|
||||
export interface FileRoutesByFullPath {
|
||||
'/': typeof AppIndexRoute
|
||||
'/login': typeof LoginRoute
|
||||
'/$': typeof AppSplatRoute
|
||||
'/chats': typeof AppChatsRoute
|
||||
}
|
||||
export interface FileRoutesByTo {
|
||||
'/login': typeof LoginRoute
|
||||
'/$': typeof AppSplatRoute
|
||||
'/chats': typeof AppChatsRoute
|
||||
'/': typeof AppIndexRoute
|
||||
}
|
||||
export interface FileRoutesById {
|
||||
__root__: typeof rootRouteImport
|
||||
'/_app': typeof AppRouteWithChildren
|
||||
'/login': typeof LoginRoute
|
||||
'/_app/$': typeof AppSplatRoute
|
||||
'/_app/chats': typeof AppChatsRoute
|
||||
'/_app/': typeof AppIndexRoute
|
||||
}
|
||||
export interface FileRouteTypes {
|
||||
fileRoutesByFullPath: FileRoutesByFullPath
|
||||
fullPaths: '/' | '/$'
|
||||
fullPaths: '/' | '/login' | '/$' | '/chats'
|
||||
fileRoutesByTo: FileRoutesByTo
|
||||
to: '/$' | '/'
|
||||
id: '__root__' | '/_app' | '/_app/$' | '/_app/'
|
||||
to: '/login' | '/$' | '/chats' | '/'
|
||||
id: '__root__' | '/_app' | '/login' | '/_app/$' | '/_app/chats' | '/_app/'
|
||||
fileRoutesById: FileRoutesById
|
||||
}
|
||||
export interface RootRouteChildren {
|
||||
AppRoute: typeof AppRouteWithChildren
|
||||
LoginRoute: typeof LoginRoute
|
||||
}
|
||||
|
||||
declare module '@tanstack/react-router' {
|
||||
@@ -63,6 +82,13 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof AppRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/login': {
|
||||
id: '/login'
|
||||
path: '/login'
|
||||
fullPath: '/login'
|
||||
preLoaderRoute: typeof LoginRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/_app/': {
|
||||
id: '/_app/'
|
||||
path: '/'
|
||||
@@ -77,16 +103,25 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof AppSplatRouteImport
|
||||
parentRoute: typeof AppRoute
|
||||
}
|
||||
'/_app/chats': {
|
||||
id: '/_app/chats'
|
||||
path: '/chats'
|
||||
fullPath: '/chats'
|
||||
preLoaderRoute: typeof AppChatsRouteImport
|
||||
parentRoute: typeof AppRoute
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface AppRouteChildren {
|
||||
AppSplatRoute: typeof AppSplatRoute
|
||||
AppChatsRoute: typeof AppChatsRoute
|
||||
AppIndexRoute: typeof AppIndexRoute
|
||||
}
|
||||
|
||||
const AppRouteChildren: AppRouteChildren = {
|
||||
AppSplatRoute: AppSplatRoute,
|
||||
AppChatsRoute: AppChatsRoute,
|
||||
AppIndexRoute: AppIndexRoute,
|
||||
}
|
||||
|
||||
@@ -94,6 +129,7 @@ const AppRouteWithChildren = AppRoute._addFileChildren(AppRouteChildren)
|
||||
|
||||
const rootRouteChildren: RootRouteChildren = {
|
||||
AppRoute: AppRouteWithChildren,
|
||||
LoginRoute: LoginRoute,
|
||||
}
|
||||
export const routeTree = rootRouteImport
|
||||
._addFileChildren(rootRouteChildren)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { createFileRoute } from "@tanstack/react-router"
|
||||
|
||||
import { ChatsDemoPage } from "@/pages/chats-demo-page"
|
||||
|
||||
export const Route = createFileRoute("/_app/chats")({
|
||||
component: ChatsDemoPage,
|
||||
})
|
||||
@@ -0,0 +1,6 @@
|
||||
import { LoginPage } from "@/pages/login-page"
|
||||
import { createFileRoute } from "@tanstack/react-router"
|
||||
|
||||
export const Route = createFileRoute("/login")({
|
||||
component: LoginPage,
|
||||
})
|
||||
Reference in New Issue
Block a user