From 1f57adc4dac16e3c179a1d015a3b78bdf6d99571 Mon Sep 17 00:00:00 2001 From: Emmanuel Pelletier Date: Mon, 29 Jul 2024 18:32:56 +0200 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F(frontend)=20rework=20how=20w?= =?UTF-8?q?e=20handle=20screens=20and=20layout=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously each route rendered its whole layout from a to z. Now each route updates a single common wrapper when the layout changes between pages. Also the Loading, Error, UserFetched, QueryAware code is more explicit and understandable. This introduces valtio as dependency, allowing us to deal with global state easily in a svelte/vue way (reactive mutable state). This limits the boilerplaty-ness of immutable state lib approaches, while keeping rendering optimization better than homemade react contexts. --- src/frontend/package-lock.json | 53 ++++++++++++++++- src/frontend/package.json | 1 + src/frontend/src/App.tsx | 8 +-- src/frontend/src/components/BackToHome.tsx | 14 +++++ src/frontend/src/components/DelayedRender.ts | 24 ++++++++ src/frontend/src/components/ErrorScreen.tsx | 12 ++++ src/frontend/src/components/LoadingScreen.tsx | 27 +++++++++ .../src/components/NotFoundScreen.tsx | 12 ++++ src/frontend/src/components/QueryAware.tsx | 28 +++++++++ .../auth/components/RenderIfUserFetched.tsx | 17 ------ .../features/auth/components/UserAware.tsx | 20 +++++++ src/frontend/src/features/auth/index.ts | 2 +- .../src/features/home/routes/Home.tsx | 19 ++++--- .../features/rooms/components/Conference.tsx | 41 ++++++------- .../src/features/rooms/components/Join.tsx | 25 ++++---- .../src/features/rooms/routes/Feedback.tsx | 18 +++--- .../src/features/rooms/routes/Room.tsx | 29 +++++----- src/frontend/src/layout/Box.tsx | 32 ----------- src/frontend/src/layout/BoxScreen.tsx | 10 ---- src/frontend/src/layout/Centered.tsx | 19 +++++++ src/frontend/src/layout/CenteredContent.tsx | 29 ++++++++++ src/frontend/src/layout/ErrorScreen.tsx | 7 --- src/frontend/src/layout/ForbiddenScreen.tsx | 7 --- src/frontend/src/layout/Layout.tsx | 42 ++++++++++++++ src/frontend/src/layout/LoadingScreen.tsx | 35 ------------ src/frontend/src/layout/NotFoundScreen.tsx | 7 --- src/frontend/src/layout/QueryAware.tsx | 21 ------- src/frontend/src/layout/Screen.tsx | 57 +++++++++---------- src/frontend/src/stores/layout.ts | 9 +++ src/frontend/src/styles/livekit.css | 1 + 30 files changed, 387 insertions(+), 239 deletions(-) create mode 100644 src/frontend/src/components/BackToHome.tsx create mode 100644 src/frontend/src/components/DelayedRender.ts create mode 100644 src/frontend/src/components/ErrorScreen.tsx create mode 100644 src/frontend/src/components/LoadingScreen.tsx create mode 100644 src/frontend/src/components/NotFoundScreen.tsx create mode 100644 src/frontend/src/components/QueryAware.tsx delete mode 100644 src/frontend/src/features/auth/components/RenderIfUserFetched.tsx create mode 100644 src/frontend/src/features/auth/components/UserAware.tsx delete mode 100644 src/frontend/src/layout/Box.tsx delete mode 100644 src/frontend/src/layout/BoxScreen.tsx create mode 100644 src/frontend/src/layout/Centered.tsx create mode 100644 src/frontend/src/layout/CenteredContent.tsx delete mode 100644 src/frontend/src/layout/ErrorScreen.tsx delete mode 100644 src/frontend/src/layout/ForbiddenScreen.tsx create mode 100644 src/frontend/src/layout/Layout.tsx delete mode 100644 src/frontend/src/layout/LoadingScreen.tsx delete mode 100644 src/frontend/src/layout/NotFoundScreen.tsx delete mode 100644 src/frontend/src/layout/QueryAware.tsx create mode 100644 src/frontend/src/stores/layout.ts diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index c93507b7..fea0d97f 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -23,6 +23,7 @@ "react-aria-components": "1.2.1", "react-dom": "18.2.0", "react-i18next": "14.1.3", + "valtio": "1.13.2", "wouter": "3.3.0" }, "devDependencies": { @@ -3643,13 +3644,13 @@ "version": "15.7.12", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==", - "dev": true + "devOptional": true }, "node_modules/@types/react": { "version": "18.3.3", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", - "dev": true, + "devOptional": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -5134,7 +5135,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true + "devOptional": true }, "node_modules/damerau-levenshtein": { "version": "1.0.8", @@ -5282,6 +5283,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/derive-valtio": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/derive-valtio/-/derive-valtio-0.1.0.tgz", + "integrity": "sha512-OCg2UsLbXK7GmmpzMXhYkdO64vhJ1ROUUGaTFyHjVwEdMEcTTRj7W1TxLbSBxdY8QLBPCcp66MTyaSy0RpO17A==", + "peerDependencies": { + "valtio": "*" + } + }, "node_modules/detect-libc": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", @@ -8937,6 +8946,11 @@ "node": "10.* || >= 12.*" } }, + "node_modules/proxy-compare": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-2.6.0.tgz", + "integrity": "sha512-8xuCeM3l8yqdmbPoYeLbrAXCBWu19XEYc5/F28f5qOaoAIMyfmBUkl5axiK+x9olUvRlcekvnm98AP9RDngOIw==" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -10101,6 +10115,39 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/valtio": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/valtio/-/valtio-1.13.2.tgz", + "integrity": "sha512-Qik0o+DSy741TmkqmRfjq+0xpZBXi/Y6+fXZLn0xNF1z/waFMbE3rkivv5Zcf9RrMUp6zswf2J7sbh2KBlba5A==", + "dependencies": { + "derive-valtio": "0.1.0", + "proxy-compare": "2.6.0", + "use-sync-external-store": "1.2.0" + }, + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/valtio/node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/value-or-function": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-4.0.0.tgz", diff --git a/src/frontend/package.json b/src/frontend/package.json index 7e068a7f..8495a491 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -26,6 +26,7 @@ "react-aria-components": "1.2.1", "react-dom": "18.2.0", "react-i18next": "14.1.3", + "valtio": "1.13.2", "wouter": "3.3.0" }, "devDependencies": { diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index 9a055d34..4c61e898 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -6,8 +6,8 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { useTranslation } from 'react-i18next' import { useLang } from 'hoofd' import { Switch, Route } from 'wouter' -import { NotFoundScreen } from './layout/NotFoundScreen' -import { RenderIfUserFetched } from './features/auth' +import { Layout } from './layout/Layout' +import { NotFoundScreen } from './components/NotFoundScreen' import { routes } from './routes' import './i18n/init' @@ -19,14 +19,14 @@ function App() { return ( - + {Object.entries(routes).map(([, route], i) => ( ))} - + diff --git a/src/frontend/src/components/BackToHome.tsx b/src/frontend/src/components/BackToHome.tsx new file mode 100644 index 00000000..4c6fc287 --- /dev/null +++ b/src/frontend/src/components/BackToHome.tsx @@ -0,0 +1,14 @@ +import { Link } from '@/primitives' +import { AProps } from '@/primitives/A' +import { useTranslation } from 'react-i18next' + +export const BackToHome = ({ size }: { size?: AProps['size'] }) => { + const { t } = useTranslation() + return ( +

+ + {t('backToHome')} + +

+ ) +} diff --git a/src/frontend/src/components/DelayedRender.ts b/src/frontend/src/components/DelayedRender.ts new file mode 100644 index 00000000..e16f3bbe --- /dev/null +++ b/src/frontend/src/components/DelayedRender.ts @@ -0,0 +1,24 @@ +import { useState, useEffect, type ReactNode } from 'react' + +export const DelayedRender = ({ + children, + delay = 500, +}: { + delay?: number + children: ReactNode +}) => { + const [show, setShow] = useState(false) + useEffect(() => { + if (delay === 0) { + setShow(true) + return + } + const timeout = setTimeout(() => setShow(true), delay) + return () => clearTimeout(timeout) + }, [delay]) + if (delay !== 0 && !show) { + return null + } + + return children +} diff --git a/src/frontend/src/components/ErrorScreen.tsx b/src/frontend/src/components/ErrorScreen.tsx new file mode 100644 index 00000000..e33d89f9 --- /dev/null +++ b/src/frontend/src/components/ErrorScreen.tsx @@ -0,0 +1,12 @@ +import { CenteredContent } from '@/layout/CenteredContent' +import { Screen } from '@/layout/Screen' +import { useTranslation } from 'react-i18next' + +export const ErrorScreen = () => { + const { t } = useTranslation() + return ( + + + + ) +} diff --git a/src/frontend/src/components/LoadingScreen.tsx b/src/frontend/src/components/LoadingScreen.tsx new file mode 100644 index 00000000..ded2c21b --- /dev/null +++ b/src/frontend/src/components/LoadingScreen.tsx @@ -0,0 +1,27 @@ +import { Screen, type ScreenProps } from '@/layout/Screen' +import { DelayedRender } from './DelayedRender' +import { CenteredContent } from '@/layout/CenteredContent' +import { useTranslation } from 'react-i18next' +import { Center } from '@/styled-system/jsx' + +export const LoadingScreen = ({ + delay = 500, + header = undefined, + layout = 'centered', +}: { + delay?: number +} & Omit) => { + const { t } = useTranslation() + + return ( + + + +
+

{t('loading')}

+
+
+
+
+ ) +} diff --git a/src/frontend/src/components/NotFoundScreen.tsx b/src/frontend/src/components/NotFoundScreen.tsx new file mode 100644 index 00000000..56caf247 --- /dev/null +++ b/src/frontend/src/components/NotFoundScreen.tsx @@ -0,0 +1,12 @@ +import { CenteredContent } from '@/layout/CenteredContent' +import { Screen } from '@/layout/Screen' +import { useTranslation } from 'react-i18next' + +export const NotFoundScreen = () => { + const { t } = useTranslation() + return ( + + + + ) +} diff --git a/src/frontend/src/components/QueryAware.tsx b/src/frontend/src/components/QueryAware.tsx new file mode 100644 index 00000000..54946b56 --- /dev/null +++ b/src/frontend/src/components/QueryAware.tsx @@ -0,0 +1,28 @@ +import { ErrorScreen } from '@/components/ErrorScreen' +import { LoadingScreen } from '@/components/LoadingScreen' + +/** + * Render an error or loading Screen while a given `status` is not a success, + * otherwise directly render children. + * + * `status` matches react query statuses. + * + * Children usually contain a Screen at some point in the render tree. + */ +export const QueryAware = ({ + status, + children, +}: { + status: 'error' | 'pending' | 'success' + children: React.ReactNode +}) => { + if (status === 'error') { + return + } + + if (status === 'pending') { + return + } + + return children +} diff --git a/src/frontend/src/features/auth/components/RenderIfUserFetched.tsx b/src/frontend/src/features/auth/components/RenderIfUserFetched.tsx deleted file mode 100644 index 276a0133..00000000 --- a/src/frontend/src/features/auth/components/RenderIfUserFetched.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { type ReactNode } from 'react' -import { useUser } from '@/features/auth' -import { LoadingScreen } from '@/layout/LoadingScreen' - -/** - * wrapper that renders children only when user info has been actually fetched - * - * this is helpful to prevent flash of logged-out content for a few milliseconds when user is actually logged in - */ -export const RenderIfUserFetched = ({ children }: { children: ReactNode }) => { - const { isLoggedIn } = useUser() - return isLoggedIn !== undefined ? ( - children - ) : ( - - ) -} diff --git a/src/frontend/src/features/auth/components/UserAware.tsx b/src/frontend/src/features/auth/components/UserAware.tsx new file mode 100644 index 00000000..3a4b84cb --- /dev/null +++ b/src/frontend/src/features/auth/components/UserAware.tsx @@ -0,0 +1,20 @@ +import { useUser } from '@/features/auth' +import { LoadingScreen } from '@/components/LoadingScreen' + +/** + * Renders a loading Screen while user info has not been fetched yet, + * otherwise directly render children. + * + * Children usually contain a Screen at some point in the render tree. + * + * This is helpful to prevent flash of logged-out content for a few milliseconds when user is actually logged in + */ +export const UserAware = ({ children }: { children: React.ReactNode }) => { + const { isLoggedIn } = useUser() + + return isLoggedIn !== undefined ? ( + children + ) : ( + + ) +} diff --git a/src/frontend/src/features/auth/index.ts b/src/frontend/src/features/auth/index.ts index 0b533fea..c08b819e 100644 --- a/src/frontend/src/features/auth/index.ts +++ b/src/frontend/src/features/auth/index.ts @@ -1,4 +1,4 @@ export { useUser } from './api/useUser' export { authUrl } from './utils/authUrl' export { logoutUrl } from './utils/logoutUrl' -export { RenderIfUserFetched } from './components/RenderIfUserFetched' +export { UserAware } from './components/UserAware' diff --git a/src/frontend/src/features/home/routes/Home.tsx b/src/frontend/src/features/home/routes/Home.tsx index 32130928..69b5e234 100644 --- a/src/frontend/src/features/home/routes/Home.tsx +++ b/src/frontend/src/features/home/routes/Home.tsx @@ -1,20 +1,21 @@ import { useTranslation } from 'react-i18next' import { DialogTrigger } from 'react-aria-components' -import { Button, Div, Text, VerticallyOffCenter } from '@/primitives' +import { Button, Text } 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 { Screen } from '@/layout/Screen' +import { Centered } from '@/layout/Centered' +import { generateRoomId } from '@/features/rooms' +import { authUrl, useUser, UserAware } from '@/features/auth' import { JoinMeetingDialog } from '../components/JoinMeetingDialog' export const Home = () => { const { t } = useTranslation('home') const { isLoggedIn } = useUser() return ( - - -
+ + + {t('heading')} @@ -49,8 +50,8 @@ export const Home = () => { -
-
-
+ + + ) } diff --git a/src/frontend/src/features/rooms/components/Conference.tsx b/src/frontend/src/features/rooms/components/Conference.tsx index 0e820338..5f0dd4ae 100644 --- a/src/frontend/src/features/rooms/components/Conference.tsx +++ b/src/frontend/src/features/rooms/components/Conference.tsx @@ -8,7 +8,8 @@ import { import { Room, RoomOptions } from 'livekit-client' import { keys } from '@/api/queryKeys' import { navigateTo } from '@/navigation/navigateTo' -import { QueryAware } from '@/layout/QueryAware' +import { Screen } from '@/layout/Screen' +import { QueryAware } from '@/components/QueryAware' import { fetchRoom } from '../api/fetchRoom' import { InviteDialog } from './InviteDialog' @@ -67,24 +68,26 @@ export const Conference = ({ return ( - - - {showInviteDialog && ( - setShowInviteDialog(false)} - /> - )} - + + + + {showInviteDialog && ( + setShowInviteDialog(false)} + /> + )} + + ) } diff --git a/src/frontend/src/features/rooms/components/Join.tsx b/src/frontend/src/features/rooms/components/Join.tsx index 03da5a4e..0657ec97 100644 --- a/src/frontend/src/features/rooms/components/Join.tsx +++ b/src/frontend/src/features/rooms/components/Join.tsx @@ -1,6 +1,7 @@ import { useTranslation } from 'react-i18next' -import { Box } from '@/layout/Box' import { PreJoin, type LocalUserChoices } from '@livekit/components-react' +import { Screen } from '@/layout/Screen' +import { CenteredContent } from '@/layout/CenteredContent' export const Join = ({ onSubmit, @@ -10,15 +11,17 @@ export const Join = ({ const { t } = useTranslation('rooms') return ( - - - + + + + + ) } diff --git a/src/frontend/src/features/rooms/routes/Feedback.tsx b/src/frontend/src/features/rooms/routes/Feedback.tsx index 468c73fb..bb4fa34a 100644 --- a/src/frontend/src/features/rooms/routes/Feedback.tsx +++ b/src/frontend/src/features/rooms/routes/Feedback.tsx @@ -1,19 +1,15 @@ import { useTranslation } from 'react-i18next' -import { BoxScreen } from '@/layout/BoxScreen' -import { Div, Link, P } from '@/primitives' +import { P } from '@/primitives' +import { Screen } from '@/layout/Screen' +import { CenteredContent } from '@/layout/CenteredContent' export const FeedbackRoute = () => { const { t } = useTranslation('rooms') return ( - -
+ +

{t('feedback.body')}

-
-
-

- {t('backToHome', { ns: 'global' })} -

-
-
+ + ) } diff --git a/src/frontend/src/features/rooms/routes/Room.tsx b/src/frontend/src/features/rooms/routes/Room.tsx index 83de6cbf..a0e5e6f8 100644 --- a/src/frontend/src/features/rooms/routes/Room.tsx +++ b/src/frontend/src/features/rooms/routes/Room.tsx @@ -4,9 +4,8 @@ import { type LocalUserChoices, } from '@livekit/components-react' import { useParams } from 'wouter' -import { Screen } from '@/layout/Screen' -import { ErrorScreen } from '@/layout/ErrorScreen' -import { useUser } from '@/features/auth' +import { ErrorScreen } from '@/components/ErrorScreen' +import { useUser, UserAware } from '@/features/auth' import { Conference } from '../components/Conference' import { Join } from '../components/Join' @@ -25,21 +24,23 @@ export const Room = () => { if (!userConfig && !skipJoinScreen) { return ( - + - + ) } return ( - + + + ) } diff --git a/src/frontend/src/layout/Box.tsx b/src/frontend/src/layout/Box.tsx deleted file mode 100644 index 90fc2b44..00000000 --- a/src/frontend/src/layout/Box.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import type { ReactNode } from 'react' -import { useTranslation } from 'react-i18next' -import { Box as BoxDiv, H, Link, VerticallyOffCenter } from '@/primitives' - -export type BoxProps = { - children?: ReactNode - title?: ReactNode - withBackButton?: boolean -} - -export const Box = ({ - children, - title = '', - withBackButton = false, -}: BoxProps) => { - const { t } = useTranslation() - return ( - - - {!!title && {title}} - {children} - {!!withBackButton && ( -

- - {t('backToHome')} - -

- )} -
-
- ) -} diff --git a/src/frontend/src/layout/BoxScreen.tsx b/src/frontend/src/layout/BoxScreen.tsx deleted file mode 100644 index 147639b2..00000000 --- a/src/frontend/src/layout/BoxScreen.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { Screen } from './Screen' -import { Box, type BoxProps } from './Box' - -export const BoxScreen = (props: BoxProps) => { - return ( - - - - ) -} diff --git a/src/frontend/src/layout/Centered.tsx b/src/frontend/src/layout/Centered.tsx new file mode 100644 index 00000000..5a403152 --- /dev/null +++ b/src/frontend/src/layout/Centered.tsx @@ -0,0 +1,19 @@ +import type { ReactNode } from 'react' +import { Div, VerticallyOffCenter } from '@/primitives' +import type { SystemStyleObject } from '../styled-system/types' + +export const Centered = ({ + width = '38rem', + children, +}: { + width?: SystemStyleObject['width'] + children?: ReactNode +}) => { + return ( + +
+ {children} +
+
+ ) +} diff --git a/src/frontend/src/layout/CenteredContent.tsx b/src/frontend/src/layout/CenteredContent.tsx new file mode 100644 index 00000000..c4a3fbf8 --- /dev/null +++ b/src/frontend/src/layout/CenteredContent.tsx @@ -0,0 +1,29 @@ +import { BackToHome } from '@/components/BackToHome' +import { H } from '@/primitives' +import { Center } from '@/styled-system/jsx' + +export const CenteredContent = ({ + title, + children, + withBackButton, +}: { + title?: string + children?: React.ReactNode + withBackButton?: boolean +}) => { + return ( + <> + {!!title && ( +
+ {title} +
+ )} + {children} + {!!withBackButton && ( +
+ +
+ )} + + ) +} diff --git a/src/frontend/src/layout/ErrorScreen.tsx b/src/frontend/src/layout/ErrorScreen.tsx deleted file mode 100644 index 6f820ad0..00000000 --- a/src/frontend/src/layout/ErrorScreen.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { BoxScreen } from './BoxScreen' -import { useTranslation } from 'react-i18next' - -export const ErrorScreen = () => { - const { t } = useTranslation() - return -} diff --git a/src/frontend/src/layout/ForbiddenScreen.tsx b/src/frontend/src/layout/ForbiddenScreen.tsx deleted file mode 100644 index fefd0555..00000000 --- a/src/frontend/src/layout/ForbiddenScreen.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { BoxScreen } from './BoxScreen' -import { useTranslation } from 'react-i18next' - -export const ForbiddenScreen = () => { - const { t } = useTranslation() - return -} diff --git a/src/frontend/src/layout/Layout.tsx b/src/frontend/src/layout/Layout.tsx new file mode 100644 index 00000000..9159a0c7 --- /dev/null +++ b/src/frontend/src/layout/Layout.tsx @@ -0,0 +1,42 @@ +import { type ReactNode } from 'react' +import { css } from '@/styled-system/css' +import { Header } from './Header' +import { layoutStore } from '@/stores/layout' +import { useSnapshot } from 'valtio' + +export type Layout = 'fullpage' | 'centered' + +/** + * Layout component for the app. + * + * This component is meant to be used as a wrapper around the whole app. + * In a specific page, use the `Screen` component and change its props to change global page layout. + */ +export const Layout = ({ children }: { children: ReactNode }) => { + const layoutSnap = useSnapshot(layoutStore) + const showHeader = layoutSnap.showHeader + + return ( +
+ {showHeader &&
} +
+ {children} +
+
+ ) +} diff --git a/src/frontend/src/layout/LoadingScreen.tsx b/src/frontend/src/layout/LoadingScreen.tsx deleted file mode 100644 index 167c6205..00000000 --- a/src/frontend/src/layout/LoadingScreen.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { useState, useEffect } from 'react' -import { useTranslation } from 'react-i18next' -import { BoxScreen } from './BoxScreen' -import { Screen } from './Screen' -import { VerticallyOffCenter } from '@/primitives' -import { Center } from '@/styled-system/jsx' - -export const LoadingScreen = ({ - asBox = false, - renderTimeout = 500, -}: { - asBox?: boolean - renderTimeout?: number -}) => { - const { t } = useTranslation() - // show the loading screen only after a little while to prevent flash of texts - const [show, setShow] = useState(false) - useEffect(() => { - const timeout = setTimeout(() => setShow(true), renderTimeout) - return () => clearTimeout(timeout) - }, [renderTimeout]) - if (!show) { - return null - } - const Container = asBox ? BoxScreen : Screen - return ( - - -
-

{t('loading')}

-
-
-
- ) -} diff --git a/src/frontend/src/layout/NotFoundScreen.tsx b/src/frontend/src/layout/NotFoundScreen.tsx deleted file mode 100644 index 7d4283a0..00000000 --- a/src/frontend/src/layout/NotFoundScreen.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { useTranslation } from 'react-i18next' -import { BoxScreen } from './BoxScreen' - -export const NotFoundScreen = () => { - const { t } = useTranslation() - return -} diff --git a/src/frontend/src/layout/QueryAware.tsx b/src/frontend/src/layout/QueryAware.tsx deleted file mode 100644 index b396d697..00000000 --- a/src/frontend/src/layout/QueryAware.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { ErrorScreen } from './ErrorScreen' -import { LoadingScreen } from './LoadingScreen' -import { Screen } from './Screen' - -export const QueryAware = ({ - status, - children, -}: { - status: 'error' | 'pending' | 'success' - children: React.ReactNode -}) => { - if (status === 'error') { - return - } - - if (status === 'pending') { - return - } - - return {children} -} diff --git a/src/frontend/src/layout/Screen.tsx b/src/frontend/src/layout/Screen.tsx index d5c144dc..189dca38 100644 --- a/src/frontend/src/layout/Screen.tsx +++ b/src/frontend/src/layout/Screen.tsx @@ -1,35 +1,30 @@ -import type { ReactNode } from 'react' -import { css } from '@/styled-system/css' -import { Header } from './Header' +import { layoutStore } from '@/stores/layout' +import { Layout } from './Layout' +import { useEffect } from 'react' +import { Centered } from './Centered' + +export type ScreenProps = { + /** + * 'fullpage' by default. + */ + layout?: Layout + /** + * Show header or not. + * True by default. Pass undefined to render the screen without modifying current header visibility + */ + header?: boolean + children: React.ReactNode +} export const Screen = ({ - type, + layout = 'fullpage', + header = true, children, -}: { - type?: 'splash' - children?: ReactNode -}) => { - return ( -
- {type !== 'splash' &&
} -
- {children} -
-
- ) +}: ScreenProps) => { + useEffect(() => { + if (header !== undefined) { + layoutStore.showHeader = header + } + }, [header]) + return layout === 'centered' ? {children} : children } diff --git a/src/frontend/src/stores/layout.ts b/src/frontend/src/stores/layout.ts new file mode 100644 index 00000000..bee6baed --- /dev/null +++ b/src/frontend/src/stores/layout.ts @@ -0,0 +1,9 @@ +import { proxy } from 'valtio' + +type State = { + showHeader: boolean +} + +export const layoutStore = proxy({ + showHeader: false, +}) diff --git a/src/frontend/src/styles/livekit.css b/src/frontend/src/styles/livekit.css index 11b940b0..07164ac0 100644 --- a/src/frontend/src/styles/livekit.css +++ b/src/frontend/src/styles/livekit.css @@ -145,6 +145,7 @@ [data-lk-theme] .lk-prejoin { padding-top: 0; + width: 100%; } [data-lk-theme] .lk-participant-tile {