ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -19,41 +19,56 @@ export default class Repo {
   }) {
     this.id = id;
     this.name = name;
-    this.full_name = full_name;
+    this.fullName = full_name;
     this.owner = owner;
-    this.html_url = html_url;
+    this.htmlUrl = html_url;
     this.description = description;
-    this.stargazers_count = stargazers_count;
-    this.watchers_count = watchers_count;
-    this.forks_count = forks_count;
+    this.stargazersCount = stargazers_count;
+    this.watchersCount = watchers_count;
+    this.forksCount = forks_count;
     this.forks = forks;
-    this.created_at = created_at;
-    this.updated_at = updated_at;
-    this.clone_url = clone_url;
+    this.createdAt = created_at;
+    this.updatedAt = updated_at;
+    this.cloneUrl = clone_url;
     this.language = language;
     this.watchers = watchers;
     this.visibility = visibility;
   }
   render() {
     return `
-      <div class="col-12 col-sm-6  mb-4" id="repo-${this.id}">
+      <div class="col-12 col-sm-6 mb-4 px-2" id="repo-${this.id}">
         <div class="card border-secondary h-100" >
-          <div class="card-body">
-            <div class="d-flex align-items-center flex-wrap">
-              <h5 class="mb-2 mr-2">
-                ${this.name}
-              </h5>
-              <span class="mb-2 badge bg-light">
+          <div class="card-body d-flex flex-column justify-content-between">
+            <div class="d-flex align-items-center flex-wrap" >
+              <a class="text-info d-flex align-items-center flex-wrap mb-2 mr-2" href="${
+                this.htmlUrl
+              }" target="_blank">
+                <h5>
+                  ${this.name}
+                </h5>
+              </a>
+              <span class="mb-2 badge bg-light mr-2">
                 ${this.visibility}
               </span>
+              <span class="mb-2 d-flex align-items-center">
+                <img alt="레포지토리 스타 아이콘" src="${
+                  this.stargazersCount > 0 ? "star_filled.svg" : "star.svg"
+                }" height="16"/>
+                ${this.stargazersCount}
+              </span>
             </div>
             <p class="card-text">${this.description ?? ""}</p>
             <div>
-              <span class="text-secondary">
+              <span class="text-secondary mr-2">
                 ${this.language ?? ""}
               </span>
+              <span class="text-secondary mr-2">
+                <img src="fork.svg" alt="포크 아이콘" height="18"/>
+                ${this.forks}
+              </span>
               <span class="text-secondary">
-                ${this.forks > 0 ? this.forks : ""}
+                <img src="watch.svg" alt="워치 아이콘" height="18"/>
+                ${this.watchersCount}
               </span>
             </div>
           </div>

리뷰: 넵~ 제가 지금 진행하고 있는 프로젝트의 경우 다 한글로 alt 적어주고 있습니다!
@@ -19,41 +19,56 @@ export default class Repo {
   }) {
     this.id = id;
     this.name = name;
-    this.full_name = full_name;
+    this.fullName = full_name;
     this.owner = owner;
-    this.html_url = html_url;
+    this.htmlUrl = html_url;
     this.description = description;
-    this.stargazers_count = stargazers_count;
-    this.watchers_count = watchers_count;
-    this.forks_count = forks_count;
+    this.stargazersCount = stargazers_count;
+    this.watchersCount = watchers_count;
+    this.forksCount = forks_count;
     this.forks = forks;
-    this.created_at = created_at;
-    this.updated_at = updated_at;
-    this.clone_url = clone_url;
+    this.createdAt = created_at;
+    this.updatedAt = updated_at;
+    this.cloneUrl = clone_url;
     this.language = language;
     this.watchers = watchers;
     this.visibility = visibility;
   }
   render() {
     return `
-      <div class="col-12 col-sm-6  mb-4" id="repo-${this.id}">
+      <div class="col-12 col-sm-6 mb-4 px-2" id="repo-${this.id}">
         <div class="card border-secondary h-100" >
-          <div class="card-body">
-            <div class="d-flex align-items-center flex-wrap">
-              <h5 class="mb-2 mr-2">
-                ${this.name}
-              </h5>
-              <span class="mb-2 badge bg-light">
+          <div class="card-body d-flex flex-column justify-content-between">
+            <div class="d-flex align-items-center flex-wrap" >
+              <a class="text-info d-flex align-items-center flex-wrap mb-2 mr-2" href="${
+                this.htmlUrl
+              }" target="_blank">
+                <h5>
+                  ${this.name}
+                </h5>
+              </a>
+              <span class="mb-2 badge bg-light mr-2">
                 ${this.visibility}
               </span>
