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

[FE][CPF-37] Profile page #50

Merged
merged 42 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
f6050a5
feat: add profile page
wiktoriasalamon Jun 25, 2024
fe55975
fix: svg properties
wiktoriasalamon Jun 25, 2024
a5fb3de
build: add react-easy-crop package
wiktoriasalamon Jun 25, 2024
71780b5
feat: add Avatar component
wiktoriasalamon Jun 25, 2024
fe7afa4
feat: crop uploaded image
wiktoriasalamon Jun 25, 2024
2074f8f
linter
wiktoriasalamon Jun 25, 2024
746effb
build: add styled components
wiktoriasalamon Jun 26, 2024
625f271
linter
wiktoriasalamon Jun 26, 2024
109d88c
feat: add Button component
wiktoriasalamon Jun 26, 2024
81ddd7b
fix: Avatar size
wiktoriasalamon Jun 26, 2024
6d0e30d
linter
wiktoriasalamon Jun 26, 2024
13de4dd
refactor: types
wiktoriasalamon Jun 26, 2024
e574461
refactor: profile settings
wiktoriasalamon Jun 26, 2024
43139d9
feat: save cropped image
wiktoriasalamon Jun 26, 2024
1e092c3
linter
wiktoriasalamon Jun 26, 2024
7d30e3e
fix: console errors
wiktoriasalamon Jun 26, 2024
af0a373
linter
wiktoriasalamon Jun 26, 2024
cbf2dfa
feat: add delete confirmation modal
wiktoriasalamon Jun 26, 2024
2712998
linter
wiktoriasalamon Jun 26, 2024
34bf195
feat: add user navigation
wiktoriasalamon Jun 26, 2024
8af0e10
fix: types
wiktoriasalamon Jun 26, 2024
710dd5e
linter
wiktoriasalamon Jun 26, 2024
79ea694
feat: add const instead of hardcoded value
wiktoriasalamon Jun 26, 2024
3f7b5e5
feat: add styled components theme
wiktoriasalamon Jun 26, 2024
884b971
feat: add missing 'use client'
wiktoriasalamon Jun 26, 2024
d943bec
feat: remove styled components
wiktoriasalamon Jun 27, 2024
44a91c0
Merge branch 'develop' into 37-feature-my-profile-employee-pov
wiktoriasalamon Jun 27, 2024
58e4ad2
linter
wiktoriasalamon Jun 27, 2024
e69461d
chore: remove redundant file
wiktoriasalamon Jun 27, 2024
96bef42
linter
wiktoriasalamon Jun 27, 2024
dc4ca9d
chore: add missing class
wiktoriasalamon Jun 27, 2024
d14f61e
Merge branch 'develop' into 37-feature-my-profile-employee-pov
wiktoriasalamon Jul 1, 2024
5606d73
chore: use routes from constant
wiktoriasalamon Jul 1, 2024
0055bd2
linter
wiktoriasalamon Jul 1, 2024
4c87cd2
Merge branch 'develop' into 37-feature-my-profile-employee-pov
wiktoriasalamon Jul 3, 2024
4a4d013
refactor: move handlers to hooks
wiktoriasalamon Jul 3, 2024
a2abaa8
refactor: move mocked data out of component scope
wiktoriasalamon Jul 3, 2024
0152631
Merge branch 'develop' into 37-feature-my-profile-employee-pov
wiktoriasalamon Jul 3, 2024
d34e512
refactor: use Typography
wiktoriasalamon Jul 3, 2024
8ea84f7
linter
wiktoriasalamon Jul 3, 2024
c8836cb
refactor: use Typography
wiktoriasalamon Jul 3, 2024
e8f2b46
linter
wiktoriasalamon Jul 3, 2024
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
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: '[email protected]',
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