Skip to content

Commit

Permalink
FE-34 ✨ 마이페이지 프로필 수정 기능 (#53)
Browse files Browse the repository at this point in the history
* FE-34 💄 마이페이지 UI 초기작업

* FE-34 ✨ 내 정보 조회 API 연동

* FE-34 💄 shadcn/ui Dialog 설치

* FE-34 ✨ 프로필 수정 API 연동

* FE-34 ✨ 이미지 실패 토스트 추가
  • Loading branch information
JeonYumin94 authored Jul 22, 2024
1 parent cdf96d7 commit d24ecd3
Show file tree
Hide file tree
Showing 17 changed files with 521 additions and 12 deletions.
17 changes: 17 additions & 0 deletions next.config.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
images: {
domains: ['sprint-fe-project.s3.ap-northeast-2.amazonaws.com', 'localhost'],
remotePatterns: [
{
protocol: 'https',
hostname: 'via.placeholder.com',
port: '',
pathname: '/**',
},
],
},
rewrites: async () => [
{
source: '/api/proxy/:path*',
destination: 'https://sprint-fe-project.s3.ap-northeast-2.amazonaws.com/:path*',
},
],
};

export default nextConfig;
119 changes: 112 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@lukemorales/query-key-factory": "^1.3.4",
"@radix-ui/react-alert-dialog": "^1.1.1",
"@radix-ui/react-avatar": "^1.1.0",
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-radio-group": "^1.2.0",
Expand All @@ -28,6 +29,7 @@
"axios": "^1.7.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"formik": "^2.4.6",
"lucide-react": "^0.402.0",
"next": "14.2.4",
"qs": "^6.12.2",
Expand All @@ -38,6 +40,7 @@
"sharp": "^0.33.4",
"tailwind-merge": "^2.4.0",
"tailwindcss-animate": "^1.0.7",
"yup": "^1.4.0",
"zod": "^3.23.8"
},
"devDependencies": {
Expand Down
26 changes: 25 additions & 1 deletion src/apis/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,34 @@
import axios from 'axios';
import qs from 'qs';

// NOTE: 토큰 가져오는 함수
const getToken = () =>
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MjQsInRlYW1JZCI6IjUtOSIsInNjb3BlIjoicmVmcmVzaCIsImlhdCI6MTcyMTYxNTYxNSwiZXhwIjoxNzIyMjIwNDE1LCJpc3MiOiJzcC1lcGlncmFtIn0.sCNFSgOQcYGbXWTxWablo9bOmbsw1EI6dTWt8n0xmDQ';

// NOTE: axios 선언
const httpClient = axios.create({
baseURL: process.env.NEXT_PUBLIC_BASE_URL,
headers: { 'Content-Type': 'application/json' },
paramsSerializer: (parameters) => qs.stringify(parameters, { arrayFormat: 'repeat', encode: false }),
});

// NOTE: 요청 인터셉터 추가
httpClient.interceptors.request.use(
(config) => {
const newConfig = { ...config };
const token = getToken();
if (token) {
newConfig.headers.Authorization = `Bearer ${token}`;
}

if (newConfig.data instanceof FormData) {
newConfig.headers['Content-Type'] = 'multipart/form-data';
} else {
newConfig.headers['Content-Type'] = 'application/json';
}

return newConfig;
},
(error) => Promise.reject(error),
);

export default httpClient;
10 changes: 9 additions & 1 deletion src/apis/user.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { GetUserReponseType, GetUserRequestType, PatchMeRequestType } from '@/schema/user';
import type { GetUserReponseType, GetUserRequestType, PatchMeRequestType, PostPresignedUrlRequestType, PostPresignedUrlResponseType } from '@/schema/user';

import httpClient from '.';

export const getMe = async (): Promise<GetUserReponseType> => {
Expand All @@ -16,3 +17,10 @@ export const updateMe = async (request: PatchMeRequestType): Promise<GetUserRepo
const response = await httpClient.patch('/users/me', { ...request });
return response.data;
};

export const createPresignedUrl = async (request: PostPresignedUrlRequestType): Promise<PostPresignedUrlResponseType> => {
const formData = new FormData();
formData.append('image', request.image);
const response = await httpClient.post('/images/upload', formData);
return response.data;
};
65 changes: 65 additions & 0 deletions src/components/ui/dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import * as React from 'react';
import * as DialogPrimitive from '@radix-ui/react-dialog';
import { X } from 'lucide-react';

import cn from '@/lib/utils';

const Dialog = DialogPrimitive.Root;

const DialogTrigger = DialogPrimitive.Trigger;

const DialogPortal = DialogPrimitive.Portal;

const DialogClose = DialogPrimitive.Close;

const DialogOverlay = React.forwardRef<React.ElementRef<typeof DialogPrimitive.Overlay>, React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn('fixed inset-0 z-50 bg-background-100 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0', className)}
{...props}
/>
));
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;

const DialogContent = React.forwardRef<React.ElementRef<typeof DialogPrimitive.Content>, React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
className,
)}
{...props}
>
{children}
<DialogPrimitive.Close className='absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground'>
<X className='h-4 w-4' />
<span className='sr-only'>Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
));
DialogContent.displayName = DialogPrimitive.Content.displayName;

function DialogHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
return <div className={cn('flex flex-col space-y-1.5 text-center sm:text-left', className)} {...props} />;
}
DialogHeader.displayName = 'DialogHeader';

function DialogFooter({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
return <div className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)} {...props} />;
}
DialogFooter.displayName = 'DialogFooter';

const DialogTitle = React.forwardRef<React.ElementRef<typeof DialogPrimitive.Title>, React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>>(({ className, ...props }, ref) => (
<DialogPrimitive.Title ref={ref} className={cn('text-lg font-semibold leading-none tracking-tight', className)} {...props} />
));
DialogTitle.displayName = DialogPrimitive.Title.displayName;

const DialogDescription = React.forwardRef<React.ElementRef<typeof DialogPrimitive.Description>, React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>>(({ className, ...props }, ref) => (
<DialogPrimitive.Description ref={ref} className={cn('text-sm text-muted-foreground', className)} {...props} />
));
DialogDescription.displayName = DialogPrimitive.Description.displayName;

export { Dialog, DialogPortal, DialogOverlay, DialogClose, DialogTrigger, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription };
Loading

0 comments on commit d24ecd3

Please sign in to comment.