Skip to content

Commit

Permalink
[이율] Sprint11 (#733)
Browse files Browse the repository at this point in the history
* feat: 회원가입 기능 구현

* feat: 로그인 기능 추가

* feat: user 정보 불러오기

* feat: 로그인시 헤더 바뀌는 기능 추가

* feat: 게시글 등록(제목, 내용, 이미지) 기능 추가

* feat: 자유게시판 글 좋아요 기능 추가

* feat: 댓글 달기 기능 추가

* Design: no-reply 이미지 추가

* refactor: 로그인 api 연동부분 수정

* feat: localstorage, context 활용 로그인 상태변경

* fix: 중고마켓 상품이미지 기본이미지 나오도록 수정

* feat: 30분마다 refresh하여 로그인 유지되는 기능 추가
  • Loading branch information
yulrang authored Jul 23, 2024
1 parent 6baae0d commit f1871ea
Show file tree
Hide file tree
Showing 27 changed files with 494 additions and 90 deletions.
1 change: 0 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ module.exports = {
],
plugins: ["react", "react-hooks", "@typescript-eslint", "jsx-a11y", "import", "prettier"],
rules: {
"prettier/prettier": "error",
"react/react-in-jsx-scope": "off", // Next.js doesn't require React to be in scope
// 'import/prefer-default-export': 'off',
// '@typescript-eslint/explicit-module-boundary-types': 'off',
Expand Down
2 changes: 1 addition & 1 deletion .prettierrc.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module.exports = {
printWidth: 200,
printWidth: 100,
tabWidth: 2,
};
3 changes: 2 additions & 1 deletion components/BoardList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Article } from "@/src/types/article";
import Link from "next/link";
import WriterInfo from "./WriterInfo";
import Styles from "./BoardList.module.scss";
import ImgProductEmpty from "@/src/img/Img_product_empty-sm.png";

interface articleListProps {
order: string;
Expand Down Expand Up @@ -58,7 +59,7 @@ export function BoardList({ order = "", pageSize = 0, keyword = "", page = undef
</h3>
{article.image && (
<figure className={Styles.image}>
<Image width="72" height="72" src={article.image} alt="이미지" />
<Image width="72" height="72" src={article?.image ?? ImgProductEmpty} alt="이미지" />
</figure>
)}
</div>
Expand Down
15 changes: 12 additions & 3 deletions components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import Link from "next/link";
import Logo from "./Logo";
import GNB from "./GNB";
import { useAuth } from "@/src/contexts/AuthProvider";
import Image from "next/image";
import ImgUser from "@/src/img/ic_profile.svg";

export default function Header() {
const { user, isAuth } = useAuth(true);

return (
<header className="header">
<div className="header-wrap">
Expand All @@ -13,9 +18,13 @@ export default function Header() {
<GNB />
</div>
<div>
<Link href="/signin" className="btn-login">
로그인
</Link>
{isAuth ? (
<Image src={ImgUser} width={40} height={40} alt="유저 이미지" />
) : (
<Link href="/signin" className="btn-login">
로그인
</Link>
)}
</div>
</div>
</header>
Expand Down
8 changes: 5 additions & 3 deletions components/Input/EmailInput.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { ChangeEvent, useState } from "react";
import { ChangeEvent, ChangeEventHandler, useState } from "react";
import Styles from "./Input.module.scss";

interface EmailInputProps {
name: string;
value: string;
onChange: () => void;
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
id: string;
className: string;
required: boolean;
setIsInvalid: (value: boolean) => void;
}

export default function EmailInput({ name, value, id, className, required, setIsInvalid }: EmailInputProps) {
export default function EmailInput({ name, value, id, className, required, setIsInvalid, onChange }: EmailInputProps) {
const [isEmpty, setIsEmpty] = useState(false);

const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
if (e.target.checkValidity()) {
setIsInvalid(false);
Expand All @@ -24,6 +25,7 @@ export default function EmailInput({ name, value, id, className, required, setIs
} else {
setIsEmpty(false);
}
onChange(e);
};
return (
<>
Expand Down
2 changes: 1 addition & 1 deletion components/Input/FileInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export default function FileInput({ name, value, onChange }: FileInputProps) {

{preview && (
<div className={Styles["file-view__preview"]}>
<img src={preview} alt="이미지 미리보기" className={Styles.img} />
<Image src={preview} width={282} height={282} alt="이미지 미리보기" className={Styles.img} />
<button type="button" onClick={handleClearClick} className={Styles["btn-close"]}>
<Image width="8" height="8" src={icoX} alt="아이콘" aria-hidden="true" />
</button>
Expand Down
9 changes: 6 additions & 3 deletions components/Input/PasswordInput.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { ChangeEvent, useState } from "react";
import { ChangeEvent, ChangeEventHandler, useState } from "react";
import Styles from "./Input.module.scss";

interface PasswordInputProps {
id: string;
name: string;
value: string;
className?: string;
required?: boolean;
inputRef: React.RefObject<HTMLInputElement>;
setIsInvalid: (value: boolean) => void;
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
}

export default function PasswordInput({ id, className, required, inputRef, setIsInvalid }: PasswordInputProps) {
export default function PasswordInput({ id, name, value, className, required, inputRef, setIsInvalid, onChange }: PasswordInputProps) {
const [isEmpty, setIsEmpty] = useState(false);

const handleCheck = (e: ChangeEvent<HTMLInputElement>) => {
Expand All @@ -34,7 +37,7 @@ export default function PasswordInput({ id, className, required, inputRef, setIs
return (
<>
<span className="section-form__pw-box">
<input type="password" className={`${Styles.input} ${className}`} placeholder="비밀번호를 입력해주세요" minLength={8} required={required} ref={inputRef} onChange={handleChange} />
<input type="password" name={name} value={value} className={`${Styles.input} ${className}`} placeholder="비밀번호를 입력해주세요" minLength={8} required={required} ref={inputRef} onChange={handleChange} />
<input type="checkbox" id={id} className="blind chk-visibility" onChange={handleCheck} />
<label htmlFor={id} className="spr visibility-off">
<span className="blind">비밀번호 보기/숨기기</span>
Expand Down
1 change: 1 addition & 0 deletions components/Input/TextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ interface TextInputProps {
}

export default function TextInput({ name, value, onChange, id, className, required, placeholder }: TextInputProps) {
const [content, setContent] = useState("");
const [isEmpty, setIsEmpty] = useState(false);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.value.length === 0) {
Expand Down
3 changes: 2 additions & 1 deletion components/ItemCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Image from "next/image";
import icoHeart from "@/src/img/ic_heart.svg";
import { Item } from "@/src/types/item";
import Styles from "./ItemCard.module.scss";
import ImgProductEmpty from "@/src/img/Img_product_empty-sm.png";

interface ItemCardProps {
item: Item;
Expand All @@ -13,7 +14,7 @@ export default function ItemCard({ item }: ItemCardProps) {
<>
<div className={Styles["img-wrap"]}>
<Link href={`/items/${item.id}`} className="link">
<img width="221" height="221" src={item?.images[0]} alt={item.name + " 이미지"} className={Styles.img} />
<Image width="221" height="221" src={item?.images[0] ?? ImgProductEmpty} alt={item.name + " 이미지"} className={Styles.img} />
</Link>
</div>
<div className={Styles.content}>
Expand Down
7 changes: 1 addition & 6 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,4 @@ import globals from "globals";
import pluginJs from "@eslint/js";
import pluginReactConfig from "eslint-plugin-react/configs/recommended.js";


export default [
{languageOptions: { globals: globals.browser }},
pluginJs.configs.recommended,
pluginReactConfig
];
export default [{ languageOptions: { globals: globals.browser } }, pluginJs.configs.recommended, pluginReactConfig];
2 changes: 1 addition & 1 deletion next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ module.exports = {
protocol: "https",
hostname: "example.com",
port: "",
pathname: "/**",
pathname: "/...",
},
{
protocol: "https",
Expand Down
5 changes: 3 additions & 2 deletions pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { AuthProvider } from "@/src/contexts/AuthProvider";
import type { AppProps } from "next/app";
import "src/styles/style.scss";

export default function MyApp({ Component, pageProps }: AppProps) {
return (
<div>
<AuthProvider>
<Component {...pageProps} />
</div>
</AuthProvider>
);
}
63 changes: 34 additions & 29 deletions pages/addboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,23 @@
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import useAsync from "@/src/hooks/useAsync";
import { createItems } from "@/src/api/api";
import { createItems, postArticle, uploadImg } from "@/src/api/api";
import Input from "@/components/Input";
import Button from "@/components/Button/Button";
import Header from "@/components/Header";
import { useAuth } from "@/src/contexts/AuthProvider";

const INITIAL_VALUES = {
name: null,
description: null,
images: null,
};

export default function AddBoardPage({ initialValues = INITIAL_VALUES }) {
export default function AddBoardPage() {
const { user } = useAuth(true);
const [isLoading, loadingError, onSubmitAsync] = useAsync(createItems);
const [isDisableSubmit, setIsDisableSubmit] = useState(true);
const [values, setValues] = useState(initialValues);
const [values, setValues] = useState({
title: "",
content: "",
image: "",
});
const router = useRouter();

const handleChange = (name: keyof typeof INITIAL_VALUES, value: string) => {
const handleChange = (name: string, value: string) => {
setValues((prevValues) => ({
...prevValues,
[name]: value,
Expand All @@ -29,31 +28,37 @@ export default function AddBoardPage({ initialValues = INITIAL_VALUES }) {

const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
handleChange(name as keyof typeof INITIAL_VALUES, value);
handleChange(name, value);
};

const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData();
formData.append("name", values.name || "");
formData.append("description", values.description || "");
formData.append("images", values.images || "");
formData.append("title", values.title);
formData.append("content", values.content);
if (values.image) {
const imgForm = new FormData();
imgForm.append("image", values.image);

if (typeof onSubmitAsync !== "function") {
console.error("onSubmitAsync is not a function");
return;
const response = await uploadImg(imgForm);
if (!response) return;
formData.append("image", response);
}

const result = await onSubmitAsync(formData);
if (!result) return;
const jsonObject: { [key: string]: any } = {};
formData.forEach((value, key) => {
jsonObject[key] = value;
});

router.push("/items");
};
const response = await postArticle(jsonObject);
if (!response) return;

useEffect(() => {
setIsDisableSubmit(Object.values(values).every((el: any) => el !== "" && el !== null && el.length !== 0));
}, [values]);
router.push(`/boards/${response.id}`);
};

if (!user) {
return null;
}
return (
<>
<Header />
Expand All @@ -62,7 +67,7 @@ export default function AddBoardPage({ initialValues = INITIAL_VALUES }) {
<div className="section-wrap">
<header className="section-header">
<h2 className="section-tit">게시글 쓰기</h2>
<Button type="submit" id="submit-article" size="small" disabled={!isDisableSubmit} className="btn-small btn-submit">
<Button type="submit" id="submit-article" size="small" disabled={values.title === "" || values.content === ""} className="btn-small btn-submit">
등록
</Button>
</header>
Expand All @@ -73,7 +78,7 @@ export default function AddBoardPage({ initialValues = INITIAL_VALUES }) {
</sup>
제목
</h3>
<Input.Text name="name" value={values.name} onChange={handleInputChange} placeholder="제목을 입력해주세요" />
<Input.Text name="title" value={values.title} onChange={handleInputChange} placeholder="제목을 입력해주세요" />
</section>
<section className="section-addItem-content">
<h3 className="section-tit">
Expand All @@ -82,11 +87,11 @@ export default function AddBoardPage({ initialValues = INITIAL_VALUES }) {
</sup>
내용
</h3>
<Input.Textarea name="description" value={values.description} onChange={handleInputChange} size="large" placeholder="내용을 입력해주세요" />
<Input.Textarea name="content" value={values.content} onChange={handleInputChange} size="large" placeholder="내용을 입력해주세요" />
</section>
<section className="section-addItem-content">
<h3 className="section-tit">상품 이미지</h3>
<Input.File name="images" value={values.images} onChange={handleChange} />
<Input.File name="image" value={values.image} onChange={handleChange} />
</section>
</div>
</form>
Expand Down
Loading

0 comments on commit f1871ea

Please sign in to comment.