♻️(frontend) trying out cleaner way to handle routes
it was a bit cumbersome to navigate by paths accross the app as if one path changes a tiny bit we have to make sure we updated everything everywhere and it's kind of error-prone and cumbersome to build paths by hand. now we have a routes file that describes everything: what is the path to each route, and how to navigate to them. We use a new navigateTo helper that helps us. It could be better typed but i'm a newbie.
This commit is contained in:
@@ -5,12 +5,11 @@ import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useLang } from 'hoofd'
|
||||
import { Route, Switch } from 'wouter'
|
||||
import { HomeRoute } from '@/features/home'
|
||||
import { RoomRoute, roomRouteRegex } from '@/features/rooms'
|
||||
import { NotFound } from './routes/NotFound'
|
||||
import './i18n/init'
|
||||
import { Switch, Route } from 'wouter'
|
||||
import { NotFoundScreen } from './layout/NotFoundScreen'
|
||||
import { RenderIfUserFetched } from './features/auth'
|
||||
import { routes } from './routes'
|
||||
import './i18n/init'
|
||||
|
||||
const queryClient = new QueryClient()
|
||||
|
||||
@@ -22,9 +21,10 @@ function App() {
|
||||
<Suspense fallback={null}>
|
||||
<RenderIfUserFetched>
|
||||
<Switch>
|
||||
<Route path="/" component={HomeRoute} />
|
||||
<Route path={roomRouteRegex} component={RoomRoute} />
|
||||
<Route component={NotFound} />
|
||||
{Object.entries(routes).map(([, route], i) => (
|
||||
<Route key={i} path={route.path} component={route.Component} />
|
||||
))}
|
||||
<Route component={NotFoundScreen} />
|
||||
</Switch>
|
||||
</RenderIfUserFetched>
|
||||
<ReactQueryDevtools initialIsOpen={false} />
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Field, Ul, H, P, Form, Dialog } from '@/primitives'
|
||||
import { isRoomValid, navigateToRoom } from '@/features/rooms'
|
||||
import { navigateTo } from '@/navigation/navigateTo'
|
||||
import { isRoomValid } from '@/features/rooms'
|
||||
|
||||
export const JoinMeetingDialog = () => {
|
||||
const { t } = useTranslation('home')
|
||||
@@ -8,7 +9,7 @@ export const JoinMeetingDialog = () => {
|
||||
<Dialog title={t('joinMeeting')}>
|
||||
<Form
|
||||
onSubmit={(data) => {
|
||||
navigateToRoom((data.roomId as string).trim())
|
||||
navigateTo('room', data.roomId as string)
|
||||
}}
|
||||
submitLabel={t('joinInputSubmit')}
|
||||
>
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
export { navigateToHome } from './navigation/navigateToHome'
|
||||
export { Home as HomeRoute } from './routes/Home'
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import { navigate } from 'wouter/use-browser-location'
|
||||
|
||||
export const navigateToHome = () => {
|
||||
navigate(`/`)
|
||||
}
|
||||
@@ -2,8 +2,9 @@ import { useTranslation } from 'react-i18next'
|
||||
import { DialogTrigger } from 'react-aria-components'
|
||||
import { Button, Div, Text, VerticallyOffCenter } from '@/primitives'
|
||||
import { HStack } from '@/styled-system/jsx'
|
||||
import { navigateTo } from '@/navigation/navigateTo'
|
||||
import { generateRoomId } from '@/features/rooms'
|
||||
import { authUrl, useUser } from '@/features/auth'
|
||||
import { navigateToNewRoom } from '@/features/rooms'
|
||||
import { Screen } from '@/layout/Screen'
|
||||
import { JoinMeetingDialog } from '../components/JoinMeetingDialog'
|
||||
|
||||
@@ -28,7 +29,11 @@ export const Home = () => {
|
||||
<HStack gap="gutter">
|
||||
<Button
|
||||
variant="primary"
|
||||
onPress={isLoggedIn ? () => navigateToNewRoom() : undefined}
|
||||
onPress={
|
||||
isLoggedIn
|
||||
? () => navigateTo('room', generateRoomId())
|
||||
: undefined
|
||||
}
|
||||
href={isLoggedIn ? undefined : authUrl()}
|
||||
>
|
||||
{isLoggedIn ? t('createMeeting') : t('login', { ns: 'global' })}
|
||||
|
||||
@@ -7,8 +7,8 @@ import {
|
||||
} from '@livekit/components-react'
|
||||
import { Room, RoomOptions } from "livekit-client";
|
||||
import { keys } from '@/api/queryKeys'
|
||||
import { navigateTo } from '@/navigation/navigateTo'
|
||||
import { QueryAware } from '@/layout/QueryAware'
|
||||
import { navigateToHome } from '@/features/home'
|
||||
import { fetchRoom } from '../api/fetchRoom'
|
||||
|
||||
export const Conference = ({
|
||||
@@ -50,7 +50,7 @@ export const Conference = ({
|
||||
audio={userConfig.audioEnabled}
|
||||
video={userConfig.videoEnabled}
|
||||
onDisconnected={() => {
|
||||
navigateToHome()
|
||||
navigateTo('home')
|
||||
}}
|
||||
>
|
||||
<VideoConference />
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export { navigateToNewRoom } from './navigation/navigateToNewRoom'
|
||||
export { navigateToRoom } from './navigation/navigateToRoom'
|
||||
export { Room as RoomRoute } from './routes/Room'
|
||||
export { roomRouteRegex, isRoomValid } from './utils/isRoomValid'
|
||||
export { roomIdPattern, isRoomValid } from './utils/isRoomValid'
|
||||
export { generateRoomId } from './utils/generateRoomId'
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import { navigate } from 'wouter/use-browser-location'
|
||||
import { generateRoomId } from '../utils/generateRoomId'
|
||||
|
||||
export const navigateToNewRoom = () => {
|
||||
navigate(`/${generateRoomId()}`)
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import { navigate } from 'wouter/use-browser-location'
|
||||
|
||||
export const navigateToRoom = (roomId: string) => {
|
||||
navigate(`/${roomId}`)
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
const roomIdPattern = '[a-z]{3}-[a-z]{4}-[a-z]{3}'
|
||||
export const roomRouteRegex = new RegExp(`^[/](?<roomId>${roomIdPattern})$`)
|
||||
export const roomIdPattern = '[a-z]{3}-[a-z]{4}-[a-z]{3}'
|
||||
|
||||
export const isRoomValid = (roomId: string) =>
|
||||
new RegExp(`^${roomIdPattern}$`).test(roomId)
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import { A, Button, Popover, PopoverList, Text } from '@/primitives'
|
||||
import { SettingsButton } from '@/features/settings'
|
||||
import { authUrl, logoutUrl, useUser } from '@/features/auth'
|
||||
import { useMatchesRoute } from '@/utils/useMatchesRoute'
|
||||
import { useMatchesRoute } from '@/navigation/useMatchesRoute'
|
||||
import { Feedback } from '@/components/Feedback'
|
||||
|
||||
export const Header = () => {
|
||||
|
||||
9
src/frontend/src/navigation/getRouteByName.ts
Normal file
9
src/frontend/src/navigation/getRouteByName.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { type RouteName, routes } from '@/routes'
|
||||
|
||||
export const getRouteByName = (routeName: RouteName) => {
|
||||
const route = routes[routeName]
|
||||
if (!route) {
|
||||
throw new Error(`Route "${routeName}" does not exist`)
|
||||
}
|
||||
return route
|
||||
}
|
||||
21
src/frontend/src/navigation/navigateTo.ts
Normal file
21
src/frontend/src/navigation/navigateTo.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { RouteName } from '@/routes'
|
||||
import { navigate } from 'wouter/use-browser-location'
|
||||
import { getRouteByName } from './getRouteByName'
|
||||
|
||||
export const navigateTo = <S = unknown>(
|
||||
routeName: RouteName,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
params?: any,
|
||||
options?: { replace?: boolean; state?: S }
|
||||
) => {
|
||||
const route = getRouteByName(routeName)
|
||||
const to = route.to
|
||||
? route.to(params)
|
||||
: typeof route.path === 'string'
|
||||
? route.path
|
||||
: null
|
||||
if (!to) {
|
||||
throw new Error(`Can't find path to navigate to for ${routeName}`)
|
||||
}
|
||||
return navigate(to, options)
|
||||
}
|
||||
7
src/frontend/src/navigation/useMatchesRoute.ts
Normal file
7
src/frontend/src/navigation/useMatchesRoute.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { useRoute } from 'wouter'
|
||||
import { type RouteName, routes } from '../routes'
|
||||
|
||||
export const useMatchesRoute = (route: RouteName) => {
|
||||
const [match] = useRoute(routes[route].path)
|
||||
return match
|
||||
}
|
||||
27
src/frontend/src/routes.ts
Normal file
27
src/frontend/src/routes.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { RoomRoute, roomIdPattern } from '@/features/rooms'
|
||||
import { HomeRoute } from '@/features/home'
|
||||
|
||||
export const routes: Record<
|
||||
'home' | 'room',
|
||||
{
|
||||
name: RouteName
|
||||
path: RegExp | string
|
||||
Component: () => JSX.Element
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
to?: (...args: any[]) => string | URL
|
||||
}
|
||||
> = {
|
||||
home: {
|
||||
name: 'home',
|
||||
path: '/',
|
||||
Component: HomeRoute,
|
||||
},
|
||||
room: {
|
||||
name: 'room',
|
||||
path: new RegExp(`^[/](?<roomId>${roomIdPattern})$`),
|
||||
to: (roomId: string) => `/${roomId.trim()}`,
|
||||
Component: RoomRoute,
|
||||
},
|
||||
}
|
||||
|
||||
export type RouteName = keyof typeof routes
|
||||
@@ -1,5 +0,0 @@
|
||||
import { NotFoundScreen } from '@/layout/NotFoundScreen'
|
||||
|
||||
export const NotFound = () => {
|
||||
return <NotFoundScreen />
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import { useRoute } from 'wouter'
|
||||
import { roomRouteRegex } from '@/features/rooms'
|
||||
|
||||
type RouteName = 'home' | 'room'
|
||||
|
||||
const routeMap = {
|
||||
home: '/',
|
||||
room: roomRouteRegex,
|
||||
}
|
||||
|
||||
export const useMatchesRoute = (route: RouteName) => {
|
||||
const [match] = useRoute(routeMap[route])
|
||||
if (!(route in routeMap)) {
|
||||
throw new Error(`Route ${route} not found`)
|
||||
}
|
||||
return match
|
||||
}
|
||||
Reference in New Issue
Block a user