diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index e8c549bf..408286b8 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -13,24 +13,40 @@ import { routes } from './routes' import './i18n/init' import { queryClient } from '@/api/queryClient' import { AppInitialization } from '@/components/AppInitialization' +import { SdkCreateButton } from './features/rooms/routes/sdk/CreateButton' function App() { const { i18n } = useTranslation() useLang(i18n.language) return ( - - - - {Object.entries(routes).map(([, route], i) => ( - - ))} - - - - {/* */} + + {/* We only want support and ReactQueryDevTools in non /sdk routes */} + + + + {Object.entries(routes).map(([, route], i) => ( + + ))} + + + + + + + + + + diff --git a/src/frontend/src/assets/VisioIcon.tsx b/src/frontend/src/assets/VisioIcon.tsx new file mode 100644 index 00000000..ba4cc801 --- /dev/null +++ b/src/frontend/src/assets/VisioIcon.tsx @@ -0,0 +1,24 @@ +export const VisioIcon = () => { + return ( + + + + + + ) +} diff --git a/src/frontend/src/features/rooms/routes/sdk/CreateButton.tsx b/src/frontend/src/features/rooms/routes/sdk/CreateButton.tsx index 1082b0eb..032a98d4 100644 --- a/src/frontend/src/features/rooms/routes/sdk/CreateButton.tsx +++ b/src/frontend/src/features/rooms/routes/sdk/CreateButton.tsx @@ -1,13 +1,90 @@ import { Button } from '@/primitives/Button' import { useTranslation } from 'react-i18next' +import { generateRoomId } from '../../utils/generateRoomId' +import { useCreateRoom } from '../../api/createRoom' +import { usePersistentUserChoices } from '@livekit/components-react' +import { useState } from 'react' +import { getRouteUrl } from '@/navigation/getRouteUrl' +import { css } from '@/styled-system/css' +import { RiCheckLine, RiFileCopyLine } from '@remixicon/react' +import { Loader } from '@/primitives/Loader' +import { VisioIcon } from '@/assets/VisioIcon' export const SdkCreateButton = () => { const { t } = useTranslation('sdk', { keyPrefix: 'createButton' }) + const { t: th } = useTranslation('home') + const [roomUrl, setRoomUrl] = useState() + const [isLoading, setIsLoading] = useState(false) + const { + userChoices: { username }, + } = usePersistentUserChoices() + + const { mutateAsync: createRoom } = useCreateRoom() + + const submit = () => { + console.log('SUBMIT') + setIsLoading(true) + setTimeout(() => { + const slug = generateRoomId() + createRoom({ slug, username }).then((data) => { + const roomUrlTmp = getRouteUrl('room', data.slug) + setRoomUrl(roomUrlTmp) + setIsLoading(false) + }) + }, 2000) + } + + const [isCopied, setIsCopied] = useState(false) + const [isHovered, setIsHovered] = useState(false) + + const copy = () => { + navigator.clipboard.writeText(roomUrl!) + setIsCopied(true) + setTimeout(() => setIsCopied(false), 1000) + } + return ( -
- +
+ {roomUrl ? ( + + ) : ( + + )}
) } diff --git a/src/frontend/src/primitives/Button.tsx b/src/frontend/src/primitives/Button.tsx index 8ee63d03..1724edd1 100644 --- a/src/frontend/src/primitives/Button.tsx +++ b/src/frontend/src/primitives/Button.tsx @@ -6,12 +6,15 @@ import { type RecipeVariantProps } from '@/styled-system/css' import { buttonRecipe, type ButtonRecipe } from './buttonRecipe' import { TooltipWrapper, type TooltipWrapperProps } from './TooltipWrapper' import { ReactNode } from 'react' +import { Loader } from './Loader' export type ButtonProps = RecipeVariantProps & RACButtonsProps & TooltipWrapperProps & { // Use tooltip as description below the button. description?: boolean + } & { + icon?: ReactNode } export const Button = ({ @@ -27,6 +30,8 @@ export const Button = ({ className={buttonRecipe(variantProps)} {...(componentProps as RACButtonsProps)} > + {!props.loading && props.icon} + {props.loading && } {componentProps.children as ReactNode} {props.description && {tooltip}} diff --git a/src/frontend/src/primitives/Loader.tsx b/src/frontend/src/primitives/Loader.tsx new file mode 100644 index 00000000..272fc380 --- /dev/null +++ b/src/frontend/src/primitives/Loader.tsx @@ -0,0 +1,45 @@ +import { cva } from '@/styled-system/css' + +const loader = cva({ + base: { + borderRadius: '50%', + position: 'relative', + animation: 'rotate 1s linear infinite', + '&:before, &:after': { + content: '""', + boxSizing: 'border-box', + position: 'absolute', + inset: '0', + borderRadius: '50%', + borderStyle: 'solid', + borderColor: 'white', + }, + _before: { + animation: 'prixClipFix 2s linear infinite', + }, + _after: { + borderColor: 'white', + animation: + 'prixClipFix 2s linear infinite, rotate 0.5s linear infinite reverse', + inset: 6, + }, + }, + variants: { + size: { + small: { + width: '24px', + height: '24px', + '&:before, &:after': { + borderWidth: '2px', + }, + }, + }, + }, + defaultVariants: { + size: 'small', + }, +}) + +export const Loader = () => { + return
+} diff --git a/src/frontend/src/primitives/buttonRecipe.ts b/src/frontend/src/primitives/buttonRecipe.ts index 7f294980..aea4deba 100644 --- a/src/frontend/src/primitives/buttonRecipe.ts +++ b/src/frontend/src/primitives/buttonRecipe.ts @@ -8,6 +8,7 @@ export const buttonRecipe = cva({ transition: 'background 200ms, outline 200ms, border-color 200ms', cursor: 'pointer', border: '1px solid transparent', + gap: '0.5rem', }, variants: { size: { @@ -229,6 +230,9 @@ export const buttonRecipe = cva({ width: 'full', }, }, + loading: { + true: {}, + }, // some toggle buttons make more sense without a "pushed button" style when selected because their content changes to mark the state shySelected: { true: {}, diff --git a/src/frontend/src/routes.ts b/src/frontend/src/routes.ts index a8c0bbd1..e50d2665 100644 --- a/src/frontend/src/routes.ts +++ b/src/frontend/src/routes.ts @@ -28,11 +28,11 @@ export const routes: Record< path: '/feedback', Component: FeedbackRoute, }, - sdkCreateButton: { - name: 'sdkCreateButton', - path: '/sdk/create-button', - Component: SdkCreateButton, - }, + // sdkCreateButton: { + // name: 'sdkCreateButton', + // path: '/sdk/create-button', + // Component: SdkCreateButton, + // }, } export type RouteName = keyof typeof routes diff --git a/src/frontend/src/styles/index.css b/src/frontend/src/styles/index.css index 1bc20436..a821a5cd 100644 --- a/src/frontend/src/styles/index.css +++ b/src/frontend/src/styles/index.css @@ -38,3 +38,34 @@ body:has(.lk-video-conference) #crisp-chatbox > div > a { transform: translateY(0); } } + +/** + * Loader animations. + */ + +@keyframes rotate { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +@keyframes prixClipFix { + 0% { + clip-path: polygon(50% 50%, 0 0, 0 0, 0 0, 0 0, 0 0); + } + 25% { + clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 0, 100% 0, 100% 0); + } + 50% { + clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 100%, 100% 100%, 100% 100%); + } + 75% { + clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 100%, 0 100%, 0 100%); + } + 100% { + clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 100%, 0 100%, 0 0); + } +}