+              <span class="mb-2 d-flex align-items-center">
+                <img alt="레포지토리 스타 아이콘" src="${
+                  this.stargazersCount > 0 ? "star_filled.svg" : "star.svg"
+                }" height="16"/>
+                ${this.stargazersCount}
+              </span>
             </div>
             <p class="card-text">${this.description ?? ""}</p>
             <div>
-              <span class="text-secondary">
+              <span class="text-secondary mr-2">
                 ${this.language ?? ""}
               </span>
+              <span class="text-secondary mr-2">
+                <img src="fork.svg" alt="포크 아이콘" height="18"/>
+                ${this.forks}
+              </span>
               <span class="text-secondary">
-                ${this.forks > 0 ? this.forks : ""}
+                <img src="watch.svg" alt="워치 아이콘" height="18"/>
+                ${this.watchersCount}
               </span>
             </div>
           </div>

리뷰: 아하! 참고해서 진행해보도록 하겠습니다~
@@ -0,0 +1,61 @@
+import React, { useState, useEffect } from "react";
+import { useNavigate } from "react-router-dom";
+import "./Login.scss";
+
+const Login = () => {
+  const navigate = useNavigate();
+  const [showErrorMessage, setShowErrorMessage] = useState(false);
+
+  const [userInfo, setUserInfo] = useState({
+    userId: "",
+    userPw: "",
+  });
+
+  const isActive = userInfo.userPw.length >= 5 && userInfo.userId.includes("@");
+
+  useEffect(() => {}, [userInfo, showErrorMessage]);
+
+  const handleInput = (event) => {
+    const { value, id } = event.target;
+    setUserInfo({ ...userInfo, [id]: value });
+  };
+
+  const goToMain = () => {
+    if (isActive) {
+      navigate("/jisun-main");
+    } else {
+      setShowErrorMessage(true);
+    }
+  };
+
+  return (
+    <div className="login">
+      <div className="box">
+        <h1>Westagram</h1>
+        <input
+          type="text"
+          id="userId"
+          onChange={handleInput}
+          placeholder="전화번호, 사용자 이름 또는 이메일"
+        />
+        <input type="password" id="userPw" onChange={handleInput} placeholder="비밀번호" />
+        <button
+          className="btnLogin"
+          onClick={goToMain}
+          style={{ backgroundColor: isActive ? "#2d94ef" : "#67b5fa" }}
+        >
+          로그인
+        </button>
+        <span>또는</span>
+        <button className="btnFacebook">Facebook으로 로그인</button>
+        {showErrorMessage && <p className="err">잘못된 비밀번호입니다. 다시 확인하세요.</p>}
+        <button className="findPassword">비밀번호를 잊으셨나요?</button>
+      </div>
+      <div className="box">
+        계정이 없으신가요? <a href="/signup">가입하기</a>
+      </div>
+    </div>
+  );
+};
+
+export default Login;

리뷰: 더 이상 사용하지 않는 불필요한 코드에 대한 주석은 꼭 삭제해주세요
@@ -0,0 +1,61 @@
+import React, { useState, useEffect } from "react";
+import { useNavigate } from "react-router-dom";
+import "./Login.scss";
+
+const Login = () => {
+  const navigate = useNavigate();
+  const [showErrorMessage, setShowErrorMessage] = useState(false);
+
+  const [userInfo, setUserInfo] = useState({
+    userId: "",
+    userPw: "",
+  });
+
+  const isActive = userInfo.userPw.length >= 5 && userInfo.userId.includes("@");
+
+  useEffect(() => {}, [userInfo, showErrorMessage]);
+
+  const handleInput = (event) => {
+    const { value, id } = event.target;
+    setUserInfo({ ...userInfo, [id]: value });
+  };
+
+  const goToMain = () => {
+    if (isActive) {
+      navigate("/jisun-main");
+    } else {
+      setShowErrorMessage(true);
+    }
+  };
+
+  return (
+    <div className="login">
+      <div className="box">
+        <h1>Westagram</h1>
+        <input
+          type="text"
+          id="userId"
+          onChange={handleInput}
+          placeholder="전화번호, 사용자 이름 또는 이메일"
+        />
+        <input type="password" id="userPw" onChange={handleInput} placeholder="비밀번호" />
+        <button
+          className="btnLogin"
+          onClick={goToMain}
+          style={{ backgroundColor: isActive ? "#2d94ef" : "#67b5fa" }}
+        >
+          로그인
+        </button>
+        <span>또는</span>
+        <button className="btnFacebook">Facebook으로 로그인</button>
+        {showErrorMessage && <p className="err">잘못된 비밀번호입니다. 다시 확인하세요.</p>}
+        <button className="findPassword">비밀번호를 잊으셨나요?</button>
+      </div>
+      <div className="box">
+        계정이 없으신가요? <a href="/signup">가입하기</a>
+      </div>
+    </div>
+  );
+};
+
+export default Login;

