Skip to content

Commit

Permalink
feat(app) refactor and add components for nice edit
Browse files Browse the repository at this point in the history
  • Loading branch information
wpuzio-estee committed Jan 12, 2024
1 parent 128ba13 commit 936d641
Show file tree
Hide file tree
Showing 13 changed files with 136 additions and 183 deletions.
108 changes: 23 additions & 85 deletions src/app/dodajProdukt/page.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,33 @@
'use client';
import { useState } from 'react';
import { AdjustmentsVerticalIcon, CurrencyDollarIcon } from '@heroicons/react/24/solid';
import { ErrorMessage, Form, Formik } from 'formik';
import { Form, Formik } from 'formik';
import * as Yup from 'yup';

import Image from 'next/image';
import {
Alert,
Button,
Card,
Carousel,
Textarea,
Typography,
} from '@material-tailwind/react';
import { Alert, Button, Card, Typography } from '@material-tailwind/react';

import CustomArrow from '@/components/CustomArrow/CustomArrow';
import CustomInput from '@/components/CustomInput/CustomInput';
import UploadIcon from '@/dodajProdukt/UploadIcon';
import CustomTextarea from '@/components/CustomTextarea/CustomtextArea';
import CustomUploadCarousel from '@/components/CustomUploadCarousel/CustomUploadCarousel';
import CustomDropZone from '@/components/CutomDropZone/CustomDropZone';
import ApiClient from '@/providers/axios-client';

const ProductSchema = Yup.object().shape({
title: Yup.string().required('Nazwa produktu jest wymagana'),
body: Yup.string().required('Opis produktu jest wymagany'),
price: Yup.number().positive('Cena musi być liczbą dodatnią').required('Cena jest wymagana'),
amount: Yup.number().positive('Ilość musi być liczbą dodatnią').required('Ilość jest wymagana'),
image: Yup.mixed()
.test('fileSize', 'Plik jest zbyt duży. Maksymalny rozmiar to 800x400px.', (value) => {
if (!value) return true;
// @ts-ignore
return value.size <= 800 * 400;
})
.test('fileType', 'Dozwolone formaty plików to JPG, PNG, SVG, GIF.', (value) => {
if (!value) return true;
// @ts-ignore
return ['image/jpeg', 'image/png', 'image/svg+xml', 'image/gif'].includes(value.type);
}),
});

