main
This commit is contained in:
15
src/app/[locale]/(auth)/layout.tsx
Normal file
15
src/app/[locale]/(auth)/layout.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
'use client';
|
||||
|
||||
import Footer from '@/components/layout/footer/footer';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
|
||||
function AuthLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<Flex minH='100vh' direction='column'>
|
||||
<Box as='main'>{children}</Box>
|
||||
<Footer />
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
export default AuthLayout;
|
||||
231
src/app/[locale]/(auth)/signin/page.tsx
Normal file
231
src/app/[locale]/(auth)/signin/page.tsx
Normal file
@@ -0,0 +1,231 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
Heading,
|
||||
Input,
|
||||
Link as ChakraLink,
|
||||
Text,
|
||||
ClientOnly,
|
||||
} from "@chakra-ui/react";
|
||||
import { Button } from "@/components/ui/buttons/button";
|
||||
import { Switch } from "@/components/ui/forms/switch";
|
||||
import { Field } from "@/components/ui/forms/field";
|
||||
import { useTranslations } from "next-intl";
|
||||
import signInImage from "/public/assets/img/sign-in-image.png";
|
||||
import { InputGroup } from "@/components/ui/forms/input-group";
|
||||
import { BiLock } from "react-icons/bi";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import * as yup from "yup";
|
||||
import { Link, useRouter } from "@/i18n/navigation";
|
||||
import { MdMail } from "react-icons/md";
|
||||
import { PasswordInput } from "@/components/ui/forms/password-input";
|
||||
import { Skeleton } from "@/components/ui/feedback/skeleton";
|
||||
import { signIn } from "next-auth/react";
|
||||
import { toaster } from "@/components/ui/feedback/toaster";
|
||||
import { useState } from "react";
|
||||
|
||||
const schema = yup.object({
|
||||
email: yup.string().email().required(),
|
||||
password: yup.string().required(),
|
||||
});
|
||||
|
||||
type SignInForm = yup.InferType<typeof schema>;
|
||||
|
||||
const defaultValues = {
|
||||
email: "test@test.com.tr",
|
||||
password: "test1234",
|
||||
};
|
||||
|
||||
function SignInPage() {
|
||||
const t = useTranslations();
|
||||
const router = useRouter();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
register,
|
||||
formState: { errors },
|
||||
} = useForm<SignInForm>({
|
||||
resolver: yupResolver(schema),
|
||||
mode: "onChange",
|
||||
defaultValues,
|
||||
});
|
||||
|
||||
const onSubmit = async (formData: SignInForm) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const res = await signIn("credentials", {
|
||||
redirect: false,
|
||||
email: formData.email,
|
||||
password: formData.password,
|
||||
});
|
||||
|
||||
if (res?.error) {
|
||||
throw new Error(res.error);
|
||||
}
|
||||
|
||||
router.replace("/home");
|
||||
} catch (error) {
|
||||
toaster.error({
|
||||
title: (error as Error).message || "Giriş yaparken hata oluştu!",
|
||||
type: "error",
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box position="relative">
|
||||
<Flex
|
||||
h={{ sm: "initial", md: "75vh", lg: "85vh" }}
|
||||
w="100%"
|
||||
maxW="1044px"
|
||||
mx="auto"
|
||||
justifyContent="space-between"
|
||||
mb="30px"
|
||||
pt={{ sm: "100px", md: "0px" }}
|
||||
>
|
||||
<Flex
|
||||
as="form"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
alignItems="center"
|
||||
justifyContent="start"
|
||||
style={{ userSelect: "none" }}
|
||||
w={{ base: "100%", md: "50%", lg: "42%" }}
|
||||
>
|
||||
<Flex
|
||||
direction="column"
|
||||
w="100%"
|
||||
background="transparent"
|
||||
p="10"
|
||||
mt={{ md: "150px", lg: "80px" }}
|
||||
>
|
||||
<Heading
|
||||
color={{ base: "primary.400", _dark: "primary.200" }}
|
||||
fontSize="32px"
|
||||
mb="10px"
|
||||
fontWeight="bold"
|
||||
>
|
||||
{t("auth.welcome-back")}
|
||||
</Heading>
|
||||
<Text
|
||||
mb="36px"
|
||||
ms="4px"
|
||||
color={{ base: "gray.400", _dark: "white" }}
|
||||
fontWeight="bold"
|
||||
fontSize="14px"
|
||||
>
|
||||
{t("auth.subtitle")}
|
||||
</Text>
|
||||
<Field
|
||||
mb="24px"
|
||||
label={t("email")}
|
||||
errorText={errors.email?.message}
|
||||
invalid={!!errors.email}
|
||||
>
|
||||
<InputGroup w="full" startElement={<MdMail size="1rem" />}>
|
||||
<Input
|
||||
borderRadius="15px"
|
||||
fontSize="sm"
|
||||
type="text"
|
||||
placeholder={t("email")}
|
||||
size="lg"
|
||||
{...register("email")}
|
||||
/>
|
||||
</InputGroup>
|
||||
</Field>
|
||||
<Field
|
||||
mb="24px"
|
||||
label={t("password")}
|
||||
errorText={errors.password?.message}
|
||||
invalid={!!errors.password}
|
||||
>
|
||||
<InputGroup w="full" startElement={<BiLock size="1rem" />}>
|
||||
<PasswordInput
|
||||
borderRadius="15px"
|
||||
fontSize="sm"
|
||||
placeholder={t("password")}
|
||||
size="lg"
|
||||
{...register("password")}
|
||||
/>
|
||||
</InputGroup>
|
||||
</Field>
|
||||
<Field mb="24px">
|
||||
<Switch colorPalette="teal" label={t("auth.remember-me")}>
|
||||
{t("auth.remember-me")}
|
||||
</Switch>
|
||||
</Field>
|
||||
<Field mb="24px">
|
||||
<ClientOnly fallback={<Skeleton height="45px" width="100%" />}>
|
||||
<Button
|
||||
loading={loading}
|
||||
type="submit"
|
||||
bg="primary.400"
|
||||
w="100%"
|
||||
h="45px"
|
||||
color="white"
|
||||
_hover={{
|
||||
bg: "primary.500",
|
||||
}}
|
||||
_active={{
|
||||
bg: "primary.400",
|
||||
}}
|
||||
>
|
||||
{t("auth.sign-in")}
|
||||
</Button>
|
||||
</ClientOnly>
|
||||
</Field>
|
||||
<Flex
|
||||
flexDirection="column"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
maxW="100%"
|
||||
>
|
||||
<Text
|
||||
color={{ base: "gray.400", _dark: "white" }}
|
||||
fontWeight="medium"
|
||||
>
|
||||
{t("auth.dont-have-account")}
|
||||
<ChakraLink
|
||||
as={Link}
|
||||
href="/signup"
|
||||
color={{ base: "primary.400", _dark: "primary.200" }}
|
||||
ms="5px"
|
||||
fontWeight="bold"
|
||||
focusRing="none"
|
||||
>
|
||||
{t("auth.sign-up")}
|
||||
</ChakraLink>
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Box
|
||||
display={{ base: "none", md: "block" }}
|
||||
overflowX="hidden"
|
||||
h="100%"
|
||||
w="40vw"
|
||||
position="absolute"
|
||||
right="0px"
|
||||
>
|
||||
<Box
|
||||
bgImage={`url(${signInImage.src})`}
|
||||
w="100%"
|
||||
h="100%"
|
||||
bgSize="cover"
|
||||
bgPos="50%"
|
||||
position="absolute"
|
||||
borderBottomLeftRadius="20px"
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default SignInPage;
|
||||
166
src/app/[locale]/(auth)/signup/page.tsx
Normal file
166
src/app/[locale]/(auth)/signup/page.tsx
Normal file
@@ -0,0 +1,166 @@
|
||||
'use client';
|
||||
|
||||
import { Box, Flex, Input, Link as ChakraLink, Text, ClientOnly } from '@chakra-ui/react';
|
||||
import signUpImage from '/public/assets/img/sign-up-image.png';
|
||||
import { Button } from '@/components/ui/buttons/button';
|
||||
import { Field } from '@/components/ui/forms/field';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import * as yup from 'yup';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { InputGroup } from '@/components/ui/forms/input-group';
|
||||
import { BiLock, BiUser } from 'react-icons/bi';
|
||||
import { Link } from '@/i18n/navigation';
|
||||
import { MdMail } from 'react-icons/md';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { PasswordInput } from '@/components/ui/forms/password-input';
|
||||
import { Skeleton } from '@/components/ui/feedback/skeleton';
|
||||
|
||||
const schema = yup.object({
|
||||
name: yup.string().required(),
|
||||
email: yup.string().email().required(),
|
||||
password: yup.string().required(),
|
||||
});
|
||||
|
||||
type SignUpForm = yup.InferType<typeof schema>;
|
||||
|
||||
function SignUpPage() {
|
||||
const t = useTranslations();
|
||||
const router = useRouter();
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
register,
|
||||
formState: { errors },
|
||||
} = useForm<SignUpForm>({ resolver: yupResolver(schema), mode: 'onChange' });
|
||||
|
||||
const onSubmit = async (formData: SignUpForm) => {
|
||||
router.replace('/signin');
|
||||
return formData;
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box
|
||||
position='absolute'
|
||||
minH={{ base: '70vh', md: '50vh' }}
|
||||
w={{ md: 'calc(100vw - 50px)' }}
|
||||
borderRadius={{ md: '15px' }}
|
||||
left='0'
|
||||
right='0'
|
||||
bgRepeat='no-repeat'
|
||||
overflow='hidden'
|
||||
zIndex='-1'
|
||||
top='0'
|
||||
bgImage={`url(${signUpImage.src})`}
|
||||
bgSize='cover'
|
||||
mx={{ md: 'auto' }}
|
||||
mt={{ md: '14px' }}
|
||||
/>
|
||||
<Flex w='full' h='full' direction='column' alignItems='center' justifyContent='center'>
|
||||
<Text
|
||||
fontSize={{ base: '2xl', md: '3xl', lg: '4xl' }}
|
||||
color='white'
|
||||
fontWeight='bold'
|
||||
mt={{ base: '2rem', md: '4.5rem', '2xl': '6.5rem' }}
|
||||
mb={{ base: '2rem', md: '3rem', '2xl': '4rem' }}
|
||||
>
|
||||
{t('auth.create-an-account-now')}
|
||||
</Text>
|
||||
<Flex
|
||||
direction='column'
|
||||
w={{ base: '100%', md: '445px' }}
|
||||
background='transparent'
|
||||
borderRadius='15px'
|
||||
p='10'
|
||||
mx={{ base: '100px' }}
|
||||
bg='bg.panel'
|
||||
boxShadow='0 20px 27px 0 rgb(0 0 0 / 5%)'
|
||||
mb='8'
|
||||
>
|
||||
<Flex
|
||||
as='form'
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
flexDirection='column'
|
||||
alignItems='center'
|
||||
justifyContent='start'
|
||||
w='100%'
|
||||
>
|
||||
<Field mb='24px' label={t('name')} errorText={errors.name?.message} invalid={!!errors.name}>
|
||||
<InputGroup w='full' startElement={<BiUser size='1rem' />}>
|
||||
<Input
|
||||
borderRadius='15px'
|
||||
fontSize='sm'
|
||||
type='text'
|
||||
placeholder={t('name')}
|
||||
size='lg'
|
||||
{...register('name')}
|
||||
/>
|
||||
</InputGroup>
|
||||
</Field>
|
||||
<Field mb='24px' label={t('email')} errorText={errors.email?.message} invalid={!!errors.email}>
|
||||
<InputGroup w='full' startElement={<MdMail size='1rem' />}>
|
||||
<Input
|
||||
borderRadius='15px'
|
||||
fontSize='sm'
|
||||
type='text'
|
||||
placeholder={t('email')}
|
||||
size='lg'
|
||||
{...register('email')}
|
||||
/>
|
||||
</InputGroup>
|
||||
</Field>
|
||||
<Field mb='24px' label={t('password')} errorText={errors.password?.message} invalid={!!errors.password}>
|
||||
<InputGroup w='full' startElement={<BiLock size='1rem' />}>
|
||||
<PasswordInput
|
||||
borderRadius='15px'
|
||||
fontSize='sm'
|
||||
placeholder={t('password')}
|
||||
size='lg'
|
||||
{...register('password')}
|
||||
/>
|
||||
</InputGroup>
|
||||
</Field>
|
||||
<Field mb='24px'>
|
||||
<ClientOnly fallback={<Skeleton height='45px' width='100%' />}>
|
||||
<Button
|
||||
type='submit'
|
||||
bg='primary.400'
|
||||
color='white'
|
||||
fontWeight='bold'
|
||||
w='100%'
|
||||
h='45px'
|
||||
_hover={{
|
||||
bg: 'primary.500',
|
||||
}}
|
||||
_active={{
|
||||
bg: 'primary.400',
|
||||
}}
|
||||
>
|
||||
{t('auth.sign-up')}
|
||||
</Button>
|
||||
</ClientOnly>
|
||||
</Field>
|
||||
</Flex>
|
||||
<Flex flexDirection='column' justifyContent='center' alignItems='center' maxW='100%'>
|
||||
<Text color={{ base: 'gray.400', _dark: 'white' }} fontWeight='medium'>
|
||||
{t('auth.already-have-an-account')}
|
||||
<ChakraLink
|
||||
as={Link}
|
||||
color={{ base: 'primary.400', _dark: 'primary.200' }}
|
||||
ml='2'
|
||||
href='/signin'
|
||||
fontWeight='bold'
|
||||
focusRing='none'
|
||||
>
|
||||
{t('auth.sign-in')}
|
||||
</ChakraLink>
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default SignUpPage;
|
||||
5
src/app/[locale]/(error)/[...slug]/page.tsx
Normal file
5
src/app/[locale]/(error)/[...slug]/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
export default function CatchAllPage() {
|
||||
notFound();
|
||||
}
|
||||
7
src/app/[locale]/(site)/about/page.tsx
Normal file
7
src/app/[locale]/(site)/about/page.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
function AboutPage() {
|
||||
return <div>AboutPage</div>;
|
||||
}
|
||||
|
||||
export default AboutPage;
|
||||
14
src/app/[locale]/(site)/home/page.tsx
Normal file
14
src/app/[locale]/(site)/home/page.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import HomeCard from "@/components/site/home/home-card";
|
||||
|
||||
export async function generateMetadata() {
|
||||
const t = await getTranslations();
|
||||
|
||||
return {
|
||||
title: `${t("home")} | FCS`,
|
||||
};
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
return <HomeCard />;
|
||||
}
|
||||
21
src/app/[locale]/(site)/layout.tsx
Normal file
21
src/app/[locale]/(site)/layout.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
'use client';
|
||||
|
||||
import { Container, Flex } from '@chakra-ui/react';
|
||||
import Header from '@/components/layout/header/header';
|
||||
import Footer from '@/components/layout/footer/footer';
|
||||
import BackToTop from '@/components/ui/back-to-top';
|
||||
|
||||
function MainLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<Flex minH='100vh' direction='column'>
|
||||
<Header />
|
||||
<Container as='main' maxW='8xl' flex='1' py={4}>
|
||||
{children}
|
||||
</Container>
|
||||
<BackToTop />
|
||||
<Footer />
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
export default MainLayout;
|
||||
BIN
src/app/[locale]/.DS_Store
vendored
Normal file
BIN
src/app/[locale]/.DS_Store
vendored
Normal file
Binary file not shown.
7
src/app/[locale]/global.css
Normal file
7
src/app/[locale]/global.css
Normal file
@@ -0,0 +1,7 @@
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
41
src/app/[locale]/layout.tsx
Normal file
41
src/app/[locale]/layout.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Provider } from '@/components/ui/provider';
|
||||
import { Bricolage_Grotesque } from 'next/font/google';
|
||||
import { hasLocale, NextIntlClientProvider } from 'next-intl';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { routing } from '@/i18n/routing';
|
||||
import { dir } from 'i18next';
|
||||
import './global.css';
|
||||
|
||||
const bricolage = Bricolage_Grotesque({
|
||||
variable: '--font-bricolage',
|
||||
subsets: ['latin'],
|
||||
});
|
||||
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
params,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
params: Promise<{ locale: string }>;
|
||||
}) {
|
||||
const { locale } = await params;
|
||||
if (!hasLocale(routing.locales, locale)) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
return (
|
||||
<html lang={locale} dir={dir(locale)} suppressHydrationWarning data-scroll-behavior='smooth'>
|
||||
<head>
|
||||
<link rel='apple-touch-icon' sizes='180x180' href='/favicon/apple-touch-icon.png' />
|
||||
<link rel='icon' type='image/png' sizes='32x32' href='/favicon/favicon-32x32.png' />
|
||||
<link rel='icon' type='image/png' sizes='16x16' href='/favicon/favicon-16x16.png' />
|
||||
<link rel='manifest' href='/favicon/site.webmanifest' />
|
||||
</head>
|
||||
<body className={bricolage.variable}>
|
||||
<NextIntlClientProvider>
|
||||
<Provider>{children}</Provider>
|
||||
</NextIntlClientProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
30
src/app/[locale]/not-found.tsx
Normal file
30
src/app/[locale]/not-found.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Link } from '@/i18n/navigation';
|
||||
import { Flex, Text, Button, VStack, Heading } from '@chakra-ui/react';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
|
||||
export default async function NotFoundPage() {
|
||||
const t = await getTranslations();
|
||||
|
||||
return (
|
||||
<Flex h='100vh' alignItems='center' justifyContent='center' textAlign='center' px={6}>
|
||||
<VStack spaceY={6}>
|
||||
<Heading
|
||||
as='h1'
|
||||
fontSize={{ base: '5xl', md: '6xl' }}
|
||||
fontWeight='bold'
|
||||
color={{ base: 'primary.600', _dark: 'primary.400' }}
|
||||
>
|
||||
{t('error.404')}
|
||||
</Heading>
|
||||
<Text fontSize={{ base: 'md', md: 'lg' }} color={{ base: 'fg.muted', _dark: 'white' }}>
|
||||
{t('error.not-found')}
|
||||
</Text>
|
||||
<Link href='/home' passHref>
|
||||
<Button size={{ base: 'md', md: 'lg' }} rounded='md'>
|
||||
{t('error.back-to-home')}
|
||||
</Button>
|
||||
</Link>
|
||||
</VStack>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
5
src/app/[locale]/page.tsx
Normal file
5
src/app/[locale]/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
export default async function Page() {
|
||||
redirect('/home');
|
||||
}
|
||||
Reference in New Issue
Block a user