diff --git "a/.github/ISSUE_TEMPLATE/\342\234\217\357\270\217-issue.md" "b/.github/ISSUE_TEMPLATE/\342\234\217\357\270\217-issue.md" new file mode 100644 index 0000000..fa16b61 --- /dev/null +++ "b/.github/ISSUE_TEMPLATE/\342\234\217\357\270\217-issue.md" @@ -0,0 +1,20 @@ +--- +name: "✏️ ISSUE" +about: 이슈 작업 사항을 입력해주세요. +title: "[FE] main task 작업 내용 {- sub task 번호}" +labels: '' +assignees: '' + +--- + +## Description + + + +## Todo + + + +- [ ] 작업1 + +## etc diff --git a/.github/backend/scripts/deploy.sh b/.github/backend/scripts/deploy.sh new file mode 100644 index 0000000..07f714c --- /dev/null +++ b/.github/backend/scripts/deploy.sh @@ -0,0 +1,25 @@ +#!/bin/bash +BUILD_JAR=$(ls /home/ubuntu/cicdproject/build/libs/*.jar) +JAR_NAME=$(basename $BUILD_JAR) +echo "> build 파일명: $JAR_NAME" >> /home/ubuntu/cicdproject/deploy.log + +echo "> build 파일 복사" >> /home/ubuntu/cicdproject/deploy.log +DEPLOY_PATH=/home/ubuntu/cicdproject/ +cp $BUILD_JAR $DEPLOY_PATH + +echo "> 현재 실행중인 애플리케이션 pid 확인" >> /home/ubuntu/cicdproject/deploy.log +CURRENT_PID=$(pgrep -f $JAR_NAME) + +if [ -z $CURRENT_PID ] +then + echo "> 현재 구동중인 애플리케이션이 없으므로 종료하지 않습니다." >> /home/ubuntu/cicdproject/deploy.log +else + echo "> kill -15 $CURRENT_PID" + kill -15 $CURRENT_PID + sleep 5 +fi + +DEPLOY_JAR=$DEPLOY_PATH$JAR_NAME +echo "> DEPLOY_JAR 배포" >> /home/ubuntu/cicdproject/deploy.log +chmod +x $DEPLOY_JAR +nohup java -jar $DEPLOY_JAR >> /home/ubuntu/deploy.log 2>/home/ubuntu/cicdproject/deploy_err.log & diff --git a/.github/issue_template.md b/.github/issue_template.md deleted file mode 100644 index b828cba..0000000 --- a/.github/issue_template.md +++ /dev/null @@ -1,11 +0,0 @@ -## Description - - - -## Todo - - - -- [ ] 작업1 - -## etc diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index edb973c..8e1d777 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,4 +1,7 @@ - + + + + diff --git a/.github/workflows/backend.yml b/.github/workflows/backend.yml new file mode 100644 index 0000000..6c250b9 --- /dev/null +++ b/.github/workflows/backend.yml @@ -0,0 +1,79 @@ +## CaArt 백엔드 서비스 배포 +name: Deploy to Amazon EC2 + + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + paths: + - '**.java' + +permissions: + contents: read + +env: + AWS_REGION: ap-northeast-2 + PROJECT_NAME: please + S3_BUCKET_NAME: a2cartagforspringboot + CODE_DEPLOY_APPLICATION_NAME: myApplicationToCICD + CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: a2Cartag + APPLICATION: ${{ secrets.APPLICATION }} + wd: ./backend + +jobs: + deploy: + runs-on: ubuntu-latest + environment: production + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up JDK 11 + uses: actions/setup-java@v3 + with: + java-version: '11' + distribution: 'temurin' + + - name: Copy application.yml + working-directory: ${{ env.wd }} + run: | + mkdir src/main/resources + touch ./src/main/resources/application.yml + echo "${{ secrets.APPLICATION }}" > ./src/main/resources/application.yml + + + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + + - name: Build with Gradle + working-directory: ${{ env.wd }} + run: | + chmod +x gradlew + ./gradlew build -x test + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ap-northeast-2 + + - name: Upload to AWS S3 + working-directory: ${{ env.wd }} + run: | + aws deploy push \ + --application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \ + --ignore-hidden-files \ + --s3-location s3://$S3_BUCKET_NAME/$PROJECT_NAME/$GITHUB_SHA.zip \ + --source . + + - name: Deploy to AWS EC2 from S3 + run: | + aws deploy create-deployment \ + --application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \ + --deployment-config-name CodeDeployDefault.AllAtOnce \ + --deployment-group-name ${{ env.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }} \ + --s3-location bucket=$S3_BUCKET_NAME,key=$PROJECT_NAME/$GITHUB_SHA.zip,bundleType=zip diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..723ef36 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..680a3ba --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 현대자동차 소프티어 부트캠프 2기 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..6c3f256 --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,40 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### yml files ### +src/main/resources/application.yml \ No newline at end of file diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..3132f34 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,4 @@ +FROM openjdk:11 +ARG JAR_FILE=/build/libs/*.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["java","-jar","/app.jar"] \ No newline at end of file diff --git a/backend/README.md b/backend/README.md deleted file mode 100644 index 2a2ee5b..0000000 --- a/backend/README.md +++ /dev/null @@ -1 +0,0 @@ -backend readme diff --git a/backend/appspec.yml b/backend/appspec.yml new file mode 100644 index 0000000..f9770bb --- /dev/null +++ b/backend/appspec.yml @@ -0,0 +1,18 @@ +version: 0.0 +os: linux +files: + - source: / + destination: /home/ubuntu/cicdproject # 배포될 위치 + overwrite: yes + +permissions: + - object: / + pattern: "**" + owner: ubuntu + group: ubuntu + +hooks: + ApplicationStart: + - location: scripts/deploy.sh # ApplicationStart 단계에서 deploy.sh 실행 + timeout: 60 + runas: ubuntu diff --git a/backend/build.gradle b/backend/build.gradle new file mode 100644 index 0000000..7922e80 --- /dev/null +++ b/backend/build.gradle @@ -0,0 +1,39 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '2.7.14' + id 'io.spring.dependency-management' version '1.0.15.RELEASE' +} + +group = 'autoever2' +version = '0.0.1-SNAPSHOT' + +java { + sourceCompatibility = '11' +} + +configurations { + compileOnly { + extendsFrom annotationProcessor + } +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-jdbc' + implementation 'org.springframework.boot:spring-boot-starter-web' + testImplementation 'junit:junit:4.13.1' + compileOnly 'org.projectlombok:lombok' + runtimeOnly 'com.mysql:mysql-connector-j' + annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation group: 'com.h2database', name: 'h2', version: '2.2.220' + implementation 'org.springdoc:springdoc-openapi-ui:1.6.9' + +} + +tasks.named('test') { + useJUnitPlatform() +} diff --git a/backend/gradle/wrapper/gradle-wrapper.jar b/backend/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..033e24c Binary files /dev/null and b/backend/gradle/wrapper/gradle-wrapper.jar differ diff --git a/backend/gradle/wrapper/gradle-wrapper.properties b/backend/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..9f4197d --- /dev/null +++ b/backend/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/backend/gradlew b/backend/gradlew new file mode 100755 index 0000000..fcb6fca --- /dev/null +++ b/backend/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/backend/gradlew.bat b/backend/gradlew.bat new file mode 100644 index 0000000..6689b85 --- /dev/null +++ b/backend/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/backend/settings.gradle b/backend/settings.gradle new file mode 100644 index 0000000..ebf5e4e --- /dev/null +++ b/backend/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'cartag' diff --git a/backend/src/.DS_Store b/backend/src/.DS_Store new file mode 100644 index 0000000..3dc4373 Binary files /dev/null and b/backend/src/.DS_Store differ diff --git a/backend/src/main/java/autoever2/cartag/CartagApplication.java b/backend/src/main/java/autoever2/cartag/CartagApplication.java new file mode 100644 index 0000000..8845a8c --- /dev/null +++ b/backend/src/main/java/autoever2/cartag/CartagApplication.java @@ -0,0 +1,13 @@ +package autoever2.cartag; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class CartagApplication { + + public static void main(String[] args) { + SpringApplication.run(CartagApplication.class, args); + } + +} diff --git a/backend/src/main/java/autoever2/cartag/OpenApiConfig.java b/backend/src/main/java/autoever2/cartag/OpenApiConfig.java new file mode 100644 index 0000000..d8a3b67 --- /dev/null +++ b/backend/src/main/java/autoever2/cartag/OpenApiConfig.java @@ -0,0 +1,20 @@ +package autoever2.cartag; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class OpenApiConfig { + @Bean + public OpenAPI openAPI() { + Info info = new Info() + .version("v1.0.0") + .title("API 타이틀") + .description("API Description"); + + return new OpenAPI() + .info(info); + } +} diff --git a/backend/src/main/java/autoever2/cartag/controller/ColorController.java b/backend/src/main/java/autoever2/cartag/controller/ColorController.java new file mode 100644 index 0000000..852da63 --- /dev/null +++ b/backend/src/main/java/autoever2/cartag/controller/ColorController.java @@ -0,0 +1,55 @@ +package autoever2.cartag.controller; + +import autoever2.cartag.domain.color.InnerColorDto; +import autoever2.cartag.domain.color.OuterColorDto; +import autoever2.cartag.service.ColorService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("api/cars") +@Tag(name = "ColorController", description = "색상 반환 api") +public class ColorController { + + private final ColorService service; + + @Operation(summary = "차량 외장 색상 조회", description = "차량 외장 색상 조회 method") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "조회 성공", content = @Content(schema = @Schema(implementation = OuterColorDto.class))), + }) + @GetMapping("/colors/outer") + public List carOuterColorInfo(@Parameter(description = "선택한 car_id") @RequestParam int carId) { + return service.findOuterColorByCarId(carId); + } + + @Operation(summary = "차량 외장 색상 이미지 조회", description = "차량 외장 색상 이미지 조회 method") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "조회 성공") + }) + @GetMapping("/colors/outer/images") + public List carOuterColorImageInfo(@Parameter(description = "선택한 color_id") @RequestParam int colorId) { + return service.changeImageToImages(colorId); + } + + @Operation(summary = "차량 내장 색상 조회", description = "차량 내장 색상 조회 method") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "조회 성공", content = @Content(schema = @Schema(implementation = InnerColorDto.class))), + }) + @GetMapping("/colors/inner") + public List carInnerColorInfo(@Parameter(description = "선택한 car_id") @RequestParam int carId) { + return service.findInnerColorByCarId(carId); + } +} diff --git a/backend/src/main/java/autoever2/cartag/controller/ModelController.java b/backend/src/main/java/autoever2/cartag/controller/ModelController.java new file mode 100644 index 0000000..2bb03e8 --- /dev/null +++ b/backend/src/main/java/autoever2/cartag/controller/ModelController.java @@ -0,0 +1,69 @@ +package autoever2.cartag.controller; + +import autoever2.cartag.domain.model.ModelDetailMappedDto; +import autoever2.cartag.domain.model.ModelEfficiencyDataDto; +import autoever2.cartag.domain.model.ModelShortDataDto; +import autoever2.cartag.domain.model.PowerTrainMappedDto; +import autoever2.cartag.service.ModelService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/modeltypes") +@Tag(name = "트림의 모델 타입", description = "트림의 모델 타입 호출 관련 api") +public class ModelController { + + private final ModelService modelTypeService; + + @Operation(summary = "차량 모델 타입 리스트 조회", description = "차량 모델 페이지에서 하단의 리스트(파워트레인 등)를 반환하는 api") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "조회 성공", content = { + @Content(array = @ArraySchema(schema = @Schema(implementation = ModelShortDataDto.class))) + }) + }) + @GetMapping("/list") + public List getTrimModelType(@Parameter(description = "선택한 차량 트림ID") @RequestParam("carid") int carId) { + return modelTypeService.getModelTypeData(carId); + } + + @Operation(summary = "모델타입 상세 데이터 조회", description = "모델명과 설명, 이미지 반환하는 api") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "조회 성공", content = @Content(schema = @Schema(implementation = ModelDetailMappedDto.class))) + }) + @GetMapping("/detail") + public ModelDetailMappedDto getModelDetail(@Parameter(description = "모델 타입 ID") @RequestParam("modelid") int modelId) { + return modelTypeService.getModelDetail(modelId); + } + + @Operation(summary = "파워트레인 HMG 데이터 호출", description = "파워트레인의 마력과 토크값 반환") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "조회 성공", content = @Content(schema = @Schema(implementation = PowerTrainMappedDto.class))) + }) + @GetMapping("/hmg-powertrain") + public PowerTrainMappedDto getPowerTrainData(@Parameter(description = "파워트레인 모델 타입 ID") @RequestParam("powertrain") int powerTrainId) { + return modelTypeService.getPowerTrainHmgData(powerTrainId); + } + + @Operation(summary = "효율 HMG 데이터 호출", description = "파워트레인과 구동방식의 조합으로 나온 효츌 HMG값 반환") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "조회 성공", content = @Content(schema = @Schema(implementation = ModelEfficiencyDataDto.class))) + }) + @GetMapping("/hmg-efficiency") + public ModelEfficiencyDataDto getPowerTrainData(@Parameter(description = "파워트레인 모델 타입 ID") @RequestParam("powertrain") int powerTrainId, @Parameter(description = "구동방식 모델 타입 ID") @RequestParam("operation") int operationId) { + return modelTypeService.getEfficiencyData(powerTrainId, operationId); + } +} diff --git a/backend/src/main/java/autoever2/cartag/controller/OptionController.java b/backend/src/main/java/autoever2/cartag/controller/OptionController.java new file mode 100644 index 0000000..d746e4d --- /dev/null +++ b/backend/src/main/java/autoever2/cartag/controller/OptionController.java @@ -0,0 +1,42 @@ +package autoever2.cartag.controller; + +import autoever2.cartag.domain.option.OptionDetailDto; +import autoever2.cartag.domain.option.SubOptionDto; +import autoever2.cartag.service.OptionService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/options") +@Tag(name = "옵션", description = "옵션 정보 관련 API") +public class OptionController { + + private final OptionService optionService; + + @Operation(summary = "추가 옵션 리스트 조회", description = "추가 옵션 데이터와 선택 비율(%) 및 HMG 데이터 존재 여부 List 제공") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "조회 성공", content = @Content(schema = @Schema(implementation = SubOptionDto.class))) + }) + @GetMapping("/list") + public List getSubOptionList(@Parameter(description = "차량 트림 ID") @RequestParam("carid") int carId) { + return optionService.getSubOptionList(carId); + } + + @GetMapping("/optiondetail") + public OptionDetailDto getOptionDetail(@Parameter(description = "차량 트림 ID") @RequestParam("carid") int carId, @Parameter(description = "옵션 ID") @RequestParam("optionid") int optionId) { + return optionService.getOptionDetailData(carId, optionId); + } +} diff --git a/backend/src/main/java/autoever2/cartag/controller/TrimController.java b/backend/src/main/java/autoever2/cartag/controller/TrimController.java new file mode 100644 index 0000000..7038d1c --- /dev/null +++ b/backend/src/main/java/autoever2/cartag/controller/TrimController.java @@ -0,0 +1,39 @@ +package autoever2.cartag.controller; + +import autoever2.cartag.domain.car.CarDto; +import autoever2.cartag.service.CarService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequestMapping("api/cars") +@RequiredArgsConstructor +@Tag(name = "TrimController", description = "트림 반환 api") +public class TrimController { + + private final CarService service; + + @Operation(summary = "trim 조회", description = "trim 조회 method") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "조회 성공", content = @Content(schema = @Schema(implementation = CarDto.class))), + }) + @GetMapping("/types") + public List carTrimInfo(@Parameter(description = "선택한 car_type") @RequestParam int carType) { + return service.findCarByCarType(carType); + } + + +} diff --git a/backend/src/main/java/autoever2/cartag/domain/car/CarDto.java b/backend/src/main/java/autoever2/cartag/domain/car/CarDto.java new file mode 100644 index 0000000..972bf7d --- /dev/null +++ b/backend/src/main/java/autoever2/cartag/domain/car/CarDto.java @@ -0,0 +1,39 @@ +package autoever2.cartag.domain.car; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import lombok.Singular; + +import java.util.List; + +@Getter +@Builder +@Schema(description = "차량 반환 DTO") +public class CarDto { + @Schema(description = "trim 명", example = "Le Blanc") + private String trim; + @Schema(description = "차량의 기본 가격") + private int carDefaultPrice; + @Schema(description = "차량 외부 이미지 url") + private String outerImage; + @Schema(description = "차량 내부 이미지 url") + private String innerImage; + @Schema(description = "차량 바퀴 이미지 url", nullable = true) + private String wheelImage; + @Schema(description = "차량에 대한 설명") + private String carDescription; + @Singular private List options; + + public static CarDto toDto(CarInfoDto carInfoDto, List optionDtos) { + return CarDto.builder() + .trim(carInfoDto.getTrim()) + .carDefaultPrice(carInfoDto.getCarDefaultPrice()) + .outerImage(carInfoDto.getOuterImage()) + .innerImage(carInfoDto.getInnerImage()) + .wheelImage(carInfoDto.getWheelImage()) + .carDescription(carInfoDto.getCarDescription()) + .options(optionDtos) + .build(); + } +} diff --git a/backend/src/main/java/autoever2/cartag/domain/car/CarInfoDto.java b/backend/src/main/java/autoever2/cartag/domain/car/CarInfoDto.java new file mode 100644 index 0000000..14bd2cc --- /dev/null +++ b/backend/src/main/java/autoever2/cartag/domain/car/CarInfoDto.java @@ -0,0 +1,37 @@ +package autoever2.cartag.domain.car; + +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +public class CarInfoDto { + + private int carId; + + private String trim; + + private int carDefaultPrice; + + private String outerImage; + + private String innerImage; + + private String wheelImage; + + private String carDescription; + + @Builder + public CarInfoDto(int carId, String trim, int carDefaultPrice, String outerImage, String innerImage, String wheelImage, String carDescription) { + this.carId = carId; + this.trim = trim; + this.carDefaultPrice = carDefaultPrice; + this.outerImage = outerImage; + this.innerImage = innerImage; + this.wheelImage = wheelImage; + this.carDescription = carDescription; + } +} diff --git a/backend/src/main/java/autoever2/cartag/domain/car/DefaultOptionDto.java b/backend/src/main/java/autoever2/cartag/domain/car/DefaultOptionDto.java new file mode 100644 index 0000000..ccd3095 --- /dev/null +++ b/backend/src/main/java/autoever2/cartag/domain/car/DefaultOptionDto.java @@ -0,0 +1,31 @@ +package autoever2.cartag.domain.car; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@Schema(description = "차량 DefaultOption 반환") +public class DefaultOptionDto { + @Schema(description = "option 이름") + private String optionName; + @Schema(description = "option image의 url") + private String optionImage; + @Schema(description = "option에 대한 설명") + private String optionDescription; + @Schema(description = "option의 구매 횟수") + private int OptionUsedCount; + + @Builder + public DefaultOptionDto(String optionName, String optionImage, String optionDescription, int OptionUsedCount) { + this.optionName = optionName; + this.optionImage = optionImage; + this.optionDescription = optionDescription; + this.OptionUsedCount = OptionUsedCount; + } + +} diff --git a/backend/src/main/java/autoever2/cartag/domain/color/InnerColorDto.java b/backend/src/main/java/autoever2/cartag/domain/color/InnerColorDto.java new file mode 100644 index 0000000..ff298bb --- /dev/null +++ b/backend/src/main/java/autoever2/cartag/domain/color/InnerColorDto.java @@ -0,0 +1,33 @@ +package autoever2.cartag.domain.color; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@Schema(description = "차량 내부 색상 반환 DTO") +public class InnerColorDto { + @Schema(description = "내부 색상 이름") + private String colorName; + @Schema(description = "내부 색상 이미지 url") + private String colorImage; + @Schema(description = "내부 색상 이미지 가격") + private Long colorPrice; + @Schema(description = "판매된 내부 색상 이미지") + private Long colorBoughtCount; + @Schema(description = "차량에 적용된 내부 색상 이미지 url") + private String colorCarImage; + + @Builder + public InnerColorDto(String colorName, String colorImage, Long colorPrice, Long colorBoughtCount, String colorCarImage) { + this.colorName = colorName; + this.colorImage = colorImage; + this.colorPrice = colorPrice; + this.colorBoughtCount = colorBoughtCount; + this.colorCarImage = colorCarImage; + } +} diff --git a/backend/src/main/java/autoever2/cartag/domain/color/OuterColorDto.java b/backend/src/main/java/autoever2/cartag/domain/color/OuterColorDto.java new file mode 100644 index 0000000..65328fd --- /dev/null +++ b/backend/src/main/java/autoever2/cartag/domain/color/OuterColorDto.java @@ -0,0 +1,29 @@ +package autoever2.cartag.domain.color; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter @Setter +@NoArgsConstructor +@Schema(description = "차량 외부 색상 반환 DTO") +public class OuterColorDto { + @Schema(description = "외부 색상 이름") + private String colorName; + @Schema(description = "외부 색상 이미지 url") + private String colorImage; + @Schema(description = "외부 색상 이미지 가격") + private Long colorPrice; + @Schema(description = "판매된 외부 색상 갯수") + private Long colorBoughtCount; + + @Builder + public OuterColorDto(String colorName, String colorImage, Long colorPrice, Long colorBoughtCount) { + this.colorName = colorName; + this.colorImage = colorImage; + this.colorPrice = colorPrice; + this.colorBoughtCount = colorBoughtCount; + } +} diff --git a/backend/src/main/java/autoever2/cartag/domain/model/ModelDetailMappedDto.java b/backend/src/main/java/autoever2/cartag/domain/model/ModelDetailMappedDto.java new file mode 100644 index 0000000..24b9e06 --- /dev/null +++ b/backend/src/main/java/autoever2/cartag/domain/model/ModelDetailMappedDto.java @@ -0,0 +1,41 @@ +package autoever2.cartag.domain.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.Objects; + +@Setter +@NoArgsConstructor +@Getter +@Schema(description = "모델 상세정보(이름, 설명, 사진) 데이터") +public class ModelDetailMappedDto { + + @Schema(description = "모델 타입 명", example = "파워트레인") + private String modelTypeName; + @Schema(description = "모델명", example = "디젤2.2") + private String modelName; + @Schema(description = "모델 설명", example = "높은 토크로 파워풀한 드라이빙이 가능하며, 차급대비 연비 효율이 우수합니다.") + private String optionDescription; + @Schema(description = "이미지 저장 URL", example = "/model/1234.jpg") + private String modelImage; + + @Builder + public ModelDetailMappedDto(String modelTypeName, String modelName, String optionDescription, String modelImage) { + this.modelTypeName = modelTypeName; + this.modelName = modelName; + this.optionDescription = optionDescription; + this.modelImage = modelImage; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ModelDetailMappedDto that = (ModelDetailMappedDto) o; + return Objects.equals(modelTypeName, that.modelTypeName) && Objects.equals(modelName, that.modelName) && Objects.equals(optionDescription, that.optionDescription) && Objects.equals(modelImage, that.modelImage); + } +} diff --git a/backend/src/main/java/autoever2/cartag/domain/model/ModelEfficiencyDataDto.java b/backend/src/main/java/autoever2/cartag/domain/model/ModelEfficiencyDataDto.java new file mode 100644 index 0000000..9581b90 --- /dev/null +++ b/backend/src/main/java/autoever2/cartag/domain/model/ModelEfficiencyDataDto.java @@ -0,0 +1,25 @@ +package autoever2.cartag.domain.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Setter +@NoArgsConstructor +@Getter +@Schema(description = "파워트레인과 모델타입의 조합으로 나온 HMG 데이터") +public class ModelEfficiencyDataDto { + + @Schema(description = "연비", example = "12.16km/s") + private String averageFuel; + @Schema(description = "배기량", example = "2,199cc") + private String displacement; + + @Builder + public ModelEfficiencyDataDto(String averageFuel, String displacement) { + this.averageFuel = averageFuel; + this.displacement = displacement; + } +} diff --git a/backend/src/main/java/autoever2/cartag/domain/model/ModelShortDataDto.java b/backend/src/main/java/autoever2/cartag/domain/model/ModelShortDataDto.java new file mode 100644 index 0000000..8f6af4c --- /dev/null +++ b/backend/src/main/java/autoever2/cartag/domain/model/ModelShortDataDto.java @@ -0,0 +1,22 @@ +package autoever2.cartag.domain.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +@Schema(description = "모델 타입 + 이름 + 선택 비율 반환") +public class ModelShortDataDto { + + @Schema(description = "모델ID", example = "1") + private int modelId; + @Schema(description = "모델명", example = "디젤 2.2") + private String modelName; + @Schema(description = "모델 타입명", example = "파워트레인") + private String modelTypeName; + @Schema(description = "모델 추가금액", example = "100000") + private Long modelPrice; + @Schema(description = "선택 비율 퍼센트 값(정수), 데이터가 없다면 0", example = "38") + private int percentage; +} diff --git a/backend/src/main/java/autoever2/cartag/domain/model/ModelShortMappedDto.java b/backend/src/main/java/autoever2/cartag/domain/model/ModelShortMappedDto.java new file mode 100644 index 0000000..429265a --- /dev/null +++ b/backend/src/main/java/autoever2/cartag/domain/model/ModelShortMappedDto.java @@ -0,0 +1,40 @@ +package autoever2.cartag.domain.model; + +import lombok.*; + +import java.util.Objects; + +@Getter +@Setter +@NoArgsConstructor +public class ModelShortMappedDto { + + private int modelId; + private String modelName; + private String modelTypeName; + private Long modelPrice; + private Long modelBoughtCount; + private boolean isDefaultModel; + + @Builder + public ModelShortMappedDto(int modelId, String modelName, String modelTypeName, Long modelPrice, Long modelBoughtCount, boolean isDefaultModel) { + this.modelId = modelId; + this.modelName = modelName; + this.modelTypeName = modelTypeName; + this.modelPrice = modelPrice; + this.modelBoughtCount = modelBoughtCount; + this.isDefaultModel = isDefaultModel; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ModelShortMappedDto that = (ModelShortMappedDto) o; + return modelId == that.modelId && isDefaultModel == that.isDefaultModel && Objects.equals(modelName, that.modelName) && Objects.equals(modelTypeName, that.modelTypeName) && Objects.equals(modelPrice, that.modelPrice) && Objects.equals(modelBoughtCount, that.modelBoughtCount); + } + + public void setIsDefaultModel(int isDefaultModel) { + this.isDefaultModel = isDefaultModel > 0; + } +} diff --git a/backend/src/main/java/autoever2/cartag/domain/model/PowerTrainMappedDto.java b/backend/src/main/java/autoever2/cartag/domain/model/PowerTrainMappedDto.java new file mode 100644 index 0000000..93b7a55 --- /dev/null +++ b/backend/src/main/java/autoever2/cartag/domain/model/PowerTrainMappedDto.java @@ -0,0 +1,31 @@ +package autoever2.cartag.domain.model; + +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.Objects; + +@Setter +@Getter +@NoArgsConstructor +public class PowerTrainMappedDto { + + private String maxPs; + private String maxKgfm; + + @Builder + public PowerTrainMappedDto(String maxPs, String maxKgfm) { + this.maxPs = maxPs; + this.maxKgfm = maxKgfm; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PowerTrainMappedDto that = (PowerTrainMappedDto) o; + return Objects.equals(maxPs, that.maxPs) && Objects.equals(maxKgfm, that.maxKgfm); + } +} diff --git a/backend/src/main/java/autoever2/cartag/domain/option/OptionDetailDto.java b/backend/src/main/java/autoever2/cartag/domain/option/OptionDetailDto.java new file mode 100644 index 0000000..811e0ec --- /dev/null +++ b/backend/src/main/java/autoever2/cartag/domain/option/OptionDetailDto.java @@ -0,0 +1,42 @@ +package autoever2.cartag.domain.option; + +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Builder +@Getter +@Schema(description = "옵션(기본/추가)의 상세 정보와 이미지, 패키지인 추가옵션의 경우 하위 패키지 정보를 제공") +public class OptionDetailDto { + + @Schema(description = "옵션의 카테고리명", example = "상세품목") + private String categoryName; + @Schema(description = "옵션명", example = "20인치 다크 스퍼터링 휠") + private String optionName; + @Schema(description = "옵션에 대한 설명", example = "다크 휠입니다.") + private String optionDescription; + @Schema(description = "옵션 이미지 저장 위치", example = "/options/rear-passenger.png") + private String optionImage; + @Schema(implementation = OptionHmgDataVo.class, description = "HMG 데이터 정보, 구매횟수와 사용횟수. 기본 옵션의 경우 모두 null, 추가옵션의 경우 사용횟수는 null일수도 있지만 구매횟수는 존재") + private OptionHmgDataVo hmgData; + @Schema(description = "패키지 여부, true이면 패키지", example = "true") + private boolean isPackage; + @ArraySchema(schema = @Schema(implementation = OptionDetailDto.class, description = "하위 옵션 존재 시 하위 옵션들의 데이터, 1레벨까지만 존재")) + private List subOptionList; + + public void setIsPackage(boolean isPackage) { + this.isPackage = isPackage; + } + + public void setSubOptionList(List list) { + subOptionList = list; + } + + public void setHmgData(OptionHmgDataVo data) { + this.hmgData = data; + } +} diff --git a/backend/src/main/java/autoever2/cartag/domain/option/OptionDetailMappedDto.java b/backend/src/main/java/autoever2/cartag/domain/option/OptionDetailMappedDto.java new file mode 100644 index 0000000..8b26018 --- /dev/null +++ b/backend/src/main/java/autoever2/cartag/domain/option/OptionDetailMappedDto.java @@ -0,0 +1,31 @@ +package autoever2.cartag.domain.option; + +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.Objects; + +@Getter +@Setter +@NoArgsConstructor +public class OptionDetailMappedDto { + + private String categoryName; + private String optionName; + private String optionDescription; + private String optionImage; + private Double optionUsedCount; + private Long optionBoughtCount; + + @Builder + public OptionDetailMappedDto(String categoryName, String optionName, String optionDescription, String optionImage, Double optionUsedCount, Long optionBoughtCount) { + this.categoryName = categoryName; + this.optionName = optionName; + this.optionDescription = optionDescription; + this.optionImage = optionImage; + this.optionUsedCount = optionUsedCount; + this.optionBoughtCount = optionBoughtCount; + } +} diff --git a/backend/src/main/java/autoever2/cartag/domain/option/OptionHmgDataVo.java b/backend/src/main/java/autoever2/cartag/domain/option/OptionHmgDataVo.java new file mode 100644 index 0000000..fca10e8 --- /dev/null +++ b/backend/src/main/java/autoever2/cartag/domain/option/OptionHmgDataVo.java @@ -0,0 +1,16 @@ +package autoever2.cartag.domain.option; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +@Schema(description = "옵션의 HMG 데이터 반환, 1개만 있어도 포함") +public class OptionHmgDataVo { + + @Schema(description = "옵션의 구매 횟수. 기본 옵션은 존재하지 않음. 최근 90일 기준", example = "1380") + private Long optionBoughtCount; + @Schema(description = "옵션의 실사용 횟수. 15000km당", example = "13.2") + private Double optionUsedCount; +} diff --git a/backend/src/main/java/autoever2/cartag/domain/option/SubOptionDto.java b/backend/src/main/java/autoever2/cartag/domain/option/SubOptionDto.java new file mode 100644 index 0000000..b6a7b88 --- /dev/null +++ b/backend/src/main/java/autoever2/cartag/domain/option/SubOptionDto.java @@ -0,0 +1,31 @@ +package autoever2.cartag.domain.option; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Builder +@Schema(description = "모델 타입 + 이름 + 선택 비율 반환") +public class SubOptionDto { + + @Schema(description = "추가옵션 ID", example = "1") + private int subOptionId; + @Schema(description = "추가옵션명, 혹은 패키지명", example = "2열 통풍시트") + private String optionName; + @Schema(description = "카테고리명", example = "휠") + private String optionCategoryName; + @Schema(description = "해시태그 리스트", example = "[장거리운전, 상쾌한]") + private List hashtagName; + @Schema(description = "옵션 이미지 URL", example = "/images/options/sub/image1.jpg") + private String optionImage; + @Schema(description = "옵션 선택 비율", example = "28") + private int percentage; + @Schema(description = "추가 금액", example = "100000") + private Long optionPrice; + @Schema(description = "HMG 데이터 존재 여부", example = "1") + private boolean hasHmgData; +} diff --git a/backend/src/main/java/autoever2/cartag/domain/option/SubOptionMappedDto.java b/backend/src/main/java/autoever2/cartag/domain/option/SubOptionMappedDto.java new file mode 100644 index 0000000..b5d56e6 --- /dev/null +++ b/backend/src/main/java/autoever2/cartag/domain/option/SubOptionMappedDto.java @@ -0,0 +1,41 @@ +package autoever2.cartag.domain.option; + +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.Objects; + +@Getter +@NoArgsConstructor +@Setter +public class SubOptionMappedDto { + + private int optionId; + private String optionName; + private String optionCategoryName; + private String optionImage; + private Long optionBoughtCount; + private Double optionUsedCount; + private Long optionPrice; + + @Builder + public SubOptionMappedDto(int optionId, String optionName, String optionCategoryName, String optionImage, Long optionBoughtCount, Double optionUsedCount, Long optionPrice) { + this.optionId = optionId; + this.optionName = optionName; + this.optionCategoryName = optionCategoryName; + this.optionImage = optionImage; + this.optionBoughtCount = optionBoughtCount; + this.optionUsedCount = optionUsedCount; + this.optionPrice = optionPrice; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SubOptionMappedDto that = (SubOptionMappedDto) o; + return optionId == that.optionId && Double.compare(optionUsedCount, that.optionUsedCount) == 0 && Objects.equals(optionName, that.optionName) && Objects.equals(optionCategoryName, that.optionCategoryName) && Objects.equals(optionImage, that.optionImage) && Objects.equals(optionBoughtCount, that.optionBoughtCount) && Objects.equals(optionPrice, that.optionPrice); + } +} diff --git a/backend/src/main/java/autoever2/cartag/repository/CarRepository.java b/backend/src/main/java/autoever2/cartag/repository/CarRepository.java new file mode 100644 index 0000000..379f408 --- /dev/null +++ b/backend/src/main/java/autoever2/cartag/repository/CarRepository.java @@ -0,0 +1,47 @@ +package autoever2.cartag.repository; + +import autoever2.cartag.domain.car.CarInfoDto; +import autoever2.cartag.domain.car.DefaultOptionDto; +import org.springframework.dao.support.DataAccessUtils; +import org.springframework.jdbc.core.BeanPropertyRowMapper; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.core.namedparam.SqlParameterSource; +import org.springframework.stereotype.Repository; + +import javax.sql.DataSource; +import java.util.List; +import java.util.Optional; + +@Repository +public class CarRepository { + private final NamedParameterJdbcTemplate template; + + public CarRepository(DataSource dataSource) { + template = new NamedParameterJdbcTemplate(dataSource); + } + + public List findCarByCarType(int carType) { + String sql = "select car_id, trim, car_default_price, outer_image, inner_image, wheel_image, " + + "car_description from Car where car_type_id = :carType"; + + SqlParameterSource param = new MapSqlParameterSource() + .addValue("carType", carType); + return template.query(sql, param, CarRowMapper()); + + } + + private RowMapper CarRowMapper() { + return BeanPropertyRowMapper.newInstance(CarInfoDto.class); + } + + public Optional findCarBoughtCountByCarId(int carId) { + String sql = "select bought_count from car where car_id = :carId"; + + SqlParameterSource param = new MapSqlParameterSource() + .addValue("carId", carId); + + return Optional.ofNullable(DataAccessUtils.singleResult(template.query(sql, param, (rs, rowNum) -> rs.getLong("bought_count")))); + } +} diff --git a/backend/src/main/java/autoever2/cartag/repository/ColorRepository.java b/backend/src/main/java/autoever2/cartag/repository/ColorRepository.java new file mode 100644 index 0000000..7f2f56b --- /dev/null +++ b/backend/src/main/java/autoever2/cartag/repository/ColorRepository.java @@ -0,0 +1,65 @@ +package autoever2.cartag.repository; + +import autoever2.cartag.domain.color.InnerColorDto; +import autoever2.cartag.domain.color.OuterColorDto; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.BeanPropertyRowMapper; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.core.namedparam.SqlParameterSource; +import org.springframework.stereotype.Repository; + +import javax.sql.DataSource; +import java.util.List; +import java.util.Optional; + +@Repository +public class ColorRepository { + private final NamedParameterJdbcTemplate template; + + public ColorRepository(DataSource dataSource) { + template = new NamedParameterJdbcTemplate(dataSource); + } + + public List findInnerColorCarByCarId(int carId) { + String sql = "select color_name, color_image, color_price, color_bought_count, " + + "color_car_image from ColorCarMapper as cm inner join Color as c " + + "on cm.color_id = c.color_id where car_id = :carId and c.is_outer_color = 0"; + + SqlParameterSource param = new MapSqlParameterSource() + .addValue("carId", carId); + return template.query(sql, param, InnerColorCarMapper()); + + } + + public List findOuterColorCarByCarId(int carId) { + String sql = "select color_name, color_image, color_price, color_bought_count " + + "from ColorCarMapper as cm inner join Color as c " + + "on cm.color_id = c.color_id where car_id = :carId and c.is_outer_color = 1"; + + SqlParameterSource param = new MapSqlParameterSource() + .addValue("carId", carId); + return template.query(sql, param, OuterColorCarMapper()); + } + + public Optional findOuterColorImagesByColorId(int colorId){ + String sql = "select color_car_image from ColorCarMapper where color_id = :colorId"; + try { + SqlParameterSource param = new MapSqlParameterSource() + .addValue("colorId", colorId); + return Optional.of(template.queryForObject(sql, param, String.class)); + } catch (EmptyResultDataAccessException e) { + return Optional.empty(); + } + + } + + private RowMapper OuterColorCarMapper() { + return BeanPropertyRowMapper.newInstance(OuterColorDto.class); + } + + private RowMapper InnerColorCarMapper() { + return BeanPropertyRowMapper.newInstance(InnerColorDto.class); + } +} diff --git a/backend/src/main/java/autoever2/cartag/repository/ModelRepository.java b/backend/src/main/java/autoever2/cartag/repository/ModelRepository.java new file mode 100644 index 0000000..ca8487a --- /dev/null +++ b/backend/src/main/java/autoever2/cartag/repository/ModelRepository.java @@ -0,0 +1,97 @@ +package autoever2.cartag.repository; + +import autoever2.cartag.domain.model.ModelDetailMappedDto; +import autoever2.cartag.domain.model.ModelEfficiencyDataDto; +import autoever2.cartag.domain.model.ModelShortMappedDto; +import autoever2.cartag.domain.model.PowerTrainMappedDto; +import org.springframework.dao.support.DataAccessUtils; +import org.springframework.jdbc.core.BeanPropertyRowMapper; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.core.namedparam.SqlParameterSource; +import org.springframework.stereotype.Repository; + +import javax.sql.DataSource; +import java.util.List; +import java.util.Optional; + +@Repository +public class ModelRepository { + + private final NamedParameterJdbcTemplate template; + + public ModelRepository(DataSource dataSource) { + this.template = new NamedParameterJdbcTemplate(dataSource); + } + + public List findAllModelTypeData(int carId) { + String sql = "select m.model_id, m.model_name, t.model_type_name, m.model_price, mm.model_bought_count, mm.is_default_model " + + "from modelcarmapper mm " + + "inner join model m " + + "on mm.model_id = m.model_id " + + "inner join modeltype t " + + "on m.model_type_id = t.model_type_id " + + "where mm.car_id = :carId"; + + SqlParameterSource param = new MapSqlParameterSource() + .addValue("carId", carId); + + return template.query(sql, param, modelShortRowMapper()); + } + + private RowMapper modelShortRowMapper() { + return BeanPropertyRowMapper.newInstance(ModelShortMappedDto.class); + } + + public Optional findModelDetailData(int modelId) { + String sql = "select mt.model_type_name, m.model_name, m.option_description, m.model_image " + + "from Model m " + + "inner join ModelType mt " + + "on m.model_type_id = mt.model_type_id " + + "where m.model_id = :modelId"; + + SqlParameterSource param = new MapSqlParameterSource() + .addValue("modelId", modelId); + + return Optional.ofNullable(DataAccessUtils.singleResult(template.query(sql, param, modelDetailRowMapper()))); + + } + + private RowMapper modelDetailRowMapper() { + return BeanPropertyRowMapper.newInstance(ModelDetailMappedDto.class); + } + + public Optional findPowerTrainData(int powerTrainId) { + String sql = "select max_ps, max_kgfm " + + "from PowerTrainData " + + "where power_train_id = :powerTrainId"; + + SqlParameterSource param = new MapSqlParameterSource() + .addValue("powerTrainId", powerTrainId); + + return Optional.ofNullable(DataAccessUtils.singleResult(template.query(sql, param, powerTrainRowMapper()))); + } + + private RowMapper powerTrainRowMapper() { + return BeanPropertyRowMapper.newInstance(PowerTrainMappedDto.class); + } + + public Optional findEfficiencyData(int powerTrainId, int operationId) { + String sql = "select average_fuel, displacement " + + "from PowerTrainOperationEfficiency " + + "where power_train_id = :powerTrainId and operation_id = :operationId"; + + SqlParameterSource param = new MapSqlParameterSource() + .addValue("powerTrainId", powerTrainId) + .addValue("operationId", operationId); + + return Optional.ofNullable(DataAccessUtils.singleResult(template.query(sql, param, efficiencyMapper()))); + } + + private RowMapper efficiencyMapper() { + return BeanPropertyRowMapper.newInstance(ModelEfficiencyDataDto.class); + } + + +} diff --git a/backend/src/main/java/autoever2/cartag/repository/OptionRepository.java b/backend/src/main/java/autoever2/cartag/repository/OptionRepository.java new file mode 100644 index 0000000..03d99dd --- /dev/null +++ b/backend/src/main/java/autoever2/cartag/repository/OptionRepository.java @@ -0,0 +1,106 @@ +package autoever2.cartag.repository; + +import autoever2.cartag.domain.car.DefaultOptionDto; +import autoever2.cartag.domain.option.OptionDetailMappedDto; +import autoever2.cartag.domain.option.SubOptionMappedDto; +import org.springframework.dao.support.DataAccessUtils; +import org.springframework.jdbc.core.BeanPropertyRowMapper; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.core.namedparam.SqlParameterSource; +import org.springframework.stereotype.Repository; + +import javax.sql.DataSource; +import java.util.List; +import java.util.Optional; + +@Repository +public class OptionRepository { + + private final NamedParameterJdbcTemplate template; + + public OptionRepository(DataSource dataSource) { + this.template = new NamedParameterJdbcTemplate(dataSource); + } + + public List findAllSubOptionWithCategoryNameByCarId(int carId) { + String sql = "select o.option_id, o.option_name, oc.option_category_name, o.option_image, ob.option_bought_count, o.option_used_count, ob.option_price " + + "from suboptiondata ob " + + "inner join Caroption o " + + "on o.option_id = ob.option_id " + + "inner join optioncategory oc " + + "on oc.option_category_id = o.option_category_id " + + "where ob.car_id = :carId"; + + SqlParameterSource param = new MapSqlParameterSource() + .addValue("carId", carId); + + return template.query(sql, param, subOptionMapper()); + } + + private RowMapper subOptionMapper() { + return BeanPropertyRowMapper.newInstance(SubOptionMappedDto.class); + } + + public List findAllHashtagNameBySubOptionId(int subOptionId) { + String sql = "select h.hashtag_name " + + "from optionhashtag oh, hashtag h " + + "where oh.hashtag_id = h.hashtag_id and oh.option_id = :subOptionId"; + + SqlParameterSource param = new MapSqlParameterSource() + .addValue("subOptionId", subOptionId); + + return template.queryForList(sql, param, String.class); + } + + public List findDefaultOptionByCarId(int carId) { + String sql = "select option_name, option_image, option_description, option_used_count " + + "from DefaultOptionData as data " + + "inner join Caroption on data.option_id = caroption.option_id " + + "where data.car_id = :carId order by option_used_count desc limit 3"; + SqlParameterSource param = new MapSqlParameterSource() + .addValue("carId", carId); + return template.query(sql, param, defaultOptionRowMapper()); + + } + + private RowMapper defaultOptionRowMapper() { + return BeanPropertyRowMapper.newInstance(DefaultOptionDto.class); + } + + public Optional findOptionDetail(int carId, int optionId) { + String sql = "select oc.option_category_name as category_name, o.option_name, o.option_description, o.option_image, o.option_used_count, so.option_bought_count " + + "from subOptionData so " + + "inner join CarOption o " + + "on so.option_id = o.option_id " + + "inner join optionCategory oc " + + "on oc.option_category_id = o.option_category_id " + + "where so.car_id = :carId and so.option_id = :optionId"; + + SqlParameterSource param = new MapSqlParameterSource() + .addValue("carId", carId) + .addValue("optionId", optionId); + + return Optional.ofNullable(DataAccessUtils.singleResult(template.query(sql, param, optionDetailRowMapper()))); + } + + private RowMapper optionDetailRowMapper() { + return BeanPropertyRowMapper.newInstance(OptionDetailMappedDto.class); + } + + public List findPackageSubOptions(int optionId) { + String sql = "select oc.option_category_name as category_name, o.option_name, o.option_description, o.option_image, o.option_used_count " + + "from subOptionPackage sp " + + "left join caroption o " + + "on sp.option_id = o.option_id " + + "inner join optionCategory oc " + + "on o.option_category_id = oc.option_category_id " + + "where sp.package_id = :packageId"; + + SqlParameterSource param = new MapSqlParameterSource() + .addValue("packageId", optionId); + + return template.query(sql, param, optionDetailRowMapper()); + } +} diff --git a/backend/src/main/java/autoever2/cartag/service/CarService.java b/backend/src/main/java/autoever2/cartag/service/CarService.java new file mode 100644 index 0000000..dadec48 --- /dev/null +++ b/backend/src/main/java/autoever2/cartag/service/CarService.java @@ -0,0 +1,34 @@ +package autoever2.cartag.service; + +import autoever2.cartag.domain.car.CarDto; +import autoever2.cartag.domain.car.CarInfoDto; +import autoever2.cartag.repository.CarRepository; +import autoever2.cartag.repository.OptionRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class CarService { + + private final CarRepository carRepository; + + private final OptionRepository optionRepository; + + + // TODO Optional로 감싸진 값이 empty일 경우 어떤 예외 발생시킬지 정하기 + public List findCarByCarType(int carType) { + List carInfos = carRepository.findCarByCarType(carType); + if(carInfos.isEmpty()){ + throw new RuntimeException("미정"); + } + + return carInfos.stream() + .map(carInfoDto -> CarDto.toDto(carInfoDto, optionRepository.findDefaultOptionByCarId(carInfoDto.getCarId()))) + .collect(Collectors.toList()); + } + +} diff --git a/backend/src/main/java/autoever2/cartag/service/ColorService.java b/backend/src/main/java/autoever2/cartag/service/ColorService.java new file mode 100644 index 0000000..a2d47c6 --- /dev/null +++ b/backend/src/main/java/autoever2/cartag/service/ColorService.java @@ -0,0 +1,53 @@ +package autoever2.cartag.service; + +import autoever2.cartag.domain.color.InnerColorDto; +import autoever2.cartag.domain.color.OuterColorDto; +import autoever2.cartag.repository.ColorRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.support.StandardMultipartHttpServletRequest; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.IntStream; + +@Service +@RequiredArgsConstructor +public class ColorService { + private final ColorRepository repository; + + // TODO : 어떤 에러를 반환할지 생각합니다. + + public List changeImageToImages(int colorId) { + Optional images = repository.findOuterColorImagesByColorId(colorId); + if (images.isEmpty()) { + throw new RuntimeException("미정"); + } + List outerColorCarImages = new ArrayList<>(); + String value = images.get(); + IntStream.rangeClosed(1, 60) + .forEach(i -> { + outerColorCarImages.add(value.substring(0, value.indexOf("*")) + i + value.substring(value.indexOf("*") + 1, value.length())); + }); + return outerColorCarImages; + } + + public List findOuterColorByCarId(int carId) { + List outerColors = repository.findOuterColorCarByCarId(carId); + if (outerColors.isEmpty()) { + throw new RuntimeException("미정"); + } + + return outerColors; + } + + public List findInnerColorByCarId(int carId) { + List innerColors = repository.findInnerColorCarByCarId(carId); + if (innerColors.isEmpty()) { + throw new RuntimeException("미정"); + } + + return innerColors; + } +} diff --git a/backend/src/main/java/autoever2/cartag/service/ModelService.java b/backend/src/main/java/autoever2/cartag/service/ModelService.java new file mode 100644 index 0000000..3704a54 --- /dev/null +++ b/backend/src/main/java/autoever2/cartag/service/ModelService.java @@ -0,0 +1,56 @@ +package autoever2.cartag.service; + +import autoever2.cartag.domain.model.*; +import autoever2.cartag.repository.CarRepository; +import autoever2.cartag.repository.ModelRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class ModelService { + + private final ModelRepository modelRepository; + private final CarRepository carRepository; + + public List getModelTypeData(int carId) { + List modelData = modelRepository.findAllModelTypeData(carId); + Long carBoughtCount = carRepository.findCarBoughtCountByCarId(carId).orElse(0L); + + return modelData.stream().map(modelTypeMappedDto -> { + int percentage = 0; + if (carBoughtCount != 0) { + percentage = (int) (modelTypeMappedDto.getModelBoughtCount() * 100 / carBoughtCount); + } + + return ModelShortDataDto.builder() + .modelId(modelTypeMappedDto.getModelId()) + .modelName(modelTypeMappedDto.getModelName()) + .modelTypeName(modelTypeMappedDto.getModelTypeName()) + .modelPrice(modelTypeMappedDto.getModelPrice()) + .percentage(percentage) + .build(); + } + ) + + .collect(Collectors.toList()); + } + + //TODO: RuntimeException 처리 + public ModelDetailMappedDto getModelDetail(int modelId) { + return modelRepository.findModelDetailData(modelId).orElseThrow(() -> new RuntimeException("데이터가 존재하지 않습니다.")); + } + + //TODO: RuntimeException 처리 + public PowerTrainMappedDto getPowerTrainHmgData(int powerTrainId) { + return modelRepository.findPowerTrainData(powerTrainId).orElseThrow(() -> new RuntimeException("데이터가 존재하지 않습니다.")); + } + + //TODO: RuntimeException 처리 + public ModelEfficiencyDataDto getEfficiencyData(int powerTrainId, int operationId) { + return modelRepository.findEfficiencyData(powerTrainId, operationId).orElseThrow(() -> new RuntimeException("ID가 일치하지 않습니다,")); + } +} diff --git a/backend/src/main/java/autoever2/cartag/service/OptionService.java b/backend/src/main/java/autoever2/cartag/service/OptionService.java new file mode 100644 index 0000000..8a51784 --- /dev/null +++ b/backend/src/main/java/autoever2/cartag/service/OptionService.java @@ -0,0 +1,82 @@ +package autoever2.cartag.service; + +import autoever2.cartag.domain.option.*; +import autoever2.cartag.repository.CarRepository; +import autoever2.cartag.repository.OptionRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class OptionService { + + private final OptionRepository optionRepository; + private final CarRepository carRepository; + + public List getSubOptionList(int carId) { + List subOptionList = optionRepository.findAllSubOptionWithCategoryNameByCarId(carId); + Long carBoughtCount = carRepository.findCarBoughtCountByCarId(carId).orElse(0L); + + return subOptionList.stream().map(subOptionMappedDto -> { + int percentage = 0; + if (carBoughtCount != 0) { + percentage = (int) (subOptionMappedDto.getOptionBoughtCount() * 100 / carBoughtCount); + } + + boolean hasHmgData = subOptionMappedDto.getOptionUsedCount() != 0; + + return SubOptionDto.builder() + .subOptionId(subOptionMappedDto.getOptionId()) + .optionCategoryName(subOptionMappedDto.getOptionCategoryName()) + .optionImage(subOptionMappedDto.getOptionImage()) + .optionPrice(subOptionMappedDto.getOptionPrice()) + .optionName(subOptionMappedDto.getOptionName()) + .percentage(percentage) + .hashtagName(optionRepository.findAllHashtagNameBySubOptionId(subOptionMappedDto.getOptionId())) + .hasHmgData(hasHmgData) + .build(); + } + ).collect(Collectors.toList()); + } + + //TODO: RuntimeException 처리 + public OptionDetailDto getOptionDetailData(int carId, int optionId) { + OptionDetailMappedDto detail = optionRepository.findOptionDetail(carId, optionId).orElseThrow(() -> new RuntimeException("데이터가 존재하지 않습니다.")); + + List packageSubOptions = optionRepository.findPackageSubOptions(optionId); + + OptionDetailDto result = OptionDetailDto.builder() + .categoryName(detail.getCategoryName()) + .optionName(detail.getOptionName()) + .optionDescription(detail.getOptionDescription()) + .optionImage(detail.getOptionImage()) + .hmgData(OptionHmgDataVo.builder().optionBoughtCount(detail.getOptionBoughtCount()).optionUsedCount(detail.getOptionUsedCount()).build()) + .build(); + + if(!packageSubOptions.isEmpty()) { + result.setIsPackage(true); + + packageSubOptions = packageSubOptions.stream().peek(option -> option.setOptionBoughtCount(detail.getOptionBoughtCount())).collect(Collectors.toList()); + result.setSubOptionList(packageSubOptions.stream().map(option -> { + OptionDetailDto detailDto = OptionDetailDto.builder() + .categoryName(option.getCategoryName()) + .isPackage(false) + .optionDescription(option.getOptionDescription()) + .optionImage(option.getOptionImage()) + .optionName(option.getOptionName()) + .hmgData(OptionHmgDataVo.builder().optionUsedCount(option.getOptionUsedCount()).optionBoughtCount(option.getOptionBoughtCount()).build()) + .build(); + + detailDto.setHmgData(OptionHmgDataVo.builder().optionBoughtCount(detail.getOptionBoughtCount()).optionUsedCount(option.getOptionUsedCount()).build()); + + return detailDto; + }).collect(Collectors.toList())); + } + + return result; + } + +} diff --git a/backend/src/test/java/autoever2/cartag/CartagApplicationTests.java b/backend/src/test/java/autoever2/cartag/CartagApplicationTests.java new file mode 100644 index 0000000..6802e3d --- /dev/null +++ b/backend/src/test/java/autoever2/cartag/CartagApplicationTests.java @@ -0,0 +1,13 @@ +package autoever2.cartag; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class CartagApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/backend/src/test/java/autoever2/cartag/controller/ColorControllerTest.java b/backend/src/test/java/autoever2/cartag/controller/ColorControllerTest.java new file mode 100644 index 0000000..f9557ba --- /dev/null +++ b/backend/src/test/java/autoever2/cartag/controller/ColorControllerTest.java @@ -0,0 +1,163 @@ +package autoever2.cartag.controller; + +import autoever2.cartag.domain.color.InnerColorDto; +import autoever2.cartag.domain.color.OuterColorDto; +import autoever2.cartag.service.ColorService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(ColorController.class) +class ColorControllerTest { + + @Autowired + MockMvc mockMvc; + + @MockBean + private ColorService service; + + private List outerColors; + + private List innerColors; + + private List images; + + @BeforeEach + void setUp() { + + outerColors = new ArrayList<>(); + outerColors.add(OuterColorDto.builder() + .colorName("어비스 블랙펄") + .colorImage("color_image_1") + .colorPrice(100000L) + .colorBoughtCount(212312L) + .build()); + + outerColors.add(OuterColorDto.builder() + .colorName("그라 파이트 그레이") + .colorImage("color_image_2") + .colorPrice(100000L) + .colorBoughtCount(203L) + .build()); + + outerColors.add(OuterColorDto.builder() + .colorName("쉬머링 실버 메탈릭") + .colorImage("color_image_3") + .colorPrice(1234440L) + .colorBoughtCount(203L) + .build()); + + outerColors.add(OuterColorDto.builder() + .colorName("크리미 화이트 펄") + .colorImage("color_image_4") + .colorPrice(100000L) + .colorBoughtCount(203L) + .build()); + + outerColors.add(OuterColorDto.builder() + .colorName("퍼플 펄") + .colorImage("color_image_5") + .colorPrice(100000L) + .colorBoughtCount(203L) + .build()); + + innerColors = new ArrayList<>(); + innerColors.add(InnerColorDto.builder() + .colorName("퀄팅 천연(블랙)") + .colorImage("color_image_1") + .colorPrice(100000L) + .colorBoughtCount(212312L) + .colorCarImage("car_image_1") + .build()); + + innerColors.add(InnerColorDto.builder() + .colorName("퀄팅 천연(그레이)") + .colorImage("color_image_2") + .colorPrice(100000L) + .colorBoughtCount(203L) + .colorCarImage("car_image_2") + .build()); + + innerColors.add(InnerColorDto.builder() + .colorName("퀄팅 천연(메탈릭)") + .colorImage("color_image_3") + .colorPrice(1234440L) + .colorBoughtCount(203L) + .colorCarImage("car_image_3") + .build()); + + innerColors.add(InnerColorDto.builder() + .colorName("퀄팅 천연(화이트)") + .colorImage("color_image_4") + .colorPrice(100000L) + .colorBoughtCount(203L) + .colorCarImage("car_image_4") + .build()); + + innerColors.add(InnerColorDto.builder() + .colorName("퀄팅 천연(퍼플)") + .colorImage("color_image_5") + .colorPrice(100000L) + .colorBoughtCount(203L) + .colorCarImage("car_image_5") + .build()); + + images = new ArrayList<>(); + + for(int i=1;i<=60;i++) { + images.add("car_image_" + i + ".jpg"); + } + } + + @Test + @DisplayName("트림의 색상 타입 데이터 호출 API") + void getTrimColor() throws Exception { + //given + int carId = 1; + int colorId = 1; + + given(service.findOuterColorByCarId(carId)).willReturn(outerColors); + given(service.findInnerColorByCarId(carId)).willReturn(innerColors); + given(service.changeImageToImages(colorId)).willReturn(images); + + //when + ResultActions resultActionsOuter = mockMvc.perform(MockMvcRequestBuilders.get("/api/cars/colors/outer/").param("carId", String.valueOf(carId))); + ResultActions resultActionsInner = mockMvc.perform(MockMvcRequestBuilders.get("/api/cars/colors/inner/").param("carId", String.valueOf(carId))); + ResultActions resultActionsImages = mockMvc.perform(MockMvcRequestBuilders.get("/api/cars/colors/outer/images").param("colorId", String.valueOf(colorId))); + //then + resultActionsOuter.andExpect(status().isOk()) + .andExpect(jsonPath("$[0].colorName").value("어비스 블랙펄")) + .andExpect(jsonPath("$[1].colorImage").value("color_image_2")) + .andExpect(jsonPath("$[2].colorPrice").value(1234440L)) + .andExpect(jsonPath("$[3].colorBoughtCount").value(203L)); + + + resultActionsInner.andExpect(status().isOk()) + .andExpect(jsonPath("$[0].colorName").value("퀄팅 천연(블랙)")) + .andExpect(jsonPath("$[1].colorImage").value("color_image_2")) + .andExpect(jsonPath("$[2].colorPrice").value(1234440L)) + .andExpect(jsonPath("$[3].colorBoughtCount").value(203L)) + .andExpect(jsonPath("$[4].colorCarImage").value("car_image_5")); + + resultActionsImages.andExpect(status().isOk()) + .andExpect(jsonPath("$[0]").value("car_image_1.jpg")) + .andExpect(jsonPath("$[1]").value("car_image_2.jpg")) + .andExpect(jsonPath("$[2]").value("car_image_3.jpg")) + .andExpect(jsonPath("$[3]").value("car_image_4.jpg")) + .andExpect(jsonPath("$[4]").value("car_image_5.jpg")); + } + +} diff --git a/backend/src/test/java/autoever2/cartag/controller/ModelControllerTest.java b/backend/src/test/java/autoever2/cartag/controller/ModelControllerTest.java new file mode 100644 index 0000000..bdc3c82 --- /dev/null +++ b/backend/src/test/java/autoever2/cartag/controller/ModelControllerTest.java @@ -0,0 +1,170 @@ +package autoever2.cartag.controller; + +import autoever2.cartag.domain.model.ModelDetailMappedDto; +import autoever2.cartag.domain.model.ModelEfficiencyDataDto; +import autoever2.cartag.domain.model.ModelShortDataDto; +import autoever2.cartag.domain.model.PowerTrainMappedDto; +import autoever2.cartag.service.ModelService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(ModelController.class) +class ModelControllerTest { + + @Autowired + MockMvc mockMvc; + + @MockBean + private ModelService modelService; + + @Test + @DisplayName("트림의 모델 타입 데이터 호출 API") + void getTrimModel() throws Exception { + List trimModelList = new ArrayList<>(); + //디젤 2.2 데이터 + trimModelList.add(ModelShortDataDto.builder() + .modelId(1) + .modelName("디젤 2.2") + .modelPrice(0L) + .modelTypeName("파워트레인") + .percentage(65) + .build()); + + //가솔린 3.8 데이터 + trimModelList.add(ModelShortDataDto.builder() + .modelId(2) + .modelName("가솔린 3.8") + .modelPrice(280000L) + .modelTypeName("파워트레인") + .percentage(35) + .build()); + + //7인승 데이터 + trimModelList.add(ModelShortDataDto.builder() + .modelId(3) + .modelName("7인승") + .modelPrice(0L) + .modelTypeName("바디타입") + .percentage(70) + .build()); + + //8인승 데이터 + trimModelList.add(ModelShortDataDto.builder() + .modelId(4) + .modelName("8인승") + .modelPrice(130000L) + .modelTypeName("바디타입") + .percentage(30) + .build()); + + //2WD 데이터 + trimModelList.add(ModelShortDataDto.builder() + .modelId(5) + .modelName("2WD") + .modelPrice(0L) + .modelTypeName("구동방식") + .percentage(50) + .build()); + + //4WD 데이터 + trimModelList.add(ModelShortDataDto.builder() + .modelId(6) + .modelName("4WD") + .modelPrice(237000L) + .modelTypeName("구동방식") + .percentage(50) + .build()); + + //given + int carId = 1; + given(modelService.getModelTypeData(carId)).willReturn(trimModelList); + + //when + ResultActions resultActions = mockMvc.perform(MockMvcRequestBuilders.get("/api/modeltypes/list").param("carid", String.valueOf(carId))); + + //then + resultActions.andExpect(status().isOk()) + .andExpect(jsonPath("$[0].modelId").value(1)) + .andExpect(jsonPath("$[1].modelName").value("가솔린 3.8")) + .andExpect(jsonPath("$[3].modelPrice").value(130000)) + .andExpect(jsonPath("$[4].percentage").value(50)) + .andExpect(jsonPath("$[5].modelTypeName").value("구동방식")); + } + + @Test + @DisplayName("모델의 상세 데이터 호출 API") + void getModelDetail() throws Exception { + int modelId = 1; + + ModelDetailMappedDto model = ModelDetailMappedDto.builder() + .modelTypeName("파워트레인") + .modelName("디젤2.2") + .optionDescription("높은 토크로 파워풀한 드라이빙이 가능하며, 차급대비 연비 효율이 우수합니다") + .modelImage("/model/diesel2-2.jpg") + .build(); + + given(modelService.getModelDetail(modelId)).willReturn(model); + + ResultActions resultActions = mockMvc.perform(MockMvcRequestBuilders.get("/api/modeltypes/detail").param("modelid", String.valueOf(modelId))); + + resultActions.andExpect(status().isOk()) + .andExpect(jsonPath("$.modelName").value("디젤2.2")) + .andExpect(jsonPath("$.optionDescription").value("높은 토크로 파워풀한 드라이빙이 가능하며, 차급대비 연비 효율이 우수합니다")) + .andExpect(jsonPath("$.modelTypeName").value("파워트레인")) + .andExpect(jsonPath("$.modelImage").value("/model/diesel2-2.jpg")); + } + + @Test + @DisplayName("파워트레인의 HMG 데이터 호출 API") + void getPowerTrainHmgData() throws Exception { + int powerTrainId = 1; + + PowerTrainMappedDto data = PowerTrainMappedDto.builder() + .maxPs("202/3,800PS/rpm") + .maxKgfm("45.0/1,750~2,750kgf-m/rpm") + .build(); + + given(modelService.getPowerTrainHmgData(powerTrainId)).willReturn(data); + + ResultActions resultActions = mockMvc.perform(MockMvcRequestBuilders.get("/api/modeltypes/hmg-powertrain").param("powertrain", String.valueOf(powerTrainId))); + + resultActions.andExpect(status().isOk()) + .andExpect(jsonPath("$.maxPs").value("202/3,800PS/rpm")) + .andExpect(jsonPath("$.maxKgfm").value("45.0/1,750~2,750kgf-m/rpm")); + } + + @Test + @DisplayName("연비와 cc HMG 데이터 호출 API") + void getEfficiencyData() throws Exception { + int powerTrainId = 1; + int operationId = 3; + + ModelEfficiencyDataDto data = ModelEfficiencyDataDto.builder() + .averageFuel("12.16km/s") + .displacement("2,199cc") + .build(); + + given(modelService.getEfficiencyData(powerTrainId, operationId)).willReturn(data); + + ResultActions resultActions = mockMvc.perform(MockMvcRequestBuilders.get("/api/modeltypes/hmg-efficiency") + .param("powertrain", String.valueOf(powerTrainId)).param("operation", String.valueOf(operationId))); + + resultActions.andExpect(status().isOk()) + .andExpect(jsonPath("$.averageFuel").value("12.16km/s")) + .andExpect(jsonPath("$.displacement").value("2,199cc")); + } +} \ No newline at end of file diff --git a/backend/src/test/java/autoever2/cartag/controller/OptionControllerTest.java b/backend/src/test/java/autoever2/cartag/controller/OptionControllerTest.java new file mode 100644 index 0000000..b8752ec --- /dev/null +++ b/backend/src/test/java/autoever2/cartag/controller/OptionControllerTest.java @@ -0,0 +1,178 @@ +package autoever2.cartag.controller; + +import autoever2.cartag.domain.option.OptionDetailDto; +import autoever2.cartag.domain.option.OptionDetailMappedDto; +import autoever2.cartag.domain.option.OptionHmgDataVo; +import autoever2.cartag.domain.option.SubOptionDto; +import autoever2.cartag.service.OptionService; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Schema; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(OptionController.class) +class OptionControllerTest { + + @Autowired + MockMvc mockMvc; + + @MockBean + private OptionService optionService; + + @Test + @DisplayName("트림의 서브 옵션 데이터 호출 API") + void getSubOptionList() throws Exception { + int carId = 1; + + List hashtagList = new ArrayList<>(); + List optionList = new ArrayList<>(); + hashtagList.add("여행"); + hashtagList.add("장거리 운전"); + hashtagList.add("시원한"); + + optionList.add(SubOptionDto.builder() + .subOptionId(1) + .optionCategoryName("상세품목") + .optionImage("/images/options/sub/2seats.jpg") + .optionPrice(100000L) + .optionName("2열 통풍 시트") + .percentage(28) + .hashtagName(hashtagList) + .hasHmgData(true) + .build()); + + optionList.add(SubOptionDto.builder() + .subOptionId(2) + .optionCategoryName("악세사리") + .optionImage("/images/options/sub/legwarmer.jpg") + .optionPrice(230000L) + .optionName("적외선 무릎 워머") + .percentage(37) + .hashtagName(hashtagList) + .hasHmgData(false) + .build()); + + optionList.add(SubOptionDto.builder() + .subOptionId(3) + .optionCategoryName("악세사리") + .optionImage("/images/options/sub/murfler.jpg") + .optionPrice(840000L) + .optionName("듀얼 머플러 패키지") + .percentage(18) + .hashtagName(hashtagList) + .hasHmgData(false) + .build()); + + optionList.add(SubOptionDto.builder() + .subOptionId(4) + .optionCategoryName("휠") + .optionImage("/images/options/sub/darkwheel.jpg") + .optionPrice(840000L) + .optionName("20인치 다크 스퍼터링 휠") + .percentage(38) + .hashtagName(hashtagList) + .hasHmgData(true) + .build()); + + given(optionService.getSubOptionList(carId)).willReturn(optionList); + + //when + ResultActions resultActions = mockMvc.perform(MockMvcRequestBuilders.get("/api/options/list").param("carid", String.valueOf(carId))); + + List expectedHashtag = new ArrayList<>(); + expectedHashtag.add("여행"); + expectedHashtag.add("장거리 운전"); + expectedHashtag.add("시원한"); + + //then + resultActions.andExpect(status().isOk()) + .andExpect(jsonPath("$[0].subOptionId").value(1)) + .andExpect(jsonPath("$[0].optionCategoryName").value("상세품목")) + .andExpect(jsonPath("$[1].optionImage").value("/images/options/sub/legwarmer.jpg")) + .andExpect(jsonPath("$[1].optionPrice").value(230000L)) + .andExpect(jsonPath("$[2].optionName").value("듀얼 머플러 패키지")) + .andExpect(jsonPath("$[2].percentage").value(18)) + .andExpect(jsonPath("$[3].hashtagName").value(expectedHashtag)) + .andExpect(jsonPath("$[3].hasHmgData").value(true)); + + } + + @Test + @DisplayName("옵션의 상세데이터 및 패키지 하위정보를 가져온다.") + void getOptionDetail() throws Exception { + int carId = 1; + int optionWithHmg = 4; + int optionPackage = 69; + + OptionDetailDto expected1 = OptionDetailDto.builder() + .categoryName("휠") + .optionDescription("다크 휠입니다.") + .optionName("20인치 다크 스퍼터링 휠") + .optionImage("/images/options/sub/darkwheel.jpg") + .hmgData(OptionHmgDataVo.builder().optionUsedCount(12.0).optionBoughtCount(3850L).build()) + .isPackage(false) + .build(); + + + List subOptions = new ArrayList<>(); + subOptions.add(OptionDetailDto.builder() + .categoryName("악세사리") + .optionName("후석 승객알림") + .optionImage("/options/rear-passenger.png") + .isPackage(false) + .optionDescription("초음파 센서를 통해 뒷좌석에 남아있는 승객의 움직임을 감지하여 운전자에게 경고함으로써 부주의에 의한 유아 또는 반려 동물 등의 방치 사고를 예방하는 신기술입니다.") + .hmgData(OptionHmgDataVo.builder() + .optionBoughtCount(7890L) + .optionUsedCount(82.0) + .build()) + .build()); + subOptions.add(OptionDetailDto.builder() + .categoryName("악세사리") + .optionName("메탈 리어범퍼스텝") + .optionImage("/options/metalrearbumper.png") + .isPackage(false) + .optionDescription("러기지 룸 앞쪽 하단부를 메탈로 만들어 물건을 싣고 내릴 때나 사람이 올라갈 때 차체를 보호해줍니다.") + .build()); + + OptionDetailDto expected2 = OptionDetailDto.builder() + .categoryName("상세품목") + .optionName("컴포트2") + .optionImage("/options/rear-passenger.png") + .isPackage(true) + .subOptionList(subOptions) + .build(); + + given(optionService.getOptionDetailData(carId, optionWithHmg)).willReturn(expected1); + given(optionService.getOptionDetailData(carId, optionPackage)).willReturn(expected2); + + ResultActions singleOption = mockMvc.perform(MockMvcRequestBuilders.get("/api/options/optiondetail").param("carid", String.valueOf(carId)).param("optionid", String.valueOf(optionWithHmg))); + ResultActions packageOption = mockMvc.perform(MockMvcRequestBuilders.get("/api/options/optiondetail").param("carid", String.valueOf(carId)).param("optionid", String.valueOf(optionPackage))); + + singleOption.andExpect(status().isOk()) + .andExpect(jsonPath("$.categoryName").value("휠")) + .andExpect(jsonPath("$.optionName").value("20인치 다크 스퍼터링 휠")) + .andExpect(jsonPath("$.optionImage").value("/images/options/sub/darkwheel.jpg")) + .andExpect(jsonPath("$.hmgData.optionBoughtCount").value(3850L)) + .andExpect(jsonPath("$.hmgData.optionUsedCount").value(12.0)); + + packageOption.andExpect(status().isOk()) + .andExpect(jsonPath("$.subOptionList[0].categoryName").value("악세사리")) + .andExpect(jsonPath("$.subOptionList[1].optionName").value("메탈 리어범퍼스텝")) + .andExpect(jsonPath("$.subOptionList[0].hmgData.optionBoughtCount").value(7890L)); + } +} \ No newline at end of file diff --git a/backend/src/test/java/autoever2/cartag/controller/TrimControllerTest.java b/backend/src/test/java/autoever2/cartag/controller/TrimControllerTest.java new file mode 100644 index 0000000..ea358cc --- /dev/null +++ b/backend/src/test/java/autoever2/cartag/controller/TrimControllerTest.java @@ -0,0 +1,123 @@ +package autoever2.cartag.controller; + +import autoever2.cartag.domain.car.CarDto; +import autoever2.cartag.domain.car.DefaultOptionDto; +import autoever2.cartag.service.CarService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(TrimController.class) +class TrimControllerTest { + @Autowired + MockMvc mockMvc; + + @MockBean + private CarService service; + + private List carDtoList; + private List defaultOptions; + @BeforeEach + void setup() { + carDtoList = new ArrayList<>(); + defaultOptions = new ArrayList<>(); + + defaultOptions.add(DefaultOptionDto + .builder() + .optionName("안전 하차 보조") + .optionImage("image_1") + .optionDescription("좋은 보조 장치") + .OptionUsedCount(42) + .build()); + defaultOptions.add(DefaultOptionDto + .builder() + .optionName("후측방 충둘 경고") + .optionImage("image_2") + .optionDescription("좋은 보조 장치") + .OptionUsedCount(98) + .build()); + defaultOptions.add(DefaultOptionDto + .builder() + .optionName("후방 교차 충돌 보조 장치") + .optionImage("image_3") + .optionDescription("좋은 보조 장치") + .OptionUsedCount(41) + .build()); + + carDtoList.add(CarDto + .builder() + .trim("Le Blanc") + .carDefaultPrice(400000003) + .outerImage("image_1") + .innerImage("image_2") + .wheelImage("image_3") + .options(defaultOptions) + .build() + ); + + carDtoList.add(CarDto + .builder() + .trim("Exclusive") + .carDefaultPrice(400000003) + .outerImage("image_1") + .innerImage("image_2") + .wheelImage("image_3") + .options(defaultOptions) + .build() + ); + + carDtoList.add(CarDto + .builder() + .trim("Prestige") + .carDefaultPrice(400000003) + .outerImage("image_1") + .innerImage("image_2") + .wheelImage("image_3") + .options(defaultOptions) + .build() + ); + + carDtoList.add(CarDto + .builder() + .trim("Calligraphy") + .carDefaultPrice(400000003) + .outerImage("image_1") + .innerImage("image_2") + .options(defaultOptions) + .build() + ); + } + + @Test + @DisplayName("트림 리스트 호출 API") + void getTrimList() throws Exception { + //given + int carType = 1; + given(service.findCarByCarType(carType)).willReturn(carDtoList); + + //when + ResultActions resultActions = mockMvc.perform(MockMvcRequestBuilders.get("/api/cars/types").param("carType", String.valueOf(carType))); + + System.out.println("jsonPath(\"$[3].options[0]\") = " + jsonPath("$[3].options[0]")); + //then + resultActions.andExpect(status().isOk()) + .andExpect(jsonPath("$[0].trim").value("Le Blanc")) + .andExpect(jsonPath("$[1].carDefaultPrice").value(400000003)) + .andExpect(jsonPath("$[2].outerImage").value("image_1")) + .andExpect(jsonPath("$[3].wheelImage").isEmpty()); + + } +} \ No newline at end of file diff --git a/backend/src/test/java/autoever2/cartag/repository/CarRepositoryTest.java b/backend/src/test/java/autoever2/cartag/repository/CarRepositoryTest.java new file mode 100644 index 0000000..ccf450b --- /dev/null +++ b/backend/src/test/java/autoever2/cartag/repository/CarRepositoryTest.java @@ -0,0 +1,52 @@ +package autoever2.cartag.repository; + +import autoever2.cartag.domain.car.CarInfoDto; +import autoever2.cartag.domain.car.DefaultOptionDto; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import javax.sql.DataSource; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@JdbcTest +@ActiveProfiles("test") +@Sql(scripts = {"classpath:/insert/insertCar-h2.sql"}) +class CarRepositoryTest { + + private final CarRepository carRepository; + + @Autowired + public CarRepositoryTest(DataSource dataSource) { + carRepository = new CarRepository(dataSource); + } + + + @Test + @DisplayName("CarType 별 트림 리스트를 반환합니다.") + void findCars() { + List carByCarType = carRepository.findCarByCarType(1); + assertEquals(4, carByCarType.size()); + assertEquals("Le Blanc", carByCarType.get(0).getTrim()); + assertEquals(40000000, carByCarType.get(0).getCarDefaultPrice()); + } + + @Test + @DisplayName("car에 있는 총 구매횟수 데이터를 반환") + void findCarBoughtCountByCarId() { + //given + int carId = 1; + //when + Long boughtCount = carRepository.findCarBoughtCountByCarId(carId).orElse(-1L); + //then + assertEquals(234L, boughtCount); + + } +} \ No newline at end of file diff --git a/backend/src/test/java/autoever2/cartag/repository/ColorRepositoryTest.java b/backend/src/test/java/autoever2/cartag/repository/ColorRepositoryTest.java new file mode 100644 index 0000000..84975e5 --- /dev/null +++ b/backend/src/test/java/autoever2/cartag/repository/ColorRepositoryTest.java @@ -0,0 +1,59 @@ +package autoever2.cartag.repository; + +import autoever2.cartag.domain.color.InnerColorDto; +import autoever2.cartag.domain.color.OuterColorDto; +import org.assertj.core.api.SoftAssertions; +import org.assertj.core.api.junit.jupiter.InjectSoftAssertions; +import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.jdbc.Sql; + +import javax.sql.DataSource; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@JdbcTest +@ActiveProfiles("test") +@Sql(scripts = {"classpath:/insert/insertColor-h2.sql"}) +class ColorRepositoryTest { + + private final ColorRepository repository; + + @Autowired + public ColorRepositoryTest(DataSource dataSource) { + this.repository = new ColorRepository(dataSource); + } + + @Test + @DisplayName("carId에 따른 모든 내장 색상 리스트를 반환합니다.") + void findInnerColor() { + List innerColors = repository.findInnerColorCarByCarId(1); + assertEquals(2, innerColors.size()); + assertEquals("퍼플 그레이 펄", innerColors.get(0).getColorName()); + assertEquals("코발트 블루", innerColors.get(1).getColorName()); + } + + @Test + @DisplayName("carId에 따른 모든 외장 색상 리스트를 반환합니다.") + void findOuterColor() { + List outerColors = repository.findOuterColorCarByCarId(1); + assertEquals(2, outerColors.size()); + assertEquals("천연 퀄팅(블랙)", outerColors.get(0).getColorName()); + assertEquals("천연 퀄팅(화이트)", outerColors.get(1).getColorName()); + } + + @Test + @DisplayName("carId에 따른 색상이 적용된 차량 이미지를 반환합니다.") + void findOuterImages(){ + String image = repository.findOuterColorImagesByColorId(1).get(); + assertEquals("red_image_*.jpg", image); + } +} \ No newline at end of file diff --git a/backend/src/test/java/autoever2/cartag/repository/ModelRepositoryTest.java b/backend/src/test/java/autoever2/cartag/repository/ModelRepositoryTest.java new file mode 100644 index 0000000..5d98cb5 --- /dev/null +++ b/backend/src/test/java/autoever2/cartag/repository/ModelRepositoryTest.java @@ -0,0 +1,152 @@ +package autoever2.cartag.repository; + +import autoever2.cartag.domain.model.ModelDetailMappedDto; +import autoever2.cartag.domain.model.ModelEfficiencyDataDto; +import autoever2.cartag.domain.model.ModelShortMappedDto; +import autoever2.cartag.domain.model.PowerTrainMappedDto; +import org.assertj.core.api.SoftAssertions; +import org.assertj.core.api.junit.jupiter.InjectSoftAssertions; +import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.jdbc.Sql; + +import javax.sql.DataSource; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; + +@ActiveProfiles("test") +@JdbcTest +@Sql({"classpath:insert/insert-model-h2.sql"}) +@ExtendWith(SoftAssertionsExtension.class) +class ModelRepositoryTest { + + @InjectSoftAssertions + SoftAssertions softAssertions; + + private final ModelRepository modelRepository; + + @Autowired + public ModelRepositoryTest(DataSource dataSource) { + modelRepository = new ModelRepository(dataSource); + } + + @Test + @DisplayName("모델 타입의 리스트를 가져온다") + void findAllModelTypeData() { + //given + int carId = 1; + ModelShortMappedDto firstModel = ModelShortMappedDto.builder() + .modelId(1) + .modelName("디젤2.2") + .modelTypeName("파워트레인") + .isDefaultModel(true) + .modelBoughtCount(800L) + .modelPrice(1480000L) + .build(); + + ModelShortMappedDto sixthModel = ModelShortMappedDto.builder() + .modelId(6) + .modelName("8인승") + .modelTypeName("바디타입") + .isDefaultModel(false) + .modelBoughtCount(1800L) + .modelPrice(0L) + .build(); + + //when + List modelList = modelRepository.findAllModelTypeData(carId); + + //then + assertEquals(6, modelList.size()); + assertTrue(modelList.contains(firstModel)); + assertTrue(modelList.contains(sixthModel)); + } + + @Test + @DisplayName("모델의 상세 데이터를 가져온다.") + void findModelDetail() { + //given + int modelId1 = 1; + ModelDetailMappedDto model1 = ModelDetailMappedDto.builder() + .modelTypeName("파워트레인") + .modelName("디젤2.2") + .optionDescription("높은 토크로 파워풀한 드라이빙이 가능하며, 차급대비 연비 효율이 우수합니다") + .modelImage("/model/diesel2-2.jpg") + .build(); + + int modelId2 = 4; + ModelDetailMappedDto model2 = ModelDetailMappedDto.builder() + .modelTypeName("구동방식") + .modelName("4WD") + .optionDescription("전자식 상시 4륜 구동 시스템 입니다.\n도로의 상황이나 주행 환경에 맞춰 전후륜 구동력을 자동배분하여 주행 안전성을 높여줍니다") + .modelImage("/model/4wd.png") + .build(); + + //when + Optional result1 = modelRepository.findModelDetailData(modelId1); + Optional result2 = modelRepository.findModelDetailData(modelId2); + + //then + assertTrue(result1.isPresent()); + assertEquals(model1, result1.get()); + assertTrue(result2.isPresent()); + assertEquals(model2, result2.get()); + } + + @Test + @DisplayName("파워트레인의 경우 HMG 데이터를 가져온다.") + void findPowerTrainData() { + //given + int powerTrainId1 = 1; + PowerTrainMappedDto powerTrain1 = PowerTrainMappedDto.builder() + .maxPs("202/3,800PS/rpm") + .maxKgfm("45.0/1,750~2,750kgf-m/rpm") + .build(); + int powerTrainId2 = 2; + PowerTrainMappedDto powerTrain2 = PowerTrainMappedDto.builder() + .maxPs("295/6,000PS/rpm") + .maxKgfm("36.2/5,200kgf-m/rpm") + .build(); + + //when + Optional result1 = modelRepository.findPowerTrainData(powerTrainId1); + Optional result2 = modelRepository.findPowerTrainData(powerTrainId2); + + //then + assertTrue(result1.isPresent()); + assertTrue(result2.isPresent()); + assertEquals(powerTrain1, result1.get()); + assertEquals(powerTrain2, result2.get()); + } + + @Test + @DisplayName("파워트레인과 구동방식이 조합된 HMG 데이터를 가져온다.") + void getPowerTrainOperationEfficiency() { + int powerTrainId1 = 1; + int powerTrainId2 = 2; + int operationId1 = 3; + int operationId2 = 4; + + Optional data1 = modelRepository.findEfficiencyData(powerTrainId1, operationId1); + Optional data2 = modelRepository.findEfficiencyData(powerTrainId1, operationId2); + Optional data3 = modelRepository.findEfficiencyData(powerTrainId2, operationId1); + Optional data4 = modelRepository.findEfficiencyData(powerTrainId2, operationId2); + + assertTrue(data1.isPresent()); + assertTrue(data2.isPresent()); + assertTrue(data3.isPresent()); + assertTrue(data4.isPresent()); + softAssertions.assertThat(data1.get().getAverageFuel()).isEqualTo("12.16km/s"); + softAssertions.assertThat(data2.get().getDisplacement()).isEqualTo("2,199cc"); + softAssertions.assertThat(data3.get().getAverageFuel()).isEqualTo("9.23km/s"); + softAssertions.assertThat(data4.get().getDisplacement()).isEqualTo("3,778cc"); + + } +} \ No newline at end of file diff --git a/backend/src/test/java/autoever2/cartag/repository/OptionRepositoryTest.java b/backend/src/test/java/autoever2/cartag/repository/OptionRepositoryTest.java new file mode 100644 index 0000000..b8d6574 --- /dev/null +++ b/backend/src/test/java/autoever2/cartag/repository/OptionRepositoryTest.java @@ -0,0 +1,196 @@ +package autoever2.cartag.repository; + +import autoever2.cartag.domain.car.DefaultOptionDto; +import autoever2.cartag.domain.option.OptionDetailMappedDto; +import autoever2.cartag.domain.option.SubOptionMappedDto; +import org.assertj.core.api.SoftAssertions; +import org.assertj.core.api.junit.jupiter.InjectSoftAssertions; +import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.jdbc.Sql; + +import javax.sql.DataSource; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@ActiveProfiles("test") +@JdbcTest +@Sql({"classpath:insert/insert-suboption-h2.sql"}) +@ExtendWith(SoftAssertionsExtension.class) +class OptionRepositoryTest { + + @InjectSoftAssertions + SoftAssertions softAssertions; + + private final OptionRepository optionRepository; + + @Autowired + public OptionRepositoryTest(DataSource dataSource) { + optionRepository = new OptionRepository(dataSource); + } + + @Test + @DisplayName("subOption 리스트를 가져온다") + void findAllSubOptionWithCategoryNameByCarId() { + //given + int carId = 1; + + //when + List optionList = optionRepository.findAllSubOptionWithCategoryNameByCarId(carId); + SubOptionMappedDto expectedResult1 = SubOptionMappedDto.builder() + .optionId(1) + .optionCategoryName("상세품목") + .optionName("2열 통풍 시트") + .optionImage("/images/options/sub/2seats.jpg") + .optionPrice(100000L) + .optionBoughtCount(2800L) + .optionUsedCount(38.0) + .build(); + SubOptionMappedDto expectedResult2 = SubOptionMappedDto.builder() + .optionId(2) + .optionCategoryName("악세사리") + .optionName("적외선 무릎 워머") + .optionImage("/images/options/sub/warmer.jpg") + .optionPrice(130000L) + .optionBoughtCount(4200L) + .optionUsedCount(42.0) + .build(); + SubOptionMappedDto expectedResult3 = SubOptionMappedDto.builder() + .optionId(3) + .optionCategoryName("악세사리") + .optionName("듀얼 머플러 패키지") + .optionImage("/images/options/sub/murfler.jpg") + .optionPrice(870000L) + .optionBoughtCount(1300L) + .optionUsedCount(55.0) + .build(); + SubOptionMappedDto expectedResult4 = SubOptionMappedDto.builder() + .optionId(4) + .optionCategoryName("휠") + .optionName("20인치 다크 스퍼터링 휠") + .optionImage("/images/options/sub/darkwheel.jpg") + .optionPrice(50000L) + .optionBoughtCount(3850L) + .optionUsedCount(12.0) + .build(); + + //then + + assertTrue(optionList.contains(expectedResult1)); + assertTrue(optionList.contains(expectedResult2)); + assertTrue(optionList.contains(expectedResult3)); + assertTrue(optionList.contains(expectedResult4)); + } + + @Test + @DisplayName("sub option에 대한 hashtag 찾기") + void findAllHashtagNameBySubOptionId() { + //given + int optionId1 = 1; + int optionId2 = 2; + + //when + List hashtag1 = optionRepository.findAllHashtagNameBySubOptionId(optionId1); + List hashtag2 = optionRepository.findAllHashtagNameBySubOptionId(optionId2); + System.out.println("hashtag2.toString() = " + hashtag2.toString()); + //then + assertTrue(hashtag1.contains("레저")); + assertTrue(hashtag1.contains("스포츠")); + assertTrue(hashtag1.contains("캠핑")); + assertTrue(hashtag2.contains("레저")); + assertTrue(hashtag2.contains("장거리 운전")); + assertTrue(hashtag2.contains("주차")); + } + + @Test + @DisplayName("carId에 해당하는 모든 defaultOption을 가져옵니다.") + void findDefaultOptions() { + List defaultOptionByCarId = optionRepository.findDefaultOptionByCarId(1); + assertEquals(3, defaultOptionByCarId.size()); + assertEquals("듀얼 머플러 패키지", defaultOptionByCarId.get(0).getOptionName()); + } + + @Test + @DisplayName("옵션의 상세 데이터를 가져옵니다.") + void findOptionDetail() { + //given + int carId = 1; + int optionWithHmg = 4; + int optionPackage = 69; + int optionWithHalfHmg = 72; + OptionDetailMappedDto data1 = OptionDetailMappedDto.builder() + .optionName("20인치 다크 스퍼터링 휠") + .optionImage("/images/options/sub/darkwheel.jpg") + .optionUsedCount(12.0) + .optionBoughtCount(3850L) + .optionDescription("다크 휠입니다.") + .categoryName("휠") + .build(); + + OptionDetailMappedDto data2 = OptionDetailMappedDto.builder() + .optionName("컴포트2") + .optionImage("/options/rear-passenger.png") + .optionBoughtCount(48015L) + .categoryName("상세품목") + .build(); + + OptionDetailMappedDto data3 = OptionDetailMappedDto.builder() + .optionName("빌트인 캠") + .optionImage("/options/builtincam.png") + .optionBoughtCount(133980L) + .optionDescription("빌트인 적용된 영상기록장치로, 내비게이션 화면을 통해 영상 확인 및 앱 연동을 통해 영상 확인 및 SNS 공유가 가능합니다.") + .categoryName("상세품목") + .build(); + + //when + Optional packageData = optionRepository.findOptionDetail(carId, optionPackage); + Optional halfHmgData = optionRepository.findOptionDetail(carId, optionWithHalfHmg); + Optional hmgData = optionRepository.findOptionDetail(carId, optionWithHmg); + + //then + assertTrue(packageData.isPresent()); + assertTrue(halfHmgData.isPresent()); + assertTrue(hmgData.isPresent()); + softAssertions.assertThat(hmgData.get()).usingRecursiveComparison().isEqualTo(data1); + softAssertions.assertThat(packageData.get()).usingRecursiveComparison().isEqualTo(data2); + softAssertions.assertThat(halfHmgData.get()).usingRecursiveComparison().isEqualTo(data3); + } + + @Test + @DisplayName("패키지 여부를 확인하고 패키지라면 하위 패키지 리스트를 호출") + void findPackageSubOptions() { + int optionPackage = 69; + int singlePackage = 4; + + OptionDetailMappedDto sub1 = OptionDetailMappedDto.builder() + .optionName("후석 승객알림") + .optionImage("/options/rear-passenger.png") + .optionUsedCount(82.0) + .optionDescription("초음파 센서를 통해 뒷좌석에 남아있는 승객의 움직임을 감지하여 운전자에게 경고함으로써 부주의에 의한 유아 또는 반려 동물 등의 방치 사고를 예방하는 신기술입니다.") + .categoryName("악세사리") + .build(); + + OptionDetailMappedDto sub2 = OptionDetailMappedDto.builder() + .optionName("메탈 리어범퍼스텝") + .optionImage("/options/metalrearbumper.png") + .optionDescription("러기지 룸 앞쪽 하단부를 메탈로 만들어 물건을 싣고 내릴 때나 사람이 올라갈 때 차체를 보호해줍니다.") + .categoryName("외관") + .build(); + + List packageSubOptions = optionRepository.findPackageSubOptions(optionPackage); + List singleOptions = optionRepository.findPackageSubOptions(singlePackage); + + assertTrue(singleOptions.isEmpty()); + softAssertions.assertThat(packageSubOptions.get(0)).usingRecursiveComparison().isEqualTo(sub1); + softAssertions.assertThat(packageSubOptions.get(1)).usingRecursiveComparison().isEqualTo(sub2); + + } +} \ No newline at end of file diff --git a/backend/src/test/java/autoever2/cartag/service/CarServiceTest.java b/backend/src/test/java/autoever2/cartag/service/CarServiceTest.java new file mode 100644 index 0000000..04ae24b --- /dev/null +++ b/backend/src/test/java/autoever2/cartag/service/CarServiceTest.java @@ -0,0 +1,128 @@ +package autoever2.cartag.service; + +import autoever2.cartag.domain.car.CarDto; +import autoever2.cartag.domain.car.CarInfoDto; +import autoever2.cartag.domain.car.DefaultOptionDto; +import autoever2.cartag.repository.CarRepository; +import autoever2.cartag.repository.OptionRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class CarServiceTest { + + @InjectMocks + private CarService service; + + @Mock + private CarRepository carRepository; + + @Mock + private OptionRepository optionRepository; + + private List carInfoDtoList; + + private List defaultOptionDtoList; + + @BeforeEach + void setup() { + carInfoDtoList = new ArrayList<>(); + defaultOptionDtoList = new ArrayList<>(); + + defaultOptionDtoList.add(DefaultOptionDto + .builder() + .optionName("안전 하차 보조") + .optionImage("image_1") + .optionDescription("좋은 보조 장치") + .OptionUsedCount(42) + .build()); + defaultOptionDtoList.add(DefaultOptionDto + .builder() + .optionName("후측방 충둘 경고") + .optionImage("image_2") + .optionDescription("좋은 보조 장치") + .OptionUsedCount(98) + .build()); + defaultOptionDtoList.add(DefaultOptionDto + .builder() + .optionName("후방 교차 충돌 보조 장치") + .optionImage("image_3") + .optionDescription("좋은 보조 장치") + .OptionUsedCount(41) + .build()); + + carInfoDtoList.add(CarInfoDto + .builder() + .carId(1) + .trim("Le Blanc") + .carDefaultPrice(123423) + .outerImage("image_1") + .innerImage("image_2") + .wheelImage("image_3") + .carDescription("Good") + .build()); + + carInfoDtoList.add(CarInfoDto + .builder() + .carId(2) + .trim("Exclusive") + .carDefaultPrice(123423) + .outerImage("image_1") + .innerImage("image_2") + .wheelImage("image_3") + .carDescription("Good") + .build()); + + carInfoDtoList.add(CarInfoDto + .builder() + .carId(3) + .trim("Calligraphy") + .carDefaultPrice(123423) + .outerImage("image_1") + .innerImage("image_2") + .wheelImage("image_3") + .carDescription("Good") + .build()); + + carInfoDtoList.add(CarInfoDto + .builder() + .carId(4) + .trim("Prestige") + .carDefaultPrice(123423) + .outerImage("image_1") + .innerImage("image_2") + .wheelImage("image_3") + .carDescription("Good") + .build()); + } + + @Test + @DisplayName("트림 리스트와 기본 옵션을 반환") + void getCarType() { + //given + int carId = 1; + int carType = 1; + + when(carRepository.findCarByCarType(carType)).thenReturn(carInfoDtoList); + when(optionRepository.findDefaultOptionByCarId(carId)).thenReturn(defaultOptionDtoList); + + List carByCarType = service.findCarByCarType(carType); + + assertEquals(carByCarType.size(), 4); + assertEquals(carByCarType.get(0).getOptions().size(), 3); + + + } + +} \ No newline at end of file diff --git a/backend/src/test/java/autoever2/cartag/service/ColorServiceTest.java b/backend/src/test/java/autoever2/cartag/service/ColorServiceTest.java new file mode 100644 index 0000000..3a29951 --- /dev/null +++ b/backend/src/test/java/autoever2/cartag/service/ColorServiceTest.java @@ -0,0 +1,140 @@ +package autoever2.cartag.service; + +import autoever2.cartag.domain.color.InnerColorDto; +import autoever2.cartag.domain.color.OuterColorDto; +import autoever2.cartag.repository.ColorRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class ColorServiceTest { + + @InjectMocks + private ColorService service; + + @Mock + private ColorRepository repository; + private List images = new ArrayList<>(); + private List innerColors; + + private List outerColors; + + @BeforeEach + void setUp() { + outerColors = new ArrayList<>(); + outerColors.add(OuterColorDto.builder() + .colorName("어비스 블랙펄") + .colorImage("color_image_1") + .colorPrice(100000L) + .colorBoughtCount(212312L) + .build()); + + outerColors.add(OuterColorDto.builder() + .colorName("그라 파이트 그레이") + .colorImage("color_image_2") + .colorPrice(100000L) + .colorBoughtCount(203L) + .build()); + + outerColors.add(OuterColorDto.builder() + .colorName("쉬머링 실버 메탈릭") + .colorImage("color_image_3") + .colorPrice(1234440L) + .colorBoughtCount(203L) + .build()); + + outerColors.add(OuterColorDto.builder() + .colorName("크리미 화이트 펄") + .colorImage("color_image_4") + .colorPrice(100000L) + .colorBoughtCount(203L) + .build()); + + outerColors.add(OuterColorDto.builder() + .colorName("퍼플 펄") + .colorImage("color_image_5") + .colorPrice(100000L) + .colorBoughtCount(203L) + .build()); + + innerColors = new ArrayList<>(); + innerColors.add(InnerColorDto.builder() + .colorName("퀄팅 천연(블랙)") + .colorImage("color_image_1") + .colorPrice(100000L) + .colorBoughtCount(212312L) + .colorCarImage("car_image_*.jpg") + .build()); + + innerColors.add(InnerColorDto.builder() + .colorName("퀄팅 천연(그레이)") + .colorImage("color_image_2") + .colorPrice(100000L) + .colorBoughtCount(203L) + .colorCarImage("car_image_*.jpg") + .build()); + + innerColors.add(InnerColorDto.builder() + .colorName("퀄팅 천연(메탈릭)") + .colorImage("color_image_3") + .colorPrice(1234440L) + .colorBoughtCount(203L) + .colorCarImage("car_image_*.jpg") + .build()); + + innerColors.add(InnerColorDto.builder() + .colorName("퀄팅 천연(화이트)") + .colorImage("color_image_4") + .colorPrice(100000L) + .colorBoughtCount(203L) + .colorCarImage("car_image_*.jpg") + .build()); + + innerColors.add(InnerColorDto.builder() + .colorName("퀄팅 천연(퍼플)") + .colorImage("color_image_5") + .colorPrice(100000L) + .colorBoughtCount(203L) + .colorCarImage("car_image_*.jpg") + .build()); + + for(int i=1;i<=60;i++) { + images.add("car_image_" + i + ".jpg"); + } + } + + @Test + @DisplayName("차량의 외장/내장 색상 리스트 반환") + void getModelTypeData() { + //given + int carId = 1; + int colorId = 1; + + when(repository.findInnerColorCarByCarId(carId)).thenReturn(innerColors); + when(repository.findOuterColorCarByCarId(carId)).thenReturn(outerColors); + when(repository.findOuterColorImagesByColorId(colorId)).thenReturn(Optional.of("red_image_*.jpg")); + + //when + List result_outer = service.findOuterColorByCarId(carId); + List result_inner = service.findInnerColorByCarId(carId); + List imageFiles = service.changeImageToImages(colorId); + + //then + assertEquals(result_outer.size(), 5); + assertEquals(result_inner.size(), 5); + assertEquals(images.size(), 60); + } + +} \ No newline at end of file diff --git a/backend/src/test/java/autoever2/cartag/service/ModelServiceTest.java b/backend/src/test/java/autoever2/cartag/service/ModelServiceTest.java new file mode 100644 index 0000000..428bf15 --- /dev/null +++ b/backend/src/test/java/autoever2/cartag/service/ModelServiceTest.java @@ -0,0 +1,177 @@ +package autoever2.cartag.service; + +import autoever2.cartag.domain.model.*; +import autoever2.cartag.repository.CarRepository; +import autoever2.cartag.repository.ModelRepository; +import org.assertj.core.api.SoftAssertions; +import org.assertj.core.api.junit.jupiter.InjectSoftAssertions; +import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; + +@ExtendWith({MockitoExtension.class, SoftAssertionsExtension.class}) +class ModelServiceTest { + + @InjectSoftAssertions + SoftAssertions softAssertions; + + @InjectMocks + private ModelService modelService; + + @Mock + private ModelRepository modelRepository; + + @Mock + private CarRepository carRepository; + + @Test + @DisplayName("트림의 모델 리스트 반환") + void getModelTypeData() { + //given + List trimModelList = new ArrayList<>(); + //디젤 2.2 데이터 + trimModelList.add(ModelShortMappedDto.builder() + .modelId(1) + .modelName("디젤 2.2") + .modelPrice(0L) + .isDefaultModel(true) + .modelTypeName("파워트레인") + .modelBoughtCount(1800L) + .build()); + + //가솔린 3.8 데이터 + trimModelList.add(ModelShortMappedDto.builder() + .modelId(2) + .modelName("가솔린 3.8") + .modelPrice(280000L) + .isDefaultModel(false) + .modelTypeName("파워트레인") + .modelBoughtCount(1900L) + .build()); + + //7인승 데이터 + trimModelList.add(ModelShortMappedDto.builder() + .modelId(3) + .modelName("7인승") + .modelPrice(0L) + .isDefaultModel(true) + .modelTypeName("바디타입") + .modelBoughtCount(900L) + .build()); + + //8인승 데이터 + trimModelList.add(ModelShortMappedDto.builder() + .modelId(4) + .modelName("8인승") + .modelPrice(130000L) + .isDefaultModel(false) + .modelTypeName("바디타입") + .modelBoughtCount(800L) + .build()); + + //2WD 데이터 + trimModelList.add(ModelShortMappedDto.builder() + .modelId(5) + .modelName("2WD") + .modelPrice(0L) + .isDefaultModel(true) + .modelTypeName("구동방식") + .modelBoughtCount(1200L) + .build()); + + //4WD 데이터 + trimModelList.add(ModelShortMappedDto.builder() + .modelId(6) + .modelName("4WD") + .modelPrice(237000L) + .isDefaultModel(false) + .modelTypeName("구동방식") + .modelBoughtCount(200L) + .build()); + + int carId = 1; + Long boughtCount = 2000L; + when(modelRepository.findAllModelTypeData(carId)).thenReturn(trimModelList); + when(carRepository.findCarBoughtCountByCarId(carId)).thenReturn(Optional.of(2000L)); + + //when + List result = modelService.getModelTypeData(carId); + + //then + assertEquals(6, result.size()); + assertEquals(1, result.get(0).getModelId()); + assertEquals("가솔린 3.8", result.get(1).getModelName()); + assertEquals("바디타입", result.get(2).getModelTypeName()); + assertEquals(130000L, result.get(3).getModelPrice()); + assertEquals(10, result.get(5).getPercentage()); + } + + @Test + @DisplayName("모델의 상세 데이터 반환") + void getModelDetailData() { + int modelId1 = 1; + ModelDetailMappedDto model1 = ModelDetailMappedDto.builder() + .modelTypeName("파워트레인") + .modelName("디젤2.2") + .optionDescription("높은 토크로 파워풀한 드라이빙이 가능하며, 차급대비 연비 효율이 우수합니다") + .modelImage("/model/diesel2-2.jpg") + .build(); + + int modelId2 = 4; + + when(modelRepository.findModelDetailData(modelId1)).thenReturn(Optional.of(model1)); + + ModelDetailMappedDto result1 = modelService.getModelDetail(modelId1); + + softAssertions.assertThat(result1).usingRecursiveComparison().isEqualTo(model1); + softAssertions.assertThatThrownBy(() -> modelService.getModelDetail(4)).isInstanceOf(RuntimeException.class); + } + + @Test + @DisplayName("파워트레인의 HMG 데이터 반환") + void getPowerTrainData() { + int powerTrainId1 = 1; + PowerTrainMappedDto powerTrain1 = PowerTrainMappedDto.builder() + .maxPs("202/3,800PS/rpm") + .maxKgfm("45.0/1,750~2,750kgf-m/rpm") + .build(); + + int powerTrainId2 = 4; + + when(modelRepository.findPowerTrainData(powerTrainId1)).thenReturn(Optional.of(powerTrain1)); + + PowerTrainMappedDto result1 = modelService.getPowerTrainHmgData(powerTrainId1); + + softAssertions.assertThat(result1).usingRecursiveComparison().isEqualTo(powerTrain1); + softAssertions.assertThatThrownBy(() -> modelService.getPowerTrainHmgData(powerTrainId2)).isInstanceOf(RuntimeException.class); + } + + @Test + @DisplayName("파워트레인과 구동방식의 조합으로 HMG 데이터 반환") + void getEfficiencyData() { + int powerTrainId = 1; + int operationId = 3; + ModelEfficiencyDataDto data = ModelEfficiencyDataDto.builder() + .averageFuel("12.16km/s") + .displacement("2,199cc") + .build(); + + when(modelRepository.findEfficiencyData(powerTrainId, operationId)).thenReturn(Optional.of(data)); + + softAssertions.assertThat(modelService.getEfficiencyData(powerTrainId, operationId)).usingRecursiveComparison().isEqualTo(data); + softAssertions.assertThatThrownBy(() -> modelService.getEfficiencyData(2, 4)).isInstanceOf(RuntimeException.class); + } +} \ No newline at end of file diff --git a/backend/src/test/java/autoever2/cartag/service/OptionServiceTest.java b/backend/src/test/java/autoever2/cartag/service/OptionServiceTest.java new file mode 100644 index 0000000..dd8dc3b --- /dev/null +++ b/backend/src/test/java/autoever2/cartag/service/OptionServiceTest.java @@ -0,0 +1,210 @@ +package autoever2.cartag.service; + +import autoever2.cartag.domain.option.*; +import autoever2.cartag.repository.CarRepository; +import autoever2.cartag.repository.OptionRepository; +import org.assertj.core.api.SoftAssertions; +import org.assertj.core.api.junit.jupiter.InjectSoftAssertions; +import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; + +@ExtendWith({MockitoExtension.class, SoftAssertionsExtension.class}) +class OptionServiceTest { + + @InjectSoftAssertions + private SoftAssertions softAssertions; + + @InjectMocks + private OptionService optionService; + + @Mock + private OptionRepository optionRepository; + + @Mock + private CarRepository carRepository; + + private List optionList; + + private List hashtagList; + + @BeforeEach + void setUp() { + hashtagList = new ArrayList<>(); + optionList = new ArrayList<>(); + hashtagList.add("여행"); + hashtagList.add("장거리 운전"); + hashtagList.add("시원한"); + + optionList.add(SubOptionMappedDto.builder() + .optionId(1) + .optionCategoryName("상세품목") + .optionImage("/images/options/sub/2seats.jpg") + .optionPrice(100000L) + .optionName("2열 통풍 시트") + .optionBoughtCount(2800L) + .optionUsedCount(30.0) + .build()); + + optionList.add(SubOptionMappedDto.builder() + .optionId(2) + .optionCategoryName("악세사리") + .optionImage("/images/options/sub/legwarmer.jpg") + .optionPrice(230000L) + .optionName("적외선 무릎 워머") + .optionBoughtCount(4200L) + .optionUsedCount(42.0) + .build()); + + optionList.add(SubOptionMappedDto.builder() + .optionId(3) + .optionCategoryName("악세사리") + .optionImage("/images/options/sub/murfler.jpg") + .optionPrice(840000L) + .optionName("듀얼 머플러 패키지") + .optionBoughtCount(1300L) + .optionUsedCount(55.0) + .build()); + + optionList.add(SubOptionMappedDto.builder() + .optionId(4) + .optionCategoryName("휠") + .optionImage("/images/options/sub/darkwheel.jpg") + .optionPrice(840000L) + .optionName("20인치 다크 스퍼터링 휠") + .optionBoughtCount(3850L) + .optionUsedCount(12.0) + .build()); + } + + @Test + @DisplayName("추가옵션 리스트를 가져와 퍼센트로 변환") + void getSubOptionList() { + int carId = 1; + Long boughtCount = 2000L; + when(optionRepository.findAllSubOptionWithCategoryNameByCarId(carId)).thenReturn(optionList); + when(carRepository.findCarBoughtCountByCarId(carId)).thenReturn(Optional.of(10000L)); + when(optionRepository.findAllHashtagNameBySubOptionId(1)).thenReturn(hashtagList); + when(optionRepository.findAllHashtagNameBySubOptionId(2)).thenReturn(hashtagList); + when(optionRepository.findAllHashtagNameBySubOptionId(3)).thenReturn(hashtagList); + when(optionRepository.findAllHashtagNameBySubOptionId(4)).thenReturn(hashtagList); + + + //when + List result = optionService.getSubOptionList(carId); + + //then + assertEquals(4, result.size()); + assertEquals(1, result.get(0).getSubOptionId()); + assertEquals("상세품목", result.get(0).getOptionCategoryName()); + assertEquals("/images/options/sub/legwarmer.jpg", result.get(1).getOptionImage()); + assertEquals(230000L, result.get(1).getOptionPrice()); + assertEquals("듀얼 머플러 패키지", result.get(2).getOptionName()); + assertEquals(hashtagList, result.get(2).getHashtagName()); + assertEquals(38, result.get(3).getPercentage()); + assertTrue(result.get(3).isHasHmgData()); + } + + @Test + @DisplayName("옵션의 상세 데이터를 가져온다.") + void getOptionDetail() { + int carId = 1; + int singleOption = 4; + int packageOption = 69; + OptionDetailMappedDto data1 = OptionDetailMappedDto.builder() + .optionName("20인치 다크 스퍼터링 휠") + .optionImage("/images/options/sub/darkwheel.jpg") + .optionUsedCount(12.0) + .optionBoughtCount(3850L) + .optionDescription("다크 휠입니다.") + .categoryName("휠") + .build(); + + OptionDetailDto expected1 = OptionDetailDto.builder() + .categoryName("휠") + .optionDescription("다크 휠입니다.") + .optionName("20인치 다크 스퍼터링 휠") + .optionImage("/images/options/sub/darkwheel.jpg") + .hmgData(OptionHmgDataVo.builder().optionUsedCount(12.0).optionBoughtCount(3850L).build()) + .isPackage(false) + .build(); + + OptionDetailMappedDto data2 = OptionDetailMappedDto.builder() + .optionName("컴포트2") + .optionImage("/options/rear-passenger.png") + .optionBoughtCount(7890L) + .categoryName("상세품목") + .build(); + + List subPackages = new ArrayList<>(); + subPackages.add(OptionDetailMappedDto.builder() + .optionName("후석 승객알림") + .optionImage("/options/rear-passenger.png") + .optionUsedCount(82.0) + .optionDescription("초음파 센서를 통해 뒷좌석에 남아있는 승객의 움직임을 감지하여 운전자에게 경고함으로써 부주의에 의한 유아 또는 반려 동물 등의 방치 사고를 예방하는 신기술입니다.") + .categoryName("악세사리") + .optionBoughtCount(7890L) + .build()); + subPackages.add(OptionDetailMappedDto.builder() + .optionName("메탈 리어범퍼스텝") + .optionImage("/options/metalrearbumper.png") + .optionDescription("러기지 룸 앞쪽 하단부를 메탈로 만들어 물건을 싣고 내릴 때나 사람이 올라갈 때 차체를 보호해줍니다.") + .categoryName("외관") + .build()); + + List subOptions = new ArrayList<>(); + subOptions.add(OptionDetailDto.builder() + .categoryName("악세사리") + .optionName("후석 승객알림") + .optionImage("/options/rear-passenger.png") + .isPackage(false) + .optionDescription("초음파 센서를 통해 뒷좌석에 남아있는 승객의 움직임을 감지하여 운전자에게 경고함으로써 부주의에 의한 유아 또는 반려 동물 등의 방치 사고를 예방하는 신기술입니다.") + .hmgData(OptionHmgDataVo.builder() + .optionBoughtCount(7890L) + .optionUsedCount(82.0) + .build()) + .build()); + subOptions.add(OptionDetailDto.builder() + .categoryName("외관") + .optionName("메탈 리어범퍼스텝") + .optionImage("/options/metalrearbumper.png") + .isPackage(false) + .hmgData(OptionHmgDataVo.builder().optionBoughtCount(7890L).build()) + .optionDescription("러기지 룸 앞쪽 하단부를 메탈로 만들어 물건을 싣고 내릴 때나 사람이 올라갈 때 차체를 보호해줍니다.") + .build()); + + OptionDetailDto expected2 = OptionDetailDto.builder() + .categoryName("상세품목") + .optionName("컴포트2") + .optionImage("/options/rear-passenger.png") + .isPackage(true) + .hmgData(OptionHmgDataVo.builder().optionBoughtCount(7890L).build()) + .subOptionList(subOptions) + .build(); + + when(optionRepository.findOptionDetail(carId, singleOption)).thenReturn(Optional.of(data1)); + when(optionRepository.findPackageSubOptions(singleOption)).thenReturn(Collections.emptyList()); + when(optionRepository.findOptionDetail(carId, packageOption)).thenReturn(Optional.of(data2)); + when(optionRepository.findPackageSubOptions(packageOption)).thenReturn(subPackages); + + OptionDetailDto singleResult = optionService.getOptionDetailData(carId, singleOption); + OptionDetailDto packageResult = optionService.getOptionDetailData(carId, packageOption); + + softAssertions.assertThat(singleResult).usingRecursiveComparison().isEqualTo(expected1); + softAssertions.assertThat(packageResult).usingRecursiveComparison().isEqualTo(expected2); + } +} \ No newline at end of file diff --git a/backend/src/test/resources/application.yml b/backend/src/test/resources/application.yml new file mode 100644 index 0000000..0b592e6 --- /dev/null +++ b/backend/src/test/resources/application.yml @@ -0,0 +1,16 @@ +spring: + config: + activate: + on-profile: test + h2: + console: + enabled: true + datasource: + url: jdbc:h2:mem:test;MODE=MySQL; + username: sa + password: + driver-class-name: org.h2.Driver + sql: + init: + mode: always + schema-locations: classpath:schema-h2.sql diff --git a/backend/src/test/resources/insert/insert-model-h2.sql b/backend/src/test/resources/insert/insert-model-h2.sql new file mode 100644 index 0000000..e010b0a --- /dev/null +++ b/backend/src/test/resources/insert/insert-model-h2.sql @@ -0,0 +1,28 @@ +insert into CarType (car_type_id, car_type_image, car_type_name) values (1, '/cartype/palisade/palisade-thumbnail.png', '팰리세이드'); +insert into Car values (1, 1, '르블랑', 41980000, '/cartype/palisade/leblanc-outer.png', '/cartype/palisade/palisade-inner.png', '/cartype/palisade/leblanc-wheel.png', 150000, '합리적인 조합의 절정'); + +insert into ModelType (model_type_id, model_type_name) VALUES (1, '파워트레인'); +insert into ModelType (model_type_id, model_type_name) VALUES (2, '구동방식'); +insert into ModelType (model_type_id, model_type_name) VALUES (3, '바디타입'); + +INSERT INTO Model VALUES (1,'디젤2.2',1,'높은 토크로 파워풀한 드라이빙이 가능하며, 차급대비 연비 효율이 우수합니다',1480000,'/model/diesel2-2.jpg'); +INSERT INTO Model VALUES (2,'가솔린3.8',1,'고마력의 우수한 가속 성능을 확보하여, 넉넉하고 안정감 있는 주행이 가능합니다.' || CHR(10) || '엔진의 진동이 적어 편안하고 조용한 드라이빙 감성을 제공합니다.',0,'/model/gasoline3-8.jpg'); +INSERT INTO Model VALUES (3,'2WD',2,'엔진에서 전달되는 동력이 전/후륜 바퀴 중 한쪽으로만 전달되어 차량을 움직이는 방식입니다.' || CHR(10) || '차체가 가벼워 연료 효율이 높습니다.',0,'/model/2wd.png'); +INSERT INTO Model VALUES (4,'4WD',2,'전자식 상시 4륜 구동 시스템 입니다.' || CHR(10) || '도로의 상황이나 주행 환경에 맞춰 전후륜 구동력을 자동배분하여 주행 안전성을 높여줍니다',2370000,'/model/4wd.png'); +INSERT INTO Model VALUES (5,'7인승',3,'기존 8인승 시트(1열 2명, 2열 3명, 3열 3명)에서 2열 가운데 시트를 없애 2열 탑승객의 편의는 물론, 3열 탑승객의 승하차가 편리합니다',0,'/model/7seats.jpg'); +INSERT INTO Model VALUES (6,'8인승',3,'1열 2명, 2열 3명, 3열 3명이 탑승할 수 있는 구조로, 많은 인원이 탑승할 수 있도록 배려하였습니다',0,'/model/8seats.jpg'); + +insert into ModelCarMapper (model_car_mapper_id, car_id, model_id, model_bought_count, is_default_model) VALUES (1, 1, 1, 800, 1); +insert into ModelCarMapper (model_car_mapper_id, car_id, model_id, model_bought_count, is_default_model) VALUES (2, 1, 2, 1300, 0); +insert into ModelCarMapper (model_car_mapper_id, car_id, model_id, model_bought_count, is_default_model) VALUES (3, 1, 3, 500, 1); +insert into ModelCarMapper (model_car_mapper_id, car_id, model_id, model_bought_count, is_default_model) VALUES (4, 1, 4, 1500, 0); +insert into ModelCarMapper (model_car_mapper_id, car_id, model_id, model_bought_count, is_default_model) VALUES (5, 1, 5, 2300, 1); +insert into ModelCarMapper (model_car_mapper_id, car_id, model_id, model_bought_count, is_default_model) VALUES (6, 1, 6, 1800, 0); + +insert into PowerTrainData values (1, '202/3,800PS/rpm', '45.0/1,750~2,750kgf-m/rpm'); +insert into PowerTrainData values (2, '295/6,000PS/rpm', '36.2/5,200kgf-m/rpm'); + +insert into PowerTrainOperationEfficiency values (1, 1, 3, '12.16km/s', '2,199cc'); +insert into PowerTrainOperationEfficiency values (2, 1, 4, '11.53km/s', '2,199cc'); +insert into PowerTrainOperationEfficiency values (3, 2, 3, '9.23km/s', '3,778cc'); +insert into PowerTrainOperationEfficiency values (4, 2, 4, '8.7km/s', '3,778cc'); \ No newline at end of file diff --git a/backend/src/test/resources/insert/insert-suboption-h2.sql b/backend/src/test/resources/insert/insert-suboption-h2.sql new file mode 100644 index 0000000..b6a4ddc --- /dev/null +++ b/backend/src/test/resources/insert/insert-suboption-h2.sql @@ -0,0 +1,45 @@ +insert into CarType values (1, '/images/cartype/palisade.jpg', '팰리세이드'); +insert into Car values (1, 1, 'Le Blanc', 38650000, 'outer-image.jpg', 'inner-image.jpg', 'wheel-image.jpg', 10000, '편안합니다.'); + +insert into OptionCategory (option_category_id, option_category_name) VALUES (1, '상세품목'); +insert into OptionCategory (option_category_id, option_category_name) VALUES (2, '악세사리'); +insert into OptionCategory (option_category_id, option_category_name) VALUES (3, '휠'); +insert into OptionCategory (option_category_id, option_category_name) VALUES (4, '외관'); +insert into OptionCategory (option_category_id, option_category_name) values (9, '상세품목'); + +insert into caroption values (1, 1, '2열 통풍 시트', '/images/options/sub/2seats.jpg', '시동이 걸린 상태에서 해당 좌석의 통풍 스위치를 누르면 표시등이 켜지면서 해당 좌석에 바람이 나오는 편의장치입니다.', 38); +insert into caroption values (2, 2, '적외선 무릎 워머', '/images/options/sub/warmer.jpg', '워머입니다.', 42); +insert into caroption values (3, 2, '듀얼 머플러 패키지', '/images/options/sub/murfler.jpg', '머플러입니다.', 55); +insert into caroption values (4, 3, '20인치 다크 스퍼터링 휠', '/images/options/sub/darkwheel.jpg', '다크 휠입니다.', 12); +insert into CarOption (option_id, option_category_id, option_name, option_image, option_description) values (72, 9, '빌트인 캠', '/options/builtincam.png', '빌트인 적용된 영상기록장치로, 내비게이션 화면을 통해 영상 확인 및 앱 연동을 통해 영상 확인 및 SNS 공유가 가능합니다.'); +insert into CarOption values (75, 2, '후석 승객알림', '/options/rear-passenger.png', '초음파 센서를 통해 뒷좌석에 남아있는 승객의 움직임을 감지하여 운전자에게 경고함으로써 부주의에 의한 유아 또는 반려 동물 등의 방치 사고를 예방하는 신기술입니다.', 82); +insert into CarOption (option_id, option_category_id, option_name, option_image, option_description) values (76, 4, '메탈 리어범퍼스텝', '/options/metalrearbumper.png', '러기지 룸 앞쪽 하단부를 메탈로 만들어 물건을 싣고 내릴 때나 사람이 올라갈 때 차체를 보호해줍니다.'); +insert into CarOption(option_id, option_category_id, option_name, option_image) values (69, 9, '컴포트2', '/options/rear-passenger.png'); + +insert into SubOptionData values (1, 1, 1, 2800, 100000); +insert into SubOptionData values (2, 1, 2, 4200, 130000); +insert into SubOptionData values (3, 1, 3, 1300, 870000); +insert into SubOptionData values (4, 1, 4, 3850, 50000); +insert into SubOptionData values (5, 1, 72, 133980, 690000); +insert into SubOptionData values (6, 1, 69, 48015, 1090000); + +insert into Hashtag values (1, '레저'); +insert into Hashtag values (2, '스포츠'); +insert into Hashtag values (3, '캠핑'); +insert into Hashtag values (4, '장거리 운전'); +insert into Hashtag values (5, '주차'); + +insert into OptionHashtag (option_hashtag_id, option_id, hashtag_id) values (1, 1, 1); +insert into OptionHashtag (option_hashtag_id, option_id, hashtag_id) values (2, 1, 2); +insert into OptionHashtag (option_hashtag_id, option_id, hashtag_id) values (3, 1, 3); +insert into OptionHashtag (option_hashtag_id, option_id, hashtag_id) values (4, 2, 1); +insert into OptionHashtag (option_hashtag_id, option_id, hashtag_id) values (5, 2, 4); +insert into OptionHashtag (option_hashtag_id, option_id, hashtag_id) values (6, 2, 5); + +insert into defaultOptionData values(1, 1, 1); +insert into defaultOptionData values(2, 1, 2); +insert into defaultOptionData values(3, 1, 3); + +insert into SubOptionPackage values (69, 75); +insert into SubOptionPackage values (69, 76); + diff --git a/backend/src/test/resources/insert/insertCar-h2.sql b/backend/src/test/resources/insert/insertCar-h2.sql new file mode 100644 index 0000000..1d7639c --- /dev/null +++ b/backend/src/test/resources/insert/insertCar-h2.sql @@ -0,0 +1,8 @@ +insert into CarType values(1, 'image_1', '펠리세이드'); + +insert into Car values(1, 1, 'Le Blanc', 40000000, 'image_1', 'image_2', 'image_3', 234, 'Good'); +insert into Car values(2, 1, 'Exclusive', 40000000, 'image_1', 'image_2', 'image_3', 24, 'Good'); +insert into Car values(3, 1, 'Calligraphy', 40000000, 'image_1', 'image_2', 'image_3', 34, 'Good'); +insert into Car values(4, 1, 'Prestige', 40000000, 'image_1', 'image_2', 'image_3', 23499, 'Good'); + + diff --git a/backend/src/test/resources/insert/insertColor-h2.sql b/backend/src/test/resources/insert/insertColor-h2.sql new file mode 100644 index 0000000..5d30052 --- /dev/null +++ b/backend/src/test/resources/insert/insertColor-h2.sql @@ -0,0 +1,13 @@ +insert into CarType values(1, 'image_1', '펠리세이드'); + +insert into Car values(1, 1, 'Le Blanc', 40000000, 'image_1', 'image_2', 'image_3', 234, 'Good'); + +insert into Color values(1, '천연 퀄팅(블랙)', 'image_1', 1); +insert into Color values(2, '천연 퀄팅(화이트)', 'image_2', 1); +insert into Color values(3, '퍼플 그레이 펄', 'image_3', 0); +insert into Color values(4, '코발트 블루', 'image_4', 0); + +insert into ColorCarMapper values(1, 1, 1, 'red_image_*.jpg', 1234, 12348); +insert into ColorCarMapper values(2, 1, 2, 'white_image_*.jpg', 555, 12346); +insert into ColorCarMapper values(3, 1, 3, 'black_image_*.jpg', 154, 12354); +insert into ColorCarMapper values(4, 1, 4, 'blue_image_*.jpg', 1734, 1234); \ No newline at end of file diff --git a/backend/src/test/resources/schema-h2.sql b/backend/src/test/resources/schema-h2.sql new file mode 100644 index 0000000..c1a0975 --- /dev/null +++ b/backend/src/test/resources/schema-h2.sql @@ -0,0 +1,269 @@ +-- Created by Vertabelo (http://vertabelo.com) +-- Last modification date: 2023-08-11 10:33:17.936 + +-- tables +-- Table: Car +CREATE TABLE Car ( + car_id int NOT NULL, + car_type_id int NOT NULL, + trim varchar(50) NOT NULL, + car_default_price int NOT NULL, + outer_image varchar(255) NOT NULL, + inner_image varchar(255) NOT NULL, + wheel_image varchar(255) NULL, + bought_count bigint NOT NULL, + car_description varchar(255) NOT NULL, + CONSTRAINT Car_pk PRIMARY KEY (car_id) +); + +-- Table: CarOption +CREATE TABLE CarOption ( + option_id int NOT NULL, + option_category_id int NOT NULL, + option_name varchar(50) NOT NULL, + option_image varchar(255) NULL, + option_description text NULL, + option_used_count double NULL, + CONSTRAINT CarOption_pk PRIMARY KEY (option_id) +); + +-- Table: CarType +CREATE TABLE CarType ( + car_type_id int NOT NULL, + car_type_image varchar(255) NOT NULL, + car_type_name varchar(50) NOT NULL, + CONSTRAINT CarType_pk PRIMARY KEY (car_type_id) +); + +-- Table: Color +CREATE TABLE Color ( + color_id int NOT NULL, + color_name varchar(50) NOT NULL, + color_image varchar(255) NOT NULL, + is_outer_color int NOT NULL, + CONSTRAINT Color_pk PRIMARY KEY (color_id) +); + +-- Table: ColorCarMapper +CREATE TABLE ColorCarMapper ( + color_car_mapper_id int NOT NULL, + car_id int NOT NULL, + color_id int NOT NULL, + color_car_image varchar(255) NOT NULL, + color_price bigint NOT NULL, + color_bought_count bigint NOT NULL, + CONSTRAINT ColorCarMapper_pk PRIMARY KEY (color_car_mapper_id) +); + +-- Table: DefaultOptionData +CREATE TABLE DefaultOptionData ( + default_option_data_id int NOT NULL, + car_id int NOT NULL, + option_id int NOT NULL, + CONSTRAINT DefaultOptionData_pk PRIMARY KEY (default_option_data_id) +); + +-- Table: Hashtag +CREATE TABLE Hashtag ( + hashtag_id int NOT NULL, + hashtag_name varchar(50) NOT NULL, + CONSTRAINT Hashtag_pk PRIMARY KEY (hashtag_id) +); + +-- Table: Model +CREATE TABLE Model ( + model_id int NOT NULL, + model_name varchar(50) NOT NULL, + model_type_id int NOT NULL, + option_description text NOT NULL, + model_price bigint NOT NULL, + model_image varchar(255) NOT NULL, + CONSTRAINT Model_pk PRIMARY KEY (model_id) +); + +-- Table: ModelCarMapper +CREATE TABLE ModelCarMapper ( + model_car_mapper_id int NOT NULL, + car_id int NOT NULL, + model_id int NOT NULL, + model_bought_count bigint NOT NULL, + is_default_model int NOT NULL, + CONSTRAINT ModelCarMapper_pk PRIMARY KEY (model_car_mapper_id) +); + +-- Table: ModelType +CREATE TABLE ModelType ( + model_type_id int NOT NULL, + model_type_name varchar(50) NOT NULL, + CONSTRAINT ModelType_pk PRIMARY KEY (model_type_id) +); + +-- Table: OptionCategory +CREATE TABLE OptionCategory ( + option_category_id int NOT NULL, + option_category_name varchar(50) NOT NULL, + CONSTRAINT OptionCategory_pk PRIMARY KEY (option_category_id) +); + +-- Table: OptionHashtag +CREATE TABLE OptionHashtag ( + option_hashtag_id int NOT NULL, + option_id int NOT NULL, + hashtag_id int NOT NULL, + CONSTRAINT OptionHashtag_pk PRIMARY KEY (option_hashtag_id) +); + +-- Table: PowerTrainData +CREATE TABLE PowerTrainData ( + power_train_id int NOT NULL, + max_ps varchar(30) NOT NULL, + max_kgfm varchar(30) NOT NULL, + CONSTRAINT PowerTrainData_pk PRIMARY KEY (power_train_id) +); + +-- Table: PowerTrainOperationEfficiency +CREATE TABLE PowerTrainOperationEfficiency ( + efficiency_id int NOT NULL, + power_train_id int NOT NULL, + operation_id int NOT NULL, + average_fuel varchar(50) NOT NULL, + displacement varchar(50) NOT NULL, + CONSTRAINT PowerTrainOperationEfficiency_pk PRIMARY KEY (efficiency_id) +); + +-- Table: SalesHistory +CREATE TABLE SalesHistory ( + sales_history_id int NOT NULL, + car_id int NOT NULL, + total_price bigint NOT NULL, + sales_count bigint NOT NULL, + CONSTRAINT SalesHistory_pk PRIMARY KEY (sales_history_id) +); + +-- Table: SalesModel +CREATE TABLE SalesModel ( + model_id int NOT NULL, + sales_history_id int NOT NULL, + CONSTRAINT SalesModel_pk PRIMARY KEY (model_id,sales_history_id) +); + +-- Table: SalesOption +CREATE TABLE SalesOption ( + sales_option_id int NOT NULL, + sales_history_id int NOT NULL, + sub_option_id int NOT NULL, + CONSTRAINT SalesOption_pk PRIMARY KEY (sales_option_id) +); + +-- Table: SubOptionData +CREATE TABLE SubOptionData ( + sub_option_data_id int NOT NULL, + car_id int NOT NULL, + option_id int NOT NULL, + option_bought_count bigint NOT NULL, + option_price bigint NOT NULL, + CONSTRAINT SubOptionData_pk PRIMARY KEY (sub_option_data_id) +); + +-- Table: SubOptionPackage +CREATE TABLE SubOptionPackage ( + package_id int NOT NULL, + option_id int NOT NULL, + CONSTRAINT SubOptionPackage_pk PRIMARY KEY (package_id,option_id) +); + +-- foreign keys +-- Reference: Car_CarType (table: Car) +ALTER TABLE Car ADD CONSTRAINT Car_CarType FOREIGN KEY (car_type_id) + REFERENCES CarType (car_type_id); + +-- Reference: ColorCarMapper_Car (table: ColorCarMapper) +ALTER TABLE ColorCarMapper ADD CONSTRAINT ColorCarMapper_Car FOREIGN KEY (car_id) + REFERENCES Car (car_id); + +-- Reference: ColorCarMapper_Color (table: ColorCarMapper) +ALTER TABLE ColorCarMapper ADD CONSTRAINT ColorCarMapper_Color FOREIGN KEY (color_id) + REFERENCES Color (color_id); + +-- Reference: DefaultOptionData_Option (table: DefaultOptionData) +ALTER TABLE DefaultOptionData ADD CONSTRAINT DefaultOptionData_Option FOREIGN KEY (option_id) + REFERENCES CarOption (option_id); + +-- Reference: MainOptionData_Car (table: DefaultOptionData) +ALTER TABLE DefaultOptionData ADD CONSTRAINT MainOptionData_Car FOREIGN KEY (car_id) + REFERENCES Car (car_id); + +-- Reference: ModelCarMapper_Car (table: ModelCarMapper) +ALTER TABLE ModelCarMapper ADD CONSTRAINT ModelCarMapper_Car FOREIGN KEY (car_id) + REFERENCES Car (car_id); + +-- Reference: ModelCarMapper_Model (table: ModelCarMapper) +ALTER TABLE ModelCarMapper ADD CONSTRAINT ModelCarMapper_Model FOREIGN KEY (model_id) + REFERENCES Model (model_id); + +-- Reference: Model_ModelType (table: Model) +ALTER TABLE Model ADD CONSTRAINT Model_ModelType FOREIGN KEY (model_type_id) + REFERENCES ModelType (model_type_id); + +-- Reference: Operation_Efficiency_Model (table: PowerTrainOperationEfficiency) +ALTER TABLE PowerTrainOperationEfficiency ADD CONSTRAINT Operation_Efficiency_Model FOREIGN KEY (operation_id) + REFERENCES Model (model_id); + +-- Reference: OptionHashtag_Hashtag (table: OptionHashtag) +ALTER TABLE OptionHashtag ADD CONSTRAINT OptionHashtag_Hashtag FOREIGN KEY (hashtag_id) + REFERENCES Hashtag (hashtag_id); + +-- Reference: OptionHashtag_SubOption (table: OptionHashtag) +ALTER TABLE OptionHashtag ADD CONSTRAINT OptionHashtag_SubOption FOREIGN KEY (option_id) + REFERENCES CarOption (option_id); + +-- Reference: Package (table: SubOptionPackage) +ALTER TABLE SubOptionPackage ADD CONSTRAINT Package FOREIGN KEY (package_id) + REFERENCES CarOption (option_id); + +-- Reference: PowerTrainData_Model (table: PowerTrainData) +ALTER TABLE PowerTrainData ADD CONSTRAINT PowerTrainData_Model FOREIGN KEY (power_train_id) + REFERENCES Model (model_id); + +-- Reference: PowerTrain_Efficiency_Model (table: PowerTrainOperationEfficiency) +ALTER TABLE PowerTrainOperationEfficiency ADD CONSTRAINT PowerTrain_Efficiency_Model FOREIGN KEY (power_train_id) + REFERENCES Model (model_id); + +-- Reference: SalesHistory_Car (table: SalesHistory) +ALTER TABLE SalesHistory ADD CONSTRAINT SalesHistory_Car FOREIGN KEY (car_id) + REFERENCES Car (car_id); + +-- Reference: SalesModel_Model (table: SalesModel) +ALTER TABLE SalesModel ADD CONSTRAINT SalesModel_Model FOREIGN KEY (model_id) + REFERENCES Model (model_id); + +-- Reference: SalesModel_SalesHistory (table: SalesModel) +ALTER TABLE SalesModel ADD CONSTRAINT SalesModel_SalesHistory FOREIGN KEY (sales_history_id) + REFERENCES SalesHistory (sales_history_id); + +-- Reference: SalesOption_SalesHistory (table: SalesOption) +ALTER TABLE SalesOption ADD CONSTRAINT SalesOption_SalesHistory FOREIGN KEY (sales_history_id) + REFERENCES SalesHistory (sales_history_id); + +-- Reference: SalesOption_SubOption (table: SalesOption) +ALTER TABLE SalesOption ADD CONSTRAINT SalesOption_SubOption FOREIGN KEY (sub_option_id) + REFERENCES CarOption (option_id); + +-- Reference: SubOptionData_Car (table: SubOptionData) +ALTER TABLE SubOptionData ADD CONSTRAINT SubOptionData_Car FOREIGN KEY (car_id) + REFERENCES Car (car_id); + +-- Reference: SubOptionData_SubOption (table: SubOptionData) +ALTER TABLE SubOptionData ADD CONSTRAINT SubOptionData_SubOption FOREIGN KEY (option_id) + REFERENCES CarOption (option_id); + +-- Reference: SubOption_OptionCategory (table: CarOption) +ALTER TABLE CarOption ADD CONSTRAINT SubOption_OptionCategory FOREIGN KEY (option_category_id) + REFERENCES OptionCategory (option_category_id); + +-- Reference: SubOption_SubOption (table: SubOptionPackage) +ALTER TABLE SubOptionPackage ADD CONSTRAINT SubOption_SubOption FOREIGN KEY (option_id) + REFERENCES CarOption (option_id); + +-- End of file. + diff --git a/frontend/.eslintignore b/frontend/.eslintignore new file mode 100644 index 0000000..9fa3e2a --- /dev/null +++ b/frontend/.eslintignore @@ -0,0 +1,10 @@ +.vscode +.github +.cache +.gitignore +public +node_modules +build +dist +README.md +package-lock.json \ No newline at end of file diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs new file mode 100644 index 0000000..baada94 --- /dev/null +++ b/frontend/.eslintrc.cjs @@ -0,0 +1,17 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react-hooks/recommended', + 'prettier', + ], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parser: '@typescript-eslint/parser', + plugins: ['react-refresh'], + rules: { + 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }], + '@typescript-eslint/no-unused-vars': ['error', { varsIgnorePattern: '^_' }], + }, +}; diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..6558f59 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,26 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +src/Test.tsx \ No newline at end of file diff --git a/frontend/.husky/pre-commit b/frontend/.husky/pre-commit new file mode 100755 index 0000000..67e6b1e --- /dev/null +++ b/frontend/.husky/pre-commit @@ -0,0 +1,5 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +cd frontend/ +npm run lint-staged diff --git a/frontend/.prettierignore b/frontend/.prettierignore new file mode 100644 index 0000000..5ff94cd --- /dev/null +++ b/frontend/.prettierignore @@ -0,0 +1,10 @@ +.vscode +.github +.cache +.gitignore +public +node_modules +build +dist +package-lock.json +README.md \ No newline at end of file diff --git a/frontend/.prettierrc b/frontend/.prettierrc new file mode 100644 index 0000000..34f439c --- /dev/null +++ b/frontend/.prettierrc @@ -0,0 +1,10 @@ +{ + "printWidth": 100, + "tabWidth": 2, + "singleQuote": true, + "trailingComma": "es5", + "useTabs": false, + "arrowParens": "always", + "bracketSpacing": true, + "bracketSameLine": false +} diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..eb591e8 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,8 @@ +FROM node:20 +WORKDIR /app +COPY package.json ./ +RUN npm install +COPY . . +RUN npm run build +COPY . . +CMD ["npm", "run", "preview"] \ No newline at end of file diff --git a/frontend/README.md b/frontend/README.md deleted file mode 100644 index b674e66..0000000 --- a/frontend/README.md +++ /dev/null @@ -1 +0,0 @@ -frontend readme diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..e4b78ea --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + TS + + +
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..dde58e9 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,5760 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "hasInstallScript": true, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.14.2", + "styled-components": "^6.0.5" + }, + "devDependencies": { + "@types/react": "^18.2.15", + "@types/react-dom": "^18.2.7", + "@types/styled-components": "^5.1.26", + "@typescript-eslint/eslint-plugin": "^6.0.0", + "@typescript-eslint/parser": "^6.0.0", + "@vitejs/plugin-react": "^4.0.3", + "eslint": "^8.45.0", + "eslint-config-prettier": "^8.9.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.3", + "husky": "^8.0.3", + "lint-staged": "^13.2.3", + "prettier": "^3.0.0", + "typescript": "^5.0.2", + "vite": "^4.4.5" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/cli": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.22.9.tgz", + "integrity": "sha512-nb2O7AThqRo7/E53EGiuAkMaRbb7J5Qp3RvN+dmua1U+kydm0oznkhqbTEG15yk26G/C3yL6OdZjzgl+DMXVVA==", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.17", + "commander": "^4.0.1", + "convert-source-map": "^1.1.0", + "fs-readdir-recursive": "^1.1.0", + "glob": "^7.2.0", + "make-dir": "^2.1.0", + "slash": "^2.0.0" + }, + "bin": { + "babel": "bin/babel.js", + "babel-external-helpers": "bin/babel-external-helpers.js" + }, + "engines": { + "node": ">=6.9.0" + }, + "optionalDependencies": { + "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3", + "chokidar": "^3.4.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/cli/node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", + "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", + "dependencies": { + "@babel/highlight": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.9.tgz", + "integrity": "sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.9.tgz", + "integrity": "sha512-G2EgeufBcYw27U4hhoIwFcgc1XU7TlXJ3mv04oOv1WCuo900U/anZSPzEqNjwdjgffkk2Gs0AN0dW1CKVLcG7w==", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.9", + "@babel/helper-compilation-targets": "^7.22.9", + "@babel/helper-module-transforms": "^7.22.9", + "@babel/helpers": "^7.22.6", + "@babel/parser": "^7.22.7", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.8", + "@babel/types": "^7.22.5", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.9.tgz", + "integrity": "sha512-KtLMbmicyuK2Ak/FTCJVbDnkN1SlT8/kceFTiuDiiRUUSMnHMidxSCdG4ndkTOHHpoomWe/4xkvHkEOncwjYIw==", + "dependencies": { + "@babel/types": "^7.22.5", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.5.tgz", + "integrity": "sha512-m1EP3lVOPptR+2DwD125gziZNcmoNSHGmJROKoy87loWUQyJaVXDgpmruWqDARZSmtYQ+Dl25okU8+qhVzuykw==", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.9.tgz", + "integrity": "sha512-7qYrNM6HjpnPHJbopxmb8hSPoZ0gsX8IvUS32JGVoy+pU9e5N0nLr1VjJoR6kA4d9dmGLxNYOjeB8sUDal2WMw==", + "dependencies": { + "@babel/compat-data": "^7.22.9", + "@babel/helper-validator-option": "^7.22.5", + "browserslist": "^4.21.9", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.9.tgz", + "integrity": "sha512-Pwyi89uO4YrGKxL/eNJ8lfEH55DnRloGPOseaA8NFNL6jAUnn+KccaISiFazCj5IolPPDjGSdzQzXVzODVRqUQ==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-member-expression-to-functions": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.9.tgz", + "integrity": "sha512-+svjVa/tFwsNSG4NEy1h85+HQ5imbT92Q5/bgtS7P0GTQlP8WuFdqsiABmQouhiFGyV66oGxZFpeYHza1rNsKw==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "regexpu-core": "^5.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.2.tgz", + "integrity": "sha512-k0qnnOqHn5dK9pZpfD5XXZ9SojAITdCKRn2Lp6rnDGzIbaP0rHyMPk/4wsSxVBVz4RfN0q6VpXWP2pDGIoQ7hw==", + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", + "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", + "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "dependencies": { + "@babel/template": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz", + "integrity": "sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", + "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz", + "integrity": "sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ==", + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.9.tgz", + "integrity": "sha512-8WWC4oR4Px+tr+Fp0X3RHDVfINGpF3ad1HIbrc8A77epiR6eMMc6jsgozkzT2uDiOOdoS9cLIQ+XD2XvI2WSmQ==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-wrap-function": "^7.22.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.9.tgz", + "integrity": "sha512-LJIKvvpgPOPUThdYqcX6IXRuIcTkcAub0IaDRGCZH0p5GPUp7PhRU9QVgFcDDd51BaPkk77ZjqFwh6DZTAEmGg==", + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-member-expression-to-functions": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", + "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", + "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.9.tgz", + "integrity": "sha512-sZ+QzfauuUEfxSEjKFmi3qDSHgLsTPK/pEpoD/qonZKOtTPTLbf59oabPQ4rKekt9lFcj/hTZaOhWwFYrgjk+Q==", + "dependencies": { + "@babel/helper-function-name": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.6.tgz", + "integrity": "sha512-YjDs6y/fVOYFV8hAf1rxd1QvR9wJe1pDBZ2AREKq/SDayfPzgk0PBnVuTCE5X1acEpMMNOVUqoe+OwiZGJ+OaA==", + "dependencies": { + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.6", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", + "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.22.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.7.tgz", + "integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.5.tgz", + "integrity": "sha512-NP1M5Rf+u2Gw9qfSO4ihjcTGW5zXTi36ITLd4/EoAcEhIZ0yjMqmftDNl3QC19CX7olhrjpyU454g/2W7X0jvQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.5.tgz", + "integrity": "sha512-31Bb65aZaUwqCbWMnZPduIZxCBngHFlzyN6Dq6KAJjtx+lx6ohKHubc61OomYi7XwVD4Ol0XCVz4h+pYFR048g==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-external-helpers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-external-helpers/-/plugin-external-helpers-7.22.5.tgz", + "integrity": "sha512-ngnNEWxmykPk82mH4ajZT0qTztr3Je6hrMuKAslZVM8G1YZTENJSYwrIGtt6KOtznug3exmAtF4so/nPqJuA4A==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-object-rest-spread": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", + "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", + "dependencies": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-unicode-property-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", + "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz", + "integrity": "sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz", + "integrity": "sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", + "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", + "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz", + "integrity": "sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.22.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.7.tgz", + "integrity": "sha512-7HmE7pk/Fmke45TODvxvkxRMV9RazV+ZZzhOL9AG8G29TLrr3jkjwF7uJfxZ30EoXpO+LJkq4oA8NjO2DTnEDg==", + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.5", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz", + "integrity": "sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ==", + "dependencies": { + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz", + "integrity": "sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.5.tgz", + "integrity": "sha512-EcACl1i5fSQ6bt+YGuU/XGCeZKStLmyVGytWkpyhCLeQVA0eu6Wtiw92V+I1T/hnezUv7j74dA/Ro69gWcU+hg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz", + "integrity": "sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.5.tgz", + "integrity": "sha512-SPToJ5eYZLxlnp1UzdARpOGeC2GbHvr9d/UV0EukuVx8atktg194oe+C5BqQ8jRTkgLRVOPYeXRSBg1IlMoVRA==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.6.tgz", + "integrity": "sha512-58EgM6nuPNG6Py4Z3zSuu0xWu2VfodiMi72Jt5Kj2FECmaYk1RrTXA45z6KBFsu9tRgwQDwIiY4FXTt+YsSFAQ==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz", + "integrity": "sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/template": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.5.tgz", + "integrity": "sha512-GfqcFuGW8vnEqTUBM7UtPd5A4q797LTvvwKxXTgRsFjoqaJiEg9deBG6kWeQYkVEL569NpnmpC0Pkr/8BLKGnQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz", + "integrity": "sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz", + "integrity": "sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.5.tgz", + "integrity": "sha512-0MC3ppTB1AMxd8fXjSrbPa7LT9hrImt+/fcj+Pg5YMD7UQyWp/02+JWpdnCymmsXwIx5Z+sYn1bwCn4ZJNvhqQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz", + "integrity": "sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g==", + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.5.tgz", + "integrity": "sha512-X4hhm7FRnPgd4nDA4b/5V280xCx6oL7Oob5+9qVS5C13Zq4bh1qq7LU0GgRU6b5dBWBvhGaXYVB4AcN6+ol6vg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.5.tgz", + "integrity": "sha512-3kxQjX1dU9uudwSshyLeEipvrLjBCVthCgeTp6CzE/9JYrlAIaeekVxRpCWsDDfYTfRZRoCeZatCQvwo+wvK8A==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz", + "integrity": "sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==", + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.5.tgz", + "integrity": "sha512-DuCRB7fu8MyTLbEQd1ew3R85nx/88yMoqo2uPSjevMj3yoN7CDM8jkgrY0wmVxfJZyJ/B9fE1iq7EQppWQmR5A==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz", + "integrity": "sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.5.tgz", + "integrity": "sha512-MQQOUW1KL8X0cDWfbwYP+TbVbZm16QmQXJQ+vndPtH/BoO0lOKpVoEDMI7+PskYxH+IiE0tS8xZye0qr1lGzSA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz", + "integrity": "sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz", + "integrity": "sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ==", + "dependencies": { + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.5.tgz", + "integrity": "sha512-B4pzOXj+ONRmuaQTg05b3y/4DuFz3WcCNAXPLb2Q0GT0TrGKGxNKV4jwsXts+StaM0LQczZbOpj8o1DLPDJIiA==", + "dependencies": { + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.5.tgz", + "integrity": "sha512-emtEpoaTMsOs6Tzz+nbmcePl6AKVtS1yC4YNAeMun9U8YCsgadPNxnOPQ8GhHFB2qdx+LZu9LgoC0Lthuu05DQ==", + "dependencies": { + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz", + "integrity": "sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ==", + "dependencies": { + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", + "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz", + "integrity": "sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.5.tgz", + "integrity": "sha512-6CF8g6z1dNYZ/VXok5uYkkBBICHZPiGEl7oDnAx2Mt1hlHVHOSIKWJaXHjQJA5VB43KZnXZDIexMchY4y2PGdA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.5.tgz", + "integrity": "sha512-NbslED1/6M+sXiwwtcAB/nieypGw02Ejf4KtDeMkCEpP6gWFMX1wI9WKYua+4oBneCCEmulOkRpwywypVZzs/g==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.5.tgz", + "integrity": "sha512-Kk3lyDmEslH9DnvCDA1s1kkd3YWQITiBOHngOtDL9Pt6BZjzqb6hiOlb8VfjiiQJ2unmegBqZu0rx5RxJb5vmQ==", + "dependencies": { + "@babel/compat-data": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz", + "integrity": "sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.5.tgz", + "integrity": "sha512-pH8orJahy+hzZje5b8e2QIlBWQvGpelS76C63Z+jhZKsmzfNaPQ+LaW6dcJ9bxTpo1mtXbgHwy765Ro3jftmUg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.6.tgz", + "integrity": "sha512-Vd5HiWml0mDVtcLHIoEU5sw6HOUW/Zk0acLs/SAeuLzkGNOPc9DB4nkUajemhCmTIz3eiaKREZn2hQQqF79YTg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.5.tgz", + "integrity": "sha512-AVkFUBurORBREOmHRKo06FjHYgjrabpdqRSwq6+C7R5iTCZOsM4QbcB27St0a4U6fffyAOqh3s/qEfybAhfivg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz", + "integrity": "sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.5.tgz", + "integrity": "sha512-/9xnaTTJcVoBtSSmrVyhtSvO3kbqS2ODoh2juEU72c3aYonNF0OMGiaz2gjukyKM2wBBYJP38S4JiE0Wfb5VMQ==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz", + "integrity": "sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.22.5.tgz", + "integrity": "sha512-PVk3WPYudRF5z4GKMEYUrLjPl38fJSKNaEOkFuoprioowGuWN6w2RKznuFNSlJx7pzzXXStPUnNSOEO0jL5EVw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.22.5.tgz", + "integrity": "sha512-rog5gZaVbUip5iWDMTYbVM15XQq+RkUKhET/IHR6oizR+JEoN6CAfTTuHcK4vwUyzca30qqHqEpzBOnaRMWYMA==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-jsx": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz", + "integrity": "sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A==", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.22.5.tgz", + "integrity": "sha512-nTh2ogNUtxbiSbxaT4Ds6aXnXEipHweN9YRgOX/oNXdf0cCrGn/+2LozFa3lnPV5D90MkjhgckCPBrsoSc1a7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.22.5.tgz", + "integrity": "sha512-yIiRO6yobeEIaI0RTbIr8iAK9FcBHLtZq0S89ZPjDLQXBA4xvghaKqI0etp/tF3htTM0sazJKKLz9oEiGRtu7w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.22.5.tgz", + "integrity": "sha512-gP4k85wx09q+brArVinTXhWiyzLl9UpmGva0+mWyKxk6JZequ05x3eUcIUE+FyttPKJFRRVtAvQaJ6YF9h1ZpA==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.5.tgz", + "integrity": "sha512-rR7KePOE7gfEtNTh9Qw+iO3Q/e4DEsoQ+hdvM6QUDH7JRJ5qxq5AA52ZzBWbI5i9lfNuvySgOGP8ZN7LAmaiPw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "regenerator-transform": "^0.15.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz", + "integrity": "sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz", + "integrity": "sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz", + "integrity": "sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz", + "integrity": "sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz", + "integrity": "sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz", + "integrity": "sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.9.tgz", + "integrity": "sha512-BnVR1CpKiuD0iobHPaM1iLvcwPYN2uVFAqoLVSpEDKWuOikoCv5HbKLxclhKYUXlWkX86DoZGtqI4XhbOsyrMg==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.9", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-typescript": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.5.tgz", + "integrity": "sha512-biEmVg1IYB/raUO5wT1tgfacCef15Fbzhkx493D3urBI++6hpJ+RFG4SrWMn0NEZLfvilqKf3QDrRVZHo08FYg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz", + "integrity": "sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz", + "integrity": "sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz", + "integrity": "sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.9.tgz", + "integrity": "sha512-wNi5H/Emkhll/bqPjsjQorSykrlfY5OWakd6AulLvMEytpKasMVUpVy8RL4qBIBs5Ac6/5i0/Rv0b/Fg6Eag/g==", + "dependencies": { + "@babel/compat-data": "^7.22.9", + "@babel/helper-compilation-targets": "^7.22.9", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.5", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.5", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.22.5", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.22.5", + "@babel/plugin-syntax-import-attributes": "^7.22.5", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.22.5", + "@babel/plugin-transform-async-generator-functions": "^7.22.7", + "@babel/plugin-transform-async-to-generator": "^7.22.5", + "@babel/plugin-transform-block-scoped-functions": "^7.22.5", + "@babel/plugin-transform-block-scoping": "^7.22.5", + "@babel/plugin-transform-class-properties": "^7.22.5", + "@babel/plugin-transform-class-static-block": "^7.22.5", + "@babel/plugin-transform-classes": "^7.22.6", + "@babel/plugin-transform-computed-properties": "^7.22.5", + "@babel/plugin-transform-destructuring": "^7.22.5", + "@babel/plugin-transform-dotall-regex": "^7.22.5", + "@babel/plugin-transform-duplicate-keys": "^7.22.5", + "@babel/plugin-transform-dynamic-import": "^7.22.5", + "@babel/plugin-transform-exponentiation-operator": "^7.22.5", + "@babel/plugin-transform-export-namespace-from": "^7.22.5", + "@babel/plugin-transform-for-of": "^7.22.5", + "@babel/plugin-transform-function-name": "^7.22.5", + "@babel/plugin-transform-json-strings": "^7.22.5", + "@babel/plugin-transform-literals": "^7.22.5", + "@babel/plugin-transform-logical-assignment-operators": "^7.22.5", + "@babel/plugin-transform-member-expression-literals": "^7.22.5", + "@babel/plugin-transform-modules-amd": "^7.22.5", + "@babel/plugin-transform-modules-commonjs": "^7.22.5", + "@babel/plugin-transform-modules-systemjs": "^7.22.5", + "@babel/plugin-transform-modules-umd": "^7.22.5", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", + "@babel/plugin-transform-new-target": "^7.22.5", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.5", + "@babel/plugin-transform-numeric-separator": "^7.22.5", + "@babel/plugin-transform-object-rest-spread": "^7.22.5", + "@babel/plugin-transform-object-super": "^7.22.5", + "@babel/plugin-transform-optional-catch-binding": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.22.6", + "@babel/plugin-transform-parameters": "^7.22.5", + "@babel/plugin-transform-private-methods": "^7.22.5", + "@babel/plugin-transform-private-property-in-object": "^7.22.5", + "@babel/plugin-transform-property-literals": "^7.22.5", + "@babel/plugin-transform-regenerator": "^7.22.5", + "@babel/plugin-transform-reserved-words": "^7.22.5", + "@babel/plugin-transform-shorthand-properties": "^7.22.5", + "@babel/plugin-transform-spread": "^7.22.5", + "@babel/plugin-transform-sticky-regex": "^7.22.5", + "@babel/plugin-transform-template-literals": "^7.22.5", + "@babel/plugin-transform-typeof-symbol": "^7.22.5", + "@babel/plugin-transform-unicode-escapes": "^7.22.5", + "@babel/plugin-transform-unicode-property-regex": "^7.22.5", + "@babel/plugin-transform-unicode-regex": "^7.22.5", + "@babel/plugin-transform-unicode-sets-regex": "^7.22.5", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.22.5", + "babel-plugin-polyfill-corejs2": "^0.4.4", + "babel-plugin-polyfill-corejs3": "^0.8.2", + "babel-plugin-polyfill-regenerator": "^0.5.1", + "core-js-compat": "^3.31.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6.tgz", + "integrity": "sha512-ID2yj6K/4lKfhuU3+EX4UvNbIt7eACFbHmNUjzA+ep+B5971CknnA/9DEWKbRokfbbtblxxxXFJJrH47UEAMVg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-react": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.22.5.tgz", + "integrity": "sha512-M+Is3WikOpEJHgR385HbuCITPTaPRaNkibTEa9oiofmJvIsrceb4yp9RL9Kb+TE8LznmeyZqpP+Lopwcx59xPQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.5", + "@babel/plugin-transform-react-display-name": "^7.22.5", + "@babel/plugin-transform-react-jsx": "^7.22.5", + "@babel/plugin-transform-react-jsx-development": "^7.22.5", + "@babel/plugin-transform-react-pure-annotations": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.22.5.tgz", + "integrity": "sha512-YbPaal9LxztSGhmndR46FmAbkJ/1fAsw293tSU+I5E5h+cnJ3d4GTwyUgGYmOXJYdGA+uNePle4qbaRzj2NISQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.5", + "@babel/plugin-syntax-jsx": "^7.22.5", + "@babel/plugin-transform-modules-commonjs": "^7.22.5", + "@babel/plugin-transform-typescript": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" + }, + "node_modules/@babel/runtime": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz", + "integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==", + "dependencies": { + "regenerator-runtime": "^0.13.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", + "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "dependencies": { + "@babel/code-frame": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.22.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.8.tgz", + "integrity": "sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw==", + "dependencies": { + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.7", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.22.7", + "@babel/types": "^7.22.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", + "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", + "dependencies": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz", + "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, + "node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + }, + "node_modules/@esbuild/android-arm": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.17.tgz", + "integrity": "sha512-wHsmJG/dnL3OkpAcwbgoBTTMHVi4Uyou3F5mf58ZtmUyIKfcdA7TROav/6tCzET4A3QW2Q2FC+eFneMU+iyOxg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.17.tgz", + "integrity": "sha512-9np+YYdNDed5+Jgr1TdWBsozZ85U1Oa3xW0c7TWqH0y2aGghXtZsuT8nYRbzOMcl0bXZXjOGbksoTtVOlWrRZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.17.tgz", + "integrity": "sha512-O+FeWB/+xya0aLg23hHEM2E3hbfwZzjqumKMSIqcHbNvDa+dza2D0yLuymRBQQnC34CWrsJUXyH2MG5VnLd6uw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.17.tgz", + "integrity": "sha512-M9uJ9VSB1oli2BE/dJs3zVr9kcCBBsE883prage1NWz6pBS++1oNn/7soPNS3+1DGj0FrkSvnED4Bmlu1VAE9g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.17.tgz", + "integrity": "sha512-XDre+J5YeIJDMfp3n0279DFNrGCXlxOuGsWIkRb1NThMZ0BsrWXoTg23Jer7fEXQ9Ye5QjrvXpxnhzl3bHtk0g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.17.tgz", + "integrity": "sha512-cjTzGa3QlNfERa0+ptykyxs5A6FEUQQF0MuilYXYBGdBxD3vxJcKnzDlhDCa1VAJCmAxed6mYhA2KaJIbtiNuQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.17.tgz", + "integrity": "sha512-sOxEvR8d7V7Kw8QqzxWc7bFfnWnGdaFBut1dRUYtu+EIRXefBc/eIsiUiShnW0hM3FmQ5Zf27suDuHsKgZ5QrA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.17.tgz", + "integrity": "sha512-2d3Lw6wkwgSLC2fIvXKoMNGVaeY8qdN0IC3rfuVxJp89CRfA3e3VqWifGDfuakPmp90+ZirmTfye1n4ncjv2lg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.17.tgz", + "integrity": "sha512-c9w3tE7qA3CYWjT+M3BMbwMt+0JYOp3vCMKgVBrCl1nwjAlOMYzEo+gG7QaZ9AtqZFj5MbUc885wuBBmu6aADQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.17.tgz", + "integrity": "sha512-1DS9F966pn5pPnqXYz16dQqWIB0dmDfAQZd6jSSpiT9eX1NzKh07J6VKR3AoXXXEk6CqZMojiVDSZi1SlmKVdg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.17.tgz", + "integrity": "sha512-EvLsxCk6ZF0fpCB6w6eOI2Fc8KW5N6sHlIovNe8uOFObL2O+Mr0bflPHyHwLT6rwMg9r77WOAWb2FqCQrVnwFg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.17.tgz", + "integrity": "sha512-e0bIdHA5p6l+lwqTE36NAW5hHtw2tNRmHlGBygZC14QObsA3bD4C6sXLJjvnDIjSKhW1/0S3eDy+QmX/uZWEYQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.17.tgz", + "integrity": "sha512-BAAilJ0M5O2uMxHYGjFKn4nJKF6fNCdP1E0o5t5fvMYYzeIqy2JdAP88Az5LHt9qBoUa4tDaRpfWt21ep5/WqQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.17.tgz", + "integrity": "sha512-Wh/HW2MPnC3b8BqRSIme/9Zhab36PPH+3zam5pqGRH4pE+4xTrVLx2+XdGp6fVS3L2x+DrsIcsbMleex8fbE6g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.17.tgz", + "integrity": "sha512-j/34jAl3ul3PNcK3pfI0NSlBANduT2UO5kZ7FCaK33XFv3chDhICLY8wJJWIhiQ+YNdQ9dxqQctRg2bvrMlYgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.17.tgz", + "integrity": "sha512-QM50vJ/y+8I60qEmFxMoxIx4de03pGo2HwxdBeFd4nMh364X6TIBZ6VQ5UQmPbQWUVWHWws5MmJXlHAXvJEmpQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.17.tgz", + "integrity": "sha512-/jGlhWR7Sj9JPZHzXyyMZ1RFMkNPjC6QIAan0sDOtIo2TYk3tZn5UDrkE0XgsTQCxWTTOcMPf9p6Rh2hXtl5TQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.17.tgz", + "integrity": "sha512-rSEeYaGgyGGf4qZM2NonMhMOP/5EHp4u9ehFiBrg7stH6BYEEjlkVREuDEcQ0LfIl53OXLxNbfuIj7mr5m29TA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.17.tgz", + "integrity": "sha512-Y7ZBbkLqlSgn4+zot4KUNYst0bFoO68tRgI6mY2FIM+b7ZbyNVtNbDP5y8qlu4/knZZ73fgJDlXID+ohY5zt5g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.17.tgz", + "integrity": "sha512-bwPmTJsEQcbZk26oYpc4c/8PvTY3J5/QK8jM19DVlEsAB41M39aWovWoHtNm78sd6ip6prilxeHosPADXtEJFw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.17.tgz", + "integrity": "sha512-H/XaPtPKli2MhW+3CQueo6Ni3Avggi6hP/YvgkEe1aSaxw+AeO8MFjq8DlgfTd9Iz4Yih3QCZI6YLMoyccnPRg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.17.tgz", + "integrity": "sha512-fGEb8f2BSA3CW7riJVurug65ACLuQAzKq0SSqkY2b2yHHH0MzDfbLyKIGzHwOI/gkHcxM/leuSW6D5w/LMNitA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.2.tgz", + "integrity": "sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.1.tgz", + "integrity": "sha512-9t7ZA7NGGK8ckelF0PQCfcxIUzs1Md5rrO6U/c+FIQNanea5UZC0wqKXH4vHBccmu4ZJgZ2idtPeW7+Q2npOEA==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.46.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.46.0.tgz", + "integrity": "sha512-a8TLtmPi8xzPkCbp/OGFUo5yhRkHM2Ko9kOWP4znJr0WAhWyThaw3PnwX4vOTWOAMsV2uRt32PPDcEz63esSaA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", + "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + }, + "node_modules/@nicolo-ribaudo/chokidar-2": { + "version": "2.1.8-no-fsevents.3", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz", + "integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==", + "optional": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@remix-run/router": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.7.2.tgz", + "integrity": "sha512-7Lcn7IqGMV+vizMPoEl5F0XDshcdDYtMI6uJLQdQz5CfZAwy3vvGKYSUk789qndt5dEC4HfSjviSYlSoHGL2+A==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "dev": true, + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "dev": true + }, + "node_modules/@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", + "dev": true + }, + "node_modules/@types/react": { + "version": "18.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.18.tgz", + "integrity": "sha512-da4NTSeBv/P34xoZPhtcLkmZuJ+oYaCxHmyHzwaDQo9RQPBeXV+06gEk2FpqEcsX9XrnNLvRpVh6bdavDSjtiQ==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.2.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.7.tgz", + "integrity": "sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.3", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", + "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==", + "dev": true + }, + "node_modules/@types/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "dev": true + }, + "node_modules/@types/styled-components": { + "version": "5.1.26", + "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.26.tgz", + "integrity": "sha512-KuKJ9Z6xb93uJiIyxo/+ksS7yLjS1KzG6iv5i78dhVg/X3u5t1H7juRWqVmodIdz6wGVaIApo1u01kmFRdJHVw==", + "dev": true, + "dependencies": { + "@types/hoist-non-react-statics": "*", + "@types/react": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-n4sx2bqL0mW1tvDf/loQ+aMX7GQD3lc3fkCMC55VFNDu/vBOabO+LTIeXKM14xK0ppk5TUGcWRjiSpIlUpghKw==" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.2.1.tgz", + "integrity": "sha512-iZVM/ALid9kO0+I81pnp1xmYiFyqibAHzrqX4q5YvvVEyJqY+e6rfTXSCsc2jUxGNqJqTfFSSij/NFkZBiBzLw==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.2.1", + "@typescript-eslint/type-utils": "6.2.1", + "@typescript-eslint/utils": "6.2.1", + "@typescript-eslint/visitor-keys": "6.2.1", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.2.1.tgz", + "integrity": "sha512-Ld+uL1kYFU8e6btqBFpsHkwQ35rw30IWpdQxgOqOh4NfxSDH6uCkah1ks8R/RgQqI5hHPXMaLy9fbFseIe+dIg==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "6.2.1", + "@typescript-eslint/types": "6.2.1", + "@typescript-eslint/typescript-estree": "6.2.1", + "@typescript-eslint/visitor-keys": "6.2.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.2.1.tgz", + "integrity": "sha512-UCqBF9WFqv64xNsIEPfBtenbfodPXsJ3nPAr55mGPkQIkiQvgoWNo+astj9ZUfJfVKiYgAZDMnM6dIpsxUMp3Q==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.2.1", + "@typescript-eslint/visitor-keys": "6.2.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.2.1.tgz", + "integrity": "sha512-fTfCgomBMIgu2Dh2Or3gMYgoNAnQm3RLtRp+jP7A8fY+LJ2+9PNpi5p6QB5C4RSP+U3cjI0vDlI3mspAkpPVbQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "6.2.1", + "@typescript-eslint/utils": "6.2.1", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.2.1.tgz", + "integrity": "sha512-528bGcoelrpw+sETlyM91k51Arl2ajbNT9L4JwoXE2dvRe1yd8Q64E4OL7vHYw31mlnVsf+BeeLyAZUEQtqahQ==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.2.1.tgz", + "integrity": "sha512-G+UJeQx9AKBHRQBpmvr8T/3K5bJa485eu+4tQBxFq0KoT22+jJyzo1B50JDT9QdC1DEmWQfdKsa8ybiNWYsi0Q==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.2.1", + "@typescript-eslint/visitor-keys": "6.2.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.2.1.tgz", + "integrity": "sha512-eBIXQeupYmxVB6S7x+B9SdBeB6qIdXKjgQBge2J+Ouv8h9Cxm5dHf/gfAZA6dkMaag+03HdbVInuXMmqFB/lKQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.2.1", + "@typescript-eslint/types": "6.2.1", + "@typescript-eslint/typescript-estree": "6.2.1", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.2.1.tgz", + "integrity": "sha512-iTN6w3k2JEZ7cyVdZJTVJx2Lv7t6zFA8DCrJEHD2mwfc16AEvvBWVhbFh34XyG2NORCd0viIgQY1+u7kPI0WpA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.2.1", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.0.4.tgz", + "integrity": "sha512-7wU921ABnNYkETiMaZy7XqpueMnpu5VxvVps13MjmCo+utBdD79sZzrApHawHtVX66cCJQQTXFcjH0y9dSUK8g==", + "dev": true, + "dependencies": { + "@babel/core": "^7.22.9", + "@babel/plugin-transform-react-jsx-self": "^7.22.5", + "@babel/plugin-transform-react-jsx-source": "^7.22.5", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0" + } + }, + "node_modules/acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "optional": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.5.tgz", + "integrity": "sha512-19hwUH5FKl49JEsvyTcoHakh6BE0wgXLLptIyKZ3PijHc/Ci521wygORCUCCred+E/twuqRyAkE02BAWPmsHOg==", + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.4.2", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.3.tgz", + "integrity": "sha512-z41XaniZL26WLrvjy7soabMXrfPWARN25PZoriDEiLMxAp50AUW3t35BGQUMg5xK3UrpVTtagIDklxYa+MhiNA==", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.4.2", + "core-js-compat": "^3.31.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.2.tgz", + "integrity": "sha512-tAlOptU0Xj34V1Y2PNTL4Y0FOJMDB6bZmoW39FeCQIhigGLkqu3Fj6uiXpxIf6Ij274ENdYx64y6Au+ZKlb1IA==", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.4.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "devOptional": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.21.10", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", + "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001517", + "electron-to-chromium": "^1.4.477", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.11" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001518", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001518.tgz", + "integrity": "sha512-rup09/e3I0BKjncL+FesTayKtPrdwKhUufQFd3riFw1hHg8JmIFoInYfB102cFcY/pPgGmdyl/iy+jgiDi2vdA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "optional": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "optional": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-truncate": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", + "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "dev": true, + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "node_modules/core-js-compat": { + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.32.0.tgz", + "integrity": "sha512-7a9a3D1k4UCVKnLhrgALyFcP7YCsLOQIxPd0dKjf/6GuPcgyiGP70ewWdCGrSK7evyhymi0qO4EqCmSJofDeYw==", + "dependencies": { + "browserslist": "^4.21.9" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, + "node_modules/csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.4.478", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.478.tgz", + "integrity": "sha512-qjTA8djMXd+ruoODDFGnRCRBpID+AAfYWCyGtYTNhsuwxI19s8q19gbjKTwRS5z/LyVf5wICaIiPQGLekmbJbA==" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.17.tgz", + "integrity": "sha512-1GJtYnUxsJreHYA0Y+iQz2UEykonY66HNWOb0yXYZi9/kNrORUEHVg87eQsCtqh59PEJ5YVZJO98JHznMJSWjg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.17", + "@esbuild/android-arm64": "0.18.17", + "@esbuild/android-x64": "0.18.17", + "@esbuild/darwin-arm64": "0.18.17", + "@esbuild/darwin-x64": "0.18.17", + "@esbuild/freebsd-arm64": "0.18.17", + "@esbuild/freebsd-x64": "0.18.17", + "@esbuild/linux-arm": "0.18.17", + "@esbuild/linux-arm64": "0.18.17", + "@esbuild/linux-ia32": "0.18.17", + "@esbuild/linux-loong64": "0.18.17", + "@esbuild/linux-mips64el": "0.18.17", + "@esbuild/linux-ppc64": "0.18.17", + "@esbuild/linux-riscv64": "0.18.17", + "@esbuild/linux-s390x": "0.18.17", + "@esbuild/linux-x64": "0.18.17", + "@esbuild/netbsd-x64": "0.18.17", + "@esbuild/openbsd-x64": "0.18.17", + "@esbuild/sunos-x64": "0.18.17", + "@esbuild/win32-arm64": "0.18.17", + "@esbuild/win32-ia32": "0.18.17", + "@esbuild/win32-x64": "0.18.17" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "8.46.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.46.0.tgz", + "integrity": "sha512-cIO74PvbW0qU8e0mIvk5IV3ToWdCq5FYG6gWPHHkx6gNdjlbAYvtfHmlCMXxjcoVaIdwy/IAt3+mDkZkfvb2Dg==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.1", + "@eslint/js": "^8.46.0", + "@humanwhocodes/config-array": "^0.11.10", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.2", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.9.0.tgz", + "integrity": "sha512-+sbni7NfVXnOpnRadUA8S28AUlsZt9GjgFvABIRL9Hkn8KqNzOp+7Lw4QWtrwn20KzU3wqu1QoOj2m+7rKRqkA==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.3.tgz", + "integrity": "sha512-Hh0wv8bUNY877+sI0BlCUlsS0TYYQqvzEwJsJJPM2WF4RnTStSnSR3zdJYa2nPOJgg3UghXi54lVyMSmpCalzA==", + "dev": true, + "peerDependencies": { + "eslint": ">=7" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.2.tgz", + "integrity": "sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", + "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^4.3.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": "^14.18.0 || ^16.14.0 || >=18.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "devOptional": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/fs-readdir-recursive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", + "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dev": true, + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/human-signals": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", + "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", + "dev": true, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/husky": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", + "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==", + "dev": true, + "bin": { + "husky": "lib/bin.js" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "optional": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", + "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "devOptional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "devOptional": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "devOptional": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/lint-staged": { + "version": "13.2.3", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-13.2.3.tgz", + "integrity": "sha512-zVVEXLuQIhr1Y7R7YAWx4TZLdvuzk7DnmrsTNL0fax6Z3jrpFcas+vKbzxhhvp6TA55m1SQuWkpzI1qbfDZbAg==", + "dev": true, + "dependencies": { + "chalk": "5.2.0", + "cli-truncate": "^3.1.0", + "commander": "^10.0.0", + "debug": "^4.3.4", + "execa": "^7.0.0", + "lilconfig": "2.1.0", + "listr2": "^5.0.7", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-inspect": "^1.12.3", + "pidtree": "^0.6.0", + "string-argv": "^0.3.1", + "yaml": "^2.2.2" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/chalk": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", + "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/listr2": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-5.0.8.tgz", + "integrity": "sha512-mC73LitKHj9w6v30nLNGPetZIlfpUniNSsxxrbaPcWOjDb92SHPzJPi/t+v1YC/lxKz/AJ9egOjww0qUuFxBpA==", + "dev": true, + "dependencies": { + "cli-truncate": "^2.1.0", + "colorette": "^2.0.19", + "log-update": "^4.0.0", + "p-map": "^4.0.0", + "rfdc": "^1.3.0", + "rxjs": "^7.8.0", + "through": "^2.3.8", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "enquirer": ">= 2.3.0 < 3" + }, + "peerDependenciesMeta": { + "enquirer": { + "optional": true + } + } + }, + "node_modules/listr2/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/listr2/node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/listr2/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/listr2/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/listr2/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/listr2/node_modules/slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/listr2/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/log-update": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-update/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/log-update/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "devOptional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "devOptional": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "engines": { + "node": ">=6" + } + }, + "node_modules/postcss": { + "version": "8.4.27", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.27.tgz", + "integrity": "sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0.tgz", + "integrity": "sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true + }, + "node_modules/react-refresh": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", + "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.14.2.tgz", + "integrity": "sha512-09Zss2dE2z+T1D03IheqAFtK4UzQyX8nFPWx6jkwdYzGLXd5ie06A6ezS2fO6zJfEb/SpG6UocN2O1hfD+2urQ==", + "dependencies": { + "@remix-run/router": "1.7.2" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.14.2.tgz", + "integrity": "sha512-5pWX0jdKR48XFZBuJqHosX3AAHjRAzygouMTyimnBPOLdY3WjzUSKhus2FVMihUFWzeLebDgr4r8UeQFAct7Bg==", + "dependencies": { + "@remix-run/router": "1.7.2", + "react-router": "6.14.2" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "optional": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", + "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, + "node_modules/regenerator-transform": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", + "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dependencies": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dependencies": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/restore-cursor/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/restore-cursor/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.27.0.tgz", + "integrity": "sha512-aOltLCrYZ0FhJDm7fCqwTjIUEVjWjcydKBV/Zeid6Mn8BWgDCUBBWT5beM5ieForYNo/1ZHuGJdka26kvQ3Gzg==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/styled-components": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.0.5.tgz", + "integrity": "sha512-308zi5o7LrA9cVaP4nPD0TaUpOjGPePkAUFb/OGB0xRI3I9ozpW5UyASvRVi9wJcYASG+Y3mLDLDUZC7nqzimw==", + "dependencies": { + "@babel/cli": "^7.21.0", + "@babel/core": "^7.21.0", + "@babel/helper-module-imports": "^7.18.6", + "@babel/plugin-external-helpers": "^7.18.6", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.20.7", + "@babel/preset-env": "^7.20.2", + "@babel/preset-react": "^7.18.6", + "@babel/preset-typescript": "^7.21.0", + "@babel/traverse": "^7.21.2", + "@emotion/is-prop-valid": "^1.2.1", + "@emotion/unitless": "^0.8.0", + "@types/stylis": "^4.0.2", + "css-to-react-native": "^3.2.0", + "csstype": "^3.1.2", + "postcss": "^8.4.23", + "shallowequal": "^1.1.0", + "stylis": "^4.3.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "babel-plugin-styled-components": ">= 2", + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "babel-plugin-styled-components": { + "optional": true + } + } + }, + "node_modules/stylis": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.0.tgz", + "integrity": "sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ==" + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "devOptional": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.1.tgz", + "integrity": "sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==", + "dev": true, + "engines": { + "node": ">=16.13.0" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/tslib": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", + "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "engines": { + "node": ">=4" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "4.4.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.7.tgz", + "integrity": "sha512-6pYf9QJ1mHylfVh39HpuSfMPojPSKVxZvnclX1K1FyZ1PXDOcLBibdq5t1qxJSnL63ca8Wf4zts6mD8u8oc9Fw==", + "dev": true, + "dependencies": { + "esbuild": "^0.18.10", + "postcss": "^8.4.26", + "rollup": "^3.25.2" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, + "node_modules/yaml": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", + "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..7db31a8 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,44 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview --port 3000", + "postinstall": "cd .. && husky install frontend/.husky && chmod ug+x frontend/.husky/*", + "format": "prettier --write .", + "lint-staged": "lint-staged" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.14.2", + "styled-components": "^6.0.5" + }, + "devDependencies": { + "@types/react": "^18.2.15", + "@types/react-dom": "^18.2.7", + "@typescript-eslint/eslint-plugin": "^6.0.0", + "@typescript-eslint/parser": "^6.0.0", + "@vitejs/plugin-react": "^4.0.3", + "@types/styled-components": "^5.1.26", + "eslint": "^8.45.0", + "eslint-config-prettier": "^8.9.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.3", + "husky": "^8.0.3", + "lint-staged": "^13.2.3", + "prettier": "^3.0.0", + "typescript": "^5.0.2", + "vite": "^4.4.5" + }, + "lint-staged": { + "*.{js,jsx,ts,tsx}": [ + "prettier --write .", + "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0" + ] + } +} diff --git a/frontend/public/fonts/HyundaiSansHead-Bold.otf b/frontend/public/fonts/HyundaiSansHead-Bold.otf new file mode 100755 index 0000000..2f08544 Binary files /dev/null and b/frontend/public/fonts/HyundaiSansHead-Bold.otf differ diff --git a/frontend/public/fonts/HyundaiSansHead-Light.otf b/frontend/public/fonts/HyundaiSansHead-Light.otf new file mode 100755 index 0000000..80686a9 Binary files /dev/null and b/frontend/public/fonts/HyundaiSansHead-Light.otf differ diff --git a/frontend/public/fonts/HyundaiSansHead-Medium.otf b/frontend/public/fonts/HyundaiSansHead-Medium.otf new file mode 100755 index 0000000..7b602df Binary files /dev/null and b/frontend/public/fonts/HyundaiSansHead-Medium.otf differ diff --git a/frontend/public/fonts/HyundaiSansHead-Regular.otf b/frontend/public/fonts/HyundaiSansHead-Regular.otf new file mode 100755 index 0000000..ea97ff6 Binary files /dev/null and b/frontend/public/fonts/HyundaiSansHead-Regular.otf differ diff --git a/frontend/public/fonts/HyundaiSansHeadKRBold.ttf b/frontend/public/fonts/HyundaiSansHeadKRBold.ttf new file mode 100755 index 0000000..bfb0bb7 Binary files /dev/null and b/frontend/public/fonts/HyundaiSansHeadKRBold.ttf differ diff --git a/frontend/public/fonts/HyundaiSansHeadKRLight.ttf b/frontend/public/fonts/HyundaiSansHeadKRLight.ttf new file mode 100755 index 0000000..8137f4d Binary files /dev/null and b/frontend/public/fonts/HyundaiSansHeadKRLight.ttf differ diff --git a/frontend/public/fonts/HyundaiSansHeadKRMedium.ttf b/frontend/public/fonts/HyundaiSansHeadKRMedium.ttf new file mode 100755 index 0000000..33cff2f Binary files /dev/null and b/frontend/public/fonts/HyundaiSansHeadKRMedium.ttf differ diff --git a/frontend/public/fonts/HyundaiSansHeadKROTFBold.otf b/frontend/public/fonts/HyundaiSansHeadKROTFBold.otf new file mode 100755 index 0000000..6a27600 Binary files /dev/null and b/frontend/public/fonts/HyundaiSansHeadKROTFBold.otf differ diff --git a/frontend/public/fonts/HyundaiSansHeadKROTFLight.otf b/frontend/public/fonts/HyundaiSansHeadKROTFLight.otf new file mode 100755 index 0000000..2985a69 Binary files /dev/null and b/frontend/public/fonts/HyundaiSansHeadKROTFLight.otf differ diff --git a/frontend/public/fonts/HyundaiSansHeadKROTFMedium.otf b/frontend/public/fonts/HyundaiSansHeadKROTFMedium.otf new file mode 100755 index 0000000..7048fad Binary files /dev/null and b/frontend/public/fonts/HyundaiSansHeadKROTFMedium.otf differ diff --git a/frontend/public/fonts/HyundaiSansHeadKROTFRegular.otf b/frontend/public/fonts/HyundaiSansHeadKROTFRegular.otf new file mode 100755 index 0000000..09cc229 Binary files /dev/null and b/frontend/public/fonts/HyundaiSansHeadKROTFRegular.otf differ diff --git a/frontend/public/fonts/HyundaiSansHeadKRRegular.ttf b/frontend/public/fonts/HyundaiSansHeadKRRegular.ttf new file mode 100755 index 0000000..59d568f Binary files /dev/null and b/frontend/public/fonts/HyundaiSansHeadKRRegular.ttf differ diff --git a/frontend/public/fonts/HyundaiSansHeadOffice-Bold.ttf b/frontend/public/fonts/HyundaiSansHeadOffice-Bold.ttf new file mode 100755 index 0000000..0783d9e Binary files /dev/null and b/frontend/public/fonts/HyundaiSansHeadOffice-Bold.ttf differ diff --git a/frontend/public/fonts/HyundaiSansHeadOffice-Light.ttf b/frontend/public/fonts/HyundaiSansHeadOffice-Light.ttf new file mode 100755 index 0000000..ca19301 Binary files /dev/null and b/frontend/public/fonts/HyundaiSansHeadOffice-Light.ttf differ diff --git a/frontend/public/fonts/HyundaiSansHeadOffice-Medium.ttf b/frontend/public/fonts/HyundaiSansHeadOffice-Medium.ttf new file mode 100755 index 0000000..b91cd11 Binary files /dev/null and b/frontend/public/fonts/HyundaiSansHeadOffice-Medium.ttf differ diff --git a/frontend/public/fonts/HyundaiSansHeadOffice-Regular.ttf b/frontend/public/fonts/HyundaiSansHeadOffice-Regular.ttf new file mode 100755 index 0000000..7a282ca Binary files /dev/null and b/frontend/public/fonts/HyundaiSansHeadOffice-Regular.ttf differ diff --git a/frontend/public/fonts/HyundaiSansText-Bold.otf b/frontend/public/fonts/HyundaiSansText-Bold.otf new file mode 100755 index 0000000..b0ffc9a Binary files /dev/null and b/frontend/public/fonts/HyundaiSansText-Bold.otf differ diff --git a/frontend/public/fonts/HyundaiSansText-BoldItalic.otf b/frontend/public/fonts/HyundaiSansText-BoldItalic.otf new file mode 100755 index 0000000..2633f76 Binary files /dev/null and b/frontend/public/fonts/HyundaiSansText-BoldItalic.otf differ diff --git a/frontend/public/fonts/HyundaiSansText-Italic.otf b/frontend/public/fonts/HyundaiSansText-Italic.otf new file mode 100755 index 0000000..aa6b433 Binary files /dev/null and b/frontend/public/fonts/HyundaiSansText-Italic.otf differ diff --git a/frontend/public/fonts/HyundaiSansText-Medium.otf b/frontend/public/fonts/HyundaiSansText-Medium.otf new file mode 100755 index 0000000..94106c5 Binary files /dev/null and b/frontend/public/fonts/HyundaiSansText-Medium.otf differ diff --git a/frontend/public/fonts/HyundaiSansText-MediumItalic.otf b/frontend/public/fonts/HyundaiSansText-MediumItalic.otf new file mode 100755 index 0000000..34629a7 Binary files /dev/null and b/frontend/public/fonts/HyundaiSansText-MediumItalic.otf differ diff --git a/frontend/public/fonts/HyundaiSansText-Regular.otf b/frontend/public/fonts/HyundaiSansText-Regular.otf new file mode 100755 index 0000000..faef7d3 Binary files /dev/null and b/frontend/public/fonts/HyundaiSansText-Regular.otf differ diff --git a/frontend/public/fonts/HyundaiSansTextKRBold.ttf b/frontend/public/fonts/HyundaiSansTextKRBold.ttf new file mode 100755 index 0000000..1912d9d Binary files /dev/null and b/frontend/public/fonts/HyundaiSansTextKRBold.ttf differ diff --git a/frontend/public/fonts/HyundaiSansTextKRMedium.ttf b/frontend/public/fonts/HyundaiSansTextKRMedium.ttf new file mode 100755 index 0000000..71831a6 Binary files /dev/null and b/frontend/public/fonts/HyundaiSansTextKRMedium.ttf differ diff --git a/frontend/public/fonts/HyundaiSansTextKROTFBold.otf b/frontend/public/fonts/HyundaiSansTextKROTFBold.otf new file mode 100755 index 0000000..7ca0d93 Binary files /dev/null and b/frontend/public/fonts/HyundaiSansTextKROTFBold.otf differ diff --git a/frontend/public/fonts/HyundaiSansTextKROTFMedium.otf b/frontend/public/fonts/HyundaiSansTextKROTFMedium.otf new file mode 100755 index 0000000..85241e2 Binary files /dev/null and b/frontend/public/fonts/HyundaiSansTextKROTFMedium.otf differ diff --git a/frontend/public/fonts/HyundaiSansTextKROTFRegular.otf b/frontend/public/fonts/HyundaiSansTextKROTFRegular.otf new file mode 100755 index 0000000..a9615be Binary files /dev/null and b/frontend/public/fonts/HyundaiSansTextKROTFRegular.otf differ diff --git a/frontend/public/fonts/HyundaiSansTextKRRegular.ttf b/frontend/public/fonts/HyundaiSansTextKRRegular.ttf new file mode 100755 index 0000000..df77b0b Binary files /dev/null and b/frontend/public/fonts/HyundaiSansTextKRRegular.ttf differ diff --git a/frontend/public/fonts/HyundaiSansTextOffice-Bold.ttf b/frontend/public/fonts/HyundaiSansTextOffice-Bold.ttf new file mode 100755 index 0000000..bdd7e9a Binary files /dev/null and b/frontend/public/fonts/HyundaiSansTextOffice-Bold.ttf differ diff --git a/frontend/public/fonts/HyundaiSansTextOffice-BoldItalic.ttf b/frontend/public/fonts/HyundaiSansTextOffice-BoldItalic.ttf new file mode 100755 index 0000000..6e4e90d Binary files /dev/null and b/frontend/public/fonts/HyundaiSansTextOffice-BoldItalic.ttf differ diff --git a/frontend/public/fonts/HyundaiSansTextOffice-Italic.ttf b/frontend/public/fonts/HyundaiSansTextOffice-Italic.ttf new file mode 100755 index 0000000..eb45014 Binary files /dev/null and b/frontend/public/fonts/HyundaiSansTextOffice-Italic.ttf differ diff --git a/frontend/public/fonts/HyundaiSansTextOffice-Medium.ttf b/frontend/public/fonts/HyundaiSansTextOffice-Medium.ttf new file mode 100755 index 0000000..3bfe2e0 Binary files /dev/null and b/frontend/public/fonts/HyundaiSansTextOffice-Medium.ttf differ diff --git a/frontend/public/fonts/HyundaiSansTextOffice-MediumItalic.ttf b/frontend/public/fonts/HyundaiSansTextOffice-MediumItalic.ttf new file mode 100755 index 0000000..1af78b9 Binary files /dev/null and b/frontend/public/fonts/HyundaiSansTextOffice-MediumItalic.ttf differ diff --git a/frontend/public/fonts/HyundaiSansTextOffice-Regular.ttf b/frontend/public/fonts/HyundaiSansTextOffice-Regular.ttf new file mode 100755 index 0000000..55b3315 Binary files /dev/null and b/frontend/public/fonts/HyundaiSansTextOffice-Regular.ttf differ diff --git a/frontend/public/images/car.png b/frontend/public/images/car.png new file mode 100644 index 0000000..b79bce3 Binary files /dev/null and b/frontend/public/images/car.png differ diff --git a/frontend/public/images/car360/img0.png b/frontend/public/images/car360/img0.png new file mode 100644 index 0000000..5b05f82 Binary files /dev/null and b/frontend/public/images/car360/img0.png differ diff --git a/frontend/public/images/car360/img1.png b/frontend/public/images/car360/img1.png new file mode 100644 index 0000000..e1ca7fe Binary files /dev/null and b/frontend/public/images/car360/img1.png differ diff --git a/frontend/public/images/car360/img10.png b/frontend/public/images/car360/img10.png new file mode 100644 index 0000000..c9e0ca3 Binary files /dev/null and b/frontend/public/images/car360/img10.png differ diff --git a/frontend/public/images/car360/img11.png b/frontend/public/images/car360/img11.png new file mode 100644 index 0000000..378e9c0 Binary files /dev/null and b/frontend/public/images/car360/img11.png differ diff --git a/frontend/public/images/car360/img12.png b/frontend/public/images/car360/img12.png new file mode 100644 index 0000000..84882b2 Binary files /dev/null and b/frontend/public/images/car360/img12.png differ diff --git a/frontend/public/images/car360/img13.png b/frontend/public/images/car360/img13.png new file mode 100644 index 0000000..76bb832 Binary files /dev/null and b/frontend/public/images/car360/img13.png differ diff --git a/frontend/public/images/car360/img14.png b/frontend/public/images/car360/img14.png new file mode 100644 index 0000000..86c0c8f Binary files /dev/null and b/frontend/public/images/car360/img14.png differ diff --git a/frontend/public/images/car360/img15.png b/frontend/public/images/car360/img15.png new file mode 100644 index 0000000..4c2dc12 Binary files /dev/null and b/frontend/public/images/car360/img15.png differ diff --git a/frontend/public/images/car360/img16.png b/frontend/public/images/car360/img16.png new file mode 100644 index 0000000..4039758 Binary files /dev/null and b/frontend/public/images/car360/img16.png differ diff --git a/frontend/public/images/car360/img17.png b/frontend/public/images/car360/img17.png new file mode 100644 index 0000000..033d5a0 Binary files /dev/null and b/frontend/public/images/car360/img17.png differ diff --git a/frontend/public/images/car360/img18.png b/frontend/public/images/car360/img18.png new file mode 100644 index 0000000..5d2727c Binary files /dev/null and b/frontend/public/images/car360/img18.png differ diff --git a/frontend/public/images/car360/img19.png b/frontend/public/images/car360/img19.png new file mode 100644 index 0000000..7ad320d Binary files /dev/null and b/frontend/public/images/car360/img19.png differ diff --git a/frontend/public/images/car360/img2.png b/frontend/public/images/car360/img2.png new file mode 100644 index 0000000..e976ed4 Binary files /dev/null and b/frontend/public/images/car360/img2.png differ diff --git a/frontend/public/images/car360/img20.png b/frontend/public/images/car360/img20.png new file mode 100644 index 0000000..4582f1f Binary files /dev/null and b/frontend/public/images/car360/img20.png differ diff --git a/frontend/public/images/car360/img21.png b/frontend/public/images/car360/img21.png new file mode 100644 index 0000000..1375fd3 Binary files /dev/null and b/frontend/public/images/car360/img21.png differ diff --git a/frontend/public/images/car360/img22.png b/frontend/public/images/car360/img22.png new file mode 100644 index 0000000..732cf08 Binary files /dev/null and b/frontend/public/images/car360/img22.png differ diff --git a/frontend/public/images/car360/img23.png b/frontend/public/images/car360/img23.png new file mode 100644 index 0000000..3c2698d Binary files /dev/null and b/frontend/public/images/car360/img23.png differ diff --git a/frontend/public/images/car360/img24.png b/frontend/public/images/car360/img24.png new file mode 100644 index 0000000..dac8c6f Binary files /dev/null and b/frontend/public/images/car360/img24.png differ diff --git a/frontend/public/images/car360/img25.png b/frontend/public/images/car360/img25.png new file mode 100644 index 0000000..d527d6b Binary files /dev/null and b/frontend/public/images/car360/img25.png differ diff --git a/frontend/public/images/car360/img26.png b/frontend/public/images/car360/img26.png new file mode 100644 index 0000000..6d7a284 Binary files /dev/null and b/frontend/public/images/car360/img26.png differ diff --git a/frontend/public/images/car360/img27.png b/frontend/public/images/car360/img27.png new file mode 100644 index 0000000..4860fb4 Binary files /dev/null and b/frontend/public/images/car360/img27.png differ diff --git a/frontend/public/images/car360/img28.png b/frontend/public/images/car360/img28.png new file mode 100644 index 0000000..d5c9b41 Binary files /dev/null and b/frontend/public/images/car360/img28.png differ diff --git a/frontend/public/images/car360/img29.png b/frontend/public/images/car360/img29.png new file mode 100644 index 0000000..1630077 Binary files /dev/null and b/frontend/public/images/car360/img29.png differ diff --git a/frontend/public/images/car360/img3.png b/frontend/public/images/car360/img3.png new file mode 100644 index 0000000..a457c56 Binary files /dev/null and b/frontend/public/images/car360/img3.png differ diff --git a/frontend/public/images/car360/img30.png b/frontend/public/images/car360/img30.png new file mode 100644 index 0000000..4b300c1 Binary files /dev/null and b/frontend/public/images/car360/img30.png differ diff --git a/frontend/public/images/car360/img31.png b/frontend/public/images/car360/img31.png new file mode 100644 index 0000000..1858338 Binary files /dev/null and b/frontend/public/images/car360/img31.png differ diff --git a/frontend/public/images/car360/img32.png b/frontend/public/images/car360/img32.png new file mode 100644 index 0000000..467bf99 Binary files /dev/null and b/frontend/public/images/car360/img32.png differ diff --git a/frontend/public/images/car360/img33.png b/frontend/public/images/car360/img33.png new file mode 100644 index 0000000..1e1e796 Binary files /dev/null and b/frontend/public/images/car360/img33.png differ diff --git a/frontend/public/images/car360/img34.png b/frontend/public/images/car360/img34.png new file mode 100644 index 0000000..1ce3b1f Binary files /dev/null and b/frontend/public/images/car360/img34.png differ diff --git a/frontend/public/images/car360/img35.png b/frontend/public/images/car360/img35.png new file mode 100644 index 0000000..1a1e847 Binary files /dev/null and b/frontend/public/images/car360/img35.png differ diff --git a/frontend/public/images/car360/img36.png b/frontend/public/images/car360/img36.png new file mode 100644 index 0000000..4374786 Binary files /dev/null and b/frontend/public/images/car360/img36.png differ diff --git a/frontend/public/images/car360/img37.png b/frontend/public/images/car360/img37.png new file mode 100644 index 0000000..de3e8db Binary files /dev/null and b/frontend/public/images/car360/img37.png differ diff --git a/frontend/public/images/car360/img38.png b/frontend/public/images/car360/img38.png new file mode 100644 index 0000000..3b5e4d5 Binary files /dev/null and b/frontend/public/images/car360/img38.png differ diff --git a/frontend/public/images/car360/img39.png b/frontend/public/images/car360/img39.png new file mode 100644 index 0000000..e39bf4b Binary files /dev/null and b/frontend/public/images/car360/img39.png differ diff --git a/frontend/public/images/car360/img4.png b/frontend/public/images/car360/img4.png new file mode 100644 index 0000000..93c28bb Binary files /dev/null and b/frontend/public/images/car360/img4.png differ diff --git a/frontend/public/images/car360/img40.png b/frontend/public/images/car360/img40.png new file mode 100644 index 0000000..6375640 Binary files /dev/null and b/frontend/public/images/car360/img40.png differ diff --git a/frontend/public/images/car360/img41.png b/frontend/public/images/car360/img41.png new file mode 100644 index 0000000..2d6569b Binary files /dev/null and b/frontend/public/images/car360/img41.png differ diff --git a/frontend/public/images/car360/img42.png b/frontend/public/images/car360/img42.png new file mode 100644 index 0000000..b959541 Binary files /dev/null and b/frontend/public/images/car360/img42.png differ diff --git a/frontend/public/images/car360/img43.png b/frontend/public/images/car360/img43.png new file mode 100644 index 0000000..a5179ba Binary files /dev/null and b/frontend/public/images/car360/img43.png differ diff --git a/frontend/public/images/car360/img44.png b/frontend/public/images/car360/img44.png new file mode 100644 index 0000000..e2fa5ff Binary files /dev/null and b/frontend/public/images/car360/img44.png differ diff --git a/frontend/public/images/car360/img45.png b/frontend/public/images/car360/img45.png new file mode 100644 index 0000000..952135b Binary files /dev/null and b/frontend/public/images/car360/img45.png differ diff --git a/frontend/public/images/car360/img46.png b/frontend/public/images/car360/img46.png new file mode 100644 index 0000000..7025745 Binary files /dev/null and b/frontend/public/images/car360/img46.png differ diff --git a/frontend/public/images/car360/img47.png b/frontend/public/images/car360/img47.png new file mode 100644 index 0000000..13c6036 Binary files /dev/null and b/frontend/public/images/car360/img47.png differ diff --git a/frontend/public/images/car360/img48.png b/frontend/public/images/car360/img48.png new file mode 100644 index 0000000..b1b1b30 Binary files /dev/null and b/frontend/public/images/car360/img48.png differ diff --git a/frontend/public/images/car360/img49.png b/frontend/public/images/car360/img49.png new file mode 100644 index 0000000..8412b35 Binary files /dev/null and b/frontend/public/images/car360/img49.png differ diff --git a/frontend/public/images/car360/img5.png b/frontend/public/images/car360/img5.png new file mode 100644 index 0000000..2603843 Binary files /dev/null and b/frontend/public/images/car360/img5.png differ diff --git a/frontend/public/images/car360/img50.png b/frontend/public/images/car360/img50.png new file mode 100644 index 0000000..f6ea8fb Binary files /dev/null and b/frontend/public/images/car360/img50.png differ diff --git a/frontend/public/images/car360/img51.png b/frontend/public/images/car360/img51.png new file mode 100644 index 0000000..ef1b3b4 Binary files /dev/null and b/frontend/public/images/car360/img51.png differ diff --git a/frontend/public/images/car360/img52.png b/frontend/public/images/car360/img52.png new file mode 100644 index 0000000..8ce2af0 Binary files /dev/null and b/frontend/public/images/car360/img52.png differ diff --git a/frontend/public/images/car360/img53.png b/frontend/public/images/car360/img53.png new file mode 100644 index 0000000..843350b Binary files /dev/null and b/frontend/public/images/car360/img53.png differ diff --git a/frontend/public/images/car360/img54.png b/frontend/public/images/car360/img54.png new file mode 100644 index 0000000..9bdf2a0 Binary files /dev/null and b/frontend/public/images/car360/img54.png differ diff --git a/frontend/public/images/car360/img55.png b/frontend/public/images/car360/img55.png new file mode 100644 index 0000000..12218f4 Binary files /dev/null and b/frontend/public/images/car360/img55.png differ diff --git a/frontend/public/images/car360/img56.png b/frontend/public/images/car360/img56.png new file mode 100644 index 0000000..de69ed7 Binary files /dev/null and b/frontend/public/images/car360/img56.png differ diff --git a/frontend/public/images/car360/img57.png b/frontend/public/images/car360/img57.png new file mode 100644 index 0000000..837c7ec Binary files /dev/null and b/frontend/public/images/car360/img57.png differ diff --git a/frontend/public/images/car360/img58.png b/frontend/public/images/car360/img58.png new file mode 100644 index 0000000..7ff017d Binary files /dev/null and b/frontend/public/images/car360/img58.png differ diff --git a/frontend/public/images/car360/img59.png b/frontend/public/images/car360/img59.png new file mode 100644 index 0000000..1ed37df Binary files /dev/null and b/frontend/public/images/car360/img59.png differ diff --git a/frontend/public/images/car360/img6.png b/frontend/public/images/car360/img6.png new file mode 100644 index 0000000..52f777a Binary files /dev/null and b/frontend/public/images/car360/img6.png differ diff --git a/frontend/public/images/car360/img7.png b/frontend/public/images/car360/img7.png new file mode 100644 index 0000000..e95c1d8 Binary files /dev/null and b/frontend/public/images/car360/img7.png differ diff --git a/frontend/public/images/car360/img8.png b/frontend/public/images/car360/img8.png new file mode 100644 index 0000000..aa30ae3 Binary files /dev/null and b/frontend/public/images/car360/img8.png differ diff --git a/frontend/public/images/car360/img9.png b/frontend/public/images/car360/img9.png new file mode 100644 index 0000000..babbc6d Binary files /dev/null and b/frontend/public/images/car360/img9.png differ diff --git a/frontend/public/images/extra_option/20_darkwheel 1.png b/frontend/public/images/extra_option/20_darkwheel 1.png new file mode 100644 index 0000000..3337325 Binary files /dev/null and b/frontend/public/images/extra_option/20_darkwheel 1.png differ diff --git a/frontend/public/images/extra_option/2_cooling 1.png b/frontend/public/images/extra_option/2_cooling 1.png new file mode 100644 index 0000000..c60f7d5 Binary files /dev/null and b/frontend/public/images/extra_option/2_cooling 1.png differ diff --git a/frontend/public/images/extra_option/dualmufflerpackage 1.png b/frontend/public/images/extra_option/dualmufflerpackage 1.png new file mode 100644 index 0000000..c7588d4 Binary files /dev/null and b/frontend/public/images/extra_option/dualmufflerpackage 1.png differ diff --git a/frontend/public/images/extra_option/pca.png b/frontend/public/images/extra_option/pca.png new file mode 100644 index 0000000..226b8fb Binary files /dev/null and b/frontend/public/images/extra_option/pca.png differ diff --git a/frontend/public/images/extra_option/roa.png b/frontend/public/images/extra_option/roa.png new file mode 100644 index 0000000..cd197c0 Binary files /dev/null and b/frontend/public/images/extra_option/roa.png differ diff --git a/frontend/public/images/inner_car.png b/frontend/public/images/inner_car.png new file mode 100644 index 0000000..873f411 Binary files /dev/null and b/frontend/public/images/inner_car.png differ diff --git a/frontend/public/images/inner_color1.png b/frontend/public/images/inner_color1.png new file mode 100644 index 0000000..fcc370e Binary files /dev/null and b/frontend/public/images/inner_color1.png differ diff --git a/frontend/public/images/inner_color2.png b/frontend/public/images/inner_color2.png new file mode 100644 index 0000000..d1cf85b Binary files /dev/null and b/frontend/public/images/inner_color2.png differ diff --git a/frontend/public/images/logo.svg b/frontend/public/images/logo.svg new file mode 100644 index 0000000..c51734b --- /dev/null +++ b/frontend/public/images/logo.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/frontend/public/images/model_engine.png b/frontend/public/images/model_engine.png new file mode 100644 index 0000000..394d81f Binary files /dev/null and b/frontend/public/images/model_engine.png differ diff --git a/frontend/public/images/result1.png b/frontend/public/images/result1.png new file mode 100644 index 0000000..41d6382 Binary files /dev/null and b/frontend/public/images/result1.png differ diff --git a/frontend/public/images/result2.png b/frontend/public/images/result2.png new file mode 100644 index 0000000..c8ddc7d Binary files /dev/null and b/frontend/public/images/result2.png differ diff --git a/frontend/public/images/result3.png b/frontend/public/images/result3.png new file mode 100644 index 0000000..812df46 Binary files /dev/null and b/frontend/public/images/result3.png differ diff --git a/frontend/public/images/result4.png b/frontend/public/images/result4.png new file mode 100644 index 0000000..e337157 Binary files /dev/null and b/frontend/public/images/result4.png differ diff --git a/frontend/public/images/result5.png b/frontend/public/images/result5.png new file mode 100644 index 0000000..bb198e0 Binary files /dev/null and b/frontend/public/images/result5.png differ diff --git a/frontend/public/images/result6.png b/frontend/public/images/result6.png new file mode 100644 index 0000000..c7d41de Binary files /dev/null and b/frontend/public/images/result6.png differ diff --git a/frontend/public/images/result7.png b/frontend/public/images/result7.png new file mode 100644 index 0000000..b46339c Binary files /dev/null and b/frontend/public/images/result7.png differ diff --git a/frontend/public/images/similar_quote/similar1.png b/frontend/public/images/similar_quote/similar1.png new file mode 100644 index 0000000..62ffc89 Binary files /dev/null and b/frontend/public/images/similar_quote/similar1.png differ diff --git a/frontend/public/images/wheel.png b/frontend/public/images/wheel.png new file mode 100644 index 0000000..95d7b31 Binary files /dev/null and b/frontend/public/images/wheel.png differ diff --git a/frontend/public/vite.svg b/frontend/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/frontend/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 0000000..1633a52 --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,25 @@ +import { BrowserRouter } from 'react-router-dom'; +import NavBar from './components/layout/NavBar'; +import PriceStaticBar from './components/priceStaticBar/PriceStaticBar'; +import CustomRouter from './components/router/CustomRouter'; +import ModalContainer from './containers/Modal/ModalContainer'; +import CloseModalProvider from './context/CloseModalContext'; +import SimilarQuoteModalProvider from './context/SimilarQuoteModalContext'; +import GuideModalProvider from './context/GuideMoadlContext'; +import Providers from './components/contextProviders/Providers'; + +function App() { + const modalProviders = [CloseModalProvider, SimilarQuoteModalProvider, GuideModalProvider]; + return ( + + + + + + + + + ); +} + +export default App; diff --git a/frontend/src/components/cardSlider/CardSlider.tsx b/frontend/src/components/cardSlider/CardSlider.tsx new file mode 100644 index 0000000..08a9e7e --- /dev/null +++ b/frontend/src/components/cardSlider/CardSlider.tsx @@ -0,0 +1,78 @@ +import { styled, useTheme } from 'styled-components'; +import { BodyKrRegular3, HeadingKrMedium6 } from '../../styles/typefaces'; +import CenterWrapper from '../../components/layout/CenterWrapper'; +import { ArrowLeft, ArrowRight } from '../../components/common/icons/Icons'; +import { ReactNode, useState } from 'react'; +import { MAX_PAGE } from '../../utils/constants'; + +interface ICardSlider { + title: string; + cardList: ReactNode[]; +} +export default function CardSlider({ title, cardList }: ICardSlider) { + const [page, setPage] = useState(0); + const theme = useTheme(); + + const arrowLeftColor = page <= 0 ? theme.color.gray200 : theme.color.gray600; + const arrowRightColor = page >= MAX_PAGE - 1 ? theme.color.gray200 : theme.color.gray600; + + const handlePageNext = () => { + setPage((cur) => { + const nextPage = cur + 1 >= MAX_PAGE ? cur : cur + 1; + return nextPage; + }); + }; + const handlePagePrev = () => { + setPage((cur) => { + const prevPage = cur - 1 < 0 ? cur : cur - 1; + return prevPage; + }); + }; + + return ( + +
+ {title} + + + + + + {page + 1}/{MAX_PAGE} + + + + + +
+ {cardList[page]} +
+ ); +} + +const Wrapper = styled(CenterWrapper)` + display: flex; + flex-direction: column; +`; +const Header = styled.div` + margin-top: 16px; + display: flex; + justify-content: space-between; +`; +const Title = styled.div` + ${HeadingKrMedium6}; +`; +const PageButtonWrapper = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + width: 116px; + ${BodyKrRegular3}; + color: ${({ theme }) => theme.color.gray500}; +`; +const PageButton = styled.button``; +const Page = styled.span``; + +const SelectSection = styled.div` + transition: all 1s; +`; diff --git a/frontend/src/components/cards/ExteriorCard.tsx b/frontend/src/components/cards/ExteriorCard.tsx new file mode 100644 index 0000000..6275c69 --- /dev/null +++ b/frontend/src/components/cards/ExteriorCard.tsx @@ -0,0 +1,81 @@ +import { styled } from 'styled-components'; +import { BodyKrMedium3, BodyKrMedium4 } from '../../styles/typefaces'; +import { flexCenterCss } from '../../utils/commonStyle'; +import DefaultCardStyle from '../common/card/DefaultCardStyle'; +import { HTMLAttributes } from 'react'; +import { CheckIcon } from '../common/icons/Icons'; + +interface IExteriorCard extends HTMLAttributes { + active: boolean; + color: string; + desc: string; + name: string; + price: number; +} + +export default function ExteriorCard({ + active, + color, + desc, + name, + price, + ...props +}: IExteriorCard) { + return ( + + + + + + {desc} + {name} + + + {price}원 + + + + + ); +} + +const Card = styled(DefaultCardStyle)` + display: flex; + align-items: center; + width: 100%; + height: 110px; +`; + +const ColorImg = styled.div<{ $color: string }>` + width: 46px; + height: 46px; + border-radius: 50%; + background-color: ${({ $color }) => $color}; +`; +const ColorWrapper = styled.div` + ${flexCenterCss} + border-radius: 50%; + width: 58px; + height: 58px; + border: 1px solid ${({ theme }) => theme.color.gray50}; + margin-left: 12px; +`; + +const ColorDesc = styled.div` + ${BodyKrMedium4} +`; +const ColorName = styled.div` + ${BodyKrMedium3} + margin-bottom: 28px; +`; +const ColorPrice = styled.div` + ${BodyKrMedium3} +`; +const DescWrapper = styled.div` + padding: 14px 16px; + width: 100%; + height: 100%; +`; +const Row = styled.div` + display: flex; + justify-content: space-between; +`; diff --git a/frontend/src/components/cards/ExtraOptionCard.tsx b/frontend/src/components/cards/ExtraOptionCard.tsx new file mode 100644 index 0000000..9633e0c --- /dev/null +++ b/frontend/src/components/cards/ExtraOptionCard.tsx @@ -0,0 +1,62 @@ +import { styled } from 'styled-components'; +import { BodyKrMedium5, HeadingKrMedium7 } from '../../styles/typefaces'; +import { CheckIcon } from '../common/icons/Icons'; +import DefaultCardStyle from '../common/card/DefaultCardStyle'; +import { HTMLAttributes } from 'react'; + +interface IExtraOptionCard extends HTMLAttributes { + active: boolean; + title: string; + price: number; +} + +export default function ExtraOptionCard({ active, title, price, ...props }: IExtraOptionCard) { + return ( + + + +
+ {title} +
+ + +{price} 원 + +
+
+ ); +} + +const Card = styled(DefaultCardStyle)` + position: relative; + width: 103px; + height: 107px; + border-radius: 2px; +`; + +const OptionImg = styled.div` + border-radius: 1px 1px 0px 0px; + width: 100%; + height: 49px; + background-image: url('/images/extra_option/roa.png'); + background-size: cover; + background-repeat: no-repeat; + background-position: center; + background-color: rgba(211, 211, 211, 0.5); +`; +const OptionCardInfo = styled.div` + padding: 4px 8px; + display: flex; + flex-direction: column; + justify-content: space-between; + height: 57px; +`; + +const OptionTitle = styled.div` + ${HeadingKrMedium7} +`; +const OptionPrice = styled.div` + ${BodyKrMedium5} + display: flex; + justify-content: space-between; + align-items: center; +`; diff --git a/frontend/src/components/cards/InteriorCard.tsx b/frontend/src/components/cards/InteriorCard.tsx new file mode 100644 index 0000000..8279860 --- /dev/null +++ b/frontend/src/components/cards/InteriorCard.tsx @@ -0,0 +1,76 @@ +import { styled } from 'styled-components'; +import { BodyKrMedium3, BodyKrMedium4 } from '../../styles/typefaces'; +import { flexCenterCss } from '../../utils/commonStyle'; +import DefaultCardStyle from '../common/card/DefaultCardStyle'; +import { CheckIcon } from '../common/icons/Icons'; +import { HTMLAttributes } from 'react'; + +interface IInTeriorCard extends HTMLAttributes { + imgSrc1: string; + imgSrc2: string; + active: boolean; + desc: string; + name: string; + price: number; +} + +export default function InteriorCard({ + imgSrc1, + imgSrc2, + active, + desc, + name, + price, + ...props +}: IInTeriorCard) { + return ( + + + + + + + {desc} + {name} + + + {price}원 + + + + + ); +} + +const Card = styled(DefaultCardStyle)` + display: flex; + align-items: center; + width: 100%; + height: 110px; + overflow: hidden; +`; +const InteriorImg = styled.img``; +const ImgWrapper = styled.div` + ${flexCenterCss} + flex-direction: column; + width: 69px; +`; + +const ColorDesc = styled.div` + ${BodyKrMedium4} +`; +const ColorName = styled.div` + ${BodyKrMedium3} + margin-bottom: 28px; +`; +const ColorPrice = styled.div` + ${BodyKrMedium3} +`; +const DescWrapper = styled.div` + padding: 14px 16px; + width: 100%; + height: 100%; +`; +const Row = styled.div` + display: flex; + justify-content: space-between; +`; diff --git a/frontend/src/components/cards/ModelTypeCard.tsx b/frontend/src/components/cards/ModelTypeCard.tsx new file mode 100644 index 0000000..e34fbcb --- /dev/null +++ b/frontend/src/components/cards/ModelTypeCard.tsx @@ -0,0 +1,48 @@ +import { styled } from 'styled-components'; +import { CheckIcon } from '../common/icons/Icons'; +import { BodyKrRegular4, HeadingEn4, HeadingKrMedium7 } from '../../styles/typefaces'; +import DefaultCardStyle from '../common/card/DefaultCardStyle'; +import { HTMLAttributes } from 'react'; + +interface IModelTypeCard extends HTMLAttributes { + active?: boolean; + desc: string; + title: string; + price: number; +} + +export default function ModelTypeCard({ + active = false, + desc, + title, + price, + ...props +}: IModelTypeCard) { + return ( + + {desc} + {title} + + +{price} 원 + + + ); +} + +const Wrapper = styled(DefaultCardStyle)` + padding: 8px 12px; + width: 100%; +`; + +const ModelTypeTitle = styled.div` + ${HeadingEn4} +`; +const ModelTypePrice = styled.div` + ${HeadingKrMedium7} + display: flex; + justify-content: space-between; + align-items: center; +`; +const ModelTypeDesc = styled.div` + ${BodyKrRegular4} +`; diff --git a/frontend/src/components/cards/OptionCard.tsx b/frontend/src/components/cards/OptionCard.tsx new file mode 100644 index 0000000..7baa827 --- /dev/null +++ b/frontend/src/components/cards/OptionCard.tsx @@ -0,0 +1,89 @@ +import { styled } from 'styled-components'; +import { BodyKrRegular4, HeadingEn4, HeadingKrMedium7 } from '../../styles/typefaces'; +import HmgTag from '../common/hmgTag/HmgTag'; +import { CheckIcon } from '../common/icons/Icons'; +import DefaultCardStyle from '../common/card/DefaultCardStyle'; +import { HTMLAttributes } from 'react'; + +interface IOptionCard extends HTMLAttributes { + type: 'default' | 'extra'; + active: boolean; + desc?: string; + title: string; + price: number; +} + +export default function OptionCard({ type, active, desc, title, price, ...props }: IOptionCard) { + const displayCaption = + type === 'default' ? ( + 기본포함 + ) : ( + + +{price} 원 + + ); + + return ( + + + + + + +
+ {desc} + {title} +
+ {displayCaption} +
+
+ ); +} + +const Card = styled(DefaultCardStyle)` + position: relative; + width: 244px; + border-radius: 2px; +`; + +const HmgWrapper = styled.div` + position: absolute; + top: 0; + right: 0; +`; + +const OptionImg = styled.div` + border-radius: 1px 1px 0px 0px; + width: 100%; + height: 160px; + background-image: url('/images/extra_option/roa.png'); + background-size: cover; + background-repeat: no-repeat; + background-position: center; + background-color: rgba(211, 211, 211, 0.5); +`; +const OptionCardInfo = styled.div` + padding: 12px 14px; + display: flex; + flex-direction: column; + justify-content: space-between; + height: 116px; +`; + +const OptionTitle = styled.div` + ${HeadingEn4} +`; +const OptionPrice = styled.div` + ${HeadingKrMedium7} + display: flex; + justify-content: space-between; + align-items: center; +`; + +const DefaultInfo = styled.div` + color: ${({ theme }) => theme.color.gray500}; +`; + +const OptionDesc = styled.div` + ${BodyKrRegular4} +`; diff --git a/frontend/src/components/common/banner/Banner.tsx b/frontend/src/components/common/banner/Banner.tsx new file mode 100644 index 0000000..ba6f664 --- /dev/null +++ b/frontend/src/components/common/banner/Banner.tsx @@ -0,0 +1,51 @@ +import { styled } from 'styled-components'; +import { BodyKrRegular3, HeadingKrBold1 } from '../../../styles/typefaces'; +import CenterWrapper from '../../layout/CenterWrapper'; + +interface IBanner extends React.HTMLAttributes { + subtitle?: string; + title?: string; +} + +export default function Banner({ subtitle, title, ...props }: IBanner) { + return ( + <> + + + + {subtitle && {subtitle}} + {title && {title}} + + + {props.children} + + + ); +} + +const BannerBg = styled.div` + position: relative; + width: 100%; + min-height: 360px; + background: linear-gradient( + 180deg, + rgba(162, 199, 231, 0.2) 24.92%, + rgba(255, 255, 255, 0) 61.36% + ), + #fff; + box-shadow: 0px 0px 8px 0px rgba(131, 133, 136, 0.2); +`; +const InfoWrapper = styled.div` + position: absolute; + top: 72px; +`; + +const SubTitle = styled.p` + color: ${({ theme }) => theme.color.gray900}; + ${BodyKrRegular3} +`; + +const Title = styled.p` + color: ${({ theme }) => theme.color.primaryColor700}; + ${HeadingKrBold1} +`; diff --git a/frontend/src/components/common/buttons/RectButton.tsx b/frontend/src/components/common/buttons/RectButton.tsx new file mode 100644 index 0000000..feb8493 --- /dev/null +++ b/frontend/src/components/common/buttons/RectButton.tsx @@ -0,0 +1,66 @@ +import { css, styled } from 'styled-components'; +import { BodyKrMedium2, CaptionEn2, HeadingKrMedium5 } from '../../../styles/typefaces'; +import React from 'react'; +import { flexCenterCss } from '../../../utils/commonStyle'; + +type rectBtnType = 'popup' | 'price' | 'trim'; + +interface IRectButton extends React.HTMLAttributes { + type: rectBtnType; + active?: boolean; +} +interface IWrapper { + $type: rectBtnType; + $active: boolean; +} + +export default function RectButton({ type, active = true, ...props }: IRectButton) { + return ; +} + +const popupCss = css` + width: 850px; + height: 52px; + ${BodyKrMedium2} +`; + +const priceCss = css` + width: 340px; + height: 44px; + ${HeadingKrMedium5}; +`; + +const trimCss = css` + width: 220px; + height: 36px; + ${CaptionEn2}; +`; + +const Wrapper = styled.button` + ${({ $type }) => $type === 'popup' && popupCss} + ${({ $type }) => $type === 'price' && priceCss} + ${({ $type }) => $type === 'trim' && trimCss} + + ${({ $active, theme }) => { + switch ($active) { + case true: + return css` + ${flexCenterCss} + background-color: ${theme.color.primaryColor700}; + color: ${theme.color.white}; + + &:hover { + background-color: ${theme.color.primaryColor500}; + } + &:active { + background-color: ${theme.color.primaryColor800}; + } + `; + case false: + return css` + background-color: ${theme.color.gray300}; + color: ${theme.color.white}; + `; + } + }} +`; diff --git a/frontend/src/components/common/buttons/RoundButton.tsx b/frontend/src/components/common/buttons/RoundButton.tsx new file mode 100644 index 0000000..ad08e0f --- /dev/null +++ b/frontend/src/components/common/buttons/RoundButton.tsx @@ -0,0 +1,82 @@ +import { css, styled } from 'styled-components'; +import { BodyKrRegular3 } from '../../../styles/typefaces'; + +interface IRoundButton extends React.HTMLAttributes { + type: 'option' | 'price'; + inactive?: boolean; +} + +export default function RoundButton({ type, inactive = false, ...props }: IRoundButton) { + return ; +} + +const priceHoverCss = css` + background: ${({ theme }) => theme.color.skyBlueCardBg}; + border-color: ${({ theme }) => theme.color.primaryColor300}; +`; + +const priceActiveCss = css` + background: ${({ theme }) => theme.color.primaryColor}; + color: ${({ theme }) => theme.color.white}; + border: 1px solid ${({ theme }) => theme.color.primaryColor300}; +`; + +const optionHoverCss = css` + background: ${({ theme }) => theme.color.white}; + border: 1px solid ${({ theme }) => theme.color.skyBlue}; + color: ${({ theme }) => theme.color.primaryColor}; +`; + +const optionActiveCss = css` + background-color: ${({ theme }) => theme.color.primaryColor100}; + color: ${({ theme }) => theme.color.primaryColor}; +`; + +const Wrapper = styled.button<{ $type: 'option' | 'price'; $incative: boolean }>` + ${BodyKrRegular3} + height: 36px; + padding-left: 17px; + padding-right: 17px; + border-radius: 18px; + color: ${({ theme }) => theme.color.primaryColor}; + + ${({ $type, $incative, theme }) => { + switch ($type) { + case 'price': + return css` + border: 1px solid ${theme.color.primaryColor}; + &:hover { + ${priceHoverCss} + } + &:active { + ${priceActiveCss} + } + `; + case 'option': + if (!$incative) { + return css` + background: ${theme.color.skyBlueCardBg}; + border: 1px solid ${theme.color.skyBlue}; + &:hover { + ${optionHoverCss} + } + &:active { + ${optionActiveCss} + } + `; + } else { + return css` + border: 1px solid ${theme.color.gray200}; + color: ${theme.color.gray600}; + background: ${theme.color.white}; + &:hover { + ${optionHoverCss} + } + &:active { + ${optionActiveCss} + } + `; + } + } + }} +`; diff --git a/frontend/src/components/common/card/DefaultCardStyle.tsx b/frontend/src/components/common/card/DefaultCardStyle.tsx new file mode 100644 index 0000000..19786df --- /dev/null +++ b/frontend/src/components/common/card/DefaultCardStyle.tsx @@ -0,0 +1,40 @@ +import { css, styled } from 'styled-components'; + +interface IDefaultCardStyle extends React.HTMLAttributes { + active?: boolean; +} + +export default function DefaultCardStyle({ active = false, ...props }: IDefaultCardStyle) { + return ; +} + +const activeCss = css` + background: ${({ theme }) => theme.color.cardBg}; + border: 1px solid ${({ theme }) => theme.color.activeBlue}; +`; + +const inactiveCss = css` + &:hover { + border: 1px solid ${({ theme }) => theme.color.activeBlue}; + background: ${({ theme }) => theme.color.cardBgOpacity05}; + } + border: 1px solid ${({ theme }) => theme.color.gray200}; + background: ${({ theme }) => theme.color.white}; + color: ${({ theme }) => theme.color.gray500}; +`; + +const Wrapper = styled.div<{ $active: boolean }>` + cursor: pointer; + border-radius: 2px; + ${({ $active }) => { + if ($active) { + return css` + ${activeCss} + `; + } else { + return css` + ${inactiveCss} + `; + } + }} +`; diff --git a/frontend/src/components/common/hmgTag/HmgTag.tsx b/frontend/src/components/common/hmgTag/HmgTag.tsx new file mode 100644 index 0000000..39077ba --- /dev/null +++ b/frontend/src/components/common/hmgTag/HmgTag.tsx @@ -0,0 +1,39 @@ +import { css, styled } from 'styled-components'; +import { HeadingEn5 } from '../../../styles/typefaces'; + +export type sizeType = 'large' | 'medium' | 'small'; +export interface IHmgTag { + size?: sizeType; +} + +export default function HmgTag({ size = 'medium' }: IHmgTag) { + // Todo. ">" 를 svg 아이콘으로 바꾸기 + return HMG Data {size === 'large' ? '>' : null}; +} +const Wrapper = styled.div<{ $size: sizeType }>` + ${HeadingEn5} + + display: flex; + justify-content: center; + align-items: center; + background-color: ${({ theme }) => theme.color.activeBlue2}; + color: ${({ theme }) => theme.color.white}; + ${({ $size }) => { + if ($size === 'large') + return css` + width: 97px; + height: 32px; + `; + else if ($size === 'medium') + return css` + width: 70px; + height: 32px; + `; + else { + return css` + width: 70px; + height: 20px; + `; + } + }} +`; diff --git a/frontend/src/components/common/hmgTag/NoneHmgTag.tsx b/frontend/src/components/common/hmgTag/NoneHmgTag.tsx new file mode 100644 index 0000000..0449e75 --- /dev/null +++ b/frontend/src/components/common/hmgTag/NoneHmgTag.tsx @@ -0,0 +1,34 @@ +import { css, styled } from 'styled-components'; +import { HeadingEn5 } from '../../../styles/typefaces'; +import { IHmgTag, sizeType } from './HmgTag'; + +export default function NoneHmgTag({ size = 'medium' }: IHmgTag) { + return 상세보기 {size === 'large' ? '>' : null}; +} + +const Wrapper = styled.div<{ $size: sizeType }>` + ${HeadingEn5} + display: flex; + justify-content: center; + align-items: center; + background-color: ${({ theme }) => theme.color.white}; + color: ${({ theme }) => theme.color.gray800}; + ${({ $size }) => { + if ($size === 'large') + return css` + width: 97px; + height: 32px; + `; + else if ($size === 'medium') + return css` + width: 70px; + height: 32px; + `; + else { + return css` + width: 70px; + height: 20px; + `; + } + }} +`; diff --git a/frontend/src/components/common/icons/Icons.tsx b/frontend/src/components/common/icons/Icons.tsx new file mode 100644 index 0000000..393814f --- /dev/null +++ b/frontend/src/components/common/icons/Icons.tsx @@ -0,0 +1,213 @@ +import React from 'react'; +import { theme } from '../../../styles/theme'; + +interface ISvg extends React.SVGProps { + active?: boolean; +} + +export function ArrowUp({ ...props }: ISvg) { + return ( + + + + ); +} + +export function ArrowDown({ ...props }: ISvg) { + return ( + + + + ); +} + +export function ArrowLeft({ ...props }: ISvg) { + return ( + + + + + + + + + + + ); +} +export function ArrowRight({ ...props }: ISvg) { + return ( + + + + + + + + + + + ); +} + +export function SearchIcon({ ...props }: ISvg) { + return ( + + + + + + ); +} + +export function CancelIcon({ ...props }: ISvg) { + return ( + + + + ); +} + +export function CheckIcon({ active, ...props }: ISvg) { + const color = active ? theme.color.activeBlue : theme.color.gray200; + return ( + + + + ); +} + +export function CloseIcon({ ...props }: ISvg) { + return ( + + + + ); +} +export function Bubble({ ...props }: ISvg) { + return ( + + + + + ); +} + +export function LoadingIcon() { + return ( + + + + ); +} + +export function LoadingIcon2() { + return ( + + + + ); +} + +export function LoadingIcon3() { + return ( + + + + ); +} diff --git a/frontend/src/components/contextProviders/Providers.tsx b/frontend/src/components/contextProviders/Providers.tsx new file mode 100644 index 0000000..6bba603 --- /dev/null +++ b/frontend/src/components/contextProviders/Providers.tsx @@ -0,0 +1,18 @@ +import { createElement } from 'react'; + +interface ProvidersProps { + contexts: React.ElementType[]; + children: React.ReactNode; +} + +export default function Providers({ contexts, children }: ProvidersProps) { + const createProviders = contexts.reduce( + (prev: React.ReactNode, context: React.ElementType) => + createElement(context, { + children: prev, + }), + children + ); + + return <>{createProviders}; +} diff --git a/frontend/src/components/details/Details.tsx b/frontend/src/components/details/Details.tsx new file mode 100644 index 0000000..efb62df --- /dev/null +++ b/frontend/src/components/details/Details.tsx @@ -0,0 +1,55 @@ +import { styled, useTheme } from 'styled-components'; +import { DetailsHTMLAttributes, useState } from 'react'; +import { HeadingKrMedium5 } from '../../styles/typefaces'; +import { ArrowDown, ArrowUp } from '../common/icons/Icons'; + +interface IDetails extends DetailsHTMLAttributes { + title: string; +} + +export default function Details({ title, ...props }: IDetails) { + const [isOpen, setIsOepn] = useState(true); + const theme = useTheme(); + const arrowColor = theme.color.gray900; + const totalPrice = 1_000_000; + + const toggleIsOpen = () => { + setIsOepn(!isOpen); + }; + + return ( + + + {title} + + +{totalPrice.toLocaleString()}원 + {isOpen ? : } + + + {props.children} + + ); +} + +const Wrapper = styled.details` + position: relative; + width: 100%; + margin-top: 12px; +`; +const Summary = styled.summary` + ${HeadingKrMedium5} + background-color: ${({ theme }) => theme.color.gray100}; + padding: 12px 20px; + list-style: none; + display: flex; + justify-content: space-between; + cursor: pointer; +`; +const Price = styled.span` + margin-right: 20px; +`; +const RightDiv = styled.div` + display: flex; + align-items: center; + color: ${({ theme }) => theme.color.primaryColor}; +`; diff --git a/frontend/src/components/details/SummaryItem.tsx b/frontend/src/components/details/SummaryItem.tsx new file mode 100644 index 0000000..de0b99f --- /dev/null +++ b/frontend/src/components/details/SummaryItem.tsx @@ -0,0 +1,66 @@ +import { styled } from 'styled-components'; +import { BodyKrRegular3 } from '../../styles/typefaces'; + +export interface ISummaryItem { + imgSrc: string; + itemName: string; + selectedName: string; + price: number; +} + +export default function SummaryItem({ imgSrc, itemName, selectedName, price }: ISummaryItem) { + return ( + + + + + {itemName} + {selectedName} + + + 수정하기 + + {price}원 + + + + ); +} + +const Item = styled.li` + display: flex; + padding: 8px 0; + height: 71px; + border-bottom: 1px solid ${({ theme }) => theme.color.gray100}; +`; +const Img = styled.img` + width: 77px; + height: 55px; +`; +const InfoWrapper = styled.div` + display: flex; + justify-content: space-between; + margin-left: 16px; + width: 100%; + height: 100%; + ${BodyKrRegular3} +`; +const LeftInfo = styled.div` + display: flex; + flex-direction: column; + justify-content: space-between; +`; +const RightInfo = styled.div` + display: flex; + flex-direction: column; + justify-content: space-between; + text-align: right; +`; + +const Price = styled.div``; +const ItemName = styled.div` + color: ${({ theme }) => theme.color.gray500}; +`; +const ModifyButton = styled.button` + color: ${({ theme }) => theme.color.primaryColor}; +`; +const SelectedName = styled.div``; diff --git a/frontend/src/components/histogram/BarHistogram.tsx b/frontend/src/components/histogram/BarHistogram.tsx new file mode 100644 index 0000000..eab0e2c --- /dev/null +++ b/frontend/src/components/histogram/BarHistogram.tsx @@ -0,0 +1,123 @@ +import { styled } from 'styled-components'; +import HmgTag from '../common/hmgTag/HmgTag'; +import { BodyKrMedium2, BodyKrRegular3, HeadingKrMedium6 } from '../../styles/typefaces'; +import { flexCenterCss } from '../../utils/commonStyle'; +import { useContext } from 'react'; +import { SimilarQuoteModalContext } from '../../context/SimilarQuoteModalContext'; + +export default function BarHistogram() { + const { setVisible: setSimilarQuoteModalVisible } = useContext(SimilarQuoteModalContext); + return ( + + + + + + 내 견적과 비슷한 실제 출고 견적 + 들을 확인하고 비교해보세요. + + + 유사 출고 견적이란, 내 견적과 해시태그 유사도가 높은 다른 사람들의 실제 출고 견적이에요. + + + + + + 3,444대 + + 내 견적 + + + 3,444대 + + 내 견적 + + + 3,444대 + + 내 견적 + + + 3,444대 + + 내 견적 + + + 3,444대 + + 내 견적 + + + + + + ); +} + +const BarChart = styled.div` + display: flex; + justify-content: space-around; + width: 100%; + height: 140px; +`; +const BarItem = styled.div<{ $active?: boolean }>` + display: flex; + flex-direction: column; + justify-content: flex-end; + align-items: center; + color: ${({ theme, $active }) => ($active ? theme.color.activeBlue : theme.color.gray300)}; +`; +const BarValue = styled.div` + ${BodyKrRegular3} +`; +const BarItemName = styled.div` + ${HeadingKrMedium6} +`; +const CaptionWrapper = styled.div` + ${BodyKrMedium2} + width: 100%; + padding-bottom: 15px; + border-bottom: 1px solid ${({ theme }) => theme.color.gray100}; + margin-bottom: 20px; +`; +const Caption = styled.div` + width: 200px; +`; +const BlueText = styled.span` + color: ${({ theme }) => theme.color.activeBlue}; +`; + +const HistogramWrapper = styled.div` + background-color: ${({ theme }) => theme.color.gray50}; + margin-top: 38px; +`; + +const PaddingWrapper = styled.div` + padding: 21px 16px; +`; + +const CaptionDesc = styled.p` + width: 280px; + ${BodyKrRegular3} + margin-top: 10px; + + color: ${({ theme }) => theme.color.gray500}; +`; + +const Bar = styled.div<{ $height: string; $active?: boolean }>` + width: 14px; + height: ${({ $height }) => $height}; + background-color: ${({ theme, $active }) => + $active ? theme.color.activeBlue : theme.color.gray200}; +`; + +const Button = styled.button` + ${flexCenterCss}; + ${HeadingKrMedium6}; + background: ${({ theme }) => theme.color.skyBlueCardBg}; + border-radius: 2px; + color: ${({ theme }) => theme.color.primaryColor}; + width: 100%; + height: 52px; + margin-top: 10px; +`; diff --git a/frontend/src/components/histogram/CurveHistogram.tsx b/frontend/src/components/histogram/CurveHistogram.tsx new file mode 100644 index 0000000..a399b73 --- /dev/null +++ b/frontend/src/components/histogram/CurveHistogram.tsx @@ -0,0 +1,220 @@ +import { styled, useTheme } from 'styled-components'; +import HmgTag from '../common/hmgTag/HmgTag'; +import { BodyKrMedium2, BodyKrRegular2, HeadingKrMedium5 } from '../../styles/typefaces'; +import { useCallback, useEffect, useRef, useState } from 'react'; + +// 임시 데이터 +const carData = { + 4300: 0, + 4400: 100, + 4500: 300, + 4600: 100, + 4700: 0, +}; +const mycarPrice = 4400; + +interface ICarData { + [key: string]: number; +} + +interface ICircle { + cx: number; + cy: number; +} + +type CoordType = [number, number]; + +export default function CurveHistogram() { + const theme = useTheme(); + const histogramRef = useRef(null); + const [svgSize, setSvgSize] = useState({ width: 0, height: 0 }); + const [d, setD] = useState(''); + const [circlePos, setCirclePos] = useState({ x: 0, y: 0 }); + + const initSvgSize = () => { + if (!histogramRef.current) { + return; + } + const { width, height } = histogramRef.current.getBoundingClientRect(); + setSvgSize({ width, height: height - 20 }); + }; + + const getCoords = useCallback( + (data: ICarData) => { + const yList = Object.values(data); + let xList = Object.keys(data).map(Number); + const minX = Math.min(...xList); + const maxX = Math.max(...xList); + const maxY = Math.max(...yList); + + xList = xList.map((value) => value - minX); + xList = xList.map((value) => (value * svgSize.width) / (maxX - minX)); + + const coords = yList.reduce((acc: CoordType[], value, idx) => { + const x = xList[idx]; + const y = (value / maxY) * svgSize.height; + const coord: CoordType = [x, y]; + return [...acc, coord]; + }, []); + + return coords; + }, + [svgSize] + ); + + const drawLine = useCallback( + (carData: ICarData) => { + const coords = getCoords(carData); + coords && setD(getPathDAttribute(coords)); + }, + [getCoords] + ); + + const drawCircle = useCallback( + (mycarPrice: number) => { + const coords = getCoords(carData); + const carDataKeys = Object.keys(carData); + const mycarIdx = carDataKeys.findIndex((value) => value === mycarPrice.toString()); + if (mycarIdx === -1) { + alert('price와 일치하는 데이터 없음'); + return; + } + const [mycarX, myCarY] = coords[mycarIdx]; + setCirclePos({ x: mycarX, y: myCarY }); + }, + [getCoords] + ); + + const drawHistogram = useCallback(() => { + drawLine(carData); + drawCircle(mycarPrice); + }, [drawLine, drawCircle]); + + useEffect(initSvgSize, [histogramRef]); + useEffect(drawHistogram, [svgSize, drawHistogram]); + + return ( + + + + + + 르블랑으로 완성된 모든 타입의 + 견적 가격의 분포 + 입니다. + + + + + + + + + 최소 + 43,000,000원 + + + 최대 + 43,000,000원 + + + + + ); +} + +function Circle({ cx, cy }: ICircle) { + return ( + <> + + + + ); +} + +const getPathDAttribute = (coords: CoordType[]) => { + const d = coords?.reduce((acc: string, coord, idx, arr) => { + const [x, y] = coord; + if (idx === 0) return `M ${x},${y}`; + const [cp1X, cp1Y] = getControllPoint(arr[idx - 2], arr[idx - 1], arr[idx]); + const [cp2X, cp2Y] = getControllPoint(arr[idx - 1], arr[idx], arr[idx + 1], true); + return `${acc} C${cp1X} ${cp1Y} ${cp2X} ${cp2Y} ${x} ${y}`; + }, ''); + + return d; +}; + +const getOpossedLine = (pointA: CoordType, pointB: CoordType) => { + const xLength = pointB[0] - pointA[0]; + const yLength = pointB[1] - pointA[1]; + const angle = Math.atan2(yLength, xLength); + const length = Math.sqrt(Math.pow(xLength, 2) + Math.pow(yLength, 2)); + + return { length, angle }; +}; + +const getControllPoint = ( + prev: CoordType, + cur: CoordType, + next: CoordType, + isEndPoint?: boolean +) => { + const smooth = 0.2; + const p = prev || cur; + const n = next || cur; + const { length, angle } = getOpossedLine(p, n); + const _length = length * smooth; + const _angle = angle + (isEndPoint ? Math.PI : 0); + const x = _length * Math.cos(_angle) + cur[0]; + const y = _length * Math.sin(_angle) + cur[1]; + return [x, y]; +}; + +const HistogramSvg = styled.svg` + transform: scaleY(-1); +`; +const CaptionWrapper = styled.div` + ${BodyKrMedium2} + width: 100%; + padding-bottom: 15px; + border-bottom: 1px solid ${({ theme }) => theme.color.gray100}; + margin-bottom: 20px; +`; +const Caption = styled.div` + width: 200px; +`; +const BlueText = styled.span` + color: ${({ theme }) => theme.color.activeBlue}; +`; + +const HistogramWrapper = styled.div` + background-color: ${({ theme }) => theme.color.gray50}; + margin-top: 38px; +`; + +const PaddingWrapper = styled.div` + padding: 21px 16px; +`; + +const XCaptionWrapper = styled.div` + display: flex; + justify-content: space-between; + color: ${({ theme }) => theme.color.gray600}; +`; +const MinXCaption = styled.div` + display: flex; + flex-direction: column; + align-items: flex-start; +`; +const MaxXCaption = styled.div` + display: flex; + flex-direction: column; + align-items: flex-end; +`; + +const XTitle = styled.div` + ${HeadingKrMedium5} +`; +const XValue = styled.div` + ${BodyKrRegular2} +`; diff --git a/frontend/src/components/layout/CenterWrapper.tsx b/frontend/src/components/layout/CenterWrapper.tsx new file mode 100644 index 0000000..c43b0eb --- /dev/null +++ b/frontend/src/components/layout/CenterWrapper.tsx @@ -0,0 +1,11 @@ +import { styled } from 'styled-components'; + +interface ICenterWrapper extends React.HTMLAttributes {} +export default function CenterWrapper({ ...props }: ICenterWrapper) { + return ; +} + +const Wrapper = styled.div` + width: 1024px; + margin: 0 auto; +`; diff --git a/frontend/src/components/layout/NavBar.tsx b/frontend/src/components/layout/NavBar.tsx new file mode 100644 index 0000000..827fb0d --- /dev/null +++ b/frontend/src/components/layout/NavBar.tsx @@ -0,0 +1,185 @@ +import React, { useContext } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { css, styled, useTheme } from 'styled-components'; +import { BodyKrMedium3, BodyKrRegular3, HeadingKrMedium6 } from '../../styles/typefaces'; +import { ArrowDown, CancelIcon } from '../common/icons/Icons'; +import hyundaiLogo from '/images/logo.svg'; +import { PATH } from '../../utils/constants'; +import { CloseModalContext } from '../../context/CloseModalContext'; + +interface INavItem extends React.HTMLAttributes { + active: boolean; +} +export default function NavBar() { + const navigate = useNavigate(); + const { pathname: currentPath } = useLocation(); + const { setVisible: setCloseModalVisible } = useContext(CloseModalContext); + const theme = useTheme(); + + const handleNavItemClick = (path: string) => { + navigate(path); + }; + const isActive = (path: string) => { + return currentPath === path; + }; + const handleCloseButtonClick = () => { + setCloseModalVisible(true); + }; + + return ( + <> + + + + + + 펠리세이드 + + + + handleNavItemClick(PATH.trim)} + active={isActive(PATH.trim) || isActive(PATH.home)} + > + 트림 + + handleNavItemClick(PATH.modelType)} + active={isActive(PATH.modelType)} + > + 타입 + + handleNavItemClick(PATH.exterior)} + active={isActive(PATH.exterior)} + > + 외장 + + handleNavItemClick(PATH.interior)} + active={isActive(PATH.interior)} + > + 내장 + + handleNavItemClick(PATH.option)} active={isActive(PATH.option)}> + 옵션 + + handleNavItemClick(PATH.result)} active={isActive(PATH.result)}> + 완료 + + + + 종료 + + + + + + ); +} + +function NavItem({ active, ...props }: INavItem) { + const Highlight = active ? : ; + return ( + + {props.children} + {Highlight} + + ); +} + +const Wrapper = styled.div` + z-index: 999; + position: fixed; + top: 0px; + left: 0px; + width: 100%; + height: 60px; + border-bottom: 2px solid ${({ theme }) => theme.color.gray200}; + background-color: ${({ theme }) => theme.color.white}; + display: flex; + justify-content: center; + align-items: flex-end; + padding-bottom: 8px; +`; + +const NavList = styled.ul` + display: flex; + gap: 40px; + align-items: flex-end; + margin: -10px; +`; + +const Item = styled.li<{ $active: boolean }>` + ${HeadingKrMedium6} + width: 52px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-end; + gap: 6px; + cursor: pointer; + + ${({ theme, $active }) => { + if ($active === true) { + return css` + color: ${theme.color.primaryColor}; + `; + } else { + return css` + color: ${theme.color.gray200}; + `; + } + }} +`; + +const CarSelect = styled.div` + ${BodyKrMedium3} + position: absolute; + left: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + padding-left: 20px; + border-left: 1px solid ${({ theme }) => theme.color.gray200}; + cursor: pointer; +`; + +const CancelButton = styled.button` + position: absolute; + right: -44px; + bottom: 0; + ${BodyKrRegular3} + display: flex; + justify-content: center; + align-items: center; + gap: 8px; +`; +const Body = styled.div` + position: relative; + width: 1024px; + display: flex; + justify-content: center; +`; + +const HyundaiLogo = styled.img` + position: absolute; + left: -59px; + bottom: 0; + width: 39px; + height: 22px; + + display: flex; + justify-content: center; + align-items: center; +`; +const Underline = styled.div` + width: 18px; + height: 2px; + background-color: ${({ theme }) => theme.color.primaryColor}; +`; + +const Span = styled.span` + ${BodyKrMedium3}; +`; diff --git a/frontend/src/components/loading/Loading.tsx b/frontend/src/components/loading/Loading.tsx new file mode 100644 index 0000000..8c9120e --- /dev/null +++ b/frontend/src/components/loading/Loading.tsx @@ -0,0 +1,58 @@ +import { keyframes, styled } from 'styled-components'; +import { BodyKrMedium3 } from '../../styles/typefaces'; +import { LoadingIcon, LoadingIcon2, LoadingIcon3 } from '../common/icons/Icons'; +import { flexCenterCss } from '../../utils/commonStyle'; + +export default function Loading() { + return ( + + + + + + + + + + + + + 데이터를 불러오는 중입니다. + + ); +} + +const spin = keyframes` + to{ + transform: rotate(360deg); + } +`; + +const Wrapper = styled.div` + display: flex; + flex-direction: column; + align-items: center; +`; + +const Spinner = styled.div` + position: relative; + ${flexCenterCss} + width: 54px; + height: 54px; + border: 3px solid ${({ theme }) => theme.color.gray200}; + border-radius: 50%; + border-top-color: ${({ theme }) => theme.color.activeBlue}; + animation: ${spin} 1.5s linear infinite; +`; + +const Text = styled.div` + ${BodyKrMedium3}; + width: 200px; + text-align: center; + margin-top: 16px; +`; + +const AbsoluteCenter = styled.div` + position: absolute; + ${flexCenterCss} +`; diff --git a/frontend/src/components/modal/CloseModal.tsx b/frontend/src/components/modal/CloseModal.tsx new file mode 100644 index 0000000..f98666b --- /dev/null +++ b/frontend/src/components/modal/CloseModal.tsx @@ -0,0 +1,76 @@ +import { HTMLAttributes, MouseEventHandler, useContext } from 'react'; +import { styled } from 'styled-components'; +import { BodyKrMedium3 } from '../../styles/typefaces'; +import { flexCenterCss } from '../../utils/commonStyle'; +import RectButton from '../common/buttons/RectButton'; +import { HYUNDAI_URL } from '../../utils/constants'; +import { DimmedBackground } from './DimmedBackground'; +import { CloseModalContext } from '../../context/CloseModalContext'; + +interface ICloseModal extends HTMLAttributes {} + +export default function CloseModal({ ...props }: ICloseModal) { + const { visible, setVisible } = useContext(CloseModalContext); + const handleCloseClick = () => { + location.href = HYUNDAI_URL; + }; + + const stopEvent: MouseEventHandler = (e) => { + e.stopPropagation(); + }; + + return ( + + + + 내 차 만들기를 종료하시겠습니까? +
+ 지금 서비스 종료 시 저장되지 않습니다. +
+ + + + +
+
+ ); +} + +const CloseDimmed = styled(DimmedBackground)` + z-index: 99999999999; +`; + +const Modal = styled.div` + position: relative; + display: flex; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + overflow: hidden; + flex-direction: column; + width: 335px; + height: 200px; + border-radius: 4px; + background-color: ${({ theme }) => theme.color.white}; +`; + +const Text = styled.div` + ${BodyKrMedium3} + ${flexCenterCss} + pointer-events: none; + height: 100%; + text-align: center; +`; + +const ButtonWrapper = styled.div` + display: flex; + width: 100%; +`; + +const Button = styled(RectButton)` + width: 100%; +`; diff --git a/frontend/src/components/modal/DimmedBackground.tsx b/frontend/src/components/modal/DimmedBackground.tsx new file mode 100644 index 0000000..f990b3f --- /dev/null +++ b/frontend/src/components/modal/DimmedBackground.tsx @@ -0,0 +1,14 @@ +import { styled } from 'styled-components'; + +export const DimmedBackground = styled.div<{ $displayDimmed: boolean }>` + z-index: 20000; + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: rgba(31, 31, 31, 0.7); + backdrop-filter: blur(6px); + mix-blend-mode: normal; + display: ${({ $displayDimmed }) => ($displayDimmed ? 'block' : 'none')}; +`; diff --git a/frontend/src/components/modal/GuideModal.tsx b/frontend/src/components/modal/GuideModal.tsx new file mode 100644 index 0000000..d57bb95 --- /dev/null +++ b/frontend/src/components/modal/GuideModal.tsx @@ -0,0 +1,113 @@ +import { HTMLAttributes, MouseEventHandler, useContext, useLayoutEffect, useRef } from 'react'; +import { styled } from 'styled-components'; +import { Bubble, CloseIcon } from '../common/icons/Icons'; +import { BodyKrRegular3, HeadingKrMedium7 } from '../../styles/typefaces'; +import CenterWrapper from '../layout/CenterWrapper'; +import { DimmedBackground } from './DimmedBackground'; +import { GuideModalContext } from '../../context/GuideMoadlContext'; +import { useLocation } from 'react-router-dom'; +import { PATH } from '../../utils/constants'; + +interface IGuideModal extends HTMLAttributes {} +export default function GuideModal({ ...props }: IGuideModal) { + const guideBubbleRef = useRef(null); + const hmgDataBgRef = useRef(null); + const { visible, setVisible } = useContext(GuideModalContext); + const { pathname } = useLocation(); + + const stopEvent: MouseEventHandler = (e) => { + e.stopPropagation(); + }; + + useLayoutEffect(() => { + if (!(pathname === PATH.home || pathname === PATH.trim)) { + setVisible(false); + } + }, [pathname, setVisible]); + + return ( + + + + + +
+ + 현대자동차만이 +
+ 제공하는 실활용 데이터
+ 합리적인 차량을 만들어 보세요. +
+ setVisible(false)}> + + +
+ + + HMG Data 마크는 Hyundai Motor Group에서만 제공하는 데이터입니다. +
주행 중 운전자들이 실제로 얼마나 활용하는지를 추적해 수치화한 데이터 입니다. +
+
+
+ +
+
+ ); +} + +const Wrapper = styled(CenterWrapper)` + width: 1280px; +`; + +const Header = styled.div` + display: flex; + align-items: flex-start; + justify-content: space-between; +`; +const CloseBtn = styled.button``; +const HmgDataBg = styled.div` + margin-top: 20px; + margin-left: 111px; + width: 316px; + height: 175px; + background-color: white; +`; +const GuideBubble = styled.div` + z-index: 10000; + width: 323px; + height: 206px; + top: 226px; + left: 440px; + position: relative; +`; + +const GuideText = styled.div` + width: 100%; + height: 100%; + padding: 17px 34px 17px 17px; + display: flex; + flex-direction: column; + justify-content: space-evenly; + position: absolute; + top: 0; + left: 17px; +`; + +const GuideTitle = styled.div` + width: 214px; + ${HeadingKrMedium7} +`; +const BlueText = styled.span` + color: ${({ theme }) => theme.color.activeBlue}; +`; +const Separator = styled.div` + width: 41px; + height: 1px; + margin: 16px 0; + background-color: ${({ theme }) => theme.color.primaryColor200}; +`; +const GuideDesc = styled.div` + width: 266px; + ${BodyKrRegular3} + color: ${({ theme }) => theme.color.primaryColor}; +`; diff --git a/frontend/src/components/modal/SimilarQuoteModal.tsx b/frontend/src/components/modal/SimilarQuoteModal.tsx new file mode 100644 index 0000000..8c025c4 --- /dev/null +++ b/frontend/src/components/modal/SimilarQuoteModal.tsx @@ -0,0 +1,248 @@ +import { HTMLAttributes, MouseEventHandler, useContext } from 'react'; +import { styled, useTheme } from 'styled-components'; +import { ArrowLeft, ArrowRight, CloseIcon } from '../common/icons/Icons'; +import { + BodyKrMedium3, + BodyKrMedium5, + BodyKrRegular4, + BodyKrRegular5, + HeadingKrBold6, + HeadingKrMedium6, +} from '../../styles/typefaces'; +import { flexCenterCss } from '../../utils/commonStyle'; +import ExtraOptionCard from '../cards/ExtraOptionCard'; +import HmgTag from '../common/hmgTag/HmgTag'; +import RectButton from '../common/buttons/RectButton'; +import { DimmedBackground } from './DimmedBackground'; +import { SimilarQuoteModalContext } from '../../context/SimilarQuoteModalContext'; +import SimilarPriceBar from '../priceStaticBar/SimilarPriceBar'; + +interface ISimilarQuoteModal extends HTMLAttributes {} + +export default function SimilarQuoteModal({ ...props }: ISimilarQuoteModal) { + const theme = useTheme(); + const { visible, setVisible } = useContext(SimilarQuoteModalContext); + const stopEvent: MouseEventHandler = (e) => { + e.stopPropagation(); + }; + return ( + + +
+ setVisible(false)}> + + +
+ + + + 내 견적과 비슷한 실제 출고 견적들을 확인하고 비교해보세요. + + + *유사 출고 견적이란, +
내 견적과 해시태그 유사도가 높은 다른 사람들의 실제 출고 견적이에요. +
+
+ +
+ + + + + + + 첫번째 유사견적서 + Le Blanc + + 디젤 2.2 + 7인승 + 2WD + + 41,980,000 원 + + 560,000원 + + + + + + + + +

내 견적에 없는 옵션이에요.

+ + null} + > + null} + > + +
+ + + +
+
+ 옵션을 선택해 추가해보세요. +
+
+ ); +} + +const Modal = styled.div` + position: relative; + display: flex; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + + width: 850px; + height: 520px; + border-radius: 8px; + background: ${({ theme }) => theme.color.white}; + + padding: 20px 20px 72px 20px; + display: flex; + flex-direction: column; +`; + +const Header = styled.div` + display: flex; + justify-content: flex-end; +`; +const CloseBtn = styled.button``; + +const InfoWrapper = styled.div` + padding: 10px 24px; + display: flex; + justify-content: space-between; +`; +const TextWrapper = styled.div` + display: flex; + flex-direction: column; + justify-content: space-between; + width: 367px; + gap: 15px; +`; +const TitleText = styled.div` + width: 223px; + ${HeadingKrMedium6} +`; +const BlueText = styled.span` + color: ${({ theme }) => theme.color.activeBlue2}; +`; + +const DescText = styled.div` + ${BodyKrRegular4} + color: ${({ theme }) => theme.color.primaryColor}; +`; + +const CardWrapper = styled.div` + position: relative; + width: 100%; + height: 100%; + margin-top: 25px; + border: 1px solid ${({ theme }) => theme.color.skyBlue}; + border-radius: 2px; +`; + +const CarInfo = styled.div` + display: flex; + border-radius: 2px; + height: 100%; + align-items: center; +`; +const OptionInfo = styled.div` + display: flex; + position: absolute; + top: 0; + right: 0; + width: 275px; + height: 100%; + border-radius: 0px 1px 1px 0px; + background-color: ${({ theme }) => theme.color.gray50}; + padding: 0 16px; + + p { + color: ${({ theme }) => theme.color.gray900}; + ${BodyKrMedium5}; + padding-bottom: 4px; + } +`; + +const OptionSection = styled.div` + margin-top: 60px; +`; +const HmgTagWrapper = styled.div` + position: absolute; + top: 29px; + left: 0; +`; +const OptionCardWrapper = styled.div` + display: flex; + gap: 8px; +`; + +const IconBtn = styled.button` + padding: 8px; +`; + +const InfoSection = styled.div` + padding: 0 20px 0 8px; +`; + +const OrderInfo = styled.div` + color: ${({ theme }) => theme.color.gray900}; + ${BodyKrRegular5} +`; + +const TrimTitle = styled.p` + color: ${({ theme }) => theme.color.primaryColor700}; + ${HeadingKrBold6} +`; +const TypeTagWrapper = styled.div` + display: flex; + gap: 10px; + height: 16px; +`; +const TypeTag = styled.div` + ${flexCenterCss} + ${BodyKrRegular5} + padding: 0px 4px; + background: rgba(117, 117, 117, 0.5); + color: ${({ theme }) => theme.color.gray50}; +`; +const TotalPrice = styled.p` + color: ${({ theme }) => theme.color.primaryColor700}; + ${BodyKrMedium3} +`; +const Difference = styled.span` + color: ${({ theme }) => theme.color.sand}; + ${BodyKrRegular5}; +`; + +const ImgWrapper = styled.div` + width: 274px; + height: 156px; + background-image: url('images/similar_quote/similar1.png'); + background-size: cover; + background-repeat: no-repeat; + background-position: center; +`; + +const TmpBtn = styled(RectButton)` + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 52px; + padding: 14px 0; + border-radius: 0 0 8px 8px; + background-color: ${({ theme }) => theme.color.gray300}; +`; diff --git a/frontend/src/components/pageAnimation/AnimationPresence.tsx b/frontend/src/components/pageAnimation/AnimationPresence.tsx new file mode 100644 index 0000000..eab3e49 --- /dev/null +++ b/frontend/src/components/pageAnimation/AnimationPresence.tsx @@ -0,0 +1,109 @@ +import { + Children, + HTMLAttributes, + Key, + ReactElement, + ReactNode, + cloneElement, + isValidElement, + useEffect, + useRef, + useState, +} from 'react'; + +interface IAnimatePresence extends HTMLAttributes { + children: ReactNode; +} +interface IElementByKeyMap { + [key: Key]: ReactElement; +} + +export default function AnimatePresence({ children }: IAnimatePresence) { + const validChildren = getAllValidChildren(children); + const childrenOfPreviousRender = useRef(validChildren); + const elementByKey = useRef(getElementByKeyMap(validChildren, {})); + const [_, forceRender] = useState(0); + const isSamePage = childrenOfPreviousRender.current[0].key === validChildren[0].key; + const animatePresence = !isSamePage; + /** + * 렌더링이 끝난 이후, 렌더링 전의 컴포넌트를 참조하여 기억 + * elementByKey는 직전 렌더링과 직후 렌더링 되는 컴포넌트 기억 + */ + useEffect(() => { + childrenOfPreviousRender.current = validChildren; + elementByKey.current = getElementByKeyMap(validChildren, elementByKey.current); + }); + + /** + * 언마운트 대상 컴포넌트 찾기 + */ + const currentKeys = validChildren.map((element: ReactElement) => { + return element.key; + }); + const prevKeys = childrenOfPreviousRender.current.map((element: ReactElement) => { + return element.key; + }); + const removedChildrenKey = new Set(prevKeys.filter((key) => !currentKeys.includes(key))); + + /** + * 왼쪽 애니메이션? 오른쪽 애니메이션? + */ + const isLeft = useRef(true); + if (prevKeys[0] && currentKeys[0] && removedChildrenKey.size) { + isLeft.current = parseInt(prevKeys[0]?.toString()) < parseInt(currentKeys[0].toString()); + } + const childrenToRender = validChildren.map((child) => + cloneElement(child, { isVisible: true, isLeft: isLeft.current, animation: animatePresence }) + ); + + /** + * isVisible 를 통 + * 해 사라질 컴포넌트 구분 + */ + removedChildrenKey.forEach((removedKey) => { + if (!removedKey) return; + + const onExitAnimationDone = () => { + removedChildrenKey.delete(removedKey); + if (!removedChildrenKey.size) { + forceRender((cur) => cur + 1); + } + }; + + const element = elementByKey.current[removedKey]; + const elementIndex = prevKeys.indexOf(removedKey); + childrenToRender.splice( + elementIndex, + 0, + cloneElement(element, { + isVisible: false, + onExitAnimationDone, + isLeft: isLeft.current, + animation: true, + }) + ); + }); + + return <>{childrenToRender}; +} + +function getElementByKeyMap(validchildren: Array, map: IElementByKeyMap) { + const result = validchildren.reduce((acc, child) => { + const key = child.key ? child.key : 'defaultKey'; + acc[key] = child; + return acc; + }, map); + + return result; +} + +function getAllValidChildren(elements: ReactNode) { + const validChildren: Array = []; + Children.forEach(elements, (element) => { + if (isValidElement(element)) { + validChildren.push(element); + } + }); + + return validChildren; +} diff --git a/frontend/src/components/pageAnimation/PageAnimationWrapper.tsx b/frontend/src/components/pageAnimation/PageAnimationWrapper.tsx new file mode 100644 index 0000000..b14b145 --- /dev/null +++ b/frontend/src/components/pageAnimation/PageAnimationWrapper.tsx @@ -0,0 +1,107 @@ +import { useEffect, useRef } from 'react'; +import { styled } from 'styled-components'; +import { PAGE_ANIMATION_DURATION } from '../../utils/constants'; + +export interface IDefaultPage { + onExitAnimationDone?: () => void; + isVisible?: boolean; + isLeft?: boolean; + animation?: boolean; +} + +interface IPageAnimationWrapper extends IDefaultPage, React.HTMLAttributes {} +export default function PageAnimationWrapper({ + onExitAnimationDone, + isVisible = true, + isLeft, + children, + animation = false, +}: IPageAnimationWrapper) { + const wrapperRef = useRef(null); + + useEffect(() => { + if (!animation) { + return; + } + const visibleAnimation = isLeft ? visibleLeftAnimation : visibleRightAnimation; + const unvisibleAnimation = isLeft ? unvisibleLeftAnimation : unvisibleRightAnimation; + + if (isVisible) { + const animate = wrapperRef.current?.animate( + visibleAnimation.keyframes, + visibleAnimation.option + ); + + return () => animate?.cancel(); + } else { + const animate = wrapperRef.current?.animate( + unvisibleAnimation.keyframes, + unvisibleAnimation.option + ); + + animate?.finished.then(onExitAnimationDone).catch((err) => console.log(err)); + return () => animate?.cancel(); + } + }, [isVisible, onExitAnimationDone, isLeft, animation]); + + return {children}; +} + +const Wrapper = styled.div` + opacity: 1; + position: absolute; + top: 60px; + right: 0; + width: 100vw; +`; + +interface IAnimation { + keyframes: Keyframe[]; + option: KeyframeAnimationOptions; +} + +const visibleLeftAnimation: IAnimation = { + keyframes: [ + { right: '-100%', opacity: 0 }, + { right: 0, opacity: 1 }, + ], + option: { + duration: PAGE_ANIMATION_DURATION, + easing: 'ease', + }, +}; + +const unvisibleLeftAnimation: IAnimation = { + keyframes: [ + { right: 0, opacity: 1 }, + { right: '100%', opacity: 0 }, + ], + option: { + duration: PAGE_ANIMATION_DURATION, + easing: 'ease', + fill: 'forwards', + }, +}; + +const visibleRightAnimation: IAnimation = { + keyframes: [ + { left: '-100%', opacity: 0 }, + { left: 0, opacity: 1 }, + ], + option: { + duration: PAGE_ANIMATION_DURATION, + easing: 'ease', + }, +}; + +const unvisibleRightAnimation: IAnimation = { + keyframes: [ + { left: 0, opacity: 1 }, + { left: '100%', opacity: 0 }, + ], + option: { + duration: PAGE_ANIMATION_DURATION, + easing: 'ease', + fill: 'forwards', + }, +}; diff --git a/frontend/src/components/priceStaticBar/PriceStaticBar.tsx b/frontend/src/components/priceStaticBar/PriceStaticBar.tsx new file mode 100644 index 0000000..4a0264f --- /dev/null +++ b/frontend/src/components/priceStaticBar/PriceStaticBar.tsx @@ -0,0 +1,136 @@ +import { ChangeEvent, useCallback, useEffect, useState } from 'react'; +import { css, keyframes, styled, useTheme } from 'styled-components'; +import { BodyKrRegular4, HeadingKrMedium6 } from '../../styles/typefaces'; +import { ArrowUp, ArrowDown } from '../common/icons/Icons'; +import React from 'react'; +import { useLocation } from 'react-router-dom'; +import { PATH } from '../../utils/constants'; +import PriceStaticSlider from './PriceStaticSlider'; + +interface IPriceStaticBar extends React.HTMLAttributes {} +export default function PriceStaticBar({ ...props }: IPriceStaticBar) { + const { pathname } = useLocation(); + const theme = useTheme(); + const lowestPrice = 3850; //단위: 만원 + const highestPrice = 4300; + const total = 4100; + const [budget, setBudget] = useState((lowestPrice + highestPrice) / 2); + + const [isOverBudget, setIsOverBudget] = useState(false); + const [isOpen, setIsOpen] = useState(false); + const balance = ((isOverBudget ? -10000 : 10000) * (budget - total)).toLocaleString(); + const getBudgetStatus = useCallback(() => { + const status = budget - total; + status >= 0 ? setIsOverBudget(false) : setIsOverBudget(true); + }, [budget]); + const handleChange = (event: ChangeEvent) => { + const newValue = Number(event.target.value); + setBudget(newValue); + }; + + useEffect(() => { + getBudgetStatus(); + }, [budget, getBudgetStatus]); + + if (pathname === PATH.home || pathname === PATH.trim) { + return <>; + } + return ( + + + 예산 범위 + + {isOverBudget ? '설정한 예산보다 ' : '설정한 예산까지 '} + {balance}원 + {isOverBudget ? ' 더 들었어요.' : ' 남았어요.'} + + + setIsOpen(!isOpen)}> + {isOpen ? : } + + + + + + + + ); +} + +const fadeIn = keyframes` + from { + opacity: 0; + } + to { + opacity: 1; + } +`; + +const withinBudgetCss = css` + background: ${({ theme }) => theme.color.primaryColor700}; +`; + +const overBudgetCss = css` + background: rgba(0, 11, 25, 0.9); +`; + +const StatusBox = styled.div<{ $isover: boolean; $isopen: boolean }>` + ${({ $isover }) => !$isover && withinBudgetCss} + ${({ $isover }) => $isover && overBudgetCss} + position: fixed; + min-width: 343px; + z-index: 10; + top: 76px; + left: 50%; + transform: translateX(-50%); + padding: 8px 16px; + border-radius: 10px; + backdrop-filter: blur(3px); + color: ${({ theme }) => theme.color.gray50}; + overflow: hidden; + height: ${({ $isopen }) => ($isopen ? '110px' : '40px')}; + transition: height 0.3s ease; + display: flex; + flex-direction: column; + justify-content: space-between; +`; + +const StatusText = styled.div` + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; +`; + +const StatusTitle = styled.p` + margin-right: 8px; + ${HeadingKrMedium6} +`; + +const StatusDesc = styled.p<{ $isover: boolean }>` + ${BodyKrRegular4} + flex:1; + text-align: end; + span { + color: ${({ theme, $isover }) => ($isover ? theme.color.sand : theme.color.activeBlue2)}; + } +`; +const AnimatedSection = styled.div<{ $isopen: boolean }>` + display: ${({ $isopen }) => ($isopen ? 'block' : 'none')}; + animation: ${({ $isopen }) => + $isopen + ? css` + ${fadeIn} 0.5s ease + ` + : 'none'}; +`; + +const IconBtn = styled.button``; diff --git a/frontend/src/components/priceStaticBar/PriceStaticSlider.tsx b/frontend/src/components/priceStaticBar/PriceStaticSlider.tsx new file mode 100644 index 0000000..bc45132 --- /dev/null +++ b/frontend/src/components/priceStaticBar/PriceStaticSlider.tsx @@ -0,0 +1,134 @@ +import { css, styled } from 'styled-components'; +import { flexCenterCss } from '../../utils/commonStyle'; +import { BodyKrRegular5 } from '../../styles/typefaces'; +import { ChangeEvent } from 'react'; + +interface ISlider extends React.HTMLAttributes { + lowestPrice: number; + highestPrice: number; + isOverBudget: boolean; + budget: number; + total: number; + percent: number; + handleChange: (event: ChangeEvent) => void; +} +export default function Slider({ + lowestPrice, + highestPrice, + isOverBudget, + budget, + total, + percent, + handleChange, + ...props +}: ISlider) { + return ( + + + + + + + + + + {lowestPrice}만원 + {highestPrice}만원 + + + ); +} + +const PriceBarWrapper = styled.div` + padding-top: 28px; + padding-bottom: 4px; +`; + +const MarkerSvgWrapper = styled.div` + height: 100%; + position: relative; +`; + +const MarkerSvg = styled.svg<{ $isover: boolean; $percent: number }>` + pointer-events: none; + position: absolute; + width: 9px; + height: 23px; + top: 6px; + fill: ${({ theme, $isover }) => ($isover ? theme.color.sand : theme.color.primaryColor400)}; + left: ${({ $percent }) => $percent}%; + transform: translate(-50%, -50%); +`; + +const PriceBar = styled.input.attrs<{ $percent: number; $isover: boolean }>( + ({ type, min, max, value, onChange, step }) => ({ + type: type, + min: min, + max: max, + value: value, + onChange: onChange, + step: step, + }) +)` + &, + &::-webkit-slider-runnable-track, + &::-webkit-slider-thumb { + -webkit-appearance: none; + } + width: 100%; + height: 6px; + border-radius: 4px; + + ${({ $percent, $isover, theme }) => { + return css` + background: linear-gradient( + to right, + #fff 0%, + #fff ${$percent}%, + ${$isover ? theme.color.gray800 : theme.color.primaryColor800} ${$percent}%, + ${$isover ? theme.color.gray800 : theme.color.primaryColor800} 100% + ); + `; + }} + + &::-webkit-slider-runnable-track { + cursor: pointer; + width: 100%; + height: 6px; + border-radius: 4px; + } + + &::-webkit-slider-thumb { + cursor: pointer; + width: 20px; + height: 20px; + background: ${({ $isover, theme }) => + $isover ? theme.color.sand : theme.color.primaryColor400}; + border-radius: 50%; + cursor: pointer; + border: 1px solid #fff; + margin-top: -6px; + } +`; + +const PriceInfo = styled.div<{ $isover: boolean }>` + ${flexCenterCss}; + justify-content: space-between; + span { + padding-top: 4px; + ${BodyKrRegular5} + color: ${({ theme, $isover }) => ($isover ? theme.color.gray200 : theme.color.primaryColor200)}; + } +`; diff --git a/frontend/src/components/priceStaticBar/SimilarPriceBar.tsx b/frontend/src/components/priceStaticBar/SimilarPriceBar.tsx new file mode 100644 index 0000000..36fcccb --- /dev/null +++ b/frontend/src/components/priceStaticBar/SimilarPriceBar.tsx @@ -0,0 +1,91 @@ +import { ChangeEvent, useCallback, useEffect, useState } from 'react'; +import { css, styled } from 'styled-components'; +import { flexCenterCss } from '../../utils/commonStyle'; +import { BodyKrRegular4, HeadingKrMedium6 } from '../../styles/typefaces'; +import React from 'react'; +import SimilarPriceSlider from './SimilarPriceSlider'; + +interface ISimilarPrice extends React.HTMLAttributes {} + +export default function SimilarPrice({ ...props }: ISimilarPrice) { + const lowestPrice = 3850; //단위: 만원 + const highestPrice = 4300; + const total = 4100; + const [budget, setBudget] = useState((lowestPrice + highestPrice) / 2); + const [isOverBudget, setIsOverBudget] = useState(false); + const balance = ((isOverBudget ? -10000 : 10000) * (budget - total)).toLocaleString(); + const getBudgetStatus = useCallback(() => { + const status = budget - total; + status >= 0 ? setIsOverBudget(false) : setIsOverBudget(true); + }, [budget]); + const handleChange = (event: ChangeEvent) => { + const newValue = Number(event.target.value); + setBudget(newValue); + }; + + useEffect(() => { + getBudgetStatus(); + }, [budget, getBudgetStatus]); + + return ( + + + 유사견적 가격 + + 내 견적 보다   + {balance}원 + {isOverBudget ? ' 비싸요.' : ' 싸요.'} + + + + + ); +} +const withinBudgetCss = css` + background: ${({ theme }) => theme.color.primaryColor700}; + #price-info { + color: ${({ theme }) => theme.color.activeBlue2}; + } +`; + +const overBudgetCss = css` + background: rgba(0, 11, 25, 0.9); + #price-info { + color: ${({ theme }) => theme.color.sand}; + } +`; + +const StatusBox = styled.div<{ $isover: boolean }>` + ${({ $isover }) => !$isover && withinBudgetCss} + ${({ $isover }) => $isover && overBudgetCss} + min-width: 343px; + padding: 8px 16px; + border-radius: 10px; + backdrop-filter: blur(3px); + color: ${({ theme }) => theme.color.gray50}; +`; + +const StatusText = styled.div` + width: 100%; + ${flexCenterCss}; + justify-content: space-between; +`; + +const StatusTitle = styled.span` + margin-right: 8px; + ${HeadingKrMedium6} +`; + +const StatusDesc = styled.span` + ${BodyKrRegular4} + flex:1; + text-align: end; +`; diff --git a/frontend/src/components/priceStaticBar/SimilarPriceSlider.tsx b/frontend/src/components/priceStaticBar/SimilarPriceSlider.tsx new file mode 100644 index 0000000..5a1d075 --- /dev/null +++ b/frontend/src/components/priceStaticBar/SimilarPriceSlider.tsx @@ -0,0 +1,113 @@ +import { styled } from 'styled-components'; +import { flexCenterCss } from '../../utils/commonStyle'; +import { BodyKrRegular5 } from '../../styles/typefaces'; +import { ChangeEvent } from 'react'; + +interface INonameS extends React.HTMLAttributes { + lowestPrice: number; + highestPrice: number; + isOverBudget: boolean; + budget: number; + total: number; + percent: number; + handleChange: (event: ChangeEvent) => void; +} +export default function NonameS({ + lowestPrice, + highestPrice, + isOverBudget, + budget, + total, + percent, + handleChange, + ...props +}: INonameS) { + return ( + + + + + + + + + + + + {lowestPrice}만원 + {highestPrice}만원 + + + ); +} + +const PriceBarWrapper = styled.div` + margin: 0px 4px; + padding-top: 34px; + padding-bottom: 8px; +`; + +const MarkerSvgWrapper = styled.div` + height: 100%; + position: relative; +`; + +const MarkerSvg = styled.svg<{ $isover: boolean; $percent: number }>` + pointer-events: none; + position: absolute; + width: 9px; + height: 23px; + top: 6px; + fill: ${({ theme, $isover }) => ($isover ? theme.color.sand : theme.color.primaryColor400)}; + left: ${({ $percent }) => $percent}%; + transform: translate(-50%, -50%); +`; + +const PriceBar = styled.input.attrs<{ $percent: number; $isover: boolean }>( + ({ type, min, max, value, onChange, step }) => ({ + type: type, + min: min, + max: max, + value: value, + onChange: onChange, + step: step, + }) +)` + &, + &::-webkit-slider-runnable-track, + &::-webkit-slider-thumb { + -webkit-appearance: none; + } + width: 100%; + height: 6px; + border-radius: 4px; + + &::-webkit-slider-runnable-track { + cursor: pointer; + width: 100%; + height: 6px; + border-radius: 4px; + } +`; + +const PriceInfo = styled.div` + ${flexCenterCss}; + justify-content: space-between; + span { + padding-top: 4px; + ${BodyKrRegular5} + color: ${({ theme }) => theme.color.primaryColor200}; + } +`; diff --git a/frontend/src/components/router/CustomRouter.tsx b/frontend/src/components/router/CustomRouter.tsx new file mode 100644 index 0000000..a8f72a0 --- /dev/null +++ b/frontend/src/components/router/CustomRouter.tsx @@ -0,0 +1,54 @@ +import { useLocation } from 'react-router-dom'; +import ExteriorPage from '../../pages/ExteriorPage'; +import AnimatePresence from '../pageAnimation/AnimationPresence'; +import PageAnimationWrapper from '../pageAnimation/PageAnimationWrapper'; +import OptionPage from '../../pages/OptionPage'; +import InteriorPage from '../../pages/InteriorPage'; +import ResultPage from '../../pages/ResultPage'; +import TrimPage from '../../pages/TrimPage'; +import ModelTypePage from '../../pages/ModelTypePage'; +import { PATH } from '../../utils/constants'; + +export default function CustomRouter() { + const { pathname } = useLocation(); + + return ( + + {pathname === PATH.home && ( + + + + )} + {pathname === PATH.trim && ( + + + + )} + {pathname === PATH.modelType && ( + + + + )} + {pathname === PATH.exterior && ( + + + + )} + {pathname === PATH.interior && ( + + + + )} + {pathname === PATH.option && ( + + + + )} + {pathname === PATH.result && ( + + + + )} + + ); +} diff --git a/frontend/src/components/searchBar/SearchBar.tsx b/frontend/src/components/searchBar/SearchBar.tsx new file mode 100644 index 0000000..1a9d0a1 --- /dev/null +++ b/frontend/src/components/searchBar/SearchBar.tsx @@ -0,0 +1,46 @@ +import { styled } from 'styled-components'; +import { BodyKrRegular4 } from '../../styles/typefaces'; +import { SearchIcon } from '../common/icons/Icons'; + +interface SearchBarProps extends React.HTMLAttributes {} +export default function SearchBar({ ...props }: SearchBarProps) { + return ( + + + + + ); +} + +const Wrapper = styled.div` + display: flex; + width: 400px; + height: 32px; + border: 1px solid ${({ theme }) => theme.color.gray200}; +`; + +const Input = styled.input` + padding: 4px 16px; + ${BodyKrRegular4} + width: 100%; + height: 100%; + border: none; + box-sizing: border-box; + outline: none; + color: ${({ theme }) => theme.color.gray900}; + + :focus { + outline: none; + border: none; + } + ::placeholder { + color: ${({ theme }) => theme.color.gray600}; + } +`; +const Button = styled.button` + width: 67px; + height: 100%; + background-color: ${({ theme }) => theme.color.gray100}; +`; diff --git a/frontend/src/components/summary/PriceSummary.tsx b/frontend/src/components/summary/PriceSummary.tsx new file mode 100644 index 0000000..d0afd81 --- /dev/null +++ b/frontend/src/components/summary/PriceSummary.tsx @@ -0,0 +1,41 @@ +import { styled } from 'styled-components'; +import RectButton from '../common/buttons/RectButton'; +import RoundButton from '../common/buttons/RoundButton'; +import { BodyKrRegular4, HeadingKrMedium2 } from '../../styles/typefaces'; + +interface IPriceSummary extends React.HTMLAttributes {} + +export default function PriceSummary({ ...props }: IPriceSummary) { + const total = 43_560_000; + return ( + + + 견적 요약 + + 현재 총 가격{total.toLocaleString()} 원 + + + 다음 + + ); +} + +const SummaryWrapper = styled.div` + display: flex; + flex-direction: column; + justify-content: space-around; + height: 114px; +`; +const InfoWrapper = styled.div` + display: flex; + justify-content: space-around; +`; +const TotalPriceText = styled.span` + ${BodyKrRegular4} + color: ${({ theme }) => theme.color.gray700}; +`; +const HighLightText = styled.span` + ${HeadingKrMedium2} + padding-left: 8px; + color: ${({ theme }) => theme.color.primaryColor}; +`; diff --git a/frontend/src/components/tabs/OptionTab.tsx b/frontend/src/components/tabs/OptionTab.tsx new file mode 100644 index 0000000..f292996 --- /dev/null +++ b/frontend/src/components/tabs/OptionTab.tsx @@ -0,0 +1,158 @@ +import { HTMLAttributes, useEffect, useRef, useState } from 'react'; +import { BodyKrMedium3, BodyKrRegular3 } from '../../styles/typefaces'; +import styled, { css, useTheme } from 'styled-components'; +import { ArrowLeft, ArrowRight } from '../common/icons/Icons'; +import { NUM_IN_A_PAGE } from '../../utils/constants'; + +interface ISubOptionTab extends HTMLAttributes { + options: string[]; +} + +export default function OptionTab({ options }: ISubOptionTab) { + const theme = useTheme(); + const TAB_MAX_PAGE = options.length / NUM_IN_A_PAGE; + const [selectedIdx, setSelectedIdx] = useState(0); + const [page, setPage] = useState(0); + const arrowLeftColor = page <= 0 ? theme.color.gray200 : theme.color.gray600; + const arrowRightColor = page >= TAB_MAX_PAGE - 1 ? theme.color.gray200 : theme.color.gray600; + const tabDivisionRef = useRef(null); + const [tabDivisionWidth, setTabDivisionWidth] = useState(0); + + useEffect(() => { + if (tabDivisionRef.current) { + const tabDivisionWidth = tabDivisionRef.current.offsetWidth; + setTabDivisionWidth(tabDivisionWidth); + } + }, [tabDivisionRef]); + + const displayUnderline = (groupIndex: number, index: number) => { + return page === groupIndex && index === selectedIdx ? ( + + ) : ( + + ); + }; + const handleOffsetNext = () => { + if (page + 1 >= TAB_MAX_PAGE) return; + setSelectedIdx(0); + setPage(page + 1); + }; + const handleOffsetPrev = () => { + if (page - 1 < 0) return; + setSelectedIdx(0); + setPage(page - 1); + }; + + const handleOptionClick = (index: number) => { + setSelectedIdx(index); + }; + + const chunkArray = (array: string[], size: number) => { + const result = []; + for (let i = 0; i < array.length; i += size) { + result.push(array.slice(i, i + size)); + } + return result; + }; + const chunkedOptions = chunkArray(options, NUM_IN_A_PAGE); + + return ( + + + + + + + {chunkedOptions.map((optionGroup: string[], groupIndex) => ( + + {optionGroup.map((option: string, index: number) => ( + handleOptionClick(index)} + $isselected={page === groupIndex && index === selectedIdx} + > +
{option}
+ {displayUnderline(groupIndex, index)} +
+ ))} +
+ ))} +
+
+ = TAB_MAX_PAGE - 1 ? 'default' : 'pointer' }} + > + + +
+ ); +} + +const TabWrapper = styled.div` + width: 488px; + display: flex; + align-items: center; + justify-content: space-between; + margin-top: 10px; + height: 40px; +`; + +const BtnWrapper = styled.button` + z-index: 10; +`; +const Tab = styled.div<{ $offset: number }>` + display: flex; + + transition: transform 1s ease; + transform: translateX(${({ $offset }) => $offset}px); +`; +const TabWrapperInner = styled.div` + overflow: hidden; + width: 408px; +`; +const TabDivision = styled.ul` + display: flex; + justify-content: space-between; + width: 408px; + padding: 0 16px; +`; + +const TabButton = styled.div<{ $isselected: boolean }>` + display: flex; + overflow: hidden; + align-items: center; + flex-direction: column; + gap: 4px; + width: 78px; + margin: 0 8px; + height: 28px; + cursor: pointer; + ${({ theme, $isselected }) => { + if ($isselected) { + return css` + ${BodyKrMedium3} + color: ${theme.color.gray800}; + `; + } else { + return css` + ${BodyKrRegular3} + color: ${theme.color.gray400}; + `; + } + }} + + div:first-child { + width: 100%; + text-align: center; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } +`; + +const Underline = styled.div` + width: 58px; + height: 2px; + background-color: ${({ theme }) => theme.color.gray800}; +`; diff --git a/frontend/src/containers/ExteriorPage/ExteriorSelectContainer.tsx b/frontend/src/containers/ExteriorPage/ExteriorSelectContainer.tsx new file mode 100644 index 0000000..aadefa3 --- /dev/null +++ b/frontend/src/containers/ExteriorPage/ExteriorSelectContainer.tsx @@ -0,0 +1,69 @@ +import { ReactNode, useState } from 'react'; +import { styled } from 'styled-components'; +import CenterWrapper from '../../components/layout/CenterWrapper'; +import PriceSummary from '../../components/summary/PriceSummary'; +import ExteriorCard from '../../components/cards/ExteriorCard'; +import CardSlider from '../../components/cardSlider/CardSlider'; +import { MAX_PAGE, NUM_IN_A_PAGE } from '../../utils/constants'; + +interface ISelected { + page: number; + idx: number; +} + +export default function ExteriorSelectContainer() { + const [selectedIdx, setSelectedIdx] = useState({ page: 0, idx: 0 }); + const cardIndices = Array.from({ length: NUM_IN_A_PAGE }, (_, index) => index + 1); + + const handleSelectedIdx = ({ page, idx }: ISelected) => { + setSelectedIdx({ page, idx }); + }; + const isActive = ({ page, idx }: ISelected) => { + return page === selectedIdx.page && idx === selectedIdx.idx; + }; + + const CardPageList: ReactNode[] = []; + for (let i = 0; i < MAX_PAGE; i++) { + const newCardPage = ( + + {cardIndices.map((idx) => ( + handleSelectedIdx({ page: i, idx })} + color="black" + desc="38%가 선택했어요" + name={`블랙_${i}_${idx}`} + price={0} + /> + ))} + + ); + CardPageList.push(newCardPage); + } + + return ( + + +
+ +
+
+ ); +} + +const Wrapper = styled(CenterWrapper)` + display: flex; + flex-direction: column; +`; +const CardPage = styled.div` + display: flex; + justify-content: space-between; + gap: 16px; + margin-top: 12px; +`; +const Footer = styled.div` + margin-top: 36px; + display: flex; + justify-content: flex-end; +`; diff --git a/frontend/src/containers/ExteriorPage/ExtreriorBannerContainer.tsx b/frontend/src/containers/ExteriorPage/ExtreriorBannerContainer.tsx new file mode 100644 index 0000000..b9b9dba --- /dev/null +++ b/frontend/src/containers/ExteriorPage/ExtreriorBannerContainer.tsx @@ -0,0 +1,99 @@ +import { MouseEventHandler, useState } from 'react'; +import { styled } from 'styled-components'; +import Banner from '../../components/common/banner/Banner'; +import CenterWrapper from '../../components/layout/CenterWrapper'; +import { flexCenterCss } from '../../utils/commonStyle'; + +export default function ExteriorBannerContainer() { + const [imgIdx, setImgIdx] = useState(0); + const [isDragging, setIsDragging] = useState(false); + const [startX, setStartX] = useState(0); + const [startIdx, setStartIdx] = useState(0); + + const handleMousedown: MouseEventHandler = ({ pageX }) => { + setIsDragging(true); + setStartX(pageX); + setStartIdx(imgIdx); + }; + const handleMousemove: MouseEventHandler = ({ pageX, currentTarget }) => { + if (!isDragging) return; + const { offsetWidth } = currentTarget; + const moveX = startX - pageX; + const percent = moveX / offsetWidth; + const moveIdx = Math.round(60 * percent); + let resultIdx = startIdx + moveIdx; + if (resultIdx < 0) { + resultIdx += 60; + } + resultIdx %= 60; + setImgIdx(resultIdx); + }; + const handleMouseup: MouseEventHandler = ({ pageX }) => { + setIsDragging(false); + setStartX(pageX); + }; + + return ( + <> + + + + + + 360° + + + + + + ); +} + +const ExteriorBanner = styled(Banner)` + background: ${({ theme }) => theme.color.blueBg}; +`; +const CarImg = styled.img` + position: absolute; + right: 0; + width: 592px; + height: auto; + -webkit-user-drag: none; + z-index: 2; +`; +const ImgWrapper = styled.div` + width: 611px; + cursor: pointer; + position: relative; + height: 325px; + bottom: 0; +`; +const FlexCenterWrapper = styled(CenterWrapper)` + ${flexCenterCss} + align-items: flex-end; + height: 100%; +`; +const CarShadow = styled.div` + ${flexCenterCss} + position: absolute; + bottom: 34px; + border: 2px solid transparent; + border-radius: 50%; + width: 611px; + height: 99px; + background-image: linear-gradient( + ${({ theme }) => theme.color.blueBg}, + ${({ theme }) => theme.color.blueBg} + ), + linear-gradient(to top, #6d7786, #6d778600); + background-origin: border-box; + background-clip: content-box, border-box; +`; + +const DegreeCaption = styled.span` + position: absolute; + bottom: -10px; + z-index: 10; + width: 61px; + text-align: center; + background-color: ${({ theme }) => theme.color.blueBg}; +`; diff --git a/frontend/src/containers/InteriorPage/InteriorBannerContainer.tsx b/frontend/src/containers/InteriorPage/InteriorBannerContainer.tsx new file mode 100644 index 0000000..403937c --- /dev/null +++ b/frontend/src/containers/InteriorPage/InteriorBannerContainer.tsx @@ -0,0 +1,21 @@ +import { styled } from 'styled-components'; +import Banner from '../../components/common/banner/Banner'; + +export default function InteriorBannerContainer() { + return ( + <> + {' '} + + ); +} + +const InteriorBanner = styled(Banner)` + overflow: hidden; + padding: 0; + p { + color: ${({ theme }) => theme.color.white}; + } + background-image: url('images/inner_car.png'); + background-position: 0% 30%; + background-size: cover; +`; diff --git a/frontend/src/containers/InteriorPage/InteriorSelectContainer.tsx b/frontend/src/containers/InteriorPage/InteriorSelectContainer.tsx new file mode 100644 index 0000000..23335a1 --- /dev/null +++ b/frontend/src/containers/InteriorPage/InteriorSelectContainer.tsx @@ -0,0 +1,72 @@ +import { ReactNode, useState } from 'react'; +import { styled } from 'styled-components'; +import CenterWrapper from '../../components/layout/CenterWrapper'; +import PriceSummary from '../../components/summary/PriceSummary'; +import InteriorCard from '../../components/cards/InteriorCard'; +import CardSlider from '../../components/cardSlider/CardSlider'; +import { MAX_PAGE, NUM_IN_A_PAGE } from '../../utils/constants'; + +interface ISelected { + page: number; + idx: number; +} + +export default function InteriorSelectContainer() { + const [selectedIdx, setSelectedIdx] = useState({ page: 0, idx: 0 }); + + const cardIndices = Array.from({ length: NUM_IN_A_PAGE }, (_, index) => index + 1); + + const handleSelectedIdx = ({ page, idx }: ISelected) => { + setSelectedIdx({ page, idx }); + }; + const isActive = ({ page, idx }: ISelected) => { + return page === selectedIdx.page && idx === selectedIdx.idx; + }; + + const CardPageList: ReactNode[] = []; + for (let i = 0; i < MAX_PAGE; i++) { + const newCardPage: ReactNode = ( + + {cardIndices.map((idx) => ( + handleSelectedIdx({ page: i, idx })} + desc="38%가 선택했어요" + name={`블랙_${i}_${idx}`} + price={0} + /> + ))} + + ); + CardPageList.push(newCardPage); + } + + return ( + + +
+ +
+
+ ); +} + +const Wrapper = styled(CenterWrapper)` + display: flex; + flex-direction: column; +`; +const CardPage = styled.div` + display: flex; + justify-content: space-between; + gap: 16px; + margin-top: 12px; + transition: all 1s; +`; +const Footer = styled.div` + margin-top: 36px; + display: flex; + justify-content: flex-end; +`; diff --git a/frontend/src/containers/Modal/ModalContainer.tsx b/frontend/src/containers/Modal/ModalContainer.tsx new file mode 100644 index 0000000..54f3db8 --- /dev/null +++ b/frontend/src/containers/Modal/ModalContainer.tsx @@ -0,0 +1,21 @@ +import { useContext } from 'react'; +import CloseModal from '../../components/modal/CloseModal'; +import GuideModal from '../../components/modal/GuideModal'; +import SimilarQuoteModal from '../../components/modal/SimilarQuoteModal'; +import { GuideModalContext } from '../../context/GuideMoadlContext'; +import { SimilarQuoteModalContext } from '../../context/SimilarQuoteModalContext'; +import { CloseModalContext } from '../../context/CloseModalContext'; + +export default function ModalContainer() { + const { setVisible: setCloseModalVisible } = useContext(CloseModalContext); + const { setVisible: setGuideModalVisible } = useContext(GuideModalContext); + const { setVisible: setSimilarQuoteModalVisible } = useContext(SimilarQuoteModalContext); + + return ( + <> + setCloseModalVisible(false)} /> + setGuideModalVisible(false)} /> + setSimilarQuoteModalVisible(false)} /> + + ); +} diff --git a/frontend/src/containers/ModelTypePage/ModelBannerContainer.tsx b/frontend/src/containers/ModelTypePage/ModelBannerContainer.tsx new file mode 100644 index 0000000..6981907 --- /dev/null +++ b/frontend/src/containers/ModelTypePage/ModelBannerContainer.tsx @@ -0,0 +1,140 @@ +import { keyframes, styled } from 'styled-components'; +import { BodyKrRegular4, BodyKrRegular5, HeadingKrRegular2 } from '../../styles/typefaces'; +import CenterWrapper from '../../components/layout/CenterWrapper'; +import Banner from '../../components/common/banner/Banner'; +import HmgTag from '../../components/common/hmgTag/HmgTag'; + +export default function ModelBannerContainer() { + return ( + <> + + + + + '높은 토크로 파워풀한 드라이빙이 가능하며, 차급대비 연비 효율이 우수합니다' + + + + + + 최고출력(PS/rpm) + + 202/3,800 + + + + + + + + + + 최대토크(kgf·m/rpm) + + 45/1,750-2,750 + + + + + + + + + + + + + + + + ); +} + +const AdditionalText = styled.p` + width: 207px; + color: ${({ theme }) => theme.color.gray800}; + ${BodyKrRegular4} +`; +const Container = styled(CenterWrapper)` + display: flex; + justify-content: space-between; + height: 100%; +`; + +const InfoWrapper = styled.div` + display: flex; + justify-content: space-evenly; + flex-direction: column; + padding-top: 100px; +`; +const HmgDataSection = styled.div``; + +const DataList = styled.ul` + display: flex; + width: 448px; + margin-top: 16px; + align-items: center; +`; +const Data = styled.li` + width: 100%; + height: 67px; + display: flex; + flex-direction: column; + &:first-child { + padding-right: 24px; + } + + &:last-child { + padding-left: 24px; + } +`; + +const Separator = styled(Data)` + width: 1px; + height: 41px; + background-color: ${({ theme }) => theme.color.gray200}; +`; + +const DataTitle = styled.div` + margin-bottom: 8px; + ${BodyKrRegular5} + color: ${({ theme }) => theme.color.gray600}; +`; +const DataInfo = styled.div` + ${HeadingKrRegular2} +`; +const DataRatio = styled.div` + height: 4px; + background-color: ${({ theme }) => theme.color.gray200}; +`; + +const Ratio = styled.div<{ $current: number; $max: number }>` + height: 4px; + width: ${({ $current, $max }) => ($current / $max) * 100}%; +`; + +const widthKeyframe = keyframes` + from { + width: 0%; + } + to { + width: 100%; + } + +`; + +const Highlight = styled.div` + height: 100%; + + background-color: ${({ theme }) => theme.color.activeBlue2}; + animation: ${widthKeyframe} 0.7s ease; +`; + +const ImgSection = styled.div` + width: 632px; + height: 360px; + background-image: url('/images/model_engine.png'); + background-size: contain; + background-repeat: no-repeat; + background-position: center; +`; diff --git a/frontend/src/containers/ModelTypePage/ModelFooterContainer.tsx b/frontend/src/containers/ModelTypePage/ModelFooterContainer.tsx new file mode 100644 index 0000000..3aef809 --- /dev/null +++ b/frontend/src/containers/ModelTypePage/ModelFooterContainer.tsx @@ -0,0 +1,101 @@ +import { styled } from 'styled-components'; +import { + BodyKrMedium2, + BodyKrMedium3, + BodyKrRegular3, + HeadingKrRegular1, +} from '../../styles/typefaces'; +import PriceSummary from '../../components/summary/PriceSummary'; +import HmgTag from '../../components/common/hmgTag/HmgTag'; +import CenterWrapper from '../../components/layout/CenterWrapper'; + +export default function ModelFooterContainer() { + return ( + + + + + + 디젤 2.22WD의 배기량과 평균연비입니다. + + + + 배기량 + 2,199cc + + + + 평균연비 + 12km/s + + + + + + + ); +} + +const Wrapper = styled(CenterWrapper)` + display: flex; + justify-content: space-between; + padding: 16px 0px; +`; +const HmgDataSection = styled.div` + padding: 0px 48px; + width: 677px; + height: 114px; + background-color: ${({ theme }) => theme.color.blueBg}; +`; + +const HmgInfoWrapper = styled.div` + display: flex; + justify-content: space-between; + margin-top: 12px; +`; + +const HmgTagDescription = styled.div` + ${BodyKrMedium3} + width: 160px; +`; + +const BlueText = styled.span` + ${BodyKrMedium2} + color: ${({ theme }) => theme.color.activeBlue}; +`; + +const DataList = styled.ul` + display: flex; + align-items: center; + margin-right: 24px; +`; +const Data = styled.li` + text-align: center; + display: flex; + flex-direction: column; + gap: 8px; + &:first-child { + padding-right: 32px; + } + + &:last-child { + padding-left: 32px; + } + + &.separator { + width: 1px; + height: 41px; + background-color: ${({ theme }) => theme.color.gray200}; + } +`; + +const DataTitle = styled.div` + ${BodyKrRegular3} + word-break: keep-all; + color: ${({ theme }) => theme.color.gray900}; +`; +const DataInfo = styled.div` + ${HeadingKrRegular1} + color: ${({ theme }) => theme.color.gray900}; + font-size: 28px; +`; diff --git a/frontend/src/containers/ModelTypePage/ModelSelectContainer.tsx b/frontend/src/containers/ModelTypePage/ModelSelectContainer.tsx new file mode 100644 index 0000000..b40c4e3 --- /dev/null +++ b/frontend/src/containers/ModelTypePage/ModelSelectContainer.tsx @@ -0,0 +1,124 @@ +import { styled } from 'styled-components'; +import CenterWrapper from '../../components/layout/CenterWrapper'; +import { HeadingKrMedium6, HeadingKrMedium7 } from '../../styles/typefaces'; +import { useState } from 'react'; +import ModelTypeCard from '../../components/cards/ModelTypeCard'; + +export default function ModelTypelSelectContainer() { + const [selectedTypeIdx, setSelectedTypeIdx] = useState({ + powerTrain: 0, + bodyType: 0, + drivingSystem: 0, + }); + const handleSelectedIdx = (key: string, idx: number) => { + setSelectedTypeIdx((prevSelectedTypeIdx) => ({ + ...prevSelectedTypeIdx, + [key]: idx, + })); + }; + + return ( + + 모델타입을 선택해주세요. + + + 파워트레인 + + {/* Todo. map() 으로 데이터 받아서 만들기! */} + + handleSelectedIdx('powerTrain', 0)} + active={selectedTypeIdx.powerTrain === 0} + desc={'38%의 선택'} + title={'디젤 2.2'} + price={0} + > + handleSelectedIdx('powerTrain', 1)} + active={selectedTypeIdx.powerTrain === 1} + desc={'38%의 선택'} + title={'디젤 2.2'} + price={0} + > + + + + + 바디타입 + + {/* Todo. map() 으로 데이터 받아서 만들기! */} + + handleSelectedIdx('bodyType', 0)} + active={selectedTypeIdx.bodyType === 0} + desc={'38%의 선택'} + title={'디젤 2.2'} + price={0} + > + handleSelectedIdx('bodyType', 1)} + active={selectedTypeIdx.bodyType === 1} + desc={'38%의 선택'} + title={'디젤 2.2'} + price={0} + > + + + + + 구동방식 + + {/* Todo. map() 으로 데이터 받아서 만들기! */} + + handleSelectedIdx('drivingSystem', 0)} + active={selectedTypeIdx.drivingSystem === 0} + desc={'38%의 선택'} + title={'디젤 2.2'} + price={0} + > + handleSelectedIdx('drivingSystem', 1)} + active={selectedTypeIdx.drivingSystem === 1} + desc={'38%의 선택'} + title={'디젤 2.2'} + price={0} + > + + + + + + ); +} + +const Wrapper = styled(CenterWrapper)``; +const Title = styled.div` + ${HeadingKrMedium6} + margin-top: 16px; +`; + +const TypeSection = styled.div` + display: flex; + justify-content: space-between; + padding: 16px 0px; +`; +const TypeWrapper = styled.div``; +const TypeTitle = styled.div` + color: ${({ theme }) => theme.color.gray600}; + ${HeadingKrMedium7}; +`; + +const ModelTypeSection = styled.div` + display: flex; + justify-content: space-between; +`; + +const ModelTypeCardWrapper = styled.div` + display: flex; + justify-content: space-between; + width: 331px; + background: ${({ theme }) => theme.color.gray50}; + padding: 4px; + gap: 5px; +`; diff --git a/frontend/src/containers/OptionPage/OptionBannerContainer.tsx b/frontend/src/containers/OptionPage/OptionBannerContainer.tsx new file mode 100644 index 0000000..8480710 --- /dev/null +++ b/frontend/src/containers/OptionPage/OptionBannerContainer.tsx @@ -0,0 +1,215 @@ +import { styled } from 'styled-components'; +import { + BodyKrMedium4, + BodyKrRegular4, + BodyKrRegular5, + HeadingKrRegular2, +} from '../../styles/typefaces'; +import CenterWrapper from '../../components/layout/CenterWrapper'; +import Banner from '../../components/common/banner/Banner'; +import HmgTag from '../../components/common/hmgTag/HmgTag'; +import OptionTab from '../../components/tabs/OptionTab'; +import { MAX_TEXT_CNT } from '../../utils/constants'; +import { useEffect, useState } from 'react'; + +export default function OptionBannerContainer() { + const suboptions = [ + '후석승객알림', + '메탈 도어스커프', + '3열파워폴딩시트', + '내비게이션기반스마트크루즈컨트롤', + '3열 열선시트', + '헤드업 디스플레이', + '3열 열선시트', + '후석승객알림', + '메탈 리어범퍼스텝', + '후석승객알림', + '내비게이션기반스마트크루즈컨트롤', + '메탈 리어범퍼스텝', + '메탈 도어스커프', + '3열파워폴딩시트', + '내비게이션기반스마트크루즈컨트롤', + '메탈 리어범퍼스텝', + '메탈 도어스커프', + '3열파워폴딩시트', + '내비게이션기반스마트크루즈컨트롤', + ]; + const [isBannerVisible, setIsBannerVisible] = useState(false); + const optionDesc = + '초음파 센서를 통해 뒷좌석에 남아있는 승객의 움직임을 감지하여 운전자에게경고함으로써 부주의에 의한 유아 또는 반려 동물 등의 방치 사고를 예방하는 신기술입니다. 초음파 센서를 통해 뒷좌석에 남아있는 승객의 움직임을 감지하여 운전자에게경고함으로써 부주의에 의한 유아 또는 반려 동물 등의 방치 사고를 예방하는 신기술입니다. '; + const displayText = + optionDesc.length > MAX_TEXT_CNT ? optionDesc.substring(0, MAX_TEXT_CNT) + '...' : optionDesc; + const handleBannerVisibility = () => { + setIsBannerVisible(!isBannerVisible); + }; + const [winY, setWinY] = useState(0); + const handleScroll = () => { + setWinY(window.scrollY); + }; + useEffect(() => { + window.addEventListener('scroll', handleScroll); + + return () => { + window.removeEventListener('scroll', handleScroll); + }; + }, []); + + return ( + + + + + + + + {displayText} + {optionDesc.length > MAX_TEXT_CNT && 더보기} + + + + + + 구매자의 절반 이상이 선택했어요. + + 2,384개 + 최근 90일 동안 + + + + + 주행 중 실제로 이만큼 사용해요. + + 73.2번 + 1.5만km 당 + + + + + + + {isBannerVisible ? '이미지 접기' : '이미지 확인'} + + + + + + + ); +} + +const Wrapper = styled.div<{ $isBannerVisible: boolean }>` + z-index: 5; + position: sticky; + top: ${({ $isBannerVisible }) => ($isBannerVisible ? '60' : '-150')}px; + transition: top 0.3s ease-in-out; + + left: 0; +`; + +const ContainerWrapper = styled(CenterWrapper)` + display: flex; + justify-content: flex-end; + width: 1280px; +`; +const OptionBanner = styled(Banner)` + background: ${({ theme }) => theme.color.blueBg}; +`; + +const Container = styled(CenterWrapper)` + position: relative; + height: 100%; +`; + +const AdditionalText = styled.p` + width: 456px; + color: ${({ theme }) => theme.color.gray800}; + ${BodyKrRegular4} + span { + padding-left: 10px; + text-decoration: underline; + ${BodyKrMedium4} + cursor:pointer; + } +`; + +const InfoWrapper = styled.div` + display: flex; + justify-content: space-evenly; + flex-direction: column; + padding-top: 120px; +`; + +const ToastPopup = styled.button<{ $offsetY: number; $isBannerVisible: boolean }>` + width: 100%; + height: 100%; + position: absolute; + left: 50%; + bottom: 18px; + transform: translate(-50%, 50%); + z-index: 10; + width: 76px; + height: 28px; + border-radius: 20px; + background: rgba(117, 117, 117, 0.5); + backdrop-filter: blur(2px); + color: ${({ theme }) => theme.color.white}; + text-align: center; + ${BodyKrMedium4} + display: ${({ $offsetY, $isBannerVisible }) => + ($offsetY >= 200 && !$isBannerVisible) || $offsetY ? 'block' : 'none'}; +`; +const HmgDataSection = styled.div` + margin-top: 12px; +`; +const DataList = styled.ul` + display: flex; + width: 448px; + margin-top: 16px; + align-items: center; +`; +const Data = styled.li` + width: 100%; + + height: 67px; + display: flex; + flex-direction: column; + &:first-child { + padding-right: 24px; + } + + &:last-child { + padding-left: 24px; + } +`; + +const DataTitle = styled.div` + margin-bottom: 8px; + ${BodyKrMedium4} + color: ${({ theme }) => theme.color.gray800}; + padding-bottom: 4px; + border-bottom: 1px solid ${({ theme }) => theme.color.gray200}; + white-space: nowrap; + width: 134px; +`; +const DataInfo = styled.div` + ${HeadingKrRegular2} +`; +const DataCaption = styled.div` + ${BodyKrRegular5} + color: ${({ theme }) => theme.color.gray600}; +`; + +const ImgSection = styled.div` + position: absolute; + width: 632px; + height: 360px; + background-image: url('/images/extra_option/roa.png'); + background-size: cover; + background-repeat: no-repeat; + background-position: center; + background-color: rgba(211, 211, 211, 0.5); +`; diff --git a/frontend/src/containers/OptionPage/OptionFooterContainer.tsx b/frontend/src/containers/OptionPage/OptionFooterContainer.tsx new file mode 100644 index 0000000..a299698 --- /dev/null +++ b/frontend/src/containers/OptionPage/OptionFooterContainer.tsx @@ -0,0 +1,29 @@ +import { styled } from 'styled-components'; +import PriceSummary from '../../components/summary/PriceSummary'; +import CenterWrapper from '../../components/layout/CenterWrapper'; + +export default function OptionFooterContainer() { + return ( + + + + + + ); +} + +const Wrapper = styled.div` + z-index: 999; + position: sticky; + bottom: 0; + left: 0; + width: 100%; + height: 120px; + background: rgba(255, 255, 255, 0.9); + backdrop-filter: blur(6px); +`; + +const FooterWrapper = styled(CenterWrapper)` + display: flex; + justify-content: flex-end; +`; diff --git a/frontend/src/containers/OptionPage/OptionSelectContainer/DefaultOptionContainer.tsx b/frontend/src/containers/OptionPage/OptionSelectContainer/DefaultOptionContainer.tsx new file mode 100644 index 0000000..56dbe14 --- /dev/null +++ b/frontend/src/containers/OptionPage/OptionSelectContainer/DefaultOptionContainer.tsx @@ -0,0 +1,69 @@ +import { styled } from 'styled-components'; +import { useState } from 'react'; +import RoundButton from '../../../components/common/buttons/RoundButton'; +import OptionCard from '../../../components/cards/OptionCard'; + +export default function DefaultOptionContainer() { + const [selectedOption, setSelectedOption] = useState(0); + + const handleClick = (index: number) => { + setSelectedOption(index); + }; + return ( + <> + + 전체 + + 상세품목 + + + 악세서리 + + + 휠 + + + + + {/* Todo. map() 으로 데이터 받아서 만들기! */} + + handleClick(0)} + > + handleClick(1)} + > + handleClick(2)} + > + + + + ); +} + +const CategoryWrapper = styled.div` + display: flex; + gap: 8px; +`; + +const OptionSection = styled.div` + margin: 16px 0px; +`; +const OptionWrapper = styled.div` + display: flex; + flex-wrap: wrap; + gap: 16px; +`; diff --git a/frontend/src/containers/OptionPage/OptionSelectContainer/ExtraOptionContainer.tsx b/frontend/src/containers/OptionPage/OptionSelectContainer/ExtraOptionContainer.tsx new file mode 100644 index 0000000..0a20afe --- /dev/null +++ b/frontend/src/containers/OptionPage/OptionSelectContainer/ExtraOptionContainer.tsx @@ -0,0 +1,158 @@ +import { useState } from 'react'; +import { styled } from 'styled-components'; +import RoundButton from '../../../components/common/buttons/RoundButton'; +import OptionCard from '../../../components/cards/OptionCard'; + +export default function ExtraOptionContainer() { + const [selectedOptions, setSelectedOptions] = useState([]); + + const handleClick = (index: number) => { + setSelectedOptions((prevSelectedOptions) => { + if (prevSelectedOptions.includes(index)) { + return prevSelectedOptions.filter((item) => item !== index); + } else { + return [...prevSelectedOptions, index]; + } + }); + }; + return ( + <> + + 전체 + + 상세품목 + + + 악세서리 + + + 휠 + + + + + {/* Todo. map() 으로 데이터 받아서 만들기! */} + handleClick(0)} + > + handleClick(1)} + > + handleClick(1)} + > + handleClick(1)} + > + handleClick(1)} + > + handleClick(1)} + > + handleClick(1)} + > + handleClick(1)} + > + handleClick(1)} + > + handleClick(1)} + > + handleClick(1)} + > + handleClick(1)} + > + handleClick(1)} + > + + + + ); +} + +const CategoryWrapper = styled.div` + display: flex; + gap: 8px; +`; + +const OptionSection = styled.div` + margin: 16px 0px; +`; +const OptionWrapper = styled.div` + display: flex; + + flex-wrap: wrap; + gap: 16px; +`; diff --git a/frontend/src/containers/OptionPage/OptionSelectContainer/OptionSelectContainer.tsx b/frontend/src/containers/OptionPage/OptionSelectContainer/OptionSelectContainer.tsx new file mode 100644 index 0000000..463f9a5 --- /dev/null +++ b/frontend/src/containers/OptionPage/OptionSelectContainer/OptionSelectContainer.tsx @@ -0,0 +1,83 @@ +import { css, styled } from 'styled-components'; +import CenterWrapper from '../../../components/layout/CenterWrapper'; +import { BodyKrMedium1 } from '../../../styles/typefaces'; +import SearchBar from '../../../components/searchBar/SearchBar'; +import ExtraOptionContainer from './ExtraOptionContainer'; +import { useState } from 'react'; +import DefaultOptionContainer from './DefaultOptionContainer'; + +interface INavItem extends React.HTMLAttributes { + active: boolean; +} +export default function OptionSelectContainer() { + const [isDefault, setIsDefault] = useState(false); + const handleTabItemClick = (isDefault: boolean) => { + setIsDefault(isDefault); + }; + return ( + +
+ + handleTabItemClick(false)} active={!isDefault}> + 추가옵션 + + handleTabItemClick(true)} active={isDefault}> + 기본옵션 + + + +
+ {isDefault ? : } +
+ ); +} + +function CategoryItem({ active, ...props }: INavItem) { + const Highlight = active ? : ; + return ( + + {props.children} + {Highlight} + + ); +} + +const CategoryList = styled.ul` + display: flex; + gap: 24px; +`; +const Item = styled.li<{ $active: boolean }>` + ${BodyKrMedium1} + display: flex; + flex-direction: column; + align-items: center; + line-height: 28px; + gap: 2px; + cursor: pointer; + + ${({ theme, $active }) => { + if ($active) { + return css` + color: ${theme.color.gray900}; + `; + } else { + return css` + color: ${theme.color.gray200}; + `; + } + }} +`; + +const Underline = styled.div` + width: 18px; + height: 2px; + background-color: ${({ theme }) => theme.color.primaryColor}; +`; + +const Wrapper = styled(CenterWrapper)``; +const Header = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + margin: 16px 0px; +`; diff --git a/frontend/src/containers/ResultPage/DetailContainer.tsx b/frontend/src/containers/ResultPage/DetailContainer.tsx new file mode 100644 index 0000000..62164ee --- /dev/null +++ b/frontend/src/containers/ResultPage/DetailContainer.tsx @@ -0,0 +1,202 @@ +import { styled } from 'styled-components'; +import { HeadingKrMedium7 } from '../../styles/typefaces'; +import Details from '../../components/details/Details'; +import SummaryItem from '../../components/details/SummaryItem'; + +export default function DetailContainer() { + return ( + + 상세 견적 +
+ + + + + +
+
+ + + + + +
+
+ + + + + +
+
+ + + + + +
+
+ + + + + +
+
+ + + + + +
+
+ + + + + +
+
+ + + + + +
+
+ ); +} + +const Wrapper = styled.div` + width: 607px; + padding-top: 20px; +`; +const Title = styled.div` + ${HeadingKrMedium7} +`; + +const ItemList = styled.ul` + :last-child { + border-bottom: none; + } +`; diff --git a/frontend/src/containers/ResultPage/HistogramContainer.tsx b/frontend/src/containers/ResultPage/HistogramContainer.tsx new file mode 100644 index 0000000..3aef195 --- /dev/null +++ b/frontend/src/containers/ResultPage/HistogramContainer.tsx @@ -0,0 +1,14 @@ +import { styled } from 'styled-components'; +import CurveHistogram from '../../components/histogram/CurveHistogram'; +import BarHistogram from '../../components/histogram/BarHistogram'; + +export default function HistogramContainer() { + return ( + + + + + ); +} + +const Wrapper = styled.div``; diff --git a/frontend/src/containers/ResultPage/QuoteSummaryContainer.tsx b/frontend/src/containers/ResultPage/QuoteSummaryContainer.tsx new file mode 100644 index 0000000..9a60bb4 --- /dev/null +++ b/frontend/src/containers/ResultPage/QuoteSummaryContainer.tsx @@ -0,0 +1,62 @@ +import { styled } from 'styled-components'; +import CenterWrapper from '../../components/layout/CenterWrapper'; +import { BodyKrMedium3, BodyKrRegular4, HeadingKrBold1 } from '../../styles/typefaces'; + +export default function QuoteSummaryContainer() { + return ( + + + 합리적인 가격으로 완성된 나만의 팰리세이드가 탄생했어요! + + + 모델 + Le Blanc(르블랑) + + + 배기량 + 2,199cc + + + 평균 연비 + 12km/l + + + + + ); +} + +const Wrapper = styled.div` + display: flex; + align-items: center; + background: ${({ theme }) => theme.color.skyBlueCardBg}; + height: 95px; +`; + +const FlexCenterWrapper = styled(CenterWrapper)` + display: flex; + align-items: center; + + justify-content: space-between; +`; + +const Caption = styled.p` + ${BodyKrMedium3} + width: 179px; + color: ${({ theme }) => theme.color.gray900}; +`; + +const Info = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + width: 500px; +`; + +const Item = styled.div``; +const Title = styled.div` + ${BodyKrRegular4} +`; +const Desc = styled.div` + ${HeadingKrBold1} +`; diff --git a/frontend/src/containers/ResultPage/ResultBannerContainer.tsx b/frontend/src/containers/ResultPage/ResultBannerContainer.tsx new file mode 100644 index 0000000..90b3479 --- /dev/null +++ b/frontend/src/containers/ResultPage/ResultBannerContainer.tsx @@ -0,0 +1,71 @@ +import { styled } from 'styled-components'; +import Banner from '../../components/common/banner/Banner'; +import { BodyKrRegular3, HeadingKrBold1 } from '../../styles/typefaces'; +import { flexCenterCss } from '../../utils/commonStyle'; +import { useState } from 'react'; + +type buttonType = 'exterior' | 'interior'; + +export default function ResultBannerContainer() { + const [selectedButton, setSelectedButton] = useState('exterior'); + + const handleSelectedButton = () => { + if (selectedButton === 'exterior') { + setSelectedButton('interior'); + } else { + setSelectedButton('exterior'); + } + }; + + return ( + <> + + Le Blanc + + + + + + + + ); +} + +const ResultBanner = styled(Banner)` + padding-bottom: 20px; + box-shadow: none; +`; +const Title = styled.div` + ${HeadingKrBold1} + font-size: 146px; + padding-top: 72px; + color: white; + text-align: center; +`; + +const CarImg = styled.img` + display: block; + margin: 0 auto; + height: 360px; +`; + +const Button = styled.button<{ $active: boolean }>` + width: 100%; + ${BodyKrRegular3} + height: 36px; + border-radius: 18px; + color: ${({ $active, theme }) => ($active ? theme.color.white : theme.color.primaryColor)}; + background-color: ${({ $active, theme }) => ($active ? theme.color.primaryColor : 'none')}; +`; +const ButtonContainer = styled.div` + margin: 0 auto; + ${flexCenterCss} + justify-content: space-between; + padding: 0 5px; + gap: 5px; + width: 213px; + height: 48px; + border-radius: 24px; + border: 1px solid ${({ theme }) => theme.color.gray100}; + cursor: pointer; +`; diff --git a/frontend/src/containers/ResultPage/ResultFooterContainer.tsx b/frontend/src/containers/ResultPage/ResultFooterContainer.tsx new file mode 100644 index 0000000..f6d98a1 --- /dev/null +++ b/frontend/src/containers/ResultPage/ResultFooterContainer.tsx @@ -0,0 +1,57 @@ +import { styled } from 'styled-components'; +import CenterWrapper from '../../components/layout/CenterWrapper'; +import { BodyKrRegular3, HeadingKrMedium2 } from '../../styles/typefaces'; +import RectButton from '../../components/common/buttons/RectButton'; + +export default function ResultFooterContainer() { + return ( + + + 최종 견적 가격 + 100원 + + + 공유하기 + PDF 다운로드 + + + + ); +} + +const Wrapper = styled(CenterWrapper)` + margin-top: 165px; + margin-bottom: 16px; + gap: 13px; + display: flex; + flex-direction: column; +`; +const PriceSection = styled.section` + width: 100%; + display: flex; + justify-content: flex-end; + align-items: flex-end; +`; +const PriceCaption = styled.span` + ${BodyKrRegular3} + margin-right:8px; + color: ${({ theme }) => theme.color.gray700}; +`; +const Price = styled.span` + ${HeadingKrMedium2} + color: ${({ theme }) => theme.color.primaryColor}; +`; + +const ButtonSection = styled.section` + width: 100%; + display: flex; + justify-content: space-between; + gap: 17px; +`; +const Button = styled(RectButton)` + border-radius: 4px; +`; + +const GrayButton = styled(Button)` + background-color: ${({ theme }) => theme.color.gray300}; +`; diff --git a/frontend/src/containers/TrimPage/TrimBannerContainer.tsx b/frontend/src/containers/TrimPage/TrimBannerContainer.tsx new file mode 100644 index 0000000..f76fcc4 --- /dev/null +++ b/frontend/src/containers/TrimPage/TrimBannerContainer.tsx @@ -0,0 +1,153 @@ +import { css, styled } from 'styled-components'; +import { BodyKrMedium3, BodyKrRegular5, HeadingKrRegular2 } from '../../styles/typefaces'; +import CenterWrapper from '../../components/layout/CenterWrapper'; +import Banner from '../../components/common/banner/Banner'; +import HmgTag from '../../components/common/hmgTag/HmgTag'; +import { useState } from 'react'; +import { flexCenterCss } from '../../utils/commonStyle'; + +export default function TrimBannerContainer() { + const [selectedImgIdx, setSelectedImgIdx] = useState(1); + const handleSelectImg = (idx: number) => { + setSelectedImgIdx(idx); + }; + + const images = ['/images/car.png', '/images/inner_car.png', '/images/wheel.png']; + const ImgSectionComponent = images.map((imgPath, idx) => { + return ( + handleSelectImg(idx)} $selected={selectedImgIdx === idx}> + + + ); + }); + + return ( + + + + + + + 해당 트림 포함된 옵션들의 + 실활용 데이터예요. + + + + 안전 하차 보조 + + 42회 + 15,000km 당 + + + + 후측방 충돌 경고 + + 42회 + 15,000km 당 + + + + 후방 교차 충돌방지 경고 + + 42회 + 15,000km 당 + + + + + + {ImgSectionComponent} + + + ); +} + +const Container = styled(CenterWrapper)` + display: flex; + justify-content: space-between; + height: 100%; +`; +const HmgDataWrapper = styled.div` + z-index: 100000; + width: 316px; + height: 175px; + margin-top: 166px; + margin-left: -16px; + pointer-events: none; +`; + +const HmgDataSection = styled.div` + margin: 0 16px; +`; + +const HmgTagDescription = styled.div` + ${BodyKrMedium3} + margin-top: 12px; +`; +const BlueText = styled.span` + color: ${({ theme }) => theme.color.activeBlue}; +`; +const DataList = styled.ul` + display: flex; + gap: 52px; + margin-top: 16px; +`; +const Data = styled.li` + display: flex; + flex-direction: column; + width: 60px; +`; +const DataTitle = styled.div` + ${BodyKrRegular5} + height: 40px; + border-bottom: 1px solid ${({ theme }) => theme.color.gray400}; + word-break: keep-all; + color: ${({ theme }) => theme.color.gray900}; +`; +const DataInfo = styled.div` + margin-top: 6px; + ${HeadingKrRegular2} +`; +const DataCaption = styled.div` + ${BodyKrRegular5} + color: ${({ theme }) => theme.color.gray600}; +`; + +const ImgSection = styled.div` + display: flex; + gap: 16px; +`; +const ImgWrapper = styled.div<{ $selected?: boolean }>` + ${({ $selected }) => { + if ($selected) + return css` + width: 504px; + `; + else { + return css` + width: 71px; + &:hover { + filter: brightness(0.6); + } + &:hover::after { + content: 'click!'; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: ${({ theme }) => theme.color.white}; + } + `; + } + }} + + ${flexCenterCss} + cursor: pointer; + overflow: hidden; + transition: all 0.2s; +`; + +const Img = styled.img` + height: auto; + width: 700px; +`; diff --git a/frontend/src/containers/TrimPage/TrimSelectContainer.tsx b/frontend/src/containers/TrimPage/TrimSelectContainer.tsx new file mode 100644 index 0000000..6d70a0d --- /dev/null +++ b/frontend/src/containers/TrimPage/TrimSelectContainer.tsx @@ -0,0 +1,88 @@ +import { styled } from 'styled-components'; +import CenterWrapper from '../../components/layout/CenterWrapper'; +import { + BodyKrRegular4, + HeadingEn4, + HeadingKrMedium6, + HeadingKrMedium7, +} from '../../styles/typefaces'; +import DefaultCardStyle from '../../components/common/card/DefaultCardStyle'; +import { useState } from 'react'; +import RectButton from '../../components/common/buttons/RectButton'; + +export default function TrimSelectContainer() { + const firstTrimIdx = 0; + const [selectedTrimIdx, setSelectedTrimIdx] = useState(firstTrimIdx); + const handleSelectedIdx = (idx: number) => { + setSelectedTrimIdx(idx); + }; + + return ( + + 트림을 선택해주세요. + + {/* Todo. map() 으로 데이터 받아서 만들기! */} + handleSelectedIdx(0)} active={selectedTrimIdx === 0}> + 기본기를 갖춘 베이직한 펠리세이드 + Exclusive + 100원! + 선택하기 + + handleSelectedIdx(1)} active={selectedTrimIdx === 1}> + 기본기를 갖춘 베이직한 펠리세이드 + Exclusive + 100원! + 선택하기 + + handleSelectedIdx(2)} active={selectedTrimIdx === 2}> + 기본기를 갖춘 베이직한 펠리세이드 + Exclusive + 100원! + 선택하기 + + handleSelectedIdx(3)} active={selectedTrimIdx === 3}> + 기본기를 갖춘 베이직한 펠리세이드 + Exclusive + 100원! + 선택하기 + + + + ); +} + +const Wrapper = styled(CenterWrapper)``; +const Title = styled.div` + ${HeadingKrMedium6} + margin-top: 16px; +`; + +const TrimSection = styled.div` + display: flex; + justify-content: space-between; + gap: 16px; + margin-top: 16px; +`; + +const TrimCard = styled(DefaultCardStyle)` + padding: 20px 16px 12px 16px; + height: 158px; + box-sizing: border-box; + width: 100%; +`; + +const TrimTitle = styled.div` + ${HeadingEn4} +`; +const TrimPrice = styled.div` + ${HeadingKrMedium7} + margin-top: 8px; +`; +const TrimDesc = styled.div` + ${BodyKrRegular4} +`; +const TrimButton = styled(RectButton)` + width: 100%; + margin-top: 8px; + border-radius: 2px; +`; diff --git a/frontend/src/context/CloseModalContext.tsx b/frontend/src/context/CloseModalContext.tsx new file mode 100644 index 0000000..05987de --- /dev/null +++ b/frontend/src/context/CloseModalContext.tsx @@ -0,0 +1,27 @@ +import { ReactNode, createContext, useState } from 'react'; + +interface ICloseModalContext { + visible: boolean; + setVisible: React.Dispatch>; +} + +interface ICloseModalProvider { + children: ReactNode; +} + +const initialContext = { + visible: false, + setVisible: () => {}, +}; + +export const CloseModalContext = createContext(initialContext); + +export default function CloseModalProvider({ children }: ICloseModalProvider) { + const [visible, setVisible] = useState(false); + + return ( + + {children} + + ); +} diff --git a/frontend/src/context/GuideMoadlContext.tsx b/frontend/src/context/GuideMoadlContext.tsx new file mode 100644 index 0000000..81e872c --- /dev/null +++ b/frontend/src/context/GuideMoadlContext.tsx @@ -0,0 +1,27 @@ +import { ReactNode, createContext, useState } from 'react'; + +interface IGuideModalContext { + visible: boolean; + setVisible: React.Dispatch>; +} + +interface IGuideModalProvider { + children: ReactNode; +} + +const initialContext = { + visible: true, + setVisible: () => {}, +}; + +export const GuideModalContext = createContext(initialContext); + +export default function GuideModalProvider({ children }: IGuideModalProvider) { + const [visible, setVisible] = useState(true); + + return ( + + {children} + + ); +} diff --git a/frontend/src/context/SimilarQuoteModalContext.tsx b/frontend/src/context/SimilarQuoteModalContext.tsx new file mode 100644 index 0000000..19e7542 --- /dev/null +++ b/frontend/src/context/SimilarQuoteModalContext.tsx @@ -0,0 +1,27 @@ +import { ReactNode, createContext, useState } from 'react'; + +interface ISimilarQuoteModalContext { + visible: boolean; + setVisible: React.Dispatch>; +} + +interface ISimilarQuoteModalProvider { + children: ReactNode; +} + +const initialContext = { + visible: false, + setVisible: () => {}, +}; + +export const SimilarQuoteModalContext = createContext(initialContext); + +export default function SimilarQuoteModalProvider({ children }: ISimilarQuoteModalProvider) { + const [visible, setVisible] = useState(false); + + return ( + + {children} + + ); +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx new file mode 100644 index 0000000..3363a61 --- /dev/null +++ b/frontend/src/main.tsx @@ -0,0 +1,13 @@ +import ReactDOM from 'react-dom/client'; +import './styles/reset.css'; +import './styles/font.css'; + +import { ThemeProvider } from 'styled-components'; +import { theme } from './styles/theme.ts'; +import App from './App.tsx'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + +); diff --git a/frontend/src/pages/ExteriorPage.tsx b/frontend/src/pages/ExteriorPage.tsx new file mode 100644 index 0000000..9d79835 --- /dev/null +++ b/frontend/src/pages/ExteriorPage.tsx @@ -0,0 +1,11 @@ +import ExteriorSelectContainer from '../containers/ExteriorPage/ExteriorSelectContainer'; +import ExteriorBannerContainer from '../containers/ExteriorPage/ExtreriorBannerContainer'; + +export default function ExteriorPage() { + return ( + <> + + + + ); +} diff --git a/frontend/src/pages/InteriorPage.tsx b/frontend/src/pages/InteriorPage.tsx new file mode 100644 index 0000000..4d47fa4 --- /dev/null +++ b/frontend/src/pages/InteriorPage.tsx @@ -0,0 +1,11 @@ +import InteriorBannerContainer from '../containers/InteriorPage/InteriorBannerContainer'; +import InteriorSelectContainer from '../containers/InteriorPage/InteriorSelectContainer'; + +export default function InteriorPage() { + return ( + <> + + + + ); +} diff --git a/frontend/src/pages/ModelTypePage.tsx b/frontend/src/pages/ModelTypePage.tsx new file mode 100644 index 0000000..62db820 --- /dev/null +++ b/frontend/src/pages/ModelTypePage.tsx @@ -0,0 +1,13 @@ +import ModelBannerContainer from '../containers/ModelTypePage/ModelBannerContainer'; +import ModelFooterContainer from '../containers/ModelTypePage/ModelFooterContainer'; +import ModelSelectContainer from '../containers/ModelTypePage/ModelSelectContainer'; + +export default function ModelTypePage() { + return ( + <> + + + + + ); +} diff --git a/frontend/src/pages/OptionPage.tsx b/frontend/src/pages/OptionPage.tsx new file mode 100644 index 0000000..99ac73e --- /dev/null +++ b/frontend/src/pages/OptionPage.tsx @@ -0,0 +1,21 @@ +import OptionBannerContainer from '../containers/OptionPage/OptionBannerContainer'; +import OptionSelectContainer from '../containers/OptionPage/OptionSelectContainer/OptionSelectContainer'; +import OptionFooterContainer from '../containers/OptionPage/OptionFooterContainer'; +import { styled } from 'styled-components'; + +export default function OptionPage() { + return ( + <> + + + + + + + ); +} + +const Wrapper = styled.div` + height: 100%; + padding-bottom: 120px; +`; diff --git a/frontend/src/pages/ResultPage.tsx b/frontend/src/pages/ResultPage.tsx new file mode 100644 index 0000000..6e364a8 --- /dev/null +++ b/frontend/src/pages/ResultPage.tsx @@ -0,0 +1,29 @@ +import { styled } from 'styled-components'; +import CenterWrapper from '../components/layout/CenterWrapper'; +import DetailContainer from '../containers/ResultPage/DetailContainer'; +import QuoteSummaryContainer from '../containers/ResultPage/QuoteSummaryContainer'; +import ResultBannerContainer from '../containers/ResultPage/ResultBannerContainer'; +import HistogramContainer from '../containers/ResultPage/HistogramContainer'; +import ResultFooterContainer from '../containers/ResultPage/ResultFooterContainer'; + +export default function ResultPage() { + return ( + <> + + + + + + + + + ); +} + +const Row = styled(CenterWrapper)` + display: flex; + flex-direction: row; + justify-content: space-between; + min-height: 300px; + gap: 70px; +`; diff --git a/frontend/src/pages/TrimPage.tsx b/frontend/src/pages/TrimPage.tsx new file mode 100644 index 0000000..48d57bf --- /dev/null +++ b/frontend/src/pages/TrimPage.tsx @@ -0,0 +1,11 @@ +import TrimBannerContainer from '../containers/TrimPage/TrimBannerContainer'; +import TrimSelectContainer from '../containers/TrimPage/TrimSelectContainer'; + +export default function TrimPage() { + return ( + <> + + + + ); +} diff --git a/frontend/src/styles/font.css b/frontend/src/styles/font.css new file mode 100644 index 0000000..4886ed5 --- /dev/null +++ b/frontend/src/styles/font.css @@ -0,0 +1,67 @@ +@font-face { + font-family: 'Hyundai Sans Head Bold'; + src: url('fonts/HyundaiSansHead-Bold.otf') format('opentype'); +} + +@font-face { + font-family: 'Hyundai Sans Head Medium'; + src: url('fonts/HyundaiSansHeadKROTFMedium.otf') format('opentype'); +} +@font-face { + font-family: 'Hyundai Sans Head Regular'; + src: url('fonts/HyundaiSansHeadKROTFRegular.otf') format('opentype'); +} +@font-face { + font-family: 'Hyundai Sans Head Light'; + src: url('fonts/HyundaiSansHeadKROTFLight.otf') format('opentype'); +} +@font-face { + font-family: 'Hyundai Sans Text Bold'; + src: url('fonts/HyundaiSansHeadKROTFBold.otf') format('opentype'); +} +@font-face { + font-family: 'Hyundai Sans Text Medium'; + src: url('fonts/HyundaiSansHeadKROTFMedium.otf') format('opentype'); +} +@font-face { + font-family: 'Hyundai Sans Text Regular'; + src: url('fonts/HyundaiSansHeadKROTFRegular.otf') format('opentype'); + font-weight: 700; +} +@font-face { + font-family: 'Hyundai Sans Text Light'; + src: url('fonts/HyundaiSansHeadKROTFLight.otf') format('opentype'); +} +@font-face { + font-family: 'Hyundai Sans Head KR Bold'; + src: url('fonts/HyundaiSansHeadKROTFBold.otf') format('opentype'); + font-weight: 700; +} +@font-face { + font-family: 'Hyundai Sans Head KR Medium'; + src: url('fonts/HyundaiSansHeadKROTFMedium.otf') format('opentype'); +} +@font-face { + font-family: 'Hyundai Sans Head KR Regular'; + src: url('fonts/HyundaiSansHeadKROTFRegular.otf') format('opentype'); +} +@font-face { + font-family: 'Hyundai Sans Head KR Light'; + src: url('fonts/HyundaiSansHeadKROTFLight.otf') format('opentype'); +} +@font-face { + font-family: 'Hyundai Sans Text KR Bold'; + src: url('fonts/HyundaiSansHeadKROTFBold.otf') format('opentype'); +} +@font-face { + font-family: 'Hyundai Sans Text KR Medium'; + src: url('fonts/HyundaiSansHeadKROTFMedium.otf') format('opentype'); +} +@font-face { + font-family: 'Hyundai Sans Text KR Regular'; + src: url('fonts/HyundaiSansHeadKROTFRegular.otf') format('opentype'); +} +@font-face { + font-family: 'Hyundai Sans Text KR Light'; + src: url('fonts/HyundaiSansHeadKROTFLight.otf') format('opentype'); +} diff --git a/frontend/src/styles/reset.css b/frontend/src/styles/reset.css new file mode 100644 index 0000000..f576485 --- /dev/null +++ b/frontend/src/styles/reset.css @@ -0,0 +1,146 @@ +html, +body, +div, +span, +applet, +object, +iframe, +h1, +h2, +h3, +h4, +h5, +h6, +p, +blockquote, +pre, +a, +abbr, +acronym, +address, +big, +cite, +code, +del, +dfn, +em, +img, +ins, +kbd, +q, +s, +samp, +small, +strike, +strong, +sub, +sup, +tt, +var, +b, +u, +i, +center, +dl, +dt, +dd, +ol, +ul, +li, +fieldset, +form, +label, +legend, +table, +caption, +tbody, +tfoot, +thead, +tr, +th, +td, +article, +aside, +canvas, +details, +embed, +figure, +figcaption, +footer, +header, +hgroup, +menu, +nav, +output, +ruby, +section, +summary, +time, +mark, +audio, +video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} +/* HTML5 display-role reset for older browsers */ +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +menu, +nav, +section { + display: block; +} +body { + line-height: 1; +} +ol, +ul { + list-style: none; +} +blockquote, +q { + quotes: none; +} +blockquote:before, +blockquote:after, +q:before, +q:after { + content: ''; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} + +button { + padding: 0; + margin: 0; + background-color: transparent; + border: none; + cursor: pointer; +} + +li { + padding: 0; + margin: 0; +} + +input { + margin: 0; + padding: 0; +} + +* { + box-sizing: border-box; +} diff --git a/frontend/src/styles/styled.d.ts b/frontend/src/styles/styled.d.ts new file mode 100644 index 0000000..22afa56 --- /dev/null +++ b/frontend/src/styles/styled.d.ts @@ -0,0 +1,49 @@ +import 'styled-components'; + +interface IColor { + // primary color 계열 + primaryColor: string; + primaryColor100: string; + primaryColor200: string; + primaryColor300: string; + primaryColor400: string; + primaryColor500: string; + primaryColor600: string; + primaryColor700: string; + primaryColor800: string; + primaryColor900: string; + + // white 계열 + white: string; + gray50: string; + gray100: string; + gray200: string; + gray300: string; + gray400: string; + gray500: string; + gray600: string; + gray700: string; + gray800: string; + gray900: string; + + // blue 계열 + activeBlue: string; + activeBlue2: string; + skyBlue: string; + + //background 색상 + skyBlueCardBg: string; + cardBg: string; + cardBgOpacity05: string; + blueBg: string; + + //sand 계열 + sand: string; + sand2: string; + lightSand: string; +} +declare module 'styled-components' { + export interface DefaultTheme { + color: IColor; + } +} diff --git a/frontend/src/styles/theme.ts b/frontend/src/styles/theme.ts new file mode 100644 index 0000000..05aff62 --- /dev/null +++ b/frontend/src/styles/theme.ts @@ -0,0 +1,48 @@ +import { DefaultTheme } from 'styled-components'; + +export const theme: DefaultTheme = { + color: { + // primary color 계열 + primaryColor: '#00397B', + primaryColor100: '#CCD7E5', + primaryColor200: '#99B0CA', + primaryColor300: '#6688B0', + primaryColor400: '#336196', + primaryColor500: '#00397B', + primaryColor600: '#002E63', + primaryColor700: '#00224A', + primaryColor800: '#001731', + primaryColor900: '#000B19', + + // white 계열 + white: '#FFF', + gray50: '#F7F8F9', + gray100: '#EDEDEE', + gray200: '#DADCDD', + gray300: '#C8CACC', + gray400: '#B6B8BB', + gray500: '#A3A6AA', + gray600: '#838588', + gray700: '#626466', + gray800: '#414344', + gray900: '#212122', + + //blue 계열 + activeBlue: '#00AAD2', + activeBlue2: '#00C3F0', + skyBlue: '#A2C7E7', + + //background 색상 + skyBlueCardBg: + 'linear-gradient(0deg, rgba(162, 199, 231, 0.30) 0%, rgba(162, 199, 231, 0.30) 100%), #FFF', + cardBg: + 'linear-gradient(0deg, rgba(0, 170, 210, 0.10) 0%, rgba(0, 170, 210, 0.10) 100%), #FFF', + cardBgOpacity05: '#A2C7E70D', + blueBg: '#F4F5F7', + + //sand 계열 + sand: '#B67B5E', + sand2: '#E4DCD3', + lightSand: '#F6F3F2', + }, +}; diff --git a/frontend/src/styles/typefaces.ts b/frontend/src/styles/typefaces.ts new file mode 100644 index 0000000..a4d5703 --- /dev/null +++ b/frontend/src/styles/typefaces.ts @@ -0,0 +1,258 @@ +import { css } from 'styled-components'; + +export const BodyEn1 = css` + font-family: 'Hyundai Sans Text Medium'; + font-size: 18px; + line-height: 28px; + letter-spacing: -0.018px; +`; + +export const BodyEn2 = css` + font-family: 'Hyundai Sans Text Medium'; + font-size: 16px; + line-height: 24px; + letter-spacing: -0.016px; +`; +export const BodyEn3 = css` + font-family: 'Hyundai Sans Text Medium'; + font-size: 14px; + line-height: 22px; + letter-spacing: -0.014px; +`; + +export const CaptionEn1 = css` + font-family: 'Hyundai Sans Text Medium'; + font-size: 14px; + line-height: 28px; + letter-spacing: -0.014px; +`; + +export const CaptionEn2 = css` + font-family: 'Hyundai Sans Text Medium'; + font-size: 12px; + line-height: 24px; + letter-spacing: -0.012px; +`; + +export const BodyKrBold1 = css` + font-family: 'Hyundai Sans Text KR Bold'; + font-size: 12px; + line-height: 18px; + letter-spacing: -0.036px; +`; +export const BodyKrMedium1 = css` + font-family: 'Hyundai Sans Text KR Medium'; + font-size: 18px; + line-height: 18px; + letter-spacing: -0.054px; +`; +export const BodyKrMedium2 = css` + font-family: 'Hyundai Sans Text Medium'; + font-size: 16px; + line-height: 18px; + letter-spacing: -0.048px; +`; +export const BodyKrMedium3 = css` + font-family: 'Hyundai Sans Text Medium'; + font-size: 14px; + line-height: 18px; + letter-spacing: -0.042px; +`; +export const BodyKrMedium4 = css` + font-family: 'Hyundai Sans Text Medium'; + font-size: 12px; + line-height: 18px; + letter-spacing: -0.036px; +`; +export const BodyKrMedium5 = css` + font-family: 'Hyundai Sans Text Medium'; + font-size: 10px; + line-height: 18px; + letter-spacing: -0.036px; +`; +export const BodyKrRegular1 = css` + font-family: 'Hyundai Sans Text KR Regular'; + font-size: 18px; + line-height: 18px; + letter-spacing: -0.054px; +`; +export const BodyKrRegular2 = css` + font-family: 'Hyundai Sans Text Regular'; + font-size: 16px; + line-height: 18px; + letter-spacing: -0.048px; +`; +export const BodyKrRegular3 = css` + font-family: 'Hyundai Sans Text Regular'; + font-size: 14px; + line-height: 18px; + letter-spacing: -0.042px; +`; +export const BodyKrRegular4 = css` + font-family: 'Hyundai Sans Text Regular'; + font-size: 12px; + line-height: 18px; + letter-spacing: -0.036px; +`; +export const BodyKrRegular5 = css` + font-family: 'Hyundai Sans Text Regular'; + font-size: 10px; + line-height: 18px; + letter-spacing: -0.036px; +`; + +export const HeadingEn1 = css` + font-family: 'Hyundai Sans Head Bold'; + font-size: 26px; + line-height: 36px; + letter-spacing: -0.001em; + text-align: left; +`; +export const HeadingEn2 = css` + font-family: 'Hyundai Sans Head Bold'; + font-size: 24px; + line-height: 32px; + letter-spacing: -0.001em; + text-align: left; +`; +export const HeadingEn3 = css` + font-family: 'Hyundai Sans Head Bold'; + font-size: 22px; + line-height: 28px; + letter-spacing: -0.001em; + text-align: left; +`; +export const HeadingEn4 = css` + font-family: 'Hyundai Sans Head Medium'; + font-size: 20px; + line-height: 28px; + letter-spacing: -0.001em; + text-align: left; +`; +export const HeadingEn5 = css` + font-family: 'Hyundai Sans Head Bold'; + font-size: 10px; + line-height: 12px; + letter-spacing: -0.001em; + text-align: left; +`; +export const HeadingKrBold1 = css` + font-family: 'Hyundai Sans Head KR Bold'; + font-size: 32px; + line-height: 36px; + letter-spacing: -0.003em; + text-align: left; +`; +export const HeadingKrBold2 = css` + font-family: 'Hyundai Sans Head KR Bold'; + font-size: 26px; + line-height: 36px; + letter-spacing: -0.003em; + text-align: left; +`; +export const HeadingKrBold3 = css` + font-family: 'Hyundai Sans Head KR Bold'; + font-size: 24px; + line-height: 32px; + letter-spacing: -0.003em; + text-align: left; +`; +export const HeadingKrBold4 = css` + font-family: 'Hyundai Sans Head KR Bold'; + font-size: 22px; + line-height: 28px; + letter-spacing: -0.003em; + text-align: left; +`; +export const HeadingKrBold5 = css` + font-family: 'Hyundai Sans Head KR Bold'; + font-size: 20px; + line-height: 24px; + letter-spacing: -0.003em; + text-align: left; +`; +export const HeadingKrBold6 = css` + font-family: 'Hyundai Sans Head KR Bold'; + font-size: 18px; + line-height: 28px; + letter-spacing: -0.003em; + text-align: left; +`; +export const HeadingKrMedium1 = css` + font-family: 'Hyundai Sans Head KR Medium'; + font-size: 26px; + line-height: 36px; + letter-spacing: -0.003em; + text-align: left; +`; +export const HeadingKrMedium2 = css` + font-family: 'Hyundai Sans Head KR Medium'; + font-size: 24px; + line-height: 32px; + letter-spacing: -0.003em; + text-align: left; +`; +export const HeadingKrMedium3 = css` + font-family: 'Hyundai Sans Head KR Medium'; + font-size: 22px; + line-height: 28px; + letter-spacing: -0.003em; + text-align: left; +`; +export const HeadingKrMedium4 = css` + font-family: 'Hyundai Sans Head KR Medium'; + font-size: 20px; + line-height: 24px; + letter-spacing: -0.003em; + text-align: left; +`; +export const HeadingKrMedium5 = css` + font-family: 'Hyundai Sans Head KR Medium'; + font-size: 18px; + line-height: 28px; + letter-spacing: -0.003em; + text-align: left; +`; +export const HeadingKrMedium6 = css` + font-family: 'Hyundai Sans Head KR Medium'; + font-size: 16px; + line-height: 24px; + letter-spacing: -0.003em; + text-align: left; +`; + +export const HeadingKrMedium7 = css` + font-family: 'Hyundai Sans Head KR Medium'; + font-size: 14px; + line-height: 22px; + letter-spacing: -0.003em; + text-align: left; +`; +export const HeadingKrRegular1 = css` + font-family: 'Hyundai Sans Head KR Regular'; + font-size: 26px; + line-height: 36px; + letter-spacing: -0.003em; + text-align: left; +`; +export const HeadingKrRegular2 = css` + font-family: 'Hyundai Sans Head KR Regular'; + font-size: 24px; + line-height: 32px; + letter-spacing: -0.003em; + text-align: left; +`; +export const HeadingKrRegular3 = css` + font-family: 'Hyundai Sans Head KR Regular'; + font-size: 22px; + line-height: 38px; + letter-spacing: -0.003em; + text-align: left; +`; +export const HeadingKrRegular4 = css` + font-family: 'Hyundai Sans Head KR Regular'; + font-size: 20px; + line-height: 24px; + letter-spacing: -0.003em; + text-align: left; +`; diff --git a/frontend/src/utils/commonStyle.ts b/frontend/src/utils/commonStyle.ts new file mode 100644 index 0000000..d085d4d --- /dev/null +++ b/frontend/src/utils/commonStyle.ts @@ -0,0 +1,7 @@ +import { css } from 'styled-components'; + +export const flexCenterCss = css` + display: flex; + justify-content: center; + align-items: center; +`; diff --git a/frontend/src/utils/constants.ts b/frontend/src/utils/constants.ts new file mode 100644 index 0000000..58d3ba6 --- /dev/null +++ b/frontend/src/utils/constants.ts @@ -0,0 +1,26 @@ +export const MAX_PAGE = 3; +export const NUM_IN_A_PAGE = 4; +export const MAX_TEXT_CNT = 98; + +export const PATH = { + home: '/', + trim: '/trim', + modelType: '/model-type', + exterior: '/exterior', + interior: '/interior', + option: '/option', + result: '/result', +}; + +export const HYUNDAI_URL = 'https://www.hyundai.com/kr/ko/e'; +export const FONT_URL = 'fonts/'; +export const PAGE_ANIMATION_DURATION = 500; + +Object.freeze({ + MAX_PAGE, + NUM_IN_A_PAGE, + MAX_TEXT_CNT, + HYUNDAI_URL, + PATH, + PAGE_ANIMATION_DURATION, +}); diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..a7fc6fb --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json new file mode 100644 index 0000000..42872c5 --- /dev/null +++ b/frontend/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..627a319 --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +});