Skip to content

Commit

Permalink
[FE][CPF-37] Profile page (#50)
Browse files Browse the repository at this point in the history
* feat: add profile page

* fix: svg properties

* build: add react-easy-crop package

* feat: add Avatar component

* feat: crop uploaded image

* linter

* build: add styled components

* linter

* feat: add Button component

* fix: Avatar size

* linter

* refactor: types

* refactor: profile settings

* feat: save cropped image

* linter

* fix: console errors

* linter

* feat: add delete confirmation modal

* linter

* feat: add user navigation

* fix: types

* linter

* feat: add const instead of hardcoded value

* feat: add styled components theme

* feat: add missing 'use client'

* feat: remove styled components

* linter

* chore: remove redundant file

* linter

* chore: add missing class

* chore: use routes from constant

* linter

* refactor: move handlers to hooks

* refactor: move mocked data out of component scope

* refactor: use Typography

* linter

* refactor: use Typography

* linter
  • Loading branch information
wiktoriasalamon authored Jul 3, 2024
1 parent 13e1d8f commit 3ce6a33
Show file tree
Hide file tree
Showing 48 changed files with 772 additions and 77 deletions.
1 change: 1 addition & 0 deletions frontend/.husky/pre-commit
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
cd frontend

yarn lint:fix && yarn format
yarn compile
2 changes: 1 addition & 1 deletion frontend/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const nextConfig = {
hostname: 'tailwindui.com',
pathname: '/img/logos/mark.svg',
}],
}
},
};

export default nextConfig;
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"next": "14.1.3",
"react": "^18",
"react-dom": "^18",
"react-easy-crop": "^5.0.7",
"react-markdown": "^9.0.1",
"react-tooltip": "^5.26.3",
"remark-gfm": "^4.0.0",
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/api/bucket.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { mapKeysToCamelCase } from '@app/utils';
import { API_URLS } from '.';
import { Bucket } from '@app/types/common';
import { Bucket } from '@app/types/library';

async function getBucketDetails(slug: string) {
const response = await fetch(`${API_URLS.library.buckets}/${slug}`);
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/api/ladder.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { mapKeysToCamelCase } from '@app/utils';
import { API_URLS } from '.';
import { LadderCardInterface } from '@app/components/common/LadderCard';
import { LadderBand } from '@app/types/common';
import { LadderBand } from '@app/types/library';

async function getLadders() {
const response = await fetch(API_URLS.library.ladders);
Expand Down
35 changes: 35 additions & 0 deletions frontend/src/app/(app)/people/my-profile/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Breadcrumbs } from '@app/components/modules/Breadcrumbs';
import { ProfileSettings } from '@app/components/modules/ProfileSetting';
import { routes } from '@app/constants';

// TODO: get data from api
const data = {
firstName: 'Jane',
lastName: 'Edge',
email: 'example@gmail.com',
ladders: [
{
ladderName: 'Front end',
technology: 'React',
band: 2,
},
],
notifications: {
slack: false,
email: false,
},
};

export default async function LibraryPage() {
return (
<div>
<Breadcrumbs
breadcrumbs={[
{ label: 'People', href: routes.people.index, current: false },
{ label: 'Profile setting', href: routes.people.myProfile, current: true },
]}
/>
<ProfileSettings data={data} />
</div>
);
}
8 changes: 8 additions & 0 deletions frontend/src/components/common/Avatar/Avatar.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export interface AvatarProps {
variant?: AvatarVariant;
firstName: string;
lastName: string;
imageUrl?: string | undefined;
}

export type AvatarVariant = '28' | '40' | '56' | '72' | '100' | '320';
34 changes: 34 additions & 0 deletions frontend/src/components/common/Avatar/Avatar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import Image from 'next/image';
import { AvatarProps, AvatarVariant } from './Avatar.interface';
import { generateClassNames } from '@app/utils';

const variants: {
[key in AvatarVariant]: string;
} = {
'28': 'w-7 h-7 text-sm text-xs',
'40': 'w-10 h-10 text-base',
'56': 'w-14 h-14 text-xl',
'72': 'w-[72px] h-[72px] text-2xl',
'100': 'w-[100px] h-[100px] text-4xl',
'320': 'w-80 h-80 text-5xl',
};

export const Avatar: React.FC<AvatarProps> = ({ firstName, lastName, imageUrl, variant = '40' }) => {
const avatarClass = generateClassNames(
'rounded-full bg-blue-700 flex justify-center items-center',
variants[variant],
);

return (
<div className={avatarClass}>
{imageUrl ? (
<Image className="inline-block h-full w-full rounded-full" src={imageUrl} alt="User avatar" />
) : (
<span className="text-white">
{firstName[0]}
{lastName[0]}
</span>
)}
</div>
);
};
1 change: 1 addition & 0 deletions frontend/src/components/common/Avatar/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Avatar } from './Avatar';
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LadderBandBucket } from '@app/types/common';
import { LadderBandBucket } from '@app/types/library';

export interface BucketCardProps {
ladderSlug: string;
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/components/common/Card/Card.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface CardProps {
title: string;
}
16 changes: 16 additions & 0 deletions frontend/src/components/common/Card/Card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { CardProps } from './Card.interface';
import { PropsWithChildren } from 'react';
import { Typography } from '@app/components/common/Typography';

export const Card: React.FC<PropsWithChildren<CardProps>> = ({ title, children }) => {
return (
<div className="rounded-xl bg-white shadow-sm ring-1 ring-navy-200/5 lg:w-[750px]">
<div className="px-4 py-6 sm:p-8">
<Typography as="h3" variant="head-s/semibold">
{title}
</Typography>
<div className="py-8">{children}</div>
</div>
</div>
);
};
1 change: 1 addition & 0 deletions frontend/src/components/common/Card/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Card } from './Card';
1 change: 1 addition & 0 deletions frontend/src/components/common/Modal/Modal.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export interface ModalProps extends PropsWithChildren {
open: boolean;
onClose: () => void;
title: string;
hideHeaderCloseButton?: boolean;
}
19 changes: 9 additions & 10 deletions frontend/src/components/common/Modal/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Inter } from 'next/font/google';

