diff --git a/src/app/routes/PrivateRoute.tsx b/src/app/routes/PrivateRoute.tsx
index b9cac16..b47472e 100644
--- a/src/app/routes/PrivateRoute.tsx
+++ b/src/app/routes/PrivateRoute.tsx
@@ -9,8 +9,13 @@ const PrivateRoute = ({
children: React.ReactNode;
}): JSX.Element => {
const user = useAuthStore((state) => state.user);
+ const isLoading = useAuthStore((state) => state.isLoading);
const location = useLocation();
+ if (isLoading) {
+ return
Loading...
;
+ }
+
if (!user) {
return ;
}
diff --git a/src/entities/user/api/userApi.ts b/src/entities/user/api/userApi.ts
deleted file mode 100644
index 0538fe0..0000000
--- a/src/entities/user/api/userApi.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { doc, setDoc, getDoc } from "firebase/firestore";
-
-import { db } from "@shared/firebase/firebase";
-import type { User } from "@shared/types/user";
-
-export const saveUser = async (uid: string, userInfo: User): Promise => {
- const userDoc = doc(db, "users", uid);
- await setDoc(userDoc, userInfo);
-};
-
-export const getUser = async (uid: string): Promise => {
- const userDoc = doc(db, "users", uid);
- const userSnap = await getDoc(userDoc);
- if (userSnap.exists()) {
- return userSnap.data() as User;
- }
- return null;
-};
diff --git a/src/entities/user/hooks/useSignUp.ts b/src/entities/user/hooks/useSignUp.ts
index 533673e..b5bf429 100644
--- a/src/entities/user/hooks/useSignUp.ts
+++ b/src/entities/user/hooks/useSignUp.ts
@@ -1,7 +1,6 @@
import { useNavigate } from "react-router-dom";
-import { saveUser } from "@entities/user/api/userApi";
-
+import { saveUser } from "@shared/api/userApi";
import { useAuthStore } from "@shared/stores/authStore";
import type { UserInput } from "@shared/types/user";
diff --git a/src/entities/user/hooks/useUpdateUser.ts b/src/entities/user/hooks/useUpdateUser.ts
new file mode 100644
index 0000000..6d6f397
--- /dev/null
+++ b/src/entities/user/hooks/useUpdateUser.ts
@@ -0,0 +1,29 @@
+import {
+ useMutation,
+ useQueryClient,
+ type UseMutationResult,
+} from "@tanstack/react-query";
+
+import { updateUser } from "@shared/api/userApi";
+import type { User } from "@shared/types/user";
+
+export const useUpdateUser = (): UseMutationResult<
+ void,
+ Error,
+ {
+ uid: string;
+ userInfo: Partial;
+ },
+ unknown
+> => {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationFn: ({ uid, userInfo }: { uid: string; userInfo: Partial }) =>
+ updateUser(uid, userInfo),
+ onSuccess: (_, { uid }) => {
+ // 유저 프로필 쿼리 무효화하여 최신 데이터로 갱신
+ queryClient.invalidateQueries({ queryKey: ["userProfile", uid] });
+ },
+ });
+};
diff --git a/src/entities/user/hooks/useUpdateUserForm.ts b/src/entities/user/hooks/useUpdateUserForm.ts
new file mode 100644
index 0000000..fa7b6be
--- /dev/null
+++ b/src/entities/user/hooks/useUpdateUserForm.ts
@@ -0,0 +1,92 @@
+import { useState } from "react";
+
+import type { User, UserInput, UserRole } from "@shared/types/user";
+import { UserExperience } from "@shared/types/user";
+
+interface UseUpdateUserFormProps {
+ defaultUser: User;
+}
+
+interface UseUpdateUserFormReturn {
+ name: string;
+ userRole: string;
+ experience: string;
+ introduceMyself: string;
+ errors: {
+ name: boolean;
+ userRole: boolean;
+ experience: boolean;
+ };
+ handleChange: (
+ field: "name" | "userRole" | "experience" | "introduceMyself"
+ ) => (value: string) => void;
+ handleSubmit: () => UserInput | null;
+}
+
+export function useUpdateUserForm({
+ defaultUser,
+}: UseUpdateUserFormProps): UseUpdateUserFormReturn {
+ const [name, setName] = useState(defaultUser.name);
+ const [userRole, setUserRole] = useState(defaultUser.userRole);
+ const [experience, setExperience] = useState(defaultUser.experience);
+ const [introduceMyself, setIntroduceMyself] = useState(
+ defaultUser.introduceMyself || ""
+ );
+ const [errors, setErrors] = useState({
+ name: false,
+ userRole: false,
+ experience: false,
+ });
+
+ const handleChange =
+ (field: "name" | "userRole" | "experience" | "introduceMyself") =>
+ (value: string) => {
+ switch (field) {
+ case "name":
+ setName(value);
+ if (errors.name) setErrors((prev) => ({ ...prev, name: false }));
+ break;
+ case "userRole":
+ setUserRole(value);
+ if (errors.userRole)
+ setErrors((prev) => ({ ...prev, userRole: false }));
+ break;
+ case "experience":
+ setExperience(value);
+ if (errors.experience)
+ setErrors((prev) => ({ ...prev, experience: false }));
+ break;
+ case "introduceMyself":
+ setIntroduceMyself(value);
+ break;
+ }
+ };
+
+ const handleSubmit = (): UserInput | null => {
+ if (name === "" || userRole === "" || experience === "") {
+ setErrors({
+ name: name === "",
+ userRole: userRole === "",
+ experience: experience === "",
+ });
+ return null;
+ }
+
+ return {
+ name,
+ userRole: userRole as UserRole,
+ experience: experience as UserExperience,
+ introduceMyself: introduceMyself || "",
+ };
+ };
+
+ return {
+ name,
+ userRole,
+ experience,
+ introduceMyself,
+ errors,
+ handleChange,
+ handleSubmit,
+ };
+}
diff --git a/src/entities/user/ui/SubmitButton.tsx b/src/entities/user/ui/SubmitButton.tsx
index 83ad08e..84f88fd 100644
--- a/src/entities/user/ui/SubmitButton.tsx
+++ b/src/entities/user/ui/SubmitButton.tsx
@@ -1,8 +1,16 @@
import { Button, styled } from "@mui/material";
import type { JSX } from "react";
-const SubmitButton = ({ onClick }: { onClick: () => void }): JSX.Element => {
- return 회원가입 완료;
+interface SubmitButtonProps {
+ onClick: () => void;
+ text?: string;
+}
+
+const SubmitButton = ({
+ onClick,
+ text = "회원가입 완료",
+}: SubmitButtonProps): JSX.Element => {
+ return {text};
};
export default SubmitButton;
diff --git a/src/entities/user/ui/UpdateUserForm.tsx b/src/entities/user/ui/UpdateUserForm.tsx
new file mode 100644
index 0000000..d8307c3
--- /dev/null
+++ b/src/entities/user/ui/UpdateUserForm.tsx
@@ -0,0 +1,253 @@
+import {
+ Box,
+ MenuItem,
+ Select,
+ TextField,
+ Typography,
+ FormControl,
+ FormHelperText,
+ InputLabel,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ Button,
+} from "@mui/material";
+import { styled } from "@mui/material/styles";
+import { type JSX } from "react";
+import { useState } from "react";
+
+import { useUpdateUserForm } from "@entities/user/hooks/useUpdateUserForm";
+
+import type { User, UserInput } from "@shared/types/user";
+import { UserExperience } from "@shared/types/user";
+
+interface UpdateUserFormProps {
+ defaultUser: User;
+ onSubmit: (userInfo: UserInput) => void;
+ onCancel: () => void;
+}
+
+const UpdateUserForm = ({
+ defaultUser,
+ onSubmit,
+ onCancel,
+}: UpdateUserFormProps): JSX.Element => {
+ const {
+ name,
+ userRole,
+ experience,
+ introduceMyself,
+ errors,
+ handleChange,
+ handleSubmit,
+ } = useUpdateUserForm({ defaultUser });
+
+ const [showConfirmDialog, setShowConfirmDialog] = useState(false);
+
+ const handleFormSubmit = (): void => {
+ const result = handleSubmit();
+ if (result) {
+ setShowConfirmDialog(true);
+ }
+ };
+
+ const handleConfirmSubmit = (): void => {
+ const result = handleSubmit();
+ if (result) {
+ onSubmit(result);
+ setShowConfirmDialog(false);
+ }
+ };
+
+ const handleCancelConfirm = (): void => {
+ setShowConfirmDialog(false);
+ };
+
+ return (
+ <>
+
+ 프로필 수정
+ {/* 이름 입력 */}
+
+ handleChange("name")(e.target.value)}
+ error={errors.name}
+ onFocus={() => errors.name && handleChange("name")(name)}
+ InputLabelProps={{ shrink: true }}
+ />
+ {errors.name && 이름을 입력해주세요.}
+
+ {/* 이메일 입력 (disabled) */}
+
+
+
+ {/* 직무 선택 */}
+
+ 👔 직무 *
+ handleChange("userRole")(e.target.value as string)}
+ displayEmpty
+ >
+
+
+
+
+
+
+
+ {errors.userRole && 직무를 선택해주세요.}
+
+ {/* 경력 선택 */}
+
+ 💼 경력 *
+
+ handleChange("experience")(e.target.value as string)
+ }
+ displayEmpty
+ >
+
+
+
+
+
+ {errors.experience && 경력을 선택해주세요.}
+
+ handleChange("introduceMyself")(e.target.value)}
+ placeholder="코딩하고 싶은 밤이에요~😘"
+ multiline
+ rows={4}
+ InputLabelProps={{ shrink: true }}
+ />
+
+ 취소
+
+ 저장
+
+
+
+
+ {/* 확인 다이얼로그 */}
+
+ >
+ );
+};
+
+export default UpdateUserForm;
+
+const FormContainer = styled(Box)({
+ display: "flex",
+ flexDirection: "column",
+ gap: "16px",
+ width: "100%",
+ maxWidth: "400px",
+ margin: "0 auto",
+ padding: "2rem 1rem",
+});
+
+const Title = styled(Typography)({
+ textAlign: "center",
+ marginBottom: "16px",
+});
+
+const StyledTextField = styled(TextField)({
+ width: "100%",
+});
+
+const StyledSelect = styled(Select)({
+ width: "100%",
+});
+
+const ErrorText = styled(FormHelperText)({
+ fontSize: "1.3rem",
+ color: "#f44336", // MUI 기본 error color
+ marginLeft: "14px",
+});
+
+const ButtonContainer = styled(Box)({
+ display: "flex",
+ marginTop: "8px",
+ gap: "2rem",
+ justifyContent: "space-between",
+});
+
+const CancelButton = styled("button")({
+ flex: 1,
+ padding: "12px 24px",
+ border: "1px solid #ccc",
+ borderRadius: "8px",
+ backgroundColor: "#fff",
+ color: "#666",
+ fontSize: "1.4rem",
+ fontWeight: 500,
+ cursor: "pointer",
+ transition: "all 0.2s ease",
+ "&:hover": {
+ backgroundColor: "#f5f5f5",
+ borderColor: "#999",
+ },
+});
+
+const SubmitButtonStyled = styled("button")({
+ flex: 1,
+ padding: "12px 24px",
+ border: "1px solid #1976d2",
+ borderRadius: "8px",
+ backgroundColor: "#1976d2",
+ color: "#fff",
+ fontSize: "1.4rem",
+ fontWeight: 500,
+ cursor: "pointer",
+ transition: "all 0.2s ease",
+ "&:hover": {
+ backgroundColor: "#1565c0",
+ borderColor: "#1565c0",
+ },
+});
diff --git a/src/entities/user/ui/user-profile/UserProfileCard.tsx b/src/entities/user/ui/user-profile/UserProfileCard.tsx
index a366c09..cdda2b0 100644
--- a/src/entities/user/ui/user-profile/UserProfileCard.tsx
+++ b/src/entities/user/ui/user-profile/UserProfileCard.tsx
@@ -7,12 +7,21 @@ import {
CardContent,
IconButton,
Divider,
+ Dialog,
+ DialogContent,
+ Tooltip,
} from "@mui/material";
import { styled as muiStyled } from "@mui/material/styles";
import type { ComponentType, JSX } from "react";
+import { useState } from "react";
+
+import { useUpdateUser } from "@entities/user/hooks/useUpdateUser";
+import UpdateUserForm from "@entities/user/ui/UpdateUserForm";
import { useProjectStore } from "@shared/stores/projectStore";
-import type { User } from "@shared/types/user";
+import type { User, UserInput } from "@shared/types/user";
+import { UserExperience } from "@shared/types/user";
+import SnackbarAlert from "@shared/ui/SnackbarAlert";
import TabWithBadge from "./TapWithBadge";
@@ -33,10 +42,11 @@ const userRoleMap: Record = {
designer: "디자이너",
pm: "PM",
};
+
const experienceMap: Record = {
- junior: "주니어 (3년 이하) 🌱",
- mid: "미들 (3년 이상 10년 이하) 🌿",
- senior: "시니어 (10년 이상) 🌳",
+ [UserExperience.junior]: "주니어 (3년 이하) 🌱",
+ [UserExperience.mid]: "미들 (3년 이상 10년 이하) 🌿",
+ [UserExperience.senior]: "시니어 (10년 이상) 🌳",
};
const UserProfileCard = ({
@@ -47,56 +57,146 @@ const UserProfileCard = ({
ProfileTabChip,
}: UserProfileCardProps): JSX.Element => {
const { likeProjects, appliedProjects } = useProjectStore();
+ const [openModal, setOpenModal] = useState(false);
+ const [snackbarOpen, setSnackbarOpen] = useState(false);
+
+ const updateUserMutation = useUpdateUser();
+
+ const handleOpenModal = (): void => {
+ setOpenModal(true);
+ };
+
+ const handleCloseModal = (): void => {
+ setOpenModal(false);
+ };
+
+ const handleSubmitUpdate = async (userInfo: UserInput): Promise => {
+ try {
+ await updateUserMutation.mutateAsync({
+ uid: userProfile.id,
+ userInfo,
+ });
+
+ setOpenModal(false);
+ setSnackbarOpen(true);
+ } catch (error) {
+ console.error("프로필 업데이트 실패:", error);
+ // 에러 처리 로직 추가 가능
+ }
+ };
return (
-
-
-
-
-
-
-
-
-
-
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {userProfile.name}
+
+
+ {userRoleMap[userProfile.userRole] || userProfile.userRole}
+
+
+ {experienceMap[userProfile.experience] ||
+ userProfile.experience}
+
+
+
+
- {userProfile.name}
-
-
- {userRoleMap[userProfile.userRole] || userProfile.userRole}
+ {userProfile.introduceMyself}
-
- {experienceMap[userProfile.experience] || userProfile.experience}
-
-
-
-
- {userProfile.introduceMyself}
-
-
-
- {PROFILE_TABS.map((tabInfo, idx) => (
- setTab(idx)}
- ProfileTabChip={ProfileTabChip}
- />
- ))}
-
- 💌 • {userProfile.email}
-
-
+
+
+
+ {PROFILE_TABS.map((tabInfo, idx) => (
+ setTab(idx)}
+ ProfileTabChip={ProfileTabChip}
+ />
+ ))}
+
+ 💌 • {userProfile.email}
+
+
+
+ {/* 프로필 수정 모달 */}
+
+
+ {/* 스낵바 알림 */}
+ setSnackbarOpen(false)}
+ message="프로필 정보가 업데이트되었습니다! ✨"
+ severity="success"
+ />
+ >
);
};
@@ -105,13 +205,11 @@ export default UserProfileCard;
// 스타일 컴포넌트 재사용
const ProfileCard = muiStyled(Card)(({ theme }) => ({
minWidth: 280,
- maxWidth: "100%",
+ maxWidth: 380,
borderRadius: 12,
boxShadow: theme.shadows[2],
position: "relative",
padding: "0 2rem",
- maxHeight: "350px",
- overflow: "auto",
}));
const ProfileCardContent = muiStyled(CardContent)(({ theme }) => ({
padding: theme.spacing(3),
diff --git a/src/features/auth/hooks/useSocialLogin.ts b/src/features/auth/hooks/useSocialLogin.ts
index beb08f4..132227f 100644
--- a/src/features/auth/hooks/useSocialLogin.ts
+++ b/src/features/auth/hooks/useSocialLogin.ts
@@ -2,6 +2,7 @@ import {
signInWithPopup,
fetchSignInMethodsForEmail,
getAdditionalUserInfo,
+ type AuthError,
} from "firebase/auth";
import type { AuthProvider } from "firebase/auth";
import { useNavigate, useSearchParams } from "react-router-dom";
@@ -18,46 +19,92 @@ export const useSocialLogin = (): {
const socialLogin = async (provider: AuthProvider): Promise => {
try {
const result = await signInWithPopup(auth, provider);
- const user = result.user;
-
- console.log("소셜 로그인 성공: ", user);
// Firebase 신규 유저 여부 확인
const additionalInfo = getAdditionalUserInfo(result);
const isNewUser = additionalInfo?.isNewUser;
if (isNewUser) {
- navigate("/signup");
+ navigate("/signup", { state: { fromSocial: true } });
} else {
navigate(redirect);
}
- } catch (error: any) {
- console.error("소셜 로그인 실패: ", error);
-
- if (error.code !== "auth/account-exists-with-different-credential")
- return;
+ } catch (error: unknown) {
+ const authError = error as AuthError;
- const email = error.customData?.email;
- if (!email) {
- alert("이메일 정보를 가져올 수 없습니다.");
+ // 계정 충돌 에러 처리
+ if (authError.code === "auth/account-exists-with-different-credential") {
+ await handleAccountConflict(authError);
return;
}
+ // 기타 에러 처리
+ handleGeneralError(authError);
+ }
+ };
+
+ const handleAccountConflict = async (error: AuthError): Promise => {
+ const email = error.customData?.email;
+ if (!email) {
+ showErrorSnackbar("이메일 정보를 가져올 수 없습니다.");
+ return;
+ }
+
+ try {
const methods = await fetchSignInMethodsForEmail(auth, email);
if (methods.length === 0) {
- alert("이미 다른 로그인 방법으로 가입된 이메일입니다.");
+ showErrorSnackbar("이미 다른 로그인 방법으로 가입된 이메일입니다.");
return;
}
if (methods.includes("google.com")) {
- alert(
+ showErrorSnackbar(
"이미 Google 계정으로 가입된 이메일입니다. Google 로그인을 이용해주세요."
);
} else {
- alert(`이미 가입된 로그인 방법: ${methods.join(", ")}`);
+ showErrorSnackbar(`이미 가입된 로그인 방법: ${methods.join(", ")}`);
}
+ } catch {
+ showErrorSnackbar("로그인 방법 조회 중 오류가 발생했습니다.");
}
};
+ const handleGeneralError = (error: AuthError): void => {
+ // 일반적인 에러 코드별 처리
+ switch (error.code) {
+ case "auth/popup-closed-by-user":
+ // 사용자가 팝업을 닫은 경우는 알림하지 않음
+ break;
+ case "auth/popup-blocked":
+ showErrorSnackbar("팝업이 차단되었습니다. 팝업 차단을 해제해주세요.");
+ break;
+ case "auth/network-request-failed":
+ showErrorSnackbar("네트워크 연결을 확인해주세요.");
+ break;
+ case "auth/user-disabled":
+ showErrorSnackbar("비활성화된 계정입니다.");
+ break;
+ case "auth/invalid-credential":
+ showErrorSnackbar("잘못된 로그인 정보입니다.");
+ break;
+ case "auth/operation-not-allowed":
+ showErrorSnackbar("해당 로그인 방법이 허용되지 않습니다.");
+ break;
+ case "auth/too-many-requests":
+ showErrorSnackbar(
+ "너무 많은 로그인 시도가 있었습니다. 잠시 후 다시 시도해주세요."
+ );
+ break;
+ default:
+ showErrorSnackbar("로그인 중 오류가 발생했습니다. 다시 시도해주세요.");
+ }
+ };
+
+ const showErrorSnackbar = (message: string): void => {
+ // TODO: 전역 스낵바 상태 관리로 에러 메시지 표시
+ // 예: useSnackbarStore.getState().showError(message);
+ console.error("로그인 에러:", message);
+ };
+
return { socialLogin };
};
diff --git a/src/features/auth/hooks/useUserProfile.ts b/src/features/auth/hooks/useUserProfile.ts
deleted file mode 100644
index 3038f9e..0000000
--- a/src/features/auth/hooks/useUserProfile.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { useQuery, type UseQueryResult } from "@tanstack/react-query";
-
-import { getUser } from "@entities/user/api/userApi";
-
-import type { User } from "@shared/types/user";
-
-export function useUserProfile(uid: string): UseQueryResult {
- return useQuery({
- queryKey: ["userProfile", uid],
- queryFn: () => getUser(uid),
- enabled: !!uid, // uid가 있을 때만 쿼리 실행
- });
-}
diff --git a/src/features/projects/hook/useProjectInsertForm.ts b/src/features/projects/hook/useProjectInsertForm.ts
index 2c7a37f..2b4b7c3 100644
--- a/src/features/projects/hook/useProjectInsertForm.ts
+++ b/src/features/projects/hook/useProjectInsertForm.ts
@@ -123,6 +123,7 @@ const initForm4 = {
// 테스트용 form 입니다.
const TestData = (user: User): ProjectItemInsertReq => ({
+ projectOwnerID: user.id, // 요거 추가!!
projectOwner: {
id: user.id,
name: user.name,
diff --git a/src/pages/home/ui/HomePage.tsx b/src/pages/home/ui/HomePage.tsx
index 81afb70..a07c3c3 100644
--- a/src/pages/home/ui/HomePage.tsx
+++ b/src/pages/home/ui/HomePage.tsx
@@ -8,7 +8,6 @@ import ProjectCard from "@entities/projects/ui/projects-card/ProjectCard";
import ProjectsStats from "@entities/projects/ui/projects-stats/ProjectsStats";
const HomePage = (): JSX.Element => {
- console.log("API_KEY: ", import.meta.env.VITE_API_KEY);
const { data: projects } = useGetProjects({ pageSize: 3 });
return (
diff --git a/src/pages/login/ui/LoginPage.tsx b/src/pages/login/ui/LoginPage.tsx
index 8461f08..8908cf6 100644
--- a/src/pages/login/ui/LoginPage.tsx
+++ b/src/pages/login/ui/LoginPage.tsx
@@ -6,7 +6,6 @@ import BackToHome from "@widgets/BackToHome/BackToHome";
import { LoginForm } from "@features/auth/ui/LoginForm";
const LoginPage = (): JSX.Element => {
- console.log("API_KEY: ", import.meta.env.VITE_API_KEY);
return (
diff --git a/src/pages/signup/ui/SignUpPage.tsx b/src/pages/signup/ui/SignUpPage.tsx
index f79e6e9..f61b916 100644
--- a/src/pages/signup/ui/SignUpPage.tsx
+++ b/src/pages/signup/ui/SignUpPage.tsx
@@ -1,11 +1,33 @@
import { Box, Paper, styled, Typography } from "@mui/material";
import type { JSX } from "react";
+import { useState } from "react";
+import { useLocation, Navigate } from "react-router-dom";
import BackToHome from "@widgets/BackToHome/BackToHome";
import UserInfoForm from "@entities/user/ui/UserInfoForm";
+import SnackbarAlert from "@shared/ui/SnackbarAlert";
+
const SignUpPage = (): JSX.Element => {
+ const location = useLocation();
+ const fromSocial = location.state?.fromSocial;
+ const [open, setOpen] = useState(!fromSocial);
+
+ if (!fromSocial) {
+ if (open) {
+ return (
+ setOpen(false)}
+ message="잘못된 접근입니다."
+ severity="error"
+ />
+ );
+ }
+ return ;
+ }
+
return (
diff --git a/src/pages/user-profile/ui/UserProfilePage.tsx b/src/pages/user-profile/ui/UserProfilePage.tsx
index 99e2b46..cd5fcb9 100644
--- a/src/pages/user-profile/ui/UserProfilePage.tsx
+++ b/src/pages/user-profile/ui/UserProfilePage.tsx
@@ -3,7 +3,6 @@ import { styled as muiStyled } from "@mui/material/styles";
import type { JSX } from "react";
import { useState, useEffect } from "react";
-import { useUserProfile } from "@features/auth/hooks/useUserProfile";
import { removeProjectsFromUser } from "@features/projects/api/projectsApi";
import { useProjectsByIds } from "@entities/projects/hook/useProjectsByIds";
@@ -11,6 +10,7 @@ import UserProfileCard from "@entities/user/ui/user-profile/UserProfileCard";
import UserProfileHeader from "@entities/user/ui/user-profile/UserProfileHeader";
import UserProfileProjectList from "@entities/user/ui/user-profile/UserProfileProjectList";
+import { useUserProfile } from "@shared/queries/useUserProfile";
import { useAuthStore } from "@shared/stores/authStore";
import { useProjectStore } from "@shared/stores/projectStore";
import LoadingSpinner from "@shared/ui/loading-spinner/LoadingSpinner";
@@ -71,6 +71,7 @@ const UserProfilePage = (): JSX.Element => {
gap={4}
flexDirection={{ xs: "column", sm: "row" }}
position="relative"
+ alignItems="flex-start"
>
{/* 왼쪽 프로필 사이드바 */}
=> {
}
return null;
};
+
+export const updateUser = async (
+ uid: string,
+ userInfo: Partial
+): Promise => {
+ const userDoc = doc(db, "users", uid);
+ await updateDoc(userDoc, userInfo);
+};
diff --git a/src/shared/types/project.ts b/src/shared/types/project.ts
index 46f3531..fc600f5 100644
--- a/src/shared/types/project.ts
+++ b/src/shared/types/project.ts
@@ -4,6 +4,7 @@ import type { ExpectedPeriod, ProjectSchedule } from "@shared/types/schedule";
import type { User, UserRole } from "@shared/types/user";
export interface ProjectItemInsertReq {
+ projectOwnerID: string; // 작성자
projectOwner: User; // 프로젝트 오너 유저 정보
status: RecruitmentStatus;
category: ProjectCategory; // 프로젝트 분야