// file: ./app/routes/index.tsx import { redirect } from "react-router"; import type { Route } from "./+types/index"; export function meta({ }: Route.MetaArgs) { return [ { title: "New React Router App" }, ]; } export function clientLoader() { return redirect("/login"); } export default function Index() { return <>; } // file: ./app/routes/auth/layout.tsx import { Outlet } from "react-router"; export default function Layout() { return (
); } // file: ./app/routes/auth/login.tsx import { zodResolver } from "@hookform/resolvers/zod"; import { AxiosError } from "axios"; import { useForm } from "react-hook-form"; import { Link, redirect, useNavigate } from "react-router"; import { z } from "zod"; import { useShallow } from "zustand/react/shallow"; import { PasswordInput } from "~/components/custom-ui/password-input"; import { ThemeToggle } from "~/components/theme/theme-toggle"; import { Button, buttonVariants } from "~/components/ui/button"; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "~/components/ui/card"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "~/components/ui/form"; import { Input } from "~/components/ui/input"; import auth from "~/lib/api/client/auth"; import { useTokenStore } from "~/stores/token-store"; import { useUsersStore } from "~/stores/users-store"; const schema = z.object({ username: z.string(), password: z.string().min(8), }); export async function clientLoader() { const { token, setToken } = useTokenStore.getState() if (token) { try { await import("~/lib/api/client/user").then(m => m.default.me()) return redirect("/app/@me") } catch (error) { const axiosError = error as AxiosError if (axiosError.status === 401) { setToken(undefined) } } } } export default function Login() { let navigate = useNavigate() let setToken = useTokenStore(state => state.setToken) const { setCurrentUserId, addUser } = useUsersStore(useShallow(state => { return { setCurrentUserId: state.setCurrentUserId, addUser: state.addUser } })) const form = useForm>({ resolver: zodResolver(schema), }) async function onSubmit(values: z.infer) { const response = await auth.login(values) setToken(response.token) setCurrentUserId(response.user.id) addUser(response.user) navigate("/app") } return ( Welcome back! Please sign in to continue.
( Username )} /> ( Password )} />
Don't have an account? Register
); } // file: ./app/routes/auth/register.tsx import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; import { Link, useNavigate } from "react-router"; import { z } from "zod"; import { PasswordInput } from "~/components/custom-ui/password-input"; import { ThemeToggle } from "~/components/theme/theme-toggle"; import { Button, buttonVariants } from "~/components/ui/button"; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "~/components/ui/card"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "~/components/ui/form"; import { Input } from "~/components/ui/input"; import auth from "~/lib/api/client/auth"; const schema = z.object({ email: z.string().email(), displayName: z.string().min(1).optional(), username: z.string().min(3).regex(/^[a-zA-Z0-9_.]+$/), password: z.string().min(8), }); export default function Register() { let navigate = useNavigate() const form = useForm>({ resolver: zodResolver(schema), }) async function onSubmit(values: z.infer) { await auth.register(values) navigate("/login") } return ( Create an account Please fill out the form below to create an account.
( Email )} /> ( Username )} /> ( Display Name )} /> ( Password )} />
Already have an account? Log In
); } // file: ./app/routes/app/layout.tsx import { Outlet, redirect } from "react-router"; import AppLayout from "~/components/app-layout"; import { useServerListStore } from "~/stores/server-list-store"; export async function clientLoader() { const { servers, addServers } = useServerListStore.getState() try { if (!servers || Object.values(servers).length === 0) { const newServers = await import("~/lib/api/client/server").then(m => m.default.list()) addServers(newServers) } } catch (error) { return redirect("/login") } } export default function Layout() { return ( ); } // file: ./app/routes/app/index.tsx import { redirect } from "react-router"; export function clientLoader() { return redirect("/app/@me") } export default function Index() { return null; } // file: ./app/routes/app/me/index.tsx export default function Index() { return ( <> {/*
{ }} placeholder="Type your message here..." // Example of custom styling: // wrapperClassName="bg-gray-700 border-gray-600 rounded-lg" // inputClassName="text-lg" aria-label="Message input" />
*/} ); } // file: ./app/routes/app/me/channel.tsx import type { Route } from ".react-router/types/app/routes/app/me/+types/channel"; import { Check } from "lucide-react"; import { useShallow } from "zustand/react/shallow"; import ChannelArea from "~/components/channel-area"; import { Badge } from "~/components/ui/badge"; import { usePrivateChannelsStore } from "~/stores/private-channels-store"; import { useUsersStore } from "~/stores/users-store"; export default function Channel({ params }: Route.ComponentProps) { const channelId = params.channelId const currentUserId = useUsersStore(state => state.currentUserId) const nativeChannel = usePrivateChannelsStore(useShallow(state => state.channels[channelId])) const recipients = nativeChannel.recipients.filter(recipient => recipient.id !== currentUserId) const renderSystemBadge = recipients.some(recipient => recipient.system) && recipients.length === 1 const channel = { ...nativeChannel, name: <>
{recipients.map(recipient => recipient.displayName || recipient.username).join(", ")}
{renderSystemBadge && System}
} return ( <> ); } // file: ./app/routes/app/me/layout.tsx import React from "react"; import { Outlet } from "react-router"; import { useShallow } from "zustand/react/shallow"; import PrivateChannelListItem from "~/components/custom-ui/private-channel-list-item"; import { ScrollArea } from "~/components/ui/scroll-area"; import { usePrivateChannelsStore } from "~/stores/private-channels-store"; export async function clientLoader() { const { channels, addChannels: setChannels } = usePrivateChannelsStore.getState() const channelList = Object.values(channels) if (!channels || channelList.length === 0) { const channels = await import("~/lib/api/client/user").then(m => m.default.channels()) setChannels(channels) } } function ListComponent() { const channels = usePrivateChannelsStore(useShallow(state => Object.values(state.channels))) return (
Private Messages
{channels.sort((a, b) => (a.lastMessageId ?? a.id) < (b.lastMessageId ?? b.id) ? 1 : -1).map((channel, _) => ( ))}
) } export const handle = { listComponent: } export default function Layout() { return ( <> ); } // file: ./app/routes/app/server/layout.tsx import React from "react"; import { Outlet, redirect, useNavigate, useParams, type ShouldRevalidateFunctionArgs } from "react-router"; import { useShallow } from "zustand/react/shallow"; import ServerChannelListItem from "~/components/custom-ui/channel-list-item"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from "~/components/ui/dropdown-menu"; import { ScrollArea } from "~/components/ui/scroll-area"; import type { ServerId } from "~/lib/api/types"; import { useGatewayStore } from "~/stores/gateway-store"; import { ModalType, useModalStore } from "~/stores/modal-store"; import { useServerChannelsStore } from "~/stores/server-channels-store"; import { useServerListStore } from "~/stores/server-list-store"; import { useUsersStore } from "~/stores/users-store"; import type { Route } from "../server/+types/layout"; export async function clientLoader({ params: { serverId } }: Route.ClientLoaderArgs) { const { channels, addChannels, addServer } = useServerChannelsStore.getState() const server = useServerListStore.getState().servers[serverId as ServerId] || undefined if (!server) { return redirect("/app/@me") } const channelList = channels[serverId as ServerId] if (channelList === undefined) { const channels = await import("~/lib/api/client/server").then(m => m.default.listChannels(serverId as ServerId)) addServer(serverId as ServerId) addChannels(channels) useGatewayStore.getState().requestVoiceStates(serverId as ServerId) } } export function shouldRevalidate( arg: ShouldRevalidateFunctionArgs ) { return true } function ListComponent() { const serverId = useParams<{ serverId: ServerId }>().serverId! const currentUserId = useUsersStore(state => state.currentUserId) const onOpen = useModalStore(state => state.onOpen) const server = useServerListStore(useShallow(state => state.servers[serverId] || null)) const channels = Array.from(useServerChannelsStore(useShallow(state => Object.values(state.channels[serverId] || {})))) if (!server) { return null } return (
{server?.name}
onOpen(ModalType.CREATE_SERVER_INVITE, { serverId })}>Create invite onOpen(ModalType.CREATE_SERVER_CHANNEL, { serverId })}>Create channel {currentUserId === server.ownerId && onOpen(ModalType.DELETE_SERVER_CONFIRM, { serverId })}>Delete} {currentUserId !== server.ownerId && Leave}
{channels.sort((a, b) => (a.lastMessageId ?? a.id) < (b.lastMessageId ?? b.id) ? 1 : -1).map((channel, _) => ( ))}
) } export const handle = { listComponent: } export default function Layout( { params: { serverId } }: Route.ComponentProps ) { const server = useServerListStore(useShallow(state => state.servers[serverId!] || null)) const navigate = useNavigate() if (!server) { setTimeout(() => navigate("/app/@me"), 0) return null; } return ( <> ); } // file: ./app/routes/app/server/index.tsx export default function Index() { return ( <> {/*
{ }} placeholder="Type your message here..." // Example of custom styling: // wrapperClassName="bg-gray-700 border-gray-600 rounded-lg" // inputClassName="text-lg" aria-label="Message input" />
*/} ); } // file: ./app/routes/app/server/channel.tsx import { useShallow } from "zustand/react/shallow"; import ChannelArea from "~/components/channel-area"; import { useServerChannelsStore } from "~/stores/server-channels-store"; import type { Route } from "./+types/channel"; export default function Channel( { params: { serverId, channelId } }: Route.ComponentProps ) { const channel = useServerChannelsStore(useShallow(state => state.channels[serverId][channelId])) return ( <> ); } // file: ./app/routes/app/invite.tsx import { redirect } from "react-router"; import type { Route } from "./+types/invite"; export async function clientLoader( { params }: Route.ClientLoaderArgs ) { const inviteCode = params.inviteCode try { const response = await import("~/lib/api/client/server").then(m => m.default.getInvite(inviteCode)) return redirect(`/app/server/${response.id}`) } catch (error) { return redirect("/app/@me") } } export default function Index() { return null; } // file: ./app/routes/app/providers.tsx import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { Outlet, redirect } from "react-router"; import { create } from "zustand"; import { GatewayWebSocketConnectionManager } from "~/components/manager/gateway-websocket-connection-manager"; import { WebRTCConnectionManager } from "~/components/manager/webrtc-connection-manager"; import ModalProvider from "~/components/providers/modal-provider"; import { useUsersStore } from "~/stores/users-store"; export async function clientLoader() { const { currentUserId, setCurrentUserId, addUser } = useUsersStore.getState() try { if (!currentUserId) { const user = await import("~/lib/api/client/user").then(m => m.default.me()) setCurrentUserId(user.id) addUser(user) } } catch (error) { return redirect("/login") } } const useQueryClient = create(() => new QueryClient()); export default function Layout() { const queryClient = useQueryClient(); return ( <> ); } // file: ./app/root.tsx import { isRouteErrorResponse, Links, Meta, Outlet, Scripts, ScrollRestoration, } from "react-router"; import { ThemeProvider } from "~/components/theme/theme-provider"; import type { Route } from "./+types/root"; import "./app.css"; export const links: Route.LinksFunction = () => [ { rel: "preconnect", href: "https://fonts.googleapis.com" }, { rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "anonymous", }, { rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap", }, ]; export default function App() { return ( ); } export function Layout({ children }: { children: React.ReactNode }) { return ( {children} ); } export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { let message = "Oops!"; let details = "An unexpected error occurred."; let stack: string | undefined; if (isRouteErrorResponse(error)) { message = error.status === 404 ? "404" : "Error"; details = error.status === 404 ? "The requested page could not be found." : error.statusText || details; } else if (import.meta.env.DEV && error && error instanceof Error) { details = error.message; stack = error.stack; } return (

