diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000..7509ce0
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,3 @@
+next.config.js
+/next.config.js
+node_modules
\ No newline at end of file
diff --git a/.eslintrc.json b/.eslintrc.json
index bffb357..9279ce2 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -1,3 +1,43 @@
{
- "extends": "next/core-web-vitals"
+ "env": {
+ "browser": true,
+ "es2021": true,
+ "node": true
+ },
+ "parser": "@typescript-eslint/parser",
+ "extends": ["next/core-web-vitals", "plugin:prettier/recommended", "plugin:@typescript-eslint/recommended"],
+
+ "overrides": [],
+ "parserOptions": {
+ "ecmaVersion": "latest",
+ "sourceType": "module",
+ "project": ["./tsconfig.json"]
+ },
+ "plugins": ["@typescript-eslint", "prettier"],
+ "rules": {
+ "no-var": "error",
+ "no-console": ["warn", { "allow": ["warn", "error", "info"] }],
+ "prefer-const": "off",
+ "@typescript-eslint/consistent-type-imports": "off",
+ "@typescript-eslint/explicit-function-return-type": "off",
+ "@typescript-eslint/space-before-function-paren": "off",
+ "@typescript-eslint/triple-slash-reference": "off",
+ "@typescript-eslint/restrict-template-expressions": "error",
+ "@typescript-eslint/no-floating-promises": "off",
+ "@typescript-eslint/no-non-null-assertion": "off",
+ "@typescript-eslint/no-unused-vars": "warn",
+ "@typescript-eslint/no-explicit-any": "warn",
+ "@typescript-eslint/no-misused-promises": [
+ "error",
+ {
+ "checksVoidReturn": false
+ }
+ ],
+ "prettier/prettier": [
+ "error",
+ {
+ "endOfLine": "auto"
+ }
+ ]
+ }
}
diff --git a/.gitconfig b/.gitconfig
new file mode 100644
index 0000000..c6cd23f
--- /dev/null
+++ b/.gitconfig
@@ -0,0 +1,3 @@
+# .gitconfig
+[core]
+ ignorecase = false
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 0000000..8997b36
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,18 @@
+## 개요
+
+{ 업무에 대한 요약 및 설명 }
+
+
+
+## 체크리스트
+
+{ 작업 체크리스트 }
+
+- [ ] 리스트 1
+- [ ] 리스트 2
+
+
+
+## 비고
+
+{ 기타 내용, 의존성있는 작업 }
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..6188a46
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,23 @@
+### 💁♂️ PR 개요
+
+- 어떤 이유로 코드를 변경했는지
+- #{issue number} (QA이슈)
+
+
+
+### 📷 스크린 샷 (선택)
+
+- 관련 스크린샷 첨부
+
+
+
+### 🗣 리뷰어한테 할 말 (선택)
+
+- 집중적으로 리뷰해주었으면 하는 부분 설명
+
+
+
+### 🧪 테스트 범위 (선택)
+
+- 테스트 계획
+- 테스트 완료 사항
diff --git a/.github/labels.json b/.github/labels.json
new file mode 100644
index 0000000..7966030
--- /dev/null
+++ b/.github/labels.json
@@ -0,0 +1,37 @@
+[
+ {
+ "color": "D4C5F9",
+ "description": "패키지 매니저 수정, 그 외 기타 수정",
+ "name": "⚙️CHORE"
+ },
+ {
+ "color": "C5DEF5",
+ "description": "문서(주석) 수정",
+ "name": "📝DOCS"
+ },
+ {
+ "color": "FEF2C0",
+ "description": "기능 구현 관련",
+ "name": "✨FEAT"
+ },
+ {
+ "color": "F9D0C4",
+ "description": "기능에 대한 버그 수정",
+ "name": "🐛FIX"
+ },
+ {
+ "color": "C2E0C6",
+ "description": "기능의 변화가 아닌 코드 리팩터링 ex) 변수명 변경",
+ "name": "♻️REFACTOR"
+ },
+ {
+ "color": "E99695",
+ "description": "코드 스타일, 포맷팅에 대한 수정",
+ "name": "🎨STYLE"
+ },
+ {
+ "color": "66ff99",
+ "description": "test 관련",
+ "name": "✅TEST"
+ }
+]
diff --git a/.github/workflows/sync_develop.yml b/.github/workflows/sync_develop.yml
new file mode 100644
index 0000000..b08d1b7
--- /dev/null
+++ b/.github/workflows/sync_develop.yml
@@ -0,0 +1,30 @@
+name: git push into another repo to deploy to vercel
+
+on:
+ push:
+ branches: [develop]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ container: pandoc/latex
+ steps:
+ - uses: actions/checkout@v3
+ - name: Install mustache (to update the date)
+ run: apk add ruby && gem install mustache
+ - name: creates output
+ run: sh ./build.sh
+ - name: Pushes to another repository
+ id: push_directory
+ uses: cpina/github-action-push-to-another-repository@main
+ env:
+ API_TOKEN_GITHUB: ${{ secrets.AUTO_SYNC_KEY }}
+ with:
+ source-directory: 'output'
+ destination-github-username: minh0518
+ destination-repository-name: DDD-10-KKEUNKKEUN-WEB
+ user-email: ${{ secrets.OFFICIAL_ACCOUNT_EMAIL }}
+ commit-message: ${{ github.event.commits[0].message }}
+ target-branch: develop
+ - name: Test get variable exported by push-to-another-repository
+ run: echo $DESTINATION_CLONED_DIRECTORY
diff --git a/.github/workflows/sync_main.yml b/.github/workflows/sync_main.yml
new file mode 100644
index 0000000..d56982e
--- /dev/null
+++ b/.github/workflows/sync_main.yml
@@ -0,0 +1,30 @@
+name: git push into another repo to deploy to vercel
+
+on:
+ push:
+ branches: [main]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ container: pandoc/latex
+ steps:
+ - uses: actions/checkout@v3
+ - name: Install mustache (to update the date)
+ run: apk add ruby && gem install mustache
+ - name: creates output
+ run: sh ./build.sh
+ - name: Pushes to another repository
+ id: push_directory
+ uses: cpina/github-action-push-to-another-repository@main
+ env:
+ API_TOKEN_GITHUB: ${{ secrets.AUTO_SYNC_KEY }}
+ with:
+ source-directory: 'output'
+ destination-github-username: minh0518
+ destination-repository-name: DDD-10-KKEUNKKEUN-WEB
+ user-email: ${{ secrets.OFFICIAL_ACCOUNT_EMAIL }}
+ commit-message: ${{ github.event.commits[0].message }}
+ target-branch: main
+ - name: Test get variable exported by push-to-another-repository
+ run: echo $DESTINATION_CLONED_DIRECTORY
diff --git a/.gitignore b/.gitignore
index fd3dbb5..8795eeb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,9 +28,16 @@ yarn-error.log*
# local env files
.env*.local
+
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
+
+
+.eslintcache
+
+
+.env
\ No newline at end of file
diff --git a/.husky/commit-msg b/.husky/commit-msg
new file mode 100644
index 0000000..8cd32e2
--- /dev/null
+++ b/.husky/commit-msg
@@ -0,0 +1,26 @@
+#!/usr/bin/env sh
+# . "$(dirname -- "$0")/_/husky.sh"
+
+# COMMIT_MSG_FILE=$1
+# COMMIT_MSG=$(awk '!/^\s*#/' "$COMMIT_MSG_FILE")
+# SECOND_LINE=$(echo "$COMMIT_MSG" | sed -n '2p')
+
+# IS_HEADER_FORMAT_VALID='^(:[a-zA-Z_]+: )?(feat|fix|docs|refactor|test|style|chore): .+'
+# IS_HEADER_LENGTH_UNDER_50='^(.{1,50}$)'
+
+# # --- header ---
+# if ! echo "$COMMIT_MSG" | grep -qP "$IS_HEADER_FORMAT_VALID"; then
+# echo "🚨 커밋 메시지는 'feat: ', 'fix: ', 'docs: ', 'refactor: ', 'test: ', 'style: ', 'chore: ' 중 하나로 시작해야 합니다. (띄워쓰기 포함)"
+# exit 1
+# fi
+
+# if ! echo "$COMMIT_MSG" | grep -qP "$IS_HEADER_LENGTH_UNDER_50"; then
+# echo "🚨 커밋 메시지의 첫 줄은 50자를 넘을 수 없습니다."
+# exit 1
+# fi
+
+# # --- description ---
+# if ! [ -z "$SECOND_LINE" ]; then
+# echo "🚨 커밋 메시지의 두 번째 줄은 비워야 합니다."
+# exit 1
+# fi
\ No newline at end of file
diff --git a/.husky/pre-commit b/.husky/pre-commit
new file mode 100644
index 0000000..34ab85d
--- /dev/null
+++ b/.husky/pre-commit
@@ -0,0 +1,16 @@
+#!/usr/bin/env sh
+# . "$(dirname -- "$0")/_/husky.sh"
+
+# branch_name=$(git branch --show-current)
+
+# # check branch naming
+# pattern="^(feat|fix|docs|refactor|test|style|chore)\/.+$"
+
+# if ! echo "$branch_name" | grep -Eq "$pattern"; then
+# echo "🚨 브랜치 이름이 규칙에 맞지 않습니다."
+# echo "수정 예시: feat/login_cookie, chore/ESLint,prettier 등"
+# exit 1
+# fi
+
+# check ESlint&prettier before commit
+npx lint-staged
diff --git a/.husky/pre-push b/.husky/pre-push
new file mode 100644
index 0000000..55f4bbb
--- /dev/null
+++ b/.husky/pre-push
@@ -0,0 +1,4 @@
+#!/usr/bin/env sh
+# . "$(dirname -- "$0")/_/husky.sh"
+
+npm run build
diff --git a/.lintstagedrc.js b/.lintstagedrc.js
new file mode 100644
index 0000000..1937674
--- /dev/null
+++ b/.lintstagedrc.js
@@ -0,0 +1,13 @@
+module.exports = {
+ // Type check TypeScript files
+ '**/*.(ts|tsx)': () => 'npx tsc --noEmit',
+
+ // Lint & Prettify TS and JS files
+ '**/*.(ts|tsx)': (filenames) => {
+ return [
+ `npx eslint --cache --fix ${filenames.join(' ')}`,
+ `npx prettier --write ${filenames.join(' ')}`,
+ // `npx stylelint --ignore-path .gitignore ${filenames.join(' ')}`,
+ ];
+ },
+};
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..f1d1b18
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,19 @@
+{
+ "tabWidth": 2,
+ "semi": true,
+ "singleQuote": true,
+ "useTabs": false,
+ "trailingComma": "all",
+ "printWidth": 100,
+ "arrowParens": "always",
+ "bracketSpacing": true,
+ "jsxBracketSameLine": false,
+ "overrides": [
+ {
+ "files": "*.json",
+ "options": {
+ "printWidth": 150
+ }
+ }
+ ]
+}
diff --git a/.stylelintrc.json b/.stylelintrc.json
new file mode 100644
index 0000000..1ea3858
--- /dev/null
+++ b/.stylelintrc.json
@@ -0,0 +1,60 @@
+{
+ "extends": ["stylelint-config-standard-scss"],
+ "plugins": ["stylelint-order", "stylelint-prettier"],
+ "overrides": [
+ {
+ "files": ["**/*.scss"],
+ "customSyntax": "postcss-scss"
+ }
+ ],
+ "rules": {
+ "media-feature-range-notation": "prefix",
+ "order/properties-order": [
+ "display",
+ "align-items",
+ "justify-content",
+ "clear",
+ "float",
+ "overflow",
+ "position",
+ "top",
+ "right",
+ "bottom",
+ "left",
+ "z-index",
+ "width",
+ "height",
+ "margin",
+ "padding",
+ "background",
+ "background-size",
+ "border",
+ "color",
+ "font-size",
+ "font-weight",
+ "text-overflow",
+ "text-align",
+ "line-height",
+ "text-indent",
+ "transform",
+ "transition",
+ "box-sizing",
+ "white-space"
+ ],
+ "scss/no-global-function-names": null,
+ "selector-class-pattern": null,
+ "selector-pseudo-class-no-unknown": [
+ true,
+ {
+ "ignorePseudoClasses": ["deep"]
+ }
+ ],
+ "no-descending-specificity": null,
+ "font-family-no-missing-generic-family-keyword": [
+ true,
+ {
+ "ignoreFontFamilies": ["Font Awesome 5 Free"]
+ }
+ ]
+ }
+}
diff --git a/build.sh b/build.sh
new file mode 100644
index 0000000..496c82c
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+cd ../
+mkdir output
+cp -R ./DDD-10-KKEUNKKEUN-WEB/* ./output
+cp -R ./output ./DDD-10-KKEUNKKEUN-WEB/
\ No newline at end of file
diff --git a/next.config.js b/next.config.js
index 767719f..80dea1b 100644
--- a/next.config.js
+++ b/next.config.js
@@ -1,4 +1,20 @@
/** @type {import('next').NextConfig} */
-const nextConfig = {}
+const nextConfig = {
+ reactStrictMode: false,
+ images: {
+ remotePatterns: [{ protocol: 'http', hostname: '124.49.161.33' }],
+ },
+ async rewrites() {
+ return [
+ {
+ source: '/api/:path*',
+ destination:
+ process.env.NODE_ENV === 'development'
+ ? `${process.env.NEXT_PUBLIC_BASE_URL_DEV}/api/:path*`
+ : `${process.env.NEXT_PUBLIC_BASE_URL_PROD}/api/:path*`,
+ },
+ ];
+ },
+};
-module.exports = nextConfig
+module.exports = nextConfig;
diff --git a/package-lock.json b/package-lock.json
index 5f69ccc..01312e6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7,17 +7,53 @@
"": {
"name": "ddd-fe",
"version": "0.1.0",
- "dependencies": {
+ "hasInstallScript": true,
+ "dependencies": {
+ "@draft-js-plugins/editor": "^4.1.4",
+ "@draft-js-plugins/inline-toolbar": "^4.2.1",
+ "@stomp/stompjs": "^7.0.0",
+ "@tanstack/react-query": "^5.17.1",
+ "@tanstack/react-query-devtools": "^5.17.1",
+ "apexcharts": "^3.48.0",
+ "classnames": "^2.5.1",
+ "draft-js": "^0.11.7",
+ "draft-js-export-html": "^1.4.1",
+ "draftjs-to-html": "^0.9.1",
+ "html-to-draftjs": "^1.5.0",
+ "lodash-es": "^4.17.21",
+ "moment": "^2.30.1",
"next": "14.0.4",
+ "next-auth": "^5.0.0-beta.4",
"react": "^18",
- "react-dom": "^18"
+ "react-apexcharts": "^1.4.1",
+ "react-beautiful-dnd": "^13.1.1",
+ "react-calendar": "^4.8.0",
+ "react-dom": "^18",
+ "react-hook-form": "^7.49.3",
+ "react-intersection-observer": "^9.8.1",
+ "react-qr-code": "^2.0.12",
+ "zustand": "^4.4.7"
},
"devDependencies": {
+ "@types/draft-js": "^0.11.17",
+ "@types/lodash-es": "^4.17.12",
"@types/node": "^20",
"@types/react": "^18",
+ "@types/react-beautiful-dnd": "^13.1.8",
"@types/react-dom": "^18",
+ "@typescript-eslint/eslint-plugin": "^6.15.0",
+ "@typescript-eslint/parser": "^6.15.0",
"eslint": "^8",
"eslint-config-next": "14.0.4",
+ "eslint-config-prettier": "^9.1.0",
+ "eslint-plugin-prettier": "^5.1.0",
+ "husky": "^8.0.3",
+ "lint-staged": "^15.2.0",
+ "sass": "^1.69.7",
+ "stylelint": "^16.1.0",
+ "stylelint-config-standard-scss": "^12.0.0",
+ "stylelint-order": "^6.0.4",
+ "stylelint-prettier": "^5.0.0",
"typescript": "^5"
}
},
@@ -30,11 +66,209 @@
"node": ">=0.10.0"
}
},
+ "node_modules/@auth/core": {
+ "version": "0.18.4",
+ "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.18.4.tgz",
+ "integrity": "sha512-GsNhsP1xE/3FoNS3dVkPjqRljLNJ4iyL2OLv3klQGNvw3bMpROFcK4lqhx7+pPHiamnVaYt2vg1xbB+lsNaevg==",
+ "dependencies": {
+ "@panva/hkdf": "^1.1.1",
+ "cookie": "0.6.0",
+ "jose": "^5.1.0",
+ "oauth4webapi": "^2.3.0",
+ "preact": "10.11.3",
+ "preact-render-to-string": "5.2.3"
+ },
+ "peerDependencies": {
+ "nodemailer": "^6.8.0"
+ },
+ "peerDependenciesMeta": {
+ "nodemailer": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.23.5",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz",
+ "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/highlight": "^7.23.4",
+ "chalk": "^2.4.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/code-frame/node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/code-frame/node_modules/chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/code-frame/node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/@babel/code-frame/node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+ "dev": true
+ },
+ "node_modules/@babel/code-frame/node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/@babel/code-frame/node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/code-frame/node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.22.20",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
+ "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/highlight": {
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz",
+ "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.22.20",
+ "chalk": "^2.4.2",
+ "js-tokens": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+ "dev": true
+ },
+ "node_modules/@babel/highlight/node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/@babel/runtime": {
"version": "7.23.6",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.6.tgz",
"integrity": "sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ==",
- "dev": true,
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
@@ -42,6 +276,157 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@csstools/css-parser-algorithms": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.5.0.tgz",
+ "integrity": "sha512-abypo6m9re3clXA00eu5syw+oaPHbJTPapu9C4pzNsJ4hdZDzushT50Zhu+iIYXgEe1CxnRMn7ngsbV+MLrlpQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "engines": {
+ "node": "^14 || ^16 || >=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-tokenizer": "^2.2.3"
+ }
+ },
+ "node_modules/@csstools/css-tokenizer": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.2.3.tgz",
+ "integrity": "sha512-pp//EvZ9dUmGuGtG1p+n17gTHEOqu9jO+FiCUjNN3BDmyhdA2Jq9QsVeR7K8/2QCK17HSsioPlTW9ZkzoWb3Lg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "engines": {
+ "node": "^14 || ^16 || >=18"
+ }
+ },
+ "node_modules/@csstools/media-query-list-parser": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.7.tgz",
+ "integrity": "sha512-lHPKJDkPUECsyAvD60joYfDmp8UERYxHGkFfyLJFTVK/ERJe0sVlIFLXU5XFxdjNDTerp5L4KeaKG+Z5S94qxQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "engines": {
+ "node": "^14 || ^16 || >=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^2.5.0",
+ "@csstools/css-tokenizer": "^2.2.3"
+ }
+ },
+ "node_modules/@csstools/selector-specificity": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.0.1.tgz",
+ "integrity": "sha512-NPljRHkq4a14YzZ3YD406uaxh7s0g6eAq3L9aLOWywoqe8PkYamAvtsh7KNX6c++ihDrJ0RiU+/z7rGnhlZ5ww==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "engines": {
+ "node": "^14 || ^16 || >=18"
+ },
+ "peerDependencies": {
+ "postcss-selector-parser": "^6.0.13"
+ }
+ },
+ "node_modules/@draft-js-plugins/buttons": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@draft-js-plugins/buttons/-/buttons-4.3.3.tgz",
+ "integrity": "sha512-XGQBiRXYLGAO6uBrajpg1Tl42FD7o12oT9bM+ikciPMXdHOyJ6y8cJhY4YZI4AYhY37MNf6bdh9/p81TtIpcHA==",
+ "dependencies": {
+ "clsx": "^1.2.1"
+ },
+ "peerDependencies": {
+ "draft-js": "^0.10.1 || ^0.11.0",
+ "react": "^16.8.0 || ^17.0.0 || >=18.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@draft-js-plugins/buttons/node_modules/clsx": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
+ "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@draft-js-plugins/editor": {
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/@draft-js-plugins/editor/-/editor-4.1.4.tgz",
+ "integrity": "sha512-NlE1AIsPPfmdn+JIwwmcAm18FgwJ9/A55+2VXf3US3PmITJVL+y9VORCwLbGh2sb0RXvgFOIbqs8pPAOe8F8WQ==",
+ "dependencies": {
+ "immutable": "~3.7.4",
+ "prop-types": "^15.8.1"
+ },
+ "peerDependencies": {
+ "draft-js": "^0.11.0",
+ "react": "^16.8.0 || ^17.0.0 || >=18.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@draft-js-plugins/editor/node_modules/immutable": {
+ "version": "3.7.6",
+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.7.6.tgz",
+ "integrity": "sha512-AizQPcaofEtO11RZhPPHBOJRdo/20MKQF9mBLnVkBoyHi1/zXK8fzVdnEpSV9gxqtnh6Qomfp3F0xT5qP/vThw==",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/@draft-js-plugins/inline-toolbar": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@draft-js-plugins/inline-toolbar/-/inline-toolbar-4.2.1.tgz",
+ "integrity": "sha512-+LNhv9UkXhKybtUVHFMh7yvp44G3HKv8E5I6BLA3el+n67mrc3I4Frm/LE/wxAJC0tIrT2sjWvg8EFs3S9CRZA==",
+ "dependencies": {
+ "@draft-js-plugins/buttons": "^4.3.3",
+ "@draft-js-plugins/utils": "^4.2.1"
+ },
+ "peerDependencies": {
+ "draft-js": "^0.10.1 || ^0.11.0",
+ "react": "^16.8.0 || ^17.0.0 || >=18.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@draft-js-plugins/utils": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@draft-js-plugins/utils/-/utils-4.2.1.tgz",
+ "integrity": "sha512-FtGtBM+zSPgwaaAujULi0eCixlOBsqRezhOcZvBi3y0A/LkvNtKhykEOzd9/96qnGVT54iMiOJb+hw62yk/CCw==",
+ "peerDependencies": {
+ "draft-js": "^0.10.1 || ^0.11.0"
+ }
+ },
"node_modules/@eslint-community/eslint-utils": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -131,6 +516,67 @@
"integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==",
"dev": true
},
+ "node_modules/@isaacs/cliui": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+ "dev": true,
+ "dependencies": {
+ "string-width": "^5.1.2",
+ "string-width-cjs": "npm:string-width@^4.2.0",
+ "strip-ansi": "^7.0.1",
+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+ "wrap-ansi": "^8.1.0",
+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/ansi-regex": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+ "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "dev": true,
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
"node_modules/@next/env": {
"version": "14.0.4",
"resolved": "https://registry.npmjs.org/@next/env/-/env-14.0.4.tgz",
@@ -315,12 +761,55 @@
"node": ">= 8"
}
},
+ "node_modules/@panva/hkdf": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.1.1.tgz",
+ "integrity": "sha512-dhPeilub1NuIG0X5Kvhh9lH4iW3ZsHlnzwgwbOlgwQ2wG1IqFzsgHqmKPk3WzsdWAeaxKJxgM0+W433RmN45GA==",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
+ "node_modules/@pkgjs/parseargs": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+ "dev": true,
+ "optional": true,
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@pkgr/utils": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz",
+ "integrity": "sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==",
+ "dev": true,
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "fast-glob": "^3.3.0",
+ "is-glob": "^4.0.3",
+ "open": "^9.1.0",
+ "picocolors": "^1.0.0",
+ "tslib": "^2.6.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/unts"
+ }
+ },
"node_modules/@rushstack/eslint-patch": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.6.1.tgz",
"integrity": "sha512-UY+FGM/2jjMkzQLn8pxcHGMaVLh9aEitG3zY2CiY7XHdLiz3bZOwa6oDxNqEMv7zZkV+cj5DOdz0cQ1BP5Hjgw==",
"dev": true
},
+ "node_modules/@stomp/stompjs": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@stomp/stompjs/-/stompjs-7.0.0.tgz",
+ "integrity": "sha512-fGdq4wPDnSV/KyOsjq4P+zLc8MFWC3lMmP5FBgLWKPJTYcuCbAIrnRGjB7q2jHZdYCOD5vxLuFoKIYLy5/u8Pw=="
+ },
"node_modules/@swc/helpers": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz",
@@ -329,16 +818,121 @@
"tslib": "^2.4.0"
}
},
- "node_modules/@types/json5": {
- "version": "0.0.29",
- "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
- "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
- "dev": true
+ "node_modules/@tanstack/query-core": {
+ "version": "5.17.1",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.17.1.tgz",
+ "integrity": "sha512-kUXozQmU7NBtzX5dM6qfFNZN+YK/9Ct37hnG/ogdgI4mExIx7VH/qRepsPhKfNrJz2w81/JykmM3Uug6sVpUSw==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
},
- "node_modules/@types/node": {
- "version": "20.10.5",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.5.tgz",
- "integrity": "sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==",
+ "node_modules/@tanstack/query-devtools": {
+ "version": "5.17.1",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.17.1.tgz",
+ "integrity": "sha512-gNdt6PYzYlyjtSAoO8Jt9GIFq5VSLLDV2qq0TCi1t/PGnpAIlIHqNZGYkQTPsy0FyGUTX3pCq4bd7v5z/wzf3A==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/react-query": {
+ "version": "5.17.1",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.17.1.tgz",
+ "integrity": "sha512-4JYgX0kU+pvwVQi5eRiHGvBK7WnahEl6lmaxd32ZVSKmByAxLgaewoxBR03cdDNse8lUD2zGOe0sx3M/EGRlmA==",
+ "dependencies": {
+ "@tanstack/query-core": "5.17.1"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^18.0.0"
+ }
+ },
+ "node_modules/@tanstack/react-query-devtools": {
+ "version": "5.17.1",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.17.1.tgz",
+ "integrity": "sha512-QWHqdEN2TJpj76r0yzdOJEopmPvdAHOJHAKXaygubRASqCqfcWGkOHGD9pqqHOfTu5eQdV1Csx97EuSjnHMKcA==",
+ "dependencies": {
+ "@tanstack/query-devtools": "5.17.1"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "@tanstack/react-query": "^5.17.1",
+ "react": "^18.0.0"
+ }
+ },
+ "node_modules/@types/draft-js": {
+ "version": "0.11.17",
+ "resolved": "https://registry.npmjs.org/@types/draft-js/-/draft-js-0.11.17.tgz",
+ "integrity": "sha512-th0OwSPftG2r5rvGxS7nt8I7hcWMlrQ9OJcNx6MdXAwO797W78IGEsGWonO+turVBWEd3qylZRGBVtpEX9vSYQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/react": "*",
+ "immutable": "~3.7.4"
+ }
+ },
+ "node_modules/@types/draft-js/node_modules/immutable": {
+ "version": "3.7.6",
+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.7.6.tgz",
+ "integrity": "sha512-AizQPcaofEtO11RZhPPHBOJRdo/20MKQF9mBLnVkBoyHi1/zXK8fzVdnEpSV9gxqtnh6Qomfp3F0xT5qP/vThw==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/@types/hoist-non-react-statics": {
+ "version": "3.3.5",
+ "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz",
+ "integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==",
+ "dependencies": {
+ "@types/react": "*",
+ "hoist-non-react-statics": "^3.3.0"
+ }
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true
+ },
+ "node_modules/@types/json5": {
+ "version": "0.0.29",
+ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
+ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
+ "dev": true
+ },
+ "node_modules/@types/lodash": {
+ "version": "4.14.202",
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz",
+ "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ=="
+ },
+ "node_modules/@types/lodash-es": {
+ "version": "4.17.12",
+ "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz",
+ "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/lodash": "*"
+ }
+ },
+ "node_modules/@types/lodash.memoize": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@types/lodash.memoize/-/lodash.memoize-4.1.9.tgz",
+ "integrity": "sha512-glY1nQuoqX4Ft8Uk+KfJudOD7DQbbEDF6k9XpGncaohW3RW4eSWBlx6AA0fZCrh40tZcQNH4jS/Oc59J6Eq+aw==",
+ "dependencies": {
+ "@types/lodash": "*"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "20.10.5",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.5.tgz",
+ "integrity": "sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==",
"dev": true,
"dependencies": {
"undici-types": "~5.26.4"
@@ -347,20 +941,27 @@
"node_modules/@types/prop-types": {
"version": "15.7.11",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
- "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==",
- "dev": true
+ "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng=="
},
"node_modules/@types/react": {
"version": "18.2.45",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.45.tgz",
"integrity": "sha512-TtAxCNrlrBp8GoeEp1npd5g+d/OejJHFxS3OWmrPBMFaVQMSN0OFySozJio5BHxTuTeug00AVXVAjfDSfk+lUg==",
- "dev": true,
"dependencies": {
"@types/prop-types": "*",
"@types/scheduler": "*",
"csstype": "^3.0.2"
}
},
+ "node_modules/@types/react-beautiful-dnd": {
+ "version": "13.1.8",
+ "resolved": "https://registry.npmjs.org/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.1.8.tgz",
+ "integrity": "sha512-E3TyFsro9pQuK4r8S/OL6G99eq7p8v29sX0PM7oT8Z+PJfZvSQTx4zTQbUJ+QZXioAF0e7TGBEcA1XhYhCweyQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
"node_modules/@types/react-dom": {
"version": "18.2.18",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.18.tgz",
@@ -370,12 +971,63 @@
"@types/react": "*"
}
},
+ "node_modules/@types/react-redux": {
+ "version": "7.1.33",
+ "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.33.tgz",
+ "integrity": "sha512-NF8m5AjWCkert+fosDsN3hAlHzpjSiXlVy9EgQEmLoBhaNXbmyeGs/aj5dQzKuF+/q+S7JQagorGDW8pJ28Hmg==",
+ "dependencies": {
+ "@types/hoist-non-react-statics": "^3.3.0",
+ "@types/react": "*",
+ "hoist-non-react-statics": "^3.3.0",
+ "redux": "^4.0.0"
+ }
+ },
"node_modules/@types/scheduler": {
"version": "0.16.8",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz",
- "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==",
+ "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A=="
+ },
+ "node_modules/@types/semver": {
+ "version": "7.5.6",
+ "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz",
+ "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==",
"dev": true
},
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "6.15.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.15.0.tgz",
+ "integrity": "sha512-j5qoikQqPccq9QoBAupOP+CBu8BaJ8BLjaXSioDISeTZkVO3ig7oSIKh3H+rEpee7xCXtWwSB4KIL5l6hWZzpg==",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.5.1",
+ "@typescript-eslint/scope-manager": "6.15.0",
+ "@typescript-eslint/type-utils": "6.15.0",
+ "@typescript-eslint/utils": "6.15.0",
+ "@typescript-eslint/visitor-keys": "6.15.0",
+ "debug": "^4.3.4",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.2.4",
+ "natural-compare": "^1.4.0",
+ "semver": "^7.5.4",
+ "ts-api-utils": "^1.0.1"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha",
+ "eslint": "^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@typescript-eslint/parser": {
"version": "6.15.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.15.0.tgz",
@@ -421,6 +1073,33 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "6.15.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.15.0.tgz",
+ "integrity": "sha512-CnmHKTfX6450Bo49hPg2OkIm/D/TVYV7jO1MCfPYGwf6x3GO0VU8YMO5AYMn+u3X05lRRxA4fWCz87GFQV6yVQ==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/typescript-estree": "6.15.0",
+ "@typescript-eslint/utils": "6.15.0",
+ "debug": "^4.3.4",
+ "ts-api-utils": "^1.0.1"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@typescript-eslint/types": {
"version": "6.15.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.15.0.tgz",
@@ -461,6 +1140,31 @@
}
}
},
+ "node_modules/@typescript-eslint/utils": {
+ "version": "6.15.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.15.0.tgz",
+ "integrity": "sha512-eF82p0Wrrlt8fQSRL0bGXzK5nWPRV2dYQZdajcfzOD9+cQz9O7ugifrJxclB+xVOvWvagXfqS4Es7vpLP4augw==",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.4.0",
+ "@types/json-schema": "^7.0.12",
+ "@types/semver": "^7.5.0",
+ "@typescript-eslint/scope-manager": "6.15.0",
+ "@typescript-eslint/types": "6.15.0",
+ "@typescript-eslint/typescript-estree": "6.15.0",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^7.0.0 || ^8.0.0"
+ }
+ },
"node_modules/@typescript-eslint/visitor-keys": {
"version": "6.15.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.15.0.tgz",
@@ -484,6 +1188,19 @@
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
"dev": true
},
+ "node_modules/@wojtekmaj/date-utils": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/@wojtekmaj/date-utils/-/date-utils-1.5.1.tgz",
+ "integrity": "sha512-+i7+JmNiE/3c9FKxzWFi2IjRJ+KzZl1QPu6QNrsgaa2MuBgXvUy4gA1TVzf/JMdIIloB76xSKikTWuyYAIVLww==",
+ "funding": {
+ "url": "https://github.com/wojtekmaj/date-utils?sponsor=1"
+ }
+ },
+ "node_modules/@yr/monotone-cubic-spline": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz",
+ "integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA=="
+ },
"node_modules/acorn": {
"version": "8.11.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz",
@@ -521,6 +1238,33 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
+ "node_modules/ansi-escapes": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.0.tgz",
+ "integrity": "sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw==",
+ "dev": true,
+ "dependencies": {
+ "type-fest": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ansi-escapes/node_modules/type-fest": {
+ "version": "3.13.1",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz",
+ "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==",
+ "dev": true,
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@@ -545,6 +1289,33 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "devOptional": true,
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/apexcharts": {
+ "version": "3.48.0",
+ "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.48.0.tgz",
+ "integrity": "sha512-Lhpj1Ij6lKlrUke8gf+P+SE6uGUn+Pe1TnCJ+zqrY0YMvbqM3LMb1lY+eybbTczUyk0RmMZomlTa2NgX2EUs4Q==",
+ "dependencies": {
+ "@yr/monotone-cubic-spline": "^1.0.3",
+ "svg.draggable.js": "^2.2.2",
+ "svg.easing.js": "^2.0.0",
+ "svg.filter.js": "^2.0.2",
+ "svg.pathmorphing.js": "^0.1.3",
+ "svg.resize.js": "^1.4.3",
+ "svg.select.js": "^3.0.1"
+ }
+ },
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -690,12 +1461,26 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/asap": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
+ "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="
+ },
"node_modules/ast-types-flow": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz",
"integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==",
"dev": true
},
+ "node_modules/astral-regex": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
+ "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/asynciterator.prototype": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz",
@@ -741,6 +1526,36 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
+ "node_modules/big-integer": {
+ "version": "1.6.52",
+ "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz",
+ "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+ "devOptional": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/bplist-parser": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz",
+ "integrity": "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==",
+ "dev": true,
+ "dependencies": {
+ "big-integer": "^1.6.44"
+ },
+ "engines": {
+ "node": ">= 5.10.0"
+ }
+ },
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -755,7 +1570,7 @@
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
- "dev": true,
+ "devOptional": true,
"dependencies": {
"fill-range": "^7.0.1"
},
@@ -763,6 +1578,21 @@
"node": ">=8"
}
},
+ "node_modules/bundle-name": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz",
+ "integrity": "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==",
+ "dev": true,
+ "dependencies": {
+ "run-applescript": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/busboy": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
@@ -832,55 +1662,342 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
- "node_modules/client-only": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
- "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
+ "node_modules/chokidar": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+ "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+ "devOptional": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ ],
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
},
- "node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
+ "node_modules/chokidar/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "devOptional": true,
"dependencies": {
- "color-name": "~1.1.4"
+ "is-glob": "^4.0.1"
},
"engines": {
- "node": ">=7.0.0"
+ "node": ">= 6"
}
},
- "node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true
+ "node_modules/classnames": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
+ "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="
},
- "node_modules/concat-map": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
- "dev": true
+ "node_modules/cli-cursor": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz",
+ "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==",
+ "dev": true,
+ "dependencies": {
+ "restore-cursor": "^4.0.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
},
- "node_modules/cross-spawn": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
- "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "node_modules/cli-truncate": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz",
+ "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==",
"dev": true,
"dependencies": {
- "path-key": "^3.1.0",
- "shebang-command": "^2.0.0",
- "which": "^2.0.1"
+ "slice-ansi": "^5.0.0",
+ "string-width": "^7.0.0"
},
"engines": {
- "node": ">= 8"
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/csstype": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
- "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+ "node_modules/cli-truncate/node_modules/ansi-regex": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+ "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/cli-truncate/node_modules/ansi-styles": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
+ "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/cli-truncate/node_modules/emoji-regex": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz",
+ "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==",
+ "dev": true
+ },
+ "node_modules/cli-truncate/node_modules/is-fullwidth-code-point": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz",
+ "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/cli-truncate/node_modules/slice-ansi": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz",
+ "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^6.0.0",
+ "is-fullwidth-code-point": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/slice-ansi?sponsor=1"
+ }
+ },
+ "node_modules/cli-truncate/node_modules/string-width": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.0.0.tgz",
+ "integrity": "sha512-GPQHj7row82Hjo9hKZieKcHIhaAIKOJvFSIZXuCU9OASVZrMNUaZuz++SPVrBjnLsnk4k+z9f2EIypgxf2vNFw==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^10.3.0",
+ "get-east-asian-width": "^1.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/cli-truncate/node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/client-only": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
+ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
+ },
+ "node_modules/clsx": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz",
+ "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/colord": {
+ "version": "2.9.3",
+ "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz",
+ "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==",
"dev": true
},
+ "node_modules/colorette": {
+ "version": "2.0.20",
+ "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
+ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
+ "dev": true
+ },
+ "node_modules/commander": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
+ "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true
+ },
+ "node_modules/cookie": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
+ "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/core-js": {
+ "version": "3.36.0",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.36.0.tgz",
+ "integrity": "sha512-mt7+TUBbTFg5+GngsAxeKBTl5/VS0guFeJacYge9OmHb+m058UwwIm41SE9T4Den7ClatV57B6TYTuJ0CX1MAw==",
+ "hasInstallScript": true,
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/core-js"
+ }
+ },
+ "node_modules/cosmiconfig": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz",
+ "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==",
+ "dev": true,
+ "dependencies": {
+ "env-paths": "^2.2.1",
+ "import-fresh": "^3.3.0",
+ "js-yaml": "^4.1.0",
+ "parse-json": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/d-fischer"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.9.5"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/cross-fetch": {
+ "version": "3.1.8",
+ "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz",
+ "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==",
+ "dependencies": {
+ "node-fetch": "^2.6.12"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+ "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/css-box-model": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz",
+ "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==",
+ "dependencies": {
+ "tiny-invariant": "^1.0.6"
+ }
+ },
+ "node_modules/css-functions-list": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.2.1.tgz",
+ "integrity": "sha512-Nj5YcaGgBtuUmn1D7oHqPW0c9iui7xsTsj5lIX8ZgevdfhmjFfKB3r8moHJtNJnctnYXJyYX5I1pp90HM4TPgQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=12 || >=16"
+ }
+ },
+ "node_modules/css-tree": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
+ "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
+ "dev": true,
+ "dependencies": {
+ "mdn-data": "2.0.30",
+ "source-map-js": "^1.0.1"
+ },
+ "engines": {
+ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
+ }
+ },
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "dev": true,
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
+ },
"node_modules/damerau-levenshtein": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
@@ -910,6 +2027,40 @@
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
"dev": true
},
+ "node_modules/default-browser": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz",
+ "integrity": "sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==",
+ "dev": true,
+ "dependencies": {
+ "bundle-name": "^3.0.0",
+ "default-browser-id": "^3.0.0",
+ "execa": "^7.1.1",
+ "titleize": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/default-browser-id": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz",
+ "integrity": "sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==",
+ "dev": true,
+ "dependencies": {
+ "bplist-parser": "^0.2.0",
+ "untildify": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/define-data-property": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz",
@@ -924,6 +2075,18 @@
"node": ">= 0.4"
}
},
+ "node_modules/define-lazy-prop": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz",
+ "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/define-properties": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
@@ -974,6 +2137,60 @@
"node": ">=6.0.0"
}
},
+ "node_modules/draft-js": {
+ "version": "0.11.7",
+ "resolved": "https://registry.npmjs.org/draft-js/-/draft-js-0.11.7.tgz",
+ "integrity": "sha512-ne7yFfN4sEL82QPQEn80xnADR8/Q6ALVworbC5UOSzOvjffmYfFsr3xSZtxbIirti14R7Y33EZC5rivpLgIbsg==",
+ "dependencies": {
+ "fbjs": "^2.0.0",
+ "immutable": "~3.7.4",
+ "object-assign": "^4.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=0.14.0",
+ "react-dom": ">=0.14.0"
+ }
+ },
+ "node_modules/draft-js-export-html": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/draft-js-export-html/-/draft-js-export-html-1.4.1.tgz",
+ "integrity": "sha512-G4VGBSalPowktIE4wp3rFbhjs+Ln9IZ2FhXeHjsZDSw0a2+h+BjKu5Enq+mcsyVb51RW740GBK8Xbf7Iic51tw==",
+ "dependencies": {
+ "draft-js-utils": "^1.4.0"
+ },
+ "peerDependencies": {
+ "draft-js": ">=0.10.0",
+ "immutable": "3.x.x"
+ }
+ },
+ "node_modules/draft-js-utils": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/draft-js-utils/-/draft-js-utils-1.4.1.tgz",
+ "integrity": "sha512-xE81Y+z/muC5D5z9qWmKfxEW1XyXfsBzSbSBk2JRsoD0yzMGGHQm/0MtuqHl/EUDkaBJJLjJ2EACycoDMY/OOg==",
+ "peerDependencies": {
+ "draft-js": ">=0.10.0",
+ "immutable": "3.x.x"
+ }
+ },
+ "node_modules/draft-js/node_modules/immutable": {
+ "version": "3.7.6",
+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.7.6.tgz",
+ "integrity": "sha512-AizQPcaofEtO11RZhPPHBOJRdo/20MKQF9mBLnVkBoyHi1/zXK8fzVdnEpSV9gxqtnh6Qomfp3F0xT5qP/vThw==",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/draftjs-to-html": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/draftjs-to-html/-/draftjs-to-html-0.9.1.tgz",
+ "integrity": "sha512-fFstE6+IayaVFBEvaFt/wN8vdj8FsTRzij7dy7LI9QIwf5LgfHFi9zSpvCg+feJ2tbYVqHxUkjcibwpsTpgFVQ=="
+ },
+ "node_modules/eastasianwidth": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+ "dev": true
+ },
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
@@ -993,6 +2210,24 @@
"node": ">=10.13.0"
}
},
+ "node_modules/env-paths": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
+ "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "dev": true,
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
"node_modules/es-abstract": {
"version": "1.22.3",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz",
@@ -1201,6 +2436,18 @@
}
}
},
+ "node_modules/eslint-config-prettier": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz",
+ "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==",
+ "dev": true,
+ "bin": {
+ "eslint-config-prettier": "bin/cli.js"
+ },
+ "peerDependencies": {
+ "eslint": ">=7.0.0"
+ }
+ },
"node_modules/eslint-import-resolver-node": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz",
@@ -1363,6 +2610,36 @@
"eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8"
}
},
+ "node_modules/eslint-plugin-prettier": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.0.tgz",
+ "integrity": "sha512-hQc+2zbnMeXcIkg+pKZtVa+3Yqx4WY7SMkn1PLZ4VbBEU7jJIpVn9347P8BBhTbz6ne85aXvQf30kvexcqBeWw==",
+ "dev": true,
+ "dependencies": {
+ "prettier-linter-helpers": "^1.0.0",
+ "synckit": "^0.8.5"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/prettier"
+ },
+ "peerDependencies": {
+ "@types/eslint": ">=8.0.0",
+ "eslint": ">=8.0.0",
+ "eslint-config-prettier": "*",
+ "prettier": ">=3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/eslint": {
+ "optional": true
+ },
+ "eslint-config-prettier": {
+ "optional": true
+ }
+ }
+ },
"node_modules/eslint-plugin-react": {
"version": "7.33.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz",
@@ -1530,19 +2807,54 @@
"node": ">=0.10.0"
}
},
- "node_modules/fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "node_modules/eventemitter3": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
+ "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
"dev": true
},
- "node_modules/fast-glob": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
- "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
+ "node_modules/execa": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz",
+ "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==",
"dev": true,
"dependencies": {
- "@nodelib/fs.stat": "^2.0.2",
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^6.0.1",
+ "human-signals": "^4.3.0",
+ "is-stream": "^3.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^5.1.0",
+ "onetime": "^6.0.0",
+ "signal-exit": "^3.0.7",
+ "strip-final-newline": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || ^16.14.0 || >=18.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true
+ },
+ "node_modules/fast-diff": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
+ "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
+ "dev": true
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
+ "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
"glob-parent": "^5.1.2",
"merge2": "^1.3.0",
@@ -1576,6 +2888,15 @@
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true
},
+ "node_modules/fastest-levenshtein": {
+ "version": "1.0.16",
+ "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz",
+ "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4.9.1"
+ }
+ },
"node_modules/fastq": {
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz",
@@ -1585,6 +2906,26 @@
"reusify": "^1.0.4"
}
},
+ "node_modules/fbjs": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-2.0.0.tgz",
+ "integrity": "sha512-8XA8ny9ifxrAWlyhAbexXcs3rRMtxWcs3M0lctLfB49jRDHiaxj+Mo0XxbwE7nKZYzgCFoq64FS+WFd4IycPPQ==",
+ "dependencies": {
+ "core-js": "^3.6.4",
+ "cross-fetch": "^3.0.4",
+ "fbjs-css-vars": "^1.0.0",
+ "loose-envify": "^1.0.0",
+ "object-assign": "^4.1.0",
+ "promise": "^7.1.1",
+ "setimmediate": "^1.0.5",
+ "ua-parser-js": "^0.7.18"
+ }
+ },
+ "node_modules/fbjs-css-vars": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz",
+ "integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ=="
+ },
"node_modules/file-entry-cache": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@@ -1601,7 +2942,7 @@
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
- "dev": true,
+ "devOptional": true,
"dependencies": {
"to-regex-range": "^5.0.1"
},
@@ -1654,6 +2995,34 @@
"is-callable": "^1.1.3"
}
},
+ "node_modules/foreground-child": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
+ "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
+ "dev": true,
+ "dependencies": {
+ "cross-spawn": "^7.0.0",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/foreground-child/node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -1696,6 +3065,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/get-east-asian-width": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz",
+ "integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==",
+ "dev": true,
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/get-intrinsic": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz",
@@ -1711,6 +3092,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/get-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/get-symbol-description": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz",
@@ -1739,6 +3132,18 @@
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
},
+ "node_modules/get-user-locale": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/get-user-locale/-/get-user-locale-2.3.1.tgz",
+ "integrity": "sha512-VEvcsqKYx7zhZYC1CjecrDC5ziPSpl1gSm0qFFJhHSGDrSC+x4+p1KojWC/83QX//j476gFhkVXP/kNUc9q+bQ==",
+ "dependencies": {
+ "@types/lodash.memoize": "^4.1.7",
+ "lodash.memoize": "^4.1.1"
+ },
+ "funding": {
+ "url": "https://github.com/wojtekmaj/get-user-locale?sponsor=1"
+ }
+ },
"node_modules/glob": {
"version": "7.1.7",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz",
@@ -1776,6 +3181,44 @@
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="
},
+ "node_modules/global-modules": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz",
+ "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==",
+ "dev": true,
+ "dependencies": {
+ "global-prefix": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/global-prefix": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz",
+ "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==",
+ "dev": true,
+ "dependencies": {
+ "ini": "^1.3.5",
+ "kind-of": "^6.0.2",
+ "which": "^1.3.1"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/global-prefix/node_modules/which": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+ "dev": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "which": "bin/which"
+ }
+ },
"node_modules/globals": {
"version": "13.24.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
@@ -1826,6 +3269,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/globjoin": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz",
+ "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==",
+ "dev": true
+ },
"node_modules/gopd": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
@@ -1930,6 +3379,59 @@
"node": ">= 0.4"
}
},
+ "node_modules/hoist-non-react-statics": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+ "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+ "dependencies": {
+ "react-is": "^16.7.0"
+ }
+ },
+ "node_modules/html-tags": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz",
+ "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/html-to-draftjs": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/html-to-draftjs/-/html-to-draftjs-1.5.0.tgz",
+ "integrity": "sha512-kggLXBNciKDwKf+KYsuE+V5gw4dZ7nHyGMX9m0wy7urzWjKGWyNFetmArRLvRV0VrxKN70WylFsJvMTJx02OBQ==",
+ "peerDependencies": {
+ "draft-js": "^0.10.x || ^0.11.x",
+ "immutable": "3.x.x || 4.x.x"
+ }
+ },
+ "node_modules/human-signals": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz",
+ "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=14.18.0"
+ }
+ },
+ "node_modules/husky": {
+ "version": "8.0.3",
+ "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz",
+ "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==",
+ "dev": true,
+ "bin": {
+ "husky": "lib/bin.js"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/typicode"
+ }
+ },
"node_modules/ignore": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz",
@@ -1939,6 +3441,15 @@
"node": ">= 4"
}
},
+ "node_modules/immutable": {
+ "version": "3.8.2",
+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz",
+ "integrity": "sha512-15gZoQ38eYjEjxkorfbcgBKBL6R7T459OuK+CpcWt7O3KF4uPCx2tD0uFETlUDIyo+1789crbMhTvQBSR5yBMg==",
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@@ -1980,6 +3491,12 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
+ "node_modules/ini": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
+ "dev": true
+ },
"node_modules/internal-slot": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz",
@@ -2008,6 +3525,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "dev": true
+ },
"node_modules/is-async-function": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz",
@@ -2035,6 +3558,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "devOptional": true,
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/is-boolean-object": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
@@ -2090,11 +3625,26 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-docker": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz",
+ "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==",
+ "dev": true,
+ "bin": {
+ "is-docker": "cli.js"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
- "dev": true,
+ "devOptional": true,
"engines": {
"node": ">=0.10.0"
}
@@ -2111,6 +3661,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/is-generator-function": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz",
@@ -2130,7 +3689,7 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
- "dev": true,
+ "devOptional": true,
"dependencies": {
"is-extglob": "^2.1.1"
},
@@ -2138,6 +3697,24 @@
"node": ">=0.10.0"
}
},
+ "node_modules/is-inside-container": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz",
+ "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==",
+ "dev": true,
+ "dependencies": {
+ "is-docker": "^3.0.0"
+ },
+ "bin": {
+ "is-inside-container": "cli.js"
+ },
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/is-map": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz",
@@ -2163,7 +3740,7 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
- "dev": true,
+ "devOptional": true,
"engines": {
"node": ">=0.12.0"
}
@@ -2192,6 +3769,15 @@
"node": ">=8"
}
},
+ "node_modules/is-plain-object": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
+ "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/is-regex": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
@@ -2229,6 +3815,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-stream": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
+ "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
+ "dev": true,
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/is-string": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
@@ -2308,6 +3906,33 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-wsl": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
+ "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
+ "dev": true,
+ "dependencies": {
+ "is-docker": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-wsl/node_modules/is-docker": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
+ "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
+ "dev": true,
+ "bin": {
+ "is-docker": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/isarray": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
@@ -2333,6 +3958,32 @@
"set-function-name": "^2.0.1"
}
},
+ "node_modules/jackspeak": {
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz",
+ "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==",
+ "dev": true,
+ "dependencies": {
+ "@isaacs/cliui": "^8.0.2"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ },
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
+ }
+ },
+ "node_modules/jose": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-5.2.0.tgz",
+ "integrity": "sha512-oW3PCnvyrcm1HMvGTzqjxxfnEs9EoFOFWi2HsEGhlFVOXxTE3K9GKWVMFoFw06yPUqwpvEWic1BmtUZBI/tIjw==",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -2356,6 +4007,12 @@
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
"dev": true
},
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "dev": true
+ },
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@@ -2404,6 +4061,21 @@
"json-buffer": "3.0.1"
}
},
+ "node_modules/kind-of": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/known-css-properties": {
+ "version": "0.29.0",
+ "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.29.0.tgz",
+ "integrity": "sha512-Ne7wqW7/9Cz54PDt4I3tcV+hAyat8ypyOGzYRJQfdxnnjeWsTxt1cy8pjvvKeI5kfXuyvULyeeAvwvvtAX3ayQ==",
+ "dev": true
+ },
"node_modules/language-subtag-registry": {
"version": "0.3.22",
"resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz",
@@ -2435,36 +4107,387 @@
"node": ">= 0.8.0"
}
},
- "node_modules/locate-path": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
- "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "node_modules/lilconfig": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz",
+ "integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==",
"dev": true,
- "dependencies": {
- "p-locate": "^5.0.0"
- },
"engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "node": ">=14"
}
},
- "node_modules/lodash.merge": {
- "version": "4.6.2",
- "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
- "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
"dev": true
},
- "node_modules/loose-envify": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
- "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "node_modules/lint-staged": {
+ "version": "15.2.0",
+ "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.0.tgz",
+ "integrity": "sha512-TFZzUEV00f+2YLaVPWBWGAMq7So6yQx+GG8YRMDeOEIf95Zn5RyiLMsEiX4KTNl9vq/w+NqRJkLA1kPIo15ufQ==",
+ "dev": true,
"dependencies": {
- "js-tokens": "^3.0.0 || ^4.0.0"
+ "chalk": "5.3.0",
+ "commander": "11.1.0",
+ "debug": "4.3.4",
+ "execa": "8.0.1",
+ "lilconfig": "3.0.0",
+ "listr2": "8.0.0",
+ "micromatch": "4.0.5",
+ "pidtree": "0.6.0",
+ "string-argv": "0.3.2",
+ "yaml": "2.3.4"
},
"bin": {
- "loose-envify": "cli.js"
+ "lint-staged": "bin/lint-staged.js"
+ },
+ "engines": {
+ "node": ">=18.12.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/lint-staged"
+ }
+ },
+ "node_modules/lint-staged/node_modules/chalk": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
+ "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
+ "dev": true,
+ "engines": {
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/lint-staged/node_modules/execa": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
+ "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==",
+ "dev": true,
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^8.0.1",
+ "human-signals": "^5.0.0",
+ "is-stream": "^3.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^5.1.0",
+ "onetime": "^6.0.0",
+ "signal-exit": "^4.1.0",
+ "strip-final-newline": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=16.17"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/lint-staged/node_modules/get-stream": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz",
+ "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==",
+ "dev": true,
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lint-staged/node_modules/human-signals": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
+ "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=16.17.0"
+ }
+ },
+ "node_modules/lint-staged/node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/listr2": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.0.0.tgz",
+ "integrity": "sha512-u8cusxAcyqAiQ2RhYvV7kRKNLgUvtObIbhOX2NCXqvp1UU32xIg5CT22ykS2TPKJXZWJwtK3IKLiqAGlGNE+Zg==",
+ "dev": true,
+ "dependencies": {
+ "cli-truncate": "^4.0.0",
+ "colorette": "^2.0.20",
+ "eventemitter3": "^5.0.1",
+ "log-update": "^6.0.0",
+ "rfdc": "^1.3.0",
+ "wrap-ansi": "^9.0.0"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/listr2/node_modules/ansi-regex": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+ "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/listr2/node_modules/ansi-styles": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
+ "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/listr2/node_modules/emoji-regex": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz",
+ "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==",
+ "dev": true
+ },
+ "node_modules/listr2/node_modules/string-width": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.0.0.tgz",
+ "integrity": "sha512-GPQHj7row82Hjo9hKZieKcHIhaAIKOJvFSIZXuCU9OASVZrMNUaZuz++SPVrBjnLsnk4k+z9f2EIypgxf2vNFw==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^10.3.0",
+ "get-east-asian-width": "^1.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/listr2/node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/listr2/node_modules/wrap-ansi": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz",
+ "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^6.2.1",
+ "string-width": "^7.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash-es": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
+ "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
+ },
+ "node_modules/lodash.memoize": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
+ "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag=="
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true
+ },
+ "node_modules/lodash.truncate": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz",
+ "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==",
+ "dev": true
+ },
+ "node_modules/log-update": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.0.0.tgz",
+ "integrity": "sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw==",
+ "dev": true,
+ "dependencies": {
+ "ansi-escapes": "^6.2.0",
+ "cli-cursor": "^4.0.0",
+ "slice-ansi": "^7.0.0",
+ "strip-ansi": "^7.1.0",
+ "wrap-ansi": "^9.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/log-update/node_modules/ansi-regex": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+ "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/log-update/node_modules/ansi-styles": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
+ "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/log-update/node_modules/emoji-regex": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz",
+ "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==",
+ "dev": true
+ },
+ "node_modules/log-update/node_modules/is-fullwidth-code-point": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz",
+ "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==",
+ "dev": true,
+ "dependencies": {
+ "get-east-asian-width": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/log-update/node_modules/slice-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz",
+ "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^6.2.1",
+ "is-fullwidth-code-point": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/slice-ansi?sponsor=1"
+ }
+ },
+ "node_modules/log-update/node_modules/string-width": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.0.0.tgz",
+ "integrity": "sha512-GPQHj7row82Hjo9hKZieKcHIhaAIKOJvFSIZXuCU9OASVZrMNUaZuz++SPVrBjnLsnk4k+z9f2EIypgxf2vNFw==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^10.3.0",
+ "get-east-asian-width": "^1.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/log-update/node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/log-update/node_modules/wrap-ansi": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz",
+ "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^6.2.1",
+ "string-width": "^7.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
}
},
"node_modules/lru-cache": {
@@ -2479,6 +4502,45 @@
"node": ">=10"
}
},
+ "node_modules/mathml-tag-names": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz",
+ "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==",
+ "dev": true,
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/mdn-data": {
+ "version": "2.0.30",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
+ "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
+ "dev": true
+ },
+ "node_modules/memoize-one": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
+ "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="
+ },
+ "node_modules/meow": {
+ "version": "13.0.0",
+ "resolved": "https://registry.npmjs.org/meow/-/meow-13.0.0.tgz",
+ "integrity": "sha512-4Hu+75Vo7EOR+8C9RmkabfLijuwd9SrzQ8f0SyC4qZZwU6BlxeOt5ulF3PGCpcMJX4hI+ktpJhea0P6PN1RiWw==",
+ "dev": true,
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true
+ },
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -2501,6 +4563,18 @@
"node": ">=8.6"
}
},
+ "node_modules/mimic-fn": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
+ "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -2522,6 +4596,23 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/minipass": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
+ "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/moment": {
+ "version": "2.30.1",
+ "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
+ "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -2597,19 +4688,99 @@
}
}
},
- "node_modules/object-assign": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
- "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
- "dev": true,
+ "node_modules/next-auth": {
+ "version": "5.0.0-beta.4",
+ "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-5.0.0-beta.4.tgz",
+ "integrity": "sha512-vgocjvwPA8gxd/zrIP/vr9lJ/HeNe+C56lPP1D3sdyenHt8KncQV6ro7q0xCsDp1fcOKx7WAWVZH5o8aMxDzgw==",
+ "dependencies": {
+ "@auth/core": "0.18.4"
+ },
+ "peerDependencies": {
+ "next": "^14",
+ "nodemailer": "^6.6.5",
+ "react": "^18.2.0"
+ },
+ "peerDependenciesMeta": {
+ "nodemailer": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/node-fetch": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ },
+ "peerDependencies": {
+ "encoding": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "encoding": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "devOptional": true,
"engines": {
"node": ">=0.10.0"
}
},
- "node_modules/object-inspect": {
- "version": "1.13.1",
- "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
- "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
+ "node_modules/npm-run-path": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz",
+ "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^4.0.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/npm-run-path/node_modules/path-key": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
+ "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/oauth4webapi": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-2.4.1.tgz",
+ "integrity": "sha512-qor45aeDGaGqDOizwut+Q/rZ+J6BIJvOp7U0LtHfbFhg3O7JV5lvQFDXPNqSmcUjuq/Zeq8CIII4RD0sWLOdSQ==",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.1",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
+ "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
"dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -2724,6 +4895,39 @@
"wrappy": "1"
}
},
+ "node_modules/onetime": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
+ "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
+ "dev": true,
+ "dependencies": {
+ "mimic-fn": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/open": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz",
+ "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==",
+ "dev": true,
+ "dependencies": {
+ "default-browser": "^4.0.0",
+ "define-lazy-prop": "^3.0.0",
+ "is-inside-container": "^1.0.0",
+ "is-wsl": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/optionator": {
"version": "0.9.3",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
@@ -2783,6 +4987,24 @@
"node": ">=6"
}
},
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -2816,6 +5038,31 @@
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true
},
+ "node_modules/path-scurry": {
+ "version": "1.10.1",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz",
+ "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==",
+ "dev": true,
+ "dependencies": {
+ "lru-cache": "^9.1.1 || ^10.0.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/path-scurry/node_modules/lru-cache": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz",
+ "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==",
+ "dev": true,
+ "engines": {
+ "node": "14 || >=16.14"
+ }
+ },
"node_modules/path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
@@ -2834,7 +5081,7 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
- "dev": true,
+ "devOptional": true,
"engines": {
"node": ">=8.6"
},
@@ -2842,6 +5089,18 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/pidtree": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz",
+ "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==",
+ "dev": true,
+ "bin": {
+ "pidtree": "bin/pidtree.js"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
"node_modules/postcss": {
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
@@ -2869,6 +5128,92 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/postcss-media-query-parser": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz",
+ "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==",
+ "dev": true
+ },
+ "node_modules/postcss-resolve-nested-selector": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz",
+ "integrity": "sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw==",
+ "dev": true
+ },
+ "node_modules/postcss-scss": {
+ "version": "4.0.9",
+ "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.9.tgz",
+ "integrity": "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss-scss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "engines": {
+ "node": ">=12.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.29"
+ }
+ },
+ "node_modules/postcss-selector-parser": {
+ "version": "6.0.15",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz",
+ "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==",
+ "dev": true,
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postcss-sorting": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-8.0.2.tgz",
+ "integrity": "sha512-M9dkSrmU00t/jK7rF6BZSZauA5MAaBW4i5EnJXspMwt4iqTh/L9j6fgMnbElEOfyRyfLfVbIHj/R52zHzAPe1Q==",
+ "dev": true,
+ "peerDependencies": {
+ "postcss": "^8.4.20"
+ }
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "dev": true
+ },
+ "node_modules/preact": {
+ "version": "10.11.3",
+ "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz",
+ "integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/preact"
+ }
+ },
+ "node_modules/preact-render-to-string": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.3.tgz",
+ "integrity": "sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==",
+ "dependencies": {
+ "pretty-format": "^3.8.0"
+ },
+ "peerDependencies": {
+ "preact": ">=10"
+ }
+ },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -2878,11 +5223,51 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/prettier": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz",
+ "integrity": "sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==",
+ "dev": true,
+ "peer": true,
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/prettier-linter-helpers": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
+ "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
+ "dev": true,
+ "dependencies": {
+ "fast-diff": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/pretty-format": {
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz",
+ "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew=="
+ },
+ "node_modules/promise": {
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
+ "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
+ "dependencies": {
+ "asap": "~2.0.3"
+ }
+ },
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
- "dev": true,
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
@@ -2898,6 +5283,11 @@
"node": ">=6"
}
},
+ "node_modules/qr.js": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/qr.js/-/qr.js-0.0.0.tgz",
+ "integrity": "sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ=="
+ },
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -2918,6 +5308,11 @@
}
]
},
+ "node_modules/raf-schd": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz",
+ "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ=="
+ },
"node_modules/react": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
@@ -2929,6 +5324,61 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-apexcharts": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/react-apexcharts/-/react-apexcharts-1.4.1.tgz",
+ "integrity": "sha512-G14nVaD64Bnbgy8tYxkjuXEUp/7h30Q0U33xc3AwtGFijJB9nHqOt1a6eG0WBn055RgRg+NwqbKGtqPxy15d0Q==",
+ "dependencies": {
+ "prop-types": "^15.8.1"
+ },
+ "peerDependencies": {
+ "apexcharts": "^3.41.0",
+ "react": ">=0.13"
+ }
+ },
+ "node_modules/react-beautiful-dnd": {
+ "version": "13.1.1",
+ "resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz",
+ "integrity": "sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.9.2",
+ "css-box-model": "^1.2.0",
+ "memoize-one": "^5.1.1",
+ "raf-schd": "^4.0.2",
+ "react-redux": "^7.2.0",
+ "redux": "^4.0.4",
+ "use-memo-one": "^1.1.1"
+ },
+ "peerDependencies": {
+ "react": "^16.8.5 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.8.5 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/react-calendar": {
+ "version": "4.8.0",
+ "resolved": "https://registry.npmjs.org/react-calendar/-/react-calendar-4.8.0.tgz",
+ "integrity": "sha512-qFgwo+p58sgv1QYMI1oGNaop90eJVKuHTZ3ZgBfrrpUb+9cAexxsKat0sAszgsizPMVo7vOXedV7Lqa0GQGMvA==",
+ "dependencies": {
+ "@wojtekmaj/date-utils": "^1.1.3",
+ "clsx": "^2.0.0",
+ "get-user-locale": "^2.2.1",
+ "prop-types": "^15.6.0",
+ "warning": "^4.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/wojtekmaj/react-calendar?sponsor=1"
+ },
+ "peerDependencies": {
+ "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/react-dom": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
@@ -2941,11 +5391,107 @@
"react": "^18.2.0"
}
},
+ "node_modules/react-hook-form": {
+ "version": "7.49.3",
+ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.49.3.tgz",
+ "integrity": "sha512-foD6r3juidAT1cOZzpmD/gOKt7fRsDhXXZ0y28+Al1CHgX+AY1qIN9VSIIItXRq1dN68QrRwl1ORFlwjBaAqeQ==",
+ "engines": {
+ "node": ">=18",
+ "pnpm": "8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/react-hook-form"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17 || ^18"
+ }
+ },
+ "node_modules/react-intersection-observer": {
+ "version": "9.8.1",
+ "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.8.1.tgz",
+ "integrity": "sha512-QzOFdROX8D8MH3wE3OVKH0f3mLjKTtEN1VX/rkNuECCff+aKky0pIjulDhr3Ewqj5el/L+MhBkM3ef0Tbt+qUQ==",
+ "peerDependencies": {
+ "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
- "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
- "dev": true
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+ },
+ "node_modules/react-qr-code": {
+ "version": "2.0.12",
+ "resolved": "https://registry.npmjs.org/react-qr-code/-/react-qr-code-2.0.12.tgz",
+ "integrity": "sha512-k+pzP5CKLEGBRwZsDPp98/CAJeXlsYRHM2iZn1Sd5Th/HnKhIZCSg27PXO58zk8z02RaEryg+60xa4vyywMJwg==",
+ "dependencies": {
+ "prop-types": "^15.8.1",
+ "qr.js": "0.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.x || ^17.x || ^18.x",
+ "react-native-svg": "*"
+ },
+ "peerDependenciesMeta": {
+ "react-native-svg": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-redux": {
+ "version": "7.2.9",
+ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz",
+ "integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.15.4",
+ "@types/react-redux": "^7.1.20",
+ "hoist-non-react-statics": "^3.3.2",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.7.2",
+ "react-is": "^17.0.2"
+ },
+ "peerDependencies": {
+ "react": "^16.8.3 || ^17 || ^18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-redux/node_modules/react-is": {
+ "version": "17.0.2",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "devOptional": true,
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/redux": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
+ "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
+ "dependencies": {
+ "@babel/runtime": "^7.9.2"
+ }
},
"node_modules/reflect.getprototypeof": {
"version": "1.0.4",
@@ -2970,8 +5516,7 @@
"node_modules/regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
- "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
- "dev": true
+ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
},
"node_modules/regexp.prototype.flags": {
"version": "1.5.1",
@@ -2990,6 +5535,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/resolve": {
"version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
@@ -3025,6 +5579,46 @@
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
}
},
+ "node_modules/restore-cursor": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz",
+ "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==",
+ "dev": true,
+ "dependencies": {
+ "onetime": "^5.1.0",
+ "signal-exit": "^3.0.2"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/restore-cursor/node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/restore-cursor/node_modules/onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "dev": true,
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/reusify": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
@@ -3035,7 +5629,13 @@
"node": ">=0.10.0"
}
},
- "node_modules/rimraf": {
+ "node_modules/rfdc": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz",
+ "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==",
+ "dev": true
+ },
+ "node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
@@ -3050,6 +5650,110 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/run-applescript": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz",
+ "integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==",
+ "dev": true,
+ "dependencies": {
+ "execa": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/run-applescript/node_modules/execa": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
+ "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+ "dev": true,
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^6.0.0",
+ "human-signals": "^2.1.0",
+ "is-stream": "^2.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^4.0.1",
+ "onetime": "^5.1.2",
+ "signal-exit": "^3.0.3",
+ "strip-final-newline": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/run-applescript/node_modules/human-signals": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
+ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10.17.0"
+ }
+ },
+ "node_modules/run-applescript/node_modules/is-stream": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/run-applescript/node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/run-applescript/node_modules/npm-run-path": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/run-applescript/node_modules/onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "dev": true,
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/run-applescript/node_modules/strip-final-newline": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -3105,6 +5809,29 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/sass": {
+ "version": "1.69.7",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.7.tgz",
+ "integrity": "sha512-rzj2soDeZ8wtE2egyLXgOOHQvaC2iosZrkF6v3EUG+tBwEvhqUCzm0VP3k9gHF9LXbSrRhT5SksoI56Iw8NPnQ==",
+ "devOptional": true,
+ "dependencies": {
+ "chokidar": ">=3.0.0 <4.0.0",
+ "immutable": "^4.0.0",
+ "source-map-js": ">=0.6.2 <2.0.0"
+ },
+ "bin": {
+ "sass": "sass.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass/node_modules/immutable": {
+ "version": "4.3.5",
+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz",
+ "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==",
+ "devOptional": true
+ },
"node_modules/scheduler": {
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
@@ -3157,6 +5884,11 @@
"node": ">= 0.4"
}
},
+ "node_modules/setimmediate": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+ "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -3192,6 +5924,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "dev": true
+ },
"node_modules/slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
@@ -3201,6 +5939,35 @@
"node": ">=8"
}
},
+ "node_modules/slice-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz",
+ "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "astral-regex": "^2.0.0",
+ "is-fullwidth-code-point": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/slice-ansi?sponsor=1"
+ }
+ },
+ "node_modules/slice-ansi/node_modules/ansi-styles": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
+ "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
"node_modules/source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
@@ -3217,6 +5984,83 @@
"node": ">=10.0.0"
}
},
+ "node_modules/string-argv": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz",
+ "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.6.19"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs": {
+ "name": "string-width",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "node_modules/string-width/node_modules/ansi-regex": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+ "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/string-width/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "node_modules/string-width/node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
"node_modules/string.prototype.matchall": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz",
@@ -3254,113 +6098,690 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/string.prototype.trimend": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz",
- "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==",
- "dev": true,
+ "node_modules/string.prototype.trimend": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz",
+ "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trimstart": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz",
+ "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi-cjs": {
+ "name": "strip-ansi",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/strip-final-newline": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
+ "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/styled-jsx": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz",
+ "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==",
+ "dependencies": {
+ "client-only": "0.0.1"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "@babel/core": {
+ "optional": true
+ },
+ "babel-plugin-macros": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/stylelint": {
+ "version": "16.1.0",
+ "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.1.0.tgz",
+ "integrity": "sha512-Sh1rRV0lN1qxz/QsuuooLWsIZ/ona7NKw/fRZd6y6PyXYdD2W0EAzJ8yJcwSx4Iw/muz0CF09VZ+z4EiTAcKmg==",
+ "dev": true,
+ "dependencies": {
+ "@csstools/css-parser-algorithms": "^2.4.0",
+ "@csstools/css-tokenizer": "^2.2.2",
+ "@csstools/media-query-list-parser": "^2.1.6",
+ "@csstools/selector-specificity": "^3.0.1",
+ "balanced-match": "^2.0.0",
+ "colord": "^2.9.3",
+ "cosmiconfig": "^9.0.0",
+ "css-functions-list": "^3.2.1",
+ "css-tree": "^2.3.1",
+ "debug": "^4.3.4",
+ "fast-glob": "^3.3.2",
+ "fastest-levenshtein": "^1.0.16",
+ "file-entry-cache": "^8.0.0",
+ "global-modules": "^2.0.0",
+ "globby": "^11.1.0",
+ "globjoin": "^0.1.4",
+ "html-tags": "^3.3.1",
+ "ignore": "^5.3.0",
+ "imurmurhash": "^0.1.4",
+ "is-plain-object": "^5.0.0",
+ "known-css-properties": "^0.29.0",
+ "mathml-tag-names": "^2.1.3",
+ "meow": "^13.0.0",
+ "micromatch": "^4.0.5",
+ "normalize-path": "^3.0.0",
+ "picocolors": "^1.0.0",
+ "postcss": "^8.4.32",
+ "postcss-resolve-nested-selector": "^0.1.1",
+ "postcss-safe-parser": "^7.0.0",
+ "postcss-selector-parser": "^6.0.13",
+ "postcss-value-parser": "^4.2.0",
+ "resolve-from": "^5.0.0",
+ "string-width": "^4.2.3",
+ "strip-ansi": "^7.1.0",
+ "supports-hyperlinks": "^3.0.0",
+ "svg-tags": "^1.0.0",
+ "table": "^6.8.1",
+ "write-file-atomic": "^5.0.1"
+ },
+ "bin": {
+ "stylelint": "bin/stylelint.mjs"
+ },
+ "engines": {
+ "node": ">=18.12.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/stylelint"
+ }
+ },
+ "node_modules/stylelint-config-recommended": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-14.0.0.tgz",
+ "integrity": "sha512-jSkx290CglS8StmrLp2TxAppIajzIBZKYm3IxT89Kg6fGlxbPiTiyH9PS5YUuVAFwaJLl1ikiXX0QWjI0jmgZQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=18.12.0"
+ },
+ "peerDependencies": {
+ "stylelint": "^16.0.0"
+ }
+ },
+ "node_modules/stylelint-config-recommended-scss": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-14.0.0.tgz",
+ "integrity": "sha512-HDvpoOAQ1RpF+sPbDOT2Q2/YrBDEJDnUymmVmZ7mMCeNiFSdhRdyGEimBkz06wsN+HaFwUh249gDR+I9JR7Onw==",
+ "dev": true,
+ "dependencies": {
+ "postcss-scss": "^4.0.9",
+ "stylelint-config-recommended": "^14.0.0",
+ "stylelint-scss": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=18.12.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.3.3",
+ "stylelint": "^16.0.2"
+ },
+ "peerDependenciesMeta": {
+ "postcss": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/stylelint-config-standard": {
+ "version": "35.0.0",
+ "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-35.0.0.tgz",
+ "integrity": "sha512-JyQrNZk2BZwVKFauGGxW2U6RuhIfQ4XoHHo+rBzMHcAkLnwI/knpszwXjzxiMgSfcxbZBckM7Vq4LHoANTR85g==",
+ "dev": true,
+ "dependencies": {
+ "stylelint-config-recommended": "^14.0.0"
+ },
+ "engines": {
+ "node": ">=18.12.0"
+ },
+ "peerDependencies": {
+ "stylelint": "^16.0.0"
+ }
+ },
+ "node_modules/stylelint-config-standard-scss": {
+ "version": "12.0.0",
+ "resolved": "https://registry.npmjs.org/stylelint-config-standard-scss/-/stylelint-config-standard-scss-12.0.0.tgz",
+ "integrity": "sha512-ATh3EcEOLZq0iwlFaBdIsSavrla0lNtJ7mO7hdE7DgVT6imozRggFSqd4cFcjzVnOLKv/uJT63MmqA1acIflbw==",
+ "dev": true,
+ "dependencies": {
+ "stylelint-config-recommended-scss": "^14.0.0",
+ "stylelint-config-standard": "^35.0.0"
+ },
+ "engines": {
+ "node": ">=18.12.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.3.3",
+ "stylelint": "^16.0.2"
+ },
+ "peerDependenciesMeta": {
+ "postcss": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/stylelint-order": {
+ "version": "6.0.4",
+ "resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-6.0.4.tgz",
+ "integrity": "sha512-0UuKo4+s1hgQ/uAxlYU4h0o0HS4NiQDud0NAUNI0aa8FJdmYHA5ZZTFHiV5FpmE3071e9pZx5j0QpVJW5zOCUA==",
+ "dev": true,
+ "dependencies": {
+ "postcss": "^8.4.32",
+ "postcss-sorting": "^8.0.2"
+ },
+ "peerDependencies": {
+ "stylelint": "^14.0.0 || ^15.0.0 || ^16.0.1"
+ }
+ },
+ "node_modules/stylelint-order/node_modules/postcss": {
+ "version": "8.4.32",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz",
+ "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.7",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/stylelint-prettier": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/stylelint-prettier/-/stylelint-prettier-5.0.0.tgz",
+ "integrity": "sha512-RHfSlRJIsaVg5Br94gZVdWlz/rBTyQzZflNE6dXvSxt/GthWMY3gEHsWZEBaVGg7GM+XrtVSp4RznFlB7i0oyw==",
+ "dev": true,
+ "dependencies": {
+ "prettier-linter-helpers": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=18.12.0"
+ },
+ "peerDependencies": {
+ "prettier": ">=3.0.0",
+ "stylelint": ">=16.0.0"
+ }
+ },
+ "node_modules/stylelint-scss": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-6.0.0.tgz",
+ "integrity": "sha512-N1xV/Ef5PNRQQt9E45unzGvBUN1KZxCI8B4FgN/pMfmyRYbZGVN4y9qWlvOMdScU17c8VVCnjIHTVn38Bb6qSA==",
+ "dev": true,
+ "dependencies": {
+ "known-css-properties": "^0.29.0",
+ "postcss-media-query-parser": "^0.2.3",
+ "postcss-resolve-nested-selector": "^0.1.1",
+ "postcss-selector-parser": "^6.0.13",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "engines": {
+ "node": ">=18.12.0"
+ },
+ "peerDependencies": {
+ "stylelint": "^16.0.2"
+ }
+ },
+ "node_modules/stylelint/node_modules/ansi-regex": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+ "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/stylelint/node_modules/balanced-match": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz",
+ "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==",
+ "dev": true
+ },
+ "node_modules/stylelint/node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/stylelint/node_modules/brace-expansion/node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true
+ },
+ "node_modules/stylelint/node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
+ "dependencies": {
+ "flat-cache": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/stylelint/node_modules/flat-cache": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.0.tgz",
+ "integrity": "sha512-EryKbCE/wxpxKniQlyas6PY1I9vwtF3uCBweX+N8KYTCn3Y12RTGtQAJ/bd5pl7kxUAc8v/R3Ake/N17OZiFqA==",
+ "dev": true,
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4",
+ "rimraf": "^5.0.5"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/stylelint/node_modules/glob": {
+ "version": "10.3.10",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
+ "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
+ "dev": true,
+ "dependencies": {
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^2.3.5",
+ "minimatch": "^9.0.1",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
+ "path-scurry": "^1.10.1"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/stylelint/node_modules/minimatch": {
+ "version": "9.0.3",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
+ "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/stylelint/node_modules/postcss": {
+ "version": "8.4.32",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz",
+ "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.7",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/stylelint/node_modules/postcss-safe-parser": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.0.tgz",
+ "integrity": "sha512-ovehqRNVCpuFzbXoTb4qLtyzK3xn3t/CUBxOs8LsnQjQrShaB4lKiHoVqY8ANaC0hBMHq5QVWk77rwGklFUDrg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "engines": {
+ "node": ">=18.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.31"
+ }
+ },
+ "node_modules/stylelint/node_modules/resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/stylelint/node_modules/rimraf": {
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz",
+ "integrity": "sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==",
+ "dev": true,
+ "dependencies": {
+ "glob": "^10.3.7"
+ },
+ "bin": {
+ "rimraf": "dist/esm/bin.mjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/stylelint/node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-hyperlinks": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.0.0.tgz",
+ "integrity": "sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0",
+ "supports-color": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=14.18"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/svg-tags": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz",
+ "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==",
+ "dev": true
+ },
+ "node_modules/svg.draggable.js": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz",
+ "integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==",
"dependencies": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.2.0",
- "es-abstract": "^1.22.1"
+ "svg.js": "^2.0.1"
},
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "engines": {
+ "node": ">= 0.8.0"
}
},
- "node_modules/string.prototype.trimstart": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz",
- "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==",
- "dev": true,
+ "node_modules/svg.easing.js": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz",
+ "integrity": "sha512-//ctPdJMGy22YoYGV+3HEfHbm6/69LJUTAqI2/5qBvaNHZ9uUFVC82B0Pl299HzgH13rKrBgi4+XyXXyVWWthA==",
"dependencies": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.2.0",
- "es-abstract": "^1.22.1"
+ "svg.js": ">=2.3.x"
},
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "engines": {
+ "node": ">= 0.8.0"
}
},
- "node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
+ "node_modules/svg.filter.js": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz",
+ "integrity": "sha512-xkGBwU+dKBzqg5PtilaTb0EYPqPfJ9Q6saVldX+5vCRy31P6TlRCP3U9NxH3HEufkKkpNgdTLBJnmhDHeTqAkw==",
"dependencies": {
- "ansi-regex": "^5.0.1"
+ "svg.js": "^2.2.5"
},
"engines": {
- "node": ">=8"
+ "node": ">= 0.8.0"
}
},
- "node_modules/strip-bom": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
- "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
- "dev": true,
+ "node_modules/svg.js": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz",
+ "integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA=="
+ },
+ "node_modules/svg.pathmorphing.js": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz",
+ "integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==",
+ "dependencies": {
+ "svg.js": "^2.4.0"
+ },
"engines": {
- "node": ">=4"
+ "node": ">= 0.8.0"
}
},
- "node_modules/strip-json-comments": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
- "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
- "dev": true,
+ "node_modules/svg.resize.js": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz",
+ "integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==",
+ "dependencies": {
+ "svg.js": "^2.6.5",
+ "svg.select.js": "^2.1.2"
+ },
"engines": {
- "node": ">=8"
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/svg.resize.js/node_modules/svg.select.js": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz",
+ "integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==",
+ "dependencies": {
+ "svg.js": "^2.2.5"
},
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "engines": {
+ "node": ">= 0.8.0"
}
},
- "node_modules/styled-jsx": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz",
- "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==",
+ "node_modules/svg.select.js": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz",
+ "integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==",
"dependencies": {
- "client-only": "0.0.1"
+ "svg.js": "^2.6.5"
},
"engines": {
- "node": ">= 12.0.0"
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/synckit": {
+ "version": "0.8.6",
+ "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.6.tgz",
+ "integrity": "sha512-laHF2savN6sMeHCjLRkheIU4wo3Zg9Ln5YOjOo7sZ5dVQW8yF5pPE5SIw1dsPhq3TRp1jisKRCdPhfs/1WMqDA==",
+ "dev": true,
+ "dependencies": {
+ "@pkgr/utils": "^2.4.2",
+ "tslib": "^2.6.2"
},
- "peerDependencies": {
- "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0"
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
},
- "peerDependenciesMeta": {
- "@babel/core": {
- "optional": true
- },
- "babel-plugin-macros": {
- "optional": true
- }
+ "funding": {
+ "url": "https://opencollective.com/unts"
}
},
- "node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "node_modules/table": {
+ "version": "6.8.1",
+ "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz",
+ "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==",
"dev": true,
"dependencies": {
- "has-flag": "^4.0.0"
+ "ajv": "^8.0.1",
+ "lodash.truncate": "^4.4.2",
+ "slice-ansi": "^4.0.0",
+ "string-width": "^4.2.3",
+ "strip-ansi": "^6.0.1"
},
"engines": {
- "node": ">=8"
+ "node": ">=10.0.0"
}
},
- "node_modules/supports-preserve-symlinks-flag": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
- "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "node_modules/table/node_modules/ajv": {
+ "version": "8.12.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
+ "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
"dev": true,
- "engines": {
- "node": ">= 0.4"
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2",
+ "uri-js": "^4.2.2"
},
"funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
}
},
+ "node_modules/table/node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "dev": true
+ },
"node_modules/tapable": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
@@ -3376,11 +6797,28 @@
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
"dev": true
},
+ "node_modules/tiny-invariant": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz",
+ "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw=="
+ },
+ "node_modules/titleize": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz",
+ "integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
- "dev": true,
+ "devOptional": true,
"dependencies": {
"is-number": "^7.0.0"
},
@@ -3388,6 +6826,11 @@
"node": ">=8.0"
}
},
+ "node_modules/tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
+ },
"node_modules/ts-api-utils": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz",
@@ -3519,6 +6962,28 @@
"node": ">=14.17"
}
},
+ "node_modules/ua-parser-js": {
+ "version": "0.7.37",
+ "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.37.tgz",
+ "integrity": "sha512-xV8kqRKM+jhMvcHWUKthV9fNebIzrNy//2O9ZwWcfiBFR5f25XVZPLlEajk/sf3Ra15V92isyQqnIEXRDaZWEA==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/ua-parser-js"
+ },
+ {
+ "type": "paypal",
+ "url": "https://paypal.me/faisalman"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/faisalman"
+ }
+ ],
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/unbox-primitive": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
@@ -3540,6 +7005,15 @@
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true
},
+ "node_modules/untildify": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz",
+ "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
@@ -3549,6 +7023,36 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/use-memo-one": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz",
+ "integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
+ "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true
+ },
+ "node_modules/warning": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
+ "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
+ "dependencies": {
+ "loose-envify": "^1.0.0"
+ }
+ },
"node_modules/watchpack": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
@@ -3561,6 +7065,20 @@
"node": ">=10.13.0"
}
},
+ "node_modules/webidl-conversions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
+ },
+ "node_modules/whatwg-url": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "dependencies": {
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -3652,18 +7170,143 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs": {
+ "name": "wrap-ansi",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-regex": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+ "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-styles": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
+ "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "dev": true,
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true
},
+ "node_modules/write-file-atomic": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz",
+ "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==",
+ "dev": true,
+ "dependencies": {
+ "imurmurhash": "^0.1.4",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
+ "node_modules/write-file-atomic/node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
+ "node_modules/yaml": {
+ "version": "2.3.4",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz",
+ "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
@@ -3675,6 +7318,33 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
+ },
+ "node_modules/zustand": {
+ "version": "4.4.7",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.4.7.tgz",
+ "integrity": "sha512-QFJWJMdlETcI69paJwhSMJz7PPWjVP8Sjhclxmxmxv/RYI7ZOvR5BHX+ktH0we9gTWQMxcne8q1OY8xxz604gw==",
+ "dependencies": {
+ "use-sync-external-store": "1.2.0"
+ },
+ "engines": {
+ "node": ">=12.7.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.8",
+ "immer": ">=9.0",
+ "react": ">=16.8"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ }
+ }
}
}
}
diff --git a/package.json b/package.json
index 1d5cd4e..d452cf3 100644
--- a/package.json
+++ b/package.json
@@ -6,19 +6,57 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
- "lint": "next lint"
+ "lint": "next lint",
+ "gitconfig": "git config --local include.path ./.gitconfig",
+ "gitcase": "git rm -r --cached . && git add .",
+ "postinstall": "husky install"
},
"dependencies": {
+ "@draft-js-plugins/editor": "^4.1.4",
+ "@draft-js-plugins/inline-toolbar": "^4.2.1",
+ "@stomp/stompjs": "^7.0.0",
+ "@tanstack/react-query": "^5.17.1",
+ "@tanstack/react-query-devtools": "^5.17.1",
+ "apexcharts": "^3.48.0",
+ "classnames": "^2.5.1",
+ "draft-js": "^0.11.7",
+ "draft-js-export-html": "^1.4.1",
+ "draftjs-to-html": "^0.9.1",
+ "html-to-draftjs": "^1.5.0",
+ "lodash-es": "^4.17.21",
+ "moment": "^2.30.1",
+ "next": "14.0.4",
+ "next-auth": "^5.0.0-beta.4",
"react": "^18",
+ "react-apexcharts": "^1.4.1",
+ "react-beautiful-dnd": "^13.1.1",
+ "react-calendar": "^4.8.0",
"react-dom": "^18",
- "next": "14.0.4"
+ "react-hook-form": "^7.49.3",
+ "react-intersection-observer": "^9.8.1",
+ "react-qr-code": "^2.0.12",
+ "zustand": "^4.4.7"
},
"devDependencies": {
- "typescript": "^5",
+ "@types/draft-js": "^0.11.17",
+ "@types/lodash-es": "^4.17.12",
"@types/node": "^20",
"@types/react": "^18",
+ "@types/react-beautiful-dnd": "^13.1.8",
"@types/react-dom": "^18",
+ "@typescript-eslint/eslint-plugin": "^6.15.0",
+ "@typescript-eslint/parser": "^6.15.0",
"eslint": "^8",
- "eslint-config-next": "14.0.4"
+ "eslint-config-next": "14.0.4",
+ "eslint-config-prettier": "^9.1.0",
+ "eslint-plugin-prettier": "^5.1.0",
+ "husky": "^8.0.3",
+ "lint-staged": "^15.2.0",
+ "sass": "^1.69.7",
+ "stylelint": "^16.1.0",
+ "stylelint-config-standard-scss": "^12.0.0",
+ "stylelint-order": "^6.0.4",
+ "stylelint-prettier": "^5.0.0",
+ "typescript": "^5"
}
}
diff --git a/src/app/(afterlogin)/(navbar)/_components/CardInfo.module.scss b/src/app/(afterlogin)/(navbar)/_components/CardInfo.module.scss
new file mode 100644
index 0000000..1242c08
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/_components/CardInfo.module.scss
@@ -0,0 +1,11 @@
+@import '@/styles/globals';
+
+.info {
+ display: flex;
+ flex-direction: column;
+
+ &__title {
+ margin: 0 0 12px;
+ font-size: $h2;
+ }
+}
diff --git a/src/app/(afterlogin)/(navbar)/_components/CardInfo.tsx b/src/app/(afterlogin)/(navbar)/_components/CardInfo.tsx
new file mode 100644
index 0000000..0409bf3
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/_components/CardInfo.tsx
@@ -0,0 +1,28 @@
+'use client';
+
+import { CardListType } from '@/types/service';
+import styles from './CardInfo.module.scss';
+
+import HomeCardDescription from './_Home/HomeCardDescription';
+import { PresentationListTypeGuard } from '@/types/guards';
+
+interface Props {
+ listInfo: CardListType;
+ usage: 'home' | 'feedback';
+}
+const CardInfo = ({ listInfo, usage }: Props) => {
+ return (
+
+ {listInfo.title}
+ {usage === 'home' && PresentationListTypeGuard(listInfo) ? (
+ <>
+
+ >
+ ) : (
+ <>>
+ )}
+
+ );
+};
+
+export default CardInfo;
diff --git a/src/app/(afterlogin)/(navbar)/_components/CardItem.module.scss b/src/app/(afterlogin)/(navbar)/_components/CardItem.module.scss
new file mode 100644
index 0000000..35f8682
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/_components/CardItem.module.scss
@@ -0,0 +1,63 @@
+@import '@/styles/globals';
+@import '@/styles/mixins';
+
+.container {
+ width: 440px;
+ height: 335px;
+}
+
+.thumbnail {
+ position: relative;
+ width: 100%;
+ height: 250px;
+ margin-bottom: 20px;
+ border: 1px solid $gray-1;
+ border-radius: 16px;
+}
+
+.menu {
+ @include pure-button;
+
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ width: 100%;
+ height: 100%;
+
+ span {
+ font-size: $font-2;
+ }
+}
+
+.info {
+ width: 280px;
+
+ span {
+ @include line(1);
+ }
+
+ em {
+ background-color: $black;
+ }
+
+ &__box {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ }
+}
+
+.action {
+ @include pure-button;
+ @include button_theme_inverted;
+
+ width: 100%;
+ height: 100%;
+ border-radius: 100px;
+ font-size: 20px;
+
+ &__box {
+ width: 120px;
+ height: 55px;
+ }
+}
diff --git a/src/app/(afterlogin)/(navbar)/_components/CardItem.tsx b/src/app/(afterlogin)/(navbar)/_components/CardItem.tsx
new file mode 100644
index 0000000..8b19f98
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/_components/CardItem.tsx
@@ -0,0 +1,106 @@
+'use client';
+
+import FlyoutMenu from '@/app/_components/_modules/FlyoutMenu';
+import styles from './CardItem.module.scss';
+
+import useToggle from '@/app/_hooks/useToggle';
+import Confirm from '@/app/_components/_modules/_modal/Confirm';
+import { CardListType } from '@/types/service';
+import Image from 'next/image';
+import { useRouter } from 'next/navigation';
+import DeleteIcon from '../home/_components/_svgs/DeleteIcon';
+import ModifyIcon from '../home/_components/_svgs/ModifyIcon';
+import MenuIcon from '../home/_components/_svgs/MenuIcon';
+import { useDeletePresentation } from '../home/_hooks/presentationList';
+import CardInfo from './CardInfo';
+import PracticeButton from './_Home/PracticeButton';
+
+interface Props {
+ listInfo: CardListType;
+ usage: 'home' | 'feedback';
+}
+
+const CardItem = ({ listInfo, usage }: Props) => {
+ const router = useRouter();
+ const flyout = useToggle();
+ const modal = useToggle();
+
+ const { mutate } = useDeletePresentation(listInfo.id);
+
+ const handleModify = () => {
+ router.push(`/upload/${listInfo.id}`);
+ flyout.onClose();
+ };
+
+ const handleDelete = () => {
+ flyout.onClose();
+ modal.onOpen();
+ };
+
+ const deleteItem = () => {
+ mutate();
+ };
+
+ return (
+ <>
+
+
+
+
+ {/* TODO: 피드백에 맞는 버튼 로직 생성 필요 */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {usage === 'home' ? (
+
router.push(`/setting/${listInfo.id}`)} />
+ ) : (
+ <>>
+ )}
+
+
+
+
+ {
+ deleteItem();
+ }}
+ />
+ >
+ );
+};
+
+export default CardItem;
diff --git a/src/app/(afterlogin)/(navbar)/_components/CardList.module.scss b/src/app/(afterlogin)/(navbar)/_components/CardList.module.scss
new file mode 100644
index 0000000..36b1d95
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/_components/CardList.module.scss
@@ -0,0 +1,37 @@
+@import '@/styles/globals';
+@import '@/styles/mixins';
+
+.container {
+ width: 1440px;
+ margin-top: 32px;
+}
+
+.exercise {
+ width: fit-content;
+
+ &__box {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 60px;
+ }
+
+ &__new {
+ @include pure-button;
+
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ width: 440px;
+ height: 250px;
+ border: 1px solid $gray-1;
+ border-radius: 16px;
+ background-color: $gray-0;
+
+ span {
+ color: $gray-3;
+ font-size: 20px;
+ margin-top: 8px;
+ }
+ }
+}
diff --git a/src/app/(afterlogin)/(navbar)/_components/CardList.tsx b/src/app/(afterlogin)/(navbar)/_components/CardList.tsx
new file mode 100644
index 0000000..8e8d9ed
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/_components/CardList.tsx
@@ -0,0 +1,94 @@
+'use client';
+import { useInfiniteQuery } from '@tanstack/react-query';
+import styles from './CardList.module.scss';
+import { clientHomeApi } from '@/services/client/home';
+import { useInView } from 'react-intersection-observer';
+import { Fragment, useEffect } from 'react';
+import { CardListType, FeedbackListType, PresentationListType } from '@/types/service';
+import { useRouter } from 'next/navigation';
+import PlusIcon from '../home/_components/_svgs/PlusIcon';
+import CardItem from './CardItem';
+import { clientFeedbackApi } from '@/services/client/feedback';
+
+// 패칭 api
+// 최근 발표 사용 여부
+// 이미지 아래 설명 컨텐츠
+// 이미지 아래 버튼
+interface Props {
+ usage: 'home' | 'feedback';
+}
+const CardList = ({ usage }: Props) => {
+ // 여기서 피드백의 경우, 완료가 안 된게 있으면 1초마다 fetch
+ const router = useRouter();
+
+ const { data, fetchNextPage, hasNextPage, isFetching, refetch } = useInfiniteQuery({
+ queryKey: [usage, 'list'],
+ queryFn: async ({ pageParam = 0 }) => {
+ if (usage === 'home') {
+ const response = await clientHomeApi.getPresentationList({ pageParam });
+ return await response.json();
+ }
+ if (usage === 'feedback') {
+ const response = await clientFeedbackApi.getFeedbackList({ pageParam });
+ return await response.json();
+ }
+ },
+ initialPageParam: 0,
+ getNextPageParam: (lastPage, pages) => {
+ if (lastPage && pages) {
+ const currentPage = pages?.length; // 현재 pageParam
+ const totalPages = lastPage?.page?.totalPage; // 전체 페이지 수
+
+ if (currentPage < totalPages) {
+ return currentPage;
+ } else {
+ return undefined;
+ }
+ }
+ },
+ });
+
+ const { ref, inView } = useInView({
+ threshold: 0,
+ delay: 0,
+ });
+
+ useEffect(() => {
+ if (inView) {
+ !isFetching && hasNextPage && fetchNextPage();
+ }
+ }, [inView, isFetching, hasNextPage, fetchNextPage]);
+
+ return (
+
+ 내 {usage === 'home' ? '발표연습' : '피드백'} 목록
+
+
+ {data?.pages.map((eachPage: PresentationListType | FeedbackListType, index) => {
+ return (
+
+ {eachPage.page.content.map((listInfo: CardListType, index) => (
+ -
+
+
+ ))}
+
+ );
+ })}
+
+
+
+
+
+ );
+};
+
+export default CardList;
diff --git a/src/app/(afterlogin)/(navbar)/_components/_Home/HomeCardDescription.module.scss b/src/app/(afterlogin)/(navbar)/_components/_Home/HomeCardDescription.module.scss
new file mode 100644
index 0000000..87f7890
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/_components/_Home/HomeCardDescription.module.scss
@@ -0,0 +1,20 @@
+@import '@/styles/globals';
+
+.title {
+ margin: 0 0 12px;
+ font-size: $h2;
+}
+
+.desc {
+ display: inline-flex;
+ align-items: center;
+ font-size: $font-2;
+}
+
+.division {
+ width: 4px;
+ height: 4px;
+ margin: 0 8px;
+ background-color: $white;
+ border-radius: 50%;
+}
diff --git a/src/app/(afterlogin)/(navbar)/_components/_Home/HomeCardDescription.tsx b/src/app/(afterlogin)/(navbar)/_components/_Home/HomeCardDescription.tsx
new file mode 100644
index 0000000..547a83d
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/_components/_Home/HomeCardDescription.tsx
@@ -0,0 +1,22 @@
+import React from 'react';
+import styles from './HomeCardDescription.module.scss';
+import { CardListType } from '@/types/service';
+import { PresentationListTypeGuard } from '@/types/guards';
+
+interface Props {
+ listInfo: CardListType;
+}
+
+const HomeCardDescription = ({ listInfo }: Props) => {
+ return listInfo && PresentationListTypeGuard(listInfo) ? (
+
+ D{listInfo.dday < 0 ? `+${Math.abs(listInfo.dday)}` : `-${listInfo.dday}`}
+
+ 발표 시간 {listInfo.timeLimit.hours * 60 + listInfo.timeLimit.minutes}분
+
+ ) : (
+ <>>
+ );
+};
+
+export default HomeCardDescription;
diff --git a/src/app/(afterlogin)/(navbar)/_components/_Home/PracticeButton.module.scss b/src/app/(afterlogin)/(navbar)/_components/_Home/PracticeButton.module.scss
new file mode 100644
index 0000000..1720706
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/_components/_Home/PracticeButton.module.scss
@@ -0,0 +1,17 @@
+@import '@/styles/globals';
+@import '@/styles/mixins';
+
+.action {
+ @include pure-button;
+ @include button_theme_inverted;
+
+ width: 100%;
+ height: 100%;
+ border-radius: 100px;
+ font-size: 20px;
+
+ &__box {
+ width: 120px;
+ height: 55px;
+ }
+}
diff --git a/src/app/(afterlogin)/(navbar)/_components/_Home/PracticeButton.tsx b/src/app/(afterlogin)/(navbar)/_components/_Home/PracticeButton.tsx
new file mode 100644
index 0000000..b14f0a4
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/_components/_Home/PracticeButton.tsx
@@ -0,0 +1,17 @@
+import React from 'react';
+import styles from './PracticeButton.module.scss';
+
+interface Props {
+ onClick: () => void;
+}
+const PracticeButton = ({ onClick }: Props) => {
+ return (
+
+
+
+ );
+};
+
+export default PracticeButton;
diff --git a/src/app/(afterlogin)/(navbar)/feedback/[id]/_components/CategoryFeedback.module.scss b/src/app/(afterlogin)/(navbar)/feedback/[id]/_components/CategoryFeedback.module.scss
new file mode 100644
index 0000000..da02b97
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/feedback/[id]/_components/CategoryFeedback.module.scss
@@ -0,0 +1,43 @@
+@import '@/styles/mixins';
+
+.content {
+ display: flex;
+ width: 100%;
+ margin-top: 20px;
+ gap: 20px;
+}
+
+.card {
+ display: flex;
+ flex-direction: column;
+ gap: 40px;
+ height: 500px;
+ padding: 40px 73px;
+ background-color: $white;
+ flex: 1;
+ border-radius: 16px;
+
+ .top {
+ & > p:nth-child(2) {
+ margin: 10px 0;
+ color: $gray-7;
+ }
+
+ & > div:nth-child(3) {
+ width: 220px;
+ height: 220px;
+ }
+ }
+
+ .bottom {
+ height: 72px;
+
+ p {
+ color: $gray-6;
+ }
+ }
+}
+
+.notFirst {
+ padding: 40px 17px;
+}
diff --git a/src/app/(afterlogin)/(navbar)/feedback/[id]/_components/CategoryFeedback.tsx b/src/app/(afterlogin)/(navbar)/feedback/[id]/_components/CategoryFeedback.tsx
new file mode 100644
index 0000000..fa00adb
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/feedback/[id]/_components/CategoryFeedback.tsx
@@ -0,0 +1,5 @@
+const CategoryFeedback = () => {
+ return ;
+};
+
+export default CategoryFeedback;
diff --git a/src/app/(afterlogin)/(navbar)/feedback/[id]/_components/MemorizeReview.tsx b/src/app/(afterlogin)/(navbar)/feedback/[id]/_components/MemorizeReview.tsx
new file mode 100644
index 0000000..f0f7e2d
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/feedback/[id]/_components/MemorizeReview.tsx
@@ -0,0 +1,5 @@
+const MemorizeReview = () => {
+ return ;
+};
+
+export default MemorizeReview;
diff --git a/src/app/(afterlogin)/(navbar)/feedback/[id]/_components/TotalScore.module.scss b/src/app/(afterlogin)/(navbar)/feedback/[id]/_components/TotalScore.module.scss
new file mode 100644
index 0000000..5ad3417
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/feedback/[id]/_components/TotalScore.module.scss
@@ -0,0 +1,98 @@
+@import '@/styles/mixins';
+
+.container {
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+}
+
+.title {
+ font-size: $h2;
+}
+
+.content {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ margin-top: 20px;
+ width: 100%;
+ height: 410px;
+ background-color: $white;
+ padding: 56px 63px;
+}
+
+.total_result {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+
+ h1 {
+ margin-top: 16px;
+ margin-bottom: 10px;
+ }
+
+ p {
+ font-size: $font-1;
+ }
+}
+
+.highlight_score {
+ color: $purple-5;
+}
+
+.total_score {
+ margin-top: 20px;
+ margin-bottom: 44px;
+ position: relative;
+ width: 906px;
+ height: 24px;
+ border-radius: 100px;
+ background-color: $purple-5;
+}
+
+.blank_first {
+ position: absolute;
+ left: 33%;
+ background-color: $white;
+ width: 11px;
+ height: 100%;
+}
+
+.blank_second {
+ position: absolute;
+ left: 66%;
+ background-color: $white;
+ width: 11px;
+ height: 100%;
+}
+
+.goal {
+ position: absolute;
+ top: 50%;
+ left: 90%;
+ width: 11px;
+ transform: translate(0, -45%);
+}
+
+.score {
+ position: absolute;
+ top: 50%;
+ left: 90%;
+ width: 11px;
+ transform: translate(0, -50%);
+}
+
+.practice_Link {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 600px;
+ height: 56px;
+ padding: 10px 200px;
+ background-color: $gray-9;
+ border-radius: 12px;
+
+ h2 {
+ color: $white;
+ }
+}
diff --git a/src/app/(afterlogin)/(navbar)/feedback/[id]/_components/TotalScore.tsx b/src/app/(afterlogin)/(navbar)/feedback/[id]/_components/TotalScore.tsx
new file mode 100644
index 0000000..2cd855b
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/feedback/[id]/_components/TotalScore.tsx
@@ -0,0 +1,39 @@
+import styles from './TotalScore.module.scss';
+import GoodIcon from '../_svgs/GoodIcon';
+import GoalIcon from '../_svgs/GoalIcon';
+import ScoreIcon from '../_svgs/ScoreIcon';
+import Link from 'next/link';
+const TotalScore = () => {
+ return (
+
+
발표 제목 종합 피드백
+
+
+
+
+ 잘 하고 있어요! 종합 점수 00점을
+ 달성했어요 👏
+
+
목표 달성까지 0점 남았어요, 조금만 더 파이팅!
+
+
+
+
+
+
+
+ {/*
+
+
*/}
+
+
+
+
발표 연습 시작하기
+
+
+
+
+ );
+};
+
+export default TotalScore;
diff --git a/src/app/(afterlogin)/(navbar)/feedback/[id]/_svgs/BadIcon.module.scss b/src/app/(afterlogin)/(navbar)/feedback/[id]/_svgs/BadIcon.module.scss
new file mode 100644
index 0000000..0870618
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/feedback/[id]/_svgs/BadIcon.module.scss
@@ -0,0 +1,20 @@
+@import '@/styles/mixins';
+
+.container {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 66px;
+ height: 32px;
+ padding: 15px 6px;
+ border: 2px solid $pink-5;
+ background-color: transparent;
+ border-radius: 16px;
+
+ p {
+ margin: 0;
+ color: $pink-5;
+ font-size: $font-3;
+ font-weight: 700;
+ }
+}
diff --git a/src/app/(afterlogin)/(navbar)/feedback/[id]/_svgs/BadIcon.tsx b/src/app/(afterlogin)/(navbar)/feedback/[id]/_svgs/BadIcon.tsx
new file mode 100644
index 0000000..8183ac8
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/feedback/[id]/_svgs/BadIcon.tsx
@@ -0,0 +1,11 @@
+import styles from './BadIcon.module.scss';
+
+const BadIcon = () => {
+ return (
+
+ );
+};
+
+export default BadIcon;
diff --git a/src/app/(afterlogin)/(navbar)/feedback/[id]/_svgs/GoalIcon.tsx b/src/app/(afterlogin)/(navbar)/feedback/[id]/_svgs/GoalIcon.tsx
new file mode 100644
index 0000000..6a17e6d
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/feedback/[id]/_svgs/GoalIcon.tsx
@@ -0,0 +1,24 @@
+const GoalIcon = () => {
+ return (
+
+
+
+ );
+};
+
+export default GoalIcon;
diff --git a/src/app/(afterlogin)/(navbar)/feedback/[id]/_svgs/GoodIcon.module.scss b/src/app/(afterlogin)/(navbar)/feedback/[id]/_svgs/GoodIcon.module.scss
new file mode 100644
index 0000000..bc6a83b
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/feedback/[id]/_svgs/GoodIcon.module.scss
@@ -0,0 +1,20 @@
+@import '@/styles/mixins';
+
+.container {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 66px;
+ height: 32px;
+ padding: 15px 6px;
+ border: 2px solid $purple-6;
+ background-color: transparent;
+ border-radius: 16px;
+
+ p {
+ margin: 0;
+ color: $purple-6;
+ font-size: $font-3;
+ font-weight: 700;
+ }
+}
diff --git a/src/app/(afterlogin)/(navbar)/feedback/[id]/_svgs/GoodIcon.tsx b/src/app/(afterlogin)/(navbar)/feedback/[id]/_svgs/GoodIcon.tsx
new file mode 100644
index 0000000..091b456
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/feedback/[id]/_svgs/GoodIcon.tsx
@@ -0,0 +1,10 @@
+import styles from './GoodIcon.module.scss';
+const GoodIcon = () => {
+ return (
+
+ );
+};
+
+export default GoodIcon;
diff --git a/src/app/(afterlogin)/(navbar)/feedback/[id]/_svgs/ScoreIcon.module.scss b/src/app/(afterlogin)/(navbar)/feedback/[id]/_svgs/ScoreIcon.module.scss
new file mode 100644
index 0000000..70567bf
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/feedback/[id]/_svgs/ScoreIcon.module.scss
@@ -0,0 +1,18 @@
+@import '@/styles/mixins';
+
+.container {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 74px;
+ height: 36px;
+ padding: 8px 10px;
+ border: 2px solid $white;
+ background-color: $purple-5;
+ border-radius: 100px;
+
+ p {
+ color: $white;
+ font-size: $font-2;
+ }
+}
diff --git a/src/app/(afterlogin)/(navbar)/feedback/[id]/_svgs/ScoreIcon.tsx b/src/app/(afterlogin)/(navbar)/feedback/[id]/_svgs/ScoreIcon.tsx
new file mode 100644
index 0000000..0350e57
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/feedback/[id]/_svgs/ScoreIcon.tsx
@@ -0,0 +1,11 @@
+import styles from './ScoreIcon.module.scss';
+
+const ScoreIcon = () => {
+ return (
+
+ );
+};
+
+export default ScoreIcon;
diff --git a/src/app/(afterlogin)/(navbar)/feedback/[id]/page.module.scss b/src/app/(afterlogin)/(navbar)/feedback/[id]/page.module.scss
new file mode 100644
index 0000000..2e7627b
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/feedback/[id]/page.module.scss
@@ -0,0 +1,14 @@
+.container {
+ background-color: aqua;
+ display: flex;
+ justify-content: center;
+}
+
+.content {
+ margin-top: 70px;
+ margin-bottom: 140px;
+ display: flex;
+ flex-direction: column;
+ gap: 70px;
+ width: 1320px;
+}
diff --git a/src/app/(afterlogin)/(navbar)/feedback/[id]/page.tsx b/src/app/(afterlogin)/(navbar)/feedback/[id]/page.tsx
new file mode 100644
index 0000000..b211af9
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/feedback/[id]/page.tsx
@@ -0,0 +1,28 @@
+import { fetch_ServerAuth } from '@/services/server/fetchServer';
+import CategoryFeedback from './_components/CategoryFeedback';
+import MemorizeReview from './_components/MemorizeReview';
+import TotalScore from './_components/TotalScore';
+import styles from './page.module.scss';
+import { serverFeedbackApi } from '@/services/server/feedback';
+
+interface Props {
+ params: {
+ id: string;
+ };
+}
+const page = async ({ params }: Props) => {
+ const id = Number(params.id);
+ const res = await serverFeedbackApi.getFeedbackInfo(id);
+
+ return (
+
+ );
+};
+
+export default page;
diff --git a/src/app/(afterlogin)/(navbar)/feedback/list/page.module.scss b/src/app/(afterlogin)/(navbar)/feedback/list/page.module.scss
new file mode 100644
index 0000000..e6063fc
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/feedback/list/page.module.scss
@@ -0,0 +1,6 @@
+.container {
+ display: flex;
+ align-items: center;
+ flex-direction: column;
+ margin-top: 36px;
+}
diff --git a/src/app/(afterlogin)/(navbar)/feedback/list/page.tsx b/src/app/(afterlogin)/(navbar)/feedback/list/page.tsx
new file mode 100644
index 0000000..3489e7a
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/feedback/list/page.tsx
@@ -0,0 +1,35 @@
+import { serverFeedbackApi } from '@/services/server/feedback';
+import { HydrationBoundary, QueryClient, dehydrate } from '@tanstack/react-query';
+import React from 'react';
+import styles from './page.module.scss';
+import CardList from '../../_components/CardList';
+
+export default async function Page() {
+ const queryClient = new QueryClient();
+ const listResponse = await queryClient.fetchInfiniteQuery({
+ queryKey: ['home', 'list'],
+ queryFn: async ({ pageParam = 0 }) => {
+ const response = await serverFeedbackApi.getFeedbackList({ pageParam });
+ return await response.json();
+ },
+ initialPageParam: 0,
+ });
+
+ const isEmpty = listResponse.pages[0].page.empty;
+
+ const dehydratedState = dehydrate(queryClient);
+
+ return (
+ <>
+ {isEmpty ? (
+ <>empty>
+ ) : (
+
+
+
+
+
+ )}
+ >
+ );
+}
diff --git a/src/app/(afterlogin)/(navbar)/home/_components/ExerciseItem.module.scss b/src/app/(afterlogin)/(navbar)/home/_components/ExerciseItem.module.scss
new file mode 100644
index 0000000..35f8682
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/home/_components/ExerciseItem.module.scss
@@ -0,0 +1,63 @@
+@import '@/styles/globals';
+@import '@/styles/mixins';
+
+.container {
+ width: 440px;
+ height: 335px;
+}
+
+.thumbnail {
+ position: relative;
+ width: 100%;
+ height: 250px;
+ margin-bottom: 20px;
+ border: 1px solid $gray-1;
+ border-radius: 16px;
+}
+
+.menu {
+ @include pure-button;
+
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ width: 100%;
+ height: 100%;
+
+ span {
+ font-size: $font-2;
+ }
+}
+
+.info {
+ width: 280px;
+
+ span {
+ @include line(1);
+ }
+
+ em {
+ background-color: $black;
+ }
+
+ &__box {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ }
+}
+
+.action {
+ @include pure-button;
+ @include button_theme_inverted;
+
+ width: 100%;
+ height: 100%;
+ border-radius: 100px;
+ font-size: 20px;
+
+ &__box {
+ width: 120px;
+ height: 55px;
+ }
+}
diff --git a/src/app/(afterlogin)/(navbar)/home/_components/ExerciseItem.tsx b/src/app/(afterlogin)/(navbar)/home/_components/ExerciseItem.tsx
new file mode 100644
index 0000000..91bc9ef
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/home/_components/ExerciseItem.tsx
@@ -0,0 +1,103 @@
+'use client';
+
+import FlyoutMenu from '@/app/_components/_modules/FlyoutMenu';
+import styles from './ExerciseItem.module.scss';
+import ExerciseInfo from './_elements/ExerciseInfo';
+import MenuIcon from './_svgs/MenuIcon';
+import ModifyIcon from './_svgs/ModifyIcon';
+import DeleteIcon from './_svgs/DeleteIcon';
+import useToggle from '@/app/_hooks/useToggle';
+import Confirm from '@/app/_components/_modules/_modal/Confirm';
+import { PresentationListType } from '@/types/service';
+import Image from 'next/image';
+import { useRouter } from 'next/navigation';
+import { useDeletePresentation } from '../_hooks/presentationList';
+
+interface Props {
+ presentation: PresentationListType['page']['content'][0];
+}
+
+const ExerciseItem = ({ presentation }: Props) => {
+ const router = useRouter();
+ const flyout = useToggle();
+ const modal = useToggle();
+
+ const { mutate } = useDeletePresentation(presentation.id);
+
+ const handleModify = () => {
+ router.push(`/upload/${presentation.id}`);
+ flyout.onClose();
+ };
+
+ const handleDelete = () => {
+ flyout.onClose();
+ modal.onOpen();
+ };
+
+ const deleteItem = () => {
+ mutate();
+ };
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ deleteItem();
+ }}
+ />
+ >
+ );
+};
+
+export default ExerciseItem;
diff --git a/src/app/(afterlogin)/(navbar)/home/_components/ExerciseList.module.scss b/src/app/(afterlogin)/(navbar)/home/_components/ExerciseList.module.scss
new file mode 100644
index 0000000..36b1d95
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/home/_components/ExerciseList.module.scss
@@ -0,0 +1,37 @@
+@import '@/styles/globals';
+@import '@/styles/mixins';
+
+.container {
+ width: 1440px;
+ margin-top: 32px;
+}
+
+.exercise {
+ width: fit-content;
+
+ &__box {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 60px;
+ }
+
+ &__new {
+ @include pure-button;
+
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ width: 440px;
+ height: 250px;
+ border: 1px solid $gray-1;
+ border-radius: 16px;
+ background-color: $gray-0;
+
+ span {
+ color: $gray-3;
+ font-size: 20px;
+ margin-top: 8px;
+ }
+ }
+}
diff --git a/src/app/(afterlogin)/(navbar)/home/_components/ExerciseList.tsx b/src/app/(afterlogin)/(navbar)/home/_components/ExerciseList.tsx
new file mode 100644
index 0000000..217063f
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/home/_components/ExerciseList.tsx
@@ -0,0 +1,82 @@
+'use client';
+import { useInfiniteQuery } from '@tanstack/react-query';
+import ExerciseItem from './ExerciseItem';
+import styles from './ExerciseList.module.scss';
+import PlusIcon from './_svgs/PlusIcon';
+import { clientHomeApi } from '@/services/client/home';
+import { useInView } from 'react-intersection-observer';
+import { Fragment, useEffect } from 'react';
+import { PresentationListType } from '@/types/service';
+import { useRouter } from 'next/navigation';
+
+const ExerciseList = () => {
+ const router = useRouter();
+
+ // const { data: latestData }: { data: LatestPresentationType | string | undefined } =
+ // useGetLatestPresentation();
+
+ const { data, fetchNextPage, hasNextPage, isFetching } = useInfiniteQuery({
+ queryKey: ['home', 'list'],
+ queryFn: async ({ pageParam = 0 }) => {
+ const response = await clientHomeApi.getPresentationList({ pageParam });
+ return await response.json();
+ },
+ initialPageParam: 0,
+ getNextPageParam: (lastPage, pages) => {
+ if (lastPage && pages) {
+ const currentPage = pages?.length; // 현재 pageParam
+ const totalPages = lastPage?.page?.totalPage; // 전체 페이지 수
+
+ if (currentPage < totalPages) {
+ return currentPage;
+ } else {
+ return undefined;
+ }
+ }
+ },
+ });
+
+ const { ref, inView } = useInView({
+ threshold: 0,
+ delay: 0,
+ });
+
+ useEffect(() => {
+ if (inView) {
+ !isFetching && hasNextPage && fetchNextPage();
+ }
+ }, [inView, isFetching, hasNextPage, fetchNextPage]);
+
+ return (
+
+ 내 발표연습 목록
+
+
+ {data?.pages.map((eachPage: PresentationListType, index) => {
+ return (
+
+ {eachPage.page.content.map((presentation, index) => (
+ -
+
+
+ ))}
+
+ );
+ })}
+
+
+
+
+
+ );
+};
+
+export default ExerciseList;
diff --git a/src/app/(afterlogin)/(navbar)/home/_components/GuideContent.module.scss b/src/app/(afterlogin)/(navbar)/home/_components/GuideContent.module.scss
new file mode 100644
index 0000000..a4501c9
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/home/_components/GuideContent.module.scss
@@ -0,0 +1,67 @@
+@import '@/styles/mixins';
+
+.description {
+ font-size: $font-2;
+
+ &__first {
+ margin: 0;
+ margin-top: 8px;
+ }
+
+ &__second {
+ margin: 0;
+ margin-top: 5px;
+ }
+}
+
+.box {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 700px;
+ height: 320px;
+ gap: 28px;
+ background-color: $purple-0;
+ border-radius: 16px;
+ margin-top: 46px;
+}
+
+.left {
+ width: 240px;
+ height: 240px;
+ background-color: $purple-1;
+ border-radius: 16px;
+}
+
+.right {
+ & > p:nth-of-type(1) {
+ font-size: $font-1;
+ font-weight: 700;
+ }
+
+ p {
+ margin: 0;
+ }
+
+ ul {
+ display: flex;
+ flex-direction: column;
+ gap: 15px;
+ }
+
+ li {
+ display: flex;
+ gap: 8px;
+ }
+
+ span {
+ color: $purple-5;
+ }
+}
+
+.startButton {
+ @include button_size_web;
+ @include button_theme_default;
+
+ margin-top: 46px;
+}
diff --git a/src/app/(afterlogin)/(navbar)/home/_components/GuideContent.tsx b/src/app/(afterlogin)/(navbar)/home/_components/GuideContent.tsx
new file mode 100644
index 0000000..61029b8
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/home/_components/GuideContent.tsx
@@ -0,0 +1,55 @@
+import styles from './GuideContent.module.scss';
+
+import classNames from 'classnames/bind';
+import OneIcon from './_svgs/OneIcon';
+import TwoIcon from './_svgs/TwoIcon';
+import ThreeIcon from './_svgs/ThreeIcon';
+import { useRouter } from 'next/navigation';
+
+const cx = classNames.bind(styles);
+const GuideContent = () => {
+ const router = useRouter();
+ return (
+ <>
+ 프리젠이 처음이신가요?
+
+ 성공적인 발표를 위해서 발표 연습은 매우 중요해요. 하지만 한번의 연습으로는 모든 내용을
+ 암기하기 힘들어요.
+
+
+ 앞으로의 성공적인 발표를 위해 지금 당장 연습하러 가볼까요?.
+
+
+
+
+
프리젠을 다음과 같이 이용해봐요!
+
+ -
+
+
+ 발표 PPT와 대본을 불러와 발표 목록에 추가해주세요
+
+
+ -
+
+
+ 연습하고 싶은 모드를 설정하여 발표 연습을 진행해주세요
+
+
+ -
+
+
+ AI 피드백을 받고 만점을 받을 때까지 연습을 해봐요!
+
+
+
+
+
+
+ >
+ );
+};
+
+export default GuideContent;
diff --git a/src/app/(afterlogin)/(navbar)/home/_components/GuideForNew.tsx b/src/app/(afterlogin)/(navbar)/home/_components/GuideForNew.tsx
new file mode 100644
index 0000000..f65c513
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/home/_components/GuideForNew.tsx
@@ -0,0 +1,23 @@
+'use client';
+
+import Modal from '@/app/_components/_modules/_modal/Modal';
+import useToggle from '@/app/_hooks/useToggle';
+import { useEffect } from 'react';
+import GuideContent from './GuideContent';
+
+const GuideForNew = () => {
+ const modal = useToggle();
+
+ useEffect(() => {
+ modal.onOpen();
+ }, []);
+ return (
+ <>
+
+
+
+ >
+ );
+};
+
+export default GuideForNew;
diff --git a/src/app/(afterlogin)/(navbar)/home/_components/HistoryBanner.module.scss b/src/app/(afterlogin)/(navbar)/home/_components/HistoryBanner.module.scss
new file mode 100644
index 0000000..66c1544
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/home/_components/HistoryBanner.module.scss
@@ -0,0 +1,50 @@
+@import '@/styles/globals';
+@import '@/styles/mixins';
+
+.container {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ width: 1440px;
+ height: 230px;
+ padding: 36px;
+ border-radius: 16px;
+ background-color: $primary;
+}
+
+.history {
+ color: $white;
+
+ &__title {
+ margin: 0 0 28px;
+ }
+
+ &__contents {
+ display: flex;
+ }
+}
+
+.presentation {
+ &__thumbnail {
+ width: 165px;
+ height: 90px;
+ background-color: $white;
+ border-radius: 12px;
+ margin: 0 36px 0 0;
+ }
+}
+
+.action {
+ @include pure-button;
+ @include button_theme_inverted;
+
+ width: 100%;
+ height: 100%;
+ border-radius: 100px;
+ font-size: $h2;
+
+ &__box {
+ width: 190px;
+ height: 85px;
+ }
+}
diff --git a/src/app/(afterlogin)/(navbar)/home/_components/HistoryBanner.tsx b/src/app/(afterlogin)/(navbar)/home/_components/HistoryBanner.tsx
new file mode 100644
index 0000000..00e40c8
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/home/_components/HistoryBanner.tsx
@@ -0,0 +1,40 @@
+import { LatestPresentationType, PresentationListType } from '@/types/service';
+import styles from './HistoryBanner.module.scss';
+import ExerciseInfo from './_elements/ExerciseInfo';
+import Image from 'next/image';
+import Link from 'next/link';
+import { useGetLatestPresentation } from '../_hooks/presentationList';
+
+interface Props {
+ presentation: PresentationListType['page']['content'][0];
+}
+const HistoryBanner = ({ presentation }: Props) => {
+ // const { data: latestData }: { data: LatestPresentationType | string | undefined } =
+ // useGetLatestPresentation();
+
+ return (
+
+ );
+};
+
+export default HistoryBanner;
diff --git a/src/app/(afterlogin)/(navbar)/home/_components/_elements/ExerciseInfo.module.scss b/src/app/(afterlogin)/(navbar)/home/_components/_elements/ExerciseInfo.module.scss
new file mode 100644
index 0000000..6123419
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/home/_components/_elements/ExerciseInfo.module.scss
@@ -0,0 +1,25 @@
+@import '@/styles/globals';
+
+.info {
+ display: flex;
+ flex-direction: column;
+
+ &__title {
+ margin: 0 0 12px;
+ font-size: $h2;
+ }
+
+ &__desc {
+ display: inline-flex;
+ align-items: center;
+ font-size: $font-2;
+ }
+
+ &__division {
+ width: 4px;
+ height: 4px;
+ margin: 0 8px;
+ background-color: $white;
+ border-radius: 50%;
+ }
+}
diff --git a/src/app/(afterlogin)/(navbar)/home/_components/_elements/ExerciseInfo.tsx b/src/app/(afterlogin)/(navbar)/home/_components/_elements/ExerciseInfo.tsx
new file mode 100644
index 0000000..6cc9c1c
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/home/_components/_elements/ExerciseInfo.tsx
@@ -0,0 +1,22 @@
+'use client';
+
+import { PresentationListType } from '@/types/service';
+import styles from './ExerciseInfo.module.scss';
+
+interface Props {
+ presentation: PresentationListType['page']['content'][0];
+}
+const ExerciseInfo = ({ presentation }: Props) => {
+ return (
+
+ {presentation.title}
+
+ D{presentation.dday < 0 ? `+${Math.abs(presentation.dday)}` : `-${presentation.dday}`}
+
+ 발표 시간 {presentation.timeLimit.hours * 60 + presentation.timeLimit.minutes}분
+
+
+ );
+};
+
+export default ExerciseInfo;
diff --git a/src/app/(afterlogin)/(navbar)/home/_components/_svgs/DeleteIcon.tsx b/src/app/(afterlogin)/(navbar)/home/_components/_svgs/DeleteIcon.tsx
new file mode 100644
index 0000000..98b51d7
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/home/_components/_svgs/DeleteIcon.tsx
@@ -0,0 +1,20 @@
+const DeleteIcon = () => {
+ return (
+
+
+
+ );
+};
+
+export default DeleteIcon;
diff --git a/src/app/(afterlogin)/(navbar)/home/_components/_svgs/MenuIcon.tsx b/src/app/(afterlogin)/(navbar)/home/_components/_svgs/MenuIcon.tsx
new file mode 100644
index 0000000..92cf424
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/home/_components/_svgs/MenuIcon.tsx
@@ -0,0 +1,13 @@
+const MenuIcon = () => {
+ return (
+
+
+
+ );
+};
+
+export default MenuIcon;
diff --git a/src/app/(afterlogin)/(navbar)/home/_components/_svgs/ModifyIcon.tsx b/src/app/(afterlogin)/(navbar)/home/_components/_svgs/ModifyIcon.tsx
new file mode 100644
index 0000000..cdb306a
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/home/_components/_svgs/ModifyIcon.tsx
@@ -0,0 +1,20 @@
+const ModifyIcon = () => {
+ return (
+
+
+
+ );
+};
+
+export default ModifyIcon;
diff --git a/src/app/(afterlogin)/(navbar)/home/_components/_svgs/OneIcon.tsx b/src/app/(afterlogin)/(navbar)/home/_components/_svgs/OneIcon.tsx
new file mode 100644
index 0000000..95b83cc
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/home/_components/_svgs/OneIcon.tsx
@@ -0,0 +1,21 @@
+const OneIcon = () => {
+ return (
+
+
+
+ );
+};
+
+export default OneIcon;
diff --git a/src/app/(afterlogin)/(navbar)/home/_components/_svgs/PlusIcon.tsx b/src/app/(afterlogin)/(navbar)/home/_components/_svgs/PlusIcon.tsx
new file mode 100644
index 0000000..6ad4b47
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/home/_components/_svgs/PlusIcon.tsx
@@ -0,0 +1,34 @@
+const PlusIcon = () => {
+ return (
+
+
+
+ );
+};
+
+export default PlusIcon;
diff --git a/src/app/(afterlogin)/(navbar)/home/_components/_svgs/ThreeIcon.tsx b/src/app/(afterlogin)/(navbar)/home/_components/_svgs/ThreeIcon.tsx
new file mode 100644
index 0000000..6936d46
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/home/_components/_svgs/ThreeIcon.tsx
@@ -0,0 +1,21 @@
+const ThreeIcon = () => {
+ return (
+
+
+
+ );
+};
+
+export default ThreeIcon;
diff --git a/src/app/(afterlogin)/(navbar)/home/_components/_svgs/TwoIcon.tsx b/src/app/(afterlogin)/(navbar)/home/_components/_svgs/TwoIcon.tsx
new file mode 100644
index 0000000..a8c421e
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/home/_components/_svgs/TwoIcon.tsx
@@ -0,0 +1,21 @@
+const TwoIcon = () => {
+ return (
+
+
+
+ );
+};
+
+export default TwoIcon;
diff --git a/src/app/(afterlogin)/(navbar)/home/_hooks/presentationList.ts b/src/app/(afterlogin)/(navbar)/home/_hooks/presentationList.ts
new file mode 100644
index 0000000..e354d3f
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/home/_hooks/presentationList.ts
@@ -0,0 +1,31 @@
+import { clientHomeApi } from '@/services/client/home';
+import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
+
+export const useGetLatestPresentation = () => {
+ const response = useQuery({
+ queryKey: ['home', 'latest'],
+ queryFn: async () => {
+ const response = await clientHomeApi.getLatestPresentation();
+ return await response.json();
+ },
+ });
+ return response;
+};
+
+export const useDeletePresentation = (id: number) => {
+ const queryClient = useQueryClient();
+
+ const response = useMutation({
+ mutationKey: ['delete', id],
+ mutationFn: async () => {
+ const response = await clientHomeApi.deletePresentationList({ presentationIds: [id] });
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['home', 'list'] });
+ },
+ onError: (error) => {
+ alert(error.message);
+ },
+ });
+ return response;
+};
diff --git a/src/app/(afterlogin)/(navbar)/home/page.module.scss b/src/app/(afterlogin)/(navbar)/home/page.module.scss
new file mode 100644
index 0000000..e6063fc
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/home/page.module.scss
@@ -0,0 +1,6 @@
+.container {
+ display: flex;
+ align-items: center;
+ flex-direction: column;
+ margin-top: 36px;
+}
diff --git a/src/app/(afterlogin)/(navbar)/home/page.tsx b/src/app/(afterlogin)/(navbar)/home/page.tsx
new file mode 100644
index 0000000..b3f3dc1
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/home/page.tsx
@@ -0,0 +1,46 @@
+import { HydrationBoundary, QueryClient, dehydrate } from '@tanstack/react-query';
+import HistoryBanner from './_components/HistoryBanner';
+import styles from './page.module.scss';
+import { serverHomeApi } from '@/services/server/home';
+import GuideForNew from './_components/GuideForNew';
+import CardList from '../_components/CardList';
+
+export default async function Page() {
+ const queryClient = new QueryClient();
+ const listResponse = await queryClient.fetchInfiniteQuery({
+ queryKey: ['home', 'list'],
+ queryFn: async ({ pageParam = 0 }) => {
+ const response = await serverHomeApi.getPresentationList({ pageParam });
+ return await response.json();
+ },
+ initialPageParam: 0,
+ });
+
+ const isEmpty = listResponse.pages[0].page.empty;
+
+ const latestResponse = await queryClient.fetchQuery({
+ queryKey: ['home', 'latest'],
+ queryFn: async () => {
+ const latestResponse = await serverHomeApi.getLatestPresentation();
+ if (latestResponse.status === 204) return 'empty';
+ return await latestResponse.json();
+ },
+ });
+
+ const dehydratedState = dehydrate(queryClient);
+ return (
+ <>
+ {isEmpty ? (
+
+ ) : (
+
+
+ {latestResponse !== 'empty' && }
+ {/* */}
+
+
+
+ )}
+ >
+ );
+}
diff --git a/src/app/(afterlogin)/(navbar)/layout.tsx b/src/app/(afterlogin)/(navbar)/layout.tsx
new file mode 100644
index 0000000..6035e78
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/layout.tsx
@@ -0,0 +1,13 @@
+import Navbar from '@/app/(afterlogin)/_components/Navbar';
+import { ReactChildrenProps } from '@/types/common';
+
+const Layout = ({ children }: ReactChildrenProps) => {
+ return (
+
+
+ {children}
+
+ );
+};
+
+export default Layout;
diff --git a/src/app/(afterlogin)/(navbar)/upload/[id]/_component/ControlButtons.module.scss b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/ControlButtons.module.scss
new file mode 100644
index 0000000..6140e33
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/ControlButtons.module.scss
@@ -0,0 +1,94 @@
+@import '@/styles/mixins';
+
+.container {
+ display: flex;
+ justify-content: center;
+ width: 100%;
+
+ ::-webkit-scrollbar {
+ height: 12px;
+ }
+
+ ::-webkit-scrollbar-track {
+ background: transparent;
+ }
+
+ ::-webkit-scrollbar-thumb {
+ background: $gray-3;
+ border-radius: 20px;
+ }
+
+ ::-webkit-scrollbar-thumb:hover {
+ background: $gray-3;
+ }
+}
+
+.buttons {
+ display: flex;
+ gap: 15px;
+ overflow-x: auto;
+}
+
+.singlePptPage {
+ position: relative;
+ width: 107px; // border-box
+ height: 62px; // border-box
+ background-color: transparent;
+ border: 1px solid $gray-3;
+ border-radius: 8px;
+ cursor: pointer;
+
+ &.selected {
+ border: 3px solid $purple-4;
+ }
+}
+
+.closeButton {
+ @include pure-button;
+ @include flex-center;
+
+ position: absolute;
+ top: 8%;
+ left: 8%;
+ width: 19px;
+ height: 19px;
+ font-weight: bold;
+ border-radius: 15px;
+ cursor: pointer;
+ box-shadow: none;
+ transition: box-shadow 0.1s ease;
+
+ &:hover {
+ box-shadow: 0 2px 4px rgb(0 0 0 / 100%);
+ }
+}
+
+.orderNumber {
+ @include flex-center;
+
+ position: absolute;
+ right: 0;
+ bottom: 0;
+ width: 20px;
+ height: 20px;
+ color: $white;
+ font-size: px-to-rem(13px);
+ background-color: $gray-5;
+ border-top-left-radius: 8px;
+ border-end-end-radius: 4px;
+
+ &.selected {
+ background-color: $purple-4;
+ }
+}
+
+.addButton {
+ @include pure-button;
+ @include flex-center;
+
+ display: inline-block;
+ min-width: 107px; // border-box
+ height: 62px; // border-box
+ border: 1px solid $gray-3;
+ border-radius: 15px;
+}
diff --git a/src/app/(afterlogin)/(navbar)/upload/[id]/_component/ControlButtons.tsx b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/ControlButtons.tsx
new file mode 100644
index 0000000..0bce133
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/ControlButtons.tsx
@@ -0,0 +1,214 @@
+'use client';
+import React from 'react';
+
+import { Dispatch, MouseEvent, SetStateAction } from 'react';
+
+import Image from 'next/image';
+
+import styles from './ControlButtons.module.scss';
+import classNames from 'classnames/bind';
+
+import { DragDropContext, Draggable, DropResult, Droppable } from 'react-beautiful-dnd';
+
+import { FieldErrors, UseFormGetValues } from 'react-hook-form';
+
+import { UploadDataType, ValidtaionType } from '@/types/service';
+
+import { MAX_LENGTH } from '@/config/const';
+import PptImageSvgs from '../_svgs/PptImgSvgs';
+
+const cx = classNames.bind(styles);
+
+interface ControlButtonsProps {
+ presentationData: UploadDataType;
+ setPresentationData: Dispatch>;
+ currentPageIndex: number;
+ slug?: string;
+ changeCurrentPageIndex: (nextIndex: number) => void;
+ getValues: UseFormGetValues;
+ errors: FieldErrors;
+}
+
+const ControlButtons = ({
+ presentationData,
+ setPresentationData,
+ currentPageIndex,
+ changeCurrentPageIndex,
+ getValues,
+ errors,
+}: ControlButtonsProps) => {
+ const remove = (e: MouseEvent, index: number) => {
+ e.stopPropagation();
+ setPresentationData((prev) => {
+ const shallow = [...prev.slides];
+ shallow.splice(index, 1);
+
+ return {
+ ...prev,
+ slides: shallow,
+ };
+ });
+
+ // 삭제 시, 관련 페이지 인덱스 당기기
+ if (index <= currentPageIndex) {
+ const target = currentPageIndex === 0 ? 0 : currentPageIndex - 1;
+ changeCurrentPageIndex(target);
+ }
+ };
+
+ const handleChange = (result: DropResult) => {
+ if (
+ !result.destination ||
+ errors.script ||
+ errors.memo ||
+ getValues('script').length > MAX_LENGTH.SCRIPT ||
+ getValues('script').length === 0 ||
+ getValues('memo').length > MAX_LENGTH.MEMO
+ )
+ return;
+
+ const to = result.destination?.index;
+ const from = result.source.index;
+
+ setPresentationData((prev) => {
+ const shallow = [...prev.slides];
+ const moveTarget = shallow.splice(from, 1);
+ shallow.splice(to, 0, ...moveTarget);
+
+ return {
+ ...prev,
+ slides: shallow,
+ };
+ });
+ };
+
+ const eachPptButtonClick = (index: number) => {
+ changeCurrentPageIndex(index);
+
+ if (
+ errors.script ||
+ errors.memo ||
+ getValues('script').length > MAX_LENGTH.SCRIPT ||
+ getValues('script').length === 0 ||
+ getValues('memo').length > MAX_LENGTH.MEMO
+ )
+ return;
+ setPresentationData((prev) => {
+ const shallow = { ...prev };
+ shallow.title = getValues('title');
+ const shallowSlides = [...shallow.slides];
+ shallowSlides[currentPageIndex] = {
+ ...shallowSlides[currentPageIndex],
+ script: getValues('script'),
+ memo: getValues('memo'),
+ };
+ return {
+ ...shallow,
+ slides: shallowSlides,
+ };
+ });
+ };
+
+ const onStart = () => {
+ if (
+ errors.script ||
+ errors.memo ||
+ getValues('script').length > MAX_LENGTH.SCRIPT ||
+ getValues('script').length === 0 ||
+ getValues('memo').length > MAX_LENGTH.MEMO
+ )
+ return;
+ setPresentationData((prev) => {
+ const shallow = { ...prev };
+ shallow.title = getValues('title');
+ const shallowSlides = [...shallow.slides];
+ shallowSlides[currentPageIndex] = {
+ ...shallowSlides[currentPageIndex],
+ script: getValues('script'),
+ memo: getValues('memo'),
+ };
+ return {
+ ...shallow,
+ slides: shallowSlides,
+ };
+ });
+ };
+
+ return (
+
+
+
+ {(provided) => (
+
+ {presentationData.slides.slice(0, -1).map((item, index) => (
+
+ {(provided, snapshot) => {
+ return (
+
+
changeCurrentPageIndex(index)}
+ onClick={() => eachPptButtonClick(index)}
+ className={cx('singlePptPage', {
+ selected: currentPageIndex === index,
+ })}
+ >
+
+
+
+ {index + 1}
+
+
+
+ );
+ }}
+
+ ))}
+ {provided.placeholder}
+
+
+ )}
+
+
+
+ );
+};
+
+export default React.memo(ControlButtons);
diff --git a/src/app/(afterlogin)/(navbar)/upload/[id]/_component/CreatePresentation.tsx b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/CreatePresentation.tsx
new file mode 100644
index 0000000..e1a37fa
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/CreatePresentation.tsx
@@ -0,0 +1,39 @@
+'use client';
+
+import { useState } from 'react';
+
+import { UploadDataType } from '@/types/service';
+
+import InputSection from './InputSection';
+
+const CreatePresentation = () => {
+ const initialState: UploadDataType = {
+ title: null,
+ deadlineDate: null,
+ timeLimit: {
+ hours: null,
+ minutes: 1,
+ },
+ alertTime: {
+ hours: null,
+ minutes: null,
+ },
+ slides: [{ imageFileId: null, imageFilePath: null, script: null, memo: null }],
+ };
+
+ const [presentationData, setPresentationData] = useState(initialState);
+ const [currentPageIndex, setCurrpentPageIndex] = useState(0);
+
+ return (
+
+ );
+};
+
+export default CreatePresentation;
diff --git a/src/app/(afterlogin)/(navbar)/upload/[id]/_component/CustomCalendar.scss b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/CustomCalendar.scss
new file mode 100644
index 0000000..fa16ab2
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/CustomCalendar.scss
@@ -0,0 +1,165 @@
+/* 달력 커스텀 */
+
+@import 'react-calendar/dist/Calendar.css';
+
+.calendarContainer {
+ position: absolute;
+ right: 0;
+}
+
+.react-calendar {
+ width: 250px;
+ max-width: 100%;
+ background: white;
+ border: 1px solid #a0a096;
+ font-family: Arial, Helvetica, sans-serif;
+ line-height: 1.125em;
+}
+
+/* 요일 밑줄 제거 */
+.react-calendar__month-view__weekdays abbr {
+ text-decoration: none;
+ font-weight: 800;
+}
+
+.react-calendar--doubleView .react-calendar__viewContainer {
+ display: flex;
+ margin: -0.5em;
+}
+
+.react-calendar--doubleView .react-calendar__viewContainer > * {
+ width: 50%;
+ margin: 0.5em;
+}
+
+.react-calendar,
+.react-calendar *,
+.react-calendar *::before,
+.react-calendar *::after {
+ box-sizing: border-box;
+}
+
+.react-calendar button {
+ margin: 0;
+ border: 0;
+ outline: none;
+}
+
+.react-calendar button:enabled:hover {
+ cursor: pointer;
+}
+
+.react-calendar__navigation {
+ display: flex;
+ height: 44px;
+ margin-bottom: 1em;
+}
+
+.react-calendar__navigation button {
+ min-width: 44px;
+ background: none;
+}
+
+.react-calendar__navigation button:disabled {
+ background-color: #f0f0f0;
+}
+
+.react-calendar__navigation button:enabled:hover,
+.react-calendar__navigation button:enabled:focus {
+ background-color: #e6e6e6;
+}
+
+.react-calendar__month-view__weekdays {
+ font: inherit;
+ font-size: 0.75em;
+ font-weight: bold;
+ text-align: center;
+ text-transform: uppercase;
+}
+
+.react-calendar__month-view__weekdays__weekday {
+ padding: 0.5em;
+}
+
+.react-calendar__month-view__weekNumbers .react-calendar__tile {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font: inherit;
+ font-size: 0.75em;
+ font-weight: bold;
+}
+
+.react-calendar__month-view__days__day--weekend {
+ color: #d10000;
+}
+
+.react-calendar__month-view__days__day--neighboringMonth,
+.react-calendar__decade-view__years__year--neighboringDecade,
+.react-calendar__century-view__decades__decade--neighboringCentury {
+ color: #757575;
+}
+
+.react-calendar__year-view .react-calendar__tile,
+.react-calendar__decade-view .react-calendar__tile,
+.react-calendar__century-view .react-calendar__tile {
+ padding: 2em 0.5em;
+}
+
+.react-calendar__tile {
+ padding: 10px 6.6667px;
+ background: none;
+ font: inherit;
+ font-size: 0.833em;
+ text-align: center;
+ line-height: 16px;
+ max-width: 100%;
+}
+
+.react-calendar__tile:disabled {
+ background-color: #f0f0f0;
+ color: #ababab;
+}
+
+.react-calendar__month-view__days__day--neighboringMonth:disabled,
+.react-calendar__decade-view__years__year--neighboringDecade:disabled,
+.react-calendar__century-view__decades__decade--neighboringCentury:disabled {
+ color: #cdcdcd;
+}
+
+.react-calendar__tile:enabled:hover,
+.react-calendar__tile:enabled:focus {
+ background-color: #e6e6e6;
+}
+
+.react-calendar__tile--now {
+ background: #ffff76;
+}
+
+.react-calendar__tile--now:enabled:hover,
+.react-calendar__tile--now:enabled:focus {
+ background: #ffffa9;
+}
+
+.react-calendar__tile--hasActive {
+ background: #76baff;
+}
+
+.react-calendar__tile--hasActive:enabled:hover,
+.react-calendar__tile--hasActive:enabled:focus {
+ background: #a9d4ff;
+}
+
+.react-calendar__tile--active {
+ background: #006edc;
+ color: white;
+}
+
+.react-calendar__tile--active:enabled:hover,
+.react-calendar__tile--active:enabled:focus {
+ background: #1087ff;
+}
+
+.react-calendar--selectRange .react-calendar__tile--hover {
+ background-color: #e6e6e6;
+}
diff --git a/src/app/(afterlogin)/(navbar)/upload/[id]/_component/CustomCalendar.tsx b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/CustomCalendar.tsx
new file mode 100644
index 0000000..07532bf
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/CustomCalendar.tsx
@@ -0,0 +1,36 @@
+import { Value } from '@/types/service';
+import { Dispatch, SetStateAction } from 'react';
+import Calendar from 'react-calendar';
+import moment from 'moment';
+import './CustomCalendar.scss';
+
+interface CustomCalendarProps {
+ today: Date;
+ date: Value;
+ setDate: Dispatch>;
+ setIsCalenderOpen: Dispatch>;
+}
+
+const CustomCalendar = ({ today, date, setDate, setIsCalenderOpen }: CustomCalendarProps) => {
+ return (
+
+ moment(date).format('YYYY')}
+ formatMonthYear={(locale, date) => moment(date).format('YYYY. MM')}
+ formatDay={(locale, date) => moment(date).format('DD')}
+ onClickDay={() => {
+ setIsCalenderOpen(false);
+ }}
+ />
+
+ );
+};
+
+export default CustomCalendar;
diff --git a/src/app/(afterlogin)/(navbar)/upload/[id]/_component/EditPresentation.tsx b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/EditPresentation.tsx
new file mode 100644
index 0000000..e7312ae
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/EditPresentation.tsx
@@ -0,0 +1,71 @@
+'use client';
+
+import { useEffect, useState } from 'react';
+
+import { UploadDataType } from '@/types/service';
+
+import { useGetPresentationData } from '../_hooks/presentation';
+
+import InputSection from './InputSection';
+import Spinner from '@/app/_components/_modules/Spinner';
+
+interface EditPresentationProps {
+ slug: number;
+}
+
+const EditPresentation = ({ slug }: EditPresentationProps) => {
+ const initialState: UploadDataType = {
+ title: null,
+ deadlineDate: null,
+ timeLimit: {
+ hours: null,
+ minutes: 1,
+ },
+ alertTime: {
+ hours: null,
+ minutes: null,
+ },
+ // slides의 ID값은 무시
+ slides: [{ imageFileId: null, imageFilePath: null, script: null, memo: null }],
+ };
+
+ const [presentationData, setPresentationData] = useState(initialState);
+ const [currentPageIndex, setCurrpentPageIndex] = useState(0);
+
+ const { value, isLoading }: { value: UploadDataType; isLoading: boolean } =
+ useGetPresentationData(slug);
+
+ useEffect(() => {
+ const initailSetting = async () => {
+ setPresentationData(() => {
+ const shallow = [...value.slides];
+ shallow.push(...initialState.slides);
+ return {
+ ...value,
+ slides: shallow,
+ };
+ });
+ };
+
+ if (value) initailSetting();
+ }, [value]);
+
+ return (
+ <>
+ {isLoading ? (
+
+ ) : (
+
+ )}
+ >
+ );
+};
+
+export default EditPresentation;
diff --git a/src/app/(afterlogin)/(navbar)/upload/[id]/_component/InputSection.module.scss b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/InputSection.module.scss
new file mode 100644
index 0000000..6dd63a3
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/InputSection.module.scss
@@ -0,0 +1,96 @@
+@import '@/styles/mixins';
+
+.container {
+ display: flex;
+ align-items: stretch;
+ gap: 62px;
+}
+
+.leftSectionWrapper {
+ display: flex;
+ align-items: flex-start;
+ justify-content: flex-end;
+ flex-grow: 1;
+}
+
+.description {
+ margin: 24px 0 40px;
+ font-size: px-to-rem(28px);
+ font-weight: bolder;
+
+ span {
+ margin-left: 20px;
+ font-size: px-to-rem(20px);
+ }
+}
+
+.leftSection {
+ width: 504px;
+}
+
+.cancelButton {
+ @include pure-button;
+
+ margin-left: auto;
+ margin-top: 24px;
+ width: 24px;
+ height: 24px;
+}
+
+.rightSectionWrapper {
+ display: flex;
+ justify-content: flex-start;
+ flex-grow: 1;
+ margin-bottom: 95px;
+}
+
+.rightSection {
+ display: flex;
+ width: 874px;
+ flex-direction: column;
+}
+
+.line {
+ width: 100%;
+ margin: 33px 0 22px;
+ border-top: 2px solid $gray-1;
+}
+
+.saveButtons {
+ margin-top: 20px;
+ display: flex;
+ justify-content: space-between;
+ height: 56px;
+
+ .save {
+ @include button_theme_lined;
+ @include flex-center;
+
+ width: 100%;
+ height: 100%;
+ max-width: 117px;
+ font-size: px-to-rem(20px);
+ border-radius: 12px;
+ }
+
+ .start {
+ @include button_theme_default;
+ @include flex-center;
+
+ width: 100%;
+ height: 100%;
+ max-width: 743px;
+ border-radius: 12px;
+ font-size: px-to-rem(20px);
+ }
+}
+
+.modlaConfirmButton {
+ @include button_size_web;
+ @include button_theme_default;
+}
+
+.modalCancelButton {
+ @include button_size_web;
+ @include button_theme_lined;
+}
diff --git a/src/app/(afterlogin)/(navbar)/upload/[id]/_component/InputSection.tsx b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/InputSection.tsx
new file mode 100644
index 0000000..0d3729f
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/InputSection.tsx
@@ -0,0 +1,272 @@
+'use client';
+
+import { Dispatch, SetStateAction, useEffect, useState, useCallback } from 'react';
+import { useForm } from 'react-hook-form';
+import { usePatchPresentationData, usePostPresentationData } from '../_hooks/presentation';
+import useToggle from '@/app/_hooks/useToggle';
+
+import UploadTitle from './UploadTitle';
+import UploadScript from './UploadScript';
+import UploadMemo from './UploadMemo';
+import UploadDeadlineDate from './UploadDeadlineDate';
+import UploadTimer from './UploadTimer';
+import UploadPpt from './UploadPpt';
+import ControlButtons from './ControlButtons';
+import Required from './Required';
+
+import Confirm from '@/app/_components/_modules/_modal/Confirm';
+
+import styles from './InputSection.module.scss';
+
+import { UploadDataType, ValidtaionType } from '@/types/service';
+import { MAX_LENGTH } from '@/config/const';
+
+import { useRouter } from 'next/navigation';
+import PptImageSvgs from '../_svgs/PptImgSvgs';
+
+interface InputSectionProps {
+ presentationData: UploadDataType;
+ setPresentationData: Dispatch>;
+ currentPageIndex: number;
+ setCurrpentPageIndex: Dispatch>;
+ initialState: UploadDataType;
+ slug?: number | 'new';
+}
+
+interface ErroForMovePageType {
+ memo: boolean;
+ script: {
+ minLength: boolean;
+ maxLength: boolean;
+ };
+}
+const InputSection = ({
+ presentationData,
+ setPresentationData,
+ currentPageIndex,
+ setCurrpentPageIndex,
+ initialState,
+ slug,
+}: InputSectionProps) => {
+ const router = useRouter();
+ const [errorForMovePage, setErrorForMovePage] = useState({
+ script: {
+ minLength: false,
+ maxLength: false,
+ },
+ memo: false,
+ });
+ const [submitAction, setSubmitAction] = useState<'save' | 'start'>('save');
+ const handleSaveClick = () => setSubmitAction('save');
+ const handleStartClick = () => setSubmitAction('start');
+
+ const postMutation = usePostPresentationData(submitAction);
+ const patchMutation = usePatchPresentationData(submitAction, slug!);
+ const confirm = useToggle();
+
+ const {
+ register,
+ handleSubmit,
+ reset,
+ setValue,
+ getValues,
+ formState: { isSubmitting, errors },
+ } = useForm();
+
+ useEffect(() => {
+ const resetFormData = () => {
+ reset({
+ title: presentationData.title || '',
+ script: presentationData.slides[currentPageIndex].script || '',
+ memo: presentationData.slides[currentPageIndex].memo || '',
+ deadlineDate: presentationData.deadlineDate,
+ });
+ };
+ resetFormData();
+ }, [presentationData, currentPageIndex]);
+
+ const changeCurrentPageIndex = useCallback(
+ (nextIndex: number) => {
+ if (currentPageIndex === presentationData.slides.length - 1) {
+ setCurrpentPageIndex(nextIndex);
+ } else {
+ // 폼 데이터 사용 (watch도 가능)
+ setErrorForMovePage({
+ memo: getValues('memo').length > MAX_LENGTH.MEMO,
+ script: {
+ minLength: getValues('script').length === 0,
+ maxLength: getValues('script').length > MAX_LENGTH.SCRIPT,
+ },
+ });
+
+ if (
+ errors.script ||
+ errors.memo ||
+ getValues('script').length > MAX_LENGTH.SCRIPT ||
+ getValues('script').length === 0 ||
+ getValues('memo').length > MAX_LENGTH.MEMO
+ )
+ return;
+
+ setCurrpentPageIndex(nextIndex);
+ }
+ },
+ [
+ currentPageIndex,
+ errors.memo,
+ errors.script,
+ getValues,
+ presentationData.slides.length,
+ setCurrpentPageIndex,
+ ],
+ );
+
+ return (
+
+
+
+
+ 발표 자료 추가
+
+
+ 필수항목
+
+
+
+
+
+
+
+
+
+
{
+ router.push('/home');
+ confirm.onClose();
+ }}
+ />
+
+
+
+
+ );
+};
+
+export default InputSection;
diff --git a/src/app/(afterlogin)/(navbar)/upload/[id]/_component/Required.tsx b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/Required.tsx
new file mode 100644
index 0000000..f5c9ead
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/Required.tsx
@@ -0,0 +1,5 @@
+const Required = () => {
+ return *;
+};
+
+export default Required;
diff --git a/src/app/(afterlogin)/(navbar)/upload/[id]/_component/UploadDeadlineDate.module.scss b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/UploadDeadlineDate.module.scss
new file mode 100644
index 0000000..0232fc1
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/UploadDeadlineDate.module.scss
@@ -0,0 +1,46 @@
+@import '@/styles/mixins';
+
+.container {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ gap: 12px;
+}
+
+.description {
+ margin: 0;
+ font-size: px-to-rem(22px);
+ font-weight: bolder;
+}
+
+.label {
+ font-size: px-to-rem(20px);
+}
+
+.inputWrapper {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.calendarButton {
+ width: 148px;
+ height: 29px;
+ padding: 5px 12px;
+ border: none;
+ font-size: px-to-rem(20px);
+ border-radius: 4px;
+ background-color: $gray-0;
+
+ p {
+ margin: 0;
+
+ &.placeholder {
+ color: $gray-3;
+ }
+
+ &.selected {
+ color: black;
+ }
+ }
+}
diff --git a/src/app/(afterlogin)/(navbar)/upload/[id]/_component/UploadDeadlineDate.tsx b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/UploadDeadlineDate.tsx
new file mode 100644
index 0000000..8994e56
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/UploadDeadlineDate.tsx
@@ -0,0 +1,111 @@
+'use client';
+
+import { Dispatch, SetStateAction, forwardRef, useState } from 'react';
+
+import { UploadDataType, ValidtaionType, Value } from '@/types/service';
+
+import styles from './UploadDeadlineDate.module.scss';
+
+import CustomCalendar from './CustomCalendar';
+import { FieldErrors, RegisterOptions, UseFormGetValues, UseFormRegister } from 'react-hook-form';
+import Required from './Required';
+import { formatDate } from '../_utils/date';
+import { VALIDATION_MESSAGE } from '@/config/const';
+
+interface UploadDeadlineDateProps {
+ deadlineDate: UploadDataType['deadlineDate'];
+ setPresentationData: Dispatch>;
+ register: UseFormRegister;
+ errors: FieldErrors;
+ currentPageIndex: number;
+ getValues: UseFormGetValues;
+}
+
+const UploadDeadlineDate = forwardRef(
+ ({ deadlineDate, setPresentationData, register, errors, getValues, currentPageIndex }, ref) => {
+ const registerOptions: RegisterOptions = {
+ required: VALIDATION_MESSAGE.DEADLINEDATE.REQUIRED,
+ };
+
+ const [isCalenderOpen, setIsCalenderOpen] = useState(false);
+
+ const today = new Date();
+
+ // 리액트 캘린더 전용 업데이트 함수
+ const setDate: Dispatch> = (newValue) => {
+ // KST를 UTC로 변환하기 위해, getTimezoneOffset()의 결과를 반전시키고 9시간(한국 시간대)을 더함
+ // (년,월,일만 적용)
+ const targetDate = newValue as Date;
+ const utcDate = new Date(
+ targetDate.getTime() - targetDate.getTimezoneOffset() * 60000 + 9 * 60 * 60000,
+ );
+
+ setPresentationData((prev) => {
+ const shallow = { ...prev };
+ shallow.title = getValues('title');
+ shallow.deadlineDate = newValue instanceof Function ? newValue(prev.deadlineDate) : utcDate;
+ const shallowSlides = [...shallow.slides];
+ shallowSlides[currentPageIndex] = {
+ ...shallowSlides[currentPageIndex],
+ script: getValues('script'),
+ memo: getValues('memo'),
+ };
+ return {
+ ...shallow,
+ slides: shallowSlides,
+ };
+ });
+ // setPresentationData((prev) => ({
+ // ...prev,
+ // deadlineDate: newValue instanceof Function ? newValue(prev.deadlineDate) : newValue,
+ // }));
+ };
+
+ return (
+
+
D-day 설정
+
+
+
+
+
+
+
+ {isCalenderOpen && (
+
+ )}
+
+
+
+ );
+ },
+);
+UploadDeadlineDate.displayName = 'UploadDeadlineDate';
+
+export default UploadDeadlineDate;
diff --git a/src/app/(afterlogin)/(navbar)/upload/[id]/_component/UploadMemo.module.scss b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/UploadMemo.module.scss
new file mode 100644
index 0000000..d212e19
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/UploadMemo.module.scss
@@ -0,0 +1,92 @@
+@import '@/styles/mixins';
+
+.container {
+ width: 100%;
+}
+
+.description {
+ display: flex;
+ align-items: center;
+ font-size: px-to-rem(22px);
+ font-weight: bolder;
+ margin-top: 37px;
+ gap: 10px;
+}
+
+.memoSection {
+ position: relative;
+ width: 100%;
+ height: 157px;
+ border: 1px solid $gray-3;
+ padding-right: 16px;
+ border-radius: 16px;
+
+ ::-webkit-scrollbar {
+ width: 7px;
+ }
+
+ ::-webkit-scrollbar-track {
+ background: transparent;
+ }
+
+ ::-webkit-scrollbar-thumb {
+ background: $gray-3;
+ border-radius: 20px;
+ }
+
+ ::-webkit-scrollbar-thumb:hover {
+ background: $gray-3;
+ }
+
+ &.warning {
+ border: 2px solid $red-7;
+ }
+
+ &:focus-within {
+ outline: 1px solid $gray-8;
+ }
+}
+
+.memoTextarea {
+ width: 100%;
+ height: 75%;
+ border-radius: 16px;
+ padding: 20px 4px 20px 20px; // 스크롤 영역 포함
+ border: none;
+ font-size: px-to-rem(20px);
+ line-height: 32px;
+ resize: none;
+
+ &::placeholder {
+ color: $gray-3;
+ }
+
+ &:focus {
+ outline: none;
+ }
+}
+
+.guide {
+ margin-top: 8px;
+ margin-bottom: 14px;
+ color: #9c9c9c;
+ font-size: px-to-rem(20px);
+}
+
+.lengthCount {
+ position: absolute;
+ right: 20px;
+ bottom: 20px;
+ z-index: 10;
+
+ p {
+ margin: 0;
+ color: $gray-4;
+ }
+
+ &.lengthWarning {
+ p {
+ color: $red-7;
+ }
+ }
+}
diff --git a/src/app/(afterlogin)/(navbar)/upload/[id]/_component/UploadMemo.tsx b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/UploadMemo.tsx
new file mode 100644
index 0000000..7ece556
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/UploadMemo.tsx
@@ -0,0 +1,86 @@
+'use client';
+
+import { ChangeEventHandler, Dispatch, SetStateAction, forwardRef, useState } from 'react';
+
+import { UploadDataType, ValidtaionType } from '@/types/service';
+
+import styles from './UploadMemo.module.scss';
+import classNames from 'classnames/bind';
+import { FieldErrors, RegisterOptions, UseFormRegister, UseFormSetValue } from 'react-hook-form';
+import { MAX_LENGTH, VALIDATION_MESSAGE } from '@/config/const';
+import InputFormSvgs from '../_svgs/InputFormSvgs';
+
+interface UploadMemoProps {
+ memo: string;
+ currentPageIndex: number;
+ register: UseFormRegister;
+ errors: FieldErrors;
+ lastDummyPageIndex: number;
+ setValue: UseFormSetValue;
+}
+
+const cx = classNames.bind(styles);
+
+const UploadMemo = forwardRef(
+ ({ memo, currentPageIndex, register, errors, lastDummyPageIndex, setValue }, ref) => {
+ const [currentLength, setCurrentLength] = useState(memo.length);
+ const registerOptions: RegisterOptions =
+ currentPageIndex === lastDummyPageIndex
+ ? {}
+ : {
+ maxLength: {
+ value: MAX_LENGTH.MEMO,
+ message: VALIDATION_MESSAGE.MEMO.MAX_LENGTH,
+ },
+ };
+
+ const onCurrentLengthChange: ChangeEventHandler = (e) => {
+ const value = e.target.value;
+ setValue('memo', value, { shouldValidate: true });
+ if (value.length > MAX_LENGTH.MEMO) {
+ setValue('memo', value.slice(0, MAX_LENGTH.MEMO + 1), { shouldValidate: true });
+ }
+ setCurrentLength(value.length);
+ };
+
+ return (
+
+
+
+
+
+
+
+ {/* 제출용 훅 폼 유효성 검사 */}
+ {errors.memo && (
+
+ {errors.memo.message as string}
+
+ )}
+
+
발표하면서 계속 확인해야 하는 내용을 메모해보세요.
+
MAX_LENGTH.MEMO && 'warning'])}>
+
+
MAX_LENGTH.MEMO && 'lengthWarning'])}>
+
+ {currentLength > MAX_LENGTH.MEMO + 1 ? MAX_LENGTH.MEMO + 1 : currentLength}/
+ {MAX_LENGTH.MEMO}
+
+
+
+
+ );
+ },
+);
+UploadMemo.displayName = 'UploadMemo';
+
+export default UploadMemo;
diff --git a/src/app/(afterlogin)/(navbar)/upload/[id]/_component/UploadPpt.module.scss b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/UploadPpt.module.scss
new file mode 100644
index 0000000..df5ecd4
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/UploadPpt.module.scss
@@ -0,0 +1,96 @@
+@import '@/styles/mixins';
+
+.container {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ width: 100%;
+}
+
+.pptUpdateSection {
+ @include flex-center;
+
+ position: relative;
+ width: 100%;
+ height: 285px;
+ border: 1px solid $gray-3;
+ border-radius: 16px;
+ margin-bottom: 21px;
+}
+
+.newPptSection {
+ @include flex-center;
+
+ flex-direction: column;
+ gap: 18px;
+ width: 214px;
+ height: 119px;
+}
+
+.updateButton {
+ @include button_theme_lined;
+
+ width: 214px;
+ height: 56px;
+ padding: 12px 24px;
+ font-size: px-to-rem(16px);
+ font-weight: bolder;
+ border-radius: 100px;
+}
+
+.pptImageSection {
+ position: relative;
+ width: 100%;
+ height: 100%;
+}
+
+.changePptImageButton {
+ @include pure-button;
+ @include button_theme_lined;
+
+ display: none;
+ width: 198px;
+ height: 56px;
+ background-color: $white;
+ font-size: px-to-rem(16px);
+ border-radius: 100px;
+}
+
+.hoverSection {
+ &:hover {
+ .changePptImageButton {
+ display: block;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ }
+
+ .pptImage {
+ filter: blur(8px);
+ transition: filter 0.3s ease;
+ }
+ }
+}
+
+.goLeft {
+ @include pure-button;
+
+ position: absolute;
+ top: 50%;
+ z-index: 10;
+ height: 100%;
+ transform: translate(0, -50%);
+ padding-left: 12px;
+}
+
+.goRight {
+ @include pure-button;
+
+ position: absolute;
+ top: 50%;
+ right: 0;
+ height: 100%;
+ transform: translate(0, -50%);
+ padding-right: 12px;
+}
diff --git a/src/app/(afterlogin)/(navbar)/upload/[id]/_component/UploadPpt.tsx b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/UploadPpt.tsx
new file mode 100644
index 0000000..23a6de4
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/UploadPpt.tsx
@@ -0,0 +1,156 @@
+'use client';
+
+import Image from 'next/image';
+import { ChangeEventHandler, Dispatch, SetStateAction, useRef } from 'react';
+import styles from './UploadPpt.module.scss';
+import { UploadDataType, ValidtaionType } from '@/types/service';
+
+import { FieldErrors, UseFormGetValues } from 'react-hook-form';
+import { MAX_LENGTH } from '@/config/const';
+import { FileService } from '@/services/client/file';
+import PptImageSvgs from '../_svgs/PptImgSvgs';
+
+interface UploadPptProps {
+ pptInfo: UploadDataType['slides'][0];
+ setPresentationData: Dispatch>;
+ currentPageIndex: number;
+ initialState: UploadDataType;
+ changeCurrentPageIndex: (nextIndex: number) => void;
+ getValues: UseFormGetValues;
+ errors: FieldErrors;
+}
+const UploadPpt = ({
+ pptInfo,
+ setPresentationData,
+ currentPageIndex,
+ changeCurrentPageIndex,
+ initialState,
+ errors,
+ getValues,
+}: UploadPptProps) => {
+ const imageRef = useRef(null);
+
+ const onClickButton = () => {
+ imageRef.current?.click();
+ };
+
+ const onUpload: ChangeEventHandler = async (e) => {
+ e.preventDefault();
+ if (imageRef.current?.files) {
+ const file = imageRef.current.files[0];
+
+ if (file) {
+ const { id, path } = await FileService.fileUpload(file);
+ setPresentationData((prev) => {
+ const shallow = { ...prev };
+ shallow.title = getValues('title');
+ const shallowSlides = [...shallow.slides];
+ shallowSlides[currentPageIndex] = {
+ ...shallowSlides[currentPageIndex],
+ script: getValues('script'),
+ memo: getValues('memo'),
+ imageFileId: id,
+ imageFilePath: path,
+ };
+ // 추가
+ if (currentPageIndex === prev.slides.length - 1) {
+ shallowSlides.push(initialState.slides[0]);
+ }
+ return {
+ ...shallow,
+ slides: shallowSlides,
+ };
+ });
+ }
+ }
+ };
+
+ const movePage = (index: number) => {
+ changeCurrentPageIndex(index);
+
+ if (
+ errors.script ||
+ errors.memo ||
+ getValues('script').length > MAX_LENGTH.SCRIPT ||
+ getValues('script').length === 0 ||
+ getValues('memo').length > MAX_LENGTH.MEMO
+ )
+ return;
+ setPresentationData((prev) => {
+ const shallow = { ...prev };
+ shallow.title = getValues('title');
+ const shallowSlides = [...shallow.slides];
+ shallowSlides[currentPageIndex] = {
+ ...shallowSlides[currentPageIndex],
+ script: getValues('script'),
+ memo: getValues('memo'),
+ };
+ return {
+ ...shallow,
+ slides: shallowSlides,
+ };
+ });
+ };
+
+ return (
+
+
+
+ {pptInfo.imageFilePath === null ? (
+
+
+
+
+
+
+ ) : (
+
+
+
+
+
+ {currentPageIndex !== 0 && (
+
+ )}
+
+
+
+ )}
+
+
+ );
+};
+
+export default UploadPpt;
diff --git a/src/app/(afterlogin)/(navbar)/upload/[id]/_component/UploadScript.module.scss b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/UploadScript.module.scss
new file mode 100644
index 0000000..14622f7
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/UploadScript.module.scss
@@ -0,0 +1,88 @@
+@import '@/styles/mixins';
+
+.container {
+ margin-top: 37px;
+ display: flex;
+ width: 100%;
+ flex-direction: column;
+ gap: 15px;
+}
+
+.description {
+ display: flex;
+ align-items: center;
+ font-size: px-to-rem(22px);
+ font-weight: bolder;
+ gap: 10px;
+}
+
+.scriptSection {
+ position: relative;
+ width: 100%;
+ height: 467px;
+ border: 1px solid $gray-3;
+ border-radius: 16px;
+ padding-right: 14px;
+
+ ::-webkit-scrollbar {
+ width: 7px;
+ }
+
+ ::-webkit-scrollbar-track {
+ background: transparent;
+ }
+
+ ::-webkit-scrollbar-thumb {
+ background: $gray-3;
+ border-radius: 20px;
+ }
+
+ ::-webkit-scrollbar-thumb:hover {
+ background: $gray-3;
+ }
+
+ &.warning {
+ border: 2px solid $red-7;
+ }
+
+ &:focus-within {
+ outline: 1px solid $gray-8;
+ }
+}
+
+.scriptTextarea {
+ width: 100%;
+ height: 90%;
+ padding: 20px 4px 20px 20px; // 스크롤 영역 포함
+ border: none;
+ border-radius: 16px;
+ font-size: px-to-rem(20px);
+ line-height: 32px;
+ resize: none;
+
+ &::placeholder {
+ color: $gray-3;
+ }
+
+ &:focus {
+ outline: none;
+ }
+}
+
+.lengthCount {
+ position: absolute;
+ right: 20px;
+ bottom: 20px;
+ z-index: 10;
+
+ p {
+ margin: 0;
+ color: $gray-4;
+ }
+
+ &.lengthWarning {
+ p {
+ color: $red-7;
+ }
+ }
+}
diff --git a/src/app/(afterlogin)/(navbar)/upload/[id]/_component/UploadScript.tsx b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/UploadScript.tsx
new file mode 100644
index 0000000..f9e6079
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/UploadScript.tsx
@@ -0,0 +1,116 @@
+'use client';
+
+import { ChangeEventHandler, forwardRef, useEffect, useState } from 'react';
+
+import { ValidtaionType } from '@/types/service';
+
+import styles from './UploadScript.module.scss';
+
+import { FieldErrors, RegisterOptions, UseFormRegister, UseFormSetValue } from 'react-hook-form';
+
+import classNames from 'classnames/bind';
+import Required from './Required';
+import { MAX_LENGTH, VALIDATION_MESSAGE } from '@/config/const';
+
+interface UploadScriptProps {
+ script: string;
+ currentPageIndex: number;
+ register: UseFormRegister;
+ errors: FieldErrors;
+ lastDummyPageIndex: number;
+ setValue: UseFormSetValue;
+ errorForMovePage: {
+ memo: boolean;
+ script: {
+ minLength: boolean;
+ maxLength: boolean;
+ };
+ };
+}
+
+const cx = classNames.bind(styles);
+
+const UploadScript = forwardRef(
+ (
+ { script, currentPageIndex, register, errors, lastDummyPageIndex, setValue, errorForMovePage },
+ ref,
+ ) => {
+ const [currentLength, setCurrentLength] = useState(script.length);
+ const registerOptions: RegisterOptions =
+ currentPageIndex === lastDummyPageIndex
+ ? {}
+ : {
+ required: VALIDATION_MESSAGE.SCRIPT.REQUIRED,
+ maxLength: {
+ value: MAX_LENGTH.SCRIPT,
+ message: VALIDATION_MESSAGE.SCRIPT.MAX_LENGTH,
+ },
+ };
+
+ const onCurrentLengthChange: ChangeEventHandler = (e) => {
+ const value = e.target.value;
+ setValue('script', value, { shouldValidate: true }); // mode : onChange 대체
+ if (value.length > MAX_LENGTH.SCRIPT) {
+ setValue('script', value.slice(0, MAX_LENGTH.SCRIPT + 1), { shouldValidate: true });
+ }
+ setCurrentLength(value.length);
+ };
+
+ useEffect(() => {
+ (function reset() {
+ setCurrentLength(script.length);
+ })();
+ }, [currentPageIndex, script.length]);
+
+ return (
+
+
+
+
+ {/* 훅 폼 유효성 검사 */}
+ {errors.script && (
+
+ {errors.script.message as string}
+
+ )}
+
+ {/* 최초 페이지 생성 후, 페이지 이동 유효성 검사 - 최소 길이 */}
+ {!errors.script &&
+ currentLength === 0 &&
+ errorForMovePage.script.minLength &&
+ lastDummyPageIndex !== currentPageIndex && (
+
+ {VALIDATION_MESSAGE.SCRIPT.REQUIRED}
+
+ )}
+
+
MAX_LENGTH.SCRIPT && 'warning'])}>
+
+
MAX_LENGTH.SCRIPT && 'lengthWarning'])}
+ >
+
+ {currentLength > MAX_LENGTH.SCRIPT + 1 ? MAX_LENGTH.SCRIPT + 1 : currentLength}/
+ {MAX_LENGTH.SCRIPT}
+
+
+
+
+ );
+ },
+);
+
+UploadScript.displayName = 'UploadScript';
+
+export default UploadScript;
diff --git a/src/app/(afterlogin)/(navbar)/upload/[id]/_component/UploadTimer.module.scss b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/UploadTimer.module.scss
new file mode 100644
index 0000000..1e80f61
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/UploadTimer.module.scss
@@ -0,0 +1,59 @@
+@import '@/styles/mixins';
+
+.container {
+ display: flex;
+ width: 100%;
+ margin-top: 18px;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.description {
+ margin: 0;
+ font-size: px-to-rem(22px);
+ font-weight: bolder;
+}
+
+.label {
+ display: flex;
+ align-items: center;
+ font-size: px-to-rem(20px);
+ gap: 8px;
+}
+
+.inputWrapper {
+ display: flex;
+ justify-content: space-between;
+}
+
+.timerInput {
+ input::-webkit-outer-spin-button,
+ input::-webkit-inner-spin-button {
+ appearance: none;
+ margin: 0;
+ }
+
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ font-size: px-to-rem(20px);
+
+ input {
+ width: 38px;
+ height: 29px;
+ border: none;
+ color: black;
+ font-size: px-to-rem(20px);
+ text-align: center;
+ border-radius: 4px;
+ background-color: $gray-0;
+
+ &:focus {
+ color: black;
+ }
+
+ &::placeholder {
+ color: $gray-3;
+ }
+ }
+}
diff --git a/src/app/(afterlogin)/(navbar)/upload/[id]/_component/UploadTimer.tsx b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/UploadTimer.tsx
new file mode 100644
index 0000000..56c4846
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/UploadTimer.tsx
@@ -0,0 +1,165 @@
+'use client';
+
+import { ChangeEventHandler, Dispatch, SetStateAction, forwardRef } from 'react';
+
+import Input from '@/app/_components/_elements/Input';
+
+import { UploadDataType, ValidtaionType } from '@/types/service';
+
+import styles from './UploadTimer.module.scss';
+import InputFormSvgs from '../_svgs/InputFormSvgs';
+import { UseFormGetValues } from 'react-hook-form';
+
+interface UploadTimerProps {
+ timeLimit: UploadDataType['timeLimit'];
+ alertTime: UploadDataType['alertTime'];
+ setPresentationData: Dispatch>;
+ currentPageIndex: number;
+ getValues: UseFormGetValues;
+}
+
+const UploadTimer = forwardRef(
+ ({ timeLimit, alertTime, setPresentationData, currentPageIndex, getValues }, ref) => {
+ const onChange: ChangeEventHandler = (e) => {
+ let { name, value } = e.target;
+ let changeValue = Number(value);
+
+ setPresentationData((prev) => {
+ const shallow = { ...prev };
+ const timeLimitShallow = { ...shallow.timeLimit };
+ const alertTimeShallow = { ...shallow.alertTime };
+
+ if (name === 'timeLimit_hour') {
+ if (changeValue > 12) changeValue = 12;
+ timeLimitShallow['hours'] = changeValue;
+ }
+
+ if (name === 'timeLimit_minute') {
+ if (changeValue > 59) changeValue = 59;
+ timeLimitShallow['minutes'] = changeValue;
+ }
+
+ if (name === 'alertTime_hour') {
+ if (changeValue > 12) changeValue = 12;
+ alertTimeShallow['hours'] = changeValue;
+ }
+
+ if (name === 'alertTime_minute') {
+ if (changeValue > 59) changeValue = 59;
+ alertTimeShallow['minutes'] = changeValue;
+ }
+
+ shallow.title = getValues('title');
+ shallow.timeLimit = timeLimitShallow;
+ shallow.alertTime = alertTimeShallow;
+
+ const shallowSlides = [...shallow.slides];
+ shallowSlides[currentPageIndex] = {
+ ...shallowSlides[currentPageIndex],
+ script: getValues('script'),
+ memo: getValues('memo'),
+ };
+ return {
+ ...shallow,
+ slides: shallowSlides,
+ };
+ });
+ // setPresentationData((prev) => {
+ // let { name, value } = e.target;
+ // let changeValue = Number(value);
+
+ // const timeLimitShallow = { ...prev.timeLimit };
+ // const alertTimeShallow = { ...prev.alertTime };
+
+ // if (name === 'timeLimit_hour') {
+ // if (changeValue > 12) changeValue = 12;
+ // timeLimitShallow['hours'] = changeValue;
+ // }
+
+ // if (name === 'timeLimit_minute') {
+ // if (changeValue > 59) changeValue = 59;
+ // timeLimitShallow['minutes'] = changeValue;
+ // }
+
+ // if (name === 'alertTime_hour') {
+ // if (changeValue > 12) changeValue = 12;
+ // alertTimeShallow['hours'] = changeValue;
+ // }
+
+ // if (name === 'alertTime_minute') {
+ // if (changeValue > 59) changeValue = 59;
+ // alertTimeShallow['minutes'] = changeValue;
+ // }
+
+ // return {
+ // ...prev,
+ // timeLimit: timeLimitShallow,
+ // alertTime: alertTimeShallow,
+ // };
+ // });
+ };
+
+ return (
+
+ );
+ },
+);
+UploadTimer.displayName = 'UploadTimer';
+
+export default UploadTimer;
diff --git a/src/app/(afterlogin)/(navbar)/upload/[id]/_component/UploadTitle.module.scss b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/UploadTitle.module.scss
new file mode 100644
index 0000000..ee76a40
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/UploadTitle.module.scss
@@ -0,0 +1,69 @@
+@import '@/styles/mixins';
+
+.container {
+ margin-top: 11px;
+ display: flex;
+ width: 100%;
+ flex-direction: column;
+ gap: 18px;
+}
+
+.description {
+ display: flex;
+ align-items: center;
+ font-size: px-to-rem(22px);
+ font-weight: bolder;
+ gap: 10px;
+}
+
+.titleSection {
+ position: relative;
+ width: 100%;
+ height: 72px;
+ border-radius: 16px;
+ border: 1px solid $gray-3;
+
+ &.warning {
+ border: 2px solid $red-7;
+ }
+
+ &:focus-within {
+ outline: 1px solid $gray-8;
+ }
+}
+
+.titleInput {
+ width: 90%;
+ height: 100%;
+ padding: 20px;
+ border: none;
+ border-radius: 16px;
+ font-size: px-to-rem(20px);
+
+ &::placeholder {
+ color: $gray-3;
+ }
+
+ &:focus {
+ outline: none;
+ }
+}
+
+.lengthCount {
+ position: absolute;
+ top: 50%;
+ right: 10px;
+ z-index: 10;
+ transform: translate(0, -50%);
+
+ p {
+ margin: 0;
+ color: $gray-4;
+ }
+
+ &.lengthWarning {
+ p {
+ color: $red-7;
+ }
+ }
+}
diff --git a/src/app/(afterlogin)/(navbar)/upload/[id]/_component/UploadTitle.tsx b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/UploadTitle.tsx
new file mode 100644
index 0000000..77bb691
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/upload/[id]/_component/UploadTitle.tsx
@@ -0,0 +1,79 @@
+'use client';
+
+import { ChangeEventHandler, Dispatch, SetStateAction, forwardRef, useState } from 'react';
+
+import { ValidtaionType } from '@/types/service';
+
+import styles from './UploadTitle.module.scss';
+import { FieldErrors, RegisterOptions, UseFormRegister, UseFormSetValue } from 'react-hook-form';
+import Required from './Required';
+
+import classNames from 'classnames/bind';
+import { MAX_LENGTH, VALIDATION_MESSAGE } from '@/config/const';
+
+interface UploadTitleProps {
+ title: string;
+ register: UseFormRegister;
+ errors: FieldErrors;
+ setValue: UseFormSetValue;
+}
+
+const cx = classNames.bind(styles);
+
+const UploadTitle = forwardRef(
+ ({ title, register, errors, setValue }, ref) => {
+ const [currentLength, setCurrentLength] = useState(title.length);
+ const registerOptions: RegisterOptions = {
+ required: VALIDATION_MESSAGE.TITLE.REQUIRED,
+ maxLength: {
+ value: MAX_LENGTH.TITLE,
+ message: VALIDATION_MESSAGE.TITLE.MAX_LENGTH,
+ },
+ };
+
+ const onCurrentLengthChange: ChangeEventHandler = (e) => {
+ const value = e.target.value;
+ setValue('title', value, { shouldValidate: true });
+ if (value.length > MAX_LENGTH.TITLE) {
+ setValue('title', value.slice(0, MAX_LENGTH.TITLE + 1), { shouldValidate: true });
+ }
+ setCurrentLength(value.length);
+ };
+ return (
+
+
+
+
+ {/* 제출용 훅 폼 유효성 검사 */}
+ {errors.title && (
+
+ {errors.title.message as string}
+
+ )}
+
+
MAX_LENGTH.TITLE && 'warning'])}>
+
+
MAX_LENGTH.TITLE && 'lengthWarning'])}>
+
+ {currentLength > MAX_LENGTH.TITLE + 1 ? MAX_LENGTH.TITLE + 1 : currentLength}/
+ {MAX_LENGTH.TITLE}
+
+
+
+
+ );
+ },
+);
+UploadTitle.displayName = 'UploadTitle';
+export default UploadTitle;
diff --git a/src/app/(afterlogin)/(navbar)/upload/[id]/_hooks/presentation.tsx b/src/app/(afterlogin)/(navbar)/upload/[id]/_hooks/presentation.tsx
new file mode 100644
index 0000000..5ccca94
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/upload/[id]/_hooks/presentation.tsx
@@ -0,0 +1,79 @@
+import SaveToast from '@/app/_components/_modules/SaveToast';
+import { clientPptApi } from '@/services/client/upload';
+import { useToastStore } from '@/store/modal';
+import { UploadDataType } from '@/types/service';
+import { useMutation, useQuery, useQueryClient, useSuspenseQuery } from '@tanstack/react-query';
+import { useRouter } from 'next/navigation';
+
+// TODO: useSuspenseQuery 사용 버그 처리
+export const useGetPresentationData = (slug: number) => {
+ const { data: value, isLoading } = useQuery({
+ queryKey: ['upload', slug],
+ queryFn: async () => {
+ const res = await clientPptApi.getPresentationData(slug);
+
+ if (res.ok) return await res.json();
+ },
+ });
+
+ return { value, isLoading };
+};
+
+export const usePostPresentationData = (submitAction: 'save' | 'start') => {
+ const router = useRouter();
+
+ const { openToast } = useToastStore();
+
+ const openToastWithData = () =>
+ openToast({
+ content: ,
+ });
+ const postMutation = useMutation({
+ mutationKey: ['upload', 'new'],
+ mutationFn: async (result: UploadDataType) => {
+ return await clientPptApi.postPresentationUpload(result);
+ },
+ onSuccess: async (response) => {
+ const { presentationId } = await response.json();
+ if (submitAction === 'start') router.push(`/setting/${presentationId}`);
+ if (submitAction === 'save') {
+ router.push(`/upload/${presentationId}`);
+ openToastWithData();
+ }
+ },
+ onError: () => {
+ alert('저장하는 도중 문제가 발생했습니다.');
+ },
+ });
+
+ return postMutation;
+};
+
+export const usePatchPresentationData = (submitAction: 'save' | 'start', slug: number | 'new') => {
+ const router = useRouter();
+ const queryClient = useQueryClient();
+
+ const { openToast } = useToastStore();
+
+ const openToastWithData = () =>
+ openToast({
+ content: ,
+ });
+
+ const patchMutation = useMutation({
+ mutationKey: ['upload', slug],
+ mutationFn: async (result: UploadDataType) => {
+ return await clientPptApi.patchPresentationData(slug as number, result);
+ },
+ onSuccess: async (response) => {
+ const { presentationId } = await response.json();
+ if (submitAction === 'start') router.push(`/setting/${presentationId}`);
+ if (submitAction === 'save') openToastWithData();
+ },
+ onError: (error) => {
+ alert(error.message);
+ },
+ });
+
+ return patchMutation;
+};
diff --git a/src/app/(afterlogin)/(navbar)/upload/[id]/_svgs/InputFormSvgs.module.scss b/src/app/(afterlogin)/(navbar)/upload/[id]/_svgs/InputFormSvgs.module.scss
new file mode 100644
index 0000000..523078f
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/upload/[id]/_svgs/InputFormSvgs.module.scss
@@ -0,0 +1,43 @@
+@import '@/styles/mixins';
+
+.memoDescriptionHover {
+ display: none;
+}
+
+.timerDescriptionHover {
+ display: none;
+}
+
+.memoDescriptionContainer {
+ position: relative;
+
+ &:hover {
+ .memoDescriptionHover {
+ display: block;
+ position: absolute;
+ bottom: 15px;
+ left: 50%;
+ transform: translate(-50%, 0);
+ }
+ }
+}
+
+.timerDescriptionContainer {
+ position: relative;
+
+ &:hover {
+ .timerDescriptionHover {
+ display: block;
+ position: absolute;
+ bottom: 15px;
+ left: 50%;
+ transform: translate(-50%, 0);
+ }
+ }
+}
+
+.descriptionText {
+ font-size: px-to-rem(16px);
+ font-weight: 100;
+ text-anchor: middle;
+}
diff --git a/src/app/(afterlogin)/(navbar)/upload/[id]/_svgs/InputFormSvgs.tsx b/src/app/(afterlogin)/(navbar)/upload/[id]/_svgs/InputFormSvgs.tsx
new file mode 100644
index 0000000..8df2756
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/upload/[id]/_svgs/InputFormSvgs.tsx
@@ -0,0 +1,86 @@
+import { ReactChildrenProps } from '@/types/common';
+import styles from './InputFormSvgs.module.scss';
+
+const InputFormSvgs = ({ children }: ReactChildrenProps) => {
+ return <>{children}>;
+};
+
+const MemoDescriptionHover = () => {
+ return (
+
+
+
+ );
+};
+
+const TimerDescriptionHover = () => {
+ return (
+
+
+
+ );
+};
+
+const HoverLogo = () => {
+ return (
+
+ );
+};
+
+const MemoDescription = () => {
+ return (
+
+
+
+
+ );
+};
+
+const DeadlineDateDescription = () => {
+ return (
+
+
+
+
+ );
+};
+
+InputFormSvgs.MemoDescription = MemoDescription;
+InputFormSvgs.DeadlineDateDescription = DeadlineDateDescription;
+export default InputFormSvgs;
diff --git a/src/app/(afterlogin)/(navbar)/upload/[id]/_svgs/PptImgSvgs.tsx b/src/app/(afterlogin)/(navbar)/upload/[id]/_svgs/PptImgSvgs.tsx
new file mode 100644
index 0000000..6bfcf84
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/upload/[id]/_svgs/PptImgSvgs.tsx
@@ -0,0 +1,78 @@
+import { ReactChildrenProps } from '@/types/common';
+
+const PptImageSvgs = ({ children }: ReactChildrenProps) => {
+ return <>{children}>;
+};
+const GoLeft = () => {
+ return (
+
+ );
+};
+
+const GoRight = () => {
+ return (
+
+ );
+};
+
+const X = () => {
+ return (
+
+ );
+};
+
+const NewPpt = () => {
+ return (
+
+ );
+};
+
+const AddNewPpt = () => {
+ return (
+
+ );
+};
+
+PptImageSvgs.GoLeft = GoLeft;
+PptImageSvgs.GoRight = GoRight;
+PptImageSvgs.X = X;
+PptImageSvgs.NewPpt = NewPpt;
+PptImageSvgs.AddNewPpt = AddNewPpt;
+
+export default PptImageSvgs;
diff --git a/src/app/(afterlogin)/(navbar)/upload/[id]/_utils/date.ts b/src/app/(afterlogin)/(navbar)/upload/[id]/_utils/date.ts
new file mode 100644
index 0000000..44a28ec
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/upload/[id]/_utils/date.ts
@@ -0,0 +1,21 @@
+import { Value } from '@/types/service';
+
+export const formatDate = (date: Value) => {
+ if (typeof date === 'string') {
+ date = new Date(date);
+ return formatDatePiece(date);
+ }
+ if (date instanceof Date) return formatDatePiece(date);
+ // if (Array.isArray(date)) return date[0] ? formatDatePiece(date[0]) : '';
+ return '';
+};
+
+const formatDatePiece = (date: Date) => {
+ const month = date.getMonth() + 1;
+ const day = date.getDate();
+
+ const monthStr = month < 10 ? '0' + month : String(month);
+ const dayStr = day < 10 ? '0' + day : String(day);
+
+ return `${date.getFullYear()}.${monthStr}.${dayStr}`;
+};
diff --git a/src/app/(afterlogin)/(navbar)/upload/[id]/_utils/validation.ts b/src/app/(afterlogin)/(navbar)/upload/[id]/_utils/validation.ts
new file mode 100644
index 0000000..74f86b8
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/upload/[id]/_utils/validation.ts
@@ -0,0 +1,16 @@
+import { MAX_LENGTH } from '@/config/const';
+import { UploadDataType } from '@/types/service';
+
+export const checkValidtaion = (presentationData: UploadDataType, currentPageIndex: number) => {
+ const memoLength = presentationData.slides[currentPageIndex].memo?.length || 0;
+ const scriptLength = presentationData.slides[currentPageIndex].script?.length || 0;
+
+ const result = {
+ memo: memoLength > MAX_LENGTH.MEMO,
+ script: {
+ minLength: scriptLength === 0,
+ maxLength: scriptLength > MAX_LENGTH.SCRIPT,
+ },
+ };
+ return result;
+};
diff --git a/src/app/(afterlogin)/(navbar)/upload/[id]/page.module.scss b/src/app/(afterlogin)/(navbar)/upload/[id]/page.module.scss
new file mode 100644
index 0000000..068384a
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/upload/[id]/page.module.scss
@@ -0,0 +1,7 @@
+@import '@/styles/mixins';
+
+.container {
+ // @include flex-center;
+
+ // margin: 0 240px;
+}
diff --git a/src/app/(afterlogin)/(navbar)/upload/[id]/page.tsx b/src/app/(afterlogin)/(navbar)/upload/[id]/page.tsx
new file mode 100644
index 0000000..885a234
--- /dev/null
+++ b/src/app/(afterlogin)/(navbar)/upload/[id]/page.tsx
@@ -0,0 +1,21 @@
+import { Suspense } from 'react';
+import CreatePresentation from './_component/CreatePresentation';
+import EditPresentation from './_component/EditPresentation';
+import Spinner from '@/app/_components/_modules/Spinner';
+
+interface PageProps {
+ params: { id: string };
+}
+const page = ({ params }: PageProps) => {
+ const slug = params.id;
+
+ return (
+
+ }>
+ {slug === 'new' ? : }
+
+
+ );
+};
+
+export default page;
diff --git a/src/app/(afterlogin)/_components/NavMenu.tsx b/src/app/(afterlogin)/_components/NavMenu.tsx
new file mode 100644
index 0000000..1e072d6
--- /dev/null
+++ b/src/app/(afterlogin)/_components/NavMenu.tsx
@@ -0,0 +1,122 @@
+'use client';
+
+import { EventHandler, MouseEventHandler, useMemo, useState } from 'react';
+
+import styles from './Navbar.module.scss';
+import classNames from 'classnames';
+
+import { fetch_ClientAuth } from '@/services/client/fetchClient';
+import { ERROR_MESSAGE } from '@/config/const';
+import { usePathname, useRouter } from 'next/navigation';
+import useToggle from '@/app/_hooks/useToggle';
+import Confirm from '@/app/_components/_modules/_modal/Confirm';
+
+type ClickedList = 'presentationList' | 'report';
+
+const isClickedList = (name: string): name is ClickedList => {
+ return name === 'presentationList' || name === 'report';
+};
+
+const NavMenu = () => {
+ const pathName = usePathname();
+ const pageName = useMemo(() => pathName.split('/')[1], [pathName]);
+ const router = useRouter();
+
+ const confirmHome = useToggle();
+ const confirmFeedback = useToggle();
+
+ // 테스트용
+ const tmpReIssueTest = async () => {
+ const nextServerUrl = `${process.env.NEXT_PUBLIC_ROUTE_HANDLER}/api/get/auth/slient`;
+ const clientUrl = `/api/accounts/reissue`;
+ await fetch(clientUrl, {
+ method: 'GET',
+ cache: 'no-store',
+ credentials: 'include',
+ });
+ };
+ // 테스트용
+ const tmpMyInfoTest = async () => {
+ try {
+ const clientUrl = `/api/accounts/me`;
+ const res = await fetch_ClientAuth(clientUrl, {
+ method: 'GET',
+ cache: 'no-store',
+ credentials: 'include',
+ });
+ if (!res.ok) throw new Error(ERROR_MESSAGE.USER.ERROR);
+ } catch (e) {
+ if (e instanceof Error) {
+ console.log(e.message);
+ }
+ }
+ };
+
+ const onClick: MouseEventHandler = (e) => {
+ const { name } = e.currentTarget;
+
+ if (pageName !== 'home' && pageName !== 'feedback' && name === 'home') {
+ confirmHome.onOpen();
+ return;
+ }
+ if (pageName !== 'home' && pageName !== 'feedback' && name === 'feedback') {
+ confirmFeedback.onOpen();
+ return;
+ }
+
+ if (name === 'home') {
+ router.push('/home');
+ return;
+ }
+ if (name === 'feedback') {
+ router.push('/feedback/list');
+ return;
+ }
+ };
+
+ return (
+ <>
+
+
+
+ {
+ router.push('/home');
+ confirmHome.onClose();
+ }}
+ />
+ {
+ router.push('/feedback/list');
+ confirmFeedback.onClose();
+ }}
+ />
+ {/*
+ */}
+ >
+ );
+};
+
+export default NavMenu;
diff --git a/src/app/(afterlogin)/_components/Navbar.module.scss b/src/app/(afterlogin)/_components/Navbar.module.scss
new file mode 100644
index 0000000..6ece53e
--- /dev/null
+++ b/src/app/(afterlogin)/_components/Navbar.module.scss
@@ -0,0 +1,46 @@
+@import '@/styles/globals';
+@import '@/styles/mixins';
+
+.container {
+ display: flex;
+ justify-content: center;
+ width: 100%;
+ min-width: 1440px;
+ height: 68px;
+ background: $primary;
+}
+
+.content {
+ display: flex;
+ justify-content: space-between;
+ width: 1440px;
+}
+
+.left {
+ display: flex;
+ align-items: center;
+ gap: 30px;
+
+ button {
+ height: 100%;
+ color: $white;
+ font-size: 1.2rem;
+ font-weight: 500;
+ opacity: 0.5;
+
+ @include pure-button;
+
+ &.clicked {
+ color: $white;
+ font-weight: 900;
+ opacity: 1;
+
+ // border-bottom: 2px solid $primary;
+ }
+ }
+}
+
+.right {
+ display: flex;
+ align-items: center;
+}
diff --git a/src/app/(afterlogin)/_components/Navbar.tsx b/src/app/(afterlogin)/_components/Navbar.tsx
new file mode 100644
index 0000000..7e5d47e
--- /dev/null
+++ b/src/app/(afterlogin)/_components/Navbar.tsx
@@ -0,0 +1,35 @@
+import NavMenu from './NavMenu';
+import styles from './Navbar.module.scss';
+import LogoIcon from '@/app/_svgs/LogoIcon';
+import UserIcon from '../_svgs/UserIcon';
+
+import { serverUserApi } from '@/services/server/user';
+import { fetch_ServerAuth } from '@/services/server/fetchServer';
+
+const Navbar = async () => {
+ // const res = await fetch_ServerAuth(`${process.env.NEXT_PUBLIC_BASE_URL_DEV}/api/accounts/me`, {
+ // method: 'GET',
+ // headers: { Cookie: cookies().toString() },
+ // cache: 'no-store',
+ // });
+
+ const res = await serverUserApi.getUserInfo();
+ console.log(await res.json());
+
+ return (
+
+ );
+};
+
+export default Navbar;
diff --git a/src/app/(afterlogin)/_components/RQProvider.tsx b/src/app/(afterlogin)/_components/RQProvider.tsx
new file mode 100644
index 0000000..7ab000f
--- /dev/null
+++ b/src/app/(afterlogin)/_components/RQProvider.tsx
@@ -0,0 +1,64 @@
+'use client';
+
+import React, { ReactNode, useState } from 'react';
+import { QueryClientProvider, QueryClient } from '@tanstack/react-query';
+import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
+import { ReactChildrenProps } from '@/types/common';
+
+function makeQueryClient() {
+ return new QueryClient({
+ defaultOptions: {
+ queries: {
+ refetchOnWindowFocus: false,
+ retryOnMount: true,
+ refetchOnReconnect: false,
+ staleTime: 60 * 1000,
+ },
+ },
+ });
+}
+
+let browserQueryClient: QueryClient | undefined = undefined;
+
+function getQueryClient() {
+ if (typeof window === 'undefined') {
+ return makeQueryClient();
+ } else {
+ if (!browserQueryClient) browserQueryClient = makeQueryClient();
+ return browserQueryClient;
+ }
+}
+
+export default function Providers({ children }: ReactChildrenProps) {
+ const queryClient = getQueryClient();
+
+ return (
+
+ {children}
+
+ );
+}
+
+// function RQProvider({ children }: { children: ReactNode }) {
+// const [client] = useState(
+// new QueryClient({
+// defaultOptions: {
+// queries: {
+// refetchOnWindowFocus: false,
+// retryOnMount: true,
+// refetchOnReconnect: false,
+// retry: false,
+// },
+// },
+// }),
+// );
+
+// return (
+//
+// {children}
+//
+//
+// );
+// }
+
+// export default RQProvider;
diff --git a/src/app/(afterlogin)/_svgs/UserIcon.tsx b/src/app/(afterlogin)/_svgs/UserIcon.tsx
new file mode 100644
index 0000000..a5dbd5c
--- /dev/null
+++ b/src/app/(afterlogin)/_svgs/UserIcon.tsx
@@ -0,0 +1,20 @@
+const UserIcon = () => {
+ return (
+
+
+
+ );
+};
+
+export default UserIcon;
diff --git a/src/app/(afterlogin)/element-test/page.tsx b/src/app/(afterlogin)/element-test/page.tsx
new file mode 100644
index 0000000..2f1dc8e
--- /dev/null
+++ b/src/app/(afterlogin)/element-test/page.tsx
@@ -0,0 +1,176 @@
+'use client';
+
+import { useState } from 'react';
+import { TimePickerProps } from '@/types/element';
+import Checkbox from '@/app/_components/_elements/Checkbox';
+import Radio from '@/app/_components/_elements/Radio';
+import TimePicker from '@/app/_components/_elements/TimePicker';
+import Switch from '@/app/_components/_elements/Switch';
+import FlyoutMenu from '@/app/_components/_modules/FlyoutMenu';
+import Alert from '@/app/_components/_modules/_modal/Alert';
+import useToggle from '@/app/_hooks/useToggle';
+import Confirm from '@/app/_components/_modules/_modal/Confirm';
+import Modal from '@/app/_components/_modules/_modal/Modal';
+import SpeechBubble from '@/app/_components/_modules/SpeechBubble';
+
+export default function Page() {
+ const [isAvailable, setIsAvailable] = useState(false);
+ const [isUsedAlarm, setIsUsedAlarm] = useState(false);
+ const [time, setTime] = useState('0');
+ const [radioValue, setRadioValue] = useState(1);
+ const alert = useToggle();
+ const alert2 = useToggle();
+ const confirm = useToggle();
+ const modal = useToggle();
+ const bubble = useToggle();
+
+ const handleOnChange = () => {
+ setIsAvailable(!isAvailable);
+ };
+
+ const handleOnChangeAlarm = () => {
+ setIsUsedAlarm(!isUsedAlarm);
+ };
+
+ const handleOnChangeTime = (newTime: TimePickerProps['selectedValue']) => {
+ setTime(newTime);
+ };
+
+ const handleOnChangeRadio = (newValue: any) => {
+ setRadioValue(newValue);
+ };
+
+ const handleOnClickModal = () => {
+ alert.onOpen();
+ };
+
+ const handleAlertModal = () => {
+ console.log('alert...');
+ // modal.onClose();
+ alert2.onOpen();
+ };
+
+ return (
+ <>
+
+
+
+
+
+
+
+ time : {time}
+
+
+
+
+
+ selected : {radioValue}
+
+
+
Flyout menu test
+ {/*
+
+ click!
+
+
+ apple
+ banana
+ cat
+
+ */}
+
+
+
+
alert test
+
+
+
+
+
+
+ confirm test
+
+ {
+ console.log('okay okay ~');
+ confirm.onClose();
+ }}
+ />
+
+
+
+ custom modal test
+
+
+ 안녕?
+
+
+
+
+ speech bubble test
+
+
+
+ >
+ );
+}
diff --git a/src/app/(afterlogin)/error.tsx b/src/app/(afterlogin)/error.tsx
new file mode 100644
index 0000000..0711e8a
--- /dev/null
+++ b/src/app/(afterlogin)/error.tsx
@@ -0,0 +1,54 @@
+'use client'; // Error components must be Client Components
+
+import { usePathname, useRouter, useSearchParams } from 'next/navigation';
+import { useEffect } from 'react';
+import Spinner from '../_components/_modules/Spinner';
+
+// 서버 컴포넌트 토큰 재발급 이슈 전용 에러 컴포넌트
+export default function Error({
+ error,
+ reset,
+}: {
+ error: Error & { digest?: string };
+ reset: () => void;
+}) {
+ // const router = useRouter();
+ const currentPath = usePathname();
+ const searchParams = useSearchParams();
+ const fullUrl = `${currentPath}?${searchParams.toString()}`;
+
+ useEffect(() => {
+ const reIssue = async () => {
+ const clientUrl = `/api/accounts/reissue`;
+ await fetch(clientUrl, {
+ method: 'GET',
+ cache: 'no-store',
+ credentials: 'include',
+ });
+
+ // 미들웨어 기반 리다이렉션
+ window.location.href = fullUrl;
+ };
+ if (error.message === 'reIssue') reIssue();
+ }, [error]);
+
+ return (
+ <>
+ {error.message === 'reIssue' ? (
+
+ ) : (
+
+
Something went wrong!
+
+
+ )}
+ >
+ );
+}
diff --git a/src/app/(afterlogin)/layout.tsx b/src/app/(afterlogin)/layout.tsx
new file mode 100644
index 0000000..4e184f1
--- /dev/null
+++ b/src/app/(afterlogin)/layout.tsx
@@ -0,0 +1,25 @@
+import { Metadata } from 'next';
+
+import RQProvider from './_components/RQProvider';
+import { ReactChildrenProps } from '@/types/common';
+
+import Toast from '../_components/_modules/_modal-pre/Toast';
+import Providers from './_components/RQProvider';
+
+export const metadata: Metadata = {
+ title: '홈',
+ description: 'HOME',
+};
+
+export default function AfterLoginLayout({ children }: ReactChildrenProps) {
+ return (
+
+ {/*
*/}
+
+ {children}
+
+
+ {/* */}
+
+ );
+}
diff --git a/src/app/(afterlogin)/practice/[id]/page.module.scss b/src/app/(afterlogin)/practice/[id]/page.module.scss
new file mode 100644
index 0000000..973a80e
--- /dev/null
+++ b/src/app/(afterlogin)/practice/[id]/page.module.scss
@@ -0,0 +1,129 @@
+@import '@/styles/globals';
+@import '@/styles/mixins';
+
+.container {
+ display: flex;
+ justify-content: center;
+ position: relative;
+ width: 100%;
+ padding: 60px;
+}
+
+.contents {
+ width: 1320px;
+ height: 870px;
+}
+
+.presentation {
+ width: 900px;
+ height: 100%;
+ border: 1px solid $gray-1;
+ border-radius: 15px;
+
+ &__box {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ width: 100%;
+ height: 510px;
+ margin-bottom: 40px;
+ }
+}
+
+.helper {
+ display: flex;
+ position: relative;
+ width: 100%;
+ height: 240px;
+ flex-direction: column;
+
+ textarea {
+ padding: 20px;
+ }
+
+ &__subtitle {
+ font-size: $font-3;
+
+ &--next {
+ margin-left: 12px;
+ color: $gray-10;
+ }
+
+ &--memo {
+ display: block;
+ margin-top: 4px;
+ font-weight: 400;
+ }
+ }
+
+ .recording {
+ color: $gray-8;
+ }
+
+ .pausing {
+ color: $pink-4;
+ }
+
+ .memo-validation {
+ margin-top: 4px;
+ color: $gray-8;
+ text-align: end;
+
+ &--error {
+ color: $error;
+ }
+ }
+
+ &__recorder {
+ @include pure-button;
+
+ position: absolute;
+ top: 0;
+ right: 0;
+ width: 80px;
+ height: 35px;
+ background: $pink-4;
+ color: $white;
+ border-radius: 12px;
+
+ &:disabled {
+ background: $gray-0;
+ color: $gray-2;
+ }
+ }
+
+ &__item {
+ width: 100%;
+ height: 100%;
+ margin-top: 12px;
+ border: 1px solid $gray-1;
+ border-radius: 15px;
+ }
+
+ &__box {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ width: 370px;
+ height: 100%;
+ }
+}
+
+.script {
+ width: 100%;
+ height: 100%;
+
+ &__box {
+ width: 100%;
+ height: 320px;
+ padding: 20px;
+ border: 1px solid $gray-1;
+ border-radius: 15px;
+ }
+}
+
+.bubble {
+ position: absolute;
+ top: 75px;
+ left: 35%;
+}
diff --git a/src/app/(afterlogin)/practice/[id]/page.tsx b/src/app/(afterlogin)/practice/[id]/page.tsx
new file mode 100644
index 0000000..3128fb7
--- /dev/null
+++ b/src/app/(afterlogin)/practice/[id]/page.tsx
@@ -0,0 +1,326 @@
+'use client';
+
+import useToggle from '@/app/_hooks/useToggle';
+import styles from './page.module.scss';
+import classNames from 'classnames/bind';
+import { useEffect, useState } from 'react';
+import useRecorder from '../_hooks/useRecorder';
+import Alert from '@/app/_components/_modules/_modal/Alert';
+import SpeechBubble from '@/app/_components/_modules/SpeechBubble';
+import { useMutation, useQuery } from '@tanstack/react-query';
+import { PracticeDetail, SavePracticeParams } from '@/types/service';
+import { PracticeService } from '@/services/client/practice';
+import Image from 'next/image';
+import PracticeNav from '../_components/PracticeNav';
+import Confirm from '@/app/_components/_modules/_modal/Confirm';
+import NextSlideConfirm from '../_components/NextSlideConfirm';
+import { useRouter } from 'next/navigation';
+import { FileService } from '@/services/client/file';
+import { FieldValues, useForm } from 'react-hook-form';
+
+export default function Page({ params }: { params: { id: string } }) {
+ const id = Number(params.id);
+ const router = useRouter();
+
+ // #region state
+ const [slideSeq, setSlideSeq] = useState(0); // INFO: 슬라이드 순서
+ const [saveMemo, setSaveMemo] = useState('');
+
+ const alert = useToggle(); // INFO: 마이크 권한 체크
+ const confirm = useToggle(); // INFO: 연습 중단 확인
+ const modal = useToggle(); // INFO: 다음 슬라이드 이동 확인
+ const bubble = useToggle();
+ const recorder = useRecorder();
+ // #endregion
+
+ //
+ const cx = classNames.bind(styles);
+
+ // #region query
+ const { isLoading, data } = useQuery({
+ queryKey: ['practice', id],
+ queryFn: () => PracticeService.getPracticeDetail(Number(id)),
+ });
+
+ /** 발표 이름 */
+ const title = data?.title ?? '';
+
+ /** 발표 시간 */
+ const timeLimit = data?.timeLimit ?? { hours: 0, minutes: 0 };
+
+ /** 슬라이드 페이징 문자열 */
+ const slidePaging = `${slideSeq + 1}/${data?.slides.length ?? 0}`;
+
+ /** 마지막 슬라이드 여부 */
+ const isLastSlide = data?.slides.length === slideSeq + 1;
+
+ /** 모달 노출 여부
+ * TODO: 이렇게 해도 로직상 문제 없는지는 확인 필요
+ */
+ const isActiveModal = data?.activateNextSlideModal ?? true;
+
+ /** 슬라이드 아이디 */
+ const slideIdx = data?.slides[slideSeq].id ?? 0;
+ const curMemo = data?.slides[slideSeq].memo ?? '';
+ // #endregion
+
+ // #region mutation
+ const modalMutation = useMutation({
+ mutationKey: ['deactive-modal'],
+ mutationFn: () => PracticeService.patchDeactiveModal(),
+ onError: (error) => {
+ console.log(error.message);
+ },
+ });
+
+ const slideMutation = useMutation({
+ mutationKey: ['slide', slideIdx],
+ mutationFn: ({ id, data }: { id: number; data: SavePracticeParams }) =>
+ PracticeService.patchSlide(id, data),
+ onError: (error) => {
+ console.log(error.message);
+ },
+ });
+ // #endregion
+
+ useEffect(() => {
+ alert.onOpen();
+ recorder.processPermission();
+ }, []);
+
+ useEffect(() => {
+ recorder.getMedia();
+ recorder.startRecording();
+
+ reset({
+ content: curMemo,
+ });
+ }, [slideSeq]);
+
+ useEffect(() => {
+ savePractice();
+ }, [recorder.audioBlob]);
+
+ // #region event-handler
+ /** 마이크 권한 체크 얼럿 액션 핸들러 */
+ const handleAlertAction = () => {
+ recorder.startRecording();
+ alert.onClose();
+ bubble.onOpen();
+ };
+
+ /** '다음 페이지' 버튼 클릭 이벤트 */
+ const onClickNextPage = () => {
+ if (isLastSlide) {
+ router.push(`/feedback/${id}`);
+ }
+
+ if (isActiveModal) {
+ modal.onOpen();
+ } else {
+ goToNextSlide();
+ }
+ };
+
+ /** 다음 슬라이드 이동 컴펌 okay 핸들러 */
+ const handleConfirmOkay = (checked: boolean) => {
+ goToNextSlide();
+
+ if (checked) {
+ modalMutation.mutate();
+ // setIsActiveModal(false);
+ }
+ };
+
+ /** 녹음 일시정지 및 재개 핸들러 */
+ const handleRecordingPause = () => {
+ if (recorder.isRecording) recorder.pauseRecording();
+ else recorder.resumeRecording();
+ };
+
+ /** 닫기 버튼 클릭 이벤트 */
+ const onCloseClick = () => {
+ confirm.onOpen();
+ };
+ // #endregion
+
+ // #region function
+ /** 다음 슬라이드 이동 함수 */
+ const goToNextSlide = () => {
+ if (isLastSlide) return;
+
+ if (modal.isOpen) modal.onClose();
+
+ recorder.stopRecording();
+
+ setSaveMemo(content);
+ setSlideSeq((prev) => prev + 1);
+ };
+
+ /** 발표 목록 페이지 이동 함수 */
+ const goToPresentationsPage = () => {
+ router.push('/home');
+ };
+
+ /** 발표 연습 저장 함수 (슬라이드 별) */
+ const savePractice = async () => {
+ const audioBlob = recorder.audioBlob;
+
+ if (audioBlob?.size) {
+ const { id, path } = await FileService.fileUpload(
+ audioBlob as Blob,
+ `practice_${slideSeq}.mp3`,
+ );
+
+ const param = {
+ memo: saveMemo,
+ audioFileId: id,
+ };
+
+ slideMutation.mutate({
+ id: slideIdx,
+ data: param,
+ });
+ }
+ };
+ // #endregion
+
+ const {
+ register,
+ handleSubmit,
+ reset,
+ formState: { errors },
+ watch,
+ } = useForm({
+ mode: 'onChange',
+ defaultValues: {
+ content: data?.slides[slideSeq].memo ?? '',
+ },
+ });
+ const content = watch('content') || '';
+
+ const onSubmit = (data: FieldValues) => {
+ console.log('??????', data);
+ };
+
+ return (
+
+ );
+}
diff --git a/src/app/(afterlogin)/practice/_components/NextSlideConfirm.module.scss b/src/app/(afterlogin)/practice/_components/NextSlideConfirm.module.scss
new file mode 100644
index 0000000..aa687bb
--- /dev/null
+++ b/src/app/(afterlogin)/practice/_components/NextSlideConfirm.module.scss
@@ -0,0 +1,38 @@
+@import '@/styles/globals';
+@import '@/styles/mixins';
+
+.container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ border-radius: 16px;
+ background: $white;
+}
+
+.message {
+ margin-top: 8px;
+ font-size: $font-1;
+}
+
+.action {
+ @include pure-button;
+
+ width: 230px;
+ height: 55px;
+ margin: 24px 0;
+ border-radius: 12px;
+
+ &--okay {
+ @include button_theme_default;
+
+ font-size: $h2;
+ margin-right: 8px;
+ }
+
+ &--cancel {
+ @include button_theme_lined;
+
+ font-size: $h2;
+ }
+}
diff --git a/src/app/(afterlogin)/practice/_components/NextSlideConfirm.tsx b/src/app/(afterlogin)/practice/_components/NextSlideConfirm.tsx
new file mode 100644
index 0000000..fa01fc0
--- /dev/null
+++ b/src/app/(afterlogin)/practice/_components/NextSlideConfirm.tsx
@@ -0,0 +1,57 @@
+import Modal from '@/app/_components/_modules/_modal/Modal';
+import { ToggleType } from '@/app/_hooks/useToggle';
+import classNames from 'classnames/bind';
+import styles from './NextSlideConfirm.module.scss';
+import Checkbox from '@/app/_components/_elements/Checkbox';
+import { useState } from 'react';
+
+interface Props {
+ /** 컨텍스트 */
+ context: ToggleType;
+ /** 다음 페이지 이동 함수 */
+ handleOkay: (checked: boolean) => void;
+}
+
+const NextSlideConfirm = ({ context, handleOkay }: Props) => {
+ const [isChecked, setIsChecked] = useState(true);
+
+ const cx = classNames.bind(styles);
+
+ const onClickOkay = () => {
+ handleOkay(isChecked);
+ };
+
+ const onClickCancel = () => {
+ context.onClose();
+ };
+
+ const onChangeCheckbox = () => {
+ setIsChecked(!isChecked);
+ };
+
+ return (
+
+
+
다음 슬라이드로 이동하시겠어요?
+
+ 처음부터 끝까지 발표문을 연습할 수 있도록
+
+ 슬라이드는 앞으로만 이동할 수 있어요.
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default NextSlideConfirm;
diff --git a/src/app/(afterlogin)/practice/_components/PracticeNav.module.scss b/src/app/(afterlogin)/practice/_components/PracticeNav.module.scss
new file mode 100644
index 0000000..6820671
--- /dev/null
+++ b/src/app/(afterlogin)/practice/_components/PracticeNav.module.scss
@@ -0,0 +1,78 @@
+@import '@/styles/globals';
+@import '@/styles/mixins';
+
+.container {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ height: 68px;
+ min-width: 1320px;
+
+ &--record-on {
+ background: $primary;
+ }
+
+ &--record-off {
+ background: $pink-4;
+ }
+}
+
+.contents {
+ display: flex;
+ align-items: center;
+ color: $white;
+
+ &__box {
+ display: flex;
+ justify-content: space-between;
+ position: relative;
+ width: 1320px;
+ height: 100%;
+ }
+
+ &--left {
+ .title {
+ @include line(1);
+
+ width: 280px;
+ margin-left: 32px;
+ }
+ }
+
+ &--center {
+ position: absolute;
+ top: 12px;
+ right: 50%;
+
+ .recorder {
+ @include pure-button;
+ }
+
+ .division {
+ width: 10px;
+ height: 10px;
+ margin: 0 8px 0 18px;
+ background: $pink-4;
+ border-radius: 100%;
+ }
+ }
+
+ .action {
+ &--next {
+ @include pure-button;
+ @include button_theme_inverted;
+
+ width: 105px;
+ height: 36px;
+ border-radius: 12px;
+ font-size: $font-3;
+ }
+
+ &--close {
+ @include pure-button;
+
+ margin-left: 40px;
+ }
+ }
+}
diff --git a/src/app/(afterlogin)/practice/_components/PracticeNav.tsx b/src/app/(afterlogin)/practice/_components/PracticeNav.tsx
new file mode 100644
index 0000000..68a94ca
--- /dev/null
+++ b/src/app/(afterlogin)/practice/_components/PracticeNav.tsx
@@ -0,0 +1,81 @@
+import classNames from 'classnames/bind';
+
+import LogoIcon from '@/app/_svgs/LogoIcon';
+import RecordIcon from '../_svgs/RecordIcon';
+import CloseIcon from '../../../_svgs/CloseIcon';
+
+import styles from './PracticeNav.module.scss';
+import Timer from './Timer';
+
+interface Props {
+ /** 발표 제목 */
+ title: string;
+ /** 녹음 진행 여부 */
+ isRecording: boolean;
+ /** 마지막 페이지 여부 */
+ isLastSlide: boolean;
+ /** 발표 시간 */
+ practiceTime: {
+ hours: number;
+ minutes: number;
+ };
+ /** 다음 페이지 이동 함수 */
+ goToNext: () => void;
+ /** 녹음 핸들러 */
+ handleRecording: () => void;
+ /** 닫기 버튼 클릭 이벤트 */
+ onCloseClick: () => void;
+}
+
+const PracticeNav = ({
+ title,
+ isRecording,
+ isLastSlide,
+ practiceTime,
+ goToNext,
+ handleRecording,
+ onCloseClick,
+}: Props) => {
+ const cx = classNames.bind(styles);
+
+ return (
+
+ );
+};
+
+export default PracticeNav;
diff --git a/src/app/(afterlogin)/practice/_components/Recorder.tsx b/src/app/(afterlogin)/practice/_components/Recorder.tsx
new file mode 100644
index 0000000..9d11df1
--- /dev/null
+++ b/src/app/(afterlogin)/practice/_components/Recorder.tsx
@@ -0,0 +1,7 @@
+'use client';
+
+import React, { useState, useRef } from 'react';
+
+const Recorder = () => {};
+
+export default Recorder;
diff --git a/src/app/(afterlogin)/practice/_components/Timer.tsx b/src/app/(afterlogin)/practice/_components/Timer.tsx
new file mode 100644
index 0000000..000cc76
--- /dev/null
+++ b/src/app/(afterlogin)/practice/_components/Timer.tsx
@@ -0,0 +1,63 @@
+import { useEffect, useState } from 'react';
+
+interface Props {
+ /** 발표 시작 여부 */
+ isRecording: boolean;
+ /** 최대 시간 값 */
+ maxHours: number;
+ /** 최대 분 값 */
+ maxMinutes: number;
+}
+
+const Timer = ({ isRecording, maxHours, maxMinutes }: Props) => {
+ const [hours, setHours] = useState(0);
+ const [minutes, setMinutes] = useState(0);
+ const [seconds, setSeconds] = useState(0);
+
+ useEffect(() => {
+ setHours(maxHours);
+ setMinutes(maxMinutes);
+ }, [maxHours, maxMinutes]);
+
+ useEffect(() => {
+ if (isRecording) {
+ let timer = setInterval(() => {
+ // 시간, 분, 초가 모두 0이라면 타이머를 멈춤
+ if (hours === 0 && minutes === 0 && seconds === 0) {
+ clearInterval(timer);
+ }
+ // 초가 0이고 분과 시간이 0이 아니라면 시간과 분을 조절하고 초를 59로 설정
+ else if (seconds === 0 && minutes !== 0) {
+ setMinutes((prevMinutes) => prevMinutes - 1);
+ setSeconds(59);
+ if (minutes === 0 && hours !== 0) {
+ setHours((prevHours) => prevHours - 1);
+ setMinutes(59);
+ }
+ }
+ // 초가 0이 아니라면 초를 하나 줄임
+ else if (seconds !== 0) {
+ setSeconds((prevSeconds) => prevSeconds - 1);
+ }
+ }, 1000);
+
+ // 컴포넌트가 언마운트될 때 타이머 정리
+ return () => {
+ clearInterval(timer);
+ };
+ }
+ }, [isRecording, hours, minutes, seconds]); // hours, minutes, seconds 상태가 변경될 때마다 useEffect 호출
+
+ // 시간을 두 자리 수로 포맷하는 함수
+ const formatTime = (time: number) => {
+ return time < 10 ? `0${time}` : time;
+ };
+
+ return (
+
+ {formatTime(hours)}:{formatTime(minutes)}:{formatTime(seconds)}
+
+ );
+};
+
+export default Timer;
diff --git a/src/app/(afterlogin)/practice/_hooks/useRecorder.ts b/src/app/(afterlogin)/practice/_hooks/useRecorder.ts
new file mode 100644
index 0000000..aeb18ad
--- /dev/null
+++ b/src/app/(afterlogin)/practice/_hooks/useRecorder.ts
@@ -0,0 +1,165 @@
+import { useEffect, useRef, useState } from 'react';
+
+interface ReturnType {
+ /** 마이크 권한 여부 */
+ isPermitted: boolean;
+ /** 녹음 진행 여부 */
+ isRecording: boolean;
+ /** 녹음 정보 객체 */
+ audioBlob: Blob | null;
+ /** 마이크 권한 변경 이벤트 */
+ processPermission: () => void;
+ /** 미디어 스트림 가져오는 함수 */
+ getMedia: () => void;
+ /** 녹음 시작 함수 */
+ startRecording: () => void;
+ /** 녹음 일시정지 함수 */
+ pauseRecording: () => void;
+ /** 녹음 재개 함수 */
+ resumeRecording: () => void;
+ /** 녹음 종료 함수 */
+ stopRecording: () => void;
+ /** 녹음 파일 반환 함수 */
+ getAudioFile: () => Promise;
+}
+
+const useRecorder = (): ReturnType => {
+ const [isPermitted, setIsPermitted] = useState(false);
+ const [stream, setStream] = useState();
+ const [isRecording, setIsRecording] = useState(false);
+ const [audioBlob, setAudioBlob] = useState(null);
+ const mediaRecorderRef = useRef(null);
+ const audioChunks: Blob[] = [];
+
+ //
+
+ /** 마이크 권한 변경 감지 이벤트
+ *
+ * TODO: 좀 더 알아볼 것
+ * - 마이크 권한 허용 X > 허용 O으로 변경 > 허용 X로 변경
+ * - 이런 경우, 마지막에 허용을 묻는 안내창이 브라우저에 다시 등장하지 않음. 해결 방법이 있는지 확인 필요
+ */
+ const processPermission = async () => {
+ try {
+ const result = await navigator.permissions.query({ name: 'microphone' as PermissionName });
+
+ checkPermission(result.state);
+
+ result.onchange = () => {
+ checkPermission(result.state);
+ getMedia();
+ };
+ } catch (error) {
+ console.error(error);
+ }
+ };
+
+ /** 마이크 권한 체크 함수 */
+ const checkPermission = (state: PermissionState) => {
+ switch (state) {
+ case 'granted':
+ setIsPermitted(true);
+ break;
+ case 'denied':
+ setIsPermitted(false);
+ break;
+ default:
+ setIsPermitted(false);
+ }
+ };
+
+ /** 미디어 스트림 가져오는 함수 */
+ const getMedia = async () => {
+ try {
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
+
+ setStream(stream);
+ } catch (error) {
+ console.error('Error accessing microphone:', error);
+ }
+ };
+
+ /** 녹음 시작하는 함수 */
+ const startRecording = () => {
+ console.log('start ~');
+ if (!stream) return;
+
+ const mediaRecorder = new MediaRecorder(stream);
+
+ mediaRecorder.ondataavailable = (e) => {
+ if (e.data.size > 0) {
+ audioChunks.push(e.data);
+ }
+ };
+
+ mediaRecorder.onstop = () => {
+ const audioBlob = new Blob(audioChunks, { type: 'audio/mp3' });
+ console.log('on stop!...', audioBlob);
+ setAudioBlob(audioBlob);
+ };
+
+ mediaRecorderRef.current = mediaRecorder;
+ mediaRecorder.start();
+
+ setIsRecording(true);
+ };
+
+ /** 녹음 일시정지 함수 */
+ const pauseRecording = () => {
+ if (mediaRecorderRef.current && isRecording) {
+ console.log('pause ...');
+ mediaRecorderRef.current.pause();
+ setIsRecording(false);
+ }
+ };
+
+ /** 녹음 재개 함수 */
+ const resumeRecording = () => {
+ if (mediaRecorderRef.current && !isRecording) {
+ console.log('resume ...!');
+ mediaRecorderRef.current.resume();
+ setIsRecording(true);
+ }
+ };
+
+ /** 녹음 멈추는 함수 */
+ const stopRecording = async () => {
+ console.log('stop!');
+ if (mediaRecorderRef.current && isRecording) {
+ mediaRecorderRef.current.stop();
+ setIsRecording(false);
+ }
+ };
+
+ /** 녹음 파일 반환 함수 */
+ const getAudioFile = async () => {
+ return new Promise((resolve, reject) => {
+ if (!mediaRecorderRef.current) return reject('media recorder is not');
+
+ // onstop 이벤트 핸들러 등록
+ mediaRecorderRef.current.onstop = () => {
+ resolve(audioBlob); // Promise 해결
+ };
+
+ // onerror 이벤트 핸들러 등록 (필요에 따라)
+ mediaRecorderRef.current.onerror = (event) => {
+ reject(`event error !`); // Promise 거부
+ };
+ });
+ };
+
+ return {
+ isPermitted,
+ isRecording,
+ audioBlob,
+ processPermission,
+ getMedia,
+ startRecording,
+ pauseRecording,
+ resumeRecording,
+ stopRecording,
+ getAudioFile,
+ };
+};
+
+export default useRecorder;
diff --git a/src/app/(afterlogin)/practice/_svgs/RecordIcon.tsx b/src/app/(afterlogin)/practice/_svgs/RecordIcon.tsx
new file mode 100644
index 0000000..057acd3
--- /dev/null
+++ b/src/app/(afterlogin)/practice/_svgs/RecordIcon.tsx
@@ -0,0 +1,95 @@
+interface Props {
+ /** 녹음 진행 여부 */
+ isRecording: boolean;
+}
+
+const RecordIcon = ({ isRecording }: Props) => {
+ return isRecording ? RecordOnIcon() : RecordOffIcon();
+};
+
+const RecordOnIcon = () => {
+ return (
+
+
+
+ );
+};
+
+const RecordOffIcon = () => {
+ return (
+
+
+
+ );
+};
+
+export default RecordIcon;
diff --git a/src/app/(afterlogin)/practice/layout.tsx b/src/app/(afterlogin)/practice/layout.tsx
new file mode 100644
index 0000000..30da7e6
--- /dev/null
+++ b/src/app/(afterlogin)/practice/layout.tsx
@@ -0,0 +1,7 @@
+import { ReactChildrenProps } from '@/types/common';
+
+const Layout = ({ children }: ReactChildrenProps) => {
+ return {children}
;
+};
+
+export default Layout;
diff --git a/src/app/(afterlogin)/setting/[id]/_components/CancelButton.tsx b/src/app/(afterlogin)/setting/[id]/_components/CancelButton.tsx
new file mode 100644
index 0000000..c9d06e9
--- /dev/null
+++ b/src/app/(afterlogin)/setting/[id]/_components/CancelButton.tsx
@@ -0,0 +1,37 @@
+'use client';
+
+import Confirm from '@/app/_components/_modules/_modal/Confirm';
+import useToggle from '@/app/_hooks/useToggle';
+import { ReactChildrenProps } from '@/types/common';
+import { useRouter } from 'next/navigation';
+import styles from './SettingNav.module.scss';
+
+const CancelButton = ({ children }: ReactChildrenProps) => {
+ const router = useRouter();
+ const confirm = useToggle();
+
+ return (
+ <>
+
+ {
+ router.push('/home');
+ confirm.onClose();
+ }}
+ />
+ >
+ );
+};
+
+export default CancelButton;
diff --git a/src/app/(afterlogin)/setting/[id]/_components/Card.module.scss b/src/app/(afterlogin)/setting/[id]/_components/Card.module.scss
new file mode 100644
index 0000000..cb28a88
--- /dev/null
+++ b/src/app/(afterlogin)/setting/[id]/_components/Card.module.scss
@@ -0,0 +1,44 @@
+@import '@/styles/mixins';
+
+.container {
+ cursor: pointer;
+ display: flex;
+ align-items: flex-start;
+ width: 480px;
+ height: 401px;
+ padding: 20px;
+ border: 1px solid $gray-1;
+ flex-direction: column;
+ border-radius: 22px;
+ background-color: $white;
+
+ &.selected {
+ box-shadow: 3px 3px 3px rgb(0 0 0 / 20%);
+ border: 1px solid $pink-2;
+ }
+}
+
+.image {
+ width: 440px;
+ height: 247px;
+ border: 1px solid $gray-1;
+ border-radius: 16px;
+}
+
+.title {
+ margin: 0;
+ margin-top: 20px;
+ color: $purple-5;
+
+ &.selected {
+ color: $pink-4;
+ }
+}
+
+.content {
+ margin: 0;
+ font-size: calc($font-1 - 1px);
+ font-weight: 500;
+ padding-top: 10px;
+ letter-spacing: -1.5%;
+}
diff --git a/src/app/(afterlogin)/setting/[id]/_components/Card.tsx b/src/app/(afterlogin)/setting/[id]/_components/Card.tsx
new file mode 100644
index 0000000..8fc5957
--- /dev/null
+++ b/src/app/(afterlogin)/setting/[id]/_components/Card.tsx
@@ -0,0 +1,45 @@
+'use client';
+import classNames from 'classnames/bind';
+import styles from './Card.module.scss';
+import Image from 'next/image';
+
+interface CardProps {
+ title: string;
+ content: string[];
+ setDevice?: () => void;
+ setMode?: () => void;
+ selected: boolean;
+ imageSrc: string;
+}
+
+const cx = classNames.bind(styles);
+
+const Card = ({ title, content, setDevice, setMode, selected, imageSrc }: CardProps) => {
+ return (
+ {
+ if (setDevice) setDevice();
+ if (setMode) setMode();
+ }}
+ >
+
+
+
+
{title}
+ {content.map((sentence, index) => (
+
+ {sentence}
+
+ ))}
+
+ );
+};
+
+export default Card;
diff --git a/src/app/(afterlogin)/setting/[id]/_components/DragSection.scss b/src/app/(afterlogin)/setting/[id]/_components/DragSection.scss
new file mode 100644
index 0000000..fb67273
--- /dev/null
+++ b/src/app/(afterlogin)/setting/[id]/_components/DragSection.scss
@@ -0,0 +1,65 @@
+@import '@/styles/mixins';
+@import 'draft-js/dist/Draft.css';
+
+.container {
+ @include flex-center;
+
+ width: 620px;
+ max-height: 383px;
+ border: 1px solid $gray-1;
+ border-radius: 16px;
+ background-color: white;
+
+ ::-webkit-scrollbar {
+ width: 7px;
+ }
+
+ ::-webkit-scrollbar-track {
+ background: transparent;
+ }
+
+ ::-webkit-scrollbar-thumb {
+ background: $gray-3;
+ border-radius: 20px;
+ }
+
+ ::-webkit-scrollbar-thumb:hover {
+ background: $gray-3;
+ }
+}
+
+.contentSection {
+ width: 100%;
+ max-height: 100%;
+ padding: 20px;
+ overflow-y: scroll;
+ font-size: $font-1;
+ line-height: 180%;
+}
+
+.DraftEditor-root,
+.DraftEditor-editorContainer,
+.notranslate {
+ height: 383px;
+
+ div {
+ height: 383px;
+ }
+
+ span {
+ max-height: 383px;
+ }
+}
+
+.toolbar {
+ display: flex;
+ padding: 14px 18px;
+ background-color: $white;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.tukdd6b {
+ border: none !important;
+ border-radius: 8px !important;
+}
diff --git a/src/app/(afterlogin)/setting/[id]/_components/DragSection.tsx b/src/app/(afterlogin)/setting/[id]/_components/DragSection.tsx
new file mode 100644
index 0000000..d836639
--- /dev/null
+++ b/src/app/(afterlogin)/setting/[id]/_components/DragSection.tsx
@@ -0,0 +1,106 @@
+'use client';
+
+import { useEffect, useMemo, useRef, useState } from 'react';
+
+import Editor from '@draft-js-plugins/editor';
+import { EditorState, convertToRaw, convertFromRaw } from 'draft-js';
+import createInlineToolbarPlugin, { Separator } from '@draft-js-plugins/inline-toolbar';
+import { stateToHTML } from 'draft-js-export-html';
+import './DragSection.scss';
+
+import '@draft-js-plugins/inline-toolbar/lib/plugin.css';
+
+import HighlightButton from './HighlightButton';
+import RemoveButton from './RemoveButton';
+import { SlidesSettingType } from '@/types/service';
+
+interface DragSectionProps {
+ currentPage: number;
+ onChangeSlide: (
+ index: number,
+ memorizationSentences: {
+ offset: SlidesSettingType['slides'][0]['memorizationSentences'][0]['offset'];
+ length: SlidesSettingType['slides'][0]['memorizationSentences'][0]['length'];
+ }[],
+ ) => void;
+}
+
+// 인라인 스타일 적용 옵션
+const styleMap = {
+ PINK: {
+ backgroundColor: '#FF2E7B',
+ },
+};
+const DragSection = ({ currentPage, onChangeSlide }: DragSectionProps) => {
+ const isFirstRender = useRef(true); // 첫 렌더링을 추적하는 ref
+ const [editorState, setEditorState] = useState(() => EditorState.createEmpty());
+ const [plugins, InlineToolbar] = useMemo(() => {
+ const inlineToolbarPlugin = createInlineToolbarPlugin();
+ return [[inlineToolbarPlugin], inlineToolbarPlugin.InlineToolbar];
+ }, []);
+
+ useEffect(() => {
+ const raw = localStorage.getItem('draftData');
+
+ if (raw) {
+ const contentState = convertFromRaw(JSON.parse(raw));
+ const newEditorState = EditorState.createWithContent(contentState);
+ setEditorState(newEditorState);
+ isFirstRender.current = false;
+ }
+ }, [currentPage]);
+
+ const saveContent = () => {
+ // 인라인 스타일 적용된 데이터
+ const contentState = editorState.getCurrentContent();
+ const raw = convertToRaw(contentState);
+
+ // 인라인 스타일이 적용된 HTML 반환값 적용
+ const options = {
+ inlineStyles: {
+ PINK: { style: { color: '#FF2E7B' } },
+ },
+ };
+ const html = stateToHTML(contentState, options);
+ // console.log(html);
+ // console.log(raw);
+
+ const infoForValue = raw.blocks[0].inlineStyleRanges.map((i) => {
+ return { offset: i.offset, length: i.length };
+ });
+
+ onChangeSlide(currentPage, infoForValue);
+ localStorage.setItem('draftData', JSON.stringify(raw, null, 2));
+ };
+
+ const onEditorChange = (newEditorState: EditorState) => {
+ setEditorState(newEditorState);
+ if (!isFirstRender.current) saveContent(); // 최초 렌더링때는 동작x
+ };
+
+ return (
+
+
+ {/*
*/}
+
+
+
+ {(externalProps) => (
+
+
+
+
+ )}
+
+
+
+ );
+};
+
+export default DragSection;
diff --git a/src/app/(afterlogin)/setting/[id]/_components/HighlightButton.tsx b/src/app/(afterlogin)/setting/[id]/_components/HighlightButton.tsx
new file mode 100644
index 0000000..5a6681c
--- /dev/null
+++ b/src/app/(afterlogin)/setting/[id]/_components/HighlightButton.tsx
@@ -0,0 +1,45 @@
+'use client';
+
+import { Modifier, EditorState } from 'draft-js';
+
+import styles from './ToolbarButtons.module.scss';
+import PenIcon from '../_svgs/PenIcon';
+
+const HighlightButton = ({ getEditorState, setEditorState }: any) => {
+ const applyHighlight = (color: any) => {
+ const editorState = getEditorState();
+ const selectionState = editorState.getSelection();
+ const currentContent = editorState.getCurrentContent();
+
+ // 현재 선택된 범위의 스타일
+ const stylesAtSelection = currentContent
+ .getBlockForKey(selectionState.getStartKey())
+ .getInlineStyleAt(selectionState.getStartOffset());
+
+ // 새로운 스타일을 추가하면서 기존 스타일을 유지
+ let newContentState = stylesAtSelection.reduce((contentState: any, style: any) => {
+ return Modifier.removeInlineStyle(contentState, selectionState, style);
+ }, currentContent);
+
+ newContentState = Modifier.applyInlineStyle(newContentState, selectionState, color);
+
+ // 새로운 에디터 상태 설정
+ const newState = EditorState.push(editorState, newContentState, 'change-inline-style');
+ setEditorState(newState);
+ };
+
+ return (
+
+ );
+};
+
+export default HighlightButton;
diff --git a/src/app/(afterlogin)/setting/[id]/_components/RemoveButton.tsx b/src/app/(afterlogin)/setting/[id]/_components/RemoveButton.tsx
new file mode 100644
index 0000000..2a86266
--- /dev/null
+++ b/src/app/(afterlogin)/setting/[id]/_components/RemoveButton.tsx
@@ -0,0 +1,36 @@
+'use client';
+
+import { Modifier, EditorState } from 'draft-js';
+
+import styles from './ToolbarButtons.module.scss';
+import TrashcanIcon from '../_svgs/TrashcanIcon';
+
+const RemoveHighlightButton = ({ getEditorState, setEditorState }: any) => {
+ const removeHighlight = (color: any) => {
+ const editorState = getEditorState();
+ const selectionState = editorState.getSelection();
+ const currentContent = editorState.getCurrentContent();
+
+ // 선택된 범위에서 하이라이팅 스타일을 제거합니다.
+ const newContentState = Modifier.removeInlineStyle(currentContent, selectionState, color);
+
+ // 새로운 에디터 상태를 설정합니다.
+ const newState = EditorState.push(editorState, newContentState, 'change-inline-style');
+ setEditorState(newState);
+ };
+
+ return (
+
+ );
+};
+
+export default RemoveHighlightButton;
diff --git a/src/app/(afterlogin)/setting/[id]/_components/SelectCardSection.module.scss b/src/app/(afterlogin)/setting/[id]/_components/SelectCardSection.module.scss
new file mode 100644
index 0000000..de61107
--- /dev/null
+++ b/src/app/(afterlogin)/setting/[id]/_components/SelectCardSection.module.scss
@@ -0,0 +1,6 @@
+@import '@/styles/mixins';
+
+.container {
+ display: flex;
+ gap: 46px;
+}
diff --git a/src/app/(afterlogin)/setting/[id]/_components/SelectCardSection.tsx b/src/app/(afterlogin)/setting/[id]/_components/SelectCardSection.tsx
new file mode 100644
index 0000000..26cb932
--- /dev/null
+++ b/src/app/(afterlogin)/setting/[id]/_components/SelectCardSection.tsx
@@ -0,0 +1,100 @@
+'use client';
+
+import { Dispatch, SetStateAction } from 'react';
+import Card from './Card';
+import styles from './SelectCardSection.module.scss';
+
+import { SettingDataType, SlidesSettingType } from '@/types/service';
+interface SelectCardSectionProps {
+ settingInfo: SlidesSettingType;
+ currentStep: number;
+ onChangePracticeMode: (practiceMode: SlidesSettingType['practiceMode']) => void;
+
+ setSelectedDevice: Dispatch>;
+ selectedDevice: 'DESKTOP' | 'BOTH';
+}
+const SelectCardSection = ({
+ currentStep,
+ onChangePracticeMode,
+ settingInfo,
+ setSelectedDevice,
+ selectedDevice,
+}: SelectCardSectionProps) => {
+ const firstStepCardInfo = [
+ {
+ imageSrc: `${process.env.NEXT_PUBLIC_BASE_URL_CDN}/data/etc/defaults/show.png`,
+ title: '모든 문장 보기',
+ content: [
+ '발표문 암기를 못 했을 때 추천드려요!',
+ '이 모드는 발표 속도에 대한 피드백만 받을 수 있어요.',
+ ],
+ },
+ {
+ imageSrc: `${process.env.NEXT_PUBLIC_BASE_URL_CDN}/data/etc/defaults/hide.png`,
+ title: '외울 문장 가리기',
+ content: [
+ '대본 암기를 잘 했는지 확인할 때 추천드려요!',
+ '이 모드는 모든 피드백을 받으실 수 있어요.',
+ ],
+ },
+ ];
+
+ const thirdStepCardInfo = [
+ {
+ imageSrc: `${process.env.NEXT_PUBLIC_BASE_URL_CDN}/data/etc/defaults/desktop.png`,
+ title: '데스크탑',
+ content: ['PPT 슬라이드, 타이머, 그리고 발표문을', ' 테스크탑에서 보실 수 있어요.'],
+ },
+ {
+ imageSrc: `${process.env.NEXT_PUBLIC_BASE_URL_CDN}/data/etc/defaults/both.png`,
+ title: '데스트탑과 모바일',
+ content: [
+ '모바일에서는 타이머와 발표문, 그리고 리모콘 기능을 제공해요.',
+ '데스크탑에서는 PPT 슬라이드를 보실 수 있어요.',
+ ],
+ },
+ ];
+
+ return (
+
+ {currentStep === 0 && (
+ <>
+ onChangePracticeMode('SHOW')}
+ selected={settingInfo.practiceMode === 'SHOW'}
+ />
+ onChangePracticeMode('HIDE')}
+ selected={settingInfo.practiceMode === 'HIDE'}
+ />
+ >
+ )}
+ {currentStep === 2 && (
+ <>
+ setSelectedDevice('DESKTOP')}
+ selected={selectedDevice === 'DESKTOP'}
+ />
+ setSelectedDevice('BOTH')}
+ selected={selectedDevice === 'BOTH'}
+ />
+ >
+ )}
+
+ );
+};
+
+export default SelectCardSection;
diff --git a/src/app/(afterlogin)/setting/[id]/_components/SelectSentenceSection.module.scss b/src/app/(afterlogin)/setting/[id]/_components/SelectSentenceSection.module.scss
new file mode 100644
index 0000000..80963fc
--- /dev/null
+++ b/src/app/(afterlogin)/setting/[id]/_components/SelectSentenceSection.module.scss
@@ -0,0 +1,40 @@
+@import '@/styles/mixins';
+
+.container {
+ display: flex;
+ gap: 46px;
+ width: 100%;
+}
+
+.pptImageSection {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ gap: 46px;
+ width: 500px;
+ height: 383px;
+}
+
+.image {
+ width: 100%;
+ height: 281px;
+ border: 1px solid $gray-1;
+ background-color: $white;
+ border-radius: 16px;
+}
+
+.movePptButtons {
+ display: flex;
+ gap: 14px;
+
+ .moveButton {
+ width: 149px;
+ height: 56px;
+ border: 1px solid $gray-3;
+ border-radius: 12px;
+ color: $gray-6;
+ font-size: $font-2;
+ background-color: $white;
+ cursor: pointer;
+ }
+}
diff --git a/src/app/(afterlogin)/setting/[id]/_components/SelectSentenceSection.tsx b/src/app/(afterlogin)/setting/[id]/_components/SelectSentenceSection.tsx
new file mode 100644
index 0000000..c1c085a
--- /dev/null
+++ b/src/app/(afterlogin)/setting/[id]/_components/SelectSentenceSection.tsx
@@ -0,0 +1,87 @@
+'use client';
+import { SettingDataType, SlidesSettingType } from '@/types/service';
+import DragSection from './DragSection';
+import styles from './SelectSentenceSection.module.scss';
+import { useState } from 'react';
+import Image from 'next/image';
+
+interface SelectSentenceSectionProps {
+ totalInfo: SettingDataType;
+ settingInfo: SlidesSettingType;
+
+ onChangeSlide: (
+ index: number,
+ memorizationSentences: {
+ offset: SlidesSettingType['slides'][0]['memorizationSentences'][0]['offset'];
+ length: SlidesSettingType['slides'][0]['memorizationSentences'][0]['length'];
+ }[],
+ ) => void;
+}
+
+const SelectSentenceSection = ({
+ totalInfo,
+ onChangeSlide,
+ settingInfo,
+}: SelectSentenceSectionProps) => {
+ const [currentPage, setCurrentPage] = useState(0);
+
+ // draft 데이터 적용을 위한 하드코딩 데이터
+ const draftData = {
+ blocks: [
+ {
+ key: 'randomKey',
+ text: totalInfo.slides[currentPage].script,
+ type: 'unstyled',
+ depth: 0,
+ inlineStyleRanges: [
+ ...settingInfo.slides[currentPage].memorizationSentences.map((i) => {
+ return {
+ ...i, // offest, length
+ style: 'PINK', // 인라인 스타일 정보 추가
+ };
+ }),
+ ],
+ entityRanges: [],
+ data: {},
+ },
+ ],
+ entityMap: {},
+ };
+
+ localStorage.setItem('draftData', JSON.stringify(draftData, null, 2));
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default SelectSentenceSection;
diff --git a/src/app/(afterlogin)/setting/[id]/_components/SettingNav.module.scss b/src/app/(afterlogin)/setting/[id]/_components/SettingNav.module.scss
new file mode 100644
index 0000000..3fee549
--- /dev/null
+++ b/src/app/(afterlogin)/setting/[id]/_components/SettingNav.module.scss
@@ -0,0 +1,50 @@
+@import '@/styles/globals';
+@import '@/styles/mixins';
+
+.container {
+ display: flex;
+ justify-content: center;
+ width: 100%;
+ min-width: 1440px;
+ height: 68px;
+ background: $primary;
+}
+
+.content {
+ display: flex;
+ justify-content: space-between;
+ width: 1440px;
+}
+
+.left {
+ display: flex;
+ align-items: center;
+ gap: 30px;
+
+ button {
+ height: 100%;
+ color: $white;
+ font-size: 1.2rem;
+ font-weight: 500;
+ opacity: 0.5;
+
+ @include pure-button;
+
+ &.clicked {
+ color: $white;
+ font-weight: 900;
+ opacity: 1;
+
+ // border-bottom: 2px solid $primary;
+ }
+ }
+}
+
+.right {
+ display: flex;
+ align-items: center;
+}
+
+.cancelButton {
+ @include pure-button;
+}
diff --git a/src/app/(afterlogin)/setting/[id]/_components/SettingNav.tsx b/src/app/(afterlogin)/setting/[id]/_components/SettingNav.tsx
new file mode 100644
index 0000000..df7d246
--- /dev/null
+++ b/src/app/(afterlogin)/setting/[id]/_components/SettingNav.tsx
@@ -0,0 +1,39 @@
+import LogoIcon from '@/app/_svgs/LogoIcon';
+import styles from './SettingNav.module.scss';
+import CloseIcon from '@/app/_svgs/CloseIcon';
+import classNames from 'classnames';
+import CancelButton from './CancelButton';
+import { UploadDataType } from '@/types/service';
+import { serverPptApi } from '@/services/server/upload';
+
+interface Props {
+ id: number;
+}
+
+const SettingNav = async ({ id }: Props) => {
+ const data = await serverPptApi.getPresentationData(id);
+ const pptResult = (await data.json()) as UploadDataType;
+ return (
+
+ );
+};
+
+export default SettingNav;
diff --git a/src/app/(afterlogin)/setting/[id]/_components/SettingProcess.module.scss b/src/app/(afterlogin)/setting/[id]/_components/SettingProcess.module.scss
new file mode 100644
index 0000000..ddf32b6
--- /dev/null
+++ b/src/app/(afterlogin)/setting/[id]/_components/SettingProcess.module.scss
@@ -0,0 +1,15 @@
+@import '@/styles/mixins';
+
+.container {
+ display: flex;
+ align-items: center;
+ width: 1006px;
+ padding-top: 70px;
+ padding-bottom: 154px;
+ flex-direction: column;
+
+ .confirmButton {
+ @include button_size_web;
+ @include button_theme_default;
+ }
+}
diff --git a/src/app/(afterlogin)/setting/[id]/_components/SettingProcess.tsx b/src/app/(afterlogin)/setting/[id]/_components/SettingProcess.tsx
new file mode 100644
index 0000000..0b0ab0c
--- /dev/null
+++ b/src/app/(afterlogin)/setting/[id]/_components/SettingProcess.tsx
@@ -0,0 +1,80 @@
+'use client';
+
+import { useState } from 'react';
+import styles from './SettingProcess.module.scss';
+import StepsBar from './StepsBar';
+import StepsDescription from './StepsDescription';
+import StepsContent from './StepsContent';
+import { SlidesSettingType } from '@/types/service';
+import {
+ initialValue,
+ useGetPrefetchSettingData,
+ usePatchSettingInfo,
+} from '../_hooks/settingInfo';
+import { usePathname } from 'next/navigation';
+import { useSettingInfo } from '../_hooks/draft';
+
+type ProcessStepType = 0 | 1 | 2;
+
+const SettingProcess = () => {
+ const pathName = usePathname();
+ const slug = Number(pathName.split('/').pop());
+
+ const [selectedDevice, setSelectedDevice] = useState<'DESKTOP' | 'BOTH'>('DESKTOP');
+ const { data: totalInfo } = useGetPrefetchSettingData(slug);
+ const defaultSettingValues: SlidesSettingType = totalInfo
+ ? {
+ practiceMode: totalInfo.practiceMode,
+ slides: totalInfo.slides.map((slide) => {
+ return {
+ id: slide.id,
+ memorizationSentences: slide.memorizationSentences.map((sentence) => {
+ return { offset: sentence.offset, length: sentence.length };
+ }),
+ };
+ }),
+ }
+ : initialValue;
+
+ const { settingInfo, onChangePracticeMode, onChangeSlide, onReset } =
+ useSettingInfo(defaultSettingValues);
+
+ const patchMutation = usePatchSettingInfo(settingInfo, totalInfo!.presentationId, selectedDevice);
+
+ const [currentStep, setCurrentStep] = useState(0);
+
+ const onNextStep = () => {
+ if (currentStep === 2) {
+ patchMutation.mutate();
+ }
+ if (currentStep === 0 && settingInfo.practiceMode === 'SHOW') {
+ setCurrentStep(2);
+ } else if (currentStep !== 2) setCurrentStep((prev) => (prev + 1) as ProcessStepType);
+ };
+
+ return (
+
+ {totalInfo && settingInfo && (
+ <>
+
+
+
+
+
+ >
+ )}
+
+ );
+};
+
+export default SettingProcess;
diff --git a/src/app/(afterlogin)/setting/[id]/_components/StepNumbers.module.scss b/src/app/(afterlogin)/setting/[id]/_components/StepNumbers.module.scss
new file mode 100644
index 0000000..bb44ae6
--- /dev/null
+++ b/src/app/(afterlogin)/setting/[id]/_components/StepNumbers.module.scss
@@ -0,0 +1,45 @@
+@import '@/styles/mixins';
+
+.container {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.number {
+ @include flex-center;
+
+ width: 25px;
+ height: 25px;
+ border-radius: 60px;
+ background-color: $gray-2;
+ color: $white;
+
+ &.selected {
+ background-color: $gray-6;
+ }
+}
+
+.label {
+ @include flex-center;
+
+ color: $gray-2;
+
+ p {
+ margin: 0;
+ }
+
+ &.selected {
+ color: $gray-6;
+ }
+}
+
+.line {
+ width: 97px;
+ height: 2px;
+ background-color: $gray-2;
+
+ &.selected {
+ background-color: $gray-6;
+ }
+}
diff --git a/src/app/(afterlogin)/setting/[id]/_components/StepNumbers.tsx b/src/app/(afterlogin)/setting/[id]/_components/StepNumbers.tsx
new file mode 100644
index 0000000..17a2425
--- /dev/null
+++ b/src/app/(afterlogin)/setting/[id]/_components/StepNumbers.tsx
@@ -0,0 +1,22 @@
+import styles from './StepNumbers.module.scss';
+import classNames from 'classnames/bind';
+
+interface StepNumbersProps {
+ number: number;
+ label: string;
+ currentStep: number; // 배열 인덱스
+}
+
+const cx = classNames.bind(styles);
+
+const StepNumbers = ({ number, label, currentStep }: StepNumbersProps) => {
+ return (
+
+ {number !== 1 &&
= number && 'selected'])} />}
+
= number && 'selected'])}>{number}
+
= number && 'selected'])}>{label}
+
+ );
+};
+
+export default StepNumbers;
diff --git a/src/app/(afterlogin)/setting/[id]/_components/StepsBar.module.scss b/src/app/(afterlogin)/setting/[id]/_components/StepsBar.module.scss
new file mode 100644
index 0000000..47b0f4f
--- /dev/null
+++ b/src/app/(afterlogin)/setting/[id]/_components/StepsBar.module.scss
@@ -0,0 +1,6 @@
+@import '@/styles/mixins';
+
+.container {
+ display: flex;
+ gap: 14px;
+}
diff --git a/src/app/(afterlogin)/setting/[id]/_components/StepsBar.tsx b/src/app/(afterlogin)/setting/[id]/_components/StepsBar.tsx
new file mode 100644
index 0000000..11dae2e
--- /dev/null
+++ b/src/app/(afterlogin)/setting/[id]/_components/StepsBar.tsx
@@ -0,0 +1,26 @@
+'use client';
+
+import StepNumbers from './StepNumbers';
+import styles from './StepsBar.module.scss';
+
+interface StepsBarProps {
+ currentStep: number;
+}
+const StepsBar = ({ currentStep }: StepsBarProps) => {
+ const steps = [
+ { number: 1, label: '연습 모드 설정' },
+ { number: 2, label: '외울 문장 설정' },
+ { number: 3, label: '발표 연습 시작' },
+ ];
+ return (
+
+ {steps.map((i) => {
+ return (
+
+ );
+ })}
+
+ );
+};
+
+export default StepsBar;
diff --git a/src/app/(afterlogin)/setting/[id]/_components/StepsContent.module.scss b/src/app/(afterlogin)/setting/[id]/_components/StepsContent.module.scss
new file mode 100644
index 0000000..eb4b02e
--- /dev/null
+++ b/src/app/(afterlogin)/setting/[id]/_components/StepsContent.module.scss
@@ -0,0 +1,6 @@
+@import '@/styles/mixins';
+
+.container {
+ margin-top: 46px;
+ margin-bottom: 46px;
+}
diff --git a/src/app/(afterlogin)/setting/[id]/_components/StepsContent.tsx b/src/app/(afterlogin)/setting/[id]/_components/StepsContent.tsx
new file mode 100644
index 0000000..c7fba60
--- /dev/null
+++ b/src/app/(afterlogin)/setting/[id]/_components/StepsContent.tsx
@@ -0,0 +1,55 @@
+'use client';
+
+import { Dispatch, SetStateAction } from 'react';
+
+import SelectCardSection from './SelectCardSection';
+import styles from './StepsContent.module.scss';
+import SelectSentenceSection from './SelectSentenceSection';
+import { SettingDataType, SlidesSettingType } from '@/types/service';
+
+interface StepsContentProps {
+ totalInfo: SettingDataType;
+ settingInfo: SlidesSettingType;
+ currentStep: number;
+ onChangePracticeMode: (practiceMode: SlidesSettingType['practiceMode']) => void;
+ onChangeSlide: (
+ index: number,
+ memorizationSentences: {
+ offset: SlidesSettingType['slides'][0]['memorizationSentences'][0]['offset'];
+ length: SlidesSettingType['slides'][0]['memorizationSentences'][0]['length'];
+ }[],
+ ) => void;
+ setSelectedDevice: Dispatch
>;
+ selectedDevice: 'DESKTOP' | 'BOTH';
+}
+const StepsContent = ({
+ totalInfo,
+ currentStep,
+ onChangePracticeMode,
+ onChangeSlide,
+ settingInfo,
+ setSelectedDevice,
+ selectedDevice,
+}: StepsContentProps) => {
+ return (
+
+ {currentStep === 1 ? (
+
+ ) : (
+
+ )}
+
+ );
+};
+
+export default StepsContent;
diff --git a/src/app/(afterlogin)/setting/[id]/_components/StepsDescription.module.scss b/src/app/(afterlogin)/setting/[id]/_components/StepsDescription.module.scss
new file mode 100644
index 0000000..4d387fa
--- /dev/null
+++ b/src/app/(afterlogin)/setting/[id]/_components/StepsDescription.module.scss
@@ -0,0 +1,18 @@
+@import '@/styles/mixins';
+
+.container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ margin-top: 130px;
+
+ h1,
+ h4 {
+ margin: 0;
+ }
+
+ h4 {
+ margin: 12px;
+ font-weight: 500;
+ }
+}
diff --git a/src/app/(afterlogin)/setting/[id]/_components/StepsDescription.tsx b/src/app/(afterlogin)/setting/[id]/_components/StepsDescription.tsx
new file mode 100644
index 0000000..88b261a
--- /dev/null
+++ b/src/app/(afterlogin)/setting/[id]/_components/StepsDescription.tsx
@@ -0,0 +1,38 @@
+'use client';
+
+import styles from './StepsDescription.module.scss';
+
+interface StepsDescriptionProps {
+ currentStep: number;
+}
+const StepsDescription = ({ currentStep }: StepsDescriptionProps) => {
+ const first = (
+ <>
+ 어떤 모드로 연습을 하시겠어요?
+ 발표문 암기 정도에 따라 선택 해 주세요
+ >
+ );
+ const second = (
+ <>
+ 외울 문장을 드래그해서 강조해 주세요.
+ 발표 연습을 할 때, 강조한 문장은 보이지 않아요.
+ >
+ );
+
+ const third = (
+ <>
+ 사용할 디바이스를 선택 해 주세요.
+ 리허설처럼 연습하려면 `데스크탑과 모바일`을 추천드려요.
+ >
+ );
+
+ return (
+
+ {currentStep === 0 && first}
+ {currentStep === 1 && second}
+ {currentStep === 2 && third}
+
+ );
+};
+
+export default StepsDescription;
diff --git a/src/app/(afterlogin)/setting/[id]/_components/ToolbarButtons.module.scss b/src/app/(afterlogin)/setting/[id]/_components/ToolbarButtons.module.scss
new file mode 100644
index 0000000..a146b8e
--- /dev/null
+++ b/src/app/(afterlogin)/setting/[id]/_components/ToolbarButtons.module.scss
@@ -0,0 +1,12 @@
+@import '@/styles/mixins';
+
+.toolbarButton {
+ @include pure-button;
+
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ width: 103px;
+ height: 24px;
+ background-color: $white;
+}
diff --git a/src/app/(afterlogin)/setting/[id]/_hooks/draft.ts b/src/app/(afterlogin)/setting/[id]/_hooks/draft.ts
new file mode 100644
index 0000000..c886697
--- /dev/null
+++ b/src/app/(afterlogin)/setting/[id]/_hooks/draft.ts
@@ -0,0 +1,53 @@
+import { SlidesSettingType } from '@/types/service';
+import { useState } from 'react';
+
+// export const useSettingInfo = (settingValue: SlidesSettingType | undefined) => {
+// const [draft, setDraft] = useState>({});
+
+// const value = { ...settingValue, ...draft };
+
+// const onChange = (key: K, value: SlidesSettingType[K]) => {
+// setDraft({ ...draft, [key]: value });
+// };
+
+// return { value, onChange };
+// };
+
+export const useSettingInfo = (settingValue: SlidesSettingType) => {
+ const [draft, setDraft] = useState>({});
+
+ const settingInfo = {
+ ...settingValue,
+ ...draft,
+ slides:
+ draft.slides ??
+ settingValue.slides.map((slide) => ({
+ ...slide,
+ memorizationSentences:
+ draft.slides?.find((d) => d.id === slide.id)?.memorizationSentences ??
+ slide.memorizationSentences,
+ })),
+ };
+ const onChangePracticeMode = (practiceMode: 'SHOW' | 'HIDE') => {
+ setDraft({ ...draft, practiceMode });
+ };
+
+ const onChangeSlide = (
+ index: number,
+ memorizationSentences: { offset: number; length: number }[],
+ ) => {
+ const updatedSlides = [...(draft.slides ?? settingValue!.slides)];
+ updatedSlides[index] = {
+ ...updatedSlides[index],
+ memorizationSentences,
+ };
+
+ setDraft({
+ ...draft,
+ slides: updatedSlides,
+ });
+ };
+ const onReset = () => setDraft({});
+
+ return { settingInfo, onChangePracticeMode, onChangeSlide, onReset };
+};
diff --git a/src/app/(afterlogin)/setting/[id]/_hooks/settingInfo.ts b/src/app/(afterlogin)/setting/[id]/_hooks/settingInfo.ts
new file mode 100644
index 0000000..9aee24a
--- /dev/null
+++ b/src/app/(afterlogin)/setting/[id]/_hooks/settingInfo.ts
@@ -0,0 +1,77 @@
+import { clientSettingApi } from '@/services/client/setting';
+import { SettingDataType, SlidesSettingType } from '@/types/service';
+import { useMutation, useQuery } from '@tanstack/react-query';
+import { useRouter } from 'next/navigation';
+
+export const initialValue: SettingDataType = {
+ presentationId: 0,
+ title: '',
+ timeLimit: {
+ hours: null,
+ minutes: null,
+ },
+ alertTime: {
+ hours: null,
+ minutes: null,
+ },
+ practiceMode: 'SHOW',
+ activateNextSlideModal: false,
+ slides: [
+ {
+ id: 0,
+ imageFilePath: '',
+ script: '',
+ memo: '',
+ memorizationSentences: [
+ {
+ offset: 0,
+ end: 0,
+ length: 0,
+ text: '',
+ },
+ ],
+ },
+ ],
+ createdAt: new Date(),
+ modifiedAt: new Date(),
+};
+
+export const useGetPrefetchSettingData = (slug: number) => {
+ const response = useQuery({
+ queryKey: ['setting', slug],
+ queryFn: async () => {
+ try {
+ const response = await clientSettingApi.getPresentationSettingData(slug);
+ const jsonData = await response.json();
+ return jsonData as SettingDataType;
+ } catch (e) {
+ if (e instanceof Error) alert(e.message);
+ return initialValue;
+ }
+ },
+ });
+ return response;
+};
+
+export const usePatchSettingInfo = (
+ settingInfo: SlidesSettingType,
+ presentationId: number,
+ device: 'DESKTOP' | 'BOTH',
+) => {
+ const router = useRouter();
+ const response = useMutation({
+ mutationKey: ['setting', presentationId],
+ mutationFn: async () => {
+ return await clientSettingApi.patchSettingInfo(settingInfo, presentationId);
+ },
+ onSuccess: async (result) => {
+ router.push(
+ `/practice/${presentationId}?mode=${device}&practiceMode=${settingInfo.practiceMode}`,
+ );
+ },
+ onError: (error) => {
+ alert(error.message);
+ },
+ });
+ return response;
+};
diff --git a/src/app/(afterlogin)/setting/[id]/_svgs/PenIcon.tsx b/src/app/(afterlogin)/setting/[id]/_svgs/PenIcon.tsx
new file mode 100644
index 0000000..008dcfd
--- /dev/null
+++ b/src/app/(afterlogin)/setting/[id]/_svgs/PenIcon.tsx
@@ -0,0 +1,24 @@
+const PenIcon = () => {
+ return (
+
+
+
+ );
+};
+
+export default PenIcon;
diff --git a/src/app/(afterlogin)/setting/[id]/_svgs/TrashcanIcon.tsx b/src/app/(afterlogin)/setting/[id]/_svgs/TrashcanIcon.tsx
new file mode 100644
index 0000000..75e0db6
--- /dev/null
+++ b/src/app/(afterlogin)/setting/[id]/_svgs/TrashcanIcon.tsx
@@ -0,0 +1,33 @@
+const TrashcanIcon = () => {
+ return (
+
+
+
+ );
+};
+
+export default TrashcanIcon;
diff --git a/src/app/(afterlogin)/setting/[id]/layout.module.scss b/src/app/(afterlogin)/setting/[id]/layout.module.scss
new file mode 100644
index 0000000..3810df5
--- /dev/null
+++ b/src/app/(afterlogin)/setting/[id]/layout.module.scss
@@ -0,0 +1,5 @@
+.container {
+ min-width: 1440px;
+ background-attachment: fixed;
+ background-image: url('https://media.discordapp.net/attachments/1180406919098269696/1207958844555141161/Rectangle_20805.jpg?ex=65e18a57&is=65cf1557&hm=2a51b1de1f55361a8e833149009308721f17e9bea0e474ce40b09bb725a52ded&=&format=webp&width=1440&height=945');
+}
diff --git a/src/app/(afterlogin)/setting/[id]/layout.tsx b/src/app/(afterlogin)/setting/[id]/layout.tsx
new file mode 100644
index 0000000..f169f59
--- /dev/null
+++ b/src/app/(afterlogin)/setting/[id]/layout.tsx
@@ -0,0 +1,10 @@
+import Navbar from '@/app/(afterlogin)/_components/Navbar';
+import styles from './layout.module.scss';
+import { ReactChildrenProps } from '@/types/common';
+import SettingNav from './_components/SettingNav';
+
+const Layout = ({ children }: ReactChildrenProps) => {
+ return {children}
;
+};
+
+export default Layout;
diff --git a/src/app/(afterlogin)/setting/[id]/page.module.scss b/src/app/(afterlogin)/setting/[id]/page.module.scss
new file mode 100644
index 0000000..9787323
--- /dev/null
+++ b/src/app/(afterlogin)/setting/[id]/page.module.scss
@@ -0,0 +1,6 @@
+@import '@/styles/mixins';
+
+.container {
+ display: flex;
+ justify-content: center;
+}
diff --git a/src/app/(afterlogin)/setting/[id]/page.tsx b/src/app/(afterlogin)/setting/[id]/page.tsx
new file mode 100644
index 0000000..39c458f
--- /dev/null
+++ b/src/app/(afterlogin)/setting/[id]/page.tsx
@@ -0,0 +1,35 @@
+import SettingNav from './_components/SettingNav';
+import SettingProcess from './_components/SettingProcess';
+import styles from './page.module.scss';
+import { serverSettingApi } from '@/services/server/setting';
+import { HydrationBoundary, QueryClient, dehydrate } from '@tanstack/react-query';
+
+interface PageProps {
+ params: { id: string };
+}
+const page = async ({ params }: PageProps) => {
+ const slug = Number(params.id);
+
+ const queryClient = new QueryClient();
+ await queryClient.fetchQuery({
+ queryKey: ['setting', slug],
+ queryFn: async () => {
+ const response = await serverSettingApi.getPresentationSettingData(slug);
+ return await response.json();
+ },
+ });
+ const dehydratedState = dehydrate(queryClient);
+
+ return (
+
+ );
+};
+
+export default page;
diff --git a/src/app/(afterlogin)/socket-test/page.tsx b/src/app/(afterlogin)/socket-test/page.tsx
new file mode 100644
index 0000000..74987de
--- /dev/null
+++ b/src/app/(afterlogin)/socket-test/page.tsx
@@ -0,0 +1,96 @@
+'use client';
+
+import Stomp from '@stomp/stompjs';
+import { Client } from '@stomp/stompjs';
+import { useQuery } from '@tanstack/react-query';
+import { useEffect, useState } from 'react';
+import { SessionId } from '@/types/service';
+import { UserService } from '@/services/client/user';
+
+const Page = () => {
+ const [stompClient, setStompClient] = useState(null);
+
+ // #region query
+ const { isLoading, data } = useQuery({
+ queryKey: ['sessionId'],
+ queryFn: () => UserService.getSessionId(),
+ });
+
+ const sessionId = data?.sessionId;
+ // #endregion
+
+ useEffect(() => {
+ const initialize = () => {
+ // stomp client 객체 만들기
+ const stomp = new Client({
+ brokerURL: process.env.NEXT_PUBLIC_BASE_URL_SOCKET,
+ debug: (str: string) => {
+ console.log(str);
+ },
+ reconnectDelay: 5000, //자동 재 연결
+ heartbeatIncoming: 4000,
+ heartbeatOutgoing: 4000,
+ });
+
+ setStompClient(stomp);
+
+ // stomp client 활성화
+ stomp.activate();
+
+ // stomp client 연결 됐을 때 동작
+ stomp.onConnect = () => {
+ console.log('WebSocket 연결이 열렸습니다.');
+
+ stomp.subscribe(`/sub/practice/${sessionId}`, (frame) => {
+ try {
+ const message = JSON.parse(frame.body);
+
+ console.log(message);
+ } catch (error) {
+ console.error('오류가 발생했습니다:', error);
+ }
+ });
+ };
+ };
+
+ if (sessionId) {
+ initialize();
+ }
+
+ return () => {
+ if (stompClient && stompClient.connected) {
+ stompClient.deactivate();
+ }
+ };
+ }, [sessionId]);
+
+ const sendMessage = () => {
+ if (stompClient && stompClient.connected) {
+ const destination = `/pub/practice/${sessionId}`;
+
+ stompClient.publish({
+ destination,
+ body: JSON.stringify({
+ sessionId,
+ message: 'PING',
+ }),
+ });
+ }
+ };
+
+ const closeSocket = () => {
+ if (stompClient && stompClient.connected) {
+ stompClient.deactivate();
+ }
+ console.log('close...');
+ };
+
+ return (
+
+
+
+
+ );
+};
+
+export default Page;
diff --git a/src/app/(beforelogin)/accounts/login/process/_components/GetToken.tsx b/src/app/(beforelogin)/accounts/login/process/_components/GetToken.tsx
new file mode 100644
index 0000000..0a3fb87
--- /dev/null
+++ b/src/app/(beforelogin)/accounts/login/process/_components/GetToken.tsx
@@ -0,0 +1,69 @@
+'use client';
+
+import { useSearchParams } from 'next/navigation';
+import { useEffect } from 'react';
+import { useRouter } from 'next/navigation';
+import Spinner from '@/app/_components/_modules/Spinner';
+
+import { useUserInfoStore } from '@/store/user';
+import { fetch_ClientAuth } from '@/services/client/fetchClient';
+import { ERROR_MESSAGE } from '@/config/const';
+
+const GetToken = () => {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+ const codeQuery = searchParams.get('code');
+ const { setUserInfo } = useUserInfoStore();
+
+ useEffect(() => {
+ const getLogin = async () => {
+ // const nextServerUrl = `${process.env.NEXT_PUBLIC_ROUTE_HANDLER}/api/get/auth/kakao?code=${codeQuery}`;
+ const clientUrl = `/api/accounts/login/process?code=${codeQuery}&provider=kakao`;
+ try {
+ const loginResponse = await fetch(`${clientUrl}`, {
+ method: 'GET',
+ cache: 'no-store',
+ credentials: 'include',
+ });
+ if (loginResponse.ok) {
+ const clientUrl = `/api/accounts/me`;
+ const userInfoResponse = await fetch_ClientAuth(clientUrl, {
+ method: 'GET',
+ cache: 'no-store',
+ credentials: 'include',
+ });
+
+ if (userInfoResponse.ok) {
+ const userInfo = await userInfoResponse.json();
+ setUserInfo({ ...userInfo });
+ router.push('/login');
+ } else {
+ throw new Error(ERROR_MESSAGE.USER.ERROR);
+ }
+ } else if (loginResponse.status === 401) {
+ const errorResponse = await loginResponse.json();
+ const errorMessage = errorResponse.message || ERROR_MESSAGE.AUTH.EXIST;
+ throw new Error(errorMessage);
+ } else {
+ throw new Error(ERROR_MESSAGE.AUTH.ERROR);
+ }
+ } catch (e) {
+ if (e instanceof Error) {
+ alert(e.message);
+ router.back(); // 위의 과정 중에서 하나라도 문제가 있다면 뒤로가기
+ }
+ }
+ };
+ if (codeQuery) {
+ getLogin();
+ }
+ }, [router, codeQuery]);
+
+ return (
+ <>
+
+ >
+ );
+};
+
+export default GetToken;
diff --git a/src/app/(beforelogin)/accounts/login/process/page.tsx b/src/app/(beforelogin)/accounts/login/process/page.tsx
new file mode 100644
index 0000000..330b1bf
--- /dev/null
+++ b/src/app/(beforelogin)/accounts/login/process/page.tsx
@@ -0,0 +1,11 @@
+import GetToken from './_components/GetToken';
+
+const page = async () => {
+ return (
+
+
+
+ );
+};
+
+export default page;
diff --git a/src/app/(beforelogin)/layout.tsx b/src/app/(beforelogin)/layout.tsx
new file mode 100644
index 0000000..9be71c4
--- /dev/null
+++ b/src/app/(beforelogin)/layout.tsx
@@ -0,0 +1,5 @@
+import { ReactChildrenProps } from '@/types/common';
+
+export default function BeforeLoginLayout({ children }: ReactChildrenProps) {
+ return {children}
;
+}
diff --git a/src/app/(beforelogin)/login/_components/SocialLoginButtons.module.scss b/src/app/(beforelogin)/login/_components/SocialLoginButtons.module.scss
new file mode 100644
index 0000000..c736078
--- /dev/null
+++ b/src/app/(beforelogin)/login/_components/SocialLoginButtons.module.scss
@@ -0,0 +1,35 @@
+@import '@/styles/mixins';
+
+.buttons {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ width: 460px;
+ height: 56px;
+ border: 1px solid $gray-1;
+ border-radius: 32px;
+ cursor: pointer;
+ font-size: $font-2;
+
+ svg {
+ width: 28px;
+ margin-left: 20px;
+ }
+
+ div:nth-of-type(1) {
+ width: 28px;
+ margin-right: 20px;
+ }
+}
+
+.google {
+ background-color: $white;
+}
+
+.kakao {
+ background-color: $yellow;
+}
+
+.naver {
+ background-color: $green-2;
+}
diff --git a/src/app/(beforelogin)/login/_components/SocialLoginButtons.tsx b/src/app/(beforelogin)/login/_components/SocialLoginButtons.tsx
new file mode 100644
index 0000000..2f116e2
--- /dev/null
+++ b/src/app/(beforelogin)/login/_components/SocialLoginButtons.tsx
@@ -0,0 +1,43 @@
+'use client';
+
+import classNames from 'classnames/bind';
+import styles from './SocialLoginButtons.module.scss';
+
+import { SOCIAL_ACCESS_URL } from '@/config/path';
+import GoogleIcon from '../_svgs/GoogleIcon';
+import NaverIcon from '../_svgs/NaverIcon';
+import KaKaoIcon from '../_svgs/KaKaoIcon';
+
+const cx = classNames.bind(styles);
+
+const SocialLoginButtons = () => {
+ const onClick = (socialType: 'google' | 'kakao' | 'naver') => {
+ let url;
+ if (socialType === 'kakao') url = SOCIAL_ACCESS_URL.KAKAO;
+ // if (socialType === 'naver') url = SOCIAL_ACCESS_URL.NAVER;
+ // if (socialType === 'google') url = SOCIAL_ACCESS_URL.GOOGLE;
+
+ window.location.href = `${url}`;
+ };
+ return (
+ <>
+
+
+
+ >
+ );
+};
+
+export default SocialLoginButtons;
diff --git a/src/app/(beforelogin)/login/_svgs/GoogleIcon.tsx b/src/app/(beforelogin)/login/_svgs/GoogleIcon.tsx
new file mode 100644
index 0000000..ad2dd56
--- /dev/null
+++ b/src/app/(beforelogin)/login/_svgs/GoogleIcon.tsx
@@ -0,0 +1,27 @@
+const GoogleIcon = () => {
+ return (
+
+
+
+ );
+};
+
+export default GoogleIcon;
diff --git a/src/app/(beforelogin)/login/_svgs/KaKaoIcon.tsx b/src/app/(beforelogin)/login/_svgs/KaKaoIcon.tsx
new file mode 100644
index 0000000..0ac0a9a
--- /dev/null
+++ b/src/app/(beforelogin)/login/_svgs/KaKaoIcon.tsx
@@ -0,0 +1,90 @@
+const KaKaoIcon = () => {
+ return (
+
+
+
+
+
+ );
+};
+
+export default KaKaoIcon;
diff --git a/src/app/(beforelogin)/login/_svgs/NaverIcon.tsx b/src/app/(beforelogin)/login/_svgs/NaverIcon.tsx
new file mode 100644
index 0000000..b0cd13f
--- /dev/null
+++ b/src/app/(beforelogin)/login/_svgs/NaverIcon.tsx
@@ -0,0 +1,31 @@
+const NaverIcon = () => {
+ return (
+
+
+
+ );
+};
+
+export default NaverIcon;
diff --git a/src/app/(beforelogin)/login/page.module.scss b/src/app/(beforelogin)/login/page.module.scss
new file mode 100644
index 0000000..1be6176
--- /dev/null
+++ b/src/app/(beforelogin)/login/page.module.scss
@@ -0,0 +1,45 @@
+@import '@/styles/mixins';
+
+.container {
+ @include flex-center;
+
+ width: 100dvw;
+ height: 100dvh;
+ background-color: $purple-6;
+}
+
+.content {
+ display: flex;
+ align-items: flex-start;
+ width: 704px;
+ height: 720px;
+ padding: 137px 122px 148px;
+ background-color: $white;
+ border-radius: 16px;
+ flex-direction: column;
+}
+
+.description {
+ margin-top: 20px;
+
+ p {
+ margin: 0;
+ }
+
+ p:nth-of-type(1) {
+ font-size: px-to-rem(24px);
+ font-weight: bolder;
+ }
+
+ p:nth-of-type(2) {
+ margin-top: 15px;
+ font-size: px-to-rem(18px);
+ }
+}
+
+.socialButtons {
+ display: flex;
+ flex-direction: column;
+ gap: 26px;
+ margin-top: 75px;
+}
diff --git a/src/app/(beforelogin)/login/page.tsx b/src/app/(beforelogin)/login/page.tsx
new file mode 100644
index 0000000..dceae20
--- /dev/null
+++ b/src/app/(beforelogin)/login/page.tsx
@@ -0,0 +1,52 @@
+import SocialLoginButtons from './_components/SocialLoginButtons';
+
+import styles from './page.module.scss';
+
+const page = () => {
+ return (
+
+
+
+
+
완벽한 발표를 위한 최고의 선택
+
프리젠에서 발표 연습을 해보세요.
+
+
+
+
+
+
+ );
+};
+
+export default page;
diff --git a/src/app/(beforelogin)/page.module.scss b/src/app/(beforelogin)/page.module.scss
new file mode 100644
index 0000000..d5ab148
--- /dev/null
+++ b/src/app/(beforelogin)/page.module.scss
@@ -0,0 +1,92 @@
+@import '@/styles/mixins';
+
+.container {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 99px;
+ width: 100dvw;
+ height: 100dvh;
+}
+
+.leftSectionWrapper {
+ display: flex;
+ justify-content: flex-end;
+ flex-grow: 1;
+}
+
+.rightSectionWrapper {
+ display: flex;
+ justify-content: flex-start;
+ flex-grow: 1;
+}
+
+.leftSection {
+ display: flex;
+ flex-direction: column;
+ width: 621px;
+ height: 383px;
+}
+
+.rightSection {
+ width: 837px;
+ height: 760px;
+}
+
+.smallDescription {
+ display: flex;
+ align-items: center;
+ width: 355px;
+
+ &::before {
+ content: '';
+ flex: 1;
+ border-bottom: 2px solid $gray-1;
+ }
+
+ & > p {
+ padding: 0 0 0 30px;
+ }
+}
+
+.mainTitle {
+ display: flex;
+ font-size: px-to-rem(49px);
+ font-weight: bolder;
+ flex-direction: column;
+ letter-spacing: -1%;
+
+ p {
+ margin: 0;
+ }
+
+ p:nth-of-type(1) {
+ color: $black;
+ }
+
+ p:nth-of-type(2) {
+ color: $pink-8;
+ }
+}
+
+.subTitle {
+ margin-top: 20px;
+ display: flex;
+ color: $gray-5;
+ font-size: px-to-rem($h2);
+ flex-direction: column;
+ letter-spacing: -1.5%;
+
+ p {
+ margin: 0;
+ }
+}
+
+.startButton {
+ @include button_size_web;
+ @include button_theme_default;
+
+ font-size: px-to-rem($h2);
+ letter-spacing: -1.5%;
+ margin-top: 58px;
+}
diff --git a/src/app/(beforelogin)/page.tsx b/src/app/(beforelogin)/page.tsx
new file mode 100644
index 0000000..40dfdc1
--- /dev/null
+++ b/src/app/(beforelogin)/page.tsx
@@ -0,0 +1,38 @@
+import Link from 'next/link';
+import styles from './page.module.scss';
+import Image from 'next/image';
+
+export default function Page() {
+ return (
+
+
+
+
+
Make perfect presentation
+
+
+
Ai와 함께하는 발표 연습,
+
프리젠으로 효과적으로 전하세요
+
+
+
성공적인 발표는 완벽한 연습에서 비롯됩니다.
+
효율적으로 발표 연습을 도와드릴게요!
+
+
+ 발표 시작하기
+
+
+
+
+
+ );
+}
diff --git a/src/app/_components/_elements/Button.tsx b/src/app/_components/_elements/Button.tsx
new file mode 100644
index 0000000..921641c
--- /dev/null
+++ b/src/app/_components/_elements/Button.tsx
@@ -0,0 +1,11 @@
+import { ButtonProps } from '@/types/element';
+
+const Button = ({ _className, _content, ...rest }: ButtonProps) => {
+ return (
+
+ );
+};
+
+export default Button;
diff --git a/src/app/_components/_elements/Checkbox.module.scss b/src/app/_components/_elements/Checkbox.module.scss
new file mode 100644
index 0000000..cad1b4e
--- /dev/null
+++ b/src/app/_components/_elements/Checkbox.module.scss
@@ -0,0 +1,14 @@
+@import '@/styles/globals';
+
+.input-box {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ input {
+ width: 24px;
+ height: 24px;
+ margin-right: 8px;
+ accent-color: $primary;
+ }
+}
diff --git a/src/app/_components/_elements/Checkbox.tsx b/src/app/_components/_elements/Checkbox.tsx
new file mode 100644
index 0000000..e06d368
--- /dev/null
+++ b/src/app/_components/_elements/Checkbox.tsx
@@ -0,0 +1,16 @@
+import { CheckboxProps } from '@/types/element';
+import styles from './Checkbox.module.scss';
+import classNames from 'classnames/bind';
+
+const Checkbox = ({ _label, ...rest }: CheckboxProps) => {
+ const cx = classNames.bind(styles);
+
+ return (
+
+
+
+
+ );
+};
+
+export default Checkbox;
diff --git a/src/app/_components/_elements/Input.tsx b/src/app/_components/_elements/Input.tsx
new file mode 100644
index 0000000..8f64aaa
--- /dev/null
+++ b/src/app/_components/_elements/Input.tsx
@@ -0,0 +1,10 @@
+import React, { forwardRef } from 'react';
+import { InputProps } from '@/types/element';
+
+const Input = forwardRef(({ _className, ...rest }, ref) => {
+ return ;
+});
+
+Input.displayName = 'Input';
+
+export default Input;
diff --git a/src/app/_components/_elements/List.tsx b/src/app/_components/_elements/List.tsx
new file mode 100644
index 0000000..d3658ab
--- /dev/null
+++ b/src/app/_components/_elements/List.tsx
@@ -0,0 +1,17 @@
+import { ListProps } from '@/types/element';
+
+const List = ({ listArr }: { listArr: ListProps }) => {
+ return (
+ <>
+ {listArr.map((item, index) => {
+ return (
+
+ {item._content}
+
+ );
+ })}
+ >
+ );
+};
+
+export default List;
diff --git a/src/app/_components/_elements/Radio.tsx b/src/app/_components/_elements/Radio.tsx
new file mode 100644
index 0000000..51ffd73
--- /dev/null
+++ b/src/app/_components/_elements/Radio.tsx
@@ -0,0 +1,27 @@
+import { ChangeEvent, useEffect, useState } from 'react';
+import { RadioProps } from '@/types/element';
+import _isEqual from 'lodash-es/isEqual';
+
+const Radio = ({ _label, _selectedValue, _onChangeSelected, ...rest }: RadioProps) => {
+ const [isChecked, setIsChecked] = useState(false);
+
+ useEffect(() => {
+ const checked = _isEqual(rest.value, _selectedValue) || true === rest.checked;
+ setIsChecked(checked);
+ }, [rest.value, rest.checked, _selectedValue]);
+
+ const handleOnChange = (e: ChangeEvent) => {
+ if (!rest.value) return;
+
+ _onChangeSelected(rest.value);
+ };
+
+ return (
+
+ );
+};
+
+export default Radio;
diff --git a/src/app/_components/_elements/Switch.module.scss b/src/app/_components/_elements/Switch.module.scss
new file mode 100644
index 0000000..ee79a3b
--- /dev/null
+++ b/src/app/_components/_elements/Switch.module.scss
@@ -0,0 +1,64 @@
+.container {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.input {
+ position: relative;
+ width: 2.25em;
+ height: 1.25em;
+ border: max(2px, 0.1em) solid gray;
+ appearance: none;
+ border-radius: 1.25em;
+
+ &::before {
+ content: '';
+ position: absolute;
+ left: 0;
+ width: 1em;
+ height: 1em;
+ border-radius: 50%;
+ transform: scale(0.8);
+ background-color: gray;
+ transition: left 250ms linear;
+ }
+
+ &:checked {
+ background-color: tomato;
+ border-color: tomato;
+
+ &::before {
+ background-color: white;
+ left: 1em;
+ }
+ }
+
+ &:disabled {
+ border-color: lightgray;
+ opacity: 0.7;
+ cursor: not-allowed;
+
+ &::before {
+ background-color: lightgray;
+ }
+
+ + span {
+ opacity: 0.7;
+ cursor: not-allowed;
+ }
+ }
+
+ &:focus-visible {
+ outline-offset: max(2px, 0.1em);
+ outline: max(2px, 0.1em) solid tomato;
+ }
+
+ &:enabled:hover {
+ box-shadow: 0 0 0 max(4px, 0.2em) lightgray;
+ }
+}
+
+.actived__label {
+ cursor: pointer;
+}
diff --git a/src/app/_components/_elements/Switch.tsx b/src/app/_components/_elements/Switch.tsx
new file mode 100644
index 0000000..aefdf7d
--- /dev/null
+++ b/src/app/_components/_elements/Switch.tsx
@@ -0,0 +1,25 @@
+import { SwitchProps } from '@/types/element';
+import { combineClassName } from '@/app/_utils/style';
+import styles from './Switch.module.scss';
+
+const Switch = ({ _label, _activedLabel, ...rest }: SwitchProps) => {
+ return (
+ <>
+ {_activedLabel ? (
+ <>
+
+ >
+ ) : (
+
+
+
+
+ )}
+ >
+ );
+};
+
+export default Switch;
diff --git a/src/app/_components/_elements/TextArea.module.scss b/src/app/_components/_elements/TextArea.module.scss
new file mode 100644
index 0000000..419f246
--- /dev/null
+++ b/src/app/_components/_elements/TextArea.module.scss
@@ -0,0 +1,30 @@
+@import '@/styles/mixins';
+
+.textarea {
+ // 공통
+ border: none;
+
+ &.size_lg {
+ @include textarea-size($textarea-size-lg);
+ }
+
+ &.size_md {
+ @include textarea-size($textarea-size-md);
+ }
+
+ &.width_full {
+ @include textarea-width($textarea-width-full);
+ }
+
+ &.theme_gray {
+ @include textarea-theme($textarea-theme-gray);
+ }
+
+ &.warning {
+ &:focus {
+ outline: none;
+ }
+
+ border: 2px solid red;
+ }
+}
diff --git a/src/app/_components/_elements/TextArea.tsx b/src/app/_components/_elements/TextArea.tsx
new file mode 100644
index 0000000..393fca9
--- /dev/null
+++ b/src/app/_components/_elements/TextArea.tsx
@@ -0,0 +1,21 @@
+import { TextAreaProps } from '@/types/element';
+import { forwardRef } from 'react';
+import styles from './TextArea.module.scss';
+import classNames from 'classnames/bind';
+
+const cx = classNames.bind(styles);
+const TextArea = forwardRef(
+ ({ _className, size, width, theme, value, warning, ...rest }, ref) => {
+ return (
+
+ );
+ },
+);
+
+TextArea.displayName = 'TextArea';
+
+export default TextArea;
diff --git a/src/app/_components/_elements/TimePicker.tsx b/src/app/_components/_elements/TimePicker.tsx
new file mode 100644
index 0000000..2059338
--- /dev/null
+++ b/src/app/_components/_elements/TimePicker.tsx
@@ -0,0 +1,31 @@
+import { ChangeEvent } from 'react';
+import { TimePickerProps } from '@/types/element';
+import { getTimeList } from '@/app/_utils/element';
+
+const TimePicker = ({ type, min, max, gap, selectedValue, onChange }: TimePickerProps) => {
+ const isHour = type === 'hour';
+ const minTime = min ?? 0;
+ const maxTime = max ?? (isHour ? 23 : 59);
+ const gapTime = gap ?? (isHour ? 1 : 30);
+
+ const timeList = getTimeList(minTime, maxTime, gapTime);
+
+ const handleOnChange = (e: ChangeEvent) => {
+ const selectedOption = timeList.find((time) => time === e.target.value);
+ if (selectedOption) onChange(selectedOption);
+ };
+
+ return (
+ <>
+
+ >
+ );
+};
+
+export default TimePicker;
diff --git a/src/app/_components/_modules/AlarmToast.module.scss b/src/app/_components/_modules/AlarmToast.module.scss
new file mode 100644
index 0000000..244034b
--- /dev/null
+++ b/src/app/_components/_modules/AlarmToast.module.scss
@@ -0,0 +1,19 @@
+.toastContent {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: fixed;
+ top: 15%;
+ left: 50%;
+ width: 250px;
+ height: 35px;
+ transform: translate(-50%, -50%);
+ gap: 20px;
+ background-color: darkgray;
+ border-radius: 20px;
+
+ p {
+ margin: 0;
+ color: white;
+ }
+}
diff --git a/src/app/_components/_modules/AlarmToast.tsx b/src/app/_components/_modules/AlarmToast.tsx
new file mode 100644
index 0000000..d2fd48e
--- /dev/null
+++ b/src/app/_components/_modules/AlarmToast.tsx
@@ -0,0 +1,12 @@
+'use client';
+import styles from './AlarmToast.module.scss';
+
+const AlarmToast = () => {
+ return (
+
+ );
+};
+
+export default AlarmToast;
diff --git a/src/app/_components/_modules/AlertModal.module.scss b/src/app/_components/_modules/AlertModal.module.scss
new file mode 100644
index 0000000..7cfd50f
--- /dev/null
+++ b/src/app/_components/_modules/AlertModal.module.scss
@@ -0,0 +1,20 @@
+@import '@/styles/mixins';
+
+.modalContent {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 582px;
+ min-height: 292px;
+ padding: 60px;
+ background-color: white;
+ flex-direction: column;
+ border-radius: 16px;
+}
+
+.actionButtons {
+ display: flex;
+ justify-content: center;
+ width: 100%;
+ margin-top: 40px;
+}
diff --git a/src/app/_components/_modules/AlertModal.tsx b/src/app/_components/_modules/AlertModal.tsx
new file mode 100644
index 0000000..51c8576
--- /dev/null
+++ b/src/app/_components/_modules/AlertModal.tsx
@@ -0,0 +1,21 @@
+'use client';
+
+import { useModalStore } from '@/store/modal';
+
+import styles from './AlertModal.module.scss';
+
+const AlertModal = () => {
+ const { modalData, closeModal } = useModalStore();
+
+ // alert는 취소 버튼이 없으므로 onSubmitButton 만 진행
+ const { content, onSubmitButton } = modalData;
+
+ return (
+
+ {content}
+
{onSubmitButton}
+
+ );
+};
+
+export default AlertModal;
diff --git a/src/app/_components/_modules/ConfirmModal.module.scss b/src/app/_components/_modules/ConfirmModal.module.scss
new file mode 100644
index 0000000..c6ade79
--- /dev/null
+++ b/src/app/_components/_modules/ConfirmModal.module.scss
@@ -0,0 +1,21 @@
+@import '@/styles/mixins';
+
+.modalContent {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 582px;
+ min-height: 292px;
+ padding: 60px;
+ background-color: white;
+ flex-direction: column;
+ border-radius: 16px;
+}
+
+.actionButtons {
+ display: flex;
+ justify-content: center;
+ gap: 6px;
+ width: 100%;
+ margin-top: 40px;
+}
diff --git a/src/app/_components/_modules/ConfirmModal.tsx b/src/app/_components/_modules/ConfirmModal.tsx
new file mode 100644
index 0000000..c321ea3
--- /dev/null
+++ b/src/app/_components/_modules/ConfirmModal.tsx
@@ -0,0 +1,24 @@
+'use client';
+
+import { useModalStore } from '@/store/modal';
+
+import styles from './ConfirmModal.module.scss';
+
+const ConfirmModal = () => {
+ const { modalData } = useModalStore();
+
+ const { content, onCancelButton, onSubmitButton } = modalData;
+
+ return (
+
+ {content}
+
+
+ {onSubmitButton}
+ {onCancelButton}
+
+
+ );
+};
+
+export default ConfirmModal;
diff --git a/src/app/_components/_modules/FlyoutMenu.module.scss b/src/app/_components/_modules/FlyoutMenu.module.scss
new file mode 100644
index 0000000..b3f9fa0
--- /dev/null
+++ b/src/app/_components/_modules/FlyoutMenu.module.scss
@@ -0,0 +1,45 @@
+@import '@/styles/globals';
+@import '@/styles/mixins';
+
+.flyout {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ position: absolute;
+ top: 20px;
+ right: 20px;
+
+ .dimmed {
+ width: 100%;
+ height: 100%;
+ }
+
+ &__button {
+ @include pure-button;
+
+ margin-bottom: 8px;
+ }
+
+ &__list {
+ position: absolute;
+ top: 30px;
+ left: 0;
+ z-index: 2;
+ width: 120px;
+ padding: 0;
+ background: $white;
+ border: 1px solid $gray-1;
+ border-radius: 8px;
+ box-shadow: 2px 2px 3px $gray-2;
+ }
+
+ &__item {
+ padding: 12px;
+ border-bottom: 1px solid $gray-1;
+
+ &:last-child {
+ border: unset;
+ }
+ }
+}
diff --git a/src/app/_components/_modules/FlyoutMenu.tsx b/src/app/_components/_modules/FlyoutMenu.tsx
new file mode 100644
index 0000000..412947a
--- /dev/null
+++ b/src/app/_components/_modules/FlyoutMenu.tsx
@@ -0,0 +1,74 @@
+import { ReactChildrenProps } from '@/types/common';
+import styles from './FlyoutMenu.module.scss';
+import { ReactNode, useEffect, useRef } from 'react';
+import { ToggleType } from '@/app/_hooks/useToggle';
+import useToggleContext, { ToggleContext } from '@/app/_hooks/useToggleContext';
+
+interface Props {
+ /** 컨텍스트 (부모에서 전달이 필요한 경우를 위해) */
+ context: ToggleType;
+ /** 자식 노드 */
+ children: ReactNode;
+}
+
+const FlyoutMenu = ({ context, children }: Props) => {
+ const flyoutContext = context;
+ const ref = useRef(null);
+
+ useEffect(() => {
+ /** 모달 닫는 함수 선언 */
+ const closeModal = (event: MouseEvent) => {
+ if (ref.current && !ref.current.contains(event.target as Node | null)) {
+ flyoutContext.onClose();
+ }
+ };
+
+ /** 이벤트 등록 */
+ window.addEventListener('mousedown', closeModal);
+ return () => {
+ window.removeEventListener('mousedown', closeModal);
+ };
+ }, [flyoutContext]);
+
+ return (
+
+
+ {children}
+
+
+ );
+};
+
+const ToggleButton = ({ children }: ReactChildrenProps) => {
+ const context = useToggleContext();
+
+ const handleToggle = () => {
+ if (context.isOpen) context.onClose();
+ else context.onOpen();
+ };
+
+ return (
+
+ );
+};
+
+const MenuList = ({ children }: ReactChildrenProps) => {
+ const context = useToggleContext();
+
+ // ToggleButton은 계속 유지한 체, flyout의 메뉴들만 보여주거나 없애거나
+ if (!context.isOpen) return null;
+
+ return ;
+};
+
+const MenuItem = ({ children }: ReactChildrenProps) => {
+ return {children};
+};
+
+FlyoutMenu.ToggleButton = ToggleButton;
+FlyoutMenu.MenuList = MenuList;
+FlyoutMenu.MenuItem = MenuItem;
+
+export default FlyoutMenu;
diff --git a/src/app/_components/_modules/ImagePreview.tsx b/src/app/_components/_modules/ImagePreview.tsx
new file mode 100644
index 0000000..2b516b9
--- /dev/null
+++ b/src/app/_components/_modules/ImagePreview.tsx
@@ -0,0 +1,24 @@
+'use client';
+
+interface Props {
+ selectedFiles: File[];
+ base64Strings: string[];
+}
+
+const ImagePreview = ({ selectedFiles, base64Strings }: Props) => {
+ return (
+
+ {selectedFiles.map((file, index) => (
+
+
File Name: {file.name}
+
File Size: {file.size} bytes
+
Base64 String: {base64Strings[index]}
+
+
+
+ ))}
+
+ );
+};
+
+export default ImagePreview;
diff --git a/src/app/_components/_modules/ImageUploader.tsx b/src/app/_components/_modules/ImageUploader.tsx
new file mode 100644
index 0000000..7a3165c
--- /dev/null
+++ b/src/app/_components/_modules/ImageUploader.tsx
@@ -0,0 +1,28 @@
+'use client';
+
+import { ChangeEvent, InputHTMLAttributes } from 'react';
+
+interface Props extends InputHTMLAttributes {
+ _classname?: string;
+ _handleFileChange: (e: ChangeEvent) => void;
+}
+
+const ImageUploader = ({ _classname, _handleFileChange, ...rest }: Props) => {
+ return (
+ <>
+
+ >
+ );
+};
+
+export default ImageUploader;
diff --git a/src/app/_components/_modules/SaveToast.module.scss b/src/app/_components/_modules/SaveToast.module.scss
new file mode 100644
index 0000000..e25b8c0
--- /dev/null
+++ b/src/app/_components/_modules/SaveToast.module.scss
@@ -0,0 +1,22 @@
+@import '@/styles/globals';
+@import '@/styles/mixins';
+
+.toastContent {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: fixed;
+ top: 92px;
+ left: 50%;
+ width: 297px;
+ height: 56px;
+ font-size: px-to-rem($h2);
+ transform: translate(-50%, 0%);
+ background-color: $gray-5;
+ border-radius: 5px;
+
+ p {
+ margin: 0;
+ color: white;
+ }
+}
diff --git a/src/app/_components/_modules/SaveToast.tsx b/src/app/_components/_modules/SaveToast.tsx
new file mode 100644
index 0000000..cb7927b
--- /dev/null
+++ b/src/app/_components/_modules/SaveToast.tsx
@@ -0,0 +1,12 @@
+'use client';
+import styles from './SaveToast.module.scss';
+
+const SaveToast = () => {
+ return (
+
+ );
+};
+
+export default SaveToast;
diff --git a/src/app/_components/_modules/SpeechBubble.module.scss b/src/app/_components/_modules/SpeechBubble.module.scss
new file mode 100644
index 0000000..ceb908a
--- /dev/null
+++ b/src/app/_components/_modules/SpeechBubble.module.scss
@@ -0,0 +1,70 @@
+@import '@/styles/globals';
+@import '@/styles/mixins';
+
+.speech-bubble {
+ position: relative;
+ width: fit-content;
+ min-width: 150px;
+ height: 40px;
+ margin: 0 auto;
+ padding: 8px 20px;
+ color: $white;
+ text-align: center;
+ line-height: 24px;
+ border-radius: 5px;
+ background-color: $gray-5;
+}
+
+.has-close {
+ padding: 8px 40px 8px 20px;
+}
+
+.close {
+ @include pure-button;
+
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ width: 20px;
+ height: 20px;
+
+ &,
+ i {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ svg {
+ width: 15px;
+ height: 15px;
+ }
+}
+
+.up::after {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 50%;
+ width: 0;
+ height: 0;
+ border: 14px solid transparent;
+ border-bottom-color: $gray-5;
+ border-top: 0;
+ margin-left: -14px;
+ margin-top: -14px;
+}
+
+.down::after {
+ content: '';
+ position: absolute;
+ bottom: 0;
+ left: 50%;
+ width: 0;
+ height: 0;
+ border: 14px solid transparent;
+ border-top-color: $gray-5;
+ border-bottom: 0;
+ margin-left: -14px;
+ margin-bottom: -14px;
+}
diff --git a/src/app/_components/_modules/SpeechBubble.tsx b/src/app/_components/_modules/SpeechBubble.tsx
new file mode 100644
index 0000000..d269eca
--- /dev/null
+++ b/src/app/_components/_modules/SpeechBubble.tsx
@@ -0,0 +1,43 @@
+import classNames from 'classnames/bind';
+import styles from './SpeechBubble.module.scss';
+import CloseIcon from '@/app/_svgs/CloseIcon';
+import { ToggleContext } from '@/app/_hooks/useToggleContext';
+import { ToggleType } from '@/app/_hooks/useToggle';
+
+interface Props {
+ /** 컨텍스트 */
+ context: ToggleType;
+ /** 말풍선 문구 */
+ message: string;
+ /** 말풍선 방향 (기본값: up) */
+ direction?: 'up' | 'down';
+ /** X 버튼 유무 (기본값: false) */
+ hasCloseBtn?: boolean;
+}
+
+const SpeechBubble = ({ context, message, direction = 'up', hasCloseBtn = false }: Props) => {
+ const cx = classNames.bind(styles);
+
+ const handleClose = () => {
+ if (hasCloseBtn) {
+ context.onClose();
+ }
+ };
+
+ if (!context.isOpen) return null;
+
+ return (
+
+
+ {message}
+ {hasCloseBtn && (
+
+ )}
+
+
+ );
+};
+
+export default SpeechBubble;
diff --git a/src/app/_components/_modules/Spinner.module.scss b/src/app/_components/_modules/Spinner.module.scss
new file mode 100644
index 0000000..9036e4d
--- /dev/null
+++ b/src/app/_components/_modules/Spinner.module.scss
@@ -0,0 +1,33 @@
+@import '@/styles/mixins';
+
+@keyframes rotating {
+ from {
+ transform: rotate(0deg);
+ }
+
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+.container {
+ @include flex-center;
+
+ width: 100dvw;
+ height: 100dvh;
+
+ circle:nth-of-type(1) {
+ stroke: $purple-5;
+ opacity: 0.2;
+ }
+
+ circle:nth-of-type(2) {
+ stroke: $purple-5;
+ stroke-dasharray: 80;
+ stroke-dashoffset: 60;
+ }
+}
+
+.loader {
+ animation: rotating 2s linear infinite;
+}
diff --git a/src/app/_components/_modules/Spinner.tsx b/src/app/_components/_modules/Spinner.tsx
new file mode 100644
index 0000000..a57749d
--- /dev/null
+++ b/src/app/_components/_modules/Spinner.tsx
@@ -0,0 +1,12 @@
+import styles from './Spinner.module.scss';
+
+export default function Spinner() {
+ return (
+
+
+
+ );
+}
diff --git a/src/app/_components/_modules/_modal-pre/Modal.module.scss b/src/app/_components/_modules/_modal-pre/Modal.module.scss
new file mode 100644
index 0000000..e2fded6
--- /dev/null
+++ b/src/app/_components/_modules/_modal-pre/Modal.module.scss
@@ -0,0 +1,8 @@
+.modalContainer {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: fixed;
+ inset: 0;
+ background-color: rgb(0 0 0 / 50%);
+}
diff --git a/src/app/_components/_modules/_modal-pre/Modal.tsx b/src/app/_components/_modules/_modal-pre/Modal.tsx
new file mode 100644
index 0000000..e7c9661
--- /dev/null
+++ b/src/app/_components/_modules/_modal-pre/Modal.tsx
@@ -0,0 +1,25 @@
+'use client';
+
+import { useModalStore } from '@/store/modal';
+
+import styles from './Modal.module.scss';
+import AlertModal from '../AlertModal';
+import ConfirmModal from '../ConfirmModal';
+
+const Modal = () => {
+ const { isOpen, modalData } = useModalStore();
+
+ const { onCancelButton } = modalData;
+
+ if (!isOpen) {
+ return <>>;
+ }
+
+ return (
+
+ {onCancelButton ?
:
}
+
+ );
+};
+
+export default Modal;
diff --git a/src/app/_components/_modules/_modal-pre/ModalContents.module.scss b/src/app/_components/_modules/_modal-pre/ModalContents.module.scss
new file mode 100644
index 0000000..66e54ae
--- /dev/null
+++ b/src/app/_components/_modules/_modal-pre/ModalContents.module.scss
@@ -0,0 +1,23 @@
+@import '@/styles/mixins';
+
+.exitUpload_firstParagraph {
+ margin: 0;
+ font-size: px-to-rem(26px);
+ font-weight: bolder;
+}
+
+.exitUpload_secondParagraph {
+ margin: 0;
+ margin-top: 20px;
+ font-size: px-to-rem(20px);
+}
+
+.exitUpload_cancelButton {
+ @include button_theme_lined;
+ @include button_size_web;
+}
+
+.exitUpload_submitButton {
+ @include button_theme_default;
+ @include button_size_web;
+}
diff --git a/src/app/_components/_modules/_modal-pre/ModalContents.tsx b/src/app/_components/_modules/_modal-pre/ModalContents.tsx
new file mode 100644
index 0000000..8d0d478
--- /dev/null
+++ b/src/app/_components/_modules/_modal-pre/ModalContents.tsx
@@ -0,0 +1,47 @@
+import { useModalStore } from '@/store/modal';
+import { ReactChildrenProps } from '@/types/common';
+import styles from './ModalContents.module.scss';
+
+const ModalContents = ({ children }: ReactChildrenProps) => {
+ return <>{children}>;
+};
+
+const ExitUpload = () => {
+ return (
+ <>
+ 발표 자료 추가를 중단하시겠어요?
+ 임시저장하지 않은 자료는 복원할 수 없어요
+ >
+ );
+};
+
+const ExitUploadCancel = () => {
+ const { closeModal } = useModalStore();
+
+ const onClick = () => {
+ closeModal();
+ };
+ return (
+
+ );
+};
+
+const ExitUploadSubmit = () => {
+ const { closeModal } = useModalStore();
+ const onClick = () => {
+ closeModal();
+ };
+ return (
+
+ );
+};
+
+ModalContents.ExitUpload = ExitUpload;
+ModalContents.ExitUploadCancel = ExitUploadCancel;
+ModalContents.ExitUploadSubmit = ExitUploadSubmit;
+
+export default ModalContents;
diff --git a/src/app/_components/_modules/_modal-pre/Toast.module.scss b/src/app/_components/_modules/_modal-pre/Toast.module.scss
new file mode 100644
index 0000000..f84431c
--- /dev/null
+++ b/src/app/_components/_modules/_modal-pre/Toast.module.scss
@@ -0,0 +1,31 @@
+@keyframes fade-in {
+ from {
+ opacity: 0;
+ }
+
+ to {
+ opacity: 1;
+ }
+}
+
+@keyframes fade-out {
+ from {
+ opacity: 1;
+ }
+
+ to {
+ opacity: 0;
+ }
+}
+
+.toastContainer {
+ // position: fixed;
+ // top: 15%;
+ // left: 50%;
+ // transform: translate(-50%, -50%);
+ animation: fade-in 0.5s;
+
+ &.fadeOut {
+ animation: fade-out 0.8s;
+ }
+}
diff --git a/src/app/_components/_modules/_modal-pre/Toast.tsx b/src/app/_components/_modules/_modal-pre/Toast.tsx
new file mode 100644
index 0000000..80737d1
--- /dev/null
+++ b/src/app/_components/_modules/_modal-pre/Toast.tsx
@@ -0,0 +1,38 @@
+'use client';
+
+import { useState, useLayoutEffect } from 'react';
+
+import styles from './Toast.module.scss';
+
+import classNames from 'classnames/bind';
+import { useToastStore } from '@/store/modal';
+
+const Toast = () => {
+ const { isOpen, toastData, closeToast } = useToastStore();
+
+ const { content } = toastData;
+
+ const [fadeOut, setFadeOut] = useState(false);
+
+ const cx = classNames.bind(styles);
+
+ useLayoutEffect(() => {
+ if (isOpen) {
+ setFadeOut(false);
+ const timer = setTimeout(() => {
+ setFadeOut(true);
+ setTimeout(closeToast, 500);
+ }, 1000);
+
+ return () => clearTimeout(timer);
+ }
+ }, [isOpen, closeToast]);
+
+ if (!isOpen) {
+ return <>>;
+ }
+
+ return {content}
;
+};
+
+export default Toast;
diff --git a/src/app/_components/_modules/_modal/Alert.module.scss b/src/app/_components/_modules/_modal/Alert.module.scss
new file mode 100644
index 0000000..4a8c1a5
--- /dev/null
+++ b/src/app/_components/_modules/_modal/Alert.module.scss
@@ -0,0 +1,29 @@
+@import '@/styles/globals';
+@import '@/styles/mixins';
+
+.container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ width: 580px;
+ height: 290px;
+ padding: 40px;
+ border-radius: 16px;
+ background: $white;
+}
+
+.message {
+ margin-top: 8px;
+ font-size: $font-1;
+}
+
+.action {
+ @include pure-button;
+ @include button_theme_default;
+
+ width: 230px;
+ height: 55px;
+ margin-top: 40px;
+ border-radius: 12px;
+}
diff --git a/src/app/_components/_modules/_modal/Alert.tsx b/src/app/_components/_modules/_modal/Alert.tsx
new file mode 100644
index 0000000..c110729
--- /dev/null
+++ b/src/app/_components/_modules/_modal/Alert.tsx
@@ -0,0 +1,51 @@
+import styles from './Alert.module.scss';
+import { ToggleType } from '@/app/_hooks/useToggle';
+import { ToggleContext } from '@/app/_hooks/useToggleContext';
+import ModalLayout from './ModalLayout';
+
+interface Props {
+ /** 컨텍스트 */
+ context: ToggleType;
+ /** 제목 */
+ title: string;
+ /** 본문 */
+ message: string;
+ /** 하단 버튼 텍스트 */
+ actionText?: string;
+ /** 하단 버튼 비활성화 여부 (true: 비활성화, false: 활성화 / 기본값: true) */
+ isDisabled?: boolean;
+ /** 하단 버튼 이벤트 (기본 값 : 창 닫음) */
+ onActionClick?: () => void;
+}
+
+const Alert = ({
+ context,
+ title,
+ message,
+ actionText = '확인',
+ isDisabled = false,
+ onActionClick,
+}: Props) => {
+ const handleOnClick = () => {
+ if (typeof onActionClick === 'function') {
+ onActionClick();
+ } else {
+ context.onClose();
+ }
+ };
+ return (
+
+
+
+
{title}
+
{message}
+
+
+
+
+ );
+};
+
+export default Alert;
diff --git a/src/app/_components/_modules/_modal/Confirm.module.scss b/src/app/_components/_modules/_modal/Confirm.module.scss
new file mode 100644
index 0000000..a751d4c
--- /dev/null
+++ b/src/app/_components/_modules/_modal/Confirm.module.scss
@@ -0,0 +1,41 @@
+@import '@/styles/globals';
+@import '@/styles/mixins';
+
+.container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ width: 600px;
+ height: 320px;
+ padding: 40px;
+ border-radius: 16px;
+ background: $white;
+}
+
+.message {
+ margin-top: 8px;
+ font-size: $font-1;
+}
+
+.action {
+ @include pure-button;
+
+ width: 230px;
+ height: 55px;
+ margin-top: 40px;
+ border-radius: 12px;
+
+ &--okay {
+ @include button_theme_default;
+
+ font-size: $h2;
+ margin-right: 8px;
+ }
+
+ &--cancel {
+ @include button_theme_lined;
+
+ font-size: $h2;
+ }
+}
diff --git a/src/app/_components/_modules/_modal/Confirm.tsx b/src/app/_components/_modules/_modal/Confirm.tsx
new file mode 100644
index 0000000..d4ab30d
--- /dev/null
+++ b/src/app/_components/_modules/_modal/Confirm.tsx
@@ -0,0 +1,67 @@
+import styles from './Confirm.module.scss';
+import { ToggleType } from '@/app/_hooks/useToggle';
+import { ToggleContext } from '@/app/_hooks/useToggleContext';
+import ModalLayout from './ModalLayout';
+import classNames from 'classnames/bind';
+
+interface Props {
+ /** 컨텍스트 */
+ context: ToggleType;
+ /** 제목 */
+ title: string;
+ /** 본문 */
+ message?: string;
+ /** 왼쪽 확인 버튼 텍스트 */
+ okayText?: string;
+ /** 오른쪽 취소 버튼 텍스트 */
+ cancelText?: string;
+ /** 왼쪽 확인 버튼 이벤트 (필수) */
+ onOkayClick: () => void;
+ /** 오른쪽 취소 버튼 이벤트 (기본 값 : 창 닫음) */
+ onCancelClick?: () => void;
+}
+
+const Confirm = ({
+ context,
+ title,
+ message,
+ okayText = '확인',
+ cancelText = '닫기',
+ onOkayClick,
+ onCancelClick,
+}: Props) => {
+ const cx = classNames.bind(styles);
+
+ const handleOkay = () => {
+ onOkayClick();
+ context.onClose();
+ };
+
+ const handleCancel = () => {
+ if (typeof onCancelClick === 'function') {
+ onCancelClick();
+ } else {
+ context.onClose();
+ }
+ };
+ return (
+
+
+
+
{title}
+
{message}
+
+
+
+
+
+
+
+ );
+};
+
+export default Confirm;
diff --git a/src/app/_components/_modules/_modal/Modal.module.scss b/src/app/_components/_modules/_modal/Modal.module.scss
new file mode 100644
index 0000000..6b25b7b
--- /dev/null
+++ b/src/app/_components/_modules/_modal/Modal.module.scss
@@ -0,0 +1,33 @@
+@import '@/styles/globals';
+@import '@/styles/mixins';
+
+.container {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ padding: 40px;
+ background: $white;
+ flex-direction: column;
+ border-radius: 16px;
+
+ &--sm {
+ width: 600px;
+ min-height: 350px;
+ }
+
+ &--lg {
+ width: 800px;
+ min-height: 650px;
+ }
+}
+
+.header {
+ position: absolute;
+ top: 20px;
+ right: 20px;
+
+ button {
+ @include pure-button;
+ }
+}
diff --git a/src/app/_components/_modules/_modal/Modal.tsx b/src/app/_components/_modules/_modal/Modal.tsx
new file mode 100644
index 0000000..a8f2f69
--- /dev/null
+++ b/src/app/_components/_modules/_modal/Modal.tsx
@@ -0,0 +1,55 @@
+import styles from './Modal.module.scss';
+import { ToggleType } from '@/app/_hooks/useToggle';
+import { ToggleContext } from '@/app/_hooks/useToggleContext';
+import ModalLayout from './ModalLayout';
+import classNames from 'classnames/bind';
+import { ReactNode } from 'react';
+import CloseIcon from '@/app/_svgs/CloseIcon';
+
+interface Props {
+ /** 컨텍스트 */
+ context: ToggleType;
+ /** 모달 사이즈 (기본 값 : sm) */
+ size?: 'sm' | 'lg';
+ /** 상단 닫기 버튼 여부 (기본 값 : true) */
+ hasCloseBtn?: boolean;
+ /** 자식 노드 */
+ children: ReactNode;
+ /** dimmed 영역을 눌러서 닫기 여부 (기본값 : true) */
+ hasClosedDim?: boolean;
+}
+
+const Modal = ({
+ context,
+ size = 'sm',
+ hasCloseBtn = true,
+ children,
+ hasClosedDim = true,
+}: Props) => {
+ const cx = classNames.bind(styles);
+
+ const handleClose = () => {
+ if (hasCloseBtn) {
+ context.onClose();
+ }
+ };
+
+ return (
+
+
+
+ {hasCloseBtn ? (
+
+
+
+ ) : null}
+ {children}
+
+
+
+ );
+};
+
+export default Modal;
diff --git a/src/app/_components/_modules/_modal/ModalLayout.module.scss b/src/app/_components/_modules/_modal/ModalLayout.module.scss
new file mode 100644
index 0000000..d06d68a
--- /dev/null
+++ b/src/app/_components/_modules/_modal/ModalLayout.module.scss
@@ -0,0 +1,14 @@
+.dimmed {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: fixed;
+ z-index: 10;
+ inset: 0;
+ background-color: rgb(0 0 0 / 50%);
+}
+
+.container {
+ width: fit-content;
+ height: fit-content;
+}
diff --git a/src/app/_components/_modules/_modal/ModalLayout.tsx b/src/app/_components/_modules/_modal/ModalLayout.tsx
new file mode 100644
index 0000000..ae0ca9c
--- /dev/null
+++ b/src/app/_components/_modules/_modal/ModalLayout.tsx
@@ -0,0 +1,45 @@
+import useToggleContext from '@/app/_hooks/useToggleContext';
+import styles from './ModalLayout.module.scss';
+import { ReactNode, useEffect, useRef } from 'react';
+
+interface Props {
+ /** dimmed 영역을 눌러서 닫기 여부 (기본값 : true) */
+ hasClosedDim?: boolean;
+ /** 자식 노드 */
+ children: ReactNode;
+}
+
+const ModalLayout = ({ hasClosedDim = true, children }: Props) => {
+ const context = useToggleContext();
+ const ref = useRef(null);
+
+ useEffect(() => {
+ /** INFO: dimmed 영역을 눌러서 닫기를 원하는 경우만 이벤트 동작 */
+ if (hasClosedDim) {
+ /** 모달 닫는 함수 선언 */
+ const closeModal = (event: MouseEvent) => {
+ if (ref.current && !ref.current.contains(event.target as Node | null)) {
+ context.onClose();
+ }
+ };
+
+ /** 이벤트 등록 */
+ window.addEventListener('mousedown', closeModal);
+ return () => {
+ window.removeEventListener('mousedown', closeModal);
+ };
+ }
+ }, [context]);
+
+ if (!context.isOpen) return null;
+
+ return (
+
+ );
+};
+
+export default ModalLayout;
diff --git a/src/app/_hooks/useImageUploader.ts b/src/app/_hooks/useImageUploader.ts
new file mode 100644
index 0000000..2f25a4f
--- /dev/null
+++ b/src/app/_hooks/useImageUploader.ts
@@ -0,0 +1,33 @@
+'use client';
+
+import { ChangeEvent, useState } from 'react';
+
+const useImageUploader = () => {
+ const [selectedFiles, setSelectedFiles] = useState([]);
+ const [base64Strings, setBase64Strings] = useState([]);
+
+ const handleFileChange = (e: ChangeEvent) => {
+ const target = e.target as HTMLInputElement;
+ const files = target.files;
+
+ if (files) Array.from(files).forEach((file) => readAndDisplayFile(file));
+ };
+
+ const readAndDisplayFile = (file: File) => {
+ const reader = new FileReader();
+
+ reader.onload = (e) => {
+ const base64String = e.target?.result;
+ if (base64String && typeof base64String === 'string') {
+ setBase64Strings((prevBase64Strings) => [...prevBase64Strings, base64String]);
+ }
+ };
+
+ reader.readAsDataURL(file);
+ setSelectedFiles((prevSelectedFiles) => [...prevSelectedFiles, file]);
+ };
+
+ return { selectedFiles, base64Strings, handleFileChange };
+};
+
+export default useImageUploader;
diff --git a/src/app/_hooks/useToggle.ts b/src/app/_hooks/useToggle.ts
new file mode 100644
index 0000000..ee0475e
--- /dev/null
+++ b/src/app/_hooks/useToggle.ts
@@ -0,0 +1,36 @@
+import { useCallback, useState } from 'react';
+
+export interface ToggleType {
+ isOpen: boolean;
+ onOpen: () => void;
+ onClose: () => void;
+}
+
+/** 특정 컴포넌트 노출 여부를 토글하는 훅
+ *
+ * - isOpen: 노출 여부
+ * - onOpen: 컴포넌트를 노출 시키는 함수
+ * - onClose: 컴포넌트를 미노출 시키는 함수
+ */
+const useToggle = (): ToggleType => {
+ const [isOpen, setIsOpen] = useState(false);
+
+ // const onOpen = useCallback(() => {
+ // setIsOpen(true);
+ // }, []);
+ const onOpen = () => {
+ setIsOpen(true);
+ };
+
+ // const onClose = useCallback(() => {
+ // setIsOpen(false);
+ // }, []);
+
+ const onClose = () => {
+ setIsOpen(false);
+ };
+
+ return { isOpen, onOpen, onClose };
+};
+
+export default useToggle;
diff --git a/src/app/_hooks/useToggleContext.ts b/src/app/_hooks/useToggleContext.ts
new file mode 100644
index 0000000..fb31b69
--- /dev/null
+++ b/src/app/_hooks/useToggleContext.ts
@@ -0,0 +1,15 @@
+import { createContext, useContext } from 'react';
+import { type ToggleType } from './useToggle';
+
+export const ToggleContext = createContext(null);
+
+const useToggleContext = () => {
+ const context = useContext(ToggleContext);
+ if (!context) {
+ throw "There's no context!";
+ }
+
+ return context;
+};
+
+export default useToggleContext;
diff --git a/src/app/_svgs/CloseIcon.tsx b/src/app/_svgs/CloseIcon.tsx
new file mode 100644
index 0000000..13d2aa6
--- /dev/null
+++ b/src/app/_svgs/CloseIcon.tsx
@@ -0,0 +1,56 @@
+interface Props {
+ color?: 'black' | 'white';
+}
+
+const CloseIcon = ({ color = 'black' }: Props) => {
+ const isBlack = color === 'black';
+
+ return isBlack ? CloseIconBlack() : CloseIconWhite();
+};
+
+const CloseIconBlack = () => {
+ return (
+
+
+
+ );
+};
+
+const CloseIconWhite = () => {
+ return (
+
+
+
+ );
+};
+
+export default CloseIcon;
diff --git a/src/app/_svgs/LogoIcon.tsx b/src/app/_svgs/LogoIcon.tsx
new file mode 100644
index 0000000..46e9426
--- /dev/null
+++ b/src/app/_svgs/LogoIcon.tsx
@@ -0,0 +1,43 @@
+import Link from 'next/link';
+
+const LogoIcon = () => {
+ return (
+
+
+
+
+
+ );
+};
+
+export default LogoIcon;
diff --git a/src/app/_utils/element.ts b/src/app/_utils/element.ts
new file mode 100644
index 0000000..acb8cf5
--- /dev/null
+++ b/src/app/_utils/element.ts
@@ -0,0 +1,13 @@
+/** 시간 목록 반환 함수
+ *
+ * TimePicker.tsx에서 사용
+ *
+ * @param min 최솟값
+ * @param max 최댓값
+ * @param gap 시간 간격
+ */
+export const getTimeList = (min: number, max: number, gap: number) => {
+ if (max - min < gap) return [];
+
+ return Array.from({ length: (max - min) / gap + 1 }, (_, i) => (min + i * gap).toString());
+};
diff --git a/src/app/_utils/style.ts b/src/app/_utils/style.ts
new file mode 100644
index 0000000..fbbecd6
--- /dev/null
+++ b/src/app/_utils/style.ts
@@ -0,0 +1,4 @@
+export const combineClassName = (classes: string[]) => {
+ if (!classes) return '';
+ return classes.join(' ');
+};
diff --git a/src/app/api/get/auth/kakao/route.ts b/src/app/api/get/auth/kakao/route.ts
new file mode 100644
index 0000000..bc691f7
--- /dev/null
+++ b/src/app/api/get/auth/kakao/route.ts
@@ -0,0 +1,25 @@
+import { NextRequest, NextResponse } from 'next/server';
+
+export async function GET(request: NextRequest) {
+ const codeQuery = request.nextUrl.searchParams.get('code') as string;
+
+ const url = `${process.env.NEXT_PUBLIC_BASE_URL_DEV}/api/accounts/login/process?code=${codeQuery}&provider=kakao`;
+
+ const response = await fetch(`${url}`, {
+ method: 'GET',
+ cache: 'no-store',
+ });
+
+ const tokens = response.headers.get('set-cookie');
+
+ if (tokens) {
+ const newResponse = new NextResponse(response.body, {
+ status: response.status,
+ headers: response.headers,
+ });
+ newResponse.headers.set('Set-Cookie', tokens);
+ return newResponse;
+ }
+
+ return new NextResponse('something wrong with token', { status: 500 });
+}
diff --git a/src/app/api/get/auth/slient/route.ts b/src/app/api/get/auth/slient/route.ts
new file mode 100644
index 0000000..56380ff
--- /dev/null
+++ b/src/app/api/get/auth/slient/route.ts
@@ -0,0 +1,24 @@
+import { NextRequest, NextResponse } from 'next/server';
+import { cookies } from 'next/headers';
+
+export async function GET(request: NextRequest) {
+ const url = `${process.env.NEXT_PUBLIC_BASE_URL_DEV}/api/accounts/reissue`;
+ const response = await fetch(`${url}`, {
+ method: 'GET',
+ headers: { Cookie: cookies().toString() },
+ cache: 'no-store',
+ });
+
+ const tokens = response.headers.get('set-cookie');
+
+ if (tokens) {
+ const newResponse = new NextResponse(response.body, {
+ status: response.status,
+ headers: response.headers,
+ });
+ newResponse.headers.set('Set-Cookie', tokens);
+ return newResponse;
+ }
+
+ return new NextResponse('something wrong with token', { status: 500 });
+}
diff --git a/src/app/api/get/list/[id]/route.ts b/src/app/api/get/list/[id]/route.ts
new file mode 100644
index 0000000..6917ed1
--- /dev/null
+++ b/src/app/api/get/list/[id]/route.ts
@@ -0,0 +1,125 @@
+// src\app\api\get\list\[id]\route.ts
+
+import { MockUploadDataType } from '@/types/service';
+import { NextRequest, NextResponse } from 'next/server';
+
+// const dummyFile = new File(['dummy content'], 'hello.png', {
+// type: 'image/png',
+// lastModified: new Date('2023-11-29T10:58:23Z').getTime(),
+// });
+
+const mockPresentData: MockUploadDataType[] = [
+ {
+ id: 11111,
+ dday: 20,
+ title: '발표1',
+ createdAt: new Date('2024-03-30'),
+ modifiedAt: new Date('2024-04-30'),
+ timeLimit: {
+ hours: 3,
+ minutes: 40,
+ },
+ alertTime: {
+ hours: 1,
+ minutes: 0,
+ },
+ deadlineDate: new Date('2024-07-30'),
+ slides: [
+ {
+ id: 1,
+ imageFileId: {
+ dataURL:
+ 'data:image/png;base64,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',
+ file: null,
+ },
+ script: '첫번째 내용내용내용내용내용내용',
+ memo: '첫번째 메모메모메모메모메모',
+ },
+ {
+ id: 2,
+ imageFileId: {
+ dataURL:
+ 'data:image/png;base64,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',
+ file: null,
+ },
+ script: '첫번째 내용내용내용내용내용내용',
+ memo: '첫번째 메모메모메모메모메모',
+ },
+ {
+ id: 3,
+ imageFileId: {
+ dataURL:
+ 'data:image/png;base64,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',
+ file: null,
+ },
+ script: '첫번째 내용내용내용내용내용내용',
+ memo: '첫번째 메모메모메모메모메모',
+ },
+ ],
+ },
+ {
+ id: 22222,
+ dday: 20,
+ title: '발표2',
+ createdAt: new Date('2024-03-30'),
+ modifiedAt: new Date('2024-04-30'),
+ timeLimit: {
+ hours: 3,
+ minutes: 40,
+ },
+ alertTime: {
+ hours: 1,
+ minutes: 0,
+ },
+ deadlineDate: new Date('2024-07-30'),
+ slides: [
+ {
+ id: 1,
+ imageFileId: {
+ dataURL:
+ 'data:image/png;base64,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',
+ file: null,
+ },
+ script: '두번째 내용내용내용내용내용내용',
+ memo: '두번째 메모메모메모메모메모',
+ },
+ {
+ id: 2,
+ imageFileId: {
+ dataURL:
+ 'data:image/png;base64,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',
+ file: null,
+ },
+ script: '두번째 내용내용내용내용내용내용',
+ memo: '두번째 메모메모메모메모메모',
+ },
+ {
+ id: 3,
+ imageFileId: {
+ dataURL:
+ 'data:image/png;base64,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',
+ file: null,
+ },
+ script: '두번째 내용내용내용내용내용내용',
+ memo: '두번째 메모메모메모메모메모',
+ },
+ ],
+ },
+];
+
+export async function GET(
+ request: NextRequest,
+ { params }: { params: { id: string } },
+): Promise {
+ const targetId = params.id;
+ try {
+ return new Promise((resolve) => {
+ const result = mockPresentData.filter((i) => i.id === Number(targetId))[0];
+ setTimeout(() => {
+ resolve(new NextResponse(JSON.stringify(result), { status: 200 }));
+ }, 500);
+ });
+ } catch (e) {
+ return new NextResponse(null, { status: 500 });
+ }
+}
diff --git a/src/app/globals.css b/src/app/globals.css
index d4f491e..036f5c3 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -1,98 +1,72 @@
-:root {
- --max-width: 1100px;
- --border-radius: 12px;
- --font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono',
- 'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro',
- 'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace;
+@import 'https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.8/dist/web/static/pretendard.css';
- --foreground-rgb: 0, 0, 0;
- --background-start-rgb: 214, 219, 220;
- --background-end-rgb: 255, 255, 255;
-
- --primary-glow: conic-gradient(
- from 180deg at 50% 50%,
- #16abff33 0deg,
- #0885ff33 55deg,
- #54d6ff33 120deg,
- #0071ff33 160deg,
- transparent 360deg
- );
- --secondary-glow: radial-gradient(
- rgba(255, 255, 255, 1),
- rgba(255, 255, 255, 0)
- );
-
- --tile-start-rgb: 239, 245, 249;
- --tile-end-rgb: 228, 232, 233;
- --tile-border: conic-gradient(
- #00000080,
- #00000040,
- #00000030,
- #00000020,
- #00000010,
- #00000010,
- #00000080
- );
+* {
+ box-sizing: border-box;
+}
- --callout-rgb: 238, 240, 241;
- --callout-border-rgb: 172, 175, 176;
- --card-rgb: 180, 185, 188;
- --card-border-rgb: 131, 134, 135;
+html,
+body {
+ margin: 0;
}
-@media (prefers-color-scheme: dark) {
- :root {
- --foreground-rgb: 255, 255, 255;
- --background-start-rgb: 0, 0, 0;
- --background-end-rgb: 0, 0, 0;
+h1 {
+ margin: 0;
+ font-size: 32px;
+}
- --primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0));
- --secondary-glow: linear-gradient(
- to bottom right,
- rgba(1, 65, 255, 0),
- rgba(1, 65, 255, 0),
- rgba(1, 65, 255, 0.3)
- );
+h2 {
+ margin: 0;
+ font-size: 24px;
+}
- --tile-start-rgb: 2, 13, 46;
- --tile-end-rgb: 2, 5, 19;
- --tile-border: conic-gradient(
- #ffffff80,
- #ffffff40,
- #ffffff30,
- #ffffff20,
- #ffffff10,
- #ffffff10,
- #ffffff80
- );
+h3 {
+ margin: 0;
+ font-size: 18px;
+}
- --callout-rgb: 20, 20, 20;
- --callout-border-rgb: 108, 108, 108;
- --card-rgb: 100, 100, 100;
- --card-border-rgb: 200, 200, 200;
- }
+h4 {
+ margin: 0;
+ font-size: 16px;
}
-* {
- box-sizing: border-box;
- padding: 0;
+h5 {
margin: 0;
+ font-size: 14px;
}
-html,
-body {
- max-width: 100vw;
- overflow-x: hidden;
+p {
+ margin: 0;
}
-body {
- color: rgb(var(--foreground-rgb));
- background: linear-gradient(
- to bottom,
- transparent,
- rgb(var(--background-end-rgb))
- )
- rgb(var(--background-start-rgb));
+button,
+p,
+textarea,
+input,
+label,
+span,
+text,
+small,
+h1,
+h2,
+h3,
+h4,
+h5 {
+ font-family:
+ 'Pretendard Variable',
+ Pretendard,
+ -apple-system,
+ BlinkMacSystemFont,
+ system-ui,
+ Roboto,
+ 'Helvetica Neue',
+ 'Segoe UI',
+ 'Apple SD Gothic Neo',
+ 'Noto Sans KR',
+ 'Malgun Gothic',
+ 'Apple Color Emoji',
+ 'Segoe UI Emoji',
+ 'Segoe UI Symbol',
+ sans-serif;
}
a {
@@ -100,8 +74,10 @@ a {
text-decoration: none;
}
-@media (prefers-color-scheme: dark) {
- html {
- color-scheme: dark;
- }
+ul {
+ padding: 0;
+}
+
+li {
+ list-style: none;
}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 40e027f..ed9cba5 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,22 +1,19 @@
-import type { Metadata } from 'next'
-import { Inter } from 'next/font/google'
-import './globals.css'
+import type { Metadata } from 'next';
+import { Inter } from 'next/font/google';
+import './globals.css';
+import { ReactChildrenProps } from '@/types/common';
-const inter = Inter({ subsets: ['latin'] })
+const inter = Inter({ subsets: ['latin'] });
export const metadata: Metadata = {
- title: 'Create Next App',
- description: 'Generated by create next app',
-}
+ title: 'KKEUNKKEUN-WEB',
+ description: 'DDD 10th',
+};
-export default function RootLayout({
- children,
-}: {
- children: React.ReactNode
-}) {
+export default function RootLayout({ children }: ReactChildrenProps) {
return (
{children}
- )
+ );
}
diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx
new file mode 100644
index 0000000..ed413d2
--- /dev/null
+++ b/src/app/not-found.tsx
@@ -0,0 +1,11 @@
+import Link from 'next/link';
+
+export default function NotFound() {
+ return (
+
+
Not Found
+
Could not find requested resource
+
Return Home
+
+ );
+}
diff --git a/src/app/page.module.css b/src/app/page.module.css
deleted file mode 100644
index 6676d2c..0000000
--- a/src/app/page.module.css
+++ /dev/null
@@ -1,229 +0,0 @@
-.main {
- display: flex;
- flex-direction: column;
- justify-content: space-between;
- align-items: center;
- padding: 6rem;
- min-height: 100vh;
-}
-
-.description {
- display: inherit;
- justify-content: inherit;
- align-items: inherit;
- font-size: 0.85rem;
- max-width: var(--max-width);
- width: 100%;
- z-index: 2;
- font-family: var(--font-mono);
-}
-
-.description a {
- display: flex;
- justify-content: center;
- align-items: center;
- gap: 0.5rem;
-}
-
-.description p {
- position: relative;
- margin: 0;
- padding: 1rem;
- background-color: rgba(var(--callout-rgb), 0.5);
- border: 1px solid rgba(var(--callout-border-rgb), 0.3);
- border-radius: var(--border-radius);
-}
-
-.code {
- font-weight: 700;
- font-family: var(--font-mono);
-}
-
-.grid {
- display: grid;
- grid-template-columns: repeat(4, minmax(25%, auto));
- max-width: 100%;
- width: var(--max-width);
-}
-
-.card {
- padding: 1rem 1.2rem;
- border-radius: var(--border-radius);
- background: rgba(var(--card-rgb), 0);
- border: 1px solid rgba(var(--card-border-rgb), 0);
- transition: background 200ms, border 200ms;
-}
-
-.card span {
- display: inline-block;
- transition: transform 200ms;
-}
-
-.card h2 {
- font-weight: 600;
- margin-bottom: 0.7rem;
-}
-
-.card p {
- margin: 0;
- opacity: 0.6;
- font-size: 0.9rem;
- line-height: 1.5;
- max-width: 30ch;
-}
-
-.center {
- display: flex;
- justify-content: center;
- align-items: center;
- position: relative;
- padding: 4rem 0;
-}
-
-.center::before {
- background: var(--secondary-glow);
- border-radius: 50%;
- width: 480px;
- height: 360px;
- margin-left: -400px;
-}
-
-.center::after {
- background: var(--primary-glow);
- width: 240px;
- height: 180px;
- z-index: -1;
-}
-
-.center::before,
-.center::after {
- content: '';
- left: 50%;
- position: absolute;
- filter: blur(45px);
- transform: translateZ(0);
-}
-
-.logo {
- position: relative;
-}
-/* Enable hover only on non-touch devices */
-@media (hover: hover) and (pointer: fine) {
- .card:hover {
- background: rgba(var(--card-rgb), 0.1);
- border: 1px solid rgba(var(--card-border-rgb), 0.15);
- }
-
- .card:hover span {
- transform: translateX(4px);
- }
-}
-
-@media (prefers-reduced-motion) {
- .card:hover span {
- transform: none;
- }
-}
-
-/* Mobile */
-@media (max-width: 700px) {
- .content {
- padding: 4rem;
- }
-
- .grid {
- grid-template-columns: 1fr;
- margin-bottom: 120px;
- max-width: 320px;
- text-align: center;
- }
-
- .card {
- padding: 1rem 2.5rem;
- }
-
- .card h2 {
- margin-bottom: 0.5rem;
- }
-
- .center {
- padding: 8rem 0 6rem;
- }
-
- .center::before {
- transform: none;
- height: 300px;
- }
-
- .description {
- font-size: 0.8rem;
- }
-
- .description a {
- padding: 1rem;
- }
-
- .description p,
- .description div {
- display: flex;
- justify-content: center;
- position: fixed;
- width: 100%;
- }
-
- .description p {
- align-items: center;
- inset: 0 0 auto;
- padding: 2rem 1rem 1.4rem;
- border-radius: 0;
- border: none;
- border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25);
- background: linear-gradient(
- to bottom,
- rgba(var(--background-start-rgb), 1),
- rgba(var(--callout-rgb), 0.5)
- );
- background-clip: padding-box;
- backdrop-filter: blur(24px);
- }
-
- .description div {
- align-items: flex-end;
- pointer-events: none;
- inset: auto 0 0;
- padding: 2rem;
- height: 200px;
- background: linear-gradient(
- to bottom,
- transparent 0%,
- rgb(var(--background-end-rgb)) 40%
- );
- z-index: 1;
- }
-}
-
-/* Tablet and Smaller Desktop */
-@media (min-width: 701px) and (max-width: 1120px) {
- .grid {
- grid-template-columns: repeat(2, 50%);
- }
-}
-
-@media (prefers-color-scheme: dark) {
- .vercelLogo {
- filter: invert(1);
- }
-
- .logo {
- filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70);
- }
-}
-
-@keyframes rotate {
- from {
- transform: rotate(360deg);
- }
- to {
- transform: rotate(0deg);
- }
-}
diff --git a/src/app/page.tsx b/src/app/page.tsx
deleted file mode 100644
index 657e775..0000000
--- a/src/app/page.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-import Image from 'next/image'
-import styles from './page.module.css'
-
-export default function Home() {
- return (
-
-
-
- Get started by editing
- src/app/page.tsx
-
-
-
-
-
-
-
-
-
-
- )
-}
diff --git a/src/config/const.ts b/src/config/const.ts
new file mode 100644
index 0000000..614c789
--- /dev/null
+++ b/src/config/const.ts
@@ -0,0 +1,40 @@
+export const MAX_LENGTH = {
+ MEMO: 500,
+ SCRIPT: 5000,
+ TITLE: 20,
+} as const;
+
+export const VALIDATION_MESSAGE = {
+ MEMO: {
+ MAX_LENGTH: `${MAX_LENGTH.MEMO}자 이내로 작성해 주세요.`,
+ },
+
+ SCRIPT: {
+ MAX_LENGTH: `${MAX_LENGTH.SCRIPT}자 이내로 작성해 주세요.`,
+ REQUIRED: `대본은 필수 입력 입니다.`,
+ },
+
+ TITLE: {
+ MAX_LENGTH: `${MAX_LENGTH.TITLE}자 이내로 작성해 주세요`,
+ REQUIRED: `발표 이름은 필수 입력 입니다.`,
+ },
+
+ DEADLINEDATE: {
+ REQUIRED: `입력한 날짜를 확인해 주세요.`,
+ },
+} as const;
+
+export const ERROR_MESSAGE = {
+ AUTH: {
+ EXIST: '이미 로그인 된 상태입니다.',
+ ERROR: '로그인 과정에서 문제가 발생했습니다',
+ EXPIRE: '다시 로그인 해주세요',
+ },
+ USER: {
+ ERROR: '유저 정보를 가져오는데 문제가 발생했습니다',
+ },
+ SERVICE: {
+ ERROR: '문제가 발생했습니다',
+ RETRY: '재시도 중 문제가 발생했습니다',
+ },
+} as const;
diff --git a/src/config/path.ts b/src/config/path.ts
new file mode 100644
index 0000000..7e06dac
--- /dev/null
+++ b/src/config/path.ts
@@ -0,0 +1,8 @@
+export const SOCIAL_ACCESS_URL = {
+ KAKAO:
+ process.env.NODE_ENV === 'development'
+ ? `https://kauth.kakao.com/oauth/authorize?response_type=code&client_id=${process.env.NEXT_PUBLIC_KAKAO_API_KEY}&redirect_uri=${process.env.NEXT_PUBLIC_ROUTE_HANDLER}/accounts/login/process`
+ : `https://kauth.kakao.com/oauth/authorize?response_type=code&client_id=${process.env.NEXT_PUBLIC_KAKAO_API_KEY}&redirect_uri=${process.env.NEXT_PUBLIC_BASE_URL_PROD}/accounts/login/process`,
+ NAVER: '',
+ GOOGLE: '',
+};
diff --git a/src/middleware.ts b/src/middleware.ts
new file mode 100644
index 0000000..9e04304
--- /dev/null
+++ b/src/middleware.ts
@@ -0,0 +1,45 @@
+import { NextRequest, NextResponse } from 'next/server';
+
+export const config = {
+ matcher: ['/((?!_next/static|_next/image|favicon.ico|fonts|images).*)'],
+};
+
+export async function middleware(request: NextRequest) {
+ const currentPath = request.nextUrl.pathname;
+ const accessToken = request.cookies.get('accessToken')?.value;
+ const refreshToken = request.cookies.get('refreshToken')?.value;
+
+ if (
+ !refreshToken &&
+ (currentPath.startsWith('/home') ||
+ currentPath.startsWith('/upload') ||
+ currentPath.startsWith('/setting') ||
+ currentPath.startsWith('/practice') ||
+ currentPath.startsWith('/feedback'))
+ ) {
+ const url = request.nextUrl.clone();
+ url.pathname = '/login';
+ return NextResponse.redirect(url);
+ }
+
+ if (refreshToken && currentPath.startsWith('/login')) {
+ const url = request.nextUrl.clone();
+ url.pathname = '/home';
+ return NextResponse.redirect(url);
+ }
+
+ return NextResponse.next();
+}
+
+function getTokenFromCookies(request: NextRequest) {
+ const cookiesHeader = request.headers.get('cookie');
+ if (!cookiesHeader) return null;
+
+ const cookiesArray: [string, string][] = cookiesHeader.split('; ').map((cookie) => {
+ const [key, value] = cookie.split('=');
+ return [key, value];
+ });
+
+ const cookies = new Map(cookiesArray);
+ return cookies;
+}
diff --git a/src/services/client/feedback.ts b/src/services/client/feedback.ts
new file mode 100644
index 0000000..7df5d60
--- /dev/null
+++ b/src/services/client/feedback.ts
@@ -0,0 +1,22 @@
+import { fetch_ClientAuth } from './fetchClient';
+
+export const clientFeedbackApi = {
+ getFeedbackInfo: async (feedbackId: number) => {
+ const response = await fetch_ClientAuth(`/api/feedbacks/${feedbackId}`, {
+ method: 'GET',
+ });
+ if (response.ok) return response;
+
+ const errorBody = await response.json();
+ throw new Error(errorBody.message || '데이터를 불러오는 도중 문제가 발생했습니다');
+ },
+ getFeedbackList: async ({ pageParam }: { pageParam?: number }) => {
+ const response = await fetch_ClientAuth(`/api/feedbacks?page=${pageParam}&size=6`, {
+ method: 'GET',
+ });
+ if (response.ok) return response;
+
+ const errorBody = await response.json();
+ throw new Error(errorBody.message || '데이터를 불러오는 도중 문제가 발생했습니다');
+ },
+};
diff --git a/src/services/client/fetchClient.ts b/src/services/client/fetchClient.ts
new file mode 100644
index 0000000..c7f13f0
--- /dev/null
+++ b/src/services/client/fetchClient.ts
@@ -0,0 +1,49 @@
+/** 클라이언트 컴포넌트에서 사용하는 인증 기반 fetch
+ *
+ * @param url: 프록시 기반 백엔드 api
+ * @param options: fetch 옵션 객체
+ */
+
+import { ERROR_MESSAGE } from '@/config/const';
+
+export const fetch_ClientAuth = async (url: string, options: RequestInit = {}) => {
+ let response = await fetch(url, {
+ credentials: 'include',
+ ...options,
+ });
+
+ // 첫번째 fetch이후, fetch_ClientAuth 만의 토큰 재발급 관련 에러는 여기서 처리.
+ // 토큰 만료 시
+ if (response.status === 401) {
+ try {
+ // accessToken 재발급
+ const clientUrl = `/api/accounts/reissue`;
+ const accessTokenResponse = await fetch(clientUrl, {
+ method: 'GET',
+ cache: 'no-store',
+ credentials: 'include',
+ });
+
+ if (accessTokenResponse.ok) {
+ response = await fetch(url, {
+ ...options,
+ cache: 'no-store',
+ credentials: 'include',
+ });
+ // if (!response.ok) throw new Error(ERROR_MESSAGE.SERVICE.RETRY); // 재요청에 대한 에러도 그대로 반환
+ } else {
+ // refreshToken의 부재로 재발급이 불가능
+ throw new Error(ERROR_MESSAGE.AUTH.EXPIRE);
+ }
+ } catch (e) {
+ if (e instanceof Error) {
+ // 토큰 재발급 과정에서 에러가 발생하면 로그인 페이지로 이동
+ alert(e.message);
+ window.location.href = '/login';
+ }
+ }
+ }
+
+ // 기존 요청에 대한 성공or에러를 그대로 반환
+ return response;
+};
diff --git a/src/services/client/file.ts b/src/services/client/file.ts
new file mode 100644
index 0000000..b543579
--- /dev/null
+++ b/src/services/client/file.ts
@@ -0,0 +1,19 @@
+import { UploadFile } from '@/types/service';
+import { fetch_ClientAuth } from './fetchClient';
+
+export const FileService = {
+ fileUpload: async (file: File | Blob, filename?: string) => {
+ const formData = new FormData();
+ formData.append('file', file, filename);
+ console.log('form data : ', formData);
+
+ const response = await fetch_ClientAuth(`/api/files/upload`, {
+ method: 'POST',
+ body: formData,
+ });
+
+ const data: UploadFile = await response.json();
+
+ return data;
+ },
+};
diff --git a/src/services/client/home.ts b/src/services/client/home.ts
new file mode 100644
index 0000000..59c389e
--- /dev/null
+++ b/src/services/client/home.ts
@@ -0,0 +1,52 @@
+import { fetch_ClientAuth } from './fetchClient';
+
+export const clientHomeApi = {
+ getPresentationList: async ({ pageParam }: { pageParam?: number }) => {
+ const response = await fetch_ClientAuth(`/api/presentations?page=${pageParam}&size=6`, {
+ method: 'GET',
+ cache: 'no-store',
+ });
+
+ if (!response.ok) {
+ const errorBody = await response.json();
+ throw new Error(errorBody.message || '데이터를 불러오는 도중 문제가 발생 했습니다');
+ }
+
+ return response;
+ },
+
+ getLatestPresentation: async () => {
+ const response = await fetch_ClientAuth(`/api/presentations/latest`, {
+ method: 'GET',
+ cache: 'no-store',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ });
+
+ if (!response.ok) {
+ const errorBody = await response.json();
+ throw new Error(errorBody.message || '데이터를 불러오는 도중 문제가 발생 했습니다');
+ }
+
+ return response;
+ },
+
+ deletePresentationList: async (presentationIds: { presentationIds: number[] }) => {
+ const response = await fetch_ClientAuth(`/api/presentations`, {
+ method: 'DELETE',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ cache: 'no-store',
+ body: JSON.stringify(presentationIds),
+ });
+
+ if (!response.ok) {
+ const errorBody = await response.json();
+ throw new Error(errorBody.message || '데이터를 삭제하는 도중 문제가 발생 했습니다');
+ }
+
+ return response;
+ },
+};
diff --git a/src/services/client/practice.ts b/src/services/client/practice.ts
new file mode 100644
index 0000000..b1339e6
--- /dev/null
+++ b/src/services/client/practice.ts
@@ -0,0 +1,46 @@
+import { SavePracticeParams } from '@/types/service';
+import { fetch_ClientAuth } from './fetchClient';
+
+export const PracticeService = {
+ /** 발표 상세 조회 */
+ getPracticeDetail: async (presentationId: number) => {
+ const response = await fetch_ClientAuth(`/api/practices/presentation/${presentationId}`, {
+ method: 'GET',
+ cache: 'no-store',
+ });
+
+ if (response.ok) return await response.json();
+
+ throw new Error('데이터를 불러오는 도중 문제가 발생했습니다');
+ },
+ /** 다음 슬라이드 모달 비활성화 */
+ patchDeactiveModal: async () => {
+ const response = await fetch_ClientAuth('/api/practices/deactivate-modal', {
+ method: 'PATCH',
+ });
+
+ if (!response.ok) {
+ const errorBody = await response.json();
+ throw new Error(errorBody.message || '데이터를 저장하는 도중 문제가 발생 했습니다');
+ }
+
+ return response;
+ },
+ /** 발표 연습 완료된 페이지(슬라이드) 저장 */
+ patchSlide: async (slideId: number, data: SavePracticeParams) => {
+ const response = await fetch_ClientAuth(`/api/slides/${slideId}`, {
+ method: 'PATCH',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(data),
+ });
+
+ if (!response.ok) {
+ const errorBody = await response.json();
+ throw new Error(errorBody.message || '데이터를 저장하는 도중 문제가 발생 했습니다');
+ }
+
+ return response;
+ },
+};
diff --git a/src/services/client/setting.ts b/src/services/client/setting.ts
new file mode 100644
index 0000000..8f60e6f
--- /dev/null
+++ b/src/services/client/setting.ts
@@ -0,0 +1,41 @@
+import { SlidesSettingType } from '@/types/service';
+import { fetch_ClientAuth } from './fetchClient';
+
+export const clientSettingApi = {
+ /**
+ * 클라이언트 컴포넌트에서, 발표 연습 세팅 데이터를 가져오는 함수
+ * @return 발표 연습 세팅 데이터를 반환 합니다
+ */
+ getPresentationSettingData: async (presentationId: number) => {
+ const response = await fetch_ClientAuth(`/api/practices/presentation/${presentationId}`, {
+ method: 'GET',
+ cache: 'no-store',
+ });
+ if (response.ok) return response;
+
+ throw new Error('데이터를 불러오는 도중 문제가 발생했습니다');
+ },
+
+ /**
+ * 클라이언트 컴포넌트에서, 발표 연습 세팅이 완료된 객체를 저장하는 함수
+ * @return void
+ */
+ patchSettingInfo: async (settingInfo: SlidesSettingType, presentationId: number) => {
+ const response = await fetch_ClientAuth(`/api/practices/presentation/${presentationId}`, {
+ method: 'PATCH',
+ cache: 'no-store',
+ body: JSON.stringify(settingInfo),
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ });
+
+ // mutation의 onError로 전달
+ if (!response.ok) {
+ const errorBody = await response.json();
+ throw new Error(errorBody.message || '데이터를 저장하는 도중 문제가 발생 했습니다');
+ }
+
+ return response;
+ },
+};
diff --git a/src/services/client/upload.ts b/src/services/client/upload.ts
new file mode 100644
index 0000000..804aae3
--- /dev/null
+++ b/src/services/client/upload.ts
@@ -0,0 +1,61 @@
+import { UploadDataType } from '@/types/service';
+import { fetch_ClientAuth } from './fetchClient';
+import { ERROR_MESSAGE } from '@/config/const';
+
+export const clientPptApi = {
+ postPresentationUpload: async (data: UploadDataType) => {
+ const response = await fetch_ClientAuth(`/api/presentations`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(data),
+ });
+ return response;
+ },
+
+ getPresentationData: async (presentationId: number) => {
+ const response = await fetch_ClientAuth(`/api/presentations/${presentationId}`, {
+ method: 'GET',
+ });
+ // const delay = (ms: any) => new Promise((resolve) => setTimeout(resolve, ms));
+ // await delay(3000);
+
+ return response;
+ },
+
+ patchPresentationData: async (presentationId: number, data: UploadDataType) => {
+ const response = await fetch_ClientAuth(`/api/presentations/${presentationId}`, {
+ method: 'PATCH',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(data),
+ });
+
+ // mutation의 onError로 전달
+ if (!response.ok) {
+ const errorBody = await response.json();
+ throw new Error(errorBody.message || '데이터를 저장하는 도중 문제가 발생 했습니다');
+ }
+
+ return response;
+ },
+
+ // mock
+ // TODO: 백엔드 api로 변경 및 삭제 예정
+ getMockPresentData: async (id: string) => {
+ const response = await fetch(`${process.env.NEXT_PUBLIC_ROUTE_HANDLER}/api/get/list/${id}`, {
+ cache: 'no-store',
+ credentials: 'include',
+ });
+
+ if (!response.ok) {
+ throw new Error('something went to wrong');
+ }
+
+ const result = await response.json();
+
+ return result as T;
+ },
+};
diff --git a/src/services/client/user.ts b/src/services/client/user.ts
new file mode 100644
index 0000000..7fbe45c
--- /dev/null
+++ b/src/services/client/user.ts
@@ -0,0 +1,14 @@
+import { fetch_ClientAuth } from './fetchClient';
+
+export const UserService = {
+ getSessionId: async () => {
+ const response = await fetch_ClientAuth(`/api/accounts/sessionId`, {
+ method: 'GET',
+ cache: 'no-store',
+ });
+
+ if (response.ok) return await response.json();
+
+ throw new Error('데이터를 불러오는 도중 문제가 발생했습니다');
+ },
+};
diff --git a/src/services/server/feedback.ts b/src/services/server/feedback.ts
new file mode 100644
index 0000000..c888ce1
--- /dev/null
+++ b/src/services/server/feedback.ts
@@ -0,0 +1,28 @@
+import { fetch_ServerAuth } from './fetchServer';
+import { SERVER_BASE_URL } from './serverApiBaseURL';
+
+export const serverFeedbackApi = {
+ getFeedbackInfo: async (feedbackId: number) => {
+ const response = await fetch_ServerAuth(`${SERVER_BASE_URL}/api/feedbacks/${feedbackId}`, {
+ method: 'GET',
+ });
+ // console.log('response');
+ // console.log(response);
+ if (response.ok) return response;
+
+ const errorBody = await response.json();
+ throw new Error(errorBody.message || '데이터를 불러오는 도중 문제가 발생했습니다');
+ },
+ getFeedbackList: async ({ pageParam }: { pageParam?: number }) => {
+ const response = await fetch_ServerAuth(
+ `${SERVER_BASE_URL}/api/feedbacks?page=${pageParam}&size=6`,
+ {
+ method: 'GET',
+ },
+ );
+ if (response.ok) return response;
+
+ const errorBody = await response.json();
+ throw new Error(errorBody.message || '데이터를 불러오는 도중 문제가 발생했습니다');
+ },
+};
diff --git a/src/services/server/fetchServer.ts b/src/services/server/fetchServer.ts
new file mode 100644
index 0000000..6cff709
--- /dev/null
+++ b/src/services/server/fetchServer.ts
@@ -0,0 +1,20 @@
+/** 서버 컴포넌트에서 사용하는 인증 기반 fetch
+ *
+ * @param url: 실제 백엔드 api(프록시 X)
+ * @param options: fetch 옵션 객체
+ */
+
+import { cookies } from 'next/headers';
+
+export const fetch_ServerAuth = async (url: string, options: RequestInit = {}) => {
+ const response = await fetch(url, {
+ headers: { Cookie: cookies().toString() },
+ ...options,
+ });
+
+ if (response.status === 401) {
+ throw new Error('reIssue');
+ }
+
+ return response;
+};
diff --git a/src/services/server/home.ts b/src/services/server/home.ts
new file mode 100644
index 0000000..20e85b7
--- /dev/null
+++ b/src/services/server/home.ts
@@ -0,0 +1,27 @@
+import { fetch_ServerAuth } from './fetchServer';
+import { SERVER_BASE_URL } from './serverApiBaseURL';
+
+export const serverHomeApi = {
+ getPresentationList: async ({ pageParam }: { pageParam?: number }) => {
+ const response = await fetch_ServerAuth(
+ `${SERVER_BASE_URL}/api/presentations?page=${pageParam}&size=6`,
+ { method: 'GET', cache: 'no-store' },
+ );
+
+ if (response.ok) return response;
+ throw new Error('데이터를 불러오는 도중 문제가 발생했습니다');
+ },
+
+ getLatestPresentation: async () => {
+ const response = await fetch_ServerAuth(`${SERVER_BASE_URL}/api/presentations/latest`, {
+ method: 'GET',
+ cache: 'no-store',
+ // headers: {
+ // 'Content-Type': 'application/json',
+ // },
+ });
+
+ if (response.ok) return response;
+ throw new Error('데이터를 불러오는 도중 문제가 발생했습니다');
+ },
+};
diff --git a/src/services/server/serverApiBaseURL.ts b/src/services/server/serverApiBaseURL.ts
new file mode 100644
index 0000000..b6ed61f
--- /dev/null
+++ b/src/services/server/serverApiBaseURL.ts
@@ -0,0 +1,4 @@
+export const SERVER_BASE_URL =
+ process.env.NODE_ENV === 'development'
+ ? process.env.NEXT_PUBLIC_BASE_URL_DEV
+ : process.env.NEXT_PUBLIC_BASE_URL_PROD;
diff --git a/src/services/server/setting.ts b/src/services/server/setting.ts
new file mode 100644
index 0000000..51552c5
--- /dev/null
+++ b/src/services/server/setting.ts
@@ -0,0 +1,19 @@
+import { fetch_ServerAuth } from './fetchServer';
+import { SERVER_BASE_URL } from './serverApiBaseURL';
+
+export const serverSettingApi = {
+ /**
+ * 서버 컴포넌트에서, 발표 연습 세팅 데이터를 가져오는 함수
+ * @return 발표 연습 세팅 데이터를 반환 합니다
+ */
+ getPresentationSettingData: async (presentationId: number) => {
+ const response = await fetch_ServerAuth(
+ `${SERVER_BASE_URL}/api/practices/presentation/${presentationId}`,
+ { method: 'GET', cache: 'no-store' },
+ );
+
+ if (response.ok) return response;
+
+ throw new Error('데이터를 불러오는 도중 문제가 발생했습니다');
+ },
+};
diff --git a/src/services/server/upload.ts b/src/services/server/upload.ts
new file mode 100644
index 0000000..97f6002
--- /dev/null
+++ b/src/services/server/upload.ts
@@ -0,0 +1,18 @@
+import { UploadDataType } from '@/types/service';
+import { fetch_ServerAuth } from './fetchServer';
+import { SERVER_BASE_URL } from './serverApiBaseURL';
+
+export const serverPptApi = {
+ getPresentationData: async (presentationId: number) => {
+ const response = await fetch_ServerAuth(
+ `${SERVER_BASE_URL}/api/presentations/${presentationId}`,
+ {
+ method: 'GET',
+ },
+ );
+ // const delay = (ms: any) => new Promise((resolve) => setTimeout(resolve, ms));
+ // await delay(3000);
+
+ return response;
+ },
+};
diff --git a/src/services/server/user.ts b/src/services/server/user.ts
new file mode 100644
index 0000000..cfdea4b
--- /dev/null
+++ b/src/services/server/user.ts
@@ -0,0 +1,18 @@
+import { cookies } from 'next/headers';
+import { fetch_ServerAuth } from './fetchServer';
+import { SERVER_BASE_URL } from './serverApiBaseURL';
+
+export const serverUserApi = {
+ /**
+ * 서버 컴포넌트에서, 유저 정보를 가져오는 함수
+ * @return 유저 정보 객체를 반환합니다
+ */
+ getUserInfo: async () => {
+ const res = await fetch_ServerAuth(`${SERVER_BASE_URL}/api/accounts/me`, {
+ method: 'GET',
+ headers: { Cookie: cookies().toString() },
+ cache: 'no-store',
+ });
+ return res;
+ },
+};
diff --git a/src/store/flyout.ts b/src/store/flyout.ts
new file mode 100644
index 0000000..9b7ee9c
--- /dev/null
+++ b/src/store/flyout.ts
@@ -0,0 +1,13 @@
+import { create } from 'zustand';
+
+interface FlyOutStore {
+ isOpen: boolean;
+ toggle: () => void;
+}
+
+export const useFlyoutStore = create((set) => ({
+ isOpen: false,
+ toggle: () => {
+ set((state) => ({ isOpen: !state.isOpen }));
+ },
+}));
diff --git a/src/store/modal.ts b/src/store/modal.ts
new file mode 100644
index 0000000..a938d99
--- /dev/null
+++ b/src/store/modal.ts
@@ -0,0 +1,78 @@
+import { ReactNode } from 'react';
+import { create } from 'zustand';
+
+/** 모달창에 사용되는 데이터 타입*/
+export type ModalData = {
+ /** 모달창 내부에 사용되는 컨텐츠 */
+ content?: ReactNode;
+
+ /** 취소버튼 관련 데이터(이벤트 콜백, 버튼 컨텐츠)*/
+ onCancelButton?: ReactNode;
+
+ /** submit버튼 관련 데이터(이벤트 콜백, 버튼 컨텐츠)*/
+ onSubmitButton?: ReactNode;
+};
+
+/** 모달 스토어 타입 */
+interface ModalStore {
+ /** 현재 모달 렌더링 유무 플래그 */
+ isOpen: boolean;
+
+ /** 인자로 받은 모달 데이터를 기반으로 모달을 생성하는 함수 */
+ openModal: (modalData: ModalData) => unknown;
+
+ /** 모달을 닫는 함수(DOM에서 제거) */
+ closeModal: () => unknown;
+
+ /** 모달창에 사용되는 데이터 */
+ modalData: ModalData;
+}
+
+/** 토스트창에 사용되는 데이터 타입*/
+export type ToastData = {
+ /** 토스트창 컨텐츠 */
+ content?: ReactNode;
+};
+
+/** 토스트 스토어 타입 */
+interface ToastStore {
+ /** 현재 토스트 렌더링 유무 플래그 */
+ isOpen: boolean;
+
+ /** 인자로 받은 토스트 데이터를 기반으로 토스트를 생성하는 함수 */
+ openToast: (toastData: ToastData) => unknown;
+
+ /** 토스트를 닫는 함수(DOM에서 제거) */
+ closeToast: () => unknown;
+
+ /** 토스트창에 사용되는 데이터 */
+ toastData: ToastData;
+}
+
+/** confirm과 alert에 사용되는 스토어 */
+export const useModalStore = create((set) => ({
+ isOpen: false,
+ modalData: {} as ModalData,
+
+ openModal: (modalData: ModalData) => {
+ set((state) => ({ isOpen: true, modalData: { ...modalData } }));
+ },
+
+ closeModal: () => {
+ set((state) => ({ isOpen: false, modalData: {} }));
+ },
+}));
+
+/** toast에 사용되는 스토어 */
+export const useToastStore = create((set) => ({
+ isOpen: false,
+ toastData: {} as ToastData,
+
+ openToast: (toastData: ToastData) => {
+ set((state) => ({ isOpen: true, toastData: { ...toastData } }));
+ },
+
+ closeToast: () => {
+ set((state) => ({ isOpen: false, toastData: {} }));
+ },
+}));
diff --git a/src/store/user.ts b/src/store/user.ts
new file mode 100644
index 0000000..9b80c0c
--- /dev/null
+++ b/src/store/user.ts
@@ -0,0 +1,34 @@
+import { UserInfoType } from '@/types/service';
+import { create } from 'zustand';
+import { persist } from 'zustand/middleware';
+
+interface UserStore {
+ userInfo: UserInfoType;
+ setUserInfo: (userInfo: UserInfoType) => void;
+ deleteUserInfo: () => void;
+}
+
+const defaultState: UserInfoType = { email: '', nickName: '', socialProvider: '' };
+
+export const useUserInfoStore = create(
+ persist(
+ (set) => ({
+ userInfo: { ...defaultState },
+ setUserInfo: (userInfo: UserInfoType) => {
+ set((state) => ({
+ ...state,
+ userInfo: { ...userInfo },
+ }));
+ },
+ deleteUserInfo: () => {
+ set((state) => ({
+ ...state,
+ userInfo: { ...defaultState },
+ }));
+ },
+ }),
+ {
+ name: 'userInfo',
+ },
+ ),
+);
diff --git a/src/styles/globals.scss b/src/styles/globals.scss
new file mode 100644
index 0000000..c871a9b
--- /dev/null
+++ b/src/styles/globals.scss
@@ -0,0 +1,95 @@
+/** Color - gray */
+$gray-10: #1e1e1e;
+$gray-9: #393939;
+$gray-8: #4b4b4b;
+$gray-7: #5e5e5e;
+$gray-6: #727272;
+$gray-5: #868686;
+$gray-4: #9b9b9b;
+$gray-3: #b0b0b0;
+$gray-2: #c6c6c6;
+$gray-1: #dddd;
+$gray-0: #f3f3f3;
+
+/** Color - purple */
+$purple-9: #0000e6;
+$purple-8: #2507eb;
+$purple-7: #4312f0;
+$purple-6: #5a1df8;
+$purple-5: #6822ff; // primary
+$purple-4: #854cff;
+$purple-3: #9f70ff;
+$purple-2: #bc9bff;
+$purple-1: #d7c4fe;
+$purple-0: #f0e7ff;
+
+/** Color - Pink */
+$pink-0: #ffe4ed;
+$pink-1: #ffbad1;
+$pink-2: #ff8db3;
+$pink-3: #ff5b94;
+$pink-4: #ff2e7b;
+$pink-5: #fe0062;
+$pink-6: #ed0060;
+$pink-7: #d7005c;
+$pink-8: #c20059;
+$pink-9: #9c0054;
+
+/** Color - green */
+$green-1: #009b00;
+$green-2: #06be34;
+
+/** Color - yellow */
+$yellow: #f8e049;
+
+/** Color - red */
+$red-7: #de3428;
+$red-5: #ff4a2e;
+$red-3: #ef7771;
+
+/** Color - black, white */
+$white: #ffff;
+$black: $gray-10;
+
+/** Color - value */
+$primary: $purple-5;
+$secondary: $gray-6;
+$error: $red-5;
+
+/** Font - Headline */
+$h1: 32px;
+$h2: 24px;
+$h3: 18px;
+$h4: 16px;
+$h5: 14px;
+
+/** Font - Body */
+$font-1: 18px;
+$font-2: 16px;
+$font-3: 14px;
+$font-4: 12px;
+
+/** # textarea */
+// 사이즈
+$textarea-size-md: (
+ height: 150px,
+ padding: 20px 20px,
+ font-size: 0.9rem,
+);
+$textarea-size-lg: (
+ height: 350px,
+ padding: 20px 30px,
+ font-size: 1.1rem,
+);
+
+// 너비
+$textarea-width-full: (
+ width: 100%,
+);
+
+// 색상 테마
+$textarea-theme-gray: (
+ background-color: $gray-1,
+ color: black,
+ border-radius: 15px,
+);
diff --git a/src/styles/mixins.scss b/src/styles/mixins.scss
new file mode 100644
index 0000000..fe8c50e
--- /dev/null
+++ b/src/styles/mixins.scss
@@ -0,0 +1,192 @@
+@import '@/styles/globals';
+
+$tmp-backgroud-color: #d7d7d7;
+
+@function px-to-rem($size, $base-font-size: $font-2) {
+ @return $size / $base-font-size * 1rem;
+}
+
+// line
+@mixin line($lineCount: 1) {
+ /* 사용법 : @include line(라인수) */
+ @if $lineCount == 1 {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ } @else if $lineCount == 'auto' {
+ display: block;
+ overflow: visible;
+ } @else {
+ /* autoprefixer: off */
+ -webkit-box-orient: vertical;
+
+ /* autoprefixer: on */
+ display: block;
+ display: -webkit-box;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ word-break: break-all;
+ -webkit-line-clamp: $lineCount;
+ }
+}
+
+// # etc
+@mixin flex-center {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+@mixin pure-button {
+ background-color: transparent;
+ cursor: pointer;
+ border: none;
+}
+
+// # 해상도
+@mixin content-width {
+ width: 1440px;
+}
+
+// # textarea
+@mixin textarea-size($size) {
+ height: map-get($size, height);
+ padding: map-get($size, padding);
+ font-size: map-get($size, font-size);
+}
+
+@mixin textarea-width($width) {
+ width: map-get($width, width);
+}
+
+@mixin textarea-theme($theme) {
+ background-color: map-get($theme, background-color);
+ color: map-get($theme, color);
+ border-radius: map-get($theme, border-radius);
+}
+
+// # button
+
+// ## size
+// ### 웹 버튼 사이즈 (모달, 그 외 기본 버튼(삭제하기,확인,..))
+@mixin button_size_web {
+ width: 227px;
+ height: 56px;
+ font-size: px-to-rem($h2);
+
+ // 내부 글씨 중앙 정렬
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding-top: 12px;
+ padding-bottom: 12px;
+ border-radius: 12px;
+ cursor: pointer;
+}
+
+// ### 모바일 모달 confirm 버튼 사이즈
+@mixin button_size_mobile_modal_confirm {
+ width: 128px;
+ height: 48px;
+ font-size: $font-2;
+
+ // 내부 글씨 중앙 정렬
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding-top: 8px;
+ padding-bottom: 8px;
+ border-radius: 12px;
+ cursor: pointer;
+}
+
+// ### 모바일 모달 alert 버튼 사이즈
+@mixin button_size_mobile_modal_alert {
+ width: 264px;
+ height: 48px;
+ font-size: $font-2;
+
+ // 내부 글씨 중앙 정렬
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding-top: 8px;
+ padding-bottom: 8px;
+ border-radius: 12px;
+}
+
+// ## theme
+
+// ### 버튼 default 테마
+@mixin button_theme_default {
+ background-color: $gray-9;
+ color: $white;
+ border: none;
+ &:hover {
+ background-color: $primary;
+ cursor: pointer;
+ }
+ &:active {
+ background-color: $gray-9;
+ cursor: pointer;
+ }
+ &:disabled {
+ background-color: $gray-3;
+ cursor: not-allowed;
+ }
+}
+
+// ### 버튼 inverted 테마
+@mixin button_theme_inverted {
+ background-color: $white;
+ color: $primary;
+ border: 1px solid $primary;
+ &:hover {
+ color: $purple-4;
+ border: 1px solid $purple-4;
+ cursor: pointer;
+ }
+ &:active {
+ background-color: $white;
+ color: $primary;
+ cursor: pointer;
+ }
+ &:disabled {
+ color: $gray-2;
+ border: 1px solid $gray-0;
+ cursor: not-allowed;
+ }
+}
+
+// ### 버튼 lined 테마
+@mixin button_theme_lined {
+ background-color: $white;
+ color: $secondary;
+ border: 1px solid $gray-3;
+ &:hover {
+ color: $gray-4;
+ border: 1px solid $gray-2;
+ cursor: pointer;
+ }
+ &:active {
+ background-color: $white;
+ color: $secondary;
+ border: 1px solid $gray-3;
+ cursor: pointer;
+ }
+}
+
+// ### 버튼 error 테마
+@mixin button_theme_error {
+ background-color: $error;
+ color: $white;
+ border: none;
+ &:hover {
+ background-color: $error;
+ cursor: pointer;
+ }
+ &:active {
+ background-color: $error;
+ cursor: pointer;
+ }
+}
diff --git a/src/types/common.ts b/src/types/common.ts
new file mode 100644
index 0000000..3876637
--- /dev/null
+++ b/src/types/common.ts
@@ -0,0 +1,5 @@
+import { ReactNode } from 'react';
+
+export interface ReactChildrenProps {
+ children: ReactNode;
+}
diff --git a/src/types/element.ts b/src/types/element.ts
new file mode 100644
index 0000000..cc7ac0d
--- /dev/null
+++ b/src/types/element.ts
@@ -0,0 +1,99 @@
+import {
+ ButtonHTMLAttributes,
+ InputHTMLAttributes,
+ LiHTMLAttributes,
+ ReactNode,
+ TextareaHTMLAttributes,
+} from 'react';
+
+// #region Button
+/** 버튼 컴포넌트 prop */
+export interface ButtonProps extends ButtonHTMLAttributes {
+ /** 버튼 내 자식 컴포넌트 */
+ _content: ReactNode; // TODO: 수정 논의 필요
+ /** 버튼 스타일 */
+ _className?: string;
+}
+// #endregion
+
+// #region List
+/** 단일 리스트(li) 타입 */
+interface SingleList extends LiHTMLAttributes {
+ /** li 내 자식 컴포넌트 */
+ _content: ReactNode; // TODO: 수정 논의 필요
+ /** li 태그 스타일 */
+ _className?: string;
+}
+
+/** ul안에 들어갈 li태그 배열
+ *
+ * TODO: 수정 논의 해봐요
+ */
+export type ListProps = SingleList[];
+// #endregion
+
+// #region Input
+/** input 태그 value 속성 타입 */
+type InputValue = InputHTMLAttributes['value'];
+
+/** 인풋 컴포넌트 prop */
+export interface InputProps extends InputHTMLAttributes {
+ /** 인풋 스타일 */
+ _className?: string;
+}
+
+/** textarea 컴포넌트 prop */
+export interface TextAreaProps extends TextareaHTMLAttributes {
+ /** textarea 테마 */
+ // theme?: 'presentation_memo' | 'presentation_script';
+ _className?: string;
+ size?: 'size_lg' | 'size_md';
+ width?: 'width_full';
+ theme?: 'theme_gray';
+ warning?: boolean;
+}
+
+/** 체크박스 컴포넌트 prop */
+export interface CheckboxProps extends InputHTMLAttributes {
+ /** 레이블 */
+ _label?: string;
+}
+
+/** 토글 버튼 컴포넌트 prop */
+export interface SwitchProps extends CheckboxProps {
+ /** 레이블 활성화 여부
+ *
+ * - true: 레이블 클릭 시에도 토글 버튼 on/off
+ * - false: 레이블 클릭 시 이벤트 없음
+ */
+ _activedLabel?: boolean;
+}
+
+/** 라디오 컴포넌트 prop */
+export interface RadioProps extends InputHTMLAttributes {
+ /** 레이블 */
+ _label?: string;
+ /** 선택된 값 */
+ _selectedValue: InputValue;
+ /** 선택된 값 변경 핸들러 */
+ _onChangeSelected: (value: InputValue) => void;
+}
+// #endregion
+
+// #region Custom
+/** 타임피커 컴포넌트 prop */
+export interface TimePickerProps {
+ /** 시간 타입 */
+ type: 'hour' | 'minute' | 'second';
+ /** 최소 값 (기본값 : 0)*/
+ min?: number;
+ /** 최대 값 (기본값: hour-23, minute, second-59)*/
+ max?: number;
+ /** 시간 간격 (기본값: hour-1, minute, second-30)*/
+ gap?: number;
+ /** 선택된 값 */
+ selectedValue: string;
+ /** 선택 값 변경 핸들러 */
+ onChange: (value: TimePickerProps['selectedValue']) => void;
+}
+// #endregion
diff --git a/src/types/guards.ts b/src/types/guards.ts
new file mode 100644
index 0000000..696b97d
--- /dev/null
+++ b/src/types/guards.ts
@@ -0,0 +1,13 @@
+import { CardListType, FeedbackListType, PresentationListType } from './service';
+
+export const PresentationListTypeGuard = (
+ data: CardListType,
+): data is PresentationListType['page']['content'][0] => {
+ return 'dday' in data;
+};
+
+export const FeedbackListTypeGuard = (
+ data: CardListType,
+): data is FeedbackListType['page']['content'][0] => {
+ return 'status' in data;
+};
diff --git a/src/types/service.ts b/src/types/service.ts
new file mode 100644
index 0000000..a019c97
--- /dev/null
+++ b/src/types/service.ts
@@ -0,0 +1,354 @@
+type ValuePiece = Date | null;
+
+export type Value = ValuePiece | [ValuePiece, ValuePiece];
+export type PracticeMode = 'SHOW' | 'HIDE';
+
+// # Mock
+
+export interface ValidtaionType {
+ title: string;
+ script: string;
+ memo: string;
+ deadlineDate: Value;
+}
+export interface MockUploadDataType {
+ id?: number;
+ dday?: number;
+ createdAt?: Date;
+ modifiedAt?: Date;
+ title: string | null;
+ timeLimit: {
+ hours: number | null;
+ minutes: number | null;
+ };
+ alertTime: {
+ hours: number | null;
+ minutes: number | null;
+ };
+ deadlineDate: Value;
+ slides: {
+ id?: number;
+ imageFileId: { dataURL: string | null; file: File | null }; // 변경 예정
+ script: string | null;
+ memo: string | null;
+ }[];
+}
+
+//
+
+// #region Account API
+/** 유저 정보를 나타내는 객체
+ * @property email - 소셜 이메일
+ * @property nickName - 소셜 닉네임
+ * @property socialProvider - 네이버, 카카오, 구글
+ */
+export interface UserInfoType {
+ email: string;
+ nickName: string;
+ socialProvider: string;
+}
+
+/** 세션 id 조회 res */
+export interface SessionId {
+ sessionId: string;
+}
+// #endregion
+
+// #region Presentation API
+/** 발표 자료 업로드 객체 - 발표 상세 조회, 생성 및 수정
+ * 기본타입 - 생성 및 수정에 사용되는 요소들
+ * 옵셔널타입 - 발표 상세 조회시, 추가되는 요소들
+ */
+export interface UploadDataType {
+ /** 발표 ID - URL에 라우팅 되는 값 */
+ id?: number;
+ /** 현재 날짜로부터 deadlineDate까지 남은 일수 */
+ dday?: number;
+ /** 최초 생성 시간 */
+ createdAt?: Date;
+ /** 마지막 수정 시간 */
+ modifiedAt?: Date;
+ /** 발표 제목 */
+ title: string | null;
+ /** 총 발표 시간 */
+ timeLimit: {
+ hours: number | null;
+ minutes: number | null;
+ };
+ /** 중간 알림 */
+ alertTime: {
+ hours: number | null;
+ minutes: number | null;
+ };
+ /** 발표 날짜 */
+ deadlineDate: Value;
+ /** ppt 슬라이드 리스트 */
+ slides: {
+ /** DB Identity Sequence. 백엔드 조회용 */
+ id?: number;
+ /** 이미지 파일 ID */
+ imageFileId: number | null;
+ /** 이미지 파일 URL */
+ imageFilePath: string | null;
+ /** 대본 */
+ script: string | null;
+ /** 메모 */
+ memo: string | null;
+ }[];
+}
+
+/** ppt 주요 문장 설정 정보 - 발표 설정 후 최종적으로 PATCH */
+export interface SlidesSettingType {
+ /** SHOW: 모든 문장 보기 HIDE: 외울 문장 가리기 */
+ practiceMode: PracticeMode;
+ /** ppt 슬라이드 리스트 */
+ slides: {
+ /** DB Identity Sequence. 백엔드 조회용 */
+ id: number;
+ /** 외울 문장 리스트 */
+ memorizationSentences: {
+ /** 외울 문장 시작 인덱스 */
+ offset: number;
+ /** 외울 문장 길이 (시작 지점 포함) */
+ length: number;
+ }[];
+ }[];
+}
+
+/** ppt 전체 정보 (+주요 문장 포함) - 최초 설정 페이지 진입시 GET */
+export interface SettingDataType {
+ /** 발표 ID - URL에 라우팅 되는 값 */
+ presentationId: number;
+ /** 발표 제목 */
+ title: string;
+ /** 총 발표 시간 */
+ timeLimit: {
+ hours: number | null;
+ minutes: number | null;
+ };
+ /** 중간 알림 */
+ alertTime: {
+ hours: number | null;
+ minutes: number | null;
+ };
+ /** SHOW: 모든 문장 보기 HIDE: 외울 문장 가리기 (default: 'SHOW')*/
+ practiceMode: PracticeMode;
+ /** 다음 슬라이드 넘어가기 모달 표시 여부 (default: true)*/
+ activateNextSlideModal: boolean;
+ /** ppt 슬라이드 리스트 */
+ slides: {
+ /** DB Identity Sequence. 백엔드 조회용 */
+ id: number;
+ /** 이미지 파일 URL */
+ imageFilePath: string;
+ /** 대본 */
+ script: string;
+ /** 메모 */
+ memo: string;
+ /** 외울 문장 리스트 */
+ memorizationSentences: {
+ /** 외울 문장 시작 인덱스 */
+ offset: number;
+ /** 외울 문장 끝 인덱스 */
+ end?: number;
+ /** 외울 문장 길이 (시작 지점 포함) */
+ length: number;
+ /** 외울 문장 텍스트 */
+ text?: string;
+ }[];
+ }[];
+ /** 최초 생성 시간 */
+ createdAt: Date;
+ /** 마지막 수정 시간 */
+ modifiedAt: Date;
+}
+// #endregion
+
+// #region Practice API
+
+/** 발표 연습 상세 조회 res */
+export interface PracticeDetail {
+ /** 발표 ID */
+ presentationId: number;
+ /** 발표 제목 */
+ title: string;
+ /** 타이머 정보 */
+ timeLimit: PracticeTime;
+ /** 종료 전 알림 정보 */
+ alertTime: PracticeTime;
+ /** 발표 모드 */
+ practiceMode: PracticeMode;
+ /** 다음 슬라이드 넘어가기 모달 표시 여부 */
+ activateNextSlideModal: boolean;
+ /** 슬라이드 리스트 */
+ slides: PracticeSlide[];
+
+ createdAt: string;
+ modifiedAt: string;
+}
+
+/** 발표연습 상세 조회 - 시간 정보 */
+export interface PracticeTime {
+ hours: number;
+ minutes: number;
+}
+
+/** 발표연습 상세 조회 - 슬라이드 정보 */
+export interface PracticeSlide {
+ /** 슬라이드 ID */
+ id: number;
+ /** 슬라이드 이미지 파일 ID */
+ imageFilePath: string | null;
+ /** 발표 스크립트 */
+ script: string;
+ /** 메모 */
+ memo: string | null;
+ /** 외울 문장 리스트 */
+ memorizationSentences: Memorization[];
+}
+
+/** 발표연습 상세 조회 - 외울문장 정보 */
+export interface Memorization {
+ /** 외울 문장 시작점 */
+ offset: number;
+ /** 외울 문장 끝점 */
+ end: number;
+ /** 외울 문장 길이 */
+ length: number;
+ /** 외울 문장 텍스트 */
+ text: string;
+}
+
+/** 발표 연습 완료된 슬라이드 저장 req */
+export interface SavePracticeParams {
+ memo: string | null;
+ audioFileId: number | null;
+}
+// #endregion
+
+// #region File API
+/** 파일 업로드 res */
+export interface UploadFile {
+ id: number;
+ path: string;
+}
+// #endregion
+
+export interface PresentationListType {
+ page: {
+ content: {
+ id: number;
+ title: string;
+ dday: number;
+ timeLimit: {
+ hours: number;
+ minutes: number;
+ };
+ thumbnailPath: string;
+ createdAt: Date;
+ modifiedAt: Date;
+ }[];
+ totalPages: number;
+ totalElements: number;
+ empty: false;
+ };
+}
+export interface LatestPresentationType {
+ id: number;
+ title: string;
+ dday: number;
+ timeLimit: {
+ hours: number;
+ minutes: number;
+ };
+ createdAt: Date;
+ modifiedAt: Date;
+}
+
+export interface FeedbackListType {
+ page: {
+ content: {
+ id: number;
+ title: string;
+ practiceDate: Date;
+ totalScore: number;
+ status: 'IN_PROGRESS' | 'DONE';
+ thumbnailPath: string; // 임시
+ createdAt: Date; // 임시
+ modifiedAt: Date; // 임시
+ }[];
+ totalPages: number;
+ totalElements: number;
+ empty: boolean;
+ };
+}
+export interface FeedbackInfoType {
+ id: number;
+ title: string;
+ practiceDate: Date;
+ practiceTimes: number;
+ isFirstPractice: boolean;
+ totalScore: number;
+ variationFeedback: {
+ beforeTotalScore: number;
+ increasePercentage: number;
+ description: string;
+ };
+ memorizationFeedback: {
+ score: number;
+ grade: string;
+ description: string;
+ };
+ speedFeedback: {
+ score: string;
+ grade: string;
+ description: string;
+ };
+ timeFeedback: {
+ targetTime: {
+ hours: number;
+ minutes: number;
+ seconds: number;
+ };
+ practicedTime: {
+ hours: number;
+ minutes: number;
+ seconds: number;
+ };
+ };
+ memorizationSentenceReivew: {
+ slides: [
+ {
+ id: number;
+ imageFilePath: string;
+ script: string;
+ hasWordError: boolean;
+ wordErros: [
+ {
+ offset: number;
+ length: number;
+ end: number;
+ text: string;
+ },
+ {
+ offset: number;
+ length: number;
+ end: number;
+ text: string;
+ },
+ ];
+ },
+ {
+ id: number;
+ imageFilePath: string;
+ script: string;
+ hasWordError: boolean;
+ wordErros: string[];
+ },
+ ];
+ };
+}
+
+export type CardListType =
+ | PresentationListType['page']['content'][0]
+ | FeedbackListType['page']['content'][0];