diff --git a/42manito/public/Slack-mark-RGB.png b/42manito/public/Slack-mark-RGB.png new file mode 100644 index 00000000..8fe767ed Binary files /dev/null and b/42manito/public/Slack-mark-RGB.png differ diff --git a/42manito/src/RTK/Apis/User.ts b/42manito/src/RTK/Apis/User.ts index c5d23e81..29d5a7ff 100644 --- a/42manito/src/RTK/Apis/User.ts +++ b/42manito/src/RTK/Apis/User.ts @@ -45,7 +45,7 @@ export const userApi = createApi({ isHide: args.isHide, }; return { - url: `/mentor_profiles/${args.id}`, + url: `/mentor_profiles/${args.id}/activation`, data: body, method: "PATCH", }; diff --git a/42manito/src/RTK/Slices/ProfileUpdate.ts b/42manito/src/RTK/Slices/ProfileUpdate.ts index cfad4870..64a93f54 100644 --- a/42manito/src/RTK/Slices/ProfileUpdate.ts +++ b/42manito/src/RTK/Slices/ProfileUpdate.ts @@ -10,6 +10,7 @@ interface InitialState { categories: CategoriesResponseDto[]; disabled: boolean; viewConnectModal: boolean; + socialLink: string; } const ProfileUpdate: InitialState = { @@ -20,6 +21,7 @@ const ProfileUpdate: InitialState = { categories: [] as CategoriesResponseDto[], disabled: false, viewConnectModal: false, + socialLink: "", }; export const ProfileUpdateSlice = createSlice({ @@ -52,7 +54,7 @@ export const ProfileUpdateSlice = createSlice({ addCategory(state, action: PayloadAction) { if ( state.categories.some( - (category) => category.name === action.payload.name, + (category) => category.name === action.payload.name ) ) { return; @@ -65,14 +67,17 @@ export const ProfileUpdateSlice = createSlice({ setViewConnectModal(state, action: PayloadAction) { state.viewConnectModal = action.payload; }, + setSocialLink(state, action: PayloadAction) { + state.socialLink = action.payload; + }, deleteOneHashtag(state, action: PayloadAction) { state.hashtags = state.hashtags.filter( - (hashtag) => hashtag.name !== action.payload, + (hashtag) => hashtag.name !== action.payload ); }, deleteOneCategory(state, action: PayloadAction) { state.categories = state.categories.filter( - (category) => category.name !== action.payload, + (category) => category.name !== action.payload ); }, deleteAll(state) { @@ -93,6 +98,7 @@ export const { deleteOneCategory, setDisabled, setViewConnectModal, + setSocialLink, } = ProfileUpdateSlice.actions; export default ProfileUpdateSlice.reducer; diff --git a/42manito/src/RTK/Slices/Reservation.ts b/42manito/src/RTK/Slices/Reservation.ts index 405186ea..cb64e3f9 100644 --- a/42manito/src/RTK/Slices/Reservation.ts +++ b/42manito/src/RTK/Slices/Reservation.ts @@ -6,6 +6,7 @@ interface currMentorType { isReservationModalOpen: boolean; isFeedbackModalOpen: boolean; isCancelModalOpen: boolean; + isSocialLinkModalOpen: boolean; selectedReservation: ReservationDefaultDto; } @@ -13,6 +14,7 @@ const InitialState: currMentorType = { isReservationModalOpen: false, isFeedbackModalOpen: false, isCancelModalOpen: false, + isSocialLinkModalOpen: false, selectedReservation: { id: 0, mentorId: 0, @@ -48,7 +50,7 @@ export const ReservationSlice = createSlice({ closeFeedbackModal(state) { state.isFeedbackModalOpen = false; }, - openCancelModal(state ) { + openCancelModal(state) { state.isCancelModalOpen = true; }, closeCancelModal(state) { @@ -57,6 +59,12 @@ export const ReservationSlice = createSlice({ setSelectedReservation(state, action) { state.selectedReservation = action.payload; }, + openSocialLinkModal(state) { + state.isSocialLinkModalOpen = true; + }, + closeSocialLinkModal(state) { + state.isSocialLinkModalOpen = false; + }, }, }); @@ -68,4 +76,6 @@ export const { openCancelModal, closeCancelModal, setSelectedReservation, + openSocialLinkModal, + closeSocialLinkModal, } = ReservationSlice.actions; diff --git a/42manito/src/Types/MentorProfiles/MentorProfile.dto.ts b/42manito/src/Types/MentorProfiles/MentorProfile.dto.ts index 78624905..6b9b82f9 100644 --- a/42manito/src/Types/MentorProfiles/MentorProfile.dto.ts +++ b/42manito/src/Types/MentorProfiles/MentorProfile.dto.ts @@ -16,4 +16,5 @@ export interface MentorProfileDto { nickname: string; profileImage: string; }; + socialLink?: string; } diff --git a/42manito/src/Types/MentorProfiles/MentorProfilePatchReq.dto.ts b/42manito/src/Types/MentorProfiles/MentorProfilePatchReq.dto.ts index eea54b3f..46bedbda 100644 --- a/42manito/src/Types/MentorProfiles/MentorProfilePatchReq.dto.ts +++ b/42manito/src/Types/MentorProfiles/MentorProfilePatchReq.dto.ts @@ -8,4 +8,5 @@ export interface MentorProfilePatchReqDto { description?: string; hashtags?: HashtagResponseDto[]; categories?: CategoriesResponseDto[]; + socialLink?: string | null; } diff --git a/42manito/src/Types/Users/UserDefault.dto.ts b/42manito/src/Types/Users/UserDefault.dto.ts index 77cb5634..a73d8fff 100644 --- a/42manito/src/Types/Users/UserDefault.dto.ts +++ b/42manito/src/Types/Users/UserDefault.dto.ts @@ -20,5 +20,6 @@ export interface UserDefaultDto { updatedAt: string; hashtags: HashtagResponseDto[]; categories: CategoriesResponseDto[]; + socialLink: string; }; } diff --git a/42manito/src/components/Profile/Info.tsx b/42manito/src/components/Profile/Info.tsx index e519ae9b..855b20bb 100644 --- a/42manito/src/components/Profile/Info.tsx +++ b/42manito/src/components/Profile/Info.tsx @@ -1,17 +1,30 @@ +import Image from "next/image"; import React from "react"; interface props { nickname: string; count?: number; + socialLink?: string; } -export default function ProfileInfo({ nickname, count }: props) { +export default function ProfileInfo({ nickname, count, socialLink }: props) { return (
-
+
{nickname} + {socialLink !== undefined && socialLink && ( + + slack-icon + + )}
diff --git a/42manito/src/components/Profile/Toggle.tsx b/42manito/src/components/Profile/Toggle.tsx index bdac56b1..82e1e508 100644 --- a/42manito/src/components/Profile/Toggle.tsx +++ b/42manito/src/components/Profile/Toggle.tsx @@ -7,30 +7,31 @@ export default function ManitoToggle() { const [isHide, setIsHide] = useState(false); const [setIsHideMutation, {}] = useSetIsHideMutation(); const userId = useSelector( - (state: RootState) => state.rootReducers.global.uId, + (state: RootState) => state.rootReducers.global.uId ); const { data: userData, isLoading: userLoading } = useGetUserQuery( { id: userId as number }, - { skip: userId === undefined }, + { skip: userId === undefined } ); const changeToggle = (e: React.ChangeEvent) => { - if (e.target.checked === false){ - setIsHideMutation({ id: userId as number, isHide: true }); - } - else{ - if (userData) { - const {categories, hashtags} = userData.mentorProfile; - if(categories.length == 0 && hashtags.length == 0) - alert("멘토링 분야와 관심 분야를 각각 최소한 하나 이상 설정해야 합니다."); - else if (categories.length == 0) - alert("멘토링 분야를 최소한 하나 이상 설정해야 합니다."); - else if (hashtags.length == 0) - alert("관심 분야를 최소한 하나 이상 설정해야 합니다."); - else - setIsHideMutation({ id: userId as number, isHide: false }); - } + if (e.target.checked === false) { + setIsHideMutation({ id: userId as number, isHide: true }); + } else { + if (userData) { + const { categories, hashtags, socialLink } = userData.mentorProfile; + if (categories.length == 0) + alert("멘토링 분야를 최소한 하나 이상 설정해야 합니다."); + else if (hashtags.length == 0) + alert("관심 분야를 최소한 하나 이상 설정해야 합니다."); + else if (socialLink == "") alert("슬랙 프로필 링크를 추가해야 합니다."); + else + setIsHideMutation({ + id: userId as number, + isHide: false, + }); } + } }; useEffect(() => { diff --git a/42manito/src/components/Profile/UserProfile.tsx b/42manito/src/components/Profile/UserProfile.tsx index 6f0eeb7c..4d5f35b6 100644 --- a/42manito/src/components/Profile/UserProfile.tsx +++ b/42manito/src/components/Profile/UserProfile.tsx @@ -5,8 +5,9 @@ import DescriptionComponent from "@/components/Profile/Description"; import { useProfileDetailModal } from "@/hooks/Profile/Component"; import { useRouter } from "next/router"; import CardHashtag from "@/components/Global/CardHashtag"; -import { useDispatch } from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; import { CurrMentorSlice } from "@/RTK/Slices/CurrMentor"; +import { RootState } from "@/RTK/store"; interface props { UserId: number; @@ -17,6 +18,9 @@ export default function UserProfile({ UserId, children }: props) { const { UserData, UserLoading } = useProfileDetailModal(UserId); const router = useRouter(); const dispatch = useDispatch(); + const loginId = useSelector( + (state: RootState) => state.rootReducers.global.uId + ); if (typeof window === "undefined") { return
로딩 중...
; // 로딩 표시를 보여주셔도 되고, 아무것도 보여주지 않으셔도 됩니다. } @@ -24,6 +28,7 @@ export default function UserProfile({ UserId, children }: props) { router.push(`/Search/${name}`); dispatch(CurrMentorSlice.actions.closeMentorModal()); }; + const uid = Number(router.query.userId); return ( <> @@ -34,6 +39,7 @@ export default function UserProfile({ UserId, children }: props) {
diff --git a/42manito/src/components/Reservation/NextProgressButton.tsx b/42manito/src/components/Reservation/NextProgressButton.tsx index e922f12f..e484d6c1 100644 --- a/42manito/src/components/Reservation/NextProgressButton.tsx +++ b/42manito/src/components/Reservation/NextProgressButton.tsx @@ -10,6 +10,7 @@ import { BaseQueryError } from "@reduxjs/toolkit/src/query/baseQueryTypes"; import { useDispatch } from "react-redux"; import { openFeedbackModal, + openSocialLinkModal, setSelectedReservation, } from "@/RTK/Slices/Reservation"; @@ -39,7 +40,7 @@ export default function NextProgressButton({ try { const res = await patchFunc(data).unwrap(); dispatch(setSelectedReservation(res)); - alert(msg ? msg : "Success"); + msg && alert(msg); } catch (e: BaseQueryError) { alert(errorMsg ? errorMsg : "Error"); } @@ -91,15 +92,11 @@ export default function NextProgressButton({ ); /* 리뷰 등록에 대해서 별도의 모달이 필요함*/ diff --git a/42manito/src/components/Reservation/modal/SocialLinkModal.tsx b/42manito/src/components/Reservation/modal/SocialLinkModal.tsx new file mode 100644 index 00000000..d45e932a --- /dev/null +++ b/42manito/src/components/Reservation/modal/SocialLinkModal.tsx @@ -0,0 +1,61 @@ +import { useDispatch, useSelector } from "react-redux"; +import { closeSocialLinkModal } from "@/RTK/Slices/Reservation"; +import { useState } from "react"; +import { Button } from "@/common"; +import { ButtonType } from "@/Types/General/ButtonType"; +import { RootState } from "@/RTK/store"; +import { useGetMentorProfileQuery } from "@/RTK/Apis/User"; + +export default function SocialLinkModal() { + const dispatch = useDispatch(); + const reservation = useSelector( + (state: RootState) => state.rootReducers.reservation.selectedReservation + ); + const getMentor = useGetMentorProfileQuery({ + id: reservation.mentorId, + }); + + const handleOnClose = () => { + dispatch(closeSocialLinkModal()); + }; + + const handleOnAccept = () => { + window.open(getMentor.data?.socialLink); + dispatch(closeSocialLinkModal()); + }; + + return ( +
e.stopPropagation()} + > +
e.stopPropagation()} + > +
+
+ + 멘토의 슬랙프로필 페이지로 이동하시겠습니까? + +
+
+ + +
+
+
+
+ ); +} diff --git a/42manito/src/pages/Mentorings/index.tsx b/42manito/src/pages/Mentorings/index.tsx index a7686005..445d9a54 100644 --- a/42manito/src/pages/Mentorings/index.tsx +++ b/42manito/src/pages/Mentorings/index.tsx @@ -11,6 +11,7 @@ import { useRouter } from "next/router"; import Loading from "@/components/Global/Loading"; import FeedbackModal from "@/components/Reservation/modal/FeedbackModal"; import CancelModal from "@/components/Cancel/CancelModal"; +import SocialLinkModal from "@/components/Reservation/modal/SocialLinkModal"; const Mentoring = () => { const banner = [ @@ -36,7 +37,10 @@ const Mentoring = () => { (state: RootState) => state.rootReducers.reservation.isFeedbackModalOpen ); const isCancelModalOpen = useSelector( - (state: RootState) => state.rootReducers.reservation.isCancelModalOpen + (state: RootState) => state.rootReducers.reservation.isCancelModalOpen + ); + const isSociaLinkModalOpen = useSelector( + (state: RootState) => state.rootReducers.reservation.isSocialLinkModalOpen ); const handleRoleSelect = (id: string) => { if (id === "mentor") { @@ -147,6 +151,7 @@ const Mentoring = () => { {isReservationModalOpen && } {isFeedbackModalOpen && } {isCancelModalOpen && } + {isSociaLinkModalOpen && } ); diff --git a/42manito/src/pages/ProfileUpdate/[userId].tsx b/42manito/src/pages/ProfileUpdate/[userId].tsx index 73c18315..1d188724 100644 --- a/42manito/src/pages/ProfileUpdate/[userId].tsx +++ b/42manito/src/pages/ProfileUpdate/[userId].tsx @@ -23,6 +23,8 @@ import CategoryUpdateMultiple from "@/components/Profile/Update/CategoryUpdateMu import HashtagUpdateInput from "@/components/Profile/Update/HashtagUpdateInput"; import { ButtonType } from "@/Types/General/ButtonType"; import CardHashtag from "@/components/Global/CardHashtag"; +import { QuestionCircleOutlined } from "@ant-design/icons"; +import Link from "next/link"; const { TextArea } = Input; @@ -31,6 +33,8 @@ export default function ProfileUpdate() { const { userId } = route.query; const [shortDescription, setShortDescription] = useState(""); const [Description, setDescription] = useState(""); + const [socialLink, setSocialLink] = useState(""); + const [invalidInput, setInvalidInput] = useState(""); const { data: allCategories, isLoading, error } = useGetCategoriesQuery(); const dispatch = useAppDispatch(); const formData = useSelector( @@ -49,11 +53,35 @@ export default function ProfileUpdate() { { data: hashtagData, isLoading: hashtagLoading, isError: hashtagError }, ] = usePostHashtagMutation(); + const socialLinkHandler = () => { + if (socialLink === "") { + setInvalidInput(""); + return true; + } + + const socialLinkRegex = + /^https:\/\/42born2code\.slack\.com\/team\/[a-zA-Z0-9_]+$/; + if (socialLink !== null && socialLinkRegex.test(socialLink) === false) { + setInvalidInput("슬랙 프로필 링크 형식을 확인해주세요."); + return false; + } else { + setInvalidInput(""); + } + return true; + }; + + const invalidInputMessage = (input: string) => { + return
{input}
; + }; + const cancelButtonHandler = () => { route.back(); }; const updateButtonHandler = () => { + if (socialLinkHandler() === false) { + return; + } if (UserData) { const form: MentorProfilePatchReqDto = {}; @@ -61,13 +89,21 @@ export default function ProfileUpdate() { form.categories = formData.categories; form.shortDescription = shortDescription; form.description = Description; - UserUpdate({ - id: Number(userId as string), - profile: form, - }); + form.socialLink = socialLink === "" ? null : socialLink; + + if (invalidInput === "") { + UserUpdate({ + id: Number(userId as string), + profile: form, + }); + } } }; + useEffect(() => { + socialLinkHandler(); + }, [socialLink]); + useEffect(() => { if (hashtagData) { dispatch(ProfileUpdateSlice.actions.addHashtag(hashtagData)); @@ -92,8 +128,14 @@ export default function ProfileUpdate() { UserData.mentorProfile.categories ) ); + dispatch( + ProfileUpdateSlice.actions.setSocialLink( + UserData.mentorProfile.socialLink + ) + ); setDescription(UserData.mentorProfile.description); setShortDescription(UserData.mentorProfile.shortDescription); + setSocialLink(UserData.mentorProfile.socialLink); } }, [UserData, dispatch]); @@ -170,6 +212,29 @@ export default function ProfileUpdate() {
+
+ + 슬랙 링크 + + + + + + 슬랙 프로필 링크를 입력해주세요 + +