Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor/Features and create page #270

Merged
merged 1 commit into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
213 changes: 166 additions & 47 deletions package-lock.json

Large diffs are not rendered by default.

13 changes: 5 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,6 @@
"@heroicons/react": "^1.0.6",
"@next-auth/prisma-adapter": "^1.0.6",
"@prisma/client": "^4.14.0",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3",
"@types/emoji-mart": "^3.0.9",
"@types/jest": "^29.5.0",
"@types/node": "^18.15.3",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"axios": "^1.6.0",
"bcrypt": "^5.1.0",
"clsx": "^1.1.1",
Expand Down Expand Up @@ -60,8 +52,13 @@
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3",
"@types/bcrypt": "^5.0.0",
"@types/emoji-mart": "^3.0.9",
"@types/jest": "^29.5.0",
"@types/node": "^18.15.3",
"@types/nprogress": "^0.2.0",
"@types/react": "^18.0.28",
"@types/react-beautiful-dnd": "^13.1.4",
"@types/react-dom": "^18.0.11",
"@types/uuid": "^9.0.1",
"@typescript-eslint/eslint-plugin": "^5.56.0",
"@typescript-eslint/parser": "^5.56.0",
Expand Down
97 changes: 97 additions & 0 deletions src/features/account/Account.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { TrashIcon } from '@heroicons/react/outline';

import useTranslation from 'next-translate/useTranslation';
import { useAccountManager } from 'features/account/accountManager';
import Button, { ButtonVariant } from 'shared/components/Button/Button';
import StyledDialog from 'shared/components/StyledDialog/StyledDialog';
import Avatar from 'shared/components/Avatar/Avatar';
import { formatDateDistance } from 'shared/utilities/convertTime';
import Header from 'shared/components/Header/Header';

export default function Account() {
const { t } = useTranslation('account');

const {
user,
isOpen,
openDeleteModal,
closeDeleteModal,
handleOnAccountDelete,
isRemoving,
} = useAccountManager();

return (
<>
<Header>Your account</Header>

{user ? (
<div className="mt-4 flex w-full items-center justify-center gap-12 text-left">
<Avatar size={160} src={user.image} classNames="border" />

<div className="mb-4">
<h2 className="mb-2 text-2xl">{user?.name}</h2>
<p>{user?.email}</p>
<p>Account created: {formatDateDistance(user.createdAt)}</p>
</div>
</div>
) : (
<></>
)}

{process.env.NEXT_PUBLIC_REMOVE_ACCOUNT && (
<>
<div className="flex flex-col items-center justify-center space-y-2">
<div className="flex w-full md:ml-2 md:w-auto">
<Button
variant={ButtonVariant.DANGER}
title={t('deleteAccountButtonTitle')}
className="ml-2 mt-2 w-full justify-center px-3 sm:mt-0 md:w-auto"
onClick={openDeleteModal}
icon={<TrashIcon className="h-5 w-5" />}
>
{t('deleteAccountButton')}
</Button>
</div>
</div>
<StyledDialog
isOpen={isOpen}
onClose={closeDeleteModal}
title={t('dialogTitle')}
content={
<>
<div className="mt-2">
<p className="text-sm text-red-500">
{t('dialogContentFirst')}&nbsp;
<span className="font-bold">
{t('dialogContentSecond')}
</span>{' '}
{t('dialogContentThird')}
</p>
</div>
<div className="mt-6 flex justify-between space-x-3">
<Button
variant={ButtonVariant.SECONDARY}
onClick={closeDeleteModal}
className="uppercase"
disabled={isRemoving}
>
{t('buttonCancle')}
</Button>
<Button
variant={ButtonVariant.DANGER}
onClick={handleOnAccountDelete}
icon={<TrashIcon className="h-5 w-5" />}
className="uppercase"
isLoading={isRemoving}
>
{t('buttonConfirm')}
</Button>
</div>
</>
}
/>
</>
)}
</>
);
}
16 changes: 9 additions & 7 deletions src/features/application/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,17 @@ import data from '@emoji-mart/data/sets/14/apple.json';
import { init as emojisInit } from 'emoji-mart';
import { EMOJI_STYLE, customEmojisData } from 'shared/constants/emojisConfig';

export interface ApplicationManager {
user: User | undefined;
loading: boolean;
error: boolean;
}

