Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
e83b512
fix: mbti dropdown으로 변경
Mimiminz Jun 26, 2025
fbcfda0
feat: 사용자 입력 태그 ui 추가
Mimiminz Jun 26, 2025
88f5df5
feat: 관심사 부분 프로필 수정 api와 연결
Mimiminz Jun 26, 2025
14a5494
fix: api 타입 변경 및 멀티 드롭다운 선택 오류 수정
Mimiminz Jun 27, 2025
8e5ab98
fix: memberId 없을 시 로그인 회원가입 버튼 추가
Mimiminz Jun 27, 2025
a494dd4
style: 회원가입 문구 개선 (QNRR-315)
Mimiminz Jun 27, 2025
24d4462
fix: file naming
Mimiminz Jun 29, 2025
23d9160
Merge pull request #70 from code-zero-to-one/QNRR-269-박경도-조민주-내-프로필-수…
Mimiminz Jun 29, 2025
cdabf20
feat: 코드 리뷰 부탁 slack 알림 자동화
aken-you Jun 29, 2025
113c741
refactor: PR 이벤트 타입 배열 포맷 수정
aken-you Jun 29, 2025
1b93f7c
feat: 코드 리뷰 slack 알림 자동화
aken-you Jun 29, 2025
8e83869
fix: 오늘의 스터디 날짜 구하는 함수 변경
Mimiminz Jun 30, 2025
2d947ac
fix: 변경된 param 이름 수정
Mimiminz Jun 30, 2025
66cf6be
fix: 주차 함수 변경
Mimiminz Jun 30, 2025
04da565
fix: console 코드 주석처리
Mimiminz Jun 30, 2025
068eb7f
fix: 금요일 처리 함수 주석 처리
Mimiminz Jun 30, 2025
cbdd958
fix: file rename
Mimiminz Jun 30, 2025
2d2d9b7
style: slack 알람 github actions 파일 이름 수정
aken-you Jun 30, 2025
e588fbd
fix: notifications 링크 제거
Mimiminz Jun 30, 2025
64a7bf5
Merge pull request #76 from code-zero-to-one/QNRR-332-조성진-조민주-요청일자에-대…
Mimiminz Jun 30, 2025
85af88b
refactor: MEMBER_INFO_MAP 디코딩하여 json 파일 생성
aken-you Jul 1, 2025
e379333
feat: 리뷰 상태에 따른 DM 내용에 변경 요청 추가
aken-you Jul 1, 2025
2e934f2
style: DM 메세지 내용 수정
aken-you Jul 1, 2025
8e9ef61
refactor: Slack DM 전송 요청하는 로직을 step으로 분리
aken-you Jul 1, 2025
4a06e4a
refactor: Slack DM 전송 요청에서 환경 변수 사용으로 코드 간소화
aken-you Jul 1, 2025
aa63368
Merge pull request #77 from code-zero-to-one/QNRR-333-유수아-코드-리뷰-알람-자동화
aken-you Jul 1, 2025
6c0e6e9
refactor: 로그인 GA 이벤트 데이터 수정
aken-you Jul 1, 2025
e611560
refactor: 회원가입 GA 이벤트 데이터 수정
aken-you Jul 1, 2025
3536167
refactor: 스터디 토글 GA 이벤트 데이터 수정
aken-you Jul 1, 2025
e75aa06
refactor: 멤버카드 GA 이벤트 데이터 수정
aken-you Jul 1, 2025
4d4a81b
style: 주석 삭제
aken-you Jul 1, 2025
7a5bbd6
refactor: 로그아웃 GA 이벤트 데이터 수정
aken-you Jul 1, 2025
95e416d
refactor: timestamp -> dl_timestamp로 변경
aken-you Jul 1, 2025
929f511
Merge pull request #78 from code-zero-to-one/QNRR-345-유수아-ga-이벤트-수정
aken-you Jul 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
70 changes: 70 additions & 0 deletions .github/workflows/notify-pr-author-on-review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: Notify PR Review to Author

on:
pull_request_review:
types: [submitted]

jobs:
notify-author:
runs-on: ubuntu-latest

steps:
- name: Extract Slack IDs and DM Text
id: extract_info
env:
MEMBER_INFO: ${{ secrets.MEMBER_INFO_MAP }}
run: |
PR_AUTHOR="${{ github.event.pull_request.user.login }}"
REVIEWER="${{ github.event.review.user.login }}"
REVIEW_STATE="${{ github.event.review.state }}"
PR_TITLE="${{ github.event.pull_request.title }}"
PR_URL="${{ github.event.pull_request.html_url }}"

