-
Notifications
You must be signed in to change notification settings - Fork 0
(i25 152) feat/inputs of modal PR #55
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
Conversation
โฆ into (I25-152)-feat/InputsOfModal
|
๐ ๋ฐฐํฌ ์ค๋น์ค ์์ํฌํธ : |
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.
Pull Request Overview
This PR implements inputs and modal components for the BSMHub v2 project, adding form elements and interface components for user interactions.
- Adds various input types with different states (text, date, lock, edit, search)
- Implements labeled inputs with required field support
- Creates button components and picture upload functionality with preview
Reviewed Changes
Copilot reviewed 103 out of 138 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/app/components/inputsOfModal/ | Core modal input components including buttons, inputs, labels, and picture upload |
| src/app/components/system/text.ts | Typography system using styled-components |
| src/app/components/layout/Header.tsx | Simplified header component with navigation |
| tailwind.config.ts | Updated color scheme and utility classes |
| package.json | Added dependencies for styled-components and icons |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| name={name} | ||
| required={required} | ||
| readOnly={type === 'lock'} | ||
| className={`flex w-full h-[3.3125rem] py-1 ${ |
Copilot
AI
Aug 27, 2025
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.
This complex template literal with conditional classes should be refactored using a utility function or clsx library for better readability and maintainability.
src/app/components/layout/Header.tsx
Outdated
| ]; | ||
|
|
||
| return ( | ||
| <header className="flex-center p-white-space-margin h-9 border-b border-[light-gray-outline]"> |
Copilot
AI
Aug 27, 2025
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.
The Tailwind class 'border-[light-gray-outline]' should use the proper color syntax 'border-light-gray-outline' to reference the custom color defined in the config.
| <header className="flex-center p-white-space-margin h-9 border-b border-[light-gray-outline]"> | |
| <header className="flex-center p-white-space-margin h-9 border-b border-light-gray-outline"> |
src/app/components/inputsOfModal/pictureUpload/PictureUpload.tsx
Outdated
Show resolved
Hide resolved
|
๐ ๋ฐฐํฌ ์ค๋น์ค ์์ํฌํธ : |
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: LEE JEONGHYEOK <157395300+GAMZAMANDU@users.noreply.github.com>
|
๐ ๋ฐฐํฌ ์ค๋น์ค ์์ํฌํธ : |
GAMZAMANDU
left a comment
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.
ํผ๋๋ฐฑ ๋ฐ์ํด์ฃผ์ธ์.
| import React from 'react'; | ||
|
|
||
| interface ButtonsProps { | ||
| color?: 'black' | 'blue' | 'red' | 'green' | 'gray'; |
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.
์ด ์๊น๋ค์ ์ด๋์ ์ฐ์ด๋ ๊ฑด๊ฐ์?
| black: 'bg-black', | ||
| blue: 'bg-blue-500', | ||
| red: 'bg-red-500', | ||
| green: 'bg-green-500', | ||
| gray: 'bg-gray-500', |
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.
์๊น๋ค์ ์ฌ๋งํ๋ฉด tailwind.config.ts์ ์ ์๋ ํด๋์ค๋ฅผ ์ฌ์ฉํด์ฃผ์ธ์.
| <button | ||
| className={`flex w-full h-[3.25rem] py-2 px-[1.375rem] justify-center items-center gap-1 shrink-0 rounded-full ${bgClass} text-white text-base font-bold leading-5`} | ||
| onClick={onClick} | ||
| > |
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.
flex, justify-center, items-center๋ฅผ ๋ชจ๋ ํฉ์ณ๋ flex-center๊ฐ ์ ์๋์ด ์์ผ๋ ํ์ธ ๋ถํ๋๋ฆฝ๋๋ค..
๋ํ ๋ชจ๋ ๊ธ์จ์ฒด๋ tailwind.config.ts์ ์ ์๋ ๊ฒ์ ์ฌ์ฉํด์ฃผ์ธ์.
| placeholder = 'Placeholder', | ||
| value, | ||
| onChange, | ||
| name, |
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.
name๋ณด๋จ label์ด ๋ ์ข์ ๊ฒ ๊ฐ์ต๋๋ค.
| ['lock', 'edit', 'search', 'date'].includes(type) ? 'pr-10' : 'px-2.5' | ||
| } pl-2.5 items-center gap-2.5 shrink-0 rounded-md text-gray-base text-base font-normal leading-6 tracking-[0.0057rem] ${ | ||
| type !== 'lock' ? 'bg-light-gray-outline' : '' |
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.
๋ถํ์ํ ์์ฑ์ด ๋ค์ ์ฌ์ฉ๋ ๊ฒ ๊ฐ์ต๋๋ค.
| return ( | ||
| <div className="inline-block h-[5.875rem]"> | ||
| <div | ||
| className="h-full aspect-1 flex flex-col items-center justify-center rounded-md bg-light-gray-outline cursor-pointer overflow-hidden relative" |
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.
๋ฅ๊ทผ ํ์๊น ์ฌ๊ฐํ์ด ์์ฃผ ์ฌ์ฉ๋๊ณ ์๋๋ฐ ์ด๋ฌํ ๋ถ๋ถ์ ๋ ์ด์์ํ ํ์ ์ผํฉ๋๋ค.
| import { ChangeEvent } from 'react'; | ||
|
|
||
| export type InputType = 'text' | 'lock' | 'date' | 'edit' | 'search'; | ||
|
|
||
| export interface BaseInputProps { | ||
| type?: InputType; | ||
| placeholder?: string; | ||
| value?: string | number; | ||
| onChange?: (e: ChangeEvent<HTMLInputElement>) => void; | ||
| name?: string; | ||
| required?: boolean; | ||
| id?: string; | ||
| } | ||
|
|
||
| export interface LabelInputsProps extends BaseInputProps { | ||
| label?: string; | ||
| } |
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.
๊ณตํต๋ ํ์ ์ ์ ์ข์ต๋๋ค.
โฆ๊ธฐ๋ณธ ์์ด์ฝ์ ์ค๋ฅธ์ชฝ์ผ๋ก ๋ถ์ฐฉ
- react-hook-form v7.64.0 ์ถ๊ฐ - ํผ ์ํ ๊ด๋ฆฌ ๋ฐ ๊ฒ์ฆ์ ์ํ ์์กด์ฑ
- MultiInput ์ปดํฌ๋ํธ ์ฌ์ฉ์ผ๋ก ์ฌ๋ฌ ํ๋ ๋์ ๊ด๋ฆฌ - onlyOne ์ต์ ์ผ๋ก ๋จ์ผ/๋ค์ค ์ ๋ ฅ ์ ์ด - InputConfig ํ์ ์ onlyOne ์์ฑ ์ถ๊ฐ - ์ถ๊ฐ ๋ฒํผ์ type='button' ์ถ๊ฐํ์ฌ ํผ ์ ์ถ ๋ฐฉ์ง - onInputsChange ์ฝ๋ฐฑ์ผ๋ก ์ธ๋ถ ์ํ ๊ด๋ฆฌ ์ง์ - config ๊ธฐ๋ฐ ๋์ ์ ๋ ฅ ์์ฑ
- FormFieldConfig: label๊ณผ InputListProvider ์ ์ค์ - FormConfig: ์ ์ฒด ํผ ์ค์ ํ์ - InputConfig import ์ถ๊ฐ - React Hook Form ํตํฉ์ ์ํ ํ์ ๊ตฌ์กฐ
- React Hook Form ํตํฉ์ผ๋ก ํผ ์ํ ๊ด๋ฆฌ - config ๊ธฐ๋ฐ ๋์ ํผ ํ๋ ์์ฑ - label๊ณผ InputListProvider ์๋ ํ์ด๋ง - ํ์ ํ๋ ์๋ ๊ฒ์ฆ ๋ฐ ์๋ฌ ๋ฉ์์ง ํ์ - title prop์ผ๋ก ํผ ์ ๋ชฉ ์ปค์คํฐ๋ง์ด์ง - Buttons ์ปดํฌ๋ํธ ํตํฉ ์ ์ถ ๋ฒํผ
- InputListProvider.md: ์ปดํฌ๋ํธ ์ฌ์ฉ๋ฒ ๋ฐ API ๋ฌธ์ - InputListProvider-with-ReactHookForm.md: React Hook Form ํตํฉ ๊ฐ์ด๋ - InputOfModal.md: config ๊ธฐ๋ฐ ํผ ์์ฑ ์ปดํฌ๋ํธ ๋ฌธ์ - ๊ฐ ๋ฌธ์์ ์ฌ์ฉ ์์ , ํ์ ์ ์, ํธ๋ฌ๋ธ์ํ ํฌํจ - onlyOne prop ์ ๋ฐ์ดํธ ๋ฐ์
- PictureUpload ์ฝ๋ ๊ฐ์ํ (59์ค โ 36์ค) - aspectRatio prop ์ถ๊ฐ๋ก ๋ค์ํ ๋น์จ ์ง์ (1:1, 3:4, 16:9 ๋ฑ) - InputOfModal์์ picture ํ์ ์ผ๋ก PictureUpload ์ฌ์ฉ ๊ฐ๋ฅ - ํ์ ์์ ์ฑ ๊ฐ์ : aspectRatio๋ picture ํ์ ์ ์ฉ์ผ๋ก ์ ํ
- ModalContext: ์ ์ญ ๋ชจ๋ฌ ์ํ ๊ด๋ฆฌ (Context API + useCallback) - Modal ์ปดํฌ๋ํธ: UI ๋ ๋๋ง ๋ฐ ๋ฐฐ๊ฒฝ ํด๋ฆญ์ผ๋ก ๋ซ๊ธฐ - ๋ชจ๋ฌ ์คํ์ผ: ๋ถ๋๋ฌ์ด ์ ๋๋ฉ์ด์ ๋ฐ backdrop blur ํจ๊ณผ - body ์คํฌ๋กค ๋ฐฉ์ง ๊ธฐ๋ฅ - inputsOfModal ์ปดํฌ๋ํธ๋ค์ modal ํด๋๋ก ์ด๋ - ํ์ ์์คํ ๊ฐ์ (extends ๋ฐฉ์์ผ๋ก ํต์ผ) - ์ฌ์ฉ ๊ฐ์ด๋ ๋ฌธ์ ์์ฑ
- inputsOfModal ํด๋ ์ญ์ (modal/inputsOfModal๋ก ์ด๋ ์๋ฃ) - @utils alias ์ถ๊ฐ (tsconfig.json) - import ๊ฒฝ๋ก๋ฅผ @components, @utils alias๋ก ํต์ผ - useInputList์ MultiInputItem ํ์ ์ MultiInput.tsx์์ import - layout.tsx์ page.tsx์ import ๊ฒฝ๋ก ์์
- write/edit/read 3๊ฐ์ง ๋ชจ๋๋ก ๊ตฌ๋ถ๋ SkillTag ์ปดํฌ๋ํธ ์ถ๊ฐ - ์ฌ๋ฌ ์คํฌ ํ๊ทธ๋ฅผ ๊ด๋ฆฌํ๋ SkillTagProvider ๊ตฌํ - react-input-autosize๋ฅผ ์ฌ์ฉํ ์๋ ๋๋น ์กฐ์ ์ธํ - ํ๊ธ IME ์ ๋ ฅ ์ฒ๋ฆฌ ์ถ๊ฐ - ํ๊ทธ ์์ฑ/์์ /์ญ์ ๊ธฐ๋ฅ ๋ฐ ์ํ ๊ด๋ฆฌ - ์ธํ ํฌ์ปค์ค ์ ๊ฑฐ๋ฅผ ์ํ CSS ์ ํธ๋ฆฌํฐ ์ถ๊ฐ
- ์ฒดํฌ๋ฐ์ค ์ธํ ์ปดํฌ๋ํธ ์ถ๊ฐ - ๋ชจ๋ฌ ์ธํ ์์คํ ์ ์ฒดํฌ๋ฐ์ค ํ์ ์ง์
- inputsOfModal ํด๋๋ฅผ inputs๋ก ๋ฆฌ๋ค์ด๋ฐ - FormFieldConfig ํ์ ์ discriminated union์ผ๋ก ๊ฐ์ - SkillTag์ Checkbox๋ฅผ InputOfModal์ ํตํฉ - Config ๊ตฌ์กฐ ๋จ์ํ (ํ์ ๋ณ ํ์ํ ํ๋๋ง ์ ์) - import ๊ฒฝ๋ก ์ ๋ฆฌ ๋ฐ ํต์ผ - common.css ์ค๋ณต ์ ๊ฑฐ
- InputType์์ 'lock' ํ์ ์ ๊ฑฐ - BaseInputPropsCommon์ readOnly prop ์ถ๊ฐ - InputListProvider์์ isLocked โ isReadOnly๋ก ๋ณ๊ฒฝ - SingleInput์์ type ๊ธฐ๋ฐ readonly โ readOnly prop์ผ๋ก ๋ณ๊ฒฝ - ์ผ๊ด๋ readOnly ํจํด์ผ๋ก API ๋จ์ํ
- ์ด๊ธฐ ํ๊ทธ ๋ฐฐ์ด์ ์ค์ ํ ์ ์๋ initialTags prop ์ถ๊ฐ - useEffect๋ก ์ด๊ธฐ ํ๊ทธ ์๋ ์ค์ ๋ก์ง ๊ตฌํ - ์ฝ๊ธฐ ์ ์ฉ ๋ชจ๋์์ ์ด๊ธฐ๊ฐ ํ์ ๊ฐ๋ฅ
- inputsOfModal โ modal/inputs๋ก ํด๋๋ช ๋ณ๊ฒฝ - layout.tsx, page.tsx์ import ๊ฒฝ๋ก ์ ๋ฐ์ดํธ - ํ ์คํธ ์ฝ๋์ SkillTag ๋ฐ Checkbox ์์ ์ถ๊ฐ
- import ๊ฒฝ๋ก ์์ (inputsOfModal โ modal/inputs) - ์ ๋ ฅ ์ ๊ธ โ ์ ๋ ฅ ์ฝ๊ธฐ ์ ์ฉ์ผ๋ก ์ฉ์ด ๋ณ๊ฒฝ - InputType์์ 'lock' ์ ๊ฑฐ, readOnly prop ์ถ๊ฐ - readOnly prop ์ฌ์ฉ๋ฒ ์ค๋ช ์ถ๊ฐ - React Hook Form ํตํฉ ๊ฐ์ด๋ ๊ฒฝ๋ก ์ ๋ฐ์ดํธ
- Discriminated Union ํ์ ๊ตฌ์กฐ ์ค๋ช ์ถ๊ฐ - InputListFieldConfig - SkillTagFieldConfig - CheckboxFieldConfig - ํ๋ ํ์ ๋ณ ์์ธ ์ค๋ช ์น์ ์ถ๊ฐ - ๊ฐ ํ๋ ํ์ ์ ๋ฐ์ดํฐ ํ์ ๋ช ์ - ์ค์ ์์ (ํ๋กํ ์์ฑ ํผ) ์ถ๊ฐ - SkillTag, Checkbox ๋ ๋๋ง ๋ก์ง ์ค๋ช - import ๊ฒฝ๋ก ์ ๋ฐ์ดํธ
- SkillTag ์ปดํฌ๋ํธ ์ ์ฉ ์์ธ ๋ฌธ์ ์์ฑ - write/edit/read ๋ชจ๋ ์ค๋ช - Discriminated Union Props ๊ตฌ์กฐ - ํ๊ธ IME Composition ์ฒ๋ฆฌ ์๋ฆฌ - ์๋ ๋๋น ์กฐ์ (react-input-autosize) - ์ด๋ฒคํธ ๋ฒ๋ธ๋ง ๋ฐฉ์ง ๋ก์ง - SkillTagProvider ์ฌ์ฉ๋ฒ - InputOfModal ํตํฉ ์์ - ์ค์ ์ฌ์ฉ ์ฌ๋ก ๋ฐ ํธ๋ฌ๋ธ์ํ - ์ฑ๋ฅ ์ต์ ํ ํ
๋ฌธ์ :
- defaultValues์์ ๋ชจ๋ ํ๋๋ฅผ ๋น ๋ฐฐ์ด([])๋ก ์ด๊ธฐํ
- JS์์ []๋ truthy ๊ฐ์ด๋ฏ๋ก checked={git diff src/app/components/modal/inputs/InputOfModal.tsxvalue}๊ฐ true๊ฐ ๋จ
ํด๊ฒฐ:
- Checkbox ํ์
์ false๋ก, ๋๋จธ์ง๋ []๋ก ์ด๊ธฐํ
- ์ผํญ ์ฐ์ฐ์๋ก ๊ฐ๊ฒฐํ๊ฒ ์ฒ๋ฆฌ
- validation ๋ก์ง ๊ฐ์ํ (๋ฐฐ์ด ํ์
๋ง ๊ฒ์ฆ)
- InputMode (write/edit/read) ๋์ ์ผ๋ก ์ํ์ ํ์ ๋ถ๋ฆฌ - Picture๋ฅผ ๋ ๋ฆฝ์ ์ธ ํ๋ ํ์ ์ผ๋ก ์ถ๊ฐ - Checkbox ๋น์ ์ด/์ ์ด ์ปดํฌ๋ํธ ๋ชจ๋ ์ง์ - SkillTag autoFocus ์ ๊ฑฐ๋ก ํฌ์ปค์ค ๊ด๋ฆฌ ๊ฐ์ - SingleInput์ icon prop ์ถ๊ฐ (check/search/calendar)
- AutosizeInput ์ปดํฌ๋ํธ์ autoFocus prop ์ถ๊ฐ - ํ๊ทธ ์์ฑ ์ ์๋์ผ๋ก ํฌ์ปค์ค ์ค์
- Checkbox.md ๋ฌธ์ ์์ฑ - Controlled/Uncontrolled ๋ชจ๋ ์ค๋ช - React Hook Form ํตํฉ ์์ - InputOfModal์์ ์ฌ์ฉ๋ฒ - ๊ฒ์ฆ ๋ก์ง ์ค๋ช - InputOfModal์ Checkbox required ๊ฒ์ฆ ์ถ๊ฐ - PictureUpload value ํ์ any๋ก ๋ณ๊ฒฝํ์ฌ ํธํ์ฑ ๊ฐ์
- onTagsChange ์ฝ๋ฐฑ์ useRef๋ก ๊ด๋ฆฌํ์ฌ ์์กด์ฑ ๋ฌดํ ๋ฃจํ ๋ฐฉ์ง - InputOfModal์ Checkbox onChange ๋จ์ํ - PictureUpload value ํ์ ์ฒดํฌ ๋ช ํํ - useEffect ์์กด์ฑ ๋ฐฐ์ด ์ต์ ํ
|
๐ ๋ฐฐํฌ ์ค๋น์ค ์์ํฌํธ : |
| > | ||
| {preview ? ( | ||
| // eslint-disable-next-line @next/next/no-img-element | ||
| <img src={preview} alt="์ ๋ก๋๋ ์ด๋ฏธ์ง" className="object-cover w-full h-full" /> |
Check warning
Code scanning / CodeQL
DOM text reinterpreted as HTML Medium
DOM text
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 3 months ago
To fix the problem, we must ensure that only safe and intended values are assigned to the src attribute of the <img> element. Specifically, when the value prop is a string (possibly attacker-supplied), we should validate that it is a safe image URL before setting it as preview. The best approach is to only allow URLs that either start with blob:, are from trusted domains, or are recognized safe protocols (e.g., https://, data:image/, etc.), and explicitly reject javascript:, data:text/html;, or other dangerous schemes.
Required changes:
- Implement a function (e.g.,
isSafeImageSrc) that validates the string is a Blob URL or trusted image resource (starts withhttp://,https://, ordata:image/). - In both the
useEffectwherepreviewis set from a stringvalue, and the rendering wherepreviewis used asimg.src, use this validator to block unsafe URLs. - Optionally, fallback to not showing a preview if validation fails.
Add the function to the same file, and update the logic in the relevant lines.
-
Copy modified lines R11-R20 -
Copy modified lines R28-R32 -
Copy modified line R60
| @@ -8,6 +8,16 @@ | ||
| onFileChange?: (file: File | null) => void; // ํ์ผ ๋ณ๊ฒฝ ์ฝ๋ฐฑ | ||
| } | ||
|
|
||
| function isSafeImageSrc(src: string): boolean { | ||
| // allow blob:, http(s), and data:image/* only | ||
| return ( | ||
| src.startsWith('blob:') || | ||
| src.startsWith('https://') || | ||
| src.startsWith('http://') || | ||
| src.startsWith('data:image/') | ||
| ); | ||
| } | ||
|
|
||
| function PictureUpload({ aspectRatio = '1:1', value, onFileChange }: PictureUploadProps) { | ||
| const [preview, setPreview] = useState<string>(); | ||
| const inputRef = useRef<HTMLInputElement>(null); | ||
| @@ -15,7 +25,11 @@ | ||
| // value prop์ด string URL์ผ ๋๋ง preview ์ ๋ฐ์ดํธ (์ด๊ธฐ ๋ก๋์ฉ) | ||
| useEffect(() => { | ||
| if (typeof value === 'string') { | ||
| setPreview(value); | ||
| if (isSafeImageSrc(value)) { | ||
| setPreview(value); | ||
| } else { | ||
| setPreview(undefined); | ||
| } | ||
| } | ||
| }, [value]); | ||
|
|
||
| @@ -43,7 +57,7 @@ | ||
| className="overflow-hidden input-common flex-col !justify-center cursor-pointer" | ||
| style={{ height: '5.875rem', width: getWidth(aspectRatio) }} | ||
| > | ||
| {preview ? ( | ||
| {(preview && isSafeImageSrc(preview)) ? ( | ||
| // eslint-disable-next-line @next/next/no-img-element | ||
| <img src={preview} alt="์ ๋ก๋๋ ์ด๋ฏธ์ง" className="object-cover w-full h-full" /> | ||
| ) : ( |

๐ก ๊ฐ์
bsmhub v2/ Inputs of Modal components ๊ตฌํ
๐ ์์ ๋ด์ฉ
์ถ๊ฐํ๊ธฐ ๋ฒํผ,
์ผ๋ฐ ๋ฒํผ(์๊น๊ณผ text props),
๋ค์ํ ํํ์ Input type(text, date, lock, edit, search),
๋ผ๋ฒจ์๋ inpput(required ์ ๋ฌด),
์ฌ์ง ์ ๋ก๋(๋ฏธ๋ฆฌ๋ณด๊ธฐ)
๐ ๋ณ๊ฒฝ์ฌํญ
๐ธ ์คํฌ๋ฆฐ์ท