-
Notifications
You must be signed in to change notification settings - Fork 3
[HotFix] 동아리 상세페이지 모집 버튼 푸터 삭제 복구 #978
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9f5691d
2fe7a98
66315d4
e6cda42
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| import styled from 'styled-components'; | ||
|
|
||
| export const ApplyButtonContainer = styled.div` | ||
| width: 100%; | ||
| display: flex; | ||
| flex-direction: row; | ||
| justify-content: center; | ||
| text-align: center; | ||
| gap: 10px; | ||
| `; | ||
|
|
||
| export const ApplyButton = styled.button` | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| border: none; | ||
| border-radius: 10px; | ||
| cursor: pointer; | ||
| transition: transform 0.2s ease-in-out; | ||
| background-color: #ff7543; | ||
|
|
||
| padding: 10px 40px; | ||
| width: 517px; | ||
| height: 50px; | ||
| font-size: 20px; | ||
| font-style: normal; | ||
| font-weight: 700; | ||
| color: #fff; | ||
| text-align: center; | ||
|
|
||
| img { | ||
| font-size: 12px; | ||
| font-weight: 600; | ||
| } | ||
|
|
||
| @media (max-width: 500px) { | ||
| width: 280px; | ||
| } | ||
| `; | ||
|
|
||
| export const Separator = styled.span` | ||
| margin: 0 8px; | ||
| border-left: 1px solid #787878; | ||
| height: 12px; | ||
| display: inline-block; | ||
| `; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| import * as Styled from './ClubApplyButton.styles'; | ||
| import { useNavigate, useParams } from 'react-router-dom'; | ||
| import { useGetClubDetail } from '@/hooks/queries/club/useGetClubDetail'; | ||
| import getApplication from '@/apis/application/getApplication'; | ||
| import useMixpanelTrack from '@/hooks/useMixpanelTrack'; | ||
| import { USER_EVENT } from '@/constants/eventName'; | ||
| import { useState } from 'react'; | ||
| import { ApplicationForm, ApplicationFormMode } from '@/types/application'; | ||
| import getApplicationOptions from '@/apis/application/getApplicationOptions'; | ||
| import ApplicationSelectModal from '@/components/application/modals/ApplicationSelectModal'; | ||
| import ShareButton from '../ShareButton/ShareButton'; | ||
|
|
||
| interface ClubApplyButtonProps { | ||
| deadlineText?: string; | ||
| } | ||
|
|
||
| const RECRUITMENT_STATUS = { | ||
| ALWAYS: '상시 모집', | ||
| CLOSED: '모집 마감', | ||
| }; | ||
|
|
||
| const ClubApplyButton = ({ deadlineText }: ClubApplyButtonProps) => { | ||
| const { clubId } = useParams<{ clubId: string }>(); | ||
| const navigate = useNavigate(); | ||
| const trackEvent = useMixpanelTrack(); | ||
| const { data: clubDetail } = useGetClubDetail(clubId!); | ||
|
|
||
| // 모달 옵션 상태 | ||
| const [isOpen, setIsOpen] = useState(false); | ||
| const [options, setOptions] = useState<ApplicationForm[]>([]); | ||
|
|
||
| if (!clubId || !clubDetail) return null; | ||
|
|
||
| // 내부 폼 이동 | ||
| const goWithForm = async (formId: string) => { | ||
| try { | ||
| const formDetail = await getApplication(clubId, formId); | ||
| if (formDetail?.formMode === ApplicationFormMode.EXTERNAL) { | ||
| const externalApplicationUrl = | ||
| formDetail.externalApplicationUrl?.trim(); | ||
| if (externalApplicationUrl) { | ||
| window.open(externalApplicationUrl, '_blank', 'noopener,noreferrer'); | ||
| return; | ||
| } | ||
| } | ||
| navigate(`/application/${clubId}/${formId}`, { state: { formDetail } }); | ||
| setIsOpen(false); | ||
| } catch (error) { | ||
| console.error('지원서 조회 중 오류가 발생했습니다', error); | ||
| alert( | ||
| '지원서 정보를 불러오는 중 오류가 발생했습니다. 다시 시도해주세요.', | ||
| ); | ||
| } | ||
| }; | ||
|
|
||
| // url 존재 시 외부, 내부 지원서 옵션에 따른 처리 | ||
| const openByOption = (option?: ApplicationForm) => { | ||
| if (!option) return; | ||
| void goWithForm(option.id); | ||
| }; | ||
|
|
||
| const handleClick = async () => { | ||
| trackEvent(USER_EVENT.CLUB_APPLY_BUTTON_CLICKED); | ||
|
|
||
| if (deadlineText === RECRUITMENT_STATUS.CLOSED) { | ||
| alert(`현재 ${clubDetail.name} 동아리는 모집 기간이 아닙니다.`); | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| const list = await getApplicationOptions(clubId); | ||
|
|
||
| if (list.length <= 0) { | ||
| return; | ||
| } | ||
lepitaaar marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| if (list.length === 1) { | ||
| await goWithForm(list[0].id); | ||
| return; | ||
| } | ||
| setOptions(list); | ||
| setIsOpen(true); | ||
| } catch (e) { | ||
| setOptions([]); | ||
| setIsOpen(true); | ||
| console.error('지원서 옵션 조회 중 오류가 발생했습니다.', e); | ||
lepitaaar marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| }; | ||
|
|
||
| const renderButtonContent = () => { | ||
| if (deadlineText === RECRUITMENT_STATUS.CLOSED) { | ||
| return RECRUITMENT_STATUS.CLOSED; | ||
| } | ||
|
|
||
| return ( | ||
| <> | ||
| 지원하기 | ||
| {deadlineText && deadlineText !== RECRUITMENT_STATUS.ALWAYS && ( | ||
| <> | ||
| <Styled.Separator /> | ||
| {deadlineText} | ||
| </> | ||
| )} | ||
| </> | ||
| ); | ||
| }; | ||
|
|
||
| return ( | ||
| <Styled.ApplyButtonContainer> | ||
| <ShareButton clubId={clubId} /> | ||
| <Styled.ApplyButton onClick={handleClick}> | ||
| {renderButtonContent()} | ||
| </Styled.ApplyButton> | ||
| <ApplicationSelectModal | ||
| isOpen={isOpen} | ||
| onClose={() => setIsOpen(false)} | ||
| options={options} | ||
| onSelect={openByOption} | ||
| /> | ||
| </Styled.ApplyButtonContainer> | ||
| ); | ||
| }; | ||
|
|
||
| export default ClubApplyButton; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import styled from 'styled-components'; | ||
|
|
||
| export const ClubDetailFooterContainer = styled.div` | ||
| position: sticky; | ||
| bottom: 0; | ||
| width: 100%; | ||
| z-index: 1050; // TODO: Portal로 모달 분리 후 header보다 낮게 재조정 | ||
| padding: 10px 40px; | ||
|
|
||
| display: flex; | ||
| align-items: center; | ||
| justify-content: space-between; | ||
| gap: 16px; | ||
|
|
||
| background-color: white; | ||
| border-top: 1px solid #cdcdcd; | ||
| `; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,28 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import getDeadlineText from '@/utils/getDeadLineText'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { recruitmentDateParser } from '@/utils/recruitmentDateParser'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import ClubApplyButton from '../ClubApplyButton/ClubApplyButton'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import * as Styled from './ClubDetailFooter.styles'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| interface ClubDetailFooterProps { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| recruitmentStart: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| recruitmentEnd: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const ClubDetailFooter = ({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| recruitmentStart, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| recruitmentEnd, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }: ClubDetailFooterProps) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const deadlineText = getDeadlineText( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| recruitmentDateParser(recruitmentStart), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| recruitmentDateParser(recruitmentEnd), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| new Date(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+15
to
+19
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 날짜 파싱 에러 처리를 추가하세요.
사용자 경험 개선을 위해 try-catch로 에러를 처리하고 적절한 fallback UI를 표시하거나, 최소한 에러 로깅을 추가하는 것을 권장합니다. 🔎 에러 처리 추가 예시 const ClubDetailFooter = ({
recruitmentStart,
recruitmentEnd,
}: ClubDetailFooterProps) => {
+ let deadlineText: string;
+ try {
- const deadlineText = getDeadlineText(
- recruitmentDateParser(recruitmentStart),
- recruitmentDateParser(recruitmentEnd),
- new Date(),
- );
+ deadlineText = getDeadlineText(
+ recruitmentDateParser(recruitmentStart),
+ recruitmentDateParser(recruitmentEnd),
+ new Date(),
+ );
+ } catch (error) {
+ console.error('모집 기간 파싱 오류:', error);
+ deadlineText = '모집 기간 확인 불가';
+ }
return (
<Styled.ClubDetailFooterContainer>
<ClubApplyButton deadlineText={deadlineText} />
</Styled.ClubDetailFooterContainer>
);
};📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Styled.ClubDetailFooterContainer> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <ClubApplyButton deadlineText={deadlineText} /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Styled.ClubDetailFooterContainer> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export default ClubDetailFooter; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| import styled from 'styled-components'; | ||
|
|
||
| export const ShareButtonContainer = styled.div` | ||
| display: flex; | ||
| justify-content: flex-end; | ||
| cursor: pointer; | ||
| `; | ||
|
|
||
| export const ShareButtonIcon = styled.img` | ||
| width: 50px; | ||
| height: 50px; | ||
| `; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,62 @@ | ||||||||||||||||||||||||||||||||||||||||||||||
| import * as Styled from './ShareButton.styles'; | ||||||||||||||||||||||||||||||||||||||||||||||
| import { useGetClubDetail } from '@/hooks/queries/club/useGetClubDetail'; | ||||||||||||||||||||||||||||||||||||||||||||||
| import useMixpanelTrack from '@/hooks/useMixpanelTrack'; | ||||||||||||||||||||||||||||||||||||||||||||||
| import ShareIcon from '@/assets/images/icons/share_icon.svg'; | ||||||||||||||||||||||||||||||||||||||||||||||
| import { USER_EVENT } from '@/constants/eventName'; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| interface ShareButtonProps { | ||||||||||||||||||||||||||||||||||||||||||||||
| clubId: string; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const MOADONG_BASE_URL = 'https://www.moadong.com/club/'; | ||||||||||||||||||||||||||||||||||||||||||||||
| const DEFAULT_IMAGE_URL = | ||||||||||||||||||||||||||||||||||||||||||||||
| 'https://avatars.githubusercontent.com/u/200371900?s=200&v=4'; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const ShareButton = ({ clubId }: ShareButtonProps) => { | ||||||||||||||||||||||||||||||||||||||||||||||
| const { data: clubDetail } = useGetClubDetail(clubId); | ||||||||||||||||||||||||||||||||||||||||||||||
| const trackEvent = useMixpanelTrack(); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| if (!clubDetail) return; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const handleShare = () => { | ||||||||||||||||||||||||||||||||||||||||||||||
| if (!window.Kakao || !window.Kakao.isInitialized()) { | ||||||||||||||||||||||||||||||||||||||||||||||
| alert('카카오 SDK가 아직 준비되지 않았습니다.'); | ||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| window.Kakao.Share.sendDefault({ | ||||||||||||||||||||||||||||||||||||||||||||||
| objectType: 'feed', | ||||||||||||||||||||||||||||||||||||||||||||||
| content: { | ||||||||||||||||||||||||||||||||||||||||||||||
| title: clubDetail.name, | ||||||||||||||||||||||||||||||||||||||||||||||
| description: clubDetail.description, | ||||||||||||||||||||||||||||||||||||||||||||||
| imageUrl: clubDetail.logo ? clubDetail.logo : DEFAULT_IMAGE_URL, | ||||||||||||||||||||||||||||||||||||||||||||||
| link: { | ||||||||||||||||||||||||||||||||||||||||||||||
| mobileWebUrl: `${MOADONG_BASE_URL}${clubDetail.id}`, | ||||||||||||||||||||||||||||||||||||||||||||||
| webUrl: `${MOADONG_BASE_URL}${clubDetail.id}`, | ||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+27
to
+37
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 카카오 공유 description에 잘못된 타입이 전달되고 있습니다. Line 31에서
🔎 수정 제안 window.Kakao.Share.sendDefault({
objectType: 'feed',
content: {
title: clubDetail.name,
- description: clubDetail.description,
+ description: clubDetail.introduction,
imageUrl: clubDetail.logo ? clubDetail.logo : DEFAULT_IMAGE_URL,
link: {
mobileWebUrl: `${MOADONG_BASE_URL}${clubDetail.id}`,
webUrl: `${MOADONG_BASE_URL}${clubDetail.id}`,
},
},📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||
| buttons: [ | ||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||
| title: '모아동에서 지원하기', | ||||||||||||||||||||||||||||||||||||||||||||||
| link: { | ||||||||||||||||||||||||||||||||||||||||||||||
| mobileWebUrl: `${MOADONG_BASE_URL}${clubDetail.id}`, | ||||||||||||||||||||||||||||||||||||||||||||||
| webUrl: `${MOADONG_BASE_URL}${clubDetail.id}`, | ||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||
| trackEvent(USER_EVENT.SHARE_BUTTON_CLICKED, { clubName: clubDetail.name }); | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||
| <Styled.ShareButtonContainer | ||||||||||||||||||||||||||||||||||||||||||||||
| onClick={handleShare} | ||||||||||||||||||||||||||||||||||||||||||||||
| role='button' | ||||||||||||||||||||||||||||||||||||||||||||||
| aria-label='카카오톡으로 동아리 정보 공유하기' | ||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||
| <Styled.ShareButtonIcon src={ShareIcon} alt='카카오톡 공유' /> | ||||||||||||||||||||||||||||||||||||||||||||||
| </Styled.ShareButtonContainer> | ||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| export default ShareButton; | ||||||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.