Skip to content

Commit

Permalink
feat: adds signup pages and completes login adds begining of a steppe…
Browse files Browse the repository at this point in the history
…r compoenent
  • Loading branch information
OnlyNico43 committed Jul 20, 2024
1 parent 2fff42b commit 14e9ad3
Show file tree
Hide file tree
Showing 30 changed files with 1,112 additions and 323 deletions.
545 changes: 341 additions & 204 deletions package-lock.json

Large diffs are not rendered by default.

20 changes: 11 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@
},
"dependencies": {
"@nextui-org/react": "2.4.2",
"@phosphor-icons/react": "^2.1.7",
"axios": "1.7.2",
"framer-motion": "11.2.13",
"i18next": "23.11.5",
"formik": "^2.4.6",
"framer-motion": "11.3.2",
"i18next": "23.12.1",
"i18next-resources-to-backend": "1.2.1",
"next": "14.2.4",
"next": "14.2.5",
"next-connect": "1.0.0",
"next-i18n-router": "5.5.0",
"next-themes": "^0.3.0",
"next-i18n-router": "5.5.1",
"next-themes": "0.3.0",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-i18next": "14.1.2",
Expand All @@ -31,17 +33,17 @@
"@types/node": "20.14.10",
"@types/react": "18.3.3",
"@types/react-dom": "18.3.0",
"@typescript-eslint/eslint-plugin": "7.15.0",
"@typescript-eslint/eslint-plugin": "7.16.0",
"eslint": "8.57.0",
"eslint-config-next": "14.2.4",
"eslint-config-next": "14.2.5",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-prettier": "5.1.3",
"husky": "9.0.11",
"lint-staged": "15.2.7",
"postcss": "8.4.39",
"postcss-flexbugs-fixes": "5.0.2",
"postcss-preset-env": "9.5.16",
"prettier": "3.3.2",
"postcss-preset-env": "9.6.0",
"prettier": "3.3.3",
"prettier-plugin-sort-json": "4.0.0",
"prettier-plugin-tailwindcss": "0.6.5",
"tailwindcss": "3.4.4",
Expand Down
16 changes: 15 additions & 1 deletion public/locales/de/login.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
{
"login": "Anmelden"
"form": {
"email": { "label": "E-Mail", "placeholder": "Email eingeben" },
"password": { "label": "Passwort", "placeholder": "Passwort eingeben" }
},
"noAccount": "Noch kein Konto? Registriere dich <signup>hier</signup>.",
"title": "Anmelden",
"validation": {
"email": {
"required": "E-Mail ist erforderlich",
"invalid": "E-Mail ist ungültig"
},
"password": {
"required": "Passwort ist erforderlich"
}
}
}
35 changes: 35 additions & 0 deletions public/locales/de/signup.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"alreadyHaveAccount": "Bereits ein Konto? Melde dich <login>hier</login> an.",
"form": {
"name": { "label": "Name", "placeholder": "Name eingeben" },
"email": { "label": "E-Mail", "placeholder": "Email eingeben" },
"password": { "label": "Passwort", "placeholder": "Passwort eingeben" },
"confirmPassword": { "label": "Passwort bestätigen", "placeholder": "Passwort bestätigen" }
},
"here": "hier",
"passwordRequirements": {
"lowerCase": "Mindestens ein Kleinbuchstabe",
"upperCase": "Mindestens ein Grossbuchstaben",
"number": "Mindestens eine Zahl",
"special": "Mindestens ein Sonderzeichen",
"minLength": "Mindestens 12 Zeichen"
},
"title": "Registrieren",
"validation": {
"email": {
"invalid": "Ungültige E-Mail-Adresse",
"required": "E-Mail-Adresse ist erforderlich"
},
"name": {
"required": "Name ist erforderlich"
},
"password": {
"required": "Passwort ist erforderlich",
"invalid": "Passwort entspricht nicht den Anforderungen"
},
"confirmPassword": {
"required": "Passwort bestätigen ist erforderlich",
"match": "Passwörter stimmen nicht überein"
}
}
}
16 changes: 15 additions & 1 deletion public/locales/en/login.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
{
"login": "Login"
"form": {
"email": { "label": "E-Mail", "placeholder": "Enter email" },
"password": { "label": "Password", "placeholder": "Enter password" }
},
"noAccount": "Don't have an account yet? Register <signup>here</signup>.",
"title": "Login",
"validation": {
"email": {
"required": "Email is required",
"invalid": "Email is invalid"
},
"password": {
"required": "Password is required"
}
}
}
34 changes: 34 additions & 0 deletions public/locales/en/signup.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"alreadyHaveAccount": "Already have an account? Login <login>here</login>.",
"form": {
"name": { "label": "Name", "placeholder": "Enter name" },
"email": { "label": "E-Mail", "placeholder": "Enter email" },
"password": { "label": "Password", "placeholder": "Enter password" },
"confirmPassword": { "label": "Confirm Password", "placeholder": "Confirm password" }
},
"passwordRequirements": {
"lowerCase": "At least one lowercase letter",
"upperCase": "At least one uppercase letter",
"number": "At least one number",
"special": "At least one special character",
"minLength": "At least 12 characters long"
},
"title": "Sign Up",
"validation": {
"email": {
"invalid": "Invalid email address",
"required": "Email address is required"
},
"name": {
"required": "Name is required"
},
"password": {
"required": "Password is required",
"invalid": "Password does not meet requirements"
},
"confirmPassword": {
"required": "Confirming password is required",
"match": "Passwords do not match"
}
}
}
14 changes: 14 additions & 0 deletions src/app/[locale]/login/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
'use server';

