Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
40 changes: 21 additions & 19 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

# production
/build
node_modules
dist
dist-ssr
*.local

# misc
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

npm-debug.log*
yarn-debug.log*
yarn-error.log*
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
# React + Vite

This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.

Currently, two official plugins are available:

- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh

## Expanding the ESLint configuration

If you are developing a production application, we recommend using TypeScript and enable type-aware lint rules. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.

# Getting Started with Create React App

This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
Expand Down Expand Up @@ -68,3 +81,5 @@ This section has moved here: [https://facebook.github.io/create-react-app/docs/d
### `npm run build` fails to minify

This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)

> > > > > > > be83c09a32b62eaaad6a248020d263bc5717e59e
48 changes: 48 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
"classnames": "^2.5.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-error-boundary": "^5.0.0",
"react-hook-form": "^7.55.0",
"react-router-dom": "^7.2.0"
},
"devDependencies": {
Expand All @@ -28,6 +30,5 @@
"postcss": "^8.5.3",
"tailwindcss": "^3.4.17",
"vite": "^6.2.0"

}
}
4 changes: 4 additions & 0 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import ItemsPage from "./pages/ItemsPage";
import FaqPage from "./pages/FaqPage";
import PrivacyPage from "./pages/PrivacyPage";
import SignupPage from "./pages/SignupPage";
import AddItemPage from "./pages/AddItemPage";
import ErrorPage from "./pages/ErrorPage";
import BoardsPage from "./pages/BoardsPage";

function App() {
return (
Expand All @@ -15,8 +17,10 @@ function App() {
<Route path="/login" element={<LoginPage />} />
<Route path="/signup" element={<SignupPage />} />
<Route path="/items" element={<ItemsPage />} />
<Route path="/additem" element={<AddItemPage />} />
<Route path="/faq" element={<FaqPage />} />
<Route path="/privacy" element={<PrivacyPage />} />
<Route path="/boards" element={<BoardsPage />} />
<Route path="*" element={<ErrorPage />} />
</Routes>
</Router>
Expand Down
18 changes: 18 additions & 0 deletions src/Components/Additem/FormField.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from "react";
import FormInput from "../Common/FormInput";

const FormField = ({ label, id, type = "text", placeholder, register }) => {
return (
<div className="mb-[24px]">
<label
htmlFor={id}
className="font-[700] text-[18px] text-[#1F2937] block mb-[8px]"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

또한 기본적인 디자인 토큰은 tailwind config에 넣어서 쓰시는 것이 좋습니다~!

>
{label}
</label>
<FormInput id={id} type={type} placeholder={placeholder} {...register} />
</div>
);
};

export default FormField;
20 changes: 20 additions & 0 deletions src/Components/Additem/FormHeader.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const FormHeader = ({ title, isSubmitEnabled }) => {
return (
<div>
<div className="flex justify-between ">
<div className="text-[20px] font-[700] text-[#1F2937]">{title}</div>
<button
type="submit"
disabled={!isSubmitEnabled}
className={`rounded-[8px] w-[74px] h-[42px] text-[#FFFFFF] ${
isSubmitEnabled ? "bg-[#3692FF]" : "bg-[#9CA3AF] cursor-not-allowed"
}`}
>
등록
</button>
</div>
</div>
);
};

export default FormHeader;
77 changes: 77 additions & 0 deletions src/Components/Additem/ImageUploader.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import addItem from "../../assets/add-item.png";
import xIcon from "../../assets/x-icon.png";

import FormInput from "../Common/FormInput";

const ImageUploader = ({
previewUrl,
setPreviewUrl,
showWarning,
setShowWarning,
imageInputRef,
}) => {
const handleUploadClick = () => {
imageInputRef.current.click();
};

const handleFileChange = (e) => {
const file = e.target.files[0];
if (previewUrl) {
setShowWarning(true);
} else if (file) {
const reader = new FileReader();
reader.onloadend = () => {
setPreviewUrl(reader.result);
setShowWarning(false);
};
reader.readAsDataURL(file);
}
e.target.value = "";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 부분은 이미지를 지우거나, 제출을 완료할 때 들어가야 됩니다~! 지금은 previewUrl만 남고 실제 제출할 데이터는 남지 않게 되네요 🤣

};
return (
<div>
<div className="font-[700] text-[18px] text-[#1F2937] mt-[24px]">
상품 이미지
</div>
<div className="flex gap-[10px] ">
<FormInput
type="file"
accept="image/*"
hidden
ref={imageInputRef}
onChange={handleFileChange}
/>
<button
onClick={handleUploadClick}
className="w-[168px] h-[168px] bg-[#F3F4F6] flex justify-center items-center rounded-[12px] pc:w-[282px] pc:h-[282px] "
>
<img src={addItem} alt="addItem" className="w-[74px] h-[86px]" />
</button>
<div className="w-[168px] h-[168px] relative pc:w-[282px] pc:h-[282px] ">
{previewUrl && (
<>
<img
src={previewUrl}
alt="미리보기"
className="w-[168px] h-[168px] object-cover rounded-[12px] pc:w-[282px] pc:h-[282px]"
/>
<img
src={xIcon}
alt="image delete button"
className="w-[22px] h-[24px] absolute top-[12px] right-[12px]"
onClick={() => setPreviewUrl("")}
/>
</>
)}
</div>
</div>
{showWarning && (
<div className="text-red-500 text-sm mt-[16px]">
*이미지 등록은 최대 1개까지 가능합니다.
</div>
)}
</div>
);
};

export default ImageUploader;
56 changes: 56 additions & 0 deletions src/Components/Additem/TagInput.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { useState } from "react";

const TagInput = ({ tags, setTags }) => {
const [tagInput, setTagInput] = useState("");

const handleKeyDown = (e) => {
if (e.nativeEvent.isComposing) return;

if (e.key === "Enter") {
e.preventDefault();
const trimmed = tagInput.trim();
if (trimmed && !tags.includes(trimmed)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

중복을 걸러낼 때 Set을 써보셔도 좋아요~! :)

setTags([...tags, trimmed]);
}
setTagInput("");
}
};
const removeTag = (index) => {
setTags(tags.filter((_, i) => i !== index));
};
return (
<>
<label htmlFor="tags" className="font-[700] text-[18px] text-[#1F2937]">
태그
</label>
<input
type="text"
value={tagInput}
onChange={(e) => setTagInput(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="태그를 입력해주세요"
className="input-primary"
/>

<div className="flex flex-wrap gap-2 mt-2">
{tags.map((tag, i) => (
<div
key={i}
className="bg-gray-100 rounded-full px-4 py-2 flex items-center gap-2 mb-[60px]"
>
<span className="text-[#1F2937]">#{tag}</span>
<button
type="button"
onClick={() => removeTag(i)}
className="w-5 h-5 flex items-center justify-center rounded-full bg-gray-400 text-white"
>
x
</button>
</div>
))}
</div>
</>
);
};

export default TagInput;
Loading
Loading