diff --git a/packages/toolpad-app/src/components/EditableText.tsx b/packages/toolpad-app/src/components/EditableText.tsx new file mode 100644 index 00000000000..f45d4a823cc --- /dev/null +++ b/packages/toolpad-app/src/components/EditableText.tsx @@ -0,0 +1,39 @@ +import * as React from 'react'; +import { Skeleton, TextField, Typography, TypographyVariant } from '@mui/material'; + +interface EditableTextProps { + defaultValue?: string; + loading: boolean; + editing: boolean; + isError: boolean; + errorText?: string; + onKeyUp: (event: React.KeyboardEvent) => void; + onBlur: (event: React.FocusEvent) => void; + variant?: TypographyVariant; + size?: 'small' | 'medium'; +} + +const EditableText = React.forwardRef( + ({ defaultValue, onKeyUp, onBlur, variant, size, editing, loading, isError, errorText }, ref) => { + return editing ? ( + {})} + onBlur={onBlur ?? (() => {})} + defaultValue={defaultValue} + error={isError} + helperText={isError ? errorText : ''} + /> + ) : ( + + {loading ? : defaultValue} + + ); + }, +); + +export default EditableText; diff --git a/packages/toolpad-app/src/components/Home.tsx b/packages/toolpad-app/src/components/Home.tsx index d4ac21c7018..58a94d03364 100644 --- a/packages/toolpad-app/src/components/Home.tsx +++ b/packages/toolpad-app/src/components/Home.tsx @@ -3,6 +3,7 @@ import { Button, Card, CardHeader, + CardContent, CardActions, Container, Dialog, @@ -31,6 +32,8 @@ import DialogForm from './DialogForm'; import type { App, Deployment } from '../../prisma/generated/client'; import useLatest from '../utils/useLatest'; import ToolpadShell from './ToolpadShell'; +import getReadableDuration from '../utils/readableDuration'; +import EditableText from './EditableText'; export interface CreateAppDialogProps { open: boolean; @@ -39,39 +42,56 @@ export interface CreateAppDialogProps { function CreateAppDialog({ onClose, ...props }: CreateAppDialogProps) { const [name, setName] = React.useState(''); - const createAppMutation = client.useMutation('createApp'); + const createAppMutation = client.useMutation('createApp', { + onSuccess: (app) => { + window.location.href = `/_toolpad/app/${app.id}/editor`; + }, + }); return ( - - { - event.preventDefault(); - - const app = await createAppMutation.mutateAsync([name]); - window.location.href = `/_toolpad/app/${app.id}/editor`; - }} - > - Create a new MUI Toolpad App - - setName(event.target.value)} - /> - - - - - Create - - - - + + + { + event.preventDefault(); + createAppMutation.mutate([name]); + }} + > + Create a new MUI Toolpad App + + { + createAppMutation.reset(); + setName(event.target.value); + }} + /> + + + + + Create + + + + + ); } @@ -114,39 +134,6 @@ function AppDeleteDialog({ app, onClose }: AppDeleteDialogProps) { ); } -export interface AppRenameErrorDialogProps { - open: boolean; - currentName: string | undefined; - newName: string | undefined; - onContinue: () => void; - onDiscard: () => void; -} - -function AppRenameErrorDialog({ - open, - currentName, - newName, - onContinue, - onDiscard, -}: AppRenameErrorDialogProps) { - return ( - - - Can't rename app "{currentName}" - An app with the name "{newName}" already exists. - - - - - - - ); -} - interface AppCardProps { app?: App; activeDeployment?: Deployment; @@ -155,7 +142,7 @@ interface AppCardProps { function AppCard({ app, activeDeployment, onDelete }: AppCardProps) { const [menuAnchorEl, setMenuAnchorEl] = React.useState(null); - const [showAppRenameErrorDialog, setShowAppRenameErrorDialog] = React.useState(false); + const [showAppRenameError, setShowAppRenameError] = React.useState(false); const [editingTitle, setEditingTitle] = React.useState(false); const [appTitle, setAppTitle] = React.useState(app?.name); const appTitleInput = React.useRef(null); @@ -189,7 +176,8 @@ function AppCard({ app, activeDeployment, onDelete }: AppCardProps) { await client.mutation.updateApp(app.id, name); await client.invalidateQueries('getApps'); } catch (err) { - setShowAppRenameErrorDialog(true); + setShowAppRenameError(true); + setEditingTitle(true); } } }, @@ -207,6 +195,7 @@ function AppCard({ app, activeDeployment, onDelete }: AppCardProps) { const handleAppTitleInput = React.useCallback( (event: React.KeyboardEvent) => { setAppTitle((event.target as HTMLInputElement).value); + setShowAppRenameError(false); if (event.key === 'Escape') { if (appTitleInput.current?.value && app?.name) { setAppTitle(app.name); @@ -252,7 +241,15 @@ function AppCard({ app, activeDeployment, onDelete }: AppCardProps) { return ( - + } disableTypography - title={ - editingTitle ? ( - - ) : ( - - {app ? appTitle : } - - ) - } subheader={ - {app ? `Edited: ${app.editedAt.toLocaleString('short')}` : } + {app ? ( + + Edited {getReadableDuration(app.editedAt)} + + ) : ( + + )} } /> - + + +