# PR 작성자와 리뷰어가 같은 경우, 스킵 표시
if [ "$PR_AUTHOR" = "$REVIEWER" ]; then
echo "skip=true" >> $GITHUB_OUTPUT
exit 0
fi

echo "$MEMBER_INFO" | base64 --decode > memberMap.json
AUTHOR_SLACK_ID=$(jq -r --arg id "$PR_AUTHOR" '.[$id].slackId' memberMap.json)
REVIEWER_SLACK_ID=$(jq -r --arg id "$REVIEWER" '.[$id].slackId' memberMap.json)

if [ -z "$AUTHOR_SLACK_ID" ] || [ -z "$REVIEWER_SLACK_ID" ] || \
[ "$AUTHOR_SLACK_ID" = "null" ] || [ "$REVIEWER_SLACK_ID" = "null" ]; then
echo "skip=true" >> $GITHUB_OUTPUT
exit 0
fi

if [ "$REVIEW_STATE" = "approved" ]; then
TEXT="<@$REVIEWER_SLACK_ID>님이 \"<$PR_URL|$PR_TITLE>\" PR를 approve 하셨습니다!"
elif [ "$REVIEW_STATE" = "changes_requested" ]; then
TEXT="<@$REVIEWER_SLACK_ID>님이 \"<$PR_URL|$PR_TITLE>\"에 변경을 요청하셨습니다!"
else
TEXT="<@$REVIEWER_SLACK_ID>님이 \"<$PR_URL|$PR_TITLE>\"에 코멘트를 남기셨습니다!"
fi

echo "author_slack_id=$AUTHOR_SLACK_ID" >> $GITHUB_OUTPUT
echo "text=$TEXT" >> $GITHUB_OUTPUT

- name: Send Slack DM to PR Author
if: steps.extract_info.outputs.skip != 'true'
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
AUTHOR_SLACK_ID: ${{ steps.extract_info.outputs.author_slack_id }}
TEXT: ${{ steps.extract_info.outputs.text }}
run: |
RESPONSE=$(curl -s -X POST https://slack.com/api/chat.postMessage \
-H "Authorization: Bearer $SLACK_BOT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"channel": "'"$AUTHOR_SLACK_ID"'",
"text": "'"$TEXT"'"
}')

echo "Slack DM 전송 응답: $RESPONSE"

if ! echo "$RESPONSE" | jq -e '.ok' | grep -q true; then
echo "❌ Slack 메시지 전송 실패"
exit 1
fi
40 changes: 40 additions & 0 deletions .github/workflows/notify-slack-on-pr-opened.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Notify Slack When PR Is Opened

on:
pull_request:
types:
- opened
- reopened
branches:
- develop

jobs:
notify-slack:
runs-on: ubuntu-latest

steps:
- name: Decode memberMap.json from secret
run: |
echo "${{ secrets.MEMBER_INFO_MAP }}" | base64 --decode > memberMap.json
- name: Get PR author and reviewers
id: get_member_info
run: |
# PR 작성자 이름 추출
PR_AUTHOR_GITHUB_ID="${{ github.event.pull_request.user.login }}"
PR_AUTHOR_NAME=$(jq -r --arg id "$PR_AUTHOR_GITHUB_ID" '.[$id].name' memberMap.json)
echo "pr_author_name=$PR_AUTHOR_NAME" >> $GITHUB_OUTPUT

# PR 작성자 제외한 멤버들의 slackId를 멘션으로 모으기
MENTIONS=$(jq -r --arg author "$PR_AUTHOR_GITHUB_ID" 'to_entries | map(select(.key != $author)) | map("<@" + .value.slackId + ">") | join(" ")' memberMap.json)
echo "reviewers_mention=$MENTIONS" >> $GITHUB_OUTPUT

- name: Send Slack message
uses: slackapi/slack-github-action@v2.1.0
with:
method: chat.postMessage
token: ${{ secrets.SLACK_BOT_TOKEN }}
payload: |
channel: ${{ secrets.SLACK_FRONT_CHANNEL_ID }}
text: "리뷰하러 가기 > <${{ github.event.pull_request.html_url }}|go>\n- PR 타이틀: ${{ github.event.pull_request.title }}\n- PR 작성자: ${{ steps.get_member_info.outputs.pr_author_name }}\n- 리뷰어: ${{ steps.get_member_info.outputs.reviewers_mention }}"
unfurl_links: false
unfurl_media: false
2 changes: 1 addition & 1 deletion app/(my)/my-page/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { redirect } from 'next/navigation';
import { getUserProfile } from '@/entities/user/api/get-user-profile';
import Profile from '@/features/my-page/ui/profile';
import ProfileInfo from '@/features/my-page/ui/profileinfo';
import ProfileInfo from '@/features/my-page/ui/profile-info';
import { getLoginUserId } from '@/shared/lib/get-login-user';

