diff --git a/apps/web/app/[meetingId]/user/modify/page.tsx b/apps/web/app/[meetingId]/user/modify/page.tsx
new file mode 100644
index 00000000..c5c6e1d5
--- /dev/null
+++ b/apps/web/app/[meetingId]/user/modify/page.tsx
@@ -0,0 +1,3 @@
+import { ModifyUserInfoScreen } from '@/user';
+
+export default ModifyUserInfoScreen;
diff --git a/apps/web/app/onboarding/page.tsx b/apps/web/app/onboarding/page.tsx
new file mode 100644
index 00000000..d6c26e4a
--- /dev/null
+++ b/apps/web/app/onboarding/page.tsx
@@ -0,0 +1,17 @@
+import { OnBoardingScreen, StepType } from '@/onboarding';
+
+interface OnBoardingPageProps {
+ searchParams: {
+ step: StepType;
+ };
+}
+
+const OnBoardingPage = async (props: OnBoardingPageProps) => {
+ const {
+ searchParams: { step },
+ } = props;
+
+ return ;
+};
+
+export default OnBoardingPage;
diff --git a/packages/web-domains/package.json b/packages/web-domains/package.json
index 288302a5..9b2ff22b 100644
--- a/packages/web-domains/package.json
+++ b/packages/web-domains/package.json
@@ -11,7 +11,8 @@
"./user": "./src/user/index.ts",
"./participate-meeting": "./src/participate-meeting/index.ts",
"./common": "./src/common/index.ts",
- "./relay-question": "./src/relay-question/index.ts"
+ "./relay-question": "./src/relay-question/index.ts",
+ "./onboarding": "./src/onboarding/index.ts"
},
"scripts": {
"lint": "eslint . --max-warnings 0",
diff --git a/packages/web-domains/src/relay-question/assets/RelayToolTipPolygon.tsx b/packages/web-domains/src/common/components/ToolTip/RelayToolTipPolygon.tsx
similarity index 100%
rename from packages/web-domains/src/relay-question/assets/RelayToolTipPolygon.tsx
rename to packages/web-domains/src/common/components/ToolTip/RelayToolTipPolygon.tsx
diff --git a/packages/web-domains/src/relay-question/features/select-relay-question/components/ToolTip/ToolTip.styles.ts b/packages/web-domains/src/common/components/ToolTip/ToolTip.styles.ts
similarity index 100%
rename from packages/web-domains/src/relay-question/features/select-relay-question/components/ToolTip/ToolTip.styles.ts
rename to packages/web-domains/src/common/components/ToolTip/ToolTip.styles.ts
diff --git a/packages/web-domains/src/relay-question/features/select-relay-question/components/ToolTip/ToolTip.tsx b/packages/web-domains/src/common/components/ToolTip/ToolTip.tsx
similarity index 88%
rename from packages/web-domains/src/relay-question/features/select-relay-question/components/ToolTip/ToolTip.tsx
rename to packages/web-domains/src/common/components/ToolTip/ToolTip.tsx
index 17e4fad0..68c1360c 100644
--- a/packages/web-domains/src/relay-question/features/select-relay-question/components/ToolTip/ToolTip.tsx
+++ b/packages/web-domains/src/common/components/ToolTip/ToolTip.tsx
@@ -3,8 +3,7 @@ import { Txt } from '@sambad/sds/components';
import { colors } from '@sambad/sds/theme';
import { PropsWithChildren } from 'react';
-import { RelayToolTipPolygon } from '../../../../assets/RelayToolTipPolygon';
-
+import { RelayToolTipPolygon } from './RelayToolTipPolygon';
import { textWrapperCss, wrapperCss } from './ToolTip.styles';
export const ToolTip = ({ children }: PropsWithChildren) => {
diff --git a/packages/web-domains/src/home/features/progressing-question/components/QuestionInfo/ActiveQuestion.tsx b/packages/web-domains/src/home/features/progressing-question/components/QuestionInfo/ActiveQuestion.tsx
index c395b4e1..6d46cdd4 100644
--- a/packages/web-domains/src/home/features/progressing-question/components/QuestionInfo/ActiveQuestion.tsx
+++ b/packages/web-domains/src/home/features/progressing-question/components/QuestionInfo/ActiveQuestion.tsx
@@ -2,6 +2,8 @@ import { Txt } from '@sambad/sds/components';
import { borderRadiusVariants, colors, size } from '@sambad/sds/theme';
import Link from 'next/link';
+import { ToolTip } from '@/common/components/ToolTip/ToolTip';
+
import { ArrowIcon } from '../../../../../common/asset/arrow';
import { ProgressingQuestionType } from '../../../../common/apis/schema/useGetProgressingQuestionQuery.type';
import { Avatar } from '../../../../common/components/Avatar/Avatar';
@@ -22,7 +24,12 @@ export const ActiveQuestion = ({ question }: ActiveQuestionProps) => {
} = question;
return (
-
+
+ {!isAnswered && (
+
+ 답변을 아직 안했어요!
+
+ )}
;
+}
+
+export const useOnBoardingCompleteMutation = ({ options }: Args = {}) => {
+ return useMutation({
+ mutationFn: async () => {
+ try {
+ const data = await onBoardingComplete();
+ return data;
+ } catch (error) {
+ if (isAxiosError(error)) {
+ console.error(error);
+ }
+ }
+ },
+ ...options,
+ });
+};
+
+export async function onBoardingComplete(): Promise {
+ const data = await Http.PATCH(`/v1/users/onboarding/complete`);
+ return data;
+}
diff --git a/packages/web-domains/src/onboarding/common/assets/icons/HandIcon.tsx b/packages/web-domains/src/onboarding/common/assets/icons/HandIcon.tsx
new file mode 100644
index 00000000..b1d05c2d
--- /dev/null
+++ b/packages/web-domains/src/onboarding/common/assets/icons/HandIcon.tsx
@@ -0,0 +1,10 @@
+export const HandIcon = () => (
+
+);
diff --git a/packages/web-domains/src/onboarding/common/assets/icons/HumanIcon.tsx b/packages/web-domains/src/onboarding/common/assets/icons/HumanIcon.tsx
new file mode 100644
index 00000000..e4d97e30
--- /dev/null
+++ b/packages/web-domains/src/onboarding/common/assets/icons/HumanIcon.tsx
@@ -0,0 +1,12 @@
+export const HumanIcon = () => (
+
+);
diff --git a/packages/web-domains/src/onboarding/common/assets/icons/QuestionIcon.tsx b/packages/web-domains/src/onboarding/common/assets/icons/QuestionIcon.tsx
new file mode 100644
index 00000000..535cca0c
--- /dev/null
+++ b/packages/web-domains/src/onboarding/common/assets/icons/QuestionIcon.tsx
@@ -0,0 +1,28 @@
+import { IconAssetProps } from '@sds/components/Icon/types';
+import { colors } from '@sds/theme';
+
+export const QuestionIcon = (props: IconAssetProps) => {
+ const { color = colors.primary500 } = props;
+
+ return (
+
+ );
+};
diff --git a/packages/web-domains/src/onboarding/common/assets/images/bg-about-me.png b/packages/web-domains/src/onboarding/common/assets/images/bg-about-me.png
new file mode 100644
index 00000000..25e88568
Binary files /dev/null and b/packages/web-domains/src/onboarding/common/assets/images/bg-about-me.png differ
diff --git a/packages/web-domains/src/onboarding/common/assets/images/bg-friendship.png b/packages/web-domains/src/onboarding/common/assets/images/bg-friendship.png
new file mode 100644
index 00000000..e06d2a28
Binary files /dev/null and b/packages/web-domains/src/onboarding/common/assets/images/bg-friendship.png differ
diff --git a/packages/web-domains/src/onboarding/common/assets/images/bg-question.png b/packages/web-domains/src/onboarding/common/assets/images/bg-question.png
new file mode 100644
index 00000000..c5954916
Binary files /dev/null and b/packages/web-domains/src/onboarding/common/assets/images/bg-question.png differ
diff --git a/packages/web-domains/src/onboarding/common/assets/images/bg-result.png b/packages/web-domains/src/onboarding/common/assets/images/bg-result.png
new file mode 100644
index 00000000..752338b0
Binary files /dev/null and b/packages/web-domains/src/onboarding/common/assets/images/bg-result.png differ
diff --git a/packages/web-domains/src/onboarding/common/assets/images/onboarding-about-me.png b/packages/web-domains/src/onboarding/common/assets/images/onboarding-about-me.png
new file mode 100644
index 00000000..c02493bd
Binary files /dev/null and b/packages/web-domains/src/onboarding/common/assets/images/onboarding-about-me.png differ
diff --git a/packages/web-domains/src/onboarding/common/assets/images/onboarding-friendship.png b/packages/web-domains/src/onboarding/common/assets/images/onboarding-friendship.png
new file mode 100644
index 00000000..452bedf1
Binary files /dev/null and b/packages/web-domains/src/onboarding/common/assets/images/onboarding-friendship.png differ
diff --git a/packages/web-domains/src/onboarding/common/assets/images/onboarding-question.png b/packages/web-domains/src/onboarding/common/assets/images/onboarding-question.png
new file mode 100644
index 00000000..7e1bb6e5
Binary files /dev/null and b/packages/web-domains/src/onboarding/common/assets/images/onboarding-question.png differ
diff --git a/packages/web-domains/src/onboarding/common/assets/images/onboarding-result.png b/packages/web-domains/src/onboarding/common/assets/images/onboarding-result.png
new file mode 100644
index 00000000..95da5d94
Binary files /dev/null and b/packages/web-domains/src/onboarding/common/assets/images/onboarding-result.png differ
diff --git a/packages/web-domains/src/onboarding/common/constants/steps.ts b/packages/web-domains/src/onboarding/common/constants/steps.ts
new file mode 100644
index 00000000..eb45f06b
--- /dev/null
+++ b/packages/web-domains/src/onboarding/common/constants/steps.ts
@@ -0,0 +1,8 @@
+export const STEPS = {
+ QUESTION: 'question',
+ RESULT: 'result',
+ FRIENDSHIP: 'friendship',
+ ABOUT_ME: 'about-me',
+} as const;
+
+export type StepType = (typeof STEPS)[keyof typeof STEPS];
diff --git a/packages/web-domains/src/onboarding/features/components/BackGroundImage/BackGroundImage.tsx b/packages/web-domains/src/onboarding/features/components/BackGroundImage/BackGroundImage.tsx
new file mode 100644
index 00000000..a14270d1
--- /dev/null
+++ b/packages/web-domains/src/onboarding/features/components/BackGroundImage/BackGroundImage.tsx
@@ -0,0 +1,30 @@
+import { css } from '@emotion/react';
+import Image, { StaticImageData } from 'next/image';
+
+interface BackGroundProps {
+ imageUrl?: string | StaticImageData;
+}
+
+export const BackGroundImage = (props: BackGroundProps) => {
+ const { imageUrl } = props;
+
+ if (!imageUrl) {
+ return null;
+ }
+
+ return (
+
+
+
+ );
+};
+
+const backGroundContainerCss = css({
+ position: 'absolute',
+ top: '0',
+ left: '50%',
+ transform: 'translate(-50%, 0)',
+ width: '100%',
+ height: 'calc(100% - 116px)',
+ zIndex: '-1',
+});
diff --git a/packages/web-domains/src/onboarding/features/components/Button/NextButton.tsx b/packages/web-domains/src/onboarding/features/components/Button/NextButton.tsx
new file mode 100644
index 00000000..162f3967
--- /dev/null
+++ b/packages/web-domains/src/onboarding/features/components/Button/NextButton.tsx
@@ -0,0 +1,26 @@
+import { Button } from '@sds/components';
+import { useRouter, useSearchParams } from 'next/navigation';
+
+import { StepType, STEPS } from '@/onboarding/common/constants/steps';
+
+const stepOrder: StepType[] = [STEPS.QUESTION, STEPS.RESULT, STEPS.FRIENDSHIP, STEPS.ABOUT_ME];
+
+export const NextButton = () => {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+
+ const currentStep = searchParams.get('step') as StepType;
+
+ const currentStepIndex = stepOrder.indexOf(currentStep);
+ const nextStep = stepOrder[currentStepIndex + 1] || stepOrder[0];
+
+ const handleGoToNextPage = () => {
+ router.push(`?step=${nextStep}`);
+ };
+
+ return (
+
+ );
+};
diff --git a/packages/web-domains/src/onboarding/features/components/Button/StartButton.tsx b/packages/web-domains/src/onboarding/features/components/Button/StartButton.tsx
new file mode 100644
index 00000000..912e82c0
--- /dev/null
+++ b/packages/web-domains/src/onboarding/features/components/Button/StartButton.tsx
@@ -0,0 +1,40 @@
+'use client';
+
+import { Button } from '@sds/components';
+import { colors } from '@sds/theme';
+import { useRouter } from 'next/navigation';
+
+import { useOnBoardingCompleteMutation } from '@/onboarding/common/apis/mutations/useOnBoardingMutation';
+
+interface StartButtonProps {
+ redirectUrl?: string;
+}
+
+export const StartButton = (props: StartButtonProps) => {
+ const { redirectUrl } = props;
+ const router = useRouter();
+ const { mutateAsync: onBoardingComplete } = useOnBoardingCompleteMutation();
+
+ const handleOnBoardingComplete = async () => {
+ if (redirectUrl) {
+ router.push(redirectUrl);
+ }
+
+ const data = await onBoardingComplete();
+
+ // 만약 가입된 모임이 없다면
+ if (data?.isNotEnteredAnyMeeting) {
+ router.push('/user');
+ }
+ // 만약 가입된 모임이 있다면
+ else {
+ router.push('/home');
+ }
+ };
+
+ return (
+
+ );
+};
diff --git a/packages/web-domains/src/onboarding/features/components/Header/Header.tsx b/packages/web-domains/src/onboarding/features/components/Header/Header.tsx
new file mode 100644
index 00000000..fe22266b
--- /dev/null
+++ b/packages/web-domains/src/onboarding/features/components/Header/Header.tsx
@@ -0,0 +1,35 @@
+'use client';
+
+import { Txt } from '@sds/components';
+import { colors, size } from '@sds/theme';
+import { ReactNode } from 'react';
+
+interface HeaderProps {
+ title: string;
+ subTitle: string;
+ Icon: ReactNode | (() => JSX.Element);
+}
+export const Header = (props: HeaderProps) => {
+ const { title, subTitle, Icon } = props;
+ return (
+
+ {typeof Icon === 'function' ? Icon() : Icon}
+
+ {title}
+ {subTitle}
+
+
+ );
+};
diff --git a/packages/web-domains/src/onboarding/features/components/Layout/OnBoardingLayout.tsx b/packages/web-domains/src/onboarding/features/components/Layout/OnBoardingLayout.tsx
new file mode 100644
index 00000000..6c102ee5
--- /dev/null
+++ b/packages/web-domains/src/onboarding/features/components/Layout/OnBoardingLayout.tsx
@@ -0,0 +1,36 @@
+import { colors } from '@sds/theme';
+import { CSSProperties, PropsWithChildren } from 'react';
+
+import { STEPS, StepType } from '@/onboarding/common/constants/steps';
+
+interface OnBoardingLayoutProps {
+ step: StepType;
+}
+
+export const OnBoardingLayout = ({ children, step }: PropsWithChildren) => {
+ const backgroundColor = layoutStyles[step] || 'none';
+ return (
+
+ {children}
+
+ );
+};
+
+const layoutStyle: CSSProperties = {
+ position: 'relative',
+ height: '100dvh',
+ display: 'flex',
+ flexDirection: 'column',
+};
+
+const layoutStyles: Record = {
+ [STEPS.QUESTION]: colors.primary300,
+ [STEPS.RESULT]: colors.tertiary300,
+ [STEPS.FRIENDSHIP]: colors.secondary400,
+ [STEPS.ABOUT_ME]: colors.quaternary300,
+};
diff --git a/packages/web-domains/src/onboarding/features/components/OnBoardingContent/OnBoardingContent.tsx b/packages/web-domains/src/onboarding/features/components/OnBoardingContent/OnBoardingContent.tsx
new file mode 100644
index 00000000..ee29469d
--- /dev/null
+++ b/packages/web-domains/src/onboarding/features/components/OnBoardingContent/OnBoardingContent.tsx
@@ -0,0 +1,39 @@
+import Image, { StaticImageData } from 'next/image';
+
+import { BackGroundImage } from '../BackGroundImage/BackGroundImage';
+
+interface OnBoardingContentProps {
+ imageUrl?: string | StaticImageData;
+ bgImageUrl?: string | StaticImageData;
+}
+export const OnBoardingContent = (props: OnBoardingContentProps) => {
+ const { imageUrl, bgImageUrl } = props;
+
+ if (!imageUrl) {
+ return null;
+ }
+
+ return (
+
+ );
+};
diff --git a/packages/web-domains/src/onboarding/features/components/ProgressIndicator/index.tsx b/packages/web-domains/src/onboarding/features/components/ProgressIndicator/index.tsx
new file mode 100644
index 00000000..eb7310d9
--- /dev/null
+++ b/packages/web-domains/src/onboarding/features/components/ProgressIndicator/index.tsx
@@ -0,0 +1,45 @@
+import { colors } from '@sds/theme';
+import { HTMLAttributes, PropsWithChildren, forwardRef } from 'react';
+
+import { progressIndicatorCss, stepCss } from './styles';
+
+export interface ProgressIndicatorProps extends Omit, 'style'> {
+ mode?: 'horizontal' | 'vertical';
+ totalStep: number;
+ currentStep: number;
+}
+
+export const ProgressIndicator = forwardRef>(
+ ({ mode = 'horizontal', totalStep, currentStep, children, ...rest }, ref) => {
+ const flexDirection = mode === 'horizontal' ? 'row' : 'column';
+ const currentStepIndex = Math.min(totalStep, currentStep) - 1;
+ const basis = 100 / totalStep;
+
+ return (
+
+ {Array.from({ length: totalStep }).map((_, index) => (
+
+ ))}
+ {children}
+
+ );
+ },
+) as React.ForwardRefExoticComponent & { Step: typeof Step };
+
+export interface StepProps extends HTMLAttributes {
+ basis: number;
+ isCurrent: boolean;
+}
+
+const Step = ({ isCurrent, basis, ...rest }: StepProps) => {
+ let color = '#FFFFFF4D';
+
+ if (isCurrent) {
+ color = colors.white;
+ }
+
+ return ;
+};
+
+ProgressIndicator.displayName = 'ProgressIndicator';
+ProgressIndicator.Step = Step;
diff --git a/packages/web-domains/src/onboarding/features/components/ProgressIndicator/styles.ts b/packages/web-domains/src/onboarding/features/components/ProgressIndicator/styles.ts
new file mode 100644
index 00000000..42b7957f
--- /dev/null
+++ b/packages/web-domains/src/onboarding/features/components/ProgressIndicator/styles.ts
@@ -0,0 +1,16 @@
+import { css } from '@emotion/react';
+
+import { ProgressIndicatorCssArgs, StepCssArgs } from './type';
+
+export const progressIndicatorCss = ({ flexDirection }: ProgressIndicatorCssArgs) =>
+ css({ width: '100%', display: 'flex', flexWrap: 'nowrap', flexDirection });
+
+export const stepCss = ({ basis, color }: StepCssArgs) =>
+ css({
+ boxSizing: 'border-box',
+ flex: `${basis}% 1 1`,
+ height: '4px',
+ backgroundColor: color,
+ borderRadius: '8px',
+ margin: '2px',
+ });
diff --git a/packages/web-domains/src/onboarding/features/components/ProgressIndicator/type.ts b/packages/web-domains/src/onboarding/features/components/ProgressIndicator/type.ts
new file mode 100644
index 00000000..3c610f8b
--- /dev/null
+++ b/packages/web-domains/src/onboarding/features/components/ProgressIndicator/type.ts
@@ -0,0 +1,8 @@
+export type ProgressIndicatorCssArgs = {
+ flexDirection: 'row' | 'column';
+};
+
+export type StepCssArgs = {
+ color: string;
+ basis: number;
+};
diff --git a/packages/web-domains/src/onboarding/features/containers/FloatingButtonContainer/FloatingButtonContainer.tsx b/packages/web-domains/src/onboarding/features/containers/FloatingButtonContainer/FloatingButtonContainer.tsx
new file mode 100644
index 00000000..08365aa5
--- /dev/null
+++ b/packages/web-domains/src/onboarding/features/containers/FloatingButtonContainer/FloatingButtonContainer.tsx
@@ -0,0 +1,39 @@
+'use client';
+
+import { css } from '@emotion/react';
+
+import { STEPS, StepType } from '@/onboarding/common/constants/steps';
+
+import { NextButton } from '../../components/Button/NextButton';
+import { StartButton } from '../../components/Button/StartButton';
+
+interface FloatingButtonContainerProps {
+ step: StepType;
+ redirectUrl?: string;
+}
+
+export const FloatingButtonContainer = (props: FloatingButtonContainerProps) => {
+ const { step, redirectUrl } = props;
+
+ return (
+
+ {step === STEPS.ABOUT_ME ? : }
+
+ );
+};
+
+const backGroundStyles: Record = {
+ [STEPS.QUESTION]: 'linear-gradient(180deg, rgba(255, 187, 162, 0.00) 0%, #FFBBA2 100%)',
+ [STEPS.RESULT]: 'linear-gradient(180deg, rgba(194, 164, 252, 0.00)0%, #C2A4FC 100%)',
+ [STEPS.FRIENDSHIP]: 'linear-gradient(180deg, rgba(201, 243, 60, 0.00)0%, #C9F33C 100%)',
+ [STEPS.ABOUT_ME]: 'linear-gradient(180deg, rgba(254, 237, 155, 0.00)0%, #FEED9B 100%)',
+};
+
+const buttonWrapperCss = css({
+ position: 'fixed',
+ zIndex: '10',
+ bottom: '0',
+ width: '100%',
+ maxWidth: '600px',
+ padding: '20px 106px 40px',
+});
diff --git a/packages/web-domains/src/onboarding/features/containers/OnBoardingContainer/OnBoardingAboutMeContainer.tsx b/packages/web-domains/src/onboarding/features/containers/OnBoardingContainer/OnBoardingAboutMeContainer.tsx
new file mode 100644
index 00000000..6c4438ea
--- /dev/null
+++ b/packages/web-domains/src/onboarding/features/containers/OnBoardingContainer/OnBoardingAboutMeContainer.tsx
@@ -0,0 +1,19 @@
+'use client';
+
+import { Fragment } from 'react/jsx-runtime';
+
+import { HumanIcon } from '@/onboarding/common/assets/icons/HumanIcon';
+import BgImage from '@/onboarding/common/assets/images/bg-about-me.png';
+import OnBoardingImage from '@/onboarding/common/assets/images/onboarding-about-me.png';
+
+import { Header } from '../../components/Header/Header';
+import { OnBoardingContent } from '../../components/OnBoardingContent/OnBoardingContent';
+
+export const OnBoardingAboutMeContainer = () => {
+ return (
+
+ } title="릴레이 질문으로" subTitle="나만의 자기소개서 완성!" />
+
+
+ );
+};
diff --git a/packages/web-domains/src/onboarding/features/containers/OnBoardingContainer/OnBoardingContainer.tsx b/packages/web-domains/src/onboarding/features/containers/OnBoardingContainer/OnBoardingContainer.tsx
new file mode 100644
index 00000000..43f88e55
--- /dev/null
+++ b/packages/web-domains/src/onboarding/features/containers/OnBoardingContainer/OnBoardingContainer.tsx
@@ -0,0 +1,29 @@
+import { notFound } from 'next/navigation';
+
+import { STEPS, StepType } from '@/onboarding/common/constants/steps';
+
+import { OnBoardingAboutMeContainer } from './OnBoardingAboutMeContainer';
+import { OnBoardingFriendShipContainer } from './OnBoardingFriendShipContainer';
+import { OnBoardingQuestionContainer } from './OnBoardingQuestionContainer';
+import { OnBoardingResultContainer } from './onBoardingResultContainer';
+
+interface OnBoardingContainerProps {
+ step: StepType;
+}
+
+export const OnBoardingContainer = (props: OnBoardingContainerProps) => {
+ const { step } = props;
+
+ switch (step) {
+ case STEPS.QUESTION:
+ return ;
+ case STEPS.RESULT:
+ return ;
+ case STEPS.FRIENDSHIP:
+ return ;
+ case STEPS.ABOUT_ME:
+ return ;
+ default:
+ return notFound();
+ }
+};
diff --git a/packages/web-domains/src/onboarding/features/containers/OnBoardingContainer/OnBoardingFriendShipContainer.tsx b/packages/web-domains/src/onboarding/features/containers/OnBoardingContainer/OnBoardingFriendShipContainer.tsx
new file mode 100644
index 00000000..eb815398
--- /dev/null
+++ b/packages/web-domains/src/onboarding/features/containers/OnBoardingContainer/OnBoardingFriendShipContainer.tsx
@@ -0,0 +1,19 @@
+'use client';
+
+import { Fragment } from 'react/jsx-runtime';
+
+import { HandIcon } from '@/onboarding/common/assets/icons/HandIcon';
+import BgImage from '@/onboarding/common/assets/images/bg-friendship.png';
+import OnBoardingImage from '@/onboarding/common/assets/images/onboarding-friendship.png';
+
+import { Header } from '../../components/Header/Header';
+import { OnBoardingContent } from '../../components/OnBoardingContent/OnBoardingContent';
+
+export const OnBoardingFriendShipContainer = () => {
+ return (
+
+ } title="릴레이 질문으로" subTitle="나만의 자기소개서 완성!" />
+
+
+ );
+};
diff --git a/packages/web-domains/src/onboarding/features/containers/OnBoardingContainer/OnBoardingQuestionContainer.tsx b/packages/web-domains/src/onboarding/features/containers/OnBoardingContainer/OnBoardingQuestionContainer.tsx
new file mode 100644
index 00000000..030786c4
--- /dev/null
+++ b/packages/web-domains/src/onboarding/features/containers/OnBoardingContainer/OnBoardingQuestionContainer.tsx
@@ -0,0 +1,18 @@
+'use client';
+import { Fragment } from 'react/jsx-runtime';
+
+import { QuestionIcon } from '@/onboarding/common/assets/icons/QuestionIcon';
+import BgImage from '@/onboarding/common/assets/images/bg-question.png';
+import OnBoardingImage from '@/onboarding/common/assets/images/onboarding-question.png';
+
+import { Header } from '../../components/Header/Header';
+import { OnBoardingContent } from '../../components/OnBoardingContent/OnBoardingContent';
+
+export const OnBoardingQuestionContainer = () => {
+ return (
+
+ } title="모임원들에게" subTitle="궁금한 것을 물어보면" />
+
+
+ );
+};
diff --git a/packages/web-domains/src/onboarding/features/containers/OnBoardingContainer/onBoardingResultContainer.tsx b/packages/web-domains/src/onboarding/features/containers/OnBoardingContainer/onBoardingResultContainer.tsx
new file mode 100644
index 00000000..22d9420e
--- /dev/null
+++ b/packages/web-domains/src/onboarding/features/containers/OnBoardingContainer/onBoardingResultContainer.tsx
@@ -0,0 +1,23 @@
+'use client';
+import { colors } from '@sds/theme';
+import { Fragment } from 'react/jsx-runtime';
+
+import { QuestionIcon } from '@/onboarding/common/assets/icons/QuestionIcon';
+import BgImage from '@/onboarding/common/assets/images/bg-result.png';
+import OnBoardingImage from '@/onboarding/common/assets/images/onboarding-result.png';
+
+import { Header } from '../../components/Header/Header';
+import { OnBoardingContent } from '../../components/OnBoardingContent/OnBoardingContent';
+
+export const OnBoardingResultContainer = () => {
+ return (
+
+ }
+ title="우리 모임원들이"
+ subTitle="어떤 답변을 했는지 볼 수 있고"
+ />
+
+
+ );
+};
diff --git a/packages/web-domains/src/onboarding/features/containers/ProgressIndicatorContainer/ProgressIndicatorContainer.tsx b/packages/web-domains/src/onboarding/features/containers/ProgressIndicatorContainer/ProgressIndicatorContainer.tsx
new file mode 100644
index 00000000..0e03a627
--- /dev/null
+++ b/packages/web-domains/src/onboarding/features/containers/ProgressIndicatorContainer/ProgressIndicatorContainer.tsx
@@ -0,0 +1,30 @@
+'use client';
+
+import { size } from '@sds/theme';
+
+import { STEPS, StepType } from '@/onboarding/common/constants/steps';
+
+import { ProgressIndicator } from '../../components/ProgressIndicator';
+
+interface ProgressIndicatorContainerProps {
+ step: StepType;
+}
+
+const stepToCurrentStepMap: Record = {
+ [STEPS.QUESTION]: 1,
+ [STEPS.RESULT]: 2,
+ [STEPS.FRIENDSHIP]: 3,
+ [STEPS.ABOUT_ME]: 4,
+};
+
+export const ProgressIndicatorContainer = (props: ProgressIndicatorContainerProps) => {
+ const { step } = props;
+
+ const currentStep = stepToCurrentStepMap[step] || 1;
+
+ return (
+
+ );
+};
diff --git a/packages/web-domains/src/onboarding/image.d.ts b/packages/web-domains/src/onboarding/image.d.ts
new file mode 100644
index 00000000..a666151b
--- /dev/null
+++ b/packages/web-domains/src/onboarding/image.d.ts
@@ -0,0 +1,4 @@
+declare module '*.png';
+declare module '*.svg';
+declare module '*.jpeg';
+declare module '*.jpg';
diff --git a/packages/web-domains/src/onboarding/index.ts b/packages/web-domains/src/onboarding/index.ts
new file mode 100644
index 00000000..1a8efe72
--- /dev/null
+++ b/packages/web-domains/src/onboarding/index.ts
@@ -0,0 +1,2 @@
+export { OnBoardingScreen } from './screens/OnBoardingScreen';
+export type { StepType } from './common/constants/steps';
diff --git a/packages/web-domains/src/onboarding/screens/OnBoardingScreen.tsx b/packages/web-domains/src/onboarding/screens/OnBoardingScreen.tsx
new file mode 100644
index 00000000..3427017a
--- /dev/null
+++ b/packages/web-domains/src/onboarding/screens/OnBoardingScreen.tsx
@@ -0,0 +1,34 @@
+import { cookies } from 'next/headers';
+import { redirect } from 'next/navigation';
+import { Suspense } from 'react';
+
+import { STEPS, StepType } from '../common/constants/steps';
+import { OnBoardingLayout } from '../features/components/Layout/OnBoardingLayout';
+import { FloatingButtonContainer } from '../features/containers/FloatingButtonContainer/FloatingButtonContainer';
+import { OnBoardingContainer } from '../features/containers/OnBoardingContainer/OnBoardingContainer';
+import { ProgressIndicatorContainer } from '../features/containers/ProgressIndicatorContainer/ProgressIndicatorContainer';
+
+interface OnBoardingScreenProps {
+ step: StepType;
+}
+
+const STEPS_VALUES = Object.values(STEPS);
+
+export const OnBoardingScreen = ({ step }: OnBoardingScreenProps) => {
+ if (!STEPS_VALUES.includes(step)) {
+ redirect(`/onboarding/?step=${STEPS.QUESTION}`);
+ }
+
+ const cookieStore = cookies();
+ const redirectUrl = cookieStore.get('client_redirect_url')?.value ?? '';
+
+ return (
+
+
+
+
+
+
+
+ );
+};
diff --git a/packages/web-domains/src/relay-question/features/select-relay-question/containers/RandomPickContainer/RandomPickContainer.tsx b/packages/web-domains/src/relay-question/features/select-relay-question/containers/RandomPickContainer/RandomPickContainer.tsx
index f4b6b1cb..bf1f00a2 100644
--- a/packages/web-domains/src/relay-question/features/select-relay-question/containers/RandomPickContainer/RandomPickContainer.tsx
+++ b/packages/web-domains/src/relay-question/features/select-relay-question/containers/RandomPickContainer/RandomPickContainer.tsx
@@ -6,12 +6,12 @@ import { size } from '@sambad/sds/theme';
import { useParams, useRouter, useSearchParams } from 'next/navigation';
import { useState } from 'react';
+import { ToolTip } from '../../../../../common/components/ToolTip/ToolTip';
import { RelayRandomButtonDocumentIcon } from '../../../../assets/RelayRandomButtonIcon';
import { Modal } from '../../../../common/Modal';
import { FIRST_STEP } from '../../../../constants';
import { QuestionDetail } from '../../components/QuestionDetail/QuestionDetail';
import { QuestionerDetail } from '../../components/QuestionerDetail/QuestionerDetail';
-import { ToolTip } from '../../components/ToolTip/ToolTip';
import { useQueryStringContext } from '../../contexts/QueryStringContext';
import { usePostRelayQuestionInfo } from '../../hooks/mutations/usePostRelayQuestionInfo';
import { useMemberMeQuery } from '../../hooks/queries/useMemberMeQuery';
diff --git a/packages/web-domains/src/user/common/api/mutations/useModifyUserInfo.ts b/packages/web-domains/src/user/common/api/mutations/useModifyUserInfo.ts
new file mode 100644
index 00000000..02a5ebea
--- /dev/null
+++ b/packages/web-domains/src/user/common/api/mutations/useModifyUserInfo.ts
@@ -0,0 +1,46 @@
+import { UseMutationOptions, useMutation } from '@tanstack/react-query';
+import { AxiosError } from 'axios';
+
+import { Http } from '@/common/apis/base.api';
+import { MbtiType } from '@/user/common/constants/mbti';
+
+import { MeetingMemberResponse } from '../schema/MeetingMemberResponse';
+
+export type Params = {
+ meetingId: number;
+ name: string;
+ gender: 'FEMALE' | 'MALE';
+ birth: string;
+ job: string;
+ location: string;
+ hobbyIds: number[];
+ mbti?: MbtiType;
+ introduction?: string;
+ // Todo: 삭제
+ role: string;
+};
+
+interface ErrorModifyUserInfoResponse {
+ code: string;
+ message: string;
+}
+
+interface Args {
+ options?: UseMutationOptions, Params>;
+}
+
+export const useModifyUserInfo = ({ options }: Args) => {
+ return useMutation({
+ mutationFn: async (params: Params) => {
+ return await modifyUserInfo(params);
+ },
+ ...options,
+ });
+};
+
+async function modifyUserInfo(params: Params): Promise {
+ const { meetingId, ...restParams } = params;
+ console.log(restParams);
+ const data = await Http.PATCH(`/v1/meetings/${meetingId}/members/me`, restParams);
+ return data;
+}
diff --git a/packages/web-domains/src/user/common/api/schema/MeetingMemberResponse.ts b/packages/web-domains/src/user/common/api/schema/MeetingMemberResponse.ts
new file mode 100644
index 00000000..908f7fbd
--- /dev/null
+++ b/packages/web-domains/src/user/common/api/schema/MeetingMemberResponse.ts
@@ -0,0 +1,4 @@
+export interface MeetingMemberResponse {
+ meetingId: number;
+ meetingMemberId: number;
+}
diff --git a/packages/web-domains/src/user/features/get-user-info/containers/GetUserBasicInfoContainer.tsx b/packages/web-domains/src/user/features/get-user-info/containers/GetUserBasicInfoContainer.tsx
index 2e150efd..1d026ee0 100644
--- a/packages/web-domains/src/user/features/get-user-info/containers/GetUserBasicInfoContainer.tsx
+++ b/packages/web-domains/src/user/features/get-user-info/containers/GetUserBasicInfoContainer.tsx
@@ -1,16 +1,9 @@
import { FormTitle, BasicInfoForm } from '../components/Form';
-import { useGetRoleName } from '../hooks/useGetRoleName';
export const GetUserBasicInfoContainer = () => {
- const role = useGetRoleName();
return (
<>
-
+
>
);
diff --git a/packages/web-domains/src/user/features/get-user-info/containers/GetUserExtraInfoContainer.tsx b/packages/web-domains/src/user/features/get-user-info/containers/GetUserExtraInfoContainer.tsx
index f654e73d..0c7a7d1b 100644
--- a/packages/web-domains/src/user/features/get-user-info/containers/GetUserExtraInfoContainer.tsx
+++ b/packages/web-domains/src/user/features/get-user-info/containers/GetUserExtraInfoContainer.tsx
@@ -1,17 +1,9 @@
import { FormTitle, ExtraInfoForm } from '../components/Form';
-import { useGetRoleName } from '../hooks/useGetRoleName';
export const GetUserExtraInfoContainer = () => {
- const role = useGetRoleName();
-
return (
<>
-
+
>
);
diff --git a/packages/web-domains/src/user/features/modify-user-info/components/ModifyIntroForm.tsx b/packages/web-domains/src/user/features/modify-user-info/components/ModifyIntroForm.tsx
new file mode 100644
index 00000000..212fdc5d
--- /dev/null
+++ b/packages/web-domains/src/user/features/modify-user-info/components/ModifyIntroForm.tsx
@@ -0,0 +1,41 @@
+import { Txt, Button } from '@sds/components';
+import { colors } from '@sds/theme';
+import { useForm } from 'react-hook-form';
+
+import { buttonWrapperCss } from '../../get-user-info/components/Form/styles';
+import { TextArea } from '../../get-user-info/components/TextInput/TextArea';
+import { useModifyUserInfoService } from '../services/useModifyUserInfoService';
+
+export interface IntroFormType {
+ introduction: string;
+}
+
+const MAX_LENGTH = 3000;
+
+export const ModifyIntroForm = () => {
+ const {
+ register,
+ formState: { isValid },
+ handleSubmit,
+ } = useForm({ defaultValues: { introduction: '' } });
+
+ const { handleModifyUserInfo } = useModifyUserInfoService();
+
+ return (
+
+ );
+};
diff --git a/packages/web-domains/src/user/features/modify-user-info/containers/ModifyUserInfoContainer.tsx b/packages/web-domains/src/user/features/modify-user-info/containers/ModifyUserInfoContainer.tsx
new file mode 100644
index 00000000..d93fd901
--- /dev/null
+++ b/packages/web-domains/src/user/features/modify-user-info/containers/ModifyUserInfoContainer.tsx
@@ -0,0 +1,36 @@
+'use client';
+
+import { notFound, redirect, useSearchParams } from 'next/navigation';
+
+import { STEPS } from '@/user/common/constants/step';
+
+import { GetUserBasicInfoContainer } from '../../get-user-info/containers/GetUserBasicInfoContainer';
+import { GetUserExtraInfoContainer } from '../../get-user-info/containers/GetUserExtraInfoContainer';
+import { GetUserHobbiesContainer } from '../../get-user-info/containers/GetUserHobbiesContainer';
+import { GetUserMbtiContainer } from '../../get-user-info/containers/GetUserMbtiContainer';
+
+import { ModifyUserIntroContainer } from './ModifyUserIntroContainer';
+
+export const ModifyUserInfoContainer = () => {
+ const searchParams = useSearchParams();
+ const step = searchParams.get('step');
+
+ if (!step) {
+ redirect(`?step=${STEPS.BASIC_INFO}`);
+ }
+
+ switch (step) {
+ case STEPS.BASIC_INFO:
+ return ;
+ case STEPS.EXTRA_INFO:
+ return ;
+ case STEPS.HOBBIES_INFO:
+ return ;
+ case STEPS.MBTI_IFNO:
+ return ;
+ case STEPS.INTRO_INFO:
+ return ;
+ default:
+ notFound();
+ }
+};
diff --git a/packages/web-domains/src/user/features/modify-user-info/containers/ModifyUserIntroContainer.tsx b/packages/web-domains/src/user/features/modify-user-info/containers/ModifyUserIntroContainer.tsx
new file mode 100644
index 00000000..8772df3f
--- /dev/null
+++ b/packages/web-domains/src/user/features/modify-user-info/containers/ModifyUserIntroContainer.tsx
@@ -0,0 +1,11 @@
+import { FormTitle } from '../../get-user-info/components/Form';
+import { ModifyIntroForm } from '../components/ModifyIntroForm';
+
+export const ModifyUserIntroContainer = () => {
+ return (
+ <>
+
+
+ >
+ );
+};
diff --git a/packages/web-domains/src/user/features/modify-user-info/services/useModifyUserInfoService.ts b/packages/web-domains/src/user/features/modify-user-info/services/useModifyUserInfoService.ts
new file mode 100644
index 00000000..b0d1a736
--- /dev/null
+++ b/packages/web-domains/src/user/features/modify-user-info/services/useModifyUserInfoService.ts
@@ -0,0 +1,51 @@
+import { useQueryClient } from '@tanstack/react-query';
+import { useParams, useRouter } from 'next/navigation';
+
+import { MEMBER_ME_QUERY_KEY } from '@/about-me/common/apis/queries/useGetMemberMe';
+import { Params as ModifyUserInfoParams, useModifyUserInfo } from '@/user/common/api/mutations/useModifyUserInfo';
+import { useQueryString } from '@/user/common/hooks/useQueryString';
+import { dateFormat } from '@/user/common/utils/format';
+
+export const useModifyUserInfoService = () => {
+ const router = useRouter();
+ const queryClient = useQueryClient();
+ const { meetingId } = useParams();
+ const { getQueryStringObj } = useQueryString();
+ const queryStringObj = getQueryStringObj();
+
+ const { mutate: modifyUserInfo } = useModifyUserInfo({
+ options: {
+ onSuccess: (_, varables) => {
+ queryClient.invalidateQueries({ queryKey: [MEMBER_ME_QUERY_KEY, varables.meetingId] });
+ router.push('/home/me');
+ },
+ onError: (error) => {
+ console.log(error);
+ },
+ },
+ });
+
+ const handleModifyUserInfo = async ({ introduction }: { introduction?: string }) => {
+ if (!meetingId) {
+ return;
+ }
+
+ const params = {
+ ...queryStringObj,
+ meetingId: Number(meetingId),
+ name: queryStringObj.userName,
+ hobbyIds: queryStringObj.hobbyIds ? queryStringObj.hobbyIds.split(',').map(Number) : [],
+ birth: dateFormat(queryStringObj.birth),
+ // Todo: 삭제
+ role: 'OWNER',
+ } as ModifyUserInfoParams;
+
+ if (introduction) {
+ params.introduction = introduction.trim();
+ }
+
+ modifyUserInfo(params);
+ };
+
+ return { handleModifyUserInfo };
+};
diff --git a/packages/web-domains/src/user/index.ts b/packages/web-domains/src/user/index.ts
index 57e4afe6..8d0ad49c 100644
--- a/packages/web-domains/src/user/index.ts
+++ b/packages/web-domains/src/user/index.ts
@@ -3,3 +3,4 @@ export { GetUserInfoScreen } from './screens/GetUserInfoScreen';
export { SelectUserRoleScreen } from './screens/SelectUserRoleScreen';
export { MemberClosingScreen } from './screens/MemberClosingScreen';
export { GetOwnerInfoStartScreen } from './screens/GetOwnerInfoStartScreen';
+export { ModifyUserInfoScreen } from './screens/ModifyUserInfoScreen';
diff --git a/packages/web-domains/src/user/screens/ModifyUserInfoScreen.tsx b/packages/web-domains/src/user/screens/ModifyUserInfoScreen.tsx
new file mode 100644
index 00000000..3faac54e
--- /dev/null
+++ b/packages/web-domains/src/user/screens/ModifyUserInfoScreen.tsx
@@ -0,0 +1,25 @@
+import { HydrationBoundary, QueryClient, dehydrate } from '@tanstack/react-query';
+import { Suspense } from 'react';
+
+import { ModifyUserInfoContainer } from '../features/modify-user-info/containers/ModifyUserInfoContainer';
+// import { getHobbyListPrefetch } from '@/common/apis/queries/useGetHobbyList';
+
+export const ModifyUserInfoScreen = async () => {
+ const { queryClient } = await getServerSideProps();
+
+ return (
+
+
+
+
+
+ );
+};
+
+const getServerSideProps = async () => {
+ const queryClient = new QueryClient();
+
+ // await getHobbyListPrefetch(queryClient);
+
+ return { queryClient };
+};