Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
3965a01
feat: input disable μƒνƒœ μ‹œ μ»€μŠ€ν…€
Mimiminz Sep 27, 2025
71a4f5a
feat: 단일 선택 μ˜΅μ…˜ μΆ”κ°€
Mimiminz Sep 27, 2025
33dc7c3
feat: κ·Έλ£Ή μŠ€ν„°λ”” μŠ€ν‚€λ§ˆ μΆ”κ°€ (μˆ˜μ •ν•„μš”)
Mimiminz Sep 27, 2025
881e717
feat: κ·Έλ£Ή μŠ€ν„°λ”” 생성 const κ°’ μΆ”κ°€
Mimiminz Sep 27, 2025
116fcf7
feat: κ·Έλ£Ή μŠ€ν„°λ”” request νƒ€μž… μΆ”κ°€ (μˆ˜μ • ν•„μš”)
Mimiminz Sep 27, 2025
2093d75
fix: κ²½λ ₯ const λ³€κ²½
Mimiminz Sep 27, 2025
5276026
feat: μŠ€ν… 1 μΆ”κ°€
Mimiminz Sep 27, 2025
4e52907
feat: μŠ€ν… 2 μΆ”κ°€
Mimiminz Sep 27, 2025
4315d08
fix: κ²½λ ₯ 인풋/아웃풋 λ‹€λ₯΄κ²Œ λ³€κ²½
Mimiminz Sep 27, 2025
6376a90
feat: μŠ€ν… 3 μΆ”κ°€
Mimiminz Sep 27, 2025
c5b14f3
feat: 폰트 크기 νƒ€μž… μΆ”κ°€
Mimiminz Sep 27, 2025
c285185
fix: group 단일/볡수 μΆ”κ°€ 및 λ‹€λ₯Έ νƒ€μž… ν™•μž₯ κ°€λŠ₯μ„± μΆ”κ°€
Mimiminz Sep 27, 2025
181ad38
feat: μŠ€ν… 별 κ·Έλ£Ή μŠ€ν„°λ”” 생성 λͺ¨λ‹¬ μΆ”κ°€
Mimiminz Sep 27, 2025
396dfbc
style: ν…ŒμŠ€νŠΈλ₯Ό μœ„ν•œ μ½”λ“œ μΆ”κ°€
Mimiminz Sep 27, 2025
4abec73
style: 넀이밍 λ³€κ²½
Mimiminz Oct 2, 2025
83c4686
Merge branch 'develop' of https://github.com/code-zero-to-one/study-p…
Mimiminz Oct 2, 2025
dec9942
style: 넀이밍 λ³€κ²½
Mimiminz Oct 2, 2025
da6e68d
feat: step1 UI μˆ˜μ •
Mimiminz Oct 2, 2025
f17bc71
feat: μŠ€ν„°λ”” κ°œμ„€ api에 맞게 μ—…λ°μ΄νŠΈ
Mimiminz Oct 2, 2025
80b6477
fix: next 쑰건 μˆ˜μ •
Mimiminz Oct 2, 2025
06d3ac4
style: 쀑간 μ—…λ°μ΄νŠΈμš© 컀밋
Mimiminz Oct 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/features/my-page/ui/profile-info-edit-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { MultiDropdown, SingleDropdown } from '@/shared/ui/dropdown';
import FormField from '@/shared/ui/form/form-field';
import { TextAreaInput } from '@/shared/ui/input';
import { Modal } from '@/shared/ui/modal';
import { ToggleGroup } from '@/shared/ui/toggle';
import { GroupItems } from '@/shared/ui/toggle';