import { APIOperation } from '@/src/services/api-services/common';
import { makeRequest } from '@/src/services/api-services/request';
import { RequestResponse } from '@/src/types/request-response.type';
import { SignupResponse } from '@/src/types/response.types';

export const login = async (email?: string, password?: string): Promise<RequestResponse<SignupResponse>> => {
const res = await makeRequest<APIOperation.LOGIN, SignupResponse>({
op: APIOperation.LOGIN,
payload: { email, password },
});
return res;
};
17 changes: 17 additions & 0 deletions src/app/[locale]/login/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { Metadata } from 'next';

export const metadata: Metadata = {
title: 'Login - EasyFlow',
description: 'Login to EasyFlow',
};

const RootLayout = ({
children,
}: Readonly<{
children: React.ReactNode;
params: { locale: string };
}>): JSX.Element => {
return <main className="flex h-screen w-screen items-center justify-center">{children}</main>;
};

export default RootLayout;
7 changes: 6 additions & 1 deletion src/app/[locale]/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import LoginForm from '@/src/components/login-form/LoginForm';
import TranslationsProvider from '@/src/components/translation-provider/TranslationsProvider';
import initTranslations from '@i18n';
import { Card, CardHeader } from '@nextui-org/react';
import { FunctionComponent } from 'react';

