}) => {
setPage(1)
}, [])
- const noContent =
- isLoading && page == 1
- ? '로딩중...'
- : error
- ? '에러 발생'
- : content?.length == 0
- ? '데이터가 없습니다'
- : null
+ const noContent = !isLoading
+ ? error
+ ? '에러 발생'
+ : content?.length == 0
+ ? '데이터가 없습니다'
+ : null
+ : null
return (
<>
-
+ {/* */}
{/* mobile view */}
-
+
{keyword === '' ? (
<>
-
+
-
+
-
}) => {
justifyContent={'space-between'}
my={'0.75rem'}
>
- {headerTitle}
+
+ {keyword}
+ 검색 결과
+
)}
@@ -285,19 +269,15 @@ const MainPage = ({ initData }: { initData: IPagination }) => {
justifyContent={'center'}
alignItems={'center'}
>
- {isLoading ? (
- {noContent}
- ) : (
-
- )}
+
) : (
<>
-
+
{content?.map((project: IPost) => (
}) => {
))}
+ {/* 무한 스크롤 */}
+
+
+
>
)}
- {/* 무한 스크롤 */}
-
{/* pc view */}
-
+
{keyword === '' ? (
<>
-
+
-
+
}) => {
justifyContent={'space-between'}
mb={'0.75rem'}
>
- {headerTitle}
+
+ {keyword}
+ 검색 결과
+
)}
@@ -370,14 +359,10 @@ const MainPage = ({ initData }: { initData: IPagination }) => {
justifyContent={'center'}
alignItems={'center'}
>
- {isLoading ? (
- {noContent}
- ) : (
-
- )}
+
) : (
<>
@@ -398,15 +383,17 @@ const MainPage = ({ initData }: { initData: IPagination }) => {
))}
{/* 무한 스크롤 */}
-
+
>
)}
-
-
-
-
-
+ {!isTablet && (
+
+
+
+
+
+ )}
diff --git a/src/app/panel/MuiThemeProvider.tsx b/src/app/panel/MuiThemeProvider.tsx
index eed8123d9..f68c258bc 100644
--- a/src/app/panel/MuiThemeProvider.tsx
+++ b/src/app/panel/MuiThemeProvider.tsx
@@ -479,6 +479,25 @@ const MuiThemeProvider = ({ children }: { children: React.ReactNode }) => {
},
}
+ theme.components.MuiOutlinedInput = {
+ styleOverrides: {
+ root: {
+ '& input': {
+ fontSize: '16px',
+ transform: 'scale(0.75)',
+ transformOrigin: 'top left',
+ marginBottom: '-10px',
+ marginRight: '-33.333333333%',
+ width: '133.333333333%',
+ lineHeight: '150%',
+ },
+ '& .MuiOutlinedInput-notchedOutline': {
+ height: '100%',
+ },
+ },
+ },
+ }
+
theme.components.MuiTextField = {
styleOverrides: {
root: {
@@ -487,11 +506,9 @@ const MuiThemeProvider = ({ children }: { children: React.ReactNode }) => {
},
'& input': {
height: '32px',
- padding: '0px 12px',
+ padding: '0px 16px',
'&::placeholder': {
color: theme.palette.text.alternative,
- fontSize: '12px',
- fontWeight: 400,
lineHeight: '150%',
},
':-webkit-autofill': {
@@ -502,9 +519,8 @@ const MuiThemeProvider = ({ children }: { children: React.ReactNode }) => {
padding: '0px',
borderRadius: '4px',
height: 'auto',
+ minHeight: '32px',
backgroundColor: theme.palette.background.tertiary,
- fontSize: '12px',
- fontWeight: 400,
color: theme.palette.text.normal,
lineHeight: '150%',
'& fieldset': {
@@ -527,7 +543,7 @@ const MuiThemeProvider = ({ children }: { children: React.ReactNode }) => {
},
'& .MuiInputBase-inputMultiline': {
height: 'auto',
- padding: '12px',
+ padding: '16px',
},
'.MuiFormHelperText-filled': {
margin: 0,
@@ -576,7 +592,8 @@ const MuiThemeProvider = ({ children }: { children: React.ReactNode }) => {
}
if (theme.typography) {
- theme.typography.fontFamily = 'Pretendard Variable, sans-serif'
+ theme.typography.fontFamily =
+ 'var(--main-font), Pretendard Variable, sans-serif'
theme.typography.HeadlineEmphasis = {
fontSize: '32px',
fontStyle: 'normal',
@@ -677,7 +694,7 @@ const MuiThemeProvider = ({ children }: { children: React.ReactNode }) => {
color: theme.palette.text.normal,
}
theme.typography.Tag = {
- fontSize: '12px',
+ fontSize: '11px',
fontStyle: 'normal',
fontWeight: 400,
lineHeight: '150%',
diff --git a/src/app/panel/OthersProfile.tsx b/src/app/panel/OthersProfile.tsx
index b5fe9274a..e5048c561 100644
--- a/src/app/panel/OthersProfile.tsx
+++ b/src/app/panel/OthersProfile.tsx
@@ -49,6 +49,9 @@ const OthersProfile = ({ name, userId, children }: IOthersProfile) => {
router.push('/my-page')
}
+ // 유령회원일 경우
+ if (Number(userId) === -1) return <>{children}>
+
return (
{children && (
diff --git a/src/app/panel/PushAlertBanner.tsx b/src/app/panel/PushAlertBanner.tsx
index 1ada7eac4..45dba5461 100644
--- a/src/app/panel/PushAlertBanner.tsx
+++ b/src/app/panel/PushAlertBanner.tsx
@@ -1,81 +1,60 @@
+'use client'
+
import useAxiosWithAuth from '@/api/config'
import { Box, Button, Stack, Typography } from '@mui/material'
import { AxiosInstance } from 'axios'
import { useEffect, useState } from 'react'
-import webpush from 'web-push'
+import { initializeApp } from 'firebase/app'
+import { getMessaging, onMessage, getToken } from 'firebase/messaging'
const PushAlertBanner = () => {
const axiosInstance: AxiosInstance = useAxiosWithAuth()
- const [isShowPush, setIsShowPush] = useState
(true)
+ const [isShowPush, setIsShowPush] = useState(false)
const [isScroll, setIsScroll] = useState(1)
- const urlBase64ToUint8Array = (base64String: string) => {
- const padding = '='.repeat((4 - (base64String.length % 4)) % 4)
- const base64 = (base64String + padding)
- .replace(/-/g, '+')
- .replace(/_/g, '/')
- const rawData = window.atob(base64)
- const outputArray = new Uint8Array(rawData.length)
-
- for (let i = 0; i < rawData.length; ++i) {
- outputArray[i] = rawData.charCodeAt(i)
- }
-
- return outputArray
- }
-
- const displayNotification = () => {
- if ('serviceWorker' in navigator && 'PushManager' in window) {
- navigator.serviceWorker.ready.then((swReg) => {
- swReg.showNotification('Hello world!')
- })
- }
- }
-
- const createPushSubscription = (swReg: ServiceWorkerRegistration) => {
- // 추후 서버 셋팅 한 뒤 사용
- const vapidPublicKey = webpush.generateVAPIDKeys().publicKey
- const convertedVapidPublicKey = urlBase64ToUint8Array(vapidPublicKey)
- swReg.pushManager
- .subscribe({
- userVisibleOnly: true,
- applicationServerKey: convertedVapidPublicKey,
- })
- .then((newSub) => {
- let newSubData = newSub.toJSON()
- let newSubString = JSON.stringify(newSubData)
-
- return axiosInstance.post(
- `${process.env.NEXT_PUBLIC_API_URL}/api/v1/push`,
- {
- subscription: newSubString,
- },
- )
- })
- .then((res) => {
- console.log(res)
- displayNotification()
+ const handlePushFCM = () => {
+ const firebaseConfig = initializeApp({
+ apiKey: `${process.env.NEXT_PUBLIC_FIREBASE_API_KEY}`,
+ authDomain: `${process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN}`,
+ projectId: `${process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID}`,
+ storageBucket: `${process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET}`,
+ messagingSenderId: `${process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID}`,
+ appId: `${process.env.NEXT_PUBLIC_FIREBASE_APP_ID}`,
+ measurementId: `${process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID}`,
+ })
+
+ const messaging = getMessaging(firebaseConfig)
+
+ getToken(messaging, {
+ vapidKey: `${process.env.NEXT_PUBLIC_FIREBASE_VAPID_KEY}`,
+ })
+ .then((currentToken: any) => {
+ if (currentToken) {
+ axiosInstance
+ .post(
+ `${process.env.NEXT_PUBLIC_NEXT_PUBLIC_CSR_API}:8082/alarm/send-push`,
+ {
+ token: currentToken,
+ title: '푸시 알림 테스트',
+ message: '푸시 알림 테스트 메시지입니다.',
+ },
+ )
+ .then(() => {
+ console.log('푸시 알림 전송 성공')
+ })
+ } else {
+ console.log(
+ 'No registration token available. Request permission to generate one.',
+ )
+ }
})
- .catch((err) => {
- console.log(err)
+ .catch(() => {
+ console.log('An error occurred while retrieving token. ')
})
- }
- const handlePushNotification = () => {
- if ('serviceWorker' in navigator && 'PushManager' in window) {
- let reg: ServiceWorkerRegistration
-
- navigator.serviceWorker.ready
- .then((swReg) => {
- reg = swReg
- return swReg.pushManager.getSubscription()
- })
- .then((subscription) => {
- if (subscription === null) {
- createPushSubscription(reg)
- }
- })
- }
+ onMessage(messaging, () => {
+ console.log('Message received. ')
+ })
}
const handlePush = () => {
@@ -83,7 +62,7 @@ const PushAlertBanner = () => {
Notification.requestPermission((permission) => {
if (permission === 'granted') {
console.log('Notification permission granted.')
- handlePushNotification()
+ handlePushFCM()
setIsShowPush(false)
localStorage.setItem('isShowPush', 'false')
} else {
@@ -100,6 +79,8 @@ const PushAlertBanner = () => {
useEffect(() => {
if (localStorage && localStorage.getItem('isShowPush') === 'false') {
setIsShowPush(false)
+ } else {
+ setIsShowPush(true)
}
window.addEventListener('scroll', handleScroll)
@@ -113,7 +94,7 @@ const PushAlertBanner = () => {
<>
{isShowPush && (
{
- const [isShowInstall, setIsShowInstall] = useState(true)
+ const [isShowInstall, setIsShowInstall] = useState(false)
const { isPc } = useMedia()
const [isSafari, setIsSafari] = useState(false)
const [deferredPrompt, setDeferredPrompt] =
@@ -23,7 +25,6 @@ const PwaInstallBanner = () => {
deferredPrompt.prompt()
deferredPrompt.userChoice.then((choiceResult) => {
if (choiceResult.outcome === 'accepted') {
- console.log('User accepted the install prompt')
setIsShowInstall(false)
localStorage.setItem('isShowInstall', 'false')
} else {
@@ -31,16 +32,21 @@ const PwaInstallBanner = () => {
}
})
} else {
- console.log('not install')
+ console.log(
+ '[Error] PWA install banner is not working. Maybe already installed?',
+ )
+ setIsShowInstall(false)
}
}
useEffect(() => {
- if (window.matchMedia('(display-mode: standalone)').matches) {
- setIsShowInstall(false)
- }
- if (localStorage.getItem('isShowInstall') === 'false') {
+ if (
+ window.matchMedia('(display-mode: standalone)').matches ||
+ localStorage.getItem('isShowInstall') === 'false'
+ ) {
setIsShowInstall(false)
+ } else {
+ setIsShowInstall(true)
}
const isSafariBrowser =
@@ -77,10 +83,14 @@ const PwaInstallBanner = () => {
<>
{isShowInstall && (
{
<>
{isShowInstall && (
diff --git a/src/app/panel/font.ts b/src/app/panel/font.ts
index 531f213c2..4b435713b 100644
--- a/src/app/panel/font.ts
+++ b/src/app/panel/font.ts
@@ -2,4 +2,6 @@ import localFont from 'next/font/local'
export const Pretendard = localFont({
src: '../../../public/fonts/PretendardVariable.woff2',
+ variable: '--main-font',
+ fallback: ['sans-serif'],
})
diff --git a/src/app/panel/layout-panel/AlertIcon.tsx b/src/app/panel/layout-panel/AlertIcon.tsx
index a00bd1a59..29dea3e37 100644
--- a/src/app/panel/layout-panel/AlertIcon.tsx
+++ b/src/app/panel/layout-panel/AlertIcon.tsx
@@ -1,53 +1,197 @@
'use client'
-// import { Badge, Drawer, Tab, Tabs } from '@mui/material'
-import { Badge } from '@mui/material'
-import { IconButton } from '@mui/material'
-// import { SyntheticEvent, useCallback, useState } from 'react'
-// import { Box } from '@mui/system'
+import { CircularProgress, IconButton } from '@mui/material'
import NotificationIcon from '@/icons/NotificationIcon'
import useMedia from '@/hook/useMedia'
-import CuModal from '@/components/CuModal'
-import ForbiddenDolphin from '@/components/WorkingDolphin'
-import useModal from '@/hook/useModal'
+import { usePathname, useSearchParams } from 'next/navigation'
-const AlertIcon = () => {
- const isAlertComing = false
- // const [tabvalue, setTabValue] = useState(0)
- // const [isAlertComing, setIsAlertComing] = useState(false)
- // const [isDrawerOpen, setIsDrawerOpen] = useState(false)
+// 알림 잠그기
+
+// import { Badge } from '@mui/material'
+// import ForbiddenDolphin from '@/components/WorkingDolphin'
+// import CuModal from '@/components/CuModal'
+// import useModal from '@/hook/useModal'
+
+// 알림탭 관련
+
+import {
+ Badge,
+ Button,
+ Drawer,
+ Stack,
+ Tab,
+ Tabs,
+ Typography,
+} from '@mui/material'
+import {
+ Dispatch,
+ SetStateAction,
+ SyntheticEvent,
+ useCallback,
+ useEffect,
+ useRef,
+ useState,
+} from 'react'
+import { Box } from '@mui/system'
+import { CloseIcon } from '@/icons'
+import useAuthStore from '@/states/useAuthStore'
+import NoDataDolphin from '@/components/NoDataDolphin'
+import { useRouter } from 'next/navigation'
+import useAlarmStorage, { IAlarm } from '@/states/useAlarmStorage'
+import AlertCard from './alert-panel/AlertCard'
+import { debounce } from 'lodash'
+
+const useInfiniteScroll = ({
+ setPage,
+ mutate,
+ isEnd,
+ page,
+ isDrawerOpen,
+ tabvalue,
+}: {
+ setPage: Dispatch>
+ mutate: any
+ isEnd: boolean
+ page: number
+ isDrawerOpen: boolean
+ tabvalue: number
+}) => {
+ const [spinner, setSpinner] = useState(false)
+ const target = useRef(null)
+
+ const debouncedFetchData = debounce(async () => {
+ // 데이터 업데이트. setSpinner을 언제 true할지 정해야.
+ setSpinner(true)
+ await mutate(page, tabvalue)
+ setPage(page + 1)
+ setSpinner(false)
+ }, 1000)
+
+ useEffect(() => {
+ const observer = new IntersectionObserver(
+ (entries) => {
+ if (entries[0].isIntersecting) {
+ if (!spinner && isEnd === false && isDrawerOpen) {
+ // 스피너를 표시하고 페이지 번호를 증가시킨 후 디바운스된 데이터 가져오기 함수 호출
+ // 가능한 페이지 양을 도달했다면 더이상 로딩하지 않는다.
+ debouncedFetchData()
+ }
+ }
+ },
+ { threshold: 0.8 },
+ )
+ const currentTarget = target.current
+
+ if (currentTarget) {
+ observer.observe(currentTarget)
+ }
+
+ // 컴포넌트가 언마운트되면 IntersectionObserver 해제
+ return () => {
+ if (currentTarget) observer.unobserve(currentTarget)
+ }
+ }, [target, spinner, debouncedFetchData, page, isEnd, isDrawerOpen, tabvalue])
+
+ return { target, spinner }
+}
+
+const AlertIcon = () => {
+ // const isAlertComing = false
const { isPc } = useMedia()
- const { isOpen, openModal, closeModal } = useModal()
-
- // const openAlertTab = useCallback(() => {
- // setIsAlertComing(true)
- // setIsDrawerOpen(true)
- // }, [setIsAlertComing, setIsDrawerOpen])
-
- // const toggleDrawer = useCallback(
- // (open: boolean) => (event: React.KeyboardEvent | React.MouseEvent) => {
- // if (
- // event.type === 'keydown' &&
- // ((event as React.KeyboardEvent).key === 'Tab' ||
- // (event as React.KeyboardEvent).key === 'Shift')
- // ) {
- // return
- // }
-
- // setIsDrawerOpen(open)
- // },
- // [setIsDrawerOpen],
- // )
-
- // const handleChange = (e: SyntheticEvent, newValue: number) => {
- // setTabValue(newValue)
- // }
+ // const { isOpen, openModal, closeModal } = useModal()
+
+ // 알림 탭 관련
+ const {
+ isNewAlarm,
+ isNew,
+ getAlarms,
+ alarms,
+ deleteAlarm,
+ deleteAllAlarms,
+ checkNewAlarm,
+ resetAlarms,
+ } = useAlarmStorage()
+ const [tabvalue, setTabValue] = useState(0)
+ const [page, setPage] = useState(1)
+ const [isAlertComing, setIsAlertComing] = useState(false)
+ const [isDrawerOpen, setIsDrawerOpen] = useState(false)
+ const { isLogin } = useAuthStore()
+ const router = useRouter()
+ const pathname = usePathname()
+ const searchParams = useSearchParams()
+
+ const { target, spinner } = useInfiniteScroll({
+ setPage,
+ mutate: getAlarms,
+ isEnd: alarms[alarms.length - 1]?.isEnd || false,
+ page,
+ isDrawerOpen,
+ tabvalue,
+ })
+
+ useEffect(() => {
+ isNewAlarm(isLogin)
+ }, [pathname, searchParams, isLogin, isNewAlarm])
+
+ useEffect(() => {
+ if (isDrawerOpen) {
+ checkNewAlarm()
+ if (page === 1) {
+ getAlarms(page, tabvalue)
+ setPage(page + 1)
+ }
+ if (isAlertComing) {
+ setIsAlertComing(false)
+ }
+ } else {
+ resetAlarms()
+ setPage(1)
+ }
+ }, [isDrawerOpen, setIsAlertComing, tabvalue])
+
+ const openAlertTab = useCallback(() => {
+ setIsAlertComing(true)
+ setIsDrawerOpen(true)
+ }, [setIsAlertComing, setIsDrawerOpen])
+
+ const toggleDrawer = useCallback(
+ (open: boolean) => (event: React.KeyboardEvent | React.MouseEvent) => {
+ if (
+ event.type === 'keydown' &&
+ ((event as React.KeyboardEvent).key === 'Tab' ||
+ (event as React.KeyboardEvent).key === 'Shift')
+ ) {
+ return
+ }
+
+ setIsDrawerOpen(open)
+ },
+ [setIsDrawerOpen],
+ )
+
+ const handleChange = useCallback(
+ (e: SyntheticEvent, newValue: number) => {
+ setTabValue(newValue)
+ setPage(1)
+ resetAlarms()
+ },
+ [tabvalue, setPage, resetAlarms],
+ )
+
+ const handleClose = () => {
+ setIsDrawerOpen(false)
+ }
+
+ const handleLogin = () => {
+ setIsDrawerOpen(false)
+ router.push('/login')
+ }
return (
<>
-
-
+
+
{
/>
-
+ {/*
-
- {/* */}
+
-
-
-
-
-
-
-
+
+
+ {!isPc && (
+
+
+
+ )}
+
+
+ 알림
+
+
+
+
+ {!isLogin ? (
+
+
+
+
+
+
+ ) : (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {alarms.length === 0 ? (
+
+ ) : (
+ <>
+ {alarms.map((item: IAlarm) => (
+ deleteAlarm(item.notificationId)}
+ alert={item}
+ />
+ ))}
+
+ {spinner && }
+
+ >
+ )}
+
+
+
+ )}
- */}
+
>
)
}
diff --git a/src/app/panel/layout-panel/Header.tsx b/src/app/panel/layout-panel/Header.tsx
index 731f1267b..9587a4fe8 100644
--- a/src/app/panel/layout-panel/Header.tsx
+++ b/src/app/panel/layout-panel/Header.tsx
@@ -28,7 +28,13 @@ import useAuthStore from '@/states/useAuthStore'
* - 오른쪽 아이콘 (있을 수도 있고 없을 수도 있음)
*/
-const Header = ({ pathname }: { pathname?: string }) => {
+const Header = ({
+ pathname,
+ backAction,
+}: {
+ pathname?: string
+ backAction?: () => void
+}) => {
const theme = useTheme()
const mobileHeader = {
...style.mobileHeader,
@@ -39,12 +45,14 @@ const Header = ({ pathname }: { pathname?: string }) => {
const [title, setTitle] = useState('')
const searchParams = useSearchParams()
const keyword = searchParams.get('keyword') ?? ''
+ const type = searchParams.get('type') ?? 'STUDY'
const regex = /^\/recruit\/\d+\/edit$/
useEffect(() => {
if (!pathname) return setTitle('')
if (pathname === '/') {
- setTitle('메인')
+ if (keyword !== '') setTitle('검색 결과')
+ else setTitle('메인')
} else if (pathname.startsWith('/login')) {
setTitle('로그인')
} else if (pathname === '/recruit/write') {
@@ -54,7 +62,7 @@ const Header = ({ pathname }: { pathname?: string }) => {
} else if (pathname.startsWith('/team-list')) {
if (!isLogin) {
router.push('/login?redirect=/team-list')
- } else setTitle('팀페이지')
+ } else setTitle('나의 팀')
} else if (pathname.startsWith('/my-page')) {
if (!isLogin) {
router.push('/login?redirect=/my-page')
@@ -62,16 +70,19 @@ const Header = ({ pathname }: { pathname?: string }) => {
} else {
setTitle('')
}
- }, [pathname])
+ }, [keyword, pathname])
const { headerTitle } = useHeaderStore()
// 타이틀만 보여주고 싶은 경우 (뒤로 가기 버튼이 보이지 않았으면 하는 경우)
- const onlyTitle = title === '마이페이지' || title === '팀페이지'
+ const onlyTitle =
+ pathname?.startsWith('/my-page') ||
+ pathname?.startsWith('/team-list') ||
+ pathname?.startsWith('/login')
return (
- {title === '메인' && keyword === '' ? (
+ {pathname === '/' && keyword === '' ? (
@@ -88,7 +99,12 @@ const Header = ({ pathname }: { pathname?: string }) => {
) : (
router.back()}
+ onClick={() => {
+ if (backAction) backAction()
+ else if (pathname === '/' && keyword !== '') {
+ router.replace(`?type=${type}`)
+ } else router.back()
+ }}
sx={{
visibility: onlyTitle ? 'hidden' : 'visible',
}}
@@ -96,7 +112,7 @@ const Header = ({ pathname }: { pathname?: string }) => {
- {/* headerTitle이 있는 경우: 페이지안에서 header를 설정하는 경우 (ex 모집글뷰, 팀페이지) */}
+ {/* headerTitle이 있는 경우: 페이지안에서 header를 설정하는 경우 (ex 모집글뷰, 나의 팀) */}
{headerTitle === '' ? title : headerTitle}
diff --git a/src/app/panel/layout-panel/MobileNav.tsx b/src/app/panel/layout-panel/MobileNav.tsx
index dd5f535d3..1594a0263 100644
--- a/src/app/panel/layout-panel/MobileNav.tsx
+++ b/src/app/panel/layout-panel/MobileNav.tsx
@@ -27,7 +27,10 @@ const MobileNav = () => {
useEffect(() => {
if (pathname === '/') {
setValue('home')
- } else if (pathname.startsWith('/team-list')) {
+ } else if (
+ pathname.startsWith('/team-list') ||
+ pathname.startsWith('/teams')
+ ) {
setValue('team-list')
} else if (pathname.startsWith('/hitchhiking')) {
setValue('hitchhiking')
@@ -36,7 +39,7 @@ const MobileNav = () => {
else setValue('my-page')
} else if (pathname.startsWith('/showcase')) {
setValue('showcase')
- } else if (pathname.startsWith('/login')) {
+ } else {
setValue('')
}
}, [pathname])
@@ -90,7 +93,7 @@ const MobileNav = () => {
/>
팀페이지}
+ label={나의 팀}
value={'team-list'}
onClick={() => {
router.push('/team-list')
diff --git a/src/app/panel/layout-panel/Nav.style.ts b/src/app/panel/layout-panel/Nav.style.ts
index decf98d01..bb121e2e1 100644
--- a/src/app/panel/layout-panel/Nav.style.ts
+++ b/src/app/panel/layout-panel/Nav.style.ts
@@ -1,4 +1,5 @@
export const navContainerStyle = {
+ display: 'flex',
position: 'fixed',
left: 0,
right: 0,
@@ -14,20 +15,20 @@ export const navContainerStyle = {
export const navStyle = {
'& .MuiBottomNavigationAction-label': {
- color: 'text.normal',
+ color: 'text.alternative',
},
'& .MuiTypography-root': {
- color: 'text.normal',
+ color: 'text.alternative',
},
'&.Mui-selected': {
'& .MuiBottomNavigationAction-label': {
- color: 'text.primary',
+ color: 'text.normal',
},
'& .MuiSvgIcon-root': {
- color: 'text.primary',
+ color: 'text.normal',
},
'& .MuiTypography-root': {
- color: 'text.primary',
+ color: 'text.normal',
},
},
}
diff --git a/src/app/panel/layout-panel/PcNav.tsx b/src/app/panel/layout-panel/PcNav.tsx
index 816f403ed..9a4c034c5 100644
--- a/src/app/panel/layout-panel/PcNav.tsx
+++ b/src/app/panel/layout-panel/PcNav.tsx
@@ -6,7 +6,9 @@ import {
Avatar,
BottomNavigation,
BottomNavigationAction,
+ Box,
Button,
+ Container,
IconButton,
Stack,
Typography,
@@ -37,29 +39,37 @@ const PcNav = () => {
: '/login?redirect=/my-page/interests'
const { data: profileData } = useSWR(
- isLogin ? `${process.env.NEXT_PUBLIC_API_URL}/api/v1/profile` : undefined,
+ isLogin ? `${process.env.NEXT_PUBLIC_CSR_API}/api/v1/profile` : undefined,
(url: string) => axiosWithAuth.get(url).then((res) => res.data),
)
useEffect(() => {
if (pathname === '/') {
setValue('home')
- } else if (pathname.includes('/team-list')) {
+ } else if (
+ pathname.startsWith('/team-list') ||
+ pathname.startsWith('/teams')
+ ) {
setValue('team-list')
- } else if (pathname.includes('/hitchhiking')) {
+ } else if (pathname.startsWith('/hitchhiking')) {
setValue('hitchhiking')
- } else if (pathname.includes('/showcase')) {
+ } else if (pathname.startsWith('/showcase')) {
setValue('showcase')
+ } else {
+ setValue('home')
}
}, [pathname])
return (
-
-
+
@@ -117,7 +127,7 @@ const PcNav = () => {
color={value === 'team-list' ? 'primary' : 'text.normal'}
variant="Caption"
>
- 팀페이지
+ 나의 팀
}
onClick={() => {
@@ -200,8 +210,8 @@ const PcNav = () => {
)}
-
-
+
+
)
}
diff --git a/src/app/panel/layout-panel/alert-panel/AlertCard.tsx b/src/app/panel/layout-panel/alert-panel/AlertCard.tsx
new file mode 100644
index 000000000..e06a30dd1
--- /dev/null
+++ b/src/app/panel/layout-panel/alert-panel/AlertCard.tsx
@@ -0,0 +1,75 @@
+'use client'
+
+import { AlarmType, IAlarm } from '@/states/useAlarmStorage'
+import {
+ Card,
+ CardActionArea,
+ IconButton,
+ Stack,
+ Typography,
+} from '@mui/material'
+import { SystemIcon, TeamIcon, MessageIcon } from './Icons'
+import { CloseIcon } from '@/icons'
+import { useRouter } from 'next/navigation'
+
+interface IAlertCard {
+ alert: IAlarm
+ handleDelete: (id: number) => void
+}
+
+const AlertCard = ({ alert, handleDelete }: IAlertCard) => {
+ const router = useRouter()
+ return (
+
+ router.push(alert.redirectUrl)}
+ >
+
+ {alert.type === AlarmType.SYSTEM && }
+ {alert.type === AlarmType.TEAM && }
+ {alert.type === AlarmType.MESSAGE && }
+
+
+
+ {alert.body}
+
+
+
+
+ handleDelete(alert.notificationId)}>
+
+
+
+
+ )
+}
+
+export default AlertCard
diff --git a/src/app/panel/layout-panel/alert-panel/Icons.tsx b/src/app/panel/layout-panel/alert-panel/Icons.tsx
new file mode 100644
index 000000000..d9d4c9e7a
--- /dev/null
+++ b/src/app/panel/layout-panel/alert-panel/Icons.tsx
@@ -0,0 +1,54 @@
+import { createSvgIcon } from '@mui/material'
+
+const SystemIcon = createSvgIcon(
+ ,
+ 'SystemIcon',
+)
+
+const MessageIcon = createSvgIcon(
+ ,
+ 'MessageIcon',
+)
+
+const TeamIcon = createSvgIcon(
+ ,
+ 'TeamIcon',
+)
+
+export { SystemIcon, MessageIcon, TeamIcon }
diff --git a/src/app/panel/main-page/FloatEditButton.tsx b/src/app/panel/main-page/FloatEditButton.tsx
index 15d2095fc..2c08ba69b 100644
--- a/src/app/panel/main-page/FloatEditButton.tsx
+++ b/src/app/panel/main-page/FloatEditButton.tsx
@@ -20,7 +20,7 @@ const FloatEditButton = () => {
>
diff --git a/src/app/panel/main-page/FormCheckbox.tsx b/src/app/panel/main-page/FormCheckbox.tsx
index df930f710..c35836227 100644
--- a/src/app/panel/main-page/FormCheckbox.tsx
+++ b/src/app/panel/main-page/FormCheckbox.tsx
@@ -6,11 +6,13 @@ const FormCheckbox = ({
label,
control,
value,
+ variant,
}: {
name: string
label: string
control: any
value?: string
+ variant?: any
}) => {
return (
@@ -25,7 +27,7 @@ const FormCheckbox = ({
)
}
/>
- {label}
+ {label}
)
}
diff --git a/src/app/panel/main-page/MainBanner.tsx b/src/app/panel/main-page/MainBanner.tsx
index 0c19a7196..a914c41d3 100644
--- a/src/app/panel/main-page/MainBanner.tsx
+++ b/src/app/panel/main-page/MainBanner.tsx
@@ -1,66 +1,30 @@
-'use client'
-
-import { Stack } from '@mui/material'
import useMedia from '@/hook/useMedia'
-import Image from 'next/image'
-import Slider from 'react-slick'
-import 'slick-carousel/slick/slick.css'
-import 'slick-carousel/slick/slick-theme.css'
import { useRouter } from 'next/navigation'
+import CuPhotoBox from '@/components/CuPhotoBox'
const MainBanner = () => {
- const router = useRouter()
const { isPc } = useMedia()
- const settings = {
- dots: false,
- infinite: true,
- speed: 500,
- slidesToShow: 1,
- slidesToScroll: 1,
- arrows: false,
- // autoplay: true,
- // autoplaySpeed: 3000,
- pauseOnHover: true,
- }
+ const router = useRouter()
return (
-
-
-
-
-
-
-
-
-
-
+ router.push('/about')}
+ style={{
+ width: '100%',
+ height: isPc ? '12.5rem' : '100px',
+ cursor: 'pointer',
+ fill: 'none',
+ }}
+ objectStyle="contain"
+ src={
+ !isPc
+ ? '/images/banners/about-mobile.svg'
+ : '/images/banners/about-pc.svg'
+ }
+ alt="main-banner"
+ priorityOption={true}
+ />
+ //
)
}
diff --git a/src/app/panel/main-page/MainCard.style.ts b/src/app/panel/main-page/MainCard.style.ts
index 10b0e7823..2ed091ccf 100644
--- a/src/app/panel/main-page/MainCard.style.ts
+++ b/src/app/panel/main-page/MainCard.style.ts
@@ -19,7 +19,7 @@ export const ChipStyle: SxProps = {
top: 16,
left: 16,
borderRadius: 1,
- backgroundColor: 'background.tertiary',
+ // backgroundColor: 'background.tertiary',
color: 'text.normal',
height: '1.25rem',
'& .MuiChip-label': {
diff --git a/src/app/panel/main-page/MainCard.tsx b/src/app/panel/main-page/MainCard.tsx
index 4b1d1bf90..cb29e7a47 100644
--- a/src/app/panel/main-page/MainCard.tsx
+++ b/src/app/panel/main-page/MainCard.tsx
@@ -5,7 +5,6 @@ import {
Card,
CardContent,
CardHeader,
- CardMedia,
Chip,
Typography,
Stack,
@@ -15,6 +14,7 @@ import OthersProfile from '../OthersProfile'
import TagChip from '@/components/TagChip'
import FavoriteButton from '@/components/FavoriteButton'
import { ChipStyle } from '@/app/panel/main-page/MainCard.style'
+import CuPhotoBox from '@/components/CuPhotoBox'
const MainCard = ({
title,
@@ -41,21 +41,68 @@ const MainCard = ({
: '모집완료'
return (
-
+
- */}
+
{status && (
{statusLabel}}
+ label={
+
+ {statusLabel}
+
+ }
+ color={statusLabel === '모집중' ? 'green' : 'error'}
sx={ChipStyle}
size="medium"
/>
@@ -84,7 +131,17 @@ const MainCard = ({
/>
}
title={
-
+
{user_nickname}
}
@@ -103,7 +160,7 @@ const MainCard = ({
>
{
const settings = {
@@ -21,36 +21,14 @@ const MainCarousel = () => {
height: '100%',
}
- const imageProps = {
- width: 310,
- height: 130,
- style: {
- borderRadius: '0.75rem',
- height: '7.5rem',
- },
- }
-
return (
-
-
-
-
-
-
-
diff --git a/src/app/panel/main-page/MainPanel.tsx b/src/app/panel/main-page/MainPanel.tsx
index 329ef5ac1..77b3dec87 100644
--- a/src/app/panel/main-page/MainPanel.tsx
+++ b/src/app/panel/main-page/MainPanel.tsx
@@ -9,7 +9,7 @@ interface ISearchOptionPanel {
openOption: boolean
setOpenOption: (value: boolean) => void
handleOption: (value: IDetailOption) => void
- type: ProjectType | undefined
+ type: ProjectType
sort: ProjectSort | undefined
handleSort: (value: ProjectSort) => void
isPc?: boolean
@@ -58,6 +58,7 @@ export const InfinityScrollPanel = ({
sx={{
bottom: 0,
height: '1vh',
+ paddingY: '0.25rem',
}}
ref={target}
/>
diff --git a/src/app/panel/main-page/MainProfile.tsx b/src/app/panel/main-page/MainProfile.tsx
index 2e4ece450..5fe1f1dc9 100644
--- a/src/app/panel/main-page/MainProfile.tsx
+++ b/src/app/panel/main-page/MainProfile.tsx
@@ -9,7 +9,7 @@ const MainProfile = () => {
const axiosWithAuth = useAxiosWithAuth()
const { isLogin } = useAuthStore()
const { data } = useSWR(
- isLogin ? `${process.env.NEXT_PUBLIC_API_URL}/api/v1/profile` : undefined,
+ isLogin ? `${process.env.NEXT_PUBLIC_CSR_API}/api/v1/profile` : undefined,
(url: string) => axiosWithAuth.get(url).then((res) => res.data),
)
const [isClient, setIsClient] = useState(false)
@@ -46,7 +46,7 @@ const MainProfile = () => {
) : (
{
const router = useRouter()
const { data, isLoading, error } = useSWR>(
- `${process.env.NEXT_PUBLIC_API_URL}/api/v1/showcase?page=1&pageSize=10`,
+ `${process.env.NEXT_PUBLIC_CSR_API}/api/v1/showcase?page=1&pageSize=10`,
defaultGetFetcher,
)
- // console.log(data)
-
const handleClick = useCallback(() => {
if (data?.content.length === 0) return
const id = data?.content[0].id
@@ -53,16 +52,16 @@ const MainShowcase = () => {
{data && data.content[0] && (
-
{
overflow={'hidden'}
textOverflow={'ellipsis'}
m={'1rem'}
+ height={'5rem'}
>
- {
}}
>
{data.content[0].description}
-
+ */}
+
diff --git a/src/app/panel/main-page/Mainpage.style.ts b/src/app/panel/main-page/Mainpage.style.ts
index aa86c8315..4b1c3e49f 100644
--- a/src/app/panel/main-page/Mainpage.style.ts
+++ b/src/app/panel/main-page/Mainpage.style.ts
@@ -9,12 +9,17 @@ export const sideMenuStyle: SxProps = {
export const cardStyle: SxProps = {
height: '21.875rem',
borderRadius: '0.75rem',
+ width: '100%',
}
export const floatButtonStyle: SxProps = {
- position: 'fixed',
- right: 20,
- bottom: 80,
+ display: 'flex',
+ width: '100%',
+ position: 'sticky',
+ justifyContent: 'flex-end',
+ boxSizing: 'border-box',
+ paddingX: '0.5rem',
+ bottom: '6.25rem',
}
export const containerStyle = {
diff --git a/src/app/panel/main-page/Options.tsx b/src/app/panel/main-page/Options.tsx
index 5f901b6df..2b00acd85 100644
--- a/src/app/panel/main-page/Options.tsx
+++ b/src/app/panel/main-page/Options.tsx
@@ -18,8 +18,11 @@ import { ITag } from '@/types/IPostDetail'
import SettingSelect from '@/app/teams/[id]/setting/panel/SettingSelect'
import useMedia from '@/hook/useMedia'
-const Options = ({ setDetailOption, setOpenOption }: {
- setDetailOption: any,
+const Options = ({
+ setDetailOption,
+ setOpenOption,
+}: {
+ setDetailOption: any
setOpenOption?: (value: boolean) => void
}) => {
const { isPc } = useMedia()
@@ -33,7 +36,7 @@ const Options = ({ setDetailOption, setOpenOption }: {
},
})
const { data: listData } = useSWR(
- `${process.env.NEXT_PUBLIC_API_URL}/api/v1/tag`,
+ `${process.env.NEXT_PUBLIC_CSR_API}/api/v1/tag`,
defaultGetFetcher,
)
const [due, setDue] = useState([0, 100])
@@ -80,6 +83,9 @@ const Options = ({ setDetailOption, setOpenOption }: {
status,
tag,
})
+ if (!isPc && setOpenOption) {
+ setOpenOption(false)
+ }
}
const handleReset = () => {
@@ -108,16 +114,16 @@ const Options = ({ setDetailOption, setOpenOption }: {
}
return (
-
-
- )
-}
+ */
export default PhoneFrame
diff --git a/src/app/showcase/panel/PostCard.tsx b/src/app/showcase/panel/PostCard.tsx
index e7a717833..26cbf9278 100644
--- a/src/app/showcase/panel/PostCard.tsx
+++ b/src/app/showcase/panel/PostCard.tsx
@@ -1,7 +1,6 @@
import {
Box,
Card,
- CardActionArea,
CardContent,
CardHeader,
CardMedia,
@@ -56,11 +55,10 @@ function PostCard({
const clickFavorite = useCallback(() => {
axiosWithAuth
.post(
- `${process.env.NEXT_PUBLIC_API_URL}/api/v1/showcase/favorite/${postId}`,
+ `${process.env.NEXT_PUBLIC_CSR_API}/api/v1/showcase/favorite/${postId}`,
)
.then((res) => {
if (res.status === 200) {
- console.log(res)
setFavorite(!favorite)
}
})
@@ -68,10 +66,9 @@ function PostCard({
const clickLike = useCallback(() => {
axiosWithAuth
- .post(`${process.env.NEXT_PUBLIC_API_URL}/api/v1/showcase/like/${postId}`)
+ .post(`${process.env.NEXT_PUBLIC_CSR_API}/api/v1/showcase/like/${postId}`)
.then((res) => {
if (res.status === 200) {
- console.log(res)
if (liked === false) {
setIsLiked(true)
setLikeNum(likeNum + 1)
@@ -92,7 +89,7 @@ function PostCard({
backfaceVisibility: 'hidden',
}}
ref={ref}
- // onClick={onClick}
+ onClick={onClick}
>
-
-
-
-
- {title}
-
-
-
-
- {tagList?.map(({ name, color }: IShowcaseTag, idx: number) => {
- return (
- {name}}
- size="small"
- key={idx}
- style={{
- backgroundColor: alpha(color, 0.3),
- borderRadius: 2,
- height: '1.25rem',
- }}
- />
- )
- })}
-
-
-
-
+
+
+
+
+ {title}
+
+
+
+
+ {tagList?.map(({ name, color }: IShowcaseTag, idx: number) => {
+ return (
+ {name}}
+ size="small"
+ key={idx}
+ style={{
+ backgroundColor: alpha(color, 0.3),
+ borderRadius: 2,
+ height: '1.25rem',
+ }}
+ />
+ )
+ })}
+
+
+
)
diff --git a/src/app/showcase/panel/ShowcaseCard.tsx b/src/app/showcase/panel/ShowcaseCard.tsx
index 96cf18a8c..4ee85f68c 100644
--- a/src/app/showcase/panel/ShowcaseCard.tsx
+++ b/src/app/showcase/panel/ShowcaseCard.tsx
@@ -32,7 +32,6 @@ const ShowcaseCardBack = ({
title,
name,
image,
- currentDomain,
}: {
postId: number
sx?: SxProps
@@ -47,6 +46,12 @@ const ShowcaseCardBack = ({
}) => {
const router = useRouter()
const { isPc } = useMedia()
+ const [currentPageUrl, setCurrentPageUrl] = useState('')
+
+ //window is not defined 에러 방지
+ useEffect(() => {
+ setCurrentPageUrl(window.location.href)
+ }, [])
const getLineCount = (originHeight: number, lineHeight: number) => {
const lineCount = Math.floor((cardWidth * originHeight) / 328 / lineHeight)
@@ -97,10 +102,11 @@ const ShowcaseCardBack = ({
- {/* TODO : 모집글 작성자 아이디 가져올 수 있는 방법 찾기 */}
+
@@ -247,7 +253,7 @@ const ShowcaseCard = ({
>
{
const axiosWithAuth = useAxiosWithAuth()
const [image, setImage] = useState([])
- const [previewImage, setPreviewImage] = useState(
- data.image ?? '/images/defaultImage.png',
- )
+ const [previewImage, setPreviewImage] = useState(data.image || '')
const { openToast } = useToast()
const { isOpen: alertOpen, closeModal, openModal } = useModal()
- const { links, addLink, isValid, setIsValid, changeLinkName, changeUrl } =
- useLinks(data.links ? data.links : [])
+ const { links, addLink, deleteLink, changeLinkName, changeUrl } = useLinks(
+ data.links ? data.links : [],
+ )
const router = useRouter()
const { isPc } = useMedia()
const editorRef = useRef(null)
+ const validateUrl = (links: ILinkInformation[]) => {
+ const regex =
+ /(https?:\/\/)?(www\.)?[-a-zA-Z0-9@:%.+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%+.~#?&//=]*)/
+
+ const validationResult = links.map((obj: ILinkInformation) => {
+ return regex.test(obj.link)
+ })
+ const isInvalid = validationResult.some((result: any) => result === false)
+ return isInvalid
+ }
+
+ const postHandler = async (linksWithoutId: any) => {
+ await axiosWithAuth.post(
+ `${process.env.NEXT_PUBLIC_CSR_API}/api/v1/showcase/write`,
+ {
+ content: editorRef.current?.getMarkdown() ?? '',
+ teamId: teamId,
+ links: linksWithoutId,
+ image: image.length ? previewImage.split(',')[1] : null,
+ },
+ )
+ }
+ const putHandler = async (linksWithoutId: any) => {
+ await axiosWithAuth.put(
+ `${process.env.NEXT_PUBLIC_CSR_API}/api/v1/showcase/edit/${showcaseId}`,
+ {
+ image: image.length ? previewImage.split(',')[1] : null,
+ content: editorRef.current?.getMarkdown() ?? '',
+ links: linksWithoutId,
+ },
+ )
+ }
+
+ const errorsHandler = (error: any) => {
+ if (error.response) {
+ switch (error.response.status) {
+ case 400:
+ openToast({
+ severity: 'error',
+ message: '요청이 올바르지 않습니다.',
+ })
+
+ break
+ case 403:
+ openToast({
+ severity: 'error',
+ message: '접근이 거부되었습니다.',
+ })
+ break
+ case 404:
+ openToast({
+ severity: 'error',
+ message: '페이지를 찾을 수 없습니다.',
+ })
+ break
+ case 409:
+ openToast({
+ severity: 'error',
+ message: '이미 쇼케이스가 존재합니다.',
+ })
+ break
+ default:
+ openToast({
+ severity: 'error',
+ message: '알 수 없는 에러가 발생했습니다.',
+ })
+ break
+ }
+ } else if (error.request) {
+ openToast({
+ severity: 'error',
+ message: '서버에서 응답이 없습니다.',
+ })
+ } else {
+ openToast({
+ severity: 'error',
+ message: '요청을 설정하는 중에 에러가 발생했습니다.',
+ })
+ }
+ }
+
const submitHandler = async () => {
const linksWithoutId = links.map(({ ...rest }) => rest)
- if (!isValid) {
+ if (validateUrl(linksWithoutId)) {
+ closeModal()
+ openToast({
+ severity: 'error',
+ message: '유효하지 않는 URL이 포함되어 있습니다.',
+ })
return
}
try {
if (requestMethodType === 'post') {
- await axiosWithAuth.post(
- `${process.env.NEXT_PUBLIC_API_URL}/api/v1/showcase/write`,
- {
- image: previewImage.split(',')[1],
- content: editorRef.current?.getMarkdown() ?? '',
- teamId: teamId,
- links: linksWithoutId,
- },
- )
+ await postHandler(linksWithoutId)
router.push(`/teams/${teamId}/showcase`)
} else if (requestMethodType === 'put') {
- await axiosWithAuth.put(
- `${process.env.NEXT_PUBLIC_API_URL}/api/v1/showcase/edit/${showcaseId}`,
- {
- image: image.length ? previewImage.split(',')[1] : null,
- content: editorRef.current?.getMarkdown() ?? '',
- links: linksWithoutId,
- },
- )
+ await putHandler(linksWithoutId)
router.push(`/showcase/detail/${showcaseId}`)
}
} catch (error: any) {
closeModal()
- if (error.response) {
- switch (error.response.status) {
- case 400:
- openToast({
- severity: 'error',
- message: '요청이 올바르지 않습니다.',
- })
-
- break
- case 403:
- openToast({
- severity: 'error',
- message: '접근이 거부되었습니다.',
- })
- break
- case 404:
- openToast({
- severity: 'error',
- message: '페이지를 찾을 수 없습니다.',
- })
- break
- case 409:
- openToast({
- severity: 'error',
- message: '이미 쇼케이스가 존재합니다.',
- })
- break
- default:
- openToast({
- severity: 'error',
- message: '알 수 없는 에러가 발생했습니다.',
- })
- break
- }
- } else if (error.request) {
- openToast({
- severity: 'error',
- message: '서버에서 응답이 없습니다.',
- })
- } else {
- openToast({
- severity: 'error',
- message: '요청을 설정하는 중에 에러가 발생했습니다.',
- })
- }
+ errorsHandler(error)
}
}
@@ -168,19 +194,19 @@ const ShowcaseEditor = ({
- {/* */}
-
+
+
+
-
+
-
+
diff --git a/src/app/showcase/panel/ShowcasePc/ShowcasePcView.tsx b/src/app/showcase/panel/ShowcasePc/ShowcasePcView.tsx
index 3d2396494..c5b0c8542 100644
--- a/src/app/showcase/panel/ShowcasePc/ShowcasePcView.tsx
+++ b/src/app/showcase/panel/ShowcasePc/ShowcasePcView.tsx
@@ -1,14 +1,12 @@
'use client'
import {
- Avatar,
Button,
Card,
CardActions,
CardContent,
IconButton,
Menu,
- MenuItem,
Stack,
Typography,
} from '@mui/material'
@@ -22,6 +20,9 @@ import TagChip from '@/components/TagChip'
import { useRouter } from 'next/navigation'
import NoDataDolphin from '@/components/NoDataDolphin'
import DynamicToastViewer from '@/components/DynamicToastViewer'
+import ShareMenuItem from '@/components/dropdownMenu/ShareMenuItem'
+import ReportMenuItem from '@/components/dropdownMenu/ReportMenuItem'
+import CuAvatar from '@/components/CuAvatar'
function leftPad(value: number) {
if (value >= 10) {
@@ -63,7 +64,7 @@ const ShowcasePcView = ({ data }: { data: ICardData | undefined }) => {
if (!data) return alert('로그인이 필요합니다.')
axiosWithAuth
.post(
- `${process.env.NEXT_PUBLIC_API_URL}/api/v1/showcase/like/${data.id}`,
+ `${process.env.NEXT_PUBLIC_CSR_API}/api/v1/showcase/like/${data.id}`,
)
.then((res) => {
if (res.status === 200) {
@@ -84,7 +85,7 @@ const ShowcasePcView = ({ data }: { data: ICardData | undefined }) => {
if (!data) return alert('로그인이 필요합니다.')
axiosWithAuth
.post(
- `${process.env.NEXT_PUBLIC_API_URL}/api/v1/showcase/favorite/${data.id}`,
+ `${process.env.NEXT_PUBLIC_CSR_API}/api/v1/showcase/favorite/${data.id}`,
)
.then((res) => {
if (res.status === 200) {
@@ -114,11 +115,12 @@ const ShowcasePcView = ({ data }: { data: ICardData | undefined }) => {
height: '37rem',
backgroundColor: 'background.tertiary',
width: '30rem',
+ py: '1rem',
}}
>
{data !== undefined ? (
-
+
{
justifyContent={'space-between'}
>
-
@@ -177,8 +179,13 @@ const ShowcasePcView = ({ data }: { data: ICardData | undefined }) => {
onClose={handleMenuClose}
anchorEl={anchorEl}
>
-
-
+
+
+
@@ -206,6 +213,7 @@ const ShowcasePcView = ({ data }: { data: ICardData | undefined }) => {
width: '100%',
wordBreak: 'break-word',
height: '20rem',
+ boxSizing: 'border-box',
}}
/>
@@ -250,7 +258,7 @@ const ShowcasePcView = ({ data }: { data: ICardData | undefined }) => {
- 기술스택
+ 관련 태그
{data.skill.map((skill, index) => (
diff --git a/src/app/showcase/panel/common/ImageInput.tsx b/src/app/showcase/panel/common/ImageInput.tsx
index fdb79eac4..df1e80ae4 100644
--- a/src/app/showcase/panel/common/ImageInput.tsx
+++ b/src/app/showcase/panel/common/ImageInput.tsx
@@ -1,5 +1,5 @@
'use client'
-import { Stack, Box, Typography } from '@mui/material'
+import { Stack, Box, Button } from '@mui/material'
import React from 'react'
import LabelWithIcon from '../../../../components/LabelWithIcon'
import ImageIcon from '@/icons/ImageIcon'
@@ -23,41 +23,48 @@ const ImageInput = ({
svgIcon={}
message={'쇼케이스 대표 이미지'}
/>
- {
- setImage(image)
- }}
- setPreviewImage={setPreviewImage}
- sx={Style.ShowcaseImageStyle}
- >
- {previewImage ? (
-
- ) : (
+
+ {previewImage && (
-
- 클릭해서 이미지를
-
- 업로드하세요
-
+
)}
-
+ {
+ setImage(image)
+ }}
+ setPreviewImage={setPreviewImage}
+ sx={{ color: 'primary' }}
+ >
+
+ }
+ sx={{ width: ['100%', '26rem'] }}
+ color={'primary'}
+ >
+ 대표이미지 등록
+
+
+
+
)
}
diff --git a/src/app/showcase/panel/common/LinkForm.tsx b/src/app/showcase/panel/common/LinkForm.tsx
index 8a05e3ef3..64e50a831 100644
--- a/src/app/showcase/panel/common/LinkForm.tsx
+++ b/src/app/showcase/panel/common/LinkForm.tsx
@@ -1,40 +1,27 @@
-import { IconButton, Stack, TextField, Typography } from '@mui/material'
-import React, { useState } from 'react'
+import { IconButton, Stack, TextField } from '@mui/material'
+import React from 'react'
import LabelWithIcon from '../../../../components/LabelWithIcon'
import LinkIcon from '@/icons/LinkIcon'
import * as Style from './SkillInput.style'
import PlusIcon from '@/icons/PlusIcon'
import { ILinkInformation } from '@/types/IShowcaseEdit'
+import { TrashIcon } from '@/icons'
interface ILinkFormProps {
links: ILinkInformation[]
- addLink: (linkName: string, linkUrl: string) => void
- isValid: boolean
- setIsValid: (isValid: boolean) => void
- changeLinkName: (id: number, content: string) => void
- changeUrl: (id: number, content: string) => void
+ addLink: (linkName: string, linkUrl: string, id: string) => void
+ deleteLink: (id: string) => void
+ changeLinkName: (id: string, content: string) => void
+ changeUrl: (id: string, content: string) => void
}
const LinkForm = ({
links,
addLink,
- // isValid,
- setIsValid,
+ deleteLink,
changeLinkName,
changeUrl,
}: ILinkFormProps) => {
- const [checker, setChecker] = useState(true)
- const validateUrl = (
- e: React.FocusEvent,
- ) => {
- const completedUrl = e.target.value
- const regex =
- // eslint-disable-next-line no-useless-escape
- /(https?:\/\/)?(www\.)?[-a-zA-Z0-9@:%.\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%\+.~#?&//=]*)/ // eslint-disable-next-line no-useless-escape
- setChecker(regex.test(completedUrl))
- setIsValid(checker)
- }
-
return (
}
- message={'링크'}
+ message={'링크모음'}
/>
{
if (links.length >= 5) return
- addLink('', '')
+ addLink('', '', crypto.randomUUID())
}}
>
@@ -59,7 +46,6 @@ const LinkForm = ({
{links.map((link, index) => (
changeUrl(link.id, e.target.value)}
- onBlur={(e) => validateUrl(e)}
/>
+ {
+ deleteLink(link.id)
+ }}
+ >
+
+
))}
- {!checker && (
-
- 유효하지 않는 URL이 포함되어 있습니다.
-
- )}
)
}
diff --git a/src/app/showcase/panel/common/LinksViewer.tsx b/src/app/showcase/panel/common/LinksViewer.tsx
index e10d076b0..19852b1a2 100644
--- a/src/app/showcase/panel/common/LinksViewer.tsx
+++ b/src/app/showcase/panel/common/LinksViewer.tsx
@@ -5,9 +5,12 @@ import { TagIcon } from '@/icons'
import * as Style from './SkillInput.style'
interface IlinksProps {
- links: string[] | undefined
+ links: ILink[] | undefined
+}
+interface ILink {
+ link: string
+ name: string
}
-
const LinksViewer = ({ links }: IlinksProps) => {
const convertLink = (link: string) => {
const httpPattern = /^https?:\/\//i
@@ -34,20 +37,19 @@ const LinksViewer = ({ links }: IlinksProps) => {
/>
- {links?.map((link: any) => {
+ {links?.map((link: ILink) => {
return (
- <>
-
- {link.name}
-
- >
+
+ {link.name}
+
)
})}
diff --git a/src/app/showcase/panel/common/SkillInput.tsx b/src/app/showcase/panel/common/SkillInput.tsx
index 63f1f9132..8c2727003 100644
--- a/src/app/showcase/panel/common/SkillInput.tsx
+++ b/src/app/showcase/panel/common/SkillInput.tsx
@@ -15,7 +15,7 @@ const SkillInput = ({ skills }: ISkillProps) => {
}
- message="기술 스택"
+ message="관련 태그"
color="text.alternative"
/>
diff --git a/src/app/showcase/panel/common/StartEndDateViewer.style.ts b/src/app/showcase/panel/common/StartEndDateViewer.style.ts
index d4720c379..e25eb346f 100644
--- a/src/app/showcase/panel/common/StartEndDateViewer.style.ts
+++ b/src/app/showcase/panel/common/StartEndDateViewer.style.ts
@@ -1,9 +1,10 @@
export const StartEndDateViewerBox = (isPc?: boolean) => ({
- display: isPc ? 'flex' : 'none',
- justifyContent: isPc ? 'space-between' : 'none',
+ display: 'flex',
+ justifyContent: 'space-between',
alignItems: 'center',
gap: '1rem',
gridArea: 'startEndDateViewer',
width: '100%',
height: isPc ? '6.25rem' : '3.375rem',
+ paddingTop: isPc ? '0' : '1.25rem',
})
diff --git a/src/app/showcase/panel/common/StartEndDateViewer.tsx b/src/app/showcase/panel/common/StartEndDateViewer.tsx
index 4fdc80a92..d19d9250e 100644
--- a/src/app/showcase/panel/common/StartEndDateViewer.tsx
+++ b/src/app/showcase/panel/common/StartEndDateViewer.tsx
@@ -21,7 +21,7 @@ const StartEndDateViewer = ({ start, end }: Idate) => {
message="시작일"
color="text.alternative"
/>
-
+
{start.split('T')[0]} (모집글 게시일)
@@ -31,7 +31,7 @@ const StartEndDateViewer = ({ start, end }: Idate) => {
message="종료일"
color="text.alternative"
/>
-
+
{end.split('T')[0]} (진행 완료일)
diff --git a/src/app/showcase/panel/common/TeamMembers.tsx b/src/app/showcase/panel/common/TeamMembers.tsx
index bf88197a8..bc05fb84b 100644
--- a/src/app/showcase/panel/common/TeamMembers.tsx
+++ b/src/app/showcase/panel/common/TeamMembers.tsx
@@ -5,6 +5,7 @@ import ListIcon from '@/icons/ListIcon'
import * as Style from './SkillInput.style'
import { IMember } from '@/types/IShowcaseEdit'
import CuAvatar from '@/components/CuAvatar'
+import OthersProfile from '@/app/panel/OthersProfile'
const MemberInformation = ({ member }: { member: IMember }) => {
return (
@@ -15,12 +16,33 @@ const MemberInformation = ({ member }: { member: IMember }) => {
width={'auto'}
height={'auto'}
>
-
+ {member.nickname ? (
+
+
+
+ ) : (
+
+ )}
+
- {member.nickname}
+ {/* /탈퇴한 유저의 경우 image = null, nickname = 탈퇴한 유저, role = null */}
+ {member.nickname ?? '탈퇴한 유저'}
{member.isLeader && (
diff --git a/src/app/showcase/panel/types.ts b/src/app/showcase/panel/types.ts
index 58aa8ceb0..e5f7360bc 100644
--- a/src/app/showcase/panel/types.ts
+++ b/src/app/showcase/panel/types.ts
@@ -16,4 +16,5 @@ export interface ICardData {
teamLogo: string | null
start: Date
end: Date
+ hasBeenRemoved?: boolean
}
diff --git a/src/app/showcase/write/page.tsx b/src/app/showcase/write/page.tsx
index 1b651f881..859f79054 100644
--- a/src/app/showcase/write/page.tsx
+++ b/src/app/showcase/write/page.tsx
@@ -8,6 +8,8 @@ import useAxiosWithAuth from '@/api/config'
import useSWR from 'swr'
import CuCircularProgress from '@/components/CuCircularProgress'
import { useRouter, useSearchParams } from 'next/navigation'
+import NoDataDolphin from '@/components/NoDataDolphin'
+import Link from 'next/link'
const ShowCaseWritePage = () => {
const router = useRouter()
@@ -16,7 +18,7 @@ const ShowCaseWritePage = () => {
const axiosWithAuth = useAxiosWithAuth()
const { data, isLoading, error } = useSWR(
- `${process.env.NEXT_PUBLIC_API_URL}/api/v1/showcase/write/${teamId}`,
+ `${process.env.NEXT_PUBLIC_CSR_API}/api/v1/showcase/write/${teamId}`,
(url: string) => axiosWithAuth.get(url).then((res) => res.data),
{ shouldRetryOnError: false },
)
@@ -25,7 +27,17 @@ const ShowCaseWritePage = () => {
if (error) {
if (error.response && error.response.status === 409) {
return (
- {error.response.data.message}
+
+
+ 팀설정으로 돌아가기
+
)
}
return 에러가 발생했습니다.
diff --git a/src/app/signup/page.tsx b/src/app/signup/page.tsx
index 450dc97af..0ce8bf372 100644
--- a/src/app/signup/page.tsx
+++ b/src/app/signup/page.tsx
@@ -20,7 +20,7 @@ import EncryptedSender from '@/components/EncryptedSender'
import { EApiType } from '@/types/EApiType'
const SignUp = () => {
- const API_URL = process.env.NEXT_PUBLIC_API_URL
+ const API_URL = process.env.NEXT_PUBLIC_CSR_API
const router = useRouter()
const searchParams = useSearchParams()
const socialEmail = searchParams.get('social-email')
@@ -119,7 +119,6 @@ const SignUp = () => {
})
.catch((error) => {
setEmailSendStatus('error')
- console.log(error)
if (error.response.data.message) {
openToast({
severity: 'error',
diff --git a/src/app/signup/panel/CodeField.tsx b/src/app/signup/panel/CodeField.tsx
index 30b06c1c6..dbdadc2e1 100644
--- a/src/app/signup/panel/CodeField.tsx
+++ b/src/app/signup/panel/CodeField.tsx
@@ -30,6 +30,7 @@ const CodeField = ({
inputProps={{
maxLength: 10,
}}
+ sx={{ width: '100%' }}
InputProps={{
endAdornment: (
diff --git a/src/app/signup/panel/EmailField.tsx b/src/app/signup/panel/EmailField.tsx
index a9716affb..e8ca8c3e3 100644
--- a/src/app/signup/panel/EmailField.tsx
+++ b/src/app/signup/panel/EmailField.tsx
@@ -33,6 +33,7 @@ const EmailField = ({
inputProps={{
maxLength: 30,
}}
+ sx={{ width: '100%' }}
InputProps={{
endAdornment: (
diff --git a/src/app/signup/panel/NameField.tsx b/src/app/signup/panel/NameField.tsx
index 59a2cefec..11ce57e65 100644
--- a/src/app/signup/panel/NameField.tsx
+++ b/src/app/signup/panel/NameField.tsx
@@ -14,7 +14,9 @@ const NameField = ({
}) => {
return (
<>
- 이름
+
+ 이름
+
{error?.message || '\u00A0'}
diff --git a/src/app/signup/panel/NickNameField.tsx b/src/app/signup/panel/NickNameField.tsx
index c17e2e87e..01f30fcd4 100644
--- a/src/app/signup/panel/NickNameField.tsx
+++ b/src/app/signup/panel/NickNameField.tsx
@@ -24,7 +24,9 @@ const NickNameField = ({
}) => {
return (
<>
- 닉네임
+
+ 닉네임
+
{
@@ -35,6 +37,7 @@ const NickNameField = ({
error={nickNameSendStatus === 'error'}
type="text"
placeholder="닉네임을 입력하세요."
+ sx={{ width: '100%' }}
inputProps={{
minLength: 2,
maxLength: 30,
diff --git a/src/app/signup/panel/PasswordField.tsx b/src/app/signup/panel/PasswordField.tsx
index a20298a43..b317f3c93 100644
--- a/src/app/signup/panel/PasswordField.tsx
+++ b/src/app/signup/panel/PasswordField.tsx
@@ -50,6 +50,7 @@ const PasswordField = ({
pattern: '[A-Za-z0-9!@#$%^&*]*',
type: showPassword,
}}
+ sx={{ width: '100%' }}
onFocus={() => {
setShowValidating(true)
}}
@@ -60,7 +61,11 @@ const PasswordField = ({
showPassword={showPassword}
setShowPassword={setShowPassword}
/>
-
+
>
diff --git a/src/app/team-list/layout.tsx b/src/app/team-list/layout.tsx
index c270f0dd7..d0801934f 100644
--- a/src/app/team-list/layout.tsx
+++ b/src/app/team-list/layout.tsx
@@ -1,12 +1,17 @@
'use client'
-import useMedia from '@/hook/useMedia'
-import { Stack } from '@mui/material'
+import { Container, Stack, Box, Typography } from '@mui/material'
import { ReactNode, useEffect } from 'react'
+import ForceTutorial from '@/components/ForceTutorial'
+import { TeamListTutorial } from '@/components/tutorialContent/TeamListTutorial'
+import useShowTeams from '@/states/useShowTeams'
+import { TeamStatus } from '@/app/teams/types/types'
+
import Sidebar from './panel/Sidebar'
+import * as style from '@/components/NavBarLayout.style'
const TeamsLayout = ({ children }: { children: ReactNode }) => {
- const { isPc, isTablet } = useMedia()
+ const { showTeams } = useShowTeams()
useEffect(() => {
let vh = window.innerHeight * 0.01
@@ -14,22 +19,35 @@ const TeamsLayout = ({ children }: { children: ReactNode }) => {
}, [])
return (
-
+
- {children}
+
+
+
+ {showTeams === TeamStatus.RECRUITING
+ ? '모집 중'
+ : showTeams === TeamStatus.COMPLETE
+ ? '진행 완료'
+ : showTeams === TeamStatus.ONGOING
+ ? '진행 중'
+ : '모집 완료'}
+
+ }
+ />
+
+
+ {children}
+
+
-
+
)
}
diff --git a/src/app/team-list/page.tsx b/src/app/team-list/page.tsx
index cc0e5bf2d..2ff52a287 100644
--- a/src/app/team-list/page.tsx
+++ b/src/app/team-list/page.tsx
@@ -29,7 +29,7 @@ const TeamsListPage = () => {
const { showTeams } = useShowTeams()
const axiosInstance = useAxiosWithAuth()
const { data, isLoading } = useSWR(
- `${process.env.NEXT_PUBLIC_API_URL}/api/v1/team/list?teamStatus=${showTeams}`,
+ `${process.env.NEXT_PUBLIC_CSR_API}/api/v1/team/list?teamStatus=${showTeams}`,
(url: string) => axiosInstance(url).then((res) => res.data),
)
diff --git a/src/app/team-list/panel/Sidebar.tsx b/src/app/team-list/panel/Sidebar.tsx
index d4bf4a875..6e447cb31 100644
--- a/src/app/team-list/panel/Sidebar.tsx
+++ b/src/app/team-list/panel/Sidebar.tsx
@@ -1,15 +1,12 @@
'use client'
import { TeamStatus } from '@/app/teams/types/types'
-import useMedia from '@/hook/useMedia'
+import { Box } from '@mui/material'
+import CuNavBar from '@/components/CuNavBar'
import useShowTeams from '@/states/useShowTeams'
-import {
- ToggleButtonGroup,
- ToggleButton,
- Typography,
- Stack,
-} from '@mui/material'
import { useEffect, useState } from 'react'
+import useMedia from '@/hook/useMedia'
+import * as style from '@/components/NavBarBox.style'
const Sidebar = () => {
const { isPc } = useMedia()
@@ -19,155 +16,65 @@ const Sidebar = () => {
const handleChange = (event: any, newAlignment: any) => {
setAlignment(newAlignment)
}
- const onClickGather = () => setShowTeams(TeamStatus.RECRUITING)
- const onClickBefore = () => setShowTeams(TeamStatus.BEFORE)
- const onClickProgress = () => setShowTeams(TeamStatus.ONGOING)
- const onClickComplete = () => setShowTeams(TeamStatus.COMPLETE)
+ const onClickGather = () => {
+ handleChange(null, TeamStatus.RECRUITING)
+ setShowTeams(TeamStatus.RECRUITING)
+ }
+ const onClickBefore = () => {
+ handleChange(null, TeamStatus.BEFORE)
+ setShowTeams(TeamStatus.BEFORE)
+ }
+ const onClickProgress = () => {
+ handleChange(null, TeamStatus.ONGOING)
+ setShowTeams(TeamStatus.ONGOING)
+ }
+ const onClickComplete = () => {
+ handleChange(null, TeamStatus.COMPLETE)
+ setShowTeams(TeamStatus.COMPLETE)
+ }
+ useEffect(() => {
+ setShowTeams(alignment)
+ }, [alignment, setShowTeams])
useEffect(() => {
setShowTeams(alignment)
}, [setShowTeams])
- return (
- <>
-
-
- 나의 팀리스트
-
- {isPc ? (
-
-
- 모집 중
-
-
-
- 모집 완료
-
-
-
- 진행 중
-
-
-
- 진행 완료
-
-
- ) : (
-
-
- 모집 중
-
+ const getTabValue = () => alignment
-
- 시작 전
-
-
-
- 진행 중
-
-
-
- 진행 완료
-
-
- )}
-
- >
+ return (
+
+ >,
+ },
+ {
+ label: '모집 완료',
+ value: TeamStatus.BEFORE,
+ onClick: onClickBefore,
+ icon: <>>,
+ },
+ {
+ label: '진행 중',
+ value: TeamStatus.ONGOING,
+ onClick: onClickProgress,
+ icon: <>>,
+ },
+ {
+ label: '진행 완료',
+ value: TeamStatus.COMPLETE,
+ onClick: onClickComplete,
+ icon: <>>,
+ },
+ ]}
+ />
+
)
}
diff --git a/src/app/team-list/panel/TeamCard.tsx b/src/app/team-list/panel/TeamCard.tsx
index 824a298b5..4a4b51296 100644
--- a/src/app/team-list/panel/TeamCard.tsx
+++ b/src/app/team-list/panel/TeamCard.tsx
@@ -1,18 +1,26 @@
import { Box, Card, CardActionArea, Stack, Typography } from '@mui/material'
import { ITeamInfo } from '../page'
import { useRouter } from 'next/navigation'
-import { TeamOperationForm } from '@/app/teams/types/types'
+import { TeamOperationForm, TeamStatus } from '@/app/teams/types/types'
import useMedia from '@/hook/useMedia'
import { GeoIcon, TargetIcon, WifiIcon } from './Icons'
const TeamType = (type: string) => {
return (
-
+
{type === 'STUDY' ? '스터디' : '프로젝트'}
@@ -30,10 +38,17 @@ const ApproveChip = ({
const isLeader = job === 'L' ? 'LEADER' : 'MEMBER'
return (
theme.palette.purple.main
+ : (theme) => theme.palette.pink.main,
+ borderRadius: '0.25rem',
+ padding: '0.25rem 0.5rem',
+ height: 'fit-content',
+ alignItems: 'center',
}}
>
@@ -43,10 +58,28 @@ const ApproveChip = ({
)
}
-const TeamCard = ({ team }: { team: ITeamInfo }) => {
+interface ITeamCard {
+ team: ITeamInfo
+}
+
+const TeamCard = ({ team }: ITeamCard) => {
const { isPc } = useMedia()
const router = useRouter()
+ if (team.isApproved === false && team.status !== TeamStatus.RECRUITING) {
+ return <>>
+ }
+
+ const handleTeam = () => {
+ if (!team.isApproved) return
+ router.push(`/teams/${team.id}`)
+ }
+
+ const handleRecruit = () => {
+ if (team.isApproved) return
+ router.push(`/recruit/${team.id}?type=${team.type}`)
+ }
+
return (
{
boxShadow: 'none',
borderRadius: '1.5rem',
backgroundColor: 'background.secondary',
+ backgroundImage: 'none',
}}
>
router.push(`/teams/${team.id}`)}
+ onClick={team.isApproved ? handleTeam : handleRecruit}
>
{
my={'0.5rem'}
justifyContent={'space-between'}
>
-
+
{TeamType(team.type)}
{team.name}
diff --git a/src/app/team-list/panel/TeamsList.tsx b/src/app/team-list/panel/TeamsList.tsx
index c4a0781ba..96ccc90dd 100644
--- a/src/app/team-list/panel/TeamsList.tsx
+++ b/src/app/team-list/panel/TeamsList.tsx
@@ -1,33 +1,21 @@
-import { Stack, Typography } from '@mui/material'
+import { Stack } from '@mui/material'
import { ITeamInfo } from '../page'
import { TeamStatus } from '@/app/teams/types/types'
//icons
-import useShowTeams from '@/states/useShowTeams'
+
import TeamCard from './TeamCard'
import NoDataDolphin from '@/components/NoDataDolphin'
const TeamsList = ({ prop }: { prop: ITeamInfo[] }) => {
- const { showTeams } = useShowTeams()
-
return (
-
-
- {showTeams === TeamStatus.RECRUITING
- ? '모집 중'
- : showTeams === TeamStatus.COMPLETE
- ? '진행 완료'
- : showTeams === TeamStatus.ONGOING
- ? '진행 중'
- : '모집 완료'}
-
-
{
maxHeight={'55vh'}
flex={'2rem'}
>
- {prop.length ? (
- prop.map((team, index) => )
+ {prop.length &&
+ prop.filter(
+ (team) =>
+ team.isApproved === true || team.status === TeamStatus.RECRUITING,
+ ).length !== 0 ? (
+ prop
+ .filter(
+ (team) =>
+ team.isApproved === true ||
+ team.status === TeamStatus.RECRUITING,
+ )
+ .map((team, index) => (
+
+ ))
) : (
)}
diff --git a/src/app/teams/[id]/board/@list/page.tsx b/src/app/teams/[id]/board/@list/page.tsx
index c37c6acf3..54226f30a 100644
--- a/src/app/teams/[id]/board/@list/page.tsx
+++ b/src/app/teams/[id]/board/@list/page.tsx
@@ -2,7 +2,7 @@
import { useState, useEffect } from 'react'
import { AxiosResponse, isAxiosError } from 'axios'
import { useRouter } from 'next/navigation'
-import { Box, Stack } from '@mui/material'
+import { Box, Stack, Typography } from '@mui/material'
import useAxiosWithAuth from '@/api/config'
import {
ListPageContainer,
@@ -34,9 +34,9 @@ const TeamBoard = ({ params }: { params: { id: string } }) => {
`/api/v1/team-page/simple/${teamId}`,
)
setBoardList(res.data)
- if (!res.data || res.data.length == 0)
- throw new Error('팀 페이지가 존재하지 않습니다.')
- setBoard('LIST', res.data[0].boardId)
+ if (!res.data) throw new Error('팀 페이지가 존재하지 않습니다.')
+ if (res.data.length === 0) setBoard('LIST', undefined)
+ else setBoard('LIST', res.data[0].boardId)
} catch (err: unknown) {
if (isAxiosError(err) && err.response?.status === 403) {
alert('팀 페이지에 접근할 권한이 없습니다.')
@@ -52,7 +52,7 @@ const TeamBoard = ({ params }: { params: { id: string } }) => {
return (
- {boardList && boardList.length > 0 && boardId && (
+ {boardList && (
<>
{
/>
-
-
-
- } />
-
-
- {
- setBoard('EDIT', boardId)
- }}
- />
- {
- setBoard('EDIT', boardId)
- }}
- />
-
-
-
+ {boardList.length > 0 && boardId ? (
+ <>
+
+
+
+ }
+ />
+
+
+ {
+ setBoard('EDIT', boardId)
+ }}
+ />
+ {
+ setBoard('EDIT', boardId)
+ }}
+ />
+
+
+
+ >
+ ) : (
+
+ 게시판이 존재하지 않습니다. 게시판 관리에서 게시판을
+ 생성해보세요! (PC에서만 가능)
+
+ )}
>
)}
diff --git a/src/app/teams/[id]/board/@setting/page.style.ts b/src/app/teams/[id]/board/@setting/page.style.ts
index 85ee441cd..aecd0cb0c 100644
--- a/src/app/teams/[id]/board/@setting/page.style.ts
+++ b/src/app/teams/[id]/board/@setting/page.style.ts
@@ -11,4 +11,5 @@ export const mobilePage = {
export const inputContainer = {
background: (theme: Theme) => theme.palette.background.tertiary,
padding: '0rem 0.75rem',
+ alignItems: 'center',
}
diff --git a/src/app/teams/[id]/board/@setting/page.tsx b/src/app/teams/[id]/board/@setting/page.tsx
index ba224d899..5a245db4c 100644
--- a/src/app/teams/[id]/board/@setting/page.tsx
+++ b/src/app/teams/[id]/board/@setting/page.tsx
@@ -1,13 +1,15 @@
'use client'
import { useRef } from 'react'
import { isAxiosError } from 'axios'
-import useSWR from 'swr'
+import useSWR, { useSWRConfig } from 'swr'
import { Stack, TextField, Typography } from '@mui/material'
import useAxiosWithAuth from '@/api/config'
import BackgroundBox from '@/components/BackgroundBox'
import CuButton from '@/components/CuButton'
+import CuCircularProgress from '@/components/CuCircularProgress'
import useMedia from '@/hook/useMedia'
import useToast from '@/states/useToast'
+import useTeamPageState from '@/states/useTeamPageState'
import { ITeamBoard } from '@/types/TeamBoardTypes'
import BoardItem from './panel/BoardItem'
import * as style from './page.style'
@@ -42,6 +44,8 @@ const TeamBoardSetting = ({ params }: { params: { id: string } }) => {
const { isPc } = useMedia()
const axiosWithAuth = useAxiosWithAuth()
const { openToast } = useToast()
+ const { resetState } = useTeamPageState()
+ const { mutate } = useSWRConfig()
const { data, isLoading, error } = useSWR(
`/api/v1/team-page/simple/${teamId}`,
@@ -70,6 +74,7 @@ const TeamBoardSetting = ({ params }: { params: { id: string } }) => {
severity: 'success',
message: '게시판을 추가했습니다.',
})
+ mutate(`/api/v1/team-page/simple/${teamId}`)
})
.catch((e: unknown) => {
if (isAxiosError(e)) {
@@ -78,16 +83,42 @@ const TeamBoardSetting = ({ params }: { params: { id: string } }) => {
severity: 'error',
message: '이미 존재하는 게시판 이름입니다.',
})
- return
+ } else if (e.response?.status === 403) {
+ openToast({
+ severity: 'error',
+ message: '게시판 추가는 팀 리더만 가능합니다.',
+ })
+ } else {
+ openToast({
+ severity: 'error',
+ message: '게시판을 추가하지 못했습니다.',
+ })
}
+ } else {
+ openToast({
+ severity: 'error',
+ message: '게시판을 추가하지 못했습니다.',
+ })
}
- openToast({
- severity: 'error',
- message: '게시판을 추가하지 못했습니다.',
- })
})
}
+ if (error) {
+ if (isAxiosError(error) && error.response?.status === 403) {
+ alert('게시판 관리는 팀 리더만 가능합니다')
+ } else {
+ alert('게시판 목록을 불러오지 못했습니다.')
+ }
+ resetState()
+ return null
+ }
+
+ if (!isLoading && !data) {
+ alert('게시판 목록을 불러오지 못했습니다.')
+ resetState()
+ return null
+ }
+
// 모바일에서는 게시판 관리가 불가능합니다.
if (!isPc)
return (
@@ -97,42 +128,69 @@ const TeamBoardSetting = ({ params }: { params: { id: string } }) => {
)
- // TODO : 좀 더 구체적인 에러처리
- if (!data || isLoading || error) return null
return (
-
-
-
-
-
-
-
-
-
-
- {data.map((board: ITeamBoard) => (
-
- ))}
+
+
+
+ {isLoading ? (
+
+ ) : (
+
+
+
+
+
+
+
+
+
+ {data?.map((board: ITeamBoard) => (
+
+ ))}
+
+
-
-
-
+ )}
+
+
)
}
diff --git a/src/app/teams/[id]/board/@setting/panel/BoardItem.tsx b/src/app/teams/[id]/board/@setting/panel/BoardItem.tsx
index d04414fb4..06eb0d4ae 100644
--- a/src/app/teams/[id]/board/@setting/panel/BoardItem.tsx
+++ b/src/app/teams/[id]/board/@setting/panel/BoardItem.tsx
@@ -1,3 +1,5 @@
+import { useSWRConfig } from 'swr'
+import { isAxiosError } from 'axios'
import useAxiosWithAuth from '@/api/config'
import { ITeamBoard } from '@/types/TeamBoardTypes'
import { IconButton, Stack, Typography } from '@mui/material'
@@ -6,21 +8,47 @@ import useModal from '@/hook/useModal'
import useToast from '@/states/useToast'
import CuTextModal from '@/components/CuTextModal'
-const BoardItem = ({ board }: { board: ITeamBoard }) => {
+const BoardItem = ({
+ board,
+ teamId,
+}: {
+ board: ITeamBoard
+ teamId: string
+}) => {
const axiosWithAuth = useAxiosWithAuth()
const { isOpen, closeModal, openModal } = useModal()
const { openToast } = useToast()
+ const { mutate } = useSWRConfig()
+
const handleDeleteBoard = (boardId: number) => {
axiosWithAuth
.delete(`/api/v1/team/board/${boardId}`)
.then(() => {
- alert('게시판을 삭제했습니다.')
- })
- .catch(() => {
openToast({
- severity: 'error',
- message: '게시판을 삭제하지 못했습니다.',
+ severity: 'success',
+ message: '게시판을 삭제했습니다.',
})
+ mutate(`/api/v1/team-page/simple/${teamId}`)
+ })
+ .catch((e: unknown) => {
+ if (isAxiosError(e)) {
+ if (e.response?.status === 403) {
+ openToast({
+ severity: 'error',
+ message: '게시판 추가는 팀 리더만 가능합니다.',
+ })
+ } else {
+ openToast({
+ severity: 'error',
+ message: '게시판을 추가하지 못했습니다.',
+ })
+ }
+ } else {
+ openToast({
+ severity: 'error',
+ message: '게시판을 추가하지 못했습니다.',
+ })
+ }
})
}
return (
diff --git a/src/app/teams/[id]/board/layout.tsx b/src/app/teams/[id]/board/layout.tsx
index deebe7215..618a2de29 100644
--- a/src/app/teams/[id]/board/layout.tsx
+++ b/src/app/teams/[id]/board/layout.tsx
@@ -1,5 +1,5 @@
'use client'
-import { ReactNode, useEffect } from 'react'
+import { ReactNode } from 'react'
import useTeamPageState from '@/states/useTeamPageState'
interface ITeamLayoutProps {
@@ -10,12 +10,7 @@ interface ITeamLayoutProps {
}
const TeamBoardMain = ({ list, detail, edit, setting }: ITeamLayoutProps) => {
- const { boardType, resetState } = useTeamPageState()
- useEffect(() => {
- return () => {
- resetState()
- }
- }, [])
+ const { boardType } = useTeamPageState()
if (boardType === 'EDIT') {
return edit
}
diff --git a/src/app/teams/[id]/layout.tsx b/src/app/teams/[id]/layout.tsx
index 17095ff41..62bc17871 100644
--- a/src/app/teams/[id]/layout.tsx
+++ b/src/app/teams/[id]/layout.tsx
@@ -1,10 +1,11 @@
'use client'
import { ReactNode, useEffect } from 'react'
-import { Stack } from '@mui/material'
+import { Stack, Container, Box, useMediaQuery } from '@mui/material'
import useTeamPageState from '@/states/useTeamPageState'
-import useMedia from '@/hook/useMedia'
import TeamSidebar from './panel/NavBar'
+import * as style from '@/components/NavBarLayout.style'
+import useMedia from '@/hook/useMedia'
const TeamLayout = ({
params,
@@ -13,40 +14,46 @@ const TeamLayout = ({
params: { id: string }
children: ReactNode
}) => {
- const { isPc } = useMedia()
const { layout, resetState } = useTeamPageState()
const id = params.id
useEffect(() => {
+ resetState()
return () => {
resetState()
}
}, [])
+ const isFourRow = useMediaQuery('(min-width:997px)')
+ const { isPc } = useMedia()
+
+ if (layout === 'FULLPAGE') {
+ return (
+
+ {children}
+
+ )
+ }
+
return (
-
+
- {layout === 'SIDEBAR' && (
-
-
-
- )}
-
+
{children}
-
+
-
+
)
}
diff --git a/src/app/teams/[id]/notice/@edit/panel/NoticeEditForm.tsx b/src/app/teams/[id]/notice/@edit/panel/NoticeEditForm.tsx
index ef9cbc0ae..d23d2a561 100644
--- a/src/app/teams/[id]/notice/@edit/panel/NoticeEditForm.tsx
+++ b/src/app/teams/[id]/notice/@edit/panel/NoticeEditForm.tsx
@@ -1,5 +1,6 @@
'use client'
import { FormEvent, useEffect, useRef, useState } from 'react'
+import { isAxiosError } from 'axios'
import { Editor } from '@toast-ui/editor'
import useAxiosWithAuth from '@/api/config'
import useTeamPageState from '@/states/useTeamPageState'
@@ -36,7 +37,7 @@ const NoticeEditForm = ({
})
})
.catch(() => {
- alert('게시글을 불러오는데 실패했습니다.')
+ alert('글을 불러오는데 실패했습니다.')
setNotice('LIST')
})
.finally(() => {
@@ -67,11 +68,18 @@ const NoticeEditForm = ({
alert('공지사항을 수정했습니다.')
setNotice('DETAIL', postId)
})
- .catch(() => {
- openToast({
- severity: 'error',
- message: '공지사항 수정에 실패했습니다.',
- })
+ .catch((e: unknown) => {
+ if (isAxiosError(e) && e.response?.status === 403) {
+ openToast({
+ severity: 'error',
+ message: '공지사항 수정은 팀 리더와 작성자만 가능합니다.',
+ })
+ } else {
+ openToast({
+ severity: 'error',
+ message: '공지사항 수정에 실패했습니다.',
+ })
+ }
})
} else {
// 글 작성
@@ -84,11 +92,18 @@ const NoticeEditForm = ({
alert('공지사항이 등록되었습니다.')
setNotice('DETAIL', res.data.postId)
})
- .catch(() => {
- openToast({
- severity: 'error',
- message: '공지사항 수정에 실패했습니다.',
- })
+ .catch((e: unknown) => {
+ if (isAxiosError(e) && e.response?.status === 403) {
+ openToast({
+ severity: 'error',
+ message: '공지사항 등록은 팀 리더와 작성자만 가능합니다.',
+ })
+ } else {
+ openToast({
+ severity: 'error',
+ message: '공지사항 등록에 실패했습니다.',
+ })
+ }
})
}
}
diff --git a/src/app/teams/[id]/notice/@list/page.tsx b/src/app/teams/[id]/notice/@list/page.tsx
index f80460dd2..b6e1168be 100644
--- a/src/app/teams/[id]/notice/@list/page.tsx
+++ b/src/app/teams/[id]/notice/@list/page.tsx
@@ -35,7 +35,10 @@ const TeamNotice = ({ params }: { params: { id: string } }) => {
공지사항
- } />
+ }
+ />
+ if (isEmpty)
+ return (
+
+ )
+
return (
{data?.map((page, index) => {
diff --git a/src/app/teams/[id]/notice/layout.tsx b/src/app/teams/[id]/notice/layout.tsx
index 2c2dc4457..215bc8143 100644
--- a/src/app/teams/[id]/notice/layout.tsx
+++ b/src/app/teams/[id]/notice/layout.tsx
@@ -1,5 +1,5 @@
'use client'
-import { ReactNode, useEffect } from 'react'
+import { ReactNode } from 'react'
import useTeamPageState from '@/states/useTeamPageState'
interface ITeamLayoutProps {
@@ -9,12 +9,7 @@ interface ITeamLayoutProps {
}
const TeamNoticeMain = ({ list, detail, edit }: ITeamLayoutProps) => {
- const { boardType, resetState } = useTeamPageState()
- useEffect(() => {
- return () => {
- resetState()
- }
- }, [])
+ const { boardType } = useTeamPageState()
if (boardType === 'EDIT') {
return edit
}
diff --git a/src/app/teams/[id]/page.tsx b/src/app/teams/[id]/page.tsx
index ba0ae4d67..70dcedaef 100644
--- a/src/app/teams/[id]/page.tsx
+++ b/src/app/teams/[id]/page.tsx
@@ -1,23 +1,37 @@
'use client'
import { useRouter } from 'next/navigation'
-import { Typography, Box, Stack } from '@mui/material'
+import { Stack } from '@mui/material'
import TeamInfoContainer from './panel/TeamInfoContainer'
// import TeamDnD from './panel/TeamDnD'
-import Image from 'next/image'
+import CuButton from '@/components/CuButton'
+import { useEffect, useState } from 'react'
+import CuCircularProgress from '@/components/CuCircularProgress'
+import ForbiddenDolphin from '@/components/WorkingDolphin'
const TeamsPage = ({ params }: { params: { id: string } }) => {
+ const [isClient, setIsClient] = useState(false)
const router = useRouter()
const { id } = params
+ useEffect(() => {
+ setIsClient(true) // 모바일 환경에서 hydration을 막기 위한 변수
+ }, [])
+
+ if (!isClient) return
+
return (
-
- router.push('/team-list')}
- sx={{ color: '#9B9B9B', cursor: 'pointer' }}
- >
- 팀리스트로 돌아가기
-
+
+ router.push('/team-list')}
+ message={'팀 리스트로 돌아가기'}
+ TypographyProps={{
+ color: 'text.alternative',
+ variant: 'Caption',
+ }}
+ variant="text"
+ style={{ width: 'fit-content' }}
+ />
{/*준비중 메세지*/}
{
height={'100%'}
alignItems={'center'}
justifyContent={'center'}
+ sx={{
+ borderRadius: '1rem',
+ backgroundColor: 'background.secondary',
+ }}
>
-
- 메인 팀페이지는 준비중입니다!
- (다른 기능은 이용 가능합니다)
+
- {/**/}
-
+ {/* */}
+
)
}
diff --git a/src/app/teams/[id]/panel/NavBar.tsx b/src/app/teams/[id]/panel/NavBar.tsx
index 8110f2055..30ca7d490 100644
--- a/src/app/teams/[id]/panel/NavBar.tsx
+++ b/src/app/teams/[id]/panel/NavBar.tsx
@@ -10,64 +10,80 @@ import {
SettingIcon,
ShowcaseIcon,
} from '@/icons/TeamPage'
+import { Box } from '@mui/material'
+import useMedia from '@/hook/useMedia'
import * as style from './NavBar.style'
+import * as navStyle from '@/components/NavBarBox.style'
const getTabValue = (path: string) => {
if (path.includes('/notice')) return 'notice'
else if (path.includes('/board')) return 'board'
else if (path.includes('/setting')) return 'setting'
+ else if (path.includes('/peerlog')) return 'peerlog'
+ else if (path.includes('/showcase')) return 'showcase'
else return 'main'
}
const TeamSidebar = ({ id }: { id: string }) => {
const router = useRouter()
+ const { isPc, isLargeTablet } = useMedia()
+
return (
- router.push(`/teams/${id}`),
- value: 'main',
- icon: ,
- },
- {
- label: '공지사항',
- onClick: () => router.push(`/teams/${id}/notice`),
- value: 'notice',
- icon: ,
- },
- {
- label: '게시판',
- onClick: () => router.push(`/teams/${id}/board`),
- value: 'board',
- icon: ,
- },
- {
- label: '팀설정',
- onClick: () => router.push(`/teams/${id}/setting`),
- value: 'setting',
- icon: ,
- },
- {
- label: '피어로그',
- onClick: () => router.push(`/teams/${id}/peerlog`),
- value: 'peerlog',
- icon: ,
- new: true,
- },
- {
- label: '쇼케이스',
- onClick: () => router.push(`/teams/${id}/showcase`),
- value: 'showcase',
- icon: ,
- new: true,
- // disabled: true,
- },
- ]}
- />
+
+ router.push(`/teams/${id}`),
+ value: 'main',
+ icon: ,
+ },
+ {
+ label: '공지사항',
+ onClick: () => router.push(`/teams/${id}/notice`),
+ value: 'notice',
+ icon: ,
+ },
+ {
+ label: '게시판',
+ onClick: () => router.push(`/teams/${id}/board`),
+ value: 'board',
+ icon: ,
+ },
+ {
+ label: '팀설정',
+ onClick: () => router.push(`/teams/${id}/setting`),
+ value: 'setting',
+ icon: ,
+ },
+ {
+ label: '피어로그',
+ onClick: () => router.push(`/teams/${id}/peerlog`),
+ value: 'peerlog',
+ icon: ,
+ isSoon: true,
+ disabled: true,
+ },
+ {
+ label: '쇼케이스',
+ onClick: () => router.push(`/teams/${id}/showcase`),
+ value: 'showcase',
+ icon: ,
+ isNew: true,
+ },
+ ]}
+ />
+
)
}
+const getNavStyle = (isTablet: boolean, isPc: boolean) => {
+ if (isTablet) return navStyle.tabletNavBar
+ if (isPc) return navStyle.pcNavBar
+ return navStyle.mobileNavBar
+}
+
export default TeamSidebar
diff --git a/src/app/teams/[id]/panel/TeamDnD.tsx b/src/app/teams/[id]/panel/TeamDnD.tsx
index 1ccf985bd..10fb1c30e 100644
--- a/src/app/teams/[id]/panel/TeamDnD.tsx
+++ b/src/app/teams/[id]/panel/TeamDnD.tsx
@@ -1,4 +1,6 @@
-import { Stack, Typography } from '@mui/material'
+'use client'
+
+import { Stack } from '@mui/material'
import useAxiosWithAuth from '@/api/config'
import 'react-grid-layout/css/styles.css'
import { ITeamDnDLayout, SizeType, WidgetType } from '@/types/ITeamDnDLayout'
@@ -9,6 +11,8 @@ import WidgetsRender from './WidgetsRender'
import WidgetList from '@/app/teams/[id]/panel/WidgetList'
import useDnDStore from '@/states/useDnDStore'
import { useParams } from 'next/navigation'
+import CuCircularProgress from '@/components/CuCircularProgress'
+import NoDataDolphin from '@/components/NoDataDolphin'
export const sizeRatio = {
S: { w: 1, h: 1 },
@@ -31,7 +35,7 @@ const TeamDnD = ({ id }: { id: string }) => {
const [size, setSize] = useState('S')
const axiosInstance = useAxiosWithAuth()
const { trigger, data, error, isMutating } = useSWRMutation(
- `${process.env.NEXT_PUBLIC_API_URL}/api/v1/dnd-main/read`,
+ `${process.env.NEXT_PUBLIC_CSR_API}/api/v1/dnd-main/read`,
(url: string) =>
axiosInstance
.post(url, { teamId: id, type: 'team' })
@@ -50,8 +54,10 @@ const TeamDnD = ({ id }: { id: string }) => {
}, [data])
// api 에러 생길 시 주석 처리 필요
- if (!data && isMutating) return 로딩중입니다...
- if (!data && error) return 에러 발생
+ if (!data && isMutating) return
+
+ if (!data && error)
+ return
return (
{
- switch (status) {
- case 'RECRUITING':
- return (
-
- 모집 중
-
- }
- sx={{ backgroundColor: '#FFFBDB' }}
- />
- )
- case 'BEFORE':
- return
- case 'ONGOING':
- return (
-
- 진행 중
-
- }
- sx={{ backgroundColor: '#EADFFF' }}
- />
- )
- case 'COMPLETE':
- return (
-
- 완료
-
- }
- sx={{ backgroundColor: '#F7C5C5' }}
- />
- )
- }
+const teamStatusMessage = {
+ RECRUITING: {
+ message: '모집중',
+ color: 'yellow.strong',
+ },
+ BEFORE: {
+ message: '시작전',
+ color: 'yellow.strong',
+ },
+ ONGOING: {
+ message: '진행중',
+ color: 'yellow.strong',
+ },
+ COMPLETE: {
+ message: '완료',
+ color: 'yellow.strong',
+ },
}
type TIconType = 'MEMBER' | 'LEADER' | 'DATE'
@@ -51,27 +28,47 @@ interface IIconInfoProps {
text: string
}
+export const StatusIcon = ({ status }: { status: TTeamStatus }) => {
+ return (
+
+ {teamStatusMessage[status].message}
+
+ )
+}
+
export const IconInfo = ({ type, text }: IIconInfoProps) => {
switch (type) {
case 'MEMBER':
return (
-
-
- {text}
+
+
+
+ {text}
+
)
case 'LEADER':
return (
-
-
- {text}
+
+
+
+ {text}
+
+
+ 리더
+
)
case 'DATE':
return (
-
-
- {text} ~
+
+
+
+ {text} ~
+
)
}
diff --git a/src/app/teams/[id]/panel/TeamInfoContainer.style.ts b/src/app/teams/[id]/panel/TeamInfoContainer.style.ts
new file mode 100644
index 000000000..fa10a531b
--- /dev/null
+++ b/src/app/teams/[id]/panel/TeamInfoContainer.style.ts
@@ -0,0 +1,5 @@
+export const teamAvatar = {
+ width: '7rem',
+ height: '7rem',
+ borderRadius: '0.5rem',
+}
diff --git a/src/app/teams/[id]/panel/TeamInfoContainer.tsx b/src/app/teams/[id]/panel/TeamInfoContainer.tsx
index 682e4ba99..a5dfc2d8c 100644
--- a/src/app/teams/[id]/panel/TeamInfoContainer.tsx
+++ b/src/app/teams/[id]/panel/TeamInfoContainer.tsx
@@ -1,19 +1,20 @@
import { useRouter } from 'next/navigation'
import useSWR from 'swr'
-import { Avatar, Stack, Typography } from '@mui/material'
+import { useEffect } from 'react'
+import { Stack, Typography } from '@mui/material'
import useAxiosWithAuth from '@/api/config'
import CuCircularProgress from '@/components/CuCircularProgress'
+import CuAvatar from '@/components/CuAvatar'
+import useHeaderStore from '@/states/useHeaderStore'
import { ITeamInfo } from '@/types/ITeamInfo'
import { StatusIcon, IconInfo } from './TeamInfoComponent'
-import { useEffect } from 'react'
-import useHeaderStore from '@/states/useHeaderStore'
-
-const defaultLogoPath = '/images/profile.jpeg' // TODO : 기본 로고 path 확인하기
+import * as style from './TeamInfoContainer.style'
+import { isAxiosError } from 'axios'
const TeamInfoContainer = ({ id }: { id: number }) => {
const axiosInstance = useAxiosWithAuth()
const { data, error, isLoading } = useSWR(
- `${process.env.NEXT_PUBLIC_API_URL}/api/v1/team/main/${id}`,
+ `${process.env.NEXT_PUBLIC_CSR_API}/api/v1/team/main/${id}`,
(url: string) => axiosInstance(url).then((res) => res.data),
)
const { setHeaderTitle } = useHeaderStore()
@@ -30,8 +31,10 @@ const TeamInfoContainer = ({ id }: { id: number }) => {
}, [data])
if (error) {
- if (error.status === 403) alert('팀 페이지에 접근할 권한이 없습니다.')
- else if (error.status === 404) alert('팀 페이지가 존재하지 않습니다.')
+ if (isAxiosError(error) && error.response?.status === 403)
+ alert('팀 페이지에 접근할 권한이 없습니다.')
+ else if (isAxiosError(error) && error.response?.status === 404)
+ alert('팀 페이지가 존재하지 않습니다.')
else alert('팀 페이지에 접근할 수 없습니다.')
router.push('/team-list')
return
@@ -45,28 +48,30 @@ const TeamInfoContainer = ({ id }: { id: number }) => {
return (
<>
-
+
{isLoading || !data ? (
) : (
<>
-
-
-
- {data.name}
+
+
+ {data.name}
-
-
-
+
+
+
>
diff --git a/src/app/teams/[id]/panel/WidgetUpdate.tsx b/src/app/teams/[id]/panel/WidgetUpdate.tsx
index 4e193dfcd..37d1d3952 100644
--- a/src/app/teams/[id]/panel/WidgetUpdate.tsx
+++ b/src/app/teams/[id]/panel/WidgetUpdate.tsx
@@ -42,19 +42,18 @@ const WidgetUpdate = ({
}
if (isCreate) {
await axiosInstance.post(
- `${process.env.NEXT_PUBLIC_API_URL}/api/v1/dnd-main/create`,
+ `${process.env.NEXT_PUBLIC_CSR_API}/api/v1/dnd-main/create`,
teamWidgetInfo,
)
} else
await axiosInstance.post(
- `${process.env.NEXT_PUBLIC_API_URL}/api/v1/dnd-main/update`,
+ `${process.env.NEXT_PUBLIC_CSR_API}/api/v1/dnd-main/update`,
teamWidgetInfo,
)
alert('저장되었습니다.')
setModalOpen(false)
setEdit(false)
} catch (e) {
- console.log('e', e)
alert('저장에 실패하였습니다.')
}
}, [teamId, layouts, isCreate, axiosInstance, setEdit])
@@ -74,7 +73,7 @@ const WidgetUpdate = ({
text: '취소',
onClick: () => setModalOpen(false),
}}
- content={'팀 페이지를 저장하시겠습니까?'}
+ content={'팀페이지를 저장하시겠습니까?'}
/>
{/* 팀페이지 수정 버튼 */}
diff --git a/src/app/teams/[id]/panel/WidgetsRender.tsx b/src/app/teams/[id]/panel/WidgetsRender.tsx
index b7f1f59c4..38ee56da3 100644
--- a/src/app/teams/[id]/panel/WidgetsRender.tsx
+++ b/src/app/teams/[id]/panel/WidgetsRender.tsx
@@ -1,6 +1,8 @@
+'use client'
+
import { Box, IconButton, useMediaQuery } from '@mui/material'
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
-import GridLayout, { Layout } from 'react-grid-layout'
+import { Layout } from 'react-grid-layout'
import {
ITeamDnDLayout,
IWidget,
@@ -10,6 +12,7 @@ import {
import RemoveCircleIcon from '@mui/icons-material/RemoveCircle'
import WidgetUpdate from '@/app/teams/[id]/panel/WidgetUpdate'
import SelectedWidget from './SelectedWidget'
+import { Responsive as ResponsiveGridLayout } from 'react-grid-layout'
interface IWidgetsRenderProps {
data: ITeamDnDLayout | undefined
@@ -27,7 +30,7 @@ const WidgetsRender = ({
data,
type,
size,
- isDropping,
+ // isDropping,
droppingItem,
edit,
setEdit,
@@ -54,10 +57,10 @@ const WidgetsRender = ({
}, [data])
const [layouts, setLayouts] = useState(setInitLayouts)
- const [prevLayouts, setPrevLayouts] = useState(null)
+ // const [prevLayouts, setPrevLayouts] = useState(null)
/* tablet 보다 크면 4개, 작으면 2개 */
- const isFourRow = useMediaQuery('(min-width:900px)')
+ const isFourRow = useMediaQuery('(min-width:997px)')
/* 지정된 레이아웃에서 벗어나지 않았는지 확인 */
const isValidLayout = useCallback((newLayout: Layout[]) => {
@@ -73,11 +76,11 @@ const WidgetsRender = ({
* 그러나 onLayoutChange시에는 react-grid-layout에서 자동으로 아이템을 넣는 방식이기 때문에 제한 불가능
* 따라서 최대 높이를 제한하기 위해 위젯이 추가될 때마다 height를 계산하여 height가 제한 값을 넘은 경우 다시 재조정해줘야함
*/
- useEffect(() => {
- if (prevLayouts) {
- setLayouts(prevLayouts)
- }
- }, [prevLayouts])
+ // useEffect(() => {
+ // if (prevLayouts) {
+ // setLayouts(prevLayouts)
+ // }
+ // }, [prevLayouts])
/* 윈도우 resize시 위젯 다시 렌더링 */
useEffect(() => {
@@ -114,26 +117,30 @@ const WidgetsRender = ({
[edit, isValidLayout, index, type, size, layouts],
)
- /* 레이아웃이 변경될때마다 호출 */
- const onLayoutChange = useCallback(
- (currentLayout: Layout[]) => {
- //드롭중일 경우 이미 onDrop에서 처리하고 있으므로 처리x
- if (isDropping) return
- //레이아웃 범위를 넘어갈 시 처리
- if (!isValidLayout(currentLayout)) {
- setPrevLayouts(layouts)
- }
- const updatedCurrentWidget: IWidget[] = currentLayout.map(
- (grid: Layout, i: number) => ({
- ...layouts[i],
- grid,
- updatedAt: new Date(),
- }),
- )
- setLayouts(updatedCurrentWidget)
- },
- [isDropping, isValidLayout, layouts],
- )
+ // /* 레이아웃이 변경될때마다 호출 */
+ // const onLayoutChange = useCallback(
+ // (currentLayout: Layout[]) => {
+ // //드롭중일 경우 이미 onDrop에서 처리하고 있으므로 처리x
+ // if (isDropping) return
+ // //레이아웃 범위를 넘어갈 시 처리
+ // if (!isValidLayout(currentLayout)) {
+ // setPrevLayouts(layouts)
+ // }
+ // const updatedCurrentWidget: IWidget[] = currentLayout.map(
+ // (grid: Layout, i: number) => ({
+ // ...layouts[i],
+ // grid,
+ // updatedAt: new Date(),
+ // }),
+ // )
+ // setLayouts(updatedCurrentWidget)
+ // // setToolbox({
+ // // ...toolbox,
+ // // [currentBreakpoint]: updatedCurrentWidget,
+ // // })
+ // },
+ // [isDropping, isValidLayout, layouts],
+ // )
const removeWidget = useCallback(
(idx: string) => {
@@ -150,6 +157,14 @@ const WidgetsRender = ({
return isFourRow ? width / 4 : width / 2
}, [isFourRow, layoutRef?.current?.clientWidth])
+ // const widgetHeight = useMemo(() => {
+ // const height = layoutRef?.current?.clientHeight
+ // if (!height) return 0
+ // return isFourRow ? height / 4 : height / 8
+ // }, [isFourRow, layoutRef?.current?.clientHeight])
+
+ const cols = { lg: 4, md: 4, sm: 4, xs: 4, xxs: 2 }
+
return (
-
@@ -224,7 +237,7 @@ const WidgetsRender = ({
)
})}
-
+
)
diff --git a/src/app/teams/[id]/panel/widgets/CalenderWidget/EditModalContent.tsx b/src/app/teams/[id]/panel/widgets/CalenderWidget/EditModalContent.tsx
index 177b308d9..5f1871b52 100644
--- a/src/app/teams/[id]/panel/widgets/CalenderWidget/EditModalContent.tsx
+++ b/src/app/teams/[id]/panel/widgets/CalenderWidget/EditModalContent.tsx
@@ -90,9 +90,7 @@ const EditModalContent = ({
.then((res) => {
scheduleData.eventId = res.data // eventId를 받아옴
})
- .catch((e) => {
- console.error(e)
- })
+ .catch(() => {})
.finally(() => {
// TODO : 위젯 업데이트
openToast({
diff --git a/src/app/teams/[id]/setting/page.tsx b/src/app/teams/[id]/setting/page.tsx
index c85df610e..04988aaa7 100644
--- a/src/app/teams/[id]/setting/page.tsx
+++ b/src/app/teams/[id]/setting/page.tsx
@@ -1,18 +1,19 @@
'use client'
+import { isAxiosError } from 'axios'
import { useRouter } from 'next/navigation'
-import { Button, Card, Stack, Typography } from '@mui/material'
+import { Button, Stack, Typography, Card } from '@mui/material'
import { useEffect, useState } from 'react'
import SetupMember from './panel/SettingTeamMember'
import ApplicantList from './panel/ApplicantList'
import useSWR from 'swr'
import useAxiosWithAuth from '@/api/config'
-import { ITeam, TeamStatus, TeamType } from '../../types/types'
+import { ITeam, TeamStatus } from '../../types/types'
+// import { ITeam, TeamStatus, TeamType } from '../../types/types'
import RedirectionRecruit from './panel/RedirectRecruitPage'
-import TeamJobAdd from './panel/SettingTeamJobs'
+// import TeamJobAdd from './panel/SettingTeamJobs'
import SetupInfo from './panel/SettingTeamInfo'
import CuCircularProgress from '@/components/CuCircularProgress'
-import useSocket from '@/states/useSocket'
import Tutorial from '@/components/Tutorial'
import TeamMemberTutorial from '@/components/tutorialContent/TeamMemberTutorial'
@@ -24,36 +25,27 @@ export interface IMyInfo {
}
const TeamsSetupPage = ({ params }: { params: { id: string } }) => {
- const { socket } = useSocket()
const axiosWithAuth = useAxiosWithAuth()
const [showApplicant, setShowApplicant] = useState(false)
- const [myInfo, setMyInfo] = useState()
- const { data, error, isLoading } = useSWR(
- `${process.env.NEXT_PUBLIC_API_URL}/api/v1/team/setting/${params.id}`,
+ const { data, error, isLoading, mutate } = useSWR(
+ `${process.env.NEXT_PUBLIC_CSR_API}/api/v1/team/setting/${params.id}`,
(url: string) => axiosWithAuth(url).then((res) => res.data),
)
const router = useRouter()
+ const [teams, setTeams] = useState()
+
+ useEffect(() => {
+ if (data) {
+ setTeams(data)
+ }
+ }, [data])
const openApplicant = () => setShowApplicant(true)
const closeApplicant = () => setShowApplicant(false)
- useEffect(() => {
- if (!socket) return
- socket.emit(
- 'whoAmI',
- {
- teamId: params.id,
- teamName: data?.team.name,
- },
- (data: any) => {
- setMyInfo(data)
- },
- )
- }, [])
-
if (error) {
- if (error.status === 403) {
- alert('팀 페이지에 접근할 권한이 없습니다.')
+ if (isAxiosError(error) && error.response?.status === 403) {
+ alert('팀 설정은 팀 리더만 가능합니다.')
} else {
alert('팀 페이지에 접근할 수 없습니다.')
}
@@ -70,32 +62,27 @@ const TeamsSetupPage = ({ params }: { params: { id: string } }) => {
if (isLoading) return
return (
-
+
설정
- {data ? (
+ {teams ? (
<>
-
-
- {data.team.type === TeamType.PROJECT && (
+
+
+ {/* {teams.team.type === TeamType.PROJECT && (
job.name != 'Leader')}
- teamStatus={data.team.status}
+ jobList={teams.job.filter((job) => job.name != 'Leader')}
+ teamStatus={teams.team.status}
/>
- )}
+ )} */}
{!showApplicant ? (
{
>
팀원 목록
- } />
+ }
+ />
) : (
-
+
)}
>
) : (
diff --git a/src/app/teams/[id]/setting/panel/AddNewJob.tsx b/src/app/teams/[id]/setting/panel/AddNewJob.tsx
index 521cb0fef..d730f2b04 100644
--- a/src/app/teams/[id]/setting/panel/AddNewJob.tsx
+++ b/src/app/teams/[id]/setting/panel/AddNewJob.tsx
@@ -6,6 +6,7 @@ import AddIcon from '@mui/icons-material/Add'
import RemoveIcon from '@mui/icons-material/Remove'
import { Job, TeamStatus } from '@/app/teams/types/types'
import useAxiosWithAuth from '@/api/config'
+import useToast from '@/states/useToast'
interface NewJob {
name: string
@@ -20,6 +21,7 @@ interface Props {
const AddNewJob = ({ onNewJob, teamId, teamStatus }: Props) => {
const axiosWithAuth = useAxiosWithAuth()
+ const { openToast } = useToast()
const [newJob, setNewJob] = useState({
name: '',
max: 1,
@@ -37,18 +39,19 @@ const AddNewJob = ({ onNewJob, teamId, teamStatus }: Props) => {
const handleAddJob = () => {
axiosWithAuth
.post(
- `${process.env.NEXT_PUBLIC_API_URL}/api/v1/team/setting/job/add/${teamId}`,
+ `${process.env.NEXT_PUBLIC_CSR_API}/api/v1/team/setting/job/add/${teamId}`,
newJob,
)
.then((res) => {
- console.log(res)
if (res.status === 200) {
- console.log(res.data)
onNewJob(res.data)
}
})
- .catch((err) => {
- console.log(err)
+ .catch(() => {
+ openToast({
+ severity: 'error',
+ message: '직업 추가에 실패했습니다.',
+ })
})
}
diff --git a/src/app/teams/[id]/setting/panel/ApplicantList.tsx b/src/app/teams/[id]/setting/panel/ApplicantList.tsx
index 3ed127fd4..a156aa621 100644
--- a/src/app/teams/[id]/setting/panel/ApplicantList.tsx
+++ b/src/app/teams/[id]/setting/panel/ApplicantList.tsx
@@ -9,32 +9,35 @@ import {
Typography,
} from '@mui/material'
import { IApplicant } from '../../../types/types'
-import { useEffect, useRef, useState } from 'react'
+import { useCallback, useEffect, useRef, useState } from 'react'
import useSWR from 'swr'
-import useMedia from '@/hook/useMedia'
import FormAnswer from './InterviewAnswerForm'
import useAxiosWithAuth from '@/api/config'
import { CloseIcon } from '@/icons'
import { NextButton, PrevButton } from './Icons'
import Tutorial from '@/components/Tutorial'
import TeamApplicantTutorial from '@/components/tutorialContent/TeamApplicantTutorial'
+import useToast from '@/states/useToast'
+import CuCircularProgress from '@/components/CuCircularProgress'
const ApplicantList = ({
+ mutate,
close,
teamId,
}: {
+ mutate: () => void
close: () => void
teamId: string
}) => {
- const { isPc } = useMedia()
const [index, setIndex] = useState(0)
const scrollRef = useRef(null)
const axiosWithAuth = useAxiosWithAuth()
+ const { openToast } = useToast()
// TODO: DTO 맞추기
const { data, isLoading } = useSWR(
- `${process.env.NEXT_PUBLIC_API_URL}/api/v1/team/applicant/${teamId}`,
+ `${process.env.NEXT_PUBLIC_CSR_API}/api/v1/team/applicant/${teamId}`,
(url: string) => axiosWithAuth.get(url).then((res) => res.data),
)
const [members, setMembers] = useState([])
@@ -43,14 +46,13 @@ const ApplicantList = ({
)
useEffect(() => {
- console.log(data)
setMember(data ? data[index] : null)
}, [index, data])
- const handleAccept = () => {
+ const handleAccept = useCallback(() => {
axiosWithAuth
.put(
- `${process.env.NEXT_PUBLIC_API_URL}/api/v1/team/applicant/accept/${teamId}`,
+ `${process.env.NEXT_PUBLIC_CSR_API}/api/v1/team/applicant/accept/${teamId}`,
{
teamJobId: member!.applyId.teamJobId,
teamUserId: member!.applyId.teamUserId,
@@ -59,21 +61,36 @@ const ApplicantList = ({
.then((res) => {
if (res.status === 200) {
// TODO:백엔드에서 제외 시키는 걸 생각
- setMembers(data)
-
- if (index > 0) setIndex(index - 1)
+ setMembers(res.data)
+ if (index > 0) {
+ setIndex(index - 1)
+ } else {
+ close()
+ }
+ mutate()
+ openToast({
+ severity: 'success',
+ message: '신청이 승인되었습니다.',
+ })
+ } else if (res.status === 403) {
+ openToast({
+ severity: 'error',
+ message: '권한이 없습니다.',
+ })
}
})
- .catch((err) => {
- console.log(err)
+ .catch(() => {
+ openToast({
+ severity: 'error',
+ message: '승인에 실패했습니다.',
+ })
})
- }
+ }, [index, member, data, mutate])
- const handleReject = () => {
- console.log('reject')
+ const handleReject = useCallback(() => {
axiosWithAuth
.put(
- `${process.env.NEXT_PUBLIC_API_URL}/api/v1/team/applicant/reject/${teamId}`,
+ `${process.env.NEXT_PUBLIC_CSR_API}/api/v1/team/applicant/reject/${teamId}`,
{
teamJobId: member!.applyId.teamJobId,
teamUserId: member!.applyId.teamUserId,
@@ -82,15 +99,32 @@ const ApplicantList = ({
.then((res) => {
if (res.status === 200) {
// TODO:백엔드에서 제외 시키는 걸 생각
- setMembers(data)
+ setMembers(res.data)
- if (index > 0) setIndex(index - 1)
+ if (index > 0) {
+ setIndex(index - 1)
+ } else {
+ close()
+ }
+ mutate()
+ openToast({
+ severity: 'success',
+ message: '신청이 거절되었습니다.',
+ })
+ } else if (res.status === 403) {
+ openToast({
+ severity: 'error',
+ message: '권한이 없습니다.',
+ })
}
})
- .catch((err) => {
- console.log(err)
+ .catch(() => {
+ openToast({
+ severity: 'error',
+ message: '승인에 실패했습니다.',
+ })
})
- }
+ }, [index, member, data, mutate])
const handleNext = () => {
if (index < members.length - 1) setIndex(index + 1)
@@ -114,16 +148,19 @@ const ApplicantList = ({
}, [index, data])
if (isLoading) {
- return (
-
- 로딩중
-
- )
+ return
}
if (!data || data.length === 0) {
return (
-
+
신청 대기자
- } />
+ }
+ />
@@ -145,16 +185,24 @@ const ApplicantList = ({
}
return (
-
+
-
- 신청 대기자 {index + 1} / {members.length}
-
+
+
+ 신청 대기자 {index + 1} / {members.length}
+
+ }
+ />
+
@@ -171,7 +219,7 @@ const ApplicantList = ({
- A
+
{member && {member.name}}
{member?.jobName && {member.jobName}}
@@ -193,20 +241,14 @@ const ApplicantList = ({
-
+
인터뷰 답변
-
- {!member && 신청한 대기자가 없습니다.}
+
+ {!member && 신청한 사람이 없습니다.}
{member && member.answers ? (
member.answers.map((interview, index) => (
- {interview.question}
+ {interview.question}
))
diff --git a/src/app/teams/[id]/setting/panel/InterviewAnswerForm.tsx b/src/app/teams/[id]/setting/panel/InterviewAnswerForm.tsx
index 08e9de9b4..0c9e4e76c 100644
--- a/src/app/teams/[id]/setting/panel/InterviewAnswerForm.tsx
+++ b/src/app/teams/[id]/setting/panel/InterviewAnswerForm.tsx
@@ -19,6 +19,7 @@ const InterviewAnswerForm = ({
index: number
}) => {
const { control } = useForm()
+
return (
<>
{interview.type === EInterviewType.OPEN && (
@@ -26,7 +27,7 @@ const InterviewAnswerForm = ({
)}
{interview.type === EInterviewType.CLOSE && (
{
const router = useRouter()
return (
-
-
+
+
모집글
+
+ }
+ />
+
-
+
@@ -207,10 +281,6 @@ const SettingTeamJobs = ({ team }: { team: ISetupTeam }) => {
onClick: closeConfirmModel,
}}
/>
-
-
- {message}
-
>
)
}
diff --git a/src/app/teams/[id]/setting/panel/SettingTeamJobs.tsx b/src/app/teams/[id]/setting/panel/SettingTeamJobs.tsx
index e9980003e..70da3b7e2 100644
--- a/src/app/teams/[id]/setting/panel/SettingTeamJobs.tsx
+++ b/src/app/teams/[id]/setting/panel/SettingTeamJobs.tsx
@@ -26,6 +26,7 @@ import { SettingIcon } from '@/icons/TeamPage'
import useAxiosWithAuth from '@/api/config'
import Tutorial from '@/components/Tutorial'
import TeamJobsTutorial from '@/components/tutorialContent/TeamJobsTutorial'
+import useToast from '@/states/useToast'
interface TableColumn {
id: string
@@ -56,6 +57,7 @@ const SettingTeamJobs = ({ teamId, jobList, teamStatus }: Props) => {
max: 0,
current: 0,
})
+ const { openToast } = useToast()
useEffect(() => {
setJobs(jobList)
@@ -97,7 +99,7 @@ const SettingTeamJobs = ({ teamId, jobList, teamStatus }: Props) => {
// const changeJob = () => {
// axiosWithAuth.put(
- // `${process.env.NEXT_PUBLIC_API_URL}/api/v1/team/setting/job/change`,
+ // `${process.env.NEXT_PUBLIC_CSR_API}/api/v1/team/setting/job/change`,
// job,
// )
// }
@@ -109,12 +111,11 @@ const SettingTeamJobs = ({ teamId, jobList, teamStatus }: Props) => {
}
axiosWithAuth
.put(
- `${process.env.NEXT_PUBLIC_API_URL}/api/v1/team/setting/change`,
+ `${process.env.NEXT_PUBLIC_CSR_API}/api/v1/team/setting/change`,
editJob,
)
.then((res) => {
if (res.status === 200) {
- console.log('역할 변경 완료')
setJobs(
jobs.map((job) =>
job.id === editJob.id
@@ -124,10 +125,13 @@ const SettingTeamJobs = ({ teamId, jobList, teamStatus }: Props) => {
)
setIsSettingButton('')
setEditJob({ id: 0, name: '', max: 0, current: 0 })
- } else console.log(res.status)
+ }
})
- .catch((err) => {
- console.log(err)
+ .catch(() => {
+ openToast({
+ severity: 'error',
+ message: '역할 수정에 실패했습니다.',
+ })
})
}
@@ -136,7 +140,7 @@ const SettingTeamJobs = ({ teamId, jobList, teamStatus }: Props) => {
역할 추가
- } />
+ } />
{
- const { isPc } = useMedia()
+const SettingTeamMember = ({ team, teamId, teamStatus }: ISetupMember) => {
+ const { isPc, isTablet } = useMedia()
const { isOpen, closeModal, openModal } = useModal()
// const {
// isOpen: isChangeOpen,
@@ -69,91 +60,117 @@ const SettingTeamMember = ({
// } = useModal()
const [members, setMembers] = useState(team)
const [member, setMember] = useState()
- const [job, setJob] = useState(jobs)
+ // const [job, setJob] = useState(jobs)
// const [selectedJobs, setSelectedJobs] = useState([])
const axiosWithAuth = useAxiosWithAuth()
+ const [canChangeLeader, setCanChangeLeader] = useState(false)
+ const { openToast } = useToast()
+ const { nickname } = useNicknameStore()
// const changeJob = () => {
// axiosWithAuth.put(
- // `${process.env.NEXT_PUBLIC_API_URL}/api/v1/team/setting/change`,
+ // `${process.env.NEXT_PUBLIC_CSR_API}/api/v1/team/setting/change`,
// job,
// )
// }
useEffect(() => {
- setJob(jobs)
- console.log(job)
- console.log(myInfo)
- console.log(member?.id)
- // if (selectedJobs.length > 0) {
- // changeJob()
- // }
- }, [setJob, jobs, myInfo])
-
- const handleGrant = (member: IMember) => {
- console.log('리더 권한 변경')
- if (member.role === TeamGrant.LEADER) {
- axiosWithAuth
- .post(
- `${process.env.NEXT_PUBLIC_API_URL}/api/v1/team/grant/${teamId}?userId=${member.id}&role=member`,
- )
- .then((res) => {
- console.log(res)
- if (res.status === 200) {
- setMembers(
- members.map((m) =>
- m.id === member.id ? { ...m, grant: TeamGrant.MEMBER } : m,
- ),
- )
- } else console.log(res.status)
- })
- .catch((err) => {
- console.log(err)
- })
- } else {
- axiosWithAuth
- .post(
- `${process.env.NEXT_PUBLIC_API_URL}/api/v1/team/grant/${teamId}?userId=${member.id}&role=leader`,
- )
- .then((res) => {
- console.log(res)
- if (res.status === 200) {
- setMembers(
- members.map((m) =>
- m.id === member.id ? { ...m, grant: TeamGrant.LEADER } : m,
- ),
- )
- } else console.log(res.status)
- })
- .catch((err) => {
- console.log(err)
- })
+ setMembers(team)
+ if (team.length > 1) {
+ setCanChangeLeader(true)
}
- }
+ }, [team])
+
+ const handleGrant = useCallback(
+ (member: IMember) => {
+ if (member.role === TeamGrant.LEADER) {
+ axiosWithAuth
+ .post(
+ `${process.env.NEXT_PUBLIC_CSR_API}/api/v1/team/grant/${teamId}?userId=${member.id}&role=member`,
+ )
+ .then((res) => {
+ if (res.status === 200) {
+ const changedMembers = members.map((m) =>
+ m.id === member.id ? { ...m, role: TeamGrant.MEMBER } : m,
+ )
+ setMembers(changedMembers)
+ openToast({
+ severity: 'success',
+ message: '리더 권한이 박탈되었습니다.',
+ })
+ }
+ })
+ .catch(() => {
+ openToast({
+ severity: 'error',
+ message: '리더 권한 부여에 실패했습니다.',
+ })
+ })
+ } else {
+ axiosWithAuth
+ .post(
+ `${process.env.NEXT_PUBLIC_CSR_API}/api/v1/team/grant/${teamId}?userId=${member.id}&role=leader`,
+ )
+ .then((res) => {
+ if (res.status === 200) {
+ const changedMembers = members.map((m) =>
+ m.id === member.id ? { ...m, role: TeamGrant.LEADER } : m,
+ )
+ setMembers(changedMembers)
+
+ openToast({
+ severity: 'success',
+ message: '리더 권한이 부여되었습니다.',
+ })
+ }
+ })
+ .catch(() => {
+ openToast({
+ severity: 'error',
+ message: '리더 권한 부여에 실패했습니다.',
+ })
+ })
+ }
+ },
+ [members, setMembers],
+ )
const handleOpenDelete = (member: IMember) => {
- console.log('팀원 삭제 모달 오픈')
setMember(member)
openModal()
}
const handleDelete = () => {
- console.log('팀원 삭제')
- if (!member) return console.log('팀원이 없습니다.')
+ if (!member)
+ return openToast({
+ severity: 'error',
+ message: '팀원이 없습니다.',
+ })
axiosWithAuth
.delete(
- `${process.env.NEXT_PUBLIC_API_URL}/api/v1/team/delete/${teamId}?userId=${member.id}`,
+ `${process.env.NEXT_PUBLIC_CSR_API}/api/v1/team/delete/${teamId}?userId=${member.id}`,
)
.then((res) => {
if (res.status === 200) {
- console.log('삭제 완료')
- team = res.data
- } else console.log(res.status)
+ setMembers(members.filter((m) => m.id !== member.id))
+ openToast({
+ severity: 'success',
+ message: '팀원이 삭제되었습니다.',
+ })
+ } else if (res.status === 403) {
+ openToast({
+ severity: 'error',
+ message: '자기 자신은 삭제시킬 수 없습니다.',
+ })
+ }
closeModal()
})
- .catch((err) => {
- console.log(err)
+ .catch(() => {
+ openToast({
+ severity: 'error',
+ message: '팀원 삭제에 실패했습니다.',
+ })
closeModal()
})
@@ -205,7 +222,7 @@ const SettingTeamMember = ({
component="div"
key={index}
item
- xs={isPc ? 3 : 6}
+ xs={isPc && !isTablet ? 3 : 6}
textAlign="center"
>
{/** TODO: 내가 누구인지를 알게 서버에서 받아야 함**/}
- {myInfo && member.id.toString() !== myInfo.userId && (
- handleOpenDelete(member)}
- style={{
+
+ {nickname !== member.name && (
+
)}
+
- A
+
- {member.name}
+
+ {member.name}
+
- 리더 권한
- handleGrant(member)}
- checked={member.role === TeamGrant.LEADER ? true : false}
- />
+ {canChangeLeader ? (
+
+ 리더 권한
+ handleGrant(member)}
+ checked={
+ member.role === TeamGrant.LEADER ? true : false
+ }
+ />
+
+ ) : (
+
+ )}
{/* 역할이 있을 때만 버튼이 보이게끔 */}
{/* {member.job && (
@@ -307,15 +353,20 @@ const SettingTeamMember = ({
*/}
-
-
-
- 정말 팀원을 내보내시겠습니까?
-
-
-
-
-
+
>
)
}
diff --git a/src/app/teams/[id]/setting/panel/TeamEndProcess/TeamCompleteButton.tsx b/src/app/teams/[id]/setting/panel/TeamEndProcess/TeamCompleteButton.tsx
index 113d73884..b78106091 100644
--- a/src/app/teams/[id]/setting/panel/TeamEndProcess/TeamCompleteButton.tsx
+++ b/src/app/teams/[id]/setting/panel/TeamEndProcess/TeamCompleteButton.tsx
@@ -9,62 +9,66 @@ import { useRouter } from 'next/navigation'
interface ITeamCompleteButton {
teamStatus: TeamStatus
teamId: string
+ mutate: () => void
}
-const TeamCompleteButton = ({ teamId, teamStatus }: ITeamCompleteButton) => {
+const TeamCompleteButton = ({
+ teamId,
+ teamStatus,
+ mutate,
+}: ITeamCompleteButton) => {
const router = useRouter()
const { openToast } = useToast()
const { isOpen, openModal, closeModal } = useModal()
const axiosWithAuth = useAxiosWithAuth()
const finishTeam = () => {
- console.log('exit team')
axiosWithAuth
- .post(`${process.env.NEXT_PUBLIC_API_URL}/api/v1/team/setting/complete`, {
+ .post(`${process.env.NEXT_PUBLIC_CSR_API}/api/v1/team/setting/complete`, {
teamId: teamId,
})
.then((res) => {
if (res.status === 200) {
- console.log(res)
openToast({
severity: 'success',
message: '팀 활동이 완료되었습니다.',
})
+ mutate()
+ } else if (res.status === 400) {
+ openToast({
+ severity: 'error',
+ message:
+ '"활동 진행 전" 상태에서는 활동을 완료할 수 없습니다. 진행 중으로 변경 후 완료해주세요.',
+ })
} else if (res.status === 401) {
- console.log(res)
router.push('/login')
openToast({
severity: 'error',
message: '로그인이 필요합니다.',
})
} else if (res.status === 403) {
- console.log(res)
openToast({
severity: 'error',
message: '권한이 없습니다.',
})
} else if (res.status === 404) {
- console.log(res)
openToast({
severity: 'error',
message: '팀이 존재하지 않습니다.',
})
} else if (res.status === 409) {
- console.log(res)
openToast({
severity: 'error',
message: '모집을 완료 후 완료할 수 있습니다.',
})
} else {
- console.log(res)
openToast({
severity: 'error',
message: '팀 활동 완료에 실패하였습니다.',
})
}
})
- .catch((err) => {
- console.log(err)
+ .catch(() => {
openToast({
severity: 'error',
message: '팀 활동 완료에 실패하였습니다.',
@@ -80,7 +84,7 @@ const TeamCompleteButton = ({ teamId, teamStatus }: ITeamCompleteButton) => {
justifyContent={'space-between'}
alignItems={'center'}
>
- 팀 활동을 완료하겠습니까?
+ 팀 활동을 완료하겠습니까?
diff --git a/src/app/teams/[id]/setting/panel/TeamEndProcess/TeamDisperseButton.tsx b/src/app/teams/[id]/setting/panel/TeamEndProcess/TeamDisperseButton.tsx
index 5e6fd96f2..472b62410 100644
--- a/src/app/teams/[id]/setting/panel/TeamEndProcess/TeamDisperseButton.tsx
+++ b/src/app/teams/[id]/setting/panel/TeamEndProcess/TeamDisperseButton.tsx
@@ -18,55 +18,48 @@ const TeamDisperseButton = ({ teamId, teamStatus }: ITeamDisperseButton) => {
const axiosWithAuth = useAxiosWithAuth()
const disperseTeam = () => {
- console.log('exit team')
axiosWithAuth
- .post(`${process.env.NEXT_PUBLIC_API_URL}/api/v1/team/setting/disperse`, {
+ .post(`${process.env.NEXT_PUBLIC_CSR_API}/api/v1/team/setting/disperse`, {
teamId: teamId,
})
.then((res) => {
if (res.status === 200) {
- console.log(res)
router.push('/team-list')
openToast({
severity: 'success',
message: '팀이 해산되었습니다.',
})
+ router.push('/team-list')
} else if (res.status === 401) {
- console.log(res)
router.push('/login')
openToast({
severity: 'error',
message: '잘못된 접근입니다.',
})
} else if (res.status === 403) {
- console.log(res)
router.push('/login')
openToast({
severity: 'error',
message: '권한이 없습니다.',
})
} else if (res.status === 404) {
- console.log(res)
openToast({
severity: 'error',
message: '팀이 존재하지 않습니다.',
})
} else if (res.status === 409) {
- console.log(res)
openToast({
severity: 'error',
message: '모집을 완료 후 팀을 해산하셔야 합니다.',
})
} else {
- console.log(res)
openToast({
severity: 'error',
message: '팀 해산에 실패하였습니다.',
})
}
})
- .catch((err) => {
- console.log(err)
+ .catch(() => {
openToast({
severity: 'error',
message: '팀 해산에 실패하였습니다.',
@@ -82,7 +75,7 @@ const TeamDisperseButton = ({ teamId, teamStatus }: ITeamDisperseButton) => {
justifyContent={'space-between'}
alignItems={'center'}
>
- 팀을 해산시겠습니까?
+ 팀을 해산시겠습니까?
diff --git a/src/app/teams/[id]/setting/panel/TeamEndProcess/TeamQuitButton.tsx b/src/app/teams/[id]/setting/panel/TeamEndProcess/TeamQuitButton.tsx
index 7787f6b3b..761c1505b 100644
--- a/src/app/teams/[id]/setting/panel/TeamEndProcess/TeamQuitButton.tsx
+++ b/src/app/teams/[id]/setting/panel/TeamEndProcess/TeamQuitButton.tsx
@@ -18,48 +18,42 @@ const TeamQuitButton = ({ teamId, teamStatus }: ITeamQuitButton) => {
const axiosWithAuth = useAxiosWithAuth()
const quitTeam = () => {
- console.log('exit team')
axiosWithAuth
- .post(`${process.env.NEXT_PUBLIC_API_URL}/api/v1/team/setting/quit`, {
+ .post(`${process.env.NEXT_PUBLIC_CSR_API}/api/v1/team/setting/quit`, {
teamId: teamId,
})
.then((res) => {
if (res.status === 200) {
- console.log(res)
openToast({
severity: 'success',
message: '팀을 나갔습니다.',
})
+ router.push('/team-list')
} else if (res.status === 401) {
- console.log(res)
router.push('/login')
openToast({
severity: 'error',
message: '잘못된 접근입니다.',
})
} else if (res.status === 404) {
- console.log(res)
openToast({
severity: 'error',
message: '팀이 존재하지 않습니다.',
})
} else if (res.status === 409) {
- console.log(res)
openToast({
severity: 'error',
message:
'혼자 남았을 경우 팀을 나갈 수 없습니다. 해산절차를 진행해주세요.',
})
} else {
- console.log(res)
openToast({
severity: 'error',
message: '팀 나가기에 실패하였습니다.',
})
}
})
- .catch((err) => {
- console.log(err)
+ .catch(() => {
openToast({
severity: 'error',
message: '팀 나가기에 실패하였습니다.',
@@ -75,7 +69,7 @@ const TeamQuitButton = ({ teamId, teamStatus }: ITeamQuitButton) => {
justifyContent={'space-between'}
alignItems={'center'}
>
- 팀을 나가겠습니까?
+ 팀을 나가겠습니까?
diff --git a/src/app/teams/[id]/setting/panel/TeamInfoProcess/SettingTeamActivity.tsx b/src/app/teams/[id]/setting/panel/TeamInfoProcess/SettingTeamActivity.tsx
index 706f6bdca..01ca273ae 100644
--- a/src/app/teams/[id]/setting/panel/TeamInfoProcess/SettingTeamActivity.tsx
+++ b/src/app/teams/[id]/setting/panel/TeamInfoProcess/SettingTeamActivity.tsx
@@ -2,16 +2,18 @@ import { MenuItem, Stack, Typography } from '@mui/material'
import { WifiClearIcon } from '../Icons'
import { Control, Controller } from 'react-hook-form'
import { Select } from '@mui/material'
-import { TeamOperationForm } from '@/app/teams/types/types'
+import { TeamOperationForm, TeamStatus } from '@/app/teams/types/types'
import { ISetupTeam } from '../SettingTeamInfo'
import useMedia from '@/hook/useMedia'
interface ISettingTeamActivity {
+ teamStatus: TeamStatus
teamActivity: TeamOperationForm
control: Control
}
const SettingTeamActivity = ({
+ teamStatus,
teamActivity,
control,
}: ISettingTeamActivity) => {
@@ -24,9 +26,9 @@ const SettingTeamActivity = ({
mx={!isPc ? '0.5rem' : ''}
spacing={'0.5rem'}
>
-
+
- 활동방식
+ 활동방식
(
diff --git a/src/app/teams/[id]/setting/panel/TeamInfoProcess/SettingTeamLocation.tsx b/src/app/teams/[id]/setting/panel/TeamInfoProcess/SettingTeamLocation.tsx
index 53e82a1e0..74774a4df 100644
--- a/src/app/teams/[id]/setting/panel/TeamInfoProcess/SettingTeamLocation.tsx
+++ b/src/app/teams/[id]/setting/panel/TeamInfoProcess/SettingTeamLocation.tsx
@@ -1,20 +1,69 @@
+'use client'
+
import { MenuItem, Select, Stack, Typography } from '@mui/material'
import { GeoClearIcon } from '../Icons'
import { Control, Controller } from 'react-hook-form'
import { locationData } from '@/api/location'
import { ISetupTeam } from '../SettingTeamInfo'
import useMedia from '@/hook/useMedia'
+import { TeamOperationForm, TeamStatus } from '@/app/teams/types/types'
interface ISettingTeamLocation {
- teamLocation: string[]
+ teamStatus: TeamStatus
+ teamLocation: Array
+ teamActivity: TeamOperationForm
control: Control
}
const SettingTeamLocation = ({
+ teamStatus,
teamLocation,
+ teamActivity,
control,
}: ISettingTeamLocation) => {
const isPc = useMedia()
+
+ if (teamActivity === 'ONLINE') {
+ return (
+
+
+
+ 활동지역
+
+
+
+
+
+
+ )
+ }
return (
-
+
- 활동지역
+ 활동지역
(