diff --git a/.env.development b/.env.development index 89db2d03..629e10c5 100644 --- a/.env.development +++ b/.env.development @@ -1,2 +1 @@ NEXT_PUBLIC_API_MOCKING=enabled - diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml index 344df101..7c80865c 100644 --- a/.github/workflows/chromatic.yml +++ b/.github/workflows/chromatic.yml @@ -4,6 +4,8 @@ name: "Chromatic Deployment" # Event for the workflow on: pull_request: + paths: + - '**/*.stories.tsx' branches: - main - develop diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index cc217b2e..00000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: CI - -on: - pull_request: - branches: - - develop - - main - workflow_dispatch: - -jobs: - job-integrtions: - name: Integrtions - runs-on: ubuntu-latest - - steps: - - name: Checkout current commit (${{ github.sha }}) - uses: actions/checkout@v3 - - - name: Install Dependencies - run: yarn install --immutable --immutable-cache - - - name: Run Lint - run: yarn lint - - - name: Run Type Check - run: yarn tsc diff --git a/.pnp.cjs b/.pnp.cjs index 705d4d51..f736ed7a 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -38,6 +38,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@emotion/styled", "virtual:1efb7fe2099bed4120fcc4f39a0c22325b69ada92fc21796c27552c271c245ad70f9ac625ace3d0f903bd6cf322956fda9e646154a3ae6dc5809e9838f04e833#npm:11.10.5"],\ ["@next/font", "npm:13.0.5"],\ ["@radix-ui/react-accordion", "virtual:1efb7fe2099bed4120fcc4f39a0c22325b69ada92fc21796c27552c271c245ad70f9ac625ace3d0f903bd6cf322956fda9e646154a3ae6dc5809e9838f04e833#npm:1.0.1"],\ + ["@sentry/nextjs", "virtual:1efb7fe2099bed4120fcc4f39a0c22325b69ada92fc21796c27552c271c245ad70f9ac625ace3d0f903bd6cf322956fda9e646154a3ae6dc5809e9838f04e833#npm:7.44.0"],\ ["@storybook/addon-actions", "virtual:1efb7fe2099bed4120fcc4f39a0c22325b69ada92fc21796c27552c271c245ad70f9ac625ace3d0f903bd6cf322956fda9e646154a3ae6dc5809e9838f04e833#npm:6.5.13"],\ ["@storybook/addon-essentials", "virtual:1efb7fe2099bed4120fcc4f39a0c22325b69ada92fc21796c27552c271c245ad70f9ac625ace3d0f903bd6cf322956fda9e646154a3ae6dc5809e9838f04e833#npm:6.5.13"],\ ["@storybook/addon-interactions", "virtual:1efb7fe2099bed4120fcc4f39a0c22325b69ada92fc21796c27552c271c245ad70f9ac625ace3d0f903bd6cf322956fda9e646154a3ae6dc5809e9838f04e833#npm:6.5.13"],\ @@ -61,6 +62,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@typescript-eslint/eslint-plugin", "virtual:1efb7fe2099bed4120fcc4f39a0c22325b69ada92fc21796c27552c271c245ad70f9ac625ace3d0f903bd6cf322956fda9e646154a3ae6dc5809e9838f04e833#npm:5.45.0"],\ ["@typescript-eslint/parser", "virtual:1efb7fe2099bed4120fcc4f39a0c22325b69ada92fc21796c27552c271c245ad70f9ac625ace3d0f903bd6cf322956fda9e646154a3ae6dc5809e9838f04e833#npm:5.45.0"],\ ["@use-gesture/react", "virtual:1efb7fe2099bed4120fcc4f39a0c22325b69ada92fc21796c27552c271c245ad70f9ac625ace3d0f903bd6cf322956fda9e646154a3ae6dc5809e9838f04e833#npm:10.2.24"],\ + ["@vercel/og", "npm:0.5.3"],\ ["autoprefixer", "virtual:1efb7fe2099bed4120fcc4f39a0c22325b69ada92fc21796c27552c271c245ad70f9ac625ace3d0f903bd6cf322956fda9e646154a3ae6dc5809e9838f04e833#npm:10.4.13"],\ ["axios", "npm:1.2.0"],\ ["babel-loader", "virtual:1efb7fe2099bed4120fcc4f39a0c22325b69ada92fc21796c27552c271c245ad70f9ac625ace3d0f903bd6cf322956fda9e646154a3ae6dc5809e9838f04e833#npm:8.3.0"],\ @@ -6727,6 +6729,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["@resvg/resvg-wasm", [\ + ["npm:2.4.1", {\ + "packageLocation": "./.yarn/cache/@resvg-resvg-wasm-npm-2.4.1-78feafa3c1-855aa1e5f1.zip/node_modules/@resvg/resvg-wasm/",\ + "packageDependencies": [\ + ["@resvg/resvg-wasm", "npm:2.4.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@rollup/plugin-babel", [\ ["npm:5.3.1", {\ "packageLocation": "./.yarn/cache/@rollup-plugin-babel-npm-5.3.1-6039a4d033-220d71e464.zip/node_modules/@rollup/plugin-babel/",\ @@ -6755,6 +6766,34 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["@rollup/plugin-commonjs", [\ + ["npm:24.0.0", {\ + "packageLocation": "./.yarn/cache/@rollup-plugin-commonjs-npm-24.0.0-b18d79acac-e2a1bf295b.zip/node_modules/@rollup/plugin-commonjs/",\ + "packageDependencies": [\ + ["@rollup/plugin-commonjs", "npm:24.0.0"]\ + ],\ + "linkType": "SOFT"\ + }],\ + ["virtual:e1ceb5a91d2ed8807a95343892aa9738ff828a27ae4ce457278d5dbf33ef47d98c4717be419a8e7cb1fbd47daa66df6a791bb05ee1bcc3e2410bba03c754fef1#npm:24.0.0", {\ + "packageLocation": "./.yarn/__virtual__/@rollup-plugin-commonjs-virtual-1b5531fce8/0/cache/@rollup-plugin-commonjs-npm-24.0.0-b18d79acac-e2a1bf295b.zip/node_modules/@rollup/plugin-commonjs/",\ + "packageDependencies": [\ + ["@rollup/plugin-commonjs", "virtual:e1ceb5a91d2ed8807a95343892aa9738ff828a27ae4ce457278d5dbf33ef47d98c4717be419a8e7cb1fbd47daa66df6a791bb05ee1bcc3e2410bba03c754fef1#npm:24.0.0"],\ + ["@rollup/pluginutils", "virtual:1b5531fce8da1a4b84cd51e7a176b477ca9556bd4505bdb1516e12860e2a6832ef20826020c3b7fb40622327f6e271c90bef25c8e44e017ef8a75bcb90947480#npm:5.0.2"],\ + ["@types/rollup", null],\ + ["commondir", "npm:1.0.1"],\ + ["estree-walker", "npm:2.0.2"],\ + ["glob", "npm:8.1.0"],\ + ["is-reference", "npm:1.2.1"],\ + ["magic-string", "npm:0.27.0"],\ + ["rollup", "npm:2.78.0"]\ + ],\ + "packagePeers": [\ + "@types/rollup",\ + "rollup"\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@rollup/plugin-node-resolve", [\ ["npm:11.2.1", {\ "packageLocation": "./.yarn/cache/@rollup-plugin-node-resolve-npm-11.2.1-1cea144df4-6f3b3ecf9a.zip/node_modules/@rollup/plugin-node-resolve/",\ @@ -6815,6 +6854,29 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ],\ "linkType": "SOFT"\ }],\ + ["npm:5.0.2", {\ + "packageLocation": "./.yarn/cache/@rollup-pluginutils-npm-5.0.2-6aa9d0ddd4-edea15e543.zip/node_modules/@rollup/pluginutils/",\ + "packageDependencies": [\ + ["@rollup/pluginutils", "npm:5.0.2"]\ + ],\ + "linkType": "SOFT"\ + }],\ + ["virtual:1b5531fce8da1a4b84cd51e7a176b477ca9556bd4505bdb1516e12860e2a6832ef20826020c3b7fb40622327f6e271c90bef25c8e44e017ef8a75bcb90947480#npm:5.0.2", {\ + "packageLocation": "./.yarn/__virtual__/@rollup-pluginutils-virtual-293e31866d/0/cache/@rollup-pluginutils-npm-5.0.2-6aa9d0ddd4-edea15e543.zip/node_modules/@rollup/pluginutils/",\ + "packageDependencies": [\ + ["@rollup/pluginutils", "virtual:1b5531fce8da1a4b84cd51e7a176b477ca9556bd4505bdb1516e12860e2a6832ef20826020c3b7fb40622327f6e271c90bef25c8e44e017ef8a75bcb90947480#npm:5.0.2"],\ + ["@types/estree", "npm:1.0.0"],\ + ["@types/rollup", null],\ + ["estree-walker", "npm:2.0.2"],\ + ["picomatch", "npm:2.3.1"],\ + ["rollup", "npm:2.78.0"]\ + ],\ + "packagePeers": [\ + "@types/rollup",\ + "rollup"\ + ],\ + "linkType": "HARD"\ + }],\ ["virtual:48b162daea0c118d7faf0304ab8992ae2eed4930ca7f9bb03d63d10c1003707b290b5c925bc8474b493d22187d8efebd8d4056c7e06a4ca4a9ecfa903bba6032#npm:3.1.0", {\ "packageLocation": "./.yarn/__virtual__/@rollup-pluginutils-virtual-9474d4f4cc/0/cache/@rollup-pluginutils-npm-3.1.0-b44b222e7d-8be16e2786.zip/node_modules/@rollup/pluginutils/",\ "packageDependencies": [\ @@ -6841,6 +6903,224 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["@sentry-internal/tracing", [\ + ["npm:7.44.0", {\ + "packageLocation": "./.yarn/cache/@sentry-internal-tracing-npm-7.44.0-2028ee1037-b15fd15467.zip/node_modules/@sentry-internal/tracing/",\ + "packageDependencies": [\ + ["@sentry-internal/tracing", "npm:7.44.0"],\ + ["@sentry/core", "npm:7.44.0"],\ + ["@sentry/types", "npm:7.44.0"],\ + ["@sentry/utils", "npm:7.44.0"],\ + ["tslib", "npm:1.14.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@sentry/browser", [\ + ["npm:7.44.0", {\ + "packageLocation": "./.yarn/cache/@sentry-browser-npm-7.44.0-cab936128d-c96781dce5.zip/node_modules/@sentry/browser/",\ + "packageDependencies": [\ + ["@sentry/browser", "npm:7.44.0"],\ + ["@sentry-internal/tracing", "npm:7.44.0"],\ + ["@sentry/core", "npm:7.44.0"],\ + ["@sentry/replay", "npm:7.44.0"],\ + ["@sentry/types", "npm:7.44.0"],\ + ["@sentry/utils", "npm:7.44.0"],\ + ["tslib", "npm:1.14.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@sentry/cli", [\ + ["npm:1.75.0", {\ + "packageLocation": "./.yarn/cache/@sentry-cli-npm-1.75.0-398a323f0b-d5b130bb45.zip/node_modules/@sentry/cli/",\ + "packageDependencies": [\ + ["@sentry/cli", "npm:1.75.0"],\ + ["https-proxy-agent", "npm:5.0.1"],\ + ["mkdirp", "npm:0.5.6"],\ + ["node-fetch", "virtual:398a323f0b1677616dc6fe2c9f8b1ee9debf90ce4562a76f813a5a72ee0d90d6e3f1449da63786ae567aa8de19c5f7566094901755009b923033906bc49a7a13#npm:2.6.7"],\ + ["progress", "npm:2.0.3"],\ + ["proxy-from-env", "npm:1.1.0"],\ + ["which", "npm:2.0.2"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@sentry/core", [\ + ["npm:7.44.0", {\ + "packageLocation": "./.yarn/cache/@sentry-core-npm-7.44.0-19a12668fb-29591ea7b2.zip/node_modules/@sentry/core/",\ + "packageDependencies": [\ + ["@sentry/core", "npm:7.44.0"],\ + ["@sentry/types", "npm:7.44.0"],\ + ["@sentry/utils", "npm:7.44.0"],\ + ["tslib", "npm:1.14.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@sentry/integrations", [\ + ["npm:7.44.0", {\ + "packageLocation": "./.yarn/cache/@sentry-integrations-npm-7.44.0-964fd76882-d2936a8357.zip/node_modules/@sentry/integrations/",\ + "packageDependencies": [\ + ["@sentry/integrations", "npm:7.44.0"],\ + ["@sentry/types", "npm:7.44.0"],\ + ["@sentry/utils", "npm:7.44.0"],\ + ["localforage", "npm:1.10.0"],\ + ["tslib", "npm:1.14.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@sentry/nextjs", [\ + ["npm:7.44.0", {\ + "packageLocation": "./.yarn/cache/@sentry-nextjs-npm-7.44.0-8371f99ce5-7350100e0d.zip/node_modules/@sentry/nextjs/",\ + "packageDependencies": [\ + ["@sentry/nextjs", "npm:7.44.0"]\ + ],\ + "linkType": "SOFT"\ + }],\ + ["virtual:1efb7fe2099bed4120fcc4f39a0c22325b69ada92fc21796c27552c271c245ad70f9ac625ace3d0f903bd6cf322956fda9e646154a3ae6dc5809e9838f04e833#npm:7.44.0", {\ + "packageLocation": "./.yarn/__virtual__/@sentry-nextjs-virtual-e1ceb5a91d/0/cache/@sentry-nextjs-npm-7.44.0-8371f99ce5-7350100e0d.zip/node_modules/@sentry/nextjs/",\ + "packageDependencies": [\ + ["@sentry/nextjs", "virtual:1efb7fe2099bed4120fcc4f39a0c22325b69ada92fc21796c27552c271c245ad70f9ac625ace3d0f903bd6cf322956fda9e646154a3ae6dc5809e9838f04e833#npm:7.44.0"],\ + ["@rollup/plugin-commonjs", "virtual:e1ceb5a91d2ed8807a95343892aa9738ff828a27ae4ce457278d5dbf33ef47d98c4717be419a8e7cb1fbd47daa66df6a791bb05ee1bcc3e2410bba03c754fef1#npm:24.0.0"],\ + ["@sentry/core", "npm:7.44.0"],\ + ["@sentry/integrations", "npm:7.44.0"],\ + ["@sentry/node", "npm:7.44.0"],\ + ["@sentry/react", "virtual:e1ceb5a91d2ed8807a95343892aa9738ff828a27ae4ce457278d5dbf33ef47d98c4717be419a8e7cb1fbd47daa66df6a791bb05ee1bcc3e2410bba03c754fef1#npm:7.44.0"],\ + ["@sentry/tracing", "npm:7.44.0"],\ + ["@sentry/types", "npm:7.44.0"],\ + ["@sentry/utils", "npm:7.44.0"],\ + ["@sentry/webpack-plugin", "npm:1.20.0"],\ + ["@types/next", null],\ + ["@types/react", "npm:18.0.25"],\ + ["@types/webpack", null],\ + ["chalk", "npm:3.0.0"],\ + ["next", "virtual:1efb7fe2099bed4120fcc4f39a0c22325b69ada92fc21796c27552c271c245ad70f9ac625ace3d0f903bd6cf322956fda9e646154a3ae6dc5809e9838f04e833#npm:13.0.5"],\ + ["react", "npm:18.2.0"],\ + ["rollup", "npm:2.78.0"],\ + ["stacktrace-parser", "npm:0.1.10"],\ + ["tslib", "npm:1.14.1"],\ + ["webpack", null]\ + ],\ + "packagePeers": [\ + "@types/next",\ + "@types/react",\ + "@types/webpack",\ + "next",\ + "react",\ + "webpack"\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@sentry/node", [\ + ["npm:7.44.0", {\ + "packageLocation": "./.yarn/cache/@sentry-node-npm-7.44.0-1d05948f24-7d8dbc4679.zip/node_modules/@sentry/node/",\ + "packageDependencies": [\ + ["@sentry/node", "npm:7.44.0"],\ + ["@sentry/core", "npm:7.44.0"],\ + ["@sentry/types", "npm:7.44.0"],\ + ["@sentry/utils", "npm:7.44.0"],\ + ["cookie", "npm:0.4.2"],\ + ["https-proxy-agent", "npm:5.0.1"],\ + ["lru_map", "npm:0.3.3"],\ + ["tslib", "npm:1.14.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@sentry/react", [\ + ["npm:7.44.0", {\ + "packageLocation": "./.yarn/cache/@sentry-react-npm-7.44.0-3ab2ed8d45-d8a166db17.zip/node_modules/@sentry/react/",\ + "packageDependencies": [\ + ["@sentry/react", "npm:7.44.0"]\ + ],\ + "linkType": "SOFT"\ + }],\ + ["virtual:e1ceb5a91d2ed8807a95343892aa9738ff828a27ae4ce457278d5dbf33ef47d98c4717be419a8e7cb1fbd47daa66df6a791bb05ee1bcc3e2410bba03c754fef1#npm:7.44.0", {\ + "packageLocation": "./.yarn/__virtual__/@sentry-react-virtual-612b3bfdac/0/cache/@sentry-react-npm-7.44.0-3ab2ed8d45-d8a166db17.zip/node_modules/@sentry/react/",\ + "packageDependencies": [\ + ["@sentry/react", "virtual:e1ceb5a91d2ed8807a95343892aa9738ff828a27ae4ce457278d5dbf33ef47d98c4717be419a8e7cb1fbd47daa66df6a791bb05ee1bcc3e2410bba03c754fef1#npm:7.44.0"],\ + ["@sentry/browser", "npm:7.44.0"],\ + ["@sentry/types", "npm:7.44.0"],\ + ["@sentry/utils", "npm:7.44.0"],\ + ["@types/react", "npm:18.0.25"],\ + ["hoist-non-react-statics", "npm:3.3.2"],\ + ["react", "npm:18.2.0"],\ + ["tslib", "npm:1.14.1"]\ + ],\ + "packagePeers": [\ + "@types/react",\ + "react"\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@sentry/replay", [\ + ["npm:7.44.0", {\ + "packageLocation": "./.yarn/cache/@sentry-replay-npm-7.44.0-81de3f117c-099ace8cd1.zip/node_modules/@sentry/replay/",\ + "packageDependencies": [\ + ["@sentry/replay", "npm:7.44.0"],\ + ["@sentry/core", "npm:7.44.0"],\ + ["@sentry/types", "npm:7.44.0"],\ + ["@sentry/utils", "npm:7.44.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@sentry/tracing", [\ + ["npm:7.44.0", {\ + "packageLocation": "./.yarn/cache/@sentry-tracing-npm-7.44.0-1a43e129ac-1286c86fc1.zip/node_modules/@sentry/tracing/",\ + "packageDependencies": [\ + ["@sentry/tracing", "npm:7.44.0"],\ + ["@sentry-internal/tracing", "npm:7.44.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@sentry/types", [\ + ["npm:7.44.0", {\ + "packageLocation": "./.yarn/cache/@sentry-types-npm-7.44.0-3942c12677-d053f37e32.zip/node_modules/@sentry/types/",\ + "packageDependencies": [\ + ["@sentry/types", "npm:7.44.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@sentry/utils", [\ + ["npm:7.44.0", {\ + "packageLocation": "./.yarn/cache/@sentry-utils-npm-7.44.0-1d375e96d7-2f19481b62.zip/node_modules/@sentry/utils/",\ + "packageDependencies": [\ + ["@sentry/utils", "npm:7.44.0"],\ + ["@sentry/types", "npm:7.44.0"],\ + ["tslib", "npm:1.14.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@sentry/webpack-plugin", [\ + ["npm:1.20.0", {\ + "packageLocation": "./.yarn/cache/@sentry-webpack-plugin-npm-1.20.0-9eeec4f0d8-d582026c36.zip/node_modules/@sentry/webpack-plugin/",\ + "packageDependencies": [\ + ["@sentry/webpack-plugin", "npm:1.20.0"],\ + ["@sentry/cli", "npm:1.75.0"],\ + ["webpack-sources", "npm:3.2.3"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@shuding/opentype.js", [\ + ["npm:1.4.0-beta.0", {\ + "packageLocation": "./.yarn/cache/@shuding-opentype.js-npm-1.4.0-beta.0-498d62cde8-af3478c40c.zip/node_modules/@shuding/opentype.js/",\ + "packageDependencies": [\ + ["@shuding/opentype.js", "npm:1.4.0-beta.0"],\ + ["fflate", "npm:0.7.4"],\ + ["string.prototype.codepointat", "npm:0.2.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@storybook/addon-actions", [\ ["npm:6.5.13", {\ "packageLocation": "./.yarn/cache/@storybook-addon-actions-npm-6.5.13-c87e087712-2679174b14.zip/node_modules/@storybook/addon-actions/",\ @@ -8307,7 +8587,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["globby", "npm:11.1.0"],\ ["ip", "npm:2.0.0"],\ ["lodash", "npm:4.17.21"],\ - ["node-fetch", "virtual:92c73b8f89621810dfb75c414e7d01a3dcab1361f182ed65606138c282b3762bcbe611ec4dffb75077ce18e8aee1419b680da01399050e935c9a11f4e43ead18#npm:2.6.7"],\ + ["node-fetch", "virtual:398a323f0b1677616dc6fe2c9f8b1ee9debf90ce4562a76f813a5a72ee0d90d6e3f1449da63786ae567aa8de19c5f7566094901755009b923033906bc49a7a13#npm:2.6.7"],\ ["open", "npm:8.4.0"],\ ["pretty-hrtime", "npm:1.0.3"],\ ["prompts", "npm:2.4.2"],\ @@ -8460,7 +8740,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["find-up", "npm:5.0.0"],\ ["fs-extra", "npm:9.1.0"],\ ["html-webpack-plugin", "virtual:8de62122fc7a96cdc92bd3c83415400233625d2b98abc846cf26dfb0065d02af5cc766f8e550f98bc825612a449de94bb96fdfc5eb48ebf94a944833d501bff6#npm:4.5.2"],\ - ["node-fetch", "virtual:92c73b8f89621810dfb75c414e7d01a3dcab1361f182ed65606138c282b3762bcbe611ec4dffb75077ce18e8aee1419b680da01399050e935c9a11f4e43ead18#npm:2.6.7"],\ + ["node-fetch", "virtual:398a323f0b1677616dc6fe2c9f8b1ee9debf90ce4562a76f813a5a72ee0d90d6e3f1449da63786ae567aa8de19c5f7566094901755009b923033906bc49a7a13#npm:2.6.7"],\ ["pnp-webpack-plugin", "npm:1.6.4"],\ ["react", "npm:18.2.0"],\ ["react-dom", "virtual:1efb7fe2099bed4120fcc4f39a0c22325b69ada92fc21796c27552c271c245ad70f9ac625ace3d0f903bd6cf322956fda9e646154a3ae6dc5809e9838f04e833#npm:18.2.0"],\ @@ -8523,7 +8803,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["find-up", "npm:5.0.0"],\ ["fs-extra", "npm:9.1.0"],\ ["html-webpack-plugin", "virtual:4a16c017eb65863a07b071ea32886b327b86b4c5afaedca1630c29bf33216fb017b7e09a1cede97e482e26fe132ec80addbcad29a59f529dc9389582cddd43c2#npm:5.5.0"],\ - ["node-fetch", "virtual:92c73b8f89621810dfb75c414e7d01a3dcab1361f182ed65606138c282b3762bcbe611ec4dffb75077ce18e8aee1419b680da01399050e935c9a11f4e43ead18#npm:2.6.7"],\ + ["node-fetch", "virtual:398a323f0b1677616dc6fe2c9f8b1ee9debf90ce4562a76f813a5a72ee0d90d6e3f1449da63786ae567aa8de19c5f7566094901755009b923033906bc49a7a13#npm:2.6.7"],\ ["process", "npm:0.11.10"],\ ["react", "npm:18.2.0"],\ ["react-dom", "virtual:1efb7fe2099bed4120fcc4f39a0c22325b69ada92fc21796c27552c271c245ad70f9ac625ace3d0f903bd6cf322956fda9e646154a3ae6dc5809e9838f04e833#npm:18.2.0"],\ @@ -9974,16 +10254,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["@types/resolve", [\ - ["npm:1.17.1", {\ - "packageLocation": "./.yarn/cache/@types-resolve-npm-1.17.1-9a8396bef2-dc6a6df507.zip/node_modules/@types/resolve/",\ - "packageDependencies": [\ - ["@types/resolve", "npm:1.17.1"],\ - ["@types/node", "npm:18.11.9"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["@types/react-transition-group", [\ ["npm:4.4.5", {\ "packageLocation": "./.yarn/cache/@types-react-transition-group-npm-4.4.5-8f92107b07-265f1c7406.zip/node_modules/@types/react-transition-group/",\ @@ -9994,6 +10264,16 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["@types/resolve", [\ + ["npm:1.17.1", {\ + "packageLocation": "./.yarn/cache/@types-resolve-npm-1.17.1-9a8396bef2-dc6a6df507.zip/node_modules/@types/resolve/",\ + "packageDependencies": [\ + ["@types/resolve", "npm:1.17.1"],\ + ["@types/node", "npm:18.11.9"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@types/scheduler", [\ ["npm:0.16.2", {\ "packageLocation": "./.yarn/cache/@types-scheduler-npm-0.16.2-ba3a7d8c68-b6b4dcfeae.zip/node_modules/@types/scheduler/",\ @@ -10395,6 +10675,18 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["@vercel/og", [\ + ["npm:0.5.3", {\ + "packageLocation": "./.yarn/cache/@vercel-og-npm-0.5.3-c734504904-efe8588faf.zip/node_modules/@vercel/og/",\ + "packageDependencies": [\ + ["@vercel/og", "npm:0.5.3"],\ + ["@resvg/resvg-wasm", "npm:2.4.1"],\ + ["satori", "npm:0.4.9"],\ + ["yoga-wasm-web", "npm:0.3.3"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@webassemblyjs/ast", [\ ["npm:1.11.1", {\ "packageLocation": "./.yarn/cache/@webassemblyjs-ast-npm-1.11.1-623d3d973e-1eee1534ad.zip/node_modules/@webassemblyjs/ast/",\ @@ -12040,6 +12332,13 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["base64-js", [\ + ["npm:0.0.8", {\ + "packageLocation": "./.yarn/cache/base64-js-npm-0.0.8-f2946f7960-e95d2fa4b9.zip/node_modules/base64-js/",\ + "packageDependencies": [\ + ["base64-js", "npm:0.0.8"]\ + ],\ + "linkType": "HARD"\ + }],\ ["npm:1.5.1", {\ "packageLocation": "./.yarn/cache/base64-js-npm-1.5.1-b2f7275641-669632eb37.zip/node_modules/base64-js/",\ "packageDependencies": [\ @@ -12638,6 +12937,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["camelize", [\ + ["npm:1.0.1", {\ + "packageLocation": "./.yarn/cache/camelize-npm-1.0.1-d86ebe085a-91d8611d09.zip/node_modules/camelize/",\ + "packageDependencies": [\ + ["camelize", "npm:1.0.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["caniuse-lite", [\ ["npm:1.0.30001435", {\ "packageLocation": "./.yarn/cache/caniuse-lite-npm-1.0.30001435-7cebb35f0a-ec88b9c37f.zip/node_modules/caniuse-lite/",\ @@ -12686,6 +12994,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ],\ "linkType": "HARD"\ }],\ + ["npm:3.0.0", {\ + "packageLocation": "./.yarn/cache/chalk-npm-3.0.0-e813208025-8e3ddf3981.zip/node_modules/chalk/",\ + "packageDependencies": [\ + ["chalk", "npm:3.0.0"],\ + ["ansi-styles", "npm:4.3.0"],\ + ["supports-color", "npm:7.2.0"]\ + ],\ + "linkType": "HARD"\ + }],\ ["npm:4.1.1", {\ "packageLocation": "./.yarn/cache/chalk-npm-4.1.1-f1ce6bae57-036e973e66.zip/node_modules/chalk/",\ "packageDependencies": [\ @@ -13559,6 +13876,33 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["css-background-parser", [\ + ["npm:0.1.0", {\ + "packageLocation": "./.yarn/cache/css-background-parser-npm-0.1.0-d1e94221f7-cf53bef8d5.zip/node_modules/css-background-parser/",\ + "packageDependencies": [\ + ["css-background-parser", "npm:0.1.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["css-box-shadow", [\ + ["npm:1.0.0-3", {\ + "packageLocation": "./.yarn/cache/css-box-shadow-npm-1.0.0-3-2a025ab419-1b7f15b291.zip/node_modules/css-box-shadow/",\ + "packageDependencies": [\ + ["css-box-shadow", "npm:1.0.0-3"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["css-color-keywords", [\ + ["npm:1.0.0", {\ + "packageLocation": "./.yarn/cache/css-color-keywords-npm-1.0.0-fc176df58b-8f125e3ad4.zip/node_modules/css-color-keywords/",\ + "packageDependencies": [\ + ["css-color-keywords", "npm:1.0.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["css-loader", [\ ["npm:3.6.0", {\ "packageLocation": "./.yarn/cache/css-loader-npm-3.6.0-3394f37d07-a45d7ee810.zip/node_modules/css-loader/",\ @@ -13664,6 +14008,18 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["css-to-react-native", [\ + ["npm:3.2.0", {\ + "packageLocation": "./.yarn/cache/css-to-react-native-npm-3.2.0-46e31a25e3-263be65e80.zip/node_modules/css-to-react-native/",\ + "packageDependencies": [\ + ["css-to-react-native", "npm:3.2.0"],\ + ["camelize", "npm:1.0.1"],\ + ["css-color-keywords", "npm:1.0.0"],\ + ["postcss-value-parser", "npm:4.2.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["css-tree", [\ ["npm:1.1.3", {\ "packageLocation": "./.yarn/cache/css-tree-npm-1.1.3-9c46f35513-79f9b81803.zip/node_modules/css-tree/",\ @@ -14328,6 +14684,13 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["emoji-regex", [\ + ["npm:10.2.1", {\ + "packageLocation": "./.yarn/cache/emoji-regex-npm-10.2.1-463e5e2567-1aa2d16881.zip/node_modules/emoji-regex/",\ + "packageDependencies": [\ + ["emoji-regex", "npm:10.2.1"]\ + ],\ + "linkType": "HARD"\ + }],\ ["npm:8.0.0", {\ "packageLocation": "./.yarn/cache/emoji-regex-npm-8.0.0-213764015c-d4c5c39d5a.zip/node_modules/emoji-regex/",\ "packageDependencies": [\ @@ -15178,6 +15541,13 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["estree-walker", "npm:1.0.1"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:2.0.2", {\ + "packageLocation": "./.yarn/cache/estree-walker-npm-2.0.2-dfab42f65c-6151e6f982.zip/node_modules/estree-walker/",\ + "packageDependencies": [\ + ["estree-walker", "npm:2.0.2"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["esutils", [\ @@ -15490,6 +15860,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["fflate", [\ + ["npm:0.7.4", {\ + "packageLocation": "./.yarn/cache/fflate-npm-0.7.4-df9245ab05-b812ab2604.zip/node_modules/fflate/",\ + "packageDependencies": [\ + ["fflate", "npm:0.7.4"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["figgy-pudding", [\ ["npm:3.5.2", {\ "packageLocation": "./.yarn/cache/figgy-pudding-npm-3.5.2-2f4e3e1305-4090bd6619.zip/node_modules/figgy-pudding/",\ @@ -16309,6 +16688,18 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["once", "npm:1.4.0"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:8.1.0", {\ + "packageLocation": "./.yarn/cache/glob-npm-8.1.0-65f64af8b1-92fbea3221.zip/node_modules/glob/",\ + "packageDependencies": [\ + ["glob", "npm:8.1.0"],\ + ["fs.realpath", "npm:1.0.0"],\ + ["inflight", "npm:1.0.6"],\ + ["inherits", "npm:2.0.4"],\ + ["minimatch", "npm:5.1.1"],\ + ["once", "npm:1.4.0"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["glob-parent", [\ @@ -17147,6 +17538,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["immediate", [\ + ["npm:3.0.6", {\ + "packageLocation": "./.yarn/cache/immediate-npm-3.0.6-c27588a2d3-f9b3486477.zip/node_modules/immediate/",\ + "packageDependencies": [\ + ["immediate", "npm:3.0.6"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["import-fresh", [\ ["npm:3.3.0", {\ "packageLocation": "./.yarn/cache/import-fresh-npm-3.3.0-3e34265ca9-2cacfad06e.zip/node_modules/import-fresh/",\ @@ -17780,6 +18180,16 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["is-reference", [\ + ["npm:1.2.1", {\ + "packageLocation": "./.yarn/cache/is-reference-npm-1.2.1-87ca1743c8-e7b48149f8.zip/node_modules/is-reference/",\ + "packageDependencies": [\ + ["is-reference", "npm:1.2.1"],\ + ["@types/estree", "npm:1.0.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["is-regex", [\ ["npm:1.1.4", {\ "packageLocation": "./.yarn/cache/is-regex-npm-1.1.4-cca193ef11-362399b335.zip/node_modules/is-regex/",\ @@ -18056,7 +18466,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "packageLocation": "./.yarn/cache/isomorphic-unfetch-npm-3.1.0-001a51c96c-82b92fe4ec.zip/node_modules/isomorphic-unfetch/",\ "packageDependencies": [\ ["isomorphic-unfetch", "npm:3.1.0"],\ - ["node-fetch", "virtual:92c73b8f89621810dfb75c414e7d01a3dcab1361f182ed65606138c282b3762bcbe611ec4dffb75077ce18e8aee1419b680da01399050e935c9a11f4e43ead18#npm:2.6.7"],\ + ["node-fetch", "virtual:398a323f0b1677616dc6fe2c9f8b1ee9debf90ce4562a76f813a5a72ee0d90d6e3f1449da63786ae567aa8de19c5f7566094901755009b923033906bc49a7a13#npm:2.6.7"],\ ["unfetch", "npm:4.2.0"]\ ],\ "linkType": "HARD"\ @@ -18522,6 +18932,16 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["lie", [\ + ["npm:3.1.1", {\ + "packageLocation": "./.yarn/cache/lie-npm-3.1.1-91350720d9-6da9f2121d.zip/node_modules/lie/",\ + "packageDependencies": [\ + ["lie", "npm:3.1.1"],\ + ["immediate", "npm:3.0.6"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["lilconfig", [\ ["npm:2.0.6", {\ "packageLocation": "./.yarn/cache/lilconfig-npm-2.0.6-6231346eaf-40a3cd72f1.zip/node_modules/lilconfig/",\ @@ -18531,6 +18951,17 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["linebreak", [\ + ["npm:1.1.0", {\ + "packageLocation": "./.yarn/cache/linebreak-npm-1.1.0-d845ecba6a-65cb66900b.zip/node_modules/linebreak/",\ + "packageDependencies": [\ + ["linebreak", "npm:1.1.0"],\ + ["base64-js", "npm:0.0.8"],\ + ["unicode-trie", "npm:2.0.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["lines-and-columns", [\ ["npm:1.2.4", {\ "packageLocation": "./.yarn/cache/lines-and-columns-npm-1.2.4-d6c7cc5799-0c37f9f7fa.zip/node_modules/lines-and-columns/",\ @@ -18644,6 +19075,16 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["localforage", [\ + ["npm:1.10.0", {\ + "packageLocation": "./.yarn/cache/localforage-npm-1.10.0-cf9ea9a436-f2978b434d.zip/node_modules/localforage/",\ + "packageDependencies": [\ + ["localforage", "npm:1.10.0"],\ + ["lie", "npm:3.1.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["locate-path", [\ ["npm:3.0.0", {\ "packageLocation": "./.yarn/cache/locate-path-npm-3.0.0-991671ae9f-53db399667.zip/node_modules/locate-path/",\ @@ -18814,6 +19255,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["lru_map", [\ + ["npm:0.3.3", {\ + "packageLocation": "./.yarn/cache/lru_map-npm-0.3.3-a038bb3418-ca9dd43c65.zip/node_modules/lru_map/",\ + "packageDependencies": [\ + ["lru_map", "npm:0.3.3"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["lz-string", [\ ["npm:1.4.4", {\ "packageLocation": "./.yarn/cache/lz-string-npm-1.4.4-59a2091d3f-54e31238a6.zip/node_modules/lz-string/",\ @@ -18831,6 +19281,14 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["sourcemap-codec", "npm:1.4.8"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:0.27.0", {\ + "packageLocation": "./.yarn/cache/magic-string-npm-0.27.0-a60a83c0b4-273faaa50b.zip/node_modules/magic-string/",\ + "packageDependencies": [\ + ["magic-string", "npm:0.27.0"],\ + ["@jridgewell/sourcemap-codec", "npm:1.4.14"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["make-dir", [\ @@ -19499,7 +19957,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["inquirer", "npm:8.2.5"],\ ["is-node-process", "npm:1.0.1"],\ ["js-levenshtein", "npm:1.1.6"],\ - ["node-fetch", "virtual:92c73b8f89621810dfb75c414e7d01a3dcab1361f182ed65606138c282b3762bcbe611ec4dffb75077ce18e8aee1419b680da01399050e935c9a11f4e43ead18#npm:2.6.7"],\ + ["node-fetch", "virtual:398a323f0b1677616dc6fe2c9f8b1ee9debf90ce4562a76f813a5a72ee0d90d6e3f1449da63786ae567aa8de19c5f7566094901755009b923033906bc49a7a13#npm:2.6.7"],\ ["outvariant", "npm:1.3.0"],\ ["path-to-regexp", "npm:6.2.1"],\ ["strict-event-emitter", "npm:0.2.8"],\ @@ -19791,10 +20249,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ],\ "linkType": "SOFT"\ }],\ - ["virtual:92c73b8f89621810dfb75c414e7d01a3dcab1361f182ed65606138c282b3762bcbe611ec4dffb75077ce18e8aee1419b680da01399050e935c9a11f4e43ead18#npm:2.6.7", {\ - "packageLocation": "./.yarn/__virtual__/node-fetch-virtual-e613b3f89d/0/cache/node-fetch-npm-2.6.7-777aa2a6df-8d816ffd1e.zip/node_modules/node-fetch/",\ + ["virtual:398a323f0b1677616dc6fe2c9f8b1ee9debf90ce4562a76f813a5a72ee0d90d6e3f1449da63786ae567aa8de19c5f7566094901755009b923033906bc49a7a13#npm:2.6.7", {\ + "packageLocation": "./.yarn/__virtual__/node-fetch-virtual-282defd654/0/cache/node-fetch-npm-2.6.7-777aa2a6df-8d816ffd1e.zip/node_modules/node-fetch/",\ "packageDependencies": [\ - ["node-fetch", "virtual:92c73b8f89621810dfb75c414e7d01a3dcab1361f182ed65606138c282b3762bcbe611ec4dffb75077ce18e8aee1419b680da01399050e935c9a11f4e43ead18#npm:2.6.7"],\ + ["node-fetch", "virtual:398a323f0b1677616dc6fe2c9f8b1ee9debf90ce4562a76f813a5a72ee0d90d6e3f1449da63786ae567aa8de19c5f7566094901755009b923033906bc49a7a13#npm:2.6.7"],\ ["@types/encoding", null],\ ["encoding", null],\ ["whatwg-url", "npm:5.0.0"]\ @@ -20448,6 +20906,13 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["pako", [\ + ["npm:0.2.9", {\ + "packageLocation": "./.yarn/cache/pako-npm-0.2.9-c88ac0d326-055f9487cd.zip/node_modules/pako/",\ + "packageDependencies": [\ + ["pako", "npm:0.2.9"]\ + ],\ + "linkType": "HARD"\ + }],\ ["npm:1.0.11", {\ "packageLocation": "./.yarn/cache/pako-npm-1.0.11-b8f1b69d3e-1be2bfa1f8.zip/node_modules/pako/",\ "packageDependencies": [\ @@ -21348,6 +21813,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["progress", [\ + ["npm:2.0.3", {\ + "packageLocation": "./.yarn/cache/progress-npm-2.0.3-d1f87e2ac6-f67403fe7b.zip/node_modules/progress/",\ + "packageDependencies": [\ + ["progress", "npm:2.0.3"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["promise-inflight", [\ ["npm:1.0.1", {\ "packageLocation": "./.yarn/cache/promise-inflight-npm-1.0.1-5bb925afac-2274948309.zip/node_modules/promise-inflight/",\ @@ -22436,6 +22910,14 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["rollup", [\ + ["npm:2.78.0", {\ + "packageLocation": "./.yarn/cache/rollup-npm-2.78.0-09284f4c78-01b5a7ae08.zip/node_modules/rollup/",\ + "packageDependencies": [\ + ["rollup", "npm:2.78.0"],\ + ["fsevents", "patch:fsevents@npm%3A2.3.2#~builtin::version=2.3.2&hash=df0bf1"]\ + ],\ + "linkType": "HARD"\ + }],\ ["npm:2.79.1", {\ "packageLocation": "./.yarn/cache/rollup-npm-2.79.1-94e707a9a3-6a2bf167b3.zip/node_modules/rollup/",\ "packageDependencies": [\ @@ -22591,6 +23073,23 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["satori", [\ + ["npm:0.4.9", {\ + "packageLocation": "./.yarn/cache/satori-npm-0.4.9-d045a2f54e-4b616c81dc.zip/node_modules/satori/",\ + "packageDependencies": [\ + ["satori", "npm:0.4.9"],\ + ["@shuding/opentype.js", "npm:1.4.0-beta.0"],\ + ["css-background-parser", "npm:0.1.0"],\ + ["css-box-shadow", "npm:1.0.0-3"],\ + ["css-to-react-native", "npm:3.2.0"],\ + ["emoji-regex", "npm:10.2.1"],\ + ["linebreak", "npm:1.1.0"],\ + ["postcss-value-parser", "npm:4.2.0"],\ + ["yoga-wasm-web", "npm:0.3.3"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["scheduler", [\ ["npm:0.23.0", {\ "packageLocation": "./.yarn/cache/scheduler-npm-0.23.0-a379a6bc3b-d79192eeaa.zip/node_modules/scheduler/",\ @@ -23205,6 +23704,16 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["stacktrace-parser", [\ + ["npm:0.1.10", {\ + "packageLocation": "./.yarn/cache/stacktrace-parser-npm-0.1.10-36f3e571bd-f4fbddfc09.zip/node_modules/stacktrace-parser/",\ + "packageDependencies": [\ + ["stacktrace-parser", "npm:0.1.10"],\ + ["type-fest", "npm:0.7.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["state-toggle", [\ ["npm:1.0.3", {\ "packageLocation": "./.yarn/cache/state-toggle-npm-1.0.3-dd096f8bd0-17398af928.zip/node_modules/state-toggle/",\ @@ -23372,6 +23881,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["string.prototype.codepointat", [\ + ["npm:0.2.1", {\ + "packageLocation": "./.yarn/cache/string.prototype.codepointat-npm-0.2.1-82003deaf5-bafa15844d.zip/node_modules/string.prototype.codepointat/",\ + "packageDependencies": [\ + ["string.prototype.codepointat", "npm:0.2.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["string.prototype.matchall", [\ ["npm:4.0.8", {\ "packageLocation": "./.yarn/cache/string.prototype.matchall-npm-4.0.8-1feb1531b6-952da3a818.zip/node_modules/string.prototype.matchall/",\ @@ -24139,6 +24657,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["tiny-inflate", [\ + ["npm:1.0.3", {\ + "packageLocation": "./.yarn/cache/tiny-inflate-npm-1.0.3-a7419a5c65-4086a1f893.zip/node_modules/tiny-inflate/",\ + "packageDependencies": [\ + ["tiny-inflate", "npm:1.0.3"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["tmp", [\ ["npm:0.0.33", {\ "packageLocation": "./.yarn/cache/tmp-npm-0.0.33-bcbf65df2a-902d7aceb7.zip/node_modules/tmp/",\ @@ -24468,6 +24995,13 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ],\ "linkType": "HARD"\ }],\ + ["npm:0.7.1", {\ + "packageLocation": "./.yarn/cache/type-fest-npm-0.7.1-7b37912923-5b1b113529.zip/node_modules/type-fest/",\ + "packageDependencies": [\ + ["type-fest", "npm:0.7.1"]\ + ],\ + "linkType": "HARD"\ + }],\ ["npm:0.8.1", {\ "packageLocation": "./.yarn/cache/type-fest-npm-0.8.1-351ad028fe-d61c4b2eba.zip/node_modules/type-fest/",\ "packageDependencies": [\ @@ -24602,6 +25136,17 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["unicode-trie", [\ + ["npm:2.0.0", {\ + "packageLocation": "./.yarn/cache/unicode-trie-npm-2.0.0-54e0a4dd52-19e637ce20.zip/node_modules/unicode-trie/",\ + "packageDependencies": [\ + ["unicode-trie", "npm:2.0.0"],\ + ["pako", "npm:0.2.9"],\ + ["tiny-inflate", "npm:1.0.3"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["unified", [\ ["npm:9.2.0", {\ "packageLocation": "./.yarn/cache/unified-npm-9.2.0-2edf64a14a-0cac4ae119.zip/node_modules/unified/",\ @@ -25945,6 +26490,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@emotion/styled", "virtual:1efb7fe2099bed4120fcc4f39a0c22325b69ada92fc21796c27552c271c245ad70f9ac625ace3d0f903bd6cf322956fda9e646154a3ae6dc5809e9838f04e833#npm:11.10.5"],\ ["@next/font", "npm:13.0.5"],\ ["@radix-ui/react-accordion", "virtual:1efb7fe2099bed4120fcc4f39a0c22325b69ada92fc21796c27552c271c245ad70f9ac625ace3d0f903bd6cf322956fda9e646154a3ae6dc5809e9838f04e833#npm:1.0.1"],\ + ["@sentry/nextjs", "virtual:1efb7fe2099bed4120fcc4f39a0c22325b69ada92fc21796c27552c271c245ad70f9ac625ace3d0f903bd6cf322956fda9e646154a3ae6dc5809e9838f04e833#npm:7.44.0"],\ ["@storybook/addon-actions", "virtual:1efb7fe2099bed4120fcc4f39a0c22325b69ada92fc21796c27552c271c245ad70f9ac625ace3d0f903bd6cf322956fda9e646154a3ae6dc5809e9838f04e833#npm:6.5.13"],\ ["@storybook/addon-essentials", "virtual:1efb7fe2099bed4120fcc4f39a0c22325b69ada92fc21796c27552c271c245ad70f9ac625ace3d0f903bd6cf322956fda9e646154a3ae6dc5809e9838f04e833#npm:6.5.13"],\ ["@storybook/addon-interactions", "virtual:1efb7fe2099bed4120fcc4f39a0c22325b69ada92fc21796c27552c271c245ad70f9ac625ace3d0f903bd6cf322956fda9e646154a3ae6dc5809e9838f04e833#npm:6.5.13"],\ @@ -25968,6 +26514,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@typescript-eslint/eslint-plugin", "virtual:1efb7fe2099bed4120fcc4f39a0c22325b69ada92fc21796c27552c271c245ad70f9ac625ace3d0f903bd6cf322956fda9e646154a3ae6dc5809e9838f04e833#npm:5.45.0"],\ ["@typescript-eslint/parser", "virtual:1efb7fe2099bed4120fcc4f39a0c22325b69ada92fc21796c27552c271c245ad70f9ac625ace3d0f903bd6cf322956fda9e646154a3ae6dc5809e9838f04e833#npm:5.45.0"],\ ["@use-gesture/react", "virtual:1efb7fe2099bed4120fcc4f39a0c22325b69ada92fc21796c27552c271c245ad70f9ac625ace3d0f903bd6cf322956fda9e646154a3ae6dc5809e9838f04e833#npm:10.2.24"],\ + ["@vercel/og", "npm:0.5.3"],\ ["autoprefixer", "virtual:1efb7fe2099bed4120fcc4f39a0c22325b69ada92fc21796c27552c271c245ad70f9ac625ace3d0f903bd6cf322956fda9e646154a3ae6dc5809e9838f04e833#npm:10.4.13"],\ ["axios", "npm:1.2.0"],\ ["babel-loader", "virtual:1efb7fe2099bed4120fcc4f39a0c22325b69ada92fc21796c27552c271c245ad70f9ac625ace3d0f903bd6cf322956fda9e646154a3ae6dc5809e9838f04e833#npm:8.3.0"],\ @@ -26062,6 +26609,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["yoga-wasm-web", [\ + ["npm:0.3.3", {\ + "packageLocation": "./.yarn/cache/yoga-wasm-web-npm-0.3.3-3f063ab1a0-ff65192a83.zip/node_modules/yoga-wasm-web/",\ + "packageDependencies": [\ + ["yoga-wasm-web", "npm:0.3.3"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["zwitch", [\ ["npm:1.0.5", {\ "packageLocation": "./.yarn/cache/zwitch-npm-1.0.5-5911cef6ce-28a1bebaca.zip/node_modules/zwitch/",\ diff --git a/.yarn/cache/@resvg-resvg-wasm-npm-2.4.1-78feafa3c1-855aa1e5f1.zip b/.yarn/cache/@resvg-resvg-wasm-npm-2.4.1-78feafa3c1-855aa1e5f1.zip new file mode 100644 index 00000000..3040ba03 Binary files /dev/null and b/.yarn/cache/@resvg-resvg-wasm-npm-2.4.1-78feafa3c1-855aa1e5f1.zip differ diff --git a/.yarn/cache/@rollup-plugin-commonjs-npm-24.0.0-b18d79acac-e2a1bf295b.zip b/.yarn/cache/@rollup-plugin-commonjs-npm-24.0.0-b18d79acac-e2a1bf295b.zip new file mode 100644 index 00000000..6d6b53c9 Binary files /dev/null and b/.yarn/cache/@rollup-plugin-commonjs-npm-24.0.0-b18d79acac-e2a1bf295b.zip differ diff --git a/.yarn/cache/@rollup-pluginutils-npm-5.0.2-6aa9d0ddd4-edea15e543.zip b/.yarn/cache/@rollup-pluginutils-npm-5.0.2-6aa9d0ddd4-edea15e543.zip new file mode 100644 index 00000000..d898c503 Binary files /dev/null and b/.yarn/cache/@rollup-pluginutils-npm-5.0.2-6aa9d0ddd4-edea15e543.zip differ diff --git a/.yarn/cache/@sentry-browser-npm-7.44.0-cab936128d-c96781dce5.zip b/.yarn/cache/@sentry-browser-npm-7.44.0-cab936128d-c96781dce5.zip new file mode 100644 index 00000000..5b57c8bd Binary files /dev/null and b/.yarn/cache/@sentry-browser-npm-7.44.0-cab936128d-c96781dce5.zip differ diff --git a/.yarn/cache/@sentry-cli-npm-1.75.0-398a323f0b-d5b130bb45.zip b/.yarn/cache/@sentry-cli-npm-1.75.0-398a323f0b-d5b130bb45.zip new file mode 100644 index 00000000..f1e22726 Binary files /dev/null and b/.yarn/cache/@sentry-cli-npm-1.75.0-398a323f0b-d5b130bb45.zip differ diff --git a/.yarn/cache/@sentry-core-npm-7.44.0-19a12668fb-29591ea7b2.zip b/.yarn/cache/@sentry-core-npm-7.44.0-19a12668fb-29591ea7b2.zip new file mode 100644 index 00000000..5ede7512 Binary files /dev/null and b/.yarn/cache/@sentry-core-npm-7.44.0-19a12668fb-29591ea7b2.zip differ diff --git a/.yarn/cache/@sentry-integrations-npm-7.44.0-964fd76882-d2936a8357.zip b/.yarn/cache/@sentry-integrations-npm-7.44.0-964fd76882-d2936a8357.zip new file mode 100644 index 00000000..441272f6 Binary files /dev/null and b/.yarn/cache/@sentry-integrations-npm-7.44.0-964fd76882-d2936a8357.zip differ diff --git a/.yarn/cache/@sentry-internal-tracing-npm-7.44.0-2028ee1037-b15fd15467.zip b/.yarn/cache/@sentry-internal-tracing-npm-7.44.0-2028ee1037-b15fd15467.zip new file mode 100644 index 00000000..3a5a86f1 Binary files /dev/null and b/.yarn/cache/@sentry-internal-tracing-npm-7.44.0-2028ee1037-b15fd15467.zip differ diff --git a/.yarn/cache/@sentry-nextjs-npm-7.44.0-8371f99ce5-7350100e0d.zip b/.yarn/cache/@sentry-nextjs-npm-7.44.0-8371f99ce5-7350100e0d.zip new file mode 100644 index 00000000..89e56170 Binary files /dev/null and b/.yarn/cache/@sentry-nextjs-npm-7.44.0-8371f99ce5-7350100e0d.zip differ diff --git a/.yarn/cache/@sentry-node-npm-7.44.0-1d05948f24-7d8dbc4679.zip b/.yarn/cache/@sentry-node-npm-7.44.0-1d05948f24-7d8dbc4679.zip new file mode 100644 index 00000000..e49ed3e2 Binary files /dev/null and b/.yarn/cache/@sentry-node-npm-7.44.0-1d05948f24-7d8dbc4679.zip differ diff --git a/.yarn/cache/@sentry-react-npm-7.44.0-3ab2ed8d45-d8a166db17.zip b/.yarn/cache/@sentry-react-npm-7.44.0-3ab2ed8d45-d8a166db17.zip new file mode 100644 index 00000000..3e7940f1 Binary files /dev/null and b/.yarn/cache/@sentry-react-npm-7.44.0-3ab2ed8d45-d8a166db17.zip differ diff --git a/.yarn/cache/@sentry-replay-npm-7.44.0-81de3f117c-099ace8cd1.zip b/.yarn/cache/@sentry-replay-npm-7.44.0-81de3f117c-099ace8cd1.zip new file mode 100644 index 00000000..3405f0af Binary files /dev/null and b/.yarn/cache/@sentry-replay-npm-7.44.0-81de3f117c-099ace8cd1.zip differ diff --git a/.yarn/cache/@sentry-tracing-npm-7.44.0-1a43e129ac-1286c86fc1.zip b/.yarn/cache/@sentry-tracing-npm-7.44.0-1a43e129ac-1286c86fc1.zip new file mode 100644 index 00000000..708ced30 Binary files /dev/null and b/.yarn/cache/@sentry-tracing-npm-7.44.0-1a43e129ac-1286c86fc1.zip differ diff --git a/.yarn/cache/@sentry-types-npm-7.44.0-3942c12677-d053f37e32.zip b/.yarn/cache/@sentry-types-npm-7.44.0-3942c12677-d053f37e32.zip new file mode 100644 index 00000000..69cf76c7 Binary files /dev/null and b/.yarn/cache/@sentry-types-npm-7.44.0-3942c12677-d053f37e32.zip differ diff --git a/.yarn/cache/@sentry-utils-npm-7.44.0-1d375e96d7-2f19481b62.zip b/.yarn/cache/@sentry-utils-npm-7.44.0-1d375e96d7-2f19481b62.zip new file mode 100644 index 00000000..000bfc09 Binary files /dev/null and b/.yarn/cache/@sentry-utils-npm-7.44.0-1d375e96d7-2f19481b62.zip differ diff --git a/.yarn/cache/@sentry-webpack-plugin-npm-1.20.0-9eeec4f0d8-d582026c36.zip b/.yarn/cache/@sentry-webpack-plugin-npm-1.20.0-9eeec4f0d8-d582026c36.zip new file mode 100644 index 00000000..edba2f27 Binary files /dev/null and b/.yarn/cache/@sentry-webpack-plugin-npm-1.20.0-9eeec4f0d8-d582026c36.zip differ diff --git a/.yarn/cache/@shuding-opentype.js-npm-1.4.0-beta.0-498d62cde8-af3478c40c.zip b/.yarn/cache/@shuding-opentype.js-npm-1.4.0-beta.0-498d62cde8-af3478c40c.zip new file mode 100644 index 00000000..96aa5098 Binary files /dev/null and b/.yarn/cache/@shuding-opentype.js-npm-1.4.0-beta.0-498d62cde8-af3478c40c.zip differ diff --git a/.yarn/cache/@vercel-og-npm-0.5.3-c734504904-efe8588faf.zip b/.yarn/cache/@vercel-og-npm-0.5.3-c734504904-efe8588faf.zip new file mode 100644 index 00000000..5b348baa Binary files /dev/null and b/.yarn/cache/@vercel-og-npm-0.5.3-c734504904-efe8588faf.zip differ diff --git a/.yarn/cache/base64-js-npm-0.0.8-f2946f7960-e95d2fa4b9.zip b/.yarn/cache/base64-js-npm-0.0.8-f2946f7960-e95d2fa4b9.zip new file mode 100644 index 00000000..6ca32726 Binary files /dev/null and b/.yarn/cache/base64-js-npm-0.0.8-f2946f7960-e95d2fa4b9.zip differ diff --git a/.yarn/cache/camelize-npm-1.0.1-d86ebe085a-91d8611d09.zip b/.yarn/cache/camelize-npm-1.0.1-d86ebe085a-91d8611d09.zip new file mode 100644 index 00000000..81cef64d Binary files /dev/null and b/.yarn/cache/camelize-npm-1.0.1-d86ebe085a-91d8611d09.zip differ diff --git a/.yarn/cache/chalk-npm-3.0.0-e813208025-8e3ddf3981.zip b/.yarn/cache/chalk-npm-3.0.0-e813208025-8e3ddf3981.zip new file mode 100644 index 00000000..47b36c70 Binary files /dev/null and b/.yarn/cache/chalk-npm-3.0.0-e813208025-8e3ddf3981.zip differ diff --git a/.yarn/cache/css-background-parser-npm-0.1.0-d1e94221f7-cf53bef8d5.zip b/.yarn/cache/css-background-parser-npm-0.1.0-d1e94221f7-cf53bef8d5.zip new file mode 100644 index 00000000..d75a2650 Binary files /dev/null and b/.yarn/cache/css-background-parser-npm-0.1.0-d1e94221f7-cf53bef8d5.zip differ diff --git a/.yarn/cache/css-box-shadow-npm-1.0.0-3-2a025ab419-1b7f15b291.zip b/.yarn/cache/css-box-shadow-npm-1.0.0-3-2a025ab419-1b7f15b291.zip new file mode 100644 index 00000000..78858862 Binary files /dev/null and b/.yarn/cache/css-box-shadow-npm-1.0.0-3-2a025ab419-1b7f15b291.zip differ diff --git a/.yarn/cache/css-color-keywords-npm-1.0.0-fc176df58b-8f125e3ad4.zip b/.yarn/cache/css-color-keywords-npm-1.0.0-fc176df58b-8f125e3ad4.zip new file mode 100644 index 00000000..9886779c Binary files /dev/null and b/.yarn/cache/css-color-keywords-npm-1.0.0-fc176df58b-8f125e3ad4.zip differ diff --git a/.yarn/cache/css-to-react-native-npm-3.2.0-46e31a25e3-263be65e80.zip b/.yarn/cache/css-to-react-native-npm-3.2.0-46e31a25e3-263be65e80.zip new file mode 100644 index 00000000..48e96509 Binary files /dev/null and b/.yarn/cache/css-to-react-native-npm-3.2.0-46e31a25e3-263be65e80.zip differ diff --git a/.yarn/cache/emoji-regex-npm-10.2.1-463e5e2567-1aa2d16881.zip b/.yarn/cache/emoji-regex-npm-10.2.1-463e5e2567-1aa2d16881.zip new file mode 100644 index 00000000..75c10d7a Binary files /dev/null and b/.yarn/cache/emoji-regex-npm-10.2.1-463e5e2567-1aa2d16881.zip differ diff --git a/.yarn/cache/estree-walker-npm-2.0.2-dfab42f65c-6151e6f982.zip b/.yarn/cache/estree-walker-npm-2.0.2-dfab42f65c-6151e6f982.zip new file mode 100644 index 00000000..71b90a2f Binary files /dev/null and b/.yarn/cache/estree-walker-npm-2.0.2-dfab42f65c-6151e6f982.zip differ diff --git a/.yarn/cache/fflate-npm-0.7.4-df9245ab05-b812ab2604.zip b/.yarn/cache/fflate-npm-0.7.4-df9245ab05-b812ab2604.zip new file mode 100644 index 00000000..982b446b Binary files /dev/null and b/.yarn/cache/fflate-npm-0.7.4-df9245ab05-b812ab2604.zip differ diff --git a/.yarn/cache/glob-npm-8.1.0-65f64af8b1-92fbea3221.zip b/.yarn/cache/glob-npm-8.1.0-65f64af8b1-92fbea3221.zip new file mode 100644 index 00000000..3fc76b57 Binary files /dev/null and b/.yarn/cache/glob-npm-8.1.0-65f64af8b1-92fbea3221.zip differ diff --git a/.yarn/cache/immediate-npm-3.0.6-c27588a2d3-f9b3486477.zip b/.yarn/cache/immediate-npm-3.0.6-c27588a2d3-f9b3486477.zip new file mode 100644 index 00000000..d3f74981 Binary files /dev/null and b/.yarn/cache/immediate-npm-3.0.6-c27588a2d3-f9b3486477.zip differ diff --git a/.yarn/cache/is-reference-npm-1.2.1-87ca1743c8-e7b48149f8.zip b/.yarn/cache/is-reference-npm-1.2.1-87ca1743c8-e7b48149f8.zip new file mode 100644 index 00000000..bae17ee6 Binary files /dev/null and b/.yarn/cache/is-reference-npm-1.2.1-87ca1743c8-e7b48149f8.zip differ diff --git a/.yarn/cache/lie-npm-3.1.1-91350720d9-6da9f2121d.zip b/.yarn/cache/lie-npm-3.1.1-91350720d9-6da9f2121d.zip new file mode 100644 index 00000000..dfbd4858 Binary files /dev/null and b/.yarn/cache/lie-npm-3.1.1-91350720d9-6da9f2121d.zip differ diff --git a/.yarn/cache/linebreak-npm-1.1.0-d845ecba6a-65cb66900b.zip b/.yarn/cache/linebreak-npm-1.1.0-d845ecba6a-65cb66900b.zip new file mode 100644 index 00000000..e4364f84 Binary files /dev/null and b/.yarn/cache/linebreak-npm-1.1.0-d845ecba6a-65cb66900b.zip differ diff --git a/.yarn/cache/localforage-npm-1.10.0-cf9ea9a436-f2978b434d.zip b/.yarn/cache/localforage-npm-1.10.0-cf9ea9a436-f2978b434d.zip new file mode 100644 index 00000000..b9f85f79 Binary files /dev/null and b/.yarn/cache/localforage-npm-1.10.0-cf9ea9a436-f2978b434d.zip differ diff --git a/.yarn/cache/lru_map-npm-0.3.3-a038bb3418-ca9dd43c65.zip b/.yarn/cache/lru_map-npm-0.3.3-a038bb3418-ca9dd43c65.zip new file mode 100644 index 00000000..7cf30ebf Binary files /dev/null and b/.yarn/cache/lru_map-npm-0.3.3-a038bb3418-ca9dd43c65.zip differ diff --git a/.yarn/cache/magic-string-npm-0.27.0-a60a83c0b4-273faaa50b.zip b/.yarn/cache/magic-string-npm-0.27.0-a60a83c0b4-273faaa50b.zip new file mode 100644 index 00000000..a34694fb Binary files /dev/null and b/.yarn/cache/magic-string-npm-0.27.0-a60a83c0b4-273faaa50b.zip differ diff --git a/.yarn/cache/pako-npm-0.2.9-c88ac0d326-055f9487cd.zip b/.yarn/cache/pako-npm-0.2.9-c88ac0d326-055f9487cd.zip new file mode 100644 index 00000000..b75362fe Binary files /dev/null and b/.yarn/cache/pako-npm-0.2.9-c88ac0d326-055f9487cd.zip differ diff --git a/.yarn/cache/progress-npm-2.0.3-d1f87e2ac6-f67403fe7b.zip b/.yarn/cache/progress-npm-2.0.3-d1f87e2ac6-f67403fe7b.zip new file mode 100644 index 00000000..0585bd0a Binary files /dev/null and b/.yarn/cache/progress-npm-2.0.3-d1f87e2ac6-f67403fe7b.zip differ diff --git a/.yarn/cache/rollup-npm-2.78.0-09284f4c78-01b5a7ae08.zip b/.yarn/cache/rollup-npm-2.78.0-09284f4c78-01b5a7ae08.zip new file mode 100644 index 00000000..f83a6421 Binary files /dev/null and b/.yarn/cache/rollup-npm-2.78.0-09284f4c78-01b5a7ae08.zip differ diff --git a/.yarn/cache/satori-npm-0.4.9-d045a2f54e-4b616c81dc.zip b/.yarn/cache/satori-npm-0.4.9-d045a2f54e-4b616c81dc.zip new file mode 100644 index 00000000..dcd1a437 Binary files /dev/null and b/.yarn/cache/satori-npm-0.4.9-d045a2f54e-4b616c81dc.zip differ diff --git a/.yarn/cache/stacktrace-parser-npm-0.1.10-36f3e571bd-f4fbddfc09.zip b/.yarn/cache/stacktrace-parser-npm-0.1.10-36f3e571bd-f4fbddfc09.zip new file mode 100644 index 00000000..7c60bb39 Binary files /dev/null and b/.yarn/cache/stacktrace-parser-npm-0.1.10-36f3e571bd-f4fbddfc09.zip differ diff --git a/.yarn/cache/string.prototype.codepointat-npm-0.2.1-82003deaf5-bafa15844d.zip b/.yarn/cache/string.prototype.codepointat-npm-0.2.1-82003deaf5-bafa15844d.zip new file mode 100644 index 00000000..e1c9a304 Binary files /dev/null and b/.yarn/cache/string.prototype.codepointat-npm-0.2.1-82003deaf5-bafa15844d.zip differ diff --git a/.yarn/cache/tiny-inflate-npm-1.0.3-a7419a5c65-4086a1f893.zip b/.yarn/cache/tiny-inflate-npm-1.0.3-a7419a5c65-4086a1f893.zip new file mode 100644 index 00000000..094ecc0e Binary files /dev/null and b/.yarn/cache/tiny-inflate-npm-1.0.3-a7419a5c65-4086a1f893.zip differ diff --git a/.yarn/cache/type-fest-npm-0.7.1-7b37912923-5b1b113529.zip b/.yarn/cache/type-fest-npm-0.7.1-7b37912923-5b1b113529.zip new file mode 100644 index 00000000..230bd110 Binary files /dev/null and b/.yarn/cache/type-fest-npm-0.7.1-7b37912923-5b1b113529.zip differ diff --git a/.yarn/cache/unicode-trie-npm-2.0.0-54e0a4dd52-19e637ce20.zip b/.yarn/cache/unicode-trie-npm-2.0.0-54e0a4dd52-19e637ce20.zip new file mode 100644 index 00000000..eaa1d677 Binary files /dev/null and b/.yarn/cache/unicode-trie-npm-2.0.0-54e0a4dd52-19e637ce20.zip differ diff --git a/.yarn/cache/yoga-wasm-web-npm-0.3.3-3f063ab1a0-ff65192a83.zip b/.yarn/cache/yoga-wasm-web-npm-0.3.3-3f063ab1a0-ff65192a83.zip new file mode 100644 index 00000000..d1342f8a Binary files /dev/null and b/.yarn/cache/yoga-wasm-web-npm-0.3.3-3f063ab1a0-ff65192a83.zip differ diff --git a/mocks/handlers/meme/index.ts b/mocks/handlers/meme/index.ts index a17fac4a..6ae15266 100644 --- a/mocks/handlers/meme/index.ts +++ b/mocks/handlers/meme/index.ts @@ -15,7 +15,7 @@ export const getMemeDetail = rest.get( return res( ctx.status(200), - ctx.delay(300), + ctx.delay(), ctx.json({ memeId, name: "제목", diff --git a/mocks/handlers/search/index.ts b/mocks/handlers/search/index.ts index 18a8c4b1..08b9d984 100644 --- a/mocks/handlers/search/index.ts +++ b/mocks/handlers/search/index.ts @@ -1,27 +1,9 @@ import { rest } from "msw"; -import type { GetMemesResponse } from "@/types"; +import type { GetMemesResponse, GetSearchMemesResponse } from "@/types"; import * as MOCK_DATA from "./data"; -export const getSearch = rest.get( - `${process.env.NEXT_PUBLIC_API_URL}/tags/search`, - (req, res, ctx) => { - const query = req.url.searchParams.get("word"); - - if (!query) { - return res(ctx.delay(), ctx.status(404)); - } - - return res( - ctx.delay(), - ctx.json({ - tags: MOCK_DATA.tags.filter((tag) => tag.name.search(query) !== -1), - }), - ); - }, -); - export const getSearchResultsByKeyword = rest.get( `${process.env.NEXT_PUBLIC_SEARCH_API_URL}/search`, (req, res, ctx) => { @@ -36,9 +18,10 @@ export const getSearchResultsByKeyword = rest.get( } return res( ctx.status(200), - ctx.json({ + ctx.json({ memes: data, count: data.length, + totalCount: 100, }), ctx.delay(500), ); @@ -59,9 +42,10 @@ export const getSearchResultsByTag = rest.get( } return res( ctx.status(200), - ctx.json({ + ctx.json({ memes: data, count: data.length, + totalCount: 100, }), ctx.delay(500), ); diff --git a/mocks/handlers/tags/data.ts b/mocks/handlers/tags/data.ts index ec58c77c..8bb1b94e 100644 --- a/mocks/handlers/tags/data.ts +++ b/mocks/handlers/tags/data.ts @@ -3,109 +3,302 @@ export const popularTag = [ tagId: 1, name: "무", categoryName: "sample", + imageUrl: "https://picsum.photos/200/100", viewCount: 3, }, { tagId: 2, name: "무한", categoryName: "sample", + imageUrl: "https://picsum.photos/200/200", viewCount: 3, }, { tagId: 3, name: "무한도", categoryName: "sample", + imageUrl: "https://picsum.photos/200/300", viewCount: 3, }, { tagId: 4, name: "무한도전", categoryName: "sample", + imageUrl: "https://picsum.photos/200/200", viewCount: 3, }, { tagId: 5, name: "무한도전전전전", categoryName: "sample", + imageUrl: "https://picsum.photos/200/300", viewCount: 3, }, { tagId: 6, name: "무한도전전전전전전전전", categoryName: "sample", - viewCount: 3, - }, - { - tagId: 7, - name: "무한도전전전전전전전전", - categoryName: "sample", + imageUrl: "https://picsum.photos/200/400", viewCount: 3, }, ]; -export const categories = [ +export const mainCategories = [ { - categoryId: 1, - name: "카테고리 명1", - icon: "https://raw.githubusercontent.com/toss/tossface/cec7ea0420b7f17d6f546fd7359da9bd4cb3315c/dist/svg/u1F363.svg", + mainCategoryId: 1, + name: "사용자", + icon: "https://raw.githubusercontent.com/toss/tossface/cec7ea0420b7f17d6f546fd7359da9bd4cb3315c/dist/svg/u1F364.svg", priority: 100, - tags: [ - { - tagId: 1, - name: "개발자", - viewCount: 52, - isFav: true, - }, + hasSub: true, + categories: [ { - tagId: 4, - name: "페페", - viewCount: 26, - isFav: false, + categoryId: 2, + name: "성격", + priority: 200, + tags: [ + { + tagId: 3, + name: "거짓말쟁이", + }, + { + tagId: 4, + name: "귀요미", + }, + ], }, { - tagId: 5, - name: "유머", - viewCount: 140, - isFav: true, + categoryId: 1, + name: "직업", + priority: 100, + tags: [ + { + tagId: 1, + name: "개발자", + }, + { + tagId: 2, + name: "디자이너", + }, + ], }, ], }, { - categoryId: 2, - name: "카테고리 명2", + mainCategoryId: 2, + name: "감정", icon: "https://raw.githubusercontent.com/toss/tossface/cec7ea0420b7f17d6f546fd7359da9bd4cb3315c/dist/svg/u1F364.svg", priority: 200, - tags: [ + hasSub: true, + categories: [ + { + categoryId: 3, + name: "긍정적", + priority: 300, + tags: [ + { + tagId: 2, + name: "기쁨", + }, + { + tagId: 3, + name: "고마움", + viewCount: 34, + isFav: false, + }, + ], + }, { - tagId: 2, - name: "에브리타임", - viewCount: 49, - isFav: true, + categoryId: 4, + name: "부정적", + priority: 300, + tags: [ + { + tagId: 13, + name: "미안함", + }, + { + tagId: 14, + name: "짜증", + }, + ], }, + ], + }, + { + mainCategoryId: 3, + name: "행위", + icon: "https://github.com/toss/tossface/blob/main/dist/svg/u1F363.svg", + priority: 300, + hasSub: true, + categories: [ + { + categoryId: 5, + name: "행동", + priority: 300, + tags: [ + { + tagId: 3, + name: "결심할때", + }, + { + tagId: 12, + name: "기다릴때", + }, + ], + }, + { + categoryId: 6, + name: "성향", + priority: 400, + tags: [ + { + tagId: 3, + name: "개발할때", + }, + { + tagId: 12, + name: "더울때", + }, + ], + }, + ], + }, + { + mainCategoryId: 4, + name: "콘텐츠", + icon: "https://github.com/toss/tossface/blob/main/dist/svg/u1F364.svg", + priority: 300, + hasSub: true, + + categories: [ { - tagId: 3, - name: "시험기간", - viewCount: 34, - isFav: true, + categoryId: 7, + name: "애니메이션", + priority: 500, + tags: [ + { + tagId: 2, + name: "핀과 제이크", + }, + ], }, { - tagId: 12, - name: "무전", - viewCount: 18, - isFav: false, + categoryId: 8, + name: "예능", + priority: 500, + tags: [ + { + tagId: 3, + name: "런닝맨", + }, + { + tagId: 12, + name: "무한도전", + }, + ], }, + ], + }, + { + mainCategoryId: 5, + name: "캐릭터", + icon: "https://raw.githubusercontent.com/toss/tossface/cec7ea0420b7f17d6f546fd7359da9bd4cb3315c/dist/svg/u1F60E.svg", + priority: 5, + hasSub: true, + categories: [ { - tagId: 13, - name: "무한전", - viewCount: 8, - isFav: false, + categoryId: 31, + name: "인물", + priority: 5, + tags: [ + { + tagId: 321, + name: "박명수", + }, + { + tagId: 343, + name: "유재석", + }, + { + tagId: 356, + name: "정준하", + }, + { + tagId: 463, + name: "정형돈", + }, + { + tagId: 355, + name: "하하", + }, + ], }, + ], + }, + { + mainCategoryId: 6, + name: "기타", + icon: "https://github.com/toss/tossface/blob/main/dist/svg/u1F365.svg", + priority: 400, + hasSub: false, + categories: [ { - tagId: 14, - name: "무도전", - viewCount: 2, - isFav: false, + categoryId: 9, + name: "기타", + priority: 500, + tags: [ + { + tagId: 2, + name: "박명수", + }, + ], }, ], }, ]; + +export const mainTags = [ + [ + { + tagId: 384, + name: "ISTJ", + }, + { + tagId: 394, + name: "디자이너", + }, + ], + [ + { + tagId: 384, + name: "서러움", + }, + { + tagId: 394, + name: "측은함", + }, + ], + [ + { + tagId: 384, + name: "거절할때", + }, + { + tagId: 394, + name: "서운할때", + }, + ], +]; + +export const favoriteTags = [ + { + tagId: 321, + name: "박명수", + isFav: true, + }, + { + tagId: 322, + name: "무한도전", + isFav: true, + }, +]; diff --git a/mocks/handlers/tags/index.ts b/mocks/handlers/tags/index.ts index 7b88b381..d66fed37 100644 --- a/mocks/handlers/tags/index.ts +++ b/mocks/handlers/tags/index.ts @@ -2,21 +2,15 @@ import { rest } from "msw"; import * as MOCK_DATA from "./data"; -export const getPopularTag = rest.get( - `${process.env.NEXT_PUBLIC_API_URL}/tags`, - async (req, res, ctx) => { - return res(ctx.delay(), ctx.status(200), ctx.json({ tags: MOCK_DATA.popularTag })); - }, -); - export const getCategoryWithTag = rest.get( - `${process.env.NEXT_PUBLIC_API_URL}/tags/categories`, + `${process.env.NEXT_PUBLIC_API_URL}/tags/categories/new`, async (req, res, ctx) => { return res( ctx.delay(300), ctx.status(200), ctx.json({ - categories: MOCK_DATA.categories, + mainCategories: MOCK_DATA.mainCategories, + mainTags: MOCK_DATA.mainTags, }), ); }, @@ -39,6 +33,19 @@ export const getMemeTagsById = rest.get( }, ); +export const getFavoriteTags = rest.get( + `${process.env.NEXT_PUBLIC_API_URL}/tags/favs`, + async (req, res, ctx) => { + return res( + ctx.delay(300), + ctx.status(200), + ctx.json({ + tags: MOCK_DATA.favoriteTags, + }), + ); + }, +); + export const postFavoriteTag = rest.post( `${process.env.NEXT_PUBLIC_API_URL}/tags/:tagId/fav`, async (req, res, ctx) => { @@ -53,6 +60,19 @@ export const deleteFavoriteTag = rest.delete( }, ); +export const getTagAutoSearch = rest.get( + `${process.env.NEXT_PUBLIC_API_URL}/tags/search`, + async (req, res, ctx) => { + return res( + ctx.delay(), + ctx.status(200), + ctx.json({ + tags: MOCK_DATA.favoriteTags, + }), + ); + }, +); + /** * 태그 상세 조회 API */ @@ -75,3 +95,16 @@ export const getTagInfo = rest.get( ); }, ); + +export const getTagRank = rest.get( + `${process.env.NEXT_PUBLIC_API_URL}/tags/rank/new`, + async (req, res, ctx) => { + return res( + ctx.delay(), + ctx.status(200), + ctx.json({ + tags: MOCK_DATA.popularTag, + }), + ); + }, +); diff --git a/next.config.js b/next.config.js index faba3ba6..efef2d23 100644 --- a/next.config.js +++ b/next.config.js @@ -1,5 +1,14 @@ +// This file sets a custom webpack configuration to use your Next.js app +// with Sentry. +// https://nextjs.org/docs/api-reference/next.config.js/introduction +// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/ +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { withSentryConfig } = require("@sentry/nextjs"); + // eslint-disable-next-line @typescript-eslint/no-var-requires const path = require("path"); +const IS_DEV = process.env.NODE_ENV === "development"; +const IS_PROD = process.env.NODE_ENV === "production"; // The folders containing files importing twin.macro const includedDirs = [ @@ -13,7 +22,7 @@ const includedDirs = [ // eslint-disable-next-line @typescript-eslint/no-var-requires const withPWA = require("next-pwa")({ dest: "public", - disable: process.env.NODE_ENV === "development", + disable: IS_DEV, }); /** @type {import("next").NextConfig} */ @@ -85,6 +94,15 @@ const nextConfig = withPWA({ deviceSizes: [512], contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;", }, + sentry: { + // Use `hidden-source-map` rather than `source-map` as the Webpack `devtool` + // for client-side builds. (This will be the default starting in + // `@sentry/nextjs` version 8.0.0.) See + // https://webpack.js.org/configuration/devtool/ and + // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/#use-hidden-source-map + // for more information. + hideSourceMaps: IS_PROD, + }, }); -module.exports = nextConfig; +module.exports = withSentryConfig(nextConfig, { silent: true }); diff --git a/package.json b/package.json index e4ac62a8..9ae5db4c 100644 --- a/package.json +++ b/package.json @@ -26,8 +26,10 @@ "@emotion/styled": "^11.10.5", "@next/font": "13.0.5", "@radix-ui/react-accordion": "^1.0.1", + "@sentry/nextjs": "^7.44.0", "@tanstack/react-query": "^4.19.1", "@use-gesture/react": "^10.2.24", + "@vercel/og": "^0.5.3", "axios": "^1.2.0", "lottie-web": "^5.10.2", "next": "13.0.5", diff --git a/public/icon/instagram-logo.svg b/public/icon/instagram-logo.svg new file mode 100644 index 00000000..0b852c4f --- /dev/null +++ b/public/icon/instagram-logo.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/icon/logo.svg b/public/icon/logo.svg index 343ad8e2..485f33e6 100644 --- a/public/icon/logo.svg +++ b/public/icon/logo.svg @@ -1,4 +1,4 @@ - - - + + + diff --git a/public/icon/memeChannel.svg b/public/icon/memeChannel.svg new file mode 100644 index 00000000..533d22ec --- /dev/null +++ b/public/icon/memeChannel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icon/recent-memesort.svg b/public/icon/recent-memesort.svg new file mode 100644 index 00000000..f5c792f0 --- /dev/null +++ b/public/icon/recent-memesort.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icon/share-memesort.svg b/public/icon/share-memesort.svg new file mode 100644 index 00000000..185972ba --- /dev/null +++ b/public/icon/share-memesort.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icon/twitter-logo.svg b/public/icon/twitter-logo.svg new file mode 100644 index 00000000..2a15d50a --- /dev/null +++ b/public/icon/twitter-logo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/img/default-avatar.png b/public/img/default-avatar.png new file mode 100644 index 00000000..bc152310 Binary files /dev/null and b/public/img/default-avatar.png differ diff --git a/public/img/serviceGuide.svg b/public/img/serviceGuide.svg new file mode 100644 index 00000000..b2735071 --- /dev/null +++ b/public/img/serviceGuide.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/open-graph/home.png b/public/open-graph/home.png index 87205977..a74c892a 100644 Binary files a/public/open-graph/home.png and b/public/open-graph/home.png differ diff --git a/sentry.client.config.js b/sentry.client.config.js new file mode 100644 index 00000000..95ffafa0 --- /dev/null +++ b/sentry.client.config.js @@ -0,0 +1,15 @@ +// This file configures the initialization of Sentry on the browser. +// The config you add here will be used whenever a page is visited. +// https://docs.sentry.io/platforms/javascript/guides/nextjs/ + +import * as Sentry from "@sentry/nextjs"; + +const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN; + +if (process.env.NODE_ENV === "production") { + Sentry.init({ + dsn: SENTRY_DSN, + // Adjust this value in production, or use tracesSampler for greater control + tracesSampleRate: 1.0, + }); +} diff --git a/sentry.edge.config.js b/sentry.edge.config.js new file mode 100644 index 00000000..14a2e151 --- /dev/null +++ b/sentry.edge.config.js @@ -0,0 +1,15 @@ +// This file configures the initialization of Sentry on the server. +// The config you add here will be used whenever middleware or an Edge route handles a request. +// https://docs.sentry.io/platforms/javascript/guides/nextjs/ + +import * as Sentry from "@sentry/nextjs"; + +const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN; + +if (process.env.NODE_ENV === "production") { + Sentry.init({ + dsn: SENTRY_DSN, + // Adjust this value in production, or use tracesSampler for greater control + tracesSampleRate: 1.0, + }); +} diff --git a/sentry.properties b/sentry.properties new file mode 100644 index 00000000..f90ea3a3 --- /dev/null +++ b/sentry.properties @@ -0,0 +1,3 @@ +defaults.url=https://sentry.io/ +defaults.org=thismeme +defaults.project=thismeme-web diff --git a/sentry.server.config.js b/sentry.server.config.js new file mode 100644 index 00000000..09491fe3 --- /dev/null +++ b/sentry.server.config.js @@ -0,0 +1,15 @@ +// This file configures the initialization of Sentry on the server. +// The config you add here will be used whenever the server handles a request. +// https://docs.sentry.io/platforms/javascript/guides/nextjs/ + +import * as Sentry from "@sentry/nextjs"; + +const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN; + +if (process.env.NODE_ENV === "production") { + Sentry.init({ + dsn: SENTRY_DSN, + // Adjust this value in production, or use tracesSampler for greater control + tracesSampleRate: 1.0, + }); +} diff --git a/src/application/hooks/api/core/useCoreInfiniteQuery.ts b/src/application/hooks/api/core/useCoreInfiniteQuery.ts index 6e3c642b..14f54432 100644 --- a/src/application/hooks/api/core/useCoreInfiniteQuery.ts +++ b/src/application/hooks/api/core/useCoreInfiniteQuery.ts @@ -40,7 +40,14 @@ export function useCoreInfiniteQuery< "queryKey" | "queryFn" | "select" > & { select: UseCoreInfiniteQuerySelect }, ): UseCoreInfiniteQueryResult { - const { data, isFetching, isFetchingNextPage, ...rest } = useInfiniteQuery(queryKey, queryFn, { + const { + data, + isFetching, + isFetchingNextPage, + isError, + fetchNextPage: oldFetchNextPage, + ...rest + } = useInfiniteQuery(queryKey, queryFn, { getNextPageParam: (lastPage, allPages) => { const { count } = lastPage; const isLastPage = count < pageSize; @@ -49,10 +56,20 @@ export function useCoreInfiniteQuery< }, ...options, }); + const fetchNextPage = isError ? ((() => {}) as typeof oldFetchNextPage) : oldFetchNextPage; const flatData = data ? data.pages.flatMap(({ data }) => data) : []; const isEmpty = data?.pages[0].data.length === 0; const isFetchingBackground = isFetching && !isFetchingNextPage; - return { data: flatData, isEmpty, isFetching, isFetchingNextPage, isFetchingBackground, ...rest }; + return { + data: flatData, + isEmpty, + isFetching, + isFetchingNextPage, + isFetchingBackground, + isError, + fetchNextPage, + ...rest, + }; } diff --git a/src/application/hooks/api/search/index.ts b/src/application/hooks/api/search/index.ts index 7a2efe6f..054ea2b8 100644 --- a/src/application/hooks/api/search/index.ts +++ b/src/application/hooks/api/search/index.ts @@ -1,4 +1,5 @@ import type { QueryClient, QueryFunctionContext } from "@tanstack/react-query"; +import { useInfiniteQuery } from "@tanstack/react-query"; import { useEffect, useRef } from "react"; import type { RecentSearch } from "@/application/hooks"; @@ -45,24 +46,28 @@ export const useGetMemesByKeyword = (keyword: string) => { * isEmpty - 밈 검색 결과가 없는 경우 true */ export const useGetMemesByTag = (tag: string) => { - const { data, isEmpty, isFetchingNextPage, fetchNextPage } = useCoreInfiniteQuery( + const { data, isFetchingNextPage, fetchNextPage } = useInfiniteQuery( QUERY_KEYS.getMemesByTag(tag), ({ pageParam = 0 }: QueryFunctionContext) => api.search.getMemesByTag({ keyword: tag, offset: pageParam, limit: PAGE_SIZE }), - PAGE_SIZE, { - enabled: !!tag, - select: (data) => { - return { - pages: data.pages.map((page) => ({ data: page.memes })), - pageParams: data.pageParams, - }; + getNextPageParam: (lastPage, allPages) => { + const { count } = lastPage; + const isLastPage = count < PAGE_SIZE; + const offset = count * (allPages.length - 1); + return isLastPage ? undefined : offset + PAGE_SIZE; }, + enabled: !!tag, }, ); - return { data, isEmpty, isFetchingNextPage, fetchNextPage }; + const memes = data?.pages.flatMap(({ memes }) => memes) || []; + const totalCount = data?.pages[0].totalCount; + const isEmpty = !memes.length; + + return { data: memes, totalCount, isEmpty, isFetchingNextPage, fetchNextPage }; }; + export const prefetchMemesByTag = (tag: string, queryClient: QueryClient) => queryClient.fetchInfiniteQuery(QUERY_KEYS.getMemesByTag(tag), ({ pageParam = 0 }) => api.search.getMemesByTag({ keyword: tag, offset: pageParam, limit: PAGE_SIZE }), diff --git a/src/application/hooks/api/tags/index.ts b/src/application/hooks/api/tags/index.ts index f7a2a045..80614d16 100644 --- a/src/application/hooks/api/tags/index.ts +++ b/src/application/hooks/api/tags/index.ts @@ -5,7 +5,11 @@ import { useSuspendedQuery } from "@/application/hooks/api/core"; import type { QuerySelectOption } from "@/application/hooks/api/core/types"; import { delay } from "@/application/util"; import { api } from "@/infra/api"; -import type { GetPopularTagsResponse, GetTagSearchResponse } from "@/infra/api/tags/types"; +import type { + GetFavoriteTagsResponse, + GetPopularTagsResponse, + GetTagSearchResponse, +} from "@/infra/api/tags/types"; import { QUERY_KEYS } from "./queryKey"; @@ -13,12 +17,12 @@ import { QUERY_KEYS } from "./queryKey"; * 인기 태그 조회 API */ export const useGetPopularTags = () => { - const { data, ...rest } = useQuery({ + const { data } = useQuery({ queryKey: QUERY_KEYS.getPopularTags, queryFn: () => api.tags.getPopularTags(), }); - return { tags: data?.tags, ...rest }; + return { ...data }; }; /** @@ -37,19 +41,16 @@ export const useGetTagSearch = (value: string) => { /** * @desc - * Navigation Drawer (SideBar) 카테고리/태그 + * Tag Category 에 즐겨찾기를 제외한 태그들 */ export const useGetCategoryWithTag = ({ select, - enabled = true, }: { select: QuerySelectOption; - enabled?: boolean; }) => useQuery({ queryKey: QUERY_KEYS.getCategoryWithTags, queryFn: api.tags.getCategoryWithTags, - enabled, select, }); @@ -80,29 +81,48 @@ export const useGetTagInfo = ( export const fetchTagInfo = (tagId: number, queryClient: QueryClient) => queryClient.fetchQuery(QUERY_KEYS.getTagInfo(tagId), () => api.tags.getTagInfo(tagId)); +export const useGetFavoriteTags = ( + options: Pick = { enabled: false }, +) => { + const { data } = useQuery({ + queryKey: QUERY_KEYS.getFavoriteTags, + queryFn: () => api.tags.getFavoriteTags(), + ...options, + }); + + return { favoriteTags: data?.tags }; +}; + export const usePostFavoriteTag = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: api.tags.postFavoriteTag, - onMutate: async (id) => { - await queryClient.cancelQueries({ queryKey: QUERY_KEYS.getTagInfo(id) }); - const previousTagInfo = queryClient.getQueryData(QUERY_KEYS.getTagInfo(id)) as Awaited< + onMutate: async ({ tagId, name }) => { + await queryClient.cancelQueries({ queryKey: QUERY_KEYS.getTagInfo(tagId) }); + + const previousTagInfo = queryClient.getQueryData(QUERY_KEYS.getTagInfo(tagId)) as Awaited< ReturnType >; - queryClient.setQueryData(QUERY_KEYS.getCategoryWithTags, (old) => { - const newCategory = ( - old as Awaited> - ).categories.map((category) => ({ - ...category, - tags: category.tags.map((tag) => (tag.tagId === id ? { ...tag, isFav: true } : tag)), - })); - - return { categories: newCategory }; - }); - - queryClient.setQueryData(QUERY_KEYS.getTagInfo(id), (old) => ({ + queryClient.setQueryData>>( + QUERY_KEYS.getFavoriteTags, + (old) => { + if (!old) return; + const newTags = [ + ...old.tags, + { + tagId: tagId, + name: name, + isFav: true, + }, + ]; + + return { tags: newTags }; + }, + ); + + queryClient.setQueryData(QUERY_KEYS.getTagInfo(tagId), (old) => ({ ...(old as Awaited>), isFav: true, })); @@ -110,8 +130,8 @@ export const usePostFavoriteTag = () => { return { previousTagInfo }; }, - onError: (err, id, context) => { - queryClient.setQueryData(QUERY_KEYS.getTagInfo(id), context?.previousTagInfo); + onError: (err, { tagId }, context) => { + queryClient.setQueryData(QUERY_KEYS.getTagInfo(tagId), context?.previousTagInfo); }, }); }; @@ -124,37 +144,35 @@ export const useDeleteFavoriteTag = (wait = 0) => { const mutation = useMutation({ mutationFn: (id: number) => api.tags.deleteFavoriteTag(id, controller.signal), onMutate: async (id) => { - await queryClient.cancelQueries({ queryKey: QUERY_KEYS.getCategoryWithTags }); await queryClient.cancelQueries({ queryKey: QUERY_KEYS.getTagInfo(id) }); + await queryClient.cancelQueries({ queryKey: QUERY_KEYS.getFavoriteTags }); - const previousCategory = queryClient.getQueryData(QUERY_KEYS.getCategoryWithTags); + const previousFavoriteCategory = queryClient.getQueryData(QUERY_KEYS.getFavoriteTags); const previousTagInfo = queryClient.getQueryData(QUERY_KEYS.getTagInfo(id)) as Awaited< ReturnType >; - queryClient.setQueryData(QUERY_KEYS.getCategoryWithTags, (old) => { - const newCategory = ( - old as Awaited> - ).categories.map((category) => ({ - ...category, - tags: category.tags.map((tag) => (tag.tagId === id ? { ...tag, isFav: false } : tag)), - })); - - return { categories: newCategory }; - }); + queryClient.setQueryData>>( + QUERY_KEYS.getFavoriteTags, + (old) => { + if (!old) return; + const newTags = old.tags.filter((tag) => tag.tagId !== id); + return { tags: newTags }; + }, + ); queryClient.setQueryData(QUERY_KEYS.getTagInfo(id), (old) => ({ - ...(old as Awaited>), + ...(old as Awaited>), isFav: false, })); await delay(wait); - return { previousCategory, previousTagInfo }; + return { previousTagInfo, previousFavoriteCategory }; }, onError: (err, id, context) => { - queryClient.setQueryData(QUERY_KEYS.getCategoryWithTags, context?.previousCategory); queryClient.setQueryData(QUERY_KEYS.getTagInfo(id), context?.previousTagInfo); + queryClient.setQueryData(QUERY_KEYS.getFavoriteTags, context?.previousFavoriteCategory); }, }); return { ...mutation, onCancel: () => controller.abort() }; diff --git a/src/application/hooks/api/tags/queryKey.ts b/src/application/hooks/api/tags/queryKey.ts index 17fa1e62..3ab109a4 100644 --- a/src/application/hooks/api/tags/queryKey.ts +++ b/src/application/hooks/api/tags/queryKey.ts @@ -2,6 +2,7 @@ export const QUERY_KEYS = { getPopularTags: ["getPopularTags"], getTagSearch: (debouncedValue: string) => ["getTagSearch", debouncedValue], getCategoryWithTags: ["getCategoryWithTags"], + getFavoriteTags: ["getFavoriteTags"], getMemeTagsById: (id: string) => ["getMemeTagsById", id], getTagInfo: (tagId: number) => ["getTagInfo", tagId], } as const; diff --git a/src/application/hooks/common/index.ts b/src/application/hooks/common/index.ts index b20c76d2..f38e554f 100644 --- a/src/application/hooks/common/index.ts +++ b/src/application/hooks/common/index.ts @@ -11,5 +11,10 @@ export * from "./useIsomorphicLayoutEffect"; export * from "./useLocalStorage"; export * from "./useLongPress"; export * from "./useModal"; +export * from "./useOverlay"; +export * from "./useRouteTracking"; +export * from "./useScrollDirection"; +export * from "./useScrollLocker"; +export * from "./useSessionStorage"; export * from "./useToast"; export * from "./useValidation"; diff --git a/src/application/hooks/common/useColoredText.tsx b/src/application/hooks/common/useColoredText.tsx index 5b3ae186..14f08d3a 100644 --- a/src/application/hooks/common/useColoredText.tsx +++ b/src/application/hooks/common/useColoredText.tsx @@ -17,13 +17,13 @@ export const useColoredText = ({ tagName, searchText }: Props) => { const ColoredText: ReactNode = ( <> {checkValidation && index !== -1 ? ( -
+ <> {tagName.slice(0, tagName.search(searchText))} {tagName.slice(index, index + searchText.length)} {tagName.slice(index + searchText.length)} -
+ ) : ( {tagName} )} diff --git a/src/application/hooks/common/useLocalStorage.ts b/src/application/hooks/common/useLocalStorage.ts index 3766e9d8..bc83bc8a 100644 --- a/src/application/hooks/common/useLocalStorage.ts +++ b/src/application/hooks/common/useLocalStorage.ts @@ -3,12 +3,7 @@ import { useCallback, useEffect, useState } from "react"; import { safeLocalStorage } from "@/application/util"; -export type Serializable = T extends - | string - | number - | boolean - | unknown[] - | Record +type Serializable = T extends string | number | boolean | unknown[] | Record ? T : never; diff --git a/src/application/hooks/common/useOverlay/OverlayController.tsx b/src/application/hooks/common/useOverlay/OverlayController.tsx new file mode 100644 index 00000000..352ba0a4 --- /dev/null +++ b/src/application/hooks/common/useOverlay/OverlayController.tsx @@ -0,0 +1,39 @@ +import type { Ref } from "react"; +import { forwardRef, useCallback, useEffect, useImperativeHandle, useState } from "react"; + +import type { CreateOverlayElement } from "./types"; + +interface Props { + overlayElement: CreateOverlayElement; + onExit: () => void; +} + +export interface OverlayControlRef { + close: () => void; +} + +export const OverlayController = forwardRef(function OverlayController( + { overlayElement: OverlayElement, onExit }: Props, + ref: Ref, +) { + const [isOpen, setIsOpen] = useState(false); + + const handleClose = useCallback(() => setIsOpen(false), []); + + useImperativeHandle( + ref, + () => { + return { close: handleClose }; + }, + [handleClose], + ); + + useEffect(() => { + // NOTE: requestAnimationFrame이 없으면 가끔 Open 애니메이션이 실행되지 않는다. + requestAnimationFrame(() => { + setIsOpen(true); + }); + }, []); + + return ; +}); diff --git a/src/application/hooks/common/useOverlay/OverlayProvider.tsx b/src/application/hooks/common/useOverlay/OverlayProvider.tsx new file mode 100644 index 00000000..8e2a8c77 --- /dev/null +++ b/src/application/hooks/common/useOverlay/OverlayProvider.tsx @@ -0,0 +1,41 @@ +import type { PropsWithChildren, ReactNode } from "react"; +import React, { createContext, useCallback, useMemo, useState } from "react"; + +export const OverlayContext = createContext<{ + mount(id: string, element: ReactNode): void; + unmount(id: string): void; +} | null>(null); +if (process.env.NODE_ENV !== "production") { + OverlayContext.displayName = "OverlayContext"; +} + +export const OverlayProvider = ({ children }: PropsWithChildren) => { + const [overlayById, setOverlayById] = useState>(new Map()); + + const mount = useCallback((id: string, element: ReactNode) => { + setOverlayById((overlayById) => { + const cloned = new Map(overlayById); + cloned.set(id, element); + return cloned; + }); + }, []); + + const unmount = useCallback((id: string) => { + setOverlayById((overlayById) => { + const cloned = new Map(overlayById); + cloned.delete(id); + return cloned; + }); + }, []); + + const context = useMemo(() => ({ mount, unmount }), [mount, unmount]); + + return ( + + {children} + {[...overlayById.entries()].map(([id, element]) => ( + {element} + ))} + + ); +}; diff --git a/src/application/hooks/common/useOverlay/index.ts b/src/application/hooks/common/useOverlay/index.ts new file mode 100644 index 00000000..d64953f4 --- /dev/null +++ b/src/application/hooks/common/useOverlay/index.ts @@ -0,0 +1,2 @@ +export { OverlayContext, OverlayProvider } from "./OverlayProvider"; +export { useOverlay } from "./useOverlay"; diff --git a/src/application/hooks/common/useOverlay/types.ts b/src/application/hooks/common/useOverlay/types.ts new file mode 100644 index 00000000..e673544b --- /dev/null +++ b/src/application/hooks/common/useOverlay/types.ts @@ -0,0 +1,5 @@ +export type CreateOverlayElement = (props: { + isOpen: boolean; + close: () => void; + exit: () => void; +}) => JSX.Element; diff --git a/src/application/hooks/common/useOverlay/useOverlay.tsx b/src/application/hooks/common/useOverlay/useOverlay.tsx new file mode 100644 index 00000000..6cd68bc8 --- /dev/null +++ b/src/application/hooks/common/useOverlay/useOverlay.tsx @@ -0,0 +1,59 @@ +import { useContext, useEffect, useMemo, useRef, useState } from "react"; + +import type { OverlayControlRef } from "./OverlayController"; +import { OverlayController } from "./OverlayController"; +import { OverlayContext } from "./OverlayProvider"; +import type { CreateOverlayElement } from "./types"; + +let elementId = 1; + +interface Options { + exitOnUnmount?: boolean; +} + +export function useOverlay({ exitOnUnmount = true }: Options = {}) { + const context = useContext(OverlayContext); + + if (context == null) { + throw new Error("useOverlay is only available within OverlayProvider."); + } + + const { mount, unmount } = context; + const [id] = useState(() => String(elementId++)); + + const overlayRef = useRef(null); + + useEffect(() => { + return () => { + if (exitOnUnmount) { + unmount(id); + } + }; + }, [exitOnUnmount, id, unmount]); + + return useMemo( + () => ({ + open: (overlayElement: CreateOverlayElement) => { + mount( + id, + { + unmount(id); + }} + />, + ); + }, + close: () => { + overlayRef.current?.close(); + }, + exit: () => { + unmount(id); + }, + }), + [id, mount, unmount], + ); +} diff --git a/src/application/hooks/common/useRouteTracking/RouteTrackingProvider.tsx b/src/application/hooks/common/useRouteTracking/RouteTrackingProvider.tsx new file mode 100644 index 00000000..e3be8959 --- /dev/null +++ b/src/application/hooks/common/useRouteTracking/RouteTrackingProvider.tsx @@ -0,0 +1,29 @@ +import { useRouter } from "next/router"; +import type { PropsWithChildren } from "react"; +import { createContext, useEffect } from "react"; + +import { useIsMount, useSessionStorage } from "@/application/hooks"; + +const defaultValue: string[] = []; + +export const RouteTrackingContext = createContext(defaultValue); +export const RouteTrackingProvider = ({ children }: PropsWithChildren) => { + const isMount = useIsMount(); + const router = useRouter(); + const [paths, set] = useSessionStorage("paths", { + defaultValue, + }); + + useEffect(() => { + if (!isMount) return; + + const isBack = paths.at(-2) === router.asPath; + const isRefresh = paths.at(-1) === router.asPath; + + if (isRefresh) return; + if (isBack) return set((prev) => prev.slice(0, -1)); + return set((prev) => [...prev, router.asPath]); + }, [isMount, paths, router.asPath, set]); + + return {children}; +}; diff --git a/src/application/hooks/common/useRouteTracking/index.ts b/src/application/hooks/common/useRouteTracking/index.ts new file mode 100644 index 00000000..bf634afe --- /dev/null +++ b/src/application/hooks/common/useRouteTracking/index.ts @@ -0,0 +1,2 @@ +export * from "./RouteTrackingProvider"; +export * from "./useRouteTracking"; diff --git a/src/application/hooks/common/useRouteTracking/useRouteTracking.ts b/src/application/hooks/common/useRouteTracking/useRouteTracking.ts new file mode 100644 index 00000000..bda8e1b5 --- /dev/null +++ b/src/application/hooks/common/useRouteTracking/useRouteTracking.ts @@ -0,0 +1,15 @@ +import { useContext, useMemo } from "react"; + +import { RouteTrackingContext } from "./RouteTrackingProvider"; + +export const useRouteTracking = () => { + const history = useContext(RouteTrackingContext); + + return useMemo( + () => ({ + isInitialPage: history.length <= 1, + history, + }), + [history], + ); +}; diff --git a/src/application/hooks/common/useScrollDirection.ts b/src/application/hooks/common/useScrollDirection.ts new file mode 100644 index 00000000..dc4b697e --- /dev/null +++ b/src/application/hooks/common/useScrollDirection.ts @@ -0,0 +1,35 @@ +import { useEffect, useRef, useState } from "react"; + +import { throttle } from "@/application/util"; + +const SCROLL_DIRECTION = { + up: "UP", + down: "DOWN", + init: "INIT", +} as const; +type DIRECTION = typeof SCROLL_DIRECTION[keyof typeof SCROLL_DIRECTION]; + +const OFFSET = 100; +const DELAY = 200; +export const useScrollDirection = () => { + const prevScrollY = useRef(0); + const [direction, setDirection] = useState(SCROLL_DIRECTION.init); + useEffect(() => { + const handleScroll = throttle(() => { + const currentScrollY = window.scrollY; + if (currentScrollY > prevScrollY.current + OFFSET) { + // 위로 스크롤하는 중 + setDirection(SCROLL_DIRECTION.up); + } else if (currentScrollY < prevScrollY.current - OFFSET) { + // 아래로 스크롤하는 중 + setDirection(SCROLL_DIRECTION.down); + } + prevScrollY.current = currentScrollY; + }, DELAY); + + window.addEventListener("scroll", handleScroll); + return () => window.removeEventListener("scroll", handleScroll); + }, []); + + return direction; +}; diff --git a/src/application/hooks/common/useScrollLocker.ts b/src/application/hooks/common/useScrollLocker.ts new file mode 100644 index 00000000..616a74af --- /dev/null +++ b/src/application/hooks/common/useScrollLocker.ts @@ -0,0 +1,85 @@ +import { useState } from "react"; + +import { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect"; + +const MARK_KEY = "scroll-locker-key"; +const UNIQUE_ID = `key-${Date.now()}`; + +let uuid = 0; + +export const useScrollLocker = (lock?: boolean) => { + const mergedLock = !!lock; + const [id] = useState(() => { + uuid += 1; + return `${UNIQUE_ID}_${uuid}`; + }); + + useIsomorphicLayoutEffect(() => { + if (mergedLock) { + updateCSS( + ` + html body { + overflow: hidden; + }`, + id, + ); + } else { + removeCSS(id); + } + + return () => { + removeCSS(id); + }; + }, [mergedLock, id]); +}; + +const updateCSS = (css: string, key: string) => { + const container = getContainer(); + const existNode = findExistNode(key); + + if (existNode) { + if (existNode.innerHTML !== css) { + existNode.innerHTML = css; + } + + return existNode; + } + + if (!canUseDom()) { + return null; + } + const newNode = document.createElement("style"); + newNode.innerHTML = css; + container.appendChild(newNode); + newNode.setAttribute(MARK_KEY, key); + return newNode; +}; + +const removeCSS = (key: string) => { + const existNode = findExistNode(key); + if (existNode) { + const container = getContainer(); + container.removeChild(existNode); + } +}; + +const getContainer = () => { + const head = document.querySelector("head"); + return head || document.body; +}; + +const canUseDom = () => { + return !!(typeof window !== "undefined" && window.document && window.document.createElement); +}; + +const findExistNode = (key: string) => { + const container = getContainer(); + + return findStyles(container).find((node) => node.getAttribute(MARK_KEY) === key); +}; + +const findStyles = (container: Element | ShadowRoot) => { + return Array.from(container.children).filter( + (node) => node.tagName === "STYLE", + ) as HTMLStyleElement[]; +}; diff --git a/src/application/hooks/common/useSessionStorage.ts b/src/application/hooks/common/useSessionStorage.ts new file mode 100644 index 00000000..027ca004 --- /dev/null +++ b/src/application/hooks/common/useSessionStorage.ts @@ -0,0 +1,85 @@ +import type { SetStateAction } from "react"; +import { useCallback, useEffect, useState } from "react"; + +type Serializable = T extends string | number | boolean | unknown[] | Record + ? T + : never; + +interface StorageStateOptions { + defaultValue?: Serializable; +} +interface StorageStateOptionsWithDefaultValue extends StorageStateOptions { + defaultValue: Serializable; +} + +export function useSessionStorage( + key: string, +): readonly [ + Serializable | undefined, + (value: SetStateAction | undefined>) => void, +]; +export function useSessionStorage( + key: string, + { defaultValue }: StorageStateOptionsWithDefaultValue, +): readonly [Serializable, (value: SetStateAction>) => void]; +export function useSessionStorage( + key: string, + { defaultValue }: StorageStateOptions, +): readonly [ + Serializable | undefined, + (value: SetStateAction | undefined>) => void, +]; +export function useSessionStorage( + key: string, + { defaultValue }: StorageStateOptions = {}, +): readonly [ + Serializable | undefined, + (value: SetStateAction | undefined>) => void, +] { + const get = useCallback(() => { + if (typeof window === "undefined") return defaultValue; + const item = sessionStorage.getItem(key); + + if (item == null) { + return defaultValue; + } + + try { + const result = JSON.parse(item); + + if (result == null) { + return defaultValue; + } + + return result as T; + } catch { + // NOTE: JSON 객체가 아닌 경우 + return defaultValue; + } + }, [defaultValue, key]); + + const [state, setState] = useState | undefined>(defaultValue); + + useEffect(() => { + setState(get()); + }, [get]); + + const set = useCallback( + (value: SetStateAction | undefined>) => { + setState((curr) => { + const nextValue = typeof value === "function" ? value(curr) : value; + + if (nextValue == null) { + window?.sessionStorage.removeItem(key); + } else { + window?.sessionStorage.setItem(key, JSON.stringify(nextValue)); + } + + return nextValue; + }); + }, + [key], + ); + + return [state, set]; +} diff --git a/src/application/hooks/domain/auth/useAuth.ts b/src/application/hooks/domain/auth/useAuth.ts index 924b44b5..73ad1fa2 100644 --- a/src/application/hooks/domain/auth/useAuth.ts +++ b/src/application/hooks/domain/auth/useAuth.ts @@ -20,9 +20,9 @@ export const useAuth = () => { }, []); const validate = useCallback( - (handler: T, options: ValidatorOptions = { needSignUpModal: true }) => + (handler?: T, options: ValidatorOptions = { needSignUpModal: true }) => (...args: Parameters) => { - if (isLogin) return handler(args) as ReturnType; + if (isLogin) return handler?.(args) as ReturnType; if (!isLogin && options.needSignUpModal) return modalProps.onOpen(); }, [isLogin, modalProps], diff --git a/src/application/hooks/domain/index.ts b/src/application/hooks/domain/index.ts index 3c3cd9d3..670b7acb 100644 --- a/src/application/hooks/domain/index.ts +++ b/src/application/hooks/domain/index.ts @@ -1,5 +1,6 @@ export * from "./auth"; export * from "./channel"; export * from "./collection"; +export * from "./meme"; export * from "./search"; export * from "./share"; diff --git a/src/application/hooks/domain/meme/index.ts b/src/application/hooks/domain/meme/index.ts new file mode 100644 index 00000000..ce59e3a4 --- /dev/null +++ b/src/application/hooks/domain/meme/index.ts @@ -0,0 +1 @@ +export * from "./useMoveMemeDetail"; diff --git a/src/application/hooks/domain/meme/useMoveMemeDetail.ts b/src/application/hooks/domain/meme/useMoveMemeDetail.ts new file mode 100644 index 00000000..2a5a8be1 --- /dev/null +++ b/src/application/hooks/domain/meme/useMoveMemeDetail.ts @@ -0,0 +1,16 @@ +import { useRouter } from "next/router"; + +import { PATH } from "@/application/util"; + +export const useMoveMemeDetail = () => { + const router = useRouter(); + const searchQueryString = (router.query.q || "") as string; + + const movePage = (memeId: number) => { + if (router.pathname.startsWith(PATH.getExplorePage())) { + router.push(PATH.getMemeDetailPage(memeId, searchQueryString)); + } else router.push(PATH.getMemeDetailPage(memeId)); + }; + + return { searchQueryString, movePage }; +}; diff --git a/src/application/hooks/domain/search/useRecentSearch/useRecentSearch.ts b/src/application/hooks/domain/search/useRecentSearch/useRecentSearch.ts index da7ac7e7..c24475f4 100644 --- a/src/application/hooks/domain/search/useRecentSearch/useRecentSearch.ts +++ b/src/application/hooks/domain/search/useRecentSearch/useRecentSearch.ts @@ -37,5 +37,5 @@ export const useRecentSearch = () => { setItems((prevItems) => [...prevItems.filter((item) => item.id !== id)]); }; - return { items, onAddItem, onDeleteItem }; + return { items: items.slice(0, 10), onAddItem, onDeleteItem }; }; diff --git a/src/application/util/constant.ts b/src/application/util/constant.ts index b74c4028..2fe89047 100644 --- a/src/application/util/constant.ts +++ b/src/application/util/constant.ts @@ -1,3 +1,15 @@ export const IS_CSR = typeof window !== "undefined"; export const DOMAIN = IS_CSR ? window.location.origin : ""; + +export const twitterUrl = "https://twitter.com/thismeme_team"; + +export const instagramUrl = "https://www.instagram.com/thismeme_official"; + +export const channelUrl = "https://thismeme.channel.io/lounge"; + +export const SITE_NAME = "그 밈"; + +export const DEFAULT_DESCRIPTION = "당신이 찾는 ‘그 밈’ 여기 있다."; + +export const thismemeGuideUrl = "https://zonemy.notion.site/ca4dba6972a340c28e4be60d1cc83547"; diff --git a/src/application/util/index.ts b/src/application/util/index.ts index 1aa32d28..ccc4dfea 100644 --- a/src/application/util/index.ts +++ b/src/application/util/index.ts @@ -5,5 +5,5 @@ export * from "./device"; export * from "./gtag"; export * from "./image-util"; export * from "./path"; -export * from "./seo"; export * from "./storage"; +export * from "./throttle"; diff --git a/src/application/util/path.ts b/src/application/util/path.ts index 236f1483..338f02e5 100644 --- a/src/application/util/path.ts +++ b/src/application/util/path.ts @@ -1,4 +1,5 @@ export const PATH = { + getExplorePage: () => "/explore", /** * 키워드 검색 결과 페이지 * @param keyword - 키워드 @@ -10,11 +11,15 @@ export const PATH = { }, /** * 태그 검색 결과 페이지 - * @param tag - 태그 - * @return /explore/tags/${tag id} + * @param tagId - 태그 ID + * @param tagName - 태그 이름 + * @return /explore/tags/${tagId}?q={tagName} */ - getExploreByTagPath: (tagId: number) => { - return `/explore/tags/${tagId}`; + getExploreByTagPath: (tagId: number, tagName?: string) => { + if (!tagName) return `/explore/tags/${tagId}`; + + const encodedValue = encodeURIComponent(`#${tagName}`); + return `/explore/tags/${tagId}?q=${encodedValue}`; }, getMainPage: "/", @@ -22,9 +27,13 @@ export const PATH = { /** * 밈 상세 페이지 * @param id - 밈 id - * @returns /memes/${meme id} + * @param search - 검색헤더 값 + * @returns /memes/${meme id}?q={search} */ - getMemeDetailPage: (id: number) => { - return `/memes/${id}`; + getMemeDetailPage: (id: number, search?: string) => { + if (!search) return `/memes/${id}`; + + const encodedValue = encodeURIComponent(search); + return `/memes/${id}?q=${encodedValue}`; }, }; diff --git a/src/application/util/seo.ts b/src/application/util/seo.ts deleted file mode 100644 index fa7f6306..00000000 --- a/src/application/util/seo.ts +++ /dev/null @@ -1,34 +0,0 @@ -export const APP_NAME = "그 밈"; - -export const DEFAULT_DESCRIPTION = "당신이 찾는 ‘그 밈’ 여기 있다."; -export const EMPTY_SEARCH_DESCRIPTION = "당신이 찾는 ‘그 밈’ 여기 없다..."; - -export const TITLE = { - /** - * 메인 페이지(/) - */ - default: `${APP_NAME} : 무한도전 밈 검색`, - /** - * 검색페이지(/search) - */ - search: `${APP_NAME} : 무한도전 밈 검색`, - /** - * 키워드 검색 결과 페이지(/explore/keywords?q=) - * @param keyword - 키워드 - */ - exploreByKeyword: (keyword?: string) => `'${keyword}' 밈`, - /** - * 태그 검색 결과 페이지(/explore/tags/${tagId}) - * @param tag - 태그 - */ - exploreByTag: (tag?: string) => `'${tag}' 밈`, - /** - * 검색결과 없을 때 페이지 - * @param search - */ - emptySearch: (search?: string) => `'${search}' 밈`, - /** - * 밈 상세 페이지(/memes/${id}) - */ - memeDetail: (title: string) => `${APP_NAME} : ${title}`, -} as const; diff --git a/src/application/util/throttle.ts b/src/application/util/throttle.ts new file mode 100644 index 00000000..926a3b35 --- /dev/null +++ b/src/application/util/throttle.ts @@ -0,0 +1,11 @@ +export const throttle = void>(cb: T, delay = 300): T => { + let timeout: NodeJS.Timeout | null; + return ((...args: any[]) => { + if (timeout) return; + + timeout = setTimeout(() => { + timeout = null; + return cb(...args); + }, delay); + }) as T; +}; diff --git a/src/components/collect/Collection/Collection.tsx b/src/components/collect/Collection/Collection.tsx index aa05ff0d..14b45a52 100644 --- a/src/components/collect/Collection/Collection.tsx +++ b/src/components/collect/Collection/Collection.tsx @@ -12,9 +12,19 @@ export const Collection = ({ collectionId }: Props) => { fetchNextPage, } = useGetMemesByCollectionId(collectionId); + /** + * TODO + * 기존 기획에서 마이, 콜렉션페이지에서는 액션시트에 '콜렉션에 저장하기' 버튼이 없었기 때문에 + * MemeItem에서 ActionSheet를 overlay.open 한다면 마이, 콜렉션페이지인지 아닌지 판단하는 로직 필요 + * + * AS-IS + * InfiniteMemeList -> MemeActionSheetContainer -> ActionSheet로 isCollection props를 내려주고 있었음 + * -> 지금은 ActionSheet를 MemeItem에서 overlay.open하므로 이 방법을 사용할 수 없음 + * TO-BE + * 마이페이지인지 아닌지 판단하는 hook을 만들고 MemeActionSheet 에서 호출 + */ return ( fetchNextPage({ cancelRefetch: false })} diff --git a/src/components/collect/SearchedCollection/SearchedCollection.tsx b/src/components/collect/SearchedCollection/SearchedCollection.tsx index 29afd670..6b36b5f8 100644 --- a/src/components/collect/SearchedCollection/SearchedCollection.tsx +++ b/src/components/collect/SearchedCollection/SearchedCollection.tsx @@ -20,9 +20,19 @@ export const SearchedCollection = ({ searchQuery, collectionId }: Props) => { if (isEmpty) { return null; } + /** + * TODO + * 기존 기획에서 마이, 콜렉션페이지에서는 액션시트에 '콜렉션에 저장하기' 버튼이 없었기 때문에 + * MemeItem에서 ActionSheet를 overlay.open 한다면 마이, 콜렉션페이지인지 아닌지 판단하는 로직 필요 + * + * AS-IS + * InfiniteMemeList -> MemeActionSheetContainer -> ActionSheet로 isCollection props를 내려주고 있었음 + * -> 지금은 ActionSheet를 MemeItem에서 overlay.open하므로 이 방법을 사용할 수 없음 + * TO-BE + * 마이페이지인지 아닌지 판단하는 hook을 만들고 MemeActionSheet 에서 호출 + */ return ( fetchNextPage({ cancelRefetch: false })} diff --git a/src/components/common/ActionSheet/ActionSheet.tsx b/src/components/common/ActionSheet/ActionSheet.tsx index b1d425fd..69f64c98 100644 --- a/src/components/common/ActionSheet/ActionSheet.tsx +++ b/src/components/common/ActionSheet/ActionSheet.tsx @@ -1,82 +1,111 @@ import type { ComponentProps, PropsWithChildren } from "react"; -import { css } from "twin.macro"; - -import { android } from "@/application/util"; +import { useRef } from "react"; +import { CSSTransition } from "react-transition-group"; +import tw, { css } from "twin.macro"; const DELAY = 300; -export const ActionSheet = ({ children, isOpen }: PropsWithChildren<{ isOpen?: boolean }>) => { - return ( -
void; +} +export const ActionSheet = ({ children, isOpen, onClose }: PropsWithChildren) => { + const backdropRef = useRef(null); + const nodeRef = useRef(null); - inset: auto 0 0 0; - display: flex; - justify-content: center; - - transform: translateY(105%); - transition: transform ${DELAY}ms cubic-bezier(0, 0.8, 0.34, 1); - `, - !android && - css` - padding-bottom: calc(env(safe-area-inset-bottom) + 4rem); - `, - isOpen && - css` - transform: translateY(0); - `, - ]} - > -
+ +
+ + +
+
- {children} -
-
+ ]} + > + {children} +
+
+ + ); }; type ActionSheetButtonProps = ComponentProps<"button">; const ActionSheetButton = ({ children, - className, + className = "", ...rest }: PropsWithChildren) => { return ( - ); diff --git a/src/components/common/Drawer/Drawer.tsx b/src/components/common/Drawer/Drawer.tsx index d91ef474..19e338e6 100644 --- a/src/components/common/Drawer/Drawer.tsx +++ b/src/components/common/Drawer/Drawer.tsx @@ -1,11 +1,18 @@ import type { PropsWithChildren, ReactNode } from "react"; import { css } from "twin.macro"; +import { useScrollLocker } from "@/application/hooks"; +import { Portal } from "@/components/common/Portal"; + import { DrawerContextProvider, useDrawerContext, useSetDrawerContext } from "./context"; -export const Drawer = ({ children }: PropsWithChildren) => { +interface DrawerProps { + isOpen?: boolean; + onOpenChange?(open: boolean): void; +} +export const Drawer = ({ children, isOpen, onOpenChange }: PropsWithChildren) => { return ( - + {/* NOTE: 공백 문자 제거 */}
{children}
@@ -23,47 +30,64 @@ const DrawerTrigger = ({ children }: DrawerTriggerProps) => { }; interface DrawerContentProps { - className: string; + className?: string; children: ReactNode; - direction: "left" | "right"; + direction: "left" | "right" | "top" | "bottom"; + top?: string | number; } -const DrawerContent = ({ children, className, direction }: DrawerContentProps) => { +const DrawerContent = ({ + children, + className = "", + direction = "right", + top = "5.4rem", +}: DrawerContentProps) => { const isOpen = useDrawerContext(); + useScrollLocker(isOpen); + return ( -
-
+
+
- {children} -
-
+ isOpen && + css` + visibility: visible; + transform: translateX(0); + transition: transform 0.4s ease; + `, + ]} + > + {children} +
+
+ ); }; diff --git a/src/components/common/Drawer/context.tsx b/src/components/common/Drawer/context.tsx index bbad8f76..ad4a57d2 100644 --- a/src/components/common/Drawer/context.tsx +++ b/src/components/common/Drawer/context.tsx @@ -1,20 +1,36 @@ import type { PropsWithChildren } from "react"; -import { createContext, useCallback, useContext, useState } from "react"; +import { createContext, useCallback, useContext, useEffect, useState } from "react"; const DrawerContext = createContext(false); const DrawerSetContext = createContext<(state: boolean) => void>(() => null); -export const DrawerContextProvider = ({ children }: PropsWithChildren) => { - const [isOpen, setIsOpen] = useState(false); +interface DrawerContextProviderProps { + isOpen?: boolean; + onOpenChange?(open: boolean): void; +} +export const DrawerContextProvider = ({ + children, + isOpen: isOpenProp, + onOpenChange, +}: PropsWithChildren) => { + const [isOpen = false, setIsOpen] = useState(isOpenProp); + const isControlled = isOpenProp !== undefined; + const value = isControlled ? isOpenProp : isOpen; - const handleDrawer = useCallback((state: boolean) => { - if (state) document.body.style.overflow = "hidden"; - else document.body.style.overflow = ""; - setIsOpen(state); - }, []); + const handleDrawer: React.Dispatch> = useCallback( + (nextValue) => { + const value = typeof nextValue === "function" ? nextValue(isOpenProp) : nextValue; + if (isControlled) { + if (value !== isOpenProp) onOpenChange?.(value as boolean); + } else { + setIsOpen(nextValue); + } + }, + [isControlled, isOpenProp, onOpenChange], + ); return ( - + {children} ); diff --git a/src/components/common/DropDown/DropDown.tsx b/src/components/common/DropDown/DropDown.tsx index 81338ed0..b23db7a1 100644 --- a/src/components/common/DropDown/DropDown.tsx +++ b/src/components/common/DropDown/DropDown.tsx @@ -64,11 +64,10 @@ const DropDownContents = ({ z-index: 10; width: ${width}rem; border-radius: 10px; - border: solid 1px ${theme`colors.gray.400`}; background: white; overflow: hidden; transition: transform 0.4s ease, opacity 0.2s ease-in-out; - box-shadow: 0px 0px 20px rgba(0, 0, 0, 20%); + box-shadow: 0px 0px 20px rgba(38, 47, 40, 0.2); ${!isOpen && "pointer-events: none;"} `, width && diff --git a/src/components/common/Error/GlobalError.tsx b/src/components/common/Error/GlobalError.tsx index 72d8f79d..07ee4478 100644 --- a/src/components/common/Error/GlobalError.tsx +++ b/src/components/common/Error/GlobalError.tsx @@ -4,7 +4,7 @@ export const GlobalError = () => { return ( <> -
+
오류 발생 이 페이지를 새로고침 해보세요. diff --git a/src/components/common/Icon/assets.ts b/src/components/common/Icon/assets.ts index b4ac6db0..2874871b 100644 --- a/src/components/common/Icon/assets.ts +++ b/src/components/common/Icon/assets.ts @@ -8,18 +8,23 @@ export { default as delete } from "/public/icon/delete.svg"; export { default as delete2 } from "/public/icon/delete2.svg"; export { default as download } from "/public/icon/download.svg"; export { default as google } from "/public/icon/google.svg"; +export { default as instagram } from "/public/icon/instagram-logo.svg"; export { default as kakao } from "/public/icon/kakao.svg"; export { default as kakao2 } from "/public/icon/kakao2.svg"; export { default as logo } from "/public/icon/logo.svg"; export { default as meatball } from "/public/icon/meatball.svg"; +export { default as memeChannel } from "/public/icon/memeChannel.svg"; export { default as memeShare } from "/public/icon/memeShare.svg"; export { default as menu } from "/public/icon/menu.svg"; export { default as pound } from "/public/icon/pound.svg"; export { default as loginprofile } from "/public/icon/profile.svg"; export { default as notloginprofile } from "/public/icon/profile2.svg"; +export { default as recentSort } from "/public/icon/recent-memesort.svg"; export { default as search } from "/public/icon/search.svg"; export { default as setting } from "/public/icon/setting.svg"; export { default as share } from "/public/icon/share.svg"; +export { default as shareSort } from "/public/icon/share-memesort.svg"; export { default as star } from "/public/icon/star.svg"; +export { default as twitter } from "/public/icon/twitter-logo.svg"; export { default as warn } from "/public/icon/warn.svg"; export { default as whiteKakao } from "/public/icon/whiteKakao.svg"; diff --git a/src/components/common/Icon/index.tsx b/src/components/common/Icon/index.tsx index da8e7e4b..e4a638b9 100644 --- a/src/components/common/Icon/index.tsx +++ b/src/components/common/Icon/index.tsx @@ -1,23 +1,46 @@ import type { FC, SVGProps } from "react"; +import { css, theme } from "twin.macro"; import * as Icons from "./assets"; -// FIXME classname 동적 할당 문제 -const colors = { - black: "[&_*]:fill-black [&_*]:stroke-black", - white: "[&_*]:fill-white [&_*]:stroke-white", - "stroke-white": "[&_*]:stroke-white", +type PrefixColor = "gray" | "primary" | "secondary"; +type Variation = "100" | "200" | "300" | "400" | "500" | "600" | "700" | "800" | "900" | "1000"; +type Colors = `${PrefixColor}-${Variation}` | "black" | "white" | "default"; + +const colors: { [K in Colors]?: string } = { + black: "black", + white: "white", default: "", + "gray-600": theme`colors.gray.600`, + "primary-500": theme`colors.primary.500`, }; export type IconName = keyof typeof Icons; interface Props extends SVGProps { name: IconName; - color?: keyof typeof colors; + color?: Colors; + stroke?: Colors; + fill?: Colors; } -export const Icon = ({ name, color = "default", ...rest }: Props) => { +export const Icon = ({ + name, + color = "default", + stroke = "default", + fill = "default", + ...rest +}: Props) => { const Svg = Icons[name] as FC>; - return ; + return ( + + ); }; diff --git a/src/components/common/Layout/Layout.tsx b/src/components/common/Layout/Layout.tsx index 39c6f5f0..da2521ce 100644 --- a/src/components/common/Layout/Layout.tsx +++ b/src/components/common/Layout/Layout.tsx @@ -2,12 +2,20 @@ import type { PropsWithChildren } from "react"; import { pretendard, suit } from "@/styles/fonts"; +import { ServiceGuide } from "./ServiceGuide"; + export const Layout = ({ children }: PropsWithChildren) => { return ( -
- {children} -
+ <> + +
100dvh 단위 변경 + className={`relative min-h-[100dvh] ${pretendard.variable} ${suit.variable} mx-auto max-w-[44rem] bg-white px-18 font-suit shadow-lg`} + > + {children} +
+ ); }; diff --git a/src/components/common/Layout/ServiceGuide.tsx b/src/components/common/Layout/ServiceGuide.tsx new file mode 100644 index 00000000..b344fdea --- /dev/null +++ b/src/components/common/Layout/ServiceGuide.tsx @@ -0,0 +1,26 @@ +import Image from "next/image"; + +import { thismemeGuideUrl } from "@/application/util"; + +import { Button } from "../Button"; + +export const ServiceGuide = () => { + const serviceGuide = "/img/serviceGuide.svg"; + + return ( + + ); +}; diff --git a/src/components/common/Masonry/Masonry.stories.tsx b/src/components/common/Masonry/Masonry.stories.tsx index 8b1330f7..25175a9d 100644 --- a/src/components/common/Masonry/Masonry.stories.tsx +++ b/src/components/common/Masonry/Masonry.stories.tsx @@ -20,7 +20,7 @@ const sampleImages = [ { url: "https://picsum.photos/236/354", width: 236, height: 354 }, ]; -const memes = Array.from(Array(1024).keys()).map((id) => { +const memes = Array.from(Array(20).keys()).map((id) => { const randomIndex = Math.floor(Math.random() * sampleImages.length); const { url, width, height } = sampleImages[randomIndex]; return { diff --git a/src/components/common/Masonry/Masonry.tsx b/src/components/common/Masonry/Masonry.tsx index 4d32eb90..fb25e15d 100644 --- a/src/components/common/Masonry/Masonry.tsx +++ b/src/components/common/Masonry/Masonry.tsx @@ -32,6 +32,7 @@ const getStyle = ({ ownerState }: { ownerState: OwnerState }): TwStyle => { ...(ownerState.maxColumnHeight && { height: ownerState.maxColumnHeight + ownerState.spacing, }), + overflowAnchor: "none", margin: `calc(0px - (${ownerState.spacing}px / 2))`, "& > *": { margin: `calc(${ownerState.spacing}px / 2)`, @@ -137,6 +138,7 @@ export const Masonry = (props: Props) => { }); } }; + const observer = useRef( typeof ResizeObserver === "undefined" ? undefined : new ResizeObserver(handleResize), ); @@ -149,9 +151,15 @@ export const Masonry = (props: Props) => { } if (masonryRef.current) { + /** + * @desc + * masonry 3-4 줄 되는 현상때문에 자식 노드를 모두 observe 하는 로직으로 롤백했습니다 + * (그러나 오류 재현이 잘 안됨..) + */ (masonryRef.current.childNodes as NodeListOf).forEach((childNode) => { resizeObserver.observe(childNode); }); + // resizeObserver.observe(masonryRef.current); } return () => resizeObserver.disconnect(); diff --git a/src/components/common/Modal/Modal.tsx b/src/components/common/Modal/Modal.tsx index bd298f79..729c81ad 100644 --- a/src/components/common/Modal/Modal.tsx +++ b/src/components/common/Modal/Modal.tsx @@ -2,22 +2,17 @@ import type { PropsWithChildren } from "react"; import { Children, createContext, isValidElement, useContext } from "react"; import { useClickOutside } from "@/application/hooks"; -import { fadeInOut } from "@/application/util/animation"; +import { fadeInOut } from "@/application/util"; import { Icon } from "@/components/common/Icon"; -import { Portal } from "@/components/common/Portal"; -import type { ModalProps } from "./types"; +interface ModalProps { + open: boolean; + onClose: () => void; +} -type ModalContextValue = ModalProps; +const ModalContext = createContext(null as unknown as ModalProps); -const ModalContext = createContext(null as unknown as ModalContextValue); - -export const Modal = ({ - children, - open, - onOpen, - onClose, -}: PropsWithChildren) => { +export const Modal = ({ children, open, onClose }: PropsWithChildren) => { const ref = useClickOutside({ onClose }); const reactChildren = Children.toArray(children); @@ -34,22 +29,23 @@ export const Modal = ({ ); return ( - - -
{ - if (event.target === event.currentTarget) event.preventDefault(); - }} + +
{ + if (event.target === event.currentTarget) event.preventDefault(); + }} + > +
-
- {header} -
{content}
- {footer} -
-
- + {header} +
{content}
+ {footer} + +
); }; @@ -59,8 +55,11 @@ const ModalHeader = () => { return (
-
); @@ -68,11 +67,7 @@ const ModalHeader = () => { const ModalHeaderType = ().type; const ModalFooter = ({ className, children }: PropsWithChildren<{ className?: string }>) => { - return ( -
- {children} -
- ); + return
{children}
; }; const ModalFooterType = ().type; diff --git a/src/components/common/Modal/ProfileModal.tsx b/src/components/common/Modal/ProfileModal.tsx index 062de360..401443eb 100644 --- a/src/components/common/Modal/ProfileModal.tsx +++ b/src/components/common/Modal/ProfileModal.tsx @@ -24,9 +24,9 @@ export const ProfileModal = () => { <> - +
diff --git a/src/components/common/Modal/SignOutModal.tsx b/src/components/common/Modal/SignOutModal.tsx index 599d4288..e96bf2c6 100644 --- a/src/components/common/Modal/SignOutModal.tsx +++ b/src/components/common/Modal/SignOutModal.tsx @@ -15,8 +15,8 @@ export const SignOutModal = (props: ModalProps) => { const handleCancel = () => { onClose(); - push(PATH.getMainPage); }; + const handleLogout = () => { logout(undefined, { onSuccess: () => { @@ -30,7 +30,7 @@ export const SignOutModal = (props: ModalProps) => { return ( -
+
킹 받는{" "} @@ -45,11 +45,11 @@ export const SignOutModal = (props: ModalProps) => {
- - diff --git a/src/components/common/Modal/SignUpModal/SignUpModal.tsx b/src/components/common/Modal/SignUpModal/SignUpModal.tsx index ffa24139..8036d13b 100644 --- a/src/components/common/Modal/SignUpModal/SignUpModal.tsx +++ b/src/components/common/Modal/SignUpModal/SignUpModal.tsx @@ -1,3 +1,4 @@ +import { IS_CSR } from "@/application/util"; import { Button } from "@/components/common/Button"; import { Icon } from "@/components/common/Icon"; import { RandomImage } from "@/components/common/RandomImge"; @@ -8,10 +9,21 @@ import { useSignUpModalContext } from "./context"; export const SignUpModal = () => { const modalProps = useSignUpModalContext(); + const handleSetCookie = () => { + // NOTE: 쿠키 만료시간은 1분으로 지정 + const EXPIRES = new Date(); + EXPIRES.setMinutes(EXPIRES.getMinutes() + 1); + if (IS_CSR) { + document.cookie = `nextPageUrl=${ + window.location + }; path=/; domain=.thismeme.me; expires= ${EXPIRES.toUTCString()} ;`; + } + }; + return ( -
+
킹 받는{" "} @@ -29,6 +41,7 @@ export const SignUpModal = () => { as="a" className="h-50 w-290 rounded-10 bg-[#FEE500] px-40 py-14 font-suit text-16-semibold-140" href={`${process.env.NEXT_PUBLIC_KAKAO_OAUTH2_URL}`} + onClick={handleSetCookie} > 카카오로 3초 만에 시작하기 diff --git a/src/components/common/Navigation/BackButton/BackButton.tsx b/src/components/common/Navigation/BackButton/BackButton.tsx index df9dd5a8..17726f18 100644 --- a/src/components/common/Navigation/BackButton/BackButton.tsx +++ b/src/components/common/Navigation/BackButton/BackButton.tsx @@ -1,13 +1,18 @@ import { useRouter } from "next/router"; +import type { ComponentProps } from "react"; +import { useRouteTracking } from "@/application/hooks"; import { Icon } from "@/components/common/Icon"; -export const BackButton = () => { +export const BackButton = (props: ComponentProps<"button">) => { const router = useRouter(); + + const { isInitialPage } = useRouteTracking(); + if (isInitialPage) return null; + return ( - ); }; -export {}; diff --git a/src/components/common/Navigation/ExplorePageNavigation.tsx b/src/components/common/Navigation/ExplorePageNavigation.tsx index dce09039..a89e4d3d 100644 --- a/src/components/common/Navigation/ExplorePageNavigation.tsx +++ b/src/components/common/Navigation/ExplorePageNavigation.tsx @@ -1,22 +1,25 @@ -import { BackButton } from "./BackButton"; +import { Logo } from "@/components/common/Navigation/Logo"; +import { SearchHeader } from "@/components/common/Navigation/SearchHeader"; + import { Navigation } from "./Navigation"; -import { Profile } from "./Profile"; import { SideBar } from "./SideBar"; interface Props { title?: string; } + export const ExplorePageNavigation = ({ title }: Props) => { return ( - - - -
{title}
-
- - - - -
+ <> + + + + + + + + + + ); }; diff --git a/src/components/common/Navigation/IntroPageNavigation.tsx b/src/components/common/Navigation/IntroPageNavigation.tsx index 442d23e6..26b4ef5e 100644 --- a/src/components/common/Navigation/IntroPageNavigation.tsx +++ b/src/components/common/Navigation/IntroPageNavigation.tsx @@ -1,18 +1,21 @@ +import { SearchHeader } from "@/components/common/Navigation/SearchHeader"; + import { Logo } from "./Logo"; import { Navigation } from "./Navigation"; -import { Profile } from "./Profile"; import { SideBar } from "./SideBar"; export const IntroPageNavigation = () => { return ( - - - - - - - - - + <> + + + + + + + + + + ); }; diff --git a/src/components/common/Navigation/Logo/Logo.tsx b/src/components/common/Navigation/Logo/Logo.tsx index e51a3dff..848e6892 100644 --- a/src/components/common/Navigation/Logo/Logo.tsx +++ b/src/components/common/Navigation/Logo/Logo.tsx @@ -1,8 +1,16 @@ +import { useRouter } from "next/router"; + import { Icon } from "@/components/common/Icon"; export const Logo = () => { + const router = useRouter(); + const handleClick = () => { + if (router.asPath === "/") location.reload(); + else router.push("/"); + }; + return ( - ); diff --git a/src/components/common/Navigation/Navigation.tsx b/src/components/common/Navigation/Navigation.tsx index 653a489f..ae6d9972 100644 --- a/src/components/common/Navigation/Navigation.tsx +++ b/src/components/common/Navigation/Navigation.tsx @@ -1,11 +1,12 @@ +import type { CSSInterpolation } from "@emotion/serialize"; import type { PropsWithChildren } from "react"; -type Props = PropsWithChildren<{ className?: string }>; +type Props = PropsWithChildren<{ className?: string; css?: CSSInterpolation }>; export const Navigation = ({ children, className = "" }: Props) => { return (
{children}
diff --git a/src/components/common/Navigation/Profile/Profile.tsx b/src/components/common/Navigation/Profile/Profile.tsx deleted file mode 100644 index f9794a11..00000000 --- a/src/components/common/Navigation/Profile/Profile.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { useAuth } from "@/application/hooks"; -import { Button } from "@/components/common/Button"; -import { Icon } from "@/components/common/Icon"; -import { ProfileModal, useSignUpModalContext } from "@/components/common/Modal"; - -import { Skeleton } from "../../Skeleton"; - -export const Profile = () => { - const { isLoading, isLogin } = useAuth(); - const modalProps = useSignUpModalContext(); - - if (isLoading) return null; - if (isLogin) return ; - - return ( - <> - - - ); -}; diff --git a/src/components/common/Navigation/Profile/index.ts b/src/components/common/Navigation/Profile/index.ts deleted file mode 100644 index b6f21ff6..00000000 --- a/src/components/common/Navigation/Profile/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./Profile"; diff --git a/src/components/common/Navigation/SearchHeader/SearchHeader.tsx b/src/components/common/Navigation/SearchHeader/SearchHeader.tsx new file mode 100644 index 00000000..b32a912e --- /dev/null +++ b/src/components/common/Navigation/SearchHeader/SearchHeader.tsx @@ -0,0 +1,58 @@ +import { useRouter } from "next/router"; +import { CSSTransition } from "react-transition-group"; +import { css } from "twin.macro"; + +import { useScrollDirection } from "@/application/hooks"; +import { BackButton } from "@/components/common/Navigation/BackButton"; +import { SearchInput } from "@/components/search"; +import { TagCategory } from "@/components/tags"; + +const DELAY = 300; + +interface Props { + searchValue?: string; + isBack?: boolean; +} + +export const SearchHeader = ({ searchValue, isBack = true }: Props) => { + const direction = useScrollDirection(); + const router = useRouter(); + + return ( +
+
+ {isBack && ( + + + + )} + + { + router.push("/search"); + }} + /> +
+ + +
+ ); +}; diff --git a/src/components/common/Navigation/SearchHeader/index.ts b/src/components/common/Navigation/SearchHeader/index.ts new file mode 100644 index 00000000..cfa1983a --- /dev/null +++ b/src/components/common/Navigation/SearchHeader/index.ts @@ -0,0 +1 @@ +export * from "./SearchHeader"; diff --git a/src/components/common/Navigation/SearchPageNavigation.tsx b/src/components/common/Navigation/SearchPageNavigation.tsx index 5ffce571..153413bf 100644 --- a/src/components/common/Navigation/SearchPageNavigation.tsx +++ b/src/components/common/Navigation/SearchPageNavigation.tsx @@ -1,11 +1,14 @@ +import { Logo } from "@/components/common/Navigation/Logo"; + import { CloseButton } from "./CloseButton"; import { Navigation } from "./Navigation"; export const SearchPageNavigation = () => { return ( - - 밈 찾기 + + + diff --git a/src/components/common/Navigation/SideBar/Category.stories.tsx b/src/components/common/Navigation/SideBar/Category.stories.tsx deleted file mode 100644 index d1ae5ac1..00000000 --- a/src/components/common/Navigation/SideBar/Category.stories.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import type { ComponentMeta, ComponentStory } from "@storybook/react"; - -import { Category } from "./Category"; - -export default { - title: "components/common/Navigation/Category", - component: Category, -} as ComponentMeta; - -export const Default: ComponentStory = () => ; diff --git a/src/components/common/Navigation/SideBar/Category.tsx b/src/components/common/Navigation/SideBar/Category.tsx deleted file mode 100644 index 8d7ad199..00000000 --- a/src/components/common/Navigation/SideBar/Category.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import { Content, Header, Item, Root, Trigger } from "@radix-ui/react-accordion"; -import { useRouter } from "next/router"; - -import { - useAuth, - useDeleteFavoriteTag, - useGetCategoryWithTag, - useToast, -} from "@/application/hooks"; -import { PATH } from "@/application/util"; -import { useSetDrawerContext } from "@/components/common/Drawer"; -import { Icon } from "@/components/common/Icon"; -import { Photo } from "@/components/common/Photo"; - -const FAVORITE_ID = "즐겨찾기"; -const TAG_DELETE_DELAY = 1500; - -export const Category = () => { - const router = useRouter(); - const { isLoading } = useAuth(); - const setDrawerOpen = useSetDrawerContext(); - const { show, close } = useToast(); - - const { data } = useGetCategoryWithTag({ - enabled: !isLoading, - select: ({ categories }) => { - const favoriteItem = { - name: FAVORITE_ID, - id: FAVORITE_ID, - icon: "/icon/star.svg", - tags: categories.map((category) => category.tags.filter((tag) => tag.isFav)).flat(), - }; - - const restItem = categories.map((category) => ({ - name: category.name, - id: String(category.categoryId), - icon: category.icon, - tags: category.tags.filter((tag) => !tag.isFav), - })); - - if (favoriteItem.tags.length) restItem.unshift(favoriteItem); - - return restItem; - }, - }); - const { mutate: deleteFavoriteTag, onCancel } = useDeleteFavoriteTag(TAG_DELETE_DELAY); - - const onClickItem = (tagId: number) => { - setDrawerOpen(false); - router.push(PATH.getExploreByTagPath(tagId)); - }; - - const handleDeleteItem = async (tagId: number) => { - show( - (id) => ( - <> -
태그가 삭제 되었습니다.
- - - ), - { duration: TAG_DELETE_DELAY }, - ); - - deleteFavoriteTag(tagId, { - onError: (err) => { - if (err instanceof Error && err.name === "CanceledError") return; - show("태그 삭제가 실패하였습니다."); - }, - }); - }; - - return ( - - {data?.map((item) => ( - -
- - - {item.name} - - -
- -
    - {item.tags.map((tag) => ( -
  • - - {item.id === FAVORITE_ID && ( - - )} -
  • - ))} -
-
-
- ))} -
- ); -}; diff --git a/src/components/common/Navigation/SideBar/LoginSideBarContent.tsx b/src/components/common/Navigation/SideBar/LoginSideBarContent.tsx new file mode 100644 index 00000000..85068d81 --- /dev/null +++ b/src/components/common/Navigation/SideBar/LoginSideBarContent.tsx @@ -0,0 +1,71 @@ +import Link from "next/link"; + +import type { useAuth } from "@/application/hooks"; +import { useModal } from "@/application/hooks"; +import { channelUrl } from "@/application/util"; +import { Icon } from "@/components/common/Icon"; +import { SignOutModal } from "@/components/common/Modal"; +import { Photo } from "@/components/common/Photo"; + +type LoginSideBarContentProps = ReturnType; + +export const LoginSideBarContent = (props: LoginSideBarContentProps) => { + const { user } = props; + const modalProps = useModal(); + + return ( + <> +
+ + + {user?.name} + + + +
+ + {user?.shareCount} + share + + + {user?.saveCount} + collect + +
+
+ +
+
+ 계정 정보 + {user?.email} +
+ + + 게시물 신고 + + + + 서비스 이용 문의 + + + +
+ + + ); +}; diff --git a/src/components/common/Navigation/SideBar/LogoutSideBarContent.tsx b/src/components/common/Navigation/SideBar/LogoutSideBarContent.tsx new file mode 100644 index 00000000..c1b9bbfa --- /dev/null +++ b/src/components/common/Navigation/SideBar/LogoutSideBarContent.tsx @@ -0,0 +1,56 @@ +import type { useAuth } from "@/application/hooks"; +import { channelUrl } from "@/application/util"; +import { Icon } from "@/components/common/Icon"; +import { Photo } from "@/components/common/Photo"; + +const defaultAvatarUrl = "/img/default-avatar.png"; +const defaultName = "로그인하기"; + +type LogoutSideBarContentProps = ReturnType; + +export const LogoutSideBarContent = (props: LogoutSideBarContentProps) => { + const { validate } = props; + + return ( + <> + + +
+ + 게시물 신고 + + + + 서비스 이용 문의 + +
+ + ); +}; diff --git a/src/components/common/Navigation/SideBar/SideBar.tsx b/src/components/common/Navigation/SideBar/SideBar.tsx index 5aed6470..30603a2b 100644 --- a/src/components/common/Navigation/SideBar/SideBar.tsx +++ b/src/components/common/Navigation/SideBar/SideBar.tsx @@ -1,27 +1,46 @@ -import Link from "next/link"; - +import { useAuth } from "@/application/hooks"; +import { instagramUrl, twitterUrl } from "@/application/util"; import { Drawer } from "@/components/common/Drawer"; import { Icon } from "@/components/common/Icon"; -import { Category } from "@/components/common/Navigation/SideBar/Category"; -import { SSRSuspense } from "@/components/common/Suspense"; -import { SearchInput } from "@/components/search"; +import { useTagCategoryContext } from "@/components/tags"; + +import { LoginSideBarContent } from "./LoginSideBarContent"; +import { LogoutSideBarContent } from "./LogoutSideBarContent"; export const SideBar = () => { + const auth = useAuth(); + const [, setIsOpenTagCategory] = useTagCategoryContext(); + return ( - - - {({ isOpen }) => (isOpen ? : )} - - - - - - -
- + <> + + + {({ isOpen }) => + isOpen ? ( + + ) : ( + setIsOpenTagCategory(false)} /> + ) + } + + +
+ {auth.isLogin ? : } + +
+ +
© thismeme.me
+
- -
-
+ + + ); }; diff --git a/src/components/common/NextSeo/NextSeo.tsx b/src/components/common/NextSeo/NextSeo.tsx index c9d4bbe9..65bbbbb2 100644 --- a/src/components/common/NextSeo/NextSeo.tsx +++ b/src/components/common/NextSeo/NextSeo.tsx @@ -6,7 +6,7 @@ import type { NextSeoProps } from "./types"; * @name NextSeo * @description * 현재 페이지에 타이틀, 설명, [OpenGraph](https://nowonbun.tistory.com/517) (공유 시 타이틀, 설명, 이미지) 를 적용할 수 있도록 하는 컴포넌트입니다. - * 추후에 twitter card 도 적용할 예정입니다. + * @see {@link https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup Cards Markup} * @example * */ export const NextSeo = (props: NextSeoProps) => { const tagsToRender = []; - const { title, description, canonical, openGraph } = props; + const { title, description, canonical, openGraph, twitter } = props; if (title) { tagsToRender.push({title}); @@ -38,6 +44,12 @@ export const NextSeo = (props: NextSeoProps) => { ); } + if (openGraph?.siteName) { + tagsToRender.push( + , + ); + } + if (openGraph?.description || description) { tagsToRender.push( { } } + if (twitter) { + if (twitter.cardType) { + tagsToRender.push(); + } + + if (twitter.site) { + tagsToRender.push(); + } + + if (twitter.handle) { + tagsToRender.push( + , + ); + } + } + if (canonical) { tagsToRender.push(); } diff --git a/src/components/common/NextSeo/types.ts b/src/components/common/NextSeo/types.ts index 4ae10097..6a154a4e 100644 --- a/src/components/common/NextSeo/types.ts +++ b/src/components/common/NextSeo/types.ts @@ -3,11 +3,18 @@ interface OpenGraph { title?: string; description?: string; imageUrl?: string; + siteName?: string; } +interface Twitter { + handle?: string; + site?: string; + cardType?: string; +} export interface NextSeoProps { title?: string; description?: string; canonical?: string; openGraph?: OpenGraph; + twitter?: Twitter; } diff --git a/src/components/common/Overlay/Overlay.tsx b/src/components/common/Overlay/Overlay.tsx deleted file mode 100644 index 29f5573d..00000000 --- a/src/components/common/Overlay/Overlay.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import type { ReactNode } from "react"; -import type { TransitionStatus } from "react-transition-group"; -import { CSSTransition } from "react-transition-group"; -import { css } from "twin.macro"; - -import { Portal } from "@/components/common/Portal"; - -const DELAY = 300; - -interface Props { - onBackdropClick: () => void; - isOpen: boolean; - children: ReactNode | ((state: TransitionStatus) => ReactNode); -} - -export const OverLay = ({ children, isOpen, onBackdropClick }: Props) => { - return ( - - -
- - - {children} - - - ); -}; diff --git a/src/components/common/Overlay/index.ts b/src/components/common/Overlay/index.ts deleted file mode 100644 index d28cdff6..00000000 --- a/src/components/common/Overlay/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./Overlay"; diff --git a/src/components/common/Portal/Portal.tsx b/src/components/common/Portal/Portal.tsx index 883c5708..a93d384a 100644 --- a/src/components/common/Portal/Portal.tsx +++ b/src/components/common/Portal/Portal.tsx @@ -29,7 +29,7 @@ export const Portal = ({ id, children }: Props) => { element.className = `${pretendard.variable} ${suit.variable}`; element.id = id; - document.body.prepend(element); + document.body.append(element); } ref.current = element as HTMLDivElement; diff --git a/src/components/common/PullToRefresh/PullToRefresh.tsx b/src/components/common/PullToRefresh/PullToRefresh.tsx index 6ffcde6a..754882bb 100644 --- a/src/components/common/PullToRefresh/PullToRefresh.tsx +++ b/src/components/common/PullToRefresh/PullToRefresh.tsx @@ -135,11 +135,12 @@ export const PullToRefresh = ({
{status !== "pulling" && } diff --git a/src/components/common/PullToRefresh/RefreshContent.tsx b/src/components/common/PullToRefresh/RefreshContent.tsx index 070ab40a..9262b663 100644 --- a/src/components/common/PullToRefresh/RefreshContent.tsx +++ b/src/components/common/PullToRefresh/RefreshContent.tsx @@ -20,5 +20,5 @@ export const RefreshContent = () => { }; }, []); - return
; + return
; }; diff --git a/src/components/common/Toast/ToastContainer.tsx b/src/components/common/Toast/ToastContainer.tsx index 6091a43f..4673f50c 100644 --- a/src/components/common/Toast/ToastContainer.tsx +++ b/src/components/common/Toast/ToastContainer.tsx @@ -41,7 +41,7 @@ export const ToastContainer = () => { return (