diff --git a/.eslintrc.json b/.eslintrc.json
index 67ca6a5..4c1fda1 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -3,9 +3,7 @@
"rules": {
"no-var": "error", // var 금지
"no-multiple-empty-lines": "error", // 여러 줄 공백 금지
- "no-console": ["error", { "allow": ["warn", "error", "info"] }], // console.log() 금지
"eqeqeq": "error", // 일치 연산자 사용 필수
- "dot-notation": "error", // 가능하다면 dot notation 사용
- "no-unused-vars": "error" // 사용하지 않는 변수 금지
+ "dot-notation": "error" // 가능하다면 dot notation 사용
}
}
diff --git a/.gitignore b/.gitignore
index e137dcd..dfc24bc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,4 +22,5 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
-.eslintcache
\ No newline at end of file
+.eslintcache
+.env
\ No newline at end of file
diff --git a/.prettierrc.js b/.prettierrc.js
index b4d2e65..1129fe3 100644
--- a/.prettierrc.js
+++ b/.prettierrc.js
@@ -1,8 +1,7 @@
module.exports = {
singleQuote: true,
bracketSpacing: true,
- bracketSameLine: true,
- arrowParens: 'avoid',
printWidth: 120,
tabWidth: 2,
+ trailingComma: 'all',
};
diff --git a/package-lock.json b/package-lock.json
index 8ae474e..69bdd40 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,14 +10,26 @@
"hasInstallScript": true,
"dependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
+ "@reduxjs/toolkit": "^1.9.5",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
+ "axios": "^1.4.0",
+ "crypto-js": "^4.1.1",
"lint-staged": "^13.2.2",
+ "lodash": "^4.17.21",
"react": "^18.2.0",
"react-dom": "^18.2.0",
+ "react-icons": "^4.10.1",
+ "react-redux": "^8.1.1",
+ "react-redux-toolkit": "^0.0.1-alpha.2",
+ "react-router": "^6.14.0",
+ "react-router-dom": "^6.14.0",
"react-scripts": "5.0.1",
+ "react-select": "^5.7.3",
"styled-components": "^6.0.0-rc.5",
+ "sweetalert": "^2.1.2",
+ "sweetalert2": "^11.7.12",
"web-vitals": "^2.1.4"
},
"devDependencies": {
@@ -3046,6 +3058,60 @@
"postcss-selector-parser": "^6.0.10"
}
},
+ "node_modules/@emotion/babel-plugin": {
+ "version": "11.11.0",
+ "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz",
+ "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.16.7",
+ "@babel/runtime": "^7.18.3",
+ "@emotion/hash": "^0.9.1",
+ "@emotion/memoize": "^0.8.1",
+ "@emotion/serialize": "^1.1.2",
+ "babel-plugin-macros": "^3.1.0",
+ "convert-source-map": "^1.5.0",
+ "escape-string-regexp": "^4.0.0",
+ "find-root": "^1.1.0",
+ "source-map": "^0.5.7",
+ "stylis": "4.2.0"
+ }
+ },
+ "node_modules/@emotion/babel-plugin/node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@emotion/babel-plugin/node_modules/source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/@emotion/cache": {
+ "version": "11.11.0",
+ "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz",
+ "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==",
+ "dependencies": {
+ "@emotion/memoize": "^0.8.1",
+ "@emotion/sheet": "^1.2.2",
+ "@emotion/utils": "^1.2.1",
+ "@emotion/weak-memoize": "^0.3.1",
+ "stylis": "4.2.0"
+ }
+ },
+ "node_modules/@emotion/hash": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz",
+ "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ=="
+ },
"node_modules/@emotion/is-prop-valid": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz",
@@ -3059,11 +3125,69 @@
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz",
"integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA=="
},
+ "node_modules/@emotion/react": {
+ "version": "11.11.1",
+ "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.1.tgz",
+ "integrity": "sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA==",
+ "dependencies": {
+ "@babel/runtime": "^7.18.3",
+ "@emotion/babel-plugin": "^11.11.0",
+ "@emotion/cache": "^11.11.0",
+ "@emotion/serialize": "^1.1.2",
+ "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1",
+ "@emotion/utils": "^1.2.1",
+ "@emotion/weak-memoize": "^0.3.1",
+ "hoist-non-react-statics": "^3.3.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@emotion/serialize": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.2.tgz",
+ "integrity": "sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==",
+ "dependencies": {
+ "@emotion/hash": "^0.9.1",
+ "@emotion/memoize": "^0.8.1",
+ "@emotion/unitless": "^0.8.1",
+ "@emotion/utils": "^1.2.1",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@emotion/sheet": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz",
+ "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA=="
+ },
"node_modules/@emotion/unitless": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz",
"integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ=="
},
+ "node_modules/@emotion/use-insertion-effect-with-fallbacks": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz",
+ "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==",
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@emotion/utils": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz",
+ "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg=="
+ },
+ "node_modules/@emotion/weak-memoize": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz",
+ "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww=="
+ },
"node_modules/@eslint-community/eslint-utils": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -3157,6 +3281,19 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
+ "node_modules/@floating-ui/core": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.3.1.tgz",
+ "integrity": "sha512-Bu+AMaXNjrpjh41znzHqaz3r2Nr8hHuHZT6V2LBKMhyMl0FgKA62PNYbqnfgmzOhoWZj70Zecisbo4H1rotP5g=="
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.4.2.tgz",
+ "integrity": "sha512-VKmvHVatWnewmGGy+7Mdy4cTJX71Pli6v/Wjb5RQBuq5wjUYx+Ef+kRThi8qggZqDgD8CogCpqhRoVp3+yQk+g==",
+ "dependencies": {
+ "@floating-ui/core": "^1.3.1"
+ }
+ },
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz",
@@ -4046,6 +4183,37 @@
}
}
},
+ "node_modules/@reduxjs/toolkit": {
+ "version": "1.9.5",
+ "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.5.tgz",
+ "integrity": "sha512-Rt97jHmfTeaxL4swLRNPD/zV4OxTes4la07Xc4hetpUW/vc75t5m1ANyxG6ymnEQ2FsLQsoMlYB2vV1sO3m8tQ==",
+ "dependencies": {
+ "immer": "^9.0.21",
+ "redux": "^4.2.1",
+ "redux-thunk": "^2.4.2",
+ "reselect": "^4.1.8"
+ },
+ "peerDependencies": {
+ "react": "^16.9.0 || ^17.0.0 || ^18",
+ "react-redux": "^7.2.1 || ^8.0.2"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ },
+ "react-redux": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@remix-run/router": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.7.0.tgz",
+ "integrity": "sha512-Eu1V3kz3mV0wUpVTiFHuaT8UD1gj/0VnoFHQYX35xlslQUpe8CuYoKFn9d4WZFHm3yDywz6ALZuGdnUPKrNeAw==",
+ "engines": {
+ "node": ">=14"
+ }
+ },
"node_modules/@rollup/plugin-babel": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
@@ -4782,6 +4950,11 @@
"@types/node": "*"
}
},
+ "node_modules/@types/cookie": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz",
+ "integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow=="
+ },
"node_modules/@types/eslint": {
"version": "8.40.2",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.40.2.tgz",
@@ -4835,6 +5008,15 @@
"@types/node": "*"
}
},
+ "node_modules/@types/hoist-non-react-statics": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
+ "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
+ "dependencies": {
+ "@types/react": "*",
+ "hoist-non-react-statics": "^3.3.0"
+ }
+ },
"node_modules/@types/html-minifier-terser": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
@@ -5137,6 +5319,11 @@
"integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==",
"dev": true
},
+ "node_modules/@types/object-assign": {
+ "version": "4.0.30",
+ "resolved": "https://registry.npmjs.org/@types/object-assign/-/object-assign-4.0.30.tgz",
+ "integrity": "sha512-HhE8gFfLj321pa6OE59QmOdL5NgIOhkdYn7MWnZTOcHOms8XFzNgr9+A0/GbN0XEX9wTM58yg4YXKhGr69QIUw=="
+ },
"node_modules/@types/parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
@@ -5185,6 +5372,14 @@
"@types/react": "*"
}
},
+ "node_modules/@types/react-transition-group": {
+ "version": "4.4.6",
+ "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.6.tgz",
+ "integrity": "sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==",
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
"node_modules/@types/resolve": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
@@ -5260,6 +5455,11 @@
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.3.tgz",
"integrity": "sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g=="
},
+ "node_modules/@types/use-sync-external-store": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
+ "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
+ },
"node_modules/@types/ws": {
"version": "8.5.5",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz",
@@ -6105,6 +6305,29 @@
"node": ">=4"
}
},
+ "node_modules/axios": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz",
+ "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==",
+ "dependencies": {
+ "follow-redirects": "^1.15.0",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/axios/node_modules/form-data": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/axobject-query": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz",
@@ -7259,6 +7482,11 @@
"node": ">= 8"
}
},
+ "node_modules/crypto-js": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz",
+ "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw=="
+ },
"node_modules/crypto-random-string": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz",
@@ -7995,6 +8223,15 @@
"utila": "~0.4"
}
},
+ "node_modules/dom-helpers": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
+ "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
+ "dependencies": {
+ "@babel/runtime": "^7.8.7",
+ "csstype": "^3.0.2"
+ }
+ },
"node_modules/dom-serializer": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz",
@@ -8323,6 +8560,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/es6-object-assign": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz",
+ "integrity": "sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw=="
+ },
"node_modules/escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@@ -9412,6 +9654,11 @@
"url": "https://github.com/avajs/find-cache-dir?sponsor=1"
}
},
+ "node_modules/find-root": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
+ "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="
+ },
"node_modules/find-up": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
@@ -9827,6 +10074,14 @@
"node": ">=10"
}
},
+ "node_modules/gitbook-plugin-github": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/gitbook-plugin-github/-/gitbook-plugin-github-2.0.0.tgz",
+ "integrity": "sha512-TDGQgdC5vFGEqn533SDf/8AV+VagxhGIUyCZAq0EqwkyS1F73lCl7w9l8UWDd2me7Sq9L3zcr7RaJ6FjSFAo7w==",
+ "engines": {
+ "gitbook": ">=2.5.0"
+ }
+ },
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
@@ -10091,6 +10346,32 @@
"he": "bin/he"
}
},
+ "node_modules/history": {
+ "version": "4.10.1",
+ "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
+ "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==",
+ "dependencies": {
+ "@babel/runtime": "^7.1.2",
+ "loose-envify": "^1.2.0",
+ "resolve-pathname": "^3.0.0",
+ "tiny-invariant": "^1.0.2",
+ "tiny-warning": "^1.0.0",
+ "value-equal": "^1.0.1"
+ }
+ },
+ "node_modules/hoist-non-react-statics": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+ "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+ "dependencies": {
+ "react-is": "^16.7.0"
+ }
+ },
+ "node_modules/hoist-non-react-statics/node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+ },
"node_modules/hoopy": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz",
@@ -10516,6 +10797,14 @@
"node": ">= 0.4"
}
},
+ "node_modules/invariant": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+ "dependencies": {
+ "loose-envify": "^1.0.0"
+ }
+ },
"node_modules/ipaddr.js": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz",
@@ -10594,6 +10883,28 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-buffer": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz",
+ "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/is-callable": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
@@ -13780,6 +14091,11 @@
"node": ">= 4.0.0"
}
},
+ "node_modules/memoize-one": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
+ "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="
+ },
"node_modules/meow": {
"version": "8.1.2",
"resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz",
@@ -16002,6 +16318,11 @@
"asap": "~2.0.6"
}
},
+ "node_modules/promise-polyfill": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-6.1.0.tgz",
+ "integrity": "sha512-g0LWaH0gFsxovsU7R5LrrhHhWAWiHRnh1GPrhXnPgYsDkIqjRYUYSZEsej/wtleDrz5xVSIDbeKfidztp2XHFQ=="
+ },
"node_modules/prompts": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
@@ -16049,6 +16370,11 @@
"node": ">= 0.10"
}
},
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+ },
"node_modules/psl": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
@@ -16336,11 +16662,237 @@
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz",
"integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg=="
},
+ "node_modules/react-icons": {
+ "version": "4.10.1",
+ "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.10.1.tgz",
+ "integrity": "sha512-/ngzDP/77tlCfqthiiGNZeYFACw85fUjZtLbedmJ5DTlNDIwETxhwBzdOJ21zj4iJdvc0J3y7yOsX3PpxAJzrw==",
+ "peerDependencies": {
+ "react": "*"
+ }
+ },
"node_modules/react-is": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
},
+ "node_modules/react-lifecycles-compat": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
+ "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
+ },
+ "node_modules/react-redux": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.1.tgz",
+ "integrity": "sha512-5W0QaKtEhj+3bC0Nj0NkqkhIv8gLADH/2kYFMTHxCVqQILiWzLv6MaLuV5wJU3BQEdHKzTfcvPN0WMS6SC1oyA==",
+ "dependencies": {
+ "@babel/runtime": "^7.12.1",
+ "@types/hoist-non-react-statics": "^3.3.1",
+ "@types/use-sync-external-store": "^0.0.3",
+ "hoist-non-react-statics": "^3.3.2",
+ "react-is": "^18.0.0",
+ "use-sync-external-store": "^1.0.0"
+ },
+ "peerDependencies": {
+ "@types/react": "^16.8 || ^17.0 || ^18.0",
+ "@types/react-dom": "^16.8 || ^17.0 || ^18.0",
+ "react": "^16.8 || ^17.0 || ^18.0",
+ "react-dom": "^16.8 || ^17.0 || ^18.0",
+ "react-native": ">=0.59",
+ "redux": "^4 || ^5.0.0-beta.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ },
+ "redux": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-redux-toolkit": {
+ "version": "0.0.1-alpha.2",
+ "resolved": "https://registry.npmjs.org/react-redux-toolkit/-/react-redux-toolkit-0.0.1-alpha.2.tgz",
+ "integrity": "sha512-lxs8RB48nFSEm5ek7wGTIhUEjIeGTVH6F8tG16GdqcMBfJ8Puq4qu5sOpVIwtcGpBY5mvRsozzQ6WfCklrAb5w==",
+ "dependencies": {
+ "axios": "^0.18.0",
+ "lodash": "^4.17.10",
+ "prop-types": "^15.6.1",
+ "react": "^16.0.0",
+ "react-redux": "^5.0.0",
+ "react-router": "^4.3.1",
+ "react-router-dom": "^4.3.1",
+ "redux": "^4.0.0",
+ "redux-observable": "^0.18.0",
+ "rxjs": "^5.5.6",
+ "shortid": "^2.2.8",
+ "universal-cookie": "^3.0.7"
+ },
+ "peerDependencies": {
+ "react": "^15.0.0-0 || ^16.0.0-0",
+ "react-redux": "^4.3.0 || ^5.0.0",
+ "react-router": "^4.0.0",
+ "react-router-dom": "^4.0.0",
+ "redux": "^3.0.0 || ^4.0.0"
+ }
+ },
+ "node_modules/react-redux-toolkit/node_modules/axios": {
+ "version": "0.18.1",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.1.tgz",
+ "integrity": "sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g==",
+ "deprecated": "Critical security vulnerability fixed in v0.21.1. For more information, see https://github.com/axios/axios/pull/3410",
+ "dependencies": {
+ "follow-redirects": "1.5.10",
+ "is-buffer": "^2.0.2"
+ }
+ },
+ "node_modules/react-redux-toolkit/node_modules/debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/react-redux-toolkit/node_modules/follow-redirects": {
+ "version": "1.5.10",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
+ "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
+ "dependencies": {
+ "debug": "=3.1.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/react-redux-toolkit/node_modules/isarray": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="
+ },
+ "node_modules/react-redux-toolkit/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ },
+ "node_modules/react-redux-toolkit/node_modules/path-to-regexp": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz",
+ "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==",
+ "dependencies": {
+ "isarray": "0.0.1"
+ }
+ },
+ "node_modules/react-redux-toolkit/node_modules/react": {
+ "version": "16.14.0",
+ "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz",
+ "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.1",
+ "prop-types": "^15.6.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-redux-toolkit/node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+ },
+ "node_modules/react-redux-toolkit/node_modules/react-redux": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.1.2.tgz",
+ "integrity": "sha512-Ns1G0XXc8hDyH/OcBHOxNgQx9ayH3SPxBnFCOidGKSle8pKihysQw2rG/PmciUQRoclhVBO8HMhiRmGXnDja9Q==",
+ "dependencies": {
+ "@babel/runtime": "^7.1.2",
+ "hoist-non-react-statics": "^3.3.0",
+ "invariant": "^2.2.4",
+ "loose-envify": "^1.1.0",
+ "prop-types": "^15.6.1",
+ "react-is": "^16.6.0",
+ "react-lifecycles-compat": "^3.0.0"
+ },
+ "peerDependencies": {
+ "react": "^0.14.0 || ^15.0.0-0 || ^16.0.0-0",
+ "redux": "^2.0.0 || ^3.0.0 || ^4.0.0-0"
+ }
+ },
+ "node_modules/react-redux-toolkit/node_modules/react-router": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-4.3.1.tgz",
+ "integrity": "sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg==",
+ "dependencies": {
+ "history": "^4.7.2",
+ "hoist-non-react-statics": "^2.5.0",
+ "invariant": "^2.2.4",
+ "loose-envify": "^1.3.1",
+ "path-to-regexp": "^1.7.0",
+ "prop-types": "^15.6.1",
+ "warning": "^4.0.1"
+ },
+ "peerDependencies": {
+ "react": ">=15"
+ }
+ },
+ "node_modules/react-redux-toolkit/node_modules/react-router-dom": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-4.3.1.tgz",
+ "integrity": "sha512-c/MlywfxDdCp7EnB7YfPMOfMD3tOtIjrQlj/CKfNMBxdmpJP8xcz5P/UAFn3JbnQCNUxsHyVVqllF9LhgVyFCA==",
+ "dependencies": {
+ "history": "^4.7.2",
+ "invariant": "^2.2.4",
+ "loose-envify": "^1.3.1",
+ "prop-types": "^15.6.1",
+ "react-router": "^4.3.1",
+ "warning": "^4.0.1"
+ },
+ "peerDependencies": {
+ "react": ">=15"
+ }
+ },
+ "node_modules/react-redux-toolkit/node_modules/react-router/node_modules/hoist-non-react-statics": {
+ "version": "2.5.5",
+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
+ "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw=="
+ },
+ "node_modules/react-redux-toolkit/node_modules/redux-observable": {
+ "version": "0.18.0",
+ "resolved": "https://registry.npmjs.org/redux-observable/-/redux-observable-0.18.0.tgz",
+ "integrity": "sha512-tu02n6jr6/bq/vyI9E/AHxIyIl0YsWloqvWqSBG0KqN6aQBujMBP6hlDAlQLj8hP+XQpqL293MLX6V612c0jSg==",
+ "dependencies": {
+ "gitbook-plugin-github": "^2.0.0"
+ },
+ "peerDependencies": {
+ "redux": ">=3 <4",
+ "rxjs": ">=5 <6"
+ }
+ },
+ "node_modules/react-redux-toolkit/node_modules/rxjs": {
+ "version": "5.5.12",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.12.tgz",
+ "integrity": "sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw==",
+ "dependencies": {
+ "symbol-observable": "1.0.1"
+ },
+ "engines": {
+ "npm": ">=2.0.0"
+ }
+ },
+ "node_modules/react-redux/node_modules/react-is": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
+ },
"node_modules/react-refresh": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
@@ -16349,6 +16901,36 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-router": {
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.14.0.tgz",
+ "integrity": "sha512-OD+vkrcGbvlwkspUFDgMzsu1RXwdjNh83YgG/28lBnDzgslhCgxIqoExLlxsfTpIygp7fc+Hd3esloNwzkm2xA==",
+ "dependencies": {
+ "@remix-run/router": "1.7.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "peerDependencies": {
+ "react": ">=16.8"
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.14.0.tgz",
+ "integrity": "sha512-YEwlApKwzMMMbGbhh+Q7MsloTldcwMgHxUY/1g0uA62+B1hZo2jsybCWIDCL8zvIDB1FA0pBKY9chHbZHt+2dQ==",
+ "dependencies": {
+ "@remix-run/router": "1.7.0",
+ "react-router": "6.14.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "peerDependencies": {
+ "react": ">=16.8",
+ "react-dom": ">=16.8"
+ }
+ },
"node_modules/react-scripts": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
@@ -16421,6 +17003,41 @@
}
}
},
+ "node_modules/react-select": {
+ "version": "5.7.3",
+ "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.7.3.tgz",
+ "integrity": "sha512-z8i3NCuFFWL3w27xq92rBkVI2onT0jzIIPe480HlBjXJ3b5o6Q+Clp4ydyeKrj9DZZ3lrjawwLC5NGl0FSvUDg==",
+ "dependencies": {
+ "@babel/runtime": "^7.12.0",
+ "@emotion/cache": "^11.4.0",
+ "@emotion/react": "^11.8.1",
+ "@floating-ui/dom": "^1.0.1",
+ "@types/react-transition-group": "^4.4.0",
+ "memoize-one": "^6.0.0",
+ "prop-types": "^15.6.0",
+ "react-transition-group": "^4.3.0",
+ "use-isomorphic-layout-effect": "^1.1.2"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/react-transition-group": {
+ "version": "4.4.5",
+ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
+ "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
+ "dependencies": {
+ "@babel/runtime": "^7.5.5",
+ "dom-helpers": "^5.0.1",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.6.2"
+ },
+ "peerDependencies": {
+ "react": ">=16.6.0",
+ "react-dom": ">=16.6.0"
+ }
+ },
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -16605,6 +17222,22 @@
"node": ">=8"
}
},
+ "node_modules/redux": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
+ "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
+ "dependencies": {
+ "@babel/runtime": "^7.9.2"
+ }
+ },
+ "node_modules/redux-thunk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz",
+ "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==",
+ "peerDependencies": {
+ "redux": "^4"
+ }
+ },
"node_modules/regenerate": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@@ -16731,6 +17364,11 @@
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
},
+ "node_modules/reselect": {
+ "version": "4.1.8",
+ "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz",
+ "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="
+ },
"node_modules/resolve": {
"version": "1.22.2",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
@@ -16778,6 +17416,11 @@
"node": ">=8"
}
},
+ "node_modules/resolve-pathname": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz",
+ "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng=="
+ },
"node_modules/resolve-url-loader": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-4.0.0.tgz",
@@ -17342,6 +17985,19 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/shortid": {
+ "version": "2.2.16",
+ "resolved": "https://registry.npmjs.org/shortid/-/shortid-2.2.16.tgz",
+ "integrity": "sha512-Ugt+GIZqvGXCIItnsL+lvFJOiN7RYqlGy7QE41O3YC1xbNSeDGIRO7xg2JJXIAj1cAGnOeC1r7/T9pgrtQbv4g==",
+ "dependencies": {
+ "nanoid": "^2.1.0"
+ }
+ },
+ "node_modules/shortid/node_modules/nanoid": {
+ "version": "2.1.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-2.1.11.tgz",
+ "integrity": "sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA=="
+ },
"node_modules/side-channel": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
@@ -18050,6 +18706,32 @@
"boolbase": "~1.0.0"
}
},
+ "node_modules/sweetalert": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/sweetalert/-/sweetalert-2.1.2.tgz",
+ "integrity": "sha512-iWx7X4anRBNDa/a+AdTmvAzQtkN1+s4j/JJRWlHpYE8Qimkohs8/XnFcWeYHH2lMA8LRCa5tj2d244If3S/hzA==",
+ "dependencies": {
+ "es6-object-assign": "^1.1.0",
+ "promise-polyfill": "^6.0.2"
+ }
+ },
+ "node_modules/sweetalert2": {
+ "version": "11.7.12",
+ "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.7.12.tgz",
+ "integrity": "sha512-TQJy8mQymJLzqWPQOMQErd81Zd/rSYr0UL4pEc7bqEihtjS+zt7LWJXLhfPp93e+Hf3Z2FHMB6QGNskAMCsdTg==",
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/sponsors/limonte"
+ }
+ },
+ "node_modules/symbol-observable": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz",
+ "integrity": "sha512-Kb3PrPYz4HanVF1LVGuAdW6LoVgIwjUYJGzFe7NDrBLCN4lsV/5J0MFurV+ygS4bRVwrCEt2c7MQ1R2a72oJDw==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/symbol-tree": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
@@ -18276,6 +18958,16 @@
"resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",
"integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA=="
},
+ "node_modules/tiny-invariant": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz",
+ "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw=="
+ },
+ "node_modules/tiny-warning": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
+ "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
+ },
"node_modules/tmpl": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
@@ -18608,6 +19300,25 @@
"node": ">=8"
}
},
+ "node_modules/universal-cookie": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-3.1.0.tgz",
+ "integrity": "sha512-sP6WuFgqIUro7ikgI2ndrsw9Ro+YvVBe5O9cQfWnjTicpLaSMUEUUDjQF8m8utzWF2ONl7tRkcZd7v4n6NnzjQ==",
+ "dependencies": {
+ "@types/cookie": "^0.3.1",
+ "@types/object-assign": "^4.0.30",
+ "cookie": "^0.3.1",
+ "object-assign": "^4.1.0"
+ }
+ },
+ "node_modules/universal-cookie/node_modules/cookie": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
+ "integrity": "sha512-+IJOX0OqlHCszo2mBUq+SrEbCj6w7Kpffqx60zYbPTFaO4+yYgRjHwcZNpWvaTylDHaV7PPmBHzSecZiMhtPgw==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
@@ -18684,6 +19395,27 @@
"requires-port": "^1.0.0"
}
},
+ "node_modules/use-isomorphic-layout-effect": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz",
+ "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
+ "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -18753,6 +19485,11 @@
"spdx-expression-parse": "^3.0.0"
}
},
+ "node_modules/value-equal": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
+ "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw=="
+ },
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@@ -18789,6 +19526,14 @@
"makeerror": "1.0.12"
}
},
+ "node_modules/warning": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
+ "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
+ "dependencies": {
+ "loose-envify": "^1.0.0"
+ }
+ },
"node_modules/watchpack": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
@@ -21744,6 +22489,53 @@
"integrity": "sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==",
"requires": {}
},
+ "@emotion/babel-plugin": {
+ "version": "11.11.0",
+ "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz",
+ "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==",
+ "requires": {
+ "@babel/helper-module-imports": "^7.16.7",
+ "@babel/runtime": "^7.18.3",
+ "@emotion/hash": "^0.9.1",
+ "@emotion/memoize": "^0.8.1",
+ "@emotion/serialize": "^1.1.2",
+ "babel-plugin-macros": "^3.1.0",
+ "convert-source-map": "^1.5.0",
+ "escape-string-regexp": "^4.0.0",
+ "find-root": "^1.1.0",
+ "source-map": "^0.5.7",
+ "stylis": "4.2.0"
+ },
+ "dependencies": {
+ "escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="
+ },
+ "source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="
+ }
+ }
+ },
+ "@emotion/cache": {
+ "version": "11.11.0",
+ "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz",
+ "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==",
+ "requires": {
+ "@emotion/memoize": "^0.8.1",
+ "@emotion/sheet": "^1.2.2",
+ "@emotion/utils": "^1.2.1",
+ "@emotion/weak-memoize": "^0.3.1",
+ "stylis": "4.2.0"
+ }
+ },
+ "@emotion/hash": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz",
+ "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ=="
+ },
"@emotion/is-prop-valid": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz",
@@ -21757,11 +22549,59 @@
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz",
"integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA=="
},
+ "@emotion/react": {
+ "version": "11.11.1",
+ "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.1.tgz",
+ "integrity": "sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA==",
+ "requires": {
+ "@babel/runtime": "^7.18.3",
+ "@emotion/babel-plugin": "^11.11.0",
+ "@emotion/cache": "^11.11.0",
+ "@emotion/serialize": "^1.1.2",
+ "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1",
+ "@emotion/utils": "^1.2.1",
+ "@emotion/weak-memoize": "^0.3.1",
+ "hoist-non-react-statics": "^3.3.1"
+ }
+ },
+ "@emotion/serialize": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.2.tgz",
+ "integrity": "sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==",
+ "requires": {
+ "@emotion/hash": "^0.9.1",
+ "@emotion/memoize": "^0.8.1",
+ "@emotion/unitless": "^0.8.1",
+ "@emotion/utils": "^1.2.1",
+ "csstype": "^3.0.2"
+ }
+ },
+ "@emotion/sheet": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz",
+ "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA=="
+ },
"@emotion/unitless": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz",
"integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ=="
},
+ "@emotion/use-insertion-effect-with-fallbacks": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz",
+ "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==",
+ "requires": {}
+ },
+ "@emotion/utils": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz",
+ "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg=="
+ },
+ "@emotion/weak-memoize": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz",
+ "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww=="
+ },
"@eslint-community/eslint-utils": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -21824,6 +22664,19 @@
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.43.0.tgz",
"integrity": "sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg=="
},
+ "@floating-ui/core": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.3.1.tgz",
+ "integrity": "sha512-Bu+AMaXNjrpjh41znzHqaz3r2Nr8hHuHZT6V2LBKMhyMl0FgKA62PNYbqnfgmzOhoWZj70Zecisbo4H1rotP5g=="
+ },
+ "@floating-ui/dom": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.4.2.tgz",
+ "integrity": "sha512-VKmvHVatWnewmGGy+7Mdy4cTJX71Pli6v/Wjb5RQBuq5wjUYx+Ef+kRThi8qggZqDgD8CogCpqhRoVp3+yQk+g==",
+ "requires": {
+ "@floating-ui/core": "^1.3.1"
+ }
+ },
"@humanwhocodes/config-array": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz",
@@ -22470,6 +23323,22 @@
"source-map": "^0.7.3"
}
},
+ "@reduxjs/toolkit": {
+ "version": "1.9.5",
+ "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.5.tgz",
+ "integrity": "sha512-Rt97jHmfTeaxL4swLRNPD/zV4OxTes4la07Xc4hetpUW/vc75t5m1ANyxG6ymnEQ2FsLQsoMlYB2vV1sO3m8tQ==",
+ "requires": {
+ "immer": "^9.0.21",
+ "redux": "^4.2.1",
+ "redux-thunk": "^2.4.2",
+ "reselect": "^4.1.8"
+ }
+ },
+ "@remix-run/router": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.7.0.tgz",
+ "integrity": "sha512-Eu1V3kz3mV0wUpVTiFHuaT8UD1gj/0VnoFHQYX35xlslQUpe8CuYoKFn9d4WZFHm3yDywz6ALZuGdnUPKrNeAw=="
+ },
"@rollup/plugin-babel": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
@@ -22997,6 +23866,11 @@
"@types/node": "*"
}
},
+ "@types/cookie": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz",
+ "integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow=="
+ },
"@types/eslint": {
"version": "8.40.2",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.40.2.tgz",
@@ -23050,6 +23924,15 @@
"@types/node": "*"
}
},
+ "@types/hoist-non-react-statics": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
+ "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
+ "requires": {
+ "@types/react": "*",
+ "hoist-non-react-statics": "^3.3.0"
+ }
+ },
"@types/html-minifier-terser": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
@@ -23299,6 +24182,11 @@
"integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==",
"dev": true
},
+ "@types/object-assign": {
+ "version": "4.0.30",
+ "resolved": "https://registry.npmjs.org/@types/object-assign/-/object-assign-4.0.30.tgz",
+ "integrity": "sha512-HhE8gFfLj321pa6OE59QmOdL5NgIOhkdYn7MWnZTOcHOms8XFzNgr9+A0/GbN0XEX9wTM58yg4YXKhGr69QIUw=="
+ },
"@types/parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
@@ -23347,6 +24235,14 @@
"@types/react": "*"
}
},
+ "@types/react-transition-group": {
+ "version": "4.4.6",
+ "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.6.tgz",
+ "integrity": "sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==",
+ "requires": {
+ "@types/react": "*"
+ }
+ },
"@types/resolve": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
@@ -23422,6 +24318,11 @@
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.3.tgz",
"integrity": "sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g=="
},
+ "@types/use-sync-external-store": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
+ "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
+ },
"@types/ws": {
"version": "8.5.5",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz",
@@ -24028,6 +24929,28 @@
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.2.tgz",
"integrity": "sha512-zIURGIS1E1Q4pcrMjp+nnEh+16G56eG/MUllJH8yEvw7asDo7Ac9uhC9KIH5jzpITueEZolfYglnCGIuSBz39g=="
},
+ "axios": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz",
+ "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==",
+ "requires": {
+ "follow-redirects": "^1.15.0",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ },
+ "dependencies": {
+ "form-data": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "requires": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ }
+ }
+ }
+ },
"axobject-query": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz",
@@ -24882,6 +25805,11 @@
"which": "^2.0.1"
}
},
+ "crypto-js": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz",
+ "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw=="
+ },
"crypto-random-string": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz",
@@ -25396,6 +26324,15 @@
"utila": "~0.4"
}
},
+ "dom-helpers": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
+ "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
+ "requires": {
+ "@babel/runtime": "^7.8.7",
+ "csstype": "^3.0.2"
+ }
+ },
"dom-serializer": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz",
@@ -25653,6 +26590,11 @@
"is-symbol": "^1.0.2"
}
},
+ "es6-object-assign": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz",
+ "integrity": "sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw=="
+ },
"escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@@ -26457,6 +27399,11 @@
"pkg-dir": "^4.1.0"
}
},
+ "find-root": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
+ "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="
+ },
"find-up": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
@@ -26731,6 +27678,11 @@
"through2": "^4.0.0"
}
},
+ "gitbook-plugin-github": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/gitbook-plugin-github/-/gitbook-plugin-github-2.0.0.tgz",
+ "integrity": "sha512-TDGQgdC5vFGEqn533SDf/8AV+VagxhGIUyCZAq0EqwkyS1F73lCl7w9l8UWDd2me7Sq9L3zcr7RaJ6FjSFAo7w=="
+ },
"glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
@@ -26916,6 +27868,34 @@
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
},
+ "history": {
+ "version": "4.10.1",
+ "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
+ "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==",
+ "requires": {
+ "@babel/runtime": "^7.1.2",
+ "loose-envify": "^1.2.0",
+ "resolve-pathname": "^3.0.0",
+ "tiny-invariant": "^1.0.2",
+ "tiny-warning": "^1.0.0",
+ "value-equal": "^1.0.1"
+ }
+ },
+ "hoist-non-react-statics": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+ "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+ "requires": {
+ "react-is": "^16.7.0"
+ },
+ "dependencies": {
+ "react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+ }
+ }
+ },
"hoopy": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz",
@@ -27222,6 +28202,14 @@
"side-channel": "^1.0.4"
}
},
+ "invariant": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+ "requires": {
+ "loose-envify": "^1.0.0"
+ }
+ },
"ipaddr.js": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz",
@@ -27276,6 +28264,11 @@
"has-tostringtag": "^1.0.0"
}
},
+ "is-buffer": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz",
+ "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ=="
+ },
"is-callable": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
@@ -29573,6 +30566,11 @@
"fs-monkey": "^1.0.4"
}
},
+ "memoize-one": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
+ "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="
+ },
"meow": {
"version": "8.1.2",
"resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz",
@@ -30968,6 +31966,11 @@
"asap": "~2.0.6"
}
},
+ "promise-polyfill": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-6.1.0.tgz",
+ "integrity": "sha512-g0LWaH0gFsxovsU7R5LrrhHhWAWiHRnh1GPrhXnPgYsDkIqjRYUYSZEsej/wtleDrz5xVSIDbeKfidztp2XHFQ=="
+ },
"prompts": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
@@ -31010,6 +32013,11 @@
}
}
},
+ "proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+ },
"psl": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
@@ -31217,16 +32225,207 @@
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz",
"integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg=="
},
+ "react-icons": {
+ "version": "4.10.1",
+ "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.10.1.tgz",
+ "integrity": "sha512-/ngzDP/77tlCfqthiiGNZeYFACw85fUjZtLbedmJ5DTlNDIwETxhwBzdOJ21zj4iJdvc0J3y7yOsX3PpxAJzrw==",
+ "requires": {}
+ },
"react-is": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
},
+ "react-lifecycles-compat": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
+ "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
+ },
+ "react-redux": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.1.tgz",
+ "integrity": "sha512-5W0QaKtEhj+3bC0Nj0NkqkhIv8gLADH/2kYFMTHxCVqQILiWzLv6MaLuV5wJU3BQEdHKzTfcvPN0WMS6SC1oyA==",
+ "requires": {
+ "@babel/runtime": "^7.12.1",
+ "@types/hoist-non-react-statics": "^3.3.1",
+ "@types/use-sync-external-store": "^0.0.3",
+ "hoist-non-react-statics": "^3.3.2",
+ "react-is": "^18.0.0",
+ "use-sync-external-store": "^1.0.0"
+ },
+ "dependencies": {
+ "react-is": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
+ }
+ }
+ },
+ "react-redux-toolkit": {
+ "version": "0.0.1-alpha.2",
+ "resolved": "https://registry.npmjs.org/react-redux-toolkit/-/react-redux-toolkit-0.0.1-alpha.2.tgz",
+ "integrity": "sha512-lxs8RB48nFSEm5ek7wGTIhUEjIeGTVH6F8tG16GdqcMBfJ8Puq4qu5sOpVIwtcGpBY5mvRsozzQ6WfCklrAb5w==",
+ "requires": {
+ "axios": "^0.18.0",
+ "lodash": "^4.17.10",
+ "prop-types": "^15.6.1",
+ "react": "^16.0.0",
+ "react-redux": "^5.0.0",
+ "react-router": "^4.3.1",
+ "react-router-dom": "^4.3.1",
+ "redux": "^4.0.0",
+ "redux-observable": "^0.18.0",
+ "rxjs": "^5.5.6",
+ "shortid": "^2.2.8",
+ "universal-cookie": "^3.0.7"
+ },
+ "dependencies": {
+ "axios": {
+ "version": "0.18.1",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.1.tgz",
+ "integrity": "sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g==",
+ "requires": {
+ "follow-redirects": "1.5.10",
+ "is-buffer": "^2.0.2"
+ }
+ },
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "follow-redirects": {
+ "version": "1.5.10",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
+ "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
+ "requires": {
+ "debug": "=3.1.0"
+ }
+ },
+ "isarray": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ },
+ "path-to-regexp": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz",
+ "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==",
+ "requires": {
+ "isarray": "0.0.1"
+ }
+ },
+ "react": {
+ "version": "16.14.0",
+ "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz",
+ "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==",
+ "requires": {
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.1",
+ "prop-types": "^15.6.2"
+ }
+ },
+ "react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+ },
+ "react-redux": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.1.2.tgz",
+ "integrity": "sha512-Ns1G0XXc8hDyH/OcBHOxNgQx9ayH3SPxBnFCOidGKSle8pKihysQw2rG/PmciUQRoclhVBO8HMhiRmGXnDja9Q==",
+ "requires": {
+ "@babel/runtime": "^7.1.2",
+ "hoist-non-react-statics": "^3.3.0",
+ "invariant": "^2.2.4",
+ "loose-envify": "^1.1.0",
+ "prop-types": "^15.6.1",
+ "react-is": "^16.6.0",
+ "react-lifecycles-compat": "^3.0.0"
+ }
+ },
+ "react-router": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-4.3.1.tgz",
+ "integrity": "sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg==",
+ "requires": {
+ "history": "^4.7.2",
+ "hoist-non-react-statics": "^2.5.0",
+ "invariant": "^2.2.4",
+ "loose-envify": "^1.3.1",
+ "path-to-regexp": "^1.7.0",
+ "prop-types": "^15.6.1",
+ "warning": "^4.0.1"
+ },
+ "dependencies": {
+ "hoist-non-react-statics": {
+ "version": "2.5.5",
+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
+ "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw=="
+ }
+ }
+ },
+ "react-router-dom": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-4.3.1.tgz",
+ "integrity": "sha512-c/MlywfxDdCp7EnB7YfPMOfMD3tOtIjrQlj/CKfNMBxdmpJP8xcz5P/UAFn3JbnQCNUxsHyVVqllF9LhgVyFCA==",
+ "requires": {
+ "history": "^4.7.2",
+ "invariant": "^2.2.4",
+ "loose-envify": "^1.3.1",
+ "prop-types": "^15.6.1",
+ "react-router": "^4.3.1",
+ "warning": "^4.0.1"
+ }
+ },
+ "redux-observable": {
+ "version": "0.18.0",
+ "resolved": "https://registry.npmjs.org/redux-observable/-/redux-observable-0.18.0.tgz",
+ "integrity": "sha512-tu02n6jr6/bq/vyI9E/AHxIyIl0YsWloqvWqSBG0KqN6aQBujMBP6hlDAlQLj8hP+XQpqL293MLX6V612c0jSg==",
+ "requires": {
+ "gitbook-plugin-github": "^2.0.0"
+ }
+ },
+ "rxjs": {
+ "version": "5.5.12",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.12.tgz",
+ "integrity": "sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw==",
+ "requires": {
+ "symbol-observable": "1.0.1"
+ }
+ }
+ }
+ },
"react-refresh": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
"integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A=="
},
+ "react-router": {
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.14.0.tgz",
+ "integrity": "sha512-OD+vkrcGbvlwkspUFDgMzsu1RXwdjNh83YgG/28lBnDzgslhCgxIqoExLlxsfTpIygp7fc+Hd3esloNwzkm2xA==",
+ "requires": {
+ "@remix-run/router": "1.7.0"
+ }
+ },
+ "react-router-dom": {
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.14.0.tgz",
+ "integrity": "sha512-YEwlApKwzMMMbGbhh+Q7MsloTldcwMgHxUY/1g0uA62+B1hZo2jsybCWIDCL8zvIDB1FA0pBKY9chHbZHt+2dQ==",
+ "requires": {
+ "@remix-run/router": "1.7.0",
+ "react-router": "6.14.0"
+ }
+ },
"react-scripts": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
@@ -31282,6 +32481,33 @@
"workbox-webpack-plugin": "^6.4.1"
}
},
+ "react-select": {
+ "version": "5.7.3",
+ "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.7.3.tgz",
+ "integrity": "sha512-z8i3NCuFFWL3w27xq92rBkVI2onT0jzIIPe480HlBjXJ3b5o6Q+Clp4ydyeKrj9DZZ3lrjawwLC5NGl0FSvUDg==",
+ "requires": {
+ "@babel/runtime": "^7.12.0",
+ "@emotion/cache": "^11.4.0",
+ "@emotion/react": "^11.8.1",
+ "@floating-ui/dom": "^1.0.1",
+ "@types/react-transition-group": "^4.4.0",
+ "memoize-one": "^6.0.0",
+ "prop-types": "^15.6.0",
+ "react-transition-group": "^4.3.0",
+ "use-isomorphic-layout-effect": "^1.1.2"
+ }
+ },
+ "react-transition-group": {
+ "version": "4.4.5",
+ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
+ "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
+ "requires": {
+ "@babel/runtime": "^7.5.5",
+ "dom-helpers": "^5.0.1",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.6.2"
+ }
+ },
"read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -31425,6 +32651,20 @@
"strip-indent": "^3.0.0"
}
},
+ "redux": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
+ "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
+ "requires": {
+ "@babel/runtime": "^7.9.2"
+ }
+ },
+ "redux-thunk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz",
+ "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==",
+ "requires": {}
+ },
"regenerate": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@@ -31526,6 +32766,11 @@
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
},
+ "reselect": {
+ "version": "4.1.8",
+ "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz",
+ "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="
+ },
"resolve": {
"version": "1.22.2",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
@@ -31558,6 +32803,11 @@
"global-dirs": "^0.1.1"
}
},
+ "resolve-pathname": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz",
+ "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng=="
+ },
"resolve-url-loader": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-4.0.0.tgz",
@@ -31957,6 +33207,21 @@
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz",
"integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA=="
},
+ "shortid": {
+ "version": "2.2.16",
+ "resolved": "https://registry.npmjs.org/shortid/-/shortid-2.2.16.tgz",
+ "integrity": "sha512-Ugt+GIZqvGXCIItnsL+lvFJOiN7RYqlGy7QE41O3YC1xbNSeDGIRO7xg2JJXIAj1cAGnOeC1r7/T9pgrtQbv4g==",
+ "requires": {
+ "nanoid": "^2.1.0"
+ },
+ "dependencies": {
+ "nanoid": {
+ "version": "2.1.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-2.1.11.tgz",
+ "integrity": "sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA=="
+ }
+ }
+ },
"side-channel": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
@@ -32494,6 +33759,25 @@
}
}
},
+ "sweetalert": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/sweetalert/-/sweetalert-2.1.2.tgz",
+ "integrity": "sha512-iWx7X4anRBNDa/a+AdTmvAzQtkN1+s4j/JJRWlHpYE8Qimkohs8/XnFcWeYHH2lMA8LRCa5tj2d244If3S/hzA==",
+ "requires": {
+ "es6-object-assign": "^1.1.0",
+ "promise-polyfill": "^6.0.2"
+ }
+ },
+ "sweetalert2": {
+ "version": "11.7.12",
+ "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.7.12.tgz",
+ "integrity": "sha512-TQJy8mQymJLzqWPQOMQErd81Zd/rSYr0UL4pEc7bqEihtjS+zt7LWJXLhfPp93e+Hf3Z2FHMB6QGNskAMCsdTg=="
+ },
+ "symbol-observable": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz",
+ "integrity": "sha512-Kb3PrPYz4HanVF1LVGuAdW6LoVgIwjUYJGzFe7NDrBLCN4lsV/5J0MFurV+ygS4bRVwrCEt2c7MQ1R2a72oJDw=="
+ },
"symbol-tree": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
@@ -32657,6 +33941,16 @@
"resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",
"integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA=="
},
+ "tiny-invariant": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz",
+ "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw=="
+ },
+ "tiny-warning": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
+ "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
+ },
"tmpl": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
@@ -32896,6 +34190,24 @@
"crypto-random-string": "^2.0.0"
}
},
+ "universal-cookie": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-3.1.0.tgz",
+ "integrity": "sha512-sP6WuFgqIUro7ikgI2ndrsw9Ro+YvVBe5O9cQfWnjTicpLaSMUEUUDjQF8m8utzWF2ONl7tRkcZd7v4n6NnzjQ==",
+ "requires": {
+ "@types/cookie": "^0.3.1",
+ "@types/object-assign": "^4.0.30",
+ "cookie": "^0.3.1",
+ "object-assign": "^4.1.0"
+ },
+ "dependencies": {
+ "cookie": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
+ "integrity": "sha512-+IJOX0OqlHCszo2mBUq+SrEbCj6w7Kpffqx60zYbPTFaO4+yYgRjHwcZNpWvaTylDHaV7PPmBHzSecZiMhtPgw=="
+ }
+ }
+ },
"universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
@@ -32942,6 +34254,18 @@
"requires-port": "^1.0.0"
}
},
+ "use-isomorphic-layout-effect": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz",
+ "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==",
+ "requires": {}
+ },
+ "use-sync-external-store": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
+ "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
+ "requires": {}
+ },
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -32999,6 +34323,11 @@
"spdx-expression-parse": "^3.0.0"
}
},
+ "value-equal": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
+ "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw=="
+ },
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@@ -33028,6 +34357,14 @@
"makeerror": "1.0.12"
}
},
+ "warning": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
+ "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
+ "requires": {
+ "loose-envify": "^1.0.0"
+ }
+ },
"watchpack": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
diff --git a/package.json b/package.json
index 8dc1d66..ea11b99 100644
--- a/package.json
+++ b/package.json
@@ -4,14 +4,26 @@
"private": true,
"dependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
+ "@reduxjs/toolkit": "^1.9.5",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
+ "axios": "^1.4.0",
+ "crypto-js": "^4.1.1",
"lint-staged": "^13.2.2",
+ "lodash": "^4.17.21",
"react": "^18.2.0",
"react-dom": "^18.2.0",
+ "react-icons": "^4.10.1",
+ "react-redux": "^8.1.1",
+ "react-redux-toolkit": "^0.0.1-alpha.2",
+ "react-router": "^6.14.0",
+ "react-router-dom": "^6.14.0",
"react-scripts": "5.0.1",
+ "react-select": "^5.7.3",
"styled-components": "^6.0.0-rc.5",
+ "sweetalert": "^2.1.2",
+ "sweetalert2": "^11.7.12",
"web-vitals": "^2.1.4"
},
"scripts": {
@@ -23,6 +35,7 @@
"format": "prettier --cache --write .",
"lint": "eslint --cache ."
},
+ "proxy": "http://localhost:8080",
"eslintConfig": {
"extends": [
"react-app",
diff --git a/public/images/splash.png b/public/images/splash.png
new file mode 100644
index 0000000..accb73a
Binary files /dev/null and b/public/images/splash.png differ
diff --git a/public/images/walkthrough.gif b/public/images/walkthrough.gif
new file mode 100644
index 0000000..65bfd0a
Binary files /dev/null and b/public/images/walkthrough.gif differ
diff --git a/public/images/walkthrough2.gif b/public/images/walkthrough2.gif
new file mode 100644
index 0000000..ca41a91
Binary files /dev/null and b/public/images/walkthrough2.gif differ
diff --git a/src/App.js b/src/App.js
index 077bec7..2e37022 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,7 +1,51 @@
+import { Routes, Route } from 'react-router-dom';
import './styles/App.css';
+import 'tailwindcss/tailwind.css';
+
+import Chat from './pages/ChatPage';
+import ChatList from './pages/ChatListPage';
+import Home from './pages/HomePage';
+import Register from './pages/RegisterPage';
+import SignIn from './pages/SignInPage';
+import SplashScreen from './pages/SplashScreenPage';
+import Walkthrough from './pages/WalkthroughPage';
+import RegisterComplete from './pages/RegisterCompletePage';
+import WritingPage from './pages/WritingPage';
+import PostsPage from './pages/PostsPage';
+import PermissionPage from './pages/PermissionPage';
+import RegisterLocationPage from './pages/RegisterLocationPage';
+import CategoryPage from './pages/CategoryPage';
+import SearchPage from './pages/SearchPage';
+import MyPage from './pages/MyPage';
+import EditProfilePage from './pages/EditProfilePage';
+import FavoriteCategories from './components/home/FavoriteCategories';
function App() {
- return
;
+ return (
+
+
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+
+ );
}
export default App;
diff --git a/src/api/testApi.js b/src/api/testApi.js
new file mode 100644
index 0000000..31ab7d9
--- /dev/null
+++ b/src/api/testApi.js
@@ -0,0 +1,5 @@
+const testApi = ({ skip, limit }) => {
+ return fetch(`https://dummyjson.com/posts?skip=${skip}&limit=${limit}`).then((res) => res.json());
+};
+
+export default testApi;
diff --git a/src/components/common/Carousel.jsx b/src/components/common/Carousel.jsx
new file mode 100644
index 0000000..e080756
--- /dev/null
+++ b/src/components/common/Carousel.jsx
@@ -0,0 +1,16 @@
+import React from 'react';
+import styled from 'styled-components';
+
+const Circle = styled.div`
+ width: 9px;
+ height: 9px;
+ border-radius: 50%;
+ transition: all 0.3s;
+ background: ${(props) => props.background || '#39B54A'};
+`;
+
+function Carousel({ carouselColor }) {
+ return ;
+}
+
+export default Carousel;
diff --git a/src/components/common/Category.jsx b/src/components/common/Category.jsx
new file mode 100644
index 0000000..a4ade47
--- /dev/null
+++ b/src/components/common/Category.jsx
@@ -0,0 +1,30 @@
+import React from 'react';
+import { useDispatch } from 'react-redux';
+import { useNavigate } from 'react-router-dom';
+
+import { selectedCategorySlice } from '../../redux/slices/selectedCategorySlice';
+
+function Category({ src, firstName, lastName = '' }) {
+ const dispatch = useDispatch();
+ const navigate = useNavigate();
+
+ const onCategorySelect = (name) => {
+ dispatch(selectedCategorySlice.actions.setCategory(name));
+ navigate('/search');
+ };
+
+ return (
+ onCategorySelect(lastName === '' ? firstName : `${firstName}/${lastName}`)}
+ >
+
+
{firstName}
+
{lastName}
+
+ );
+}
+
+export default Category;
diff --git a/src/components/common/CategoryList.jsx b/src/components/common/CategoryList.jsx
new file mode 100644
index 0000000..3c1187d
--- /dev/null
+++ b/src/components/common/CategoryList.jsx
@@ -0,0 +1,46 @@
+import React, { useState } from 'react';
+
+import Category from './Category';
+import { TEMPORARY_SRC } from '../../static/constants';
+import aquatic from '../../images/aquatic.png';
+import bread from '../../images/bread.png';
+import eco from '../../images/eco.png';
+import fruit from '../../images/fruit.png';
+import kimchi from '../../images/kimchi.png';
+import meat from '../../images/meat.png';
+import milk from '../../images/milk.png';
+import water from '../../images/water.png';
+import noodles from '../../images/noodles.png';
+import rice from '../../images/rice.png';
+import seasoning from '../../images/seasoning.png';
+import snack from '../../images/snack.png';
+import vegetable from '../../images/vegetable.png';
+
+// TODO: 하드코딩된 값 constants로 추후 변경 예정
+function CategoryList() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default CategoryList;
diff --git a/src/components/common/FavoriteButton.jsx b/src/components/common/FavoriteButton.jsx
new file mode 100644
index 0000000..32a5726
--- /dev/null
+++ b/src/components/common/FavoriteButton.jsx
@@ -0,0 +1,38 @@
+import React, { useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+
+const FavoriteButton = () => {
+ const [selectedCategory, setSelectedCategory] = useState(null);
+
+ const navigate = useNavigate();
+ const handleAddFavorite = () => {
+ navigate('/favorite-categories');
+ if (selectedCategory) {
+ console.log(`추가 - 선택된 카테고리: ${selectedCategory}`);
+ }
+ };
+
+ const handleCategorySelection = (category) => {
+ setSelectedCategory(category);
+ };
+
+ return (
+
+ );
+};
+
+export default FavoriteButton;
diff --git a/src/components/common/FriendProfile.jsx b/src/components/common/FriendProfile.jsx
new file mode 100644
index 0000000..620fd28
--- /dev/null
+++ b/src/components/common/FriendProfile.jsx
@@ -0,0 +1,20 @@
+import React from 'react';
+import styled from 'styled-components';
+
+const Div = styled.div`
+ background-color: ${(props) => props.color || '#39B54A'};
+`;
+
+function FriendProfile({ name = '', svg, color }) {
+ return (
+
+ );
+}
+
+export default FriendProfile;
diff --git a/src/components/common/FriendsProfile.jsx b/src/components/common/FriendsProfile.jsx
new file mode 100644
index 0000000..e3a7e1d
--- /dev/null
+++ b/src/components/common/FriendsProfile.jsx
@@ -0,0 +1,62 @@
+import React from 'react';
+
+import { useSelector } from 'react-redux';
+import FriendProfile from './FriendProfile';
+
+function FriendsProfile() {
+ let friendsList = useSelector((state) => state.friends.friendsList);
+ let recruteList = JSON.parse(localStorage.getItem('recruteList'));
+ let isJoin = JSON.parse(localStorage.getItem('isJoin'));
+ console.log(friendsList, recruteList);
+
+ return (
+
+
+
+
+ }
+ />
+
+ {!isJoin
+ ? friendsList.map((el, index) => (
+
+ ))
+ : recruteList.map((el, index) => (
+
+ ))}
+
+ );
+}
+
+export default FriendsProfile;
diff --git a/src/components/common/IdPasswordForm.jsx b/src/components/common/IdPasswordForm.jsx
new file mode 100644
index 0000000..343ceef
--- /dev/null
+++ b/src/components/common/IdPasswordForm.jsx
@@ -0,0 +1,46 @@
+import React from 'react';
+import Input from './Input';
+
+const IdPasswordForm = ({ label, type, value, onChange, color, errors, readOnly }) => {
+ return (
+
+
+
+
+ {type === 'password' && (
+
+ )}
+
+ {errors.isError && (
+
+ {errors.message}
+
+ )}
+
+ );
+};
+
+export default IdPasswordForm;
diff --git a/src/components/common/ImageAndMessage.jsx b/src/components/common/ImageAndMessage.jsx
new file mode 100644
index 0000000..01a9425
--- /dev/null
+++ b/src/components/common/ImageAndMessage.jsx
@@ -0,0 +1,30 @@
+import React from 'react';
+import styled from 'styled-components';
+
+const Wrapper = styled.div`
+ opacity: ${(props) => props.opacity};
+ margin-top: ${(props) => props.marginTop};
+`;
+
+const SubTitle = styled.h1`
+ color: ${(props) => props.color || '#00c92c'};
+`;
+
+function ImageAndMessage({ mainMessage, subMessage, color, opacity, src, marginTop }) {
+ return (
+
+
+
+
+ {mainMessage}
+
+
{subMessage}
+
+
+ );
+}
+
+export default ImageAndMessage;
diff --git a/src/components/common/Input.jsx b/src/components/common/Input.jsx
new file mode 100644
index 0000000..133211a
--- /dev/null
+++ b/src/components/common/Input.jsx
@@ -0,0 +1,33 @@
+import React from 'react';
+import styled from 'styled-components';
+
+const StyledInput = styled.input`
+ width: ${(props) => props.width || '360px'};
+ margin-bottom: ${(props) => props.mb || 0};
+ border-color: ${(props) => props.color};
+ background: ${(props) => (props.readOnly ? '#ccc' : '#fff')};
+
+ &:focus {
+ outline-color: ${(props) => (props.readOnly ? 'initial' : '#39b54a')};
+ outline-style: ${(props) => (props.readOnly ? 'none' : 'initial')};
+ }
+`;
+
+function Input({ type, onChange, placeholder, name, value, width, mb, color, autoComplete, readOnly }) {
+ return (
+
+ );
+}
+export default Input;
diff --git a/src/components/common/LongButton.jsx b/src/components/common/LongButton.jsx
new file mode 100644
index 0000000..9f71cc0
--- /dev/null
+++ b/src/components/common/LongButton.jsx
@@ -0,0 +1,27 @@
+import React from 'react';
+import styled from 'styled-components';
+
+const ButtonWrapper = styled.div`
+ bottom: ${(props) => props.bottom || '56px'};
+`;
+
+const StyledButton = styled.button`
+ background: ${(props) => props.background || '#00c92c'};
+`;
+
+function LongButton({ type, contents, onClick, background, bottom, customStyle }) {
+ return (
+
+
+ {contents}
+
+
+ );
+}
+
+export default LongButton;
diff --git a/src/components/common/MyProfile.jsx b/src/components/common/MyProfile.jsx
new file mode 100644
index 0000000..6922df2
--- /dev/null
+++ b/src/components/common/MyProfile.jsx
@@ -0,0 +1,92 @@
+import React, { useCallback, useEffect, useRef, useState } from 'react';
+import { useSelector, useDispatch } from 'react-redux';
+import { FcCheckmark } from 'react-icons/fc';
+import { debounce } from 'lodash';
+import {
+ setErrors,
+ setNewNickname,
+ updateNicknameFailure,
+ updateNicknameStart,
+ updateNicknameSuccess,
+} from '../../redux/slices/userInfoChangeSlice';
+
+function MyProfile({ cameraSvg = '', writingSvg }) {
+ const [isEditing, setIsEditing] = useState(false);
+ const { newNickname, errors } = useSelector((state) => state.userInfoChange);
+
+ /** TODO: 서버에서 가져온 데이터로 추후 변경 */
+ const nickname = localStorage.getItem('signup-nickname');
+ const inputRef = useRef();
+ const dispatch = useDispatch();
+
+ /** 닉네임 초기값 렌더링 */
+ useEffect(() => {
+ if (inputRef.current) {
+ inputRef.current.value = nickname;
+ }
+ }, [nickname]);
+
+ /** 수정모드 토글 */
+ const isUpdateMode = () => {
+ setIsEditing((prevState) => !prevState);
+ if (inputRef.current) {
+ inputRef.current.readOnly = !inputRef.current.readOnly;
+ inputRef.current.focus();
+ }
+ };
+
+ /** 닉네임 변경 시도 */
+ const updateNickname = useCallback(async () => {
+ isUpdateMode();
+
+ try {
+ dispatch(updateNicknameStart());
+ await dispatch(updateNicknameSuccess({ newNickname }));
+ } catch (error) {
+ dispatch(updateNicknameFailure(error.message));
+ }
+ }, [dispatch, newNickname]);
+
+ /** 닉네임 유효성 검사 */
+ const handleNicknameChange = useCallback(
+ debounce((event) => {
+ event.preventDefault();
+ const { value } = event.target;
+
+ const validationErrors = { ...errors };
+ dispatch(setNewNickname(value));
+ validationErrors.newNickname = {
+ message: value.trim() === '' ? '닉네임을 입력해주세요.' : '',
+ isError: value.trim() === '',
+ };
+
+ dispatch(setErrors(validationErrors));
+ }, 300),
+ [dispatch],
+ );
+
+ return (
+
+ {/*
*/}
+
+
+
+
+
+
+
+
+ );
+}
+
+export default MyProfile;
diff --git a/src/components/common/SearchedOutput.jsx b/src/components/common/SearchedOutput.jsx
new file mode 100644
index 0000000..069dd0f
--- /dev/null
+++ b/src/components/common/SearchedOutput.jsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import { WON } from '../../static/constants';
+
+function SearchedOutput({ src, name, location, price }) {
+ return (
+
+
+
+
+ );
+}
+
+export default SearchedOutput;
diff --git a/src/components/common/SearchedOutputLists.jsx b/src/components/common/SearchedOutputLists.jsx
new file mode 100644
index 0000000..8e30b87
--- /dev/null
+++ b/src/components/common/SearchedOutputLists.jsx
@@ -0,0 +1,82 @@
+import React, { useEffect, useRef, useState } from 'react';
+
+import SearchedOutput from './SearchedOutput';
+// TODO: 임시 API로 추후 수정 예정
+import testApi from '../../api/testApi';
+import useThrottle from '../../hooks/useThrottle';
+import useCustomQuery from '../../hooks/useCustomQuery';
+
+import { ERROR_ALERT_MESSAGE, TIMEOUT, TEMPORARY_SRC } from '../../static/constants';
+
+function SearchedOutputList({ searchedOutputList }) {
+ // TODO: skip, limit 값은 변경될 수도
+ const skip = 15;
+ const limit = 15;
+
+ const { isLoading, data } = useCustomQuery(() => testApi({ skip, limit }));
+ const [newData, setNewData] = useState(searchedOutputList);
+ const metaRef = useRef({ fetching: false, skip: 0 });
+
+ useEffect(() => {
+ if (!isLoading && data) setNewData(data);
+ }, [data]);
+
+ const onScrollDropdown = (e) => {
+ if (!newData) return;
+ if (metaRef.current.skip + limit >= newData.total) return;
+
+ const { clientHeight, scrollHeight, scrollTop } = e.target;
+ if (scrollHeight * 0.8 <= clientHeight + scrollTop) getMoreData();
+ };
+
+ const getMoreData = async () => {
+ try {
+ if (metaRef.current.fetching) return;
+
+ const skipNumber = metaRef.current.skip + limit;
+ metaRef.current = { ...metaRef.current, fetching: true, skip: skipNumber };
+
+ const data = await testApi({ skip: skipNumber, limit: limit });
+ metaRef.current = { ...metaRef.current, fetching: false };
+ // TODO: data에 담겨지는 키와 값들은 추후 API 확정되면 확인 후 코드 수정 예정
+ setNewData((beforeData) => ({
+ ...beforeData,
+ ...data,
+ posts: [...beforeData?.posts, ...data?.posts],
+ skip: skipNumber,
+ }));
+ } catch (error) {
+ console.error(ERROR_ALERT_MESSAGE);
+ alert(ERROR_ALERT_MESSAGE);
+ }
+ };
+
+ const throttleScroll = useThrottle(onScrollDropdown, TIMEOUT);
+
+ return (
+
+ {!isLoading ? (
+ <>
+ {newData?.posts?.map((data, id) =>
+ searchedOutputList.map((el) => (
+
+ )),
+ )}
+ >
+ ) : (
+ ''
+ )}
+
+ );
+}
+
+export default SearchedOutputList;
diff --git a/src/components/common/SelectBoxs.jsx b/src/components/common/SelectBoxs.jsx
new file mode 100644
index 0000000..48ed6f6
--- /dev/null
+++ b/src/components/common/SelectBoxs.jsx
@@ -0,0 +1,82 @@
+import React from 'react';
+import styled from 'styled-components';
+import { SlArrowDown } from 'react-icons/sl';
+
+const SelectBox = styled.select`
+ position: relative;
+ width: 113px;
+ height: 48px;
+ border: 1px solid #d9d9d9;
+ border-radius: 10px;
+ font-size: 14px;
+ padding-left: 20px;
+ -webkit-appearance: none; /* Safari에서 기본 화살표 숨김 */
+ -moz-appearance: none; /* Firefox에서 기본 화살표 숨김 */
+ appearance: none; /* 기본 화살표 숨김 */
+`;
+const IconWrapper = styled.div`
+ position: absolute;
+ top: 50%;
+ right: 10px;
+ transform: translateY(-50%);
+ pointer-events: none;
+`;
+
+export const MonthSelectBox = () => {
+ const months = Array.from({ length: 12 }, (_, index) => {
+ const monthIndex = index + 1;
+ const monthName = new Date(0, monthIndex).toLocaleString('default', { month: 'long' });
+ return { id: monthName.toUpperCase(), value: `${monthIndex}월` };
+ });
+
+ return (
+
+
+
+ {months.map((month) => (
+
+ ))}
+
+
+
+
+
+ );
+};
+
+export const DaySelectBox = () => {
+ const days = Array.from({ length: 31 }, (_, index) => ({ id: `${index + 1}day`, value: `${index + 1}일` }));
+
+ return (
+
+
+
+ {days.map((day) => (
+
+ ))}
+
+
+
+
+
+ );
+};
+
+export const GenderSelectBox = () => {
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/components/common/ShowCase.jsx b/src/components/common/ShowCase.jsx
new file mode 100644
index 0000000..658dcde
--- /dev/null
+++ b/src/components/common/ShowCase.jsx
@@ -0,0 +1,27 @@
+import React from 'react';
+import styled from 'styled-components';
+
+const Show = styled.div`
+ /* 스크롤바 숨기기 */
+ overflow-x: auto;
+ overflow-y: auto;
+ scrollbar-width: thin;
+ scrollbar-color: transparent transparent;
+
+ &::-webkit-scrollbar {
+ width: 0.4rem;
+ }
+
+ &::-webkit-scrollbar-track {
+ background-color: transparent;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background-color: transparent;
+ }
+`;
+
+function ShowCase({ contents }) {
+ return {contents};
+}
+export default ShowCase;
diff --git a/src/components/common/navBar/BackButton.jsx b/src/components/common/navBar/BackButton.jsx
new file mode 100644
index 0000000..1759054
--- /dev/null
+++ b/src/components/common/navBar/BackButton.jsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import { useNavigate } from 'react-router-dom';
+
+function BackButton() {
+ const navigate = useNavigate();
+ const onClick = () => {
+ navigate(-1);
+ };
+
+ return (
+
+ );
+}
+
+export default BackButton;
diff --git a/src/components/common/navBar/ChatBar.jsx b/src/components/common/navBar/ChatBar.jsx
new file mode 100644
index 0000000..41b6158
--- /dev/null
+++ b/src/components/common/navBar/ChatBar.jsx
@@ -0,0 +1,49 @@
+import React, { useState } from 'react';
+
+function ChatBar() {
+ const [isInputFocused, setIsInputFocused] = useState(false);
+ const [inputValue, setInputValue] = useState('');
+
+ const handleInputChange = (e) => {
+ const inputValue = e.target.value;
+ setInputValue(inputValue);
+ };
+
+ const handleInputFocus = () => {
+ setIsInputFocused(true);
+ };
+
+ const handleInputBlur = () => {
+ setIsInputFocused(false);
+ };
+
+ return (
+
+ );
+}
+
+export default ChatBar;
diff --git a/src/components/common/navBar/HomeBar.jsx b/src/components/common/navBar/HomeBar.jsx
new file mode 100644
index 0000000..df76a7a
--- /dev/null
+++ b/src/components/common/navBar/HomeBar.jsx
@@ -0,0 +1,73 @@
+import React from 'react';
+import { useNavigate } from 'react-router-dom';
+
+const HomeBar = () => {
+ const navigate = useNavigate();
+
+ return (
+
+ {/* 로고 */}
+
+
+
+
navigate('/search')}>
+ {/* 검색아이콘 */}
+
+
+
+ );
+};
+
+export default HomeBar;
diff --git a/src/components/common/navBar/SearchBar.jsx b/src/components/common/navBar/SearchBar.jsx
new file mode 100644
index 0000000..2b3661c
--- /dev/null
+++ b/src/components/common/navBar/SearchBar.jsx
@@ -0,0 +1,20 @@
+import React from 'react';
+import { useNavigate } from 'react-router-dom';
+
+import BackButton from './BackButton';
+
+function SearchBar({ placeholder }) {
+ const navigate = useNavigate();
+
+ return (
+
+
+
+
+ );
+}
+
+export default SearchBar;
diff --git a/src/components/common/navBar/TabBar.jsx b/src/components/common/navBar/TabBar.jsx
new file mode 100644
index 0000000..f23cdb8
--- /dev/null
+++ b/src/components/common/navBar/TabBar.jsx
@@ -0,0 +1,154 @@
+import React, { useState, useEffect } from 'react';
+import { useNavigate, useLocation } from 'react-router-dom';
+
+const TabBar = () => {
+ const [activeTab, setActiveTab] = useState('TabHome');
+ const navigate = useNavigate();
+ const location = useLocation();
+
+ const changeTab = (tabId) => {
+ let path = '/';
+
+ if (tabId === 'TabCategories') path = '/category';
+ else if (tabId === 'TabAddPost') path = '/writing';
+ else if (tabId === 'TabChat') path = '/chatlist';
+ else if (tabId === 'TabMyPage') path = '/myPage';
+
+ navigate(path);
+ };
+
+ useEffect(() => {
+ const currentPath = location.pathname;
+ let tabId = 'TabHome';
+
+ if (currentPath === '/') tabId = 'TabHome';
+ else if (currentPath === '/category') tabId = 'TabCategories';
+ else if (currentPath === '/writing') tabId = 'TabAddPost';
+ else if (currentPath === '/chatlist') tabId = 'TabChat';
+ else if (currentPath === '/myPage') tabId = 'TabMyPage';
+
+ setActiveTab(tabId);
+ }, [location.pathname]);
+
+ const Tabs = [
+ {
+ id: 'TabHome',
+ title: '홈',
+ svg: (
+
+ ),
+ },
+ {
+ id: 'TabCategories',
+ title: '카테고리',
+ svg: (
+
+ ),
+ },
+ {
+ id: 'TabAddPost',
+ title: '글쓰기',
+ svg: (
+
+ ),
+ },
+ {
+ id: 'TabChat',
+ title: '채팅',
+ svg: (
+
+ ),
+ },
+ {
+ id: 'TabMyPage',
+ title: '마이페이지',
+ svg: (
+
+ ),
+ },
+ ];
+
+ return (
+
+
+ {Tabs.map((tab) => (
+ - changeTab(tab.id)}
+ >
+
{tab.svg}
+ {tab.title}
+
+ ))}
+
+
+ );
+};
+
+export default TabBar;
diff --git a/src/components/common/navBar/TextAndBackBar.jsx b/src/components/common/navBar/TextAndBackBar.jsx
new file mode 100644
index 0000000..2ccdcd2
--- /dev/null
+++ b/src/components/common/navBar/TextAndBackBar.jsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import { useNavigate } from 'react-router-dom';
+import BackButton from './BackButton';
+
+function TextAndBackBar({ title }) {
+ const navigate = useNavigate();
+ const handleMoveBack = () => navigate(-1);
+
+ return (
+ <>
+
+ >
+ );
+}
+
+export default TextAndBackBar;
diff --git a/src/components/common/navBar/TextBar.jsx b/src/components/common/navBar/TextBar.jsx
new file mode 100644
index 0000000..4d9b51a
--- /dev/null
+++ b/src/components/common/navBar/TextBar.jsx
@@ -0,0 +1,11 @@
+function TextBar({ title }) {
+ return (
+
+
+ {title}
+
+
+ );
+}
+
+export default TextBar;
diff --git a/src/components/home/Favorite.jsx b/src/components/home/Favorite.jsx
new file mode 100644
index 0000000..b5575ff
--- /dev/null
+++ b/src/components/home/Favorite.jsx
@@ -0,0 +1,20 @@
+import React from 'react';
+import FavoriteButton from '../common/FavoriteButton';
+import Title from './Title';
+
+const Favorite = () => {
+ return (
+
+ );
+};
+
+export default Favorite;
diff --git a/src/components/home/FavoriteCategories.jsx b/src/components/home/FavoriteCategories.jsx
new file mode 100644
index 0000000..9506166
--- /dev/null
+++ b/src/components/home/FavoriteCategories.jsx
@@ -0,0 +1,14 @@
+import React from 'react';
+import CategoryList from '../common/CategoryList';
+import TextAndBackBar from '../common/navBar/TextAndBackBar';
+
+const FavoriteCategories = () => {
+ return (
+
+
+
+
+ );
+};
+
+export default FavoriteCategories;
diff --git a/src/components/home/PostList.jsx b/src/components/home/PostList.jsx
new file mode 100644
index 0000000..c276b00
--- /dev/null
+++ b/src/components/home/PostList.jsx
@@ -0,0 +1,68 @@
+import React from 'react';
+import Title from './Title';
+
+// 해당 페이지 프로젝트 기간 이후 개발예정
+const PostList = () => {
+ const postTitle = ['실시간 인기글', '방금 올라온 글', '나를 위한 추천'];
+ const posts = [
+ {
+ id: 1,
+ image: '',
+ title: '프로틴 바/ 다크 초코씨솔트&카라멜넛, 마이프로마이프로마이프로',
+ address: '서울시 서초구 서초대로',
+ price: '20,000원',
+ },
+ {
+ id: 2,
+ image: '',
+ title: '프로틴 바/ 다크 초코씨솔트&카라멜넛, 마이프로마이프로마이프로',
+ address: '서울시 서초구 서초대로',
+ price: '20,000원',
+ },
+ {
+ id: 3,
+ image: '',
+ title: '프로틴 바/ 다크 초코씨솔트&카라멜넛, 마이프로마이프로마이프로',
+ address: '서울시 서초구 서초대로',
+ price: '20,000원',
+ },
+ {
+ id: 4,
+ image: '',
+ title: '프로틴 바/ 다크 초코씨솔트&카라멜넛, 마이프로마이프로마이프로',
+ address: '서울시 서초구 서초대로',
+ price: '20,000원',
+ },
+ ];
+
+ return (
+
+ {postTitle.map((title) => (
+
+
+
+ {posts.map((post, index) => (
+ -
+
+
+
+
+ {post.title}
+
+ {post.address}
+ {post.price}
+
+ ))}
+
+
+ ))}
+
+ );
+};
+
+export default PostList;
diff --git a/src/components/home/Title.jsx b/src/components/home/Title.jsx
new file mode 100644
index 0000000..de56211
--- /dev/null
+++ b/src/components/home/Title.jsx
@@ -0,0 +1,7 @@
+import React from 'react';
+
+function Title({ title }) {
+ return {title}
;
+}
+
+export default Title;
diff --git a/src/components/myPage/MyPageCategory.jsx b/src/components/myPage/MyPageCategory.jsx
new file mode 100644
index 0000000..5cc16e8
--- /dev/null
+++ b/src/components/myPage/MyPageCategory.jsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import styled from 'styled-components';
+
+const Button = styled.button`
+ color: ${(props) => props.color || '#6B6B6B'};
+`;
+
+function MyPageCategory({ name, color, onClick }) {
+ return (
+
+ );
+}
+
+export default MyPageCategory;
diff --git a/src/components/permissionPage/OptionalPermission.jsx b/src/components/permissionPage/OptionalPermission.jsx
new file mode 100644
index 0000000..1ee1c0a
--- /dev/null
+++ b/src/components/permissionPage/OptionalPermission.jsx
@@ -0,0 +1,15 @@
+import React from 'react';
+
+function OptionalPermission({ svg, title, description }) {
+ return (
+
+
{svg}
+
+
{title}
+
{description}
+
+
+ );
+}
+
+export default OptionalPermission;
diff --git a/src/components/registerLocationPage/NearLoacation.jsx b/src/components/registerLocationPage/NearLoacation.jsx
new file mode 100644
index 0000000..e987784
--- /dev/null
+++ b/src/components/registerLocationPage/NearLoacation.jsx
@@ -0,0 +1,14 @@
+import React from 'react';
+
+function NearLoacation({ location, onClick }) {
+ return (
+
+ {location}
+
+ );
+}
+
+export default NearLoacation;
diff --git a/src/components/writingPage/Input.jsx b/src/components/writingPage/Input.jsx
new file mode 100644
index 0000000..ef5c19d
--- /dev/null
+++ b/src/components/writingPage/Input.jsx
@@ -0,0 +1,11 @@
+import React from 'react';
+
+function Input({ placeholder, onChange }) {
+ return (
+
+
+
+ );
+}
+
+export default Input;
diff --git a/src/hooks/useCustomQuery.js b/src/hooks/useCustomQuery.js
new file mode 100644
index 0000000..f5ccee7
--- /dev/null
+++ b/src/hooks/useCustomQuery.js
@@ -0,0 +1,29 @@
+import { useState, useRef } from 'react';
+import { ERROR_ALERT_MESSAGE } from '../static/constants';
+
+const useCustomQuery = (query) => {
+ const requestRef = useRef(false);
+ const [isLoading, setIsLoading] = useState(true);
+ const [isError, setIsError] = useState(false);
+ const [error, setError] = useState('');
+ const [data, setData] = useState();
+
+ if (requestRef.current) return { isLoading, data };
+ requestRef.current = true;
+
+ query()
+ .then((data) => {
+ setData(data);
+ })
+ .catch(() => {
+ setIsError(true);
+ setError(ERROR_ALERT_MESSAGE);
+ })
+ .finally(() => {
+ setIsLoading(false);
+ });
+
+ return { isLoading, isError, setData, data, error };
+};
+
+export default useCustomQuery;
diff --git a/src/hooks/useThrottle.js b/src/hooks/useThrottle.js
new file mode 100644
index 0000000..ffd3b9a
--- /dev/null
+++ b/src/hooks/useThrottle.js
@@ -0,0 +1,16 @@
+import { useRef } from 'react';
+
+const useThrottle = (callback, timeout) => {
+ const timer = useRef(null);
+
+ return (...args) => {
+ if (!timer.current) {
+ timer.current = setTimeout(() => {
+ callback(...args);
+ timer.current = null;
+ }, timeout);
+ }
+ };
+};
+
+export default useThrottle;
diff --git a/src/images/aquatic.png b/src/images/aquatic.png
new file mode 100755
index 0000000..d07c051
Binary files /dev/null and b/src/images/aquatic.png differ
diff --git a/src/images/bread.png b/src/images/bread.png
new file mode 100755
index 0000000..8b3fede
Binary files /dev/null and b/src/images/bread.png differ
diff --git a/src/images/eco.png b/src/images/eco.png
new file mode 100755
index 0000000..eb665d2
Binary files /dev/null and b/src/images/eco.png differ
diff --git a/src/images/fruit.png b/src/images/fruit.png
new file mode 100755
index 0000000..5f522a9
Binary files /dev/null and b/src/images/fruit.png differ
diff --git a/src/images/kimchi.png b/src/images/kimchi.png
new file mode 100755
index 0000000..e6115d8
Binary files /dev/null and b/src/images/kimchi.png differ
diff --git a/src/images/meat.png b/src/images/meat.png
new file mode 100755
index 0000000..49a4fb7
Binary files /dev/null and b/src/images/meat.png differ
diff --git a/src/images/milk.png b/src/images/milk.png
new file mode 100755
index 0000000..5226357
Binary files /dev/null and b/src/images/milk.png differ
diff --git a/src/images/noodles.png b/src/images/noodles.png
new file mode 100755
index 0000000..76e3dda
Binary files /dev/null and b/src/images/noodles.png differ
diff --git a/src/images/rice.png b/src/images/rice.png
new file mode 100755
index 0000000..fa01d16
Binary files /dev/null and b/src/images/rice.png differ
diff --git a/src/images/seasoning.png b/src/images/seasoning.png
new file mode 100755
index 0000000..63774e1
Binary files /dev/null and b/src/images/seasoning.png differ
diff --git a/src/images/snack.png b/src/images/snack.png
new file mode 100755
index 0000000..5de4308
Binary files /dev/null and b/src/images/snack.png differ
diff --git a/src/images/vegetable.png b/src/images/vegetable.png
new file mode 100755
index 0000000..abe0dec
Binary files /dev/null and b/src/images/vegetable.png differ
diff --git a/src/images/water.png b/src/images/water.png
new file mode 100755
index 0000000..0e770f2
Binary files /dev/null and b/src/images/water.png differ
diff --git a/src/index.js b/src/index.js
index 593edf1..a085fe7 100644
--- a/src/index.js
+++ b/src/index.js
@@ -2,9 +2,15 @@ import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
+import { BrowserRouter } from 'react-router-dom';
+import { Provider } from 'react-redux';
+import { store } from './redux/store';
+
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
-
-
-
+
+
+
+
+ ,
);
diff --git a/src/pages/CategoryListPage.jsx b/src/pages/CategoryListPage.jsx
new file mode 100644
index 0000000..968c8ad
--- /dev/null
+++ b/src/pages/CategoryListPage.jsx
@@ -0,0 +1,20 @@
+import React from 'react';
+
+import CategoryList from '../components/common/CategoryList';
+import LongButton from '../components/common/LongButton';
+import TextAndBackBar from '../components/common/navBar/TextAndBackBar';
+
+import { CATEGORY, SELECT } from '../static/constants';
+
+function CategoryListPage() {
+ return (
+
+
+
+ {/* TODO: 원하는 카테고리 선택 후 버튼 클릭 했을 때 즐겨찾기에 추가 */}
+
+
+ );
+}
+
+export default CategoryListPage;
diff --git a/src/pages/CategoryPage.jsx b/src/pages/CategoryPage.jsx
new file mode 100644
index 0000000..6fc0fd2
--- /dev/null
+++ b/src/pages/CategoryPage.jsx
@@ -0,0 +1,20 @@
+import React from 'react';
+
+import SearchBar from '../components/common/navBar/SearchBar';
+import CategoryList from '../components/common/CategoryList';
+import TabBar from '../components/common/navBar/TabBar';
+
+import { ENTER_INPUT } from '../static/constants';
+
+function CategoryPage() {
+ return (
+
+ );
+}
+
+export default CategoryPage;
diff --git a/src/pages/ChatListPage.jsx b/src/pages/ChatListPage.jsx
new file mode 100644
index 0000000..65750a9
--- /dev/null
+++ b/src/pages/ChatListPage.jsx
@@ -0,0 +1,181 @@
+import React from 'react';
+import { useNavigate } from 'react-router-dom';
+import TabBar from '../components/common/navBar/TabBar';
+import TextBar from '../components/common/navBar/TextBar';
+import ShowCase from '../components/common/ShowCase';
+
+const ChatListPage = () => {
+ const chatData = [
+ {
+ id: 1,
+ photo: '',
+ title: '프로틴 바/ 다크 초코씨솔트 프로틴 바/ 다크 초코씨솔트 프로틴 바/ 다크 초코씨솔트',
+ people: 5,
+ message: 'TODO님, 근처에서 다양한 물품들이 매일 올',
+ timestamp: '2023년06월25일 12:20',
+ unread: 10,
+ },
+ {
+ id: 2,
+ photo: '',
+ title: '프로틴 바/ 다크 초코씨솔트 프로틴 바/ 다크 초코씨솔트 프로틴 바/ 다크 초코씨솔트',
+ people: 5,
+ message: 'TODO님, 근처에서 다양한 물품들이 매일 올',
+ timestamp: '2023년06월25일 12:20',
+ unread: 10,
+ },
+ {
+ id: 3,
+ photo: '',
+ title: '프로틴 바/ 다크 초코씨솔트 프로틴 바/ 다크 초코씨솔트 프로틴 바/ 다크 초코씨솔트',
+ people: 5,
+ message: 'TODO님, 근처에서 다양한 물품들이 매일 올',
+ timestamp: '2023년06월25일 12:20',
+ unread: 10,
+ },
+ {
+ id: 4,
+ photo: '',
+ title: '프로틴 바/ 다크 초코씨솔트 프로틴 바/ 다크 초코씨솔트 프로틴 바/ 다크 초코씨솔트',
+ people: 5,
+ message: 'TODO님, 근처에서 다양한 물품들이 매일 올',
+ timestamp: '2023년06월25일 12:20',
+ unread: 10,
+ },
+ {
+ id: 5,
+ photo: '',
+ title: '프로틴 바/ 다크 초코씨솔트 프로틴 바/ 다크 초코씨솔트 프로틴 바/ 다크 초코씨솔트',
+ people: 5,
+ message: 'TODO님, 근처에서 다양한 물품들이 매일 올',
+ timestamp: '2023년06월25일 12:20',
+ unread: 10,
+ },
+ {
+ id: 6,
+ photo: '',
+ title: '프로틴 바/ 다크 초코씨솔트 프로틴 바/ 다크 초코씨솔트 프로틴 바/ 다크 초코씨솔트',
+ people: 5,
+ message: 'TODO님, 근처에서 다양한 물품들이 매일 올',
+ timestamp: '2023년06월25일 12:20',
+ unread: 10,
+ },
+ {
+ id: 7,
+ photo: '',
+ title: '프로틴 바/ 다크 초코씨솔트 프로틴 바/ 다크 초코씨솔트 프로틴 바/ 다크 초코씨솔트',
+ people: 5,
+ message: 'TODO님, 근처에서 다양한 물품들이 매일 올',
+ timestamp: '2023년06월25일 12:20',
+ unread: 10,
+ },
+ {
+ id: 8,
+ photo: '',
+ title: '프로틴 바/ 다크 초코씨솔트 프로틴 바/ 다크 초코씨솔트 프로틴 바/ 다크 초코씨솔트',
+ people: 5,
+ message: 'TODO님, 근처에서 다양한 물품들이 매일 올',
+ timestamp: '2023년06월25일 12:20',
+ unread: 10,
+ },
+ {
+ id: 9,
+ photo: '',
+ title: '프로틴 바/ 다크 초코씨솔트 프로틴 바/ 다크 초코씨솔트 프로틴 바/ 다크 초코씨솔트',
+ people: 5,
+ message: 'TODO님, 근처에서 다양한 물품들이 매일 올',
+ timestamp: '2023년06월25일 12:20',
+ unread: 10,
+ },
+ {
+ id: 10,
+ photo: '',
+ title: '프로틴 바/ 다크 초코씨솔트 프로틴 바/ 다크 초코씨솔트 프로틴 바/ 다크 초코씨솔트',
+ people: 5,
+ message: 'TODO님, 근처에서 다양한 물품들이 매일 올',
+ timestamp: '2023년06월25일 12:20',
+ unread: 10,
+ },
+ ];
+ const lastChat = chatData[chatData.length - 1]; // 마지막 채팅 데이터 가져오기
+ const lastChatTime = new Date(lastChat.timestamp); // 채팅 시간을 Date 객체로 변환
+ const formattedTime = lastChatTime.toLocaleTimeString('ko-KR', { hour: 'numeric', minute: 'numeric' }); // 원하는 형식으로 시간 변환
+
+ const navigate = useNavigate();
+ const handleOpenChat = () => {
+ navigate('/chat');
+ };
+
+ return (
+ <>
+
+
+ {/* 채팅리스트가 없을 때 */}
+ {!chatData && (
+
+
+
채팅 내역이 없습니다
+
+ )}
+ {/* 채팅리스트가 있을 때 */}
+
+
+ {chatData.map((chat) => (
+ -
+
+
+
+
+
+
+
+ {chat.title}
+
+ {chat.people}
+
+
+ {chat.message}
+
+
+
+
+
+
+ ))}
+
+
+
+ }
+ />
+
+ >
+ );
+};
+
+export default ChatListPage;
diff --git a/src/pages/ChatPage.jsx b/src/pages/ChatPage.jsx
new file mode 100644
index 0000000..a1f1156
--- /dev/null
+++ b/src/pages/ChatPage.jsx
@@ -0,0 +1,167 @@
+import React from 'react';
+import FriendsProfile from '../components/common/FriendsProfile';
+import ChatBar from '../components/common/navBar/ChatBar';
+import TextAndBackBar from '../components/common/navBar/TextAndBackBar';
+import ShowCase from '../components/common/ShowCase';
+
+const chatData = [
+ {
+ id: 1,
+ name: '동네친구',
+ profile: '',
+ title: '프로틴 바/ 다크 초코씨솔트 프로틴 바/ 다크 초코씨솔트 프로틴 바/ 다크 초코씨솔트',
+ postTimestamp: '2023년 06월 25일 오전 12:20',
+ people: 5,
+ unread: 10,
+ me: true,
+ messages: [
+ {
+ message: 'TODO님, 근처에서 다양한 물품들이 매일 올라오고 있어요',
+ timestamp: '2023년 06월 25일 오전 12:20',
+ },
+ {
+ message: '확인해보세요',
+ timestamp: '2023년 06월 25일 오전 12:21',
+ },
+ ],
+ },
+ {
+ id: 2,
+ name: '친구2',
+ profile: '',
+ title: '프로틴 바/ 다크 초코씨솔트 프로틴 바/ 다크 초코씨솔트 프로틴 바/ 다크 초코씨솔트',
+ people: 5,
+ unread: 10,
+ me: false,
+ messages: [
+ {
+ message: 'TODO님, 근처에서 다양한 물품들이 매일 올라오고 있어요',
+ timestamp: '2023년 06월 25일 오전 12:20',
+ },
+ {
+ message: '확인해보세요',
+ timestamp: '2023년 06월 25일 오전 12:21',
+ },
+ ],
+ },
+ {
+ id: 3,
+ name: '친구3',
+ profile: '',
+ title: '프로틴 바/ 다크 초코씨솔트 프로틴 바/ 다크 초코씨솔트 프로틴 바/ 다크 초코씨솔트',
+ people: 5,
+ unread: 10,
+ me: false,
+ messages: [
+ {
+ message: 'TODO님, 근처에서 다양한 물품들이 매일 올라오고 있어요',
+ timestamp: '2023년 06월 25일 오전 12:20',
+ },
+ {
+ message: '확인해보세요',
+ timestamp: '2023년 06월 25일 오전 12:21',
+ },
+ ],
+ },
+ {
+ id: 4,
+ name: '친구1',
+ profile: '',
+ title: '프로틴 바/ 다크 초코씨솔트 프로틴 바/ 다크 초코씨솔트 프로틴 바/ 다크 초코씨솔트',
+ people: 5,
+ unread: 10,
+ me: false,
+ messages: [
+ {
+ message: 'TODO님, 근처에서 다양한 물품들이 매일 올라오고 있어요',
+ timestamp: '2023년 06월 25일 오전 12:20',
+ },
+ {
+ message: '확인해보세요',
+ timestamp: '2023년 06월 25일 오전 12:21',
+ },
+ ],
+ },
+ {
+ id: 5,
+ name: '친구4',
+ profile: '',
+ title: '프로틴 바/ 다크 초코씨솔트 프로틴 바/ 다크 초코씨솔트 프로틴 바/ 다크 초코씨솔트',
+ people: 5,
+ unread: 10,
+ me: false,
+ messages: [
+ {
+ message: 'TODO님, 근처에서 다양한 물품들이 매일 올라오고 있어요',
+ timestamp: '2023년 06월 25일 오전 12:20',
+ },
+ {
+ message: '확인해보세요',
+ timestamp: '2023년 06월 25일 오전 12:21',
+ },
+ ],
+ },
+];
+
+const ChatPage = () => {
+ return (
+ <>
+
+
+
+
파티구성원
+
+
+
+
+ {chatData[0].postTimestamp.substring(0, 13)}
+
+
+
+ {chatData.map((chat) => (
+
+ {!chat.me && (
+
+
+
+ )}
+
+ {!chat.me &&
{chat.name}
}
+ {chat.messages.map((msg, index) => (
+
+
+ {/* 추후 메세지 보낸 시간이 1분 지나면 보여지도록 설정 */}
+
+ {msg.timestamp.substring(14, 22)}
+
+
+ ))}
+
+
+ ))}
+
+ >
+ }
+ />
+
+ >
+ );
+};
+
+export default ChatPage;
diff --git a/src/pages/EditProfilePage.jsx b/src/pages/EditProfilePage.jsx
new file mode 100644
index 0000000..c95fe3f
--- /dev/null
+++ b/src/pages/EditProfilePage.jsx
@@ -0,0 +1,128 @@
+import React, { useCallback } from 'react';
+import { useSelector, useDispatch } from 'react-redux';
+import { useNavigate } from 'react-router-dom';
+import { debounce } from 'lodash';
+
+import MyProfile from '../components/common/MyProfile';
+import LongButton from '../components/common/LongButton';
+import TextAndBackBar from '../components/common/navBar/TextAndBackBar';
+import IdPasswordForm from '../components/common/IdPasswordForm';
+
+import { EDIT, CHANGE_INFO } from '../static/constants';
+import {
+ resetFields,
+ setErrors,
+ setNewPassword,
+ setNewPasswordCheck,
+ updatePasswordFailure,
+ updatePasswordStart,
+ updatePasswordSuccess,
+} from '../redux/slices/userInfoChangeSlice';
+
+function EditProfilePage() {
+ const inputFields = [
+ { id: 'email', label: '아이디', type: 'email' },
+ { id: 'newPassword', label: '비밀번호', type: 'password' },
+ { id: 'newPasswordCheck', label: '비밀번호 재확인', type: 'password' },
+ ];
+ const { newPassword, newPasswordCheck, errors } = useSelector((state) => state.userInfoChange);
+
+ const navigate = useNavigate();
+ const dispatch = useDispatch();
+
+ // TODO: 서버에서 가져온 데이터로 추후 변경
+ const email = localStorage.getItem('signup-email');
+
+ // 유효성 검사
+ const validateField = useCallback(
+ debounce((name, value) => {
+ const validationErrors = { ...errors };
+
+ if (name === 'newPassword') {
+ dispatch(setNewPassword(value));
+ validationErrors.newPassword = {
+ message:
+ value.trim() === ''
+ ? ''
+ : value.length < 8 || value.length > 16
+ ? '8~16자리의 비밀번호를 입력해주세요.'
+ : '',
+ isError: value.trim() === '' || value.length < 8 || value.length > 16,
+ };
+ } else if (name === 'newPasswordCheck') {
+ dispatch(setNewPasswordCheck(value));
+ validationErrors.newPasswordCheck = {
+ message: value.trim() === '' ? '' : value !== newPassword ? '비밀번호가 틀렸습니다. 다시 입력해주세요.' : '',
+ isError: value.trim() === '' || value !== newPassword,
+ };
+ }
+
+ dispatch(setErrors(validationErrors));
+ }, 300),
+ [errors, newPassword, newPasswordCheck, dispatch],
+ );
+
+ /** 비밀번호 변경 */
+ const updatePassword = () => {
+ return async (dispatch) => {
+ try {
+ dispatch(updatePasswordStart());
+ await dispatch(updatePasswordSuccess({ newPassword }));
+ alert('비밀번호 변경이 완료되었습니다.');
+ dispatch(resetFields());
+ navigate('/myPage');
+ } catch (error) {
+ dispatch(updatePasswordFailure(error.message));
+ }
+ };
+ };
+
+ /** 유효성검사 확인 후 폼제출 */
+ const handleSubmit = (event) => {
+ event.preventDefault();
+ const validationErrors = {};
+
+ Object.keys(errors).forEach((key) => {
+ if (validationErrors[key] === undefined) {
+ validationErrors[key] = errors[key];
+ }
+ });
+ dispatch(setErrors(validationErrors));
+
+ const isFormValid = Object.values(validationErrors).every((error) => !error.isError);
+
+ if (isFormValid) {
+ dispatch(updatePassword());
+ console.log('클릭');
+ }
+ };
+
+ return (
+
+ );
+}
+
+export default EditProfilePage;
diff --git a/src/pages/HomePage.jsx b/src/pages/HomePage.jsx
new file mode 100644
index 0000000..dc4d127
--- /dev/null
+++ b/src/pages/HomePage.jsx
@@ -0,0 +1,25 @@
+import React from 'react';
+import HomeBar from '../components/common/navBar/HomeBar';
+import TabBar from '../components/common/navBar/TabBar';
+import ShowCase from '../components/common/ShowCase';
+import Favorite from '../components/home/Favorite';
+import PostList from '../components/home/PostList';
+
+const HomePage = () => {
+ return (
+ <>
+
+
+
+
+
+ }
+ />
+
+ >
+ );
+};
+
+export default HomePage;
diff --git a/src/pages/MyPage.jsx b/src/pages/MyPage.jsx
new file mode 100644
index 0000000..0d58d95
--- /dev/null
+++ b/src/pages/MyPage.jsx
@@ -0,0 +1,83 @@
+import React from 'react';
+import { useDispatch } from 'react-redux';
+import { useNavigate } from 'react-router-dom';
+import MyPageCategory from '../components/myPage/MyPageCategory';
+import MyProfile from '../components/common/MyProfile';
+import TabBar from '../components/common/navBar/TabBar';
+
+import { MY_PAGE, SETTING_LOCATION, CHANGE_INFO, LOGOUT } from '../static/constants';
+import { logoutFailure, logoutStart, logoutSuccess } from '../redux/slices/authSlice';
+import { getUserInfoFailure, getUserInfoStart, getUserInfoSuccess } from '../redux/slices/myPageSlice';
+
+function MyPage() {
+ const dispatch = useDispatch();
+ const navigate = useNavigate();
+ const username = localStorage.getItem('username');
+
+ /** 내정보 변경 */
+ const moveToEditProfile = () => {
+ try {
+ dispatch(getUserInfoStart());
+ dispatch(getUserInfoSuccess(username));
+ navigate('/editProfile');
+ } catch (error) {
+ dispatch(getUserInfoFailure());
+ }
+ };
+
+ /** 로그아웃 시도 */
+ const handleLogout = async () => {
+ try {
+ dispatch(logoutStart());
+ dispatch(logoutSuccess());
+ navigate('/');
+ } catch (error) {
+ console.log('로그아웃 실패');
+ dispatch(logoutFailure('로그아웃에 실패했습니다.'));
+ }
+ };
+
+ return (
+
+
+
{MY_PAGE}
+
+ //
+ //
+ //
+ // }
+ writingSvg={
+
+ }
+ />
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default MyPage;
diff --git a/src/pages/PermissionPage.jsx b/src/pages/PermissionPage.jsx
new file mode 100644
index 0000000..59a025d
--- /dev/null
+++ b/src/pages/PermissionPage.jsx
@@ -0,0 +1,131 @@
+import React from 'react';
+import { useNavigate } from 'react-router-dom';
+import OptionalPermission from '../components/permissionPage/OptionalPermission';
+import LongButton from '../components/common/LongButton';
+
+import {
+ PERMISSION_FIRST_TITLE,
+ PERMISSION_SECOND_TITLE,
+ SELECT_PERMISSION,
+ NOTIFICATION,
+ NOTIFICATION_DESCRIPTION,
+ LOCATION,
+ LOCATION_DESCRIPTION,
+ CAMERA,
+ CAMERA_DESCRIPTION,
+ MIKE,
+ MIKE_DESCRIPTION,
+ STORAGE,
+ STORAGE_DESCRIPTION,
+ PERMISSION_MESSAGE,
+ CONFIRM,
+} from '../static/constants';
+
+function PermissionPage() {
+ const navigate = useNavigate();
+ const handleConfirm = () => {
+ navigate('/register-location');
+ };
+
+ return (
+
+
+
+ {PERMISSION_FIRST_TITLE}
+
+ {PERMISSION_SECOND_TITLE}
+
+
+
+
+ {SELECT_PERMISSION}
+
+
+
+
+
+
+ }
+ title={NOTIFICATION}
+ description={NOTIFICATION_DESCRIPTION}
+ />
+
+
+
+
+ }
+ title={LOCATION}
+ description={LOCATION_DESCRIPTION}
+ />
+
+
+
+ }
+ title={CAMERA}
+ description={CAMERA_DESCRIPTION}
+ />
+
+
+
+
+ }
+ title={MIKE}
+ description={MIKE_DESCRIPTION}
+ />
+
+
+
+ }
+ title={STORAGE}
+ description={STORAGE_DESCRIPTION}
+ />
+
+
+
+ {PERMISSION_MESSAGE}
+
+
+
+
+ );
+}
+
+export default PermissionPage;
diff --git a/src/pages/PostsPage.jsx b/src/pages/PostsPage.jsx
new file mode 100644
index 0000000..1ec8cff
--- /dev/null
+++ b/src/pages/PostsPage.jsx
@@ -0,0 +1,130 @@
+import React, { useState, useEffect } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import Swal from 'sweetalert2';
+
+import BackButton from '../components/common/navBar/BackButton';
+import FriendsProfile from '../components/common/FriendsProfile';
+import { friendsSlice } from '../redux/slices/friendsSlice';
+
+import { JOIN_ALERT, CONFIRM, CANCEL, SUM, WON, DIVISION, ACTUAL_PAYMENT_AMOUNT, JOIN } from '../static/constants';
+
+function PostsPage() {
+ const dispatch = useDispatch();
+
+ const imageUrl = JSON.parse(localStorage.getItem('imageUrl'));
+ const title = JSON.parse(localStorage.getItem('title'));
+ const category = JSON.parse(localStorage.getItem('category'));
+ const totalAmount = JSON.parse(localStorage.getItem('totalAmount'));
+ const maxPeople = JSON.parse(localStorage.getItem('maxPeople'));
+ const textarea = JSON.parse(localStorage.getItem('textarea'));
+ const divisionAmount = (totalAmount / (maxPeople + 1)).toLocaleString();
+
+ let friendsList = useSelector((state) => state.friends.friendsList);
+ friendsList = friendsList.map((el, idx) => (idx < maxPeople ? (el = true) : (el = false)));
+ let recruteList = useSelector((state) => state.friends.recruteList);
+ recruteList = friendsList.map((el) => (el ? (el = '모집대기중') : ''));
+
+ useEffect(() => {
+ dispatch(friendsSlice.actions.setFriendsList(friendsList));
+ dispatch(friendsSlice.actions.setRecruteList(recruteList));
+ }, []);
+
+ const joinAsMember = () => {
+ Swal.fire({
+ text: JOIN_ALERT,
+ showCancelButton: true,
+ confirmButtonColor: '#39B54A',
+ cancelButtonColor: '#CCCCCC',
+ confirmButtonText: CONFIRM,
+ cancelButtonText: CANCEL,
+ reverseButtons: true,
+ }).then((result) => {
+ if (result.isConfirmed) {
+ localStorage.setItem('isJoin', true);
+ localStorage.setItem('recruteList', JSON.stringify(recruteList));
+
+ const index = recruteList.findIndex((item) => item === '모집대기중');
+
+ if (index !== -1) {
+ recruteList[index] = '파티원';
+ localStorage.setItem('recruteList', JSON.stringify(recruteList));
+
+ let test = JSON.parse(localStorage.getItem('recruteList'));
+ dispatch(friendsSlice.actions.setRecruteList(test));
+ }
+ }
+ });
+ };
+
+ let isJoin = JSON.parse(localStorage.getItem('isJoin'));
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{title}
+
{category}
+
{textarea}
+
+
+
+
+
{SUM}
+
+
{`${totalAmount.toLocaleString()}${WON}`}
+
{DIVISION}
+
+
+
+
+
{ACTUAL_PAYMENT_AMOUNT}
+
{`${divisionAmount}${WON}`}
+
+
+
+
+
{`내 1/${maxPeople + 1} 부담금`}
+
{`${divisionAmount}${WON}`}
+
+
+
+
{`파티원 ${maxPeople}명의 몫`}
+
{`${(totalAmount - totalAmount / maxPeople).toLocaleString()}${WON}`}
+
+
+
+
+
+
+
{`${SUM} ${totalAmount.toLocaleString()}${WON}`}
+
{`${ACTUAL_PAYMENT_AMOUNT} ${divisionAmount}${WON}`}
+
+
+
+
+ );
+}
+
+export default PostsPage;
diff --git a/src/pages/RegisterCompletePage.jsx b/src/pages/RegisterCompletePage.jsx
new file mode 100644
index 0000000..e1d8163
--- /dev/null
+++ b/src/pages/RegisterCompletePage.jsx
@@ -0,0 +1,38 @@
+import React from 'react';
+import { useNavigate } from 'react-router-dom';
+import ImageAndMessage from '../components/common/ImageAndMessage';
+import LongButton from '../components/common/LongButton';
+import TextBar from '../components/common/navBar/TextAndBackBar';
+
+const RegisterCompletePage = () => {
+ const navigate = useNavigate();
+
+ const handleMoveSignIn = () => {
+ navigate(`/signin`);
+ localStorage.removeItem('signup-nickname');
+ localStorage.removeItem('signup-password');
+ localStorage.removeItem('registeredLocation');
+ };
+ const nickname = localStorage.getItem('signup-nickname');
+
+ return (
+ <>
+
+
+ {/* data 받아와서 user.name 넣어주기 */}'{nickname}' 님의 회원가입이
+
+ 성공적으로 완료되었습니다.
+ >
+ }
+ />
+
+ >
+ );
+};
+export default RegisterCompletePage;
diff --git a/src/pages/RegisterLocationCompletePage.jsx b/src/pages/RegisterLocationCompletePage.jsx
new file mode 100644
index 0000000..e212bf5
--- /dev/null
+++ b/src/pages/RegisterLocationCompletePage.jsx
@@ -0,0 +1,26 @@
+import React from 'react';
+import { useNavigate } from 'react-router-dom';
+
+import TextAndBackBar from '../components/common/navBar/TextAndBackBar';
+import LongButton from '../components/common/LongButton';
+import ImageAndMessage from '../components/common/ImageAndMessage';
+
+import { MOVE_TO_HOME, REGISTER_LOCATION_COMPLETE } from '../static/constants';
+
+const RegisterLocationCompletePage = () => {
+ const navigate = useNavigate();
+ const moveToHomePage = () => navigate('/');
+
+ return (
+
+ );
+};
+
+export default RegisterLocationCompletePage;
diff --git a/src/pages/RegisterLocationPage.jsx b/src/pages/RegisterLocationPage.jsx
new file mode 100644
index 0000000..46a70c1
--- /dev/null
+++ b/src/pages/RegisterLocationPage.jsx
@@ -0,0 +1,70 @@
+import React from 'react';
+import { useNavigate } from 'react-router-dom';
+import CryptoJS from 'crypto-js';
+
+import NearLoacation from '../components/registerLocationPage/NearLoacation';
+import SearchBar from '../components/common/navBar/SearchBar';
+import ShowCase from '../components/common/ShowCase';
+
+import { SEARCH_LOCATION, NEAR_LOCATION } from '../static/constants';
+
+function RegisterLocationPage() {
+ // TODO: 추후 받아온 데이터를 사용할 예정
+ const locationList = [
+ '서울특별시 서초구 1',
+ '서울특별시 서초구 2',
+ '서울특별시 서초구 3',
+ '서울특별시 서초구 4',
+ '서울특별시 서초구 5',
+ '서울특별시 서초구 역삼동',
+ '서울특별시 서초구 역삼동',
+ '서울특별시 서초구 역삼동',
+ '서울특별시 서초구 역삼동',
+ '서울특별시 서초구 역삼동',
+ '서울특별시 서초구 역삼동',
+ '서울특별시 서초구 역삼동',
+ '서울특별시 서초구 역삼동',
+ '서울특별시 서초구 역삼동',
+ '서울특별시 서초구 역삼동',
+ '서울특별시 서초구 역삼동',
+ '서울특별시 서초구 역삼동',
+ '서울특별시 서초구 역삼동',
+ '서울특별시 서초구 역삼동',
+ '서울특별시 서초구 역삼동',
+ '서울특별시 서초구 역삼동',
+ '서울특별시 서초구 역삼동',
+ '서울특별시 서초구 역삼동',
+ '서울특별시 서초구 역삼동',
+ '서울특별시 서초구 역삼동',
+ '서울특별시 서초구 역삼동',
+ '서울특별시 서초구 역삼동',
+ '서울특별시 서초구 역삼동',
+ '서울특별시 서초구 역삼동',
+ '서울특별시 서초구 역삼동',
+ ];
+ const REACT_APP_SECRET_KEY = process.env.REACT_APP_SECRET_KEY;
+
+ const navigate = useNavigate();
+ const onClickLocation = (location) => {
+ const encrypt = CryptoJS.AES.encrypt(location, REACT_APP_SECRET_KEY).toString();
+ localStorage.setItem('registeredLocation', encrypt);
+ navigate('/register-complete');
+ };
+
+ return (
+
+
+
+
{NEAR_LOCATION}
+
onClickLocation(el)} />)
+ }
+ />
+
+ );
+}
+
+export default RegisterLocationPage;
diff --git a/src/pages/RegisterPage.jsx b/src/pages/RegisterPage.jsx
new file mode 100644
index 0000000..faf4e43
--- /dev/null
+++ b/src/pages/RegisterPage.jsx
@@ -0,0 +1,192 @@
+import React, { useEffect, useCallback } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { useSelector, useDispatch } from 'react-redux';
+import { debounce } from 'lodash';
+import TextAndBackBar from '../components/common/navBar/TextAndBackBar';
+import LongButton from '../components/common/LongButton';
+import IdPasswordForm from '../components/common/IdPasswordForm';
+import { signupFailure, signupStart, signupSuccess } from '../redux/slices/authSlice';
+import {
+ setEmail,
+ setPassword,
+ setPasswordCheck,
+ setNickname,
+ setErrors,
+ resetFields,
+} from '../redux/slices/registerSlice';
+import { checkEmail } from '../redux/api/authApi';
+
+const RegisterPage = () => {
+ const inputFields = [
+ { id: 'email', label: '아이디', type: 'email' },
+ { id: 'password', label: '비밀번호', type: 'password' },
+ { id: 'passwordCheck', label: '비밀번호 재확인', type: 'password' },
+ { id: 'nickname', label: '닉네임', type: 'text' },
+ ];
+ const nicknameField = inputFields.find((field) => field.id === 'nickname');
+
+ const emailRegex = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i;
+ const existedEmail = 'test@test.com';
+
+ const { email, password, passwordCheck, nickname, errors } = useSelector((state) => state.register);
+ const dispatch = useDispatch();
+ const navigate = useNavigate();
+
+ // 회원가입 API 호출
+ const callSaveUserInfo = () => {
+ try {
+ dispatch(signupStart());
+ dispatch(signupSuccess({ email, password, nickname }));
+ dispatch(resetFields());
+ navigate(`/permission`);
+ } catch (error) {
+ dispatch(signupFailure(error.message));
+ }
+ };
+
+ // 사용자 이메일 체크
+ const handleCheckEmail = async (value) => {
+ try {
+ const result = await checkEmail(value);
+ return result === null;
+ } catch (error) {
+ console.error('사용자 이메일 체크 오류 :', error);
+ return false;
+ }
+ };
+
+ // 유효성 검사 후 상태 업데이트
+ const validateField = useCallback(
+ debounce(async (name, value) => {
+ const validationErrors = { ...errors };
+ console.log(name, value);
+ if (name === 'email') {
+ dispatch(setEmail(value));
+ const isValidEmail = emailRegex.test(value);
+
+ if (!value.trim()) {
+ validationErrors.email = { message: '아이디를 입력해주세요.', isError: true };
+ } else if (!isValidEmail) {
+ validationErrors.email = { message: '유효한 이메일 형식이 아닙니다.', isError: true };
+ } else {
+ const isAvailable = await handleCheckEmail(value);
+ if (!isAvailable) {
+ validationErrors.email = { message: '이미 사용중인 아이디입니다.', isError: true };
+ } else {
+ validationErrors.email = { message: '', isError: false };
+ }
+ }
+ } else if (name === 'password') {
+ dispatch(setPassword(value));
+ validationErrors.password = {
+ message:
+ value.trim() === ''
+ ? '8~16자리의 비밀번호를 입력해주세요.'
+ : value.length < 8 || value.length > 16
+ ? '비밀번호는 8~16자리여야 합니다.'
+ : '',
+ isError: value.trim() === '' || value.length < 8 || value.length > 16,
+ };
+ } else if (name === 'passwordCheck') {
+ dispatch(setPasswordCheck(value));
+ validationErrors.passwordCheck = {
+ message:
+ value.trim() === ''
+ ? '확인을 위하여 위와 동일하게 입력해주세요.'
+ : value !== password
+ ? '비밀번호가 틀렸습니다. 다시 입력해주세요.'
+ : '',
+ isError: value.trim() === '' || value !== password,
+ };
+ } else if (name === 'nickname') {
+ dispatch(setNickname(value));
+ validationErrors.nickname = {
+ message: value.trim() === '' ? '닉네임을 입력해주세요.' : '',
+ isError: value.trim() === '',
+ };
+ }
+
+ dispatch(setErrors(validationErrors));
+ }, 300),
+ [errors, emailRegex, existedEmail, email, password, passwordCheck, nickname],
+ );
+
+ // 유효성검사 확인 후 폼제출
+ const handleSubmit = (event) => {
+ event.preventDefault();
+ const validationErrors = {};
+
+ inputFields.forEach((field) => {
+ if (field.id === 'email' && email.trim() === '') {
+ validationErrors[field.id] = { message: `${field.label}를 입력해주세요.`, isError: true };
+ } else if (field.id === 'password' && password.trim() === '') {
+ validationErrors[field.id] = { message: `8~16자리의 ${field.label}를 입력해주세요.`, isError: true };
+ } else if (field.id === 'passwordCheck' && passwordCheck.trim() === '') {
+ validationErrors[field.id] = { message: `확인을 위하여 위와 동일하게 입력해주세요.`, isError: true };
+ } else if (field.id === 'nickname' && nickname.trim() === '') {
+ validationErrors[field.id] = { message: '닉네임을 입력해주세요.', isError: true };
+ }
+ });
+
+ Object.keys(errors).forEach((key) => {
+ if (validationErrors[key] === undefined) {
+ validationErrors[key] = errors[key];
+ }
+ });
+
+ const isFormValid = Object.values(validationErrors).every((error) => !error.isError);
+
+ dispatch(setErrors(validationErrors));
+
+ if (isFormValid) {
+ callSaveUserInfo();
+ }
+ };
+
+ // 에러메세지 초기값 보여주기
+ useEffect(() => {
+ const validationErrors = {};
+
+ inputFields.forEach((field) => {
+ validationErrors[field.id] = errors[field.id];
+ });
+
+ dispatch(setErrors(validationErrors));
+ }, []);
+
+ return (
+ <>
+
+
+
+ >
+ );
+};
+
+export default RegisterPage;
diff --git a/src/pages/SearchPage.jsx b/src/pages/SearchPage.jsx
new file mode 100644
index 0000000..4acb27e
--- /dev/null
+++ b/src/pages/SearchPage.jsx
@@ -0,0 +1,168 @@
+import React, { useEffect, useState } from 'react';
+import { useSelector } from 'react-redux';
+import Select from 'react-select';
+
+import SearchBar from '../components/common/navBar/SearchBar';
+import SearchedOutputList from '../components/common/SearchedOutputLists';
+import TabBar from '../components/common/navBar/TabBar';
+
+import { ENTER_INPUT } from '../static/constants';
+
+// TODO: 하드코딩되어 있는 값들 모두 수정 필요
+function SearchPage() {
+ const options = [
+ { value: '낮은 가격순', label: '낮은 가격순' },
+ { value: '높은 가격순', label: '높은 가격순' },
+ ];
+ const searchedOutputList = [
+ {
+ id: 1,
+ name: '전체',
+ location: '서울시 서초구 서초대로',
+ price: '20000',
+ category: '전체',
+ },
+ {
+ id: 2,
+ name: '과일',
+ location: '서울시 서초구 서초대로',
+ price: '20000',
+ category: '과일',
+ },
+ {
+ id: 3,
+ name: '채소',
+ location: '서울시 서초구 서초대로',
+ price: '20000',
+ category: '채소',
+ },
+ {
+ id: 4,
+ name: '쌀/잡곡/견과',
+ location: '서울시 서초구 서초대로',
+ price: '20000',
+ category: '쌀/잡곡/견과',
+ },
+ {
+ id: 5,
+ name: '정육/계란류',
+ location: '서울시 서초구 서초대로',
+ price: '20000',
+ category: '정육/계란류',
+ },
+ {
+ id: 6,
+ name: '수산물/건해산',
+ location: '서울시 서초구 서초대로',
+ price: '20000',
+ category: '수산물/건해산',
+ },
+ {
+ id: 7,
+ name: '우유/유제품',
+ location: '서울시 서초구 서초대로',
+ price: '20000',
+ category: '우유/유제품',
+ },
+ {
+ id: 8,
+ name: '김치/반찬/델리',
+ location: '서울시 서초구 서초대로',
+ price: '20000',
+ category: '김치/반찬/델리',
+ },
+ {
+ id: 100,
+ name: '김치/반찬/델리',
+ location: '서울시 서초구 서초대로',
+ price: '15000',
+ category: '김치/반찬/델리',
+ },
+ {
+ id: 9,
+ name: '생수/음료/주류',
+ location: '서울시 서초구 서초대로',
+ price: '20000',
+ category: '생수/음료/주류',
+ },
+ {
+ id: 10,
+ name: '커피/차/원두',
+ location: '서울시 서초구 서초대로',
+ price: '20000',
+ category: '커피/차/원두',
+ },
+ {
+ id: 11,
+ name: '면류/통조림',
+ location: '서울시 서초구 서초대로',
+ price: '20000',
+ category: '면류/통조림',
+ },
+ {
+ id: 12,
+ name: '양념/오일',
+ location: '서울시 서초구 서초대로',
+ price: '20000',
+ category: '양념/오일',
+ },
+ {
+ id: 13,
+ name: '과자/간식',
+ location: '서울시 서초구 서초대로',
+ price: '20000',
+ category: '과자/간식',
+ },
+ {
+ id: 14,
+ name: '베이커리/잼',
+ location: '서울시 서초구 서초대로',
+ price: '20000',
+ category: '베이커리/잼',
+ },
+ {
+ id: 15,
+ name: '친환경/유기농',
+ location: '서울시 서초구 서초대로',
+ price: '20000',
+ category: '친환경/유기농',
+ },
+ ];
+
+ const [selectedOption, setSelectedOption] = useState(options[0]);
+ const selectedCategory = useSelector((state) => state.selectedCategory.category);
+ const onChangeSelector = (selectedOption) => setSelectedOption(selectedOption.value);
+
+ useEffect(() => {
+ setSelectedOption(options[0].value);
+ }, []);
+
+ return (
+
+
+
+
+
+ {selectedCategory}
+
+
+
+
+
총 {searchedOutputList.length}개
+
el.category === selectedCategory)
+ .sort((a, b) => parseInt(a.price) - parseInt(b.price))
+ : searchedOutputList
+ .filter((el) => el.category === selectedCategory)
+ .sort((a, b) => parseInt(b.price) - parseInt(a.price))
+ }
+ />
+
+
+ );
+}
+
+export default SearchPage;
diff --git a/src/pages/SignInPage.jsx b/src/pages/SignInPage.jsx
new file mode 100644
index 0000000..e05ab9f
--- /dev/null
+++ b/src/pages/SignInPage.jsx
@@ -0,0 +1,113 @@
+import React, { useCallback, useEffect, useRef, useState } from 'react';
+import { useSelector, useDispatch } from 'react-redux';
+import { useNavigate } from 'react-router-dom';
+import { debounce } from 'lodash';
+import Input from '../components/common/Input';
+import LongButton from '../components/common/LongButton';
+import TextAndBackBar from '../components/common/navBar/TextAndBackBar';
+import { setUsername, setError, setPassword } from '../redux/slices/signinSlice';
+import { loginFailure, loginStart } from '../redux/slices/authSlice';
+
+const SignInPage = () => {
+ const { username, password, error } = useSelector((state) => state.signin);
+ const [loginSuccess, setLoginSuccess] = useState(false);
+ const [isButtonClicked, setIsButtonClicked] = useState(false);
+ const dispatch = useDispatch();
+ const navigate = useNavigate();
+ const emailRef = useRef();
+
+ /** TODO: 응답 데이터의 이메일 정보를 이메일 입력 필드에 자동으로 채워넣기 수정예정 */
+ // const storedEmail = localStorage.getItem('signup-username');
+ // useEffect(() => {
+ // if (emailRef.current && storedEmail) {
+ // emailRef.current.value = storedEmail;
+ // }
+ // }, [storedEmail]);
+
+ // 아이디/비밀번호 상태관리 & 디바운스 처리
+ const onChangeHandler = useCallback(
+ debounce((event) => {
+ event.preventDefault();
+ const { name, value } = event.target;
+ if (name === 'email') {
+ dispatch(setUsername(value));
+ } else if (name === 'password') {
+ dispatch(setPassword(value));
+ }
+ console.log(value);
+ }, 300),
+ [dispatch],
+ );
+
+ // 로그인 시도
+ const handleSignIn = () => {
+ setIsButtonClicked(true);
+
+ try {
+ dispatch(loginStart());
+
+ if (loginSuccess) {
+ dispatch(loginSuccess({ username, password }));
+ navigate('/');
+ localStorage.removeItem('signup-username');
+ } else {
+ dispatch(setError('일치하는 회원정보가 없거나, 비밀번호가 일치하지 않습니다.'));
+ setLoginSuccess(false);
+ }
+ } catch (error) {
+ dispatch(loginFailure());
+ }
+ };
+
+ // 회원가입 페이지 & 게스트 로그인 이동
+ /** 게스트 로그인은 추후 서버에서 데이터 받아와서 연결 필요 */
+ const handleMovePage = (route) => {
+ navigate(route);
+ };
+
+ return (
+
+
+
+
+
+
handleMovePage('/')}>
+ 게스트 로그인
+
+
handleMovePage('/register')}>
+ 회원가입
+
+
+
+ {/* 간편로그인 : 추후 api 생성 시 기능개발예정 */}
+ {/*
*/}
+
+ );
+};
+export default SignInPage;
diff --git a/src/pages/SplashScreenPage.jsx b/src/pages/SplashScreenPage.jsx
new file mode 100644
index 0000000..736ad57
--- /dev/null
+++ b/src/pages/SplashScreenPage.jsx
@@ -0,0 +1,90 @@
+import React, { useEffect } from 'react';
+import { useNavigate } from 'react-router-dom';
+
+const SplashScreenPage = () => {
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ const moveToWalkthrough = setTimeout(() => {
+ navigate('/walkthrough');
+ }, 1500);
+ return () => clearTimeout(moveToWalkthrough);
+ }, [navigate]);
+
+ return (
+
+
+ 식재료 쉐어의 즐거움
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default SplashScreenPage;
diff --git a/src/pages/WalkthroughPage.jsx b/src/pages/WalkthroughPage.jsx
new file mode 100644
index 0000000..25f6578
--- /dev/null
+++ b/src/pages/WalkthroughPage.jsx
@@ -0,0 +1,76 @@
+import React, { useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import Carousel from '../components/common/Carousel';
+import ImageAndMessage from '../components/common/ImageAndMessage';
+import LongButton from '../components/common/LongButton';
+
+const WalkthroughPage = () => {
+ const [carouselColor, setCarouselColor] = useState(['#00c92c', '#d9d9d9']);
+ const [walkthroughPosition, setWalkthroughPosition] = useState(0);
+ const [firstImageVisible, setFirstImageVisible] = useState(true);
+ const [buttonClicked, setButtonClicked] = useState(false);
+ const navigate = useNavigate();
+
+ const startHandler = () => {
+ if (buttonClicked) {
+ navigate('/signin');
+ } else {
+ setCarouselColor(['#d9d9d9', '#00c92c']);
+ setFirstImageVisible(false);
+ if (firstImageVisible) {
+ setTimeout(() => {
+ setWalkthroughPosition(-50);
+ }, 100);
+ }
+ setButtonClicked(true);
+ }
+ };
+
+ return (
+
+
+
+ 가까운 동네 친구들과
+
혼자 사기 힘들었던 상품을
+
이제 부담없이 나눠가져요
+ >
+ }
+ />
+
+ 한번에 많은 물건을 혼자사기
+
+ 부담스럽다면 동네 친구들과 함께
+
+ 구입하고 먹을만큼 나눠가져요
+ >
+ }
+ />
+
+
+
+
+
+
+
+ );
+};
+
+export default WalkthroughPage;
diff --git a/src/pages/WritingPage.jsx b/src/pages/WritingPage.jsx
new file mode 100644
index 0000000..80cd7ee
--- /dev/null
+++ b/src/pages/WritingPage.jsx
@@ -0,0 +1,190 @@
+import React, { useRef, useState } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import { useNavigate } from 'react-router-dom';
+import Select from 'react-select';
+
+import LongButton from '../components/common/LongButton';
+import Input from '../components/writingPage/Input';
+import BackButton from '../components/common/navBar/BackButton';
+import { writingSlice } from '../redux/slices/writingSlice';
+
+import { ADD_IMAGE, ARTICLE_TITLE, TOTAL_AMOUNT, MAXIMUM_PEOPLE, PLEASE_WRITE_TEXT, DONE } from '../static/constants';
+
+function WritingPage() {
+ const options = [
+ // TODO: 하드코딩된 값 constants로 추후 변경 예정
+ { value: '전체', label: '전체' },
+ { value: '과일', label: '과일' },
+ { value: '채소', label: '채소' },
+ { value: '쌀/잡곡/견과', label: '쌀/잡곡/견과' },
+ { value: '정육/계란류', label: '정육/계란류' },
+ { value: '수산물/건해산', label: '수산물/건해산' },
+ { value: '우유/유제품', label: '우유/유제품' },
+ { value: '김치/반찬/델리', label: '김치/반찬/델리' },
+ { value: '생수/음료/주류', label: '생수/음료/주류' },
+ { value: '정육/계란류', label: '정육/계란류' },
+ { value: '커피/차/원두', label: '커피/차/원두' },
+ { value: '면류/통조림', label: '면류/통조림' },
+ { value: '양념/오일', label: '양념/오일' },
+ { value: '과자/간식', label: '과자/간식' },
+ { value: '베이커리/잼', label: '베이커리/잼' },
+ { value: '친환경/유기농', label: '친환경/유기농' },
+ ];
+ const min = 1;
+ const max = 4;
+
+ const imgRef = useRef(null);
+ const navigate = useNavigate();
+ const dispatch = useDispatch();
+ const title = useSelector((state) => state.writing.title);
+ const imageUrl = useSelector((state) => state.writing.imageUrl);
+ const totalAmount = useSelector((state) => state.writing.totalAmount);
+ const textarea = useSelector((state) => state.writing.textarea);
+
+ const [count, setCount] = useState(min);
+ const [selectedCategory, setSelectedCategory] = useState(options[0]);
+
+ const addImages = () => imgRef.current?.click();
+ const removeImage = () => dispatch(writingSlice.actions.setImageUrl(null));
+ const onChangeImage = (e) => {
+ const files = e.target.files[0];
+ const reader = new FileReader();
+
+ if (files === undefined) return;
+
+ reader.readAsDataURL(files);
+ reader.onload = () => {
+ dispatch(writingSlice.actions.setImageUrl(reader.result));
+ };
+ };
+
+ const onChangeTitle = (e) => {
+ dispatch(writingSlice.actions.setTitle(e.target.value));
+ };
+
+ const onChangeCategory = (e) => {
+ const selectedValue = e.value;
+ setSelectedCategory(selectedValue);
+ dispatch(writingSlice.actions.setCategory(selectedValue));
+ };
+
+ const onChangeTotalAmount = (e) => {
+ const totalAmount = e.target.value;
+
+ if (!/^[0-9]*$/.test(totalAmount)) e.target.value = '';
+ else dispatch(writingSlice.actions.setTotalAmount(totalAmount));
+ };
+
+ const decreaseCount = () => {
+ const newCount = Math.max(count - 1, min);
+ setCount(newCount);
+ dispatch(writingSlice.actions.setMaxPeople(newCount));
+ };
+ const increaseCount = () => {
+ const newCount = Math.min(count + 1, max);
+ setCount(newCount);
+ dispatch(writingSlice.actions.setMaxPeople(newCount));
+ };
+
+ const onChangeTextarea = (e) => {
+ dispatch(writingSlice.actions.setTextarea(e.target.value));
+ };
+
+ const onclickDoneButton = () => navigate('/posts');
+
+ return (
+
+
+
+
+
+ {imageUrl ? (
+
+ ) : (
+ ''
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
{MAXIMUM_PEOPLE}
+
+
+
{count}
+
+
+
+
+
+ {!title || totalAmount === 0 || !textarea ? (
+
+ ) : (
+
+ )}
+
+ );
+}
+
+export default WritingPage;
diff --git a/src/redux/api/authApi.js b/src/redux/api/authApi.js
new file mode 100644
index 0000000..294d872
--- /dev/null
+++ b/src/redux/api/authApi.js
@@ -0,0 +1,87 @@
+import axios from 'axios';
+import CryptoJS from 'crypto-js';
+
+const encryptionKey = process.env.REACT_APP_SECRET_KEY;
+const BASE_URL = 'http://localhost:8080/api/v1';
+
+// 유저정보 암호화 후 로컬스토리지에 저장
+export const saveUserInfo = async ({ username, password, nickname }) => {
+ const encryptedusername = CryptoJS.AES.encrypt(username, encryptionKey).toString();
+ const encryptedPassword = CryptoJS.AES.encrypt(password, encryptionKey).toString();
+
+ try {
+ localStorage.setItem('signup-username', encryptedusername);
+ localStorage.setItem('signup-password', encryptedPassword);
+ localStorage.setItem('signup-nickname', nickname);
+ } catch (error) {
+ throw new Error(error.message);
+ }
+};
+
+// 사용자 아이디 검증
+export const checkEmail = async ({ email }) => {
+ try {
+ const response = await axios.post(`${BASE_URL}/auth/join/check`, email);
+ const result = response.data;
+ return result; // 서버로부터 중복 여부 확인 결과 반환
+ } catch (error) {
+ throw new Error(error.message);
+ }
+};
+
+// 회원가입 API 호출
+export const signUpAPI = async ({ address }) => {
+ const nickname = localStorage.getItem('signup-nickname');
+ const encryptedUsername = localStorage.getItem('signup-email');
+ const encryptedPassword = localStorage.getItem('signup-password');
+
+ const decryptedUsername = CryptoJS.AES.decrypt(encryptedUsername, encryptionKey).toString(CryptoJS.enc.Utf8);
+ const decryptedPassword = CryptoJS.AES.decrypt(encryptedPassword, encryptionKey).toString(CryptoJS.enc.Utf8);
+
+ const userInfo = {
+ userId: '',
+ pw: decryptedPassword,
+ email: decryptedUsername,
+ name: nickname,
+ address: address, // 복호화 확인 필요
+ };
+
+ try {
+ const response = await axios.post(`${BASE_URL}/auth/join/Proc`, userInfo);
+ const result = response.data;
+
+ if (result.ACCESS_TOKEN) {
+ userInfo.userId = result.ACCESS_TOKEN; // userId 값 설정
+ }
+ console.log(result);
+ } catch (error) {
+ throw new Error(error.message);
+ }
+};
+
+// 로그인 API 호출
+export const signInAPI = async ({ email, password }) => {
+ try {
+ const response = await axios.post(`${BASE_URL}/auth/login`, {
+ email,
+ password,
+ });
+ const result = response.data;
+
+ if (result.ACCESS_TOKEN) {
+ localStorage.setItem('signin-token', result.ACCESS_TOKEN);
+ }
+ } catch (error) {
+ throw new Error(error.message);
+ }
+};
+
+// 로그아웃 API 호출
+export const logoutAPI = async () => {
+ try {
+ await axios.get(`${BASE_URL}/logout`);
+ localStorage.removeItem('signin-token');
+ } catch (error) {
+ throw new Error(error.message);
+ }
+};
\ No newline at end of file
diff --git a/src/redux/api/userInfoUpdateAPI.js b/src/redux/api/userInfoUpdateAPI.js
new file mode 100644
index 0000000..887e572
--- /dev/null
+++ b/src/redux/api/userInfoUpdateAPI.js
@@ -0,0 +1,56 @@
+import axios from 'axios';
+import CryptoJS from 'crypto-js';
+
+const encryptionKey = process.env.REACT_APP_SECRET_KEY;
+const BASE_URL = 'http://localhost:8080/api/v1';
+
+// 유저정보 암호화 후 로컬스토리지에 저장
+export const UserInfoUpdate = async ({ newPassword, newNickname }) => {
+ const encryptedPassword = CryptoJS.AES.encrypt(newPassword, encryptionKey).toString();
+
+ try {
+ // TODO: 서버에 저장된 유저정보로 변경하기
+ const storedPassword = localStorage.getItem('signup-password');
+ const storedNickname = localStorage.getItem('signup-nickname');
+
+ // 새로운 값을 로컬 스토리지에 비동기적으로 저장하기
+ if (newPassword !== undefined) {
+ localStorage.setItem('signup-password', encryptedPassword);
+ } else {
+ localStorage.setItem('signup-password', storedPassword);
+ }
+
+ if (newNickname !== undefined) {
+ localStorage.setItem('signup-nickname', newNickname);
+ } else {
+ localStorage.setItem('signup-nickname', storedNickname);
+ }
+ } catch (error) {
+ throw new Error(error.message);
+ }
+};
+
+// 사용자 회원정보 조회
+export const inquireUserInfoAPI = async ({ username }) => {
+ try {
+ const response = await axios.post(`${BASE_URL}/user/detail`, username);
+ const result = response.data;
+ console.log(result);
+ localStorage.setItem('username', result.username);
+ return result;
+ } catch (error) {
+ throw new Error(error.message);
+ }
+};
+
+// 사용자 정보(비밀번호) 변경
+export const updatePasswordAPI = async ({ username }) => {
+ try {
+ const response = await axios.post(`${BASE_URL}/user/modify`, username);
+ const result = response.data;
+ console.log(result);
+ return result;
+ } catch (error) {
+ throw new Error(error.message);
+ }
+};
diff --git a/src/redux/slices/authSlice.js b/src/redux/slices/authSlice.js
new file mode 100644
index 0000000..f5ff667
--- /dev/null
+++ b/src/redux/slices/authSlice.js
@@ -0,0 +1,77 @@
+import { createSlice } from '@reduxjs/toolkit';
+import { signInAPI, signupAPI, logoutAPI, inquireUserInfoAPI } from '../api/authApi';
+import { saveUserInfo } from '../api/authApi';
+
+const initialState = {
+ user: {
+ id: '',
+ username: '',
+ password: '',
+ nickname: '',
+ address: '',
+ },
+ isLoading: false,
+ error: null,
+};
+
+const authSlice = createSlice({
+ name: 'auth',
+ initialState,
+ reducers: {
+ loginStart(state) {
+ state.isLoading = true;
+ state.error = null;
+ },
+ loginSuccess(state, action) {
+ state.isLoading = false;
+ state.user = action.payload;
+ signInAPI(state.user);
+ state.error = null;
+ },
+ loginFailure(state, action) {
+ state.isLoading = false;
+ state.error = action.payload;
+ },
+ logoutStart: (state) => {
+ state.isLoading = true;
+ state.error = null;
+ },
+ logoutSuccess: (state) => {
+ state.isLoading = false;
+ logoutAPI()
+ state.error = null;
+ },
+ logoutFailure: (state, action) => {
+ state.isLoading = false;
+ state.error = action.payload;
+ },
+ signupStart(state) {
+ state.isLoading = true;
+ state.error = null;
+ },
+ signupSuccess(state, action) {
+ state.isLoading = false;
+ state.user = action.payload;
+ saveUserInfo(state.user);
+ state.error = null;
+ },
+ signupFailure(state, action) {
+ state.isLoading = false;
+ state.error = action.payload;
+ },
+ },
+});
+
+export const {
+ loginStart,
+ loginSuccess,
+ loginFailure,
+ logoutStart,
+ logoutSuccess,
+ logoutFailure,
+ signupStart,
+ signupSuccess,
+ signupFailure,
+} = authSlice.actions;
+
+export default authSlice.reducer;
diff --git a/src/redux/slices/friendsSlice.js b/src/redux/slices/friendsSlice.js
new file mode 100644
index 0000000..65722bb
--- /dev/null
+++ b/src/redux/slices/friendsSlice.js
@@ -0,0 +1,19 @@
+import { createSlice } from '@reduxjs/toolkit';
+
+const initialState = {
+ friendsList: [false, false, false, false],
+ recruteList: ['', '', '', ''],
+};
+
+export const friendsSlice = createSlice({
+ name: 'friends',
+ initialState,
+ reducers: {
+ setFriendsList: (state, action) => {
+ state.friendsList = action.payload;
+ },
+ setRecruteList: (state, action) => {
+ state.recruteList = action.payload;
+ },
+ },
+});
diff --git a/src/redux/slices/myPageSlice.js b/src/redux/slices/myPageSlice.js
new file mode 100644
index 0000000..005e6ab
--- /dev/null
+++ b/src/redux/slices/myPageSlice.js
@@ -0,0 +1,33 @@
+import { createSlice } from '@reduxjs/toolkit';
+import { inquireUserInfoAPI } from '../api/userInfoUpdateAPI';
+
+const initialState = {
+ username: '',
+ isLoading: false,
+ error: null,
+};
+
+const myPageSlice = createSlice({
+ name: 'mypage',
+ initialState,
+ reducers: {
+ getUserInfoStart(state) {
+ state.isLoading = true;
+ state.error = null;
+ },
+ getUserInfoSuccess(state, action) {
+ state.isLoading = false;
+ state.username = action.payload;
+ inquireUserInfoAPI(state.username);
+ state.error = null;
+ },
+ getUserInfoFailure(state, action) {
+ state.isLoading = false;
+ state.error = action.payload;
+ },
+ },
+});
+
+export const { getUserInfoStart, getUserInfoSuccess, getUserInfoFailure } = myPageSlice.actions;
+
+export default myPageSlice.reducer;
diff --git a/src/redux/slices/registerSlice.js b/src/redux/slices/registerSlice.js
new file mode 100644
index 0000000..9803d0b
--- /dev/null
+++ b/src/redux/slices/registerSlice.js
@@ -0,0 +1,48 @@
+import { createSlice } from '@reduxjs/toolkit';
+
+const initialState = {
+ userId: '',
+ email: '',
+ password: '',
+ passwordCheck: '',
+ nickname: '',
+ errors: {
+ email: { message: '아이디를 입력해주세요.', isError: false },
+ password: { message: '8~16자리의 비밀번호를 입력해주세요.', isError: false },
+ passwordCheck: { message: '확인을 위하여 위와 동일하게 입력해주세요.', isError: false },
+ nickname: { message: '', isError: false },
+ },
+};
+
+const registerSlice = createSlice({
+ name: 'signup',
+ initialState,
+ reducers: {
+ setEmail: (state, action) => {
+ state.email = action.payload;
+ },
+ setPassword: (state, action) => {
+ state.password = action.payload;
+ },
+ setPasswordCheck: (state, action) => {
+ state.passwordCheck = action.payload;
+ },
+ setNickname: (state, action) => {
+ state.nickname = action.payload;
+ },
+ setErrors: (state, action) => {
+ state.errors = action.payload;
+ },
+ resetFields: (state, action) => {
+ state.nickname = '';
+ state.email = '';
+ state.password = '';
+ state.passwordCheck = '';
+ state.errors = {};
+ },
+ },
+});
+
+export const { setEmail, setPassword, setPasswordCheck, setNickname, setErrors, resetFields } = registerSlice.actions;
+
+export default registerSlice.reducer;
diff --git a/src/redux/slices/selectedCategorySlice.js b/src/redux/slices/selectedCategorySlice.js
new file mode 100644
index 0000000..ed8c0a0
--- /dev/null
+++ b/src/redux/slices/selectedCategorySlice.js
@@ -0,0 +1,16 @@
+import { createSlice } from '@reduxjs/toolkit';
+import { TOTAL_SEARCHED_OUTPUT } from '../../static/constants';
+
+const initialState = {
+ category: TOTAL_SEARCHED_OUTPUT,
+};
+
+export const selectedCategorySlice = createSlice({
+ name: 'selectedCategory',
+ initialState,
+ reducers: {
+ setCategory: (state, action) => {
+ state.category = action.payload;
+ },
+ },
+});
diff --git a/src/redux/slices/signinSlice.js b/src/redux/slices/signinSlice.js
new file mode 100644
index 0000000..02b89ef
--- /dev/null
+++ b/src/redux/slices/signinSlice.js
@@ -0,0 +1,32 @@
+import { createSlice } from '@reduxjs/toolkit';
+
+const initialState = {
+ username: '',
+ password: '',
+ error: '',
+};
+
+const signinSlice = createSlice({
+ name: 'signip',
+ initialState,
+ reducers: {
+ setUsername: (state, action) => {
+ state.username = action.payload;
+ },
+ setPassword: (state, action) => {
+ state.password = action.payload;
+ },
+ setError: (state, action) => {
+ state.error = action.payload;
+ },
+ resetFields: (state, action) => {
+ state.username = '';
+ state.password = '';
+ state.errors = '';
+ },
+ },
+});
+
+export const { setUsername, setPassword, setError, resetFields } = signinSlice.actions;
+
+export default signinSlice.reducer;
diff --git a/src/redux/slices/userInfoChangeSlice.js b/src/redux/slices/userInfoChangeSlice.js
new file mode 100644
index 0000000..3971aeb
--- /dev/null
+++ b/src/redux/slices/userInfoChangeSlice.js
@@ -0,0 +1,86 @@
+import { createSlice } from '@reduxjs/toolkit';
+import { UserInfoUpdate } from '../api/userInfoUpdateAPI';
+
+const userInfoChangeSlice = createSlice({
+ name: 'userInfoChange',
+ initialState: {
+ newPassword: '',
+ newPasswordCheck: '',
+ currentNickname: '',
+ newNickname: '',
+ errors: {
+ newPassword: { message: '8~16자리의 비밀번호를 입력해주세요.', isError: false },
+ newPasswordCheck: { message: '', isError: false },
+ newNickname: { message: '', isError: false },
+ },
+ updateError: null,
+ },
+ reducers: {
+ setNewPassword: (state, action) => {
+ state.newPassword = action.payload;
+ },
+ setNewPasswordCheck: (state, action) => {
+ state.newPasswordCheck = action.payload;
+ },
+ getCurrentNickname: (state, action) => {
+ state.currentNickname = action.payload;
+ },
+ setNewNickname: (state, action) => {
+ state.newNickname = action.payload;
+ },
+ setErrors: (state, action) => {
+ state.errors = action.payload;
+ },
+ updatePasswordStart(state) {
+ state.isLoading = true;
+ state.updateError = null;
+ },
+ updatePasswordSuccess(state, action) {
+ state.isLoading = false;
+ state.currentPassword = action.payload;
+ UserInfoUpdate(state.currentPassword);
+ state.updateError = null;
+ },
+ updatePasswordFailure(state, action) {
+ state.isLoading = false;
+ state.updateError = action.payload;
+ },
+ updateNicknameStart(state) {
+ state.isLoading = true;
+ state.updateError = null;
+ },
+ updateNicknameSuccess(state, action) {
+ state.isLoading = false;
+ state.currentNickname = action.payload;
+ UserInfoUpdate(state.currentNickname);
+ state.updateError = null;
+ },
+ updateNicknameFailure(state, action) {
+ state.isLoading = false;
+ state.updateError = action.payload;
+ alert('닉네임을 입력해주세요.');
+ },
+ resetFields: (state) => {
+ state.userId = '';
+ state.newPassword = '';
+ state.newPasswordCheck = '';
+ state.errors = {};
+ },
+ },
+});
+
+export const {
+ setNewPasswordCheck,
+ setNewPassword,
+ getCurrentNickname,
+ setNewNickname,
+ setErrors,
+ updatePasswordStart,
+ updatePasswordSuccess,
+ updatePasswordFailure,
+ updateNicknameStart,
+ updateNicknameSuccess,
+ updateNicknameFailure,
+ resetFields,
+} = userInfoChangeSlice.actions;
+export default userInfoChangeSlice.reducer;
diff --git a/src/redux/slices/writingSlice.js b/src/redux/slices/writingSlice.js
new file mode 100644
index 0000000..4aa42b2
--- /dev/null
+++ b/src/redux/slices/writingSlice.js
@@ -0,0 +1,41 @@
+import { createSlice } from '@reduxjs/toolkit';
+
+const initialState = {
+ imageUrl: null,
+ title: '',
+ category: '전체',
+ totalAmount: 0,
+ maxPeople: 1,
+ textarea: '',
+};
+
+export const writingSlice = createSlice({
+ name: 'writing',
+ initialState,
+ reducers: {
+ setImageUrl: (state, action) => {
+ state.imageUrl = action.payload;
+ localStorage.setItem('imageUrl', JSON.stringify(state.imageUrl));
+ },
+ setTitle: (state, action) => {
+ state.title = action.payload;
+ localStorage.setItem('title', JSON.stringify(state.title));
+ },
+ setCategory: (state, action) => {
+ state.category = action.payload;
+ localStorage.setItem('category', JSON.stringify(state.category));
+ },
+ setTotalAmount: (state, action) => {
+ state.totalAmount = action.payload;
+ localStorage.setItem('totalAmount', JSON.stringify(state.totalAmount));
+ },
+ setMaxPeople: (state, action) => {
+ state.maxPeople = action.payload;
+ localStorage.setItem('maxPeople', JSON.stringify(state.maxPeople));
+ },
+ setTextarea: (state, action) => {
+ state.textarea = action.payload;
+ localStorage.setItem('textarea', JSON.stringify(state.textarea));
+ },
+ },
+});
diff --git a/src/redux/store.js b/src/redux/store.js
new file mode 100644
index 0000000..a4e4b33
--- /dev/null
+++ b/src/redux/store.js
@@ -0,0 +1,23 @@
+import { configureStore } from '@reduxjs/toolkit';
+import authSlice from './slices/authSlice';
+import registerSlice from './slices/registerSlice';
+import signinSlice from './slices/signinSlice';
+import userInfoChangeSlice from './slices/userInfoChangeSlice';
+import myPageSlice from './slices/myPageSlice';
+
+import { writingSlice } from './slices/writingSlice';
+import { selectedCategorySlice } from './slices/selectedCategorySlice';
+import { friendsSlice } from './slices/friendsSlice';
+
+export const store = configureStore({
+ reducer: {
+ auth: authSlice,
+ register: registerSlice,
+ signin: signinSlice,
+ myPage: myPageSlice,
+ userInfoChange: userInfoChangeSlice,
+ writing: writingSlice.reducer,
+ selectedCategory: selectedCategorySlice.reducer,
+ friends: friendsSlice.reducer,
+ },
+});
diff --git a/src/static/constants.js b/src/static/constants.js
new file mode 100644
index 0000000..f6a9157
--- /dev/null
+++ b/src/static/constants.js
@@ -0,0 +1,113 @@
+// PermissionPage
+const PERMISSION_FIRST_TITLE = '우리동네 함께사요 이용을 위해';
+const PERMISSION_SECOND_TITLE = '앱 권한을 허용해주세요';
+const SELECT_PERMISSION = '선택 권한';
+const TEMPORARY_SRC = 'https://img.freepik.com/free-psd/portrait-of-an-adorable-golden-retriever-puppy_53876-73975.jpg';
+const NOTIFICATION = '알림';
+const NOTIFICATION_DESCRIPTION = '채팅 등 앱 알림 수신 시 필요';
+const LOCATION = '위치';
+const LOCATION_DESCRIPTION = '지역별 상품 등록, 상품 검색 시 허용';
+const CAMERA = '카메라/앨범';
+const CAMERA_DESCRIPTION = '상품 등록, 채팅에서 사진 전송 시 사용';
+const MIKE = '마이크';
+const MIKE_DESCRIPTION = '채팅에서 동영상 촬영 시 사용';
+const STORAGE = '저장소';
+const STORAGE_DESCRIPTION = '사진 편집 및 저장 시 사용';
+const PERMISSION_MESSAGE =
+ '해당 기능을 사용할 때 권한 허용이 필요하며, 허용되지 않아도 해당 기능 외 서비스 이용이 가능합니다.';
+const CONFIRM = '확인';
+
+// RegisterLocationPage
+const SEARCH_LOCATION = '내 동네 이름(동,읍,면)으로 검색';
+const NEAR_LOCATION = '근처 동네';
+
+// RegisterLocationCompletePage
+const MOVE_TO_HOME = '홈으로 가기';
+const REGISTER_LOCATION_COMPLETE = '위치등록 완료';
+
+// SearchPage
+const ENTER_INPUT = '검색어를 입력하세요';
+const TOTAL_SEARCHED_OUTPUT = '전체';
+
+// WritingPage
+const ADD_IMAGE = '이미지 추가';
+const ARTICLE_TITLE = '글 제목';
+const CATEGORY = '카테고리';
+const TOTAL_AMOUNT = '총 가격 (ex. 35000)';
+const MAXIMUM_PEOPLE = '희망 인원 (최대 4명)';
+const PLEASE_WRITE_TEXT = '내용을 작성해주세요';
+const DONE = '완료';
+
+// CategoryListPage
+const SELECT = '선택';
+
+// MyPage
+const MY_PAGE = '마이 페이지';
+const SETTING_LOCATION = '내 동네 설정';
+const CHANGE_INFO = '내 정보 변경';
+const LOGOUT = '로그아웃';
+
+// PostsPage
+const JOIN_ALERT = '현재 게시물에 참여하시겠습니까?';
+const CANCEL = '취소';
+const BEFORE = ' 전';
+const SUM = '총 금액';
+const WON = '원';
+const DIVISION = '1/N';
+const ACTUAL_PAYMENT_AMOUNT = '실제 결제 금액';
+const JOIN = '참여하기';
+
+// ProfileEditPage
+const EDIT = '수정하기';
+
+// SearchedOutputList
+const TIMEOUT = 100;
+const ERROR_ALERT_MESSAGE = 'something went wrong';
+
+export {
+ PERMISSION_FIRST_TITLE,
+ PERMISSION_SECOND_TITLE,
+ SELECT_PERMISSION,
+ TEMPORARY_SRC,
+ NOTIFICATION,
+ NOTIFICATION_DESCRIPTION,
+ LOCATION,
+ LOCATION_DESCRIPTION,
+ CAMERA,
+ CAMERA_DESCRIPTION,
+ MIKE,
+ MIKE_DESCRIPTION,
+ STORAGE,
+ STORAGE_DESCRIPTION,
+ PERMISSION_MESSAGE,
+ CONFIRM,
+ SEARCH_LOCATION,
+ NEAR_LOCATION,
+ ENTER_INPUT,
+ TOTAL_SEARCHED_OUTPUT,
+ ADD_IMAGE,
+ ARTICLE_TITLE,
+ CATEGORY,
+ TOTAL_AMOUNT,
+ MAXIMUM_PEOPLE,
+ PLEASE_WRITE_TEXT,
+ DONE,
+ SELECT,
+ MY_PAGE,
+ SETTING_LOCATION,
+ CHANGE_INFO,
+ LOGOUT,
+ JOIN_ALERT,
+ CANCEL,
+ SUM,
+ WON,
+ BEFORE,
+ DIVISION,
+ ACTUAL_PAYMENT_AMOUNT,
+ JOIN,
+ EDIT,
+ MOVE_TO_HOME,
+ REGISTER_LOCATION_COMPLETE,
+ TIMEOUT,
+ ERROR_ALERT_MESSAGE,
+};
diff --git a/src/static/fonts/AppleSDGothicNeoB.ttf b/src/static/fonts/AppleSDGothicNeoB.ttf
new file mode 100644
index 0000000..ebd50e2
Binary files /dev/null and b/src/static/fonts/AppleSDGothicNeoB.ttf differ
diff --git a/src/static/fonts/AppleSDGothicNeoEB.ttf b/src/static/fonts/AppleSDGothicNeoEB.ttf
new file mode 100644
index 0000000..d293d59
Binary files /dev/null and b/src/static/fonts/AppleSDGothicNeoEB.ttf differ
diff --git a/src/static/fonts/AppleSDGothicNeoH.ttf b/src/static/fonts/AppleSDGothicNeoH.ttf
new file mode 100644
index 0000000..9a438b2
Binary files /dev/null and b/src/static/fonts/AppleSDGothicNeoH.ttf differ
diff --git a/src/static/fonts/AppleSDGothicNeoL.ttf b/src/static/fonts/AppleSDGothicNeoL.ttf
new file mode 100644
index 0000000..ec189fb
Binary files /dev/null and b/src/static/fonts/AppleSDGothicNeoL.ttf differ
diff --git a/src/static/fonts/AppleSDGothicNeoM.ttf b/src/static/fonts/AppleSDGothicNeoM.ttf
new file mode 100644
index 0000000..b6eed02
Binary files /dev/null and b/src/static/fonts/AppleSDGothicNeoM.ttf differ
diff --git a/src/static/fonts/AppleSDGothicNeoR.ttf b/src/static/fonts/AppleSDGothicNeoR.ttf
new file mode 100644
index 0000000..4ab04da
Binary files /dev/null and b/src/static/fonts/AppleSDGothicNeoR.ttf differ
diff --git a/src/static/fonts/AppleSDGothicNeoSB.ttf b/src/static/fonts/AppleSDGothicNeoSB.ttf
new file mode 100644
index 0000000..eb8017e
Binary files /dev/null and b/src/static/fonts/AppleSDGothicNeoSB.ttf differ
diff --git a/src/static/fonts/AppleSDGothicNeoT.ttf b/src/static/fonts/AppleSDGothicNeoT.ttf
new file mode 100644
index 0000000..9b137fd
Binary files /dev/null and b/src/static/fonts/AppleSDGothicNeoT.ttf differ
diff --git a/src/static/fonts/AppleSDGothicNeoUL.ttf b/src/static/fonts/AppleSDGothicNeoUL.ttf
new file mode 100644
index 0000000..7576e3d
Binary files /dev/null and b/src/static/fonts/AppleSDGothicNeoUL.ttf differ
diff --git a/src/styles/App.css b/src/styles/App.css
index b5c61c9..6781a3e 100644
--- a/src/styles/App.css
+++ b/src/styles/App.css
@@ -1,3 +1,27 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
+
+html {
+ height: 100vh;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background-color: #ddd;
+}
+
+body {
+ width: 390px;
+ height: 100vh;
+ background: #fff;
+}
+
+.page-wrapper {
+ width: 390px;
+ height: 844px;
+ position: absolute;
+ top: 50%;
+ transform: translateY(-50%);
+ overflow: hidden;
+ background: #fff;
+}
diff --git a/tailwind.config.js b/tailwind.config.js
index 0171921..6c1b2e9 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -4,6 +4,17 @@ module.exports = {
content: ['./src/**/*.{js,jsx,ts,tsx}'],
theme: {
extend: {},
+ colors: {
+ white: '#FFFFFF',
+ lightGray: '#F0F0F0',
+ hexGray: '#F2F2F2',
+ gray: '#D9D9D9',
+ darkGray: '#A4A4A4',
+ deepGray: '#9D9D9D',
+ smokeGray: '#6B6B6B',
+ mainColor: '#39B54A',
+ orange: '#FF6B00',
+ },
},
plugins: [],
};