export default function Home() {
Expand Down Expand Up @@ -75,16 +79,16 @@ export default function Home() {
setSubmissionStatus('success');
resetForm();
setImagePreviews([]);
return response;
} catch (error) {
setSubmissionStatus('error');
console.error('Error:', error);
} finally {
setSubmitting(false);
}
}}
>
{({ setFieldValue, isSubmitting, handleChange, values, handleBlur }) => (
<Form className="mt-8 mb-2 w-80 w-full sm:w-96">
<Form className="mt-8 mb-2 w-full sm:w-96">
<div className="mb-1 flex flex-col gap-6">
<CustomInput
label="Nazwa Produktu"
Expand All @@ -94,14 +98,13 @@ export default function Home() {
value={values.title}
onChange={handleChange}
/>
<Textarea
label="Opis Produktu"
name="body"
<CustomTextarea
label={'Opis produktu'}
name={'body'}
onBlur={handleBlur}
value={values.body}
onChange={handleChange}
/>
<ErrorMessage name="body" component="div" />
<CustomInput
label="Cena"
name="price"
Expand All @@ -121,73 +124,8 @@ export default function Home() {
value={values.amount}
onChange={handleChange}
/>
{imagePreviews && (
<div>
<Typography variant="small">
Podgląd obrazków został dostosowany do szerokości formularza
</Typography>
<Carousel
className="rounded-xl"
prevArrow={({ handlePrev, firstIndex }) => (
<CustomArrow
onClick={handlePrev}
direction="prev"
isDisabled={firstIndex}
/>
)}
nextArrow={({ handleNext, activeIndex }) => (
<CustomArrow
onClick={handleNext}
direction="next"
isDisabled={activeIndex === imagePreviews.length - 1}
/>
)}
>
{imagePreviews.map((img, i) => {
return (
// eslint-disable-next-line @next/next/no-img-element
<Image
width={0}
height={0}
sizes="100vw"
style={{ width: '100%' }}
src={img}
alt={'image-' + i}
key={i}
className="object-cover h-48 w-96"
/>
);
})}
</Carousel>
</div>
)}

<div className="flex items-center justify-center w-full">
<label
htmlFor="dropzone-file"
className="flex flex-col items-center justify-center w-full h-64 border-2 border-gray-300 border-dashed rounded-lg cursor-pointer bg-gray-50 dark:hover:bg-bray-800 dark:bg-gray-700 hover:bg-gray-100 dark:border-gray-600 dark:hover:border-gray-500 dark:hover:bg-gray-600"
>
<div className="flex flex-col items-center justify-center pt-5 pb-6 ">
<UploadIcon />
<p className="text-sm mb-2 text-gray-500 dark:text-gray-400 text-center">
<span className="font-semibold">Kliknij by dodać obrazek</span> albo
przeciągnij i upuść
</p>
<p className="text-xs text-gray-500 dark:text-gray-400 text-center">
SVG, PNG, JPG bądź GIF (MAX. 800x400px)
</p>
</div>
<input
onChange={(e) => onSelectFile(setFieldValue, e)}
id="dropzone-file"
name="image"
type="file"
className="hidden"
accept=".jpg,.png,.svg,.gif"
// multiple
/>
</label>
</div>
{imagePreviews && <CustomUploadCarousel imagePreviews={imagePreviews} />}
<CustomDropZone onSelectFile={(e) => onSelectFile(setFieldValue, e)} name="image" />
</div>
<Button className="mt-6" fullWidth type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Przetwarzanie...' : 'Dodaj Produkt'}
Expand Down
64 changes: 2 additions & 62 deletions src/app/edycjaProduktu/page.tsx
Original file line number Diff line number Diff line change
@@ -1,85 +1,24 @@
'use client';
import React, { FunctionComponent } from 'react';
import { CurrencyDollarIcon } from '@heroicons/react/24/solid';

import {
Button,
Card,
IconButton,
Input,
Option,
Select,
Textarea,
Typography,
} from '@/providers/ThemeProvider';

interface IButtonIcon {
onClick?: () => void;
direction: 'prev' | 'next';
isDisabled?: boolean;
}

const CustomArrow: FunctionComponent<IButtonIcon> = ({ onClick, direction, isDisabled }) => (
<IconButton
variant="text"
color="gray"
size="lg"
onClick={onClick}
className={`!absolute top-2/4 ${
direction === 'prev' ? 'left-4' : '!right-4'
} -translate-y-2/4 backdrop-blur-sm bg-white/30 ${isDisabled ? 'cursor-not-allowed' : ''}`}
disabled={isDisabled}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={2}
stroke="currentColor"
className="h-6 w-6"
>
{direction === 'prev' ? (
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18"
/>
) : (
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3"
/>
)}
</svg>
</IconButton>
);

export default function Home() {
const [selectedFiles, setSelectedFiles] = React.useState<FileList | null>(null);
const [imagePreviews, setImagePreviews] = React.useState<string[] | []>([]);

const onSelectFile = (e: React.ChangeEvent<HTMLInputElement>) => {
const images = [];
const files = e.target.files;

if (files) {
for (let i = 0; i < files.length; i++) {
images.push(URL.createObjectURL(files[i]));
}

setSelectedFiles(files);
setImagePreviews(images);
}
};

return (
<main className="container mx-auto py-8 px-8">
<Card color="transparent" shadow={false}>
<Typography variant="h4" color="blue-gray">
Edycja Produktu
</Typography>
<form className="mt-8 mb-2 w-80 w-full sm:w-96" action="#send">
<form className="mt-8 mb-2 w-full sm:w-96">
<div className="mb-1 flex flex-col gap-6">
<Input variant="static" size="lg" label="Nazwa Produktu" crossOrigin="" />
<Textarea label="Opis Produktu" />
Expand All @@ -90,6 +29,7 @@ export default function Home() {
</Select>
<Input label="Cena" icon={<CurrencyDollarIcon />} crossOrigin="" />
<div>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src="https://images.unsplash.com/photo-1629367494173-c78a56567877?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=927&q=80"
alt=""
Expand Down
1 change: 0 additions & 1 deletion src/app/usunProdukt/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
'use client';
import React from 'react';

import { Card, Typography } from '@material-tailwind/react';

Expand Down
4 changes: 2 additions & 2 deletions src/components/CustomInput/CustomInput.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React from 'react';
import { FC } from 'react';
import { ErrorMessage } from 'formik';

import { Input } from '@material-tailwind/react';

import { ICustomInput } from '@/components/CustomInput/ICustomInput';

const CustomInput: React.FC<ICustomInput> = ({
const CustomInput: FC<ICustomInput> = ({
label,
name,
type = 'text',
Expand Down
8 changes: 4 additions & 4 deletions src/components/CustomInput/ICustomInput.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React from 'react';
import { ChangeEvent, FocusEvent, ReactNode } from 'react';

export interface ICustomInput {
label: string;
name: string;
type?: string;
icon?: React.ReactNode;
icon?: ReactNode;
step?: string;
onBlur: (e: React.FocusEvent<HTMLInputElement>) => void;
onBlur: (e: FocusEvent<HTMLInputElement>) => void;
value: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
}
2 changes: 1 addition & 1 deletion src/components/CustomTextarea/CustomtextArea.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ChangeEvent,FC,FocusEvent } from 'react';
import { ChangeEvent, FC, FocusEvent } from 'react';
import { ErrorMessage } from 'formik';

import { Textarea } from '@material-tailwind/react';
Expand Down
49 changes: 49 additions & 0 deletions src/components/CustomUploadCarousel/CustomUploadCarousel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { FC } from 'react';

import Image from 'next/image';
import { Carousel } from '@material-tailwind/react';

import CustomArrow from '@/components/CustomArrow/CustomArrow';
import { Typography } from '@/providers/ThemeProvider';

export interface IImageCarousel {
imagePreviews: string[]; // Assuming imagePreviews is an array of strings (URLs)
}

const CustomUploadCarousel: FC<IImageCarousel> = ({ imagePreviews }) => {
return (
<div>
<Typography variant="small">
Podgląd obrazków został dostosowany do szerokości formularza
</Typography>
<Carousel
className="rounded-xl"
prevArrow={({ handlePrev, firstIndex }) => (
<CustomArrow onClick={handlePrev} direction="prev" isDisabled={firstIndex} />
)}
nextArrow={({ handleNext, activeIndex }) => (
<CustomArrow
onClick={handleNext}
direction="next"
isDisabled={activeIndex === imagePreviews.length - 1}
/>
)}
>
{imagePreviews.map((img, i) => (
<Image
width={0}
height={0}
sizes="100vw"
style={{ width: '100%' }}
src={img}
alt={'image-' + i}
key={i}
className="object-cover h-48 w-96"
/>
))}
</Carousel>
</div>
);
};

export default CustomUploadCarousel;
40 changes: 40 additions & 0 deletions src/components/CutomDropZone/CustomDropZone.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { ChangeEvent, FC } from 'react';
import { ErrorMessage } from 'formik';

import UploadIcon from '@/dodajProdukt/UploadIcon';

interface IDropzone {
name?: string;
onSelectFile: (e: ChangeEvent<HTMLInputElement>) => void;
}

const Dropzone: FC<IDropzone> = ({ name = '', onSelectFile }) => (
<div className="flex flex-wrap items-center justify-center ">
<ErrorMessage name={name} component="div" className="w-full" />
<label
htmlFor="dropzone-file"
className="flex flex-col items-center justify-center w-full h-64 border-2 border-gray-300 border-dashed rounded-lg cursor-pointer bg-gray-50 dark:hover:bg-bray-800 dark:bg-gray-700 hover:bg-gray-100 dark:border-gray-600 dark:hover:border-gray-500 dark:hover:bg-gray-600"
>
<div className="flex flex-col items-center justify-center pt-5 pb-6 ">
<UploadIcon />
<p className="text-sm mb-2 text-gray-500 dark:text-gray-400 text-center">
<span className="font-semibold">Kliknij by dodać obrazek</span> albo przeciągnij i upuść
</p>
<p className="text-xs text-gray-500 dark:text-gray-400 text-center">
SVG, PNG, JPG bądź GIF (MAX. 800x400px)
</p>
</div>
<input
onChange={(e) => onSelectFile(e)}
id="dropzone-file"
name={name}
type="file"
className="hidden"
accept=".jpg,.png,.svg,.gif"
// multiple
/>
</label>
</div>
);

export default Dropzone;
Loading

0 comments on commit 936d641

Please sign in to comment.