-
Notifications
You must be signed in to change notification settings - Fork 3
[콜밴] 생성/참여 제한 사용자 모달 노출 #1238
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
[콜밴] 생성/참여 제한 사용자 모달 노출 #1238
Changes from all commits
e4dd43a
e412ccd
fd4d0f8
436d78c
7a386df
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,97 @@ | ||
| $callvan-restriction-accent-color: #b611f5; | ||
| $callvan-restriction-font-family: var(--font-pretendard), sans-serif; | ||
|
|
||
| .modal { | ||
| &__overlay { | ||
| position: fixed; | ||
| inset: 0; | ||
| z-index: 300; | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| padding: 24px; | ||
| box-sizing: border-box; | ||
| } | ||
|
|
||
| &__dim { | ||
| position: absolute; | ||
| inset: 0; | ||
| border: none; | ||
| background: rgb(0 0 0 / 70%); | ||
| padding: 0; | ||
| cursor: pointer; | ||
| } | ||
|
|
||
| &__sheet { | ||
| position: relative; | ||
| z-index: 301; | ||
| width: min(301px, 100%); | ||
| border-radius: 8px; | ||
| background: #fff; | ||
| box-shadow: | ||
| 0 1px 2px rgb(0 0 0 / 4%), | ||
| 0 4px 5px rgb(0 0 0 / 8%); | ||
| } | ||
|
|
||
| &__content { | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: 24px; | ||
| align-items: center; | ||
| padding: 24px 32px; | ||
| } | ||
|
|
||
| &__text { | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: 8px; | ||
| align-items: center; | ||
| width: 100%; | ||
| text-align: center; | ||
| } | ||
|
|
||
| &__title { | ||
| margin: 0; | ||
| color: #4b4b4b; | ||
| font-family: $callvan-restriction-font-family; | ||
| font-size: 18px; | ||
| font-weight: 500; | ||
| line-height: 1.6; | ||
| } | ||
|
|
||
| &__accent { | ||
| color: $callvan-restriction-accent-color; | ||
| } | ||
|
|
||
| &__description { | ||
| display: flex; | ||
| flex-direction: column; | ||
| width: 100%; | ||
| color: #727272; | ||
| font-family: $callvan-restriction-font-family; | ||
| font-size: 14px; | ||
| font-weight: 400; | ||
| line-height: 1.6; | ||
|
|
||
| p { | ||
| margin: 0; | ||
| } | ||
| } | ||
|
|
||
| &__button { | ||
| display: flex; | ||
| width: 100%; | ||
| align-items: center; | ||
| justify-content: center; | ||
| border: none; | ||
| border-radius: 8px; | ||
| background: $callvan-restriction-accent-color; | ||
| padding: 12px; | ||
| color: #fff; | ||
| font-family: $callvan-restriction-font-family; | ||
| font-size: 15px; | ||
| font-weight: 500; | ||
| line-height: 1.6; | ||
| cursor: pointer; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| import { useEffect } from 'react'; | ||
| import { RestrictedCallvanResponse } from 'api/callvan/entity'; | ||
| import { getCallvanRestrictionModalCopy } from 'components/Callvan/utils/callvanRestriction'; | ||
| import { useBodyScrollLock } from 'utils/hooks/ui/useBodyScrollLock'; | ||
| import styles from './CallvanRestrictionModal.module.scss'; | ||
|
|
||
| interface CallvanRestrictionModalProps { | ||
| restriction: RestrictedCallvanResponse | null; | ||
| onClose: () => void; | ||
| } | ||
|
|
||
| export default function CallvanRestrictionModal({ restriction, onClose }: CallvanRestrictionModalProps) { | ||
| useBodyScrollLock(); | ||
|
|
||
| const { titleAccent, titleRest, descriptionLines } = getCallvanRestrictionModalCopy(restriction); | ||
|
|
||
| useEffect(() => { | ||
| const handleKeyDown = (event: KeyboardEvent) => { | ||
| if (event.key === 'Escape') { | ||
| onClose(); | ||
| } | ||
| }; | ||
|
|
||
| document.addEventListener('keydown', handleKeyDown); | ||
|
|
||
| return () => { | ||
| document.removeEventListener('keydown', handleKeyDown); | ||
| }; | ||
| }, [onClose]); | ||
|
|
||
| return ( | ||
| <div className={styles.modal__overlay} role="dialog" aria-modal="true" aria-labelledby="callvan-restriction-title"> | ||
| <button type="button" className={styles.modal__dim} onClick={onClose} aria-label="닫기" /> | ||
| <div className={styles.modal__sheet}> | ||
| <div className={styles.modal__content}> | ||
| <div className={styles.modal__text}> | ||
| <h2 id="callvan-restriction-title" className={styles.modal__title}> | ||
| <span className={styles.modal__accent}>{titleAccent}</span> | ||
| {titleRest} | ||
| </h2> | ||
| <div className={styles.modal__description}> | ||
| {descriptionLines.map((line, index) => ( | ||
| <p key={`${line}-${index}`}>{line}</p> | ||
| ))} | ||
| </div> | ||
| </div> | ||
| <button type="button" className={styles.modal__button} onClick={onClose}> | ||
| 닫기 | ||
| </button> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ); | ||
|
Comment on lines
+31
to
+53
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. 모달 포커스 관리(접근성) 보완 필요
권장: ♻️ 최소한의 초기 포커스 및 복원 예시-import { useEffect } from 'react';
+import { useEffect, useRef } from 'react';
@@
export default function CallvanRestrictionModal({ restriction, onClose }: CallvanRestrictionModalProps) {
const { titleAccent, titleRest, descriptionLines } = getCallvanRestrictionModalCopy(restriction);
+ const closeButtonRef = useRef<HTMLButtonElement>(null);
useEffect(() => {
+ const previouslyFocused = document.activeElement as HTMLElement | null;
+ closeButtonRef.current?.focus();
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
onClose();
}
};
document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('keydown', handleKeyDown);
+ previouslyFocused?.focus?.();
};
}, [onClose]);
@@
- <button type="button" className={styles.modal__button} onClick={onClose}>
+ <button type="button" ref={closeButtonRef} className={styles.modal__button} onClick={onClose}>
닫기
</button>코딩 가이드라인에 따라( 🤖 Prompt for AI Agents |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| import { useCallback } from 'react'; | ||
| import { sendClientError } from '@bcsdlab/koin'; | ||
| import { useQueryClient } from '@tanstack/react-query'; | ||
| import { RestrictedCallvanResponse } from 'api/callvan/entity'; | ||
| import { callvanQueries } from 'api/callvan/queries'; | ||
| import CallvanRestrictionModal from 'components/Callvan/components/CallvanRestrictionModal'; | ||
| import { isCallvanRestrictedError } from 'components/Callvan/utils/callvanRestriction'; | ||
| import { Portal } from 'components/modal/Modal/PortalProvider'; | ||
| import useModalPortal from 'utils/hooks/layout/useModalPortal'; | ||
|
|
||
| export default function useCallvanRestrictionModal(token: string) { | ||
| const portalManager = useModalPortal(); | ||
| const queryClient = useQueryClient(); | ||
|
|
||
| const open = useCallback( | ||
| (restriction: RestrictedCallvanResponse) => { | ||
| portalManager.open((portalOption: Portal) => ( | ||
| <CallvanRestrictionModal restriction={restriction} onClose={portalOption.close} /> | ||
| )); | ||
| }, | ||
| [portalManager], | ||
| ); | ||
|
Comment on lines
+11
to
+22
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. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# callvanQueries.restriction 정의 및 queryKey/staleTime 확인
fd -t f 'queries' src/api/callvan
rg -nP -C8 "restriction\s*[:=]\s*\(?token" --type=ts
rg -nP -C5 "queryKey|staleTime" src/api/callvanRepository: BCSDLab/KOIN_WEB_RECODE Length of output: 6139 🏁 Script executed: # callvanQueryKeys 정의 확인
rg -nP "callvanQueryKeys\s*=" src/api/callvan --type=ts -A 15Repository: BCSDLab/KOIN_WEB_RECODE Length of output: 1321 🏁 Script executed: # useCallvanRestrictionModal 전체 코드 확인
cat -n src/components/Callvan/hooks/useCallvanRestrictionModal.tsxRepository: BCSDLab/KOIN_WEB_RECODE Length of output: 1958 queryKey 및 staleTime 설정 수정 필요
수정 제안: // src/api/callvan/queries.ts
export const callvanQueryKeys = {
// ...
restriction: (token: string) => ['callvan', 'restriction', token] as const,
};
export const callvanQueries = {
// ...
restriction: (token: string) =>
queryOptions({
queryKey: callvanQueryKeys.restriction(token),
queryFn: () => getCallvanRestriction(token),
staleTime: 60000, // 또는 적절한 값
}),
};동일한 문제가 🤖 Prompt for AI Agents |
||
|
|
||
| const openFromError = useCallback( | ||
| async (error: unknown) => { | ||
| if (!isCallvanRestrictedError(error)) return false; | ||
|
|
||
| if (!token) return false; | ||
|
|
||
| try { | ||
| const restriction = await queryClient.fetchQuery(callvanQueries.restriction(token)); | ||
| if (restriction.is_restricted) { | ||
| open(restriction); | ||
| return true; | ||
| } | ||
|
|
||
| return false; | ||
| } catch (restrictionError) { | ||
| sendClientError(restrictionError); | ||
| return false; | ||
| } | ||
| }, | ||
| [open, queryClient, token], | ||
| ); | ||
|
|
||
| return { openFromError }; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
cat -n src/api/callvan/queries.ts | head -70Repository: BCSDLab/KOIN_WEB_RECODE
Length of output: 3050
🏁 Script executed:
rg -n "callvanQueryKeys\.(notifications|restriction)" src/Repository: BCSDLab/KOIN_WEB_RECODE
Length of output: 459
🏁 Script executed:
cat -n src/api/callvan/entity.ts | head -40Repository: BCSDLab/KOIN_WEB_RECODE
Length of output: 1291
🏁 Script executed:
Repository: BCSDLab/KOIN_WEB_RECODE
Length of output: 49
🏁 Script executed:
Repository: BCSDLab/KOIN_WEB_RECODE
Length of output: 1865
🏁 Script executed:
Repository: BCSDLab/KOIN_WEB_RECODE
Length of output: 49
🏁 Script executed:
cat -n src/pages/callvan/notifications/index.tsx | head -60Repository: BCSDLab/KOIN_WEB_RECODE
Length of output: 2987
🏁 Script executed:
Repository: BCSDLab/KOIN_WEB_RECODE
Length of output: 49
🏁 Script executed:
Repository: BCSDLab/KOIN_WEB_RECODE
Length of output: 293
🏁 Script executed:
rg -n "dehydrate" src/pages/callvan -B 2 -A 2Repository: BCSDLab/KOIN_WEB_RECODE
Length of output: 2621
🏁 Script executed:
Repository: BCSDLab/KOIN_WEB_RECODE
Length of output: 1206
🏁 Script executed:
rg -n "useTokenStore\|token" src/utils/zustand/auth.ts -A 5Repository: BCSDLab/KOIN_WEB_RECODE
Length of output: 49
🏁 Script executed:
fd -i "auth" src/utils/zustand --type fRepository: BCSDLab/KOIN_WEB_RECODE
Length of output: 129
🏁 Script executed:
Repository: BCSDLab/KOIN_WEB_RECODE
Length of output: 1400
Raw token을 queryKey에 포함하지 마세요.
notifications/restriction의 queryKey에 인증 토큰을 포함하면 React Query 캐시와 SSRdehydrate()페이로드에 토큰이 평문으로 노출됩니다. 같은 파일의postDetail,chat처럼 queryKey에는 비민감 값만 포함하고, 토큰은queryFn실행 시점에 별도로 주입하는 방식을 권장합니다.사용자당 캐시 격리가 필요하면 토큰 대신 사용자 ID나 별도 식별값을 사용하세요.
🤖 Prompt for AI Agents