Skip to content

Commit 3089d01

Browse files
authored
Merge pull request #94 from DevKor-github/feat/#93/post-limit
[#93] post ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ์ œํ•œ ๊ฑธ๊ธฐ
2 parents 5110af6 + 0102abc commit 3089d01

7 files changed

Lines changed: 75 additions & 14 deletions

File tree

โ€Žsrc/features/post/components/InputField/index.tsxโ€Ž

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1+
import { MAX_PRICE } from '@/libs/constants';
12
import * as s from './style.css';
23
import { useState } from 'react';
34

45
interface InputProps<T extends number | string> {
56
className?: string;
67
value: T;
78
setValue: (value: T) => void;
9+
maxLength?: number;
810
}
911

10-
const InputField = <T extends number | string>({ className, value, setValue }: InputProps<T>) => {
12+
const InputField = <T extends number | string>({ className, value, setValue, maxLength }: InputProps<T>) => {
1113
const [isFocused, setIsFocused] = useState(false);
1214

1315
const isNumber = typeof value === 'number';
@@ -19,6 +21,7 @@ const InputField = <T extends number | string>({ className, value, setValue }: I
1921
if (isNumber) {
2022
const number = Number(raw.replace(/,/g, ''));
2123
if (isNaN(number)) return;
24+
if (number > MAX_PRICE) return;
2225
setValue(number as T);
2326
} else {
2427
setValue(raw as T);
@@ -45,6 +48,7 @@ const InputField = <T extends number | string>({ className, value, setValue }: I
4548
onBlur={handleBlur}
4649
inputMode={isNumber ? 'numeric' : 'text'}
4750
pattern={isNumber ? '[0-9]*' : undefined}
51+
maxLength={maxLength}
4852
/>
4953
);
5054
};

โ€Žsrc/features/post/components/StepFunnel/Step3/index.tsxโ€Ž

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Token from '@/common/components/Token';
55
import InputField from '../../InputField';
66
import { useStep3Store } from '@/features/post/stores/Step3Store';
77
import { useStep1Store } from '@/features/post/stores/Step1Store';
8+
import { MAX_LOCATION } from '@/libs/constants';
89

910
const Location = () => {
1011
const locationStore = useStep3Store(state => state.location);
@@ -14,7 +15,7 @@ const Location = () => {
1415
<div className={c.DetailContent}>
1516
<div className={c.DetailContent}>
1617
<span>์ง๊ฑฐ๋ž˜ ์žฅ์†Œ๋ฅผ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”</span>
17-
<InputField value={locationStore} setValue={locationSetter} />
18+
<InputField value={locationStore} setValue={locationSetter} maxLength={MAX_LOCATION} />
1819
</div>
1920
</div>
2021
);

โ€Žsrc/features/post/components/StepFunnel/Step5/index.tsxโ€Ž

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import * as c from '../style.css';
66
import SelectedPhoto from '../../uploadedPhoto';
77
import UploadFile from '../../UploadFile';
88
import { useStep5Store } from '@/features/post/stores/Step5Store';
9+
import { ALLOWED_EXTENSIONS, MAX_DESC, MAX_SIZE_BYTES, MAX_SIZE_MB, MAX_TITLE } from '@/libs/constants';
910

1011
const Step5 = () => {
1112
const fileStore = useStep5Store(state => state.files);
@@ -23,7 +24,22 @@ const Step5 = () => {
2324

2425
const fileArray = Array.from(files);
2526

26-
const updatedFiles = [...fileStore, ...fileArray];
27+
const validFiles = fileArray.filter(file => {
28+
// TODO: alert ์–ด๋–ป๊ฒŒ ๋„์›Œ์ค„์ง€ ๋””์ž์ธ ์š”์ฒญ
29+
const ext = file.name.split('.').pop()?.toLowerCase();
30+
console.log(ext);
31+
if (!ext || !ALLOWED_EXTENSIONS.includes(ext)) {
32+
alert(`"${file.name}"์€(๋Š”) ์ง€์›ํ•˜์ง€ ์•Š๋Š” ํ™•์žฅ์ž์ž…๋‹ˆ๋‹ค.`);
33+
return false;
34+
}
35+
if (file.size > MAX_SIZE_BYTES) {
36+
alert(`"${file.name}"์€(๋Š”) ${MAX_SIZE_MB}MB๋ฅผ ์ดˆ๊ณผํ•ฉ๋‹ˆ๋‹ค.`);
37+
return false;
38+
}
39+
return true;
40+
});
41+
42+
const updatedFiles = [...fileStore, ...validFiles];
2743
fileSetter(updatedFiles);
2844
};
2945

@@ -44,18 +60,24 @@ const Step5 = () => {
4460
<header className={c.Head}>์ƒํ’ˆ ์†Œ๊ฐœ๋ฅผ ์ž‘์„ฑํ•ด ์ฃผ์„ธ์š”</header>
4561
<div className={c.Content}>
4662
<div className={c.DetailContent}>
47-
์ƒํ’ˆ๋ช…์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”
48-
<InputField value={titleStore} setValue={titleSetter}></InputField>
63+
์ƒํ’ˆ๋ช…์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š” ({titleStore.length}/{MAX_TITLE})
64+
<InputField value={titleStore} setValue={titleSetter} maxLength={MAX_TITLE}></InputField>
4965
</div>
5066
<div className={c.DetailContent}>
51-
์ƒํ’ˆ ์„ค๋ช…์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”
67+
์ƒํ’ˆ ์„ค๋ช…์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š” ({descStore.length}/{MAX_DESC})
5268
<div className={s.ProductDesc}>
53-
<MultilineInputfield onChange={handleDesc} value={descStore} />
54-
<div className={s.SelectPhotoContainer}>
55-
<UploadFile onChange={handleImageUploaded} />
56-
{images.map((file, index) => (
57-
<SelectedPhoto key={index} file={file} onClick={() => removeUploadedImage(index)} />
58-
))}
69+
<MultilineInputfield onChange={handleDesc} value={descStore} maxLength={MAX_DESC} />
70+
<div className={s.PhotoLimit}>
71+
<div className={s.SelectPhotoContainer}>
72+
<UploadFile onChange={handleImageUploaded} />
73+
{images.map((file, index) => (
74+
<SelectedPhoto key={index} file={file} onClick={() => removeUploadedImage(index)} />
75+
))}
76+
</div>
77+
<div className={s.AlertText}>
78+
<span className="mgc_alert_octagon_fill"></span>
79+
์‚ฌ์ง„์€ ์ตœ๋Œ€ 6์žฅ๊นŒ์ง€ ๋“ฑ๋ก์ด ๊ฐ€๋Šฅํ•ด์š”.
80+
</div>
5981
</div>
6082
</div>
6183
</div>

โ€Žsrc/features/post/components/StepFunnel/Step5/style.css.tsโ€Ž

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,22 @@ export const SelectPhotoContainer = css({
1313
alignItems: 'center',
1414
flexWrap: 'wrap',
1515
});
16+
17+
export const PhotoLimit = css({
18+
display: 'flex',
19+
flexDir: 'column',
20+
gap: '1rem',
21+
});
22+
23+
export const AlertText = css({
24+
display: 'flex',
25+
alignItems: 'center',
26+
gap: '0.25rem',
27+
color: '80',
28+
fontFamily: 'Pretendard',
29+
fontSize: '0.75rem',
30+
fontStyle: 'normal',
31+
fontWeight: '400',
32+
lineHeight: 'normal',
33+
letterSpacing: '-0.03rem',
34+
});

โ€Žsrc/features/post/components/UploadFile/index.tsxโ€Ž

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { ALLOWED_EXTENSIONS } from '@/libs/constants';
12
import * as s from './style.css';
23

34
interface UploadPhotoProps {
@@ -7,7 +8,13 @@ interface UploadPhotoProps {
78
const UploadFile = ({ onChange }: UploadPhotoProps) => {
89
return (
910
<label className={s.SelectedPhotoBtn}>
10-
<input type="file" accept="image/*" multiple onChange={onChange} className={s.Input} />
11+
<input
12+
type="file"
13+
accept={ALLOWED_EXTENSIONS.map(val => '.' + val).join()}
14+
multiple
15+
onChange={onChange}
16+
className={s.Input}
17+
/>
1118
<div className="mgc_camera_2_fill" />
1219
</label>
1320
);

โ€Žsrc/features/post/stores/Step5Store.tsโ€Ž

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { MAX_FILE_LENGTH } from '@/libs/constants';
12
import { create } from 'zustand';
23

34
// ์ƒํ’ˆ๋ช…, ์ƒํ’ˆ์„ค๋ช…, ์‚ฌ์ง„
@@ -25,7 +26,7 @@ export const useStep5Store = create<Step5Store>((set, get) => ({
2526

2627
isBtnValid: () => {
2728
const { title, desc, files } = get();
28-
return title.trim() !== '' && desc.trim() !== '' && files.length !== 0;
29+
return title.trim() !== '' && desc.trim() !== '' && files.length !== 0 && files.length <= MAX_FILE_LENGTH;
2930
},
3031

3132
reset: () => {
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,8 @@
11
export const MAX_PRICE = 999999;
2+
export const MAX_LOCATION = 100;
3+
export const MAX_TITLE = 64;
4+
export const MAX_DESC = 1000;
5+
export const ALLOWED_EXTENSIONS = ['jpg', 'jpeg', 'png', 'webp'];
6+
export const MAX_SIZE_MB = 5;
7+
export const MAX_SIZE_BYTES = MAX_SIZE_MB * 1024 * 1024;
8+
export const MAX_FILE_LENGTH = 6;

0 commit comments

Comments
ย (0)