{message}

{details}

{stack && (
          {stack}
        
)}
); } // file: ./app/components/ui/button.tsx import { Slot } from "@radix-ui/react-slot" import { cva, type VariantProps } from "class-variance-authority" import * as React from "react" import { cn } from "~/lib/utils" const buttonVariants = cva( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", { variants: { variant: { default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", destructive: "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", link: "text-primary underline-offset-4 hover:underline", }, size: { default: "h-9 px-4 py-2 has-[>svg]:px-3", sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", lg: "h-10 rounded-md px-6 has-[>svg]:px-4", icon: "size-9", none: "" }, }, defaultVariants: { variant: "default", size: "default", }, } ) function Button({ className, variant, size, asChild = false, ...props }: React.ComponentProps<"button"> & VariantProps & { asChild?: boolean }) { const Comp = asChild ? Slot : "button" return ( ) } export { Button, buttonVariants } // file: ./app/components/ui/form.tsx import * as LabelPrimitive from "@radix-ui/react-label" import { Slot } from "@radix-ui/react-slot" import * as React from "react" import { Controller, FormProvider, useFormContext, useFormState, type ControllerProps, type FieldPath, type FieldValues, } from "react-hook-form" import { Label } from "~/components/ui/label" import { cn } from "~/lib/utils" const Form = FormProvider type FormFieldContextValue< TFieldValues extends FieldValues = FieldValues, TName extends FieldPath = FieldPath, > = { name: TName, } const FormFieldContext = React.createContext( {} as FormFieldContextValue ) const FormField = < TFieldValues extends FieldValues = FieldValues, TName extends FieldPath = FieldPath, >({ ...props }: ControllerProps) => { return ( ) } const useFormField = () => { const fieldContext = React.useContext(FormFieldContext) const itemContext = React.useContext(FormItemContext) const { getFieldState } = useFormContext() const formState = useFormState({ name: fieldContext.name }) const fieldState = getFieldState(fieldContext.name, formState) if (!fieldContext) { throw new Error("useFormField should be used within ") } const { id } = itemContext return { id, name: fieldContext.name, formItemId: `${id}-form-item`, formDescriptionId: `${id}-form-item-description`, formMessageId: `${id}-form-item-message`, ...fieldState, } } type FormItemContextValue = { id: string, } const FormItemContext = React.createContext( {} as FormItemContextValue ) function FormItem({ className, ...props }: React.ComponentProps<"div">) { const id = React.useId() return (
) } function FormLabel({ className, required, ...props }: React.ComponentProps & { required?: boolean }) { const { error, formItemId } = useFormField() return (
) } function FormControl({ ...props }: React.ComponentProps) { const { error, formItemId, formDescriptionId, formMessageId } = useFormField() return ( ) } function FormDescription({ className, ...props }: React.ComponentProps<"p">) { const { formDescriptionId } = useFormField() return (

) } function FormMessage({ className, ...props }: React.ComponentProps<"p">) { const { error, formMessageId } = useFormField() const body = error ? String(error?.message ?? "") : props.children if (!body) { return null } return (

{body}

) } export { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, useFormField } // file: ./app/components/ui/label.tsx "use client" import * as React from "react" import * as LabelPrimitive from "@radix-ui/react-label" import { cn } from "~/lib/utils" function Label({ className, ...props }: React.ComponentProps) { return ( ) } export { Label } // file: ./app/components/ui/input.tsx import * as React from "react" import { cn } from "~/lib/utils" function Input({ className, type, ...props }: React.ComponentProps<"input">) { return ( ) } export { Input } // file: ./app/components/ui/tabs.tsx import * as React from "react" import * as TabsPrimitive from "@radix-ui/react-tabs" import { cn } from "~/lib/utils" function Tabs({ className, ...props }: React.ComponentProps) { return ( ) } function TabsList({ className, ...props }: React.ComponentProps) { return ( ) } function TabsTrigger({ className, ...props }: React.ComponentProps) { return ( ) } function TabsContent({ className, ...props }: React.ComponentProps) { return ( ) } export { Tabs, TabsList, TabsTrigger, TabsContent } // file: ./app/components/ui/card.tsx import * as React from "react" import { cn } from "~/lib/utils" function Card({ className, ...props }: React.ComponentProps<"div">) { return (
) } function CardHeader({ className, ...props }: React.ComponentProps<"div">) { return (
) } function CardTitle({ className, ...props }: React.ComponentProps<"div">) { return (
) } function CardDescription({ className, ...props }: React.ComponentProps<"div">) { return (
) } function CardAction({ className, ...props }: React.ComponentProps<"div">) { return (
) } function CardContent({ className, ...props }: React.ComponentProps<"div">) { return (
) } function CardFooter({ className, ...props }: React.ComponentProps<"div">) { return (
) } export { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } // file: ./app/components/ui/dropdown-menu.tsx import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react" import * as React from "react" import { cn } from "~/lib/utils" function DropdownMenu({ ...props }: React.ComponentProps) { return } function DropdownMenuPortal({ ...props }: React.ComponentProps) { return ( ) } function DropdownMenuTrigger({ ...props }: React.ComponentProps) { return ( ) } function DropdownMenuContent({ className, sideOffset = 4, ...props }: React.ComponentProps) { return ( ) } function DropdownMenuGroup({ ...props }: React.ComponentProps) { return ( ) } function DropdownMenuItem({ className, inset, variant = "default", ...props }: React.ComponentProps & { inset?: boolean variant?: "default" | "destructive" }) { return ( ) } function DropdownMenuCheckboxItem({ className, children, checked, ...props }: React.ComponentProps) { return ( {children} ) } function DropdownMenuRadioGroup({ ...props }: React.ComponentProps) { return ( ) } function DropdownMenuRadioItem({ className, children, ...props }: React.ComponentProps) { return ( {children} ) } function DropdownMenuLabel({ className, inset, ...props }: React.ComponentProps & { inset?: boolean }) { return ( ) } function DropdownMenuSeparator({ className, ...props }: React.ComponentProps) { return ( ) } function DropdownMenuShortcut({ className, ...props }: React.ComponentProps<"span">) { return ( ) } function DropdownMenuSub({ ...props }: React.ComponentProps) { return } function DropdownMenuSubTrigger({ className, inset, children, ...props }: React.ComponentProps & { inset?: boolean }) { return ( {children} ) } function DropdownMenuSubContent({ className, ...props }: React.ComponentProps) { return ( ) } export { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger } // file: ./app/components/ui/separator.tsx import * as React from "react" import * as SeparatorPrimitive from "@radix-ui/react-separator" import { cn } from "~/lib/utils" function Separator({ className, orientation = "horizontal", decorative = true, ...props }: React.ComponentProps) { return ( ) } export { Separator } // file: ./app/components/ui/avatar.tsx import * as AvatarPrimitive from "@radix-ui/react-avatar" import * as React from "react" import { cn } from "~/lib/utils" function Avatar({ className, ...props }: React.ComponentProps) { return ( ) } function AvatarImage({ className, ...props }: React.ComponentProps) { return ( ) } function AvatarFallback({ className, ...props }: React.ComponentProps) { return ( ) } export { Avatar, AvatarFallback, AvatarImage } // file: ./app/components/ui/scroll-area.tsx import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" import * as React from "react" import { cn } from "~/lib/utils" function ScrollArea({ className, children, scrollbarSize, viewportRef, ...props }: React.ComponentProps & { scrollbarSize?: "default" | "narrow" | "none", viewportRef?: React.Ref }) { return ( {children} ) } function ScrollBar({ className, orientation = "vertical", size = "default", ...props }: React.ComponentProps & { size?: "default" | "narrow" | "none" }) { const classes = { vertical: { className: "h-full border-l border-l-transparent", size: { default: "w-2.5", narrow: "w-1.5", none: "hidden", }, }, horizontal: { className: "flex-col border-t border-t-transparent", size: { default: "h-2.5", narrow: "h-1.5", none: "hidden", }, }, } return ( ) } export { ScrollArea, ScrollBar } // file: ./app/components/ui/textarea.tsx import * as React from "react" import { cn } from "~/lib/utils" function Textarea({ className, ...props }: React.ComponentProps<"textarea">) { return (