리뷰: 콘솔도 마찬가지입니다 테스트가 끝난 코드는 항상 지워주세요!
아래에 남아있는 모든 콘솔은 삭제 해주세요
@@ -0,0 +1,61 @@
+import React, { useState, useEffect } from "react";
+import { useNavigate } from "react-router-dom";
+import "./Login.scss";
+
+const Login = () => {
+  const navigate = useNavigate();
+  const [showErrorMessage, setShowErrorMessage] = useState(false);
+
+  const [userInfo, setUserInfo] = useState({
+    userId: "",
+    userPw: "",
+  });
+
+  const isActive = userInfo.userPw.length >= 5 && userInfo.userId.includes("@");
+
+  useEffect(() => {}, [userInfo, showErrorMessage]);
+
+  const handleInput = (event) => {
+    const { value, id } = event.target;
+    setUserInfo({ ...userInfo, [id]: value });
+  };
+
+  const goToMain = () => {
+    if (isActive) {
+      navigate("/jisun-main");
+    } else {
+      setShowErrorMessage(true);
+    }
+  };
+
+  return (
+    <div className="login">
+      <div className="box">
+        <h1>Westagram</h1>
+        <input
+          type="text"
+          id="userId"
+          onChange={handleInput}
+          placeholder="전화번호, 사용자 이름 또는 이메일"
+        />
+        <input type="password" id="userPw" onChange={handleInput} placeholder="비밀번호" />
+        <button
+          className="btnLogin"
+          onClick={goToMain}
+          style={{ backgroundColor: isActive ? "#2d94ef" : "#67b5fa" }}
+        >
+          로그인
+        </button>
+        <span>또는</span>
+        <button className="btnFacebook">Facebook으로 로그인</button>
+        {showErrorMessage && <p className="err">잘못된 비밀번호입니다. 다시 확인하세요.</p>}
+        <button className="findPassword">비밀번호를 잊으셨나요?</button>
+      </div>
+      <div className="box">
+        계정이 없으신가요? <a href="/signup">가입하기</a>
+      </div>
+    </div>
+  );
+};
+
+export default Login;

리뷰: 👍 useEffect 까지 활용해보셨군요! 
condition 변수가 userInfo라는 state를 이미 참조한 값이기때문에 isActive라는 값을 따로 state로 관리하지않아도 
condition 변수가 참조하고있는 값이 이미 state라서 state변화에 따라 즉각적으로 다른 값을 가질 수 있습니다.

