| @@ -0,0 +1,37 @@ | |||
| import styles from './hover-image-card.module.css'; | |||
| const HoverImageCard = () => { | |||
| return ( | |||
| <div className={styles.container}> | |||
| <div className={styles.card}> | |||
| <div className={styles.content}> | |||
| <p>Next JS Path</p> | |||
| <p>18-8-2022</p> | |||
| <button className={styles.btn}>More Details</button> | |||
| </div> | |||
| {/*Change with Next Image*/} | |||
| <img src="/images/image-one.jpg" alt="text" /> | |||
| </div> | |||
| <div className={styles.card}> | |||
| <div className={styles.content}> | |||
| <p>Text 1</p> | |||
| <p>Text 2</p> | |||
| <button className={styles.btn}>Button Text</button> | |||
| </div> | |||
| {/*Change with Next Image*/} | |||
| <img src="/images/image-one.jpg" alt="text" /> | |||
| </div> | |||
| <div className={styles.card}> | |||
| <div className={styles.content}> | |||
| <p>Text 1</p> | |||
| <p>Text 2</p> | |||
| <button className={styles.btn}>Button Text</button> | |||
| </div> | |||
| {/*Change with Next Image*/} | |||
| <img src="/images/image-one.jpg" alt="text" /> | |||
| </div> | |||
| </div> | |||
| ); | |||
| }; | |||
| export default HoverImageCard; | |||
| @@ -0,0 +1,75 @@ | |||
| .container { | |||
| display: flex; | |||
| justify-content: center; | |||
| margin-top: 30px; | |||
| } | |||
| .card { | |||
| position: relative; | |||
| width: 230px; | |||
| height: 260px; | |||
| margin: 0 5px; | |||
| background-color: red; | |||
| transition: 0.3s; | |||
| overflow: hidden; | |||
| cursor: pointer; | |||
| } | |||
| .card img { | |||
| width: 100%; | |||
| height: 100%; | |||
| object-fit: cover; | |||
| transition: 0.3s; | |||
| } | |||
| .card::after { | |||
| content: ''; | |||
| position: absolute; | |||
| left: 0; | |||
| bottom: 0; | |||
| width: 100%; | |||
| height: 100%; | |||
| opacity: 0; | |||
| transition: 0.3s; | |||
| background: linear-gradient(to bottom, rgba(0, 0, 0, 0), rgba(0, 0, 0, 1)); | |||
| } | |||
| .content { | |||
| position: absolute; | |||
| bottom: 0; | |||
| width: 100%; | |||
| padding: 1rem; | |||
| z-index: 1; | |||
| color: #fff; | |||
| transition: 0.3s; | |||
| opacity: 0; | |||
| } | |||
| .btn { | |||
| padding: 0.3rem 0.8rem; | |||
| font-size: 0.6rem; | |||
| border: none; | |||
| cursor: pointer; | |||
| outline: none; | |||
| color: #fff; | |||
| background: transparent; | |||
| border: 2px solid #fff; | |||
| } | |||
| .content p { | |||
| font-size: 0.6rem; | |||
| margin: 0.5rem 0; | |||
| } | |||
| .card:hover { | |||
| width: 350px; | |||
| } | |||
| .card:hover img { | |||
| transform: scale(1.1); | |||
| } | |||
| .card:hover:after, | |||
| .card:hover .content { | |||
| opacity: 1; | |||
| } | |||
| @@ -0,0 +1,118 @@ | |||
| import { | |||
| Box, | |||
| Button, | |||
| Container, | |||
| Grid, | |||
| TextField, | |||
| Typography | |||
| } from '@mui/material'; | |||
| import { useFormik } from 'formik'; | |||
| import { useTranslation } from 'next-i18next'; | |||
| import Link from 'next/link'; | |||
| import React from 'react'; | |||
| import { BASE_PAGE } from '../../../constants/pages'; | |||
| import { contactSchema } from '../../../schemas/contactSchema'; | |||
| const ContactForm = () => { | |||
| const { t } = useTranslation('forms', 'contact', 'common'); | |||
| const handleSubmit = (values) => { | |||
| console.log('Values', values); | |||
| }; | |||
| const formik = useFormik({ | |||
| initialValues: { | |||
| firstName: '', | |||
| lastName: '', | |||
| email: '', | |||
| message: '' | |||
| }, | |||
| validationSchema: contactSchema, | |||
| onSubmit: handleSubmit, | |||
| validateOnBlur: true, | |||
| enableReinitialize: true, | |||
| }); | |||
| return ( | |||
| <Container component="main" maxWidth="md"> | |||
| <Box | |||
| sx={{ | |||
| marginTop: 32, | |||
| display: 'flex', | |||
| flexDirection: 'column', | |||
| alignItems: 'center', | |||
| }} | |||
| > | |||
| <Typography component="h1" variant="h5"> | |||
| {t('contact:Title')} | |||
| </Typography> | |||
| <Box | |||
| component="form" | |||
| onSubmit={formik.handleSubmit} | |||
| sx={{ position: 'relative', mt: 1, p: 1 }} | |||
| > | |||
| <TextField | |||
| name="firstName" | |||
| label={t('forms:FirstName')} | |||
| margin="normal" | |||
| value={formik.values.firstName} | |||
| onChange={formik.handleChange} | |||
| error={formik.touched.firstName && Boolean(formik.errors.firstName)} | |||
| helperText={formik.touched.firstName && formik.errors.firstName} | |||
| autoFocus | |||
| fullWidth | |||
| /> | |||
| <TextField | |||
| name="lastName" | |||
| label={t('forms:LastName')} | |||
| margin="normal" | |||
| value={formik.values.lastName} | |||
| onChange={formik.handleChange} | |||
| error={formik.touched.lastName && Boolean(formik.errors.lastName)} | |||
| helperText={formik.touched.lastName && formik.errors.lastName} | |||
| autoFocus | |||
| fullWidth | |||
| /> | |||
| <TextField | |||
| name="email" | |||
| label={t('forms:Email')} | |||
| margin="normal" | |||
| value={formik.values.email} | |||
| onChange={formik.handleChange} | |||
| error={formik.touched.email && Boolean(formik.errors.email)} | |||
| helperText={formik.touched.email && formik.errors.email} | |||
| autoFocus | |||
| fullWidth | |||
| /> | |||
| <TextField | |||
| name="message" | |||
| label={t('forms:Message')} | |||
| multiline | |||
| margin="normal" | |||
| value={formik.values.message} | |||
| onChange={formik.handleChange} | |||
| error={formik.touched.message && Boolean(formik.errors.message)} | |||
| helperText={formik.touched.message && formik.errors.message} | |||
| rows={4} | |||
| autoFocus | |||
| fullWidth | |||
| /> | |||
| <Button | |||
| type="submit" | |||
| variant="contained" | |||
| sx={{ mt: 3, mb: 2 }} | |||
| fullWidth | |||
| > | |||
| {t('contact:SendBtn')} | |||
| </Button> | |||
| <Grid container justifyContent="center"> | |||
| <Link href={BASE_PAGE}>{t('common:Back')}</Link> | |||
| </Grid> | |||
| </Box> | |||
| </Box> | |||
| </Container> | |||
| ); | |||
| }; | |||
| export default ContactForm; | |||
| @@ -0,0 +1,16 @@ | |||
| import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; | |||
| import ContactForm from '../components/forms/contact/ContactForm'; | |||
| const ContactPage = () => { | |||
| return <ContactForm />; | |||
| }; | |||
| export async function getStaticProps({ locale }) { | |||
| return { | |||
| props: { | |||
| ...(await serverSideTranslations(locale, ['forms', 'contact', 'common'])), | |||
| }, | |||
| }; | |||
| } | |||
| export default ContactPage; | |||
| @@ -1,6 +1,7 @@ | |||
| import { dehydrate, QueryClient } from '@tanstack/react-query'; | |||
| import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; | |||
| import Head from 'next/head'; | |||
| import HoverImageCard from '../components/cards/hover-image-card/hover-image-card'; | |||
| import PaginationComponentRQ from '../components/pagination/react-query/PaginationComponentRQ'; | |||
| import { getData } from '../requests/dataRequest'; | |||
| @@ -12,6 +13,7 @@ const Home = () => { | |||
| <meta name="description" content="Random data with pagination..." /> | |||
| </Head> | |||
| <PaginationComponentRQ></PaginationComponentRQ> | |||
| <HoverImageCard /> | |||
| </> | |||
| ); | |||
| }; | |||
| @@ -0,0 +1,4 @@ | |||
| { | |||
| "Title": "Contact", | |||
| "SendBtn": "Submit" | |||
| } | |||
| @@ -1,4 +1,7 @@ | |||
| { | |||
| "FirstName": "First name", | |||
| "LastName": "Last name", | |||
| "Message": "Message", | |||
| "FullName": "Full name", | |||
| "Username": "Username", | |||
| "Email": "Email", | |||
| @@ -0,0 +1,8 @@ | |||
| import * as Yup from 'yup'; | |||
| export const contactSchema = Yup.object().shape({ | |||
| firstName: Yup.string().required('First name is required'), | |||
| lastName: Yup.string().required('Last name is required'), | |||
| email: Yup.string().email('Enter valid email').required('Email is required'), | |||
| message: Yup.string().required('Message is required'), | |||
| }); | |||