diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index fa157a67..34acfae9 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -12,7 +12,6 @@ "@livekit/components-styles": "1.0.12", "@pandacss/preset-panda": "0.41.0", "@tanstack/react-query": "5.49.2", - "classnames": "2.5.1", "livekit-client": "2.3.1", "react": "18.2.0", "react-aria-components": "1.2.1", @@ -4732,11 +4731,6 @@ "node": ">= 6" } }, - "node_modules/classnames": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", - "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" - }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", diff --git a/src/frontend/package.json b/src/frontend/package.json index ee3a9775..058dab22 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -14,7 +14,6 @@ "@livekit/components-styles": "1.0.12", "@pandacss/preset-panda": "0.41.0", "@tanstack/react-query": "5.49.2", - "classnames": "2.5.1", "livekit-client": "2.3.1", "react": "18.2.0", "react-aria-components": "1.2.1", diff --git a/src/frontend/panda.config.ts b/src/frontend/panda.config.ts index dfdfffe7..e46a9dab 100644 --- a/src/frontend/panda.config.ts +++ b/src/frontend/panda.config.ts @@ -1,7 +1,35 @@ import pandaPreset from '@pandacss/preset-panda' -import { defineConfig, defineTokens } from '@pandacss/dev' +import { + Config, + Tokens, + defineConfig, + defineSemanticTokens, + defineTextStyles, + defineTokens, +} from '@pandacss/dev' -export default defineConfig({ +const spacing: Tokens['spacing'] = { + 0: { value: '0rem' }, + 0.125: { value: '0.125rem' }, + 0.25: { value: '0.25rem' }, + 0.375: { value: '0.375rem' }, + 0.5: { value: '0.5rem' }, + 0.625: { value: '0.625rem' }, + 0.75: { value: '0.75rem' }, + 1: { value: '1rem' }, + 1.25: { value: '1.25rem' }, + 1.5: { value: '1.5rem' }, + 1.75: { value: '1.75rem' }, + 2: { value: '2rem' }, + 2.25: { value: '2.25rem' }, + 2.5: { value: '2.5rem' }, + 2.75: { value: '2.75rem' }, + 3: { value: '3rem' }, + 3.5: { value: '3.5rem' }, + 4: { value: '4rem' }, +} + +const config: Config = { preflight: true, include: ['./src/**/*.{js,jsx,ts,tsx}'], exclude: [], @@ -9,61 +37,244 @@ export default defineConfig({ outdir: 'src/styled-system', conditions: { extend: { - // React Aria builds upon data attributes instead of css pseudo-classes, make sure to only work based on react aria stuff - hover: '&:is([data-hovered])', - focus: '&:is([data-focused])', - focusVisible: '&:is([data-focus-visible])', - disabled: '&:is([data-disabled])', + // React Aria builds upon data attributes instead of css pseudo-classes, in case we style a React Aria component + // we dont want to trigger pseudo class related styles + 'ra-hover': '&:is([data-hovered])', + 'ra-focus': '&:is([data-focused])', + 'ra-focusVisible': '&:is([data-focus-visible])', + 'ra-disabled': '&:is([data-disabled])', pressed: '&:is([data-pressed])', + 'ra-pressed': '&:is([data-pressed])', }, }, theme: { ...pandaPreset.theme, + // media queries are defined in em so that zooming with text-only mode triggers breakpoints + breakpoints: { + xs: '22.6em', // 360px (we assume less than that are old/entry level mobile phones) + sm: '40em', // 640px + md: '48em', // 768px + lg: '64em', // 1024px + xl: '80em', // 1280px + '2xl': '96em', // 1536px + }, tokens: defineTokens({ + /* we take a few things from the panda preset but for now we clear out some stuff. + * This way we'll only add the things we need step by step and prevent using lots of differents things. + */ ...pandaPreset.theme.tokens, + animations: {}, + blurs: {}, + /* just directly use values as tokens. This allows us to follow a specific design scale, + * without having to remember what 'sm' or '2xl' actually means. + * + * see semanticTokens for tokens targeting specific usages + */ + fonts: { + sans: { + value: [ + 'Source Sans', + 'Source Sans fallback', + 'ui-sans-serif', + 'system-ui', + '-apple-system', + 'BlinkMacSystemFont', + '"Segoe UI"', + 'Roboto', + '"Helvetica Neue"', + 'Arial', + '"Noto Sans"', + 'sans-serif', + '"Apple Color Emoji"', + '"Segoe UI Emoji"', + '"Segoe UI Symbol"', + '"Noto Color Emoji"', + ], + }, + serif: { + value: [ + 'ui-serif', + 'Georgia', + 'Cambria', + '"Times New Roman"', + 'Times', + 'serif', + ], + }, + mono: { + value: [ + 'Source Code Pro', + 'ui-monospace', + 'SFMono-Regular', + 'Menlo', + 'Monaco', + 'Consolas', + '"Liberation Mono"', + '"Courier New"', + 'monospace', + ], + }, + }, + fontSizes: { + 10: { value: '0.625rem' }, + 12: { value: '0.75rem' }, + 14: { value: '0.875rem' }, + 16: { value: '1rem' }, + 20: { value: '1.25rem' }, + 24: { value: '1.5rem' }, + 28: { value: '1.75rem' }, + 32: { value: '2rem' }, + 40: { value: '2.375rem' }, + 48: { value: '3rem' }, + 64: { value: '4rem' }, + }, + letterSpacings: {}, + shadows: { + sm: { + value: [ + '0 1px 3px 0 rgb(0 0 0 / 0.1)', + '0 1px 2px -1px rgb(0 0 0 / 0.1)', + ], + }, + }, + lineHeights: { + 1: { value: '1' }, + 1.25: { value: '1.25' }, + 1.375: { value: '1.375' }, + 1.5: { value: '1.5' }, + 1.625: { value: '1.625' }, + 2: { value: '2' }, + }, + radii: { + 6: { value: '0.375rem' }, + 8: { value: '0.5rem' }, + 16: { value: '1rem' }, + full: { value: '9999px' }, + }, + sizes: { + ...spacing, + full: { value: '100%' }, + min: { value: 'min-content' }, + max: { value: 'max-content' }, + fit: { value: 'fit-content' }, + }, + spacing, + }), + semanticTokens: defineSemanticTokens({ + colors: { + default: { + text: { value: '{colors.gray.900}' }, + bg: { value: '{colors.slate.50}' }, + subtle: { value: '{colors.gray.100}' }, + 'subtle-text': { value: '{colors.gray.600}' }, + }, + box: { + text: { value: '{colors.default.text}' }, + bg: { value: '{colors.white}' }, + border: { value: '{colors.gray.300}' }, + }, + control: { + DEFAULT: { value: '{colors.gray.100}' }, + hover: { value: '{colors.gray.200}' }, + active: { value: '{colors.gray.300}' }, + text: { value: '{colors.default.text}' }, + border: { value: '{colors.gray.300}' }, + }, + primary: { + DEFAULT: { value: '{colors.blue.700}' }, + hover: { value: '{colors.blue.800}' }, + active: { value: '{colors.blue.900}' }, + text: { value: '{colors.white}' }, + warm: { value: '{colors.blue.300}' }, + subtle: { value: '{colors.blue.100}' }, + 'subtle-text': { value: '{colors.sky.700}' }, + }, + danger: { + DEFAULT: { value: '{colors.red.600}' }, + hover: { value: '{colors.red.700}' }, + active: { value: '{colors.red.800}' }, + text: { value: '{colors.white}' }, + subtle: { value: '{colors.red.100}' }, + 'subtle-text': { value: '{colors.red.700}' }, + }, + success: { + DEFAULT: { value: '{colors.emerald.700}' }, + hover: { value: '{colors.emerald.800}' }, + active: { value: '{colors.emerald.900}' }, + text: { value: '{colors.white}' }, + subtle: { value: '{colors.emerald.100}' }, + 'subtle-text': { value: '{colors.emerald.700}' }, + }, + warning: { + DEFAULT: { value: '{colors.amber.700}' }, + hover: { value: '{colors.amber.800}' }, + active: { value: '{colors.amber.900}' }, + text: { value: '{colors.white}' }, + subtle: { value: '{colors.amber.100}' }, + 'subtle-text': { value: '{colors.amber.700}' }, + }, + }, + shadows: { + box: { value: '{shadows.sm}' }, + }, spacing: { - 0: { value: '0rem' }, - 0.125: { value: '0.125rem' }, - 0.25: { value: '0.25rem' }, - 0.375: { value: '0.375rem' }, - 0.5: { value: '0.5rem' }, - 0.75: { value: '0.75rem' }, - 1: { value: '1rem' }, - 1.25: { value: '1.25rem' }, - 1.5: { value: '1.5rem' }, - 1.75: { value: '1.75rem' }, - 2: { value: '2rem' }, - 2.25: { value: '2.25rem' }, - 2.5: { value: '2.5rem' }, - 2.75: { value: '2.75rem' }, - 3: { value: '3rem' }, - 3.5: { value: '3.5rem' }, - 4: { value: '4rem' }, - 5: { value: '5rem' }, - 6: { value: '6rem' }, - 7: { value: '7rem' }, - 8: { value: '8rem' }, - 9: { value: '9rem' }, - 10: { value: '10rem' }, - 12: { value: '12rem' }, - 14: { value: '14rem' }, - 16: { value: '16rem' }, - 20: { value: '20rem' }, - 24: { value: '24rem' }, - 28: { value: '28rem' }, - 32: { value: '32rem' }, - 36: { value: '36rem' }, - 40: { value: '40rem' }, - 44: { value: '44rem' }, - 48: { value: '48rem' }, - 52: { value: '52rem' }, - 56: { value: '56rem' }, - 60: { value: '60rem' }, - 64: { value: '64rem' }, - 72: { value: '72rem' }, - 80: { value: '80rem' }, - 96: { value: '96rem' }, + boxPadding: { + DEFAULT: { value: '{spacing.2}' }, + sm: { value: '{spacing.1}' }, + xs: { value: '{spacing.0.5}' }, + }, + boxMargin: { + xs: { value: '{spacing.0.5}' }, + DEFAULT: { value: '{spacing.1}' }, + lg: { value: '{spacing.2}' }, + }, + paragraph: { value: '{spacing.1}' }, + heading: { value: '{spacing.1}' }, + gutter: { value: '{spacing.1}' }, + }, + }), + textStyles: defineTextStyles({ + h1: { + value: { + fontSize: '1.5rem', + lineHeight: '2rem', + fontWeight: 700, + }, + }, + h2: { + value: { + fontSize: '1.25rem', + lineHeight: '1.75rem', + fontWeight: 700, + }, + }, + h3: { + value: { + fontSize: '1.125rem', + lineHeight: '1.75rem', + fontWeight: 700, + }, + }, + body: { + value: { + fontSize: '1rem', + lineHeight: '1.5', + }, + }, + small: { + value: { + fontSize: '0.875rem', + lineHeight: '1.25rem', + }, + }, + badge: { + value: { + fontSize: '0.75rem', + lineHeight: '1rem', + }, }, }), }, -}) +} + +export default defineConfig(config) diff --git a/src/frontend/public/fonts/sourcecodepro-regular-subset.woff2 b/src/frontend/public/fonts/sourcecodepro-regular-subset.woff2 new file mode 100644 index 00000000..4b1b188b Binary files /dev/null and b/src/frontend/public/fonts/sourcecodepro-regular-subset.woff2 differ diff --git a/src/frontend/public/fonts/sourcesans3-bold-subset.woff2 b/src/frontend/public/fonts/sourcesans3-bold-subset.woff2 new file mode 100644 index 00000000..199c4ace Binary files /dev/null and b/src/frontend/public/fonts/sourcesans3-bold-subset.woff2 differ diff --git a/src/frontend/public/fonts/sourcesans3-it-subset.woff2 b/src/frontend/public/fonts/sourcesans3-it-subset.woff2 new file mode 100644 index 00000000..3440a8ba Binary files /dev/null and b/src/frontend/public/fonts/sourcesans3-it-subset.woff2 differ diff --git a/src/frontend/public/fonts/sourcesans3-regular-subset.woff2 b/src/frontend/public/fonts/sourcesans3-regular-subset.woff2 new file mode 100644 index 00000000..3ef2d301 Binary files /dev/null and b/src/frontend/public/fonts/sourcesans3-regular-subset.woff2 differ diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index 63fd9e7a..6c327ad7 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -4,8 +4,8 @@ import { ReactQueryDevtools } from '@tanstack/react-query-devtools' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { Route, Switch } from 'wouter' import { Home } from './routes/Home' -import { Conference } from './routes/Conference' -import { NotFoundScreen } from './layout/NotFoundScreen' +import { NotFound } from './routes/NotFound' +import { RoomRoute } from '@/features/rooms' const queryClient = new QueryClient() @@ -14,8 +14,8 @@ function App() { - - + + diff --git a/src/frontend/src/api/apiUrl.ts b/src/frontend/src/api/apiUrl.ts index f4032197..a7d666bb 100644 --- a/src/frontend/src/api/apiUrl.ts +++ b/src/frontend/src/api/apiUrl.ts @@ -1,12 +1,11 @@ export const apiUrl = (path: string, apiVersion = '1.0') => { - const origin = - import.meta.env.VITE_API_BASE_URL - || (typeof window !== 'undefined' ? window.location.origin : ''); + import.meta.env.VITE_API_BASE_URL || + (typeof window !== 'undefined' ? window.location.origin : '') // Remove leading/trailing slashes from origin/path if it exists const sanitizedOrigin = origin.replace(/\/$/, '') - const sanitizedPath = path.replace(/^\//, ''); + const sanitizedPath = path.replace(/^\//, '') - return `${sanitizedOrigin}/api/v${apiVersion}/${sanitizedPath}`; + return `${sanitizedOrigin}/api/v${apiVersion}/${sanitizedPath}` } diff --git a/src/frontend/src/api/fetchRoom.ts b/src/frontend/src/api/fetchRoom.ts deleted file mode 100644 index 5a7c255f..00000000 --- a/src/frontend/src/api/fetchRoom.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { ApiRoom } from './ApiRoom' -import { fetchApi } from './fetchApi' - -export const fetchRoom = (roomId: string) => { - return fetchApi(`/rooms/${roomId}`) -} diff --git a/src/frontend/src/api/fetchUser.ts b/src/frontend/src/api/fetchUser.ts deleted file mode 100644 index 44bb1f9e..00000000 --- a/src/frontend/src/api/fetchUser.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { ApiUser } from './ApiUser' -import { fetchApi } from './fetchApi' - -export const fetchUser = () => { - return fetchApi('/users/me') -} diff --git a/src/frontend/src/queries/keys.ts b/src/frontend/src/api/queryKeys.ts similarity index 100% rename from src/frontend/src/queries/keys.ts rename to src/frontend/src/api/queryKeys.ts diff --git a/src/frontend/src/api/ApiUser.ts b/src/frontend/src/features/auth/api/ApiUser.ts similarity index 100% rename from src/frontend/src/api/ApiUser.ts rename to src/frontend/src/features/auth/api/ApiUser.ts diff --git a/src/frontend/src/features/auth/api/fetchUser.ts b/src/frontend/src/features/auth/api/fetchUser.ts new file mode 100644 index 00000000..c2a60c9a --- /dev/null +++ b/src/frontend/src/features/auth/api/fetchUser.ts @@ -0,0 +1,25 @@ +import { ApiError } from '@/api/ApiError' +import { fetchApi } from '@/api/fetchApi' +import { type ApiUser } from './ApiUser' + +/** + * fetch the logged in user from the api. + * + * If the user is not logged in, the api returns a 401 error. + * Here our wrapper just returns false in that case, without triggering an error: + * this is done to prevent unnecessary query retries with react query + */ +export const fetchUser = (): Promise => { + return new Promise((resolve, reject) => { + fetchApi('/users/me') + .then(resolve) + .catch((error) => { + // we assume that a 401 means the user is not logged in + if (error instanceof ApiError && error.statusCode === 401) { + resolve(false) + } else { + reject(error) + } + }) + }) +} diff --git a/src/frontend/src/features/auth/api/useUser.tsx b/src/frontend/src/features/auth/api/useUser.tsx new file mode 100644 index 00000000..6286ca12 --- /dev/null +++ b/src/frontend/src/features/auth/api/useUser.tsx @@ -0,0 +1,17 @@ +import { useQuery } from '@tanstack/react-query' +import { keys } from '@/api/queryKeys' +import { fetchUser } from './fetchUser' + +export const useUser = () => { + const query = useQuery({ + queryKey: [keys.user], + queryFn: fetchUser, + }) + + return { + ...query, + // if fetchUser returns false, it means the user is not logged in: expose that + user: query.data === false ? undefined : query.data, + isLoggedIn: query.data !== undefined && query.data !== false, + } +} diff --git a/src/frontend/src/features/auth/index.ts b/src/frontend/src/features/auth/index.ts new file mode 100644 index 00000000..4d2344cf --- /dev/null +++ b/src/frontend/src/features/auth/index.ts @@ -0,0 +1,2 @@ +export { useUser } from './api/useUser' +export { authUrl } from './utils/authUrl' diff --git a/src/frontend/src/features/auth/utils/authUrl.ts b/src/frontend/src/features/auth/utils/authUrl.ts new file mode 100644 index 00000000..111d1399 --- /dev/null +++ b/src/frontend/src/features/auth/utils/authUrl.ts @@ -0,0 +1,5 @@ +import { apiUrl } from '@/api/apiUrl' + +export const authUrl = () => { + return apiUrl('/authenticate') +} diff --git a/src/frontend/src/api/ApiRoom.ts b/src/frontend/src/features/rooms/api/ApiRoom.ts similarity index 100% rename from src/frontend/src/api/ApiRoom.ts rename to src/frontend/src/features/rooms/api/ApiRoom.ts diff --git a/src/frontend/src/features/rooms/api/fetchRoom.ts b/src/frontend/src/features/rooms/api/fetchRoom.ts new file mode 100644 index 00000000..cd2f9c0d --- /dev/null +++ b/src/frontend/src/features/rooms/api/fetchRoom.ts @@ -0,0 +1,20 @@ +import { ApiError } from '@/api/ApiError' +import { type ApiRoom } from './ApiRoom' +import { fetchApi } from '@/api/fetchApi' + +export const fetchRoom = ({ + roomId, + username = '', +}: { + roomId: string + username?: string +}) => { + return fetchApi( + `/rooms/${roomId}?username=${encodeURIComponent(username)}` + ).then((room) => { + if (!room.livekit?.token || !room.livekit?.url) { + throw new ApiError(500, 'LiveKit info not found') + } + return room + }) +} diff --git a/src/frontend/src/features/rooms/components/Conference.tsx b/src/frontend/src/features/rooms/components/Conference.tsx new file mode 100644 index 00000000..18c6e963 --- /dev/null +++ b/src/frontend/src/features/rooms/components/Conference.tsx @@ -0,0 +1,48 @@ +import { useParams } from 'wouter' +import { useQuery } from '@tanstack/react-query' +import { + LiveKitRoom, + VideoConference, + type LocalUserChoices, +} from '@livekit/components-react' +import { keys } from '@/api/queryKeys' +import { QueryAware } from '@/layout/QueryAware' +import { navigateToHome } from '@/navigation/navigateToHome' +import { fetchRoom } from '../api/fetchRoom' + +export const Conference = ({ + userConfig, +}: { + userConfig: LocalUserChoices +}) => { + const { roomId } = useParams() + const { status, data } = useQuery({ + queryKey: [keys.room, roomId, userConfig.username], + queryFn: () => + fetchRoom({ + roomId: roomId as string, + username: userConfig.username, + }), + }) + + return ( + + { + navigateToHome() + }} + > + + + + ) +} diff --git a/src/frontend/src/features/rooms/components/Join.tsx b/src/frontend/src/features/rooms/components/Join.tsx new file mode 100644 index 00000000..3ef5d411 --- /dev/null +++ b/src/frontend/src/features/rooms/components/Join.tsx @@ -0,0 +1,17 @@ +import { Box } from '@/layout/Box' +import { PreJoin, type LocalUserChoices } from '@livekit/components-react' + +export const Join = ({ + onSubmit, +}: { + onSubmit: (choices: LocalUserChoices) => void +}) => { + return ( + + + + ) +} diff --git a/src/frontend/src/features/rooms/index.ts b/src/frontend/src/features/rooms/index.ts new file mode 100644 index 00000000..dcfe38ef --- /dev/null +++ b/src/frontend/src/features/rooms/index.ts @@ -0,0 +1,2 @@ +export { navigateToNewRoom } from './navigation/navigateToNewRoom' +export { Room as RoomRoute } from './routes/Room' diff --git a/src/frontend/src/features/rooms/navigation/navigateToNewRoom.ts b/src/frontend/src/features/rooms/navigation/navigateToNewRoom.ts new file mode 100644 index 00000000..4d20b364 --- /dev/null +++ b/src/frontend/src/features/rooms/navigation/navigateToNewRoom.ts @@ -0,0 +1,6 @@ +import { navigate } from 'wouter/use-browser-location' +import { generateRoomId } from '../utils/generateRoomId' + +export const navigateToNewRoom = () => { + navigate(`/${generateRoomId()}`) +} diff --git a/src/frontend/src/features/rooms/routes/Room.tsx b/src/frontend/src/features/rooms/routes/Room.tsx new file mode 100644 index 00000000..c2552f40 --- /dev/null +++ b/src/frontend/src/features/rooms/routes/Room.tsx @@ -0,0 +1,18 @@ +import { type LocalUserChoices } from '@livekit/components-react' +import { useState } from 'react' +import { Conference } from '../components/Conference' +import { Join } from '../components/Join' +import { Screen } from '@/layout/Screen' + +export const Room = () => { + const [userConfig, setUserConfig] = useState(null) + return ( + + {userConfig ? ( + + ) : ( + + )} + + ) +} diff --git a/src/frontend/src/features/rooms/utils/generateRoomId.ts b/src/frontend/src/features/rooms/utils/generateRoomId.ts new file mode 100644 index 00000000..f8be43bb --- /dev/null +++ b/src/frontend/src/features/rooms/utils/generateRoomId.ts @@ -0,0 +1,5 @@ +import { slugify } from '@/utils/slugify' + +export const generateRoomId = () => { + return slugify(crypto.randomUUID()) +} diff --git a/src/frontend/src/layout/Box.tsx b/src/frontend/src/layout/Box.tsx new file mode 100644 index 00000000..23fe93b9 --- /dev/null +++ b/src/frontend/src/layout/Box.tsx @@ -0,0 +1,28 @@ +import type { ReactNode } from 'react' +import { Box as BoxDiv, H, Link } from '@/primitives' + +export type BoxProps = { + children?: ReactNode + title?: ReactNode + withBackButton?: boolean +} + +export const Box = ({ + children, + title = '', + withBackButton = false, +}: BoxProps) => { + return ( + + {!!title && {title}} + {children} + {!!withBackButton && ( +

+ + Back to homescreen + +

+ )} +
+ ) +} diff --git a/src/frontend/src/layout/BoxScreen.tsx b/src/frontend/src/layout/BoxScreen.tsx index 6919359b..147639b2 100644 --- a/src/frontend/src/layout/BoxScreen.tsx +++ b/src/frontend/src/layout/BoxScreen.tsx @@ -1,30 +1,10 @@ -import type { ReactNode } from 'react' -import classNames from 'classnames' -import { css } from '@/styled-system/css' import { Screen } from './Screen' +import { Box, type BoxProps } from './Box' -export const BoxScreen = ({ children }: { children: ReactNode }) => { +export const BoxScreen = (props: BoxProps) => { return ( -
- {children} -
+
) } diff --git a/src/frontend/src/layout/ErrorScreen.tsx b/src/frontend/src/layout/ErrorScreen.tsx index 23551c96..31d5241f 100644 --- a/src/frontend/src/layout/ErrorScreen.tsx +++ b/src/frontend/src/layout/ErrorScreen.tsx @@ -1,17 +1,7 @@ -import { Link } from 'wouter' -import { A } from '@/primitives/A' -import { H1 } from '@/primitives/H' import { BoxScreen } from './BoxScreen' export const ErrorScreen = () => { return ( - -

An error occured while loading the page

-

- - Back to homescreen - -

-
+ ) } diff --git a/src/frontend/src/layout/ForbiddenScreen.tsx b/src/frontend/src/layout/ForbiddenScreen.tsx index 6e1e44c5..c74adfd2 100644 --- a/src/frontend/src/layout/ForbiddenScreen.tsx +++ b/src/frontend/src/layout/ForbiddenScreen.tsx @@ -1,17 +1,10 @@ -import { Link } from 'wouter' -import { A } from '@/primitives/A' -import { H1 } from '@/primitives/H' import { BoxScreen } from './BoxScreen' export const ForbiddenScreen = () => { return ( - -

You don't have the permission to view this page

-

- - Back to homescreen - -

-
+ ) } diff --git a/src/frontend/src/layout/Header.tsx b/src/frontend/src/layout/Header.tsx index ba0f94fb..338aba67 100644 --- a/src/frontend/src/layout/Header.tsx +++ b/src/frontend/src/layout/Header.tsx @@ -1,19 +1,22 @@ -import { apiUrl } from '@/api/apiUrl' -import { A } from '@/primitives/A' -import { useUser } from '@/queries/useUser' import { css } from '@/styled-system/css' import { flex } from '@/styled-system/patterns' +import { apiUrl } from '@/api/apiUrl' +import { A, Badge, Text } from '@/primitives' +import { useUser } from '@/features/auth/api/useUser' export const Header = () => { const { user, isLoggedIn } = useUser() return (
{ })} >
-

+ Meet -

+
{isLoggedIn === false && Login} {!!user && ( -

- {user.email}  Logout +

+ {user.email} + + Logout +

)}
diff --git a/src/frontend/src/layout/NotFoundScreen.tsx b/src/frontend/src/layout/NotFoundScreen.tsx index 87dba1c1..397a51a0 100644 --- a/src/frontend/src/layout/NotFoundScreen.tsx +++ b/src/frontend/src/layout/NotFoundScreen.tsx @@ -1,17 +1,5 @@ -import { Link } from 'wouter' -import { A } from '@/primitives/A' -import { H1 } from '@/primitives/H' import { BoxScreen } from './BoxScreen' export const NotFoundScreen = () => { - return ( - -

Page not found

-

- - Back to homescreen - -

-
- ) + return } diff --git a/src/frontend/src/layout/QueryAware.tsx b/src/frontend/src/layout/QueryAware.tsx new file mode 100644 index 00000000..466d6a7c --- /dev/null +++ b/src/frontend/src/layout/QueryAware.tsx @@ -0,0 +1,20 @@ +import { ErrorScreen } from './ErrorScreen' +import { LoadingScreen } from './LoadingScreen' + +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 211ad3d7..f9bc4d0f 100644 --- a/src/frontend/src/layout/Screen.tsx +++ b/src/frontend/src/layout/Screen.tsx @@ -1,6 +1,6 @@ import type { ReactNode } from 'react' import { css } from '@/styled-system/css' -import { Header } from '@/layout/Header' +import { Header } from './Header' export const Screen = ({ children }: { children: ReactNode }) => { return ( @@ -9,7 +9,8 @@ export const Screen = ({ children }: { children: ReactNode }) => { height: '100%', display: 'flex', flexDirection: 'column', - backgroundColor: 'slate.50', + backgroundColor: 'default.bg', + color: 'default.text', })} >
diff --git a/src/frontend/src/navigation/navigateToHome.ts b/src/frontend/src/navigation/navigateToHome.ts new file mode 100644 index 00000000..42f84d16 --- /dev/null +++ b/src/frontend/src/navigation/navigateToHome.ts @@ -0,0 +1,5 @@ +import { navigate } from 'wouter/use-browser-location' + +export const navigateToHome = () => { + navigate(`/`) +} diff --git a/src/frontend/src/primitives/A.tsx b/src/frontend/src/primitives/A.tsx index a3105d8e..3dc9e69b 100644 --- a/src/frontend/src/primitives/A.tsx +++ b/src/frontend/src/primitives/A.tsx @@ -1,8 +1,5 @@ -import { RecipeVariantProps, cva } from '@/styled-system/css' -import { - Link as Link, - type LinkProps as LinksProps, -} from 'react-aria-components' +import { Link, type LinkProps } from 'react-aria-components' +import { cva, type RecipeVariantProps } from '@/styled-system/css' const link = cva({ base: { @@ -10,25 +7,27 @@ const link = cva({ textUnderlineOffset: '2', transition: 'all 200ms', cursor: 'pointer', - _hover: { + '_ra-hover': { textDecoration: 'none', }, - _pressed: { + '_ra-pressed': { textDecoration: 'underline', }, }, variants: { size: { small: { - fontSize: 'sm', + textStyle: 'small', }, }, }, }) -export const A = ({ - size, - ...props -}: LinksProps & RecipeVariantProps) => { +export type AProps = LinkProps & RecipeVariantProps + +/** + * anchor component styled with underline + */ +export const A = ({ size, ...props }: AProps) => { return } diff --git a/src/frontend/src/primitives/Badge.tsx b/src/frontend/src/primitives/Badge.tsx new file mode 100644 index 00000000..08ac9b06 --- /dev/null +++ b/src/frontend/src/primitives/Badge.tsx @@ -0,0 +1,29 @@ +import { cva, type RecipeVariantProps } from '@/styled-system/css' + +const badge = cva({ + base: { + display: 'inline-block', + padding: '0.25rem 0.5rem', + backgroundColor: 'primary.subtle', + color: 'primary.subtle-text', + borderRadius: '6', + }, + variants: { + size: { + small: { + textStyle: 'badge', + }, + normal: {}, + }, + }, + defaultVariants: { + size: 'normal', + }, +}) + +export type BadgeProps = React.HTMLAttributes & + RecipeVariantProps + +export const Badge = ({ size, ...props }: BadgeProps) => { + return +} diff --git a/src/frontend/src/primitives/Bold.tsx b/src/frontend/src/primitives/Bold.tsx index 50be31b6..9eeb11c6 100644 --- a/src/frontend/src/primitives/Bold.tsx +++ b/src/frontend/src/primitives/Bold.tsx @@ -1,9 +1,5 @@ -import { css } from '@/styled-system/css' +import { Text, type As } from './Text' -const bold = css({ - fontWeight: 'bold', -}) - -export const Bold = (props: React.HTMLAttributes) => { - return +export const Bold = (props: React.HTMLAttributes & As) => { + return } diff --git a/src/frontend/src/primitives/Box.tsx b/src/frontend/src/primitives/Box.tsx new file mode 100644 index 00000000..a59486b0 --- /dev/null +++ b/src/frontend/src/primitives/Box.tsx @@ -0,0 +1,51 @@ +import { cva } from '@/styled-system/css' +import { styled } from '../styled-system/jsx' + +const box = cva({ + base: { + gap: 'gutter', + borderRadius: 8, + padding: 'boxPadding', + flex: 1, + }, + variants: { + asScreen: { + true: { + margin: 'auto', + width: '38rem', + maxWidth: '100%', + marginTop: '6rem', + textAlign: 'center', + }, + }, + variant: { + default: { + borderWidth: '1px', + borderStyle: 'solid', + borderColor: 'box.border', + backgroundColor: 'box.bg', + color: 'box.text', + boxShadow: 'box', + }, + subtle: { + color: 'default.subtle-text', + backgroundColor: 'default.subtle', + }, + }, + size: { + default: { + padding: 'boxPadding', + }, + sm: { + padding: 'boxPadding.sm', + }, + }, + }, + defaultVariants: { + asScreen: false, + variant: 'default', + size: 'default', + }, +}) + +export const Box = styled('div', box) diff --git a/src/frontend/src/primitives/Button.tsx b/src/frontend/src/primitives/Button.tsx index 7f37079b..4e47470c 100644 --- a/src/frontend/src/primitives/Button.tsx +++ b/src/frontend/src/primitives/Button.tsx @@ -1,8 +1,58 @@ import { Button as RACButton, type ButtonProps as RACButtonsProps, + Link, + LinkProps, } from 'react-aria-components' +import { cva, type RecipeVariantProps } from '@/styled-system/css' -export const Button = (props: RACButtonsProps) => { - return +const button = cva({ + base: { + display: 'inline-block', + paddingX: '1', + paddingY: '0.625', + transition: 'all 200ms', + borderRadius: 8, + cursor: 'pointer', + }, + variants: { + variant: { + default: { + color: 'control.text', + backgroundColor: 'control', + '_ra-hover': { + backgroundColor: 'control.hover', + }, + '_ra-pressed': { + backgroundColor: 'control.active', + }, + }, + primary: { + color: 'primary.text', + backgroundColor: 'primary', + '_ra-hover': { + backgroundColor: 'primary.hover', + }, + '_ra-pressed': { + backgroundColor: 'primary.active', + }, + }, + }, + }, +}) + +type ButtonProps = RecipeVariantProps & + (RACButtonsProps | LinkProps) + +export const Button = (props: ButtonProps) => { + const [variantProps, componentProps] = button.splitVariantProps(props) + if ((props as LinkProps).href !== undefined) { + return + } + return ( + + ) } diff --git a/src/frontend/src/primitives/Div.tsx b/src/frontend/src/primitives/Div.tsx new file mode 100644 index 00000000..47e3d574 --- /dev/null +++ b/src/frontend/src/primitives/Div.tsx @@ -0,0 +1,3 @@ +import { Box } from '@/styled-system/jsx' + +export const Div = Box diff --git a/src/frontend/src/primitives/H.tsx b/src/frontend/src/primitives/H.tsx index b9b1b5eb..99b1e122 100644 --- a/src/frontend/src/primitives/H.tsx +++ b/src/frontend/src/primitives/H.tsx @@ -1,24 +1,14 @@ -import type { HTMLAttributes } from 'react' -import classNames from 'classnames' -import { css } from '@/styled-system/css' +import { Text } from './Text' -export const H1 = ({ +export const H = ({ children, - className, + lvl, ...props -}: HTMLAttributes) => { +}: React.HTMLAttributes & { lvl: 1 | 2 | 3 }) => { + const tag = `h${lvl}` as const return ( -

+ {children} -

+
) } diff --git a/src/frontend/src/primitives/Hr.tsx b/src/frontend/src/primitives/Hr.tsx index 43ddf348..2faee93c 100644 --- a/src/frontend/src/primitives/Hr.tsx +++ b/src/frontend/src/primitives/Hr.tsx @@ -1,10 +1,13 @@ import { css } from '@/styled-system/css' -const hr = css({ - marginY: '1', - borderColor: 'neutral.300', -}) - export const Hr = (props: React.HTMLAttributes) => { - return
+ return ( +
+ ) } diff --git a/src/frontend/src/primitives/Italic.tsx b/src/frontend/src/primitives/Italic.tsx new file mode 100644 index 00000000..544b6a36 --- /dev/null +++ b/src/frontend/src/primitives/Italic.tsx @@ -0,0 +1,5 @@ +import { Text, type As } from './Text' + +export const Italic = (props: React.HTMLAttributes & As) => { + return +} diff --git a/src/frontend/src/primitives/Link.tsx b/src/frontend/src/primitives/Link.tsx new file mode 100644 index 00000000..1b834a5d --- /dev/null +++ b/src/frontend/src/primitives/Link.tsx @@ -0,0 +1,18 @@ +import { Link as WouterLink } from 'wouter' +import { A, type AProps } from './A' + +/** + * Wouter link wrapper to use our A primitive + */ +export const Link = ({ + to, + ...props +}: { + to: string +} & AProps) => { + return ( + + + + ) +} diff --git a/src/frontend/src/primitives/P.tsx b/src/frontend/src/primitives/P.tsx index f7701460..c613d357 100644 --- a/src/frontend/src/primitives/P.tsx +++ b/src/frontend/src/primitives/P.tsx @@ -1,23 +1,5 @@ -import type { HTMLAttributes } from 'react' -import classNames from 'classnames' -import { css } from '@/styled-system/css' +import { Text, type As } from './Text' -export const P = ({ - children, - className, - ...props -}: HTMLAttributes) => { - return ( -

- {children} -

- ) +export const P = (props: React.HTMLAttributes & As) => { + return } diff --git a/src/frontend/src/primitives/Text.tsx b/src/frontend/src/primitives/Text.tsx new file mode 100644 index 00000000..4fc1dcbe --- /dev/null +++ b/src/frontend/src/primitives/Text.tsx @@ -0,0 +1,78 @@ +import type { HTMLAttributes } from 'react' +import { RecipeVariantProps, cva, cx } from '@/styled-system/css' + +const text = cva({ + base: {}, + variants: { + variant: { + h1: { + textStyle: 'h1', + marginBottom: 'heading', + }, + h2: { + textStyle: 'h2', + marginBottom: 'heading', + }, + h3: { + textStyle: 'h3', + marginBottom: 'heading', + }, + body: { + textStyle: 'body', + }, + paragraph: { + textStyle: 'body', + marginBottom: 'paragraph', + }, + small: { + textStyle: 'small', + }, + inherits: {}, + }, + bold: { + true: { + fontWeight: 'bold', + }, + false: { + fontWeight: 'normal', + }, + }, + italic: { + true: { + fontStyle: 'italic', + }, + }, + margin: { + false: { + margin: '0!', + }, + }, + }, + defaultVariants: { + variant: 'inherits', + }, +}) + +type TextHTMLProps = HTMLAttributes +type TextElement = + | 'h1' + | 'h2' + | 'h3' + | 'h4' + | 'h5' + | 'h6' + | 'p' + | 'span' + | 'strong' + | 'em' + | 'div' +export type As = { as?: TextElement } +export type TextProps = RecipeVariantProps & TextHTMLProps & As + +export function Text(props: TextProps) { + const [variantProps, componentProps] = text.splitVariantProps(props) + const { as: Component = 'p', className, ...tagProps } = componentProps + return ( + + ) +} diff --git a/src/frontend/src/primitives/index.ts b/src/frontend/src/primitives/index.ts new file mode 100644 index 00000000..a13a55e1 --- /dev/null +++ b/src/frontend/src/primitives/index.ts @@ -0,0 +1,12 @@ +export { A } from './A' +export { Badge } from './Badge' +export { Bold } from './Bold' +export { Box } from './Box' +export { Button } from './Button' +export { Div } from './Div' +export { H } from './H' +export { Hr } from './Hr' +export { Italic } from './Italic' +export { Link } from './Link' +export { P } from './P' +export { Text } from './Text' diff --git a/src/frontend/src/queries/useUser.tsx b/src/frontend/src/queries/useUser.tsx deleted file mode 100644 index 8262e06e..00000000 --- a/src/frontend/src/queries/useUser.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { useQuery } from '@tanstack/react-query' -import { keys } from './keys' -import { fetchUser } from '@/api/fetchUser' - -export const useUser = () => { - const query = useQuery({ - queryKey: [keys.user], - queryFn: fetchUser, - refetchOnMount: false, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - retryOnMount: false, - retry: (_, error) => { - return error.statusCode !== 401 - }, - }) - - let isLoggedIn - if (query.status === 'success' && query.data?.email !== null) { - isLoggedIn = true - } - if (query.status === 'error' && query.failureReason?.statusCode === 401) { - isLoggedIn = false - } - return { - ...query, - user: query.data, - isLoggedIn, - } -} diff --git a/src/frontend/src/routes/Conference.tsx b/src/frontend/src/routes/Conference.tsx deleted file mode 100644 index ab45cb8a..00000000 --- a/src/frontend/src/routes/Conference.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { fetchRoom } from '@/api/fetchRoom' -import { BoxScreen } from '@/layout/BoxScreen' -import { ErrorScreen } from '@/layout/ErrorScreen' -import { ForbiddenScreen } from '@/layout/ForbiddenScreen' -import { LoadingScreen } from '@/layout/LoadingScreen' -import { Screen } from '@/layout/Screen' -import { A } from '@/primitives/A' -import { H1 } from '@/primitives/H' -import { keys } from '@/queries/keys' -import { - LiveKitRoom, - VideoConference, - PreJoin, - LocalUserChoices, -} from '@livekit/components-react' -import { useQuery } from '@tanstack/react-query' -import { useState } from 'react' -import { Link, useLocation, useParams } from 'wouter' - -export const Conference = () => { - const { roomId } = useParams() - const [, navigate] = useLocation() - const { status, data } = useQuery({ - queryKey: [keys.room, roomId], - queryFn: ({ queryKey }) => fetchRoom(queryKey[1] as string), - }) - - const [userConfig, setUserConfig] = useState(null) - - if (!userConfig) { - return ( - -

Verify your settings before joining

- { - setUserConfig(choices) - }} - /> -

- - Back to homescreen - -

- - ) - } - - if (status === 'error' || (status === 'success' && !data?.livekit)) { - return - } - - if (data?.is_public === false) { - return - } - - if (data?.livekit?.token && data?.livekit?.url) { - return ( - - { - navigate('/') - }} - > - - - - ) - } - - return -} diff --git a/src/frontend/src/routes/Home.tsx b/src/frontend/src/routes/Home.tsx index 38f036c8..4118235a 100644 --- a/src/frontend/src/routes/Home.tsx +++ b/src/frontend/src/routes/Home.tsx @@ -1,49 +1,41 @@ -import { useLocation } from 'wouter' -import { useUser } from '@/queries/useUser' -import { Button } from '@/primitives/Button' -import { A } from '@/primitives/A' -import { Bold } from '@/primitives/Bold' -import { H1 } from '@/primitives/H' -import { P } from '@/primitives/P' -import { Hr } from '@/primitives/Hr' +import { A, Button, Italic, P, Div, H, Box } from '@/primitives' +import { useUser } from '@/features/auth' import { apiUrl } from '@/api/apiUrl' -import { createRandomRoom } from '@/utils/createRandomRoom' -import { LoadingScreen } from '@/layout/LoadingScreen' -import { BoxScreen } from '@/layout/BoxScreen' +import { navigateToNewRoom } from '@/features/rooms' +import { Screen } from '@/layout/Screen' export const Home = () => { - const { status, isLoggedIn } = useUser() - const [, navigate] = useLocation() - - if (status === 'pending') { - return - } - + const { isLoggedIn } = useUser() return ( - -

- Welcome in Meet! -

- {isLoggedIn ? ( - - ) : ( - <> -

What do you want to do? You can either:

- -

- - Login to create a conference call - -

- -

- Or copy a URL in your - browser address bar to join an existing conference call -

- - )} -
+ + + Welcome in Meet +

What do you want to do? You can either:

+
+ + {isLoggedIn ? ( + + ) : ( +

+ + Login to create a conference call + +

+ )} +
+
+

+ Or +

+ +

+ copy a meeting URL in your browser address bar to join an existing + conference call +

+
+
+
) } diff --git a/src/frontend/src/routes/NotFound.tsx b/src/frontend/src/routes/NotFound.tsx index 5054f24c..0dedc9ca 100644 --- a/src/frontend/src/routes/NotFound.tsx +++ b/src/frontend/src/routes/NotFound.tsx @@ -1,8 +1,5 @@ +import { NotFoundScreen } from '@/layout/NotFoundScreen' + export const NotFound = () => { - return ( -
-

404

-

Page not found

-
- ) + return } diff --git a/src/frontend/src/styles/fonts.css b/src/frontend/src/styles/fonts.css new file mode 100644 index 00000000..5af0abc7 --- /dev/null +++ b/src/frontend/src/styles/fonts.css @@ -0,0 +1,43 @@ +@font-face { + font-family: 'Source Sans'; + src: url('/fonts/sourcesans3-regular-subset.woff2') format('woff2'); + font-weight: 400; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Source Sans'; + src: url('/fonts/sourcesans3-it-subset.woff2') format('woff2'); + font-weight: 400; + font-style: italic; + font-display: swap; +} + +@font-face { + font-family: 'Source Sans'; + src: url('/fonts/sourcesans3-bold-subset.woff2') format('woff2'); + font-weight: 700; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Source Code Pro'; + src: url('/fonts/sourcecodepro-regular-subset.woff2') format('woff2'); + font-weight: 400; + font-style: normal; + font-display: swap; +} + +/* +* to reduce CLS +* values taken from https://github.com/khempenius/font-fallbacks-dataset/blob/main/font-metric-overrides.csv#L2979 +*/ +@font-face { + font-family: 'Source Sans fallback'; + src: local('Arial'); + ascent-override: 98.4%; + descent-override: 27.3%; + line-gap-override: 0%; +} diff --git a/src/frontend/src/styles/index.css b/src/frontend/src/styles/index.css index 45024c16..630c8f8a 100644 --- a/src/frontend/src/styles/index.css +++ b/src/frontend/src/styles/index.css @@ -1,3 +1,4 @@ +@import './fonts.css'; @import './livekit.css'; @layer reset, base, tokens, recipes, utilities; html, diff --git a/src/frontend/src/styles/livekit.css b/src/frontend/src/styles/livekit.css index ed66de4c..11b940b0 100644 --- a/src/frontend/src/styles/livekit.css +++ b/src/frontend/src/styles/livekit.css @@ -1,114 +1,57 @@ /* based on https://github.com/livekit/components-js/blob/main/packages/styles/scss/themes/default.scss -for now only "visio-light" is actually used */ -[data-lk-theme='visio-dark'] { - color-scheme: dark; - --lk-bg: #111; - --lk-bg2: #1e1e1e; - --lk-bg3: #2b2b2b; - --lk-bg4: #373737; - --lk-bg5: #444444; - --lk-fg: #fff; - --lk-fg2: whitesmoke; - --lk-fg3: #ebebeb; - --lk-fg4: #e0e0e0; - --lk-fg5: #d6d6d6; - --lk-border-color: rgba(255, 255, 255, 0.1); - --lk-accent-fg: #fff; - --lk-accent-bg: #1f8cf9; - --lk-accent2: #3396fa; - --lk-accent3: #47a0fa; - --lk-accent4: #5babfb; - --lk-danger-fg: #fff; - --lk-danger: #842029; - --lk-danger2: #b02a37; - --lk-danger3: #dc3545; - --lk-danger4: #e35d6a; - --lk-success-fg: #fff; - --lk-success: #146c43; - --lk-success2: #198754; - --lk-success3: #479f76; - --lk-success4: #75b798; - --lk-control-fg: var(--lk-fg); - --lk-control-bg: var(--lk-bg2); - --lk-control-hover-bg: var(--lk-bg3); - --lk-control-active-bg: var(--lk-bg4); - --lk-control-active-hover-bg: var(--lk-bg5); - --lk-connection-excellent: #198754; - --lk-connection-good: #fd7e14; - --lk-connection-poor: #dc3545; - --lk-font-family: system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, - Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'; - --lk-font-size: 16px; - --lk-line-height: 1.5; - --lk-border-radius: 0.5rem; - --lk-box-shadow: 0 0.5rem 1.5rem rgba(0, 0, 0, 0.15); - --lk-grid-gap: 0.5rem; - --lk-control-bar-height: 69px; - --lk-chat-header-height: 69px; - - --lk-bg6: #777; - --lk-control-border-width: 1px; - --lk-control-border-color: var(--lk-bg5); - --lk-control-border: var(--lk-control-border-color); - --lk-control-hover-border: var(--lk-bg6); -} - [data-lk-theme='visio-light'] { color-scheme: light; - --lk-bg: #fff; - --lk-bg2: #f3f4f6; - --lk-bg3: #e5e7eb; - --lk-bg4: #d1d5db; - --lk-bg5: #9ca3af; - --lk-fg: #111; - --lk-fg2: #18181b; - --lk-fg3: #27272a; - --lk-fg4: #3f3f46; + --lk-bg: var(--colors-white); + --lk-room-bg: var(--colors-default-bg); + --lk-bg2: var(--colors-gray-100); + --lk-bg3: var(--colors-gray-200); + --lk-bg4: var(--colors-gray-300); + --lk-bg5: var(--colors-gray-400); + --lk-fg: var(--colors-text); --lk-fg5: #52525b; - --lk-border-color: rgba(255, 255, 255, 0.1); - --lk-accent-fg: #fff; - --lk-accent-bg: #1f8cf9; - --lk-accent2: #3396fa; - --lk-accent3: #47a0fa; - --lk-accent4: #5babfb; - --lk-danger-fg: var(--lk-fg); - --lk-danger: #f8d7da; - --lk-danger2: #f1aeb5; - --lk-danger3: #ea868f; - --lk-danger4: #e35d6a; - --lk-success-fg: #fff; - --lk-success: #146c43; - --lk-success2: #198754; - --lk-success3: #479f76; - --lk-success4: #75b798; - --lk-control-fg: var(--lk-fg); - --lk-control-bg: var(--lk-bg2); - --lk-control-hover-bg: var(--lk-bg3); - --lk-control-active-bg: var(--lk-bg4); - --lk-control-active-hover-bg: var(--lk-bg5); - --lk-connection-excellent: #198754; - --lk-connection-good: #fd7e14; - --lk-connection-poor: #dc3545; - --lk-font-family: system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, - Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'; - --lk-font-size: 16px; - --lk-line-height: 1.5; - --lk-border-radius: 0.5rem; - --lk-box-shadow: 0 0.5rem 1.5rem rgba(0, 0, 0, 0.15); - --lk-grid-gap: 0.5rem; + --lk-border-color: var(--colors-gray-400); + --lk-accent-fg: var(--colors-primary-text); + --lk-accent-bg: var(--colors-primary); + --lk-accent2: var(--colors-primary-hover); + --lk-accent3: var(--colors-primary-active); + --lk-accent4: var(--colors-primary-active); + --lk-danger-fg: var(--colors-danger-text); + --lk-danger: var(--colors-danger); + --lk-danger-hover-bg: var(--colors-danger-hover); + --lk-danger-active-bg: var(--colors-danger-active); + --lk-success-fg: var(--colors-success-text); + --lk-success: var(--colors-success); + --lk-control-fg: var(--colors-control-text); + --lk-control-bg: var(--colors-control); + --lk-control-hover-bg: var(--colors-control-hover); + --lk-control-toggled-on-bg: var(--colors-control-hover); + --lk-control-active-bg: var(--colors-control-active); + --lk-control-active-hover-bg: var(--colors-control-active); + --lk-connection-excellent: var(--colors-success); + --lk-connection-good: var(--colors-warning); + --lk-connection-poor: var(--colors-danger); + --lk-line-height: var(--line-heights-1\.5); + --lk-border-radius: var(--radii-8); + --lk-box-shadow: var(--shadows-sm); + --lk-grid-gap: 1.5rem; --lk-control-bar-height: 69px; --lk-chat-header-height: 69px; + --lk-font-family: var(--fonts-sans); + --lk-font-size: 1rem; --lk-bg6: #6b7280; --lk-control-border-width: 1px; --lk-control-border-color: var(--lk-bg5); --lk-control-border: var(--lk-control-border-color); --lk-control-hover-border: var(--lk-bg6); + + --lk-controlbar-bg: var(--colors-gray-300); + --lk-participant-border: var(--colors-gray-400); } -[data-lk-theme] { - background-color: var(--lk-bg); +[data-lk-theme] .lk-room-container { + background-color: var(--lk-room-bg); } .lk-button, @@ -133,14 +76,77 @@ for now only "visio-light" is actually used border-color: var(--lk-control-hover-border); } -.lk-disconnect-button { - --lk-control-border: var(--lk-danger3); +.lk-button:not(:disabled):active, +.lk-start-audio-button:not(:disabled):active, +.lk-close-button:not(:disabled):active, +.lk-chat-toggle:not(:disabled):active, +.lk-button:not(:disabled):is([data-pressed]) { + background-color: var(--lk-control-active-bg); } -.lk-disconnect-button:not(:disabled):hover { - --lk-control-hover-bg: var(--lk-danger2); +.lk-button[aria-pressed='true'], +[aria-pressed='true'].lk-start-audio-button, +[aria-pressed='true'].lk-chat-toggle, +[aria-pressed='true'].lk-disconnect-button { + background-color: var(--lk-control-toggled-on-bg); } -.lk-prejoin { +[data-lk-theme] .lk-disconnect-button { + --lk-control-border: var(--colors-danger-hover); +} + +[data-lk-theme] .lk-disconnect-button:not(:disabled):hover { + --lk-control-hover-bg: var(--lk-danger-hover-bg); +} + +[data-lk-theme] .lk-disconnect-button:not(:disabled):active { + background-color: var(--lk-danger-active-bg); +} + +[data-lk-theme='visio-light'] .lk-close-button path { + fill: var(--lk-fg); +} + +[data-lk-theme='visio-light'] .lk-participant-metadata-item, +[data-lk-theme='visio-light'] + .lk-participant-tile + .lk-focus-toggle-button:not(:hover) { + color: white; +} + +[data-lk-theme='visio-light'] + .lk-chat-entry[data-lk-message-origin='local'] + .lk-message-body { + align-self: flex-end; + background-color: var(--colors-primary-subtle); +} +[data-lk-theme='visio-light'] + .lk-chat-entry[data-lk-message-origin='remote'] + .lk-message-body { + background-color: var(--colors-blue-300); +} + +[data-lk-theme] .lk-chat-header { + font-weight: bold; +} + +[data-lk-theme] .lk-chat-messages { + padding: 0.5rem; +} + +.lk-chat-entry .lk-meta-data { + padding-left: 0.75rem; + padding-right: 0.75rem; +} + +[data-lk-theme] .lk-control-bar { + background-color: var(--lk-controlbar-bg); +} + +[data-lk-theme] .lk-prejoin { padding-top: 0; } + +[data-lk-theme] .lk-participant-tile { + box-shadow: var(--lk-box-shadow); +} diff --git a/src/frontend/src/utils/createRandomRoom.ts b/src/frontend/src/utils/createRandomRoom.ts deleted file mode 100644 index 10f0c412..00000000 --- a/src/frontend/src/utils/createRandomRoom.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { slugify } from './slugify' - -export const createRandomRoom = () => { - return slugify(crypto.randomUUID()) -}