isActive를 state가 아닌 일반변수로 관리해보시는게 어떨까요?
@@ -0,0 +1,61 @@
+import React, { useState, useEffect } from "react";
+import { useNavigate } from "react-router-dom";
+import "./Login.scss";
+
+const Login = () => {
+  const navigate = useNavigate();
+  const [showErrorMessage, setShowErrorMessage] = useState(false);
+
+  const [userInfo, setUserInfo] = useState({
+    userId: "",
+    userPw: "",
+  });
+
+  const isActive = userInfo.userPw.length >= 5 && userInfo.userId.includes("@");
+
+  useEffect(() => {}, [userInfo, showErrorMessage]);
+
+  const handleInput = (event) => {
+    const { value, id } = event.target;
+    setUserInfo({ ...userInfo, [id]: value });
+  };
+
+  const goToMain = () => {
+    if (isActive) {
+      navigate("/jisun-main");
+    } else {
+      setShowErrorMessage(true);
+    }
+  };
+
+  return (
+    <div className="login">
+      <div className="box">
+        <h1>Westagram</h1>
+        <input
+          type="text"
+          id="userId"
+          onChange={handleInput}
+          placeholder="전화번호, 사용자 이름 또는 이메일"
+        />
+        <input type="password" id="userPw" onChange={handleInput} placeholder="비밀번호" />
+        <button
+          className="btnLogin"
+          onClick={goToMain}
+          style={{ backgroundColor: isActive ? "#2d94ef" : "#67b5fa" }}
+        >
+          로그인
+        </button>
+        <span>또는</span>
+        <button className="btnFacebook">Facebook으로 로그인</button>
+        {showErrorMessage && <p className="err">잘못된 비밀번호입니다. 다시 확인하세요.</p>}
+        <button className="findPassword">비밀번호를 잊으셨나요?</button>
+      </div>
+      <div className="box">
+        계정이 없으신가요? <a href="/signup">가입하기</a>
+      </div>
+    </div>
+  );
+};
+
+export default Login;

리뷰: 👍 조건부랜더링 활용해보셨군요!! 오히려 showErrorMessage를 관리하는 함수를 useEffect함수 내에서 선언한 condition값에 따라 다르게 관리해볼 수 있겠습니다!
@@ -0,0 +1,144 @@
+.login {
+  width: 100%;
+  height: 100vh;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+
+  .box {
+    padding: 25px 40px;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    width: 100%;
+    max-width: 350px;
+    box-sizing: border-box;
+    border: 1px solid #dbdbdb;
+
+    &:first-of-type {
+      margin-bottom: 12px;
+    }
+
+    h1 {
+      margin: 15px 0 40px;
+      font-family: "Lobster", cursive;
+      font-weight: 100;
+      font-size: 42px;
+    }
+
+    input {
+      padding: 8px 8px;
+      width: 100%;
+      background: #fafafa;
+      color: #737373;
+      font-size: 12px;
+      box-sizing: border-box;
+      border: 1px solid #dbdbdb;
+      border-radius: 4px;
+
+      &:first-of-type {
+        margin-bottom: 7px;
+      }
+
+      &:last-of-type {
+        margin-bottom: 14px;
+      }
+    }
+
+    span {
+      margin: 20px 0 30px;
+      padding: 0 20px;
+      position: relative;
+      font-size: 12px;
+      color: #737373;
+
+      &:before {
+        content: "";
+        position: absolute;
+        top: 50%;
+        right: -110px;
+        width: 110px;
+        height: 1px;
+        background: #dbdbdb;
+      }
+
+      &:after {
+        content: "";
+        position: absolute;
+        top: 50%;
+        left: -110px;
+        width: 110px;
+        height: 1px;
+        background: #dbdbdb;
+      }
+    }
+
+    .btnLogin {
+      padding: 10px 0;
+      width: 100%;
+      background: #67b5fa;
+      color: #fff;
+      font-size: 14px;
+      font-weight: 500;
+      border: none;
+      border-radius: 8px;
+
+      // &:hover {
+      //   background: #2d94ef;
+      // }
+    }
+
+    .btnLoginChange {
+      background: #2d94ef;
+    }
+
+    .btnFacebook {
+      padding-left: 24px;
+      position: relative;
+      height: 16px;
+      background: none;
+      color: #375085;
+      border: none;
+      font-size: 14px;
+      font-weight: 600;
+
+      &:before {
+        content: "";
+        position: absolute;
+        left: 0;
+        width: 16px;
+        height: 16px;
+        background: url("../../../assets/jisun/Login/icon_facebook.png") no-repeat;
+      }
+    }
+
+    .err {
+      margin: 35px 0 10px;
+      color: #ed4956;
+      font-size: 14px;
+    }
+
+    .findPassword {
+      margin-top: 25px;
+      color: #375085;
+      font-size: 12px;
+      font-weight: 600;
+      background: none;
+      border: none;
+    }
+  }
+
+  .box:last-of-type {
+    flex-direction: row;
+    justify-content: center;
+    color: #000;
+    font-size: 13px;
+    font-weight: 500;
+
+    &:last-of-type > a {
+      margin-left: 4px;
+      color: #0095f6;
+    }
+  }
+}

