Skip to content

Commit

Permalink
Upload and preview photo in AddOrEditNoteFlow
Browse files Browse the repository at this point in the history
  • Loading branch information
pkirilin committed Jun 29, 2024
1 parent f46f60d commit 1ca7e74
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 6 deletions.
2 changes: 1 addition & 1 deletion src/frontend/src/features/note/addEdit/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { type productModel } from '@/entities/product';

export type DialogStateType = 'note' | 'product';

export type InputMethod = 'default' | 'photo';
export type InputMethod = 'fromInput' | 'fromPhoto';

export interface DialogState {
type: DialogStateType;
Expand Down
15 changes: 10 additions & 5 deletions src/frontend/src/features/note/addEdit/ui/NoteInputDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { type UseInputResult } from '@/shared/hooks';
import { Button, Dialog } from '@/shared/ui';
import { type Note, type InputMethod } from '../model';
import { NoteInputForm } from './NoteInputForm';
import { NoteInputFromPhotoFlow } from './NoteInputFromPhotoFlow';

interface Props {
opened: boolean;
Expand Down Expand Up @@ -46,7 +47,7 @@ export const NoteInputDialog: FC<Props> = ({
onSubmit,
onProductChange,
}) => {
const [selectedInputMethod, setSelectedInputMethod] = useState<InputMethod>('default');
const [selectedInputMethod, setSelectedInputMethod] = useState<InputMethod>('fromInput');
const [submitDisabled, setSubmitDisabled] = useState(true);

const handleSelectedInputMethodChange = (_: React.SyntheticEvent, value: InputMethod): void => {
Expand All @@ -57,6 +58,8 @@ export const NoteInputDialog: FC<Props> = ({
setSubmitDisabled(disabled);
}, []);

const handleFileUploaded = async (_: File): Promise<void> => {};

return (
<Dialog
disableContentPadding
Expand All @@ -76,17 +79,17 @@ export const NoteInputDialog: FC<Props> = ({
icon={<KeyboardIcon />}
iconPosition="start"
label="From input"
value={'default' satisfies InputMethod}
value={'fromInput' satisfies InputMethod}
/>
<Tab
icon={<PhotoCameraIcon />}
iconPosition="start"
label="From photo"
value={'photo' satisfies InputMethod}
value={'fromPhoto' satisfies InputMethod}
/>
</TabList>
</Box>
<Box pb={0} component={TabPanel} value={'default' satisfies InputMethod}>
<Box pb={0} component={TabPanel} value={'fromInput' satisfies InputMethod}>
<NoteInputForm
id="note-input-form"
pageId={pageId}
Expand All @@ -108,7 +111,9 @@ export const NoteInputDialog: FC<Props> = ({
onSubmitDisabledChange={handleSubmitDisabledChange}
/>
</Box>
<TabPanel value={'photo' satisfies InputMethod}>WIP</TabPanel>
<TabPanel value={'fromPhoto' satisfies InputMethod}>
<NoteInputFromPhotoFlow onUploadSuccess={handleFileUploaded} />
</TabPanel>
</TabContext>
}
onClose={onClose}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Box, ImageList, ImageListItem, ImageListItemBar, Stack } from '@mui/material';
import { useState, type FC } from 'react';
import { convertToBase64String, resizeImage } from '@/shared/lib';
import { UploadButton } from '@/shared/ui';
import { type UploadedPhoto } from '../model';

interface Props {
onUploadSuccess: (file: File) => Promise<void>;
}

export const NoteInputFromPhotoFlow: FC<Props> = ({ onUploadSuccess }) => {
const [uploadedPhotos, setUploadedPhotos] = useState<UploadedPhoto[]>([]);

const handleUpload = async (file: File): Promise<void> => {
const base64 = await convertToBase64String(file);
const resizedFile = await resizeImage(base64, 512, file.name);
const resizedBase64 = await convertToBase64String(resizedFile);

setUploadedPhotos([
{
src: resizedBase64,
name: file.name,
file,
},
]);

await onUploadSuccess(resizedFile);
};

if (uploadedPhotos.length === 0) {
return (
<UploadButton name="photos" accept="image/*" onUpload={handleUpload}>
Upload photo
</UploadButton>
);
}

return (
<Stack spacing={3}>
<ImageList cols={2} gap={16}>
{uploadedPhotos.map((photo, index) => (
<ImageListItem key={index}>
<Box
component="img"
height="194px"
src={photo.src}
alt={photo.name}
sx={{ objectFit: 'cover' }}
/>
<ImageListItemBar title={photo.name} />
</ImageListItem>
))}
</ImageList>
</Stack>
);
};
37 changes: 37 additions & 0 deletions src/frontend/src/shared/ui/UploadButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Button, styled } from '@mui/material';
import { visuallyHidden } from '@mui/utils';
import { type PropsWithChildren, type ChangeEventHandler, type FC } from 'react';

const FileInputStyled = styled('input')(() => ({ ...visuallyHidden }));

interface Props {
name: string;
accept: string;
onUpload: (file: File) => Promise<void>;
}

export const UploadButton: FC<PropsWithChildren<Props>> = ({
children,
name,
accept,
onUpload,
}) => {
const handleUpload: ChangeEventHandler<HTMLInputElement> = async event => {
try {
const file = event.target?.files?.item(0);

if (file) {
await onUpload(file);
}
} finally {
event.target.value = '';
}
};

return (
<Button role={undefined} component="label" variant="outlined" fullWidth>
{children}
<FileInputStyled type="file" name={name} accept={accept} onChange={handleUpload} />
</Button>
);
};
1 change: 1 addition & 0 deletions src/frontend/src/shared/ui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export * from './DatePicker';
export * from './Dialog';
export * from './AppDialog';
export * from './LoadingContainer';
export * from './UploadButton';

0 comments on commit 1ca7e74

Please sign in to comment.