const inter = Inter({ subsets: ['latin'] });

export const Modal = ({ children, open, onClose, title }: ModalProps) => (
export const Modal = ({ children, open, onClose, title, hideHeaderCloseButton }: ModalProps) => (
<Transition.Root show={open} as={Fragment}>
<Dialog as="div" className={`${inter.className} relative z-50`} onClose={onClose}>
<Transition.Child
Expand All @@ -22,8 +22,8 @@ export const Modal = ({ children, open, onClose, title }: ModalProps) => (
<div className="fixed inset-0 h-full w-full bg-navy-50/75 transition-opacity" />
</Transition.Child>

<div className="fixed inset-0 z-10">
<div className="flex h-full items-center justify-center text-center">
<div className="fixed inset-0 z-10 flex h-full items-center justify-center">
<div className="flex max-h-full items-center justify-center text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
Expand All @@ -38,14 +38,13 @@ export const Modal = ({ children, open, onClose, title }: ModalProps) => (
<Dialog.Title as="h3" className="text-xl font-medium text-navy-900">
{title}
</Dialog.Title>
<button onClick={onClose} type="button">
<CloseIcon className="h-5 w-5 text-navy-600 hover:text-navy-900" aria-hidden="true" />
</button>
{!hideHeaderCloseButton && (
<button onClick={onClose} type="button">
<CloseIcon className="h-5 w-5 text-navy-600 hover:text-navy-900" aria-hidden="true" />
</button>
)}
</div>

<Dialog.Description className="h-[calc(100%-6rem)] overflow-y-auto px-8 py-5">
{children}
</Dialog.Description>
<div className="max-h-[calc(100%-6rem)] overflow-y-auto px-8 py-5">{children}</div>
</Dialog.Panel>
</Transition.Child>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AdvancementLevel } from '@app/types/common';
import { AdvancementLevel } from '@app/types/library';

export interface AdvancementLevelProps {
data: AdvancementLevel;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Bucket } from '@app/types/common';
import { Bucket } from '@app/types/library';

export interface BucketDetailsProps {
data: Bucket;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LadderBand } from '@app/types/common';
import { LadderBand } from '@app/types/library';

export interface LadderDetailsProps {
ladder: LadderBand;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Tooltip } from 'react-tooltip';
import { LadderDetailsProps } from '@app/components/modules/LadderDetails/LadderDetails.interface';
import { InfoIcon } from '@app/static/icons/InfoIcon';
import { LadderBandBucket } from '@app/types/common';
import { LadderBandBucket } from '@app/types/library';
import { BucketCard } from '@app/components/common/BucketCard';
import { AccordionCard } from '@app/components/common/AccordionCard';
import { AccordionList } from '@app/components/common/AccordionList';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LadderBand } from '@app/types/common';
import { LadderBand } from '@app/types/library';

export interface LibraryDetailedProps {
ladderSlug: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { ProfileSettingsHook } from '@app/components/modules/ProfileSetting/ProfileSetting.interface';
import { ChangeEvent, useState } from 'react';
import { getCroppedImg, readFile } from '@app/utils';
import { Area } from 'react-easy-crop';

export const useProfileSettings = (): ProfileSettingsHook => {
const [imageSrc, setImageSrc] = useState<string | null>(null);
const [cropModalOpen, setCropModalOpen] = useState(false);
const [deleteModalOpen, setDeleteModalOpen] = useState(false);

const handleFileChange = async (event: ChangeEvent<HTMLInputElement>) => {
if (event.target.files && event.target.files.length > 0) {
const file = event.target.files?.[0];

const imageDataUrl = await readFile(file);
setImageSrc(imageDataUrl);
setCropModalOpen(true);
}
};

const handleCloseCropModal = () => {
setCropModalOpen(false);
setImageSrc(null);
};

const handleCloseDeleteModal = () => {
setDeleteModalOpen(false);
};

const handleOpenDeleteModal = () => setDeleteModalOpen(true);

const handleSaveCroppedImage = async (croppedAreaPixels: Area | null) => {
if (!croppedAreaPixels || !imageSrc) {
// TODO
return;
}

try {
const img = await getCroppedImg(imageSrc, croppedAreaPixels);
if (img) {
// TODO: send new avatar to api
setCropModalOpen(false);
}
} catch (e) {
console.log(e);
}
};

const handleDeleteImage = () => {
// TODO: delete photo
setDeleteModalOpen(false);
};

return {
imageSrc,
handleFileChange,
cropModalOpen,
handleCloseCropModal,
handleSaveCroppedImage,
deleteModalOpen,
handleCloseDeleteModal,
handleDeleteImage,
handleOpenDeleteModal,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { ChangeEvent } from 'react';
import { Area } from 'react-easy-crop';
import { User } from '@app/types/people';

export interface ProfileSettingsProps {
data: User;
}

export interface ProfileSettingsHook {
imageSrc: string | null;
handleFileChange: (event: ChangeEvent<HTMLInputElement>) => Promise<void>;
cropModalOpen: boolean;
deleteModalOpen: boolean;
handleCloseCropModal: () => void;
handleCloseDeleteModal: () => void;
handleDeleteImage: () => void;
handleSaveCroppedImage: (croppedAreaPixels: Area | null) => void;
handleOpenDeleteModal: () => void;
}
Loading

0 comments on commit 3ce6a33

Please sign in to comment.