리뷰: 웹폰트 같은경우는 index.html파일에서 추가할 수도 있고
common.scss에서 관리할 수도 있습니다
@@ -78,6 +78,7 @@ public List<ReviewPhoto> createReviewPhoto(Long reviewId, List<MultipartFile> re
         List<ReviewPhoto> reviewPhotoList = new ArrayList<>();
         List<String> photoUrls = new ArrayList<>();
 
+        if(reviewPhotos == null) return reviewPhotoList;
         reviewPhotos.forEach(multipartFile -> {
             try {
                 photoUrls.add(s3Util.upload(multipartFile));
@@ -87,7 +88,6 @@ public List<ReviewPhoto> createReviewPhoto(Long reviewId, List<MultipartFile> re
         });
 
         photoUrls.forEach(photoUrl -> reviewPhotoList.add(reviewPhotoPort.save(new ReviewPhoto(reviewId, photoUrl))));
-
         return reviewPhotoList;
     }
 

리뷰: 이 부분은 어떤 의미인가요?
@@ -0,0 +1,34 @@
+package com.luckytree.shop.utils;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Type;
+
+@Component
+public class MultipartJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {
+
+    /**
+     * Converter for support http request with header Content-Type: multipart/form-data
+     */
+    public MultipartJackson2HttpMessageConverter(ObjectMapper objectMapper) {
+        super(objectMapper, MediaType.APPLICATION_OCTET_STREAM);
+    }
+
+    @Override
+    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
+        return false;
+    }
+
+    @Override
+    public boolean canWrite(Type type, Class<?> clazz, MediaType mediaType) {
+        return false;
+    }
+
+    @Override
+    protected boolean canWrite(MediaType mediaType) {
+        return false;
+    }
+}

리뷰: 이 클래스를 사용하는 곳이 어딘가요?
@@ -32,12 +32,11 @@ public class ReviewController {
     private final ReviewUseCase reviewUseCase;
 
     @Operation(summary = "리뷰 등록")
-    @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+    @PostMapping(consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE})
     public ResponseEntity<CreateReviewResponse> createReview(
-            @RequestBody @Valid CreateReviewRequest createReviewRequest,
-            @RequestPart("reviewPhotos") @Valid List<MultipartFile> reviewPhotos) {
+            @RequestPart("createReviewRequest") @Valid CreateReviewRequest createReviewRequest,
+            @RequestPart(value = "reviewPhotos", required = false) @Valid List<MultipartFile> reviewPhotos) {
         Review review = reviewUseCase.create(createReviewRequest.toDomain(Long.valueOf(SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString())));
-
         List<ReviewPhoto> reviewPhotoList = reviewUseCase.createReviewPhoto(review.getId(), reviewPhotos);
 
         return ResponseEntity.ok(new CreateReviewResponse(review, reviewPhotoList));

리뷰: 리퀘스트바디가 사라졌는데, 이 경우에도 클라이언트에서 JSON 형식의 Body 태그 데이터를 받아오는 데에 문제가 없나요?
@@ -78,6 +78,7 @@ public List<ReviewPhoto> createReviewPhoto(Long reviewId, List<MultipartFile> re
         List<ReviewPhoto> reviewPhotoList = new ArrayList<>();
         List<String> photoUrls = new ArrayList<>();
 
+        if(reviewPhotos == null) return reviewPhotoList;
         reviewPhotos.forEach(multipartFile -> {
             try {
                 photoUrls.add(s3Util.upload(multipartFile));
@@ -87,7 +88,6 @@ public List<ReviewPhoto> createReviewPhoto(Long reviewId, List<MultipartFile> re
         });
 
         photoUrls.forEach(photoUrl -> reviewPhotoList.add(reviewPhotoPort.save(new ReviewPhoto(reviewId, photoUrl))));
-
         return reviewPhotoList;
     }
 

리뷰: 리뷰 생성 시 포토를 선택하지 않아도 이 서비스가 실행되어서 선택되지 않았을 때 바로 빠져나가게 한겁니다
@@ -0,0 +1,34 @@
+package com.luckytree.shop.utils;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Type;
+
+@Component
+public class MultipartJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {
+
+    /**
+     * Converter for support http request with header Content-Type: multipart/form-data
+     */
+    public MultipartJackson2HttpMessageConverter(ObjectMapper objectMapper) {
+        super(objectMapper, MediaType.APPLICATION_OCTET_STREAM);
+    }
+
+    @Override
+    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
+        return false;
+    }
+
+    @Override
+    public boolean canWrite(Type type, Class<?> clazz, MediaType mediaType) {
+        return false;
+    }
+
+    @Override
+    protected boolean canWrite(MediaType mediaType) {
+        return false;
+    }
+}

리뷰: swagger에서 리뷰 생성 API에 multipartfile을 사용해서 그거 사용하게 만들어주는 클래스입니다
@@ -32,12 +32,11 @@ public class ReviewController {
     private final ReviewUseCase reviewUseCase;
 
     @Operation(summary = "리뷰 등록")
-    @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+    @PostMapping(consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE})
     public ResponseEntity<CreateReviewResponse> createReview(
-            @RequestBody @Valid CreateReviewRequest createReviewRequest,
-            @RequestPart("reviewPhotos") @Valid List<MultipartFile> reviewPhotos) {
+            @RequestPart("createReviewRequest") @Valid CreateReviewRequest createReviewRequest,
+            @RequestPart(value = "reviewPhotos", required = false) @Valid List<MultipartFile> reviewPhotos) {
         Review review = reviewUseCase.create(createReviewRequest.toDomain(Long.valueOf(SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString())));
-
         List<ReviewPhoto> reviewPhotoList = reviewUseCase.createReviewPhoto(review.getId(), reviewPhotos);
 
         return ResponseEntity.ok(new CreateReviewResponse(review, reviewPhotoList));

리뷰: swagger랑 postman에서 확인했고 오히려 리퀘스트바디를 썼을 때 안돼서 바꿨습니다. 이유는 찾아보겠습니다
@@ -0,0 +1,43 @@
+//
+//  PopcornOrangeButton.swift
+//  Popcorn-iOS
+//
+//  Created by 제민우 on 1/12/25.
+//
+
+import UIKit
+
+final class PopcornOrangeButton: UIButton {
+    init(text: String, isEnabled: Bool) {
+        super.init(frame: .zero)
+        configureInitialSetting(isEnabled: isEnabled)
+        configureButtonUI(text: text)
+    }
+
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+}
+
+// MARK: - Configure Initial Setting
+extension PopcornOrangeButton {
+    private func configureInitialSetting(isEnabled: Bool) {
+        self.isEnabled = isEnabled
+        self.cornerRadius(radius: 10)
+    }
+
+    private func configureButtonUI(text: String) {
+        var config = UIButton.Configuration.filled()
+        config.baseBackgroundColor = isEnabled == true
+        ? UIColor(resource: .popcornOrange)
+        : UIColor(resource: .popcornGray2)
+        config.attributedTitle = AttributedString(
+            text,
+            attributes: AttributeContainer([
+                .font: UIFont(name: RobotoFontName.robotoSemiBold, size: 15)!,
+                .foregroundColor: UIColor(.white)
+            ])
+        )
+        configuration = config
+    }
+}

리뷰: 저도 조만간 이걸로 리팩토링하겠습니다!!
@@ -0,0 +1,43 @@
+//
+//  PopcornOrangeButton.swift
+//  Popcorn-iOS
+//
+//  Created by 제민우 on 1/12/25.
+//
+
+import UIKit
+
+final class PopcornOrangeButton: UIButton {
+    init(text: String, isEnabled: Bool) {
+        super.init(frame: .zero)
+        configureInitialSetting(isEnabled: isEnabled)
+        configureButtonUI(text: text)
+    }
+
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+}
+
+// MARK: - Configure Initial Setting
+extension PopcornOrangeButton {
+    private func configureInitialSetting(isEnabled: Bool) {
+        self.isEnabled = isEnabled
+        self.cornerRadius(radius: 10)
+    }
+
+    private func configureButtonUI(text: String) {
+        var config = UIButton.Configuration.filled()
+        config.baseBackgroundColor = isEnabled == true
+        ? UIColor(resource: .popcornOrange)
+        : UIColor(resource: .popcornGray2)
+        config.attributedTitle = AttributedString(
+            text,
+            attributes: AttributeContainer([
+                .font: UIFont(name: RobotoFontName.robotoSemiBold, size: 15)!,
+                .foregroundColor: UIColor(.white)
+            ])
+        )
+        configuration = config
+    }
+}

리뷰: 넵! 나중에 화면 연결 시 내비게이션 바 구현하려고 하였습니다!