diff --git a/next.config.ts b/next.config.ts
index e9ffa30..0ccb075 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -1,7 +1,9 @@
-import type { NextConfig } from "next";
+import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
- /* config options here */
+ images: {
+ domains: ['sprint-fe-project.s3.ap-northeast-2.amazonaws.com'],
+ },
};
export default nextConfig;
diff --git a/src/app/(with-header-sidebar)/dashboard/[id]/page.tsx b/src/app/(with-header-sidebar)/dashboard/[id]/page.tsx
new file mode 100644
index 0000000..9ea98f6
--- /dev/null
+++ b/src/app/(with-header-sidebar)/dashboard/[id]/page.tsx
@@ -0,0 +1,3 @@
+export default function Page() {
+ return
dashboard page
;
+}
diff --git a/src/app/(with-header-sidebar)/mydashboard/hooks/useApi.ts b/src/app/(with-header-sidebar)/mydashboard/hooks/useApi.ts
index 3af9bdc..45b8381 100644
--- a/src/app/(with-header-sidebar)/mydashboard/hooks/useApi.ts
+++ b/src/app/(with-header-sidebar)/mydashboard/hooks/useApi.ts
@@ -31,15 +31,18 @@ export default function useApi<
const [error, setError] = useState(null);
const fetchData = useCallback(async () => {
+ // TODO if (loading) return; add?
setLoading(true);
+ setError(null);
+
+ const config = {
+ method: options.method,
+ url,
+ params: options.params,
+ data: options.body,
+ };
try {
- const config = {
- method: options.method,
- url,
- params: options.params,
- data: options.body,
- };
const response: AxiosResponse = await axiosInstance.request(config);
setData(response.data);
} catch (err) {
@@ -47,7 +50,7 @@ export default function useApi<
} finally {
setLoading(false);
}
- }, [url, options]);
+ }, [url, options.method, options.params, options.body]);
useEffect(() => {
fetchData();
diff --git a/src/app/(with-header-sidebar)/mydashboard/hooks/useWindowSize.ts b/src/app/(with-header-sidebar)/mydashboard/hooks/useWindowSize.ts
index d7b6aaa..10a6e90 100644
--- a/src/app/(with-header-sidebar)/mydashboard/hooks/useWindowSize.ts
+++ b/src/app/(with-header-sidebar)/mydashboard/hooks/useWindowSize.ts
@@ -18,7 +18,7 @@ export default function useWindowSize(): WindowSize {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
- isMobile: window.innerWidth <= 768,
+ isMobile: window.innerWidth < 768,
});
}
diff --git a/src/app/favicon.ico b/src/app/favicon.ico
index 718d6fe..ac28f4a 100644
Binary files a/src/app/favicon.ico and b/src/app/favicon.ico differ
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 540ff9d..8535599 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -11,8 +11,8 @@ const pretendard = localFont({
});
export const metadata: Metadata = {
- title: 'Create Next App',
- description: 'Generated by create next app',
+ title: 'Taskify',
+ description: 'Task management application',
};
export default function RootLayout({
diff --git a/src/components/Button.module.css b/src/components/Button.module.css
index 9d5de36..2c0dc85 100644
--- a/src/components/Button.module.css
+++ b/src/components/Button.module.css
@@ -5,6 +5,7 @@
font-size: 14px;
font-weight: 600;
color: var(--white);
+ white-space: nowrap;
}
.button:disabled {
diff --git a/src/components/Header.module.css b/src/components/Header.module.css
index c646ab7..ea977d4 100644
--- a/src/components/Header.module.css
+++ b/src/components/Header.module.css
@@ -1,3 +1,91 @@
.header {
+ padding: 16px;
border-bottom: 1px solid var(--gray-300);
+ display: flex;
+ justify-content: space-around;
+ align-items: center;
+ gap: 16px;
+}
+
+.title {
+ flex: 1;
+ color: var(--black-100);
+ font-size: 16px;
+ font-weight: 700;
+}
+
+.buttonContainer {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 6px;
+}
+
+.button {
+ padding: 6px 12px;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ background-color: transparent;
+ border-radius: 6px;
+ border: 1px solid var(--gray-300);
+ color: var(--gray-500);
+ font-size: 14px;
+ font-weight: 500;
+}
+
+.icon {
+ display: none;
+}
+
+.userInfoContainer {
+ display: flex;
+ align-items: center;
+}
+
+.userInfoContainer::before {
+ content: '';
+ border: 0.5px solid var(--gray-300);
+ height: 34px;
+}
+
+.userInfoWrapper {
+ margin-left: 16px;
+ display: flex;
+ align-items: center;
+}
+
+@media screen and (min-width: 768px) {
+ .header {
+ padding-left: 40px;
+ padding-right: 30px;
+ gap: 36px;
+ }
+
+ .title {
+ font-size: 20px;
+ }
+
+ .buttonContainer {
+ gap: 16px;
+ }
+
+ .button {
+ padding: 10px 16px;
+ font-size: 16px;
+ }
+
+ .userInfoWrapper {
+ margin-left: 36px;
+ }
+}
+
+@media screen and (min-width: 1199px) {
+ .header {
+ padding-right: 80px;
+ }
+
+ .icon {
+ display: block;
+ }
}
diff --git a/src/components/Header.tsx b/src/components/Header.tsx
index 182e1a4..ef44bfa 100644
--- a/src/components/Header.tsx
+++ b/src/components/Header.tsx
@@ -1,20 +1,52 @@
'use client';
import { usePathname } from 'next/navigation';
+import Image from 'next/image';
+import Button from './Button';
+import UserInfo from './UserInfo';
import styles from './Header.module.css';
-import type { User } from '@/app/(with-header-sidebar)/mydashboard/types/user';
-const user: User = {
- id: 1,
- email: 'heejin@gmail.com',
- nickname: 'heejin',
- profileImageUrl: null,
- createdAt: '2024-11-15T14:29:07.482Z',
-};
+interface HeaderProps {
+ component: React.ComponentType;
+}
-export default function Header() {
+export default function Header({ component: Component }: HeaderProps) {
const pathname = usePathname();
- const { nickname, profileImageUrl } = user;
-
- return ;
+ return (
+
+ );
}
diff --git a/src/components/SideBar.module.css b/src/components/SideBar.module.css
index 9d6700a..5b07566 100644
--- a/src/components/SideBar.module.css
+++ b/src/components/SideBar.module.css
@@ -5,9 +5,15 @@
}
.button {
- background-color: transparent !important;
+ background-color: transparent;
}
.active {
color: salmon;
}
+
+@media screen and (min-width: 768px) {
+ .sideBar {
+ min-width: 155px;
+ }
+}
diff --git a/src/components/SideBar.tsx b/src/components/SideBar.tsx
index 54fb51c..bb6e058 100644
--- a/src/components/SideBar.tsx
+++ b/src/components/SideBar.tsx
@@ -2,10 +2,10 @@
import Link from 'next/link';
import { usePathname } from 'next/navigation';
-import styles from './SideBar.module.css';
import Image from 'next/image';
import Button from './Button';
import useWindowSize from '@/app/(with-header-sidebar)/mydashboard/hooks/useWindowSize';
+import styles from './SideBar.module.css';
export default function SideBar() {
const pathname = usePathname();
diff --git a/src/components/UserInfo.module.css b/src/components/UserInfo.module.css
new file mode 100644
index 0000000..19bd968
--- /dev/null
+++ b/src/components/UserInfo.module.css
@@ -0,0 +1,25 @@
+.image {
+ border-radius: 50%;
+ object-fit: cover;
+}
+
+.userInfo {
+ display: flex;
+ align-items: center;
+ gap: 15px;
+}
+
+.userIcon {
+ width: 30px;
+ height: 30px;
+ border-radius: 50%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.nickname {
+ color: var(--black-100);
+ font-size: 16px;
+ font-weight: 500;
+}
diff --git a/src/components/UserInfo.tsx b/src/components/UserInfo.tsx
new file mode 100644
index 0000000..f51e6ef
--- /dev/null
+++ b/src/components/UserInfo.tsx
@@ -0,0 +1,76 @@
+'use client';
+
+import type { User } from '@/app/(with-header-sidebar)/mydashboard/types/user';
+import styles from './UserInfo.module.css';
+import Image from 'next/image';
+import useWindowSize from '@/app/(with-header-sidebar)/mydashboard/hooks/useWindowSize';
+import { useState, useEffect } from 'react';
+import UserInfoSkeleton from './skeleton/UserInfoSkeleton';
+
+const user: User = {
+ id: 1,
+ email: 'heejin@gmail.com',
+ nickname: 'heejin',
+ // profileImageUrl:
+ // 'https://sprint-fe-project.s3.ap-northeast-2.amazonaws.com/taskify/profile_image/10-1_4804_1731757528194.jpeg',
+ profileImageUrl: null,
+ createdAt: '2024-11-15T14:29:07.482Z',
+};
+
+export default function UserInfo() {
+ const { email, nickname, profileImageUrl } = user;
+ const { isMobile } = useWindowSize();
+
+ const [colors, setColors] = useState<{
+ randomColor: string;
+ invertedColor: string;
+ } | null>(null);
+
+ useEffect(() => {
+ const { randomColor, invertedColor } = getRandomColor();
+ setColors({ randomColor, invertedColor });
+ }, []);
+
+ if (!colors) {
+ return ;
+ }
+
+ return (
+ <>
+ {profileImageUrl ? (
+
+ ) : (
+
+
+ {email[0].toUpperCase()}
+
+ {!isMobile &&
{nickname}}
+
+ )}
+ >
+ );
+}
+
+function getRandomColor() {
+ const randomColor = `#${Math.floor(Math.random() * 16777215).toString(16)}`;
+
+ const r = parseInt(randomColor.slice(1, 3), 16);
+ const g = parseInt(randomColor.slice(3, 5), 16);
+ const b = parseInt(randomColor.slice(5, 7), 16);
+
+ const invertedColor = `rgb(${255 - r}, ${255 - g}, ${255 - b})`;
+
+ return { randomColor, invertedColor };
+}
diff --git a/src/components/skeleton/UserInfoSkeleton.module.css b/src/components/skeleton/UserInfoSkeleton.module.css
new file mode 100644
index 0000000..4afea0f
--- /dev/null
+++ b/src/components/skeleton/UserInfoSkeleton.module.css
@@ -0,0 +1,23 @@
+.userIcon {
+ width: 30px;
+ height: 30px;
+ border-radius: 50%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background-color: var(--gray-300);
+}
+
+@media screen and (min-width: 768px) {
+ .userInfo {
+ display: flex;
+ align-items: center;
+ gap: 15px;
+ }
+
+ .nickname {
+ background-color: var(--gray-300);
+ width: 45px;
+ height: 20px;
+ }
+}
diff --git a/src/components/skeleton/UserInfoSkeleton.tsx b/src/components/skeleton/UserInfoSkeleton.tsx
new file mode 100644
index 0000000..c12ab80
--- /dev/null
+++ b/src/components/skeleton/UserInfoSkeleton.tsx
@@ -0,0 +1,10 @@
+import styles from './UserInfoSkeleton.module.css';
+
+export default function UserInfoSkeleton() {
+ return (
+
+ );
+}