export const useApplicationManager = (): ApplicationManager => {
export const useApplicationManager = () => {
const [loading, setIsLoading] = useState(true);
const [error, setError] = useState(false);
const [user, setUser] = useState<User>();
const [isBrowser, setIsBrowser] = useState(false);

useEffect(() => {
if (typeof window !== 'undefined') {
setIsBrowser(true);
}

init();
}, []);

Expand All @@ -37,5 +36,8 @@ export const useApplicationManager = (): ApplicationManager => {
user,
loading,
error,
isBrowser,
};
};

export type ApplicationManager = ReturnType<typeof useApplicationManager>;
100 changes: 100 additions & 0 deletions src/features/authorization/LoginCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { Form, Formik } from 'formik';
import Link from 'next/link';
import Header from 'shared/components/Header/Header';
import LoginButton from 'shared/components/LoginButton/LoginButton';
import Input from 'shared/components/Input/Input';
import { useLoginManager } from 'features/authorization/managers/loginManager';
import Github from '../../../public/images/github.svg';
import Google from '../../../public/images/google.svg';
import useTranslation from 'next-translate/useTranslation';
import AuthFormWrapper from 'features/authorization/components/AuthFormWrapper';

export default function LoginCard() {
const { t } = useTranslation('login');

const {
initialValues,
LoginSchema,
onSubmit,
onGoogleLogin,
onGithubLogin,
isSubmitting,
isGoogleLoading,
isGithubLoading,
} = useLoginManager();

return (
<AuthFormWrapper>
<Header>{t('login:heading')}</Header>
<Formik
initialValues={initialValues}
onSubmit={onSubmit}
validationSchema={LoginSchema}
>
{({ values, errors, handleChange, handleSubmit, touched }) => (
<Form className="flex w-full flex-col">
<LoginButton
isLoading={isGoogleLoading}
image={Google}
onClick={onGoogleLogin}
>
{t('login:googleButton')}
</LoginButton>
<LoginButton
image={Github}
isLoading={isGithubLoading}
onClick={onGithubLogin}
className="mb-3"
>
{t('login:githubButton')}
</LoginButton>
<p>{t('login:or')}</p>

<Input
type="email"
value={values.email}
required
error={touched.email ? errors.email : undefined}
placeholder={t('login:email')}
className="!mb-1 mt-3"
onChange={handleChange('email')}
/>
<Input
type="password"
value={values.password}
error={touched.password ? errors.password : undefined}
required
placeholder={t('login:password')}
className="!my-1"
onChange={handleChange('password')}
/>

{!!errors.message && (
<p className="mb-4 max-w-sm self-center text-center text-sm text-red-300">
{errors.message}
</p>
)}
<div className="flex flex-col items-center justify-center">
<LoginButton
className="mb-2 mt-1 border-indigo-200 !bg-indigo-200 !text-indigo-900 hover:!bg-indigo-300"
type="submit"
onClick={handleSubmit}
isLoading={isSubmitting}
>
{t('login:signInButton')}
</LoginButton>
</div>
<Link scroll={false} href={'/signup'} passHref>
<p
data-test-id="signup-link"
className="mt-2 text-center text-sm text-zinc-600 underline hover:cursor-pointer"
>
{t('login:dontHaveAccount')}
</p>
</Link>
</Form>
)}
</Formik>
</AuthFormWrapper>
);
}
80 changes: 80 additions & 0 deletions src/features/authorization/SignUpCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { Form, Formik } from 'formik';
import Link from 'next/link';
import Header from 'shared/components/Header/Header';
import LoginButton from 'shared/components/LoginButton/LoginButton';
import Input from 'shared/components/Input/Input';
import { useRegisterManager } from 'features/authorization/managers/registerManager';
import useTranslation from 'next-translate/useTranslation';
import AuthFormWrapper from 'features/authorization/components/AuthFormWrapper';

export default function SignUpCard() {
const { t } = useTranslation('signup');

const { initialValues, onSubmit, SignupSchema, isRegistering } =
useRegisterManager();

return (
<AuthFormWrapper>
<Header>{t('heading')}</Header>
<Formik
initialValues={initialValues}
onSubmit={onSubmit}
validationSchema={SignupSchema}
>
{({ values, errors, handleChange, handleSubmit, touched }) => (
<Form className="flex w-full flex-col">
<Input
type="text"
name="name"
value={values.name}
required
error={touched.name ? errors.name : undefined}
placeholder={t('name')}
onChange={handleChange('name')}
className="!my-1"
/>
<Input
type="email"
className="!my-1"
value={values.email}
required
error={touched.email ? errors.email : undefined}
placeholder={t('email')}
onChange={handleChange('email')}
/>
<Input
type="password"
className="!my-1"
value={values.password}
error={touched.password ? errors.password : undefined}
required
placeholder={t('password')}
onChange={handleChange('password')}
/>
{!!errors.message && (
<p className="mb-4 max-w-sm self-center text-center text-sm text-red-300">
{errors.message}
</p>
)}

<div className="flex flex-col items-center justify-center">
<LoginButton
className="mb-2 mt-1 border-indigo-200 !bg-indigo-200 !text-indigo-900 hover:!bg-indigo-300"
type="submit"
onClick={handleSubmit}
isLoading={isRegistering}
>
{t('signUpButton')}
</LoginButton>
</div>
<Link scroll={false} href={'/login'} passHref>
<p className="mt-2 text-center text-sm text-zinc-600 underline hover:cursor-pointer">
{t('alreadyHaveAccount')}
</p>
</Link>
</Form>
)}
</Formik>
</AuthFormWrapper>
);
}
Loading