export default async function MyPage() {
Expand Down
12 changes: 6 additions & 6 deletions app/redirection/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ function RedirectionContent() {
);
const isGuest = searchParams.get('is-guest');
const memberId = searchParams.get('member-id');
const authVendor = searchParams.get('auth-vendor');

setCookie('accessToken', accessToken);
setCookie('memberId', memberId);
Expand All @@ -31,19 +32,18 @@ function RedirectionContent() {
if (isGuest === 'true') {
router.push('/sign-up');
sendGTMEvent({
event: 'member_join',
timestamp: new Date().toISOString(),
event: 'custom_member_join',
dl_timestamp: new Date().toISOString(),
dl_member_id: hashValue(memberId),
});
} else {
// todo: login_method는 google 또는 kakao로 설정
router.push('/');
router.refresh();
sendGTMEvent({
event_name: 'member_login',
timestamp: new Date().toISOString(),
event: 'custom_member_login',
dl_timestamp: new Date().toISOString(),
dl_member_id: hashValue(memberId),
// dl_login_method: '',
dl_login_method: authVendor || '',
});
}
} catch (error) {
Expand Down
8 changes: 4 additions & 4 deletions src/entities/user/model/use-user-profile-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ export const usePatchAutoMatchingMutation = () => {
onSuccess: (_, variables) => {
if (variables.autoMatching) {
sendGTMEvent({
event_name: 'member_study_toggle_on',
timestamp: new Date().toISOString(),
event: 'custom_member_study_toggle_on',
dl_timestamp: new Date().toISOString(),
dl_member_id: hashValue(String(variables.memberId)),
});
} else {
sendGTMEvent({
event_name: 'member_study_toggle_off',
timestamp: new Date().toISOString(),
event: 'custom_member_study_toggle_off',
dl_timestamp: new Date().toISOString(),
dl_member_id: hashValue(String(variables.memberId)),
});
}
Expand Down
4 changes: 1 addition & 3 deletions src/features/auth/ui/sign-up-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,7 @@ export default function SignupModal({
handleImageChange={handleImageChange}
/>
<div className="font-designer-24b text-text-default mt-2 text-center">
서비스 이용을 위해
<br />
닉네임 대신 이름을 입력해주세요.
서비스 이용을 위해 이름을 입력해주세요.
</div>
<SignupNameInput
name={name}
Expand Down
2 changes: 1 addition & 1 deletion src/features/auth/ui/sign-up-name-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default function SignupNameInput({
>
{error
? '이름에는 숫자나 특수문자를 사용할 수 없습니다. 두 글자 이상 입력해주세요.'
: '닉네임이 아닌 실명을 입력해주세요. (예: 홍길동 )'}
: '신뢰 있는 매칭을 위해 실명을 사용해주세요. (예: 홍길동 )'}
</div>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion src/features/my-page/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export interface UpdateUserProfileResponse {
export interface UpdateUserProfileInfoRequest {
selfIntroduction: string;
studyPlan: string;
preferredStudySubject: string;
preferredStudySubjectId: string;
availableStudyTimeIds: number[];
techStackIds: number[];
}
Expand Down
47 changes: 37 additions & 10 deletions src/features/my-page/ui/profile-edit-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,40 @@ interface Props {
memberId: number;
}

const skillOptions = [
{ label: 'HTML/CSS', value: 'HTML/CSS' },
{ label: 'JavaScript', value: 'JavaScript' },
{ label: 'React', value: 'React' },
{ label: 'Django', value: 'Django' },
{ label: 'MySQL', value: 'MySQL' },
const DEFAULT_OPTIONS = [
{ label: '운동/헬스', value: '운동/헬스' },
{ label: '여행', value: '여행' },
{ label: '음악', value: '음악' },
{ label: '영화/드라마', value: '영화/드라마' },
{ label: '독서', value: '독서' },
{ label: '게임', value: '게임' },
{ label: '요리/맛집 탐방', value: '요리/맛집 탐방' },
{ label: '패션/뷰티', value: '패션/뷰티' },
{ label: '사진/영상', value: '사진/영상' },
{ label: '자기계발', value: '자기계발' },
];

export const MBTI_OPTIONS = [
{ label: 'ISTJ', value: 'ISTJ' },
{ label: 'ISFJ', value: 'ISFJ' },
{ label: 'INFJ', value: 'INFJ' },
{ label: 'INTJ', value: 'INTJ' },
{ label: 'ISTP', value: 'ISTP' },
{ label: 'ISFP', value: 'ISFP' },
{ label: 'INFP', value: 'INFP' },
{ label: 'INTP', value: 'INTP' },
{ label: 'ESTP', value: 'ESTP' },
{ label: 'ESFP', value: 'ESFP' },
{ label: 'ENFP', value: 'ENFP' },
{ label: 'ENTP', value: 'ENTP' },
{ label: 'ESTJ', value: 'ESTJ' },
{ label: 'ESFJ', value: 'ESFJ' },
{ label: 'ENFJ', value: 'ENFJ' },
{ label: 'ENTJ', value: 'ENTJ' },
];

export type MbtiValue = (typeof MBTI_OPTIONS)[number]['value'];

export default function ProfileEditModal({
onSubmit,
memberProfile,
Expand Down Expand Up @@ -182,17 +208,18 @@ export default function ProfileEditModal({
/>
<FormField
label="MBTI"
type="text"
type="singledropdown"
description="자신의 성격 유형을 입력해 주세요."
value={mbti}
onChange={setMbti}
options={MBTI_OPTIONS}
/>
<FormField
label="관심사"
type="multidropdown"
label="관심 태그"
type="userselect"
value={interests}
onChange={setInterests}
options={skillOptions}
options={DEFAULT_OPTIONS}
/>
<FormField
label="한마디 소개"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ export default function ProfileInfoEditModal({ memberInfo, onSubmit }: Props) {
const [studyPlan, setStudyPlan] = useState<
UpdateUserProfileInfoRequest['studyPlan']
>(memberInfo.studyPlan ?? '');
const [preferredSubject, setPreferredSubject] = useState<
UpdateUserProfileInfoRequest['preferredStudySubject']
const [preferredStudySubjectId, setPreferredStudySubjectId] = useState<
UpdateUserProfileInfoRequest['preferredStudySubjectId']
>(memberInfo.preferredStudySubject?.studySubjectId ?? undefined);
const [availableTimeSlots, setAvailableTimeSlots] = useState<
UpdateUserProfileInfoRequest['availableStudyTimeIds']
Expand All @@ -42,7 +42,7 @@ export default function ProfileInfoEditModal({ memberInfo, onSubmit }: Props) {
const formData: UpdateUserProfileInfoRequest = {
selfIntroduction,
studyPlan,
preferredStudySubject: preferredSubject,
preferredStudySubjectId: preferredStudySubjectId,
availableStudyTimeIds: availableTimeSlots
.filter((id) => id)
.map((id) => Number(id)),
Expand Down Expand Up @@ -98,8 +98,8 @@ export default function ProfileInfoEditModal({ memberInfo, onSubmit }: Props) {
label="선호하는 스터디 주제"
type="singledropdown"
description="관심있는 스터디 유형을 선택해주세요."
value={preferredSubject}
onChange={setPreferredSubject}
value={preferredStudySubjectId}
onChange={setPreferredStudySubjectId}
direction="vertical"
required
options={
Expand All @@ -112,15 +112,17 @@ export default function ProfileInfoEditModal({ memberInfo, onSubmit }: Props) {
<FormField
label="가능 시간대"
type="togglegroup"
value={availableTimeSlots}
value={availableTimeSlots.map(String)}
direction="vertical"
options={
availableStudyTimes?.map(({ availableTimeId, display }) => ({
value: availableTimeId.toString(),
label: display,
})) ?? []
}
onChange={setAvailableTimeSlots}
onChange={(availableStudyTimeIds) =>
setAvailableTimeSlots(availableStudyTimeIds.map(Number))
}
required
/>
<FormField
Expand Down Expand Up @@ -157,8 +159,8 @@ export default function ProfileInfoEditModal({ memberInfo, onSubmit }: Props) {
);

sendGTMEvent({
event_name: 'member_card',
timestamp: new Date().toISOString(),
event: 'custom_member_card',
dl_timestamp: new Date().toISOString(),
dl_member_id: hashValue(memberId),
dl_tags: selectedSkillNames,
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';

import { MemberInfo } from '@/entities/user/api/types';
import ProfileInfoEditModal from '@/features/my-page/ui/profileinfo-edit-modal';
import ProfileInfoEditModal from '@/features/my-page/ui/profile-info-edit-modal';
import ProfileInfoCard from '@/widgets/my-page/profileinfo-card';
import { UpdateUserProfileInfoRequest } from '../api/types';
import { useUpdateUserProfileInfoMutation } from '../model/use-update-user-profile-mutation';
Expand Down
2 changes: 1 addition & 1 deletion src/features/study/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export interface DailyStudy {
interviewerImage: string;
interviewee: string;
intervieweeImage: string;
studySpaceId: number;
dailyStudyId: number;
subject: string;
feedback: string | undefined;
progressStatus: StudyProgressStatus;
Expand Down
Loading