diff --git a/.github/workflows/deploy-frontend.yml b/.github/workflows/deploy-frontend.yml index 3e5c5f94..70dbbd06 100644 --- a/.github/workflows/deploy-frontend.yml +++ b/.github/workflows/deploy-frontend.yml @@ -1,4 +1,4 @@ -name: Deploy Frontend to Self-hosted +name: Deploy Frontend Apps to Self-Host on: push: @@ -13,88 +13,238 @@ on: required: false default: 'latest' type: string - port: - description: 'Port to expose (default: 3133)' + deploy_admin: + description: 'Deploy admin app' required: false - default: '3133' - type: string + default: true + type: boolean + deploy_contents: + description: 'Deploy contents app' + required: false + default: true + type: boolean + deploy_seller: + description: 'Deploy seller app' + required: false + default: true + type: boolean env: ECR_REGISTRY: public.ecr.aws/x2l9m6x8/selfscape/moimjang - CONTAINER_NAME: moimjang-frontend-container - IMAGE_NAME: moimjang-frontend jobs: - deploy: - runs-on: [self-hosted, home] + # Admin ์•ฑ ๋ฐฐํฌ + deploy-admin: + if: | + (github.event_name == 'workflow_dispatch' && github.event.inputs.deploy_admin == 'true') || + (github.event_name == 'push') + runs-on: self-hosted environment: 'production' steps: - - name: Set deployment parameters + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: us-east-1 + + - name: Login to Amazon ECR Public + id: login-ecr-public + uses: aws-actions/amazon-ecr-login@v2 + with: + registry-type: public + + - name: Pull Admin App Image + run: | + IMAGE_TAG="${{ github.event.inputs.image_tag || 'latest' }}" + echo "๐Ÿ”„ Pulling admin app image with tag: $IMAGE_TAG" + docker pull ${{ env.ECR_REGISTRY }}-admin:$IMAGE_TAG + echo "โœ… Successfully pulled admin app image" + + - name: Stop Existing Admin Container + run: | + echo "๐Ÿ›‘ Stopping existing admin container..." + docker stop moimjang-admin || echo "No running admin container found" + docker rm moimjang-admin || echo "No admin container to remove" + echo "โœ… Existing admin container stopped and removed" + continue-on-error: true + + - name: Start New Admin Container + run: | + IMAGE_TAG="${{ github.event.inputs.image_tag || 'latest' }}" + echo "๐Ÿš€ Starting new admin container..." + docker run -d \ + --name moimjang-admin \ + --restart unless-stopped \ + -p 4131:80 \ + ${{ env.ECR_REGISTRY }}-admin:$IMAGE_TAG + echo "โœ… New admin container started successfully" + + - name: Health Check Admin App run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - echo "IMAGE_TAG=${{ github.event.inputs.image_tag }}" >> $GITHUB_ENV - echo "DEPLOY_PORT=${{ github.event.inputs.port }}" >> $GITHUB_ENV + echo "๐Ÿ” Performing health check for admin app..." + sleep 10 + if curl -f http://localhost:3131 > /dev/null 2>&1; then + echo "โœ… Admin app is healthy and responding" else - echo "IMAGE_TAG=latest" >> $GITHUB_ENV - echo "DEPLOY_PORT=3133" >> $GITHUB_ENV + echo "โŒ Admin app health check failed" + exit 1 fi - echo "Using image tag: ${{ env.IMAGE_TAG || 'latest' }}" - echo "Using port: ${{ env.DEPLOY_PORT || '3133' }}" - - # - name: Configure AWS credentials - # uses: aws-actions/configure-aws-credentials@v4 - # with: - # aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - # aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - # aws-region: us-east-1 - - # - name: Login to Amazon ECR Public - # id: login-ecr-public - # uses: aws-actions/amazon-ecr-login@v2 - # with: - # registry-type: public + + # Contents ์•ฑ ๋ฐฐํฌ + deploy-contents: + if: | + (github.event_name == 'workflow_dispatch' && github.event.inputs.deploy_contents == 'true') || + (github.event_name == 'push') + runs-on: self-hosted + environment: 'production' + + steps: + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: us-east-1 - - name: Pull image from ECR + - name: Login to Amazon ECR Public + id: login-ecr-public + uses: aws-actions/amazon-ecr-login@v2 + with: + registry-type: public + + - name: Pull Contents App Image run: | - IMAGE_TAG=${{ env.IMAGE_TAG || 'latest' }} - echo "Pulling image from ECR with tag: $IMAGE_TAG" - docker pull ${{ env.ECR_REGISTRY }}:$IMAGE_TAG + IMAGE_TAG="${{ github.event.inputs.image_tag || 'latest' }}" + echo "๐Ÿ”„ Pulling contents app image with tag: $IMAGE_TAG" + docker pull ${{ env.ECR_REGISTRY }}-contents:$IMAGE_TAG + echo "โœ… Successfully pulled contents app image" - - name: Stop and remove existing container + - name: Stop Existing Contents Container + run: | + echo "๐Ÿ›‘ Stopping existing contents container..." + docker stop moimjang-contents || echo "No running contents container found" + docker rm moimjang-contents || echo "No contents container to remove" + echo "โœ… Existing contents container stopped and removed" + continue-on-error: true + + - name: Start New Contents Container + run: | + IMAGE_TAG="${{ github.event.inputs.image_tag || 'latest' }}" + echo "๐Ÿš€ Starting new contents container..." + docker run -d \ + --name moimjang-contents \ + --restart unless-stopped \ + -p 3132:3000 \ + ${{ env.ECR_REGISTRY }}-contents:$IMAGE_TAG + echo "โœ… New contents container started successfully" + + - name: Health Check Contents App run: | - echo "Stopping and removing existing container..." - docker stop ${{ env.CONTAINER_NAME }} || true - docker rm ${{ env.CONTAINER_NAME }} || true - docker image prune -f || true + echo "๐Ÿ” Performing health check for contents app..." + sleep 10 + if curl -f http://localhost:3132 > /dev/null 2>&1; then + echo "โœ… Contents app is healthy and responding" + else + echo "โŒ Contents app health check failed" + exit 1 + fi + + # Seller ์•ฑ ๋ฐฐํฌ + deploy-seller: + if: | + (github.event_name == 'workflow_dispatch' && github.event.inputs.deploy_seller == 'true') || + (github.event_name == 'push') + runs-on: self-hosted + environment: 'production' + + steps: + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: us-east-1 - - name: Run new container + - name: Login to Amazon ECR Public + id: login-ecr-public + uses: aws-actions/amazon-ecr-login@v2 + with: + registry-type: public + + - name: Pull Seller App Image run: | - IMAGE_TAG=${{ env.IMAGE_TAG || 'latest' }} - DEPLOY_PORT=${{ env.DEPLOY_PORT || '3133' }} - echo "Starting new container with image: ${{ env.ECR_REGISTRY }}:$IMAGE_TAG" - echo "Exposing on port: $DEPLOY_PORT" + IMAGE_TAG="${{ github.event.inputs.image_tag || 'latest' }}" + echo "๐Ÿ”„ Pulling seller app image with tag: $IMAGE_TAG" + docker pull ${{ env.ECR_REGISTRY }}-seller:$IMAGE_TAG + echo "โœ… Successfully pulled seller app image" + - name: Stop Existing Seller Container + run: | + echo "๐Ÿ›‘ Stopping existing seller container..." + docker stop moimjang-seller || echo "No running seller container found" + docker rm moimjang-seller || echo "No seller container to remove" + echo "โœ… Existing seller container stopped and removed" + continue-on-error: true + + - name: Start New Seller Container + run: | + IMAGE_TAG="${{ github.event.inputs.image_tag || 'latest' }}" + echo "๐Ÿš€ Starting new seller container..." docker run -d \ - --name ${{ env.CONTAINER_NAME }} \ + --name moimjang-seller \ --restart unless-stopped \ - -p $DEPLOY_PORT:80 \ - ${{ env.ECR_REGISTRY }}:$IMAGE_TAG + -p 3133:3000 \ + ${{ env.ECR_REGISTRY }}-seller:$IMAGE_TAG + echo "โœ… New seller container started successfully" - echo "Container started successfully!" - - - name: Verify deployment + - name: Health Check Seller App run: | - DEPLOY_PORT=${{ env.DEPLOY_PORT || '3133' }} - echo "Verifying container status..." - docker ps | grep ${{ env.CONTAINER_NAME }} + echo "๐Ÿ” Performing health check for seller app..." + sleep 10 + if curl -f http://localhost:3133 > /dev/null 2>&1; then + echo "โœ… Seller app is healthy and responding" + else + echo "โŒ Seller app health check failed" + exit 1 + fi + + # ๋ฐฐํฌ ์ƒํƒœ ์•Œ๋ฆผ + notify-deployment-status: + needs: [deploy-admin, deploy-contents, deploy-seller] + if: always() + runs-on: ubuntu-24.04 + + steps: + - name: Deployment Summary + run: | + echo "๐Ÿš€ Frontend Apps ๋ฐฐํฌ ๊ฒฐ๊ณผ:" + echo "" - echo "Checking container logs..." - docker logs ${{ env.CONTAINER_NAME }} --tail 20 + if [[ "${{ needs.deploy-admin.result }}" == "success" ]]; then + echo "โœ… Admin App: ๋ฐฐํฌ ์„ฑ๊ณต (ํฌํŠธ: 3131)" + elif [[ "${{ needs.deploy-admin.result }}" == "failure" ]]; then + echo "โŒ Admin App: ๋ฐฐํฌ ์‹คํŒจ" + elif [[ "${{ needs.deploy-admin.result }}" == "skipped" ]]; then + echo "โญ๏ธ Admin App: ๊ฑด๋„ˆ๋œ€" + fi - echo "Waiting for container to be ready..." - sleep 10 + if [[ "${{ needs.deploy-contents.result }}" == "success" ]]; then + echo "โœ… Contents App: ๋ฐฐํฌ ์„ฑ๊ณต (ํฌํŠธ: 3132)" + elif [[ "${{ needs.deploy-contents.result }}" == "failure" ]]; then + echo "โŒ Contents App: ๋ฐฐํฌ ์‹คํŒจ" + elif [[ "${{ needs.deploy-contents.result }}" == "skipped" ]]; then + echo "โญ๏ธ Contents App: ๊ฑด๋„ˆ๋œ€" + fi - echo "Testing HTTP response on port $DEPLOY_PORT..." - curl -f -s http://localhost:$DEPLOY_PORT > /dev/null && echo "โœ… HTTP test successful!" || echo "โš ๏ธ HTTP test failed, but container might still be starting..." + if [[ "${{ needs.deploy-seller.result }}" == "success" ]]; then + echo "โœ… Seller App: ๋ฐฐํฌ ์„ฑ๊ณต (ํฌํŠธ: 3133)" + elif [[ "${{ needs.deploy-seller.result }}" == "failure" ]]; then + echo "โŒ Seller App: ๋ฐฐํฌ ์‹คํŒจ" + elif [[ "${{ needs.deploy-seller.result }}" == "skipped" ]]; then + echo "โญ๏ธ Seller App: ๊ฑด๋„ˆ๋œ€" + fi + echo "" + echo "๐Ÿ“ ๋ฐฐํฌ๋œ ์ด๋ฏธ์ง€ ํƒœ๊ทธ: ${{ github.event.inputs.image_tag || 'latest' }}" diff --git a/.github/workflows/pr-frontend.yml b/.github/workflows/pr-frontend.yml index 4e29dc63..7c823f53 100644 --- a/.github/workflows/pr-frontend.yml +++ b/.github/workflows/pr-frontend.yml @@ -8,16 +8,40 @@ on: - '.github/workflows/pr-frontend.yml' env: - REACT_APP_SERVER_URI: ${{ vars.REACT_APP_SERVER_URI }} - REACT_APP_NODE_ENV: ${{ vars.REACT_APP_NODE_ENV }} - REACT_APP_DANGEROUSLY_DISABLE_HOST_CHECK: ${{ vars.REACT_APP_DANGEROUSLY_DISABLE_HOST_CHECK }} - REACT_APP_ENVIRONMENT: ${{ vars.REACT_APP_ENVIRONMENT }} - REACT_APP_SITE_URL: ${{ vars.REACT_APP_SITE_URL }} ECR_REGISTRY: public.ecr.aws/x2l9m6x8/selfscape/moimjang - IMAGE_NAME: moimjang-frontend jobs: - build-and-push: + # ๋ณ€๊ฒฝ๋œ ์•ฑ ๊ฐ์ง€ + detect-changes: + runs-on: ubuntu-24.04 + outputs: + admin-changed: ${{ steps.changes.outputs.admin }} + contents-changed: ${{ steps.changes.outputs.contents }} + seller-changed: ${{ steps.changes.outputs.seller }} + packages-changed: ${{ steps.changes.outputs.packages }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Detect changes + uses: dorny/paths-filter@v3 + id: changes + with: + filters: | + admin: + - 'frontend/apps/admin/**' + contents: + - 'frontend/apps/contents/**' + seller: + - 'frontend/apps/seller/**' + packages: + - 'frontend/packages/**' + + # Admin ์•ฑ ๋นŒ๋“œ + build-admin: + needs: detect-changes + if: needs.detect-changes.outputs.admin-changed == 'true' || needs.detect-changes.outputs.packages-changed == 'true' runs-on: ubuntu-24.04 environment: 'production' @@ -38,47 +62,140 @@ jobs: with: registry-type: public - - name: Verify environment variables + - name: Build Docker image + run: | + cd frontend + echo "Building admin app..." + docker build \ + --build-arg REACT_APP_SERVER_URI="${{ vars.REACT_APP_SERVER_URI }}" \ + --build-arg REACT_APP_NODE_ENV="${{ vars.REACT_APP_NODE_ENV }}" \ + --build-arg REACT_APP_DANGEROUSLY_DISABLE_HOST_CHECK="${{ vars.REACT_APP_DANGEROUSLY_DISABLE_HOST_CHECK }}" \ + --build-arg REACT_APP_ENVIRONMENT="${{ vars.REACT_APP_ENVIRONMENT }}" \ + --build-arg REACT_APP_SITE_URL="${{ vars.REACT_APP_SITE_URL }}" \ + -f apps/admin/Dockerfile \ + -t moimjang-admin . + + - name: Tag and Push Docker images run: | - echo "Environment variables loaded:" - echo "REACT_APP_SERVER_URI: ${{ env.REACT_APP_SERVER_URI }}" - echo "REACT_APP_NODE_ENV: ${{ env.REACT_APP_NODE_ENV }}" - echo "REACT_APP_DANGEROUSLY_DISABLE_HOST_CHECK: ${{ env.REACT_APP_DANGEROUSLY_DISABLE_HOST_CHECK }}" - echo "REACT_APP_ENVIRONMENT: ${{ env.REACT_APP_ENVIRONMENT }}" - echo "REACT_APP_SITE_URL: ${{ env.REACT_APP_SITE_URL }}" + TODAY=$(date +%Y-%m-%d) + PR_NUMBER=${{ github.event.number }} + + # Tag images + docker tag moimjang-admin ${{ env.ECR_REGISTRY }}-admin:pr-$PR_NUMBER + docker tag moimjang-admin ${{ env.ECR_REGISTRY }}-admin:latest + docker tag moimjang-admin ${{ env.ECR_REGISTRY }}-admin:$TODAY + + # Push images + docker push ${{ env.ECR_REGISTRY }}-admin:pr-$PR_NUMBER + docker push ${{ env.ECR_REGISTRY }}-admin:latest + docker push ${{ env.ECR_REGISTRY }}-admin:$TODAY + + echo "Successfully pushed admin app to ECR" + + # Contents ์•ฑ ๋นŒ๋“œ + build-contents: + needs: detect-changes + if: needs.detect-changes.outputs.contents-changed == 'true' || needs.detect-changes.outputs.packages-changed == 'true' + runs-on: ubuntu-24.04 + environment: 'production' + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: us-east-1 + + - name: Login to Amazon ECR Public + id: login-ecr-public + uses: aws-actions/amazon-ecr-login@v2 + with: + registry-type: public - name: Build Docker image run: | cd frontend + echo "Building contents app..." docker build \ - --build-arg REACT_APP_SERVER_URI="${{ env.REACT_APP_SERVER_URI }}" \ - --build-arg REACT_APP_NODE_ENV="${{ env.REACT_APP_NODE_ENV }}" \ - --build-arg REACT_APP_DANGEROUSLY_DISABLE_HOST_CHECK="${{ env.REACT_APP_DANGEROUSLY_DISABLE_HOST_CHECK }}" \ - --build-arg REACT_APP_ENVIRONMENT="${{ env.REACT_APP_ENVIRONMENT }}" \ - --build-arg REACT_APP_SITE_URL="${{ env.REACT_APP_SITE_URL }}" \ - -t ${{ env.IMAGE_NAME }} . + --build-arg NEXT_PUBLIC_SERVER_URL="${{ vars.REACT_APP_SERVER_URI }}" \ + --build-arg NEXT_PUBLIC_NODE_ENV="${{ vars.REACT_APP_NODE_ENV }}" \ + --build-arg NEXT_PUBLIC_DANGEROUSLY_DISABLE_HOST_CHECK="${{ vars.REACT_APP_DANGEROUSLY_DISABLE_HOST_CHECK }}" \ + --build-arg NEXT_PUBLIC_ENVIRONMENT="${{ vars.REACT_APP_ENVIRONMENT }}" \ + --build-arg NEXT_PUBLIC_SITE_URL="${{ vars.REACT_APP_SITE_URL }}" \ + -f apps/contents/Dockerfile \ + -t moimjang-contents . - - name: Tag Docker images + - name: Tag and Push Docker images run: | - # Get today's date in YYYY-MM-DD format TODAY=$(date +%Y-%m-%d) + PR_NUMBER=${{ github.event.number }} + + # Tag images + docker tag moimjang-contents ${{ env.ECR_REGISTRY }}-contents:pr-$PR_NUMBER + docker tag moimjang-contents ${{ env.ECR_REGISTRY }}-contents:latest + docker tag moimjang-contents ${{ env.ECR_REGISTRY }}-contents:$TODAY - # Tag with latest and today's date - docker tag ${{ env.IMAGE_NAME }} ${{ env.ECR_REGISTRY }}:latest - docker tag ${{ env.IMAGE_NAME }} ${{ env.ECR_REGISTRY }}:$TODAY + # Push images + docker push ${{ env.ECR_REGISTRY }}-contents:pr-$PR_NUMBER + docker push ${{ env.ECR_REGISTRY }}-contents:latest + docker push ${{ env.ECR_REGISTRY }}-contents:$TODAY - echo "Tagged images:" - echo "- ${{ env.ECR_REGISTRY }}:latest" - echo "- ${{ env.ECR_REGISTRY }}:$TODAY" + echo "Successfully pushed contents app to ECR" + + # Seller ์•ฑ ๋นŒ๋“œ + build-seller: + needs: detect-changes + if: needs.detect-changes.outputs.seller-changed == 'true' || needs.detect-changes.outputs.packages-changed == 'true' + runs-on: ubuntu-24.04 + environment: 'production' + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: us-east-1 - - name: Push to ECR + - name: Login to Amazon ECR Public + id: login-ecr-public + uses: aws-actions/amazon-ecr-login@v2 + with: + registry-type: public + + - name: Build Docker image + run: | + cd frontend + echo "Building seller app..." + docker build \ + --build-arg NEXT_PUBLIC_SERVER_URL="${{ vars.REACT_APP_SERVER_URI }}" \ + --build-arg NEXT_PUBLIC_NODE_ENV="${{ vars.REACT_APP_NODE_ENV }}" \ + --build-arg NEXT_PUBLIC_DANGEROUSLY_DISABLE_HOST_CHECK="${{ vars.REACT_APP_DANGEROUSLY_DISABLE_HOST_CHECK }}" \ + --build-arg NEXT_PUBLIC_ENVIRONMENT="${{ vars.REACT_APP_ENVIRONMENT }}" \ + --build-arg NEXT_PUBLIC_SITE_URL="${{ vars.REACT_APP_SITE_URL }}" \ + -f apps/seller/Dockerfile \ + -t moimjang-seller . + + - name: Tag and Push Docker images run: | TODAY=$(date +%Y-%m-%d) + PR_NUMBER=${{ github.event.number }} + + # Tag images + docker tag moimjang-seller ${{ env.ECR_REGISTRY }}-seller:pr-$PR_NUMBER + docker tag moimjang-seller ${{ env.ECR_REGISTRY }}-seller:latest + docker tag moimjang-seller ${{ env.ECR_REGISTRY }}-seller:$TODAY - # Push both tags - docker push ${{ env.ECR_REGISTRY }}:latest - docker push ${{ env.ECR_REGISTRY }}:$TODAY + # Push images + docker push ${{ env.ECR_REGISTRY }}-seller:pr-$PR_NUMBER + docker push ${{ env.ECR_REGISTRY }}-seller:latest + docker push ${{ env.ECR_REGISTRY }}-seller:$TODAY - echo "Successfully pushed to ECR:" - echo "- ${{ env.ECR_REGISTRY }}:latest" - echo "- ${{ env.ECR_REGISTRY }}:$TODAY" \ No newline at end of file + echo "Successfully pushed seller app to ECR" \ No newline at end of file diff --git a/.gitignore b/.gitignore index b4f7dbd5..79a67272 100644 --- a/.gitignore +++ b/.gitignore @@ -182,3 +182,61 @@ pyrightconfig.json *.pem **/migrate-mongo-config.js *.ini + +# Ignore all node_modules folders +**/node_modules/ + +# Next.js build output +**/.next/ +**/out/ +**/dist/ + +# Logs and debug files +logs/ +*.log +npm-debug.log* +yarn-error.log* +pnpm-debug.log* + +# Runtime files +pids/ +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov/ + +# Coverage directory used by tools like istanbul +coverage/ + +# nyc test coverage +.nyc_output/ + +# Environment variables +.env +.env.local +.env.*.local + +# Mac system files +.DS_Store + +# IDE/editor directories and files +.vscode/ +.idea/ +*.suo +*.ntvs* +*.njsproj +*.sln + +# TypeScript cache +**/.turbo/ + +# Optional lock files +package-lock.json +pnpm-lock.yaml +yarn.lock + +# Misc +*.tgz + diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml index 522e000d..28bf83ab 100644 --- a/backend/docker-compose.yml +++ b/backend/docker-compose.yml @@ -3,6 +3,7 @@ version: "3.7" services: postgres: image: postgres + container_name: moimjang-backend-postgres environment: POSTGRES_USER: ${MOIMJANG_POSTGRES_USER} POSTGRES_PASSWORD: ${MOIMJANG_POSTGRES_PASSWORD} @@ -22,6 +23,7 @@ services: mongodb: image: mongo:latest + container_name: moimjang-backend-mongodb ports: - "27017:27017" environment: @@ -40,6 +42,7 @@ services: migrate: image: webapp + container_name: moimjang-backend-migrate build: ./ depends_on: - postgres @@ -59,6 +62,7 @@ services: condition: service_healthy build: ./ image: webapp + container_name: moimjang-backend-webapp ports: - "8001:8000" volumes: @@ -70,6 +74,7 @@ services: restart: unless-stopped networks: - moimjang-network + - minio_minio_network volumes: postgres_data: @@ -81,3 +86,5 @@ networks: moimjang-network: driver: bridge name: moimjang-network-default + minio_minio_network: + external: true diff --git a/frontend/.gitignore b/frontend/.gitignore index 3e7eac30..f4fcd01f 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -1,28 +1,42 @@ -# dependencies -/node_modules +# Ignore all node_modules folders +**/node_modules/ -# production -/build -/dist +# Next.js build output +**/.next/ +**/out/ +**/dist/ -# misc -.DS_Store -.env -.env.local -.env.development.local -.env.test.local -.env.production.local - -# testing -/coverage - -# logs +# Logs and debug files +logs/ +*.log npm-debug.log* -yarn-debug.log* yarn-error.log* pnpm-debug.log* + +# Runtime files +pids/ +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov/ + +# Coverage directory used by tools like istanbul +coverage/ + +# nyc test coverage +.nyc_output/ -# IDEs and editors +# Environment variables +.env +.env.local +.env.*.local + +# Mac system files +.DS_Store + +# IDE/editor directories and files .vscode/ .idea/ *.suo @@ -30,5 +44,13 @@ pnpm-debug.log* *.njsproj *.sln -# system files -Thumbs.db \ No newline at end of file +# TypeScript cache +**/.turbo/ + +# Optional lock files +package-lock.json +pnpm-lock.yaml +yarn.lock + +# Misc +*.tgz diff --git a/frontend/README.md b/frontend/README.md index 8aab74d1..f4506c73 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,67 +1,159 @@ -## ๐Ÿ–ฅ๏ธ ์„œ๋น„์Šค URL +## **๐Ÿ“˜ Moimjang ํ”„๋ก ํŠธ์—”๋“œ ๋ฆฌํฌ์ง€ํ„ฐ๋ฆฌ** -- **์ปจํ…์ธ  ์‚ฌ์ดํŠธ**: [https://www.moimjang.com/login](https://www.moimjang.com/login) -- **์–ด๋“œ๋ฏผ ์‚ฌ์ดํŠธ**: [https://www.moimjang.com/admin/login](https://www.moimjang.com/admin/login) -- **ํŒ๋งค์ž ์‚ฌ์ดํŠธ**: [https://www.moimjang.com/landing](https://www.moimjang.com/landing) +### **๐Ÿท๏ธ ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ** + +- ๋ชจ์ž„์žฅ(Moimjang)์€ ์˜คํ”„๋ผ์ธ ๋ชจ์ž„์„ ์šด์˜ํ•˜๋Š” ํ˜ธ์ŠคํŠธ์™€ ์ฐธ๊ฐ€์ž๋ฅผ ์œ„ํ•œ ํ†ตํ•ฉ ๊ด€๋ฆฌ ํ”Œ๋žซํผ์ž…๋‹ˆ๋‹ค. + +๋…๋ฆฝ์ ์ธ 3๊ฐœ์˜ ์›น ๋„๋ฉ”์ธ(์†Œ๋น„์ž, ํŒ๋งค์ž, ์ฝ˜ํ…์ธ  ๊ด€๋ฆฌ์ž)์œผ๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์œผ๋ฉฐ, ๊ฐ ๋„๋ฉ”์ธ์€ ๋ชฉ์ ์— ๋”ฐ๋ผ ์—ญํ• ์ด ๊ตฌ๋ถ„๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. + +- ์†Œ๋น„์ž(์œ ์ €) ์‚ฌ์ดํŠธ: [https://contents.moimjang.com](https://contents.moimjang.com/?host=tester) +- ํŒ๋งค์ž(ํ˜ธ์ŠคํŠธ) ์‚ฌ์ดํŠธ: [https://seller.moimjang.com](https://seller.moimjang.com/?host=tester) +- ์ฝ˜ํ…์ธ  ๊ด€๋ฆฌ์ž(์–ด๋“œ๋ฏผ) ์‚ฌ์ดํŠธ: [https://admin.moimjang.com](https://admin.moimjang.com/?host=tester) + +**ํ…Œ์ŠคํŠธ ๊ณ„์ •** + +- ์•„์ด๋””: tester@naver.com +- ๋น„๋ฐ€๋ฒˆํ˜ธ: 123123123a --- -## ๐Ÿš€ ์‹œ์ž‘ํ•˜๊ธฐ +### **๐Ÿ›  ๊ธฐ์ˆ  ์Šคํƒ** -```bash -1. ํ”„๋กœ์ ํŠธ ํด๋ก  +๋ชจ์ž„์žฅ์€ ์ตœ์‹  ๋ชจ๋˜ ํ”„๋ก ํŠธ์—”๋“œ ๊ธฐ์ˆ ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•œ **๋ชจ๋…ธ๋ ˆํฌ ๊ตฌ์กฐ**๋กœ ๊ฐœ๋ฐœ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. + +- **Monorepo ๊ด€๋ฆฌ**: npm workspaces +- **ํ”„๋ ˆ์ž„์›Œํฌ**: Next.js 15, React 18, TypeScript +- **์ƒํƒœ ๊ด€๋ฆฌ**: Zustand, Immer +- **๋ฐ์ดํ„ฐ ํŒจ์นญ ๋ฐ ์บ์‹ฑ**: React Query +- **์Šคํƒ€์ผ๋ง**: css-module , styled-components +- **API ํ†ต์‹ **: fetch & axios +- **๊ธฐํƒ€**: MSW, Swiper, React Icons, React Spinners -git clone https://github.com/selfscape/moimjang.git +--- -2. ํŒจํ‚ค์ง€ ์„ค์น˜ +### **๐Ÿ“Œ ์ฃผ์š” ๊ธฐ๋Šฅ ์†Œ๊ฐœ** -npm install +๊ฐ ๋„๋ฉ”์ธ์—์„œ ์ œ๊ณตํ•˜๋Š” ์ฃผ์š” ๊ธฐ๋Šฅ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: + +### **โœ… ์†Œ๋น„์ž(์œ ์ €)** + +- ๋ชจ์ž„ ๋ฆฌ์ŠคํŠธ ์กฐํšŒ ๋ฐ ์ƒ์„ธ ๋ณด๊ธฐ +- ์งˆ๋ฌธ ์นด๋“œ ๊ธฐ๋ฐ˜ ์ฒซ์ธ์ƒ ๋ฆฌ๋ทฐ ์ž‘์„ฑ +- ํ›„๊ธฐ ๋ฐ ํ”ผ๋“œ๋ฐฑ ์ œ๊ณต + +### **โœ… ํŒ๋งค์ž(ํ˜ธ์ŠคํŠธ)** + +- ๋ชจ์ž„ ์ƒ์„ฑ ๋ฐ ์ˆ˜์ • +- ์ฐธ์—ฌ์ž ๋žœ๋ค ๋งค์นญ +- ์ฐธ์—ฌ์ž ๋ฆฌ๋ทฐ ์—ด๋žŒ ๋ฐ ์‘๋‹ต ๊ด€๋ฆฌ + +### **โœ… ์ฝ˜ํ…์ธ  ๊ด€๋ฆฌ์ž(์–ด๋“œ๋ฏผ)** + +- ์ „์ฒด ๋ชจ์ž„/์‚ฌ์šฉ์ž/๋ฆฌ๋ทฐ ํ†ต๊ณ„ ๊ด€๋ฆฌ +- ์œ ์ € ์ œ์žฌ ๋ฐ ์ƒํƒœ ๋ชจ๋‹ˆํ„ฐ๋ง +- ์šด์˜ ์ •์ฑ… ๊ธฐ๋ฐ˜ ์ฝ˜ํ…์ธ  ์ˆ˜๋™ ์กฐ์ž‘ + +--- -3. ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹คํ–‰ +### **๐Ÿงฑ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ** -npm start +``` +apps/ + โ”œโ”€โ”€ admin # ์ฝ˜ํ…์ธ  ๊ด€๋ฆฌ ์›น์•ฑ + โ”œโ”€โ”€ contents # ์†Œ๋น„์ž(์œ ์ €)์šฉ ์›น์•ฑ + โ””โ”€โ”€ seller # ํŒ๋งค์ž(ํ˜ธ์ŠคํŠธ) ์›น์•ฑ -4. ๋นŒ๋“œ +packages/ + โ””โ”€โ”€ (๊ณตํ†ต ์ปดํฌ๋„ŒํŠธ, ์œ ํ‹ธ๋ฆฌํ‹ฐ, ํƒ€์ž… ๊ด€๋ฆฌ ์˜ˆ์ •) +``` -npm run build +--- +### **๐Ÿš€ ์‹คํ–‰ ๋ฐฉ๋ฒ•** -โธป +1. **ํŒจํ‚ค์ง€ ์„ค์น˜** +``` +npm install +``` + +1. **๋„๋ฉ”์ธ๋ณ„ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ •** -๐Ÿ“ฆ ์ฃผ์š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ -โ€ข React 18 -โ€ข React Router DOM -โ€ข Recoil -โ€ข @tanstack/react-query -โ€ข MSW (Mock Service Worker) -โ€ข styled-components -โ€ข TypeScript -โ€ข Axios +๊ฐ apps/\* ๋””๋ ‰ํ† ๋ฆฌ์— .env.local ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๊ณ  ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค: -โธป +``` +REACT_APP_SERVER_URI=https://matchlog.chanyoung.site +``` -๐Ÿ› ๏ธ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ -โ€ข Node.js 16 ์ด์ƒ -โ€ข npm 8 ์ด์ƒ -โ€ข TypeScript 4.9 ์ด์ƒ +> โš ๏ธ ํ™˜๊ฒฝ๋ณ€์ˆ˜ ํŒŒ์ผ์ด ์—†์„ ๊ฒฝ์šฐ API ํ†ต์‹  ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -โธป +1. **๋„๋ฉ”์ธ ์‹คํ–‰ ๋ช…๋ น์–ด** -โš™๏ธ MSW ์„ค์ • -โ€ข msw์˜ ์›Œ์ปค๋Š” public/ ๋””๋ ‰ํ† ๋ฆฌ์— ์œ„์น˜ํ•ฉ๋‹ˆ๋‹ค. -โ€ข ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ mock API๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ๋ธŒ๋ผ์šฐ์ € ๊ธฐ๋ฐ˜ ์„œ๋น„์Šค ์›Œ์ปค๊ฐ€ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. +``` +npm run dev:admin # ์–ด๋“œ๋ฏผ ์‹คํ–‰ +npm run dev:seller # ํŒ๋งค์ž ๋„๋ฉ”์ธ ์‹คํ–‰ +npm run dev:contents # ์†Œ๋น„์ž ๋„๋ฉ”์ธ ์‹คํ–‰ +``` + +### **๐Ÿšข ๋ฐฐํฌ ๋ฐฉ๋ฒ•** + +#### **1. Admin ์•ฑ (React + nginx)** + +```bash +cd frontend + +# ๋นŒ๋“œ +docker build \ + --build-arg REACT_APP_SERVER_URI=https://matchlog.chanyoung.site \ + --build-arg REACT_APP_NODE_ENV=production \ + --build-arg REACT_APP_ENVIRONMENT=production \ + --build-arg REACT_APP_SITE_URL=https://admin.moimjang.site \ + -f apps/admin/Dockerfile \ + -t moimjang/admin . + +# ์‹คํ–‰ (ํฌํŠธ 3131) +docker run -d --name moimjang-admin -p 3131:80 moimjang/admin ``` -## ๋ฐฐํฌํ•˜๊ธฐ -1. ๋ฐฐํฌ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ • +#### **2. Contents ์•ฑ (Next.js + Node.js)** + ```bash -cp .env.example .env +cd frontend + +# ๋นŒ๋“œ +docker build \ + --build-arg NEXT_PUBLIC_SERVER_URL=https://matchlog.chanyoung.site \ + --build-arg NEXT_PUBLIC_NODE_ENV=production \ + --build-arg NEXT_PUBLIC_ENVIRONMENT=production \ + --build-arg NEXT_PUBLIC_SITE_URL=https://contents.moimjang.site \ + -f apps/contents/Dockerfile \ + -t moimjang/contents . + +# ์‹คํ–‰ (ํฌํŠธ 3133) +docker run -d --name moimjang-contents -p 3133:3000 moimjang/contents ``` -2. ๋ฐฐํฌ +#### **3. Seller ์•ฑ (Next.js + Node.js)** + ```bash -docker build -t moimjang-frontend . -docker run -d -p 80:80 moimjang-frontend +cd frontend + +# ๋นŒ๋“œ +docker build \ + --build-arg NEXT_PUBLIC_SERVER_URL=https://matchlog.chanyoung.site \ + --build-arg NEXT_PUBLIC_NODE_ENV=production \ + --build-arg NEXT_PUBLIC_ENVIRONMENT=production \ + --build-arg NEXT_PUBLIC_SITE_URL=https://seller.moimjang.site \ + -f apps/seller/Dockerfile \ + -t moimjang/seller . + +# ์‹คํ–‰ (ํฌํŠธ 3132) +docker run -d --name moimjang-seller -p 3132:3000 moimjang/seller ``` +> **์ฃผ์˜์‚ฌํ•ญ**: +> +> - Admin ์•ฑ์€ `REACT_APP_*` ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค (React) +> - Contents/Seller ์•ฑ์€ `NEXT_PUBLIC_*` ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค (Next.js) +> - Admin ์•ฑ์€ nginx๋กœ ์„œ๋น™๋˜์–ด ํฌํŠธ 80์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค +> - Contents/Seller ์•ฑ์€ Node.js๋กœ ์„œ๋น™๋˜์–ด ํฌํŠธ 3000์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค diff --git a/frontend/apps/admin/.gitignore b/frontend/apps/admin/.gitignore new file mode 100644 index 00000000..3e7eac30 --- /dev/null +++ b/frontend/apps/admin/.gitignore @@ -0,0 +1,34 @@ +# dependencies +/node_modules + +# production +/build +/dist + +# misc +.DS_Store +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# testing +/coverage + +# logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# IDEs and editors +.vscode/ +.idea/ +*.suo +*.ntvs* +*.njsproj +*.sln + +# system files +Thumbs.db \ No newline at end of file diff --git a/frontend/Dockerfile b/frontend/apps/admin/Dockerfile similarity index 50% rename from frontend/Dockerfile rename to frontend/apps/admin/Dockerfile index 5791f757..bcc2ddbf 100644 --- a/frontend/Dockerfile +++ b/frontend/apps/admin/Dockerfile @@ -1,4 +1,5 @@ # ๋ฉ€ํ‹ฐ ์Šคํ…Œ์ด์ง€ ๋นŒ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ตœ์ ํ™”๋œ ํ”„๋กœ๋•์…˜ ์ด๋ฏธ์ง€ ์ƒ์„ฑ +# ์‚ฌ์šฉ๋ฒ•: cd /frontend && docker build -f apps/admin/Dockerfile -t moimjang/admin . # 1๋‹จ๊ณ„: ๋นŒ๋“œ ์Šคํ…Œ์ด์ง€ FROM node:18-alpine AS builder @@ -20,29 +21,34 @@ ENV REACT_APP_DANGEROUSLY_DISABLE_HOST_CHECK=$REACT_APP_DANGEROUSLY_DISABLE_HOST ENV REACT_APP_ENVIRONMENT=$REACT_APP_ENVIRONMENT ENV REACT_APP_SITE_URL=$REACT_APP_SITE_URL -# package.json๊ณผ package-lock.json ๋ณต์‚ฌ (์บ์‹œ ์ตœ์ ํ™”) +# ๋ฃจํŠธ์˜ package.json๊ณผ package-lock.json ๋ณต์‚ฌ (monorepo ์„ค์ •) COPY package*.json ./ -# ์˜์กด์„ฑ ์„ค์น˜ (devDependencies ํฌํ•จํ•ด์•ผ ๋นŒ๋“œ ๊ฐ€๋Šฅ) -RUN npm ci +# admin ์•ฑ์˜ package.json๋„ ๋ณต์‚ฌ (workspace ์„ค์ •์„ ์œ„ํ•ด) +COPY apps/admin/package*.json ./apps/admin/ -# ์†Œ์Šค ์ฝ”๋“œ ๋ณต์‚ฌ -COPY . . +# workspace ์ „์ฒด ์˜์กด์„ฑ ์„ค์น˜ (๋ชจ๋“  workspace์˜ ์˜์กด์„ฑ ํฌํ•จ) +RUN npm install --include-workspace-root --workspaces -# React ์•ฑ ๋นŒ๋“œ (ํ”„๋กœ๋•์…˜ ๋ชจ๋“œ) +# admin ์•ฑ ์†Œ์Šค ์ฝ”๋“œ๋งŒ ๋ณต์‚ฌ +COPY apps/admin ./apps/admin/ + +# admin ์•ฑ ๋นŒ๋“œ๋ฅผ ์œ„ํ•ด ์ž‘์—… ๋””๋ ‰ํ† ๋ฆฌ ๋ณ€๊ฒฝ +WORKDIR /app/apps/admin RUN npm run build # 2๋‹จ๊ณ„: ํ”„๋กœ๋•์…˜ ์Šคํ…Œ์ด์ง€ FROM nginx:alpine # ๋นŒ๋“œ๋œ ํŒŒ์ผ์„ nginx ์ •์  ํŒŒ์ผ ๋””๋ ‰ํ† ๋ฆฌ๋กœ ๋ณต์‚ฌ -COPY --from=builder /app/build /usr/share/nginx/html +COPY --from=builder /app/apps/admin/build /usr/share/nginx/html +COPY --from=builder /app/apps/admin/build /etc/nginx/html -# nginx ์„ค์ • ํŒŒ์ผ ๋ณต์‚ฌ -COPY nginx.conf /etc/nginx/conf.d/default.conf +# nginx ์„ค์ • ํŒŒ์ผ ๋ณต์‚ฌ (build context๊ฐ€ frontend์ด๋ฏ€๋กœ) +COPY apps/admin/nginx.conf /etc/nginx/conf.d/default.conf # ํฌํŠธ 80 ๋…ธ์ถœ EXPOSE 80 # nginx ์‹คํ–‰ -CMD ["nginx", "-g", "daemon off;"] +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/frontend/nginx.conf b/frontend/apps/admin/nginx.conf similarity index 100% rename from frontend/nginx.conf rename to frontend/apps/admin/nginx.conf diff --git a/frontend/apps/admin/package.json b/frontend/apps/admin/package.json new file mode 100644 index 00000000..0bf1cb34 --- /dev/null +++ b/frontend/apps/admin/package.json @@ -0,0 +1,67 @@ +{ + "name": "admin", + "version": "0.1.0", + "private": true, + "proxy": "https://socialing.chanyoung.site", + "dependencies": { + "@tanstack/react-query": "^5.62.3", + "@tanstack/react-query-devtools": "^5.62.3", + "@testing-library/jest-dom": "^5.17.0", + "@testing-library/react": "^13.4.0", + "@testing-library/user-event": "^13.5.0", + "@types/jest": "^27.5.2", + "@types/node": "^16.18.121", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "axios": "^1.7.9", + "graphql-request": "^7.1.2", + "lodash": "^4.17.21", + "msw": "^1.3.5", + "react": "^18.3.1", + "react-beautiful-dnd": "^13.1.1", + "react-dnd": "^16.0.1", + "react-dnd-html5-backend": "^16.0.1", + "react-dom": "^18.3.1", + "react-icons": "^4.10.1", + "react-loader-spinner": "^6.1.6", + "react-router-dom": "^7.0.1", + "react-scripts": "5.0.1", + "recoil": "^0.7.7", + "styled-components": "^6.1.13", + "styled-reset": "^4.5.2", + "swiper": "^11.2.5", + "typescript": "^4.9.5", + "uuid": "^11.0.3", + "web-vitals": "^2.1.4" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "devDependencies": { + "@babel/plugin-proposal-private-property-in-object": "^7.21.11" + }, + "msw": { + "workerDirectory": "public" + } +} diff --git a/frontend/public/favicon/apple-touch-icon.png b/frontend/apps/admin/public/favicon/apple-touch-icon.png similarity index 100% rename from frontend/public/favicon/apple-touch-icon.png rename to frontend/apps/admin/public/favicon/apple-touch-icon.png diff --git a/frontend/public/favicon/favicon-96x96.png b/frontend/apps/admin/public/favicon/favicon-96x96.png similarity index 100% rename from frontend/public/favicon/favicon-96x96.png rename to frontend/apps/admin/public/favicon/favicon-96x96.png diff --git a/frontend/public/favicon/favicon.ico b/frontend/apps/admin/public/favicon/favicon.ico similarity index 100% rename from frontend/public/favicon/favicon.ico rename to frontend/apps/admin/public/favicon/favicon.ico diff --git a/frontend/public/favicon/favicon.svg b/frontend/apps/admin/public/favicon/favicon.svg similarity index 100% rename from frontend/public/favicon/favicon.svg rename to frontend/apps/admin/public/favicon/favicon.svg diff --git a/frontend/public/favicon/site.webmanifest b/frontend/apps/admin/public/favicon/site.webmanifest similarity index 100% rename from frontend/public/favicon/site.webmanifest rename to frontend/apps/admin/public/favicon/site.webmanifest diff --git a/frontend/public/favicon/web-app-manifest-192x192.png b/frontend/apps/admin/public/favicon/web-app-manifest-192x192.png similarity index 100% rename from frontend/public/favicon/web-app-manifest-192x192.png rename to frontend/apps/admin/public/favicon/web-app-manifest-192x192.png diff --git a/frontend/public/favicon/web-app-manifest-512x512.png b/frontend/apps/admin/public/favicon/web-app-manifest-512x512.png similarity index 100% rename from frontend/public/favicon/web-app-manifest-512x512.png rename to frontend/apps/admin/public/favicon/web-app-manifest-512x512.png diff --git a/frontend/public/fonts/spoqaHanSansNeo/Bold.woff2 b/frontend/apps/admin/public/fonts/spoqaHanSansNeo/Bold.woff2 similarity index 100% rename from frontend/public/fonts/spoqaHanSansNeo/Bold.woff2 rename to frontend/apps/admin/public/fonts/spoqaHanSansNeo/Bold.woff2 diff --git a/frontend/public/fonts/spoqaHanSansNeo/Light.woff2 b/frontend/apps/admin/public/fonts/spoqaHanSansNeo/Light.woff2 similarity index 100% rename from frontend/public/fonts/spoqaHanSansNeo/Light.woff2 rename to frontend/apps/admin/public/fonts/spoqaHanSansNeo/Light.woff2 diff --git a/frontend/public/fonts/spoqaHanSansNeo/Medium.woff2 b/frontend/apps/admin/public/fonts/spoqaHanSansNeo/Medium.woff2 similarity index 100% rename from frontend/public/fonts/spoqaHanSansNeo/Medium.woff2 rename to frontend/apps/admin/public/fonts/spoqaHanSansNeo/Medium.woff2 diff --git a/frontend/public/fonts/spoqaHanSansNeo/Regular.woff2 b/frontend/apps/admin/public/fonts/spoqaHanSansNeo/Regular.woff2 similarity index 100% rename from frontend/public/fonts/spoqaHanSansNeo/Regular.woff2 rename to frontend/apps/admin/public/fonts/spoqaHanSansNeo/Regular.woff2 diff --git a/frontend/public/fonts/spoqaHanSansNeo/Thin.woff2 b/frontend/apps/admin/public/fonts/spoqaHanSansNeo/Thin.woff2 similarity index 100% rename from frontend/public/fonts/spoqaHanSansNeo/Thin.woff2 rename to frontend/apps/admin/public/fonts/spoqaHanSansNeo/Thin.woff2 diff --git a/frontend/public/index.html b/frontend/apps/admin/public/index.html similarity index 100% rename from frontend/public/index.html rename to frontend/apps/admin/public/index.html diff --git a/frontend/public/logo192.png b/frontend/apps/admin/public/logo192.png similarity index 100% rename from frontend/public/logo192.png rename to frontend/apps/admin/public/logo192.png diff --git a/frontend/public/logo512.png b/frontend/apps/admin/public/logo512.png similarity index 100% rename from frontend/public/logo512.png rename to frontend/apps/admin/public/logo512.png diff --git a/frontend/public/manifest.json b/frontend/apps/admin/public/manifest.json similarity index 100% rename from frontend/public/manifest.json rename to frontend/apps/admin/public/manifest.json diff --git a/frontend/public/mockServiceWorker.js b/frontend/apps/admin/public/mockServiceWorker.js similarity index 100% rename from frontend/public/mockServiceWorker.js rename to frontend/apps/admin/public/mockServiceWorker.js diff --git a/frontend/public/robots.txt b/frontend/apps/admin/public/robots.txt similarity index 100% rename from frontend/public/robots.txt rename to frontend/apps/admin/public/robots.txt diff --git a/frontend/apps/admin/src/App.tsx b/frontend/apps/admin/src/App.tsx new file mode 100644 index 00000000..1a9cade0 --- /dev/null +++ b/frontend/apps/admin/src/App.tsx @@ -0,0 +1,58 @@ +import { BrowserRouter, Routes, Route } from "react-router-dom"; +import { DndProvider } from "react-dnd"; +import { HTML5Backend } from "react-dnd-html5-backend"; +import { ThemeProvider } from "styled-components"; + +import { Pathnames } from "constants/index"; +import GlobalStyles from "styles/GlobalStyles"; +import theme from "styles/Theme"; +import User from "pages/user"; +import Channel from "pages/channel"; +import UserDetail from "pages/user/UserDetail"; +import ChannelForm from "pages/channel/form/ChannelForm"; +import Brand from "pages/brand"; +import EditBrand from "pages/brand/form/EditBrand"; +import Landing from "pages/landing"; +import Submission from "pages/submission"; +import Host from "pages/host"; +import AdminSignUpForm from "pages/SignUpForm"; +import Login from "pages/Login"; +import RoleBasedHome from "pages/home/RoleBasedHome"; + +function App() { + return ( + + + + + + } /> + } /> + } /> + } /> + } /> + } + /> + } /> + } /> + } + /> + } /> + } + /> + } /> + } /> + + + + + ); +} + +export default App; diff --git a/frontend/src/api/admin/axiosInstance.ts b/frontend/apps/admin/src/api/axiosInstance.ts similarity index 94% rename from frontend/src/api/admin/axiosInstance.ts rename to frontend/apps/admin/src/api/axiosInstance.ts index 74ee92b0..7c2e22ab 100644 --- a/frontend/src/api/admin/axiosInstance.ts +++ b/frontend/apps/admin/src/api/axiosInstance.ts @@ -1,7 +1,6 @@ import { serverUrl } from "configs"; import axios from "axios"; -// axios ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ const axiosInstance = axios.create({ baseURL: `${serverUrl}`, }); diff --git a/frontend/src/api/admin/brand/createBrand.tsx b/frontend/apps/admin/src/api/brand/createBrand.tsx similarity index 100% rename from frontend/src/api/admin/brand/createBrand.tsx rename to frontend/apps/admin/src/api/brand/createBrand.tsx diff --git a/frontend/src/api/admin/brand/deleteBrand.tsx b/frontend/apps/admin/src/api/brand/deleteBrand.tsx similarity index 100% rename from frontend/src/api/admin/brand/deleteBrand.tsx rename to frontend/apps/admin/src/api/brand/deleteBrand.tsx diff --git a/frontend/src/api/admin/brand/deleteDetailImage.tsx b/frontend/apps/admin/src/api/brand/deleteDetailImage.tsx similarity index 100% rename from frontend/src/api/admin/brand/deleteDetailImage.tsx rename to frontend/apps/admin/src/api/brand/deleteDetailImage.tsx diff --git a/frontend/src/api/admin/brand/editBrand.tsx b/frontend/apps/admin/src/api/brand/editBrand.tsx similarity index 100% rename from frontend/src/api/admin/brand/editBrand.tsx rename to frontend/apps/admin/src/api/brand/editBrand.tsx diff --git a/frontend/src/api/admin/brand/getBrandById.tsx b/frontend/apps/admin/src/api/brand/getBrandById.tsx similarity index 100% rename from frontend/src/api/admin/brand/getBrandById.tsx rename to frontend/apps/admin/src/api/brand/getBrandById.tsx diff --git a/frontend/src/api/admin/brand/getBrandReviewList.tsx b/frontend/apps/admin/src/api/brand/getBrandReviewList.tsx similarity index 100% rename from frontend/src/api/admin/brand/getBrandReviewList.tsx rename to frontend/apps/admin/src/api/brand/getBrandReviewList.tsx diff --git a/frontend/src/api/admin/brand/getBrands.tsx b/frontend/apps/admin/src/api/brand/getBrands.tsx similarity index 100% rename from frontend/src/api/admin/brand/getBrands.tsx rename to frontend/apps/admin/src/api/brand/getBrands.tsx diff --git a/frontend/src/api/admin/brand/getQuestionCategories.tsx b/frontend/apps/admin/src/api/brand/getQuestionCategories.tsx similarity index 100% rename from frontend/src/api/admin/brand/getQuestionCategories.tsx rename to frontend/apps/admin/src/api/brand/getQuestionCategories.tsx diff --git a/frontend/src/api/admin/brand/hooks/useCreateBrandReview.ts b/frontend/apps/admin/src/api/brand/hooks/useCreateBrandReview.ts similarity index 86% rename from frontend/src/api/admin/brand/hooks/useCreateBrandReview.ts rename to frontend/apps/admin/src/api/brand/hooks/useCreateBrandReview.ts index 426c23f7..d15ebcaa 100644 --- a/frontend/src/api/admin/brand/hooks/useCreateBrandReview.ts +++ b/frontend/apps/admin/src/api/brand/hooks/useCreateBrandReview.ts @@ -1,9 +1,10 @@ import { AxiosError } from "axios"; import { useMutation } from "@tanstack/react-query"; -import axiosInstance from "api/consumer/axiosInstance"; +import axiosInstance from "api/axiosInstance"; import { ACCEESS_TOKEN, OWNER, serverUrl } from "configs"; import { BrandReview } from "../types/brandReview"; +import { getCookie } from "hooks/auth/useOwnerCookie"; export interface CreateBrandReviewInput { user_id: number; @@ -14,7 +15,7 @@ export const createBrandReview = async ( requestBody: CreateBrandReviewInput ): Promise => { const token = localStorage.getItem(ACCEESS_TOKEN); - const owner = localStorage.getItem(OWNER); + const owner = getCookie(OWNER); const result = await axiosInstance.post( `${serverUrl}/customers/brandReviews`, diff --git a/frontend/src/api/admin/brand/hooks/useDeleteBrandReview.ts b/frontend/apps/admin/src/api/brand/hooks/useDeleteBrandReview.ts similarity index 82% rename from frontend/src/api/admin/brand/hooks/useDeleteBrandReview.ts rename to frontend/apps/admin/src/api/brand/hooks/useDeleteBrandReview.ts index b14eaa5b..0741c4cb 100644 --- a/frontend/src/api/admin/brand/hooks/useDeleteBrandReview.ts +++ b/frontend/apps/admin/src/api/brand/hooks/useDeleteBrandReview.ts @@ -1,12 +1,13 @@ import { AxiosError } from "axios"; import { useMutation } from "@tanstack/react-query"; -import axiosInstance from "api/consumer/axiosInstance"; +import axiosInstance from "api/axiosInstance"; import { ACCEESS_TOKEN, OWNER, serverUrl } from "configs"; +import { getCookie } from "hooks/auth/useOwnerCookie"; export const deleteBrandReview = async (review_id: number): Promise => { const token = localStorage.getItem(ACCEESS_TOKEN); - const owner = localStorage.getItem(OWNER); + const owner = getCookie(OWNER); const response = await axiosInstance.delete( `${serverUrl}/brandReviews/${review_id}`, diff --git a/frontend/src/api/admin/brand/hooks/useGetBrandReviews.ts b/frontend/apps/admin/src/api/brand/hooks/useGetBrandReviews.ts similarity index 87% rename from frontend/src/api/admin/brand/hooks/useGetBrandReviews.ts rename to frontend/apps/admin/src/api/brand/hooks/useGetBrandReviews.ts index e159e031..853d1459 100644 --- a/frontend/src/api/admin/brand/hooks/useGetBrandReviews.ts +++ b/frontend/apps/admin/src/api/brand/hooks/useGetBrandReviews.ts @@ -1,8 +1,9 @@ import { useQuery } from "@tanstack/react-query"; import { BrandReview } from "../types/brandReview"; -import { BRAND_REVIEWS } from "constants/admin/queryKeys"; +import { BRAND_REVIEWS } from "constants/queryKeys"; import { ACCEESS_TOKEN, OWNER, serverUrl } from "configs"; import axios from "axios"; +import { getCookie } from "hooks/auth/useOwnerCookie"; export interface GetBrandReviewsOutput { reviews: Array; @@ -20,7 +21,7 @@ export const getBrandReviews = async ( params: GetBrandReviewsParams ): Promise => { const token = localStorage.getItem(ACCEESS_TOKEN); - const owner = localStorage.getItem(OWNER); + const owner = getCookie(OWNER); const response = await axios.get(`${serverUrl}/brandReviews`, { headers: { diff --git a/frontend/src/api/admin/brand/hooks/useGetQuestionCardCategories.ts b/frontend/apps/admin/src/api/brand/hooks/useGetQuestionCardCategories.ts similarity index 85% rename from frontend/src/api/admin/brand/hooks/useGetQuestionCardCategories.ts rename to frontend/apps/admin/src/api/brand/hooks/useGetQuestionCardCategories.ts index 1795d52d..4628b71d 100644 --- a/frontend/src/api/admin/brand/hooks/useGetQuestionCardCategories.ts +++ b/frontend/apps/admin/src/api/brand/hooks/useGetQuestionCardCategories.ts @@ -1,7 +1,8 @@ import { useQuery } from "@tanstack/react-query"; -import { GET_QUESTION_CATEGORIES } from "constants/admin/queryKeys"; +import { GET_QUESTION_CATEGORIES } from "constants/queryKeys"; import { ACCEESS_TOKEN, OWNER, serverUrl } from "configs"; -import axiosInstance from "api/consumer/axiosInstance"; +import axiosInstance from "api/axiosInstance"; +import { getCookie } from "hooks/auth/useOwnerCookie"; export interface GetQuestionCardCategoriesOutput { id: number; @@ -14,7 +15,7 @@ export const getQuestionCardCategories = async ( brandId: number ): Promise> => { const token = localStorage.getItem(ACCEESS_TOKEN); - const owner = localStorage.getItem(OWNER); + const owner = getCookie(OWNER); const response = await axiosInstance.get( `${serverUrl}/customers/questionCardCategories`, diff --git a/frontend/src/api/admin/brand/hooks/useGetQuestionCategories.ts b/frontend/apps/admin/src/api/brand/hooks/useGetQuestionCategories.ts similarity index 86% rename from frontend/src/api/admin/brand/hooks/useGetQuestionCategories.ts rename to frontend/apps/admin/src/api/brand/hooks/useGetQuestionCategories.ts index 1c44e51c..2677e371 100644 --- a/frontend/src/api/admin/brand/hooks/useGetQuestionCategories.ts +++ b/frontend/apps/admin/src/api/brand/hooks/useGetQuestionCategories.ts @@ -1,10 +1,11 @@ import { AxiosError } from "axios"; import { useQuery } from "@tanstack/react-query"; -import { GET_QUESTION_CATEGORIES } from "constants/admin/queryKeys"; +import { GET_QUESTION_CATEGORIES } from "constants/queryKeys"; import { ACCEESS_TOKEN, OWNER, serverUrl } from "configs"; import { QuestionCardDeck } from "interfaces/questionCardCategory"; -import axiosInstance from "api/admin/axiosInstance"; +import axiosInstance from "api/axiosInstance"; +import { getCookie } from "hooks/auth/useOwnerCookie"; export interface QuestionCategoriesOutput { id: number; @@ -21,7 +22,7 @@ export const getQuestionCategories = async ( brand_id: string ): Promise => { const token = localStorage.getItem(ACCEESS_TOKEN); - const owner = localStorage.getItem(OWNER); + const owner = getCookie(OWNER); const response = await axiosInstance.get( `${serverUrl}/brands/${brand_id}/questionCardCategories/questionCards`, diff --git a/frontend/src/api/admin/brand/hooks/useUpdateBrandReview.ts b/frontend/apps/admin/src/api/brand/hooks/useUpdateBrandReview.ts similarity index 81% rename from frontend/src/api/admin/brand/hooks/useUpdateBrandReview.ts rename to frontend/apps/admin/src/api/brand/hooks/useUpdateBrandReview.ts index f7baa2d3..0219d24e 100644 --- a/frontend/src/api/admin/brand/hooks/useUpdateBrandReview.ts +++ b/frontend/apps/admin/src/api/brand/hooks/useUpdateBrandReview.ts @@ -1,9 +1,10 @@ import { AxiosError } from "axios"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { useMutation } from "@tanstack/react-query"; -import axiosInstance from "api/consumer/axiosInstance"; +import axiosInstance from "api/axiosInstance"; import { ACCEESS_TOKEN, OWNER, serverUrl } from "configs"; import { BrandReview } from "../types/brandReview"; +import { getCookie } from "hooks/auth/useOwnerCookie"; export interface Params { review_id: number; @@ -17,7 +18,7 @@ export const updateBrandReview = async ( param: Params ): Promise => { const token = localStorage.getItem(ACCEESS_TOKEN); - const owner = localStorage.getItem(OWNER); + const owner = getCookie(OWNER); const response = await axiosInstance.put( `${serverUrl}/brandReviews/${param.review_id}`, diff --git a/frontend/src/api/admin/brand/hooks/useUpdateBrandState.ts b/frontend/apps/admin/src/api/brand/hooks/useUpdateBrandState.ts similarity index 94% rename from frontend/src/api/admin/brand/hooks/useUpdateBrandState.ts rename to frontend/apps/admin/src/api/brand/hooks/useUpdateBrandState.ts index c8d3ca89..f9dcea4c 100644 --- a/frontend/src/api/admin/brand/hooks/useUpdateBrandState.ts +++ b/frontend/apps/admin/src/api/brand/hooks/useUpdateBrandState.ts @@ -1,6 +1,6 @@ import { AxiosError } from "axios"; import { useMutation } from "@tanstack/react-query"; -import axiosInstance from "api/admin/axiosInstance"; +import axiosInstance from "api/axiosInstance"; import { ACCEESS_TOKEN, serverUrl } from "configs"; import { Brand, BrandState } from "interfaces/brand"; diff --git a/frontend/src/api/admin/brand/joinCategoryWithBrand.tsx b/frontend/apps/admin/src/api/brand/joinCategoryWithBrand.tsx similarity index 100% rename from frontend/src/api/admin/brand/joinCategoryWithBrand.tsx rename to frontend/apps/admin/src/api/brand/joinCategoryWithBrand.tsx diff --git a/frontend/src/api/admin/brand/types/brandReview.ts b/frontend/apps/admin/src/api/brand/types/brandReview.ts similarity index 100% rename from frontend/src/api/admin/brand/types/brandReview.ts rename to frontend/apps/admin/src/api/brand/types/brandReview.ts diff --git a/frontend/src/api/admin/brand/uploadDetailImage.tsx b/frontend/apps/admin/src/api/brand/uploadDetailImage.tsx similarity index 100% rename from frontend/src/api/admin/brand/uploadDetailImage.tsx rename to frontend/apps/admin/src/api/brand/uploadDetailImage.tsx diff --git a/frontend/src/api/admin/brand/uploadThumbnailImage.tsx b/frontend/apps/admin/src/api/brand/uploadThumbnailImage.tsx similarity index 91% rename from frontend/src/api/admin/brand/uploadThumbnailImage.tsx rename to frontend/apps/admin/src/api/brand/uploadThumbnailImage.tsx index 79ef319f..2220922f 100644 --- a/frontend/src/api/admin/brand/uploadThumbnailImage.tsx +++ b/frontend/apps/admin/src/api/brand/uploadThumbnailImage.tsx @@ -1,5 +1,4 @@ import { ACCEESS_TOKEN, serverUrl } from "configs"; -import { QuestionCard } from "interfaces/questionCardCategory"; import axiosInstance from "../axiosInstance"; export const uploadThumbnailImage = async ( diff --git a/frontend/src/api/admin/channel/createChannel.tsx b/frontend/apps/admin/src/api/channel/createChannel.tsx similarity index 100% rename from frontend/src/api/admin/channel/createChannel.tsx rename to frontend/apps/admin/src/api/channel/createChannel.tsx diff --git a/frontend/src/api/admin/channel/deleteChannel.tsx b/frontend/apps/admin/src/api/channel/deleteChannel.tsx similarity index 100% rename from frontend/src/api/admin/channel/deleteChannel.tsx rename to frontend/apps/admin/src/api/channel/deleteChannel.tsx diff --git a/frontend/src/api/admin/channel/editChannel.tsx b/frontend/apps/admin/src/api/channel/editChannel.tsx similarity index 100% rename from frontend/src/api/admin/channel/editChannel.tsx rename to frontend/apps/admin/src/api/channel/editChannel.tsx diff --git a/frontend/src/api/admin/channel/getChannelsById.tsx b/frontend/apps/admin/src/api/channel/getChannelsById.tsx similarity index 100% rename from frontend/src/api/admin/channel/getChannelsById.tsx rename to frontend/apps/admin/src/api/channel/getChannelsById.tsx diff --git a/frontend/src/api/admin/channel/hooks/useGetChannels.ts b/frontend/apps/admin/src/api/channel/hooks/useGetChannels.ts similarity index 83% rename from frontend/src/api/admin/channel/hooks/useGetChannels.ts rename to frontend/apps/admin/src/api/channel/hooks/useGetChannels.ts index c782c986..cab39fbd 100644 --- a/frontend/src/api/admin/channel/hooks/useGetChannels.ts +++ b/frontend/apps/admin/src/api/channel/hooks/useGetChannels.ts @@ -1,10 +1,11 @@ import { useQuery } from "@tanstack/react-query"; import { AxiosError } from "axios"; -import axiosInstance from "api/admin/axiosInstance"; +import axiosInstance from "api/axiosInstance"; import { ACCEESS_TOKEN, OWNER, serverUrl, USER_EMAIL } from "configs"; -import { GET_CHANNELS } from "constants/admin/queryKeys"; +import { GET_CHANNELS } from "constants/queryKeys"; import { Channel, ChannelState } from "interfaces/channels/index"; +import { getCookie } from "hooks/auth/useOwnerCookie"; export interface Output { channels: Array; @@ -22,7 +23,7 @@ export interface Params { export const getChannels = async (params: Params): Promise => { const token = localStorage.getItem(ACCEESS_TOKEN); - const owner = localStorage.getItem(OWNER); + const owner = getCookie(OWNER); const response = await axiosInstance.get(`${serverUrl}/channels`, { headers: { diff --git a/frontend/src/api/admin/channel/hooks/useUpdateChannelState.tsx b/frontend/apps/admin/src/api/channel/hooks/useUpdateChannelState.tsx similarity index 100% rename from frontend/src/api/admin/channel/hooks/useUpdateChannelState.tsx rename to frontend/apps/admin/src/api/channel/hooks/useUpdateChannelState.tsx diff --git a/frontend/src/api/admin/channel/queries/updateChannelState.tsx b/frontend/apps/admin/src/api/channel/queries/updateChannelState.tsx similarity index 91% rename from frontend/src/api/admin/channel/queries/updateChannelState.tsx rename to frontend/apps/admin/src/api/channel/queries/updateChannelState.tsx index 25577309..3254588a 100644 --- a/frontend/src/api/admin/channel/queries/updateChannelState.tsx +++ b/frontend/apps/admin/src/api/channel/queries/updateChannelState.tsx @@ -1,5 +1,5 @@ import { ACCEESS_TOKEN, serverUrl } from "configs"; -import axiosInstance from "api/admin/axiosInstance"; +import axiosInstance from "api/axiosInstance"; import { Channel, ChannelState } from "../type/channel"; export const updateChannelState = async ({ diff --git a/frontend/src/api/admin/channel/type/channel.ts b/frontend/apps/admin/src/api/channel/type/channel.ts similarity index 100% rename from frontend/src/api/admin/channel/type/channel.ts rename to frontend/apps/admin/src/api/channel/type/channel.ts diff --git a/frontend/src/api/admin/channel/updateSurveyResponse.tsx b/frontend/apps/admin/src/api/channel/updateSurveyResponse.tsx similarity index 100% rename from frontend/src/api/admin/channel/updateSurveyResponse.tsx rename to frontend/apps/admin/src/api/channel/updateSurveyResponse.tsx diff --git a/frontend/src/api/admin/game/createGamePairs.ts b/frontend/apps/admin/src/api/game/createGamePairs.ts similarity index 100% rename from frontend/src/api/admin/game/createGamePairs.ts rename to frontend/apps/admin/src/api/game/createGamePairs.ts diff --git a/frontend/src/api/admin/game/deleteGame.ts b/frontend/apps/admin/src/api/game/deleteGame.ts similarity index 100% rename from frontend/src/api/admin/game/deleteGame.ts rename to frontend/apps/admin/src/api/game/deleteGame.ts diff --git a/frontend/src/api/admin/game/getSearchGames.ts b/frontend/apps/admin/src/api/game/getSearchGames.ts similarity index 100% rename from frontend/src/api/admin/game/getSearchGames.ts rename to frontend/apps/admin/src/api/game/getSearchGames.ts diff --git a/frontend/src/api/admin/group/addUsersToGroup.tsx b/frontend/apps/admin/src/api/group/addUsersToGroup.tsx similarity index 100% rename from frontend/src/api/admin/group/addUsersToGroup.tsx rename to frontend/apps/admin/src/api/group/addUsersToGroup.tsx diff --git a/frontend/src/api/admin/group/createGroup.tsx b/frontend/apps/admin/src/api/group/createGroup.tsx similarity index 100% rename from frontend/src/api/admin/group/createGroup.tsx rename to frontend/apps/admin/src/api/group/createGroup.tsx diff --git a/frontend/src/api/admin/group/deleteGroup.tsx b/frontend/apps/admin/src/api/group/deleteGroup.tsx similarity index 100% rename from frontend/src/api/admin/group/deleteGroup.tsx rename to frontend/apps/admin/src/api/group/deleteGroup.tsx diff --git a/frontend/src/api/admin/group/deleteUsersToGroup.tsx b/frontend/apps/admin/src/api/group/deleteUsersToGroup.tsx similarity index 100% rename from frontend/src/api/admin/group/deleteUsersToGroup.tsx rename to frontend/apps/admin/src/api/group/deleteUsersToGroup.tsx diff --git a/frontend/src/api/admin/group/editGroup.tsx b/frontend/apps/admin/src/api/group/editGroup.tsx similarity index 100% rename from frontend/src/api/admin/group/editGroup.tsx rename to frontend/apps/admin/src/api/group/editGroup.tsx diff --git a/frontend/src/api/admin/group/editUserToGroup.tsx b/frontend/apps/admin/src/api/group/editUserToGroup.tsx similarity index 100% rename from frontend/src/api/admin/group/editUserToGroup.tsx rename to frontend/apps/admin/src/api/group/editUserToGroup.tsx diff --git a/frontend/src/api/admin/group/getGroupById.tsx b/frontend/apps/admin/src/api/group/getGroupById.tsx similarity index 100% rename from frontend/src/api/admin/group/getGroupById.tsx rename to frontend/apps/admin/src/api/group/getGroupById.tsx diff --git a/frontend/src/api/admin/group/getGroups.tsx b/frontend/apps/admin/src/api/group/getGroups.tsx similarity index 100% rename from frontend/src/api/admin/group/getGroups.tsx rename to frontend/apps/admin/src/api/group/getGroups.tsx diff --git a/frontend/src/api/admin/group/getSearchGroups.tsx b/frontend/apps/admin/src/api/group/getSearchGroups.tsx similarity index 100% rename from frontend/src/api/admin/group/getSearchGroups.tsx rename to frontend/apps/admin/src/api/group/getSearchGroups.tsx diff --git a/frontend/src/api/admin/group/getUserGroup.ts b/frontend/apps/admin/src/api/group/getUserGroup.ts similarity index 100% rename from frontend/src/api/admin/group/getUserGroup.ts rename to frontend/apps/admin/src/api/group/getUserGroup.ts diff --git a/frontend/src/api/admin/hostRegist/useCreateHostRegist.ts b/frontend/apps/admin/src/api/hostRegist/useCreateHostRegist.ts similarity index 92% rename from frontend/src/api/admin/hostRegist/useCreateHostRegist.ts rename to frontend/apps/admin/src/api/hostRegist/useCreateHostRegist.ts index ab078b7d..7e2a556b 100644 --- a/frontend/src/api/admin/hostRegist/useCreateHostRegist.ts +++ b/frontend/apps/admin/src/api/hostRegist/useCreateHostRegist.ts @@ -1,7 +1,7 @@ import { AxiosError } from "axios"; import { useMutation } from "@tanstack/react-query"; -import axiosInstance from "api/admin/axiosInstance"; +import axiosInstance from "api/axiosInstance"; import { ACCEESS_TOKEN, serverUrl } from "configs"; import { User } from "interfaces/user"; diff --git a/frontend/src/api/admin/hostRegist/useGetHostRegistAdmin.ts b/frontend/apps/admin/src/api/hostRegist/useGetHostRegistAdmin.ts similarity index 88% rename from frontend/src/api/admin/hostRegist/useGetHostRegistAdmin.ts rename to frontend/apps/admin/src/api/hostRegist/useGetHostRegistAdmin.ts index d9279114..26edc347 100644 --- a/frontend/src/api/admin/hostRegist/useGetHostRegistAdmin.ts +++ b/frontend/apps/admin/src/api/hostRegist/useGetHostRegistAdmin.ts @@ -1,8 +1,8 @@ import { AxiosError } from "axios"; import { useQuery } from "@tanstack/react-query"; -import axiosInstance from "api/admin/axiosInstance"; -import { GET_HOST_REGIST_ADMIN } from "constants/admin/queryKeys"; +import axiosInstance from "api/axiosInstance"; +import { GET_HOST_REGIST_ADMIN } from "constants/queryKeys"; import { ACCEESS_TOKEN, serverUrl } from "configs"; import { HostRegist } from "interfaces/hostRegist"; diff --git a/frontend/src/api/admin/hostRegist/useGetMyHostRegist.ts b/frontend/apps/admin/src/api/hostRegist/useGetMyHostRegist.ts similarity index 84% rename from frontend/src/api/admin/hostRegist/useGetMyHostRegist.ts rename to frontend/apps/admin/src/api/hostRegist/useGetMyHostRegist.ts index 0abe3a27..a2b39f20 100644 --- a/frontend/src/api/admin/hostRegist/useGetMyHostRegist.ts +++ b/frontend/apps/admin/src/api/hostRegist/useGetMyHostRegist.ts @@ -1,9 +1,9 @@ import { AxiosError } from "axios"; import { useQuery } from "@tanstack/react-query"; -import axiosInstance from "api/admin/axiosInstance"; +import axiosInstance from "api/axiosInstance"; import { ACCEESS_TOKEN, serverUrl } from "configs"; -import { GET_MY_HOST_REGIST } from "constants/admin/queryKeys"; +import { GET_MY_HOST_REGIST } from "constants/queryKeys"; import { User } from "interfaces/user"; export const getMyHostRegist = async (): Promise => { diff --git a/frontend/src/api/admin/hostRegist/useUpdateHostRegist.ts b/frontend/apps/admin/src/api/hostRegist/useUpdateHostRegist.ts similarity index 94% rename from frontend/src/api/admin/hostRegist/useUpdateHostRegist.ts rename to frontend/apps/admin/src/api/hostRegist/useUpdateHostRegist.ts index 3ad3e12d..7a791bae 100644 --- a/frontend/src/api/admin/hostRegist/useUpdateHostRegist.ts +++ b/frontend/apps/admin/src/api/hostRegist/useUpdateHostRegist.ts @@ -1,7 +1,7 @@ import { AxiosError } from "axios"; import { useMutation } from "@tanstack/react-query"; -import axiosInstance from "api/admin/axiosInstance"; +import axiosInstance from "api/axiosInstance"; import { ACCEESS_TOKEN, serverUrl } from "configs"; import { HostRegistState, User } from "interfaces/user"; diff --git a/frontend/src/api/admin/landing/hooks/useDeleteGalleryImage.ts b/frontend/apps/admin/src/api/landing/hooks/useDeleteGalleryImage.ts similarity index 84% rename from frontend/src/api/admin/landing/hooks/useDeleteGalleryImage.ts rename to frontend/apps/admin/src/api/landing/hooks/useDeleteGalleryImage.ts index a39c1e05..d6258404 100644 --- a/frontend/src/api/admin/landing/hooks/useDeleteGalleryImage.ts +++ b/frontend/apps/admin/src/api/landing/hooks/useDeleteGalleryImage.ts @@ -2,13 +2,14 @@ import { useMutation } from "@tanstack/react-query"; import { AxiosError } from "axios"; import { ACCEESS_TOKEN, OWNER, serverUrl } from "configs"; -import axiosInstance from "api/admin/axiosInstance"; +import axiosInstance from "api/axiosInstance"; +import { getCookie } from "hooks/auth/useOwnerCookie"; export const fetchDeleteGalleryImage = async ( image_id: number ): Promise => { const token = localStorage.getItem(ACCEESS_TOKEN); - const owner = localStorage.getItem(OWNER); + const owner = getCookie(OWNER); const result = await axiosInstance.delete( `${serverUrl}/landingAdmin/deleteImage/gallery`, diff --git a/frontend/src/api/admin/landing/hooks/useDeleteMainImage.ts b/frontend/apps/admin/src/api/landing/hooks/useDeleteMainImage.ts similarity index 82% rename from frontend/src/api/admin/landing/hooks/useDeleteMainImage.ts rename to frontend/apps/admin/src/api/landing/hooks/useDeleteMainImage.ts index df3d52f3..7e9712e9 100644 --- a/frontend/src/api/admin/landing/hooks/useDeleteMainImage.ts +++ b/frontend/apps/admin/src/api/landing/hooks/useDeleteMainImage.ts @@ -1,11 +1,12 @@ import { ACCEESS_TOKEN, OWNER, serverUrl } from "configs"; -import axiosInstance from "api/admin/axiosInstance"; +import axiosInstance from "api/axiosInstance"; import { AxiosError } from "axios"; import { useMutation } from "@tanstack/react-query"; +import { getCookie } from "hooks/auth/useOwnerCookie"; export const fetchDeleteMainImage = async (): Promise => { const token = localStorage.getItem(ACCEESS_TOKEN); - const owner = localStorage.getItem(OWNER); + const owner = getCookie(OWNER); const result = await axiosInstance.delete( `${serverUrl}/landingAdmin/deleteImage/main`, diff --git a/frontend/src/api/admin/landing/hooks/useGetGalleryImages.ts b/frontend/apps/admin/src/api/landing/hooks/useGetGalleryImages.ts similarity index 79% rename from frontend/src/api/admin/landing/hooks/useGetGalleryImages.ts rename to frontend/apps/admin/src/api/landing/hooks/useGetGalleryImages.ts index 21bd3d51..4d42335f 100644 --- a/frontend/src/api/admin/landing/hooks/useGetGalleryImages.ts +++ b/frontend/apps/admin/src/api/landing/hooks/useGetGalleryImages.ts @@ -1,15 +1,16 @@ import { useQuery } from "@tanstack/react-query"; import { AxiosError } from "axios"; -import axiosInstance from "api/admin/axiosInstance"; +import axiosInstance from "api/axiosInstance"; import { ACCEESS_TOKEN, OWNER, serverUrl } from "configs"; -import { GET_GALLERY_IMAGES } from "constants/landing/queryKeys"; +import { GET_GALLERY_IMAGES } from "constants/queryKeys"; +import { getCookie } from "hooks/auth/useOwnerCookie"; export const fetchGetGalleryImages = async (): Promise< Array<{ id: number; url: string }> > => { const token = localStorage.getItem(ACCEESS_TOKEN); - const owner = localStorage.getItem(OWNER); + const owner = getCookie(OWNER); const response = await axiosInstance.get( `${serverUrl}/landingAdmin/galleryImages`, diff --git a/frontend/src/api/admin/landing/hooks/useGetMainImage.ts b/frontend/apps/admin/src/api/landing/hooks/useGetMainImage.ts similarity index 78% rename from frontend/src/api/admin/landing/hooks/useGetMainImage.ts rename to frontend/apps/admin/src/api/landing/hooks/useGetMainImage.ts index a9015e5f..13798342 100644 --- a/frontend/src/api/admin/landing/hooks/useGetMainImage.ts +++ b/frontend/apps/admin/src/api/landing/hooks/useGetMainImage.ts @@ -2,15 +2,16 @@ import { ACCEESS_TOKEN, OWNER, serverUrl } from "configs"; import { AxiosError } from "axios"; import { useQuery } from "@tanstack/react-query"; -import axiosInstance from "api/admin/axiosInstance"; -import { GET_MAIN_IMAGE } from "constants/landing/queryKeys"; +import axiosInstance from "api/axiosInstance"; +import { GET_MAIN_IMAGE } from "constants/queryKeys"; +import { getCookie } from "hooks/auth/useOwnerCookie"; export const fetchGetMainImage = async (): Promise<{ id: number; url: string; }> => { const token = localStorage.getItem(ACCEESS_TOKEN); - const owner = localStorage.getItem(OWNER); + const owner = getCookie(OWNER); const response = await axiosInstance.get( `${serverUrl}/landingAdmin/mainImage`, diff --git a/frontend/src/api/admin/landing/hooks/useUpdateMainImage.ts b/frontend/apps/admin/src/api/landing/hooks/useUpdateMainImage.ts similarity index 93% rename from frontend/src/api/admin/landing/hooks/useUpdateMainImage.ts rename to frontend/apps/admin/src/api/landing/hooks/useUpdateMainImage.ts index 6b84b13b..7078ae34 100644 --- a/frontend/src/api/admin/landing/hooks/useUpdateMainImage.ts +++ b/frontend/apps/admin/src/api/landing/hooks/useUpdateMainImage.ts @@ -1,5 +1,5 @@ import { ACCEESS_TOKEN, serverUrl } from "configs"; -import axiosInstance from "api/admin/axiosInstance"; +import axiosInstance from "api/axiosInstance"; import { useMutation } from "@tanstack/react-query"; import { AxiosError } from "axios"; diff --git a/frontend/src/api/admin/landing/hooks/useUploadGalleryImage.ts b/frontend/apps/admin/src/api/landing/hooks/useUploadGalleryImage.ts similarity index 86% rename from frontend/src/api/admin/landing/hooks/useUploadGalleryImage.ts rename to frontend/apps/admin/src/api/landing/hooks/useUploadGalleryImage.ts index 32fd8e5e..148c4c6b 100644 --- a/frontend/src/api/admin/landing/hooks/useUploadGalleryImage.ts +++ b/frontend/apps/admin/src/api/landing/hooks/useUploadGalleryImage.ts @@ -1,7 +1,8 @@ import { AxiosError } from "axios"; import { useMutation } from "@tanstack/react-query"; -import axiosInstance from "api/admin/axiosInstance"; +import axiosInstance from "api/axiosInstance"; import { ACCEESS_TOKEN, OWNER, serverUrl } from "configs"; +import { getCookie } from "hooks/auth/useOwnerCookie"; interface Output { id: number; @@ -10,7 +11,7 @@ interface Output { export const fetchUploadGalleryImage = async (file: File): Promise => { const token = localStorage.getItem(ACCEESS_TOKEN); - const owner = localStorage.getItem(OWNER); + const owner = getCookie(OWNER); const formData = new FormData(); formData.append("file", file); diff --git a/frontend/src/api/admin/landing/hooks/useUploadMainImage.ts b/frontend/apps/admin/src/api/landing/hooks/useUploadMainImage.ts similarity index 86% rename from frontend/src/api/admin/landing/hooks/useUploadMainImage.ts rename to frontend/apps/admin/src/api/landing/hooks/useUploadMainImage.ts index 24c1fcf1..f12970e4 100644 --- a/frontend/src/api/admin/landing/hooks/useUploadMainImage.ts +++ b/frontend/apps/admin/src/api/landing/hooks/useUploadMainImage.ts @@ -1,7 +1,8 @@ import { AxiosError } from "axios"; import { useMutation } from "@tanstack/react-query"; import { ACCEESS_TOKEN, OWNER, serverUrl } from "configs"; -import axiosInstance from "api/admin/axiosInstance"; +import axiosInstance from "api/axiosInstance"; +import { getCookie } from "hooks/auth/useOwnerCookie"; export interface Output { id: number; @@ -10,7 +11,7 @@ export interface Output { export const fetchUploadMainImage = async (file: File): Promise => { const token = localStorage.getItem(ACCEESS_TOKEN); - const owner = localStorage.getItem(OWNER); + const owner = getCookie(OWNER); const formData = new FormData(); formData.append("file", file); diff --git a/frontend/src/api/admin/questionCard/card/createQuestionCard.tsx b/frontend/apps/admin/src/api/questionCard/card/createQuestionCard.tsx similarity index 100% rename from frontend/src/api/admin/questionCard/card/createQuestionCard.tsx rename to frontend/apps/admin/src/api/questionCard/card/createQuestionCard.tsx diff --git a/frontend/src/api/admin/questionCard/card/deleteQuestionCard.tsx b/frontend/apps/admin/src/api/questionCard/card/deleteQuestionCard.tsx similarity index 100% rename from frontend/src/api/admin/questionCard/card/deleteQuestionCard.tsx rename to frontend/apps/admin/src/api/questionCard/card/deleteQuestionCard.tsx diff --git a/frontend/src/api/admin/questionCard/card/updateQuestionCard.tsx b/frontend/apps/admin/src/api/questionCard/card/updateQuestionCard.tsx similarity index 100% rename from frontend/src/api/admin/questionCard/card/updateQuestionCard.tsx rename to frontend/apps/admin/src/api/questionCard/card/updateQuestionCard.tsx diff --git a/frontend/src/api/admin/questionCard/card/uploadCardImage.tsx b/frontend/apps/admin/src/api/questionCard/card/uploadCardImage.tsx similarity index 100% rename from frontend/src/api/admin/questionCard/card/uploadCardImage.tsx rename to frontend/apps/admin/src/api/questionCard/card/uploadCardImage.tsx diff --git a/frontend/src/api/admin/questionCard/category/createCategory.tsx b/frontend/apps/admin/src/api/questionCard/category/createCategory.tsx similarity index 100% rename from frontend/src/api/admin/questionCard/category/createCategory.tsx rename to frontend/apps/admin/src/api/questionCard/category/createCategory.tsx diff --git a/frontend/src/api/admin/questionCard/category/deleteCategory.tsx b/frontend/apps/admin/src/api/questionCard/category/deleteCategory.tsx similarity index 100% rename from frontend/src/api/admin/questionCard/category/deleteCategory.tsx rename to frontend/apps/admin/src/api/questionCard/category/deleteCategory.tsx diff --git a/frontend/src/api/admin/questionCard/category/updateCategory.tsx b/frontend/apps/admin/src/api/questionCard/category/updateCategory.tsx similarity index 100% rename from frontend/src/api/admin/questionCard/category/updateCategory.tsx rename to frontend/apps/admin/src/api/questionCard/category/updateCategory.tsx diff --git a/frontend/src/api/admin/questionCard/category/uploadCoverImage.tsx b/frontend/apps/admin/src/api/questionCard/category/uploadCoverImage.tsx similarity index 100% rename from frontend/src/api/admin/questionCard/category/uploadCoverImage.tsx rename to frontend/apps/admin/src/api/questionCard/category/uploadCoverImage.tsx diff --git a/frontend/src/api/admin/survey/hook/useCreateSurvey.ts b/frontend/apps/admin/src/api/survey/hook/useCreateSurvey.ts similarity index 90% rename from frontend/src/api/admin/survey/hook/useCreateSurvey.ts rename to frontend/apps/admin/src/api/survey/hook/useCreateSurvey.ts index eaf54a63..7c4f6dd6 100644 --- a/frontend/src/api/admin/survey/hook/useCreateSurvey.ts +++ b/frontend/apps/admin/src/api/survey/hook/useCreateSurvey.ts @@ -4,6 +4,7 @@ import axiosInstance from "../../axiosInstance"; import { Question } from "interfaces/brand/survey"; import { useMutation } from "@tanstack/react-query"; import { AxiosError } from "axios"; +import { getCookie } from "hooks/auth/useOwnerCookie"; export interface SurveyType { brand_id: string; @@ -16,7 +17,7 @@ export const fetchCreateSurvey = async ( requestBody: SurveyType ): Promise> => { const token = localStorage.getItem(ACCEESS_TOKEN); - const owner = localStorage.getItem(OWNER); + const owner = getCookie(OWNER); const result = await axiosInstance.post(`${serverUrl}/surveys`, requestBody, { headers: { diff --git a/frontend/src/api/admin/survey/hook/useGetSurvey.ts b/frontend/apps/admin/src/api/survey/hook/useGetSurvey.ts similarity index 80% rename from frontend/src/api/admin/survey/hook/useGetSurvey.ts rename to frontend/apps/admin/src/api/survey/hook/useGetSurvey.ts index e5773c3f..9360c8c9 100644 --- a/frontend/src/api/admin/survey/hook/useGetSurvey.ts +++ b/frontend/apps/admin/src/api/survey/hook/useGetSurvey.ts @@ -2,15 +2,16 @@ import { AxiosError } from "axios"; import { useQuery } from "@tanstack/react-query"; import { ACCEESS_TOKEN, OWNER, serverUrl } from "configs"; -import axiosInstance from "api/admin/axiosInstance"; -import { GET_SURVEYS } from "constants/admin/queryKeys"; +import axiosInstance from "api/axiosInstance"; +import { GET_SURVEYS } from "constants/queryKeys"; import { Survey } from "interfaces/brand/survey"; +import { getCookie } from "hooks/auth/useOwnerCookie"; export const fetchGetSurvey = async ( brand_id: string ): Promise> => { const token = localStorage.getItem(ACCEESS_TOKEN); - const owner = localStorage.getItem(OWNER); + const owner = getCookie(OWNER); const response = await axiosInstance.get(`${serverUrl}/surveys`, { headers: { diff --git a/frontend/src/api/admin/survey/hook/useGetSurveyById.ts b/frontend/apps/admin/src/api/survey/hook/useGetSurveyById.ts similarity index 86% rename from frontend/src/api/admin/survey/hook/useGetSurveyById.ts rename to frontend/apps/admin/src/api/survey/hook/useGetSurveyById.ts index 5b18d99e..32ae433b 100644 --- a/frontend/src/api/admin/survey/hook/useGetSurveyById.ts +++ b/frontend/apps/admin/src/api/survey/hook/useGetSurveyById.ts @@ -1,9 +1,9 @@ import { AxiosError } from "axios"; import { ACCEESS_TOKEN, serverUrl } from "configs"; -import axiosInstance from "api/admin/axiosInstance"; +import axiosInstance from "api/axiosInstance"; import { useQuery } from "@tanstack/react-query"; -import { GET_SURVEY_BY_ID } from "constants/admin/queryKeys"; +import { GET_SURVEY_BY_ID } from "constants/queryKeys"; import { Survey } from "interfaces/brand/survey"; export const fetchGetSurveyById = async ( diff --git a/frontend/src/api/admin/survey/hook/useUpdateSurvey.ts b/frontend/apps/admin/src/api/survey/hook/useUpdateSurvey.ts similarity index 92% rename from frontend/src/api/admin/survey/hook/useUpdateSurvey.ts rename to frontend/apps/admin/src/api/survey/hook/useUpdateSurvey.ts index 86f2a603..5a989a37 100644 --- a/frontend/src/api/admin/survey/hook/useUpdateSurvey.ts +++ b/frontend/apps/admin/src/api/survey/hook/useUpdateSurvey.ts @@ -4,6 +4,7 @@ import { ACCEESS_TOKEN, OWNER, serverUrl } from "configs"; import axiosInstance from "../../axiosInstance"; import { Question } from "interfaces/brand/survey"; +import { getCookie } from "hooks/auth/useOwnerCookie"; export interface SurveyType { brand_id: string; @@ -17,7 +18,7 @@ export const fetchUpdateSurvey = async ( survey_id: string ): Promise => { const token = localStorage.getItem(ACCEESS_TOKEN); - const owner = localStorage.getItem(OWNER); + const owner = getCookie(OWNER); const result = await axiosInstance.put( `${serverUrl}/surveys/${survey_id}`, diff --git a/frontend/src/api/admin/survey/hook/useUpdateSurveyState.ts b/frontend/apps/admin/src/api/survey/hook/useUpdateSurveyState.ts similarity index 100% rename from frontend/src/api/admin/survey/hook/useUpdateSurveyState.ts rename to frontend/apps/admin/src/api/survey/hook/useUpdateSurveyState.ts diff --git a/frontend/src/api/admin/users/addGroup.tsx b/frontend/apps/admin/src/api/users/addGroup.tsx similarity index 100% rename from frontend/src/api/admin/users/addGroup.tsx rename to frontend/apps/admin/src/api/users/addGroup.tsx diff --git a/frontend/src/api/admin/users/deleteChannelUser.tsx b/frontend/apps/admin/src/api/users/deleteChannelUser.tsx similarity index 100% rename from frontend/src/api/admin/users/deleteChannelUser.tsx rename to frontend/apps/admin/src/api/users/deleteChannelUser.tsx diff --git a/frontend/src/api/admin/users/deleteUser.tsx b/frontend/apps/admin/src/api/users/deleteUser.tsx similarity index 100% rename from frontend/src/api/admin/users/deleteUser.tsx rename to frontend/apps/admin/src/api/users/deleteUser.tsx diff --git a/frontend/src/api/admin/users/editProfile.tsx b/frontend/apps/admin/src/api/users/editProfile.tsx similarity index 100% rename from frontend/src/api/admin/users/editProfile.tsx rename to frontend/apps/admin/src/api/users/editProfile.tsx diff --git a/frontend/src/api/admin/users/getUserById.tsx b/frontend/apps/admin/src/api/users/getUserById.tsx similarity index 100% rename from frontend/src/api/admin/users/getUserById.tsx rename to frontend/apps/admin/src/api/users/getUserById.tsx diff --git a/frontend/src/api/admin/users/getUsers.tsx b/frontend/apps/admin/src/api/users/getUsers.tsx similarity index 100% rename from frontend/src/api/admin/users/getUsers.tsx rename to frontend/apps/admin/src/api/users/getUsers.tsx diff --git a/frontend/src/api/admin/users/getUsersNotInGroup.tsx b/frontend/apps/admin/src/api/users/getUsersNotInGroup.tsx similarity index 100% rename from frontend/src/api/admin/users/getUsersNotInGroup.tsx rename to frontend/apps/admin/src/api/users/getUsersNotInGroup.tsx diff --git a/frontend/src/api/admin/users/joinChannel.tsx b/frontend/apps/admin/src/api/users/joinChannel.tsx similarity index 100% rename from frontend/src/api/admin/users/joinChannel.tsx rename to frontend/apps/admin/src/api/users/joinChannel.tsx diff --git a/frontend/src/assets/icons/add-option-input.svg b/frontend/apps/admin/src/assets/icons/add-option-input.svg similarity index 100% rename from frontend/src/assets/icons/add-option-input.svg rename to frontend/apps/admin/src/assets/icons/add-option-input.svg diff --git a/frontend/src/assets/icons/addImage.svg b/frontend/apps/admin/src/assets/icons/addImage.svg similarity index 100% rename from frontend/src/assets/icons/addImage.svg rename to frontend/apps/admin/src/assets/icons/addImage.svg diff --git a/frontend/src/assets/icons/addphoto.svg b/frontend/apps/admin/src/assets/icons/addphoto.svg similarity index 100% rename from frontend/src/assets/icons/addphoto.svg rename to frontend/apps/admin/src/assets/icons/addphoto.svg diff --git a/frontend/src/assets/icons/apple.svg b/frontend/apps/admin/src/assets/icons/apple.svg similarity index 100% rename from frontend/src/assets/icons/apple.svg rename to frontend/apps/admin/src/assets/icons/apple.svg diff --git a/frontend/src/assets/icons/apply.svg b/frontend/apps/admin/src/assets/icons/apply.svg similarity index 100% rename from frontend/src/assets/icons/apply.svg rename to frontend/apps/admin/src/assets/icons/apply.svg diff --git a/frontend/src/assets/icons/arrow-down-small.svg b/frontend/apps/admin/src/assets/icons/arrow-down-small.svg similarity index 100% rename from frontend/src/assets/icons/arrow-down-small.svg rename to frontend/apps/admin/src/assets/icons/arrow-down-small.svg diff --git a/frontend/src/assets/icons/arrow-downward-big.svg b/frontend/apps/admin/src/assets/icons/arrow-downward-big.svg similarity index 100% rename from frontend/src/assets/icons/arrow-downward-big.svg rename to frontend/apps/admin/src/assets/icons/arrow-downward-big.svg diff --git a/frontend/src/assets/icons/arrow-downward-medium.svg b/frontend/apps/admin/src/assets/icons/arrow-downward-medium.svg similarity index 100% rename from frontend/src/assets/icons/arrow-downward-medium.svg rename to frontend/apps/admin/src/assets/icons/arrow-downward-medium.svg diff --git a/frontend/src/assets/icons/arrow-downward-small-red.svg b/frontend/apps/admin/src/assets/icons/arrow-downward-small-red.svg similarity index 100% rename from frontend/src/assets/icons/arrow-downward-small-red.svg rename to frontend/apps/admin/src/assets/icons/arrow-downward-small-red.svg diff --git a/frontend/src/assets/icons/arrow-downward-small.svg b/frontend/apps/admin/src/assets/icons/arrow-downward-small.svg similarity index 100% rename from frontend/src/assets/icons/arrow-downward-small.svg rename to frontend/apps/admin/src/assets/icons/arrow-downward-small.svg diff --git a/frontend/src/assets/icons/arrow-rightward-small.svg b/frontend/apps/admin/src/assets/icons/arrow-rightward-small.svg similarity index 100% rename from frontend/src/assets/icons/arrow-rightward-small.svg rename to frontend/apps/admin/src/assets/icons/arrow-rightward-small.svg diff --git a/frontend/src/assets/icons/arrow-top-small.svg b/frontend/apps/admin/src/assets/icons/arrow-top-small.svg similarity index 100% rename from frontend/src/assets/icons/arrow-top-small.svg rename to frontend/apps/admin/src/assets/icons/arrow-top-small.svg diff --git a/frontend/src/assets/icons/arrow-triangle-small.svg b/frontend/apps/admin/src/assets/icons/arrow-triangle-small.svg similarity index 100% rename from frontend/src/assets/icons/arrow-triangle-small.svg rename to frontend/apps/admin/src/assets/icons/arrow-triangle-small.svg diff --git a/frontend/src/assets/icons/changeImage.svg b/frontend/apps/admin/src/assets/icons/changeImage.svg similarity index 100% rename from frontend/src/assets/icons/changeImage.svg rename to frontend/apps/admin/src/assets/icons/changeImage.svg diff --git a/frontend/src/assets/icons/check.svg b/frontend/apps/admin/src/assets/icons/check.svg similarity index 100% rename from frontend/src/assets/icons/check.svg rename to frontend/apps/admin/src/assets/icons/check.svg diff --git a/frontend/src/assets/icons/checkbox-checked-grey.svg b/frontend/apps/admin/src/assets/icons/checkbox-checked-grey.svg similarity index 100% rename from frontend/src/assets/icons/checkbox-checked-grey.svg rename to frontend/apps/admin/src/assets/icons/checkbox-checked-grey.svg diff --git a/frontend/src/assets/icons/checkbox-checked-white.svg b/frontend/apps/admin/src/assets/icons/checkbox-checked-white.svg similarity index 100% rename from frontend/src/assets/icons/checkbox-checked-white.svg rename to frontend/apps/admin/src/assets/icons/checkbox-checked-white.svg diff --git a/frontend/src/assets/icons/close-white.svg b/frontend/apps/admin/src/assets/icons/close-white.svg similarity index 100% rename from frontend/src/assets/icons/close-white.svg rename to frontend/apps/admin/src/assets/icons/close-white.svg diff --git a/frontend/src/assets/icons/delete.svg b/frontend/apps/admin/src/assets/icons/delete.svg similarity index 100% rename from frontend/src/assets/icons/delete.svg rename to frontend/apps/admin/src/assets/icons/delete.svg diff --git a/frontend/src/assets/icons/dragHandle.svg b/frontend/apps/admin/src/assets/icons/dragHandle.svg similarity index 100% rename from frontend/src/assets/icons/dragHandle.svg rename to frontend/apps/admin/src/assets/icons/dragHandle.svg diff --git a/frontend/src/assets/icons/exclamationmark.svg b/frontend/apps/admin/src/assets/icons/exclamationmark.svg similarity index 100% rename from frontend/src/assets/icons/exclamationmark.svg rename to frontend/apps/admin/src/assets/icons/exclamationmark.svg diff --git a/frontend/src/assets/icons/google.svg b/frontend/apps/admin/src/assets/icons/google.svg similarity index 100% rename from frontend/src/assets/icons/google.svg rename to frontend/apps/admin/src/assets/icons/google.svg diff --git a/frontend/src/assets/icons/info.svg b/frontend/apps/admin/src/assets/icons/info.svg similarity index 100% rename from frontend/src/assets/icons/info.svg rename to frontend/apps/admin/src/assets/icons/info.svg diff --git a/frontend/src/assets/icons/kakao.svg b/frontend/apps/admin/src/assets/icons/kakao.svg similarity index 100% rename from frontend/src/assets/icons/kakao.svg rename to frontend/apps/admin/src/assets/icons/kakao.svg diff --git a/frontend/src/assets/icons/login.svg b/frontend/apps/admin/src/assets/icons/login.svg similarity index 100% rename from frontend/src/assets/icons/login.svg rename to frontend/apps/admin/src/assets/icons/login.svg diff --git a/frontend/src/assets/icons/logo-black.svg b/frontend/apps/admin/src/assets/icons/logo-black.svg similarity index 100% rename from frontend/src/assets/icons/logo-black.svg rename to frontend/apps/admin/src/assets/icons/logo-black.svg diff --git a/frontend/src/assets/icons/logo-white.svg b/frontend/apps/admin/src/assets/icons/logo-white.svg similarity index 100% rename from frontend/src/assets/icons/logo-white.svg rename to frontend/apps/admin/src/assets/icons/logo-white.svg diff --git a/frontend/src/assets/icons/medium-double-left-active.svg b/frontend/apps/admin/src/assets/icons/medium-double-left-active.svg similarity index 100% rename from frontend/src/assets/icons/medium-double-left-active.svg rename to frontend/apps/admin/src/assets/icons/medium-double-left-active.svg diff --git a/frontend/src/assets/icons/medium-double-left-inactive.svg b/frontend/apps/admin/src/assets/icons/medium-double-left-inactive.svg similarity index 100% rename from frontend/src/assets/icons/medium-double-left-inactive.svg rename to frontend/apps/admin/src/assets/icons/medium-double-left-inactive.svg diff --git a/frontend/src/assets/icons/medium-double-right-active.svg b/frontend/apps/admin/src/assets/icons/medium-double-right-active.svg similarity index 100% rename from frontend/src/assets/icons/medium-double-right-active.svg rename to frontend/apps/admin/src/assets/icons/medium-double-right-active.svg diff --git a/frontend/src/assets/icons/medium-double-right-inactive.svg b/frontend/apps/admin/src/assets/icons/medium-double-right-inactive.svg similarity index 100% rename from frontend/src/assets/icons/medium-double-right-inactive.svg rename to frontend/apps/admin/src/assets/icons/medium-double-right-inactive.svg diff --git a/frontend/src/assets/icons/medium-left-active.svg b/frontend/apps/admin/src/assets/icons/medium-left-active.svg similarity index 100% rename from frontend/src/assets/icons/medium-left-active.svg rename to frontend/apps/admin/src/assets/icons/medium-left-active.svg diff --git a/frontend/src/assets/icons/medium-left-inactive.svg b/frontend/apps/admin/src/assets/icons/medium-left-inactive.svg similarity index 100% rename from frontend/src/assets/icons/medium-left-inactive.svg rename to frontend/apps/admin/src/assets/icons/medium-left-inactive.svg diff --git a/frontend/src/assets/icons/medium-right-active.svg b/frontend/apps/admin/src/assets/icons/medium-right-active.svg similarity index 100% rename from frontend/src/assets/icons/medium-right-active.svg rename to frontend/apps/admin/src/assets/icons/medium-right-active.svg diff --git a/frontend/src/assets/icons/medium-right-inactive.svg b/frontend/apps/admin/src/assets/icons/medium-right-inactive.svg similarity index 100% rename from frontend/src/assets/icons/medium-right-inactive.svg rename to frontend/apps/admin/src/assets/icons/medium-right-inactive.svg diff --git a/frontend/src/assets/icons/naver.svg b/frontend/apps/admin/src/assets/icons/naver.svg similarity index 100% rename from frontend/src/assets/icons/naver.svg rename to frontend/apps/admin/src/assets/icons/naver.svg diff --git a/frontend/src/assets/icons/photochange.svg b/frontend/apps/admin/src/assets/icons/photochange.svg similarity index 100% rename from frontend/src/assets/icons/photochange.svg rename to frontend/apps/admin/src/assets/icons/photochange.svg diff --git a/frontend/src/assets/icons/plus.svg b/frontend/apps/admin/src/assets/icons/plus.svg similarity index 100% rename from frontend/src/assets/icons/plus.svg rename to frontend/apps/admin/src/assets/icons/plus.svg diff --git a/frontend/src/assets/icons/questionmark.svg b/frontend/apps/admin/src/assets/icons/questionmark.svg similarity index 100% rename from frontend/src/assets/icons/questionmark.svg rename to frontend/apps/admin/src/assets/icons/questionmark.svg diff --git a/frontend/src/assets/icons/rectangle-delete.svg b/frontend/apps/admin/src/assets/icons/rectangle-delete.svg similarity index 100% rename from frontend/src/assets/icons/rectangle-delete.svg rename to frontend/apps/admin/src/assets/icons/rectangle-delete.svg diff --git a/frontend/src/assets/icons/rectangle-plus.svg b/frontend/apps/admin/src/assets/icons/rectangle-plus.svg similarity index 100% rename from frontend/src/assets/icons/rectangle-plus.svg rename to frontend/apps/admin/src/assets/icons/rectangle-plus.svg diff --git a/frontend/src/assets/icons/remove-option.svg b/frontend/apps/admin/src/assets/icons/remove-option.svg similarity index 100% rename from frontend/src/assets/icons/remove-option.svg rename to frontend/apps/admin/src/assets/icons/remove-option.svg diff --git a/frontend/src/assets/icons/removeImage.svg b/frontend/apps/admin/src/assets/icons/removeImage.svg similarity index 100% rename from frontend/src/assets/icons/removeImage.svg rename to frontend/apps/admin/src/assets/icons/removeImage.svg diff --git a/frontend/src/assets/icons/required.svg b/frontend/apps/admin/src/assets/icons/required.svg similarity index 100% rename from frontend/src/assets/icons/required.svg rename to frontend/apps/admin/src/assets/icons/required.svg diff --git a/frontend/src/assets/icons/reset.svg b/frontend/apps/admin/src/assets/icons/reset.svg similarity index 100% rename from frontend/src/assets/icons/reset.svg rename to frontend/apps/admin/src/assets/icons/reset.svg diff --git a/frontend/src/assets/icons/search-default.svg b/frontend/apps/admin/src/assets/icons/search-default.svg similarity index 100% rename from frontend/src/assets/icons/search-default.svg rename to frontend/apps/admin/src/assets/icons/search-default.svg diff --git a/frontend/src/assets/icons/search-focus.svg b/frontend/apps/admin/src/assets/icons/search-focus.svg similarity index 100% rename from frontend/src/assets/icons/search-focus.svg rename to frontend/apps/admin/src/assets/icons/search-focus.svg diff --git a/frontend/src/assets/icons/triangle.svg b/frontend/apps/admin/src/assets/icons/triangle.svg similarity index 100% rename from frontend/src/assets/icons/triangle.svg rename to frontend/apps/admin/src/assets/icons/triangle.svg diff --git a/frontend/src/assets/images/logo.png b/frontend/apps/admin/src/assets/images/logo.png similarity index 100% rename from frontend/src/assets/images/logo.png rename to frontend/apps/admin/src/assets/images/logo.png diff --git a/frontend/src/components/admin/brand/BrandTable.tsx b/frontend/apps/admin/src/components/brand/BrandTable.tsx similarity index 88% rename from frontend/src/components/admin/brand/BrandTable.tsx rename to frontend/apps/admin/src/components/brand/BrandTable.tsx index 94d24b65..fe32e9fa 100644 --- a/frontend/src/components/admin/brand/BrandTable.tsx +++ b/frontend/apps/admin/src/components/brand/BrandTable.tsx @@ -2,19 +2,20 @@ import styled from "styled-components"; import { useQueryClient } from "@tanstack/react-query"; import { useNavigate, useSearchParams } from "react-router-dom"; -import useUpdateBrandState from "api/admin/brand/hooks/useUpdateBrandState"; -import { Pathnames } from "constants/admin"; -import { GET_BRANDS } from "constants/admin/queryKeys"; +import useUpdateBrandState from "api/brand/hooks/useUpdateBrandState"; +import { Pathnames } from "constants/index"; +import { GET_BRANDS } from "constants/queryKeys"; -import useBrandTable from "hooks/admin/users/useBrandTable"; +import useBrandTable from "hooks/users/useBrandTable"; import useSystemModal from "hooks/common/components/useSystemModal"; -import useDeleteBrand from "hooks/admin/brand/useDeleteBrand"; +import useDeleteBrand from "hooks/brand/useDeleteBrand"; import { BrandState } from "interfaces/brand"; import { Table, TableContainer } from "../common/Table"; import Button from "../common/Button"; -import OptimizedImage from "components/common/OptimizedImage"; -import { GetBrandOutput } from "hooks/admin/brand/useGetBrands"; +import OptimizedImage from "components/common/image/OptimizedImage"; +import { GetBrandOutput } from "hooks/brand/useGetBrands"; +import useOwnerCookie from "hooks/auth/useOwnerCookie"; const brandStateLabels: Record = { [BrandState.ONGOING]: "์ง„ํ–‰์ค‘", @@ -24,15 +25,22 @@ const brandStateLabels: Record = { const BrandTable = () => { const [searchParams] = useSearchParams(); const page = Number(searchParams.get("page")) || 1; + const owner = useOwnerCookie(); + const isTester = owner === "tester"; const queryClient = useQueryClient(); const { brands, refetch, filter } = useBrandTable(); const navigate = useNavigate(); const { mutate: deleteBrand } = useDeleteBrand(); - const { openModal, showErrorModal, showAnyMessageModal } = useSystemModal(); const { mutate: updateBrandState } = useUpdateBrandState(); + const { openModal, showErrorModal, showAnyMessageModal } = useSystemModal(); const handleDeleteButtonClick = (id: number, brandname: string) => { + if (isTester) { + showAnyMessageModal("ํ…Œ์Šคํ„ฐ ๊ณ„์ •์€ ๋ธŒ๋žœ๋“œ ์‚ญ์ œ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค"); + return; + } + const queryKey = [ GET_BRANDS, { @@ -73,6 +81,11 @@ const BrandTable = () => { }; const handleStateChange = (brandId: number, newState: BrandState) => { + if (isTester) { + showAnyMessageModal("ํ…Œ์Šคํ„ฐ ๊ณ„์ •์€ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค"); + return; + } + updateBrandState( { brand_id: brandId, brand_state: newState }, { diff --git a/frontend/src/components/admin/brand/Controller.tsx b/frontend/apps/admin/src/components/brand/Controller.tsx similarity index 95% rename from frontend/src/components/admin/brand/Controller.tsx rename to frontend/apps/admin/src/components/brand/Controller.tsx index fb4a29cd..e1f5bd7b 100644 --- a/frontend/src/components/admin/brand/Controller.tsx +++ b/frontend/apps/admin/src/components/brand/Controller.tsx @@ -2,10 +2,10 @@ import { useNavigate, useSearchParams } from "react-router-dom"; import styled from "styled-components"; import { FaPlus } from "react-icons/fa"; -import { CreateBrandOutput } from "api/admin/brand/createBrand"; -import { Pathnames } from "constants/admin"; -import useBrandTable from "hooks/admin/users/useBrandTable"; -import useCreateBrand from "hooks/admin/brand/useCreateBrand"; +import { CreateBrandOutput } from "api/brand/createBrand"; +import { Pathnames } from "constants/index"; +import useBrandTable from "hooks/users/useBrandTable"; +import useCreateBrand from "hooks/brand/useCreateBrand"; import useSystemModal from "hooks/common/components/useSystemModal"; import { BrandState } from "interfaces/brand"; diff --git a/frontend/src/components/admin/brand/EditBrandForm.tsx b/frontend/apps/admin/src/components/brand/EditBrandForm.tsx similarity index 100% rename from frontend/src/components/admin/brand/EditBrandForm.tsx rename to frontend/apps/admin/src/components/brand/EditBrandForm.tsx diff --git a/frontend/src/components/admin/brand/SectionToggle.tsx b/frontend/apps/admin/src/components/brand/SectionToggle.tsx similarity index 100% rename from frontend/src/components/admin/brand/SectionToggle.tsx rename to frontend/apps/admin/src/components/brand/SectionToggle.tsx diff --git a/frontend/src/components/admin/brand/management/DescriptionInput.tsx b/frontend/apps/admin/src/components/brand/management/DescriptionInput.tsx similarity index 93% rename from frontend/src/components/admin/brand/management/DescriptionInput.tsx rename to frontend/apps/admin/src/components/brand/management/DescriptionInput.tsx index b2448405..3f6c17e3 100644 --- a/frontend/src/components/admin/brand/management/DescriptionInput.tsx +++ b/frontend/apps/admin/src/components/brand/management/DescriptionInput.tsx @@ -1,4 +1,4 @@ -import { useBrandFormContext } from "hooks/admin/brand/context/useBrandFormContext"; +import { useBrandFormContext } from "hooks/brand/context/useBrandFormContext"; import React from "react"; import styled from "styled-components"; diff --git a/frontend/src/components/admin/brand/management/DetailImageUpload.tsx b/frontend/apps/admin/src/components/brand/management/DetailImageUpload.tsx similarity index 83% rename from frontend/src/components/admin/brand/management/DetailImageUpload.tsx rename to frontend/apps/admin/src/components/brand/management/DetailImageUpload.tsx index f9d8ec2d..7e880068 100644 --- a/frontend/src/components/admin/brand/management/DetailImageUpload.tsx +++ b/frontend/apps/admin/src/components/brand/management/DetailImageUpload.tsx @@ -1,18 +1,23 @@ import React, { useCallback } from "react"; import styled from "styled-components"; -import { uploadDetailImage } from "api/admin/brand/uploadDetailImage"; -import { deleteDetailImage } from "api/admin/brand/deleteDetailImage"; +import { uploadDetailImage } from "api/brand/uploadDetailImage"; +import { deleteDetailImage } from "api/brand/deleteDetailImage"; -import { useBrandFormContext } from "hooks/admin/brand/context/useBrandFormContext"; +import { useBrandFormContext } from "hooks/brand/context/useBrandFormContext"; -import ProductImage from "components/admin/common/image/ProductImage"; -import AddImageInput from "components/admin/common/image/addImageInput"; +import ProductImage from "components/common/image/ProductImage"; +import AddImageInput from "components/common/image/addImageInput"; +import useSystemModal from "hooks/common/components/useSystemModal"; +import useOwnerCookie from "hooks/auth/useOwnerCookie"; -type DetailImage = { id?: number; url: string }; const DetailImageUpload = () => { const { brandId, setBrand, brand } = useBrandFormContext(); + const owner = useOwnerCookie(); + const isTester = owner === "tester"; + const { showAnyMessageModal } = useSystemModal(); + const images = brand.detailImages && brand.detailImages.length > 0 ? brand.detailImages.map((img) => ({ id: img.id, url: img.url })) @@ -27,6 +32,11 @@ const DetailImageUpload = () => { const handleImageChange = useCallback( async (index: number, file: File) => { + if (isTester) { + showAnyMessageModal("ํ…Œ์Šคํ„ฐ ๊ณ„์ •์€ ์ด๋ฏธ์ง€ ๋ณ€๊ฒฝ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค"); + return; + } + if (!file) return; try { const uploadedImage = await uploadDetailImage(file, brandId); @@ -46,6 +56,11 @@ const DetailImageUpload = () => { const handleDeleteImage = useCallback( async (index: number) => { + if (isTester) { + showAnyMessageModal("ํ…Œ์Šคํ„ฐ ๊ณ„์ •์€ ๋ธŒ๋žœ๋“œ ์‚ญ์ œ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค"); + return; + } + const image = images[index]; if (image.id) { try { diff --git a/frontend/src/components/admin/brand/management/MeetingLocation.tsx b/frontend/apps/admin/src/components/brand/management/MeetingLocation.tsx similarity index 95% rename from frontend/src/components/admin/brand/management/MeetingLocation.tsx rename to frontend/apps/admin/src/components/brand/management/MeetingLocation.tsx index 9afd387e..28583a91 100644 --- a/frontend/src/components/admin/brand/management/MeetingLocation.tsx +++ b/frontend/apps/admin/src/components/brand/management/MeetingLocation.tsx @@ -1,4 +1,4 @@ -import { useBrandFormContext } from "hooks/admin/brand/context/useBrandFormContext"; +import { useBrandFormContext } from "hooks/brand/context/useBrandFormContext"; import React from "react"; import styled from "styled-components"; diff --git a/frontend/src/components/admin/brand/management/Participants.tsx b/frontend/apps/admin/src/components/brand/management/Participants.tsx similarity index 95% rename from frontend/src/components/admin/brand/management/Participants.tsx rename to frontend/apps/admin/src/components/brand/management/Participants.tsx index d866d657..63fca739 100644 --- a/frontend/src/components/admin/brand/management/Participants.tsx +++ b/frontend/apps/admin/src/components/brand/management/Participants.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { useBrandFormContext } from "hooks/admin/brand/context/useBrandFormContext"; +import { useBrandFormContext } from "hooks/brand/context/useBrandFormContext"; import styled from "styled-components"; const Participants: React.FC = () => { diff --git a/frontend/src/components/admin/brand/management/SocialingDuration.tsx b/frontend/apps/admin/src/components/brand/management/SocialingDuration.tsx similarity index 94% rename from frontend/src/components/admin/brand/management/SocialingDuration.tsx rename to frontend/apps/admin/src/components/brand/management/SocialingDuration.tsx index b865a647..d3648fb0 100644 --- a/frontend/src/components/admin/brand/management/SocialingDuration.tsx +++ b/frontend/apps/admin/src/components/brand/management/SocialingDuration.tsx @@ -1,4 +1,4 @@ -import { useBrandFormContext } from "hooks/admin/brand/context/useBrandFormContext"; +import { useBrandFormContext } from "hooks/brand/context/useBrandFormContext"; import styled from "styled-components"; const SocialingDuration: React.FC = () => { diff --git a/frontend/src/components/admin/brand/management/ThumbnailUpload.tsx b/frontend/apps/admin/src/components/brand/management/ThumbnailUpload.tsx similarity index 75% rename from frontend/src/components/admin/brand/management/ThumbnailUpload.tsx rename to frontend/apps/admin/src/components/brand/management/ThumbnailUpload.tsx index 2dd7697d..2576b519 100644 --- a/frontend/src/components/admin/brand/management/ThumbnailUpload.tsx +++ b/frontend/apps/admin/src/components/brand/management/ThumbnailUpload.tsx @@ -1,15 +1,21 @@ import React, { useState, useEffect } from "react"; import styled from "styled-components"; -import { uploadThumbnailImage } from "api/admin/brand/uploadThumbnailImage"; +import { uploadThumbnailImage } from "api/brand/uploadThumbnailImage"; import { convertFileToBase64 } from "utils/image"; -import { useBrandFormContext } from "hooks/admin/brand/context/useBrandFormContext"; +import { useBrandFormContext } from "hooks/brand/context/useBrandFormContext"; -import AddImageInput from "components/admin/common/image/addImageInput"; -import ProductImage from "components/admin/common/image/ProductImage"; +import AddImageInput from "components/common/image/addImageInput"; +import ProductImage from "components/common/image/ProductImage"; +import useSystemModal from "hooks/common/components/useSystemModal"; +import useOwnerCookie from "hooks/auth/useOwnerCookie"; const ThumbnailUpload: React.FC = () => { const { brandId, setBrand, brand } = useBrandFormContext(); + const { showAnyMessageModal } = useSystemModal(); + const owner = useOwnerCookie(); + const isTester = owner === "tester"; + const [preview, setPreview] = useState( brand?.thumbnailImage?.url || "" ); @@ -17,6 +23,11 @@ const ThumbnailUpload: React.FC = () => { const handleThumbnailUpload = async ( e: React.ChangeEvent ) => { + if (isTester) { + showAnyMessageModal("ํ…Œ์Šคํ„ฐ ๊ณ„์ •์€ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค"); + return; + } + if (!e.target.files) return; const file = e.target.files[0]; const base64 = await convertFileToBase64(file); @@ -37,6 +48,11 @@ const ThumbnailUpload: React.FC = () => { }; const handleRemove = () => { + if (isTester) { + showAnyMessageModal("ํ…Œ์Šคํ„ฐ ๊ณ„์ •์€ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค"); + return; + } + setPreview(""); setBrand((prev) => ({ ...prev, diff --git a/frontend/src/components/admin/brand/management/TitleInput.tsx b/frontend/apps/admin/src/components/brand/management/TitleInput.tsx similarity index 93% rename from frontend/src/components/admin/brand/management/TitleInput.tsx rename to frontend/apps/admin/src/components/brand/management/TitleInput.tsx index b6b00c0b..15064957 100644 --- a/frontend/src/components/admin/brand/management/TitleInput.tsx +++ b/frontend/apps/admin/src/components/brand/management/TitleInput.tsx @@ -1,4 +1,4 @@ -import { useBrandFormContext } from "hooks/admin/brand/context/useBrandFormContext"; +import { useBrandFormContext } from "hooks/brand/context/useBrandFormContext"; import React from "react"; import styled from "styled-components"; diff --git a/frontend/src/components/admin/brand/management/index.tsx b/frontend/apps/admin/src/components/brand/management/index.tsx similarity index 85% rename from frontend/src/components/admin/brand/management/index.tsx rename to frontend/apps/admin/src/components/brand/management/index.tsx index 82180bbd..66f5cddf 100644 --- a/frontend/src/components/admin/brand/management/index.tsx +++ b/frontend/apps/admin/src/components/brand/management/index.tsx @@ -2,15 +2,15 @@ import React, { useEffect } from "react"; import styled from "styled-components"; import { useNavigate, useParams } from "react-router-dom"; -import { Pathnames } from "constants/admin"; -import { useBrandFormContext } from "hooks/admin/brand/context/useBrandFormContext"; +import { Pathnames } from "constants/index"; +import { useBrandFormContext } from "hooks/brand/context/useBrandFormContext"; import useSystemModal from "hooks/common/components/useSystemModal"; -import useSaveBar from "hooks/admin/components/useSaveBar"; -import useEditBrand from "hooks/admin/brand/useEditBrand"; +import useSaveBar from "hooks/components/useSaveBar"; +import useEditBrand from "hooks/brand/useEditBrand"; -import TitleInput from "../management/TitleInput"; -import DescriptionInput from "../management/DescriptionInput"; -import ThumbnailUpload from "../management/ThumbnailUpload"; +import TitleInput from "./TitleInput"; +import DescriptionInput from "./DescriptionInput"; +import ThumbnailUpload from "./ThumbnailUpload"; import DetailImageUpload from "./DetailImageUpload"; import Participants from "./Participants"; import MeetingLocation from "./MeetingLocation"; diff --git a/frontend/src/components/admin/brand/questionCard/BackImageItem.tsx b/frontend/apps/admin/src/components/brand/questionCard/BackImageItem.tsx similarity index 88% rename from frontend/src/components/admin/brand/questionCard/BackImageItem.tsx rename to frontend/apps/admin/src/components/brand/questionCard/BackImageItem.tsx index 9629884d..7aa86e2a 100644 --- a/frontend/src/components/admin/brand/questionCard/BackImageItem.tsx +++ b/frontend/apps/admin/src/components/brand/questionCard/BackImageItem.tsx @@ -3,16 +3,18 @@ import { useQueryClient } from "@tanstack/react-query"; import styled from "styled-components"; import { useParams } from "react-router-dom"; -import { QuestionCategoriesOutput } from "api/admin/brand/getQuestionCategories"; -import { GET_QUESTION_CATEGORIES } from "constants/admin/queryKeys"; -import useDeleteQuestionCard from "hooks/admin/questionCard/card/useDeleteQuestionCard"; -import useUpdateQuestionCard from "hooks/admin/questionCard/card/useUpdateQuestionCard"; -import useUploadCardImage from "hooks/admin/questionCard/card/useUploadCardImage"; +import { QuestionCategoriesOutput } from "api/brand/getQuestionCategories"; +import { GET_QUESTION_CATEGORIES } from "constants/queryKeys"; +import useDeleteQuestionCard from "hooks/questionCard/card/useDeleteQuestionCard"; +import useUpdateQuestionCard from "hooks/questionCard/card/useUpdateQuestionCard"; +import useUploadCardImage from "hooks/questionCard/card/useUploadCardImage"; import useSystemModal from "hooks/common/components/useSystemModal"; import { QuestionCard } from "interfaces/questionCardCategory"; -import AddImageInput from "components/admin/common/image/addImageInput"; -import ProductImage from "components/admin/common/image/ProductImage"; +import AddImageInput from "components/common/image/addImageInput"; +import ProductImage from "components/common/image/ProductImage"; +import { OWNER } from "configs"; +import useOwnerCookie from "hooks/auth/useOwnerCookie"; interface BackImageItemProps { cardIndex: number; @@ -27,13 +29,21 @@ const BackImageItem: React.FC = ({ }) => { const { brandId } = useParams(); const queryClient = useQueryClient(); - const { showErrorModal } = useSystemModal(); + const { showErrorModal, showAnyMessageModal } = useSystemModal(); + + const owner = useOwnerCookie(); + const isTester = owner === "tester"; const { mutate: deleteQuestionCard } = useDeleteQuestionCard(); const { mutate: updateQuestionCard } = useUpdateQuestionCard(); const { mutate: uploadCardImage } = useUploadCardImage(); const handleUpload = (e: React.ChangeEvent) => { + if (isTester) { + showAnyMessageModal("ํ…Œ์Šคํ„ฐ ๊ณ„์ •์€ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค"); + return; + } + if (!e.target.files) return; const file = e.target.files[0]; @@ -83,6 +93,11 @@ const BackImageItem: React.FC = ({ }; const handleClear = () => { + if (isTester) { + showAnyMessageModal("ํ…Œ์Šคํ„ฐ ๊ณ„์ •์€ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค"); + return; + } + queryClient.setQueryData( [GET_QUESTION_CATEGORIES, brandId], (prevData: QuestionCategoriesOutput | undefined) => { @@ -113,6 +128,11 @@ const BackImageItem: React.FC = ({ }; const handleToggleVisibility = () => { + if (isTester) { + showAnyMessageModal("ํ…Œ์Šคํ„ฐ ๊ณ„์ •์€ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค"); + return; + } + updateQuestionCard( { card_id: questionCardInfo.id, @@ -163,6 +183,11 @@ const BackImageItem: React.FC = ({ }; const handleDelete = (question_card_id: number) => { + if (isTester) { + showAnyMessageModal("ํ…Œ์Šคํ„ฐ ๊ณ„์ •์€ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค"); + return; + } + deleteQuestionCard(question_card_id, { onSuccess: () => { queryClient.setQueryData( diff --git a/frontend/src/components/admin/brand/questionCard/CardFooter.tsx b/frontend/apps/admin/src/components/brand/questionCard/CardFooter.tsx similarity index 82% rename from frontend/src/components/admin/brand/questionCard/CardFooter.tsx rename to frontend/apps/admin/src/components/brand/questionCard/CardFooter.tsx index ae326d09..d7eece50 100644 --- a/frontend/src/components/admin/brand/questionCard/CardFooter.tsx +++ b/frontend/apps/admin/src/components/brand/questionCard/CardFooter.tsx @@ -2,13 +2,15 @@ import { useQueryClient } from "@tanstack/react-query"; import { useParams } from "react-router-dom"; import styled from "styled-components"; -import { QuestionCategoriesOutput } from "api/admin/brand/getQuestionCategories"; -import { UpdateCategoryOutput } from "api/admin/questionCard/category/updateCategory"; -import { GET_QUESTION_CATEGORIES } from "constants/admin/queryKeys"; +import { QuestionCategoriesOutput } from "api/brand/getQuestionCategories"; +import { UpdateCategoryOutput } from "api/questionCard/category/updateCategory"; +import { GET_QUESTION_CATEGORIES } from "constants/queryKeys"; import { QuestionCardDeck } from "interfaces/questionCardCategory"; -import useDeleteCategory from "hooks/admin/questionCard/category/useDeleteCategory"; -import useUpdateCategory from "hooks/admin/questionCard/category/useUpdateCategory"; +import useDeleteCategory from "hooks/questionCard/category/useDeleteCategory"; +import useUpdateCategory from "hooks/questionCard/category/useUpdateCategory"; import useSystemModal from "hooks/common/components/useSystemModal"; +import { OWNER } from "configs"; +import useOwnerCookie from "hooks/auth/useOwnerCookie"; interface Props { cardIndex: number; @@ -18,12 +20,19 @@ interface Props { const CardFooter = ({ cardIndex, card }: Props) => { const queryClient = useQueryClient(); const { brandId } = useParams(); - const { openModal, showErrorModal } = useSystemModal(); + const owner = useOwnerCookie(); + const isTester = owner === "tester"; + const { openModal, showErrorModal, showAnyMessageModal } = useSystemModal(); const { mutate: deleteCategory } = useDeleteCategory(); const { mutate: updateCategory } = useUpdateCategory(); const handleToggleVisibility = () => { + if (isTester) { + showAnyMessageModal("ํ…Œ์Šคํ„ฐ ๊ณ„์ •์€ ๋ธŒ๋žœ๋“œ ์‚ญ์ œ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค"); + return; + } + updateCategory( { question_card_category_id: card.id, @@ -56,6 +65,11 @@ const CardFooter = ({ cardIndex, card }: Props) => { }; const handleDeleteButtonClick = () => { + if (isTester) { + showAnyMessageModal("ํ…Œ์Šคํ„ฐ ๊ณ„์ •์€ ๋ธŒ๋žœ๋“œ ์‚ญ์ œ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค"); + return; + } + openModal({ isOpen: true, showCancel: true, diff --git a/frontend/src/components/admin/brand/questionCard/QuestionCardItem.tsx b/frontend/apps/admin/src/components/brand/questionCard/QuestionCardItem.tsx similarity index 92% rename from frontend/src/components/admin/brand/questionCard/QuestionCardItem.tsx rename to frontend/apps/admin/src/components/brand/questionCard/QuestionCardItem.tsx index 28320315..7ba297c1 100644 --- a/frontend/src/components/admin/brand/questionCard/QuestionCardItem.tsx +++ b/frontend/apps/admin/src/components/brand/questionCard/QuestionCardItem.tsx @@ -3,11 +3,11 @@ import styled from "styled-components"; import { useParams } from "react-router-dom"; import { useQueryClient } from "@tanstack/react-query"; -import { QuestionCategoriesOutput } from "api/admin/brand/getQuestionCategories"; -import { UploadCoverImageOutput } from "api/admin/questionCard/category/uploadCoverImage"; -import { GET_QUESTION_CATEGORIES } from "constants/admin/queryKeys"; -import useUploadCoverImage from "hooks/admin/questionCard/category/useUploadCoverImage"; -import useCreateQuestionCard from "hooks/admin/questionCard/card/useCreateQuestionCard"; +import { QuestionCategoriesOutput } from "api/brand/getQuestionCategories"; +import { UploadCoverImageOutput } from "api/questionCard/category/uploadCoverImage"; +import { GET_QUESTION_CATEGORIES } from "constants/queryKeys"; +import useUploadCoverImage from "hooks/questionCard/category/useUploadCoverImage"; +import useCreateQuestionCard from "hooks/questionCard/card/useCreateQuestionCard"; import useSystemModal from "hooks/common/components/useSystemModal"; import { QuestionCard, @@ -15,8 +15,8 @@ import { } from "interfaces/questionCardCategory"; import BackImageItem from "./BackImageItem"; -import AddImageInput from "components/admin/common/image/addImageInput"; -import ProductImage from "components/admin/common/image/ProductImage"; +import AddImageInput from "components/common/image/addImageInput"; +import ProductImage from "components/common/image/ProductImage"; import CardFooter from "./CardFooter"; interface QuestionCardItemProps { diff --git a/frontend/src/components/admin/brand/questionCard/QuestionCardSection.tsx b/frontend/apps/admin/src/components/brand/questionCard/QuestionCardSection.tsx similarity index 87% rename from frontend/src/components/admin/brand/questionCard/QuestionCardSection.tsx rename to frontend/apps/admin/src/components/brand/questionCard/QuestionCardSection.tsx index ce860257..cd081db2 100644 --- a/frontend/src/components/admin/brand/questionCard/QuestionCardSection.tsx +++ b/frontend/apps/admin/src/components/brand/questionCard/QuestionCardSection.tsx @@ -3,14 +3,14 @@ import styled from "styled-components"; import { useParams } from "react-router-dom"; import { useQueryClient } from "@tanstack/react-query"; -import { CreateCategoryOutput } from "api/admin/questionCard/category/createCategory"; -import { JoinCategoryWithBrandOutput } from "api/admin/brand/joinCategoryWithBrand"; -import { QuestionCategoriesOutput } from "api/admin/brand/getQuestionCategories"; -import { GET_QUESTION_CATEGORIES } from "constants/admin/queryKeys"; +import { CreateCategoryOutput } from "api/questionCard/category/createCategory"; +import { JoinCategoryWithBrandOutput } from "api/brand/joinCategoryWithBrand"; +import { QuestionCategoriesOutput } from "api/brand/getQuestionCategories"; +import { GET_QUESTION_CATEGORIES } from "constants/queryKeys"; import { QuestionCardDeck } from "interfaces/questionCardCategory"; -import useGetQuestionCategories from "api/admin/brand/hooks/useGetQuestionCategories"; -import useJoinCategoryWithBrand from "hooks/admin/brand/useJoinCategoryWithBrand"; -import useCreateCategory from "hooks/admin/questionCard/category/useCreateCategory"; +import useGetQuestionCategories from "api/brand/hooks/useGetQuestionCategories"; +import useJoinCategoryWithBrand from "hooks/brand/useJoinCategoryWithBrand"; +import useCreateCategory from "hooks/questionCard/category/useCreateCategory"; import useSystemModal from "hooks/common/components/useSystemModal"; import QuestionCardItem from "./QuestionCardItem"; diff --git a/frontend/src/components/admin/brand/review/Controller.tsx b/frontend/apps/admin/src/components/brand/review/Controller.tsx similarity index 96% rename from frontend/src/components/admin/brand/review/Controller.tsx rename to frontend/apps/admin/src/components/brand/review/Controller.tsx index 4abf5c5d..1571830f 100644 --- a/frontend/src/components/admin/brand/review/Controller.tsx +++ b/frontend/apps/admin/src/components/brand/review/Controller.tsx @@ -1,6 +1,6 @@ import styled from "styled-components"; import { useSearchParams } from "react-router-dom"; -import { useBrandReviewContext } from "hooks/admin/brand/context/useBrandReviewContext"; +import { useBrandReviewContext } from "hooks/brand/context/useBrandReviewContext"; const Controller = () => { const { refetch, filter, setFilter } = useBrandReviewContext(); diff --git a/frontend/src/components/admin/brand/review/ReviewTable.tsx b/frontend/apps/admin/src/components/brand/review/ReviewTable.tsx similarity index 87% rename from frontend/src/components/admin/brand/review/ReviewTable.tsx rename to frontend/apps/admin/src/components/brand/review/ReviewTable.tsx index 636ec5c3..375a66f4 100644 --- a/frontend/src/components/admin/brand/review/ReviewTable.tsx +++ b/frontend/apps/admin/src/components/brand/review/ReviewTable.tsx @@ -1,24 +1,34 @@ import styled from "styled-components"; import { useQueryClient } from "@tanstack/react-query"; -import useUpdateBrandReview from "api/admin/brand/hooks/useUpdateBrandReview"; -import useDeleteBrandReview from "api/admin/brand/hooks/useDeleteBrandReview"; -import { useBrandReviewContext } from "hooks/admin/brand/context/useBrandReviewContext"; +import useUpdateBrandReview from "api/brand/hooks/useUpdateBrandReview"; +import useDeleteBrandReview from "api/brand/hooks/useDeleteBrandReview"; +import { useBrandReviewContext } from "hooks/brand/context/useBrandReviewContext"; import useSystemModal from "hooks/common/components/useSystemModal"; -import { BRAND_REVIEWS } from "constants/admin/queryKeys"; -import OptimizedImage from "components/common/OptimizedImage"; +import { BRAND_REVIEWS } from "constants/queryKeys"; +import OptimizedImage from "components/common/image/OptimizedImage"; +import { OWNER } from "configs"; +import useOwnerCookie from "hooks/auth/useOwnerCookie"; const ReviewTable = () => { const { isLoading, brandReviews, error, setEnlargedImageUrl } = useBrandReviewContext(); - const queryClient = useQueryClient(); + const owner = useOwnerCookie(); + const isTester = owner === "tester"; + const { mutate: deleteBrandReview } = useDeleteBrandReview(); const { mutate: updateBrandReview } = useUpdateBrandReview(); - const { openModal, showErrorModal, closeModal } = useSystemModal(); + const { openModal, showErrorModal, closeModal, showAnyMessageModal } = + useSystemModal(); const handleDeleteReview = (id: number) => { + if (isTester) { + showAnyMessageModal("ํ…Œ์Šคํ„ฐ ๊ณ„์ •์€ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค"); + return; + } + openModal({ isOpen: true, confirmText: "์‚ญ์ œํ•˜๊ธฐ", @@ -46,6 +56,11 @@ const ReviewTable = () => { }; const handleToggleDisplay = (review: any) => { + if (isTester) { + showAnyMessageModal("ํ…Œ์Šคํ„ฐ ๊ณ„์ •์€ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค"); + return; + } + updateBrandReview( { requestBody: { diff --git a/frontend/src/components/admin/brand/review/index.tsx b/frontend/apps/admin/src/components/brand/review/index.tsx similarity index 81% rename from frontend/src/components/admin/brand/review/index.tsx rename to frontend/apps/admin/src/components/brand/review/index.tsx index d9a0577c..b226e4d8 100644 --- a/frontend/src/components/admin/brand/review/index.tsx +++ b/frontend/apps/admin/src/components/brand/review/index.tsx @@ -1,10 +1,10 @@ import { useState } from "react"; import { useSearchParams } from "react-router-dom"; -import useGetBrandReviews from "api/admin/brand/hooks/useGetBrandReviews"; -import { BrandReviewContext } from "hooks/admin/brand/context/useBrandReviewContext"; +import useGetBrandReviews from "api/brand/hooks/useGetBrandReviews"; +import { BrandReviewContext } from "hooks/brand/context/useBrandReviewContext"; -import PopupImageModal from "components/admin/channel/channelDetail/application/PopupImageModal"; -import Pagination from "components/admin/common/Pagination"; +import PopupImageModal from "components/channel/channelDetail/application/PopupImageModal"; +import Pagination from "components/common/Pagination"; import ReviewTable from "./ReviewTable"; import Controller from "./Controller"; diff --git a/frontend/src/components/admin/brand/surveyForm/builder/Agreement.tsx b/frontend/apps/admin/src/components/brand/surveyForm/builder/Agreement.tsx similarity index 97% rename from frontend/src/components/admin/brand/surveyForm/builder/Agreement.tsx rename to frontend/apps/admin/src/components/brand/surveyForm/builder/Agreement.tsx index 35f5721c..fa01e6f9 100644 --- a/frontend/src/components/admin/brand/surveyForm/builder/Agreement.tsx +++ b/frontend/apps/admin/src/components/brand/surveyForm/builder/Agreement.tsx @@ -12,7 +12,7 @@ import { AgreementQuestion, Question } from "interfaces/brand/survey"; import { FaTrash } from "react-icons/fa"; import styled from "styled-components"; -import useSurveyBuilder from "hooks/admin/brand/context/useSurveyBuilder"; +import useSurveyBuilder from "hooks/brand/context/useSurveyBuilder"; interface Props { question: Question; diff --git a/frontend/src/components/admin/brand/surveyForm/builder/Dropdown.tsx b/frontend/apps/admin/src/components/brand/surveyForm/builder/Dropdown.tsx similarity index 97% rename from frontend/src/components/admin/brand/surveyForm/builder/Dropdown.tsx rename to frontend/apps/admin/src/components/brand/surveyForm/builder/Dropdown.tsx index 2bf75f5f..63d736dd 100644 --- a/frontend/src/components/admin/brand/surveyForm/builder/Dropdown.tsx +++ b/frontend/apps/admin/src/components/brand/surveyForm/builder/Dropdown.tsx @@ -15,7 +15,7 @@ import { Question, SelectQuestion } from "interfaces/brand/survey"; import { FaTrash, FaTimes } from "react-icons/fa"; import styled from "styled-components"; -import useSurveyBuilder from "hooks/admin/brand/context/useSurveyBuilder"; +import useSurveyBuilder from "hooks/brand/context/useSurveyBuilder"; interface Props { question: Question; diff --git a/frontend/src/components/admin/brand/surveyForm/builder/Image.tsx b/frontend/apps/admin/src/components/brand/surveyForm/builder/Image.tsx similarity index 96% rename from frontend/src/components/admin/brand/surveyForm/builder/Image.tsx rename to frontend/apps/admin/src/components/brand/surveyForm/builder/Image.tsx index 0065865b..4ca2dc12 100644 --- a/frontend/src/components/admin/brand/surveyForm/builder/Image.tsx +++ b/frontend/apps/admin/src/components/brand/surveyForm/builder/Image.tsx @@ -12,7 +12,7 @@ import { Question } from "interfaces/brand/survey"; import { FaImage, FaTrash } from "react-icons/fa"; import styled from "styled-components"; -import useSurveyBuilder from "hooks/admin/brand/context/useSurveyBuilder"; +import useSurveyBuilder from "hooks/brand/context/useSurveyBuilder"; interface Props { question: Question; diff --git a/frontend/src/components/admin/brand/surveyForm/builder/PlainText.tsx b/frontend/apps/admin/src/components/brand/surveyForm/builder/PlainText.tsx similarity index 96% rename from frontend/src/components/admin/brand/surveyForm/builder/PlainText.tsx rename to frontend/apps/admin/src/components/brand/surveyForm/builder/PlainText.tsx index 60715b8e..e964fcf4 100644 --- a/frontend/src/components/admin/brand/surveyForm/builder/PlainText.tsx +++ b/frontend/apps/admin/src/components/brand/surveyForm/builder/PlainText.tsx @@ -11,7 +11,7 @@ import { ToggleRow, } from "./Styles"; import { PlaintextQuestion, Question } from "interfaces/brand/survey"; -import useSurveyBuilder from "hooks/admin/brand/context/useSurveyBuilder"; +import useSurveyBuilder from "hooks/brand/context/useSurveyBuilder"; interface Props { question: Question; diff --git a/frontend/src/components/admin/brand/surveyForm/builder/Select.tsx b/frontend/apps/admin/src/components/brand/surveyForm/builder/Select.tsx similarity index 97% rename from frontend/src/components/admin/brand/surveyForm/builder/Select.tsx rename to frontend/apps/admin/src/components/brand/surveyForm/builder/Select.tsx index eaceab92..adce470e 100644 --- a/frontend/src/components/admin/brand/surveyForm/builder/Select.tsx +++ b/frontend/apps/admin/src/components/brand/surveyForm/builder/Select.tsx @@ -14,7 +14,7 @@ import { } from "./Styles"; import { SelectQuestion, Question } from "interfaces/brand/survey"; import { FaTrash, FaTimes } from "react-icons/fa"; -import useSurveyBuilder from "hooks/admin/brand/context/useSurveyBuilder"; +import useSurveyBuilder from "hooks/brand/context/useSurveyBuilder"; interface Props { question: Question; diff --git a/frontend/src/components/admin/brand/surveyForm/builder/Styles.tsx b/frontend/apps/admin/src/components/brand/surveyForm/builder/Styles.tsx similarity index 100% rename from frontend/src/components/admin/brand/surveyForm/builder/Styles.tsx rename to frontend/apps/admin/src/components/brand/surveyForm/builder/Styles.tsx diff --git a/frontend/src/components/admin/brand/surveyForm/builder/index.tsx b/frontend/apps/admin/src/components/brand/surveyForm/builder/index.tsx similarity index 94% rename from frontend/src/components/admin/brand/surveyForm/builder/index.tsx rename to frontend/apps/admin/src/components/brand/surveyForm/builder/index.tsx index c5ae4472..6597e666 100644 --- a/frontend/src/components/admin/brand/surveyForm/builder/index.tsx +++ b/frontend/apps/admin/src/components/brand/surveyForm/builder/index.tsx @@ -2,8 +2,7 @@ import React, { useEffect } from "react"; import styled from "styled-components"; import { SurveyType } from "interfaces/brand/survey"; -import useSurveyBuilder from "hooks/admin/brand/context/useSurveyBuilder"; -import useSaveBar from "hooks/admin/components/useSaveBar"; +import useSurveyBuilder from "hooks/brand/context/useSurveyBuilder"; import PlainText from "./PlainText"; import Select from "./Select"; diff --git a/frontend/src/components/admin/brand/surveyForm/index.tsx b/frontend/apps/admin/src/components/brand/surveyForm/index.tsx similarity index 77% rename from frontend/src/components/admin/brand/surveyForm/index.tsx rename to frontend/apps/admin/src/components/brand/surveyForm/index.tsx index 8c449178..9407c7a8 100644 --- a/frontend/src/components/admin/brand/surveyForm/index.tsx +++ b/frontend/apps/admin/src/components/brand/surveyForm/index.tsx @@ -1,16 +1,18 @@ import React, { useEffect } from "react"; import styled from "styled-components"; -import useCreateSurvey from "api/admin/survey/hook/useCreateSurvey"; -import useGetSurvey from "api/admin/survey/hook/useGetSurvey"; -import useUpdateSurvey from "api/admin/survey/hook/useUpdateSurvey"; -import { useBrandFormContext } from "hooks/admin/brand/context/useBrandFormContext"; -import useSaveBar from "hooks/admin/components/useSaveBar"; +import useCreateSurvey from "api/survey/hook/useCreateSurvey"; +import useGetSurvey from "api/survey/hook/useGetSurvey"; +import useUpdateSurvey from "api/survey/hook/useUpdateSurvey"; +import { useBrandFormContext } from "hooks/brand/context/useBrandFormContext"; +import useSaveBar from "hooks/components/useSaveBar"; import useSystemModal from "hooks/common/components/useSystemModal"; -import useSurveyBuilder from "hooks/admin/brand/context/useSurveyBuilder"; +import useSurveyBuilder from "hooks/brand/context/useSurveyBuilder"; import Preview from "./preview"; import Builder from "./builder"; +import { OWNER } from "configs"; +import useOwnerCookie from "hooks/auth/useOwnerCookie"; const SurveyFormSection: React.FC = () => { const { brandId } = useBrandFormContext(); @@ -21,7 +23,15 @@ const SurveyFormSection: React.FC = () => { const { showSaveBar, closeSaveBar } = useSaveBar(); const { showErrorModal, showAnyMessageModal } = useSystemModal(); + const owner = useOwnerCookie(); + const isTester = owner === "tester"; + const handleSubmit = () => { + if (isTester) { + showAnyMessageModal("ํ…Œ์Šคํ„ฐ ๊ณ„์ •์€ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค"); + return; + } + const payload = { brand_id: brandId, title: "", diff --git a/frontend/src/components/admin/brand/surveyForm/preview/Agreement.tsx b/frontend/apps/admin/src/components/brand/surveyForm/preview/Agreement.tsx similarity index 100% rename from frontend/src/components/admin/brand/surveyForm/preview/Agreement.tsx rename to frontend/apps/admin/src/components/brand/surveyForm/preview/Agreement.tsx diff --git a/frontend/src/components/admin/brand/surveyForm/preview/Dropdown.tsx b/frontend/apps/admin/src/components/brand/surveyForm/preview/Dropdown.tsx similarity index 100% rename from frontend/src/components/admin/brand/surveyForm/preview/Dropdown.tsx rename to frontend/apps/admin/src/components/brand/surveyForm/preview/Dropdown.tsx diff --git a/frontend/src/components/admin/brand/surveyForm/preview/Image.tsx b/frontend/apps/admin/src/components/brand/surveyForm/preview/Image.tsx similarity index 100% rename from frontend/src/components/admin/brand/surveyForm/preview/Image.tsx rename to frontend/apps/admin/src/components/brand/surveyForm/preview/Image.tsx diff --git a/frontend/src/components/admin/brand/surveyForm/preview/PlainText.tsx b/frontend/apps/admin/src/components/brand/surveyForm/preview/PlainText.tsx similarity index 100% rename from frontend/src/components/admin/brand/surveyForm/preview/PlainText.tsx rename to frontend/apps/admin/src/components/brand/surveyForm/preview/PlainText.tsx diff --git a/frontend/src/components/admin/brand/surveyForm/preview/Select.tsx b/frontend/apps/admin/src/components/brand/surveyForm/preview/Select.tsx similarity index 100% rename from frontend/src/components/admin/brand/surveyForm/preview/Select.tsx rename to frontend/apps/admin/src/components/brand/surveyForm/preview/Select.tsx diff --git a/frontend/src/components/admin/brand/surveyForm/preview/Styles.tsx b/frontend/apps/admin/src/components/brand/surveyForm/preview/Styles.tsx similarity index 100% rename from frontend/src/components/admin/brand/surveyForm/preview/Styles.tsx rename to frontend/apps/admin/src/components/brand/surveyForm/preview/Styles.tsx diff --git a/frontend/src/components/admin/brand/surveyForm/preview/index.tsx b/frontend/apps/admin/src/components/brand/surveyForm/preview/index.tsx similarity index 93% rename from frontend/src/components/admin/brand/surveyForm/preview/index.tsx rename to frontend/apps/admin/src/components/brand/surveyForm/preview/index.tsx index 3921c604..ee42ab99 100644 --- a/frontend/src/components/admin/brand/surveyForm/preview/index.tsx +++ b/frontend/apps/admin/src/components/brand/surveyForm/preview/index.tsx @@ -1,9 +1,6 @@ import { useRecoilState } from "recoil"; import styled from "styled-components"; -import { - surveyMetaAtom, - surveyQuestionsAtom, -} from "recoils/atoms/admin/surveyState"; +import { surveyMetaAtom, surveyQuestionsAtom } from "recoils/atoms/surveyState"; import { SurveyType } from "interfaces/landing"; import PlainText from "./PlainText"; diff --git a/frontend/src/components/admin/channel/ChannelTable.tsx b/frontend/apps/admin/src/components/channel/ChannelTable.tsx similarity index 87% rename from frontend/src/components/admin/channel/ChannelTable.tsx rename to frontend/apps/admin/src/components/channel/ChannelTable.tsx index 426ec7e9..a4ad79ac 100644 --- a/frontend/src/components/admin/channel/ChannelTable.tsx +++ b/frontend/apps/admin/src/components/channel/ChannelTable.tsx @@ -1,13 +1,15 @@ import styled from "styled-components"; import { useNavigate } from "react-router-dom"; -import { ChannelState } from "api/admin/channel/type/channel"; -import useUpdateChannelState from "api/admin/channel/hooks/useUpdateChannelState"; -import { Pathnames } from "constants/admin/index"; -import useDeleteChannel from "hooks/admin/channel/useDeleteChannel"; +import { ChannelState } from "api/channel/type/channel"; +import useUpdateChannelState from "api/channel/hooks/useUpdateChannelState"; +import { Pathnames } from "constants/index"; +import useDeleteChannel from "hooks/channel/useDeleteChannel"; import useSystemModal from "hooks/common/components/useSystemModal"; -import { useChannelTableContext } from "hooks/admin/channel/context/useChannelTableContext"; -import Button from "components/admin/common/Button"; +import { useChannelTableContext } from "hooks/channel/context/useChannelTableContext"; +import Button from "components/common/Button"; +import { OWNER } from "configs"; +import useOwnerCookie from "hooks/auth/useOwnerCookie"; const channelStateLabels: Record = { [ChannelState.PENDING]: "๋Œ€๊ธฐ", @@ -23,11 +25,19 @@ const ChannelTable = () => { const { mutate: updateChannelState } = useUpdateChannelState(); const { channelData, refetch } = useChannelTableContext(); + const owner = useOwnerCookie(); + const isTester = owner === "tester"; + const handleTableRowClick = (channelId: number) => { navigate(`${Pathnames.EditChannel}/${channelId}`); }; const handleDeleteButtonClick = (channelId: number, channelName: string) => { + if (isTester) { + showAnyMessageModal("ํ…Œ์Šคํ„ฐ ๊ณ„์ •์€ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค"); + return; + } + openModal({ isOpen: true, title: `${channelName}์„ ์ •๋ง๋กœ ์‚ญ์ œํ•˜์‹œ๊ฒ ์–ด์š”?`, @@ -56,6 +66,11 @@ const ChannelTable = () => { }; const handleStateChange = (channelId: number, newState: ChannelState) => { + if (isTester) { + showAnyMessageModal("ํ…Œ์Šคํ„ฐ ๊ณ„์ •์€ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค"); + return; + } + updateChannelState( { channel_id: channelId, channel_state: newState }, { diff --git a/frontend/src/components/admin/channel/Controller.tsx b/frontend/apps/admin/src/components/channel/Controller.tsx similarity index 95% rename from frontend/src/components/admin/channel/Controller.tsx rename to frontend/apps/admin/src/components/channel/Controller.tsx index 202175f7..520a463c 100644 --- a/frontend/src/components/admin/channel/Controller.tsx +++ b/frontend/apps/admin/src/components/channel/Controller.tsx @@ -2,11 +2,11 @@ import styled from "styled-components"; import { useNavigate, useSearchParams } from "react-router-dom"; import { FaPlus } from "react-icons/fa"; -import { Pathnames } from "constants/admin"; -import { useChannelTableContext } from "hooks/admin/channel/context/useChannelTableContext"; +import { Pathnames } from "constants/index"; +import { useChannelTableContext } from "hooks/channel/context/useChannelTableContext"; import { ChannelState } from "interfaces/channels"; -import Button from "components/admin/common/Button"; +import Button from "components/common/Button"; const Controller = () => { const { filter, setFilter, refetch } = useChannelTableContext(); diff --git a/frontend/src/components/admin/channel/channelDetail/SectionToggle.tsx b/frontend/apps/admin/src/components/channel/channelDetail/SectionToggle.tsx similarity index 96% rename from frontend/src/components/admin/channel/channelDetail/SectionToggle.tsx rename to frontend/apps/admin/src/components/channel/channelDetail/SectionToggle.tsx index ab58ffd9..28988bb9 100644 --- a/frontend/src/components/admin/channel/channelDetail/SectionToggle.tsx +++ b/frontend/apps/admin/src/components/channel/channelDetail/SectionToggle.tsx @@ -1,4 +1,4 @@ -import { useChannelFormContext } from "hooks/admin/channel/context/useChannelFormContext"; +import { useChannelFormContext } from "hooks/channel/context/useChannelFormContext"; import React, { useState } from "react"; import styled from "styled-components"; diff --git a/frontend/src/components/admin/channel/channelDetail/application/Controller.tsx b/frontend/apps/admin/src/components/channel/channelDetail/application/Controller.tsx similarity index 96% rename from frontend/src/components/admin/channel/channelDetail/application/Controller.tsx rename to frontend/apps/admin/src/components/channel/channelDetail/application/Controller.tsx index d614d68a..f6ebf775 100644 --- a/frontend/src/components/admin/channel/channelDetail/application/Controller.tsx +++ b/frontend/apps/admin/src/components/channel/channelDetail/application/Controller.tsx @@ -1,6 +1,6 @@ import styled from "styled-components"; import { useSearchParams } from "react-router-dom"; -import { useApplicationTableContext } from "hooks/admin/channel/context/useApplicationTableContext"; +import { useApplicationTableContext } from "hooks/channel/context/useApplicationTableContext"; const Controller = () => { const [_, setSearchParams] = useSearchParams(); diff --git a/frontend/src/components/admin/channel/channelDetail/application/PopupImageModal.tsx b/frontend/apps/admin/src/components/channel/channelDetail/application/PopupImageModal.tsx similarity index 100% rename from frontend/src/components/admin/channel/channelDetail/application/PopupImageModal.tsx rename to frontend/apps/admin/src/components/channel/channelDetail/application/PopupImageModal.tsx diff --git a/frontend/src/components/admin/channel/channelDetail/application/Table.tsx b/frontend/apps/admin/src/components/channel/channelDetail/application/Table.tsx similarity index 93% rename from frontend/src/components/admin/channel/channelDetail/application/Table.tsx rename to frontend/apps/admin/src/components/channel/channelDetail/application/Table.tsx index 4e16dad9..c18a75c7 100644 --- a/frontend/src/components/admin/channel/channelDetail/application/Table.tsx +++ b/frontend/apps/admin/src/components/channel/channelDetail/application/Table.tsx @@ -1,10 +1,10 @@ import styled from "styled-components"; -import { useQueryClient } from "@tanstack/react-query"; - -import useUpdateSurveyState from "api/admin/survey/hook/useUpdateSurveyState"; +import useUpdateSurveyState from "api/survey/hook/useUpdateSurveyState"; import useSystemModal from "hooks/common/components/useSystemModal"; -import { useApplicationTableContext } from "hooks/admin/channel/context/useApplicationTableContext"; +import { useApplicationTableContext } from "hooks/channel/context/useApplicationTableContext"; import { SurveyRegistState } from "interfaces/landing"; +import { OWNER } from "configs"; +import useOwnerCookie from "hooks/auth/useOwnerCookie"; const Table = () => { const { setEnlargedImageUrl, surveyResponses, survey, refetch } = @@ -13,7 +13,15 @@ const Table = () => { const { mutate: updateSurveyResponse } = useUpdateSurveyState(); const { showErrorModal, openModal, showAnyMessageModal } = useSystemModal(); + const owner = useOwnerCookie(); + const isTester = owner === "tester"; + const handleUpdateButtonClick = (id: string, state: SurveyRegistState) => { + if (isTester) { + showAnyMessageModal("ํ…Œ์Šคํ„ฐ ๊ณ„์ •์€ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค"); + return; + } + const action = state === SurveyRegistState.ACCEPT ? "์ˆ˜๋ฝ" diff --git a/frontend/src/components/admin/channel/channelDetail/application/index.tsx b/frontend/apps/admin/src/components/channel/channelDetail/application/index.tsx similarity index 82% rename from frontend/src/components/admin/channel/channelDetail/application/index.tsx rename to frontend/apps/admin/src/components/channel/channelDetail/application/index.tsx index 81be3ed2..14d051f1 100644 --- a/frontend/src/components/admin/channel/channelDetail/application/index.tsx +++ b/frontend/apps/admin/src/components/channel/channelDetail/application/index.tsx @@ -2,12 +2,12 @@ import { useState } from "react"; import styled from "styled-components"; import { useSearchParams } from "react-router-dom"; -import useGetSurvey from "api/admin/survey/hook/useGetSurvey"; -import useGetSurveyResponses from "hooks/admin/channel/useGetSurveyResponses"; -import { ApplicationTableContext } from "hooks/admin/channel/context/useApplicationTableContext"; -import { useChannelFormContext } from "hooks/admin/channel/context/useChannelFormContext"; +import useGetSurvey from "api/survey/hook/useGetSurvey"; +import useGetSurveyResponses from "hooks/channel/useGetSurveyResponses"; +import { ApplicationTableContext } from "hooks/channel/context/useApplicationTableContext"; +import { useChannelFormContext } from "hooks/channel/context/useChannelFormContext"; -import Pagination from "components/admin/common/Pagination"; +import Pagination from "components/common/Pagination"; import Controller from "./Controller"; import PopupImageModal from "./PopupImageModal"; import Table from "./Table"; diff --git a/frontend/src/components/admin/channel/channelDetail/game/JoinUserManagement.tsx b/frontend/apps/admin/src/components/channel/channelDetail/game/JoinUserManagement.tsx similarity index 93% rename from frontend/src/components/admin/channel/channelDetail/game/JoinUserManagement.tsx rename to frontend/apps/admin/src/components/channel/channelDetail/game/JoinUserManagement.tsx index 70a2cfee..f6ed70f0 100644 --- a/frontend/src/components/admin/channel/channelDetail/game/JoinUserManagement.tsx +++ b/frontend/apps/admin/src/components/channel/channelDetail/game/JoinUserManagement.tsx @@ -1,13 +1,15 @@ import styled from "styled-components"; import { useDrag } from "react-dnd"; -import { PARTICIPANT } from "constants/admin/dnd"; +import { PARTICIPANT } from "constants/dnd"; import { User } from "interfaces/user"; import { QueryObserverResult, RefetchOptions } from "@tanstack/react-query"; -import useDeleteChannelUser from "hooks/admin/users/useDeleteChannelUser"; +import useDeleteChannelUser from "hooks/users/useDeleteChannelUser"; import { FaTrash } from "react-icons/fa"; import { useParams } from "react-router-dom"; import useSystemModal from "hooks/common/components/useSystemModal"; +import { OWNER } from "configs"; +import useOwnerCookie from "hooks/auth/useOwnerCookie"; interface Props { users: Array; @@ -88,7 +90,10 @@ const Participant = ({ }) => { const { channelId } = useParams(); const { mutate: deleteChannelUser } = useDeleteChannelUser(); - const { openModal, showErrorModal } = useSystemModal(); + const { openModal, showErrorModal, showAnyMessageModal } = useSystemModal(); + + const owner = useOwnerCookie(); + const isTester = owner === "tester"; const [{ isDragging }, drag] = useDrag(() => ({ type: PARTICIPANT, @@ -97,6 +102,11 @@ const Participant = ({ })); const handleDeleteUserButtonClick = (user_id: number, username: string) => { + if (isTester) { + showAnyMessageModal("ํ…Œ์Šคํ„ฐ ๊ณ„์ •์€ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค"); + return; + } + openModal({ isOpen: true, title: `${username}๋‹˜์„ ์†Œ์…œ๋ง์—์„œ ๋‚ด๋ณด๋‚ด์‹œ๊ฒ ์–ด์š”?`, diff --git a/frontend/src/components/admin/channel/channelDetail/game/ReviewCard.tsx b/frontend/apps/admin/src/components/channel/channelDetail/game/ReviewCard.tsx similarity index 100% rename from frontend/src/components/admin/channel/channelDetail/game/ReviewCard.tsx rename to frontend/apps/admin/src/components/channel/channelDetail/game/ReviewCard.tsx diff --git a/frontend/src/components/admin/channel/channelDetail/game/UserCard.tsx b/frontend/apps/admin/src/components/channel/channelDetail/game/UserCard.tsx similarity index 100% rename from frontend/src/components/admin/channel/channelDetail/game/UserCard.tsx rename to frontend/apps/admin/src/components/channel/channelDetail/game/UserCard.tsx diff --git a/frontend/src/components/admin/channel/channelDetail/game/UserListSection.tsx b/frontend/apps/admin/src/components/channel/channelDetail/game/UserListSection.tsx similarity index 100% rename from frontend/src/components/admin/channel/channelDetail/game/UserListSection.tsx rename to frontend/apps/admin/src/components/channel/channelDetail/game/UserListSection.tsx diff --git a/frontend/src/components/admin/channel/channelDetail/game/index.tsx b/frontend/apps/admin/src/components/channel/channelDetail/game/index.tsx similarity index 88% rename from frontend/src/components/admin/channel/channelDetail/game/index.tsx rename to frontend/apps/admin/src/components/channel/channelDetail/game/index.tsx index 0b8db747..140664f0 100644 --- a/frontend/src/components/admin/channel/channelDetail/game/index.tsx +++ b/frontend/apps/admin/src/components/channel/channelDetail/game/index.tsx @@ -3,13 +3,13 @@ import { useParams } from "react-router-dom"; import styled from "styled-components"; import { ChannelFeatureButton } from "constants/common"; -import useSearchGroups from "hooks/admin/group/useSearchGroups"; -import useGetUsersNotInGroup from "hooks/admin/users/useGetUsersNotInGroup"; -import { useChannelFormContext } from "hooks/admin/channel/context/useChannelFormContext"; +import useSearchGroups from "hooks/group/useSearchGroups"; +import useGetUsersNotInGroup from "hooks/users/useGetUsersNotInGroup"; +import { useChannelFormContext } from "hooks/channel/context/useChannelFormContext"; -import JoinUserManagement from "components/admin/channel/channelDetail/game/JoinUserManagement"; -import GroupManagement from "components/admin/channel/channelDetail/game/teamManagement"; -import MatchManagement from "components/admin/channel/channelDetail/game/matchManagement"; +import JoinUserManagement from "components/channel/channelDetail/game/JoinUserManagement"; +import GroupManagement from "components/channel/channelDetail/game/teamManagement"; +import MatchManagement from "components/channel/channelDetail/game/matchManagement"; const Game: React.FC = () => { const { channelId } = useParams<{ channelId?: string }>(); diff --git a/frontend/src/components/admin/channel/channelDetail/game/matchManagement/GameCard.tsx b/frontend/apps/admin/src/components/channel/channelDetail/game/matchManagement/GameCard.tsx similarity index 85% rename from frontend/src/components/admin/channel/channelDetail/game/matchManagement/GameCard.tsx rename to frontend/apps/admin/src/components/channel/channelDetail/game/matchManagement/GameCard.tsx index 28fc7ca2..24635abb 100644 --- a/frontend/src/components/admin/channel/channelDetail/game/matchManagement/GameCard.tsx +++ b/frontend/apps/admin/src/components/channel/channelDetail/game/matchManagement/GameCard.tsx @@ -1,9 +1,12 @@ import React from "react"; import styled from "styled-components"; import { FaDeleteLeft } from "react-icons/fa6"; -import { StructuredGame } from "utils/admin/channel/structureGameData"; -import useDeleteGame from "hooks/admin/game/useDeleteGame"; +import { StructuredGame } from "utils/channel/structureGameData"; +import useDeleteGame from "hooks/game/useDeleteGame"; import { Game } from "interfaces/game"; +import { OWNER } from "configs"; +import useSystemModal from "hooks/common/components/useSystemModal"; +import useOwnerCookie from "hooks/auth/useOwnerCookie"; interface GameCardProps { game: StructuredGame; @@ -12,8 +15,17 @@ interface GameCardProps { const GameCard: React.FC = ({ game, setGameList }) => { const { mutate: deleteGame } = useDeleteGame(); + const { showAnyMessageModal } = useSystemModal(); + + const owner = useOwnerCookie(); + const isTester = owner === "tester"; const handleDelete = (id: number) => { + if (isTester) { + showAnyMessageModal("ํ…Œ์Šคํ„ฐ ๊ณ„์ •์€ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค"); + return; + } + deleteGame(String(id), { onSuccess: () => { setGameList((prev) => prev.filter((game) => game.id !== id)); @@ -66,7 +78,6 @@ const GameCard: React.FC = ({ game, setGameList }) => { export default GameCard; -/* Styled Components */ const Card = styled.div` display: flex; align-items: center; diff --git a/frontend/src/components/admin/channel/channelDetail/game/matchManagement/GameContainer.tsx b/frontend/apps/admin/src/components/channel/channelDetail/game/matchManagement/GameContainer.tsx similarity index 85% rename from frontend/src/components/admin/channel/channelDetail/game/matchManagement/GameContainer.tsx rename to frontend/apps/admin/src/components/channel/channelDetail/game/matchManagement/GameContainer.tsx index c4c69727..d36b6c4d 100644 --- a/frontend/src/components/admin/channel/channelDetail/game/matchManagement/GameContainer.tsx +++ b/frontend/apps/admin/src/components/channel/channelDetail/game/matchManagement/GameContainer.tsx @@ -3,11 +3,13 @@ import styled from "styled-components"; import { Game } from "interfaces/game"; import structureGameData, { StructuredGame, -} from "utils/admin/channel/structureGameData"; +} from "utils/channel/structureGameData"; import { JoinedUser } from "interfaces/group"; import GameCard from "./GameCard"; -import matchValidation from "utils/admin/channel/matchValidation"; -import useDeleteGame from "hooks/admin/game/useDeleteGame"; +import matchValidation from "utils/channel/matchValidation"; +import useDeleteGame from "hooks/game/useDeleteGame"; +import useSystemModal from "hooks/common/components/useSystemModal"; +import useOwnerCookie from "hooks/auth/useOwnerCookie"; interface Props { gameList: Array; @@ -17,6 +19,10 @@ interface Props { const GameContainer = ({ gameList, setGameList, joinedUsers }: Props) => { const { mutateAsync: deleteGame } = useDeleteGame(); + const { showAnyMessageModal } = useSystemModal(); + + const owner = useOwnerCookie(); + const isTester = owner === "tester"; // gameList๋‚˜ joinedUsers๊ฐ€ ์—†์œผ๋ฉด ํŒŒ์ƒ ์ƒํƒœ ๊ณ„์‚ฐํ•˜์ง€ ์•Š์Œ const structuredGame = useMemo(() => { @@ -30,6 +36,11 @@ const GameContainer = ({ gameList, setGameList, joinedUsers }: Props) => { }, [gameList, joinedUsers, structuredGame]); const handleDeleteButtonClick = async () => { + if (isTester) { + showAnyMessageModal("ํ…Œ์Šคํ„ฐ ๊ณ„์ •์€ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค"); + return; + } + try { await Promise.all(gameList.map((game) => deleteGame(String(game.id)))); // ์‚ญ์ œ ํ›„ ๋ถ€๋ชจ ์ƒํƒœ๋ฅผ ๋น„์›€ @@ -73,7 +84,6 @@ const GameContainer = ({ gameList, setGameList, joinedUsers }: Props) => { export default GameContainer; -/* Styled Components */ const Container = styled.div` padding: 40px 30px; background-color: #f4f6f9; diff --git a/frontend/src/components/admin/channel/channelDetail/game/matchManagement/index.tsx b/frontend/apps/admin/src/components/channel/channelDetail/game/matchManagement/index.tsx similarity index 97% rename from frontend/src/components/admin/channel/channelDetail/game/matchManagement/index.tsx rename to frontend/apps/admin/src/components/channel/channelDetail/game/matchManagement/index.tsx index e27ec8c3..1281cb87 100644 --- a/frontend/src/components/admin/channel/channelDetail/game/matchManagement/index.tsx +++ b/frontend/apps/admin/src/components/channel/channelDetail/game/matchManagement/index.tsx @@ -3,14 +3,14 @@ import styled from "styled-components"; import { FaRandom } from "react-icons/fa"; import { Group } from "interfaces/group"; -import generateRandomMatch from "utils/admin/channel/generateRandomMatch"; -import useCreateGamePairs from "hooks/admin/game/useCreateGamePairs"; +import generateRandomMatch from "utils/channel/generateRandomMatch"; +import useCreateGamePairs from "hooks/game/useCreateGamePairs"; import { alertErrorMessage } from "utils/error"; -import useSearchGames from "hooks/admin/game/useSearchGames"; +import useSearchGames from "hooks/game/useSearchGames"; import GameContainer from "./GameContainer"; import { Game } from "interfaces/game"; import { useQueryClient } from "@tanstack/react-query"; -import { GET_SEARCH_GAMES } from "constants/admin/queryKeys"; +import { GET_SEARCH_GAMES } from "constants/queryKeys"; interface Props { createdGroups: Group[]; diff --git a/frontend/src/components/admin/channel/channelDetail/game/teamManagement/GroupCard.tsx b/frontend/apps/admin/src/components/channel/channelDetail/game/teamManagement/GroupCard.tsx similarity index 91% rename from frontend/src/components/admin/channel/channelDetail/game/teamManagement/GroupCard.tsx rename to frontend/apps/admin/src/components/channel/channelDetail/game/teamManagement/GroupCard.tsx index b63b832f..7f5bd0bb 100644 --- a/frontend/src/components/admin/channel/channelDetail/game/teamManagement/GroupCard.tsx +++ b/frontend/apps/admin/src/components/channel/channelDetail/game/teamManagement/GroupCard.tsx @@ -1,16 +1,16 @@ import styled from "styled-components"; import { useDrop } from "react-dnd"; import { FaTrash } from "react-icons/fa"; -import { JOINED_PARTICIPANT, PARTICIPANT } from "constants/admin/dnd"; +import { JOINED_PARTICIPANT, PARTICIPANT } from "constants/dnd"; import { Group } from "interfaces/group"; -import useDeleteGroup from "hooks/admin/group/useDeleteGroup"; -import useAddUsersToGroup from "hooks/admin/group/useAddUsersToGroup"; -import useDeleteUserToGroup from "hooks/admin/group/useDeleteUserToGroup"; +import useDeleteGroup from "hooks/group/useDeleteGroup"; +import useAddUsersToGroup from "hooks/group/useAddUsersToGroup"; +import useDeleteUserToGroup from "hooks/group/useDeleteUserToGroup"; import { QueryObserverResult, RefetchOptions } from "@tanstack/react-query"; import { alertErrorMessage } from "utils/error"; import JoinedUserList from "./JoinedUserList"; -import useEditUserToGroup from "hooks/admin/group/useEditUserToGroup"; +import useEditUserToGroup from "hooks/group/useEditUserToGroup"; import { User } from "interfaces/user"; interface Props { diff --git a/frontend/src/components/admin/channel/channelDetail/game/teamManagement/GroupList.tsx b/frontend/apps/admin/src/components/channel/channelDetail/game/teamManagement/GroupList.tsx similarity index 100% rename from frontend/src/components/admin/channel/channelDetail/game/teamManagement/GroupList.tsx rename to frontend/apps/admin/src/components/channel/channelDetail/game/teamManagement/GroupList.tsx diff --git a/frontend/src/components/admin/channel/channelDetail/game/teamManagement/JoinedUserList.tsx b/frontend/apps/admin/src/components/channel/channelDetail/game/teamManagement/JoinedUserList.tsx similarity index 98% rename from frontend/src/components/admin/channel/channelDetail/game/teamManagement/JoinedUserList.tsx rename to frontend/apps/admin/src/components/channel/channelDetail/game/teamManagement/JoinedUserList.tsx index fac2a84f..672128ea 100644 --- a/frontend/src/components/admin/channel/channelDetail/game/teamManagement/JoinedUserList.tsx +++ b/frontend/apps/admin/src/components/channel/channelDetail/game/teamManagement/JoinedUserList.tsx @@ -1,6 +1,6 @@ import { useDrag } from "react-dnd"; import { FaRegTrashAlt } from "react-icons/fa"; -import { JOINED_PARTICIPANT, PARTICIPANT } from "constants/admin/dnd"; +import { JOINED_PARTICIPANT } from "constants/dnd"; import styled from "styled-components"; import { JoinedUser } from "interfaces/group/index"; diff --git a/frontend/src/components/admin/channel/channelDetail/game/teamManagement/ParticipantList.tsx b/frontend/apps/admin/src/components/channel/channelDetail/game/teamManagement/ParticipantList.tsx similarity index 98% rename from frontend/src/components/admin/channel/channelDetail/game/teamManagement/ParticipantList.tsx rename to frontend/apps/admin/src/components/channel/channelDetail/game/teamManagement/ParticipantList.tsx index 2dd02af0..fe9b7410 100644 --- a/frontend/src/components/admin/channel/channelDetail/game/teamManagement/ParticipantList.tsx +++ b/frontend/apps/admin/src/components/channel/channelDetail/game/teamManagement/ParticipantList.tsx @@ -1,7 +1,7 @@ import { JoinedUser } from "interfaces/group"; import { useDrag } from "react-dnd"; import styled from "styled-components"; -import { PARTICIPANT } from "constants/admin/dnd"; +import { PARTICIPANT } from "constants/dnd"; const ParticipantList = ({ users }: { users?: JoinedUser[] }) => { const validUsers = users || []; diff --git a/frontend/src/components/admin/channel/channelDetail/game/teamManagement/index.tsx b/frontend/apps/admin/src/components/channel/channelDetail/game/teamManagement/index.tsx similarity index 94% rename from frontend/src/components/admin/channel/channelDetail/game/teamManagement/index.tsx rename to frontend/apps/admin/src/components/channel/channelDetail/game/teamManagement/index.tsx index 41450f28..2c50cdd1 100644 --- a/frontend/src/components/admin/channel/channelDetail/game/teamManagement/index.tsx +++ b/frontend/apps/admin/src/components/channel/channelDetail/game/teamManagement/index.tsx @@ -2,10 +2,10 @@ import { Group, JoinedUser } from "interfaces/group"; import styled from "styled-components"; import { FaPlus, FaRandom } from "react-icons/fa"; -import executeMatchMaking from "utils/admin/channel/executeMatchMaking"; +import executeMatchMaking from "utils/channel/executeMatchMaking"; import { alertErrorMessage } from "utils/error"; -import useAddUsersToGroup from "hooks/admin/group/useAddUsersToGroup"; -import useCreateGroup from "hooks/admin/group/useCreateGroup"; +import useAddUsersToGroup from "hooks/group/useAddUsersToGroup"; +import useCreateGroup from "hooks/group/useCreateGroup"; import GroupList from "./GroupList"; import { QueryObserverResult, RefetchOptions } from "@tanstack/react-query"; import { User } from "interfaces/user"; diff --git a/frontend/src/components/admin/channel/channelDetail/socialing/index.tsx b/frontend/apps/admin/src/components/channel/channelDetail/socialing/index.tsx similarity index 91% rename from frontend/src/components/admin/channel/channelDetail/socialing/index.tsx rename to frontend/apps/admin/src/components/channel/channelDetail/socialing/index.tsx index e97a56ce..7a07a815 100644 --- a/frontend/src/components/admin/channel/channelDetail/socialing/index.tsx +++ b/frontend/apps/admin/src/components/channel/channelDetail/socialing/index.tsx @@ -1,16 +1,19 @@ -import useSaveBar from "hooks/admin/components/useSaveBar"; +import useSaveBar from "hooks/components/useSaveBar"; import { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; import styled from "styled-components"; -import { Pathnames } from "constants/admin"; -import { FormData } from "pages/admin/channel/form/ChannelForm"; -import { useChannelFormContext } from "hooks/admin/channel/context/useChannelFormContext"; -import useGetBrands from "hooks/admin/brand/useGetBrands"; -import useCreateChannel from "hooks/admin/channel/useCreateChannel"; -import useEditChannel from "hooks/admin/channel/useEditChannel"; +import { Pathnames } from "constants/index"; +import { FormData } from "pages/channel/form/ChannelForm"; +import { useChannelFormContext } from "hooks/channel/context/useChannelFormContext"; +import useGetBrands from "hooks/brand/useGetBrands"; +import useCreateChannel from "hooks/channel/useCreateChannel"; +import useEditChannel from "hooks/channel/useEditChannel"; import { ChannelState } from "interfaces/channels"; import { ChannelFeatureButton } from "constants/common"; +import { OWNER } from "configs"; +import useSystemModal from "hooks/common/components/useSystemModal"; +import useOwnerCookie from "hooks/auth/useOwnerCookie"; interface Errors { title?: string; @@ -31,6 +34,9 @@ interface ChannelPayload { const Socialing = () => { const { isEditMode, formData, setFormData, channelId } = useChannelFormContext(); + const { showAnyMessageModal } = useSystemModal(); + const owner = useOwnerCookie(); + const isTester = owner === "tester"; const { showSaveBar, closeSaveBar } = useSaveBar(); const { data } = useGetBrands({ @@ -69,6 +75,11 @@ const Socialing = () => { }; const handleCheckboxChange = (value: ChannelFeatureButton) => { + if (isTester) { + showAnyMessageModal("ํ…Œ์Šคํ„ฐ ๊ณ„์ •์€ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค"); + return; + } + setFormData((prevData) => { const updatedComponents = prevData.visible_components.includes(value) ? prevData.visible_components.filter((item) => item !== value) @@ -78,6 +89,11 @@ const Socialing = () => { }; const handleSubmit = () => { + if (isTester) { + showAnyMessageModal("ํ…Œ์Šคํ„ฐ ๊ณ„์ •์€ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค"); + return; + } + const requiredFields: (keyof FormData)[] = [ "title", "description", diff --git a/frontend/src/components/admin/common/AdminLayout.tsx b/frontend/apps/admin/src/components/common/AdminLayout.tsx similarity index 70% rename from frontend/src/components/admin/common/AdminLayout.tsx rename to frontend/apps/admin/src/components/common/AdminLayout.tsx index e2761303..eee7f9bb 100644 --- a/frontend/src/components/admin/common/AdminLayout.tsx +++ b/frontend/apps/admin/src/components/common/AdminLayout.tsx @@ -1,9 +1,9 @@ import styled from "styled-components"; import useCheckUserRole from "hooks/auth/useCheckUserRole"; -import ManagementTabs from "components/admin/common/ManagementTabs"; -import SystemModal from "components/consumer/common/SystemModal"; -import PopupModal from "components/consumer/common/PopupModal"; +import ManagementTabs from "components/common/ManagementTabs"; +import SystemModal from "components/common/SystemModal"; +import PopupModal from "./PopupModal"; import SaveBar from "./SaveBar"; import { useRecoilState } from "recoil"; import userState from "recoils/atoms/auth/userState"; @@ -13,7 +13,7 @@ interface Props { } const AdminLayout = ({ children }: Props) => { - const [userData, setUserData] = useRecoilState(userState); + const [userData] = useRecoilState(userState); const { isUser } = useCheckUserRole(userData?.role); return ( diff --git a/frontend/src/components/admin/common/Button.tsx b/frontend/apps/admin/src/components/common/Button.tsx similarity index 100% rename from frontend/src/components/admin/common/Button.tsx rename to frontend/apps/admin/src/components/common/Button.tsx diff --git a/frontend/src/components/admin/common/EmptyState.tsx b/frontend/apps/admin/src/components/common/EmptyState.tsx similarity index 100% rename from frontend/src/components/admin/common/EmptyState.tsx rename to frontend/apps/admin/src/components/common/EmptyState.tsx diff --git a/frontend/src/components/admin/common/ManagementTabs.tsx b/frontend/apps/admin/src/components/common/ManagementTabs.tsx similarity index 84% rename from frontend/src/components/admin/common/ManagementTabs.tsx rename to frontend/apps/admin/src/components/common/ManagementTabs.tsx index 54eac72e..14a386f4 100644 --- a/frontend/src/components/admin/common/ManagementTabs.tsx +++ b/frontend/apps/admin/src/components/common/ManagementTabs.tsx @@ -1,7 +1,7 @@ import { useLocation, useNavigate } from "react-router-dom"; import styled from "styled-components"; -import { AdminPathnames, Pathnames } from "constants/admin/index"; +import { Pathnames } from "constants/index"; import useCheckUserRole from "hooks/auth/useCheckUserRole"; import { useRecoilState } from "recoil"; import userState from "recoils/atoms/auth/userState"; @@ -13,7 +13,7 @@ const ManagementTabs = () => { const [userData] = useRecoilState(userState); const { isSuperAdmin, isAdmin } = useCheckUserRole(userData?.role); - const handleTabClick = (pathName: Pathnames | AdminPathnames) => { + const handleTabClick = (pathName: Pathnames) => { navigate(pathName); }; @@ -23,10 +23,10 @@ const ManagementTabs = () => { {isSuperAdmin && ( handleTabClick(AdminPathnames.Submission)} + onClick={() => handleTabClick(Pathnames.Submission)} > ์‹ ์ฒญ @@ -35,10 +35,10 @@ const ManagementTabs = () => { {isAdmin && ( handleTabClick(AdminPathnames.Host)} + onClick={() => handleTabClick(Pathnames.Host)} > ๋ชจ์ž„์žฅ @@ -64,8 +64,8 @@ const ManagementTabs = () => { ์œ ์ € handleTabClick(AdminPathnames.Landing)} + isActive={location.pathname.includes(Pathnames.Landing)} + onClick={() => handleTabClick(Pathnames.Landing)} > ๋ฉ”์ธ ํŽ˜์ด์ง€ diff --git a/frontend/src/components/admin/common/Pagination.tsx b/frontend/apps/admin/src/components/common/Pagination.tsx similarity index 100% rename from frontend/src/components/admin/common/Pagination.tsx rename to frontend/apps/admin/src/components/common/Pagination.tsx diff --git a/frontend/src/components/consumer/common/PopupModal.tsx b/frontend/apps/admin/src/components/common/PopupModal.tsx similarity index 100% rename from frontend/src/components/consumer/common/PopupModal.tsx rename to frontend/apps/admin/src/components/common/PopupModal.tsx diff --git a/frontend/apps/admin/src/components/common/RequireAuth.tsx b/frontend/apps/admin/src/components/common/RequireAuth.tsx new file mode 100644 index 00000000..d6c72589 --- /dev/null +++ b/frontend/apps/admin/src/components/common/RequireAuth.tsx @@ -0,0 +1,30 @@ +import { Navigate, useLocation } from "react-router-dom"; +import { useRecoilValue } from "recoil"; +import { useEffect } from "react"; +import userState from "recoils/atoms/auth/userState"; +import { OWNER } from "configs"; + +const RequireAuth = (Component: React.ComponentType) => { + return function WithAuth(props: any) { + const location = useLocation(); + const user = useRecoilValue(userState); + + useEffect(() => { + const params = new URLSearchParams(location.search); + const host = params.get("host"); + + if (host) { + document.cookie = `${OWNER}=${host}; path=/; max-age=${60 * 60 * 24}`; + window.history.replaceState(null, "", location.pathname); + } + }, [location.search]); + + if (!user) { + return ; + } + + return ; + }; +}; + +export default RequireAuth; diff --git a/frontend/src/components/admin/common/SaveBar.tsx b/frontend/apps/admin/src/components/common/SaveBar.tsx similarity index 93% rename from frontend/src/components/admin/common/SaveBar.tsx rename to frontend/apps/admin/src/components/common/SaveBar.tsx index f80eea4f..325b2127 100644 --- a/frontend/src/components/admin/common/SaveBar.tsx +++ b/frontend/apps/admin/src/components/common/SaveBar.tsx @@ -1,6 +1,6 @@ import { useRecoilState } from "recoil"; import styled from "styled-components"; -import { saveBarState } from "recoils/atoms/admin/saveBar"; +import { saveBarState } from "recoils/atoms/saveBar"; const SaveBar = () => { const [saveBar] = useRecoilState(saveBarState); diff --git a/frontend/src/components/consumer/common/SystemModal.tsx b/frontend/apps/admin/src/components/common/SystemModal.tsx similarity index 100% rename from frontend/src/components/consumer/common/SystemModal.tsx rename to frontend/apps/admin/src/components/common/SystemModal.tsx diff --git a/frontend/src/components/admin/common/Table.tsx b/frontend/apps/admin/src/components/common/Table.tsx similarity index 100% rename from frontend/src/components/admin/common/Table.tsx rename to frontend/apps/admin/src/components/common/Table.tsx diff --git a/frontend/src/components/admin/common/TextArea.tsx b/frontend/apps/admin/src/components/common/TextArea.tsx similarity index 100% rename from frontend/src/components/admin/common/TextArea.tsx rename to frontend/apps/admin/src/components/common/TextArea.tsx diff --git a/frontend/src/components/common/OptimizedImage.tsx b/frontend/apps/admin/src/components/common/image/OptimizedImage.tsx similarity index 100% rename from frontend/src/components/common/OptimizedImage.tsx rename to frontend/apps/admin/src/components/common/image/OptimizedImage.tsx diff --git a/frontend/src/components/admin/common/image/ProductImage.tsx b/frontend/apps/admin/src/components/common/image/ProductImage.tsx similarity index 96% rename from frontend/src/components/admin/common/image/ProductImage.tsx rename to frontend/apps/admin/src/components/common/image/ProductImage.tsx index c3e7f9f2..5b6afd71 100644 --- a/frontend/src/components/admin/common/image/ProductImage.tsx +++ b/frontend/apps/admin/src/components/common/image/ProductImage.tsx @@ -3,7 +3,7 @@ import styled from "styled-components"; import changeImageIconSrc from "assets/icons/changeImage.svg"; import removeImageIconSrc from "assets/icons/removeImage.svg"; -import OptimizedImage from "components/common/OptimizedImage"; +import OptimizedImage from "components/common/image/OptimizedImage"; interface ProductImageProps { imageSource: string; diff --git a/frontend/src/components/admin/common/image/addImageInput.tsx b/frontend/apps/admin/src/components/common/image/addImageInput.tsx similarity index 100% rename from frontend/src/components/admin/common/image/addImageInput.tsx rename to frontend/apps/admin/src/components/common/image/addImageInput.tsx diff --git a/frontend/src/components/admin/common/input/Dropdown.tsx b/frontend/apps/admin/src/components/common/input/Dropdown.tsx similarity index 100% rename from frontend/src/components/admin/common/input/Dropdown.tsx rename to frontend/apps/admin/src/components/common/input/Dropdown.tsx diff --git a/frontend/src/components/admin/common/input/Input.tsx b/frontend/apps/admin/src/components/common/input/Input.tsx similarity index 100% rename from frontend/src/components/admin/common/input/Input.tsx rename to frontend/apps/admin/src/components/common/input/Input.tsx diff --git a/frontend/src/components/admin/landing/Gallery.tsx b/frontend/apps/admin/src/components/landing/Gallery.tsx similarity index 83% rename from frontend/src/components/admin/landing/Gallery.tsx rename to frontend/apps/admin/src/components/landing/Gallery.tsx index 1726c5d2..46c23d71 100644 --- a/frontend/src/components/admin/landing/Gallery.tsx +++ b/frontend/apps/admin/src/components/landing/Gallery.tsx @@ -1,13 +1,15 @@ import { useEffect, useState, useCallback } from "react"; import styled from "styled-components"; -import useGetGalleryImages from "api/landing/hook/useGetGalleryImages"; -import useUploadGalleryImage from "api/admin/landing/hooks/useUploadGalleryImage"; -import useDeleteGalleryImage from "api/admin/landing/hooks/useDeleteGalleryImage"; +import useGetGalleryImages from "api/landing/hooks/useGetGalleryImages"; +import useUploadGalleryImage from "api/landing/hooks/useUploadGalleryImage"; +import useDeleteGalleryImage from "api/landing/hooks/useDeleteGalleryImage"; import useSystemModal from "hooks/common/components/useSystemModal"; -import ProductImage from "components/admin/common/image/ProductImage"; -import AddImageInput from "components/admin/common/image/addImageInput"; +import ProductImage from "components/common/image/ProductImage"; +import AddImageInput from "components/common/image/addImageInput"; +import { OWNER } from "configs"; +import useOwnerCookie from "hooks/auth/useOwnerCookie"; type GalleryImage = { id?: number; url: string }; const emptyImage: GalleryImage = { id: null, url: "" }; @@ -17,7 +19,10 @@ const Gallery = () => { const { mutate: uploadGalleryImage } = useUploadGalleryImage(); const { mutate: deleteGalleryImage } = useDeleteGalleryImage(); - const { showErrorModal } = useSystemModal(); + const owner = useOwnerCookie(); + const isTester = owner === "tester"; + + const { showErrorModal, showAnyMessageModal } = useSystemModal(); const [images, setImages] = useState([emptyImage]); const handleAddImage = useCallback(() => { @@ -26,6 +31,11 @@ const Gallery = () => { const handleImageChange = useCallback( (index: number) => (e: React.ChangeEvent) => { + if (isTester) { + showAnyMessageModal("ํ…Œ์Šคํ„ฐ ๊ณ„์ •์€ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค"); + return; + } + const file = e.target.files?.[0]; if (!file) return; uploadGalleryImage(file, { @@ -46,6 +56,11 @@ const Gallery = () => { const handleDeleteImage = useCallback( (index: number) => { + if (isTester) { + showAnyMessageModal("ํ…Œ์Šคํ„ฐ ๊ณ„์ •์€ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค"); + return; + } + const image = images[index]; if (!image.id) { setImages((prev) => prev.filter((_, i) => i !== index)); diff --git a/frontend/src/components/admin/landing/MainImage.tsx b/frontend/apps/admin/src/components/landing/MainImage.tsx similarity index 75% rename from frontend/src/components/admin/landing/MainImage.tsx rename to frontend/apps/admin/src/components/landing/MainImage.tsx index fcd80763..e183b568 100644 --- a/frontend/src/components/admin/landing/MainImage.tsx +++ b/frontend/apps/admin/src/components/landing/MainImage.tsx @@ -2,25 +2,34 @@ import React from "react"; import styled from "styled-components"; import { useQueryClient } from "@tanstack/react-query"; -import useUploadMainImage from "api/admin/landing/hooks/useUploadMainImage"; -import useDeleteMainImage from "api/admin/landing/hooks/useDeleteMainImage"; -import useGetMainImage from "api/admin/landing/hooks/useGetMainImage"; -import { GET_MAIN_IMAGE } from "constants/landing/queryKeys"; +import useUploadMainImage from "api/landing/hooks/useUploadMainImage"; +import useDeleteMainImage from "api/landing/hooks/useDeleteMainImage"; +import useGetMainImage from "api/landing/hooks/useGetMainImage"; +import { GET_MAIN_IMAGE } from "constants/queryKeys"; import useSystemModal from "hooks/common/components/useSystemModal"; -import AddImageInput from "components/admin/common/image/addImageInput"; +import AddImageInput from "components/common/image/addImageInput"; import ProductImage from "../common/image/ProductImage"; +import useOwnerCookie from "hooks/auth/useOwnerCookie"; const MainImage = () => { const { mutate: uploadMainImage } = useUploadMainImage(); const { mutate: deleteMainImgae } = useDeleteMainImage(); const queryClient = useQueryClient(); const { data } = useGetMainImage(); - const { showErrorModal } = useSystemModal(); + const { showErrorModal, showAnyMessageModal } = useSystemModal(); + + const owner = useOwnerCookie(); + const isTester = owner === "tester"; const handleMainImageUpload = async ( e: React.ChangeEvent ) => { + if (isTester) { + showAnyMessageModal("ํ…Œ์Šคํ„ฐ ๊ณ„์ •์€ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค"); + return; + } + if (!e.target.files) return; const file = e.target.files[0]; @@ -35,6 +44,11 @@ const MainImage = () => { }; const handleRemove = async () => { + if (isTester) { + showAnyMessageModal("ํ…Œ์Šคํ„ฐ ๊ณ„์ •์€ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค"); + return; + } + deleteMainImgae(null, { onSuccess: () => { queryClient.setQueryData([GET_MAIN_IMAGE], { id: null, url: "" }); diff --git a/frontend/src/components/admin/landing/SectionToggle.tsx b/frontend/apps/admin/src/components/landing/SectionToggle.tsx similarity index 100% rename from frontend/src/components/admin/landing/SectionToggle.tsx rename to frontend/apps/admin/src/components/landing/SectionToggle.tsx diff --git a/frontend/src/components/admin/submission/Controller.tsx b/frontend/apps/admin/src/components/submission/Controller.tsx similarity index 97% rename from frontend/src/components/admin/submission/Controller.tsx rename to frontend/apps/admin/src/components/submission/Controller.tsx index eba5ca3e..91bae864 100644 --- a/frontend/src/components/admin/submission/Controller.tsx +++ b/frontend/apps/admin/src/components/submission/Controller.tsx @@ -1,4 +1,4 @@ -import { useSubmissionContext } from "hooks/admin/submission/context/useSubmissionContext"; +import { useSubmissionContext } from "hooks/submission/context/useSubmissionContext"; import React from "react"; import { useSearchParams } from "react-router-dom"; import styled from "styled-components"; diff --git a/frontend/src/components/admin/submission/SubmissionTable.tsx b/frontend/apps/admin/src/components/submission/SubmissionTable.tsx similarity index 94% rename from frontend/src/components/admin/submission/SubmissionTable.tsx rename to frontend/apps/admin/src/components/submission/SubmissionTable.tsx index 43bd627a..f66f053b 100644 --- a/frontend/src/components/admin/submission/SubmissionTable.tsx +++ b/frontend/apps/admin/src/components/submission/SubmissionTable.tsx @@ -3,11 +3,10 @@ import { useRecoilState } from "recoil"; import styled from "styled-components"; import { useQueryClient } from "@tanstack/react-query"; -import useUpdateHostRegist from "api/admin/hostRegist/useUpdateHostRegist"; -import type { GetHostRegistsOutput } from "api/admin/hostRegist/useGetHostRegistAdmin"; -import { GET_HOST_REGIST_ADMIN } from "constants/admin/queryKeys"; -import { siteUrl } from "configs"; -import { useSubmissionContext } from "hooks/admin/submission/context/useSubmissionContext"; +import useUpdateHostRegist from "api/hostRegist/useUpdateHostRegist"; +import type { GetHostRegistsOutput } from "api/hostRegist/useGetHostRegistAdmin"; +import { GET_HOST_REGIST_ADMIN } from "constants/queryKeys"; +import { useSubmissionContext } from "hooks/submission/context/useSubmissionContext"; import useSystemModal from "hooks/common/components/useSystemModal"; import useCheckUserRole from "hooks/auth/useCheckUserRole"; import { HostRegistState } from "interfaces/user"; @@ -32,7 +31,7 @@ const SubmissionTable = () => { if (entry.state === HostRegistState.ACCEPT && entry.user?.email) { setHashUrls((prev) => ({ ...prev, - [entry.id]: `${siteUrl}/admin/login?host=${entry.user.username}`, + [entry.id]: `https://admin.moimjang.com/login?host=${entry.user.username}`, })); } }); diff --git a/frontend/src/components/admin/user/Controller.tsx b/frontend/apps/admin/src/components/user/Controller.tsx similarity index 97% rename from frontend/src/components/admin/user/Controller.tsx rename to frontend/apps/admin/src/components/user/Controller.tsx index 1906fcba..7fa039a4 100644 --- a/frontend/src/components/admin/user/Controller.tsx +++ b/frontend/apps/admin/src/components/user/Controller.tsx @@ -1,7 +1,7 @@ import { useSearchParams } from "react-router-dom"; import styled from "styled-components"; -import useUserTable from "hooks/admin/users/useUserTable"; +import useUserTable from "hooks/users/useUserTable"; import Button from "../common/Button"; const Controller = () => { diff --git a/frontend/src/components/admin/user/UserTable.tsx b/frontend/apps/admin/src/components/user/UserTable.tsx similarity index 95% rename from frontend/src/components/admin/user/UserTable.tsx rename to frontend/apps/admin/src/components/user/UserTable.tsx index 9be32158..1994e4b6 100644 --- a/frontend/src/components/admin/user/UserTable.tsx +++ b/frontend/apps/admin/src/components/user/UserTable.tsx @@ -1,10 +1,10 @@ import { useNavigate } from "react-router-dom"; -import { Pathnames } from "constants/admin/index"; +import { Pathnames } from "constants/index"; -import useUserTable from "hooks/admin/users/useUserTable"; +import useUserTable from "hooks/users/useUserTable"; import useSystemModal from "hooks/common/components/useSystemModal"; import { Table, TableContainer } from "../common/Table"; -import Button from "components/admin/common/Button"; +import Button from "components/common/Button"; import { UserRole } from "interfaces/user"; import { USER_ROLE } from "configs"; import useCheckUserRole from "hooks/auth/useCheckUserRole"; diff --git a/frontend/src/configs/index.ts b/frontend/apps/admin/src/configs/index.ts similarity index 100% rename from frontend/src/configs/index.ts rename to frontend/apps/admin/src/configs/index.ts diff --git a/frontend/src/constants/common.ts b/frontend/apps/admin/src/constants/common.ts similarity index 100% rename from frontend/src/constants/common.ts rename to frontend/apps/admin/src/constants/common.ts diff --git a/frontend/src/constants/admin/dnd.ts b/frontend/apps/admin/src/constants/dnd.ts similarity index 100% rename from frontend/src/constants/admin/dnd.ts rename to frontend/apps/admin/src/constants/dnd.ts diff --git a/frontend/src/constants/env.ts b/frontend/apps/admin/src/constants/env.ts similarity index 100% rename from frontend/src/constants/env.ts rename to frontend/apps/admin/src/constants/env.ts diff --git a/frontend/apps/admin/src/constants/index.ts b/frontend/apps/admin/src/constants/index.ts new file mode 100644 index 00000000..0e816ade --- /dev/null +++ b/frontend/apps/admin/src/constants/index.ts @@ -0,0 +1,18 @@ +export enum Pathnames { + Login = "/login", + SignUp = "/signup", + Home = "/", + Social = "/social", + CreateSocial = "/social/create", + User = "/user", + CreateUser = "/user/create", + Channel = "/channel", + CreateChannel = "/channel/create", + EditChannel = "/channel/edit", + Brand = "/brand", + CreateBrand = "/brand/create", + EditBrand = "/brand/edit", + Host = "/host", + Landing = "/landing", + Submission = "/submission", +} diff --git a/frontend/src/constants/admin/queryKeys.ts b/frontend/apps/admin/src/constants/queryKeys.ts similarity index 92% rename from frontend/src/constants/admin/queryKeys.ts rename to frontend/apps/admin/src/constants/queryKeys.ts index bb02860d..3a9c32bf 100644 --- a/frontend/src/constants/admin/queryKeys.ts +++ b/frontend/apps/admin/src/constants/queryKeys.ts @@ -34,3 +34,6 @@ export const GET_SURVEY_BY_ID = "GET_SURVEY_BY_ID"; // hostRegistAdmin export const GET_HOST_REGIST_ADMIN = "GET_HOST_REGIST_ADMIN"; export const GET_MY_HOST_REGIST = "GET_MY_HOST_REGIST"; + +export const GET_MAIN_IMAGE = "GET_MAIN_IMAGE"; +export const GET_GALLERY_IMAGES = "GET_GALLERY_IMAGES"; diff --git a/frontend/src/hooks/auth/useCheckUserRole.tsx b/frontend/apps/admin/src/hooks/auth/useCheckUserRole.tsx similarity index 100% rename from frontend/src/hooks/auth/useCheckUserRole.tsx rename to frontend/apps/admin/src/hooks/auth/useCheckUserRole.tsx diff --git a/frontend/src/hooks/auth/useLogin.tsx b/frontend/apps/admin/src/hooks/auth/useLogin.tsx similarity index 100% rename from frontend/src/hooks/auth/useLogin.tsx rename to frontend/apps/admin/src/hooks/auth/useLogin.tsx diff --git a/frontend/apps/admin/src/hooks/auth/useOwnerCookie.ts b/frontend/apps/admin/src/hooks/auth/useOwnerCookie.ts new file mode 100644 index 00000000..7fc2255a --- /dev/null +++ b/frontend/apps/admin/src/hooks/auth/useOwnerCookie.ts @@ -0,0 +1,24 @@ +import { OWNER } from "configs"; +import { useState, useEffect } from "react"; + +export function getCookie(name: string): string | null { + const cookies = document.cookie.split(";").map((c) => c.trim()); + for (const cookie of cookies) { + const [key, ...rest] = cookie.split("="); + if (key === name) { + return decodeURIComponent(rest.join("=")); + } + } + return null; +} + +export default function useOwnerCookie() { + const [owner, setOwner] = useState(null); + + useEffect(() => { + const value = getCookie(OWNER); + setOwner(value); + }, []); + + return owner; +} diff --git a/frontend/src/hooks/auth/useSignUp.tsx b/frontend/apps/admin/src/hooks/auth/useSignUp.tsx similarity index 89% rename from frontend/src/hooks/auth/useSignUp.tsx rename to frontend/apps/admin/src/hooks/auth/useSignUp.tsx index 2a732973..bd31efda 100644 --- a/frontend/src/hooks/auth/useSignUp.tsx +++ b/frontend/apps/admin/src/hooks/auth/useSignUp.tsx @@ -4,6 +4,7 @@ import { User } from "interfaces/user"; import axios from "axios"; import { OWNER, serverUrl } from "configs"; +import useOwnerCookie, { getCookie } from "./useOwnerCookie"; export interface SignUpInput { username: string; @@ -21,7 +22,7 @@ export interface SignUpInput { } export const signUp = async (userData: SignUpInput): Promise => { - const owner = localStorage.getItem(OWNER); + const owner = getCookie(OWNER); const result = await axios.post(`${serverUrl}/auth/signup`, userData, { headers: { diff --git a/frontend/src/hooks/admin/brand/context/useBrandFormContext.tsx b/frontend/apps/admin/src/hooks/brand/context/useBrandFormContext.tsx similarity index 87% rename from frontend/src/hooks/admin/brand/context/useBrandFormContext.tsx rename to frontend/apps/admin/src/hooks/brand/context/useBrandFormContext.tsx index ebe17c14..7c72a8b3 100644 --- a/frontend/src/hooks/admin/brand/context/useBrandFormContext.tsx +++ b/frontend/apps/admin/src/hooks/brand/context/useBrandFormContext.tsx @@ -1,5 +1,5 @@ import { createContext, useContext } from "react"; -import { EditBrandInputType } from "pages/admin/brand/form/EditBrand"; +import { EditBrandInputType } from "pages/brand/form/EditBrand"; interface BrandFormContextType { brand: EditBrandInputType; diff --git a/frontend/src/hooks/admin/brand/context/useBrandReviewContext.tsx b/frontend/apps/admin/src/hooks/brand/context/useBrandReviewContext.tsx similarity index 86% rename from frontend/src/hooks/admin/brand/context/useBrandReviewContext.tsx rename to frontend/apps/admin/src/hooks/brand/context/useBrandReviewContext.tsx index 71164a20..e01ddc79 100644 --- a/frontend/src/hooks/admin/brand/context/useBrandReviewContext.tsx +++ b/frontend/apps/admin/src/hooks/brand/context/useBrandReviewContext.tsx @@ -1,7 +1,7 @@ import { createContext, useContext } from "react"; import { QueryObserverResult, RefetchOptions } from "@tanstack/react-query"; -import { BrandReview } from "api/admin/brand/types/brandReview"; -import { GetBrandReviewsOutput } from "api/admin/brand/hooks/useGetBrandReviews"; +import { BrandReview } from "api/brand/types/brandReview"; +import { GetBrandReviewsOutput } from "api/brand/hooks/useGetBrandReviews"; interface BrandReviewContextType { brandReviews: Array; diff --git a/frontend/src/hooks/admin/brand/context/useSurveyBuilder.tsx b/frontend/apps/admin/src/hooks/brand/context/useSurveyBuilder.tsx similarity index 97% rename from frontend/src/hooks/admin/brand/context/useSurveyBuilder.tsx rename to frontend/apps/admin/src/hooks/brand/context/useSurveyBuilder.tsx index e0e01e8c..c2b1efad 100644 --- a/frontend/src/hooks/admin/brand/context/useSurveyBuilder.tsx +++ b/frontend/apps/admin/src/hooks/brand/context/useSurveyBuilder.tsx @@ -1,7 +1,7 @@ import { Question } from "interfaces/brand/survey"; import { SurveyType } from "interfaces/brand/survey"; import { useRecoilState } from "recoil"; -import { surveyQuestionsAtom } from "recoils/atoms/admin/surveyState"; +import { surveyQuestionsAtom } from "recoils/atoms/surveyState"; import { v4 as uuid } from "uuid"; const useSurveyBuilder = () => { diff --git a/frontend/src/hooks/admin/brand/useCreateBrand.tsx b/frontend/apps/admin/src/hooks/brand/useCreateBrand.tsx similarity index 93% rename from frontend/src/hooks/admin/brand/useCreateBrand.tsx rename to frontend/apps/admin/src/hooks/brand/useCreateBrand.tsx index 0bd20abf..7bea10fd 100644 --- a/frontend/src/hooks/admin/brand/useCreateBrand.tsx +++ b/frontend/apps/admin/src/hooks/brand/useCreateBrand.tsx @@ -1,7 +1,7 @@ import { AxiosError } from "axios"; import { useMutation } from "@tanstack/react-query"; import { ACCEESS_TOKEN, serverUrl } from "configs"; -import axiosInstance from "api/admin/axiosInstance"; +import axiosInstance from "api/axiosInstance"; export interface CreateBrandOutput { title: string; diff --git a/frontend/src/hooks/admin/brand/useDeleteBrand.tsx b/frontend/apps/admin/src/hooks/brand/useDeleteBrand.tsx similarity index 80% rename from frontend/src/hooks/admin/brand/useDeleteBrand.tsx rename to frontend/apps/admin/src/hooks/brand/useDeleteBrand.tsx index 1c22b2e4..eedcd7fb 100644 --- a/frontend/src/hooks/admin/brand/useDeleteBrand.tsx +++ b/frontend/apps/admin/src/hooks/brand/useDeleteBrand.tsx @@ -1,6 +1,6 @@ import { AxiosError } from "axios"; import { useMutation } from "@tanstack/react-query"; -import { deleteBrand } from "api/admin/brand/deleteBrand"; +import { deleteBrand } from "api/brand/deleteBrand"; const useDeleteBrand = () => { return useMutation({ diff --git a/frontend/src/hooks/admin/brand/useEditBrand.tsx b/frontend/apps/admin/src/hooks/brand/useEditBrand.tsx similarity index 81% rename from frontend/src/hooks/admin/brand/useEditBrand.tsx rename to frontend/apps/admin/src/hooks/brand/useEditBrand.tsx index 18ec8720..93cc2b4b 100644 --- a/frontend/src/hooks/admin/brand/useEditBrand.tsx +++ b/frontend/apps/admin/src/hooks/brand/useEditBrand.tsx @@ -1,7 +1,7 @@ import { AxiosError } from "axios"; import { useMutation } from "@tanstack/react-query"; -import { editBrand, EditBrandOutput, Input } from "api/admin/brand/editBrand"; +import { editBrand, EditBrandOutput, Input } from "api/brand/editBrand"; interface Variables { requestBody: Input; diff --git a/frontend/src/hooks/admin/brand/useGetBrandById.tsx b/frontend/apps/admin/src/hooks/brand/useGetBrandById.tsx similarity index 81% rename from frontend/src/hooks/admin/brand/useGetBrandById.tsx rename to frontend/apps/admin/src/hooks/brand/useGetBrandById.tsx index f6c0a120..ab08d2ac 100644 --- a/frontend/src/hooks/admin/brand/useGetBrandById.tsx +++ b/frontend/apps/admin/src/hooks/brand/useGetBrandById.tsx @@ -2,8 +2,8 @@ import { useQuery } from "@tanstack/react-query"; import { useParams } from "react-router-dom"; import { AxiosError } from "axios"; -import { getBrandById } from "api/admin/brand/getBrandById"; -import { GET_BRAND_BY_ID } from "constants/admin/queryKeys"; +import { getBrandById } from "api/brand/getBrandById"; +import { GET_BRAND_BY_ID } from "constants/queryKeys"; import { Brand } from "interfaces/brand"; const useGetBrandById = (brandId: string) => { diff --git a/frontend/src/hooks/admin/brand/useGetBrandReviewList.tsx b/frontend/apps/admin/src/hooks/brand/useGetBrandReviewList.tsx similarity index 84% rename from frontend/src/hooks/admin/brand/useGetBrandReviewList.tsx rename to frontend/apps/admin/src/hooks/brand/useGetBrandReviewList.tsx index 11ac64d3..8bdecba6 100644 --- a/frontend/src/hooks/admin/brand/useGetBrandReviewList.tsx +++ b/frontend/apps/admin/src/hooks/brand/useGetBrandReviewList.tsx @@ -1,12 +1,12 @@ import { useQuery } from "@tanstack/react-query"; import { AxiosError } from "axios"; -import { GET_BRAND_REVIEW_LIST } from "constants/admin/queryKeys"; +import { GET_BRAND_REVIEW_LIST } from "constants/queryKeys"; import { getBrandReviewList, Output, Params, -} from "api/admin/brand/getBrandReviewList"; +} from "api/brand/getBrandReviewList"; const useGetBrandReviewList = (params: Params) => { const { data, isLoading, error, isSuccess, refetch } = useQuery< diff --git a/frontend/src/hooks/admin/brand/useGetBrands.tsx b/frontend/apps/admin/src/hooks/brand/useGetBrands.tsx similarity index 83% rename from frontend/src/hooks/admin/brand/useGetBrands.tsx rename to frontend/apps/admin/src/hooks/brand/useGetBrands.tsx index 944e0b08..fbeb8255 100644 --- a/frontend/src/hooks/admin/brand/useGetBrands.tsx +++ b/frontend/apps/admin/src/hooks/brand/useGetBrands.tsx @@ -1,10 +1,11 @@ import { useQuery } from "@tanstack/react-query"; import { AxiosError } from "axios"; -import axiosInstance from "api/admin/axiosInstance"; -import { GET_BRANDS } from "constants/admin/queryKeys"; +import axiosInstance from "api/axiosInstance"; +import { GET_BRANDS } from "constants/queryKeys"; import { ACCEESS_TOKEN, OWNER, serverUrl } from "configs"; import { Brand, BrandState } from "interfaces/brand"; +import { getCookie } from "hooks/auth/useOwnerCookie"; export interface GetBrandOutput { brands: Array; @@ -23,7 +24,7 @@ export const getBrands = async ( params: GetBrandParams ): Promise => { const token = localStorage.getItem(ACCEESS_TOKEN); - const owner = localStorage.getItem(OWNER); + const owner = getCookie(OWNER); const response = await axiosInstance.get(`${serverUrl}/brands`, { headers: { diff --git a/frontend/src/hooks/admin/brand/useJoinCategoryWithBrand.tsx b/frontend/apps/admin/src/hooks/brand/useJoinCategoryWithBrand.tsx similarity index 91% rename from frontend/src/hooks/admin/brand/useJoinCategoryWithBrand.tsx rename to frontend/apps/admin/src/hooks/brand/useJoinCategoryWithBrand.tsx index 4637d1f1..c7fe6955 100644 --- a/frontend/src/hooks/admin/brand/useJoinCategoryWithBrand.tsx +++ b/frontend/apps/admin/src/hooks/brand/useJoinCategoryWithBrand.tsx @@ -4,7 +4,7 @@ import { useMutation } from "@tanstack/react-query"; import { joinCategoryWithBrand, JoinCategoryWithBrandOutput, -} from "api/admin/brand/joinCategoryWithBrand"; +} from "api/brand/joinCategoryWithBrand"; const useJoinCategoryWithBrand = () => { return useMutation< diff --git a/frontend/src/hooks/admin/channel/context/useApplicationTableContext.ts b/frontend/apps/admin/src/hooks/channel/context/useApplicationTableContext.ts similarity index 100% rename from frontend/src/hooks/admin/channel/context/useApplicationTableContext.ts rename to frontend/apps/admin/src/hooks/channel/context/useApplicationTableContext.ts diff --git a/frontend/src/hooks/admin/channel/context/useChannelFormContext.ts b/frontend/apps/admin/src/hooks/channel/context/useChannelFormContext.ts similarity index 90% rename from frontend/src/hooks/admin/channel/context/useChannelFormContext.ts rename to frontend/apps/admin/src/hooks/channel/context/useChannelFormContext.ts index 12ef57f3..108fd62f 100644 --- a/frontend/src/hooks/admin/channel/context/useChannelFormContext.ts +++ b/frontend/apps/admin/src/hooks/channel/context/useChannelFormContext.ts @@ -1,6 +1,6 @@ import { createContext, useContext } from "react"; import { Channel } from "interfaces/channels"; -import { FormData } from "pages/admin/channel/form/ChannelForm"; +import { FormData } from "pages/channel/form/ChannelForm"; interface ChannelFormContextType { channelData: Channel; diff --git a/frontend/src/hooks/admin/channel/context/useChannelTableContext.ts b/frontend/apps/admin/src/hooks/channel/context/useChannelTableContext.ts similarity index 93% rename from frontend/src/hooks/admin/channel/context/useChannelTableContext.ts rename to frontend/apps/admin/src/hooks/channel/context/useChannelTableContext.ts index 6c78179b..3cc9adf0 100644 --- a/frontend/src/hooks/admin/channel/context/useChannelTableContext.ts +++ b/frontend/apps/admin/src/hooks/channel/context/useChannelTableContext.ts @@ -2,7 +2,7 @@ import { createContext, useContext } from "react"; import { Channel, ChannelState } from "interfaces/channels"; import { QueryObserverResult, RefetchOptions } from "@tanstack/react-query"; import { AxiosError } from "axios"; -import { Output } from "api/admin/channel/hooks/useGetChannels"; +import { Output } from "api/channel/hooks/useGetChannels"; interface ChannelTableContextType { channelData: Array; diff --git a/frontend/src/hooks/admin/channel/useCreateChannel.ts b/frontend/apps/admin/src/hooks/channel/useCreateChannel.ts similarity index 73% rename from frontend/src/hooks/admin/channel/useCreateChannel.ts rename to frontend/apps/admin/src/hooks/channel/useCreateChannel.ts index d77075e9..f291cfbc 100644 --- a/frontend/src/hooks/admin/channel/useCreateChannel.ts +++ b/frontend/apps/admin/src/hooks/channel/useCreateChannel.ts @@ -1,10 +1,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { - createChannel, - CreateChannelInput, -} from "api/admin/channel/createChannel"; +import { createChannel, CreateChannelInput } from "api/channel/createChannel"; import { AxiosError } from "axios"; -import { GET_CHANNELS } from "constants/admin/queryKeys"; +import { GET_CHANNELS } from "constants/queryKeys"; const useCreateChannel = () => { const queryClient = useQueryClient(); diff --git a/frontend/src/hooks/admin/channel/useDeleteChannel.ts b/frontend/apps/admin/src/hooks/channel/useDeleteChannel.ts similarity index 77% rename from frontend/src/hooks/admin/channel/useDeleteChannel.ts rename to frontend/apps/admin/src/hooks/channel/useDeleteChannel.ts index a9d577b7..b2408936 100644 --- a/frontend/src/hooks/admin/channel/useDeleteChannel.ts +++ b/frontend/apps/admin/src/hooks/channel/useDeleteChannel.ts @@ -1,7 +1,7 @@ import { AxiosError } from "axios"; import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { deleteChannel } from "api/admin/channel/deleteChannel"; -import { GET_CHANNELS } from "constants/admin/queryKeys"; +import { deleteChannel } from "api/channel/deleteChannel"; +import { GET_CHANNELS } from "constants/queryKeys"; const useDeleteChannel = () => { const queryClient = useQueryClient(); diff --git a/frontend/src/hooks/admin/channel/useEditChannel.ts b/frontend/apps/admin/src/hooks/channel/useEditChannel.ts similarity index 79% rename from frontend/src/hooks/admin/channel/useEditChannel.ts rename to frontend/apps/admin/src/hooks/channel/useEditChannel.ts index bdcc9e2e..13c6e823 100644 --- a/frontend/src/hooks/admin/channel/useEditChannel.ts +++ b/frontend/apps/admin/src/hooks/channel/useEditChannel.ts @@ -1,6 +1,6 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { editChannel, EditChannelInput } from "api/admin/channel/editChannel"; -import { GET_CHANNELS } from "constants/admin/queryKeys"; +import { editChannel, EditChannelInput } from "api/channel/editChannel"; +import { GET_CHANNELS } from "constants/queryKeys"; interface EditChannelVariables { channel_id: string; diff --git a/frontend/src/hooks/admin/channel/useGetChannelById.ts b/frontend/apps/admin/src/hooks/channel/useGetChannelById.ts similarity index 82% rename from frontend/src/hooks/admin/channel/useGetChannelById.ts rename to frontend/apps/admin/src/hooks/channel/useGetChannelById.ts index c2b83cb7..5437a93d 100644 --- a/frontend/src/hooks/admin/channel/useGetChannelById.ts +++ b/frontend/apps/admin/src/hooks/channel/useGetChannelById.ts @@ -1,6 +1,6 @@ import { useQuery } from "@tanstack/react-query"; -import { GET_CHANNELS_BY_ID } from "constants/admin/queryKeys"; -import { getChannelsById } from "api/admin/channel/getChannelsById"; +import { GET_CHANNELS_BY_ID } from "constants/queryKeys"; +import { getChannelsById } from "api/channel/getChannelsById"; import { useParams } from "react-router-dom"; import { Channel } from "interfaces/channels"; import { AxiosError } from "axios"; diff --git a/frontend/src/hooks/admin/channel/useGetSurveyResponses.tsx b/frontend/apps/admin/src/hooks/channel/useGetSurveyResponses.tsx similarity index 86% rename from frontend/src/hooks/admin/channel/useGetSurveyResponses.tsx rename to frontend/apps/admin/src/hooks/channel/useGetSurveyResponses.tsx index 5b6b2c47..28c6d05a 100644 --- a/frontend/src/hooks/admin/channel/useGetSurveyResponses.tsx +++ b/frontend/apps/admin/src/hooks/channel/useGetSurveyResponses.tsx @@ -1,10 +1,11 @@ import { useQuery } from "@tanstack/react-query"; import { AxiosError } from "axios"; -import { GET_SURVEY_RESPONSES } from "constants/admin/queryKeys"; +import { GET_SURVEY_RESPONSES } from "constants/queryKeys"; import { SurveyResponse } from "interfaces/landing"; import { ACCEESS_TOKEN, OWNER, serverUrl } from "configs"; -import { axiosInstance } from "api/landing/index"; +import axiosInstance from "api/axiosInstance"; +import { getCookie } from "hooks/auth/useOwnerCookie"; export interface GetSurveyResponseParams { survey_id: string; @@ -24,7 +25,7 @@ export const getSurveyResponses = async ( params: GetSurveyResponseParams ): Promise => { const token = localStorage.getItem(ACCEESS_TOKEN); - const owner = localStorage.getItem(OWNER); + const owner = getCookie(OWNER); const response = await axiosInstance.get(`${serverUrl}/surveysResponses`, { headers: { diff --git a/frontend/src/hooks/common/components/usePopupModal.tsx b/frontend/apps/admin/src/hooks/common/components/usePopupModal.tsx similarity index 100% rename from frontend/src/hooks/common/components/usePopupModal.tsx rename to frontend/apps/admin/src/hooks/common/components/usePopupModal.tsx diff --git a/frontend/src/hooks/common/components/useSystemModal.tsx b/frontend/apps/admin/src/hooks/common/components/useSystemModal.tsx similarity index 100% rename from frontend/src/hooks/common/components/useSystemModal.tsx rename to frontend/apps/admin/src/hooks/common/components/useSystemModal.tsx diff --git a/frontend/src/hooks/admin/components/useSaveBar.tsx b/frontend/apps/admin/src/hooks/components/useSaveBar.tsx similarity index 87% rename from frontend/src/hooks/admin/components/useSaveBar.tsx rename to frontend/apps/admin/src/hooks/components/useSaveBar.tsx index 62fc4434..8634d76d 100644 --- a/frontend/src/hooks/admin/components/useSaveBar.tsx +++ b/frontend/apps/admin/src/hooks/components/useSaveBar.tsx @@ -1,5 +1,5 @@ import { useSetRecoilState, useRecoilState } from "recoil"; -import { saveBarState, SaveBarConfig } from "recoils/atoms/admin/saveBar"; +import { saveBarState, SaveBarConfig } from "recoils/atoms/saveBar"; const useSaveBar = () => { const setSaveBar = useSetRecoilState(saveBarState); diff --git a/frontend/src/hooks/admin/game/useCreateGamePairs.tsx b/frontend/apps/admin/src/hooks/game/useCreateGamePairs.tsx similarity index 83% rename from frontend/src/hooks/admin/game/useCreateGamePairs.tsx rename to frontend/apps/admin/src/hooks/game/useCreateGamePairs.tsx index b793408e..47281a5e 100644 --- a/frontend/src/hooks/admin/game/useCreateGamePairs.tsx +++ b/frontend/apps/admin/src/hooks/game/useCreateGamePairs.tsx @@ -1,10 +1,11 @@ import { QueryClient, useMutation } from "@tanstack/react-query"; import { AxiosError } from "axios"; -import axiosInstance from "api/admin/axiosInstance"; +import axiosInstance from "api/axiosInstance"; import { ACCEESS_TOKEN, OWNER, serverUrl } from "configs"; -import { GET_SEARCH_GAMES } from "constants/admin/queryKeys"; +import { GET_SEARCH_GAMES } from "constants/queryKeys"; import { Game } from "interfaces/game"; +import { getCookie } from "hooks/auth/useOwnerCookie"; export interface IRequestBody { group_id: number; @@ -15,7 +16,7 @@ export const createGamePairs = async ( requestBody: IRequestBody ): Promise> => { const token = localStorage.getItem(ACCEESS_TOKEN); - const owner = localStorage.getItem(OWNER); + const owner = getCookie(OWNER); const result = await axiosInstance.post( `${serverUrl}/games/pairs`, diff --git a/frontend/src/hooks/admin/game/useDeleteGame.ts b/frontend/apps/admin/src/hooks/game/useDeleteGame.ts similarity index 76% rename from frontend/src/hooks/admin/game/useDeleteGame.ts rename to frontend/apps/admin/src/hooks/game/useDeleteGame.ts index e65a15fd..1ea9f9a2 100644 --- a/frontend/src/hooks/admin/game/useDeleteGame.ts +++ b/frontend/apps/admin/src/hooks/game/useDeleteGame.ts @@ -1,6 +1,6 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { deleteGame } from "api/admin/game/deleteGame"; -import { GET_SEARCH_GAMES } from "constants/admin/queryKeys"; +import { deleteGame } from "api/game/deleteGame"; +import { GET_SEARCH_GAMES } from "constants/queryKeys"; const useDeleteGame = () => { const queryClient = useQueryClient(); diff --git a/frontend/src/hooks/admin/game/useSearchGames.ts b/frontend/apps/admin/src/hooks/game/useSearchGames.ts similarity index 83% rename from frontend/src/hooks/admin/game/useSearchGames.ts rename to frontend/apps/admin/src/hooks/game/useSearchGames.ts index e8ae17db..13a41910 100644 --- a/frontend/src/hooks/admin/game/useSearchGames.ts +++ b/frontend/apps/admin/src/hooks/game/useSearchGames.ts @@ -1,7 +1,7 @@ import { useState } from "react"; import { useQuery } from "@tanstack/react-query"; -import { GET_SEARCH_GAMES } from "constants/admin/queryKeys"; -import { getSearchGames } from "api/admin/game/getSearchGames"; +import { GET_SEARCH_GAMES } from "constants/queryKeys"; +import { getSearchGames } from "api/game/getSearchGames"; import { Game } from "interfaces/game"; const useSearchGames = (group_id: number) => { diff --git a/frontend/src/hooks/admin/group/useAddUsersToGroup.tsx b/frontend/apps/admin/src/hooks/group/useAddUsersToGroup.tsx similarity index 85% rename from frontend/src/hooks/admin/group/useAddUsersToGroup.tsx rename to frontend/apps/admin/src/hooks/group/useAddUsersToGroup.tsx index efcc3f9d..470afd9d 100644 --- a/frontend/src/hooks/admin/group/useAddUsersToGroup.tsx +++ b/frontend/apps/admin/src/hooks/group/useAddUsersToGroup.tsx @@ -3,12 +3,9 @@ import { addUsersToGroup, AddUsersToGroupInput, AddUsersToGroupOutput, -} from "api/admin/group/addUsersToGroup"; +} from "api/group/addUsersToGroup"; import { AxiosError } from "axios"; -import { - GET_SERACH_GROUPS, - GET_USERS_NOT_IN_GROUP, -} from "constants/admin/queryKeys"; +import { GET_SERACH_GROUPS, GET_USERS_NOT_IN_GROUP } from "constants/queryKeys"; interface AddUsersToGroupVariable { group_id: number; diff --git a/frontend/src/hooks/admin/group/useCreateGroup.tsx b/frontend/apps/admin/src/hooks/group/useCreateGroup.tsx similarity index 82% rename from frontend/src/hooks/admin/group/useCreateGroup.tsx rename to frontend/apps/admin/src/hooks/group/useCreateGroup.tsx index 6392cc1a..7e882fae 100644 --- a/frontend/src/hooks/admin/group/useCreateGroup.tsx +++ b/frontend/apps/admin/src/hooks/group/useCreateGroup.tsx @@ -1,8 +1,9 @@ import { QueryClient, useMutation } from "@tanstack/react-query"; -import { GET_SERACH_GROUPS } from "constants/admin/queryKeys"; +import { GET_SERACH_GROUPS } from "constants/queryKeys"; import { Group } from "interfaces/group"; import { ACCEESS_TOKEN, OWNER, serverUrl } from "configs"; -import axiosInstance from "api/admin/axiosInstance"; +import axiosInstance from "api/axiosInstance"; +import { getCookie } from "hooks/auth/useOwnerCookie"; export interface CreateGroupInput { channel_id: number; @@ -13,7 +14,7 @@ export const createGroup = async ( requestBody: CreateGroupInput ): Promise => { const token = localStorage.getItem(ACCEESS_TOKEN); - const owner = localStorage.getItem(OWNER); + const owner = getCookie(OWNER); const response = await axiosInstance.post( `${serverUrl}/groups`, diff --git a/frontend/src/hooks/admin/group/useDeleteGroup.tsx b/frontend/apps/admin/src/hooks/group/useDeleteGroup.tsx similarity index 75% rename from frontend/src/hooks/admin/group/useDeleteGroup.tsx rename to frontend/apps/admin/src/hooks/group/useDeleteGroup.tsx index 74362deb..4f51029a 100644 --- a/frontend/src/hooks/admin/group/useDeleteGroup.tsx +++ b/frontend/apps/admin/src/hooks/group/useDeleteGroup.tsx @@ -1,6 +1,6 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { deleteGroup } from "api/admin/group/deleteGroup"; -import { GET_CHANNELS } from "constants/admin/queryKeys"; +import { deleteGroup } from "api/group/deleteGroup"; +import { GET_CHANNELS } from "constants/queryKeys"; const useDeleteGroup = () => { const queryClient = useQueryClient(); diff --git a/frontend/src/hooks/admin/group/useDeleteUserToGroup.tsx b/frontend/apps/admin/src/hooks/group/useDeleteUserToGroup.tsx similarity index 81% rename from frontend/src/hooks/admin/group/useDeleteUserToGroup.tsx rename to frontend/apps/admin/src/hooks/group/useDeleteUserToGroup.tsx index 5e54e61c..180e5d9a 100644 --- a/frontend/src/hooks/admin/group/useDeleteUserToGroup.tsx +++ b/frontend/apps/admin/src/hooks/group/useDeleteUserToGroup.tsx @@ -2,8 +2,8 @@ import React from "react"; import { Axios, AxiosError } from "axios"; import { QueryClient, useMutation } from "@tanstack/react-query"; -import { deleteUserToGroup } from "api/admin/group/deleteUsersToGroup"; -import { GET_SERACH_GROUPS } from "constants/admin/queryKeys"; +import { deleteUserToGroup } from "api/group/deleteUsersToGroup"; +import { GET_SERACH_GROUPS } from "constants/queryKeys"; interface DeleteUsersToGroupVariable { group_id: number; diff --git a/frontend/src/hooks/admin/group/useEditGroup.tsx b/frontend/apps/admin/src/hooks/group/useEditGroup.tsx similarity index 85% rename from frontend/src/hooks/admin/group/useEditGroup.tsx rename to frontend/apps/admin/src/hooks/group/useEditGroup.tsx index 2d8186eb..67dba3b3 100644 --- a/frontend/src/hooks/admin/group/useEditGroup.tsx +++ b/frontend/apps/admin/src/hooks/group/useEditGroup.tsx @@ -3,9 +3,9 @@ import { editGroup, EditGroupOutput, EditGroupInput, -} from "api/admin/group/editGroup"; +} from "api/group/editGroup"; -import { GET_CHANNELS } from "constants/admin/queryKeys"; +import { GET_CHANNELS } from "constants/queryKeys"; interface EditChannelVariables { group_id: number; diff --git a/frontend/src/hooks/admin/group/useEditUserToGroup.tsx b/frontend/apps/admin/src/hooks/group/useEditUserToGroup.tsx similarity index 80% rename from frontend/src/hooks/admin/group/useEditUserToGroup.tsx rename to frontend/apps/admin/src/hooks/group/useEditUserToGroup.tsx index 12eda68f..51da85aa 100644 --- a/frontend/src/hooks/admin/group/useEditUserToGroup.tsx +++ b/frontend/apps/admin/src/hooks/group/useEditUserToGroup.tsx @@ -1,13 +1,13 @@ import { AxiosError } from "axios"; import { QueryClient, useMutation } from "@tanstack/react-query"; -import { deleteUserToGroup } from "api/admin/group/deleteUsersToGroup"; -import { GET_SERACH_GROUPS } from "constants/admin/queryKeys"; +import { deleteUserToGroup } from "api/group/deleteUsersToGroup"; +import { GET_SERACH_GROUPS } from "constants/queryKeys"; import editGroupToUser, { EditGroupToUserOutput, EditGroupToUserRequestBody, -} from "api/admin/group/editUserToGroup"; +} from "api/group/editUserToGroup"; interface EditUserToGroupVariable { group_id: number; diff --git a/frontend/src/hooks/admin/group/useGetGroupById.tsx b/frontend/apps/admin/src/hooks/group/useGetGroupById.tsx similarity index 76% rename from frontend/src/hooks/admin/group/useGetGroupById.tsx rename to frontend/apps/admin/src/hooks/group/useGetGroupById.tsx index f3264af5..c82f5bd4 100644 --- a/frontend/src/hooks/admin/group/useGetGroupById.tsx +++ b/frontend/apps/admin/src/hooks/group/useGetGroupById.tsx @@ -1,7 +1,7 @@ import { useQuery } from "@tanstack/react-query"; -import { GET_GROUP_BY_ID } from "constants/admin/queryKeys"; +import { GET_GROUP_BY_ID } from "constants/queryKeys"; import { Channel } from "interfaces/channels"; -import { getGroupById } from "api/admin/group/getGroupById"; +import { getGroupById } from "api/group/getGroupById"; const useGetGroupById = (group_id: number) => { const { data, isLoading, error, isSuccess } = useQuery({ diff --git a/frontend/src/hooks/admin/group/useGetUserGroups.tsx b/frontend/apps/admin/src/hooks/group/useGetUserGroups.tsx similarity index 78% rename from frontend/src/hooks/admin/group/useGetUserGroups.tsx rename to frontend/apps/admin/src/hooks/group/useGetUserGroups.tsx index c88c13a3..26159e94 100644 --- a/frontend/src/hooks/admin/group/useGetUserGroups.tsx +++ b/frontend/apps/admin/src/hooks/group/useGetUserGroups.tsx @@ -1,6 +1,6 @@ import { useQuery } from "@tanstack/react-query"; -import { GET_USER_GROUPS } from "constants/admin/queryKeys"; -import { getUserGroups } from "api/admin/group/getUserGroup"; +import { GET_USER_GROUPS } from "constants/queryKeys"; +import { getUserGroups } from "api/group/getUserGroup"; import { Group } from "interfaces/group"; const useGetUserGroups = (user_id: number) => { diff --git a/frontend/src/hooks/admin/group/useSearchGroups.tsx b/frontend/apps/admin/src/hooks/group/useSearchGroups.tsx similarity index 84% rename from frontend/src/hooks/admin/group/useSearchGroups.tsx rename to frontend/apps/admin/src/hooks/group/useSearchGroups.tsx index d3315879..9e5837bd 100644 --- a/frontend/src/hooks/admin/group/useSearchGroups.tsx +++ b/frontend/apps/admin/src/hooks/group/useSearchGroups.tsx @@ -1,8 +1,8 @@ import React, { useState } from "react"; import { useQuery } from "@tanstack/react-query"; -import { getSearchGroups } from "api/admin/group/getSearchGroups"; +import { getSearchGroups } from "api/group/getSearchGroups"; import { Group } from "interfaces/group"; -import { GET_SERACH_GROUPS } from "constants/admin/queryKeys"; +import { GET_SERACH_GROUPS } from "constants/queryKeys"; const useSearchGroups = (channel_id: number) => { const [enabled, setEnabled] = useState(false); // Control the query execution manually diff --git a/frontend/src/hooks/admin/questionCard/card/useCreateQuestionCard.tsx b/frontend/apps/admin/src/hooks/questionCard/card/useCreateQuestionCard.tsx similarity index 87% rename from frontend/src/hooks/admin/questionCard/card/useCreateQuestionCard.tsx rename to frontend/apps/admin/src/hooks/questionCard/card/useCreateQuestionCard.tsx index 949cfd63..25f35577 100644 --- a/frontend/src/hooks/admin/questionCard/card/useCreateQuestionCard.tsx +++ b/frontend/apps/admin/src/hooks/questionCard/card/useCreateQuestionCard.tsx @@ -3,7 +3,7 @@ import { useMutation } from "@tanstack/react-query"; import { createQuestionCard, RequestBody, -} from "api/admin/questionCard/card/createQuestionCard"; +} from "api/questionCard/card/createQuestionCard"; import { QuestionCard } from "interfaces/questionCardCategory"; const useCreateQuestionCard = () => { diff --git a/frontend/src/hooks/admin/questionCard/card/useDeleteQuestionCard.tsx b/frontend/apps/admin/src/hooks/questionCard/card/useDeleteQuestionCard.tsx similarity index 75% rename from frontend/src/hooks/admin/questionCard/card/useDeleteQuestionCard.tsx rename to frontend/apps/admin/src/hooks/questionCard/card/useDeleteQuestionCard.tsx index 5660deff..b703baa4 100644 --- a/frontend/src/hooks/admin/questionCard/card/useDeleteQuestionCard.tsx +++ b/frontend/apps/admin/src/hooks/questionCard/card/useDeleteQuestionCard.tsx @@ -1,6 +1,6 @@ import { AxiosError } from "axios"; import { useMutation } from "@tanstack/react-query"; -import { deleteQuestionCard } from "api/admin/questionCard/card/deleteQuestionCard"; +import { deleteQuestionCard } from "api/questionCard/card/deleteQuestionCard"; const useDeleteQuestionCard = () => { return useMutation({ diff --git a/frontend/src/hooks/admin/questionCard/card/useUpdateQuestionCard.tsx b/frontend/apps/admin/src/hooks/questionCard/card/useUpdateQuestionCard.tsx similarity index 89% rename from frontend/src/hooks/admin/questionCard/card/useUpdateQuestionCard.tsx rename to frontend/apps/admin/src/hooks/questionCard/card/useUpdateQuestionCard.tsx index 8be0a31f..c15b7c50 100644 --- a/frontend/src/hooks/admin/questionCard/card/useUpdateQuestionCard.tsx +++ b/frontend/apps/admin/src/hooks/questionCard/card/useUpdateQuestionCard.tsx @@ -3,7 +3,7 @@ import { useMutation } from "@tanstack/react-query"; import { updateQuestionCard, RequestBody, -} from "api/admin/questionCard/card/updateQuestionCard"; +} from "api/questionCard/card/updateQuestionCard"; import { QuestionCard } from "interfaces/questionCardCategory"; interface Variables { diff --git a/frontend/src/hooks/admin/questionCard/card/useUploadCardImage.tsx b/frontend/apps/admin/src/hooks/questionCard/card/useUploadCardImage.tsx similarity index 84% rename from frontend/src/hooks/admin/questionCard/card/useUploadCardImage.tsx rename to frontend/apps/admin/src/hooks/questionCard/card/useUploadCardImage.tsx index a9972802..a76b90da 100644 --- a/frontend/src/hooks/admin/questionCard/card/useUploadCardImage.tsx +++ b/frontend/apps/admin/src/hooks/questionCard/card/useUploadCardImage.tsx @@ -1,7 +1,7 @@ import { AxiosError } from "axios"; import { useMutation } from "@tanstack/react-query"; -import { uploadCardImage } from "api/admin/questionCard/card/uploadCardImage"; +import { uploadCardImage } from "api/questionCard/card/uploadCardImage"; import { QuestionCard } from "interfaces/questionCardCategory"; interface Variables { diff --git a/frontend/src/hooks/admin/questionCard/category/useCreateCategory.tsx b/frontend/apps/admin/src/hooks/questionCard/category/useCreateCategory.tsx similarity index 86% rename from frontend/src/hooks/admin/questionCard/category/useCreateCategory.tsx rename to frontend/apps/admin/src/hooks/questionCard/category/useCreateCategory.tsx index ca8596d1..085f30e5 100644 --- a/frontend/src/hooks/admin/questionCard/category/useCreateCategory.tsx +++ b/frontend/apps/admin/src/hooks/questionCard/category/useCreateCategory.tsx @@ -5,7 +5,7 @@ import { createCategory, CreateCategoryOutput, ReqeustBody, -} from "api/admin/questionCard/category/createCategory"; +} from "api/questionCard/category/createCategory"; const useCreateCategory = () => { return useMutation({ diff --git a/frontend/src/hooks/admin/questionCard/category/useDeleteCategory.tsx b/frontend/apps/admin/src/hooks/questionCard/category/useDeleteCategory.tsx similarity index 79% rename from frontend/src/hooks/admin/questionCard/category/useDeleteCategory.tsx rename to frontend/apps/admin/src/hooks/questionCard/category/useDeleteCategory.tsx index a078ef6e..fbcc9ce2 100644 --- a/frontend/src/hooks/admin/questionCard/category/useDeleteCategory.tsx +++ b/frontend/apps/admin/src/hooks/questionCard/category/useDeleteCategory.tsx @@ -1,7 +1,7 @@ import { AxiosError } from "axios"; import { useMutation } from "@tanstack/react-query"; -import { deleteCategory } from "api/admin/questionCard/category/deleteCategory"; +import { deleteCategory } from "api/questionCard/category/deleteCategory"; const useDeleteCategory = () => { return useMutation({ diff --git a/frontend/src/hooks/admin/questionCard/category/useUpdateCategory.tsx b/frontend/apps/admin/src/hooks/questionCard/category/useUpdateCategory.tsx similarity index 90% rename from frontend/src/hooks/admin/questionCard/category/useUpdateCategory.tsx rename to frontend/apps/admin/src/hooks/questionCard/category/useUpdateCategory.tsx index 14480fdf..580b8dd6 100644 --- a/frontend/src/hooks/admin/questionCard/category/useUpdateCategory.tsx +++ b/frontend/apps/admin/src/hooks/questionCard/category/useUpdateCategory.tsx @@ -3,7 +3,7 @@ import { useMutation } from "@tanstack/react-query"; import { updateCategory, UpdateCategoryOutput, -} from "api/admin/questionCard/category/updateCategory"; +} from "api/questionCard/category/updateCategory"; interface Variables { question_card_category_id: number; diff --git a/frontend/src/hooks/admin/questionCard/category/useUploadCoverImage.tsx b/frontend/apps/admin/src/hooks/questionCard/category/useUploadCoverImage.tsx similarity index 74% rename from frontend/src/hooks/admin/questionCard/category/useUploadCoverImage.tsx rename to frontend/apps/admin/src/hooks/questionCard/category/useUploadCoverImage.tsx index 1b5bcd31..96615925 100644 --- a/frontend/src/hooks/admin/questionCard/category/useUploadCoverImage.tsx +++ b/frontend/apps/admin/src/hooks/questionCard/category/useUploadCoverImage.tsx @@ -1,8 +1,8 @@ import { AxiosError } from "axios"; import { useMutation } from "@tanstack/react-query"; -import { uploadCoverImage } from "api/admin/questionCard/category/uploadCoverImage"; -import { UploadCoverImageOutput } from "api/admin/questionCard/category/uploadCoverImage"; +import { uploadCoverImage } from "api/questionCard/category/uploadCoverImage"; +import { UploadCoverImageOutput } from "api/questionCard/category/uploadCoverImage"; interface Variables { file: File; diff --git a/frontend/src/hooks/admin/submission/context/useSubmissionContext.ts b/frontend/apps/admin/src/hooks/submission/context/useSubmissionContext.ts similarity index 92% rename from frontend/src/hooks/admin/submission/context/useSubmissionContext.ts rename to frontend/apps/admin/src/hooks/submission/context/useSubmissionContext.ts index a4a5eee6..de844b1f 100644 --- a/frontend/src/hooks/admin/submission/context/useSubmissionContext.ts +++ b/frontend/apps/admin/src/hooks/submission/context/useSubmissionContext.ts @@ -1,7 +1,7 @@ import { createContext, useContext } from "react"; import { HostRegist } from "interfaces/hostRegist"; import { QueryObserverResult, RefetchOptions } from "@tanstack/react-query"; -import { GetHostRegistsOutput } from "api/admin/hostRegist/useGetHostRegistAdmin"; +import { GetHostRegistsOutput } from "api/hostRegist/useGetHostRegistAdmin"; import { AxiosError } from "axios"; interface SubmissionContextType { diff --git a/frontend/src/hooks/admin/users/useAddGroup.tsx b/frontend/apps/admin/src/hooks/users/useAddGroup.tsx similarity index 76% rename from frontend/src/hooks/admin/users/useAddGroup.tsx rename to frontend/apps/admin/src/hooks/users/useAddGroup.tsx index 711d6de8..5fa87352 100644 --- a/frontend/src/hooks/admin/users/useAddGroup.tsx +++ b/frontend/apps/admin/src/hooks/users/useAddGroup.tsx @@ -1,11 +1,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { GET_CHANNELS } from "constants/admin/queryKeys"; -import { - addGroup, - AddGroupInput, - AddGroupOutput, -} from "api/admin/users/addGroup"; +import { GET_CHANNELS } from "constants/queryKeys"; +import { addGroup, AddGroupInput, AddGroupOutput } from "api/users/addGroup"; interface AddGroupVariables { user_id: number; diff --git a/frontend/src/hooks/admin/users/useBrandTable.tsx b/frontend/apps/admin/src/hooks/users/useBrandTable.tsx similarity index 100% rename from frontend/src/hooks/admin/users/useBrandTable.tsx rename to frontend/apps/admin/src/hooks/users/useBrandTable.tsx diff --git a/frontend/src/hooks/admin/users/useDeleteChannelUser.tsx b/frontend/apps/admin/src/hooks/users/useDeleteChannelUser.tsx similarity index 83% rename from frontend/src/hooks/admin/users/useDeleteChannelUser.tsx rename to frontend/apps/admin/src/hooks/users/useDeleteChannelUser.tsx index 26259096..6251a638 100644 --- a/frontend/src/hooks/admin/users/useDeleteChannelUser.tsx +++ b/frontend/apps/admin/src/hooks/users/useDeleteChannelUser.tsx @@ -1,6 +1,6 @@ import { AxiosError } from "axios"; import { useMutation } from "@tanstack/react-query"; -import { deleteChannelUser } from "api/admin/users/deleteChannelUser"; +import { deleteChannelUser } from "api/users/deleteChannelUser"; const useDeleteChannelUser = () => { return useMutation( diff --git a/frontend/src/hooks/admin/users/useDeleteUser.tsx b/frontend/apps/admin/src/hooks/users/useDeleteUser.tsx similarity index 81% rename from frontend/src/hooks/admin/users/useDeleteUser.tsx rename to frontend/apps/admin/src/hooks/users/useDeleteUser.tsx index ebeb3a93..e639f243 100644 --- a/frontend/src/hooks/admin/users/useDeleteUser.tsx +++ b/frontend/apps/admin/src/hooks/users/useDeleteUser.tsx @@ -1,5 +1,5 @@ import { useMutation } from "@tanstack/react-query"; -import { deleteUser } from "api/admin/users/deleteUser"; +import { deleteUser } from "api/users/deleteUser"; import { AxiosError } from "axios"; const useDeleteUser = () => { diff --git a/frontend/src/hooks/admin/users/useEditProfile.tsx b/frontend/apps/admin/src/hooks/users/useEditProfile.tsx similarity index 84% rename from frontend/src/hooks/admin/users/useEditProfile.tsx rename to frontend/apps/admin/src/hooks/users/useEditProfile.tsx index 77237927..ec9b474e 100644 --- a/frontend/src/hooks/admin/users/useEditProfile.tsx +++ b/frontend/apps/admin/src/hooks/users/useEditProfile.tsx @@ -1,5 +1,5 @@ import { useMutation } from "@tanstack/react-query"; -import { editUser, EditProfileInput } from "api/admin/users/editProfile"; +import { editUser, EditProfileInput } from "api/users/editProfile"; import { User } from "interfaces/user"; import { AxiosError } from "axios"; diff --git a/frontend/src/hooks/admin/users/useGetUserById.tsx b/frontend/apps/admin/src/hooks/users/useGetUserById.tsx similarity index 80% rename from frontend/src/hooks/admin/users/useGetUserById.tsx rename to frontend/apps/admin/src/hooks/users/useGetUserById.tsx index 1e697cf4..e07b350d 100644 --- a/frontend/src/hooks/admin/users/useGetUserById.tsx +++ b/frontend/apps/admin/src/hooks/users/useGetUserById.tsx @@ -1,8 +1,8 @@ import { useParams } from "react-router-dom"; import { useQuery } from "@tanstack/react-query"; -import { getUserById } from "api/admin/users/getUserById"; -import { GET_USER_BY_ID } from "constants/admin/queryKeys"; +import { getUserById } from "api/users/getUserById"; +import { GET_USER_BY_ID } from "constants/queryKeys"; import { User } from "interfaces/user"; const useGetUserById = () => { diff --git a/frontend/src/hooks/admin/users/useGetUsers.tsx b/frontend/apps/admin/src/hooks/users/useGetUsers.tsx similarity index 85% rename from frontend/src/hooks/admin/users/useGetUsers.tsx rename to frontend/apps/admin/src/hooks/users/useGetUsers.tsx index 06e7ee04..1c9cc559 100644 --- a/frontend/src/hooks/admin/users/useGetUsers.tsx +++ b/frontend/apps/admin/src/hooks/users/useGetUsers.tsx @@ -1,10 +1,11 @@ import { useQuery } from "@tanstack/react-query"; import { AxiosError } from "axios"; -import axiosInstance from "api/admin/axiosInstance"; -import { GET_USERS } from "constants/admin/queryKeys"; +import axiosInstance from "api/axiosInstance"; +import { GET_USERS } from "constants/queryKeys"; import { ACCEESS_TOKEN, OWNER, serverUrl } from "configs"; import { User } from "interfaces/user"; +import { getCookie } from "hooks/auth/useOwnerCookie"; export interface GetUsersOutput { users: Array; @@ -22,7 +23,7 @@ export const getUsers = async ( params: GetUserParams ): Promise => { const token = localStorage.getItem(ACCEESS_TOKEN); - const owner = localStorage.getItem(OWNER); + const owner = getCookie(OWNER); const response = await axiosInstance.get(`${serverUrl}/users`, { headers: { diff --git a/frontend/src/hooks/admin/users/useGetUsersNotInGroup.tsx b/frontend/apps/admin/src/hooks/users/useGetUsersNotInGroup.tsx similarity index 88% rename from frontend/src/hooks/admin/users/useGetUsersNotInGroup.tsx rename to frontend/apps/admin/src/hooks/users/useGetUsersNotInGroup.tsx index 6f8c5881..e76739d4 100644 --- a/frontend/src/hooks/admin/users/useGetUsersNotInGroup.tsx +++ b/frontend/apps/admin/src/hooks/users/useGetUsersNotInGroup.tsx @@ -1,9 +1,9 @@ import { useParams } from "react-router-dom"; import { useQuery } from "@tanstack/react-query"; -import { GET_USERS_NOT_IN_GROUP } from "constants/admin/queryKeys"; +import { GET_USERS_NOT_IN_GROUP } from "constants/queryKeys"; import { User } from "interfaces/user"; -import { getUsersNotInGroup } from "api/admin/users/getUsersNotInGroup"; +import { getUsersNotInGroup } from "api/users/getUsersNotInGroup"; import { AxiosError } from "axios"; const useGetUsersNotInGroup = () => { diff --git a/frontend/src/hooks/admin/users/useJoinChannel.tsx b/frontend/apps/admin/src/hooks/users/useJoinChannel.tsx similarity index 78% rename from frontend/src/hooks/admin/users/useJoinChannel.tsx rename to frontend/apps/admin/src/hooks/users/useJoinChannel.tsx index 8177105b..5947010a 100644 --- a/frontend/src/hooks/admin/users/useJoinChannel.tsx +++ b/frontend/apps/admin/src/hooks/users/useJoinChannel.tsx @@ -1,7 +1,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { joinChannel, JoinChannelInput } from "api/admin/users/joinChannel"; +import { joinChannel, JoinChannelInput } from "api/users/joinChannel"; import { AxiosError } from "axios"; -import { GET_USERS } from "constants/admin/queryKeys"; +import { GET_USERS } from "constants/queryKeys"; const useJoinChannel = () => { const queryClient = useQueryClient(); // โœ… QueryClient ์ธ์Šคํ„ด์Šค ๊ฐ€์ ธ์˜ค๊ธฐ diff --git a/frontend/src/hooks/admin/users/useUserTable.tsx b/frontend/apps/admin/src/hooks/users/useUserTable.tsx similarity index 100% rename from frontend/src/hooks/admin/users/useUserTable.tsx rename to frontend/apps/admin/src/hooks/users/useUserTable.tsx diff --git a/frontend/src/index.css b/frontend/apps/admin/src/index.css similarity index 100% rename from frontend/src/index.css rename to frontend/apps/admin/src/index.css diff --git a/frontend/src/index.tsx b/frontend/apps/admin/src/index.tsx similarity index 100% rename from frontend/src/index.tsx rename to frontend/apps/admin/src/index.tsx diff --git a/frontend/src/interfaces/brand/index.ts b/frontend/apps/admin/src/interfaces/brand/index.ts similarity index 89% rename from frontend/src/interfaces/brand/index.ts rename to frontend/apps/admin/src/interfaces/brand/index.ts index c0550920..daea52f9 100644 --- a/frontend/src/interfaces/brand/index.ts +++ b/frontend/apps/admin/src/interfaces/brand/index.ts @@ -1,5 +1,3 @@ -// ๋ธŒ๋žœ๋“œ ์ƒํƒœ Enum ๋ฐ ํ•œ๊ธ€ ๋ผ๋ฒจ ๋งคํ•‘ - export enum BrandState { ONGOING = "ONGOING", FINISH = "FINISH", diff --git a/frontend/src/interfaces/brand/survey/index.ts b/frontend/apps/admin/src/interfaces/brand/survey/index.ts similarity index 100% rename from frontend/src/interfaces/brand/survey/index.ts rename to frontend/apps/admin/src/interfaces/brand/survey/index.ts diff --git a/frontend/src/interfaces/channels/index.ts b/frontend/apps/admin/src/interfaces/channels/index.ts similarity index 100% rename from frontend/src/interfaces/channels/index.ts rename to frontend/apps/admin/src/interfaces/channels/index.ts diff --git a/frontend/src/interfaces/game/index.ts b/frontend/apps/admin/src/interfaces/game/index.ts similarity index 100% rename from frontend/src/interfaces/game/index.ts rename to frontend/apps/admin/src/interfaces/game/index.ts diff --git a/frontend/src/interfaces/group/index.ts b/frontend/apps/admin/src/interfaces/group/index.ts similarity index 100% rename from frontend/src/interfaces/group/index.ts rename to frontend/apps/admin/src/interfaces/group/index.ts diff --git a/frontend/src/interfaces/hostRegist/index.ts b/frontend/apps/admin/src/interfaces/hostRegist/index.ts similarity index 100% rename from frontend/src/interfaces/hostRegist/index.ts rename to frontend/apps/admin/src/interfaces/hostRegist/index.ts diff --git a/frontend/src/interfaces/index.ts b/frontend/apps/admin/src/interfaces/index.ts similarity index 100% rename from frontend/src/interfaces/index.ts rename to frontend/apps/admin/src/interfaces/index.ts diff --git a/frontend/src/interfaces/landing/index.ts b/frontend/apps/admin/src/interfaces/landing/index.ts similarity index 100% rename from frontend/src/interfaces/landing/index.ts rename to frontend/apps/admin/src/interfaces/landing/index.ts diff --git a/frontend/src/interfaces/questionCardCategory/index.ts b/frontend/apps/admin/src/interfaces/questionCardCategory/index.ts similarity index 100% rename from frontend/src/interfaces/questionCardCategory/index.ts rename to frontend/apps/admin/src/interfaces/questionCardCategory/index.ts diff --git a/frontend/src/interfaces/user/index.ts b/frontend/apps/admin/src/interfaces/user/index.ts similarity index 100% rename from frontend/src/interfaces/user/index.ts rename to frontend/apps/admin/src/interfaces/user/index.ts diff --git a/frontend/src/logo.svg b/frontend/apps/admin/src/logo.svg similarity index 100% rename from frontend/src/logo.svg rename to frontend/apps/admin/src/logo.svg diff --git a/frontend/src/mocks/browser.ts b/frontend/apps/admin/src/mocks/browser.ts similarity index 100% rename from frontend/src/mocks/browser.ts rename to frontend/apps/admin/src/mocks/browser.ts diff --git a/frontend/src/mocks/data/admin/brand/brandReviewList.ts b/frontend/apps/admin/src/mocks/data/admin/brand/brandReviewList.ts similarity index 100% rename from frontend/src/mocks/data/admin/brand/brandReviewList.ts rename to frontend/apps/admin/src/mocks/data/admin/brand/brandReviewList.ts diff --git a/frontend/src/mocks/data/getChannels.ts b/frontend/apps/admin/src/mocks/data/getChannels.ts similarity index 100% rename from frontend/src/mocks/data/getChannels.ts rename to frontend/apps/admin/src/mocks/data/getChannels.ts diff --git a/frontend/src/mocks/data/landing/registerList.ts b/frontend/apps/admin/src/mocks/data/landing/registerList.ts similarity index 100% rename from frontend/src/mocks/data/landing/registerList.ts rename to frontend/apps/admin/src/mocks/data/landing/registerList.ts diff --git a/frontend/src/mocks/handlers.ts b/frontend/apps/admin/src/mocks/handlers.ts similarity index 100% rename from frontend/src/mocks/handlers.ts rename to frontend/apps/admin/src/mocks/handlers.ts diff --git a/frontend/src/mocks/handlers/admin/brand/review/getBrandReviews.ts b/frontend/apps/admin/src/mocks/handlers/admin/brand/review/getBrandReviews.ts similarity index 100% rename from frontend/src/mocks/handlers/admin/brand/review/getBrandReviews.ts rename to frontend/apps/admin/src/mocks/handlers/admin/brand/review/getBrandReviews.ts diff --git a/frontend/src/mocks/handlers/admin/landing/deleteGalleryImage.ts b/frontend/apps/admin/src/mocks/handlers/admin/landing/deleteGalleryImage.ts similarity index 100% rename from frontend/src/mocks/handlers/admin/landing/deleteGalleryImage.ts rename to frontend/apps/admin/src/mocks/handlers/admin/landing/deleteGalleryImage.ts diff --git a/frontend/src/mocks/handlers/admin/landing/deleteMainImage.ts b/frontend/apps/admin/src/mocks/handlers/admin/landing/deleteMainImage.ts similarity index 100% rename from frontend/src/mocks/handlers/admin/landing/deleteMainImage.ts rename to frontend/apps/admin/src/mocks/handlers/admin/landing/deleteMainImage.ts diff --git a/frontend/src/mocks/handlers/admin/landing/getGalleryImage.ts b/frontend/apps/admin/src/mocks/handlers/admin/landing/getGalleryImage.ts similarity index 100% rename from frontend/src/mocks/handlers/admin/landing/getGalleryImage.ts rename to frontend/apps/admin/src/mocks/handlers/admin/landing/getGalleryImage.ts diff --git a/frontend/src/mocks/handlers/admin/landing/getMainImage.ts b/frontend/apps/admin/src/mocks/handlers/admin/landing/getMainImage.ts similarity index 100% rename from frontend/src/mocks/handlers/admin/landing/getMainImage.ts rename to frontend/apps/admin/src/mocks/handlers/admin/landing/getMainImage.ts diff --git a/frontend/src/mocks/handlers/admin/landing/uploadGalleryImage.ts b/frontend/apps/admin/src/mocks/handlers/admin/landing/uploadGalleryImage.ts similarity index 100% rename from frontend/src/mocks/handlers/admin/landing/uploadGalleryImage.ts rename to frontend/apps/admin/src/mocks/handlers/admin/landing/uploadGalleryImage.ts diff --git a/frontend/src/mocks/handlers/admin/landing/uploadMainImage.ts b/frontend/apps/admin/src/mocks/handlers/admin/landing/uploadMainImage.ts similarity index 100% rename from frontend/src/mocks/handlers/admin/landing/uploadMainImage.ts rename to frontend/apps/admin/src/mocks/handlers/admin/landing/uploadMainImage.ts diff --git a/frontend/src/mocks/handlers/admin/socialing/getChannels.ts b/frontend/apps/admin/src/mocks/handlers/admin/socialing/getChannels.ts similarity index 100% rename from frontend/src/mocks/handlers/admin/socialing/getChannels.ts rename to frontend/apps/admin/src/mocks/handlers/admin/socialing/getChannels.ts diff --git a/frontend/src/mocks/handlers/admin/socialing/updateRegister.ts b/frontend/apps/admin/src/mocks/handlers/admin/socialing/updateRegister.ts similarity index 100% rename from frontend/src/mocks/handlers/admin/socialing/updateRegister.ts rename to frontend/apps/admin/src/mocks/handlers/admin/socialing/updateRegister.ts diff --git a/frontend/src/mocks/handlers/admin/submission/createHostRegist.ts b/frontend/apps/admin/src/mocks/handlers/admin/submission/createHostRegist.ts similarity index 100% rename from frontend/src/mocks/handlers/admin/submission/createHostRegist.ts rename to frontend/apps/admin/src/mocks/handlers/admin/submission/createHostRegist.ts diff --git a/frontend/src/mocks/handlers/admin/submission/getHostRegistAdmin.ts b/frontend/apps/admin/src/mocks/handlers/admin/submission/getHostRegistAdmin.ts similarity index 100% rename from frontend/src/mocks/handlers/admin/submission/getHostRegistAdmin.ts rename to frontend/apps/admin/src/mocks/handlers/admin/submission/getHostRegistAdmin.ts diff --git a/frontend/src/mocks/handlers/admin/submission/getMyHostRegist.ts b/frontend/apps/admin/src/mocks/handlers/admin/submission/getMyHostRegist.ts similarity index 100% rename from frontend/src/mocks/handlers/admin/submission/getMyHostRegist.ts rename to frontend/apps/admin/src/mocks/handlers/admin/submission/getMyHostRegist.ts diff --git a/frontend/src/mocks/handlers/admin/submission/updateHostRegist.ts b/frontend/apps/admin/src/mocks/handlers/admin/submission/updateHostRegist.ts similarity index 100% rename from frontend/src/mocks/handlers/admin/submission/updateHostRegist.ts rename to frontend/apps/admin/src/mocks/handlers/admin/submission/updateHostRegist.ts diff --git a/frontend/src/mocks/handlers/auth/Login.ts b/frontend/apps/admin/src/mocks/handlers/auth/Login.ts similarity index 100% rename from frontend/src/mocks/handlers/auth/Login.ts rename to frontend/apps/admin/src/mocks/handlers/auth/Login.ts diff --git a/frontend/src/mocks/handlers/landing/getGalleryImages.ts b/frontend/apps/admin/src/mocks/handlers/landing/getGalleryImages.ts similarity index 100% rename from frontend/src/mocks/handlers/landing/getGalleryImages.ts rename to frontend/apps/admin/src/mocks/handlers/landing/getGalleryImages.ts diff --git a/frontend/src/mocks/handlers/landing/getMainImage.ts b/frontend/apps/admin/src/mocks/handlers/landing/getMainImage.ts similarity index 100% rename from frontend/src/mocks/handlers/landing/getMainImage.ts rename to frontend/apps/admin/src/mocks/handlers/landing/getMainImage.ts diff --git a/frontend/src/mocks/handlers/landing/getRegisterList.ts b/frontend/apps/admin/src/mocks/handlers/landing/getRegisterList.ts similarity index 100% rename from frontend/src/mocks/handlers/landing/getRegisterList.ts rename to frontend/apps/admin/src/mocks/handlers/landing/getRegisterList.ts diff --git a/frontend/src/mocks/handlers/landing/registForm.ts b/frontend/apps/admin/src/mocks/handlers/landing/registForm.ts similarity index 100% rename from frontend/src/mocks/handlers/landing/registForm.ts rename to frontend/apps/admin/src/mocks/handlers/landing/registForm.ts diff --git a/frontend/src/pages/admin/AdminLogin.tsx b/frontend/apps/admin/src/pages/Login.tsx similarity index 95% rename from frontend/src/pages/admin/AdminLogin.tsx rename to frontend/apps/admin/src/pages/Login.tsx index 7d471150..a29b231c 100644 --- a/frontend/src/pages/admin/AdminLogin.tsx +++ b/frontend/apps/admin/src/pages/Login.tsx @@ -4,11 +4,12 @@ import { useRecoilState } from "recoil"; import styled, { keyframes } from "styled-components"; import logo from "assets/images/logo.png"; -import { AdminPathnames, Pathnames } from "constants/admin/index"; +import { Pathnames } from "constants/index"; import useLogin from "hooks/auth/useLogin"; import userState from "recoils/atoms/auth/userState"; +import useOwnerCookie from "hooks/auth/useOwnerCookie"; -const AdminLogin = () => { +const Login = () => { const navigate = useNavigate(); const { mutate: login } = useLogin(); const [_, setUserData] = useRecoilState(userState); @@ -19,7 +20,7 @@ const AdminLogin = () => { const [errorMessage, setErrorMessage] = useState(""); const handleSignUpButtonClick = () => { - navigate(AdminPathnames.SignUp); + navigate(Pathnames.SignUp); }; const handleLogin = async (event: React.FormEvent) => { @@ -33,8 +34,9 @@ const AdminLogin = () => { { onSuccess: (data) => { const { user } = data; + setUserData(user); - navigate(Pathnames.AdminHome); + navigate(Pathnames.Home); }, onError: () => { setErrorMessage( @@ -95,7 +97,7 @@ const AdminLogin = () => { ); }; -export default AdminLogin; +export default Login; const fadeIn = keyframes` from { diff --git a/frontend/src/pages/admin/SignUpForm.tsx b/frontend/apps/admin/src/pages/SignUpForm.tsx similarity index 99% rename from frontend/src/pages/admin/SignUpForm.tsx rename to frontend/apps/admin/src/pages/SignUpForm.tsx index 70bea453..973ab3ea 100644 --- a/frontend/src/pages/admin/SignUpForm.tsx +++ b/frontend/apps/admin/src/pages/SignUpForm.tsx @@ -5,9 +5,9 @@ import { useNavigate } from "react-router-dom"; import useSignUp from "hooks/auth/useSignUp"; import userState from "recoils/atoms/auth/userState"; -import { Pathnames } from "constants/admin/index"; +import { Pathnames } from "constants/index"; import useSystemModal from "hooks/common/components/useSystemModal"; -import SystemModal from "components/consumer/common/SystemModal"; +import SystemModal from "components/common/SystemModal"; interface SignUpFormState { username: string; diff --git a/frontend/src/pages/admin/brand/form/EditBrand.tsx b/frontend/apps/admin/src/pages/brand/form/EditBrand.tsx similarity index 83% rename from frontend/src/pages/admin/brand/form/EditBrand.tsx rename to frontend/apps/admin/src/pages/brand/form/EditBrand.tsx index 33223b3c..5639ac91 100644 --- a/frontend/src/pages/admin/brand/form/EditBrand.tsx +++ b/frontend/apps/admin/src/pages/brand/form/EditBrand.tsx @@ -1,12 +1,13 @@ import { useEffect, useState } from "react"; import { useParams } from "react-router-dom"; -import { BrandFormContext } from "hooks/admin/brand/context/useBrandFormContext"; -import useGetBrandById from "hooks/admin/brand/useGetBrandById"; +import { BrandFormContext } from "hooks/brand/context/useBrandFormContext"; +import useGetBrandById from "hooks/brand/useGetBrandById"; import { Brand } from "interfaces/brand"; -import AdminLayout from "components/admin/common/AdminLayout"; -import EditBrandForm from "components/admin/brand/EditBrandForm"; +import AdminLayout from "components/common/AdminLayout"; +import EditBrandForm from "components/brand/EditBrandForm"; +import RequireAuth from "components/common/RequireAuth"; export type EditBrandInputType = Pick< Brand, @@ -77,4 +78,4 @@ const EditBrand = () => { ); }; -export default EditBrand; +export default RequireAuth(EditBrand); diff --git a/frontend/src/pages/admin/brand/index.tsx b/frontend/apps/admin/src/pages/brand/index.tsx similarity index 72% rename from frontend/src/pages/admin/brand/index.tsx rename to frontend/apps/admin/src/pages/brand/index.tsx index c678448e..6a4f7ea7 100644 --- a/frontend/src/pages/admin/brand/index.tsx +++ b/frontend/apps/admin/src/pages/brand/index.tsx @@ -2,14 +2,15 @@ import { useState } from "react"; import styled from "styled-components"; import { useSearchParams } from "react-router-dom"; -import { BrandTableContext } from "hooks/admin/users/useBrandTable"; -import useGetBrands from "hooks/admin/brand/useGetBrands"; +import { BrandTableContext } from "hooks/users/useBrandTable"; +import useGetBrands from "hooks/brand/useGetBrands"; import { BrandState } from "interfaces/brand"; -import AdminLayout from "components/admin/common/AdminLayout"; -import Controller from "components/admin/brand/Controller"; -import BrandTable from "components/admin/brand/BrandTable"; -import Pagination from "components/admin/common/Pagination"; +import AdminLayout from "components/common/AdminLayout"; +import Controller from "components/brand/Controller"; +import BrandTable from "components/brand/BrandTable"; +import Pagination from "components/common/Pagination"; +import RequireAuth from "components/common/RequireAuth"; const Brand = () => { const [searchParams] = useSearchParams(); @@ -53,4 +54,4 @@ const Brand = () => { const Container = styled.div``; -export default Brand; +export default RequireAuth(Brand); diff --git a/frontend/src/pages/admin/channel/form/ChannelForm.tsx b/frontend/apps/admin/src/pages/channel/form/ChannelForm.tsx similarity index 81% rename from frontend/src/pages/admin/channel/form/ChannelForm.tsx rename to frontend/apps/admin/src/pages/channel/form/ChannelForm.tsx index 81f74bbc..77ccedca 100644 --- a/frontend/src/pages/admin/channel/form/ChannelForm.tsx +++ b/frontend/apps/admin/src/pages/channel/form/ChannelForm.tsx @@ -3,16 +3,17 @@ import { useParams } from "react-router-dom"; import styled from "styled-components"; import { ChannelFeatureButton } from "constants/common"; -import useGetChannelById from "hooks/admin/channel/useGetChannelById"; -import { ChannelFormContext } from "hooks/admin/channel/context/useChannelFormContext"; +import useGetChannelById from "hooks/channel/useGetChannelById"; +import { ChannelFormContext } from "hooks/channel/context/useChannelFormContext"; -import AdminLayout from "components/admin/common/AdminLayout"; +import AdminLayout from "components/common/AdminLayout"; import SectionToggle, { ChannelSection, -} from "components/admin/channel/channelDetail/SectionToggle"; -import Application from "components/admin/channel/channelDetail/application"; -import Game from "components/admin/channel/channelDetail/game"; -import Socialing from "components/admin/channel/channelDetail/socialing"; +} from "components/channel/channelDetail/SectionToggle"; +import Application from "components/channel/channelDetail/application"; +import Game from "components/channel/channelDetail/game"; +import Socialing from "components/channel/channelDetail/socialing"; +import RequireAuth from "components/common/RequireAuth"; export interface FormData { title: string; @@ -98,4 +99,4 @@ const MainContent = styled.main` box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); `; -export default ChannelForm; +export default RequireAuth(ChannelForm); diff --git a/frontend/src/pages/admin/channel/index.tsx b/frontend/apps/admin/src/pages/channel/index.tsx similarity index 72% rename from frontend/src/pages/admin/channel/index.tsx rename to frontend/apps/admin/src/pages/channel/index.tsx index bd4d18aa..49f73d17 100644 --- a/frontend/src/pages/admin/channel/index.tsx +++ b/frontend/apps/admin/src/pages/channel/index.tsx @@ -2,14 +2,15 @@ import { useState } from "react"; import { useSearchParams } from "react-router-dom"; import styled from "styled-components"; -import useGetChannels from "api/admin/channel/hooks/useGetChannels"; -import { ChannelTableContext } from "hooks/admin/channel/context/useChannelTableContext"; +import useGetChannels from "api/channel/hooks/useGetChannels"; +import { ChannelTableContext } from "hooks/channel/context/useChannelTableContext"; import { ChannelState } from "interfaces/channels"; -import AdminLayout from "components/admin/common/AdminLayout"; -import Pagination from "components/admin/common/Pagination"; -import Controller from "components/admin/channel/Controller"; -import ChannelTable from "components/admin/channel/ChannelTable"; +import AdminLayout from "components/common/AdminLayout"; +import Pagination from "components/common/Pagination"; +import Controller from "components/channel/Controller"; +import ChannelTable from "components/channel/ChannelTable"; +import RequireAuth from "components/common/RequireAuth"; const Channel = () => { const [searchParams] = useSearchParams(); @@ -57,4 +58,4 @@ const Container = styled.div` padding: 16px; `; -export default Channel; +export default RequireAuth(Channel); diff --git a/frontend/src/pages/admin/guest/index.tsx b/frontend/apps/admin/src/pages/guest/index.tsx similarity index 93% rename from frontend/src/pages/admin/guest/index.tsx rename to frontend/apps/admin/src/pages/guest/index.tsx index d170f5e4..018ee7c6 100644 --- a/frontend/src/pages/admin/guest/index.tsx +++ b/frontend/apps/admin/src/pages/guest/index.tsx @@ -1,7 +1,8 @@ import styled from "styled-components"; -import AdminLayout from "components/admin/common/AdminLayout"; -import useCreateHostRegist from "api/admin/hostRegist/useCreateHostRegist"; +import AdminLayout from "components/common/AdminLayout"; +import useCreateHostRegist from "api/hostRegist/useCreateHostRegist"; import useSystemModal from "hooks/common/components/useSystemModal"; +import RequireAuth from "components/common/RequireAuth"; const GuestPage = () => { const { mutate: createHostRegist } = useCreateHostRegist(); @@ -144,4 +145,4 @@ const ApplyButton = styled.button` } `; -export default GuestPage; +export default RequireAuth(GuestPage); diff --git a/frontend/apps/admin/src/pages/home/RoleBasedHome.tsx b/frontend/apps/admin/src/pages/home/RoleBasedHome.tsx new file mode 100644 index 00000000..1251e737 --- /dev/null +++ b/frontend/apps/admin/src/pages/home/RoleBasedHome.tsx @@ -0,0 +1,20 @@ +import { useRecoilValue } from "recoil"; +import userState from "recoils/atoms/auth/userState"; +import useCheckUserRole from "hooks/auth/useCheckUserRole"; +import GuestPage from "pages/guest"; +import Host from "pages/host"; +import Submission from "pages/submission"; + +const RoleBasedHome = () => { + const userData = useRecoilValue(userState); + const { isUser, isSuperAdmin, isAdmin } = useCheckUserRole(userData?.role); + + if (!userData || isAdmin) { + return ; + } + + if (isSuperAdmin) return ; + if (isUser) return ; +}; + +export default RoleBasedHome; diff --git a/frontend/src/pages/admin/host/index.tsx b/frontend/apps/admin/src/pages/host/index.tsx similarity index 82% rename from frontend/src/pages/admin/host/index.tsx rename to frontend/apps/admin/src/pages/host/index.tsx index b58b0b43..5d0f72c9 100644 --- a/frontend/src/pages/admin/host/index.tsx +++ b/frontend/apps/admin/src/pages/host/index.tsx @@ -1,14 +1,16 @@ import { useCallback } from "react"; import styled from "styled-components"; -import { OWNER, siteUrl } from "configs"; -import AdminLayout from "components/admin/common/AdminLayout"; +import AdminLayout from "components/common/AdminLayout"; +import RequireAuth from "components/common/RequireAuth"; +import useOwnerCookie from "hooks/auth/useOwnerCookie"; const Host = () => { - const userName = localStorage.getItem(OWNER); - const contentSiteUrl = `${siteUrl}/?host=${userName}`; - const landingSiteUrl = `${siteUrl}/landing?host=${userName}`; - const adminSiteUrl = `${siteUrl}/admin/login?host=${userName}`; + const hostName = useOwnerCookie(); + + const contentSiteUrl = `https://contents.moimjang.com/?host=${hostName}`; + const sellerSiteUrl = `https://seller.moimjang.com?host=${hostName}`; + const adminSiteUrl = `https://admin.moimjang.com/login?host=${hostName}`; const handleCopy = useCallback((url: string) => { navigator.clipboard.writeText(url); @@ -37,14 +39,14 @@ const Host = () => { ์†Œ๊ฐœ ํŽ˜์ด์ง€ URL - {landingSiteUrl} + {sellerSiteUrl} - handleCopy(landingSiteUrl)}> + handleCopy(sellerSiteUrl)}> Copy @@ -137,4 +139,4 @@ const Notice = styled.p` padding: 0 24px; `; -export default Host; +export default RequireAuth(Host); diff --git a/frontend/src/pages/admin/landing/index.tsx b/frontend/apps/admin/src/pages/landing/index.tsx similarity index 70% rename from frontend/src/pages/admin/landing/index.tsx rename to frontend/apps/admin/src/pages/landing/index.tsx index 2b477f4f..87f1245d 100644 --- a/frontend/src/pages/admin/landing/index.tsx +++ b/frontend/apps/admin/src/pages/landing/index.tsx @@ -1,12 +1,13 @@ import { useState } from "react"; import styled from "styled-components"; -import AdminLayout from "components/admin/common/AdminLayout"; +import AdminLayout from "components/common/AdminLayout"; import SectionToggle, { LandingSection, -} from "components/admin/landing/SectionToggle"; -import MainImage from "components/admin/landing/MainImage"; -import Gallery from "components/admin/landing/Gallery"; +} from "components/landing/SectionToggle"; +import MainImage from "components/landing/MainImage"; +import Gallery from "components/landing/Gallery"; +import RequireAuth from "components/common/RequireAuth"; const Landing = () => { const [landingSection, setLandingSection] = useState( @@ -35,4 +36,4 @@ const Container = styled.div` background-color: #fff; `; -export default Landing; +export default RequireAuth(Landing); diff --git a/frontend/src/pages/admin/submission/index.tsx b/frontend/apps/admin/src/pages/submission/index.tsx similarity index 68% rename from frontend/src/pages/admin/submission/index.tsx rename to frontend/apps/admin/src/pages/submission/index.tsx index 21d32058..e4b29b84 100644 --- a/frontend/src/pages/admin/submission/index.tsx +++ b/frontend/apps/admin/src/pages/submission/index.tsx @@ -1,12 +1,13 @@ import { useState } from "react"; import styled from "styled-components"; -import useGetHostRegistAdmin from "api/admin/hostRegist/useGetHostRegistAdmin"; -import { SubmissionContext } from "hooks/admin/submission/context/useSubmissionContext"; +import useGetHostRegistAdmin from "api/hostRegist/useGetHostRegistAdmin"; +import { SubmissionContext } from "hooks/submission/context/useSubmissionContext"; -import AdminLayout from "components/admin/common/AdminLayout"; -import Controller from "components/admin/submission/Controller"; -import Pagination from "components/admin/common/Pagination"; -import SubmissionTable from "components/admin/submission/SubmissionTable"; +import AdminLayout from "components/common/AdminLayout"; +import Controller from "components/submission/Controller"; +import Pagination from "components/common/Pagination"; +import SubmissionTable from "components/submission/SubmissionTable"; +import RequireAuth from "components/common/RequireAuth"; const Submission = () => { const [filter, setFilter] = useState<{ @@ -52,4 +53,4 @@ const Container = styled.div` padding: 16px 0px; `; -export default Submission; +export default RequireAuth(Submission); diff --git a/frontend/src/pages/admin/user/UserDetail.tsx b/frontend/apps/admin/src/pages/user/UserDetail.tsx similarity index 98% rename from frontend/src/pages/admin/user/UserDetail.tsx rename to frontend/apps/admin/src/pages/user/UserDetail.tsx index 5a48579e..7f38c5c4 100644 --- a/frontend/src/pages/admin/user/UserDetail.tsx +++ b/frontend/apps/admin/src/pages/user/UserDetail.tsx @@ -1,7 +1,7 @@ import styled from "styled-components"; import { FaEnvelope } from "react-icons/fa"; -import useGetUserById from "hooks/admin/users/useGetUserById"; -import AdminLayout from "components/admin/common/AdminLayout"; +import useGetUserById from "hooks/users/useGetUserById"; +import AdminLayout from "components/common/AdminLayout"; const UserDetail = () => { const { data } = useGetUserById(); diff --git a/frontend/src/pages/admin/user/index.tsx b/frontend/apps/admin/src/pages/user/index.tsx similarity index 71% rename from frontend/src/pages/admin/user/index.tsx rename to frontend/apps/admin/src/pages/user/index.tsx index 5877f578..d03cc17b 100644 --- a/frontend/src/pages/admin/user/index.tsx +++ b/frontend/apps/admin/src/pages/user/index.tsx @@ -1,14 +1,15 @@ import { useState } from "react"; import { useSearchParams } from "react-router-dom"; import styled from "styled-components"; -import useDeleteUser from "hooks/admin/users/useDeleteUser"; -import useGetUsers from "hooks/admin/users/useGetUsers"; -import { UserTableContext } from "hooks/admin/users/useUserTable"; +import useDeleteUser from "hooks/users/useDeleteUser"; +import useGetUsers from "hooks/users/useGetUsers"; +import { UserTableContext } from "hooks/users/useUserTable"; -import AdminLayout from "components/admin/common/AdminLayout"; -import Pagination from "components/admin/common/Pagination"; -import Controller from "components/admin/user/Controller"; -import UserTable from "components/admin/user/UserTable"; +import AdminLayout from "components/common/AdminLayout"; +import Pagination from "components/common/Pagination"; +import Controller from "components/user/Controller"; +import UserTable from "components/user/UserTable"; +import RequireAuth from "components/common/RequireAuth"; const User = () => { const [searchParams] = useSearchParams(); @@ -58,4 +59,4 @@ const User = () => { const Container = styled.div``; -export default User; +export default RequireAuth(User); diff --git a/frontend/src/react-app-env.d.ts b/frontend/apps/admin/src/react-app-env.d.ts similarity index 100% rename from frontend/src/react-app-env.d.ts rename to frontend/apps/admin/src/react-app-env.d.ts diff --git a/frontend/apps/admin/src/recoils/atoms/auth/userState.ts b/frontend/apps/admin/src/recoils/atoms/auth/userState.ts new file mode 100644 index 00000000..2947c5d3 --- /dev/null +++ b/frontend/apps/admin/src/recoils/atoms/auth/userState.ts @@ -0,0 +1,24 @@ +import { atom } from "recoil"; +import { User } from "interfaces/user"; + +const userState = atom({ + key: "userState", + default: null, + effects_UNSTABLE: [ + ({ setSelf, onSet }) => { + const savedUserData = localStorage.getItem("userData"); + if (savedUserData) { + setSelf(JSON.parse(savedUserData)); + } + + onSet((newUserData) => { + if (newUserData) { + localStorage.setItem("userData", JSON.stringify(newUserData)); + localStorage.removeItem("userData"); + } + }); + }, + ], +}); + +export default userState; diff --git a/frontend/src/recoils/atoms/common/modal.tsx b/frontend/apps/admin/src/recoils/atoms/common/modal.tsx similarity index 100% rename from frontend/src/recoils/atoms/common/modal.tsx rename to frontend/apps/admin/src/recoils/atoms/common/modal.tsx diff --git a/frontend/src/recoils/atoms/admin/saveBar.ts b/frontend/apps/admin/src/recoils/atoms/saveBar.ts similarity index 100% rename from frontend/src/recoils/atoms/admin/saveBar.ts rename to frontend/apps/admin/src/recoils/atoms/saveBar.ts diff --git a/frontend/src/recoils/atoms/admin/surveyState.ts b/frontend/apps/admin/src/recoils/atoms/surveyState.ts similarity index 100% rename from frontend/src/recoils/atoms/admin/surveyState.ts rename to frontend/apps/admin/src/recoils/atoms/surveyState.ts diff --git a/frontend/src/styles/Functions.tsx b/frontend/apps/admin/src/styles/Functions.tsx similarity index 100% rename from frontend/src/styles/Functions.tsx rename to frontend/apps/admin/src/styles/Functions.tsx diff --git a/frontend/src/styles/GlobalStyles.tsx b/frontend/apps/admin/src/styles/GlobalStyles.tsx similarity index 100% rename from frontend/src/styles/GlobalStyles.tsx rename to frontend/apps/admin/src/styles/GlobalStyles.tsx diff --git a/frontend/src/styles/Theme.tsx b/frontend/apps/admin/src/styles/Theme.tsx similarity index 100% rename from frontend/src/styles/Theme.tsx rename to frontend/apps/admin/src/styles/Theme.tsx diff --git a/frontend/src/utils/admin/channel/executeMatchMaking.ts b/frontend/apps/admin/src/utils/channel/executeMatchMaking.ts similarity index 100% rename from frontend/src/utils/admin/channel/executeMatchMaking.ts rename to frontend/apps/admin/src/utils/channel/executeMatchMaking.ts diff --git a/frontend/src/utils/admin/channel/generateRandomMatch.ts b/frontend/apps/admin/src/utils/channel/generateRandomMatch.ts similarity index 100% rename from frontend/src/utils/admin/channel/generateRandomMatch.ts rename to frontend/apps/admin/src/utils/channel/generateRandomMatch.ts diff --git a/frontend/src/utils/admin/channel/matchValidation.ts b/frontend/apps/admin/src/utils/channel/matchValidation.ts similarity index 100% rename from frontend/src/utils/admin/channel/matchValidation.ts rename to frontend/apps/admin/src/utils/channel/matchValidation.ts diff --git a/frontend/src/utils/admin/channel/structureGameData.ts b/frontend/apps/admin/src/utils/channel/structureGameData.ts similarity index 100% rename from frontend/src/utils/admin/channel/structureGameData.ts rename to frontend/apps/admin/src/utils/channel/structureGameData.ts diff --git a/frontend/src/utils/error.ts b/frontend/apps/admin/src/utils/error.ts similarity index 100% rename from frontend/src/utils/error.ts rename to frontend/apps/admin/src/utils/error.ts diff --git a/frontend/src/utils/image.ts b/frontend/apps/admin/src/utils/image.ts similarity index 100% rename from frontend/src/utils/image.ts rename to frontend/apps/admin/src/utils/image.ts diff --git a/frontend/apps/admin/tsconfig.json b/frontend/apps/admin/tsconfig.json new file mode 100644 index 00000000..ac9d0f25 --- /dev/null +++ b/frontend/apps/admin/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "es5", + "downlevelIteration": true, + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "noImplicitAny": false, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "baseUrl": "./src", + "strictNullChecks": false, + "strictPropertyInitialization": false + }, + "include": ["src", "types", "public/mockServiceWorker.js"] +} diff --git a/frontend/types/styled.d.ts b/frontend/apps/admin/types/styled.d.ts similarity index 100% rename from frontend/types/styled.d.ts rename to frontend/apps/admin/types/styled.d.ts diff --git a/frontend/types/uuid.d.ts b/frontend/apps/admin/types/uuid.d.ts similarity index 100% rename from frontend/types/uuid.d.ts rename to frontend/apps/admin/types/uuid.d.ts diff --git a/frontend/webpack.config.js b/frontend/apps/admin/webpack.config.js similarity index 100% rename from frontend/webpack.config.js rename to frontend/apps/admin/webpack.config.js diff --git a/frontend/apps/contents/.gitignore b/frontend/apps/contents/.gitignore new file mode 100644 index 00000000..5ef6a520 --- /dev/null +++ b/frontend/apps/contents/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/frontend/apps/contents/Dockerfile b/frontend/apps/contents/Dockerfile new file mode 100644 index 00000000..1966a62f --- /dev/null +++ b/frontend/apps/contents/Dockerfile @@ -0,0 +1,42 @@ +# ๋‹จ์ผ ์Šคํ…Œ์ด์ง€๋ฅผ ์‚ฌ์šฉํ•œ ๊ฐ„๋‹จํ•œ Next.js ๋„์ปคํ™” +# ์‚ฌ์šฉ๋ฒ•: cd /frontend && docker build -f apps/contents/Dockerfile -t moimjang/contents . + +FROM node:18-alpine + +# ์ž‘์—… ๋””๋ ‰ํ† ๋ฆฌ ์„ค์ • +WORKDIR /app + +# ๋นŒ๋“œ ์ธ์ˆ˜ ์ •์˜ (Next.js ๊ทœ์น™์— ๋งž๊ฒŒ ๋ณ€๊ฒฝ) +ARG NEXT_PUBLIC_SERVER_URL +ARG NEXT_PUBLIC_NODE_ENV +ARG NEXT_PUBLIC_DANGEROUSLY_DISABLE_HOST_CHECK +ARG NEXT_PUBLIC_ENVIRONMENT +ARG NEXT_PUBLIC_SITE_URL + +# ํ™˜๊ฒฝ๋ณ€์ˆ˜๋กœ ์„ค์ • +ENV NEXT_PUBLIC_SERVER_URL=$NEXT_PUBLIC_SERVER_URL +ENV NEXT_PUBLIC_NODE_ENV=$NEXT_PUBLIC_NODE_ENV +ENV NEXT_PUBLIC_DANGEROUSLY_DISABLE_HOST_CHECK=$NEXT_PUBLIC_DANGEROUSLY_DISABLE_HOST_CHECK +ENV NEXT_PUBLIC_ENVIRONMENT=$NEXT_PUBLIC_ENVIRONMENT +ENV NEXT_PUBLIC_SITE_URL=$NEXT_PUBLIC_SITE_URL + +# ํŒจํ‚ค์ง€ ํŒŒ์ผ๋“ค ๋ณต์‚ฌ +COPY package*.json ./ +COPY apps/contents/package*.json ./apps/contents/ + +# ์˜์กด์„ฑ ์„ค์น˜ +RUN npm install --include-workspace-root --workspaces --include=optional sharp + +# ์ „์ฒด ์†Œ์Šค ๋ณต์‚ฌ +COPY packages ./packages/ +COPY apps/contents ./apps/contents/ + +# contents ์•ฑ์œผ๋กœ ์ด๋™ ํ›„ ๋นŒ๋“œ +WORKDIR /app/apps/contents +RUN npm run build + +# ํฌํŠธ 3000 ๋…ธ์ถœ +EXPOSE 3000 + +# Next.js ์‹คํ–‰ +CMD ["npm", "start"] \ No newline at end of file diff --git a/frontend/apps/contents/eslint.config.mjs b/frontend/apps/contents/eslint.config.mjs new file mode 100644 index 00000000..c85fb67c --- /dev/null +++ b/frontend/apps/contents/eslint.config.mjs @@ -0,0 +1,16 @@ +import { dirname } from "path"; +import { fileURLToPath } from "url"; +import { FlatCompat } from "@eslint/eslintrc"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const compat = new FlatCompat({ + baseDirectory: __dirname, +}); + +const eslintConfig = [ + ...compat.extends("next/core-web-vitals", "next/typescript"), +]; + +export default eslintConfig; diff --git a/frontend/apps/contents/next.config.ts b/frontend/apps/contents/next.config.ts new file mode 100644 index 00000000..37597c6c --- /dev/null +++ b/frontend/apps/contents/next.config.ts @@ -0,0 +1,31 @@ +// apps/seller/next.config.ts +import path from "path"; +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + images: { + domains: ["cdn.chanyoung.site"], // โ† ์—ฌ๊ธฐ์— ํ—ˆ์šฉํ•  ์™ธ๋ถ€ ์ด๋ฏธ์ง€ ๋„๋ฉ”์ธ ์ถ”๊ฐ€ + }, + + webpack(config) { + // resolve ํ˜น์€ alias ๊ฐ€ undefined ์ผ ์ˆ˜ ์žˆ์œผ๋‹ˆ ์•ˆ์ „ํ•˜๊ฒŒ ์ดˆ๊ธฐํ™” + config.resolve = config.resolve || {}; + config.resolve.alias = { + ...(config.resolve.alias || {}), + + // ๊ธฐ์กด "@/โ€ฆ" alias + "@": path.resolve(__dirname, "src"), + + // monorepo ํŒจํ‚ค์ง€ alias + "@ui": path.resolve(__dirname, "../../packages/ui/src"), + "@util": path.resolve(__dirname, "../../packages/util"), + "@mocks": path.resolve(__dirname, "../../packages/mocks"), + "@constants": path.resolve(__dirname, "../../packages/constants"), + "@model": path.resolve(__dirname, "../../packages/model"), + }; + + return config; + }, +}; + +export default nextConfig; diff --git a/frontend/apps/contents/package.json b/frontend/apps/contents/package.json new file mode 100644 index 00000000..c539baf8 --- /dev/null +++ b/frontend/apps/contents/package.json @@ -0,0 +1,17 @@ +{ + "name": "contents", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "devDependencies": { + "@eslint/eslintrc": "^3", + "eslint": "^9", + "eslint-config-next": "15.1.8", + "typescript": "^5" + } +} diff --git a/frontend/apps/contents/public/favicon/apple-touch-icon.png b/frontend/apps/contents/public/favicon/apple-touch-icon.png new file mode 100644 index 00000000..ab4e6639 Binary files /dev/null and b/frontend/apps/contents/public/favicon/apple-touch-icon.png differ diff --git a/frontend/apps/contents/public/favicon/favicon-96x96.png b/frontend/apps/contents/public/favicon/favicon-96x96.png new file mode 100644 index 00000000..7bdc922b Binary files /dev/null and b/frontend/apps/contents/public/favicon/favicon-96x96.png differ diff --git a/frontend/apps/contents/public/favicon/favicon.ico b/frontend/apps/contents/public/favicon/favicon.ico new file mode 100644 index 00000000..90fb9815 Binary files /dev/null and b/frontend/apps/contents/public/favicon/favicon.ico differ diff --git a/frontend/apps/contents/public/favicon/favicon.svg b/frontend/apps/contents/public/favicon/favicon.svg new file mode 100644 index 00000000..4037739e --- /dev/null +++ b/frontend/apps/contents/public/favicon/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/apps/contents/public/favicon/manifest.json b/frontend/apps/contents/public/favicon/manifest.json new file mode 100644 index 00000000..06fe4034 --- /dev/null +++ b/frontend/apps/contents/public/favicon/manifest.json @@ -0,0 +1,21 @@ +{ + "name": "๋ชจ์ž„์žฅ", + "short_name": "๋ชจ์ž„์žฅ", + "icons": [ + { + "src": "/web-app-manifest-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "/web-app-manifest-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} \ No newline at end of file diff --git a/frontend/apps/contents/public/file.svg b/frontend/apps/contents/public/file.svg new file mode 100644 index 00000000..004145cd --- /dev/null +++ b/frontend/apps/contents/public/file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/apps/contents/public/globe.svg b/frontend/apps/contents/public/globe.svg new file mode 100644 index 00000000..567f17b0 --- /dev/null +++ b/frontend/apps/contents/public/globe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/apps/contents/public/logo.png b/frontend/apps/contents/public/logo.png new file mode 100644 index 00000000..c1b4abed Binary files /dev/null and b/frontend/apps/contents/public/logo.png differ diff --git a/frontend/apps/contents/public/mockServiceWorker.js b/frontend/apps/contents/public/mockServiceWorker.js new file mode 100644 index 00000000..bdfdb11d --- /dev/null +++ b/frontend/apps/contents/public/mockServiceWorker.js @@ -0,0 +1,284 @@ +/* eslint-disable */ +/* tslint:disable */ + +/** + * Mock Service Worker. + * @see https://github.com/mswjs/msw + * - Please do NOT modify this file. + * - Please do NOT serve this file on production. + */ + +const PACKAGE_VERSION = '2.3.0' +const INTEGRITY_CHECKSUM = '26357c79639bfa20d64c0efca2a87423' +const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') +const activeClientIds = new Set() + +self.addEventListener('install', function () { + self.skipWaiting() +}) + +self.addEventListener('activate', function (event) { + event.waitUntil(self.clients.claim()) +}) + +self.addEventListener('message', async function (event) { + const clientId = event.source.id + + if (!clientId || !self.clients) { + return + } + + const client = await self.clients.get(clientId) + + if (!client) { + return + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + switch (event.data) { + case 'KEEPALIVE_REQUEST': { + sendToClient(client, { + type: 'KEEPALIVE_RESPONSE', + }) + break + } + + case 'INTEGRITY_CHECK_REQUEST': { + sendToClient(client, { + type: 'INTEGRITY_CHECK_RESPONSE', + payload: { + packageVersion: PACKAGE_VERSION, + checksum: INTEGRITY_CHECKSUM, + }, + }) + break + } + + case 'MOCK_ACTIVATE': { + activeClientIds.add(clientId) + + sendToClient(client, { + type: 'MOCKING_ENABLED', + payload: true, + }) + break + } + + case 'MOCK_DEACTIVATE': { + activeClientIds.delete(clientId) + break + } + + case 'CLIENT_CLOSED': { + activeClientIds.delete(clientId) + + const remainingClients = allClients.filter((client) => { + return client.id !== clientId + }) + + // Unregister itself when there are no more clients + if (remainingClients.length === 0) { + self.registration.unregister() + } + + break + } + } +}) + +self.addEventListener('fetch', function (event) { + const { request } = event + + // Bypass navigation requests. + if (request.mode === 'navigate') { + return + } + + // Opening the DevTools triggers the "only-if-cached" request + // that cannot be handled by the worker. Bypass such requests. + if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { + return + } + + // Bypass all requests when there are no active clients. + // Prevents the self-unregistered worked from handling requests + // after it's been deleted (still remains active until the next reload). + if (activeClientIds.size === 0) { + return + } + + // Generate unique request ID. + const requestId = crypto.randomUUID() + event.respondWith(handleRequest(event, requestId)) +}) + +async function handleRequest(event, requestId) { + const client = await resolveMainClient(event) + const response = await getResponse(event, client, requestId) + + // Send back the response clone for the "response:*" life-cycle events. + // Ensure MSW is active and ready to handle the message, otherwise + // this message will pend indefinitely. + if (client && activeClientIds.has(client.id)) { + ;(async function () { + const responseClone = response.clone() + + sendToClient( + client, + { + type: 'RESPONSE', + payload: { + requestId, + isMockedResponse: IS_MOCKED_RESPONSE in response, + type: responseClone.type, + status: responseClone.status, + statusText: responseClone.statusText, + body: responseClone.body, + headers: Object.fromEntries(responseClone.headers.entries()), + }, + }, + [responseClone.body], + ) + })() + } + + return response +} + +// Resolve the main client for the given event. +// Client that issues a request doesn't necessarily equal the client +// that registered the worker. It's with the latter the worker should +// communicate with during the response resolving phase. +async function resolveMainClient(event) { + const client = await self.clients.get(event.clientId) + + if (client?.frameType === 'top-level') { + return client + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + return allClients + .filter((client) => { + // Get only those clients that are currently visible. + return client.visibilityState === 'visible' + }) + .find((client) => { + // Find the client ID that's recorded in the + // set of clients that have registered the worker. + return activeClientIds.has(client.id) + }) +} + +async function getResponse(event, client, requestId) { + const { request } = event + + // Clone the request because it might've been already used + // (i.e. its body has been read and sent to the client). + const requestClone = request.clone() + + function passthrough() { + const headers = Object.fromEntries(requestClone.headers.entries()) + + // Remove internal MSW request header so the passthrough request + // complies with any potential CORS preflight checks on the server. + // Some servers forbid unknown request headers. + delete headers['x-msw-intention'] + + return fetch(requestClone, { headers }) + } + + // Bypass mocking when the client is not active. + if (!client) { + return passthrough() + } + + // Bypass initial page load requests (i.e. static assets). + // The absence of the immediate/parent client in the map of the active clients + // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet + // and is not ready to handle requests. + if (!activeClientIds.has(client.id)) { + return passthrough() + } + + // Notify the client that a request has been intercepted. + const requestBuffer = await request.arrayBuffer() + const clientMessage = await sendToClient( + client, + { + type: 'REQUEST', + payload: { + id: requestId, + url: request.url, + mode: request.mode, + method: request.method, + headers: Object.fromEntries(request.headers.entries()), + cache: request.cache, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body: requestBuffer, + keepalive: request.keepalive, + }, + }, + [requestBuffer], + ) + + switch (clientMessage.type) { + case 'MOCK_RESPONSE': { + return respondWithMock(clientMessage.data) + } + + case 'PASSTHROUGH': { + return passthrough() + } + } + + return passthrough() +} + +function sendToClient(client, message, transferrables = []) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel() + + channel.port1.onmessage = (event) => { + if (event.data && event.data.error) { + return reject(event.data.error) + } + + resolve(event.data) + } + + client.postMessage( + message, + [channel.port2].concat(transferrables.filter(Boolean)), + ) + }) +} + +async function respondWithMock(response) { + // Setting response status code to 0 is a no-op. + // However, when responding with a "Response.error()", the produced Response + // instance will have status code set to 0. Since it's not possible to create + // a Response instance with status code 0, handle that use-case separately. + if (response.status === 0) { + return Response.error() + } + + const mockedResponse = new Response(response.body, response) + + Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, { + value: true, + enumerable: true, + }) + + return mockedResponse +} diff --git a/frontend/apps/contents/public/web-app-manifest-192x192.png b/frontend/apps/contents/public/web-app-manifest-192x192.png new file mode 100644 index 00000000..5ea434a9 Binary files /dev/null and b/frontend/apps/contents/public/web-app-manifest-192x192.png differ diff --git a/frontend/apps/contents/public/web-app-manifest-512x512.png b/frontend/apps/contents/public/web-app-manifest-512x512.png new file mode 100644 index 00000000..97e5d4d0 Binary files /dev/null and b/frontend/apps/contents/public/web-app-manifest-512x512.png differ diff --git a/frontend/apps/contents/src/app/(afterLogin)/_api/useGetBrandById.ts b/frontend/apps/contents/src/app/(afterLogin)/_api/useGetBrandById.ts new file mode 100644 index 00000000..3b2ef80d --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/_api/useGetBrandById.ts @@ -0,0 +1,58 @@ +import { useEffect, useState } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { GET_BRAND_BY_ID } from "@/app/_constant/queryKeys"; +import { ACCEESS_TOKEN } from "@constants/auth"; +import { serverUrl } from "@/app/_constant/config"; +import { Image } from "@model/common/index"; + +export interface Response { + title: string; + description: string; + min_participants: number; + max_participants: number; + meeting_location: string; + location_link: string; + brand_state: string; + id: number; + thumbnailImage: Image; + detailImages: Array; +} + +export const getBrandById = async ( + brand_id: number | null, + owner: string | null, + token: string | null +): Promise => { + const result = await fetch(`${serverUrl}/customers/brand/${brand_id}`, { + method: "GET", + headers: { + Authorization: `Bearer ${token}`, + Owner: owner || "", + }, + next: { + tags: [GET_BRAND_BY_ID], + }, + cache: "no-store", + }); + if (!result.ok) new Error("Failed to fetch data"); + + return result.json(); +}; + +const useGetBrandById = (brand_id: number, owner: string) => { + const [token, setToken] = useState(null); + + useEffect(() => { + const token = localStorage.getItem(ACCEESS_TOKEN); + setToken(token); + }, []); + + return useQuery({ + queryKey: [GET_BRAND_BY_ID, brand_id, owner], + queryFn: () => getBrandById(brand_id, owner, token), + staleTime: 60 * 1000, + enabled: Boolean(owner) && Boolean(token) && Boolean(brand_id), + }); +}; + +export default useGetBrandById; diff --git a/frontend/apps/contents/src/app/(afterLogin)/_api/useGetChannelList.ts b/frontend/apps/contents/src/app/(afterLogin)/_api/useGetChannelList.ts new file mode 100644 index 00000000..f76a06af --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/_api/useGetChannelList.ts @@ -0,0 +1,73 @@ +import { useEffect, useState } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { GET_CHANNEL_LIST } from "@/app/_constant/queryKeys"; +import { ACCEESS_TOKEN } from "@constants/auth"; +import { serverUrl } from "@/app/_constant/config"; +import { Channel, ChannelState } from "@model/channel"; + +export interface Output { + channels: Array; + totalCount: number; +} + +export interface Params { + state: ChannelState; + sort_by: string; + descending: boolean; + offset: number; + limit: number; +} + +export interface Response { + channels: Array; + totalCount: number; +} + +export const getChannelList = async ( + params: Params, + token: string | null, + owner: string | null +): Promise => { + const queryString = new URLSearchParams({ + state: params.state, + sort_by: params.sort_by, + descending: params.descending.toString(), + limit: params.limit.toString(), + }).toString(); + + const result = await fetch( + `${serverUrl}/customers/channel_list?${queryString}`, + { + method: "GET", + headers: { + Authorization: `Bearer ${token}`, + Owner: owner || "", + }, + next: { + tags: [GET_CHANNEL_LIST], + }, + cache: "no-store", + } + ); + if (!result.ok) new Error("Failed to fetch data"); + + return result.json(); +}; + +const useGetChannelList = (params: Params, owner: string) => { + const [token, setToken] = useState(null); + + useEffect(() => { + const token = localStorage.getItem(ACCEESS_TOKEN); + setToken(token); + }, []); + + return useQuery({ + queryKey: [GET_CHANNEL_LIST, params], + queryFn: () => getChannelList(params, token, owner), + staleTime: 60 * 1000, + enabled: Boolean(owner) && Boolean(token), // token์ด null์ด ์•„๋‹ ๋•Œ๋งŒ ์‹คํ–‰ + }); +}; + +export default useGetChannelList; diff --git a/frontend/apps/contents/src/app/(afterLogin)/_api/useGetMyInfo.ts b/frontend/apps/contents/src/app/(afterLogin)/_api/useGetMyInfo.ts new file mode 100644 index 00000000..d9e384a3 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/_api/useGetMyInfo.ts @@ -0,0 +1,35 @@ +import { useQuery } from "@tanstack/react-query"; +import { GET_MY_INFO } from "@/app/_constant/queryKeys"; +import { useState, useEffect } from "react"; +import { User } from "@model/user"; +import { ACCEESS_TOKEN } from "@constants/auth"; +import { serverUrl } from "@/app/_constant/config"; + +export const getMyInfo = async (token: string | null): Promise => { + const result = await fetch(`${serverUrl}/customers/my_info`, { + method: "GET", + headers: { + Authorization: `Bearer ${token}`, + }, + cache: "no-store", + }); + if (!result.ok) throw new Error("Failed to fetch data"); + return result.json(); +}; + +const useGetMyInfo = () => { + const [token, setToken] = useState(null); + + useEffect(() => { + const token = localStorage.getItem(ACCEESS_TOKEN); + setToken(token); + }, []); + + return useQuery({ + queryKey: [GET_MY_INFO], + queryFn: () => getMyInfo(token), + enabled: Boolean(token), + }); +}; + +export default useGetMyInfo; diff --git a/frontend/apps/contents/src/app/(afterLogin)/_api/useJoinChannel.ts b/frontend/apps/contents/src/app/(afterLogin)/_api/useJoinChannel.ts new file mode 100644 index 00000000..0fd27941 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/_api/useJoinChannel.ts @@ -0,0 +1,36 @@ +import { useState, useEffect } from "react"; +import { useMutation } from "@tanstack/react-query"; +import { ACCEESS_TOKEN } from "@constants/auth"; +import { serverUrl } from "@/app/_constant/config"; +import { ApiError, ErrorType } from "@model/error/index"; + +export const joinChannel = async ( + target_channel_id: number, + token: string | null +): Promise => { + const res = await fetch(`${serverUrl}/customers/channel`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ target_channel_id }), + }); + + return res.json(); +}; + +const useJoinChannel = () => { + const [token, setToken] = useState(null); + + useEffect(() => { + const stored = localStorage.getItem(ACCEESS_TOKEN); + setToken(stored); + }, []); + + return useMutation({ + mutationFn: (channelId) => joinChannel(channelId, token), + }); +}; + +export default useJoinChannel; diff --git a/frontend/apps/contents/src/app/(afterLogin)/_components/Event.module.css b/frontend/apps/contents/src/app/(afterLogin)/_components/Event.module.css new file mode 100644 index 00000000..f6bcbbe9 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/_components/Event.module.css @@ -0,0 +1,102 @@ +.container { + background-color: white; + border-radius: 16px; + padding: 1rem; + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.3); + border: none; + transition: transform 0.2s ease-in-out; + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.container:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); +} + +.thumbnailImage { + width: 100%; + height: auto; + border-radius: 16px; + margin-bottom: 0.75rem; +} + +.channelHeader { + display: flex; + justify-content: space-between; + font-weight: bold; +} + +.channelTitle { + font-size: 1.125rem; +} + +@media (max-width: 430px) { + .channelTitle { + font-size: 1rem; + } + .gameInfo { + font-size: 0.75rem; + } + .joinButton { + padding: 0.4rem 0.8rem; + } +} + +.gameInfo { + display: flex; + align-items: center; + gap: 0.5rem; + color: #6b7280; + font-size: 0.875rem; +} + +.locationInfo { + display: flex; + align-items: center; + gap: 0.5rem; + color: #4b5563; + font-size: 0.875rem; +} + +.locationLink { + color: #2563eb; + font-weight: 500; + text-decoration: none; +} +.locationLink:hover { + text-decoration: underline; +} + +.participantsInfo { + display: flex; + align-items: center; + gap: 0.5rem; + color: #4b5563; + font-size: 0.875rem; +} + +.joinButton { + background-color: #524634; + color: white; + font-weight: bold; + padding: 1rem; + border-radius: 16px; + border: none; + cursor: pointer; + transition: background-color 0.2s ease-in-out; + font-size: 1.3rem; +} + +.joinButton:hover { + background-color: #260000; +} + +.joinButton:disabled { + background-color: #cdcdcc; +} + +.joinButton:active { + background-color: #260000; +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/_components/Event.tsx b/frontend/apps/contents/src/app/(afterLogin)/_components/Event.tsx new file mode 100644 index 00000000..a5e38afd --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/_components/Event.tsx @@ -0,0 +1,66 @@ +"use client"; + +import React from "react"; +import styles from "./Event.module.css"; +import { FaGamepad, FaUsers } from "react-icons/fa"; +import Thumbnail from "./Thumbnail"; +import { Channel } from "@model/channel"; +import useJoinChannel from "../_api/useJoinChannel"; +import pathnames from "@/app/_constant/pathnames"; +import { useRouter } from "next/navigation"; +import { useSystemModalStore } from "@ui/store/useSystemModalStore"; +import { isErrorType } from "@model/error/util/isErrorType"; + +interface Props { + socialing: Channel; +} + +export default function Event({ socialing }: Props) { + const router = useRouter(); + const { mutate: joinChannel } = useJoinChannel(); + const { showErrorModal } = useSystemModalStore(); + + const handleButtonClick = () => { + joinChannel(socialing.id, { + onSuccess: (data) => { + if (isErrorType(data)) { + if (data.detail === "์ด๋ฏธ ์ฑ„๋„์— ๊ฐ€์ž…๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค") { + router.push(`${pathnames.detail}/${socialing.id}`); + return; + } + } + + router.push(`${pathnames.detail}/${socialing.id}`); + }, + onError(error) { + showErrorModal(error.message); + }, + }); + }; + + return ( +
+ + +
+

{socialing.title}

+
+ +
+ + {socialing.description} +
+
+
+ + {socialing?.joined_users?.length || 0}๋ช… ์ฐธ์—ฌ ์ค‘ +
+
+ +
+ ); +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/_components/HasNoSocialing.module.css b/frontend/apps/contents/src/app/(afterLogin)/_components/HasNoSocialing.module.css new file mode 100644 index 00000000..c41e5b92 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/_components/HasNoSocialing.module.css @@ -0,0 +1,30 @@ +.noSocialing { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100vh; + padding: 2rem; + background-color: #f3f4f6; + border-radius: 16px; + color: #4b5563; + text-align: center; + gap: 0.5rem; +} + +.noSocialingIcon { + font-size: 3rem; + color: #9ca3af; +} + +.noSocialingText { + font-size: 1.25rem; + font-weight: 500; + margin: 0; +} + +.noSocialingSubtext { + font-size: 1rem; + color: #9ca3af; + margin: 0; +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/_components/HasNoSocialing.tsx b/frontend/apps/contents/src/app/(afterLogin)/_components/HasNoSocialing.tsx new file mode 100644 index 00000000..65ba2b51 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/_components/HasNoSocialing.tsx @@ -0,0 +1,17 @@ +import React from "react"; +import styles from "./HasNoSocialing.module.css"; +import { FaRegCalendarTimes } from "react-icons/fa"; + +export default function HasNoSocialing() { + return ( +
+ +

์ง„ํ–‰ ์ค‘์ธ ์†Œ์…œ๋ง์ด ์—†์Šต๋‹ˆ๋‹ค.

+

+ ์ƒˆ๋กœ์šด ๋ชจ์ž„์„ ๊ณง ์ถ”๊ฐ€ํ•  ์˜ˆ์ •์ด๋‹ˆ, +
+ ์ž ์‹œ๋งŒ ๊ธฐ๋‹ค๋ ค์ฃผ์„ธ์š”. +

+
+ ); +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/_components/Navigation/Navigation.module.css b/frontend/apps/contents/src/app/(afterLogin)/_components/Navigation/Navigation.module.css new file mode 100644 index 00000000..bf70fc75 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/_components/Navigation/Navigation.module.css @@ -0,0 +1,39 @@ +.container { + position: fixed; + bottom: 0; + left: 0; + right: 0; + background-color: white; + border-top: 1px solid #e5e7eb; +} + +.bottomNavContainer { + display: flex; + justify-content: space-around; + height: 4rem; +} + +.navItem { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; + background: none; + border: none; + cursor: pointer; + color: #9ca3af; /* ๋น„ํ™œ์„ฑ ์ƒ‰์ƒ */ +} + +.active { + color: #3b82f6; /* ํ™œ์„ฑ ์ƒ‰์ƒ */ +} + +.navIcon { + font-size: 1.25rem; + margin-bottom: 0.25rem; +} + +.navText { + font-size: 0.75rem; +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/_components/Navigation/Navigation.tsx b/frontend/apps/contents/src/app/(afterLogin)/_components/Navigation/Navigation.tsx new file mode 100644 index 00000000..4634dc6c --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/_components/Navigation/Navigation.tsx @@ -0,0 +1,45 @@ +import { usePathname, useRouter } from "next/navigation"; +import { FaUser, FaHome } from "react-icons/fa"; +import styles from "./Navigation.module.css"; +import pathnames from "@/app/_constant/pathnames"; + +const Navigation = () => { + const router = useRouter(); + const pathname = usePathname(); + + const handleButtonClick = (pathName: string) => { + router.push(pathName); + }; + + return ( + + ); +}; + +export default Navigation; diff --git a/frontend/apps/contents/src/app/(afterLogin)/_components/Navigation/NavigationConfigurator.tsx b/frontend/apps/contents/src/app/(afterLogin)/_components/Navigation/NavigationConfigurator.tsx new file mode 100644 index 00000000..af64f246 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/_components/Navigation/NavigationConfigurator.tsx @@ -0,0 +1,21 @@ +"use client"; + +import { useEffect } from "react"; +import useNavigationStore from "@/app/_store/useNavigationStore"; + +type Config = { + visible: boolean; +}; + +export default function NavigationConfigurator({ config }: { config: Config }) { + const { handler } = useNavigationStore(); + + useEffect(() => { + handler(config.visible); + return () => { + handler(true); + }; + }, [config.visible, handler]); + + return null; +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/_components/Navigation/NavigationRoot.tsx b/frontend/apps/contents/src/app/(afterLogin)/_components/Navigation/NavigationRoot.tsx new file mode 100644 index 00000000..22401f1a --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/_components/Navigation/NavigationRoot.tsx @@ -0,0 +1,11 @@ +"use client"; + +import React from "react"; +import Navigation from "./Navigation"; +import useNavigationStore from "@/app/_store/useNavigationStore"; + +export default function NavigationRoot() { + const visible = useNavigationStore((config) => config.visible); + + if (visible) return ; +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/_components/SocialingEvents.module.css b/frontend/apps/contents/src/app/(afterLogin)/_components/SocialingEvents.module.css new file mode 100644 index 00000000..82c5ffde --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/_components/SocialingEvents.module.css @@ -0,0 +1,17 @@ +.container { + display: flex; + flex-direction: column; + gap: 2rem; + + height: 100vh; + overflow-y: scroll; + padding: 0px 16px; + + /* Hide scrollbar */ + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ +} + +.container::-webkit-scrollbar { + display: none; /* Chrome, Safari, Opera */ +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/_components/SocialingEvents.tsx b/frontend/apps/contents/src/app/(afterLogin)/_components/SocialingEvents.tsx new file mode 100644 index 00000000..971df6b4 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/_components/SocialingEvents.tsx @@ -0,0 +1,52 @@ +"use client"; + +import React from "react"; +import { ChannelState } from "@model/channel"; + +import useGetChannelList from "../_api/useGetChannelList"; +import ErrorBox from "@ui/components/Error/ErrorBox"; +import LoadingSpinner from "@ui/components/Spinner/FadeLoader"; +import { OWNER } from "@constants/auth"; +import useCookie from "@util/hooks/useCookie"; +import HasNoSocialing from "./HasNoSocialing"; +import Event from "./Event"; +import HeaderConfigurator from "@ui/components/Header/HeaderConfigurator"; +import styles from "./SocialingEvents.module.css"; + +export default function SocialingEvents() { + const owner = useCookie(OWNER); + + const { data, isLoading, isError, refetch } = useGetChannelList( + { + state: ChannelState.ONGOING, + descending: false, + sort_by: "id", + limit: 20, + offset: 0, + }, + owner + ); + + const channels = data?.channels; + + if (!channels?.length) return ; + if (isLoading) return ; + if (isError) return ; + + return ( + <> + +
+ {channels.map((socialing) => ( + + ))} +
+ + ); +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/_components/Thumbnail.module.css b/frontend/apps/contents/src/app/(afterLogin)/_components/Thumbnail.module.css new file mode 100644 index 00000000..fc789396 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/_components/Thumbnail.module.css @@ -0,0 +1,6 @@ +.thumbnail { + width: 100%; + height: auto; + border-radius: 16px; + margin-bottom: 0.75rem; +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/_components/Thumbnail.tsx b/frontend/apps/contents/src/app/(afterLogin)/_components/Thumbnail.tsx new file mode 100644 index 00000000..301d93a2 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/_components/Thumbnail.tsx @@ -0,0 +1,28 @@ +"use client"; + +import React from "react"; +import useGetBrandById from "../_api/useGetBrandById"; +import styles from "./Thumbnail.module.css"; +import Image from "@ui/components/Image/OptimizedNextImage"; +import useCookie from "@util/hooks/useCookie"; +import { OWNER } from "@constants/auth"; + +interface Props { + brandId: number; +} + +export default function Thumbnail({ brandId }: Props) { + const owner = useCookie(OWNER); + const { data } = useGetBrandById(brandId, owner); + const thumbnailImage = data?.thumbnailImage; + + if (!thumbnailImage) return null; + + return ( + ์†Œ์…œ๋ง ์ธ๋„ค์ผ ์ด๋ฏธ์ง€ + ); +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/_api/useGetChannelById.ts b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/_api/useGetChannelById.ts new file mode 100644 index 00000000..472aa02f --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/_api/useGetChannelById.ts @@ -0,0 +1,44 @@ +import { useEffect, useState } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { GET_CHANNELS_BY_ID } from "@/app/_constant/queryKeys"; +import { ACCEESS_TOKEN } from "@constants/auth"; +import { serverUrl } from "@/app/_constant/config"; +import { Channel } from "@model/channel"; + +export const getChannelById = async ( + channelId: string | string[] | undefined, + token: string | null, + owner: string | null +): Promise => { + const result = await fetch(`${serverUrl}/customers/channel/${channelId}`, { + method: "GET", + headers: { + Authorization: `Bearer ${token}`, + Owner: owner || "", + }, + cache: "no-store", + }); + if (!result.ok) throw new Error("Failed to fetch data"); + return result.json(); +}; + +const useGetChannelById = ( + channelId: string | string[] | undefined, + owner: string +) => { + const [token, setToken] = useState(null); + + useEffect(() => { + const token = localStorage.getItem(ACCEESS_TOKEN); + setToken(token); + }, []); + + return useQuery({ + queryKey: [GET_CHANNELS_BY_ID, channelId, owner], + queryFn: () => getChannelById(channelId, token, owner), + staleTime: 60 * 1000, + enabled: Boolean(channelId) && Boolean(token) && Boolean(owner), + }); +}; + +export default useGetChannelById; diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/_components/ActionButtons.module.css b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/_components/ActionButtons.module.css new file mode 100644 index 00000000..59f52798 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/_components/ActionButtons.module.css @@ -0,0 +1,60 @@ +.actionButtons { + width: 100%; + display: flex; + flex-direction: column; + gap: 1rem; +} + +.actionButton { + display: flex; + justify-content: center; + align-items: center; + border: none; + border-radius: 12px; + padding: 1.5rem 1.5rem; + font-size: 1rem; + gap: 0.5rem; + cursor: pointer; + color: #2f2b2b; + background: white; + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.35); + transition: background 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease; +} + +.actionButton:hover { + background: "#f8f3d7"; + box-shadow: 0 6px 8px rgba(0, 0, 0, 0.15); +} + +.actionButton:active { + transform: scale(0.98); + box-shadow: 0 3px 4px rgba(0, 0, 0, 0.1); +} + +.buttonText { + font-weight: 500; +} + +.emptyState { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 2rem; + border: 2px dashed #ddd; + border-radius: 12px; + background-color: #fffefa; + box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.05); + text-align: center; +} +.emptyTitle { + font-size: 1.125rem; + font-weight: 600; + color: #5f3f20; + margin-bottom: 0.5rem; +} +.emptyDescription { + font-size: 0.95rem; + color: #8d6b3e; + line-height: 1.4; +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/_components/ActionButtons.tsx b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/_components/ActionButtons.tsx new file mode 100644 index 00000000..e8247351 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/_components/ActionButtons.tsx @@ -0,0 +1,108 @@ +import React from "react"; +import styles from "./ActionButtons.module.css"; +import { ChannelFeatureButton } from "@model/channel"; +import { + FaUsers, + FaGamepad, + FaComments, + FaBoxOpen, + FaPenNib, + FaPencilAlt, +} from "react-icons/fa"; +import { useRouter } from "next/navigation"; + +interface Props { + visible_components: Array; + channelId: string | string[] | undefined; + brandId: number; +} + +export default function ActionButtons({ + visible_components, + channelId, + brandId, +}: Props) { + const router = useRouter(); + if (visible_components?.length === 0) + return ( +
+

์ปจํ…์ธ ๋ฅผ ์ค€๋น„ ์ค‘์ž…๋‹ˆ๋‹ค.

+ ์ž ์‹œ๋งŒ ๊ธฐ๋‹ค๋ ค์ฃผ์„ธ์š”. +
+ ); + + const handleButtonClick = (component: string, brandId?: number) => { + if (!channelId) return; + let url = `./${channelId}/${component}`; + + if (brandId) { + url += `?brandId=${brandId}`; + } + + router.push(url); + }; + + return ( +
+ {visible_components.includes(ChannelFeatureButton.GROUP) && ( + + )} + + {visible_components.includes(ChannelFeatureButton.QUESTION_CARD) && ( + + )} + + {visible_components.includes(ChannelFeatureButton.MATCHLOG) && ( + + )} + + {visible_components.includes(ChannelFeatureButton.REVIEW_FORM) && ( + + )} + + {visible_components.includes(ChannelFeatureButton.REVIEW_LIST) && ( + + )} + + {visible_components.includes(ChannelFeatureButton.WRITE_REVIEW) && ( + + )} +
+ ); +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/_components/ChannelInfo.module.css b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/_components/ChannelInfo.module.css new file mode 100644 index 00000000..62cc18a8 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/_components/ChannelInfo.module.css @@ -0,0 +1,7 @@ +.cardContainer { + display: flex; + flex-direction: column; + border-radius: 12px; + width: 100%; + gap: 1.5rem; +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/_components/ChannelInfo.tsx b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/_components/ChannelInfo.tsx new file mode 100644 index 00000000..b21c6a18 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/_components/ChannelInfo.tsx @@ -0,0 +1,47 @@ +"use client"; +import React from "react"; +import { useParams } from "next/navigation"; +import styles from "./ChannelInfo.module.css"; +import Thumbnail from "./Thumbnail"; +import ActionButtons from "./ActionButtons"; +import Details from "./Details"; +import useCookie from "@util/hooks/useCookie"; +import { OWNER } from "@constants/auth"; +import useGetChannelById from "../_api/useGetChannelById"; +import ErrorBox from "@ui/components/Error/ErrorBox"; +import LoadingSpinner from "react-spinners/FadeLoader"; +import HeaderConfigurator from "@ui/components/Header/HeaderConfigurator"; + +export default function ChannelInfo() { + const { channelId } = useParams(); + const owner = useCookie(OWNER); + const { data, isError, isLoading, refetch } = useGetChannelById( + channelId, + owner + ); + + if (isLoading) return ; + if (isError) return ; + if (!data) return
๋ฐ์ดํ„ฐ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค
; + + return ( + <> + +
+ + +
+
+ + ); +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/_components/Details.module.css b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/_components/Details.module.css new file mode 100644 index 00000000..9f73f90e --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/_components/Details.module.css @@ -0,0 +1,20 @@ +.channelInfo { + background-color: #fdf7ed; + border-radius: 12px; + padding: 1.5rem; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); +} +.channelInfoContent { + display: flex; + flex-direction: column; + gap: 8px; +} +.channelName { + font-size: 1.25rem; + font-weight: bold; + color: #5f3f20; +} +.channelDescription { + color: #8d6b3e; + line-height: 1.4; +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/_components/Details.tsx b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/_components/Details.tsx new file mode 100644 index 00000000..d4deed42 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/_components/Details.tsx @@ -0,0 +1,19 @@ +import React from "react"; +import styles from "./Details.module.css"; +interface Props { + title: string; + description: string; +} + +export default function Details({ title, description }: Props) { + if (!title && !description) return <>๋ฐ์ดํ„ฐ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.; + + return ( +
+
+

{title}

+ {description} +
+
+ ); +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/_components/Thubnail.module.css b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/_components/Thubnail.module.css new file mode 100644 index 00000000..9c72817a --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/_components/Thubnail.module.css @@ -0,0 +1,8 @@ +.thumbnail { + width: 400px; + height: 400px; + height: auto; + border-radius: 16px; + margin-bottom: 1rem; + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/_components/Thumbnail.tsx b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/_components/Thumbnail.tsx new file mode 100644 index 00000000..4c1418e0 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/_components/Thumbnail.tsx @@ -0,0 +1,26 @@ +"use client"; + +import React from "react"; +import styles from "./Thubnail.module.css"; +import useGetBrandById from "../../../_api/useGetBrandById"; +import Image from "@ui/components/Image/OptimizedNextImage"; + +interface Props { + brandId: number; + owner: string; +} + +export default function Thumbnail({ brandId, owner }: Props) { + const { data } = useGetBrandById(brandId, owner); + const thumbnailImage = data?.thumbnailImage; + + if (!thumbnailImage) return null; + + return ( + ์†Œ์…œ๋ง ์ธ๋„ค์ผ ์ด๋ฏธ์ง€ + ); +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/contents/[categoryId]/_api/useGetQuestionCards.ts b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/contents/[categoryId]/_api/useGetQuestionCards.ts new file mode 100644 index 00000000..44e8a333 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/contents/[categoryId]/_api/useGetQuestionCards.ts @@ -0,0 +1,57 @@ +import { useState, useEffect } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { GET_QUESTION_CATEGORIES } from "@/app/_constant/queryKeys"; +import { ACCEESS_TOKEN } from "@constants/auth"; +import { serverUrl } from "@/app/_constant/config"; + +export interface GetQuestionCardsOutput { + cardCategoryId: number; + name: string; + isCardVisible: boolean; + id: number; + image: { + id: number; + url: string; + }; +} + +export const getQuestionCards = async ( + categoryId: string | string[] | undefined, + token: string | null, + owner: string | null +): Promise> => { + const response = await fetch( + `${serverUrl}/customers/questionCards?categoryId=${categoryId}`, + { + method: "GET", + headers: { + Authorization: `Bearer ${token}`, + Owner: owner || "", + }, + cache: "no-store", + } + ); + if (!response.ok) { + throw new Error("Failed to fetch question cards"); + } + return response.json(); +}; + +const useGetQuestionCards = ( + categoryId: string | string[] | undefined, + owner: string +) => { + const [token, setToken] = useState(null); + + useEffect(() => { + setToken(localStorage.getItem(ACCEESS_TOKEN)); + }, []); + + return useQuery, Error>({ + queryKey: [GET_QUESTION_CATEGORIES, categoryId, owner], + queryFn: () => getQuestionCards(categoryId, token, owner), + enabled: Boolean(categoryId) && Boolean(token) && Boolean(owner), + }); +}; + +export default useGetQuestionCards; diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/contents/[categoryId]/_components/Card.module.css b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/contents/[categoryId]/_components/Card.module.css new file mode 100644 index 00000000..6e954e27 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/contents/[categoryId]/_components/Card.module.css @@ -0,0 +1,22 @@ +.container { + position: relative; + width: 100%; + cursor: pointer; + padding: 0px 16px; + + height: 100vh; +} + +.noData { + text-align: center; + margin-top: 20px; + color: #888; +} + +.swipeHint { + text-align: center; + margin-top: 8px; + font-size: 0.875rem; + color: #666; + opacity: 0.8; +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/contents/[categoryId]/_components/Card.tsx b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/contents/[categoryId]/_components/Card.tsx new file mode 100644 index 00000000..0f38cb67 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/contents/[categoryId]/_components/Card.tsx @@ -0,0 +1,57 @@ +"use client"; + +import React from "react"; +import { useParams, useSearchParams } from "next/navigation"; +import styles from "./Card.module.css"; +import Carousel from "@ui/components/Swiper/SwiperSlide"; +import useCookie from "@util/hooks/useCookie"; +import { OWNER } from "@constants/auth"; +import useGetQuestionCards from "../_api/useGetQuestionCards"; +import Loading from "@ui/components/Spinner/FadeLoader"; +import ErrorBox from "@ui/components/Error/ErrorBox"; +import HeaderConfigurator from "@ui/components/Header/HeaderConfigurator"; + +export default function Card() { + const owner = useCookie(OWNER); + const { categoryId } = useParams(); + const categoryName = useSearchParams().get("categoryName"); + + const { + data: questionCards, + isLoading, + isError, + refetch, + } = useGetQuestionCards(categoryId, owner); + + const filteredImages = questionCards + ?.filter((card) => card.isCardVisible) + .map((card) => ({ id: card.id, thumbnail: card?.image?.url })); + + if (isLoading) return ; + if (isError) return ; + + if (!questionCards?.length) + return
์•„์ง ์ปจํ…์ธ ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.
; + + return ( + <> + + +
+ {filteredImages && filteredImages.length > 0 && ( + + )} + + {filteredImages && filteredImages.length >= 2 && ( +
์˜†์œผ๋กœ ์Šค์™€์ดํ”„ํ•ด๋ณด์„ธ์š”
+ )} +
+ + ); +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/contents/[categoryId]/page.tsx b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/contents/[categoryId]/page.tsx new file mode 100644 index 00000000..8e9f2939 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/contents/[categoryId]/page.tsx @@ -0,0 +1,10 @@ +import React from "react"; +import Card from "./_components/Card"; + +export default function page() { + return ( +
+ +
+ ); +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/contents/_api/useGetQuestionCard.ts b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/contents/_api/useGetQuestionCard.ts new file mode 100644 index 00000000..258f9ff8 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/contents/_api/useGetQuestionCard.ts @@ -0,0 +1,55 @@ +import { useState, useEffect } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { GET_QUESTION_CATEGORIES } from "@/app/_constant/queryKeys"; +import { serverUrl } from "@/app/_constant/config"; +import { ACCEESS_TOKEN } from "@constants/auth"; + +export interface Response { + id: number; + name: string; + coverImage: { id: number; url: string }; + isDeckVisible: boolean; +} + +export const getQuestionCardCategories = async ( + brandId: string | null, + token: string | null, + owner: string | null +): Promise> => { + const response = await fetch( + `${serverUrl}/customers/questionCardCategories?brandId=${brandId}`, + { + method: "GET", + headers: { + Authorization: `Bearer ${token}`, + Owner: owner || "", + }, + cache: "no-store", + } + ); + + if (!response.ok) { + throw new Error("Failed to fetch question card categories"); + } + + return response.json(); +}; + +const useGetQuestionCardCategories = ( + brandId: string | null, + owner: string | null +) => { + const [token, setToken] = useState(null); + + useEffect(() => { + setToken(localStorage.getItem(ACCEESS_TOKEN)); + }, []); + + return useQuery, Error>({ + queryKey: [GET_QUESTION_CATEGORIES, brandId, owner], + queryFn: () => getQuestionCardCategories(brandId, token, owner), + enabled: Boolean(brandId) && Boolean(token) && Boolean(owner), + }); +}; + +export default useGetQuestionCardCategories; diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/contents/_components/ContentBox.module.css b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/contents/_components/ContentBox.module.css new file mode 100644 index 00000000..5925ab68 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/contents/_components/ContentBox.module.css @@ -0,0 +1,42 @@ +.container { + height: 100vh; + padding: 0px 16px; +} + +.wrapper { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; +} + +.coverImage { + width: 100%; + height: 350px; +} + +.cardWrapper { + position: relative; + overflow: hidden; + border-radius: 12px; + box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.3); + transition: transform 0.3s ease, box-shadow 0.3s ease; + cursor: pointer; +} + +.cardWrapper:hover { + transform: translateY(-5px); + box-shadow: 0px 8px 16px rgba(0, 0, 0, 0.25); +} + +.cardLabel { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + padding: 8px 0; + background: rgba(0, 0, 0, 0.6); + color: #fff; + text-align: center; + font-size: 1rem; + font-weight: bold; +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/contents/_components/ContentBox.tsx b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/contents/_components/ContentBox.tsx new file mode 100644 index 00000000..ac685f54 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/contents/_components/ContentBox.tsx @@ -0,0 +1,61 @@ +"use client"; + +import React from "react"; +import styles from "./ContentBox.module.css"; +import { useRouter, useSearchParams } from "next/navigation"; +import useGetQuestionCardCategories from "../_api/useGetQuestionCard"; +import useCookie from "@util/hooks/useCookie"; +import { OWNER } from "@constants/auth"; +import OptimizedNextImage from "@ui/components/Image/OptimizedNextImage"; +import HeaderConfigurator from "@ui/components/Header/HeaderConfigurator"; + +export default function ContentBox() { + const router = useRouter(); + + const searchParams = useSearchParams(); + const brandId = searchParams.get("brandId"); + + const owner = useCookie(OWNER); + + const { data: questionCardCategories, refetch } = + useGetQuestionCardCategories(brandId, owner); + + const handleCardClick = (categoryId: string, categoryName: string) => { + router.push(`./contents/${categoryId}?categoryName=${categoryName}`); + }; + + return ( + <> + + +
+
+ {questionCardCategories + ?.filter((category) => category.isDeckVisible) + .map((category) => ( +
+ handleCardClick(String(category.id), category.name) + } + > + +
{category.name}
+
+ ))} +
+
+ + ); +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/contents/page.module.css b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/contents/page.module.css new file mode 100644 index 00000000..6a9d10ab --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/contents/page.module.css @@ -0,0 +1,7 @@ +.sectionContainer { + max-width: 430px; + margin: 0 auto 40px; + background-color: #ffffff; + border-radius: 12px; + white-space: pre-line; +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/contents/page.tsx b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/contents/page.tsx new file mode 100644 index 00000000..3abb77a0 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/contents/page.tsx @@ -0,0 +1,14 @@ +import React from "react"; +import ContentBox from "./_components/ContentBox"; +import OwnerCookieSetter from "@util/hooks/OwnerCookieSetter"; +import RequireAuth from "@/app/_components/RequireAuth"; + +export default function page() { + return ( + <> + + + ; + + ); +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/_api/useCreateReview.ts b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/_api/useCreateReview.ts new file mode 100644 index 00000000..d8078f28 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/_api/useCreateReview.ts @@ -0,0 +1,62 @@ +import { useEffect, useState } from "react"; +import { useMutation } from "@tanstack/react-query"; +import { serverUrl } from "@/app/_constant/config"; +import { ACCEESS_TOKEN } from "@constants/auth"; + +export interface RequestBody { + target_user_id: number | null; + reviewer_user_id: number | null; + channel_id: number; + style: string; + impression: string; + conversation: string; + additional_info: string; + keywords: string; + is_reviewer_anonymous: boolean; +} + +export const createReview = async ( + requestBody: RequestBody, + owner: string, + token: string | null +) => { + const response = await fetch(`${serverUrl}/customers/review`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + Owner: owner, + }, + body: JSON.stringify(requestBody), + cache: "no-cache", + }); + + if (!response.ok) { + throw new Error("Failed to regist form"); + } + + const result = await response.json(); + + return result; +}; + +const useCreateReview = (owner: string) => { + const [token, setToken] = useState(null); + + useEffect(() => { + const token = localStorage.getItem(ACCEESS_TOKEN); + setToken(token); + }, []); + + return useMutation< + Response, + Error, + { + requestBody: RequestBody; + } + >({ + mutationFn: ({ requestBody }) => createReview(requestBody, owner, token), + }); +}; + +export default useCreateReview; diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/_api/useGetGroups.ts b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/_api/useGetGroups.ts new file mode 100644 index 00000000..b53ab211 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/_api/useGetGroups.ts @@ -0,0 +1,45 @@ +import { useQuery } from "@tanstack/react-query"; + +import { GET_GROUPS } from "@/app/_constant/queryKeys"; +import { useState, useEffect } from "react"; +import { Group } from "@model/channel/group"; +import { serverUrl } from "@/app/_constant/config"; +import { ACCEESS_TOKEN } from "@constants/auth"; + +export const getGroups = async ( + channel_id: string | string[] | undefined, + token: string | null +): Promise> => { + const response = await fetch( + `${serverUrl}/customers/groups?channel_id=${channel_id}`, + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ); + if (!response.ok) { + throw new Error("Failed to fetch groups"); + } + return response.json(); +}; + +const useGetGroups = ( + channelId: string | string[] | undefined, + owner: string | null +) => { + const [token, setToken] = useState(null); + + useEffect(() => { + const tok = localStorage.getItem(ACCEESS_TOKEN); + setToken(tok); + }, []); + + return useQuery, Error>({ + queryKey: [GET_GROUPS, channelId, owner], + queryFn: () => getGroups(channelId!, token), + enabled: Boolean(channelId) && Boolean(token), + }); +}; + +export default useGetGroups; diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/_components/CheckboxOptions.module.css b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/_components/CheckboxOptions.module.css new file mode 100644 index 00000000..b928f321 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/_components/CheckboxOptions.module.css @@ -0,0 +1,39 @@ +.questionSection { + margin-bottom: 32px; + padding: 16px 0; + background-color: #fff; + border-radius: 8px; +} + +.label { + font-size: 1.2em; + font-weight: 500; + margin-bottom: 12px; + color: #2d3748; + line-height: 1.4; +} + +.requiredMark { + color: red; +} + +.errorMessage { + color: red; + font-size: 0.9rem; + margin-top: 4px; +} + +.checkboxLabel { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 16px; + margin-bottom: 10px; + border: 1px solid #e2e8f0; + border-radius: 8px; + background-color: #f7fafc; + cursor: pointer; + font-size: 1rem; + line-height: 1.5; + transition: background-color 0.2s, border-color 0.2s; +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/_components/CheckboxOptions.tsx b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/_components/CheckboxOptions.tsx new file mode 100644 index 00000000..66779189 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/_components/CheckboxOptions.tsx @@ -0,0 +1,43 @@ +import React from "react"; +import { ReviewField } from "../_model"; +import styles from "./CheckboxOptions.module.css"; +import { useReviewStore } from "../_store/useReviewStore"; + +interface Props { + options: string[]; + field: "style" | "impression" | "conversation" | "keywords"; + label: string; + refProp?: React.Ref; + errors: Partial>; + state: Array; +} + +export default function CheckboxOptions({ + options, + field, + label, + refProp, + errors, + state, +}: Props) { + const { toggleField } = useReviewStore(); + return ( +
+

+ {label} * +

+ {errors[field] &&

{errors[field]}

} + {options.map((opt) => ( + + ))} +
+ ); +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/_components/Form.module.css b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/_components/Form.module.css new file mode 100644 index 00000000..25328374 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/_components/Form.module.css @@ -0,0 +1,166 @@ +.container { + padding: 0px 16px; +} + +.questionSection { + margin-bottom: 32px; + padding: 16px 0; + background-color: #fff; + border-radius: 8px; +} + +.label { + font-size: 1.2em; + font-weight: 500; + margin-bottom: 12px; + color: #2d3748; + line-height: 1.4; +} + +.description { + font-size: 0.9rem; + font-weight: 500; + margin-bottom: 12px; + color: #2d3748; + line-height: 1.4; +} + +.requiredMark { + color: red; +} + +.errorMessage { + color: red; + font-size: 0.9rem; + margin-top: 4px; +} + +.select { + width: 100%; + padding: 14px 16px; + border: 1px solid #cbd5e0; + border-radius: 8px; + background-color: #f7fafc; + font-size: 1rem; + color: #2d3748; + transition: border-color 0.2s; +} + +.select:focus { + outline: none; + border-color: #3182ce; +} + +.checkboxLabel { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 16px; + margin-bottom: 10px; + border: 1px solid #e2e8f0; + border-radius: 8px; + background-color: #f7fafc; + cursor: pointer; + font-size: 1rem; + line-height: 1.5; + transition: background-color 0.2s, border-color 0.2s; +} + +.checkboxLabel:hover { + background-color: #edf2f7; + border-color: #cbd5e0; +} + +.checkboxLabel input { + min-width: 18px; + min-height: 18px; +} + +.keywordContainer { + display: flex; + flex-wrap: wrap; + gap: 12px; +} + +.keywordButton { + padding: 10px 18px; + border: 1px solid #cbd5e0; + border-radius: 20px; + background-color: #f7fafc; + color: #2d3748; + cursor: pointer; + font-size: 0.95rem; + transition: background-color 0.2s, color 0.2s; +} + +.keywordButton:hover { + background-color: #edf2f7; +} + +.keywordButtonSelected { + background-color: #3182ce; + color: #ffffff; +} + +.keywordButtonSelected:hover { + background-color: #2b6cb0; +} + +.input { + width: 100%; + padding: 14px 16px; + border: 1px solid #cbd5e0; + border-radius: 8px; + background-color: #f7fafc; + font-size: 1rem; + color: #2d3748; + transition: border-color 0.2s; +} + +.input:focus { + outline: none; + border-color: #3182ce; +} + +.radioContainer { + display: flex; + justify-content: space-around; + margin-top: 8px; +} + +.radioLabel { + display: flex; + align-items: center; + gap: 8px; + font-size: 1rem; + cursor: pointer; + padding: 1rem; + border: 1px solid #cbd5e0; + border-radius: 20px; + background-color: #f7fafc; +} + +.radioLabel input { + cursor: pointer; +} + +.buttonWrapper { + padding: 0 1rem; +} + +.submitButton { + width: 100%; + background-color: #3182ce; + color: #ffffff; + font-weight: 600; + font-size: 1.1rem; + padding: 16px; + border: none; + border-radius: 8px; + cursor: pointer; + transition: background-color 0.2s; +} + +.submitButton:hover { + background-color: #2b6cb0; +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/_components/Form.tsx b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/_components/Form.tsx new file mode 100644 index 00000000..f0efbbec --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/_components/Form.tsx @@ -0,0 +1,298 @@ +"use client"; + +import React, { useRef, useState } from "react"; +import { useRouter } from "next/navigation"; +import styles from "./Form.module.css"; +import { ReviewField } from "../_model"; +import CheckboxOptions from "./CheckboxOptions"; +import TextInputSection from "./TextInputSection"; + +import { + styleOptions, + firstImpressionOptions, + memorablePartOptions, + keywordOptions, +} from "../_constants"; +import { useReviewStore } from "../_store/useReviewStore"; +import useGetGroups from "../_api/useGetGroups"; +import { useParams } from "next/navigation"; +import useCookie from "@util/hooks/useCookie"; +import { OWNER } from "@constants/auth"; +import { useSystemModalStore } from "@ui/store/useSystemModalStore"; +import pathnames from "@/app/_constant/pathnames"; +import useCreateReview from "../_api/useCreateReview"; +import { USER_DATA } from "@/app/_constant/auth"; + +export default function Form() { + const router = useRouter(); + const { channelId } = useParams(); + const owner = useCookie(OWNER); + + const user = JSON.parse(localStorage.getItem(USER_DATA) || ""); + const { data } = useGetGroups(channelId, owner); + const { mutate: createReview } = useCreateReview(owner); + const groupMembers = data?.[0]?.joined_users || []; + const { open } = useSystemModalStore(); + + const { + setField, + toggleField, + selectedParticipant, + style, + impression, + conversation, + keywords, + instagram, + kakao, + phoneNumber, + isAnonymous, + } = useReviewStore(); + const [errors, setErrors] = useState< + Partial> + >({}); + + const participantRef = useRef(null); + const styleRef = useRef(null); + const impressionRef = useRef(null); + const conversationRef = useRef(null); + const keywordRef = useRef(null); + const isAnonymousRef = useRef(null); + + const removeError = (field: keyof ReviewField) => { + if (errors[field]) { + setErrors((prev) => { + const o = { ...prev }; + delete o[field]; + return o; + }); + } + }; + + const handleCheckboxToggle = ( + field: "style" | "impression" | "conversation" | "keywords", + value: string + ) => { + removeError(field); + toggleField(field, value); + }; + + const handleInputChange = + (field: keyof ReviewField) => (e: React.ChangeEvent) => { + removeError(field); + setField(field, e.target.value); + }; + + const handleRadioChange = (value: boolean) => { + removeError("isAnonymous"); + setField("isAnonymous", value); + }; + + const handleSubmit = () => { + const newErr: Partial> = {}; + if (!selectedParticipant) + newErr.selectedParticipant = "ํ•„์ˆ˜ ์ž…๋ ฅ๊ฐ’ ์ž…๋‹ˆ๋‹ค."; + if (!style.length) newErr.style = "ํ•„์ˆ˜ ์ž…๋ ฅ๊ฐ’ ์ž…๋‹ˆ๋‹ค."; + if (!impression.length) newErr.impression = "ํ•„์ˆ˜ ์ž…๋ ฅ๊ฐ’ ์ž…๋‹ˆ๋‹ค."; + if (!conversation.length) newErr.conversation = "ํ•„์ˆ˜ ์ž…๋ ฅ๊ฐ’ ์ž…๋‹ˆ๋‹ค."; + if (!keywords.length) newErr.keywords = "ํ•„์ˆ˜ ์ž…๋ ฅ๊ฐ’ ์ž…๋‹ˆ๋‹ค."; + if (isAnonymous === undefined) newErr.isAnonymous = "ํ•„์ˆ˜ ์„ ํƒ๊ฐ’ ์ž…๋‹ˆ๋‹ค."; + + if (Object.keys(newErr).length) { + setErrors(newErr); + const scrollMap: Record> = { + selectedParticipant: participantRef, + style: styleRef, + impression: impressionRef, + conversation: conversationRef, + keywords: keywordRef, + isAnonymous: isAnonymousRef, + }; + const first = Object.keys(newErr)[0] as keyof ReviewField; + scrollMap[first]?.current?.scrollIntoView({ behavior: "smooth" }); + return; + } + + const additional_info = `${instagram ? `Instagram: ${instagram}, ` : ""}${ + kakao ? `Kakao: ${kakao}, ` : "" + }${phoneNumber ? `Phone: ${phoneNumber}` : ""}`; + + createReview( + { + requestBody: { + target_user_id: Number(selectedParticipant) || null, + reviewer_user_id: user?.id || null, + channel_id: Number(channelId), + style: style.join(", "), + impression: impression.join(", "), + conversation: conversation.join(", "), + keywords: keywords.join(", "), + additional_info, + is_reviewer_anonymous: isAnonymous, + }, + }, + { + onSuccess: () => { + open({ + isOpen: true, + title: "ํ”ผ๋“œ๋ฐฑ ์ œ์ถœ ์™„๋ฃŒ", + message: "๊ณ„์† ์ž‘์„ฑํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?", + showCancel: true, + onConfirm: () => { + setErrors({}); + window.scrollTo({ top: 0, behavior: "smooth" }); + }, + onCancel: () => router.push(`${pathnames.detail}/${channelId}`), + }); + }, + } + ); + }; + + return ( +
+
+

+ ์ฐธ์—ฌ์ž ์„ ํƒ * +

+ {errors.selectedParticipant && ( +

{errors.selectedParticipant}

+ )} + +
+ + {/* ์Šคํƒ€์ผ, ์ฒซ์ธ์ƒ, ๋Œ€ํ™” ๋‚ด์šฉ */} + + + + + + + {/* ํ‚ค์›Œ๋“œ */} +
+

+ ๊ทธ๋ถ„๊ณผ ์–ด์šธ๋ฆฌ๋Š” ํ‚ค์›Œ๋“œ๋ฅผ ๊ณจ๋ผ์ฃผ์„ธ์š”{" "} + * +

+ {errors.keywords && ( +

{errors.keywords}

+ )} +
+ {keywordOptions.map((kw) => { + const sel = keywords.includes(kw); + return ( + + ); + })} +
+
+ + {/* ์ต๋ช… ์—ฌ๋ถ€ */} +
+

์„ค๋ฌธ ๊ฒฐ๊ณผ๋ฅผ ์ต๋ช…์œผ๋กœ ํ•˜์‹œ๊ฒ ์–ด์š”?

+ {errors.isAnonymous && ( +

{errors.isAnonymous}

+ )} +
+ + +
+
+ + {/* ์ถ”๊ฐ€ ์—ฐ๋ฝ์ฒ˜ */} +
+

์•ž์œผ๋กœ ์นœํ•˜๊ฒŒ ์ง€๋‚ด์š”!

+

+ ์•ž์œผ๋กœ๋„ ์นœํ•˜๊ฒŒ ์ง€๋‚ด๊ณ  ์‹ถ๋‹ค๋ฉด ์ธ์Šคํƒ€๊ทธ๋žจ, ์นด์นด์˜คํ†ก ๋˜๋Š” ์ „ํ™”๋ฒˆํ˜ธ๋ฅผ + ๋‚จ๊ฒจ์ฃผ์„ธ์š”. ์„œ๋กœ ์—ฐ๋ฝํ•˜๋ฉฐ ๋” ์ข‹์€ ์ธ์—ฐ์„ ์ด์–ด๊ฐˆ ์ˆ˜ ์žˆ์„ ๊ฑฐ์˜ˆ์š”! ๐Ÿ˜Š +
+ (์ž…๋ ฅ์€ ์„ ํƒ์‚ฌํ•ญ์ž…๋‹ˆ๋‹ค.) +

+ + + + +
+ +
+ +
+
+ ); +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/_components/TextInputSection.module.css b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/_components/TextInputSection.module.css new file mode 100644 index 00000000..8c5c942e --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/_components/TextInputSection.module.css @@ -0,0 +1,30 @@ +.questionSection { + margin-bottom: 32px; + padding: 16px 0; + background-color: #fff; + border-radius: 8px; +} + +.label { + font-size: 1.2em; + font-weight: 500; + margin-bottom: 12px; + color: #2d3748; + line-height: 1.4; +} + +.input { + width: 100%; + padding: 14px 16px; + border: 1px solid #cbd5e0; + border-radius: 8px; + background-color: #f7fafc; + font-size: 1rem; + color: #2d3748; + transition: border-color 0.2s; +} + +.input:focus { + outline: none; + border-color: #3182ce; +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/_components/TextInputSection.tsx b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/_components/TextInputSection.tsx new file mode 100644 index 00000000..1b51f6c3 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/_components/TextInputSection.tsx @@ -0,0 +1,31 @@ +import React from "react"; +import styles from "./TextInputSection.module.css"; +import { ReviewField } from "../_model"; + +interface Props { + label: string; + field: keyof ReviewField; + state: string; + onChange: ( + field: keyof ReviewField + ) => (e: React.ChangeEvent) => void; +} + +export default function TextInputSection({ + label, + field, + state, + onChange, +}: Props) { + return ( +
+

{label}

+ +
+ ); +} diff --git a/frontend/src/constants/consumer/channel/reviewOption.ts b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/_constants/index.ts similarity index 66% rename from frontend/src/constants/consumer/channel/reviewOption.ts rename to frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/_constants/index.ts index 14d2779c..6d29ff85 100644 --- a/frontend/src/constants/consumer/channel/reviewOption.ts +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/_constants/index.ts @@ -1,4 +1,16 @@ -import { Review } from "interfaces/channels"; +import { ReviewField } from "../_model"; + +export const initialState: ReviewField = { + selectedParticipant: "", + style: [], + impression: [], + conversation: [], + keywords: [], + instagram: "", + kakao: "", + phoneNumber: "", + isAnonymous: false, // ๊ธฐ๋ณธ๊ฐ’: ์ต๋ช… ์•„๋‹˜ +}; export const styleOptions = [ "[ํฌ๋ฉ€] \n ๊น”๋”ํ•˜๊ณ  ์„ธ๋ จ๋œ ์ •์žฅ ๋˜๋Š” ์˜คํ”ผ์Šค๋ฃฉ", @@ -81,38 +93,3 @@ export const keywordOptions = [ "๋‹จ์ •ํ•œ", "์ด์œ", ]; - -export const noReviewUsers: Array = [ - { - is_reviewer_anonymous: true, - channel_id: 10, - style: - "[์บ์ฃผ์–ผ]\n ํŽธ์•ˆํ•˜๊ณ  ์ผ์ƒ์ ์ธ ๋ณต์žฅ, [๋…ธ๋ฉ€]\n ํŠ€์ง€ ์•Š๊ณ  ๋ฌด๋‚œํ•œ ์Šคํƒ€์ผ, [๋ฏธ๋‹ˆ๋ฉ€]\n ๋‹จ์ˆœํ•˜๊ณ  ์‹ฌํ”Œํ•œ ๋””์ž์ธ", - impression: - "์ฐจ๋ถ„ํ•˜๊ณ  ์ง„์ง€ํ•จ:\n ์‹ ์ค‘ํ•˜๋ฉด์„œ ์•ˆ์ •๊ฐ์ด ๋А๊ปด์ง, ๋”ฐ๋œปํ•˜๊ณ  ๋ฐฐ๋ ค์‹ฌ ์žˆ์Œ:\n ๋ถ€๋“œ๋Ÿฝ๊ณ  ํฌ์šฉ๋ ฅ ์žˆ๋Š” ๋А๋‚Œ", - conversation: - "์ด์•ผ๊ธฐ ๋‚ด์šฉ๊ณผ ํฅ๋ฏธ:\n์ด์•ผ๊ธฐ์˜ ์ฃผ์ œ๋‚˜ ๋‚ด์šฉ์ด ํฅ๋ฏธ๋กญ๊ณ  ์‚ฌ๋žŒ์˜ ๊ด€์‹ฌ์„ ๋Œ์—ˆ์Œ. ์ƒˆ๋กœ์šด ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜๊ฑฐ๋‚˜ ํฅ๋ฏธ๋กœ์šด ๊ด€์ ์„ ์ œ์‹œํ•จ, ๊ณต๊ฐํ•˜๊ณ  ๊ฒฝ์ฒญํ•˜๋Š” ํƒœ๋„:\n ์ƒ๋Œ€๋ฐฉ์˜ ๋ง์— ์ง‘์ค‘ํ•˜๊ณ  ์ง„์ง€ํ•˜๊ฒŒ ๊ฒฝ์ฒญํ•˜๋ฉฐ ๊ณต๊ฐ์„ ํ‘œํ˜„ํ•จ. ์ƒ๋Œ€๋ฐฉ์ด ์กด์ค‘๋ฐ›๊ณ  ์žˆ๋‹ค๋Š” ๋А๋‚Œ์„ ๋ฐ›์Œ, ํŽธ์•ˆํ•˜๊ณ  ์ž์—ฐ์Šค๋Ÿฌ์›€:\n ๋Œ€ํ™”๊ฐ€ ํŽธ์•ˆํ•˜๊ณ  ์ž์—ฐ์Šค๋Ÿฌ์›Œ ์•„๋ฌด๋Ÿฐ ๋…ธ๋ ฅ ์—†์ด ํ˜๋Ÿฌ๊ฐ”์Œ, ์นœ์ ˆํ•˜๊ณ  ๋ฐฐ๋ ค์‹ฌ ์žˆ๋Š” ๋Œ€ํ™” ํƒœ๋„:\n ์ƒ๋Œ€๋ฐฉ์ด ๋‚˜์˜ ์˜๊ฒฌ์„ ์กด์ค‘ํ•˜๋ฉฐ ๋Œ€ํ™”์— ์ฐธ์—ฌํ•จ", - additional_info: "", - keywords: "ํŽธ์•ˆํ•จ, ๋”ฐ๋“ฏํ•จ, ํ‰ํ™”๋กœ์›€", - id: 353, - created_at: "2025-03-10T08:02:41.510367", - reviewer_user_gender: "anonymous", - reviewer_user_name: "anonymous", - }, - { - is_reviewer_anonymous: true, - channel_id: 10, - style: - "[๋…ธ๋ฉ€]\n ํŠ€์ง€ ์•Š๊ณ  ๋ฌด๋‚œํ•œ ์Šคํƒ€์ผ, [๋ฏธ๋‹ˆ๋ฉ€]\n ๋‹จ์ˆœํ•˜๊ณ  ์‹ฌํ”Œํ•œ ๋””์ž์ธ", - impression: - "์‹ ๋ขฐ๊ฐ ์žˆ์Œ:\n ์•ˆ์ •์ ์ด๊ณ  ๋ฏฟ์„ ์ˆ˜ ์žˆ๋Š” ์ธ์ƒ์„ ์คŒ, ์„ฌ์„ธํ•˜๊ณ  ๊ด€์ฐฐ๋ ฅ ์žˆ์Œ:\n ์ž‘์€ ๋””ํ…Œ์ผ๊นŒ์ง€ ์ž˜ ์บ์น˜ํ•˜๋ฉฐ ์„ธ์‹ฌํ•œ ๋А๋‚Œ์„ ์คŒ", - conversation: - "์ด์•ผ๊ธฐ ๋‚ด์šฉ๊ณผ ํฅ๋ฏธ:\n์ด์•ผ๊ธฐ์˜ ์ฃผ์ œ๋‚˜ ๋‚ด์šฉ์ด ํฅ๋ฏธ๋กญ๊ณ  ์‚ฌ๋žŒ์˜ ๊ด€์‹ฌ์„ ๋Œ์—ˆ์Œ. ์ƒˆ๋กœ์šด ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜๊ฑฐ๋‚˜ ํฅ๋ฏธ๋กœ์šด ๊ด€์ ์„ ์ œ์‹œํ•จ, ํŽธ์•ˆํ•˜๊ณ  ์ž์—ฐ์Šค๋Ÿฌ์›€:\n ๋Œ€ํ™”๊ฐ€ ํŽธ์•ˆํ•˜๊ณ  ์ž์—ฐ์Šค๋Ÿฌ์›Œ ์•„๋ฌด๋Ÿฐ ๋…ธ๋ ฅ ์—†์ด ํ˜๋Ÿฌ๊ฐ”์Œ, ์นœ์ ˆํ•˜๊ณ  ๋ฐฐ๋ ค์‹ฌ ์žˆ๋Š” ๋Œ€ํ™” ํƒœ๋„:\n ์ƒ๋Œ€๋ฐฉ์ด ๋‚˜์˜ ์˜๊ฒฌ์„ ์กด์ค‘ํ•˜๋ฉฐ ๋Œ€ํ™”์— ์ฐธ์—ฌํ•จ, ์ž์‹ ๊ฐ ์žˆ๋Š” ํ‘œํ˜„:\n ์ž์‹ ์˜ ์ƒ๊ฐ์„ ๋ช…ํ™•ํ•˜๊ณ  ์ž์‹  ์žˆ๊ฒŒ ํ‘œํ˜„ํ•จ", - additional_info: "", - keywords: "์ง„์ง€ํ•œ, ๋ฐฐ๋ ค๊นŠ์€, ์นœ์ ˆํ•œ, ํŽธ์•ˆํ•จ, ๋‹จ์ •ํ•œ", - id: 354, - created_at: "2025-03-10T08:02:41.510367", - reviewer_user_gender: "anonymous", - reviewer_user_name: "anonymous", - }, -]; diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/_model/index.ts b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/_model/index.ts new file mode 100644 index 00000000..cb680cd0 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/_model/index.ts @@ -0,0 +1,11 @@ +export interface ReviewField { + selectedParticipant: string; + style: string[]; + impression: string[]; + conversation: string[]; + keywords: string[]; + instagram: string; + kakao: string; + phoneNumber: string; + isAnonymous: boolean; // ์ถ”๊ฐ€๋œ ํ•„๋“œ +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/_store/useReviewStore.ts b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/_store/useReviewStore.ts new file mode 100644 index 00000000..16582296 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/_store/useReviewStore.ts @@ -0,0 +1,56 @@ +import { create } from "zustand"; +import { immer } from "zustand/middleware/immer"; +import { ReviewField } from "../_model"; +import { Draft } from "immer"; + +type ReviewActions = { + setField: ( + field: K, + value: ReviewField[K] + ) => void; + toggleField: ( + field: "style" | "impression" | "conversation" | "keywords", + value: string + ) => void; + reset: () => void; +}; + +export const useReviewStore = create()( + immer((set) => ({ + selectedParticipant: "", + style: [], + impression: [], + conversation: [], + keywords: [], + instagram: "", + kakao: "", + phoneNumber: "", + isAnonymous: false, + setField: (field: K, value: ReviewField[K]) => + set((state: Draft) => { + state[field] = value; + }), + + toggleField: (field, value) => + set((state) => { + const arr = state[field]; + if (arr.includes(value)) { + state[field] = arr.filter((v: string) => v !== value); + } else { + arr.push(value); + } + }), + reset: () => + set((state) => { + state.selectedParticipant = ""; + state.style = []; + state.impression = []; + state.conversation = []; + state.keywords = []; + state.instagram = ""; + state.kakao = ""; + state.phoneNumber = ""; + state.isAnonymous = false; + }), + })) +); diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/page.module.css b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/page.module.css new file mode 100644 index 00000000..6a9d10ab --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/page.module.css @@ -0,0 +1,7 @@ +.sectionContainer { + max-width: 430px; + margin: 0 auto 40px; + background-color: #ffffff; + border-radius: 12px; + white-space: pre-line; +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/page.tsx b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/page.tsx new file mode 100644 index 00000000..3c49f2ea --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/feedback/page.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import styles from "./page.module.css"; +import Form from "./_components/Form"; +import HeaderConfigurator from "@ui/components/Header/HeaderConfigurator"; +import OwnerCookieSetter from "@util/hooks/OwnerCookieSetter"; +import RequireAuth from "@/app/_components/RequireAuth"; + +export default function page() { + return ( + <> + + + +
+
+
+ + ); +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/game/_api/useGetMatchedUser.ts b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/game/_api/useGetMatchedUser.ts new file mode 100644 index 00000000..9ecd6c33 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/game/_api/useGetMatchedUser.ts @@ -0,0 +1,55 @@ +import { useEffect, useState } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { GET_MATCHED_USER } from "@/app/_constant/queryKeys"; + +import { ACCEESS_TOKEN } from "@constants/auth"; +import { serverUrl } from "@/app/_constant/config"; +import { Game } from "@model/channel/game"; +import { User } from "@model/user"; + +export interface Response { + game: Game; + matched_user: User; +} + +export const getMatchedUser = async ( + channel_id: string | string[] | undefined, + token: string | null, + owner: string | null +): Promise> => { + const response = await fetch( + `${serverUrl}/customers/games/matched_user?channel_id=${channel_id}`, + { + headers: { + Authorization: `Bearer ${token}`, + Owner: owner || "", + }, + cache: "no-store", + } + ); + + if (!response.ok) { + throw new Error("Failed to fetch matched users"); + } + return response.json(); +}; + +const useGetMatchedUser = ( + channelId: string | string[] | undefined, + owner: string | null +) => { + const [token, setToken] = useState(null); + + useEffect(() => { + const token = localStorage.getItem(ACCEESS_TOKEN); + setToken(token); + }, []); + + return useQuery({ + queryKey: [GET_MATCHED_USER, channelId, owner], + queryFn: () => getMatchedUser(channelId, token, owner), + enabled: Boolean(channelId) && Boolean(token) && Boolean(owner), + }); +}; + +export default useGetMatchedUser; diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/game/_components/Game.module.css b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/game/_components/Game.module.css new file mode 100644 index 00000000..db203cf1 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/game/_components/Game.module.css @@ -0,0 +1,60 @@ +.container { + display: flex; + flex-direction: column; + align-items: center; +} + +.imageWrapper { + position: relative; + width: 100%; + border-radius: 10px; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3); + display: flex; + flex-direction: column; + gap: 0; + padding: 0; + margin: 0; +} + +.bottomMargin { + margin-bottom: 32px; +} + +.memberImage, +.image { + width: 100%; +} + +.memberCard { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: #fff; + white-space: nowrap; + display: flex; + align-items: center; + padding: 1rem; + border-radius: 10px; + margin-bottom: 1rem; + border: 1px solid #e5e7eb; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3); +} + +.memberGender { + width: 2.5rem; + height: 2.5rem; + border-radius: 50%; + background: #e0f2fe; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.5rem; + margin-right: 1rem; +} + +.memberName { + font-size: 1rem; + font-weight: 500; + color: #111; +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/game/_components/Game.tsx b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/game/_components/Game.tsx new file mode 100644 index 00000000..887c0372 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/game/_components/Game.tsx @@ -0,0 +1,78 @@ +"use client"; + +import React from "react"; +import { useParams } from "next/navigation"; + +import styles from "./Game.module.css"; +import useGetMatchedUser from "../_api/useGetMatchedUser"; +import useCookie from "@util/hooks/useCookie"; +import { OWNER } from "@constants/auth"; + +import explainGameRule0 from "@/app/_asset/game-0.png"; +import explainGameRule1 from "@/app/_asset/game-1.png"; +import explainGameRule2 from "@/app/_asset/game-2.png"; +import ErrorBox from "@ui/components/Error/ErrorBox"; +import Loading from "@ui/components/Spinner/FadeLoader"; +import HeaderConfigurator from "@ui/components/Header/HeaderConfigurator"; +import OptimizedNextImage from "@ui/components/Image/OptimizedNextImage"; + +export default function Game() { + const { channelId } = useParams(); + const owner = useCookie(OWNER); + const { data, isError, isLoading, refetch } = useGetMatchedUser( + channelId, + owner + ); + + if (isError) return ; + if (isLoading) return ; + + const matchedUser = data?.[0]?.matched_user; + const matchedUserName = matchedUser?.username || null; + const matchedUserGender = matchedUser?.gender === "male" ? "๐Ÿ™‹โ€โ™‚๏ธ" : "๐Ÿ™‹โ€โ™€๏ธ"; + + return ( + <> + +
+
+ +
+ {matchedUserName ? ( + <> +
{matchedUserGender}
+ {matchedUserName} + + ) : ( + ??? + )} +
+
+
+ + +
+
+ + ); +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/game/page.tsx b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/game/page.tsx new file mode 100644 index 00000000..6667043a --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/game/page.tsx @@ -0,0 +1,14 @@ +import React from "react"; +import Game from "./_components/Game"; +import OwnerCookieSetter from "@util/hooks/OwnerCookieSetter"; +import RequireAuth from "@/app/_components/RequireAuth"; + +export default function page() { + return ( + <> + + + + + ); +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/group/_api/useGetGroups.ts b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/group/_api/useGetGroups.ts new file mode 100644 index 00000000..d105d582 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/group/_api/useGetGroups.ts @@ -0,0 +1,48 @@ +import { useQuery } from "@tanstack/react-query"; +import { useState, useEffect } from "react"; + +import { GET_GROUPS } from "@/app/_constant/queryKeys"; +import { Group } from "@model/channel/group"; +import { ACCEESS_TOKEN } from "@constants/auth"; +import { serverUrl } from "@/app/_constant/config"; + +export const getGroups = async ( + channelId: string | string[] | undefined, + token: string | null, + owner: string | null +): Promise> => { + const result = await fetch( + `${serverUrl}/customers/groups?channel_id=${channelId}`, + { + method: "GET", + headers: { + Authorization: `Bearer ${token}`, + Owner: owner || "", + }, + cache: "no-store", + } + ); + if (!result.ok) { + throw new Error("Failed to fetch data"); + } + return result.json(); +}; + +const useGetGroups = ( + owner: string, + channelId: string | string[] | undefined +) => { + const [token, setToken] = useState(null); + useEffect(() => { + const token = localStorage.getItem(ACCEESS_TOKEN); + setToken(token); + }, []); + + return useQuery, Error>({ + queryKey: [GET_GROUPS, channelId, owner], + queryFn: () => getGroups(channelId, token, owner), + enabled: Boolean(channelId) && Boolean(token) && Boolean(owner), + }); +}; + +export default useGetGroups; diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/group/_components/Group.module.css b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/group/_components/Group.module.css new file mode 100644 index 00000000..0a0e2111 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/group/_components/Group.module.css @@ -0,0 +1,105 @@ +.container { + display: flex; + flex-direction: column; + align-items: center; + + min-height: 100vh; + padding: 0px 16px; +} + +.noGroup { + min-height: 100vh; +} + +.groupStatus { + width: 100%; + background: #fff; + border-radius: 1rem; + padding: 1rem; + box-shadow: 0 10px 15px rgba(0, 0, 0, 0.3); + margin-bottom: 2rem; +} + +.groupInfo { + display: flex; + align-items: center; + gap: 1rem; +} + +.groupIcon { + width: 3.5rem; + height: 3.5rem; + background: linear-gradient(135deg, #60a5fa, #2563eb); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + color: #fff; +} + +.groupText { + display: flex; + flex-direction: column; + gap: 6px; +} + +.groupName { + font-size: 1.25rem; + font-weight: 600; + color: #111; +} + +.groupCount { + font-size: 1rem; + color: #555; +} + +.membersSection { + width: 100%; + background: #fff; + border-radius: 1rem; + padding: 1rem; + box-shadow: 0 10px 15px rgba(0, 0, 0, 0.3); +} + +.sectionTitle { + font-size: 1.5rem; + font-weight: 700; + color: #333; + margin-bottom: 1.5rem; +} + +.memberCard { + display: flex; + align-items: center; + padding: 1rem; + background: #fff; + border-radius: 10px; + margin-bottom: 1rem; + border: 1px solid #e5e7eb; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.memberCard:hover { + transform: translateY(-3px); + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1); +} + +.memberGender { + width: 2.5rem; + height: 2.5rem; + border-radius: 50%; + background: #e0f2fe; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.5rem; + margin-right: 1rem; +} + +.memberName { + font-size: 1rem; + font-weight: 500; + color: #111; +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/group/_components/Group.tsx b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/group/_components/Group.tsx new file mode 100644 index 00000000..9d4a9aa5 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/group/_components/Group.tsx @@ -0,0 +1,70 @@ +"use client"; + +import React from "react"; +import styles from "./Group.module.css"; +import { FaUsers } from "react-icons/fa"; +import { findMyGroup, getGenderEmoji } from "../_util"; +import useCookie from "@util/hooks/useCookie"; +import { OWNER } from "@constants/auth"; +import { useParams } from "next/navigation"; +import useGetGroups from "../_api/useGetGroups"; +import Loading from "@ui/components/Spinner/FadeLoader"; +import ErrorBox from "@ui/components/Error/ErrorBox"; +import HeaderConfigurator from "@ui/components/Header/HeaderConfigurator"; +import { USER_DATA } from "@/app/_constant/auth"; + +export default function Group() { + const user = JSON.parse(localStorage.getItem(USER_DATA) || ""); + const owner = useCookie(OWNER); + const { channelId } = useParams(); + const { data, isLoading, isError, refetch } = useGetGroups(owner, channelId); + const myGroup = user?.id && data?.length ? findMyGroup(user.id, data) : null; + + if (isLoading) return ; + if (isError) return ; + + return ( + <> + +
+
+
+
+ +
+ {myGroup ? ( +
+

{myGroup?.group_name}

+

{myGroup?.joined_users.length}๋ช…

+
+ ) : ( +
๊ทธ๋ฃน์ง€์ • ์ค‘์ž…๋‹ˆ๋‹ค..
+ )} +
+
+ + {myGroup ? ( +
+

ํ•จ๊ป˜ํ•˜๋Š” ๋ฉค๋ฒ„

+ {myGroup.joined_users.map((member) => ( +
+
+ {getGenderEmoji(member.gender)} +
+ {member.username} +
+ ))} +
+ ) : ( + <> + )} +
+ + ); +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/group/_util/index.ts b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/group/_util/index.ts new file mode 100644 index 00000000..4efeb8e0 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/group/_util/index.ts @@ -0,0 +1,19 @@ +import { Group } from "@model/channel/group"; + +export const findMyGroup = ( + userId: number, + groups: Array +): Group | null => { + for (const group of groups) { + if (group.joined_users.some((user) => user.id === userId)) { + return group; + } + } + return null; +}; + +export const getGenderEmoji = (gender: string): string => { + if (gender === "male") return "๐Ÿ™‹โ€โ™‚๏ธ"; + if (gender === "female") return "๐Ÿ™‹โ€โ™€๏ธ"; + return ""; +}; diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/group/page.tsx b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/group/page.tsx new file mode 100644 index 00000000..ed0187e2 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/group/page.tsx @@ -0,0 +1,14 @@ +import React from "react"; +import Group from "./_components/Group"; +import OwnerCookieSetter from "@util/hooks/OwnerCookieSetter"; +import RequireAuth from "@/app/_components/RequireAuth"; + +export default function page() { + return ( + <> + + + ; + + ); +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/page.module.css b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/page.module.css new file mode 100644 index 00000000..3942721e --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/page.module.css @@ -0,0 +1,16 @@ +.container { + display: flex; + flex-direction: column; + align-items: center; + + height: 100vh; + overflow-y: scroll; + padding: 0px 16px; + /* Hide scrollbar */ + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ +} + +.container::-webkit-scrollbar { + display: none; /* Chrome, Safari, Opera */ +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/page.tsx b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/page.tsx new file mode 100644 index 00000000..785617a2 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/page.tsx @@ -0,0 +1,19 @@ +import React from "react"; +import styles from "./page.module.css"; +import ChannelInfo from "./_components/ChannelInfo"; +import RequireAuth from "@/app/_components/RequireAuth"; +import OwnerCookieSetter from "@util/hooks/OwnerCookieSetter"; + +export default async function page() { + return ( + <> + + +
+
+ +
+
+ + ); +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/received-feedback/_api/useGetReviewList.ts b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/received-feedback/_api/useGetReviewList.ts new file mode 100644 index 00000000..167891e8 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/received-feedback/_api/useGetReviewList.ts @@ -0,0 +1,49 @@ +import { useState, useEffect } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { serverUrl } from "@/app/_constant/config"; +import { ACCEESS_TOKEN } from "@constants/auth"; +import { Review } from "@model/channel/review"; +import { GET_REVIEW_LIST } from "@/app/_constant/queryKeys"; + +export const getReviewList = async ( + channelId: string | string[] | undefined, + token: string | null, + owner: string | null +): Promise => { + const response = await fetch( + `${serverUrl}/customers/reviews?channel_id=${channelId}`, + { + method: "GET", + headers: { + Authorization: `Bearer ${token}`, + Owner: owner || "", + }, + cache: "no-store", + } + ); + + if (!response.ok) { + throw new Error("Failed to fetch reviews"); + } + + return response.json(); +}; + +const useGetReviewList = ( + channelId: string | string[] | undefined, + owner: string | null +) => { + const [token, setToken] = useState(null); + + useEffect(() => { + setToken(localStorage.getItem(ACCEESS_TOKEN)); + }, []); + + return useQuery, Error>({ + queryKey: [GET_REVIEW_LIST, channelId, owner], + queryFn: () => getReviewList(channelId, token, owner), + enabled: Boolean(channelId) && Boolean(token) && Boolean(owner), + }); +}; + +export default useGetReviewList; diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/received-feedback/_components/HasNoReview.module.css b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/received-feedback/_components/HasNoReview.module.css new file mode 100644 index 00000000..ec72b319 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/received-feedback/_components/HasNoReview.module.css @@ -0,0 +1,20 @@ +.container { + padding: 0px 16px; +} + +.mainContent { + margin: 0 auto; +} + +.welcomeMessage { + background: linear-gradient(135deg, #fff, #f7f7f7); + border: 2px solid #dfe4ea; + border-radius: 16px; + padding: 32px; + margin-bottom: 32px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + font-size: 18px; + line-height: 1.6; + text-align: center; + font-family: "Arial", sans-serif; +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/received-feedback/_components/HasNoReview.tsx b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/received-feedback/_components/HasNoReview.tsx new file mode 100644 index 00000000..edadfff9 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/received-feedback/_components/HasNoReview.tsx @@ -0,0 +1,43 @@ +import React from "react"; +import styles from "./HasNoReview.module.css"; +import { noReviewUsers } from "../_constants"; +import ReviewCard from "./ReviewCard"; +import { Review } from "@model/channel/review"; +import { USER_DATA } from "@/app/_constant/auth"; + +export default function HasNoReview() { + const user = JSON.parse(localStorage.getItem(USER_DATA) || ""); + + const userGender = user?.gender === "male" ? "๐Ÿ™‹" : "๐Ÿ™‹โ€โ™€๏ธ"; + + return ( +
+
+

+ ์•ˆ๋…•ํ•˜์„ธ์š” {userGender} + {user?.username}๋‹˜, +

+

+ ์ด 2๊ฐœ์˜ ํ›„๊ธฐ๊ฐ€ ๋„์ฐฉํ–ˆ์–ด์š”. +

+

ํ›„๊ธฐ ์ž‘์„ฑ์ž๋“ค์˜ ์ฒซ์ธ์ƒ๊ณผ

+

๋Œ€ํ™” ๋‚ด์šฉ์„ ํ™•์ธํ•ด๋ณด์„ธ์š”.

+
+ {noReviewUsers.map((review: Review) => ( + + ))} +
+ {user?.username}๋‹˜ ์ด๋ฒˆ ์†Œ์…œ๋ง์—์„œ
+ ์ฆ๊ฑฐ์šด ์‹œ๊ฐ„ ๋˜์…จ๋‚˜์š”? ๐Ÿ˜Š
+
+ ๋งŒ์กฑํ•˜์…จ๋‹ค๋ฉด, ํ›„๊ธฐ๋กœ ๋”ฐ๋œปํ•œ ํ•œ๋งˆ๋”” +
+ ๋‚จ๊ฒจ์ฃผ์‹œ๋ฉด ํฐ ํž˜์ด ๋  ๊ฒƒ ๊ฐ™์•„์š”! +
+
+ ์—ฌ๋Ÿฌ๋ถ„์˜ ํ›„๊ธฐ๊ฐ€ ๋‹ค์Œ ๋ชจ์ž„์„
+ ๋”์šฑ ํŠน๋ณ„ํ•˜๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ๐Ÿ’› +
+
+ ); +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/received-feedback/_components/Review.module.css b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/received-feedback/_components/Review.module.css new file mode 100644 index 00000000..7155ecf7 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/received-feedback/_components/Review.module.css @@ -0,0 +1,17 @@ +.mainContent { + margin: 0 auto; + padding: 0px 16px; +} + +.welcomeMessage { + background: linear-gradient(135deg, #fff, #f7f7f7); + border: 2px solid #dfe4ea; + border-radius: 16px; + padding: 32px; + margin-bottom: 32px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + font-size: 18px; + line-height: 1.6; + text-align: center; + font-family: "Arial", sans-serif; +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/received-feedback/_components/ReviewCard.module.css b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/received-feedback/_components/ReviewCard.module.css new file mode 100644 index 00000000..4aa7f749 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/received-feedback/_components/ReviewCard.module.css @@ -0,0 +1,99 @@ +.card { + background: #ffffff; + border-radius: 16px; + padding: 20px; + margin-bottom: 20px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + transition: transform 0.2s ease; +} +.card:hover { + transform: translateY(-4px); +} + +.reviewHeader { + display: flex; + justify-content: flex-start; + align-items: center; + margin-bottom: 16px; +} + +.reviewerInfo { + display: flex; + align-items: center; + font-size: 18px; + font-weight: 600; + color: #333333; + font-family: Arial, sans-serif; + margin-bottom: 8px; +} + +.section { + margin-top: 16px; + border-top: 1px solid #e5e7eb; + padding-top: 16px; +} + +.sectionTitle { + font-size: 16px; + font-weight: bold; + line-height: 1.6; + margin-bottom: 8px; + display: flex; + align-items: center; + color: #333333; +} + +.icon { + margin-right: 4px; +} + +.list { + list-style: none; + padding-left: 0; + margin: 0; +} + +.listItem { + font-size: 16px; + line-height: 1.6; + color: #555555; + margin-bottom: 4px; + position: relative; + padding-left: 1em; +} +.listItem::before { + content: "โ€ข"; + color: #2563eb; + position: absolute; + left: 0; +} + +.contactInfo { + font-size: 16px; + color: #555555; +} + +.contactItem { + margin-bottom: 8px; +} +.contactItem a { + color: #2563eb; + text-decoration: none; +} +.contactItem a:hover { + text-decoration: underline; +} + +.keywordList { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.keyword { + padding: 6px 12px; + background: #eff6ff; + color: #2563eb; + border-radius: 20px; + font-size: 14px; +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/received-feedback/_components/ReviewCard.tsx b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/received-feedback/_components/ReviewCard.tsx new file mode 100644 index 00000000..782f0bc3 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/received-feedback/_components/ReviewCard.tsx @@ -0,0 +1,120 @@ +import React from "react"; +import styles from "./ReviewCard.module.css"; +import { extractContact, parseList } from "../_util"; +import { Review } from "@model/channel/review"; + +interface Props { + review: Review; +} + +export default function ReviewCard({ review }: Props) { + const contact = extractContact(review.additional_info); + const styleList = parseList(review.style); + const impressionList = parseList(review.impression); + const conversationList = parseList(review.conversation); + const genderIcon = review.reviewer_user_gender === "male" ? "๐Ÿ™‹โ€โ™‚๏ธ" : "๐Ÿ™‹โ€โ™€๏ธ"; + + return ( +
+
+
+ {review.is_reviewer_anonymous + ? "์ž‘์„ฑ์ž:๐Ÿ‘ค ๋น„๊ณต๊ฐœ" + : `์ž‘์„ฑ์ž:${genderIcon} ${review.reviewer_user_name}`} +
+
+ + {/* ์Šคํƒ€์ผ ์„น์…˜ */} +
+

+ ๐Ÿ‘•์Šคํƒ€์ผ +

+
    + {styleList.map((item, idx) => ( +
  • + {item} +
  • + ))} +
+
+ + {/* ์ฒซ์ธ์ƒ ์„น์…˜ */} +
+

+ ๐Ÿ˜Ž์ฒซ์ธ์ƒ +

+
    + {impressionList.map((item, idx) => ( +
  • + {item} +
  • + ))} +
+
+ + {/* ๋Œ€ํ™” ์ค‘ ์™€๋‹ฟ์•˜๋˜ ๋ถ€๋ถ„ */} +
+

+ ๐Ÿ’ฌ๋Œ€ํ™” ์ค‘ ์™€๋‹ฟ์•˜๋˜ ๋ถ€๋ถ„ +

+
    + {conversationList.map((item, idx) => ( +
  • + {item} +
  • + ))} +
+
+ + {/* ํ‚ค์›Œ๋“œ */} + {review.keywords?.trim() && ( +
+

+ ๐Ÿ’ก์–ด์šธ๋ฆฌ๋Š” ํ‚ค์›Œ๋“œ +

+
+ {review.keywords.split(",").map((kw, idx) => ( + + {kw.trim()} + + ))} +
+
+ )} + + {/* ์—ฐ๋ฝ์ฒ˜ */} + {(contact.instagram || contact.kakao || contact.phone) && ( +
+

์—ฐ๋ฝ์ฒ˜

+
+ {contact.instagram && ( +
+ Instagram:{" "} + + {contact.instagram} + +
+ )} + {contact.kakao && ( +
+ Kakao: {contact.kakao} +
+ )} + {contact.phone && ( +
+ Phone: {contact.phone} +
+ )} +
+
+ )} +
+ ); +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/received-feedback/_components/ReviewList.tsx b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/received-feedback/_components/ReviewList.tsx new file mode 100644 index 00000000..8ae768a2 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/received-feedback/_components/ReviewList.tsx @@ -0,0 +1,83 @@ +"use client"; + +import React from "react"; +import { useParams } from "next/navigation"; +import useGetReviewList from "../_api/useGetReviewList"; +import styles from "./Review.module.css"; +import ReviewCard from "./ReviewCard"; +import useCookie from "@util/hooks/useCookie"; +import { OWNER } from "@constants/auth"; +import HasNoReview from "./HasNoReview"; +import Loading from "@ui/components/Spinner/FadeLoader"; +import ErrorBox from "@ui/components/Error/ErrorBox"; +import { Review } from "@model/channel/review"; +import HeaderConfigurator from "@ui/components/Header/HeaderConfigurator"; +import { USER_DATA } from "@/app/_constant/auth"; + +const ReviewList: React.FC = () => { + const user = JSON.parse(localStorage.getItem(USER_DATA) || ""); + const { channelId } = useParams(); + const owner = useCookie(OWNER); + + const { + data: reviews, + isError, + isLoading, + refetch, + } = useGetReviewList(channelId, owner); + + const userGender = user?.gender === "male" ? "๐Ÿ™‹" : "๐Ÿ™‹โ€โ™€๏ธ"; + const hasReveiws = !!reviews?.length; + + if (isError) return ; + if (isLoading) return ; + + return ( + <> + + + {!hasReveiws && } + + {hasReveiws && ( + <> +
+
+

+ ์•ˆ๋…•ํ•˜์„ธ์š” {userGender} + {user?.username}๋‹˜, +

+

+ ์ด {reviews.length}๊ฐœ์˜ ํ›„๊ธฐ๊ฐ€ ๋„์ฐฉํ–ˆ์–ด์š”. +

+

ํ›„๊ธฐ ์ž‘์„ฑ์ž๋“ค์˜ ์ฒซ์ธ์ƒ๊ณผ

+

๋Œ€ํ™” ๋‚ด์šฉ์„ ํ™•์ธํ•ด๋ณด์„ธ์š”.

+
+ {reviews.map((review: Review) => ( + + ))} +
+
+ {user?.username}๋‹˜ ์ด๋ฒˆ ์†Œ์…œ๋ง์—์„œ
+ ์ฆ๊ฑฐ์šด ์‹œ๊ฐ„ ๋˜์…จ๋‚˜์š”? ๐Ÿ˜Š
+
+ ๋งŒ์กฑํ•˜์…จ๋‹ค๋ฉด, ํ›„๊ธฐ๋กœ ๋”ฐ๋œปํ•œ ํ•œ๋งˆ๋”” +
+ ๋‚จ๊ฒจ์ฃผ์‹œ๋ฉด ํฐ ํž˜์ด ๋  ๊ฒƒ ๊ฐ™์•„์š”! +
+
+ ์—ฌ๋Ÿฌ๋ถ„์˜ ํ›„๊ธฐ๊ฐ€ ๋‹ค์Œ ๋ชจ์ž„์„
+ ๋”์šฑ ํŠน๋ณ„ํ•˜๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ๐Ÿ’› +
+ + )} + + ); +}; + +export default ReviewList; diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/received-feedback/_constants/index.ts b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/received-feedback/_constants/index.ts new file mode 100644 index 00000000..d528213c --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/received-feedback/_constants/index.ts @@ -0,0 +1,36 @@ +import { Review } from "@model/channel/review"; + +export const noReviewUsers: Array = [ + { + is_reviewer_anonymous: true, + channel_id: 10, + style: + "[์บ์ฃผ์–ผ]\n ํŽธ์•ˆํ•˜๊ณ  ์ผ์ƒ์ ์ธ ๋ณต์žฅ, [๋…ธ๋ฉ€]\n ํŠ€์ง€ ์•Š๊ณ  ๋ฌด๋‚œํ•œ ์Šคํƒ€์ผ, [๋ฏธ๋‹ˆ๋ฉ€]\n ๋‹จ์ˆœํ•˜๊ณ  ์‹ฌํ”Œํ•œ ๋””์ž์ธ", + impression: + "์ฐจ๋ถ„ํ•˜๊ณ  ์ง„์ง€ํ•จ:\n ์‹ ์ค‘ํ•˜๋ฉด์„œ ์•ˆ์ •๊ฐ์ด ๋А๊ปด์ง, ๋”ฐ๋œปํ•˜๊ณ  ๋ฐฐ๋ ค์‹ฌ ์žˆ์Œ:\n ๋ถ€๋“œ๋Ÿฝ๊ณ  ํฌ์šฉ๋ ฅ ์žˆ๋Š” ๋А๋‚Œ", + conversation: + "์ด์•ผ๊ธฐ ๋‚ด์šฉ๊ณผ ํฅ๋ฏธ:\n์ด์•ผ๊ธฐ์˜ ์ฃผ์ œ๋‚˜ ๋‚ด์šฉ์ด ํฅ๋ฏธ๋กญ๊ณ  ์‚ฌ๋žŒ์˜ ๊ด€์‹ฌ์„ ๋Œ์—ˆ์Œ. ์ƒˆ๋กœ์šด ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜๊ฑฐ๋‚˜ ํฅ๋ฏธ๋กœ์šด ๊ด€์ ์„ ์ œ์‹œํ•จ, ๊ณต๊ฐํ•˜๊ณ  ๊ฒฝ์ฒญํ•˜๋Š” ํƒœ๋„:\n ์ƒ๋Œ€๋ฐฉ์˜ ๋ง์— ์ง‘์ค‘ํ•˜๊ณ  ์ง„์ง€ํ•˜๊ฒŒ ๊ฒฝ์ฒญํ•˜๋ฉฐ ๊ณต๊ฐ์„ ํ‘œํ˜„ํ•จ. ์ƒ๋Œ€๋ฐฉ์ด ์กด์ค‘๋ฐ›๊ณ  ์žˆ๋‹ค๋Š” ๋А๋‚Œ์„ ๋ฐ›์Œ, ํŽธ์•ˆํ•˜๊ณ  ์ž์—ฐ์Šค๋Ÿฌ์›€:\n ๋Œ€ํ™”๊ฐ€ ํŽธ์•ˆํ•˜๊ณ  ์ž์—ฐ์Šค๋Ÿฌ์›Œ ์•„๋ฌด๋Ÿฐ ๋…ธ๋ ฅ ์—†์ด ํ˜๋Ÿฌ๊ฐ”์Œ, ์นœ์ ˆํ•˜๊ณ  ๋ฐฐ๋ ค์‹ฌ ์žˆ๋Š” ๋Œ€ํ™” ํƒœ๋„:\n ์ƒ๋Œ€๋ฐฉ์ด ๋‚˜์˜ ์˜๊ฒฌ์„ ์กด์ค‘ํ•˜๋ฉฐ ๋Œ€ํ™”์— ์ฐธ์—ฌํ•จ", + additional_info: "", + keywords: "ํŽธ์•ˆํ•จ, ๋”ฐ๋“ฏํ•จ, ํ‰ํ™”๋กœ์›€", + id: 353, + created_at: "2025-03-10T08:02:41.510367", + reviewer_user_gender: "anonymous", + reviewer_user_name: "anonymous", + }, + { + is_reviewer_anonymous: true, + channel_id: 10, + style: + "[๋…ธ๋ฉ€]\n ํŠ€์ง€ ์•Š๊ณ  ๋ฌด๋‚œํ•œ ์Šคํƒ€์ผ, [๋ฏธ๋‹ˆ๋ฉ€]\n ๋‹จ์ˆœํ•˜๊ณ  ์‹ฌํ”Œํ•œ ๋””์ž์ธ", + impression: + "์‹ ๋ขฐ๊ฐ ์žˆ์Œ:\n ์•ˆ์ •์ ์ด๊ณ  ๋ฏฟ์„ ์ˆ˜ ์žˆ๋Š” ์ธ์ƒ์„ ์คŒ, ์„ฌ์„ธํ•˜๊ณ  ๊ด€์ฐฐ๋ ฅ ์žˆ์Œ:\n ์ž‘์€ ๋””ํ…Œ์ผ๊นŒ์ง€ ์ž˜ ์บ์น˜ํ•˜๋ฉฐ ์„ธ์‹ฌํ•œ ๋А๋‚Œ์„ ์คŒ", + conversation: + "์ด์•ผ๊ธฐ ๋‚ด์šฉ๊ณผ ํฅ๋ฏธ:\n์ด์•ผ๊ธฐ์˜ ์ฃผ์ œ๋‚˜ ๋‚ด์šฉ์ด ํฅ๋ฏธ๋กญ๊ณ  ์‚ฌ๋žŒ์˜ ๊ด€์‹ฌ์„ ๋Œ์—ˆ์Œ. ์ƒˆ๋กœ์šด ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜๊ฑฐ๋‚˜ ํฅ๋ฏธ๋กœ์šด ๊ด€์ ์„ ์ œ์‹œํ•จ, ํŽธ์•ˆํ•˜๊ณ  ์ž์—ฐ์Šค๋Ÿฌ์›€:\n ๋Œ€ํ™”๊ฐ€ ํŽธ์•ˆํ•˜๊ณ  ์ž์—ฐ์Šค๋Ÿฌ์›Œ ์•„๋ฌด๋Ÿฐ ๋…ธ๋ ฅ ์—†์ด ํ˜๋Ÿฌ๊ฐ”์Œ, ์นœ์ ˆํ•˜๊ณ  ๋ฐฐ๋ ค์‹ฌ ์žˆ๋Š” ๋Œ€ํ™” ํƒœ๋„:\n ์ƒ๋Œ€๋ฐฉ์ด ๋‚˜์˜ ์˜๊ฒฌ์„ ์กด์ค‘ํ•˜๋ฉฐ ๋Œ€ํ™”์— ์ฐธ์—ฌํ•จ, ์ž์‹ ๊ฐ ์žˆ๋Š” ํ‘œํ˜„:\n ์ž์‹ ์˜ ์ƒ๊ฐ์„ ๋ช…ํ™•ํ•˜๊ณ  ์ž์‹  ์žˆ๊ฒŒ ํ‘œํ˜„ํ•จ", + additional_info: "", + keywords: "์ง„์ง€ํ•œ, ๋ฐฐ๋ ค๊นŠ์€, ์นœ์ ˆํ•œ, ํŽธ์•ˆํ•จ, ๋‹จ์ •ํ•œ", + id: 354, + created_at: "2025-03-10T08:02:41.510367", + reviewer_user_gender: "anonymous", + reviewer_user_name: "anonymous", + }, +]; diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/received-feedback/_util/index.ts b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/received-feedback/_util/index.ts new file mode 100644 index 00000000..bb6e8f8f --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/received-feedback/_util/index.ts @@ -0,0 +1,19 @@ +export const extractContact = (info: string) => { + if (!info) return { instagram: "", kakao: "", phone: "" }; + const instagramMatch = info.match(/Instagram:\s*([^,]+)/i); + const kakaoMatch = info.match(/Kakao:\s*([^,]+)/i); + const phoneMatch = info.match(/Phone:\s*([^\s,]+)/i); + return { + instagram: instagramMatch ? instagramMatch[1].trim() : "", + kakao: kakaoMatch ? kakaoMatch[1].trim() : "", + phone: phoneMatch ? phoneMatch[1].trim() : "", + }; +}; + +export const parseList = (text: string) => + text + ? text + .split(",") + .map((item) => item.trim()) + .filter(Boolean) + : []; diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/received-feedback/page.tsx b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/received-feedback/page.tsx new file mode 100644 index 00000000..5d7295da --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/received-feedback/page.tsx @@ -0,0 +1,14 @@ +import React from "react"; +import ReviewList from "./_components/ReviewList"; +import OwnerCookieSetter from "@util/hooks/OwnerCookieSetter"; +import RequireAuth from "@/app/_components/RequireAuth"; + +export default function page() { + return ( + <> + + + ; + + ); +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/write-review/_api/uploadBrandReviewImage.ts b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/write-review/_api/uploadBrandReviewImage.ts new file mode 100644 index 00000000..16d677ad --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/write-review/_api/uploadBrandReviewImage.ts @@ -0,0 +1,36 @@ +import { ACCEESS_TOKEN } from "@constants/auth"; +import { serverUrl } from "@/app/_constant/config"; + +export interface Response { + imageUrl: string; + imagePath: string; +} + +export const uploadBrandReviewImage = async ( + file: File, + brand_review_id: number +): Promise => { + const formData = new FormData(); + formData.append("file", file); + formData.append("brand_review_id", String(brand_review_id)); + + const token = localStorage.getItem(ACCEESS_TOKEN); + + const result = await fetch( + `${serverUrl}/customers/brandReviews/uploadImage`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token ?? ""}`, + }, + body: formData, + cache: "no-cache", + } + ); + if (!result.ok) { + throw new Error("Failed to create brand review"); + } + + return result.json(); +}; diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/write-review/_api/useCreateBrandReview.ts b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/write-review/_api/useCreateBrandReview.ts new file mode 100644 index 00000000..493fadc2 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/write-review/_api/useCreateBrandReview.ts @@ -0,0 +1,47 @@ +import { useState, useEffect } from "react"; +import { useMutation } from "@tanstack/react-query"; +import { BrandReview } from "@model/brand/review"; +import { ACCEESS_TOKEN } from "@constants/auth"; +import { serverUrl } from "@/app/_constant/config"; + +export interface requestBody { + user_id: number | undefined; + brand_id: string | null; +} + +export const createBrandReview = async ( + body: requestBody, + owner: string | null, + token: string | null +): Promise => { + const res = await fetch(`${serverUrl}/customers/brandReviews`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token ?? ""}`, + Owner: owner ?? "", + }, + body: JSON.stringify(body), + cache: "no-cache", + }); + + if (!res.ok) { + throw new Error("Failed to create brand review"); + } + return res.json(); +}; + +const useCreateBrandReview = (owner: string | null) => { + const [token, setToken] = useState(null); + + useEffect(() => { + setToken(localStorage.getItem(ACCEESS_TOKEN)); + }, []); + + return useMutation({ + mutationFn: ({ requestBody }) => + createBrandReview(requestBody, owner, token), + }); +}; + +export default useCreateBrandReview; diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/write-review/_api/useUpdateBrandReview.ts b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/write-review/_api/useUpdateBrandReview.ts new file mode 100644 index 00000000..8ec8fd12 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/write-review/_api/useUpdateBrandReview.ts @@ -0,0 +1,58 @@ +import { useEffect, useState } from "react"; +import { useMutation } from "@tanstack/react-query"; +import { BrandReview } from "@model/brand/review/index"; +import { ACCEESS_TOKEN } from "@constants/auth"; +import { serverUrl } from "@/app/_constant/config"; + +export const updateBrandReview = async ( + review_id: number, + requestBody: { + contents?: string; + is_display?: boolean; + }, + owner: string | null, + token: string | null +): Promise => { + const res = await fetch(`${serverUrl}/brandReviews/${review_id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token ?? ""}`, + Owner: owner ?? "", + }, + body: JSON.stringify(requestBody), + cache: "no-cache", + }); + + if (!res.ok) { + throw new Error("Failed to update brand review"); + } + + return res.json(); +}; + +const useUpdateBrandReview = (owner: string | null) => { + const [token, setToken] = useState(null); + + useEffect(() => { + const t = localStorage.getItem(ACCEESS_TOKEN); + setToken(t); + }, []); + + return useMutation< + BrandReview, + Error, + { + review_id: number; + requestBody: { + contents: string; + is_display: boolean; + }; + } + >({ + mutationFn: ({ review_id, requestBody }) => + updateBrandReview(review_id, requestBody, owner, token), + }); +}; + +export default useUpdateBrandReview; diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/write-review/_components/Form.module.css b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/write-review/_components/Form.module.css new file mode 100644 index 00000000..9d786805 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/write-review/_components/Form.module.css @@ -0,0 +1,108 @@ +.container { + display: flex; + justify-content: center; + height: 100vh; +} + +.wrapper { + padding-top: 300px; +} + +.title { + font-size: 20px; + font-weight: bold; + margin-bottom: 12px; +} + +.subText { + color: gray; + margin-bottom: 20px; +} + +.imageWrapper { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 10px; + margin-bottom: 20px; +} + +.imageItem { + position: relative; + width: 100px; + height: 100px; +} + +.imagePreview { + width: 100px; + height: 100px; + border-radius: 8px; + object-fit: cover; +} + +.deleteButton { + position: absolute; + right: 0; + background: #ff4d4f; + color: white; + border: none; + border-radius: 50%; + width: 22px; + height: 22px; + font-size: 14px; + cursor: pointer; +} + +.imageInputLabel { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + border: 2px dashed #ccc; + border-radius: 8px; + cursor: pointer; + width: 100px; + height: 100px; +} + +.imageInputLabel input { + display: none; +} + +.plus { + font-size: 24px; + color: #aaa; +} + +.count { + font-size: 12px; + color: #aaa; +} + +.reviewTextarea { + width: 100%; + height: 120px; + resize: none; + border: 1px solid #ddd; + padding: 12px; + border-radius: 8px; + font-size: 14px; + margin-bottom: 20px; +} + +.submitButton { + width: 100%; + padding: 12px 20px; + border: none; + border-radius: 8px; + cursor: pointer; + transition: background-color 0.3s; +} +.submitButton:disabled { + background-color: #e0e0e0; + color: #999; + cursor: not-allowed; +} +.submitButton:not(:disabled) { + background-color: black; + color: white; +} diff --git a/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/write-review/_components/Form.tsx b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/write-review/_components/Form.tsx new file mode 100644 index 00000000..062a5614 --- /dev/null +++ b/frontend/apps/contents/src/app/(afterLogin)/detail/[channelId]/write-review/_components/Form.tsx @@ -0,0 +1,143 @@ +"use client"; + +import React, { useState } from "react"; +import { useRouter, useSearchParams } from "next/navigation"; +import styles from "./Form.module.css"; +import useUpdateBrandReview from "../_api/useUpdateBrandReview"; +import useCreateBrandReview from "../_api/useCreateBrandReview"; +import useCookie from "@util/hooks/useCookie"; +import { OWNER } from "@constants/auth"; + +import { useSystemModalStore } from "@ui/store/useSystemModalStore"; +import { uploadBrandReviewImage } from "../_api/uploadBrandReviewImage"; +import { useParams } from "next/navigation"; +import pathnames from "@/app/_constant/pathnames"; +import { USER_DATA } from "@/app/_constant/auth"; +import OptimizedNextImage from "@ui/components/Image/OptimizedNextImage"; + +const MAX_IMAGES = 6; + +export default function Form() { + const { channelId } = useParams(); + const brandId = useSearchParams().get("brandId"); + const router = useRouter(); + const owner = useCookie(OWNER); + const user = JSON.parse(localStorage.getItem(USER_DATA) || ""); + + const { open, close, showErrorModal } = useSystemModalStore(); + const { mutate: updateBrandReview } = useUpdateBrandReview(owner); + const { mutate: createBrandReview } = useCreateBrandReview(owner); + + const [images, setImages] = useState([]); + const [review, setReview] = useState(""); + + const handleImageChange = (e: React.ChangeEvent) => { + if (!e.target.files) return; + const fileArray = Array.from(e.target.files); + if (images.length + fileArray.length <= MAX_IMAGES) { + setImages((prev) => [...prev, ...fileArray]); + } + }; + + const handleDeleteImage = (index: number) => { + setImages((prev) => prev.filter((_, idx) => idx !== index)); + }; + + const handleSubmit = () => { + createBrandReview( + { requestBody: { user_id: user?.id, brand_id: brandId } }, + { + onSuccess: (data) => { + const brandReviewId = data.id; + Promise.all( + images.map((img) => uploadBrandReviewImage(img, brandReviewId)) + ) + .then(() => { + updateBrandReview( + { + review_id: brandReviewId, + requestBody: { contents: review, is_display: true }, + }, + { + onSuccess: () => { + open({ + isOpen: true, + confirmText: "ํ™•์ธ", + showCancel: false, + title: "๋ฆฌ๋ทฐ ์ž‘์„ฑ ์™„๋ฃŒ", + message: "๋ฆฌ๋ทฐ ์ž‘์„ฑ์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.๐Ÿ˜Š", + onConfirm: () => { + close(); + router.push(`${pathnames.detail}/${channelId}`); + }, + }); + }, + onError: (error) => showErrorModal(error.message), + } + ); + }) + .catch((error) => showErrorModal(error.message)); + }, + onError: (error) => showErrorModal(error.message), + } + ); + }; + + return ( +
+
+

ํ˜ธ์ŠคํŠธ์— ๋Œ€ํ•œ ๋ฆฌ๋ทฐ๋ฅผ ๋‚จ๊ฒจ์ฃผ์„ธ์š”

+

+ ๋‚จ๊ฒจ์ฃผ์‹  ๋ฆฌ๋ทฐ๋Š” ํ˜ธ์ŠคํŠธ์—๊ฒŒ ํฐ ๋„์›€์ด ๋ผ์š”. +

+ +
+ {images.map((img, idx) => ( +
+ + +
+ ))} + {images.length < MAX_IMAGES && ( + + )} +
+ +