import {
ProfileInfoFormSchema,
Expand Down Expand Up @@ -210,7 +210,7 @@ function ProfileInfoEditForm({
direction="vertical"
required
>
<ToggleGroup options={timeOptions} />
<GroupItems options={timeOptions} />
</FormField>

<FormField<ProfileInfoFormValues, 'techStackIds', string[]>
Expand Down
52 changes: 52 additions & 0 deletions src/features/study/group/api/group-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
export interface BasicInfo {
groupStudyId: number;
type: string;
targetRoles: string[];
maxMembersCount: number;
experienceLevels: string[];
method: 'ONLINE' | 'OFFLINE' | 'HYBRID';
regularMeeting: 'NONE' | 'WEEKLY' | 'BIWEEKLY' | 'TRIPLE_WEEKLY_OR_MORE';
location: string;
startDate: string;
endDate: string;
price: number;
status: 'RECRUITING' | 'IN_PROGRESS' | 'COMPLETED';
createdAt: string;
updatedAt: string;
}

export interface DetailInfo {
thumbnail: {
imageId: number;
resizedImages: {
resizedImageId: number;
resizedImageUrl: string;
imageSizeType: {
imageTypeName: string;
width: number | undefined;
height: number | undefined;
};
}[];
};
title: string;
description: string;
summary: string;
}

export interface InterviewPost {
interviewPost: string;
}

export interface OpenGroupRequest {
basicInfo: BasicInfo;
detailInfo: DetailInfo;
interviewPost: InterviewPost;
thumbnailExtension:
| 'DEFAULT'
| 'JPG'
| 'PNG'
| 'GIF'
| 'WEBP'
| 'SVG'
| 'JPEG';
}
26 changes: 26 additions & 0 deletions src/features/study/group/const/group-const.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export const STUDY_TYPES = [
'ν”„λ‘œμ νŠΈ',
'λ©˜ν† λ§',
'μ„Έλ―Έλ‚˜',
'μ±Œλ¦°μ§€',
'μ±… μŠ€ν„°λ””',
'κ°•μ˜ μŠ€ν„°λ””',
] as const;

export const EXPERIENCE_LEVEL_OPTIONS = [
'μž…λ¬Έμž',
'취쀀생',
'μ£Όλ‹ˆμ–΄ (1~3λ…„μ°¨)',
'μ€‘λ‹ˆμ–΄ (4~6λ…„μ°¨)',
'μ‹œλ‹ˆμ–΄ (7λ…„μ°¨ 이상)',
'κ²½λ ₯ 무관',
] as const;

export const METHOD_OPTIONS = ['온라인', 'μ˜€ν”„λΌμΈ', 'μ˜¨μ˜€ν”„λΌμΈ'] as const;

export const MEETING_OPTIONS = [
'μ—†μŒ',
'주 1회',
'주 2회',
'μ£Ό 3회 이상',
] as const;
139 changes: 139 additions & 0 deletions src/features/study/group/model/open-group-form.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { z } from 'zod';
import { OpenGroupRequest } from '../api/group-types';
import {
METHOD_OPTIONS,
MEETING_OPTIONS,
STUDY_TYPES,
} from '../const/group-const';

const ISO_DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/;

const StudyMap: Record<
string,
| 'PROJECT'
| 'MENTORING'
| 'SEMINAR'
| 'CHALLENGE'
| 'BOOK_STUDY'
| 'LECTURE_STUDY'
> = {
ν”„λ‘œμ νŠΈ: 'PROJECT',
λ©˜ν† λ§: 'MENTORING',
μ„Έλ―Έλ‚˜: 'SEMINAR',
μ±Œλ¦°μ§€: 'CHALLENGE',
'μ±… μŠ€ν„°λ””': 'BOOK_STUDY',
'κ°•μ˜ μŠ€ν„°λ””': 'LECTURE_STUDY',
};

const MethodMap: Record<string, 'ONLINE' | 'OFFLINE' | 'HYBRID'> = {
온라인: 'ONLINE',
μ˜€ν”„λΌμΈ: 'OFFLINE',
μ˜¨μ˜€ν”„λΌμΈ: 'HYBRID',
};

const RegularMeetingMap: Record<
string,
'WEEKLY' | 'BIWEEKLY' | 'TRIPLE_WEEKLY_OR_MORE' | 'NONE'
> = {
'주 1회': 'WEEKLY',
'주 2회': 'BIWEEKLY',
'μ£Ό 3회 이상': 'TRIPLE_WEEKLY_OR_MORE',
μ—†μŒ: 'NONE',
};

export const OpenGroupFormSchema = z.object({
type: z.enum(STUDY_TYPES),
targetRole: z.array(z.string()).min(1, '역할을 1개 이상 선택해 μ£Όμ„Έμš”.'),
maxMembersCount: z
.string()
.trim()
.regex(/^[1-9]\d*$/, 'μ΅œμ†Œ 1 μ΄μƒμ˜ μ •μˆ˜λ₯Ό μž…λ ₯ν•΄ μ£Όμ„Έμš”.'),
experienceLevels: z
.array(z.string())
.min(1, 'κ²½λ ₯을 1개 이상 선택해 μ£Όμ„Έμš”.'),
method: z.enum(METHOD_OPTIONS),
location: z.string().trim(),
regularMeeting: z.enum(MEETING_OPTIONS),
startDate: z
.string()
.trim()
.regex(ISO_DATE_REGEX, 'YYYY-MM-DD ν˜•μ‹μ˜ μ‹œμž‘μΌμ„ μž…λ ₯ν•΄ μ£Όμ„Έμš”.'),
endDate: z
.string()
.trim()
.regex(ISO_DATE_REGEX, 'YYYY-MM-DD ν˜•μ‹μ˜ μ‹œμž‘μΌμ„ μž…λ ₯ν•΄ μ£Όμ„Έμš”.'),
price: z
.string()
.trim()
.regex(/^\d+$/, '가격은 0 이상 μ •μˆ˜λ‘œ μž…λ ₯ν•΄ μ£Όμ„Έμš”.'),
title: z.string().trim(),
description: z.string().trim(),
summary: z.string().trim(),
interviewPost: z.string().optional(),
thumbnailExtension: z.enum([
'DEFAULT',
'JPG',
'PNG',
'GIF',
'WEBP',
'SVG',
'JPEG',
]),
});

export type OpenGroupFormValues = z.input<typeof OpenGroupFormSchema>;
export type OpenGroupParsedValues = z.output<typeof OpenGroupFormSchema>;

export function buildOpenGroupDefaultValues(): OpenGroupFormValues {
return {
type: 'ν”„λ‘œμ νŠΈ',
targetRole: [],
maxMembersCount: '',
experienceLevels: [],
method: '온라인',
location: '',
regularMeeting: '주 1회',
startDate: '',
endDate: '',
price: '',
title: '',
description: '',
summary: '',
interviewPost: '',
thumbnailExtension: 'DEFAULT',
};
}

export function toOpenGroupRequest(v: OpenGroupParsedValues): OpenGroupRequest {
return {
basicInfo: {
groupStudyId: 0,
type: StudyMap[v.type],
targetRoles: v.targetRole,
maxMembersCount: Number(v.maxMembersCount),
experienceLevels: v.experienceLevels ?? [],
method: MethodMap[v.method],
regularMeeting: RegularMeetingMap[v.regularMeeting],
location: v.location.trim(),
startDate: v.startDate.trim(),
endDate: v.endDate.trim(),
price: Number(v.price),
status: 'RECRUITING',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
},
detailInfo: {
thumbnail: {
imageId: 0,
resizedImages: [],
},
title: '',
description: '',
summary: '',
},
interviewPost: {
interviewPost: v.interviewPost ?? '',
},
thumbnailExtension: v.thumbnailExtension,
};
}
Loading