refactor: migrate modal components

This commit is contained in:
Carlos Valente
2025-07-04 10:34:30 +02:00
committed by Carlos Valente
parent 36223ff49f
commit a8ea1080f3
13 changed files with 288 additions and 307 deletions
@@ -1,22 +1,15 @@
import { useState } from 'react';
import { IoArrowForward } from 'react-icons/io5';
import {
IconButton,
Input,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalHeader,
ModalOverlay,
Select,
} from '@chakra-ui/react';
import { navigatorConstants } from '../../../viewerConfig';
import { setClientRemote } from '../../hooks/useSocket';
import useUrlPresets from '../../hooks-query/useUrlPresets';
import Button from '../buttons/Button';
import Info from '../info/Info';
import Input from '../input/input/Input';
import AppLink from '../link/app-link/AppLink';
import Modal from '../modal/Modal';
import Select from '../select/Select';
import style from './RedirectClientModal.module.scss';
@@ -29,8 +22,7 @@ interface RedirectClientModalProps {
onClose: () => void;
}
export function RedirectClientModal(props: RedirectClientModalProps) {
const { id, isOpen, name, currentPath, origin, onClose } = props;
export function RedirectClientModal({ id, isOpen, name, currentPath, origin, onClose }: RedirectClientModalProps) {
const { data } = useUrlPresets();
const [path, setPath] = useState(currentPath);
const [selected, setSelected] = useState('/');
@@ -47,13 +39,26 @@ export function RedirectClientModal(props: RedirectClientModalProps) {
const enabledPresets = data.filter((preset) => preset.enabled);
const viewOptions = [
...navigatorConstants.map((view) => ({
value: `/${view.url}`,
label: view.label,
})),
...enabledPresets.map((preset) => ({
value: preset.pathAndParams,
label: `Preset: ${preset.alias}`,
})),
];
return (
<Modal isOpen={isOpen} onClose={onClose} variant='ontime'>
<ModalOverlay />
<ModalContent maxWidth='max(480px, 35vw)'>
<ModalHeader>Redirect: {name}</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Modal
isOpen={isOpen}
onClose={onClose}
showCloseButton
showBackdrop
title={`Redirect: ${name}`}
bodyElements={
<>
<Info>
Remotely redirect the client to a different URL. <br />
Either by selecting a URL Preset or entering a custom path.
@@ -65,36 +70,21 @@ export function RedirectClientModal(props: RedirectClientModalProps) {
<span className={style.label}>Select View or URL Preset</span>
<div className={style.textEntry}>
<Select
size='md'
variant='ontime'
isDisabled={enabledPresets.length === 0}
onChange={(event) => setSelected(event.target.value)}
>
<option value='/'>Select view or preset</option>
{navigatorConstants.map((view) => {
return (
<option key={view.url} value={`/${view.url}`}>
{view.label}
</option>
);
})}
{enabledPresets.map((preset) => {
return (
<option key={preset.pathAndParams} value={preset.pathAndParams}>
{`Preset: ${preset.alias}`}
</option>
);
})}
</Select>
<IconButton
variant='ontime-filled'
size='md'
fluid
options={viewOptions}
defaultValue={viewOptions[0].value}
onValueChange={(value) => setSelected(value)}
disabled={enabledPresets.length === 0}
/>
<Button
variant='primary'
aria-label='Redirect to preset'
className={style.redirect}
icon={<IoArrowForward />}
isDisabled={enabledPresets.length === 0 || selected === '/'}
disabled={enabledPresets.length === 0 || selected === '/'}
onClick={() => handleRedirect(selected)}
/>
>
Redirect <IoArrowForward />
</Button>
</div>
</div>
<div className={style.inlineEntry}>
@@ -102,25 +92,24 @@ export function RedirectClientModal(props: RedirectClientModalProps) {
<label className={style.textEntry}>
{origin}
<Input
variant='ontime-filled'
size='md'
placeholder='eg. /minimal?key=0000ffff'
fluid
value={path}
onChange={(event) => setPath(event.target.value)}
/>
</label>
<IconButton
variant='ontime-filled'
size='md'
<Button
variant='primary'
aria-label='Redirect'
isDisabled={path === currentPath || path === ''}
disabled={path === currentPath || path === ''}
className={style.redirect}
icon={<IoArrowForward />}
onClick={() => handleRedirect(path)}
/>
>
Redirect <IoArrowForward />
</Button>
</div>
</ModalBody>
</ModalContent>
</Modal>
</>
}
/>
);
}
@@ -1,13 +1,13 @@
.modal {
position: fixed;
top: 50%;
top: 10vh;
left: 50%;
transform: translate(-50%, -50%);
transform: translateX(-50%);
padding-inline: 1rem;
min-width: min(680px, 90vw);
min-height: min(200px, 10vh);
max-width: min(680px, 90vw);
background-color: $gray-1250;
color: $ui-white;
@@ -19,7 +19,6 @@
.backdrop {
position: fixed;
inset: 0;
z-index: $zindex-backdrop;
background-color: $backdrop-color;
transition: opacity 300ms cubic-bezier(0.45, 1.005, 0, 1.005);
@@ -44,7 +43,7 @@
flex-direction: column;
gap: 0.5rem;
max-height: min(80vh, 600px);
max-height: 60vh;
overflow-y: auto;
}
@@ -8,7 +8,7 @@ import style from './Modal.module.scss';
interface ModalProps {
isOpen: boolean;
title: string;
title?: string;
showCloseButton?: boolean;
showBackdrop?: boolean;
bodyElements: ReactNode;
@@ -33,6 +33,10 @@
opacity: 0.4;
cursor: not-allowed;
}
&.fluid {
width: 100%;
}
}
.selectIcon {
@@ -2,6 +2,8 @@ import { IoCheckmark } from 'react-icons/io5';
import { LuChevronsUpDown } from 'react-icons/lu';
import { Select as BaseSelect } from '@base-ui-components/react/select';
import { cx } from '../../utils/styleUtils';
import styles from './Select.module.scss';
interface SelectProps<T> extends Omit<BaseSelect.Root.Props<T>, 'items'> {
@@ -10,12 +12,13 @@ interface SelectProps<T> extends Omit<BaseSelect.Root.Props<T>, 'items'> {
value: T;
label: string;
}[];
fluid?: boolean;
}
export default function Select<T>({ options, ...selectRootProps }: SelectProps<T>) {
export default function Select<T>({ options, fluid, ...selectRootProps }: SelectProps<T>) {
return (
<BaseSelect.Root items={options} {...selectRootProps}>
<BaseSelect.Trigger className={styles.select}>
<BaseSelect.Trigger className={cx([styles.select, fluid && styles.fluid])}>
<BaseSelect.Value />
<BaseSelect.Icon className={styles.selectIcon}>
<LuChevronsUpDown />
@@ -8,5 +8,8 @@
}
.column {
width: 100%;
display: flex;
gap: 1rem;
flex-direction: column;
}
@@ -1,17 +1,9 @@
import { lazy, useEffect, useRef, useState } from 'react';
import {
Button,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
} from '@chakra-ui/react';
import { getCSSContents, postCSSContents, restoreCSSContents } from '../../../../common/api/assets';
import Button from '../../../../common/components/buttons/Button';
import Info from '../../../../common/components/info/Info';
import Modal from '../../../../common/components/modal/Modal';
import * as Panel from '../../panel-utils/PanelUtils';
import style from './StyleEditorModal.module.scss';
@@ -27,9 +19,7 @@ interface CSSRef {
getCss: () => string;
}
export default function CodeEditorModal(props: CodeEditorModalProps) {
const { isOpen, onClose } = props;
export default function CodeEditorModal({ isOpen, onClose }: CodeEditorModalProps) {
const [css, setCSS] = useState('');
const [isDirty, setIsDirty] = useState(false);
const [saveLoading, setSaveLoading] = useState(false);
@@ -84,46 +74,43 @@ export default function CodeEditorModal(props: CodeEditorModalProps) {
}, [isOpen]);
return (
<Modal isOpen={isOpen} onClose={onClose} variant='ontime' isCentered>
<ModalOverlay />
<ModalContent maxWidth='max(800px, 40vw)'>
<ModalHeader>Edit CSS override</ModalHeader>
<ModalCloseButton />
<ModalBody>
<CodeEditor ref={cssRef} initialValue={css} language='css' isDirty={isDirty} setIsDirty={setIsDirty} />
</ModalBody>
<ModalFooter className={style.column}>
<Modal
title='Edit CSS override'
isOpen={isOpen}
onClose={onClose}
showCloseButton
showBackdrop
bodyElements={
<CodeEditor ref={cssRef} initialValue={css} language='css' isDirty={isDirty} setIsDirty={setIsDirty} />
}
footerElements={
<div className={style.column}>
<Info>Invalid CSS will be refused by the browser</Info>
{error && <Panel.Error className={style.right}>{`Error: ${error}`}</Panel.Error>}
<Panel.InlineElements align='apart' className={style.editorActions}>
<Button
variant='ontime-ghosted'
onClick={handleRestore}
isDisabled={saveLoading || resetLoading}
isLoading={resetLoading}
>
<Button variant='ghosted' size='large' onClick={handleRestore} disabled={saveLoading || resetLoading}>
Reset to example
</Button>
<Panel.InlineElements>
<Button variant='ontime-ghosted' onClick={clear}>
<Button variant='ghosted' size='large' onClick={clear}>
Clear
</Button>
<Button variant='ontime-subtle' onClick={onClose}>
<Button size='large' onClick={onClose}>
Cancel
</Button>
<Button
variant='ontime-filled'
variant='primary'
size='large'
onClick={handleSave}
isDisabled={saveLoading || resetLoading || !isDirty}
isLoading={saveLoading}
disabled={saveLoading || resetLoading || !isDirty}
loading={saveLoading}
>
Save changes
</Button>
</Panel.InlineElements>
</Panel.InlineElements>
</ModalFooter>
</ModalContent>
</Modal>
</div>
}
/>
);
}
@@ -1,8 +0,0 @@
.scrollContainer {
max-height: 70vh;
overflow: auto;
display: flex;
flex-direction: column;
gap: 1rem;
}
@@ -1,45 +1,35 @@
import { Controller, useForm } from 'react-hook-form';
import {
Button,
Input,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Select,
Switch,
} from '@chakra-ui/react';
import { useForm } from 'react-hook-form';
import { QuickStartData } from 'ontime-types';
import { parseUserTime } from 'ontime-utils';
import { quickProject } from '../../../common/api/db';
import { invalidateAllCaches, maybeAxiosError } from '../../../common/api/utils';
import Button from '../../../common/components/buttons/Button';
import Input from '../../../common/components/input/input/Input';
import TimeInput from '../../../common/components/input/time-input/TimeInput';
import Modal from '../../../common/components/modal/Modal';
import Select from '../../../common/components/select/Select';
import Switch from '../../../common/components/switch/Switch';
import { editorSettingsDefaults, useEditorSettings } from '../../../common/stores/editorSettings';
import * as Panel from '../panel-utils/PanelUtils';
import { quickStartDefaults } from './quickStart.utils';
import style from './QuickStart.module.scss';
interface QuickStartProps {
isOpen: boolean;
onClose: () => void;
}
export default function QuickStart(props: QuickStartProps) {
const { isOpen, onClose } = props;
export default function QuickStart({ isOpen, onClose }: QuickStartProps) {
const { defaultWarnTime, defaultDangerTime, setDangerTime, setWarnTime } = useEditorSettings();
const {
control,
handleSubmit,
register,
formState: { errors, isSubmitting, isValid },
watch,
setError,
setValue,
} = useForm<QuickStartData>({
defaultValues: quickStartDefaults,
values: quickStartDefaults,
@@ -65,123 +55,114 @@ export default function QuickStart(props: QuickStartProps) {
const dangerTimeInMs = parseUserTime(defaultDangerTime);
return (
<Modal isOpen={isOpen} onClose={onClose} closeOnOverlayClick={false} variant='ontime'>
<ModalOverlay />
<ModalCloseButton />
<ModalContent maxWidth='max(640px, 40vw)'>
<Modal
isOpen={isOpen}
onClose={onClose}
showBackdrop
showCloseButton
title='Create new project...'
bodyElements={
<form onSubmit={handleSubmit(onSubmit)} id='quick-start'>
<ModalHeader>Create new project...</ModalHeader>
<ModalBody className={style.scrollContainer}>
<ModalCloseButton />
<Panel.ListGroup>
<Panel.ListItem>
<Panel.Field title='Project title' description='Shown as the title in some views' />
<Input
variant='ontime-filled'
size='sm'
maxLength={150}
placeholder='Project title'
autoComplete='off'
width='20rem'
{...register('project.title')}
/>
</Panel.ListItem>
<Panel.ListItem>
<Panel.Field
title='Time format'
description='Default time format to show in views 12 /24 hours'
error={errors.settings?.timeFormat?.message}
/>
<Select variant='ontime' size='sm' width='auto' isDisabled={false} {...register('settings.timeFormat')}>
<option value='12'>12 hours 11:00:10 PM</option>
<option value='24'>24 hours 23:00:10</option>
</Select>
</Panel.ListItem>
<Panel.ListItem>
<Panel.Field
title='Views language'
description='Language to be displayed in views'
error={errors.settings?.language?.message}
/>
<Select variant='ontime' size='sm' width='auto' isDisabled={false} {...register('settings.language')}>
<option value='en'>English</option>
<option value='fr'>French</option>
<option value='de'>German</option>
<option value='hu'>Hungarian</option>
<option value='it'>Italian</option>
<option value='no'>Norwegian</option>
<option value='pt'>Portuguese</option>
<option value='es'>Spanish</option>
<option value='sv'>Swedish</option>
<option value='pl'>Polish</option>
<option value='zh'>Chinese (Simplified)</option>
</Select>
</Panel.ListItem>
</Panel.ListGroup>
<Panel.ListGroup>
<Panel.ListItem>
<Panel.Field title='Project title' description='Shown as the title in some views' />
<Input maxLength={150} placeholder='Project title' fluid {...register('project.title')} />
</Panel.ListItem>
<Panel.ListItem>
<Panel.Field
title='Time format'
description='Default time format to show in views 12 /24 hours'
error={errors.settings?.timeFormat?.message}
/>
<Select
{...register('settings.timeFormat')}
defaultValue='24'
options={[
{ value: '12', label: '12 hours 11:00:10 PM' },
{ value: '24', label: '24 hours 23:00:10' },
]}
/>
</Panel.ListItem>
<Panel.ListItem>
<Panel.Field
title='Views language'
description='Language to be displayed in views'
error={errors.settings?.language?.message}
/>
<Select
{...register('settings.language')}
defaultValue='en'
options={[
{ value: 'en', label: 'English' },
{ value: 'fr', label: 'French' },
{ value: 'de', label: 'German' },
{ value: 'hu', label: 'Hungarian' },
{ value: 'it', label: 'Italian' },
{ value: 'no', label: 'Norwegian' },
{ value: 'pt', label: 'Portuguese' },
{ value: 'es', label: 'Spanish' },
{ value: 'sv', label: 'Swedish' },
{ value: 'pl', label: 'Polish' },
{ value: 'zh', label: 'Chinese (Simplified)' },
]}
/>
</Panel.ListItem>
</Panel.ListGroup>
<Panel.ListGroup>
<Panel.ListItem>
<Panel.Field title='Warning time' description='Default threshold for warning time in an event' />
<TimeInput<'warnTime'>
name='warnTime'
submitHandler={(_field, value) => setWarnTime(value)}
time={warnTimeInMs}
placeholder={editorSettingsDefaults.warnTime}
/>
</Panel.ListItem>
<Panel.ListItem>
<Panel.Field title='Danger time' description='Default threshold for danger time in an event' />
<TimeInput<'dangerTime'>
name='dangerTime'
submitHandler={(_field, value) => setDangerTime(value)}
time={dangerTimeInMs}
placeholder={editorSettingsDefaults.dangerTime}
/>
</Panel.ListItem>
</Panel.ListGroup>
<Panel.ListGroup>
<Panel.ListItem>
<Panel.Field title='Warning time' description='Default threshold for warning time in an event' />
<TimeInput<'warnTime'>
name='warnTime'
submitHandler={(_field, value) => setWarnTime(value)}
time={warnTimeInMs}
placeholder={editorSettingsDefaults.warnTime}
/>
</Panel.ListItem>
<Panel.ListItem>
<Panel.Field title='Danger time' description='Default threshold for danger time in an event' />
<TimeInput<'dangerTime'>
name='dangerTime'
submitHandler={(_field, value) => setDangerTime(value)}
time={dangerTimeInMs}
placeholder={editorSettingsDefaults.dangerTime}
/>
</Panel.ListItem>
</Panel.ListGroup>
<Panel.ListGroup>
<Panel.ListItem>
<Panel.Field
title='Freeze timer on end'
description='When a timer hits 00:00:00, it freezes instead of going negative. It invalidates the End Message.'
/>
<Controller
control={control}
name='viewSettings.freezeEnd'
render={({ field: { onChange, value, ref } }) => (
<Switch variant='ontime' size='lg' isChecked={value} onChange={onChange} ref={ref} />
)}
/>
</Panel.ListItem>
<Panel.ListItem>
<Panel.Field
title='End message'
description='Message for negative timers; applies only if the timer isn`t frozen on End. If no message is provided, it continues into negative time'
/>
<Input
size='sm'
autoComplete='off'
variant='ontime-filled'
maxLength={150}
width='20rem'
placeholder='Shown when timer reaches end'
{...register('viewSettings.endMessage')}
/>
</Panel.ListItem>
</Panel.ListGroup>
</ModalBody>
<ModalFooter>
{errors?.root && <Panel.Error>{errors.root.message}</Panel.Error>}
<Button variant='ontime-ghosted' size='md' onClick={onClose} isDisabled={false}>
Cancel
</Button>
<Button variant='ontime-filled' size='md' type='submit' isDisabled={!isValid} isLoading={isSubmitting}>
Create project
</Button>
</ModalFooter>
<Panel.ListGroup>
<Panel.ListItem>
<Panel.Field
title='Freeze timer on end'
description='When a timer hits 00:00:00, it freezes instead of going negative. It invalidates the End Message.'
/>
<Switch
name='viewSettings.freezeEnd'
checked={watch('viewSettings.freezeEnd')}
onCheckedChange={(checked) => setValue('viewSettings.freezeEnd', checked)}
/>
</Panel.ListItem>
<Panel.ListItem>
<Panel.Field
title='End message'
description='Message for negative timers; applies only if the timer isn`t frozen on End. If no message is provided, it continues into negative time'
/>
<Input maxLength={150} fluid placeholder='eg: Time is up!' {...register('viewSettings.endMessage')} />
</Panel.ListItem>
</Panel.ListGroup>
</form>
</ModalContent>
</Modal>
}
footerElements={
<>
{errors?.root && <Panel.Error>{errors.root.message}</Panel.Error>}
<Button variant='ghosted' onClick={onClose} disabled={false}>
Cancel
</Button>
<Button variant='primary' type='submit' form='quick-start' disabled={!isValid} loading={isSubmitting}>
Create project
</Button>
</>
}
/>
);
}
+18 -13
View File
@@ -1,8 +1,9 @@
import { KeyboardEvent, useState } from 'react';
import { Input, Modal, ModalBody, ModalContent, ModalFooter, ModalOverlay } from '@chakra-ui/react';
import { useDebouncedCallback } from '@mantine/hooks';
import { SupportedEntry } from 'ontime-types';
import Input from '../../../common/components/input/input/Input';
import Modal from '../../../common/components/modal/Modal';
import { useEventSelection } from '../../../features/rundown/useEventSelection';
import useFinder from './useFinder';
@@ -14,8 +15,7 @@ interface FinderProps {
onClose: () => void;
}
export default function Finder(props: FinderProps) {
const { isOpen, onClose } = props;
export default function Finder({ isOpen, onClose }: FinderProps) {
const { find, results, error } = useFinder();
const [selected, setSelected] = useState(0);
@@ -56,11 +56,14 @@ export default function Finder(props: FinderProps) {
};
return (
<Modal isOpen={isOpen} onClose={onClose} variant='ontime'>
<ModalOverlay />
<ModalContent maxWidth='max(640px, 40vw)'>
<ModalBody onKeyDown={navigate}>
<Input size='lg' onChange={debouncedFind} variant='ontime-filled' placeholder='Search...' />
<Modal
title=''
isOpen={isOpen}
onClose={onClose}
showBackdrop
bodyElements={
<div onKeyDown={navigate}>
<Input height='large' fluid onChange={debouncedFind} placeholder='Search...' />
<ul className={style.scrollContainer} onMouseMove={handleMouseMoveEvent}>
{error && <li className={style.error}>{error}</li>}
{results.length === 0 && <li className={style.empty}>No results</li>}
@@ -91,12 +94,14 @@ export default function Finder(props: FinderProps) {
);
})}
</ul>
</ModalBody>
<ModalFooter className={style.footer}>
</div>
}
footerElements={
<div className={style.footer}>
Use the keywords <span className={style.em}>cue</span>, <span className={style.em}>index</span> or
<span className={style.em}>title</span> to filter search
</ModalFooter>
</ModalContent>
</Modal>
</div>
}
/>
);
}
@@ -8,10 +8,25 @@
display: flex;
flex-direction: column;
gap: 1rem;
width: 100%;
}
.about {
@extend .column;
font-size: calc(1rem - 2px);
}
.inline {
display: flex;
align-items: center;
gap: 0.5rem;
}
.header {
font-size: 1.5rem;
display: flex;
align-items: center;
justify-content: space-between;
}
.logo {
@@ -20,7 +35,6 @@
}
.buttonRow {
margin-top: 1rem;
display: flex;
gap: 1rem;
justify-content: end;
@@ -38,6 +52,7 @@
.table {
width: 100%;
font-size: calc(1rem - 2px);
tbody {
background-color: $gray-1300;
@@ -1,11 +1,15 @@
import { IoClose } from 'react-icons/io5';
import { useNavigate } from 'react-router-dom';
import { Button, Checkbox, Modal, ModalBody, ModalCloseButton, ModalContent, ModalOverlay } from '@chakra-ui/react';
import { loadDemo, loadProject } from '../../../common/api/db';
import { postShowWelcomeDialog } from '../../../common/api/settings';
import { invalidateAllCaches } from '../../../common/api/utils';
import Button from '../../../common/components/buttons/Button';
import IconButton from '../../../common/components/buttons/IconButton';
import Checkbox from '../../../common/components/checkbox/Checkbox';
import * as Editor from '../../../common/components/editor-utils/EditorUtils';
import ExternalLink from '../../../common/components/link/external-link/ExternalLink';
import Modal from '../../../common/components/modal/Modal';
import { appVersion, discordUrl, documentationUrl, websiteUrl } from '../../../externals';
import ImportProjectButton from './composite/ImportProjectButton';
@@ -17,8 +21,7 @@ interface WelcomeProps {
onClose: () => void;
}
export default function Welcome(props: WelcomeProps) {
const { onClose } = props;
export default function Welcome({ onClose }: WelcomeProps) {
const navigate = useNavigate();
/** handle cleanup actions before request closing the modal */
@@ -55,54 +58,56 @@ export default function Welcome(props: WelcomeProps) {
};
return (
<Modal isOpen onClose={handleClose} closeOnOverlayClick={false} variant='ontime'>
<ModalOverlay />
<ModalContent maxWidth='max(640px, 40vw)'>
<ModalCloseButton />
<ModalBody>
<div className={style.sections}>
<div className={style.column}>
<img src='ontime-logo.png' alt='ontime' className={style.logo} />
<div>Ontime v{appVersion}</div>
<ExternalLink href={websiteUrl}>Website</ExternalLink>
<ExternalLink href={documentationUrl}>Read the docs</ExternalLink>
<ExternalLink href={discordUrl}>Discord server</ExternalLink>
<Modal
isOpen
onClose={handleClose}
showBackdrop
bodyElements={
<div className={style.sections}>
<div className={style.about}>
<img src='ontime-logo.png' alt='ontime' className={style.logo} />
<div>Ontime v{appVersion}</div>
<ExternalLink href={websiteUrl}>Website</ExternalLink>
<ExternalLink href={documentationUrl}>Read the docs</ExternalLink>
<ExternalLink href={discordUrl}>Discord server</ExternalLink>
</div>
<div className={style.column}>
<div className={style.header}>
Welcome to Ontime
<IconButton variant='subtle-white'>
<IoClose />
</IconButton>
</div>
<div className={style.column}>
<div className={style.header}>Welcome to Ontime</div>
<Editor.Title>Select project</Editor.Title>
<div className={style.tableContainer}>
<table className={style.table}>
<thead>
<tr>
<th>File Name</th>
<th>Last Used</th>
</tr>
</thead>
<WelcomeProjectList loadProject={handleLoadProject} onClose={handleClose} />
</table>
</div>
<Editor.Title>Select project</Editor.Title>
<div className={style.tableContainer}>
<table className={style.table}>
<thead>
<tr>
<th>File Name</th>
<th>Last Used</th>
</tr>
</thead>
<WelcomeProjectList loadProject={handleLoadProject} onClose={handleClose} />
</table>
</div>
</div>
</div>
}
footerElements={
<div className={style.column}>
<div className={style.buttonRow}>
<Button size='sm' variant='ontime-subtle' onClick={handleLoadDemo}>
Load demo project
</Button>
<Button onClick={handleLoadDemo}>Load demo project</Button>
<ImportProjectButton onFinish={handleClose} />
<Button size='sm' variant='ontime-filled' onClick={handleCallCreate}>
<Button variant='primary' onClick={handleCallCreate}>
Create new...
</Button>
</div>
<Checkbox
size='sm'
variant='ontime-ondark'
defaultChecked
onChange={(event) => postShowWelcomeDialog(event.target.checked)}
>
<Editor.Label className={style.inline}>
<Checkbox defaultChecked onCheckedChange={(checked) => postShowWelcomeDialog(checked)} />
Show this modal on next startup
</Checkbox>
</ModalBody>
</ModalContent>
</Modal>
</Editor.Label>
</div>
}
/>
);
}
@@ -4,18 +4,18 @@
*/
import { ChangeEvent, useRef } from 'react';
import { Button, Input } from '@chakra-ui/react';
import { Input } from '@chakra-ui/react';
import { uploadProjectFile } from '../../../../common/api/db';
import { invalidateAllCaches } from '../../../../common/api/utils';
import Button from '../../../../common/components/buttons/Button';
import { validateProjectFile } from '../../../../common/utils/uploadUtils';
interface ImportProjectButtonProps {
onFinish: () => void;
}
export default function ImportProjectButton(props: ImportProjectButtonProps) {
const { onFinish } = props;
export default function ImportProjectButton({ onFinish }: ImportProjectButtonProps) {
const fileInputRef = useRef<HTMLInputElement>(null);
const handleSelectFile = () => {
@@ -50,9 +50,7 @@ export default function ImportProjectButton(props: ImportProjectButtonProps) {
data-testid='file-input'
/>
<Button size='sm' variant='ontime-subtle' onClick={handleSelectFile}>
Import project
</Button>
<Button onClick={handleSelectFile}>Import project</Button>
</>
);
}