diff --git a/.github/workflows/cicd-be.yml b/.github/workflows/cicd-be.yml new file mode 100644 index 00000000..362c33fc --- /dev/null +++ b/.github/workflows/cicd-be.yml @@ -0,0 +1,112 @@ +name: Backend CI/CD + +on: + push: + branches: [ main ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Check out Repository + uses: actions/checkout@v3 + with: + token: ${{ secrets.ACTION_TOKEN }} + submodules: true + + - name: Display first three lines of application.yml (debug) + run: head -n 3 src/main/resources/application.yml + + - name: Set up JDK21 + uses: actions/setup-java@v3 + with: + distribution: 'corretto' + java-version: '21' + + - name: Gradle 캐싱 + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Build with Gradle + run: ./gradlew clean build + + - name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: cicdsample + path: build/libs/*.jar + + - name: Slack notification when build fail + if: failure() + uses: 8398a7/action-slack@v3 + with: + status: ${{ job.status }} + author_name: [CI/CD] 백엔드 빌드 실패 + fields: repo, message, commit, author, action, eventName, ref, workflow, job, took + env: + SLACK_COLOR: '#FF2D00' + SLACK_USERNAME: 'Github Action' + SLACK_ICON: https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL_CICD }} + + deploy: + needs: build + runs-on: ubuntu-latest + steps: + - name: Download artifact + uses: actions/download-artifact@v2 + with: + name: cicdsample + + - name: Setup SSH + uses: webfactory/ssh-agent@v0.5.4 + with: + ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} + + - name: Add known hosts + run: | + ssh-keyscan -H ${{ secrets.SERVER_IP }} >> ~/.ssh/known_hosts + chmod 644 ~/.ssh/known_hosts + + - name: SCP transfer + run: scp *.jar ${{ secrets.SSH_USER }}@${{ secrets.SERVER_IP }}:~/deploy + + - name: Execute remote shell script + run: | + ssh ${{ secrets.SSH_USER }}@${{ secrets.SERVER_IP }} "chmod +x ./deploy.sh" + ssh ${{ secrets.SSH_USER }}@${{ secrets.SERVER_IP }} "./deploy.sh" + + - name: Slack notification when deploy fail + if: failure() + uses: 8398a7/action-slack@v3 + with: + status: ${{ job.status }} + author_name: [CI/CD] 백엔드 배포 실패 + fields: repo, message, commit, author, action, eventName, ref, workflow, job, took + env: + SLACK_COLOR: '#FF2D00' + SLACK_USERNAME: 'Github Action' + SLACK_ICON: https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL_CICD }} + + - name: Slack notification when deploy success + if: success() + uses: 8398a7/action-slack@v3 + with: + status: ${{ job.status }} + author_name: [CI/CD] 백엔드 배포 성공 + fields: repo, message, commit, author, action, eventName, ref, workflow, job, took + env: + SLACK_COLOR: '#0019F4' + SLACK_USERNAME: 'Github Action' + SLACK_ICON: https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL_CICD }} diff --git a/.github/workflows/test-fe.yml b/.github/workflows/test-fe.yml deleted file mode 100644 index 4c2de232..00000000 --- a/.github/workflows/test-fe.yml +++ /dev/null @@ -1,76 +0,0 @@ -name: Frontend PR Test - -on: - pull_request: - branches: - - main - - develop - paths: - - '.github/**' - - 'frontend/**' - -jobs: - test: - runs-on: ubuntu-latest - timeout-minutes: 10 - - permissions: - checks: write - pull-requests: write - - steps: - - name: Repository 체크아웃 - uses: actions/checkout@v3 - - - name: Node 설정 - uses: actions/setup-node@v3 - with: - node-version: '18.16.1' - - - name: node_modules 캐싱 - id: cache - uses: actions/cache@v3 - with: - path: '**/frontend/node_modules' - key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-node- - - - name: 의존성 설치 - working-directory: frontend/ - if: steps.cache.outputs.cache-hit != 'true' - run: yarn install --pure-lockfile - - - name: 테스트 실행 - working-directory: frontend/ - run: yarn test - continue-on-error: true - - - name: 테스트 결과 PR에 코멘트 등록 - uses: EnricoMi/publish-unit-test-result-action@v2 - if: always() - with: - files: '**/frontend/test-results/results.xml' - - - name: 테스트 실패 시, 실패한 코드 라인에 Check 코멘트를 등록 - uses: mikepenz/action-junit-report@v3 - if: always() - with: - report_paths: '**/frontend/test-results/results.xml' - token: ${{ github.token }} - - - name: build 실패 시 Slack으로 알립니다 - uses: 8398a7/action-slack@v3 - with: - status: ${{ job.status }} - author_name: 프론트엔드 테스트 실패 알림 - fields: repo, message, commit, author, action, eventName, ref, workflow, job, took - env: - SLACK_CHANNEL: group-dev - SLACK_COLOR: '#FF2D00' - SLACK_USERNAME: 'Github Action' - SLACK_ICON: https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png - SLACK_TITLE: Build Failure - ${{ github.event.pull_request.title }} - SLACK_MESSAGE: PR Url - ${{ github.event.pull_request.url }} - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} - if: failure() diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 160ab01e..bd060e54 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,9 +5,6 @@ on: branches: - main - develop - paths: - - '.github/**' - - 'backend/**' jobs: test: @@ -22,11 +19,11 @@ jobs: - name: Repository 체크아웃 uses: actions/checkout@v3 - - name: JDK 11 설정 + - name: JDK 21 설정 uses: actions/setup-java@v3 with: - java-version: 11 - distribution: temurin + java-version: 21 + distribution: corretto - name: Gradle 캐싱 uses: actions/cache@v3 @@ -39,11 +36,9 @@ jobs: ${{ runner.os }}-gradle- - name: Gradle 권한 부여 - working-directory: backend/ - run: chmod +x ./gradlew + run: chmod +x gradlew - name: 테스트 실행 - working-directory: backend/ run: ./gradlew --info test - name: 테스트 결과 PR에 코멘트 등록 @@ -72,5 +67,5 @@ jobs: SLACK_ICON: https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png SLACK_TITLE: Build Failure - ${{ github.event.pull_request.title }} SLACK_MESSAGE: PR Url - ${{ github.event.pull_request.url }} - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL_PR_TEST }} if: failure() diff --git a/backend/.gitignore b/.gitignore similarity index 100% rename from backend/.gitignore rename to .gitignore diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..8e4ce64a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "src/main/resources"] + path = src/main/resources + url = git@github.com:fun-eat/funeat-env.git + branch = main diff --git a/README.md b/README.md deleted file mode 100644 index a3819192..00000000 --- a/README.md +++ /dev/null @@ -1,107 +0,0 @@ -
- -
- - - -
-
- -궁금해? 맛있을걸? 먹어봐!
-🍙 편의점 음식 리뷰 & 꿀조합 공유 서비스 🍙
- -
- -[![Application](http://img.shields.io/badge/funeat.site-D8EAFF?style=for-the-badge&logo=aHR0cHM6Ly9naXRodWIuY29tL3dvb3dhY291cnNlLXRlYW1zLzIwMjMtZnVuLWVhdC9hc3NldHMvODA0NjQ5NjEvOWI1OWY3NzktY2M5MS00MTJhLWE3NDUtZGQ3M2IzY2UxZGNk&logoColor=black&link=https://funeat.site/)](https://funeat.site/) -[![WIKI](http://img.shields.io/badge/-GitHub%20WiKi-FFEC99?style=for-the-badge&logoColor=black&link=https://github.com/woowacourse-teams/2023-fun-eat/wiki)](https://github.com/woowacourse-teams/2023-fun-eat/wiki) -[![Release](https://img.shields.io/github/v/release/woowacourse-teams/2023-fun-eat?style=for-the-badge&color=FFCFCF)](https://github.com/woowacourse-teams/2023-fun-eat/releases/tag/v1.3.0) - -
- -
- -# 🥄 서비스 소개 - -![1_메인페이지](https://github.com/woowacourse-teams/2023-fun-eat/assets/55427367/9663f7b5-cd38-4f06-86fb-c6636fc364c6) - -
- -## 1. 편의점마다 특색있는 음식 궁금해? - -![5_상품목록](https://github.com/woowacourse-teams/2023-fun-eat/assets/55427367/03fb9955-61fa-4228-a270-ce9dffc710c6) -![6_상품상세](https://github.com/woowacourse-teams/2023-fun-eat/assets/55427367/694bc8db-74bd-4fa1-b499-900cd27f5028) -![4_검색](https://github.com/woowacourse-teams/2023-fun-eat/assets/55427367/6a157e08-79d8-450b-9511-ffa461000a22) - -
-
- -## 2. 솔직한 리뷰를 보면 더 맛있을걸? - -![2_리뷰](https://github.com/woowacourse-teams/2023-fun-eat/assets/55427367/4bf5ecd7-df08-45d0-b592-8629f3a4e3e6) - -
-
- -## 3. 생각지 못했던 꿀조합, 먹어봐! - -![3_꿀조합](https://github.com/woowacourse-teams/2023-fun-eat/assets/55427367/8e560b40-d039-47ce-ad29-5e244cba4bf2) - -
-
- -# 🛠️ 기술 스택 - -### 백엔드 - -
- BE_기술스택 -
- -
- -### 프론트엔드 - -
- FE_기술스택 -
- -
- -### 인프라 - -
- 인프라_기술스택 -
- -
-
- -# 인프라 구조 - -### CI/CD - -
- cicd -
- -### 구조 - -
- 인프라 구조 -
- -
-
- -# 👨‍👨‍👧‍👧👩‍👦‍👦 팀원 - -| Frontend | Frontend | Frontend | Backend | Backend | Backend | Backend | -| :-------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------: | -| 타미 | 해온 | 황펭 | 로건 | 망고 | 오잉 | 우가 | -| [🐰 타미](https://github.com/xodms0309) | [🌞 해온](https://github.com/hae-on) | [🐧 황펭](https://github.com/Leejin-Yang) | [😺 로건](https://github.com/70825) | [🥭 망고](https://github.com/Go-Jaecheol) | [👻 오잉](https://github.com/hanueleee) | [🍖 우가](https://github.com/wugawuga) | - -
- -
- 팀소개 -
diff --git a/backend/build.gradle b/backend/build.gradle deleted file mode 100644 index 3d46c806..00000000 --- a/backend/build.gradle +++ /dev/null @@ -1,40 +0,0 @@ -plugins { - id 'java' - id 'org.springframework.boot' version '2.7.13' - id 'io.spring.dependency-management' version '1.0.15.RELEASE' -} - -group = 'com.funeat' -version = '0.0.1-SNAPSHOT' - -java { - sourceCompatibility = '11' -} - -repositories { - mavenCentral() -} - -dependencies { - implementation 'org.springframework.boot:spring-boot-starter-validation' - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - implementation 'org.springframework.boot:spring-boot-starter-web' - runtimeOnly 'com.mysql:mysql-connector-j' - testImplementation 'org.springframework.boot:spring-boot-starter-test' - testImplementation 'io.rest-assured:rest-assured:4.4.0' - testRuntimeOnly 'com.h2database:h2' - - implementation 'org.springdoc:springdoc-openapi-ui:1.7.0' - implementation 'com.github.maricn:logback-slack-appender:1.4.0' - - implementation 'org.springframework.boot:spring-boot-starter-actuator' - runtimeOnly 'io.micrometer:micrometer-registry-prometheus' - - implementation 'com.amazonaws:aws-java-sdk-s3:1.12.547' - - implementation 'org.springframework.session:spring-session-jdbc' -} - -tasks.named('test') { - useJUnitPlatform() -} diff --git a/backend/settings.gradle b/backend/settings.gradle deleted file mode 100644 index 7f580408..00000000 --- a/backend/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = 'funeat' diff --git a/backend/src/main/java/com/funeat/product/dto/SearchProductsResponse.java b/backend/src/main/java/com/funeat/product/dto/SearchProductsResponse.java deleted file mode 100644 index ccdeade5..00000000 --- a/backend/src/main/java/com/funeat/product/dto/SearchProductsResponse.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.funeat.product.dto; - -import com.funeat.common.dto.PageDto; -import java.util.List; - -public class SearchProductsResponse { - - private final PageDto page; - private final List products; - - public SearchProductsResponse(final PageDto page, final List products) { - this.page = page; - this.products = products; - } - - public static SearchProductsResponse toResponse(final PageDto page, final List products) { - return new SearchProductsResponse(page, products); - } - - public PageDto getPage() { - return page; - } - - public List getProducts() { - return products; - } -} diff --git a/backend/src/main/resources/application-dev.yml b/backend/src/main/resources/application-dev.yml deleted file mode 100644 index 0c1a7222..00000000 --- a/backend/src/main/resources/application-dev.yml +++ /dev/null @@ -1,46 +0,0 @@ -spring: - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: { DEV_DB_URL } - username: { DEV_DB_USERNAME } - password: { DEV_DB_PASSWORD } - - jpa: - hibernate: - ddl-auto: update - properties: - hibernate: - format_sql: true - show_sql: true - -kakao: - rest-api-key: { DEV_REST_API_KEY } - redirect-uri: { DEV_REDIRECT_URI } - admin-key: { DEV_ADMIN_KEY } - -management: - endpoints: - enabled-by-default: false - web: - exposure: - include: health, metrics, prometheus - base-path: { ACTUATOR_BASE_PATH } - jmx: - exposure: - exclude: "*" - endpoint: - health: - enabled: true - metrics: - enabled: true - prometheus: - enabled: true - -cloud: - aws: - region: - static: { S3_REGION } - s3: - bucket: { S3_BUCKET } - folder: { S3_DEV_FOLDER } - cloudfrontPath: { S3_DEV_CLOUDFRONT_PATH } diff --git a/backend/src/main/resources/application-local.yml b/backend/src/main/resources/application-local.yml deleted file mode 100644 index 27ba9bcc..00000000 --- a/backend/src/main/resources/application-local.yml +++ /dev/null @@ -1,31 +0,0 @@ -spring: - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: - username: - password: - - jpa: - hibernate: - ddl-auto: create - properties: - hibernate: - format_sql: true - show_sql: true -logging: - level: - org.hibernate.type.descriptor.sql: trace - -kakao: - rest-api-key: { LOCAL_REST_API_KEY } - redirect-uri: { LOCAL_REDIRECT_URI } - admin-key: { LOCAL_ADMIN_KEY } - -cloud: - aws: - region: - static: { S3_REGION } - s3: - bucket: { S3_BUCKET } - folder: { S3_LOCAL_FOLDER } - cloudfrontPath: { S3_LOCAL_CLOUDFRONT_PATH } diff --git a/backend/src/main/resources/application-prod.yml b/backend/src/main/resources/application-prod.yml deleted file mode 100644 index 1943e2bf..00000000 --- a/backend/src/main/resources/application-prod.yml +++ /dev/null @@ -1,45 +0,0 @@ -spring: - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: { PROD_DB_URL } - username: { PROD_DB_USERNAME } - password: { PROD_DB_PASSWORD } - - jpa: - hibernate: - ddl-auto: none - properties: - hibernate: - show_sql: true - -kakao: - rest-api-key: { PROD_REST_API_KEY } - redirect-uri: { PROD_REDIRECT_URI } - admin-key: { PROD_ADMIN_KEY } - -management: - endpoints: - enabled-by-default: false - web: - exposure: - include: health, metrics, prometheus - base-path: { ACTUATOR_BASE_PATH } - jmx: - exposure: - exclude: "*" - endpoint: - health: - enabled: true - metrics: - enabled: true - prometheus: - enabled: true - -cloud: - aws: - region: - static: { S3_REGION } - s3: - bucket: { S3_BUCKET } - folder: { S3_PROD_FOLDER } - cloudfrontPath: { S3_PROD_CLOUDFRONT_PATH } diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml deleted file mode 100644 index 941adb9c..00000000 --- a/backend/src/main/resources/application.yml +++ /dev/null @@ -1,42 +0,0 @@ -spring: - profiles: - active: { DEPLOY_ACTIVE } - servlet: - multipart: - enabled: true - maxFileSize: 10MB - maxRequestSize: 15MB - task: - execution: - pool: - core-size: { THREAD_CORE_SIZE } - max-size: { THREAD_MAX_SIZE } - session: - store-type: jdbc - jdbc: - initialize-schema: never - datasource: - hikari: - connection-timeout: { CONNECTION_TIMEOUT } - maximum-pool-size: { MAXIMUM_POOL_SIZE } - -springdoc: - swagger-ui: - path: /funeat-api - enabled: true - tags-sorter: alpha - -logging: - file: - path: { LOG_DIR } - -server: - tomcat: - threads: - max: { MAX_THREADS } - max-connections: { MAX_CONNECTIONS } - accept-count: { ACCEPT_COUNT } - -back-office: - id: { BACK_OFFICE_ID } - key: { BACK_OFFICE_KEY } diff --git a/backend/src/main/resources/logback-spring-dev.xml b/backend/src/main/resources/logback-spring-dev.xml deleted file mode 100644 index b86839da..00000000 --- a/backend/src/main/resources/logback-spring-dev.xml +++ /dev/null @@ -1,121 +0,0 @@ - - - - - - - - ${dev_slack_webhook_uri} - - ${log_pattern} - - open-macbook - :face_with_symbols_on_mouth: - true - - - - - - WARN - - - - - - INFO - ACCEPT - DENY - - ${log_dir}/info.log - - - ${log_dir}/info/info.%d{yyyy-MM-dd}_%i.log - - ${dev_file_size} - ${dev_file_max_history} - - - - ${log_pattern} - - true - - - - - - WARN - ACCEPT - DENY - - ${log_dir}/warn.log - - - ${log_dir}/warn/%d{yyyy-MM-dd}_%i.log - - ${dev_file_size} - ${dev_file_max_history} - - - - ${log_pattern} - - true - - - - - - ERROR - ACCEPT - DENY - - ${log_dir}/error.log - - - ${log_dir}/error/%d{yyyy-MM-dd}_%i.log - - ${dev_file_size} - ${dev_file_max_history} - - - - ${log_pattern} - - true - - - - - ${log_dir}/query_log.log - - - ${log_dir}/query/%d{yyyy-MM-dd}_%i.log - - 10kb - 1 - - - - ${log_pattern} - - true - - - - - - - - - - - - - - - - - - diff --git a/backend/src/main/resources/logback-spring-prod.xml b/backend/src/main/resources/logback-spring-prod.xml deleted file mode 100644 index 101ba8d7..00000000 --- a/backend/src/main/resources/logback-spring-prod.xml +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - ${prod_slack_webhook_uri} - - ${log_pattern} - - open-macbook - :face_with_symbols_on_mouth: - true - - - - - - ERROR - - - - - - INFO - ACCEPT - DENY - - ${log_dir}/info.log - - - ${log_dir}/info/info.%d{yyyy-MM-dd}_%i.log - - ${prod_file_size} - ${prod_file_max_history} - - - - ${log_pattern} - - true - - - - - - WARN - ACCEPT - DENY - - ${log_dir}/warn.log - - - ${log_dir}/warn/%d{yyyy-MM-dd}_%i.log - - ${prod_file_size} - ${prod_file_max_history} - - - - ${log_pattern} - - true - - - - - - ERROR - ACCEPT - DENY - - ${log_dir}/error.log - - - ${log_dir}/error/%d{yyyy-MM-dd}_%i.log - - ${prod_file_size} - ${prod_file_max_history} - - - - ${log_pattern} - - true - - - - - - - - - - - diff --git a/backend/src/main/resources/logback-spring.xml b/backend/src/main/resources/logback-spring.xml deleted file mode 100644 index ccea7008..00000000 --- a/backend/src/main/resources/logback-spring.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/backend/src/main/resources/logback-variables.yml b/backend/src/main/resources/logback-variables.yml deleted file mode 100644 index 82334293..00000000 --- a/backend/src/main/resources/logback-variables.yml +++ /dev/null @@ -1,7 +0,0 @@ -log_pattern: { LOG_PATTERN } -dev_slack_webhook_uri: { DEV_SLACK_WEBHOOK_URI } -dev_file_size: { DEV_FILE_SIZE } -dev_file_max_history: { DEV_FILE_MAX_HISTORY } -prod_slack_webhook_uri: { PROD_SLACK_WEBHOOK_URI } -prod_file_size: { PROD_FILE_SIZE } -prod_file_max_history: { PROD_FILE_MAX_HISTORY } diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 00000000..46b9b6a4 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,48 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + id ("org.springframework.boot") version "3.2.1" + id ("io.spring.dependency-management") version "1.1.4" + id ("java") + kotlin("jvm") version "1.9.20" + kotlin("plugin.spring") version "1.9.20" + kotlin("plugin.jpa") version "1.9.20" +} + +group = "com.funeat" +version = "0.0.1-SNAPSHOT" +java.sourceCompatibility = JavaVersion.VERSION_21 + +repositories { + mavenCentral() +} + +dependencies { + implementation("org.springframework.boot:spring-boot-starter-validation") + implementation("org.springframework.boot:spring-boot-starter-data-jpa") + implementation("org.springframework.boot:spring-boot-starter-web") + runtimeOnly("com.mysql:mysql-connector-j") + testImplementation("org.springframework.boot:spring-boot-starter-test") + testImplementation("io.rest-assured:rest-assured:5.3.2") + testRuntimeOnly("com.h2database:h2") + + implementation("org.springdoc:springdoc-openapi-ui:1.7.0") + implementation("com.github.maricn:logback-slack-appender:1.4.0") + + implementation("org.springframework.boot:spring-boot-starter-actuator") + runtimeOnly("io.micrometer:micrometer-registry-prometheus") + + implementation("com.amazonaws:aws-java-sdk-s3:1.12.547") + + implementation("org.springframework.session:spring-session-jdbc") +} + +tasks.withType { + kotlinOptions { + jvmTarget = "21" + } +} + +tasks.withType { + useJUnitPlatform() +} diff --git a/frontend/.babelrc.json b/frontend/.babelrc.json deleted file mode 100644 index 2c055d07..00000000 --- a/frontend/.babelrc.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "sourceType": "unambiguous", - "presets": [ - [ - "@babel/preset-env", - { - "targets": { - "chrome": 100 - } - } - ], - "@babel/preset-typescript", - "@babel/preset-react" - ], - "plugins": ["babel-plugin-styled-components"] -} diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js deleted file mode 100644 index 963fcc6c..00000000 --- a/frontend/.eslintrc.js +++ /dev/null @@ -1,113 +0,0 @@ -module.exports = { - env: { - browser: true, - es2021: true, - }, - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:react/recommended', - 'plugin:storybook/recommended', - 'plugin:import/recommended', - ], - ignorePatterns: ['*.js'], - overrides: [ - { - env: { - node: true, - }, - files: ['.eslintrc.{js,cjs}'], - parserOptions: { - sourceType: 'script', - }, - }, - { - env: { - jest: true, - }, - files: ['__tests__/**/*.{ts,tsx}'], - parserOptions: { - ecmaVersion: 'latest', - sourceType: 'module', - project: './tsconfig.json', - tsconfigRootDir: __dirname, - }, - }, - ], - parser: '@typescript-eslint/parser', - parserOptions: { - ecmaVersion: 'latest', - sourceType: 'module', - project: './tsconfig.json', - tsconfigRootDir: __dirname, - }, - plugins: ['@typescript-eslint', 'react', 'import'], - rules: { - 'react/react-in-jsx-scope': 'off', - '@typescript-eslint/no-var-requires': 0, - '@typescript-eslint/consistent-type-imports': [ - 'error', - { - prefer: 'type-imports', - disallowTypeAnnotations: false, - }, - ], - 'react/jsx-key': [ - 'error', - { - warnOnDuplicates: true, - }, - ], - 'react/self-closing-comp': [ - 'error', - { - component: true, - html: true, - }, - ], - 'import/order': [ - 'error', - { - groups: ['builtin', 'external', 'internal', ['parent', 'sibling', 'index'], 'object', 'unknown'], - pathGroups: [ - { - pattern: '@storybook/**', - group: 'external', - }, - { - pattern: '@fun-eat/**', - group: 'external', - }, - { - pattern: '@tanstack/**', - group: 'external', - }, - { - pattern: '@*/**', - group: 'unknown', - }, - { - pattern: '@*', - group: 'unknown', - }, - ], - pathGroupsExcludedImportTypes: ['unknown'], - alphabetize: { - order: 'asc', - caseInsensitive: true, - }, - 'newlines-between': 'always', - }, - ], - 'import/no-unresolved': 'off', - '@typescript-eslint/no-empty-function': 'off', - '@typescript-eslint/ban-types': 'off', - 'import/export': 'off', - }, - settings: { - 'import/resolver': { - typescript: {}, - webpack: {}, - }, - }, -}; diff --git a/frontend/.gitignore b/frontend/.gitignore deleted file mode 100644 index 8a2a134c..00000000 --- a/frontend/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -node_modules -dist -.DS_Store -.AppleDouble -.LSOverride -.env -coverage -test-results -junit.xml diff --git a/frontend/.nvmrc b/frontend/.nvmrc deleted file mode 100644 index 3876fd49..00000000 --- a/frontend/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -18.16.1 diff --git a/frontend/.prettierrc b/frontend/.prettierrc deleted file mode 100644 index 8c2d4fe2..00000000 --- a/frontend/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "printWidth": 120, - "singleQuote": true, - "endOfLine": "auto", - "semi": true, - "tabWidth": 2 -} diff --git a/frontend/.storybook/main.ts b/frontend/.storybook/main.ts deleted file mode 100644 index 8791c62d..00000000 --- a/frontend/.storybook/main.ts +++ /dev/null @@ -1,65 +0,0 @@ -import type { StorybookConfig } from '@storybook/react-webpack5'; -import path from 'path'; - -const config: StorybookConfig = { - stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'], - addons: [ - '@storybook/addon-links', - '@storybook/addon-essentials', - '@storybook/addon-interactions', - 'msw-storybook-addon', - '@storybook/addon-onboarding', - ], - framework: { - name: '@storybook/react-webpack5', - options: {}, - }, - core: { - builder: { - name: '@storybook/builder-webpack5', - options: { - fsCache: true, - lazyCompilation: true, - }, - }, - }, - webpackFinal: async (config) => { - if (config.resolve) { - config.resolve.alias = { - ...config.resolve.alias, - '@': path.resolve(__dirname, '../src'), - '@apis': path.resolve(__dirname, '../src/apis'), - '@assets': path.resolve(__dirname, '../src/assets'), - '@components': path.resolve(__dirname, '../src/components'), - '@constants': path.resolve(__dirname, '../src/constants'), - '@hooks': path.resolve(__dirname, '../src/hooks'), - '@mocks': path.resolve(__dirname, '../src/mocks'), - '@pages': path.resolve(__dirname, '../src/pages'), - '@router': path.resolve(__dirname, '../src/router'), - '@styles': path.resolve(__dirname, '../src/styles'), - '@utils': path.resolve(__dirname, '../src/utils'), - }; - } - const imageRule = config.module?.rules?.find((rule) => { - const test = (rule as { test: RegExp }).test; - - if (!test) return false; - - return test.test('.svg'); - }) as { [key: string]: any }; - - imageRule.exclude = /\.svg$/; - - config.module?.rules?.push({ - test: /\.svg$/, - use: ['@svgr/webpack'], - }); - - return config; - }, - docs: { - autodocs: true, - }, - staticDirs: ['../public'], -}; -export default config; diff --git a/frontend/.storybook/preview-body.html b/frontend/.storybook/preview-body.html deleted file mode 100644 index a37b26cb..00000000 --- a/frontend/.storybook/preview-body.html +++ /dev/null @@ -1,129 +0,0 @@ - -
-
- diff --git a/frontend/.storybook/preview.tsx b/frontend/.storybook/preview.tsx deleted file mode 100644 index 944ee1c0..00000000 --- a/frontend/.storybook/preview.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import React from 'react'; -import { FunEatProvider } from '@fun-eat/design-system'; -import type { Preview } from '@storybook/react'; -import { initialize, mswDecorator } from 'msw-storybook-addon'; -import { - loginHandlers, - productHandlers, - reviewHandlers, - rankingHandlers, - memberHandlers, - recipeHandlers, - searchHandlers, -} from '../src/mocks/handlers'; -import { BrowserRouter } from 'react-router-dom'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; - -initialize({ - serviceWorker: { - url: '/mockServiceWorker.js', - }, -}); - -const queryClient = new QueryClient(); - -export const decorators = [ - (Story) => ( - - - - - - - - ), - mswDecorator, -]; - -const preview: Preview = { - parameters: { - actions: { argTypesRegex: '^on[A-Z].*' }, - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/, - }, - }, - msw: { - handlers: [ - ...productHandlers, - ...reviewHandlers, - ...loginHandlers, - ...rankingHandlers, - ...memberHandlers, - ...recipeHandlers, - ...searchHandlers, - ], - }, - }, -}; - -export default preview; diff --git a/frontend/.stylelintrc.js b/frontend/.stylelintrc.js deleted file mode 100644 index 914c886f..00000000 --- a/frontend/.stylelintrc.js +++ /dev/null @@ -1,15 +0,0 @@ -const { propertyOrdering, selectorOrdering } = require('stylelint-semantic-groups'); - -propertyOrdering[0] = propertyOrdering[0].map((rule) => { - rule.emptyLineBefore = 'never'; - return rule; -}); - -module.exports = { - plugins: ['stylelint-order'], - customSyntax: 'postcss-styled-syntax', - rules: { - 'order/order': selectorOrdering, - 'order/properties-order': propertyOrdering, - }, -}; diff --git a/frontend/__tests__/hooks/useImageUploader.test.ts b/frontend/__tests__/hooks/useImageUploader.test.ts deleted file mode 100644 index 3de2da05..00000000 --- a/frontend/__tests__/hooks/useImageUploader.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { useImageUploader } from '@/hooks/common'; -import { renderHook, act } from '@testing-library/react'; - -const originalCreateObjectUrl = URL.createObjectURL; -const originalRevokeObjectUrl = URL.revokeObjectURL; - -beforeAll(() => { - URL.createObjectURL = jest.fn(() => 'mocked url'); - URL.revokeObjectURL = jest.fn(); -}); - -afterAll(() => { - URL.createObjectURL = originalCreateObjectUrl; - URL.revokeObjectURL = originalRevokeObjectUrl; -}); - -it('uploadImage를 사용하여 이미지 파일을 업로드할 수 있다.', () => { - const { result } = renderHook(() => useImageUploader()); - - const file = new File(['dummy content'], 'example.png', { type: 'image/png' }); - - act(() => { - result.current.uploadImage(file); - }); - - expect(result.current.imageFile).toBe(file); - expect(result.current.previewImage).toBe('mocked url'); - expect(URL.createObjectURL).toHaveBeenCalledWith(file); -}); - -it('이미지 파일이 아니면 "이미지 파일만 업로드 가능합니다." 메시지를 보여주는 alert 창이 뜬다.', () => { - const { result } = renderHook(() => useImageUploader()); - - const file = new File(['dummy content'], 'example.txt', { type: 'text/plain' }); - - global.alert = jest.fn(); - - act(() => { - result.current.uploadImage(file); - }); - - expect(global.alert).toHaveBeenCalledWith('이미지 파일만 업로드 가능합니다.'); -}); - -it('deleteImage를 사용하여 이미지 파일을 삭제할 수 있다.', () => { - const { result } = renderHook(() => useImageUploader()); - - const file = new File(['dummy content'], 'example.png', { type: 'image/png' }); - - act(() => { - result.current.uploadImage(file); - }); - - act(() => { - result.current.deleteImage(); - }); - - expect(result.current.imageFile).toBeNull(); - expect(result.current.previewImage).toBe(''); - expect(URL.revokeObjectURL).toHaveBeenCalledWith('mocked url'); -}); diff --git a/frontend/__tests__/hooks/useStarRating.test.ts b/frontend/__tests__/hooks/useStarRating.test.ts deleted file mode 100644 index 66ed60cb..00000000 --- a/frontend/__tests__/hooks/useStarRating.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { useStarRatingHover } from '@/hooks/review'; -import { renderHook, act } from '@testing-library/react'; - -it('handleMouseEnter를 사용하여 마우스 호버된 별점 값을 저장할 수 있다.', () => { - const { result } = renderHook(() => useStarRatingHover()); - - expect(result.current.hovering).toBe(0); - - act(() => { - result.current.handleMouseEnter(3); - }); - - expect(result.current.hovering).toBe(3); -}); - -it('handleMouseLeave를 사용하여 마우스 호버된 별점을 초기화 할 수 있다.', () => { - const { result } = renderHook(() => useStarRatingHover()); - - expect(result.current.hovering).toBe(0); - - act(() => { - result.current.handleMouseEnter(3); - }); - - expect(result.current.hovering).toBe(3); - - act(() => { - result.current.handleMouseLeave(); - }); - - expect(result.current.hovering).toBe(0); -}); diff --git a/frontend/__tests__/hooks/useTabMenu.test.ts b/frontend/__tests__/hooks/useTabMenu.test.ts deleted file mode 100644 index d8ca5317..00000000 --- a/frontend/__tests__/hooks/useTabMenu.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { useTabMenu } from '@/hooks/common'; -import { renderHook, act } from '@testing-library/react'; - -it('선택된 탭 초기 상태는 0번 인덱스이다.', () => { - const { result } = renderHook(() => useTabMenu()); - - expect(result.current.selectedTabMenu).toBe(0); - expect(result.current.isFirstTabMenu).toBe(true); -}); - -it('handleTabMenuClick를 사용하여 선택한 탭 인덱스를 저장할 수 있다. ', () => { - const { result } = renderHook(() => useTabMenu()); - - act(() => { - result.current.handleTabMenuClick(1); - }); - - expect(result.current.selectedTabMenu).toBe(1); -}); - -it('initTabMenu를 사용하여 선택된 탭을 맨 처음 탭으로 초기화할 수 있다.', () => { - const { result } = renderHook(() => useTabMenu()); - - act(() => { - result.current.handleTabMenuClick(1); - result.current.initTabMenu(); - }); - - expect(result.current.selectedTabMenu).toBe(0); -}); diff --git a/frontend/jest.config.js b/frontend/jest.config.js deleted file mode 100644 index 82a71339..00000000 --- a/frontend/jest.config.js +++ /dev/null @@ -1,20 +0,0 @@ -module.exports = { - testEnvironment: 'jsdom', - transform: { - '^.+\\.(js|ts|tsx)?$': 'ts-jest', - }, - moduleNameMapper: { - '^@/(.*)$': '/src/$1', - }, - testMatch: ['/__tests__/**/*.test.(js|jsx|ts|tsx)'], - reporters: [ - 'default', - [ - 'jest-junit', - { - outputDirectory: '/test-results', - outputName: 'results.xml', - }, - ], - ], -}; diff --git a/frontend/package.json b/frontend/package.json deleted file mode 100644 index 358f1fe4..00000000 --- a/frontend/package.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "name": "fun-eat", - "version": "0.0.0", - "main": "index.js", - "license": "MIT", - "scripts": { - "start": "webpack serve --open --config webpack.dev.js", - "build": "webpack --config webpack.prod.js", - "build-dev": "webpack --config webpack.dev.js", - "storybook": "storybook dev -p 6006", - "lint:styled": "stylelint './src/**/*.tsx' --fix", - "test": "jest", - "test:coverage": "jest --watchAll --coverage" - }, - "dependencies": { - "@fun-eat/design-system": "^0.3.18", - "@tanstack/react-query": "^4.32.6", - "@tanstack/react-query-devtools": "^4.32.6", - "browser-image-compression": "^2.0.2", - "dayjs": "^1.11.9", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-ga4": "^2.1.0", - "react-router-dom": "^6.14.2", - "styled-components": "^6.0.2" - }, - "devDependencies": { - "@babel/preset-env": "^7.22.9", - "@babel/preset-react": "^7.22.5", - "@babel/preset-typescript": "^7.22.5", - "@storybook/addon-essentials": "^7.0.27", - "@storybook/addon-interactions": "^7.0.27", - "@storybook/addon-links": "^7.0.27", - "@storybook/addon-onboarding": "^1.0.8", - "@storybook/blocks": "^7.0.27", - "@storybook/react": "^7.0.27", - "@storybook/react-webpack5": "^7.0.27", - "@storybook/testing-library": "^0.0.14-next.2", - "@svgr/webpack": "^8.0.1", - "@testing-library/jest-dom": "^5.17.0", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.4.3", - "@types/jest": "^29.5.3", - "@types/react": "^18.2.14", - "@types/react-dom": "^18.2.6", - "@types/styled-components": "^5.1.26", - "@typescript-eslint/eslint-plugin": "^5.60.1", - "@typescript-eslint/parser": "^5.60.1", - "babel-plugin-styled-components": "^2.1.4", - "copy-webpack-plugin": "^11.0.0", - "dotenv-webpack": "^8.0.1", - "eslint": "^8.44.0", - "eslint-import-resolver-typescript": "^3.5.5", - "eslint-import-resolver-webpack": "^0.13.2", - "eslint-plugin-import": "^2.27.5", - "eslint-plugin-react": "^7.32.2", - "eslint-plugin-storybook": "^0.6.12", - "html-webpack-plugin": "^5.5.3", - "jest": "^29.6.2", - "jest-environment-jsdom": "^29.6.2", - "jest-junit": "^16.0.0", - "msw": "^1.2.3", - "msw-storybook-addon": "^1.8.0", - "postcss": "^8.4.29", - "postcss-styled-syntax": "^0.4.0", - "prettier": "^2.8.8", - "storybook": "^7.1.1", - "stylelint": "^15.10.3", - "stylelint-order": "^6.0.3", - "stylelint-semantic-groups": "^1.2.0", - "ts-jest": "^29.1.1", - "ts-loader": "^9.4.4", - "typescript": "^5.1.6", - "webpack": "^5.88.1", - "webpack-cli": "^5.1.4", - "webpack-dev-server": "^4.15.1" - }, - "msw": { - "workerDirectory": "public" - }, - "resolutions": { - "jackspeak": "2.1.1" - } -} diff --git a/frontend/public/assets/apple-icon-180x180.png b/frontend/public/assets/apple-icon-180x180.png deleted file mode 100644 index 986c9be8..00000000 Binary files a/frontend/public/assets/apple-icon-180x180.png and /dev/null differ diff --git a/frontend/public/assets/favicon-16x16.png b/frontend/public/assets/favicon-16x16.png deleted file mode 100644 index a277e183..00000000 Binary files a/frontend/public/assets/favicon-16x16.png and /dev/null differ diff --git a/frontend/public/assets/favicon-32x32.png b/frontend/public/assets/favicon-32x32.png deleted file mode 100644 index 89b5dff5..00000000 Binary files a/frontend/public/assets/favicon-32x32.png and /dev/null differ diff --git a/frontend/public/assets/favicon.ico b/frontend/public/assets/favicon.ico deleted file mode 100644 index 48fea60f..00000000 Binary files a/frontend/public/assets/favicon.ico and /dev/null differ diff --git a/frontend/public/assets/og-image.png b/frontend/public/assets/og-image.png deleted file mode 100644 index 3d4b525e..00000000 Binary files a/frontend/public/assets/og-image.png and /dev/null differ diff --git a/frontend/public/index.html b/frontend/public/index.html deleted file mode 100644 index 36782ea8..00000000 --- a/frontend/public/index.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - 펀잇 - - - - - - - - - - - - - - - - -
-
-
- - diff --git a/frontend/public/manifest.json b/frontend/public/manifest.json deleted file mode 100644 index 7e44fe2f..00000000 --- a/frontend/public/manifest.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "펀잇", - "short_name": "펀잇", - "description": "궁금해? 맛있을걸? 먹어봐 🥄", - "display": "standalone", - "icons": [ - { - "src": "/assets/favicon-16x16.png", - "sizes": "16x16", - "type": "image/png" - }, - { - "src": "/assets/favicon-32x32.png", - "sizes": "32x32", - "type": "image/png" - }, - { - "src": "/assets/apple-icon-180x180.png", - "sizes": "180x180", - "type": "image/png" - } - ] -} diff --git a/frontend/public/mockServiceWorker.js b/frontend/public/mockServiceWorker.js deleted file mode 100644 index 51d85eee..00000000 --- a/frontend/public/mockServiceWorker.js +++ /dev/null @@ -1,303 +0,0 @@ -/* eslint-disable */ -/* tslint:disable */ - -/** - * Mock Service Worker (1.3.2). - * @see https://github.com/mswjs/msw - * - Please do NOT modify this file. - * - Please do NOT serve this file on production. - */ - -const INTEGRITY_CHECKSUM = '3d6b9f06410d179a7f7404d4bf4c3c70' -const activeClientIds = new Set() - -self.addEventListener('install', function () { - self.skipWaiting() -}) - -self.addEventListener('activate', function (event) { - event.waitUntil(self.clients.claim()) -}) - -self.addEventListener('message', async function (event) { - const clientId = event.source.id - - if (!clientId || !self.clients) { - return - } - - const client = await self.clients.get(clientId) - - if (!client) { - return - } - - const allClients = await self.clients.matchAll({ - type: 'window', - }) - - switch (event.data) { - case 'KEEPALIVE_REQUEST': { - sendToClient(client, { - type: 'KEEPALIVE_RESPONSE', - }) - break - } - - case 'INTEGRITY_CHECK_REQUEST': { - sendToClient(client, { - type: 'INTEGRITY_CHECK_RESPONSE', - payload: INTEGRITY_CHECKSUM, - }) - break - } - - case 'MOCK_ACTIVATE': { - activeClientIds.add(clientId) - - sendToClient(client, { - type: 'MOCKING_ENABLED', - payload: true, - }) - break - } - - case 'MOCK_DEACTIVATE': { - activeClientIds.delete(clientId) - break - } - - case 'CLIENT_CLOSED': { - activeClientIds.delete(clientId) - - const remainingClients = allClients.filter((client) => { - return client.id !== clientId - }) - - // Unregister itself when there are no more clients - if (remainingClients.length === 0) { - self.registration.unregister() - } - - break - } - } -}) - -self.addEventListener('fetch', function (event) { - const { request } = event - const accept = request.headers.get('accept') || '' - - // Bypass server-sent events. - if (accept.includes('text/event-stream')) { - return - } - - // Bypass navigation requests. - if (request.mode === 'navigate') { - return - } - - // Opening the DevTools triggers the "only-if-cached" request - // that cannot be handled by the worker. Bypass such requests. - if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { - return - } - - // Bypass all requests when there are no active clients. - // Prevents the self-unregistered worked from handling requests - // after it's been deleted (still remains active until the next reload). - if (activeClientIds.size === 0) { - return - } - - // Generate unique request ID. - const requestId = Math.random().toString(16).slice(2) - - event.respondWith( - handleRequest(event, requestId).catch((error) => { - if (error.name === 'NetworkError') { - console.warn( - '[MSW] Successfully emulated a network error for the "%s %s" request.', - request.method, - request.url, - ) - return - } - - // At this point, any exception indicates an issue with the original request/response. - console.error( - `\ -[MSW] Caught an exception from the "%s %s" request (%s). This is probably not a problem with Mock Service Worker. There is likely an additional logging output above.`, - request.method, - request.url, - `${error.name}: ${error.message}`, - ) - }), - ) -}) - -async function handleRequest(event, requestId) { - const client = await resolveMainClient(event) - const response = await getResponse(event, client, requestId) - - // Send back the response clone for the "response:*" life-cycle events. - // Ensure MSW is active and ready to handle the message, otherwise - // this message will pend indefinitely. - if (client && activeClientIds.has(client.id)) { - ;(async function () { - const clonedResponse = response.clone() - sendToClient(client, { - type: 'RESPONSE', - payload: { - requestId, - type: clonedResponse.type, - ok: clonedResponse.ok, - status: clonedResponse.status, - statusText: clonedResponse.statusText, - body: - clonedResponse.body === null ? null : await clonedResponse.text(), - headers: Object.fromEntries(clonedResponse.headers.entries()), - redirected: clonedResponse.redirected, - }, - }) - })() - } - - return response -} - -// Resolve the main client for the given event. -// Client that issues a request doesn't necessarily equal the client -// that registered the worker. It's with the latter the worker should -// communicate with during the response resolving phase. -async function resolveMainClient(event) { - const client = await self.clients.get(event.clientId) - - if (client?.frameType === 'top-level') { - return client - } - - const allClients = await self.clients.matchAll({ - type: 'window', - }) - - return allClients - .filter((client) => { - // Get only those clients that are currently visible. - return client.visibilityState === 'visible' - }) - .find((client) => { - // Find the client ID that's recorded in the - // set of clients that have registered the worker. - return activeClientIds.has(client.id) - }) -} - -async function getResponse(event, client, requestId) { - const { request } = event - const clonedRequest = request.clone() - - function passthrough() { - // Clone the request because it might've been already used - // (i.e. its body has been read and sent to the client). - const headers = Object.fromEntries(clonedRequest.headers.entries()) - - // Remove MSW-specific request headers so the bypassed requests - // comply with the server's CORS preflight check. - // Operate with the headers as an object because request "Headers" - // are immutable. - delete headers['x-msw-bypass'] - - return fetch(clonedRequest, { headers }) - } - - // Bypass mocking when the client is not active. - if (!client) { - return passthrough() - } - - // Bypass initial page load requests (i.e. static assets). - // The absence of the immediate/parent client in the map of the active clients - // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet - // and is not ready to handle requests. - if (!activeClientIds.has(client.id)) { - return passthrough() - } - - // Bypass requests with the explicit bypass header. - // Such requests can be issued by "ctx.fetch()". - if (request.headers.get('x-msw-bypass') === 'true') { - return passthrough() - } - - // Notify the client that a request has been intercepted. - const clientMessage = await sendToClient(client, { - type: 'REQUEST', - payload: { - id: requestId, - url: request.url, - method: request.method, - headers: Object.fromEntries(request.headers.entries()), - cache: request.cache, - mode: request.mode, - credentials: request.credentials, - destination: request.destination, - integrity: request.integrity, - redirect: request.redirect, - referrer: request.referrer, - referrerPolicy: request.referrerPolicy, - body: await request.text(), - bodyUsed: request.bodyUsed, - keepalive: request.keepalive, - }, - }) - - switch (clientMessage.type) { - case 'MOCK_RESPONSE': { - return respondWithMock(clientMessage.data) - } - - case 'MOCK_NOT_FOUND': { - return passthrough() - } - - case 'NETWORK_ERROR': { - const { name, message } = clientMessage.data - const networkError = new Error(message) - networkError.name = name - - // Rejecting a "respondWith" promise emulates a network error. - throw networkError - } - } - - return passthrough() -} - -function sendToClient(client, message) { - return new Promise((resolve, reject) => { - const channel = new MessageChannel() - - channel.port1.onmessage = (event) => { - if (event.data && event.data.error) { - return reject(event.data.error) - } - - resolve(event.data) - } - - client.postMessage(message, [channel.port2]) - }) -} - -function sleep(timeMs) { - return new Promise((resolve) => { - setTimeout(resolve, timeMs) - }) -} - -async function respondWithMock(response) { - await sleep(response.delay) - return new Response(response.body, response) -} diff --git a/frontend/public/robots.txt b/frontend/public/robots.txt deleted file mode 100644 index 7b8e4fcf..00000000 --- a/frontend/public/robots.txt +++ /dev/null @@ -1,9 +0,0 @@ -User-agent: * -Disallow: /404 -Allow: / - -# Host -Host: https://funeat.site/ - -# Sitemaps -Sitemap: https://funeat.site/sitemap.xml \ No newline at end of file diff --git a/frontend/public/sitemap.xml b/frontend/public/sitemap.xml deleted file mode 100644 index 9c261be7..00000000 --- a/frontend/public/sitemap.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - https://funeat.site - 2023-09-25T05:39:39+00:00 - - - https://funeat.site/products/food - 2023-09-25T05:39:39+00:00 - - - https://funeat.site/products/store - 2023-09-25T05:39:39+00:00 - - - https://funeat.site/recipes - 2023-09-25T05:39:39+00:00 - - \ No newline at end of file diff --git a/frontend/src/apis/ApiClient.ts b/frontend/src/apis/ApiClient.ts deleted file mode 100644 index 26fe6284..00000000 --- a/frontend/src/apis/ApiClient.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { fetchApi } from './fetch'; - -interface RequestOptions { - params?: string; - queries?: string; - credentials?: boolean; -} - -export class ApiClient { - #path: string; - - #headers: HeadersInit; - - constructor(path: string, headers: HeadersInit = {}) { - this.#path = path; - this.#headers = headers; - } - - getUrl(params = '', queries = '') { - return '/api' + this.#path + params + queries; - } - - get({ params, queries, credentials = false }: RequestOptions) { - return fetchApi(this.getUrl(params, queries), { - method: 'GET', - headers: this.#headers, - credentials: credentials ? 'include' : 'omit', - }); - } - - post({ params, queries, credentials = false }: RequestOptions, headers?: HeadersInit, body?: B) { - return fetchApi(this.getUrl(params, queries), { - method: 'POST', - headers: headers, - body: body ? JSON.stringify(body) : null, - credentials: credentials ? 'include' : 'omit', - }); - } - - postData({ params, queries, credentials = false }: RequestOptions, body: FormData) { - return fetchApi(this.getUrl(params, queries), { - method: 'POST', - headers: this.#headers, - body: body, - credentials: credentials ? 'include' : 'omit', - }); - } - - patch({ params, queries, credentials = false }: RequestOptions, headers: HeadersInit, body?: B) { - return fetchApi(this.getUrl(params, queries), { - method: 'PATCH', - headers: headers, - body: body ? JSON.stringify(body) : null, - credentials: credentials ? 'include' : 'omit', - }); - } - - put({ params, queries, credentials = false }: RequestOptions, headers?: HeadersInit, body?: B) { - return fetchApi(this.getUrl(params, queries), { - method: 'PUT', - headers: headers, - body: body ? JSON.stringify(body) : null, - credentials: credentials ? 'include' : 'omit', - }); - } - - putData({ params, queries, credentials = false }: RequestOptions, body: FormData) { - return fetchApi(this.getUrl(params, queries), { - method: 'PUT', - headers: this.#headers, - body: body, - credentials: credentials ? 'include' : 'omit', - }); - } - - delete({ params, queries, credentials = false }: RequestOptions, body?: B) { - return fetchApi(this.getUrl(params, queries), { - method: 'DELETE', - headers: this.#headers, - body: body ? JSON.stringify(body) : null, - credentials: credentials ? 'include' : 'omit', - }); - } -} diff --git a/frontend/src/apis/fetch.ts b/frontend/src/apis/fetch.ts deleted file mode 100644 index 6c760b43..00000000 --- a/frontend/src/apis/fetch.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { ErrorResponse } from '@/types/response'; - -export const fetchApi = async (url: string, options: RequestInit) => { - if (!navigator.onLine) { - throw new Error('네트워크 오프라인이 감지되었습니다'); - } - - const response = await fetch(url, options); - - if (!response.ok) { - const errorData: ErrorResponse = await response.json(); - throw new Error(errorData.message); - } - - return response; -}; diff --git a/frontend/src/apis/index.ts b/frontend/src/apis/index.ts deleted file mode 100644 index c258c161..00000000 --- a/frontend/src/apis/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ApiClient } from './ApiClient'; - -export const categoryApi = new ApiClient('/categories'); -export const productApi = new ApiClient('/products'); -export const tagApi = new ApiClient('/tags'); -export const rankApi = new ApiClient('/ranks'); -export const loginApi = new ApiClient('/login'); -export const memberApi = new ApiClient('/members'); -export const recipeApi = new ApiClient('/recipes'); -export const searchApi = new ApiClient('/search'); -export const logoutApi = new ApiClient('/logout'); -export const reviewApi = new ApiClient('/reviews'); -export const bannerApi = new ApiClient('/banners'); diff --git a/frontend/src/assets/characters.svg b/frontend/src/assets/characters.svg deleted file mode 100644 index 6580162c..00000000 --- a/frontend/src/assets/characters.svg +++ /dev/null @@ -1,63 +0,0 @@ - - -펀잇의 캐릭터 - -상품 미리보기 사진입니다. - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/src/assets/logo.svg b/frontend/src/assets/logo.svg deleted file mode 100644 index 26cb9858..00000000 --- a/frontend/src/assets/logo.svg +++ /dev/null @@ -1,101 +0,0 @@ - - - 펀잇 로고 - -펀잇 로고 사진입니다. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/src/assets/plate.svg b/frontend/src/assets/plate.svg deleted file mode 100644 index 1a5f2f61..00000000 --- a/frontend/src/assets/plate.svg +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/src/assets/samgakgimbab.svg b/frontend/src/assets/samgakgimbab.svg deleted file mode 100644 index a5e9872f..00000000 --- a/frontend/src/assets/samgakgimbab.svg +++ /dev/null @@ -1,87 +0,0 @@ - - - 펀잇의 캐릭터 - -상품 미리보기 사진입니다. - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/src/components/Common/Banner/Banner.tsx b/frontend/src/components/Common/Banner/Banner.tsx deleted file mode 100644 index 89af08c6..00000000 --- a/frontend/src/components/Common/Banner/Banner.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { Link } from '@fun-eat/design-system'; -import styled from 'styled-components'; - -import { useBannerQuery } from '@/hooks/queries/banner'; - -const Banner = () => { - const { data: banners } = useBannerQuery(); - const { link, image } = banners[Math.floor(Math.random() * banners.length)]; - - if (!link) { - return ; - } - - return ( - - - - ); -}; - -export default Banner; - -const BannerImage = styled.img` - width: 100%; - height: auto; -`; diff --git a/frontend/src/components/Common/Carousel/Carousel.stories.tsx b/frontend/src/components/Common/Carousel/Carousel.stories.tsx deleted file mode 100644 index 8d7eccf6..00000000 --- a/frontend/src/components/Common/Carousel/Carousel.stories.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import Carousel from './Carousel'; - -import { RecipeItem } from '@/components/Recipe'; -import mockRecipe from '@/mocks/data/recipes.json'; - -const meta: Meta = { - title: 'common/Carousel', - component: Carousel, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - args: { - carouselList: [ - { - id: 0, - children:
1
, - }, - { - id: 1, - children:
2
, - }, - { - id: 2, - children:
3
, - }, - ], - }, -}; - -export const RecipeRanking: Story = { - args: { - carouselList: [ - { - id: 0, - children: , - }, - { - id: 1, - children: , - }, - { - id: 2, - children: , - }, - ], - }, -}; diff --git a/frontend/src/components/Common/Carousel/Carousel.tsx b/frontend/src/components/Common/Carousel/Carousel.tsx deleted file mode 100644 index 038b5cef..00000000 --- a/frontend/src/components/Common/Carousel/Carousel.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { useEffect, useState } from 'react'; -import styled from 'styled-components'; - -import type { CarouselChildren } from '@/types/common'; - -interface CarouselProps { - carouselList: CarouselChildren[]; -} - -const Carousel = ({ carouselList }: CarouselProps) => { - const extendedCarouselList = [...carouselList, carouselList[0]]; - const [currentIndex, setCurrentIndex] = useState(0); - - const CAROUSEL_WIDTH = window.innerWidth; - - const showNextSlide = () => { - setCurrentIndex((prev) => (prev === carouselList.length ? 0 : prev + 1)); - }; - - useEffect(() => { - const timer = setInterval(showNextSlide, 2000); - - return () => clearInterval(timer); - }, [currentIndex]); - - return ( - - - {extendedCarouselList.map(({ id, children }, index) => ( - - {children} - - ))} - - - ); -}; - -export default Carousel; - -const CarouselContainer = styled.div` - display: flex; - width: 100%; - border: 1px solid ${({ theme }) => theme.colors.gray2}; - border-radius: 10px; - overflow: hidden; -`; - -const CarouselWrapper = styled.ul` - display: flex; -`; - -const CarouselItem = styled.li` - height: fit-content; -`; diff --git a/frontend/src/components/Common/CategoryFoodList/CategoryFoodList.stories.tsx b/frontend/src/components/Common/CategoryFoodList/CategoryFoodList.stories.tsx deleted file mode 100644 index 25de7b81..00000000 --- a/frontend/src/components/Common/CategoryFoodList/CategoryFoodList.stories.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import CategoryFoodList from './CategoryFoodList'; - -import CategoryProvider from '@/contexts/CategoryContext'; - -const meta: Meta = { - title: 'common/CategoryFoodList', - component: CategoryFoodList, - decorators: [ - (Story) => ( - - - - ), - ], -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Common/CategoryFoodList/CategoryFoodList.tsx b/frontend/src/components/Common/CategoryFoodList/CategoryFoodList.tsx deleted file mode 100644 index 921584b8..00000000 --- a/frontend/src/components/Common/CategoryFoodList/CategoryFoodList.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import styled from 'styled-components'; - -import CategoryItem from '../CategoryItem/CategoryItem'; - -import { CATEGORY_TYPE } from '@/constants'; -import { useCategoryFoodQuery } from '@/hooks/queries/product'; - -const categoryType = CATEGORY_TYPE.FOOD; - -const CategoryFoodList = () => { - const { data: categories } = useCategoryFoodQuery(categoryType); - - return ( - - {categories.map(({ id, name, image }) => ( - - ))} - - ); -}; - -export default CategoryFoodList; - -const CategoryFoodListWrapper = styled.div` - display: flex; - gap: 16px; -`; diff --git a/frontend/src/components/Common/CategoryFoodTab/CategoryFoodTab.stories.tsx b/frontend/src/components/Common/CategoryFoodTab/CategoryFoodTab.stories.tsx deleted file mode 100644 index 7cbb6e1f..00000000 --- a/frontend/src/components/Common/CategoryFoodTab/CategoryFoodTab.stories.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import CategoryFoodTab from './CategoryFoodTab'; - -import CategoryProvider from '@/contexts/CategoryContext'; - -const meta: Meta = { - title: 'common/CategoryFoodTab', - component: CategoryFoodTab, - decorators: [ - (Story) => ( - - - - ), - ], -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Common/CategoryFoodTab/CategoryFoodTab.tsx b/frontend/src/components/Common/CategoryFoodTab/CategoryFoodTab.tsx deleted file mode 100644 index 56817526..00000000 --- a/frontend/src/components/Common/CategoryFoodTab/CategoryFoodTab.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { Button, theme } from '@fun-eat/design-system'; -import styled from 'styled-components'; - -import { CATEGORY_TYPE } from '@/constants'; -import { useGA } from '@/hooks/common'; -import { useCategoryActionContext, useCategoryValueContext } from '@/hooks/context'; -import { useCategoryFoodQuery } from '@/hooks/queries/product/useCategoryQuery'; -import { getTargetCategoryName } from '@/utils/category'; - -const categoryType = CATEGORY_TYPE.FOOD; - -const CategoryFoodTab = () => { - const { data: categories } = useCategoryFoodQuery(categoryType); - - const { categoryIds } = useCategoryValueContext(); - const { selectCategory } = useCategoryActionContext(); - - const currentCategoryId = categoryIds[categoryType]; - - const { gaEvent } = useGA(); - - const handleCategoryButtonClick = (menuId: number) => { - selectCategory(categoryType, menuId); - gaEvent({ - category: 'button', - action: `${getTargetCategoryName(categories, menuId)} 카테고리 버튼 클릭`, - label: '카테고리', - }); - }; - - return ( - - {categories.map(({ id, name }) => { - const isSelected = id === currentCategoryId; - return ( -
  • - handleCategoryButtonClick(id)} - aria-pressed={isSelected} - > - {name} - -
  • - ); - })} -
    - ); -}; - -export default CategoryFoodTab; - -const CategoryMenuContainer = styled.ul` - display: flex; - gap: 8px; - white-space: nowrap; - overflow-x: auto; - - &::-webkit-scrollbar { - display: none; - } -`; - -const CategoryButton = styled(Button)<{ isSelected: boolean }>` - padding: 6px 12px; - ${({ isSelected }) => - isSelected && - ` - background: ${theme.colors.gray5}; - color: ${theme.textColors.white}; - `} -`; diff --git a/frontend/src/components/Common/CategoryItem/CategoryItem.stories.tsx b/frontend/src/components/Common/CategoryItem/CategoryItem.stories.tsx deleted file mode 100644 index 3dc17ddf..00000000 --- a/frontend/src/components/Common/CategoryItem/CategoryItem.stories.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import CategoryItem from './CategoryItem'; - -import CategoryProvider from '@/contexts/CategoryContext'; - -const meta: Meta = { - title: 'common/CategoryItem', - component: CategoryItem, - decorators: [ - (Story) => ( - - - - ), - ], - args: { - categoryId: 1, - name: '즉석 식품', - image: 'https://tqklhszfkvzk6518638.cdn.ntruss.com/product/8801771029052.jpg', - categoryType: 'food', - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Common/CategoryItem/CategoryItem.tsx b/frontend/src/components/Common/CategoryItem/CategoryItem.tsx deleted file mode 100644 index 051c9707..00000000 --- a/frontend/src/components/Common/CategoryItem/CategoryItem.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { Button } from '@fun-eat/design-system'; -import { useNavigate } from 'react-router-dom'; -import styled from 'styled-components'; - -import { PATH } from '@/constants/path'; -import { useGA } from '@/hooks/common'; -import { useCategoryActionContext } from '@/hooks/context'; - -interface CategoryItemProps { - categoryId: number; - name: string; - image: string; - categoryType: 'food' | 'store'; -} - -const CategoryItem = ({ categoryId, name, image, categoryType }: CategoryItemProps) => { - const navigate = useNavigate(); - const { selectCategory } = useCategoryActionContext(); - - const { gaEvent } = useGA(); - - const handleCategoryItemClick = (categoryId: number) => { - selectCategory(categoryType, categoryId); - navigate(PATH.PRODUCT_LIST + '/' + categoryType); - - gaEvent({ - category: 'button', - action: `${name} 카테고리 링크 클릭`, - label: '카테고리', - }); - }; - - return ( - - ); -}; - -export default CategoryItem; - -const ImageWrapper = styled.div` - display: flex; - justify-content: center; - align-items: center; - width: 60px; - height: 60px; - border-radius: 10px; - background: ${({ theme }) => theme.colors.white}; - - & > img { - width: 100%; - height: auto; - object-fit: cover; - } -`; - -const CategoryName = styled.p` - margin-top: 10px; - font-weight: 600; - font-size: ${({ theme }) => theme.fontSizes.xs}; -`; diff --git a/frontend/src/components/Common/CategoryStoreList/CategoryStoreList.stories.tsx b/frontend/src/components/Common/CategoryStoreList/CategoryStoreList.stories.tsx deleted file mode 100644 index d26be6f4..00000000 --- a/frontend/src/components/Common/CategoryStoreList/CategoryStoreList.stories.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import CategoryStoreList from './CategoryStoreList'; - -import CategoryProvider from '@/contexts/CategoryContext'; - -const meta: Meta = { - title: 'common/CategoryStoreList', - component: CategoryStoreList, - decorators: [ - (Story) => ( - - - - ), - ], -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Common/CategoryStoreList/CategoryStoreList.tsx b/frontend/src/components/Common/CategoryStoreList/CategoryStoreList.tsx deleted file mode 100644 index 6bf2c36a..00000000 --- a/frontend/src/components/Common/CategoryStoreList/CategoryStoreList.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import styled from 'styled-components'; - -import CategoryItem from '../CategoryItem/CategoryItem'; - -import { CATEGORY_TYPE } from '@/constants'; -import { useCategoryStoreQuery } from '@/hooks/queries/product'; - -const categoryType = CATEGORY_TYPE.STORE; - -const CategoryStoreList = () => { - const { data: categories } = useCategoryStoreQuery(categoryType); - - return ( - - {categories.map(({ id, name, image }) => ( - - ))} - - ); -}; - -export default CategoryStoreList; - -const CategoryStoreListWrapper = styled.div` - display: flex; - gap: 16px; -`; diff --git a/frontend/src/components/Common/CategoryStoreTab/CategoryStoreTab.stories.tsx b/frontend/src/components/Common/CategoryStoreTab/CategoryStoreTab.stories.tsx deleted file mode 100644 index 7fca5880..00000000 --- a/frontend/src/components/Common/CategoryStoreTab/CategoryStoreTab.stories.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import CategoryStoreTab from './CategoryStoreTab'; - -import CategoryProvider from '@/contexts/CategoryContext'; - -const meta: Meta = { - title: 'common/CategoryStoreTab', - component: CategoryStoreTab, - decorators: [ - (Story) => ( - - - - ), - ], -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Common/CategoryStoreTab/CategoryStoreTab.tsx b/frontend/src/components/Common/CategoryStoreTab/CategoryStoreTab.tsx deleted file mode 100644 index b75abb7b..00000000 --- a/frontend/src/components/Common/CategoryStoreTab/CategoryStoreTab.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { Button, theme } from '@fun-eat/design-system'; -import styled from 'styled-components'; - -import { CATEGORY_TYPE } from '@/constants'; -import { useGA } from '@/hooks/common'; -import { useCategoryActionContext, useCategoryValueContext } from '@/hooks/context'; -import { useCategoryStoreQuery } from '@/hooks/queries/product/useCategoryQuery'; -import { getTargetCategoryName } from '@/utils/category'; - -const categoryType = CATEGORY_TYPE.STORE; - -const CategoryStoreTab = () => { - const { data: categories } = useCategoryStoreQuery(categoryType); - - const { categoryIds } = useCategoryValueContext(); - const { selectCategory } = useCategoryActionContext(); - const currentCategoryId = categoryIds[categoryType]; - - const { gaEvent } = useGA(); - - const handleCategoryButtonClick = (menuId: number) => { - selectCategory(categoryType, menuId); - gaEvent({ - category: 'button', - action: `${getTargetCategoryName(categories, menuId)} 카테고리 버튼 클릭`, - label: '카테고리', - }); - }; - - return ( - - {categories.map(({ id, name }) => { - const isSelected = id === currentCategoryId; - return ( -
  • - handleCategoryButtonClick(id)} - aria-pressed={isSelected} - > - {name} - -
  • - ); - })} -
    - ); -}; - -export default CategoryStoreTab; - -const CategoryMenuContainer = styled.ul` - display: flex; - gap: 8px; - white-space: nowrap; - overflow-x: auto; - - &::-webkit-scrollbar { - display: none; - } -`; - -const CategoryButton = styled(Button)<{ isSelected: boolean }>` - padding: 6px 12px; - ${({ isSelected }) => - isSelected && - ` - background: ${theme.colors.primary}; - color: ${theme.textColors.default}; - `} -`; diff --git a/frontend/src/components/Common/ErrorBoundary/ErrorBoundary.tsx b/frontend/src/components/Common/ErrorBoundary/ErrorBoundary.tsx deleted file mode 100644 index d8491896..00000000 --- a/frontend/src/components/Common/ErrorBoundary/ErrorBoundary.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { Button } from '@fun-eat/design-system'; -import type { ComponentType, PropsWithChildren } from 'react'; -import { Component } from 'react'; - -export interface FallbackProps { - message: string; -} - -interface ErrorBoundaryProps { - handleReset?: () => void; - fallback: ComponentType; -} - -interface ErrorBoundaryState { - error: Error | null; -} - -class ErrorBoundary extends Component, ErrorBoundaryState> { - state: ErrorBoundaryState = { - error: null, - }; - - static getDerivedStateFromError(error: Error): ErrorBoundaryState { - return { error }; - } - - resetError = () => { - if (this.props.handleReset) { - this.props.handleReset(); - } - - this.setState({ error: null }); - }; - - render() { - const { fallback: FallbackComponent } = this.props; - - if (this.state.error) { - return ( - <> - - - - ); - } - - return this.props.children; - } -} - -export default ErrorBoundary; diff --git a/frontend/src/components/Common/ErrorComponent/ErrorComponent.stories.tsx b/frontend/src/components/Common/ErrorComponent/ErrorComponent.stories.tsx deleted file mode 100644 index ee51fcea..00000000 --- a/frontend/src/components/Common/ErrorComponent/ErrorComponent.stories.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import ErrorComponent from './ErrorComponent'; - -const meta: Meta = { - title: 'common/ErrorComponent', - component: ErrorComponent, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Common/ErrorComponent/ErrorComponent.tsx b/frontend/src/components/Common/ErrorComponent/ErrorComponent.tsx deleted file mode 100644 index bc66e1f1..00000000 --- a/frontend/src/components/Common/ErrorComponent/ErrorComponent.tsx +++ /dev/null @@ -1,5 +0,0 @@ -const ErrorComponent = () => { - return
    에러가 발생했습니다.
    ; -}; - -export default ErrorComponent; diff --git a/frontend/src/components/Common/Header/Header.stories.tsx b/frontend/src/components/Common/Header/Header.stories.tsx deleted file mode 100644 index b88b4258..00000000 --- a/frontend/src/components/Common/Header/Header.stories.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import Header from './Header'; - -const meta: Meta = { - title: 'common/Header', - component: Header, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Common/Header/Header.tsx b/frontend/src/components/Common/Header/Header.tsx deleted file mode 100644 index ae2431ed..00000000 --- a/frontend/src/components/Common/Header/Header.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { Link } from '@fun-eat/design-system'; -import { Link as RouterLink } from 'react-router-dom'; -import styled from 'styled-components'; - -import SvgIcon from '../Svg/SvgIcon'; - -import Logo from '@/assets/logo.svg'; -import { PATH } from '@/constants/path'; - -interface HeaderProps { - hasSearch?: boolean; -} - -const Header = ({ hasSearch = true }: HeaderProps) => { - if (hasSearch) { - return ( - - - - - - - - - ); - } - - return ( - - - - - - ); -}; - -export default Header; - -const HeaderWithSearchContainer = styled.header` - display: flex; - justify-content: space-between; - align-items: center; - width: calc(100% - 40px); - height: 60px; - margin: 0 auto; -`; - -const HeaderContainer = styled.header` - display: flex; - justify-content: center; - align-items: center; - width: 100%; - height: 60px; -`; diff --git a/frontend/src/components/Common/ImageUploader/ImageUploader.stories.tsx b/frontend/src/components/Common/ImageUploader/ImageUploader.stories.tsx deleted file mode 100644 index 02de4489..00000000 --- a/frontend/src/components/Common/ImageUploader/ImageUploader.stories.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import ImageUploader from './ImageUploader'; - -const meta: Meta = { - title: 'common/ImageUploader', - component: ImageUploader, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Common/ImageUploader/ImageUploader.tsx b/frontend/src/components/Common/ImageUploader/ImageUploader.tsx deleted file mode 100644 index 9c915081..00000000 --- a/frontend/src/components/Common/ImageUploader/ImageUploader.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { Button } from '@fun-eat/design-system'; -import type { ChangeEventHandler } from 'react'; -import styled from 'styled-components'; - -import { IMAGE_MAX_SIZE } from '@/constants'; -import { useEnterKeyDown } from '@/hooks/common'; -import { useToastActionContext } from '@/hooks/context'; - -interface ReviewImageUploaderProps { - previewImage: string; - uploadImage: (imageFile: File) => void; - deleteImage: () => void; -} - -const ImageUploader = ({ previewImage, uploadImage, deleteImage }: ReviewImageUploaderProps) => { - const { inputRef, handleKeydown } = useEnterKeyDown(); - const { toast } = useToastActionContext(); - - const handleImageUpload: ChangeEventHandler = (event) => { - if (!event.target.files) { - return; - } - - const imageFile = event.target.files[0]; - - if (imageFile.size > IMAGE_MAX_SIZE) { - toast.error('이미지 크기가 너무 커요. 5MB 이하의 이미지를 골라주세요.'); - event.target.value = ''; - return; - } - - uploadImage(imageFile); - }; - - return ( - <> - {previewImage ? ( - - 업로드한 사진 - - - ) : ( - - + - - - )} - - ); -}; - -export default ImageUploader; - -const ImageUploadLabel = styled.label` - display: flex; - justify-content: center; - align-items: center; - width: 92px; - height: 95px; - border: 1px solid ${({ theme }) => theme.borderColors.disabled}; - border-radius: ${({ theme }) => theme.borderRadius.xs}; - background: ${({ theme }) => theme.colors.gray1}; - cursor: pointer; - - & > input { - display: none; - } -`; - -const PreviewImageWrapper = styled.div` - display: flex; - flex-direction: column; - gap: 20px; - align-items: center; -`; diff --git a/frontend/src/components/Common/Input/Input.stories.tsx b/frontend/src/components/Common/Input/Input.stories.tsx deleted file mode 100644 index 48e8856a..00000000 --- a/frontend/src/components/Common/Input/Input.stories.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import Input from './Input'; -import SvgIcon from '../Svg/SvgIcon'; - -const meta: Meta = { - title: 'common/Input', - component: Input, - argTypes: { - rightIcon: { - control: { type: 'boolean' }, - mapping: { false: '', true: }, - }, - }, - args: { - customWidth: '300px', - isError: false, - rightIcon: false, - errorMessage: '', - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; - -export const WithPlaceholder: Story = { - args: { - placeholder: '상품 이름을 검색하세요.', - }, -}; - -export const WithIcon: Story = { - args: { - placeholder: '상품 이름을 검색하세요.', - rightIcon: true, - }, -}; - -export const Error: Story = { - args: { - isError: true, - errorMessage: '10글자 이내로 입력해주세요.', - }, -}; - -export const Disabled: Story = { - render: () => , -}; diff --git a/frontend/src/components/Common/Input/Input.tsx b/frontend/src/components/Common/Input/Input.tsx deleted file mode 100644 index c3b3b40f..00000000 --- a/frontend/src/components/Common/Input/Input.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import { Text, theme } from '@fun-eat/design-system'; -import type { ComponentPropsWithRef, ForwardedRef, ReactNode } from 'react'; -import { forwardRef } from 'react'; -import styled from 'styled-components'; - -interface InputProps extends ComponentPropsWithRef<'input'> { - /** - * Input 컴포넌트의 너비값입니다. - */ - customWidth?: string; - /** - * Input 컴포넌트의 최소 너비값입니다. - */ - minWidth?: string; - /** - * Input value에 에러가 있는지 여부입니다. - */ - isError?: boolean; - /** - * Input 컴포넌트 오른쪽에 위치할 아이콘입니다. - */ - rightIcon?: ReactNode; - /** - * isError가 true일 때 보여줄 에러 메시지입니다. - */ - errorMessage?: string; -} - -const Input = forwardRef( - ( - { customWidth = '300px', minWidth, isError = false, rightIcon, errorMessage, ...props }: InputProps, - ref: ForwardedRef - ) => { - return ( - <> - - - {rightIcon && {rightIcon}} - - {isError && {errorMessage}} - - ); - } -); - -Input.displayName = 'Input'; - -export default Input; - -type InputContainerStyleProps = Pick; -type CustomInputStyleProps = Pick; - -const InputContainer = styled.div` - position: relative; - min-width: ${({ minWidth }) => minWidth ?? 0}; - max-width: ${({ customWidth }) => customWidth}; - text-align: center; -`; - -const CustomInput = styled.input` - width: 100%; - height: 40px; - padding: 10px 0 10px 12px; - color: ${({ isError }) => (isError ? theme.colors.error : theme.textColors.default)}; - border: 1px solid ${({ isError }) => (isError ? theme.colors.error : theme.borderColors.default)}; - border-radius: 5px; - - &:focus { - border: 2px solid ${({ isError }) => (isError ? theme.colors.error : theme.borderColors.strong)}; - outline: none; - } - - &:disabled { - border: 1px solid ${({ theme }) => theme.borderColors.disabled}; - background: ${({ theme }) => theme.colors.gray1}; - } - - &::placeholder { - color: ${theme.textColors.disabled}; - font-size: ${theme.fontSizes.sm}; - } -`; - -const IconWrapper = styled.div` - position: absolute; - top: 0; - right: 0; - display: flex; - align-items: center; - height: 100%; - margin-right: 8px; -`; - -const ErrorMessage = styled(Text)` - color: ${theme.colors.error}; - font-size: ${theme.fontSizes.xs}; -`; diff --git a/frontend/src/components/Common/Loading/Loading.stories.tsx b/frontend/src/components/Common/Loading/Loading.stories.tsx deleted file mode 100644 index 3b866175..00000000 --- a/frontend/src/components/Common/Loading/Loading.stories.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import Loading from './Loading'; - -const meta: Meta = { - title: 'common/Loading', - component: Loading, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Common/Loading/Loading.tsx b/frontend/src/components/Common/Loading/Loading.tsx deleted file mode 100644 index 7c58614a..00000000 --- a/frontend/src/components/Common/Loading/Loading.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { Text } from '@fun-eat/design-system'; -import styled, { keyframes } from 'styled-components'; - -import PlateImage from '@/assets/plate.svg'; - -const DEFAULT_DESCRIPTION = '잠시만 기다려주세요 🥄'; - -interface LoadingProps { - customHeight?: string; - description?: string; -} - -const Loading = ({ customHeight = '100%', description = DEFAULT_DESCRIPTION }: LoadingProps) => { - return ( - - - - - {description} - - ); -}; - -export default Loading; - -type LoadingContainerStyleProps = Pick; - -const LoadingContainer = styled.div` - display: flex; - flex-direction: column; - row-gap: 36px; - justify-content: center; - align-items: center; - height: ${({ customHeight }) => customHeight}; -`; - -const rotate = keyframes` - 0% { - transform: rotate(0deg); - } - - 100% { - transform: rotate(360deg); - } -`; - -const PlateImageWrapper = styled.div` - animation: ${rotate} 1.5s ease-in-out infinite; -`; diff --git a/frontend/src/components/Common/MarkedText/MarkedText.tsx b/frontend/src/components/Common/MarkedText/MarkedText.tsx deleted file mode 100644 index 341b3c92..00000000 --- a/frontend/src/components/Common/MarkedText/MarkedText.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { Fragment } from 'react'; -import styled from 'styled-components'; - -interface MarkedTextProps { - text: string; - mark: string; -} - -const MarkedText = ({ text, mark }: MarkedTextProps) => { - const textFragments = text.split(new RegExp(`(${mark})`, 'gi')); - - return ( - <> - {textFragments.map((fragment, index) => ( - - {fragment.toLowerCase() === mark.toLowerCase() ? {fragment} : <>{fragment}} - - ))} - - ); -}; - -export default MarkedText; - -const Mark = styled.mark` - font-weight: ${({ theme }) => theme.fontWeights.bold}; - background-color: ${({ theme }) => theme.backgroundColors.default}; -`; diff --git a/frontend/src/components/Common/NavigableSectionTitle/NavigableSectionTitle.stories.tsx b/frontend/src/components/Common/NavigableSectionTitle/NavigableSectionTitle.stories.tsx deleted file mode 100644 index 739bde3e..00000000 --- a/frontend/src/components/Common/NavigableSectionTitle/NavigableSectionTitle.stories.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import NavigableSectionTitle from './NavigableSectionTitle'; - -const meta: Meta = { - title: 'common/NavigableSectionTitle', - component: NavigableSectionTitle, - args: { - title: '내가 작성한 리뷰 (12개)', - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Common/NavigableSectionTitle/NavigableSectionTitle.tsx b/frontend/src/components/Common/NavigableSectionTitle/NavigableSectionTitle.tsx deleted file mode 100644 index a24ba0ed..00000000 --- a/frontend/src/components/Common/NavigableSectionTitle/NavigableSectionTitle.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { Heading, Link, theme } from '@fun-eat/design-system'; -import { Link as RouterLink } from 'react-router-dom'; -import styled from 'styled-components'; - -import { SvgIcon } from '@/components/Common'; - -interface NavigableSectionTitleProps { - title: string; - routeDestination: string; -} - -const NavigableSectionTitle = ({ title, routeDestination }: NavigableSectionTitleProps) => { - return ( - - - {title} - - - - - - ); -}; - -export default NavigableSectionTitle; - -const NavigableSectionTitleContainer = styled.div` - display: flex; - justify-content: space-between; - align-items: center; -`; - -const ArrowIcon = styled(SvgIcon)` - transform: translateY(3px) rotate(180deg); -`; diff --git a/frontend/src/components/Common/NavigationBar/NavigationBar.stories.tsx b/frontend/src/components/Common/NavigationBar/NavigationBar.stories.tsx deleted file mode 100644 index a9242715..00000000 --- a/frontend/src/components/Common/NavigationBar/NavigationBar.stories.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import NavigationBar from './NavigationBar'; - -const meta: Meta = { - title: 'common/NavigationBar', - component: NavigationBar, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - render: () => ( -
    - -
    - ), -}; diff --git a/frontend/src/components/Common/NavigationBar/NavigationBar.tsx b/frontend/src/components/Common/NavigationBar/NavigationBar.tsx deleted file mode 100644 index 8cbfbee3..00000000 --- a/frontend/src/components/Common/NavigationBar/NavigationBar.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { Link, Text, theme } from '@fun-eat/design-system'; -import { Link as RouterLink, useLocation } from 'react-router-dom'; -import styled from 'styled-components'; - -import SvgIcon from '../Svg/SvgIcon'; - -import { NAVIGATION_MENU } from '@/constants'; - -const NavigationBar = () => { - const location = useLocation(); - - return ( - - - {NAVIGATION_MENU.map(({ variant, name, path }) => { - const currentPath = location.pathname.split('/')[1]; - const isSelected = currentPath === path.split('/')[1]; - - return ( - - - - - {name} - - - - ); - })} - - - ); -}; - -export default NavigationBar; - -const NavigationBarContainer = styled.nav` - width: 100%; - height: 60px; -`; - -const NavigationBarList = styled.ul` - display: flex; - justify-content: space-around; - align-items: center; - padding-top: 12px; - border: 1px solid ${({ theme }) => theme.borderColors.disabled}; - border-bottom: none; - border-top-left-radius: 20px; - border-top-right-radius: 20px; -`; - -const NavigationItem = styled.li` - height: 50px; -`; - -const NavigationLink = styled(Link)` - display: flex; - flex-direction: column; - gap: 4px; - justify-content: flex-end; - align-items: center; -`; diff --git a/frontend/src/components/Common/RegisterButton/RegisterButton.tsx b/frontend/src/components/Common/RegisterButton/RegisterButton.tsx deleted file mode 100644 index 1601e560..00000000 --- a/frontend/src/components/Common/RegisterButton/RegisterButton.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { Button } from '@fun-eat/design-system'; -import styled from 'styled-components'; - -import { useMemberQuery } from '@/hooks/queries/members'; - -interface RegisterButtonProps { - activeLabel: string; - disabledLabel: string; - onClick: () => void; -} - -const RegisterButton = ({ activeLabel, disabledLabel, onClick }: RegisterButtonProps) => { - const { data: member } = useMemberQuery(); - - return ( - - {member ? activeLabel : disabledLabel} - - ); -}; - -export default RegisterButton; - -const RegisterButtonContainer = styled(Button)` - cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')}; -`; diff --git a/frontend/src/components/Common/ScrollButton/ScrollButton.stories.tsx b/frontend/src/components/Common/ScrollButton/ScrollButton.stories.tsx deleted file mode 100644 index 50b796d2..00000000 --- a/frontend/src/components/Common/ScrollButton/ScrollButton.stories.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import ScrollButton from './ScrollButton'; - -const meta: Meta = { - title: 'common/ScrollButton', - component: ScrollButton, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Common/ScrollButton/ScrollButton.tsx b/frontend/src/components/Common/ScrollButton/ScrollButton.tsx deleted file mode 100644 index 6bfcc509..00000000 --- a/frontend/src/components/Common/ScrollButton/ScrollButton.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { Button } from '@fun-eat/design-system'; -import type { RefObject } from 'react'; -import { styled } from 'styled-components'; - -import SvgIcon from '../Svg/SvgIcon'; - -import { useScroll } from '@/hooks/common'; - -interface ScrollButtonProps { - targetRef: RefObject; - isRecipePage?: boolean; -} - -const ScrollButton = ({ targetRef, isRecipePage = false }: ScrollButtonProps) => { - const { scrollToTop } = useScroll(); - - const handleScroll = () => { - if (targetRef) { - scrollToTop(targetRef); - } - }; - - return ( - - - - ); -}; - -export default ScrollButton; - -const ScrollButtonWrapper = styled(Button)>` - position: fixed; - bottom: ${({ isRecipePage }) => (isRecipePage ? '210px' : '90px')}; - right: 20px; - border-radius: 50%; - box-shadow: rgba(0, 0, 0, 0.2) 0px 1px 4px; - - @media screen and (min-width: 600px) { - left: calc(50% + 234px); - } - - &:hover { - transform: scale(1.1); - transition: all 200ms ease-in-out; - } - - svg { - rotate: 90deg; - } -`; diff --git a/frontend/src/components/Common/SectionTitle/SectionTitle.stories.tsx b/frontend/src/components/Common/SectionTitle/SectionTitle.stories.tsx deleted file mode 100644 index 05883aea..00000000 --- a/frontend/src/components/Common/SectionTitle/SectionTitle.stories.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import SectionTitle from './SectionTitle'; - -const meta: Meta = { - title: 'common/SectionTitle', - component: SectionTitle, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - args: { - name: '사이다', - }, -}; - -export const Bookmarked: Story = { - args: { - name: '사이다', - }, -}; diff --git a/frontend/src/components/Common/SectionTitle/SectionTitle.tsx b/frontend/src/components/Common/SectionTitle/SectionTitle.tsx deleted file mode 100644 index c9d649dd..00000000 --- a/frontend/src/components/Common/SectionTitle/SectionTitle.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { Button, Heading, Link, theme } from '@fun-eat/design-system'; -import { Link as RouterLink } from 'react-router-dom'; -import styled from 'styled-components'; - -import { SvgIcon } from '@/components/Common'; -import { useRoutePage } from '@/hooks/common'; - -interface SectionTitleProps { - name: string; - link?: string; -} - -const SectionTitle = ({ name, link }: SectionTitleProps) => { - const { routeBack } = useRoutePage(); - - return ( - - - - {link ? ( - - {name} - - ) : ( - {name} - )} - {link && } - - - ); -}; - -export default SectionTitle; - -const SectionTitleContainer = styled.div` - display: flex; - justify-content: space-between; - align-items: center; -`; - -const SectionTitleWrapper = styled.div` - display: flex; - align-items: center; - - svg { - padding-top: 2px; - } -`; - -const ProductName = styled(Heading)` - margin: 0 5px 0 16px; -`; diff --git a/frontend/src/components/Common/Skeleton/Skeleton.stories.tsx b/frontend/src/components/Common/Skeleton/Skeleton.stories.tsx deleted file mode 100644 index e8952c31..00000000 --- a/frontend/src/components/Common/Skeleton/Skeleton.stories.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import Skeleton from './Skeleton'; - -const meta: Meta = { - title: 'common/Skeleton', - component: Skeleton, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - args: { - width: 100, - height: 100, - }, -}; diff --git a/frontend/src/components/Common/Skeleton/Skeleton.tsx b/frontend/src/components/Common/Skeleton/Skeleton.tsx deleted file mode 100644 index 857d0307..00000000 --- a/frontend/src/components/Common/Skeleton/Skeleton.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import type { ComponentPropsWithoutRef } from 'react'; -import styled from 'styled-components'; - -interface SkeletonProps extends ComponentPropsWithoutRef<'div'> { - width?: string | number; - height?: string | number; -} - -const Skeleton = ({ width, height }: SkeletonProps) => { - return ; -}; - -export default Skeleton; - -export const SkeletonContainer = styled.div` - position: absolute; - width: ${({ width }) => (typeof width === 'number' ? width + 'px' : width)}; - height: ${({ height }) => (typeof height === 'number' ? height + 'px' : height)}; - border-radius: 8px; - background: linear-gradient(-90deg, #dddddd, #f7f7f7, #dddddd, #f7f7f7); - background-size: 400%; - overflow: hidden; - animation: skeleton-gradient 5s infinite ease-out; - - @keyframes skeleton-gradient { - 0% { - background-position: 0% 50%; - } - 50% { - background-position: 100% 50%; - } - 100% { - background-position: 0% 50%; - } - } -`; diff --git a/frontend/src/components/Common/SortButton/SortButton.stories.tsx b/frontend/src/components/Common/SortButton/SortButton.stories.tsx deleted file mode 100644 index 004845ef..00000000 --- a/frontend/src/components/Common/SortButton/SortButton.stories.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import SortButton from './SortButton'; - -import { PRODUCT_SORT_OPTIONS } from '@/constants'; - -const meta: Meta = { - title: 'common/SortButton', - component: SortButton, - args: { - option: PRODUCT_SORT_OPTIONS[0], - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Common/SortButton/SortButton.tsx b/frontend/src/components/Common/SortButton/SortButton.tsx deleted file mode 100644 index 54d7acc1..00000000 --- a/frontend/src/components/Common/SortButton/SortButton.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { Button, useTheme } from '@fun-eat/design-system'; -import styled from 'styled-components'; - -import SvgIcon from '../Svg/SvgIcon'; - -import type { SortOption } from '@/types/common'; - -interface SortButtonProps { - option: SortOption; - onClick: () => void; -} - -const SortButton = ({ option, onClick }: SortButtonProps) => { - const theme = useTheme(); - - return ( - - - {option.label} - - ); -}; - -export default SortButton; - -const SortButtonContainer = styled(Button)` - display: flex; - justify-content: flex-end; - align-items: center; - padding: 0; - column-gap: 4px; -`; diff --git a/frontend/src/components/Common/SortOptionList/SortOptionList.stories.tsx b/frontend/src/components/Common/SortOptionList/SortOptionList.stories.tsx deleted file mode 100644 index 5e7c2f93..00000000 --- a/frontend/src/components/Common/SortOptionList/SortOptionList.stories.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { BottomSheet, useBottomSheet } from '@fun-eat/design-system'; -import type { Meta, StoryObj } from '@storybook/react'; -import { useEffect } from 'react'; - -import SortOptionList from './SortOptionList'; - -import { PRODUCT_SORT_OPTIONS } from '@/constants'; -import { useSortOption } from '@/hooks/common'; - -const meta: Meta = { - title: 'common/SortOptionList', - component: SortOptionList, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - render: () => { - const { isOpen, isClosing, handleOpenBottomSheet, handleCloseBottomSheet } = useBottomSheet(); - const { selectedOption, selectSortOption } = useSortOption(PRODUCT_SORT_OPTIONS[0]); - - useEffect(() => { - handleOpenBottomSheet(); - }, []); - - return ( - - - - ); - }, -}; diff --git a/frontend/src/components/Common/SortOptionList/SortOptionList.tsx b/frontend/src/components/Common/SortOptionList/SortOptionList.tsx deleted file mode 100644 index fb62c72c..00000000 --- a/frontend/src/components/Common/SortOptionList/SortOptionList.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { Button } from '@fun-eat/design-system'; -import styled from 'styled-components'; - -import type { SortOption } from '@/types/common'; - -interface SortOptionListProps { - options: readonly SortOption[]; - selectedOption: SortOption; - selectSortOption: (selectedOptionLabel: SortOption) => void; - close: () => void; -} - -const SortOptionList = ({ options, selectedOption, selectSortOption, close }: SortOptionListProps) => { - const handleSelectedOption = (sortOption: SortOption) => { - selectSortOption(sortOption); - close(); - }; - - return ( - - {options.map((sortOption) => { - const isSelected = sortOption.label === selectedOption.label; - return ( -
  • - handleSelectedOption(sortOption)} - > - {sortOption.label} - -
  • - ); - })} -
    - ); -}; - -export default SortOptionList; - -const SortOptionListContainer = styled.ul` - padding: 20px; - - & > li { - height: 60px; - line-height: 60px; - border-bottom: 1px solid ${({ theme }) => theme.dividerColors.disabled}; - } - - & > li:last-of-type { - border: none; - } -`; - -const SortOptionButton = styled(Button)` - padding: 10px 0; - text-align: left; - border: none; - outline: transparent; - - &:hover { - color: ${({ theme }) => theme.textColors.default}; - font-weight: ${({ theme }) => theme.fontWeights.bold}; - transition: all 200ms ease-in; - } -`; diff --git a/frontend/src/components/Common/Svg/SvgIcon.stories.tsx b/frontend/src/components/Common/Svg/SvgIcon.stories.tsx deleted file mode 100644 index 87c7f7e0..00000000 --- a/frontend/src/components/Common/Svg/SvgIcon.stories.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { theme } from '@fun-eat/design-system'; -import type { Meta, StoryObj } from '@storybook/react'; - -import SvgIcon, { SVG_ICON_VARIANTS } from './SvgIcon'; - -const meta: Meta = { - title: 'common/SvgIcon', - component: SvgIcon, - argTypes: { - color: { - control: { - type: 'color', - }, - }, - }, - args: { - variant: 'recipe', - color: theme.colors.gray4, - }, -}; - -export default meta; -type Story = StoryObj; - -export const Playground: Story = {}; - -export const SvgIcons: Story = { - render: () => { - return ( - <> - {SVG_ICON_VARIANTS.map((variant) => ( - - ))} - - ); - }, -}; diff --git a/frontend/src/components/Common/Svg/SvgIcon.tsx b/frontend/src/components/Common/Svg/SvgIcon.tsx deleted file mode 100644 index e31256ae..00000000 --- a/frontend/src/components/Common/Svg/SvgIcon.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { theme } from '@fun-eat/design-system'; -import type { ComponentPropsWithoutRef, CSSProperties } from 'react'; - -export const SVG_ICON_VARIANTS = [ - 'recipe', - 'list', - 'member', - 'search', - 'arrow', - 'bookmark', - 'bookmarkFilled', - 'review', - 'star', - 'favorite', - 'favoriteFilled', - 'home', - 'sort', - 'kakao', - 'close', - 'triangle', - 'plus', - 'pencil', - 'camera', - 'link', - 'plane', - 'info', - 'trashcan', -] as const; -export type SvgIconVariant = (typeof SVG_ICON_VARIANTS)[number]; - -interface SvgIconProps extends ComponentPropsWithoutRef<'svg'> { - /** - * SvgSprite 컴포넌트의 symbol id입니다. - */ - variant: SvgIconVariant; - /** - * SvgIcon의 색상입니다. (기본값 gray4) - */ - color?: CSSProperties['color']; - /** - * SvgIcon의 너비입니다. (기본값 24) - */ - width?: number; - /** - * SvgIcon의 높이입니다. (기본값 24) - */ - height?: number; -} - -const SvgIcon = ({ variant, width = 24, height = 24, color = theme.colors.gray4, ...props }: SvgIconProps) => { - return ( - - - - ); -}; - -export default SvgIcon; diff --git a/frontend/src/components/Common/Svg/SvgSprite.tsx b/frontend/src/components/Common/Svg/SvgSprite.tsx deleted file mode 100644 index e0fa06dd..00000000 --- a/frontend/src/components/Common/Svg/SvgSprite.tsx +++ /dev/null @@ -1,96 +0,0 @@ -const SvgSprite = () => { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); -}; - -export default SvgSprite; diff --git a/frontend/src/components/Common/TabMenu/TabMenu.stories.tsx b/frontend/src/components/Common/TabMenu/TabMenu.stories.tsx deleted file mode 100644 index 4ba72445..00000000 --- a/frontend/src/components/Common/TabMenu/TabMenu.stories.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import TabMenu from './TabMenu'; - -const meta: Meta = { - title: 'common/TabMenu', - component: TabMenu, - args: { - tabMenus: ['리뷰 1,200', '꿀조합'], - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - render: ({ ...args }) => ( -
    - -
    - ), -}; diff --git a/frontend/src/components/Common/TabMenu/TabMenu.tsx b/frontend/src/components/Common/TabMenu/TabMenu.tsx deleted file mode 100644 index 548b2f36..00000000 --- a/frontend/src/components/Common/TabMenu/TabMenu.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { Button } from '@fun-eat/design-system'; -import type { ForwardedRef, MouseEventHandler } from 'react'; -import { forwardRef } from 'react'; -import styled from 'styled-components'; - -interface TabMenuProps { - tabMenus: readonly string[]; - selectedTabMenu: number; - handleTabMenuSelect: (index: number) => void; -} - -const TabMenu = ( - { tabMenus, selectedTabMenu, handleTabMenuSelect }: TabMenuProps, - ref: ForwardedRef -) => { - const handleTabMenuClick: MouseEventHandler = (event) => { - const { index } = event.currentTarget.dataset; - - if (index) { - handleTabMenuSelect(Number(index)); - } - }; - - return ( - - {tabMenus.map((menu, index) => { - const isSelected = selectedTabMenu === index; - return ( - - - {menu} - - - ); - })} - - ); -}; - -export default forwardRef(TabMenu); - -const TabMenuContainer = styled.ul` - display: flex; -`; - -const TabMenuItem = styled.li<{ isSelected: boolean }>` - flex-grow: 1; - width: 50%; - height: 45px; - border-bottom: 2px solid - ${({ isSelected, theme }) => (isSelected ? theme.borderColors.strong : theme.borderColors.disabled)}; -`; - -const TabMenuButton = styled(Button)` - padding: 0; - line-height: 45px; -`; diff --git a/frontend/src/components/Common/TagList/TagList.stories.tsx b/frontend/src/components/Common/TagList/TagList.stories.tsx deleted file mode 100644 index 4ac76785..00000000 --- a/frontend/src/components/Common/TagList/TagList.stories.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import TagList from './TagList'; - -import productDetails from '@/mocks/data/productDetails.json'; - -const meta: Meta = { - title: 'common/TagList', - component: TagList, - args: { - tags: productDetails[0].tags, - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Common/TagList/TagList.tsx b/frontend/src/components/Common/TagList/TagList.tsx deleted file mode 100644 index b36cd70a..00000000 --- a/frontend/src/components/Common/TagList/TagList.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Badge } from '@fun-eat/design-system'; -import styled from 'styled-components'; - -import type { Tag } from '@/types/common'; -import { convertTagColor } from '@/utils/convertTagColor'; - -interface TagListProps { - tags: Tag[]; -} - -const TagList = ({ tags }: TagListProps) => { - return ( - - {tags.map((tag) => { - const tagColor = convertTagColor(tag.tagType); - return ( -
  • - - {tag.name} - -
  • - ); - })} -
    - ); -}; - -export default TagList; - -const TagListContainer = styled.ul` - display: flex; - margin: 12px 0; - column-gap: 8px; -`; - -const TagBadge = styled(Badge)` - font-weight: bold; -`; diff --git a/frontend/src/components/Common/Toast/Toast.stories.tsx b/frontend/src/components/Common/Toast/Toast.stories.tsx deleted file mode 100644 index 383c4375..00000000 --- a/frontend/src/components/Common/Toast/Toast.stories.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import Toast from './Toast'; - -import ToastProvider from '@/contexts/ToastContext'; -import { useToastActionContext } from '@/hooks/context'; - -const meta: Meta = { - title: 'common/Toast', - component: Toast, - decorators: [ - (Story) => ( - - - - ), - ], -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - render: () => { - const { toast } = useToastActionContext(); - const handleClick = () => { - toast.success('성공'); - }; - return ( -
    - -
    - ); - }, -}; - -export const Error: Story = { - render: () => { - const { toast } = useToastActionContext(); - const handleClick = () => { - toast.error('실패'); - }; - return ( -
    - -
    - ); - }, -}; diff --git a/frontend/src/components/Common/Toast/Toast.tsx b/frontend/src/components/Common/Toast/Toast.tsx deleted file mode 100644 index c9d9dc46..00000000 --- a/frontend/src/components/Common/Toast/Toast.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { Text, useTheme } from '@fun-eat/design-system'; -import styled from 'styled-components'; - -import { useToast } from '@/hooks/common'; -import { fadeOut, slideIn } from '@/styles/animations'; - -interface ToastProps { - id: number; - message: string; - isError?: boolean; -} - -const Toast = ({ id, message, isError = false }: ToastProps) => { - const theme = useTheme(); - const isShown = useToast(id); - - return ( - - {message} - - ); -}; - -export default Toast; - -type ToastStyleProps = Pick & { isAnimating?: boolean }; - -const ToastWrapper = styled.div` - position: relative; - width: calc(100% - 20px); - height: 55px; - max-width: 560px; - border-radius: 10px; - background: ${({ isError, theme }) => (isError ? theme.colors.error : theme.colors.black)}; - animation: ${({ isAnimating }) => (isAnimating ? slideIn : fadeOut)} 0.3s ease-in-out forwards; -`; - -const Message = styled(Text)` - margin-left: 20px; - line-height: 55px; -`; diff --git a/frontend/src/components/Common/index.ts b/frontend/src/components/Common/index.ts deleted file mode 100644 index 070263f5..00000000 --- a/frontend/src/components/Common/index.ts +++ /dev/null @@ -1,27 +0,0 @@ -export { default as CategoryFoodTab } from './CategoryFoodTab/CategoryFoodTab'; -export { default as CategoryStoreTab } from './CategoryStoreTab/CategoryStoreTab'; -export { default as Header } from './Header/Header'; -export { default as NavigationBar } from './NavigationBar/NavigationBar'; -export { default as SortButton } from './SortButton/SortButton'; -export { default as SortOptionList } from './SortOptionList/SortOptionList'; -export { default as SvgSprite } from './Svg/SvgSprite'; -export { default as SvgIcon } from './Svg/SvgIcon'; -export { default as TabMenu } from './TabMenu/TabMenu'; -export { default as TagList } from './TagList/TagList'; -export { default as SectionTitle } from './SectionTitle/SectionTitle'; -export { default as ScrollButton } from './ScrollButton/ScrollButton'; -export { default as Input } from './Input/Input'; -export { default as ImageUploader } from './ImageUploader/ImageUploader'; -export { default as ErrorBoundary } from './ErrorBoundary/ErrorBoundary'; -export { default as ErrorComponent } from './ErrorComponent/ErrorComponent'; -export { default as Loading } from './Loading/Loading'; -export { default as MarkedText } from './MarkedText/MarkedText'; -export { default as NavigableSectionTitle } from './NavigableSectionTitle/NavigableSectionTitle'; -export { default as Carousel } from './Carousel/Carousel'; -export { default as RegisterButton } from './RegisterButton/RegisterButton'; -export { default as Toast } from './Toast/Toast'; -export { default as CategoryItem } from './CategoryItem/CategoryItem'; -export { default as CategoryFoodList } from './CategoryFoodList/CategoryFoodList'; -export { default as CategoryStoreList } from './CategoryStoreList/CategoryStoreList'; -export { default as Skeleton } from './Skeleton/Skeleton'; -export { default as Banner } from './Banner/Banner'; diff --git a/frontend/src/components/Layout/AuthLayout.tsx b/frontend/src/components/Layout/AuthLayout.tsx deleted file mode 100644 index 0cc0570c..00000000 --- a/frontend/src/components/Layout/AuthLayout.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { Navigate } from 'react-router-dom'; - -import { PATH } from '@/constants/path'; -import { useMemberQuery } from '@/hooks/queries/members'; - -interface AuthLayoutProps { - children: JSX.Element; -} - -const AuthLayout = ({ children }: AuthLayoutProps) => { - const { data: member } = useMemberQuery(); - - if (!member) { - return ; - } - - return children; -}; - -export default AuthLayout; diff --git a/frontend/src/components/Layout/DefaultLayout.tsx b/frontend/src/components/Layout/DefaultLayout.tsx deleted file mode 100644 index 39f1f8cf..00000000 --- a/frontend/src/components/Layout/DefaultLayout.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import type { PropsWithChildren } from 'react'; -import styled from 'styled-components'; - -import Header from '../Common/Header/Header'; -import NavigationBar from '../Common/NavigationBar/NavigationBar'; - -const DefaultLayout = ({ children }: PropsWithChildren) => { - return ( - -
    - {children} - - - ); -}; - -export default DefaultLayout; - -const DefaultLayoutContainer = styled.div` - height: 100%; - max-width: 600px; - margin: 0 auto; -`; - -const MainWrapper = styled.main` - position: relative; - height: calc(100% - 120px); - overflow-x: hidden; - overflow-y: auto; -`; diff --git a/frontend/src/components/Layout/HeaderOnlyLayout.tsx b/frontend/src/components/Layout/HeaderOnlyLayout.tsx deleted file mode 100644 index ec3ad899..00000000 --- a/frontend/src/components/Layout/HeaderOnlyLayout.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import type { PropsWithChildren } from 'react'; -import styled from 'styled-components'; - -import Header from '../Common/Header/Header'; - -const HeaderOnlyLayout = ({ children }: PropsWithChildren) => { - return ( - -
    - {children} - - ); -}; - -export default HeaderOnlyLayout; - -const HeaderOnlyLayoutContainer = styled.div` - height: 100%; - max-width: 600px; - margin: 0 auto; -`; - -const MainWrapper = styled.main` - position: relative; - height: calc(100% - 60px); - padding: 20px; - overflow-x: hidden; - overflow-y: auto; -`; diff --git a/frontend/src/components/Layout/MinimalLayout.tsx b/frontend/src/components/Layout/MinimalLayout.tsx deleted file mode 100644 index 1fdfc36f..00000000 --- a/frontend/src/components/Layout/MinimalLayout.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import type { PropsWithChildren } from 'react'; -import styled from 'styled-components'; - -const MinimalLayout = ({ children }: PropsWithChildren) => { - return ( - - {children} - - ); -}; - -export default MinimalLayout; - -const MinimalLayoutContainer = styled.div` - height: 100%; - max-width: 600px; - margin: 0 auto; -`; - -const MainWrapper = styled.main` - position: relative; - height: 100%; - padding: 20px; - overflow-x: hidden; - overflow-y: auto; -`; diff --git a/frontend/src/components/Layout/SimpleHeaderLayout.tsx b/frontend/src/components/Layout/SimpleHeaderLayout.tsx deleted file mode 100644 index 4f17b93a..00000000 --- a/frontend/src/components/Layout/SimpleHeaderLayout.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import type { PropsWithChildren } from 'react'; -import styled from 'styled-components'; - -import Header from '../Common/Header/Header'; -import NavigationBar from '../Common/NavigationBar/NavigationBar'; - -const SimpleHeaderLayout = ({ children }: PropsWithChildren) => { - return ( - -
    - {children} - - - ); -}; - -export default SimpleHeaderLayout; - -const SimpleHeaderLayoutContainer = styled.div` - height: 100%; - max-width: 600px; - margin: 0 auto; -`; - -const MainWrapper = styled.main` - position: relative; - height: calc(100% - 120px); - padding: 20px 20px 0; - overflow-x: hidden; - overflow-y: auto; -`; diff --git a/frontend/src/components/Layout/index.ts b/frontend/src/components/Layout/index.ts deleted file mode 100644 index 69ec1a40..00000000 --- a/frontend/src/components/Layout/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { default as DefaultLayout } from './DefaultLayout'; -export { default as MinimalLayout } from './MinimalLayout'; -export { default as HeaderOnlyLayout } from './HeaderOnlyLayout'; -export { default as AuthLayout } from './AuthLayout'; -export { default as SimpleHeaderLayout } from './SimpleHeaderLayout'; diff --git a/frontend/src/components/Members/MemberModifyInput/MemberModifyInput.tsx b/frontend/src/components/Members/MemberModifyInput/MemberModifyInput.tsx deleted file mode 100644 index 39a78e6c..00000000 --- a/frontend/src/components/Members/MemberModifyInput/MemberModifyInput.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { Heading, Spacing, Text, useTheme } from '@fun-eat/design-system'; -import type { ChangeEventHandler } from 'react'; -import styled from 'styled-components'; - -import { Input } from '@/components/Common'; - -const MIN_LENGTH = 1; -const MAX_LENGTH = 10; - -interface MemberModifyInputProps { - nickname: string; - modifyNickname: ChangeEventHandler; -} - -const MemberModifyInput = ({ nickname, modifyNickname }: MemberModifyInputProps) => { - const theme = useTheme(); - - return ( - - - 닉네임 - - - {nickname.length}자 / {MAX_LENGTH}자 - - - - - ); -}; - -export default MemberModifyInput; - -const MemberModifyInputContainer = styled.div` - position: relative; -`; - -const NicknameStatusText = styled(Text)` - position: absolute; - top: 0; - right: 0; -`; diff --git a/frontend/src/components/Members/MemberRecipeList/MemberRecipeList.stories.tsx b/frontend/src/components/Members/MemberRecipeList/MemberRecipeList.stories.tsx deleted file mode 100644 index 9b28324e..00000000 --- a/frontend/src/components/Members/MemberRecipeList/MemberRecipeList.stories.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import MemberRecipeList from './MemberRecipeList'; - -const meta: Meta = { - title: 'members/ MemberRecipeList', - component: MemberRecipeList, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Members/MemberRecipeList/MemberRecipeList.tsx b/frontend/src/components/Members/MemberRecipeList/MemberRecipeList.tsx deleted file mode 100644 index 740daddf..00000000 --- a/frontend/src/components/Members/MemberRecipeList/MemberRecipeList.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { Link, Spacing, Text, theme } from '@fun-eat/design-system'; -import { useRef } from 'react'; -import { Link as RouterLink } from 'react-router-dom'; -import styled from 'styled-components'; - -import { RecipeItem } from '@/components/Recipe'; -import { PATH } from '@/constants/path'; -import { useIntersectionObserver } from '@/hooks/common'; -import { useInfiniteMemberRecipeQuery } from '@/hooks/queries/members'; -import useDisplaySlice from '@/utils/displaySlice'; - -interface MemberRecipeListProps { - isPreview?: boolean; -} - -const MemberRecipeList = ({ isPreview = false }: MemberRecipeListProps) => { - const scrollRef = useRef(null); - - const { fetchNextPage, hasNextPage, data } = useInfiniteMemberRecipeQuery(); - const memberRecipes = data?.pages.flatMap((page) => page.recipes); - const recipeToDisplay = useDisplaySlice(isPreview, memberRecipes); - - useIntersectionObserver(fetchNextPage, scrollRef, hasNextPage); - - const totalRecipeCount = data?.pages[0].page.totalDataCount; - - if (totalRecipeCount === 0) { - return ( - - - 앗, 작성한 꿀조합이 없네요 🥲 - - - - 꿀조합 작성하러 가기 - - - ); - } - - return ( - - {!isPreview && ( - - 총 {totalRecipeCount}개의 꿀조합을 남겼어요! - - )} - - - {recipeToDisplay?.map((recipe) => ( -
  • - - - -
  • - ))} -
    -
    - - ); -}; - -export default MemberRecipeList; - -const MemberRecipeListContainer = styled.section` - display: flex; - flex-direction: column; -`; - -const MemberRecipeListWrapper = styled.ul` - display: flex; - flex-direction: column; - gap: 20px; -`; - -const TotalRecipeCount = styled(Text)` - text-align: right; -`; - -const ErrorContainer = styled.div` - display: flex; - flex-direction: column; - align-items: center; - margin-top: 20px; -`; - -const RecipeLink = styled(Link)` - padding: 12px 12px; - border: 1px solid ${({ theme }) => theme.colors.gray4}; - border-radius: 8px; -`; diff --git a/frontend/src/components/Members/MemberReviewItem/MemberReviewItem.stories.tsx b/frontend/src/components/Members/MemberReviewItem/MemberReviewItem.stories.tsx deleted file mode 100644 index a631341d..00000000 --- a/frontend/src/components/Members/MemberReviewItem/MemberReviewItem.stories.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import MemberReviewItem from './MemberReviewItem'; - -import ToastProvider from '@/contexts/ToastContext'; - -const meta: Meta = { - title: 'members/MemberReviewItem', - component: MemberReviewItem, - decorators: [ - (Story) => ( - - - - ), - ], - args: { - review: { - reviewId: 1, - productId: 5, - productName: '구운감자슬림명란마요', - content: - '할머니가 먹을 거 같은 맛입니다. 1960년 전쟁 때 맛 보고 싶었는데 그때는 너무 가난해서 먹을 수 없었는데요 이것보다 긴 리뷰도 잘려 보인답니다', - rating: 4.0, - favoriteCount: 1256, - categoryType: 'food', - }, - isPreview: true, - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Members/MemberReviewItem/MemberReviewItem.tsx b/frontend/src/components/Members/MemberReviewItem/MemberReviewItem.tsx deleted file mode 100644 index 1d450385..00000000 --- a/frontend/src/components/Members/MemberReviewItem/MemberReviewItem.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import { useTheme, Spacing, Text, Button } from '@fun-eat/design-system'; -import type { MouseEventHandler } from 'react'; -import styled from 'styled-components'; - -import { SvgIcon } from '@/components/Common'; -import { useToastActionContext } from '@/hooks/context'; -import { useDeleteReview } from '@/hooks/queries/members'; -import type { MemberReview } from '@/types/review'; - -interface MemberReviewItemProps { - review: MemberReview; - isPreview: boolean; -} - -const MemberReviewItem = ({ review, isPreview }: MemberReviewItemProps) => { - const theme = useTheme(); - - const { mutate } = useDeleteReview(); - - const { toast } = useToastActionContext(); - - const { reviewId, productName, content, rating, favoriteCount } = review; - - const handleReviewDelete: MouseEventHandler = (e) => { - e.preventDefault(); - - const result = window.confirm('리뷰를 삭제하시겠습니까?'); - if (!result) { - return; - } - - mutate(reviewId, { - onSuccess: () => { - toast.success('리뷰를 삭제했습니다.'); - }, - onError: (error) => { - if (error instanceof Error) { - toast.error(error.message); - return; - } - - toast.error('리뷰 좋아요를 다시 시도해주세요.'); - }, - }); - }; - - return ( - - - - {productName} - - {!isPreview && ( - - )} - - - {content} - - - - - - - {favoriteCount} - - - - - - {rating.toFixed(1)} - - - - - ); -}; - -export default MemberReviewItem; - -const ReviewRankingItemContainer = styled.div` - display: flex; - flex-direction: column; - gap: 4px; - padding: 12px 0; - border-bottom: ${({ theme }) => `1px solid ${theme.borderColors.disabled}`}; -`; - -const ProductNameIconWrapper = styled.div` - display: flex; - justify-content: space-between; -`; - -const ReviewText = styled(Text)` - display: -webkit-inline-box; - text-overflow: ellipsis; - overflow: hidden; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; -`; - -const FavoriteStarWrapper = styled.div` - display: flex; - gap: 4px; -`; - -const FavoriteIconWrapper = styled.div` - display: flex; - gap: 4px; - align-items: center; -`; - -const RatingIconWrapper = styled.div` - display: flex; - gap: 2px; - align-items: center; - - & > svg { - padding-bottom: 2px; - } -`; diff --git a/frontend/src/components/Members/MemberReviewList/MemberReviewList.stories.tsx b/frontend/src/components/Members/MemberReviewList/MemberReviewList.stories.tsx deleted file mode 100644 index 97c95ac9..00000000 --- a/frontend/src/components/Members/MemberReviewList/MemberReviewList.stories.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import MemberReviewList from './MemberReviewList'; - -const meta: Meta = { - title: 'members/MemberReviewList', - component: MemberReviewList, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Members/MemberReviewList/MemberReviewList.tsx b/frontend/src/components/Members/MemberReviewList/MemberReviewList.tsx deleted file mode 100644 index b622d9f6..00000000 --- a/frontend/src/components/Members/MemberReviewList/MemberReviewList.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { Link, Spacing, Text, theme } from '@fun-eat/design-system'; -import { useRef } from 'react'; -import { Link as RouterLink } from 'react-router-dom'; -import styled from 'styled-components'; - -import MemberReviewItem from '../MemberReviewItem/MemberReviewItem'; - -import { PATH } from '@/constants/path'; -import { useIntersectionObserver } from '@/hooks/common'; -import { useInfiniteMemberReviewQuery } from '@/hooks/queries/members'; -import useDisplaySlice from '@/utils/displaySlice'; - -interface MemberReviewListProps { - isPreview?: boolean; -} - -const MemberReviewList = ({ isPreview = false }: MemberReviewListProps) => { - const scrollRef = useRef(null); - const { fetchNextPage, hasNextPage, data } = useInfiniteMemberReviewQuery(); - const memberReviews = data.pages.flatMap((page) => page.reviews); - const reviewsToDisplay = useDisplaySlice(isPreview, memberReviews); - - useIntersectionObserver(fetchNextPage, scrollRef, hasNextPage); - - const totalReviewCount = data.pages[0].page.totalDataCount; - - if (totalReviewCount === 0) { - return ( - - - 앗, 작성한 리뷰가 없네요 🥲 - - - - 리뷰 작성하러 가기 - - - ); - } - - return ( - - {!isPreview && ( - - 총 {totalReviewCount}개의 리뷰를 남겼어요! - - )} - - - {reviewsToDisplay.map((review) => ( -
  • - - - -
  • - ))} -
    -
    - - ); -}; - -export default MemberReviewList; - -const MemberReviewListContainer = styled.section` - display: flex; - flex-direction: column; -`; - -const MemberReviewListWrapper = styled.ul` - display: flex; - flex-direction: column; - gap: 20px; -`; - -const TotalReviewCount = styled(Text)` - text-align: right; -`; - -const ErrorContainer = styled.div` - display: flex; - flex-direction: column; - align-items: center; - margin-top: 20px; -`; - -const ReviewLink = styled(Link)` - padding: 12px 12px; - border: 1px solid ${({ theme }) => theme.colors.gray4}; - border-radius: 8px; -`; diff --git a/frontend/src/components/Members/MembersInfo/MembersInfo.tsx b/frontend/src/components/Members/MembersInfo/MembersInfo.tsx deleted file mode 100644 index 1b2e9483..00000000 --- a/frontend/src/components/Members/MembersInfo/MembersInfo.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { Button, Heading, Link, theme } from '@fun-eat/design-system'; -import { Link as RouterLink } from 'react-router-dom'; -import styled from 'styled-components'; - -import { SvgIcon } from '@/components/Common'; -import { PATH } from '@/constants/path'; -import { useLogoutMutation, useMemberQuery } from '@/hooks/queries/members'; - -const MembersInfo = () => { - const { data: member } = useMemberQuery(); - const { mutate } = useLogoutMutation(); - - if (!member) { - return null; - } - - const { nickname, profileImage } = member; - - const handleLogout = () => { - mutate(); - }; - - return ( - - - - - {nickname} 님 - - - - - - - - ); -}; - -export default MembersInfo; - -const MembersInfoContainer = styled.div` - display: flex; - justify-content: space-between; - align-items: center; -`; - -const MemberInfoWrapper = styled.div` - display: flex; - align-items: center; -`; - -const MemberModifyLink = styled(Link)` - margin-left: 5px; - transform: translateY(1px); -`; - -const MembersImage = styled.img` - margin-right: 16px; - border: 2px solid ${({ theme }) => theme.colors.primary}; - border-radius: 50%; - object-fit: cover; -`; diff --git a/frontend/src/components/Members/MembersInfo/MyPageInfo.stories.tsx b/frontend/src/components/Members/MembersInfo/MyPageInfo.stories.tsx deleted file mode 100644 index a44e59f4..00000000 --- a/frontend/src/components/Members/MembersInfo/MyPageInfo.stories.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import MembersInfo from './MembersInfo'; - -import mockMember from '@/mocks/data/members.json'; - -const meta: Meta = { - title: 'members/MembersInfo', - component: MembersInfo, - args: { - member: mockMember, - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Members/index.ts b/frontend/src/components/Members/index.ts deleted file mode 100644 index 4e31460e..00000000 --- a/frontend/src/components/Members/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { default as MembersInfo } from './MembersInfo/MembersInfo'; -export { default as MemberReviewList } from './MemberReviewList/MemberReviewList'; -export { default as MemberRecipeList } from './MemberRecipeList/MemberRecipeList'; -export { default as MemberModifyInput } from './MemberModifyInput/MemberModifyInput'; -export { default as MemberReviewItem } from './MemberReviewItem/MemberReviewItem'; diff --git a/frontend/src/components/Product/ProductDetailItem/ProductDetailItem.stories.tsx b/frontend/src/components/Product/ProductDetailItem/ProductDetailItem.stories.tsx deleted file mode 100644 index 32226fec..00000000 --- a/frontend/src/components/Product/ProductDetailItem/ProductDetailItem.stories.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import ProductDetailItem from './ProductDetailItem'; - -import productDetail from '@/mocks/data/productDetail.json'; - -const meta: Meta = { - title: 'product/ProductDetailItem', - component: ProductDetailItem, - args: { - productDetail: productDetail, - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - render: ({ ...args }) => ( -
    - -
    - ), -}; diff --git a/frontend/src/components/Product/ProductDetailItem/ProductDetailItem.tsx b/frontend/src/components/Product/ProductDetailItem/ProductDetailItem.tsx deleted file mode 100644 index b91b97e2..00000000 --- a/frontend/src/components/Product/ProductDetailItem/ProductDetailItem.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { Text, useTheme } from '@fun-eat/design-system'; -import styled from 'styled-components'; - -import PreviewImage from '@/assets/characters.svg'; -import PBPreviewImage from '@/assets/samgakgimbab.svg'; -import { SvgIcon, TagList } from '@/components/Common'; -import { CATEGORY_TYPE } from '@/constants'; -import type { ProductDetail } from '@/types/product'; - -interface ProductDetailItemProps { - category: string; - productDetail: ProductDetail; -} - -const ProductDetailItem = ({ category, productDetail }: ProductDetailItemProps) => { - const { name, price, image, content, averageRating, tags } = productDetail; - - const theme = useTheme(); - - return ( - - - {image ? ( - {name} - ) : category === CATEGORY_TYPE.FOOD ? ( - - ) : ( - - )} - - - - - 가격 - {price.toLocaleString('ko-KR')}원 - - - 상품 설명 - {content} - - - 평균 평점 - - - {averageRating.toFixed(1)} - - - - - ); -}; - -export default ProductDetailItem; - -const ProductDetailContainer = styled.div` - display: flex; - flex-direction: column; - row-gap: 30px; - g & > img, - svg { - align-self: center; - } -`; - -const ImageWrapper = styled.div` - display: flex; - justify-content: center; - align-items: center; -`; - -const DetailInfoWrapper = styled.div` - & > div + div { - margin-top: 10px; - } -`; - -const DescriptionWrapper = styled.div` - display: flex; - column-gap: 20px; - - & > p:first-of-type { - flex-shrink: 0; - width: 60px; - } -`; - -const ProductContent = styled(Text)` - white-space: pre-wrap; -`; - -const RatingIconWrapper = styled.div` - display: flex; - align-items: center; - margin-left: -4px; - column-gap: 4px; - - & > svg { - padding-bottom: 2px; - } -`; diff --git a/frontend/src/components/Product/ProductItem/ProductItem.stories.tsx b/frontend/src/components/Product/ProductItem/ProductItem.stories.tsx deleted file mode 100644 index 6afb4a23..00000000 --- a/frontend/src/components/Product/ProductItem/ProductItem.stories.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import ProductItem from './ProductItem'; - -import mockProducts from '@/mocks/data/products.json'; - -const meta: Meta = { - title: 'product/ProductItem', - component: ProductItem, - args: { - product: mockProducts.products[0], - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Product/ProductItem/ProductItem.tsx b/frontend/src/components/Product/ProductItem/ProductItem.tsx deleted file mode 100644 index 43dc773e..00000000 --- a/frontend/src/components/Product/ProductItem/ProductItem.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import { Text, useTheme } from '@fun-eat/design-system'; -import { memo, useState } from 'react'; -import { useParams } from 'react-router-dom'; -import styled from 'styled-components'; - -import PreviewImage from '@/assets/characters.svg'; -import PBPreviewImage from '@/assets/samgakgimbab.svg'; -import { Skeleton, SvgIcon } from '@/components/Common'; -import { CATEGORY_TYPE } from '@/constants'; -import type { Product } from '@/types/product'; - -interface ProductItemProps { - product: Product; -} - -const ProductItem = ({ product }: ProductItemProps) => { - const theme = useTheme(); - const { category } = useParams(); - const { name, price, image, averageRating, reviewCount } = product; - const [isImageLoading, setIsImageLoading] = useState(true); - - return ( - - {image ? ( - <> - setIsImageLoading(false)} - /> - {isImageLoading && } - - ) : category === CATEGORY_TYPE.FOOD ? ( - - ) : ( - - )} - - - {name} - - - {price.toLocaleString('ko-KR')}원 - - - - - - {averageRating.toFixed(1)} - - - - - - {reviewCount} - - - - - - ); -}; - -export default memo(ProductItem); - -const ProductItemContainer = styled.div` - position: relative; - display: flex; - align-items: center; - padding: 12px 0; -`; - -const ProductImage = styled.img` - object-fit: cover; -`; - -const ProductInfoWrapper = styled.div` - display: flex; - flex-direction: column; - justify-content: space-between; - height: 100%; - margin-left: 30px; -`; - -const ProductReviewWrapper = styled.div` - display: flex; - margin-left: -2px; - column-gap: 20px; -`; - -const RatingIconWrapper = styled.div` - display: flex; - align-items: center; - column-gap: 4px; - - & > svg { - padding-bottom: 2px; - } -`; - -const ReviewIconWrapper = styled.div` - display: flex; - align-items: center; - column-gap: 4px; - - & > svg { - padding-top: 2px; - } -`; diff --git a/frontend/src/components/Product/ProductList/ProductList.stories.tsx b/frontend/src/components/Product/ProductList/ProductList.stories.tsx deleted file mode 100644 index de89073b..00000000 --- a/frontend/src/components/Product/ProductList/ProductList.stories.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import ProductList from './ProductList'; - -const meta: Meta = { - title: 'product/ProductList', - component: ProductList, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Product/ProductList/ProductList.tsx b/frontend/src/components/Product/ProductList/ProductList.tsx deleted file mode 100644 index 2bdaf30e..00000000 --- a/frontend/src/components/Product/ProductList/ProductList.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { Link } from '@fun-eat/design-system'; -import { useRef } from 'react'; -import { Link as RouterLink } from 'react-router-dom'; -import styled from 'styled-components'; - -import ProductItem from '../ProductItem/ProductItem'; - -import { PATH } from '@/constants/path'; -import { useIntersectionObserver } from '@/hooks/common'; -import { useCategoryValueContext } from '@/hooks/context'; -import { useInfiniteProductsQuery } from '@/hooks/queries/product'; -import type { CategoryVariant, SortOption } from '@/types/common'; - -interface ProductListProps { - category: CategoryVariant; - selectedOption?: SortOption; -} - -const ProductList = ({ category, selectedOption }: ProductListProps) => { - const scrollRef = useRef(null); - const { categoryIds } = useCategoryValueContext(); - - const { fetchNextPage, hasNextPage, data } = useInfiniteProductsQuery( - categoryIds[category], - selectedOption?.value ?? 'reviewCount,desc' - ); - - useIntersectionObserver(fetchNextPage, scrollRef, hasNextPage); - - const productList = data.pages.flatMap((page) => page.products); - - return ( - <> - - {productList.map((product) => ( -
  • - - - -
  • - ))} -
    -
    - - ); -}; - -export default ProductList; - -const ProductListContainer = styled.ul` - display: flex; - flex-direction: column; - - & > li { - border-bottom: 1px solid ${({ theme }) => theme.borderColors.disabled}; - } -`; diff --git a/frontend/src/components/Product/ProductOverviewItem/ProductOverviewItem.stories.tsx b/frontend/src/components/Product/ProductOverviewItem/ProductOverviewItem.stories.tsx deleted file mode 100644 index 4dc5590b..00000000 --- a/frontend/src/components/Product/ProductOverviewItem/ProductOverviewItem.stories.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import ProductOverviewItem from './ProductOverviewItem'; - -const meta: Meta = { - title: 'product/ProductOverviewItem', - component: ProductOverviewItem, - args: { - image: 'https://t3.ftcdn.net/jpg/06/06/91/70/240_F_606917032_4ujrrMV8nspZDX8nTgGrTpJ69N9JNxOL.jpg', - name: '소금빵', - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; - -export const Ranking: Story = { - args: { - rank: 1, - }, -}; diff --git a/frontend/src/components/Product/ProductOverviewItem/ProductOverviewItem.tsx b/frontend/src/components/Product/ProductOverviewItem/ProductOverviewItem.tsx deleted file mode 100644 index ef281107..00000000 --- a/frontend/src/components/Product/ProductOverviewItem/ProductOverviewItem.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { Text } from '@fun-eat/design-system'; -import styled from 'styled-components'; - -import PreviewImage from '@/assets/characters.svg'; - -interface ProductOverviewItemProps { - name: string; - image: string | null; - rank?: number; -} - -const ProductOverviewItem = ({ name, image, rank }: ProductOverviewItemProps) => { - return ( - - - {rank ?? ''} - - {image !== null ? ( - - ) : ( - - )} - - {name} - - - ); -}; - -export default ProductOverviewItem; - -const ProductOverviewContainer = styled.div>` - display: flex; - gap: 15px; - align-items: center; - height: 50px; - padding: 0 15px; - border-radius: ${({ theme }) => theme.borderRadius.xs}; - background: ${({ theme, rank }) => (rank ? theme.colors.gray1 : theme.colors.white)}; -`; - -const ProductOverviewImage = styled.img` - width: 45px; - height: 45px; - border-radius: 50%; -`; - -const ProductPreviewImage = styled(PreviewImage)` - width: 45px; - height: 45px; - border-radius: 50%; - background-color: ${({ theme }) => theme.colors.white}; -`; - -const ProductOverviewText = styled(Text)` - white-space: nowrap; - text-overflow: ellipsis; - word-break: break-all; - overflow: hidden; -`; diff --git a/frontend/src/components/Product/ProductRecipeList/ProductRecipeList.tsx b/frontend/src/components/Product/ProductRecipeList/ProductRecipeList.tsx deleted file mode 100644 index 7298f3c8..00000000 --- a/frontend/src/components/Product/ProductRecipeList/ProductRecipeList.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { Link, Text } from '@fun-eat/design-system'; -import { useRef } from 'react'; -import { Link as RouterLink } from 'react-router-dom'; -import styled from 'styled-components'; - -import { RecipeItem } from '@/components/Recipe'; -import { PATH } from '@/constants/path'; -import { useIntersectionObserver } from '@/hooks/common'; -import { useInfiniteProductRecipesQuery } from '@/hooks/queries/product'; -import type { SortOption } from '@/types/common'; - -interface ProductRecipeListProps { - productId: number; - productName: string; - selectedOption: SortOption; -} - -const ProductRecipeList = ({ productId, productName, selectedOption }: ProductRecipeListProps) => { - const scrollRef = useRef(null); - const { fetchNextPage, hasNextPage, data } = useInfiniteProductRecipesQuery(productId, selectedOption.value); - useIntersectionObserver(fetchNextPage, scrollRef, hasNextPage); - - const recipes = data.pages.flatMap((page) => page.recipes); - - if (recipes.length === 0) { - return ( - - - {productName}을/를 {'\n'}사용한 꿀조합을 만들어보세요 🍯 - - - 꿀조합 작성하러 가기 - - - ); - } - - return ( - <> - - {recipes.map((recipe) => ( -
  • - - - -
  • - ))} -
    -
    - - ); -}; - -export default ProductRecipeList; - -const ProductRecipeListContainer = styled.ul` - & > li + li { - margin-top: 40px; - } -`; - -const ErrorContainer = styled.div` - display: flex; - flex-direction: column; - align-items: center; -`; - -const ErrorDescription = styled(Text)` - padding: 20px 0; - white-space: pre-wrap; -`; - -const RecipeLink = styled(Link)` - padding: 16px 24px; - border: 1px solid ${({ theme }) => theme.colors.gray4}; - border-radius: 8px; -`; diff --git a/frontend/src/components/Product/ProductTitle/ProductTitle.stories.tsx b/frontend/src/components/Product/ProductTitle/ProductTitle.stories.tsx deleted file mode 100644 index fbd2b1ed..00000000 --- a/frontend/src/components/Product/ProductTitle/ProductTitle.stories.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import ProductTitle from './ProductTitle'; - -const meta: Meta = { - title: 'common/ProductTitle', - component: ProductTitle, - args: { - content: '상품 목록', - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Product/ProductTitle/ProductTitle.tsx b/frontend/src/components/Product/ProductTitle/ProductTitle.tsx deleted file mode 100644 index 1c10cf2f..00000000 --- a/frontend/src/components/Product/ProductTitle/ProductTitle.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { Heading, Link, theme } from '@fun-eat/design-system'; -import { Link as RouterLink } from 'react-router-dom'; -import styled from 'styled-components'; - -import SvgIcon from '../../Common/Svg/SvgIcon'; - -import { PATH } from '@/constants/path'; - -interface ProductTitleProps { - content: string; - routeDestination: string; -} - -const ProductTitle = ({ content, routeDestination }: ProductTitleProps) => { - return ( - - - {content} - - - - - - - ); -}; - -export default ProductTitle; - -const ProductTitleContainer = styled.div` - position: relative; - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - height: 30px; -`; - -const ProductTitleLink = styled(Link)` - display: flex; - gap: 20px; - align-items: center; - margin-left: 36%; -`; - -const HeadingTitle = styled(Heading)` - font-size: 2.4rem; -`; - -const DropDownIcon = styled(SvgIcon)` - rotate: 270deg; -`; diff --git a/frontend/src/components/Product/index.ts b/frontend/src/components/Product/index.ts deleted file mode 100644 index 25a06f28..00000000 --- a/frontend/src/components/Product/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { default as ProductDetailItem } from './ProductDetailItem/ProductDetailItem'; -export { default as ProductItem } from './ProductItem/ProductItem'; -export { default as ProductList } from './ProductList/ProductList'; -export { default as ProductOverviewItem } from './ProductOverviewItem/ProductOverviewItem'; -export { default as ProductRecipeList } from './ProductRecipeList/ProductRecipeList'; -export { default as ProductTitle } from './ProductTitle/ProductTitle'; diff --git a/frontend/src/components/Rank/ProductRankingList/ProductRankingList.stories.tsx b/frontend/src/components/Rank/ProductRankingList/ProductRankingList.stories.tsx deleted file mode 100644 index 68b955e5..00000000 --- a/frontend/src/components/Rank/ProductRankingList/ProductRankingList.stories.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import ProductRankingList from './ProductRankingList'; - -const meta: Meta = { - title: 'product/ProductRankingList', - component: ProductRankingList, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Rank/ProductRankingList/ProductRankingList.tsx b/frontend/src/components/Rank/ProductRankingList/ProductRankingList.tsx deleted file mode 100644 index 309f5219..00000000 --- a/frontend/src/components/Rank/ProductRankingList/ProductRankingList.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { Link, Spacing } from '@fun-eat/design-system'; -import { Link as RouterLink } from 'react-router-dom'; - -import { ProductOverviewItem } from '@/components/Product'; -import { PATH } from '@/constants/path'; -import { useGA } from '@/hooks/common'; -import { useProductRankingQuery } from '@/hooks/queries/rank'; -import displaySlice from '@/utils/displaySlice'; - -interface ProductRankingListProps { - isHomePage?: boolean; -} - -const ProductRankingList = ({ isHomePage = false }: ProductRankingListProps) => { - const { data: productRankings } = useProductRankingQuery(); - const { gaEvent } = useGA(); - const productsToDisplay = displaySlice(isHomePage, productRankings.products, 3); - - const handleProductRankingLinkClick = () => { - gaEvent({ category: 'link', action: '상품 랭킹 링크 클릭', label: '랭킹' }); - }; - - return ( -
      - {productsToDisplay.map(({ id, name, image, categoryType }, index) => ( -
    • - - - - -
    • - ))} -
    - ); -}; - -export default ProductRankingList; diff --git a/frontend/src/components/Rank/RecipeRankingItem/RecipeRankingItem.stories.tsx b/frontend/src/components/Rank/RecipeRankingItem/RecipeRankingItem.stories.tsx deleted file mode 100644 index c20fa9fa..00000000 --- a/frontend/src/components/Rank/RecipeRankingItem/RecipeRankingItem.stories.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import RecipeRankingItem from './RecipeRankingItem'; - -import mockRecipeRankingList from '@/mocks/data/recipeRankingList.json'; - -const meta: Meta = { - title: 'recipe/RecipeRankingItem', - component: RecipeRankingItem, - args: { - rank: 1, - recipe: mockRecipeRankingList.recipes[0], - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Rank/RecipeRankingItem/RecipeRankingItem.tsx b/frontend/src/components/Rank/RecipeRankingItem/RecipeRankingItem.tsx deleted file mode 100644 index b887ad10..00000000 --- a/frontend/src/components/Rank/RecipeRankingItem/RecipeRankingItem.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import { Spacing, Text, useTheme } from '@fun-eat/design-system'; -import { useState } from 'react'; -import styled from 'styled-components'; - -import RecipePreviewImage from '@/assets/plate.svg'; -import { Skeleton, SvgIcon } from '@/components/Common'; -import type { RecipeRanking } from '@/types/ranking'; -import { getRelativeDate } from '@/utils/date'; - -interface RecipeRankingItemProps { - rank: number; - recipe: RecipeRanking; -} - -const RecipeRankingItem = ({ rank, recipe }: RecipeRankingItemProps) => { - const theme = useTheme(); - const { - image, - title, - author: { nickname, profileImage }, - favoriteCount, - createdAt, - } = recipe; - const [isImageLoading, setIsImageLoading] = useState(true); - - return ( - - - - - - {image !== null ? ( - <> - setIsImageLoading(false)} - /> - {isImageLoading && } - - ) : ( - - )} - - - {title} - - - - {favoriteCount} - - - - {getRelativeDate(createdAt)} - - - - - - - - {nickname} 님 - - - - - ); -}; - -export default RecipeRankingItem; - -const RecipeRankingItemContainer = styled.div` - width: calc(100% - 50px); - max-width: 560px; - margin: 12px 0; - padding: 0 5px; -`; - -const RecipeRankingWrapper = styled.div` - display: flex; - justify-content: space-between; - width: 95%; -`; - -const RankingRecipeWrapper = styled.div` - display: flex; - align-items: center; -`; - -const RecipeImage = styled.img` - border-radius: 5px; - object-fit: cover; -`; - -const TitleFavoriteWrapper = styled.div` - display: flex; - flex-direction: column; - justify-content: space-around; - height: 100%; -`; - -const FavoriteWrapper = styled.div` - display: flex; - gap: 4px; - align-items: center; -`; - -const AuthorWrapper = styled.div` - display: flex; - flex-direction: column; - justify-content: space-around; - align-items: center; - height: 100%; -`; - -const AuthorImage = styled.img` - border: 2px solid ${({ theme }) => theme.colors.primary}; - border-radius: 50%; - object-fit: cover; -`; diff --git a/frontend/src/components/Rank/RecipeRankingList/RecipeRankingList.stories.tsx b/frontend/src/components/Rank/RecipeRankingList/RecipeRankingList.stories.tsx deleted file mode 100644 index d13bad6f..00000000 --- a/frontend/src/components/Rank/RecipeRankingList/RecipeRankingList.stories.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import RecipeRankingList from './RecipeRankingList'; - -const meta: Meta = { - title: 'recipe/RecipeRankingList', - component: RecipeRankingList, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Rank/RecipeRankingList/RecipeRankingList.tsx b/frontend/src/components/Rank/RecipeRankingList/RecipeRankingList.tsx deleted file mode 100644 index 76397964..00000000 --- a/frontend/src/components/Rank/RecipeRankingList/RecipeRankingList.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { Link, Text } from '@fun-eat/design-system'; -import { Link as RouterLink } from 'react-router-dom'; - -import RecipeRankingItem from '../RecipeRankingItem/RecipeRankingItem'; - -import { Carousel } from '@/components/Common'; -import { PATH } from '@/constants/path'; -import { useGA } from '@/hooks/common'; -import { useRecipeRankingQuery } from '@/hooks/queries/rank'; - -const RecipeRankingList = () => { - const { data: recipeResponse } = useRecipeRankingQuery(); - const { gaEvent } = useGA(); - - if (recipeResponse.recipes.length === 0) return 아직 랭킹이 없어요!; - - const handleRecipeRankingLinkClick = () => { - gaEvent({ category: 'link', action: '꿀조합 랭킹 링크 클릭', label: '랭킹' }); - }; - - const carouselList = recipeResponse.recipes.map((recipe, index) => ({ - id: index, - children: ( - - - - ), - })); - - return ; -}; - -export default RecipeRankingList; diff --git a/frontend/src/components/Rank/ReviewRankingItem/ReviewRankingItem.stories.tsx b/frontend/src/components/Rank/ReviewRankingItem/ReviewRankingItem.stories.tsx deleted file mode 100644 index 099f3473..00000000 --- a/frontend/src/components/Rank/ReviewRankingItem/ReviewRankingItem.stories.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import ReviewRankingItem from './ReviewRankingItem'; - -const meta: Meta = { - title: 'review/ReviewRankingItem', - component: ReviewRankingItem, - args: { - reviewRanking: { - reviewId: 1, - productId: 5, - productName: '구운감자슬림명란마요', - content: - '할머니가 먹을 거 같은 맛입니다. 1960년 전쟁 때 맛 보고 싶었는데 그때는 너무 가난해서 먹을 수 없었는데요 이것보다 긴 리뷰도 잘려 보인답니다', - rating: 4.0, - favoriteCount: 1256, - categoryType: 'food', - createdAt: '2021-08-01T00:00:00.000Z', - }, - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Rank/ReviewRankingItem/ReviewRankingItem.tsx b/frontend/src/components/Rank/ReviewRankingItem/ReviewRankingItem.tsx deleted file mode 100644 index 205cfb03..00000000 --- a/frontend/src/components/Rank/ReviewRankingItem/ReviewRankingItem.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { Spacing, Text, useTheme } from '@fun-eat/design-system'; -import { memo } from 'react'; -import styled from 'styled-components'; - -import { SvgIcon } from '@/components/Common'; -import type { ReviewRanking } from '@/types/ranking'; -import { getRelativeDate } from '@/utils/date'; - -interface ReviewRankingItemProps { - reviewRanking: ReviewRanking; -} - -const ReviewRankingItem = ({ reviewRanking }: ReviewRankingItemProps) => { - const theme = useTheme(); - - const { productName, content, rating, favoriteCount, createdAt } = reviewRanking; - - return ( - - - {productName} - - - {content} - - - - - - - {favoriteCount} - - - - - - {rating.toFixed(1)} - - - - {getRelativeDate(createdAt)} - - - - ); -}; - -export default memo(ReviewRankingItem); - -const ReviewRankingItemContainer = styled.div` - display: flex; - flex-direction: column; - gap: 4px; - padding: 12px; - border: ${({ theme }) => `1px solid ${theme.borderColors.disabled}`}; - border-radius: ${({ theme }) => theme.borderRadius.sm}; -`; - -const ReviewText = styled(Text)` - display: -webkit-inline-box; - text-overflow: ellipsis; - overflow: hidden; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; -`; - -const FavoriteStarWrapper = styled.div` - display: flex; - gap: 4px; -`; - -const FavoriteIconWrapper = styled.div` - display: flex; - gap: 4px; - align-items: center; -`; - -const RatingIconWrapper = styled.div` - display: flex; - gap: 2px; - align-items: center; - - & > svg { - padding-bottom: 2px; - } -`; - -const ReviewDate = styled(Text)` - margin-left: auto; -`; diff --git a/frontend/src/components/Rank/ReviewRankingList/ReviewRankingList.stories.tsx b/frontend/src/components/Rank/ReviewRankingList/ReviewRankingList.stories.tsx deleted file mode 100644 index 671dd89e..00000000 --- a/frontend/src/components/Rank/ReviewRankingList/ReviewRankingList.stories.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import ReviewRankingList from './ReviewRankingList'; - -const meta: Meta = { - title: 'review/ReviewRankingList', - component: ReviewRankingList, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Rank/ReviewRankingList/ReviewRankingList.tsx b/frontend/src/components/Rank/ReviewRankingList/ReviewRankingList.tsx deleted file mode 100644 index 99f10d5f..00000000 --- a/frontend/src/components/Rank/ReviewRankingList/ReviewRankingList.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { Link } from '@fun-eat/design-system'; -import { Link as RouterLink } from 'react-router-dom'; -import styled from 'styled-components'; - -import ReviewRankingItem from '../ReviewRankingItem/ReviewRankingItem'; - -import { PATH } from '@/constants/path'; -import { useGA } from '@/hooks/common'; -import { useReviewRankingQuery } from '@/hooks/queries/rank'; -import useDisplaySlice from '@/utils/displaySlice'; - -interface ReviewRankingListProps { - isHomePage?: boolean; -} - -const ReviewRankingList = ({ isHomePage = false }: ReviewRankingListProps) => { - const { data: reviewRankings } = useReviewRankingQuery(); - const { gaEvent } = useGA(); - const reviewsToDisplay = useDisplaySlice(isHomePage, reviewRankings.reviews); - - const handleReviewRankingLinkClick = () => { - gaEvent({ category: 'link', action: '리뷰 랭킹 링크 클릭', label: '랭킹' }); - }; - - return ( - - {reviewsToDisplay.map((reviewRanking) => ( -
  • - - - -
  • - ))} -
    - ); -}; - -export default ReviewRankingList; - -const ReviewRankingListContainer = styled.ul` - display: flex; - flex-direction: column; - gap: 20px; -`; diff --git a/frontend/src/components/Rank/index.ts b/frontend/src/components/Rank/index.ts deleted file mode 100644 index 1b39a108..00000000 --- a/frontend/src/components/Rank/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { default as ReviewRankingItem } from '../Rank/ReviewRankingItem/ReviewRankingItem'; -export { default as ReviewRankingList } from './ReviewRankingList/ReviewRankingList'; -export { default as ProductRankingList } from './ProductRankingList/ProductRankingList'; -export { default as RecipeRankingItem } from './RecipeRankingItem/RecipeRankingItem'; -export { default as RecipeRankingList } from './RecipeRankingList/RecipeRankingList'; diff --git a/frontend/src/components/Recipe/CommentForm/CommentForm.stories.tsx b/frontend/src/components/Recipe/CommentForm/CommentForm.stories.tsx deleted file mode 100644 index e65b8722..00000000 --- a/frontend/src/components/Recipe/CommentForm/CommentForm.stories.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import CommentForm from './CommentForm'; - -const meta: Meta = { - title: 'recipe/CommentForm', - component: CommentForm, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Recipe/CommentForm/CommentForm.tsx b/frontend/src/components/Recipe/CommentForm/CommentForm.tsx deleted file mode 100644 index 10465755..00000000 --- a/frontend/src/components/Recipe/CommentForm/CommentForm.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { Button, Spacing, Text, Textarea, useTheme } from '@fun-eat/design-system'; -import type { ChangeEventHandler, FormEventHandler, RefObject } from 'react'; -import { useState } from 'react'; -import styled from 'styled-components'; - -import { SvgIcon } from '@/components/Common'; -import { useScroll } from '@/hooks/common'; -import { useToastActionContext } from '@/hooks/context'; -import { useRecipeCommentMutation } from '@/hooks/queries/recipe'; - -interface CommentFormProps { - recipeId: number; - scrollTargetRef: RefObject; -} - -const MAX_COMMENT_LENGTH = 200; - -const CommentForm = ({ recipeId, scrollTargetRef }: CommentFormProps) => { - const [commentValue, setCommentValue] = useState(''); - const { mutate } = useRecipeCommentMutation(recipeId); - - const theme = useTheme(); - const { toast } = useToastActionContext(); - - const { scrollToPosition } = useScroll(); - - const handleCommentInput: ChangeEventHandler = (e) => { - setCommentValue(e.target.value); - }; - - const handleSubmitComment: FormEventHandler = (e) => { - e.preventDefault(); - - mutate( - { comment: commentValue }, - { - onSuccess: () => { - setCommentValue(''); - scrollToPosition(scrollTargetRef); - toast.success('댓글이 등록되었습니다.'); - }, - onError: (error) => { - if (error instanceof Error) { - toast.error(error.message); - return; - } - - toast.error('댓글을 등록하는데 오류가 발생했습니다.'); - }, - } - ); - }; - - return ( - -
    - - - - - - - - {commentValue.length}자 / {MAX_COMMENT_LENGTH}자 - -
    - ); -}; - -export default CommentForm; - -const CommentFormContainer = styled.div` - position: fixed; - bottom: 0; - width: calc(100% - 40px); - max-width: 540px; - padding: 16px 0; - background: ${({ theme }) => theme.backgroundColors.default}; -`; - -const Form = styled.form` - display: flex; - gap: 4px; - justify-content: space-around; - align-items: center; -`; - -const CommentTextarea = styled(Textarea)` - height: 50px; - padding: 8px; - font-size: 1.4rem; -`; - -const SubmitButton = styled(Button)` - cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')}; -`; diff --git a/frontend/src/components/Recipe/CommentItem/CommentItem.stories.tsx b/frontend/src/components/Recipe/CommentItem/CommentItem.stories.tsx deleted file mode 100644 index 70bf1f9a..00000000 --- a/frontend/src/components/Recipe/CommentItem/CommentItem.stories.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import CommentItem from './CommentItem'; - -import comments from '@/mocks/data/comments.json'; - -const meta: Meta = { - title: 'recipe/CommentItem', - component: CommentItem, - args: { - recipeComment: comments.comments[0], - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Recipe/CommentItem/CommentItem.tsx b/frontend/src/components/Recipe/CommentItem/CommentItem.tsx deleted file mode 100644 index 847194b7..00000000 --- a/frontend/src/components/Recipe/CommentItem/CommentItem.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { Divider, Spacing, Text, useTheme } from '@fun-eat/design-system'; -import styled from 'styled-components'; - -import type { Comment } from '@/types/recipe'; -import { getFormattedDate } from '@/utils/date'; - -interface CommentItemProps { - recipeComment: Comment; -} - -const CommentItem = ({ recipeComment }: CommentItemProps) => { - const theme = useTheme(); - const { author, comment, createdAt } = recipeComment; - - return ( - <> - - -
    - - {author.nickname} 님 - - - {getFormattedDate(createdAt)} - -
    -
    - {comment} - - - - ); -}; - -export default CommentItem; - -const AuthorWrapper = styled.div` - display: flex; - gap: 12px; - align-items: center; -`; - -const AuthorProfileImage = styled.img` - border: 1px solid ${({ theme }) => theme.colors.primary}; - border-radius: 50%; -`; - -const CommentContent = styled(Text)` - margin: 16px 0; -`; diff --git a/frontend/src/components/Recipe/CommentList/CommentList.stories.tsx b/frontend/src/components/Recipe/CommentList/CommentList.stories.tsx deleted file mode 100644 index ebad218d..00000000 --- a/frontend/src/components/Recipe/CommentList/CommentList.stories.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import CommentList from './CommentList'; - -const meta: Meta = { - title: 'recipe/CommentList', - component: CommentList, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Recipe/CommentList/CommentList.tsx b/frontend/src/components/Recipe/CommentList/CommentList.tsx deleted file mode 100644 index d44f33c3..00000000 --- a/frontend/src/components/Recipe/CommentList/CommentList.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { Heading, Spacing, Text, theme } from '@fun-eat/design-system'; -import { useRef } from 'react'; - -import CommentItem from '../CommentItem/CommentItem'; - -import { useIntersectionObserver } from '@/hooks/common'; -import { useInfiniteRecipeCommentQuery } from '@/hooks/queries/recipe'; - -interface CommentListProps { - recipeId: number; -} - -const CommentList = ({ recipeId }: CommentListProps) => { - const scrollRef = useRef(null); - - const { fetchNextPage, hasNextPage, data } = useInfiniteRecipeCommentQuery(Number(recipeId)); - useIntersectionObserver(fetchNextPage, scrollRef, hasNextPage); - - const [{ totalElements }] = data.pages.flatMap((page) => page); - const comments = data.pages.flatMap((page) => page.comments); - - return ( - <> - - 댓글 ({totalElements}개) - - - {totalElements === 0 && 꿀조합의 첫번째 댓글을 달아보세요!} - {comments.map((comment) => ( - - ))} -
    - - ); -}; - -export default CommentList; diff --git a/frontend/src/components/Recipe/RecipeDetailTextarea/RecipeDetailTextarea.stories.tsx b/frontend/src/components/Recipe/RecipeDetailTextarea/RecipeDetailTextarea.stories.tsx deleted file mode 100644 index eefc6267..00000000 --- a/frontend/src/components/Recipe/RecipeDetailTextarea/RecipeDetailTextarea.stories.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import { RecipeDetailTextarea } from '..'; - -import RecipeFormProvider from '@/contexts/RecipeFormContext'; - -const meta: Meta = { - title: 'recipe/RecipeDetailTextarea', - component: RecipeDetailTextarea, - args: { - recipeDetail: '', - }, - decorators: [ - (Story) => ( - - - - ), - ], -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/Recipe/RecipeDetailTextarea/RecipeDetailTextarea.tsx b/frontend/src/components/Recipe/RecipeDetailTextarea/RecipeDetailTextarea.tsx deleted file mode 100644 index 79d5cdd4..00000000 --- a/frontend/src/components/Recipe/RecipeDetailTextarea/RecipeDetailTextarea.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { Heading, Spacing, Textarea, Text, useTheme } from '@fun-eat/design-system'; -import type { ChangeEventHandler } from 'react'; -import styled from 'styled-components'; - -import { useRecipeFormActionContext } from '@/hooks/context'; - -const MAX_LENGTH = 500; - -interface RecipeDetailTextareaProps { - recipeDetail: string; -} - -const RecipeDetailTextarea = ({ recipeDetail }: RecipeDetailTextareaProps) => { - const theme = useTheme(); - - const { handleRecipeFormValue } = useRecipeFormActionContext(); - - const handleRecipeDetail: ChangeEventHandler = (e) => { - handleRecipeFormValue({ target: 'content', value: e.currentTarget.value }); - }; - - return ( - <> - - 자세한 설명을 남겨주세요. - * - - -