interface HomeProps {
Expand All @@ -15,7 +17,10 @@ const Home: FunctionComponent<HomeProps> = async ({ params: { locale } }) => {

return (
<TranslationsProvider resources={resources} locale={locale} namespaces={i18nNamespaces}>
<h1>{t('login:login')}</h1>
<Card className="w-[80%] max-w-[500px] p-5">
<CardHeader>{t('login:title')}</CardHeader>
<LoginForm />
</Card>
</TranslationsProvider>
);
};
Expand Down
17 changes: 17 additions & 0 deletions src/app/[locale]/signup/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { Metadata } from 'next';

export const metadata: Metadata = {
title: 'Signup - EasyFlow',
description: 'Signup for EasyFlow',
};

const RootLayout = ({
children,
}: Readonly<{
children: React.ReactNode;
params: { locale: string };
}>): JSX.Element => {
return <main className="flex h-screen w-screen items-center justify-center">{children}</main>;
};

export default RootLayout;
27 changes: 27 additions & 0 deletions src/app/[locale]/signup/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import SignupForm from '@/src/components/signup-form/SignupForm';
import TranslationsProvider from '@/src/components/translation-provider/TranslationsProvider';
import initTranslations from '@i18n';
import { FunctionComponent } from 'react';

interface HomeProps {
params: {
locale: string;
};
}

const i18nNamespaces = ['signup'];

const Home: FunctionComponent<HomeProps> = async ({ params: { locale } }) => {
const { t, resources } = await initTranslations(locale, i18nNamespaces);

return (
<TranslationsProvider resources={resources} locale={locale} namespaces={i18nNamespaces}>
<div>
<h3>{t('signup:title')}</h3>
<SignupForm />
</div>
</TranslationsProvider>
);
};

export default Home;
24 changes: 18 additions & 6 deletions src/app/api/route.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import { serverSideRequest } from '@/src/services/api-services/server-side';
import { AxiosError } from 'axios';
import { NextApiRequest, NextApiResponse } from 'next';
import { NextRequest, NextResponse } from 'next/server';

export const POST = async (req: NextApiRequest, res: NextApiResponse): Promise<void> => {
export const POST = async (req: NextRequest): Promise<NextResponse> => {
try {
const response = await serverSideRequest(req, req.body);
res.setHeader('Set-Cookie', response.headers['set-cookie'] ? response.headers['set-cookie'] : []);
res.status(200).send(response.data);
const data = await req.json();
const response = await serverSideRequest(data);
const res = NextResponse.json(response.data, {
status: response.status,
statusText: response.statusText,
// eslint-disable-next-line
headers: Object.keys(response.headers).map(key => [key, response.headers[key].toString()]),
});
return res;
} catch (err) {
if (!(err instanceof AxiosError)) throw err;
const msg = {
Expand All @@ -21,7 +27,13 @@ export const POST = async (req: NextApiRequest, res: NextApiResponse): Promise<v
console.error(msg);

const statusCode = err.response?.status ?? 500;
const statusText = err.response?.statusText ?? 'Internal Server Error';

res.status(statusCode).send(err.response?.data ?? { statusCode, message: 'Internal Server Error' });
const res = NextResponse.json(err.response?.data ?? { statusCode, message: 'Internal Server Error' }, {
status: statusCode,
statusText: statusText,
});

return res;
}
};
83 changes: 83 additions & 0 deletions src/components/login-form/LoginForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
'use client';
import { login } from '@/src/app/[locale]/login/actions';
import { LoginType } from '@/src/types/login.type';
import { Button, Input, Link } from '@nextui-org/react';
import { Form, Formik } from 'formik';
import { FunctionComponent, ReactElement } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import PasswordInput from '../password-input/PasswordInput';
import createValidationSchema from './validation-schema';

const LoginForm: FunctionComponent = (): ReactElement => {
const { t } = useTranslation();

const validationSchema = createValidationSchema(t);

const initialValues: LoginType = {
email: undefined,
password: undefined,
};

return (
<>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={async values => {
const res = await login(values.email, values.password);
if (!res.success) {
console.log(res.errorCode);
} else {
console.log('Login success');
}
}}
>
{({ setFieldTouched, setFieldValue, values, errors, touched, isSubmitting, submitCount, isValid }) => (
<Form>
<Input
className="mb-3"
type="text"
label={t('login:form.email.label')}
placeholder={t('login:form.email.placeholder')}
value={values.email}
onChange={e => setFieldValue('email', e.target.value)}
onBlur={() => setFieldTouched('email', true)}
isInvalid={touched.email && !!errors.email}
errorMessage={errors.email ? errors.email : undefined}
isRequired
/>
<PasswordInput
label={t('login:form.password.label')}
value={values.password}
placeholder={t('login:form.password.placeholder')}
onChange={e => setFieldValue('password', e.target.value)}
onBlur={() => setFieldTouched('password', true)}
touched={!!touched.password}
error={errors.password ? errors.password : undefined}
/>
<Button
color="primary"
type="submit"
isLoading={isSubmitting}
isDisabled={submitCount > 0 && !isValid}
fullWidth
>
{t('login:title')}
</Button>
</Form>
)}
</Formik>

<p className="mt-5 text-center text-content4-foreground">
<Trans
i18nKey="login:noAccount"
components={{
signup: <Link href="/signup" underline="hover" />,
}}
/>
</p>
</>
);
};

export default LoginForm;
12 changes: 12 additions & 0 deletions src/components/login-form/validation-schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { TFunction } from 'i18next';
import { object, string } from 'yup';

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
const createValidationSchema = (t: TFunction) => {
return object().shape({
email: string().required(t('validation.email.required')).email(t('validation.email.invalid')),
password: string().required(t('validation.password.required')),
});
};

export default createValidationSchema;
Loading

0 comments on commit 14e9ad3

Please sign in to comment.