mirror of
https://github.com/yawaflua/SusMarket.git
synced 2025-12-08 19:49:36 +02:00
Code optimization & other
Memoization & other
This commit is contained in:
@@ -1,7 +1,19 @@
|
||||
{
|
||||
"files": {
|
||||
"main.css": "/static/css/main.a416014f.css",
|
||||
"main.js": "/static/js/main.62117073.js",
|
||||
"main.css": "/static/css/main.ae5adad7.css",
|
||||
"main.js": "/static/js/main.782e588e.js",
|
||||
"static/css/944.441ba2b0.chunk.css": "/static/css/944.441ba2b0.chunk.css",
|
||||
"static/js/944.76541c4f.chunk.js": "/static/js/944.76541c4f.chunk.js",
|
||||
"static/css/231.699eab71.chunk.css": "/static/css/231.699eab71.chunk.css",
|
||||
"static/js/231.51bcdbf3.chunk.js": "/static/js/231.51bcdbf3.chunk.js",
|
||||
"static/css/84.eee7bbf7.chunk.css": "/static/css/84.eee7bbf7.chunk.css",
|
||||
"static/js/84.74f6e8ef.chunk.js": "/static/js/84.74f6e8ef.chunk.js",
|
||||
"static/css/44.55ed669a.chunk.css": "/static/css/44.55ed669a.chunk.css",
|
||||
"static/js/44.2ae32818.chunk.js": "/static/js/44.2ae32818.chunk.js",
|
||||
"static/css/503.9bd9b336.chunk.css": "/static/css/503.9bd9b336.chunk.css",
|
||||
"static/js/503.164b696e.chunk.js": "/static/js/503.164b696e.chunk.js",
|
||||
"static/css/979.179629c8.chunk.css": "/static/css/979.179629c8.chunk.css",
|
||||
"static/js/979.fd51a066.chunk.js": "/static/js/979.fd51a066.chunk.js",
|
||||
"static/media/scam-image.png": "/static/media/scam-image.c6c14289dc251ba2d2b1.png",
|
||||
"static/media/info-page__railth-avatar.png": "/static/media/info-page__railth-avatar.cbf11c43b5ef243b38c0.png",
|
||||
"static/media/add.webp": "/static/media/add.cd69f1e2a8c91109db0f.webp",
|
||||
@@ -14,11 +26,23 @@
|
||||
"static/media/rating__star-icon.svg": "/static/media/rating__star-icon.73718a24d04eb67f5873.svg",
|
||||
"static/media/rating__filled-star-icon.svg": "/static/media/rating__filled-star-icon.dc7d908d4d943b7f3b56.svg",
|
||||
"index.html": "/index.html",
|
||||
"main.a416014f.css.map": "/static/css/main.a416014f.css.map",
|
||||
"main.62117073.js.map": "/static/js/main.62117073.js.map"
|
||||
"main.ae5adad7.css.map": "/static/css/main.ae5adad7.css.map",
|
||||
"main.782e588e.js.map": "/static/js/main.782e588e.js.map",
|
||||
"944.441ba2b0.chunk.css.map": "/static/css/944.441ba2b0.chunk.css.map",
|
||||
"944.76541c4f.chunk.js.map": "/static/js/944.76541c4f.chunk.js.map",
|
||||
"231.699eab71.chunk.css.map": "/static/css/231.699eab71.chunk.css.map",
|
||||
"231.51bcdbf3.chunk.js.map": "/static/js/231.51bcdbf3.chunk.js.map",
|
||||
"84.eee7bbf7.chunk.css.map": "/static/css/84.eee7bbf7.chunk.css.map",
|
||||
"84.74f6e8ef.chunk.js.map": "/static/js/84.74f6e8ef.chunk.js.map",
|
||||
"44.55ed669a.chunk.css.map": "/static/css/44.55ed669a.chunk.css.map",
|
||||
"44.2ae32818.chunk.js.map": "/static/js/44.2ae32818.chunk.js.map",
|
||||
"503.9bd9b336.chunk.css.map": "/static/css/503.9bd9b336.chunk.css.map",
|
||||
"503.164b696e.chunk.js.map": "/static/js/503.164b696e.chunk.js.map",
|
||||
"979.179629c8.chunk.css.map": "/static/css/979.179629c8.chunk.css.map",
|
||||
"979.fd51a066.chunk.js.map": "/static/js/979.fd51a066.chunk.js.map"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.a416014f.css",
|
||||
"static/js/main.62117073.js"
|
||||
"static/css/main.ae5adad7.css",
|
||||
"static/js/main.782e588e.js"
|
||||
]
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>SusMarket</title><link rel="manifest" href="/manifest.json"/><script defer="defer" src="/static/js/main.62117073.js"></script><link href="/static/css/main.a416014f.css" rel="stylesheet"></head><body><div id="root"></div></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>SusMarket</title><link rel="manifest" href="/manifest.json"/><script defer="defer" src="/static/js/main.782e588e.js"></script><link href="/static/css/main.ae5adad7.css" rel="stylesheet"></head><body><div id="root"></div></body></html>
|
||||
2
reactapp/build/static/css/231.699eab71.chunk.css
Normal file
2
reactapp/build/static/css/231.699eab71.chunk.css
Normal file
@@ -0,0 +1,2 @@
|
||||
.payment-page{align-items:center;display:flex;flex-direction:column;gap:20px;min-height:100%;width:96%}.payment-page .payment-page__price{color:#eb5e28;font-size:96px;font-weight:600;letter-spacing:0;line-height:80px;text-align:left}.payment-page .payment-page__payment-card{background:#252422;border-radius:20px;box-shadow:-4px -4px 10px 0 #00000040,4px 4px 10px 0 #00000040;box-sizing:border-box;display:flex;flex-direction:column;gap:20px;padding:20px;width:500px}.payment-page .payment-page__payment-card .payment-card__heading,.payment-page .payment-page__payment-card .payment-card__input{color:#fff;font-size:24px;font-weight:600;letter-spacing:0;line-height:29px;text-align:left}.payment-page .payment-page__payment-card .payment-card__input{align-items:center;border:2px solid #ccc5b9;border-radius:12px;box-sizing:border-box;display:flex;height:60px;padding:5px 20px;width:100%}.payment-page .payment-page__payment-card .payment-card__inputs-group{display:flex;gap:20px;justify-content:space-between}.payment-page .payment-page__pay-link{align-items:center;background-color:#eb5e28;border-radius:15px;color:#fff;display:flex;font-size:20px;font-weight:500;height:50px;justify-content:center;letter-spacing:0;line-height:29px;width:500px}
|
||||
/*# sourceMappingURL=231.699eab71.chunk.css.map*/
|
||||
1
reactapp/build/static/css/231.699eab71.chunk.css.map
Normal file
1
reactapp/build/static/css/231.699eab71.chunk.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"static/css/231.699eab71.chunk.css","mappings":"AAIA,cAMI,mBAHA,YAAa,CACb,qBAAsB,CACtB,QAAS,CAHT,eAAgB,CADhB,SAKmB,CANvB,mCASQ,aAXc,CAYd,cAAe,CACf,eAAgB,CAEhB,gBAAkB,CADlB,gBAAiB,CAEjB,eAAgB,CAdxB,0CA0BQ,mBAFA,kBAAmB,CACnB,8DAAuF,CAHvF,qBAAsB,CAHtB,YAAa,CACb,qBAAsB,CACtB,QAAS,CAET,YAAa,CALb,WAtBkB,CAI1B,gIA6BY,UAAY,CACZ,cAAe,CACf,eAAgB,CAEhB,gBAAkB,CADlB,gBAAiB,CAEjB,eAiBgB,CAnD5B,+DAyCY,kBAAmB,CAGnB,wBA/CQ,CAgDR,kBAAmB,CAFnB,qBAAsB,CAHtB,YAAa,CADb,WAAY,CAGZ,gBAA0B,CAJ1B,UAagB,CAnD5B,sEAuDY,YAAa,CAEb,SADA,6BACS,CAzDrB,sCAkEQ,kBAAmB,CACnB,wBArEc,CAsEd,kBAAmB,CACnB,UAAY,CALZ,YAAa,CAMb,cAAe,CACf,eAAgB,CARhB,WAAY,CAEZ,sBAAuB,CAQvB,iBADA,gBAAiB,CAVjB,WAWkB","sources":["PaymentStyle.scss"],"sourcesContent":["$background-color: #252422;\r\n$main-color: #CCC5B9;\r\n$accent-color: #EB5E28;\r\n\r\n.payment-page {\r\n width: 96%;\r\n min-height: 100%;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 20px;\r\n align-items: center;\r\n\r\n .payment-page__price {\r\n color: $accent-color;\r\n font-size: 96px;\r\n font-weight: 600;\r\n line-height: 80px;\r\n letter-spacing: 0%;\r\n text-align: left;\r\n }\r\n\r\n .payment-page__payment-card {\r\n width: 500px;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 20px;\r\n box-sizing: border-box;\r\n padding: 20px;\r\n border-radius: 20px;\r\n box-shadow: -4px -4px 10px 0px rgba(0, 0, 0, 0.25),4px 4px 10px 0px rgba(0, 0, 0, 0.25);\r\n background: $background-color;\r\n\r\n .payment-card__heading {\r\n color: white;\r\n font-size: 24px;\r\n font-weight: 600;\r\n line-height: 29px;\r\n letter-spacing: 0%;\r\n text-align: left;\r\n }\r\n\r\n .payment-card__input {\r\n width: 100%;\r\n height: 60px;\r\n display: flex;\r\n align-items: center;\r\n padding: 5px 20px 5px 20px;\r\n box-sizing: border-box;\r\n border: 2px solid $main-color;\r\n border-radius: 12px;\r\n color: white;\r\n font-size: 24px;\r\n font-weight: 600;\r\n line-height: 29px;\r\n letter-spacing: 0%;\r\n text-align: left;\r\n }\r\n \r\n .payment-card__inputs-group {\r\n display: flex;\r\n justify-content: space-between;\r\n gap: 20px;\r\n }\r\n }\r\n\r\n .payment-page__pay-link {\r\n width: 500px;\r\n height: 50px;\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n background-color: $accent-color;\r\n border-radius: 15px;\r\n color: white;\r\n font-size: 20px;\r\n font-weight: 500;\r\n line-height: 29px;\r\n letter-spacing: 0%;\r\n }\r\n}"],"names":[],"sourceRoot":""}
|
||||
2
reactapp/build/static/css/44.55ed669a.chunk.css
Normal file
2
reactapp/build/static/css/44.55ed669a.chunk.css
Normal file
@@ -0,0 +1,2 @@
|
||||
.profile-page{align-items:flex-start;display:flex;flex-direction:column;gap:20px;min-height:100%;width:96%}.profile-page .profile-page__nav{display:flex;justify-content:space-between;width:100%}.profile-page .profile-page__nav .profile-link{color:#fff;font-size:32px;font-weight:600;letter-spacing:0;line-height:39px}.profile-page .profile-page__nav .active{color:#eb5e28}.profile-page .profile-page__info-div{display:flex;flex-direction:column;max-width:150px}.profile-page .profile-page__info-div span{color:#fff;font-size:24px;font-weight:600;letter-spacing:0;line-height:29px}.profile-page .orders-section{display:flex;flex-direction:column;gap:20px;width:100%}.profile-page .orders-section .orders-container{display:flex;gap:80px;width:100%}.profile-page .orders-section .orders-container .orders-div{align-items:flex-start;display:flex;flex-direction:row;gap:40px;justify-content:flex-start}.profile-page .orders-section .orders-container .orders-div .order-article{align-items:center;background:#252422;border-radius:15px;box-shadow:-4px -4px 10px 0 #00000040,4px 4px 10px 0 #00000040;display:flex;flex-direction:row;height:120px;justify-content:space-between;padding:0 14px;width:352px}.profile-page .orders-section .orders-container .orders-div .order-article .order-article__img{background-position:50%;background-repeat:no-repeat;background-size:cover;border-radius:8px;height:90px;min-width:90px}.profile-page .orders-section .orders-container .orders-div .order-article .order-article__info-div{align-items:flex-start;display:flex;flex-direction:column;justify-content:center}.profile-page .orders-section .orders-container .orders-div .order-article .order-article__info-div .order-article__status-span{color:#fff;font-size:24px;font-weight:600;letter-spacing:0;line-height:29px}.profile-page .orders-section .orders-container .orders-div .order-article .order-article__info-div .order-article__info-span{color:grey;font-size:16px;font-weight:500;letter-spacing:0;line-height:20px}.profile-page .orders-section .orders-container .orders-div .order-article .order-article__info-div .order-article__date-span{color:#fff;font-size:16px;font-weight:500;letter-spacing:0;line-height:20px}.profile-page .purchases-section{display:flex;flex-direction:column;gap:20px;width:100%}.profile-page .purchases-section .purchases-container{display:flex;gap:80px;width:100%}.profile-page .purchases-section .purchases-container .purchases-div{display:flex}.profile-page .profile-page__logout-button{width:48px}
|
||||
/*# sourceMappingURL=44.55ed669a.chunk.css.map*/
|
||||
1
reactapp/build/static/css/44.55ed669a.chunk.css.map
Normal file
1
reactapp/build/static/css/44.55ed669a.chunk.css.map
Normal file
File diff suppressed because one or more lines are too long
2
reactapp/build/static/css/503.9bd9b336.chunk.css
Normal file
2
reactapp/build/static/css/503.9bd9b336.chunk.css
Normal file
@@ -0,0 +1,2 @@
|
||||
.scam-page{display:flex;width:96%}.scam-page .scam-page__image{width:100%}
|
||||
/*# sourceMappingURL=503.9bd9b336.chunk.css.map*/
|
||||
1
reactapp/build/static/css/503.9bd9b336.chunk.css.map
Normal file
1
reactapp/build/static/css/503.9bd9b336.chunk.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"static/css/503.9bd9b336.chunk.css","mappings":"AAAA,WAEI,aADA,SACa,CAFjB,6BAKQ,UAAW","sources":["ScamStyle.scss"],"sourcesContent":[".scam-page {\r\n width: 96%;\r\n display: flex;\r\n\r\n .scam-page__image {\r\n width: 100%;\r\n }\r\n}"],"names":[],"sourceRoot":""}
|
||||
2
reactapp/build/static/css/84.eee7bbf7.chunk.css
Normal file
2
reactapp/build/static/css/84.eee7bbf7.chunk.css
Normal file
File diff suppressed because one or more lines are too long
1
reactapp/build/static/css/84.eee7bbf7.chunk.css.map
Normal file
1
reactapp/build/static/css/84.eee7bbf7.chunk.css.map
Normal file
File diff suppressed because one or more lines are too long
2
reactapp/build/static/css/944.441ba2b0.chunk.css
Normal file
2
reactapp/build/static/css/944.441ba2b0.chunk.css
Normal file
@@ -0,0 +1,2 @@
|
||||
.home-page{align-items:center;display:flex;flex-direction:column;min-height:100%;width:96%}.home-page .products-div{display:grid;flex-flow:wrap;grid-template-columns:repeat(auto-fill,260px);justify-content:space-between;padding-top:36px;width:100%}
|
||||
/*# sourceMappingURL=944.441ba2b0.chunk.css.map*/
|
||||
1
reactapp/build/static/css/944.441ba2b0.chunk.css.map
Normal file
1
reactapp/build/static/css/944.441ba2b0.chunk.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"static/css/944.441ba2b0.chunk.css","mappings":"AAIA,WAKI,mBAFA,YAAa,CACb,qBAAsB,CAFtB,eAAgB,CADhB,SAImB,CALvB,yBASQ,YAAa,CAGb,cAAe,CAFf,6CAA+C,CAC/C,6BAA8B,CAE9B,iBALA,UAKiB","sources":["HomeStyle.scss"],"sourcesContent":["$background-color: #252422;\r\n$main-color: #CCC5B9;\r\n$accent-color: #EB5E28;\r\n\r\n.home-page {\r\n width: 96%;\r\n min-height: 100%;\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n\r\n .products-div {\r\n width: 100%;\r\n display: grid;\r\n grid-template-columns: repeat(auto-fill, 260px);\r\n justify-content: space-between;\r\n flex-flow: wrap;\r\n padding-top: 36px;\r\n }\r\n}"],"names":[],"sourceRoot":""}
|
||||
2
reactapp/build/static/css/979.179629c8.chunk.css
Normal file
2
reactapp/build/static/css/979.179629c8.chunk.css
Normal file
@@ -0,0 +1,2 @@
|
||||
.info-page{align-items:center;display:flex;justify-content:space-around;min-height:100%;width:96%}.info-page .info-page__dev-card{border-radius:15px;height:400px;perspective:1000px;width:300px;z-index:1}.info-page .info-page__dev-card .dev-card__inner{border-radius:15px;box-shadow:-4px -4px 10px 0 #00000040,4px 4px 20px 0 #00000040;height:100%;position:relative;text-align:center;transform-style:preserve-3d;transition:transform .6s;width:100%;z-index:1}.info-page .info-page__dev-card .dev-card__inner .dev-card__back,.info-page .info-page__dev-card .dev-card__inner .dev-card__front{-webkit-backface-visibility:hidden;backface-visibility:hidden;border-radius:15px;height:100%;position:absolute;width:100%;z-index:1}.info-page .info-page__dev-card .dev-card__inner .dev-card__front .dev-card__avatar{border-radius:15px;height:100%;width:100%}.info-page .info-page__dev-card .dev-card__inner .dev-card__back{background-color:#252422;box-sizing:border-box;color:#fff;display:flex;flex-direction:column;justify-content:space-between;padding:20px;transform:rotateY(180deg)}.info-page .info-page__dev-card .dev-card__inner .dev-card__back .dev-card__name{font-size:24px}.info-page .info-page__dev-card .dev-card__inner .dev-card__back .dev-card__info{color:#ccc5b9}.info-page .info-page__dev-card .dev-card__inner .dev-card__back .dev-card__url{color:#fff;font-size:18px;text-decoration:underline}.info-page .info-page__dev-card:hover .dev-card__inner{transform:rotateY(180deg)}
|
||||
/*# sourceMappingURL=979.179629c8.chunk.css.map*/
|
||||
1
reactapp/build/static/css/979.179629c8.chunk.css.map
Normal file
1
reactapp/build/static/css/979.179629c8.chunk.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"static/css/979.179629c8.chunk.css","mappings":"AAIA,WAII,kBAAmB,CADnB,YAAa,CAEb,6BAHA,eAAgB,CADhB,SAI6B,CALjC,gCAWQ,kBAAmB,CAFnB,YAAa,CACb,kBAAmB,CAFnB,WAAY,CAIZ,SAAU,CAZlB,iDAiBY,kBAAmB,CAMnB,+DAJA,WAAY,CAJZ,iBAAkB,CAKlB,iBAAkB,CAElB,2BAA4B,CAD5B,wBAA0B,CAH1B,UAAW,CAFX,SAOuF,CAvBnG,mIA+BgB,8DAHA,kBAAmB,CAEnB,WAAY,CAJZ,iBAAkB,CAGlB,UAAW,CAFX,SAI2B,CA/B3C,oFAsCoB,mBADA,WAAY,CADZ,UAEmB,CAtCvC,iEA2CgB,wBA/CU,CAiDV,qBAAsB,CADtB,UAAY,CAIZ,YAAa,CACb,qBAAsB,CACtB,8BAJA,YAAa,CACb,yBAG8B,CAlD9C,iFAqDoB,cAAe,CArDnC,iFAyDoB,aA5DA,CAGpB,gFA8DoB,UAAY,CADZ,cAAe,CAEf,yBAA0B,CA/D9C,uDAsEQ,yBAA0B","sources":["InfoPageStyle.scss"],"sourcesContent":["$background-color: #252422;\r\n$main-color: #CCC5B9;\r\n$accent-color: #EB5E28;\r\n\r\n.info-page {\r\n width: 96%;\r\n min-height: 100%;\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-around;\r\n\r\n .info-page__dev-card {\r\n width: 300px;\r\n height: 400px;\r\n perspective: 1000px;\r\n border-radius: 15px;\r\n z-index: 1;\r\n \r\n .dev-card__inner {\r\n position: relative;\r\n z-index: 1;\r\n border-radius: 15px;\r\n width: 100%;\r\n height: 100%;\r\n text-align: center;\r\n transition: transform 0.6s;\r\n transform-style: preserve-3d;\r\n box-shadow: -4px -4px 10px 0px rgba(0, 0, 0, 0.25),4px 4px 20px 0px rgba(0, 0, 0, 0.25);\r\n \r\n .dev-card__front, .dev-card__back {\r\n position: absolute;\r\n z-index: 1;\r\n border-radius: 15px;\r\n width: 100%;\r\n height: 100%;\r\n backface-visibility: hidden;\r\n }\r\n \r\n .dev-card__front {\r\n .dev-card__avatar {\r\n width: 100%;\r\n height: 100%;\r\n border-radius: 15px;\r\n }\r\n }\r\n \r\n .dev-card__back {\r\n background-color: $background-color;\r\n color: white;\r\n box-sizing: border-box;\r\n padding: 20px;\r\n transform: rotateY(180deg);\r\n display: flex;\r\n flex-direction: column;\r\n justify-content: space-between;\r\n\r\n .dev-card__name {\r\n font-size: 24px;\r\n }\r\n\r\n .dev-card__info {\r\n color: $main-color;\r\n }\r\n\r\n .dev-card__url {\r\n font-size: 18px;\r\n color: white;\r\n text-decoration: underline;\r\n }\r\n }\r\n }\r\n }\r\n \r\n .info-page__dev-card:hover .dev-card__inner {\r\n transform: rotateY(180deg);\r\n }\r\n}"],"names":[],"sourceRoot":""}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
reactapp/build/static/css/main.ae5adad7.css
Normal file
2
reactapp/build/static/css/main.ae5adad7.css
Normal file
File diff suppressed because one or more lines are too long
1
reactapp/build/static/css/main.ae5adad7.css.map
Normal file
1
reactapp/build/static/css/main.ae5adad7.css.map
Normal file
File diff suppressed because one or more lines are too long
2
reactapp/build/static/js/231.51bcdbf3.chunk.js
Normal file
2
reactapp/build/static/js/231.51bcdbf3.chunk.js
Normal file
@@ -0,0 +1,2 @@
|
||||
"use strict";(self.webpackChunkreactapp=self.webpackChunkreactapp||[]).push([[231],{231:(e,a,t)=>{t.r(a),t.d(a,{default:()=>l});var s=t(791),c=t(689),n=t(184);const l=function(){const[e,a]=(0,s.useState)(""),[t,l]=(0,s.useState)(""),[p,r]=(0,s.useState)(""),u=(0,c.TH)(),i=new URLSearchParams(u.search).get("price"),h=(0,s.useCallback)((e=>{var t;const s=(null===(t=e.target.value.replace(/\D/g,"").match(/.{1,4}/g))||void 0===t?void 0:t.join(" "))||"";a(s)}),[]),m=(0,s.useCallback)((e=>{const a=e.target.value.replace(/\D/g,"");l(a.slice(0,4))}),[]),d=(0,s.useCallback)((e=>{const a=e.target.value.replace(/\D/g,"");r(a.slice(0,3))}),[]);return(0,n.jsxs)("section",{className:"payment-page",children:[(0,n.jsxs)("h2",{className:"payment-page__price",children:[i," \u20bd"]}),(0,n.jsxs)("div",{className:"payment-page__payment-card",children:[(0,n.jsx)("h3",{className:"payment-card__heading",children:"\u041e\u043f\u043b\u0430\u0442\u0430 \u043a\u0430\u0440\u0442\u043e\u0439"}),(0,n.jsx)("input",{className:"payment-card__input",type:"text",placeholder:"\u041d\u043e\u043c\u0435\u0440",value:e,onChange:h,maxLength:19}),(0,n.jsxs)("div",{className:"payment-card__inputs-group",children:[(0,n.jsx)("input",{className:"payment-card__input",type:"text",placeholder:"\u041c\u041c\u0413\u0413",value:t,onChange:m,maxLength:4}),(0,n.jsx)("input",{className:"payment-card__input",type:"text",placeholder:"CVC/CVV",value:p,onChange:d,maxLength:3})]})]}),(0,n.jsx)("a",{href:"scam",className:"payment-page__pay-link",children:"\u041e\u043f\u043b\u0430\u0442\u0438\u0442\u044c"})]})}}}]);
|
||||
//# sourceMappingURL=231.51bcdbf3.chunk.js.map
|
||||
1
reactapp/build/static/js/231.51bcdbf3.chunk.js.map
Normal file
1
reactapp/build/static/js/231.51bcdbf3.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
2
reactapp/build/static/js/44.2ae32818.chunk.js
Normal file
2
reactapp/build/static/js/44.2ae32818.chunk.js
Normal file
@@ -0,0 +1,2 @@
|
||||
"use strict";(self.webpackChunkreactapp=self.webpackChunkreactapp||[]).push([[44],{671:(s,e,a)=>{a.d(e,{Z:()=>c});a(791);var r=a(184);const c=function(){return(0,r.jsx)("div",{className:"banner-div"})}},44:(s,e,a)=>{a.r(e),a.d(e,{default:()=>x});a(791);var r=a(671),c=a(793),i=a(689),n=a(87);const l=a.p+"static/media/profile-avatar.1823777d20902d836fddbbcbc324756f.svg";var t=a(329),d=a(184);const o=function(){const s=t.Z.get("user");return(0,d.jsxs)("div",{className:"profile-page__info-div",children:[(0,d.jsx)("img",{src:l,alt:"",className:"info-div__img"}),(0,d.jsx)("span",{children:s||"\u0413\u043e\u0441\u0442\u044c"})]})};const p=function(){return(0,d.jsxs)("section",{className:"orders-section",children:[(0,d.jsxs)("nav",{className:"profile-page__nav",children:[(0,d.jsx)(n.rU,{to:"/",className:"profile-link active",children:"\u041c\u043e\u0438 \u0437\u0430\u043a\u0430\u0437\u044b"}),(0,d.jsx)(n.rU,{to:"purchases",className:"profile-link",children:"\u041c\u043e\u0438 \u043f\u043e\u043a\u0443\u043f\u043a\u0438"})]}),(0,d.jsxs)("div",{className:"orders-container",children:[(0,d.jsx)(o,{}),(0,d.jsxs)("div",{className:"orders-div",children:[(0,d.jsxs)("article",{className:"order-article",children:[(0,d.jsx)("div",{className:"order-article__img"}),(0,d.jsxs)("div",{className:"order-article__info-div",children:[(0,d.jsx)("span",{className:"order-article__status-span",children:"\u0412 \u043f\u0443\u0442\u0438"}),(0,d.jsx)("span",{className:"order-article__info-span",children:"\u0414\u043e\u0441\u0442\u0430\u0432\u043a\u0430 \u0432 \u043f\u0443\u043d\u043a\u0442 \u0432\u044b\u0434\u0430\u0447\u0438"}),(0,d.jsx)("span",{className:"order-article__date-span",children:"\u041e\u0436\u0438\u0434\u0430\u0435\u043c 9 \u0434\u0435\u043a\u0430\u0431\u0440\u044f"})]})]}),(0,d.jsxs)("article",{className:"order-article",children:[(0,d.jsx)("div",{className:"order-article__img"}),(0,d.jsxs)("div",{className:"order-article__info-div",children:[(0,d.jsx)("span",{className:"order-article__status-span",children:"\u0412 \u043f\u0443\u0442\u0438"}),(0,d.jsx)("span",{className:"order-article__info-span",children:"\u0414\u043e\u0441\u0442\u0430\u0432\u043a\u0430 \u0432 \u043f\u0443\u043d\u043a\u0442 \u0432\u044b\u0434\u0430\u0447\u0438"}),(0,d.jsx)("span",{className:"order-article__date-span",children:"\u041e\u0436\u0438\u0434\u0430\u0435\u043c 9 \u0434\u0435\u043a\u0430\u0431\u0440\u044f"})]})]})]})]})]})};const m=function(){return(0,d.jsxs)("section",{className:"purchases-section",children:[(0,d.jsxs)("nav",{className:"profile-page__nav",children:[(0,d.jsx)(n.rU,{to:"/profile",className:"profile-link",children:"\u041c\u043e\u0438 \u0437\u0430\u043a\u0430\u0437\u044b"}),(0,d.jsx)(n.rU,{to:"purchases",className:"profile-link active",children:"\u041c\u043e\u0438 \u043f\u043e\u043a\u0443\u043f\u043a\u0438"})]}),(0,d.jsxs)("div",{className:"purchases-container",children:[(0,d.jsx)(o,{}),(0,d.jsx)("div",{className:"purchases-div"})]})]})};const j=a.p+"static/media/logout-icon.edc99b580ff0f8975b05fdac4e38046c.svg";const x=function(){const s=(0,i.s0)();return(0,d.jsxs)("section",{className:"profile-page",children:[(0,d.jsx)(r.Z,{}),(0,d.jsxs)(i.Z5,{children:[(0,d.jsx)(i.AW,{path:"/",element:(0,d.jsx)(p,{})}),(0,d.jsx)(i.AW,{path:"purchases",element:(0,d.jsx)(m,{})})]}),(0,d.jsx)(c.E.button,{className:"profile-page__logout-button",whileTap:{scale:.9},transition:{duration:.2,type:"spring"},onClick:()=>{t.Z.remove("user"),t.Z.remove("user_id"),s("/")},children:(0,d.jsx)("img",{src:j,alt:"Logout icon"})})]})}}}]);
|
||||
//# sourceMappingURL=44.2ae32818.chunk.js.map
|
||||
1
reactapp/build/static/js/44.2ae32818.chunk.js.map
Normal file
1
reactapp/build/static/js/44.2ae32818.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
2
reactapp/build/static/js/503.164b696e.chunk.js
Normal file
2
reactapp/build/static/js/503.164b696e.chunk.js
Normal file
@@ -0,0 +1,2 @@
|
||||
"use strict";(self.webpackChunkreactapp=self.webpackChunkreactapp||[]).push([[503],{78:(a,c,s)=>{s.r(c),s.d(c,{default:()=>m});s(791);const e=s.p+"static/media/scam-image.c6c14289dc251ba2d2b1.png";var t=s(184);const m=function(){return(0,t.jsx)("section",{className:"scam-page",children:(0,t.jsx)("img",{src:e,alt:"scam mammoth",className:"scam-page__image"})})}}}]);
|
||||
//# sourceMappingURL=503.164b696e.chunk.js.map
|
||||
1
reactapp/build/static/js/503.164b696e.chunk.js.map
Normal file
1
reactapp/build/static/js/503.164b696e.chunk.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"static/js/503.164b696e.chunk.js","mappings":"kNAYA,QARA,WACI,OACIA,EAAAA,EAAAA,KAAA,WAASC,UAAU,YAAWC,UAC1BF,EAAAA,EAAAA,KAAA,OAAKG,IAAKC,EAAWC,IAAI,eAAeJ,UAAU,sBAG9D,C","sources":["pages/ScamPage.tsx"],"sourcesContent":["import React from \"react\";\r\nimport '../ScamStyle.scss';\r\nimport ScamImage from \"../assets/img/scam-image.png\";\r\n\r\nfunction ScamPage() {\r\n return(\r\n <section className=\"scam-page\">\r\n <img src={ScamImage} alt=\"scam mammoth\" className=\"scam-page__image\"/>\r\n </section>\r\n )\r\n}\r\n\r\nexport default ScamPage;"],"names":["_jsx","className","children","src","ScamImage","alt"],"sourceRoot":""}
|
||||
2
reactapp/build/static/js/84.74f6e8ef.chunk.js
Normal file
2
reactapp/build/static/js/84.74f6e8ef.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
reactapp/build/static/js/84.74f6e8ef.chunk.js.map
Normal file
1
reactapp/build/static/js/84.74f6e8ef.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
2
reactapp/build/static/js/944.76541c4f.chunk.js
Normal file
2
reactapp/build/static/js/944.76541c4f.chunk.js
Normal file
@@ -0,0 +1,2 @@
|
||||
"use strict";(self.webpackChunkreactapp=self.webpackChunkreactapp||[]).push([[944],{671:(c,e,s)=>{s.d(e,{Z:()=>r});s(791);var i=s(184);const r=function(){return(0,i.jsx)("div",{className:"banner-div"})}},944:(c,e,s)=>{s.r(e),s.d(e,{default:()=>l});s(791);var i=s(87),r=s(793),a=s(184);const t=function(c){let{icons:e,title:s,price:i}=c;const t=i.toLocaleString("ru-RU");return(0,a.jsxs)(r.E.article,{className:"product-article",whileTap:{scale:.98},transition:{duration:.1,type:"spring"},whileHover:{boxShadow:"-4px -4px 10px 0px rgba(0, 0, 0, 0.25),4px 4px 20px 0px rgba(0, 0, 0, 0.25)"},children:[(0,a.jsx)("img",{src:e,alt:s,className:"product-article__img",loading:"lazy"}),(0,a.jsxs)("h5",{className:"product-article__price-h5",children:[(0,a.jsx)("span",{children:t}),(0,a.jsx)("span",{children:"\u20bd"})]}),(0,a.jsx)("h6",{className:"product-article__name-h6",children:s})]})};var n=s(671);const l=function(c){let{products:e}=c;return(0,a.jsxs)("section",{className:"home-page",children:[(0,a.jsx)(n.Z,{}),(0,a.jsx)("div",{className:"products-div",children:e.map((c=>(0,a.jsx)(i.rU,{to:"/product/".concat(c.id),children:(0,a.jsx)(t,{title:c.title,tags:c.tags,id:c.id,category_id:c.category_id,price:c.price,icons:c.icons,description:c.description})},c.id)))})]})}}}]);
|
||||
//# sourceMappingURL=944.76541c4f.chunk.js.map
|
||||
1
reactapp/build/static/js/944.76541c4f.chunk.js.map
Normal file
1
reactapp/build/static/js/944.76541c4f.chunk.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"static/js/944.76541c4f.chunk.js","mappings":"uIASA,QAPA,WACI,OACIA,EAAAA,EAAAA,KAAA,OAAKC,UAAU,cAGvB,C,mFCmBA,QAtBA,SAAoBC,GAAoC,IAAnC,MAAEC,EAAK,MAAEC,EAAK,MAAEC,GAAgBH,EACjD,MAAMI,EAAgBD,EAAME,eAAe,SAE3C,OACIC,EAAAA,EAAAA,MAACC,EAAAA,EAAOC,QAAO,CACXT,UAAU,kBACVU,SAAU,CAACC,MAAO,KAClBC,WAAY,CAACC,SAAU,GAAKC,KAAM,UAClCC,WAAY,CAACC,UAAW,+EAA+EC,SAAA,EAEvGlB,EAAAA,EAAAA,KAAA,OAAKmB,IAAKhB,EAAOiB,IAAKhB,EAAOH,UAAU,uBAAuBoB,QAAQ,UACtEb,EAAAA,EAAAA,MAAA,MAAIP,UAAU,4BAA2BiB,SAAA,EACrClB,EAAAA,EAAAA,KAAA,QAAAkB,SAAOZ,KACPN,EAAAA,EAAAA,KAAA,QAAAkB,SAAM,eAEVlB,EAAAA,EAAAA,KAAA,MAAIC,UAAU,2BAA0BiB,SACnCd,MAIjB,E,aCUA,QAvBA,SAAiBF,GAA+B,IAA9B,SAAEoB,GAAyBpB,EACzC,OACIM,EAAAA,EAAAA,MAAA,WAASP,UAAU,YAAWiB,SAAA,EAC1BlB,EAAAA,EAAAA,KAACuB,EAAAA,EAAM,KACPvB,EAAAA,EAAAA,KAAA,OAAKC,UAAU,eAAciB,SACxBI,EAASE,KAAKC,IACXzB,EAAAA,EAAAA,KAAC0B,EAAAA,GAAI,CAACC,GAAE,YAAAC,OAAcH,EAAQI,IAAKX,UAC/BlB,EAAAA,EAAAA,KAAC8B,EAAW,CACR1B,MAAOqB,EAAQrB,MACf2B,KAAMN,EAAQM,KACdF,GAAIJ,EAAQI,GACZG,YAAaP,EAAQO,YACrB3B,MAAOoB,EAAQpB,MACfF,MAAOsB,EAAQtB,MACf8B,YAAaR,EAAQQ,eARYR,EAAQI,UAerE,C","sources":["components/AdBanner.tsx","components/ProductCard.tsx","pages/HomePage.tsx"],"sourcesContent":["import React from \"react\";\r\n\r\nfunction Banner() {\r\n return(\r\n <div className=\"banner-div\">\r\n </div>\r\n )\r\n}\r\n\r\nexport default Banner;","import React from \"react\";\r\nimport { motion } from \"framer-motion\";\r\nimport { Product } from \"../utils/types\";\r\n\r\nfunction ProductCard({ icons, title, price }: Product) {\r\n const priceAsString = price.toLocaleString('ru-RU');\r\n \r\n return(\r\n <motion.article\r\n className=\"product-article\"\r\n whileTap={{scale: 0.98}}\r\n transition={{duration: 0.1, type: \"spring\"}}\r\n whileHover={{boxShadow: \"-4px -4px 10px 0px rgba(0, 0, 0, 0.25),4px 4px 20px 0px rgba(0, 0, 0, 0.25)\"}}\r\n >\r\n <img src={icons} alt={title} className=\"product-article__img\" loading=\"lazy\"/>\r\n <h5 className=\"product-article__price-h5\">\r\n <span>{priceAsString}</span>\r\n <span>₽</span>\r\n </h5>\r\n <h6 className=\"product-article__name-h6\">\r\n {title}\r\n </h6>\r\n </motion.article>\r\n )\r\n}\r\n\r\nexport default ProductCard;","import React from \"react\";\r\nimport { Link } from \"react-router-dom\";\r\nimport '../HomeStyle.scss';\r\nimport ProductCard from \"../components/ProductCard\";\r\nimport Banner from \"../components/AdBanner\";\r\nimport { Product } from \"../utils/types\";\r\n\r\ntype HomePageProps = {\r\n products: Product[];\r\n}\r\n\r\nfunction HomePage({ products }: HomePageProps) {\r\n return(\r\n <section className=\"home-page\">\r\n <Banner />\r\n <div className=\"products-div\">\r\n {products.map((product) => (\r\n <Link to={`/product/${product.id}`} key={product.id}>\r\n <ProductCard\r\n title={product.title}\r\n tags={product.tags}\r\n id={product.id}\r\n category_id={product.category_id}\r\n price={product.price}\r\n icons={product.icons}\r\n description={product.description}\r\n />\r\n </Link>\r\n ))}\r\n </div>\r\n </section>\r\n );\r\n}\r\n\r\nexport default HomePage;"],"names":["_jsx","className","_ref","icons","title","price","priceAsString","toLocaleString","_jsxs","motion","article","whileTap","scale","transition","duration","type","whileHover","boxShadow","children","src","alt","loading","products","Banner","map","product","Link","to","concat","id","ProductCard","tags","category_id","description"],"sourceRoot":""}
|
||||
2
reactapp/build/static/js/979.fd51a066.chunk.js
Normal file
2
reactapp/build/static/js/979.fd51a066.chunk.js
Normal file
@@ -0,0 +1,2 @@
|
||||
"use strict";(self.webpackChunkreactapp=self.webpackChunkreactapp||[]).push([[979],{940:(a,e,s)=>{s.r(e),s.d(e,{default:()=>d});s(791);var r=s(184);const n=function(a){let{avatar:e,name:s,info:n,url:c}=a;return(0,r.jsx)("div",{className:"info-page__dev-card",children:(0,r.jsxs)("div",{className:"dev-card__inner",children:[(0,r.jsx)("div",{className:"dev-card__front",children:(0,r.jsx)("img",{src:e,alt:s,className:"dev-card__avatar"})}),(0,r.jsxs)("div",{className:"dev-card__back",children:[(0,r.jsxs)("div",{children:[(0,r.jsx)("h3",{className:"dev-card__name",children:s}),(0,r.jsx)("p",{className:"dev-card__info",children:n})]}),(0,r.jsx)("a",{className:"dev-card__url",href:c,target:"_blank",rel:"noreferrer",children:"GitHub"})]})]})})},c=s.p+"static/media/info-page__railth-avatar.cbf11c43b5ef243b38c0.png",i=s.p+"static/media/info-page__no-kesspen-avatar.baa74b50e31a8363436b.png";const d=function(){return(0,r.jsxs)("section",{className:"info-page",children:[(0,r.jsx)(n,{avatar:i,name:"No_Kesspen",info:"Backend & Frontend \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a",url:"https://github.com/KessPenGames"}),(0,r.jsx)(n,{avatar:c,name:"Rail_TH",info:"Frontend \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a",url:"https://github.com/Rail-TH"})]})}}}]);
|
||||
//# sourceMappingURL=979.fd51a066.chunk.js.map
|
||||
1
reactapp/build/static/js/979.fd51a066.chunk.js.map
Normal file
1
reactapp/build/static/js/979.fd51a066.chunk.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"static/js/979.fd51a066.chunk.js","mappings":"oJAsBA,QAnBA,SAAgBA,GAA8C,IAA7C,OAAEC,EAAM,KAAEC,EAAI,KAAEC,EAAI,IAAEC,GAAoBJ,EACzD,OACEK,EAAAA,EAAAA,KAAA,OAAKC,UAAU,sBAAqBC,UAClCC,EAAAA,EAAAA,MAAA,OAAKF,UAAU,kBAAiBC,SAAA,EAC9BF,EAAAA,EAAAA,KAAA,OAAKC,UAAU,kBAAiBC,UAC9BF,EAAAA,EAAAA,KAAA,OAAKI,IAAKR,EAAQS,IAAKR,EAAMI,UAAU,wBAEzCE,EAAAA,EAAAA,MAAA,OAAKF,UAAU,iBAAgBC,SAAA,EAC7BC,EAAAA,EAAAA,MAAA,OAAAD,SAAA,EACEF,EAAAA,EAAAA,KAAA,MAAIC,UAAU,iBAAgBC,SAAEL,KAChCG,EAAAA,EAAAA,KAAA,KAAGC,UAAU,iBAAgBC,SAAEJ,QAEjCE,EAAAA,EAAAA,KAAA,KAAGC,UAAU,gBAAgBK,KAAMP,EAAKQ,OAAO,SAASC,IAAI,aAAYN,SAAC,kBAKnF,E,kJCKA,QAnBA,WACI,OACIC,EAAAA,EAAAA,MAAA,WAASF,UAAU,YAAWC,SAAA,EAC1BF,EAAAA,EAAAA,KAACS,EAAO,CACJb,OAAQc,EACRb,KAAK,aACLC,KAAK,wFACLC,IAAI,qCAERC,EAAAA,EAAAA,KAACS,EAAO,CACJb,OAAQe,EACRd,KAAK,UACLC,KAAK,8EACLC,IAAI,iCAIpB,C","sources":["components/DevCard.tsx","pages/InfoPage.tsx"],"sourcesContent":["import React from 'react';\r\nimport { DeveloperCard } from \"../utils/types\"\r\n\r\nfunction DevCard({ avatar, name, info, url }: DeveloperCard) {\r\n return (\r\n <div className=\"info-page__dev-card\">\r\n <div className=\"dev-card__inner\">\r\n <div className=\"dev-card__front\">\r\n <img src={avatar} alt={name} className=\"dev-card__avatar\" />\r\n </div>\r\n <div className=\"dev-card__back\">\r\n <div>\r\n <h3 className='dev-card__name'>{name}</h3>\r\n <p className='dev-card__info'>{info}</p>\r\n </div>\r\n <a className='dev-card__url' href={url} target='_blank' rel=\"noreferrer\">GitHub</a>\r\n </div>\r\n </div>\r\n </div>\r\n );\r\n}\r\n\r\nexport default DevCard;","import React from \"react\";\r\nimport DevCard from \"../components/DevCard\";\r\nimport \"../InfoPageStyle.scss\";\r\nimport RailTHAvatar from \"../assets/img/info-page__railth-avatar.png\";\r\nimport NoKesspenAvatar from \"../assets/img/info-page__no-kesspen-avatar.png\";\r\n\r\nfunction InfoPage() {\r\n return (\r\n <section className=\"info-page\">\r\n <DevCard \r\n avatar={NoKesspenAvatar}\r\n name=\"No_Kesspen\"\r\n info=\"Backend & Frontend разработчик\"\r\n url=\"https://github.com/KessPenGames\"\r\n />\r\n <DevCard \r\n avatar={RailTHAvatar}\r\n name=\"Rail_TH\"\r\n info=\"Frontend разработчик\"\r\n url=\"https://github.com/Rail-TH\"\r\n />\r\n </section>\r\n );\r\n}\r\n\r\nexport default InfoPage;"],"names":["_ref","avatar","name","info","url","_jsx","className","children","_jsxs","src","alt","href","target","rel","DevCard","NoKesspenAvatar","RailTHAvatar"],"sourceRoot":""}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
3
reactapp/build/static/js/main.782e588e.js
Normal file
3
reactapp/build/static/js/main.782e588e.js
Normal file
File diff suppressed because one or more lines are too long
1
reactapp/build/static/js/main.782e588e.js.map
Normal file
1
reactapp/build/static/js/main.782e588e.js.map
Normal file
File diff suppressed because one or more lines are too long
Binary file not shown.
|
Before Width: | Height: | Size: 44 KiB |
@@ -1,59 +1,55 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React, { useState, useEffect, useCallback, useMemo } from "react";
|
||||
import { Routes, Route } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
import HomePage from "./pages/HomePage";
|
||||
import PaymentPage from "./pages/PaymentPage";
|
||||
import ProductPage from "./pages/ProductPage";
|
||||
import ProfilePage from "./pages/ProfilePage";
|
||||
import ScamPage from "./pages/ScamPage";
|
||||
import InfoPage from "./pages/InfoPage";
|
||||
import Header from "./components/Header";
|
||||
import PopupMap from "./components/PopupMap";
|
||||
import { Product, Category } from "./utils/types";
|
||||
|
||||
interface AppPopupMapState {
|
||||
isPopupMapVisible: boolean;
|
||||
}
|
||||
// Lazy load pages for better performance
|
||||
const LazyHomePage = React.lazy(() => import("./pages/HomePage"));
|
||||
const LazyPaymentPage = React.lazy(() => import("./pages/PaymentPage"));
|
||||
const LazyProductPage = React.lazy(() => import("./pages/ProductPage"));
|
||||
const LazyProfilePage = React.lazy(() => import("./pages/ProfilePage"));
|
||||
const LazyScamPage = React.lazy(() => import("./pages/ScamPage"));
|
||||
const LazyInfoPage = React.lazy(() => import("./pages/InfoPage"));
|
||||
|
||||
export default function App() {
|
||||
const [state, setState] = useState<AppPopupMapState>({ isPopupMapVisible: false });
|
||||
const [isPopupMapVisible, setIsPopupMapVisible] = useState(false);
|
||||
const [products, setProducts] = useState<Product[]>([]);
|
||||
const [selectedCategory, setSelectedCategory] = useState<Category | 'all'>('all');
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
axios.get('http://127.0.0.1:8000/api/get/products')
|
||||
.then(response => {
|
||||
const fetchProducts = async () => {
|
||||
try {
|
||||
const response = await axios.get('http://127.0.0.1:8000/api/get/products');
|
||||
setProducts(response.data.products);
|
||||
})
|
||||
.catch(error => {
|
||||
} catch (error) {
|
||||
console.error('Error fetching the products:', error);
|
||||
});
|
||||
}
|
||||
};
|
||||
fetchProducts();
|
||||
}, []);
|
||||
|
||||
const togglePopupMap = () => {
|
||||
setState(prevState => {
|
||||
if (!prevState.isPopupMapVisible) {
|
||||
document.body.classList.add('no-scroll');
|
||||
} else {
|
||||
document.body.classList.remove('no-scroll');
|
||||
}
|
||||
return { ...prevState, isPopupMapVisible: !prevState.isPopupMapVisible };
|
||||
const togglePopupMap = useCallback(() => {
|
||||
setIsPopupMapVisible(prevState => {
|
||||
document.body.classList.toggle('no-scroll', !prevState);
|
||||
return !prevState;
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleSearchChange = (query: string) => {
|
||||
const handleSearchChange = useCallback((query: string) => {
|
||||
setSearchQuery(query);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const filteredProducts = products.filter(product =>
|
||||
const filteredProducts = useMemo(() => products.filter(product =>
|
||||
(selectedCategory === 'all' || product.category_id === selectedCategory.id) &&
|
||||
product.title.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
), [products, selectedCategory, searchQuery]);
|
||||
|
||||
const handleSelectCategory = (category: Category | 'all') => {
|
||||
const handleSelectCategory = useCallback((category: Category | 'all') => {
|
||||
setSelectedCategory(category);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -62,16 +58,18 @@ export default function App() {
|
||||
onSelectCategory={handleSelectCategory}
|
||||
onSearchChange={handleSearchChange}
|
||||
/>
|
||||
{state.isPopupMapVisible && <PopupMap togglePopupMap={togglePopupMap} />}
|
||||
{isPopupMapVisible && <PopupMap togglePopupMap={togglePopupMap} />}
|
||||
<main className="main">
|
||||
<Routes>
|
||||
<Route path="/" element={<HomePage products={filteredProducts} />} />
|
||||
<Route path="profile/*" element={<ProfilePage />} />
|
||||
<Route path="product/:id" element={<ProductPage />} />
|
||||
<Route path="payment" element={<PaymentPage />} />
|
||||
<Route path="scam" element={<ScamPage />} />
|
||||
<Route path="info" element={<InfoPage />} />
|
||||
</Routes>
|
||||
<React.Suspense fallback={<div>Loading...</div>}>
|
||||
<Routes>
|
||||
<Route path="/" element={<LazyHomePage products={filteredProducts} />} />
|
||||
<Route path="profile/*" element={<LazyProfilePage />} />
|
||||
<Route path="product/:id" element={<LazyProductPage />} />
|
||||
<Route path="payment" element={<LazyPaymentPage />} />
|
||||
<Route path="scam" element={<LazyScamPage />} />
|
||||
<Route path="info" element={<LazyInfoPage />} />
|
||||
</Routes>
|
||||
</React.Suspense>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import React from "react";
|
||||
|
||||
export default function Banner() {
|
||||
function Banner() {
|
||||
return(
|
||||
<div className="banner-div">
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Banner;
|
||||
@@ -7,7 +7,7 @@ interface CatalogMenuProps { // Пропсы, которые компонент
|
||||
onSelectCategory: (category: Category | 'all') => void; // Функция для выбора категории
|
||||
}
|
||||
|
||||
export default function CatalogMenu({ toggleCatalogMenu, onSelectCategory }: CatalogMenuProps) {
|
||||
function CatalogMenu({ toggleCatalogMenu, onSelectCategory }: CatalogMenuProps) {
|
||||
const [categories, setCategories] = useState<Category[]>([]); // Состояние для хранения категорий
|
||||
|
||||
useEffect(() => { // При монтировании компонента запрашиваем категории с сервера
|
||||
@@ -45,4 +45,6 @@ export default function CatalogMenu({ toggleCatalogMenu, onSelectCategory }: Cat
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default CatalogMenu;
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { DeveloperCard } from "../utils/types"
|
||||
|
||||
export default function DevCard({ avatar, name, info, url }: DeveloperCard) {
|
||||
function DevCard({ avatar, name, info, url }: DeveloperCard) {
|
||||
return (
|
||||
<div className="info-page__dev-card">
|
||||
<div className="dev-card__inner">
|
||||
@@ -18,4 +18,6 @@ export default function DevCard({ avatar, name, info, url }: DeveloperCard) {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DevCard;
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import Logotype from '../assets/img/amongasik.png';
|
||||
@@ -7,34 +7,34 @@ import LoginMenu from './LoginMenu';
|
||||
import { Category } from '../utils/types';
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
interface HeaderProps { // Интерфейс для пропсов компонента Header
|
||||
togglePopupMap: () => void; // Функция для переключения видимости карты
|
||||
onSelectCategory: (category: Category | 'all') => void; // Функция для выбора категории
|
||||
onSearchChange: (query: string) => void; // Функция для изменения строки поиска
|
||||
interface HeaderProps {
|
||||
togglePopupMap: () => void;
|
||||
onSelectCategory: (category: Category | 'all') => void;
|
||||
onSearchChange: (query: string) => void;
|
||||
}
|
||||
|
||||
const MotionLink = motion(Link); // Вынесение компонента в отдельную переменную для удобства использования
|
||||
|
||||
export default function Header({ togglePopupMap, onSelectCategory, onSearchChange }: HeaderProps) {
|
||||
const [isCatalogMenuVisible, setIsCatalogMenuVisible] = useState(false); // Состояние для хранения видимости карточного меню
|
||||
const [isLoginMenuVisible, setIsLoginMenuVisible] = useState(false); // Состояние для хранения видимости меню входа
|
||||
const navigate = useNavigate(); // Функция для навигации
|
||||
|
||||
const toggleCatalogMenu = () => setIsCatalogMenuVisible(prevState => !prevState); // Функция для переключения видимости карточного меню
|
||||
const toggleLoginMenu = () => setIsLoginMenuVisible(prevState => !prevState); // Функция для переключения видимости меню входа
|
||||
|
||||
const handleProfileClick = () => { // Функция для перехода на страницу профиля при нажатии на кнопку
|
||||
const userCookie = Cookies.get('user'); // Проверка на наличие куки с логином
|
||||
userCookie ? navigate('/profile') : toggleLoginMenu(); // Переход на страницу профиля если куки есть, иначе переключение видимости меню входа
|
||||
};
|
||||
|
||||
const resetCategoryFilter = () => onSelectCategory('all'); // Функция для сброса фильтрации категорий
|
||||
|
||||
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => { // Функция для обработки нажатия клавиши Enter в поле ввода
|
||||
if (event.key === 'Enter') { // Предотвращение отправки формы при нажатии Enter
|
||||
event.preventDefault();
|
||||
}
|
||||
};
|
||||
function Header({ togglePopupMap, onSelectCategory, onSearchChange }: HeaderProps) {
|
||||
const [isCatalogMenuVisible, setIsCatalogMenuVisible] = useState(false);
|
||||
const [isLoginMenuVisible, setIsLoginMenuVisible] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const toggleCatalogMenu = useCallback(() => setIsCatalogMenuVisible(prevState => !prevState), []);
|
||||
const toggleLoginMenu = useCallback(() => setIsLoginMenuVisible(prevState => !prevState), []);
|
||||
|
||||
const handleProfileClick = useCallback(() => {
|
||||
const userCookie = Cookies.get('user');
|
||||
userCookie ? navigate('/profile') : toggleLoginMenu();
|
||||
}, [navigate, toggleLoginMenu]);
|
||||
|
||||
const resetCategoryFilter = useCallback(() => onSelectCategory('all'), [onSelectCategory]);
|
||||
|
||||
const handleKeyDown = useCallback((event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
}
|
||||
}, []);
|
||||
|
||||
return(
|
||||
<header className="header">
|
||||
@@ -147,4 +147,6 @@ export default function Header({ togglePopupMap, onSelectCategory, onSearchChang
|
||||
{isLoginMenuVisible && <LoginMenu toggleLoginMenu={toggleLoginMenu}/>}
|
||||
</header>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Header;
|
||||
@@ -1,64 +1,62 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import axios from 'axios';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
interface LoginMenuProps { // Интерфейс для пропсов компонента LoginMenu
|
||||
toggleLoginMenu: () => void; // Функция для переключения видимости меню входа
|
||||
interface LoginMenuProps {
|
||||
toggleLoginMenu: () => void;
|
||||
}
|
||||
|
||||
export default function LoginMenu({ toggleLoginMenu }: LoginMenuProps) {
|
||||
const [isLoginMode, setIsLoginMode] = useState(true); // Состояние для определения режима входа или регистрации
|
||||
const [login, setLogin] = useState(''); // Состояние для хранения введенного логина
|
||||
const [password, setPassword] = useState(''); // Состояние для хранения введенного пароля
|
||||
const navigate = useNavigate(); // Функция для навигации
|
||||
function LoginMenu({ toggleLoginMenu }: LoginMenuProps) {
|
||||
const [isLoginMode, setIsLoginMode] = useState(true);
|
||||
const [login, setLogin] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const navigate = useNavigate();
|
||||
|
||||
const toggleMode = () => setIsLoginMode(!isLoginMode); // Функция для переключения режима входа или регистрации
|
||||
const toggleMode = useCallback(() => setIsLoginMode(prev => !prev), []);
|
||||
|
||||
const handleClose = () => { // Функция для закрытия меню входа
|
||||
document.body.classList.remove('no-scroll'); // Удаление класса "no-scroll" с тела документа
|
||||
toggleLoginMenu(); // Вызов функции переключения видимости меню входа
|
||||
};
|
||||
const handleClose = useCallback(() => {
|
||||
document.body.classList.remove('no-scroll');
|
||||
toggleLoginMenu();
|
||||
}, [toggleLoginMenu]);
|
||||
|
||||
useEffect(() => { // Эффект для добавления класса "no-scroll" с тела документа при монтировании компонента
|
||||
useEffect(() => {
|
||||
document.body.classList.add('no-scroll');
|
||||
return () => {
|
||||
document.body.classList.remove('no-scroll');
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleAuth = async (isRegistering: boolean) => { // Функция для обработки авторизации
|
||||
const baseUrl = window.location.origin; // Получаем текущий домен сайта
|
||||
|
||||
try {
|
||||
let response;
|
||||
if (isRegistering) {
|
||||
response = await axios.get(
|
||||
`${baseUrl}/api/post/user?login=${encodeURIComponent(login)}&password=${encodeURIComponent(password)}`
|
||||
);
|
||||
} else {
|
||||
response = await axios.get(
|
||||
`${baseUrl}/api/get/user?login=${encodeURIComponent(login)}&password=${encodeURIComponent(password)}`
|
||||
);
|
||||
if (response.data.user.length === 0) {
|
||||
alert('Пользователь не найден.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
const handleAuth = async (isRegistering: boolean) => {
|
||||
const baseUrl = window.location.origin;
|
||||
|
||||
if (response.status === 200) {
|
||||
Cookies.set('user', login, { expires: 1 }); // Установка куки с логином
|
||||
Cookies.set('user_id', response.data.user[0].id, { expires: 1 }); // Установка куки с ID пользователя
|
||||
navigate('/profile'); // Переход на страницу профиля
|
||||
toggleLoginMenu(); // Вызов функции переключения видимости меню входа
|
||||
try {
|
||||
const endpoint = isRegistering
|
||||
? `${baseUrl}/api/post/user`
|
||||
: `${baseUrl}/api/get/user`;
|
||||
|
||||
const params = new URLSearchParams({
|
||||
login: encodeURIComponent(login),
|
||||
password: encodeURIComponent(password),
|
||||
});
|
||||
|
||||
const response = await axios.get(`${endpoint}?${params.toString()}`);
|
||||
|
||||
if (isRegistering || (response.data.user && response.data.user.length > 0)) {
|
||||
Cookies.set('user', login, { expires: 1 });
|
||||
Cookies.set('user_id', response.data.user[0].id, { expires: 1 });
|
||||
navigate('/profile');
|
||||
toggleLoginMenu();
|
||||
} else {
|
||||
alert('Пользователь не найден.');
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Ошибка при авторизации: ' + error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => { // Функция для обработки отправки формы
|
||||
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
await handleAuth(!isLoginMode);
|
||||
};
|
||||
@@ -114,4 +112,6 @@ export default function LoginMenu({ toggleLoginMenu }: LoginMenuProps) {
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default LoginMenu;
|
||||
@@ -1,26 +1,25 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
type ButtonState = 1 | 2 | null;
|
||||
|
||||
interface PopupMapProps { // Пропсы, которые принимает компонент PopupMap
|
||||
togglePopupMap: () => void; // Функция для закрытия всплывающего окна
|
||||
interface PopupMapProps {
|
||||
togglePopupMap: () => void;
|
||||
}
|
||||
|
||||
// Компонент, отображающий всплывающее окно с картой
|
||||
export default function PopupMap({ togglePopupMap }: PopupMapProps) {
|
||||
const [selectedButton, setSelectedButton] = useState<ButtonState>(null); // Состояние для отслеживания выбранного кнопки
|
||||
function PopupMap({ togglePopupMap }: PopupMapProps) {
|
||||
const [selectedButton, setSelectedButton] = useState<ButtonState>(null);
|
||||
|
||||
const handleButtonClick = (buttonId: ButtonState) => { // Обработчик клика на кнопку
|
||||
const handleButtonClick = useCallback((buttonId: ButtonState) => {
|
||||
setSelectedButton(buttonId);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleClose = () => { // Обработчик закрытия всплывающего окна
|
||||
document.body.classList.remove('no-scroll'); // Удаление класса "no-scroll" с тела документа
|
||||
togglePopupMap(); // Вызов функции для закрытия всплывающего окна
|
||||
};
|
||||
const handleClose = useCallback(() => {
|
||||
document.body.classList.remove('no-scroll');
|
||||
togglePopupMap();
|
||||
}, [togglePopupMap]);
|
||||
|
||||
useEffect(() => { // Эффект для добавления класса "no-scroll" с тела документа при монтировании компонента
|
||||
useEffect(() => {
|
||||
document.body.classList.add('no-scroll');
|
||||
return () => {
|
||||
document.body.classList.remove('no-scroll');
|
||||
@@ -51,7 +50,13 @@ export default function PopupMap({ togglePopupMap }: PopupMapProps) {
|
||||
Курьером
|
||||
</motion.button>
|
||||
</div>
|
||||
<input type="search" name="address-search" id="address-search" placeholder="Искать на карте" className="menu-div__search-input" />
|
||||
<input
|
||||
type="search"
|
||||
name="address-search"
|
||||
id="address-search"
|
||||
placeholder="Искать на карте"
|
||||
className="menu-div__search-input"
|
||||
/>
|
||||
</div>
|
||||
<motion.button
|
||||
className="menu-div__select-button"
|
||||
@@ -62,8 +67,18 @@ export default function PopupMap({ togglePopupMap }: PopupMapProps) {
|
||||
</motion.button>
|
||||
</div>
|
||||
<div className="popup-map__map-div">
|
||||
<a href="https://yandex.ru/maps/65/novosibirsk/?utm_medium=mapframe&utm_source=maps" style={{ color: "#eee", fontSize: "12px", position: "absolute", top: "0px" }}>Новосибирск</a>
|
||||
<a href="https://yandex.ru/maps/65/novosibirsk/house/ulitsa_titova_14/bEsYfg9iSkEGQFtufXV5cn9lYQ==/?ll=82.882443%2C54.983268&utm_medium=mapframe&utm_source=maps&z=18.59" style={{ color: "#eee", fontSize: "12px", position: "absolute", top: "14px" }}>Улица Титова, 14 — Яндекс Карты</a>
|
||||
<a
|
||||
href="https://yandex.ru/maps/65/novosibirsk/?utm_medium=mapframe&utm_source=maps"
|
||||
style={{ color: "#eee", fontSize: "12px", position: "absolute", top: "0px" }}
|
||||
>
|
||||
Новосибирск
|
||||
</a>
|
||||
<a
|
||||
href="https://yandex.ru/maps/65/novosibirsk/house/ulitsa_titova_14/bEsYfg9iSkEGQFtufXV5cn9lYQ==/?ll=82.882443%2C54.983268&utm_medium=mapframe&utm_source=maps&z=18.59"
|
||||
style={{ color: "#eee", fontSize: "12px", position: "absolute", top: "14px" }}
|
||||
>
|
||||
Улица Титова, 14 — Яндекс Карты
|
||||
</a>
|
||||
<iframe
|
||||
title="map"
|
||||
src="https://yandex.ru/map-widget/v1/?ll=82.882443%2C54.983268&mode=search&ol=geo&ouri=ymapsbm1%3A%2F%2Fgeo%3Fdata%3DCgg1NzA5NDgyMhJB0KDQvtGB0YHQuNGPLCDQndC-0LLQvtGB0LjQsdC40YDRgdC6LCDRg9C70LjRhtCwINCi0LjRgtC-0LLQsCwgMTQiCg3Dw6VCFffuW0I%2C&z=18.59"
|
||||
@@ -76,4 +91,6 @@ export default function PopupMap({ togglePopupMap }: PopupMapProps) {
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default PopupMap;
|
||||
@@ -2,7 +2,7 @@ import React from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { Product } from "../utils/types";
|
||||
|
||||
export default function ProductCard({ icons, title, price }: Product) {
|
||||
function ProductCard({ icons, title, price }: Product) {
|
||||
const priceAsString = price.toLocaleString('ru-RU');
|
||||
|
||||
return(
|
||||
@@ -22,4 +22,6 @@ export default function ProductCard({ icons, title, price }: Product) {
|
||||
</h6>
|
||||
</motion.article>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default ProductCard;
|
||||
@@ -3,7 +3,7 @@ import ProfileAvatar from '../assets/icons/profile-avatar.svg';
|
||||
import '../ProfileStyle.scss';
|
||||
import Cookies from "js-cookie";
|
||||
|
||||
export default function ProfileInfo() {
|
||||
function ProfileInfo() {
|
||||
const userLogin = Cookies.get('user');
|
||||
|
||||
return(
|
||||
@@ -12,4 +12,6 @@ export default function ProfileInfo() {
|
||||
<span>{userLogin || 'Гость'}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default ProfileInfo;
|
||||
@@ -3,7 +3,7 @@ import { Link } from "react-router-dom";
|
||||
import '../ProfileStyle.scss';
|
||||
import ProfileInfo from "./ProfileInfo";
|
||||
|
||||
export default function ProfileOrders() {
|
||||
function ProfileOrders() {
|
||||
return(
|
||||
<section className="orders-section">
|
||||
<nav className="profile-page__nav">
|
||||
@@ -37,4 +37,6 @@ export default function ProfileOrders() {
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default ProfileOrders;
|
||||
@@ -3,7 +3,7 @@ import { Link } from "react-router-dom";
|
||||
import '../ProfileStyle.scss';
|
||||
import ProfileInfo from "./ProfileInfo";
|
||||
|
||||
export default function ProfilePurchases() {
|
||||
function ProfilePurchases() {
|
||||
return(
|
||||
<section className="purchases-section">
|
||||
<nav className="profile-page__nav">
|
||||
@@ -22,4 +22,6 @@ export default function ProfilePurchases() {
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default ProfilePurchases
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import axios from "axios";
|
||||
import { Reviews } from "../utils/types";
|
||||
import "../index.scss";
|
||||
@@ -8,23 +8,39 @@ type ReviewProps = {
|
||||
review: Reviews;
|
||||
};
|
||||
|
||||
export default function Review({ review }: ReviewProps) {
|
||||
const [userName, setUserName] = useState<string>(""); // Состояние для имени пользователя
|
||||
const readableDate = new Date(review.date).toLocaleDateString('ru-RU'); // Преобразование даты в читабельную форму
|
||||
function Review({ review }: ReviewProps) {
|
||||
const [userName, setUserName] = useState<string>("");
|
||||
const readableDate = new Date(review.date).toLocaleDateString('ru-RU');
|
||||
|
||||
useEffect(() => { // Получение имени пользователя по его ID
|
||||
const baseUrl = window.location.origin; // Получаем текущий домен сайта
|
||||
|
||||
axios.get(`${baseUrl}/api/get/user/${review.user_id}`)
|
||||
.then(response => {
|
||||
useEffect(() => {
|
||||
const fetchUserName = async () => {
|
||||
try {
|
||||
const baseUrl = window.location.origin;
|
||||
const response = await axios.get(`${baseUrl}/api/get/user/${review.user_id}`);
|
||||
const user = response.data.user[0];
|
||||
setUserName(user.login);
|
||||
})
|
||||
.catch(error => {
|
||||
} catch (error) {
|
||||
console.error('Ошибка при получении логина пользователя:', error);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
fetchUserName();
|
||||
}, [review.user_id]);
|
||||
|
||||
const renderStars = useCallback(() => {
|
||||
return [1, 2, 3, 4, 5].map(rate => (
|
||||
<input
|
||||
key={rate}
|
||||
type="radio"
|
||||
className="star-rate__star-radio"
|
||||
value={rate}
|
||||
aria-label={rate === 1 ? "Плохо" : rate === 2 ? "Удовлетворительно" : rate === 3 ? "Нормально" : rate === 4 ? "Хорошо" : "Отлично"}
|
||||
checked={review.rate === rate}
|
||||
readOnly
|
||||
/>
|
||||
));
|
||||
}, [review.rate]);
|
||||
|
||||
return (
|
||||
<article className="review-article">
|
||||
<div className="review-article__review-container">
|
||||
@@ -34,17 +50,7 @@ export default function Review({ review }: ReviewProps) {
|
||||
</div>
|
||||
<div className="review-container__review-info">
|
||||
<div className="review-info__star-rate">
|
||||
{[1, 2, 3, 4, 5].map(rate => (
|
||||
<input
|
||||
key={rate}
|
||||
type="radio"
|
||||
className="star-rate__star-radio"
|
||||
value={rate}
|
||||
aria-label={rate === 1 ? "Плохо" : rate === 2 ? "Удовлетворительно" : rate === 3 ? "Нормально" : rate === 4 ? "Хорошо" : "Отлично"}
|
||||
checked={review.rate === rate}
|
||||
readOnly
|
||||
/>
|
||||
))}
|
||||
{renderStars()}
|
||||
</div>
|
||||
<time className="review-info__review-date" dateTime={new Date(review.date).toISOString()}>
|
||||
{readableDate}
|
||||
@@ -55,4 +61,6 @@ export default function Review({ review }: ReviewProps) {
|
||||
{review.icons && <img className="review-article__product-image" src={review.icons} alt="Review product" />}
|
||||
</article>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Review;
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import '../ProductStyle.scss';
|
||||
import ImageAttachIcon from "../assets/icons/review-form__add-image-icon.svg";
|
||||
import { motion } from 'framer-motion';
|
||||
@@ -11,47 +11,47 @@ interface ReviewState {
|
||||
image?: string | ArrayBuffer | null;
|
||||
}
|
||||
|
||||
export default function ReviewForm({ productId }: { productId: string }) {
|
||||
const [review, setReview] = useState<ReviewState>({ text: '', rating: 1 }); // Состояние для отзыва
|
||||
const [userId, setUserId] = useState<string | null>(null); // Состояние для ID пользователя
|
||||
const [imageName, setImageName] = useState<string | null>(null); // Состояние для имени изображения
|
||||
function ReviewForm({ productId }: { productId: string }) {
|
||||
const [review, setReview] = useState<ReviewState>({ text: '', rating: 1 });
|
||||
const [userId, setUserId] = useState<string | null>(null);
|
||||
const [imageName, setImageName] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => { // Получение ID пользователя из cookie при инициализации компонента
|
||||
useEffect(() => {
|
||||
const userIdFromCookie = Cookies.get('user_id');
|
||||
if (userIdFromCookie) {
|
||||
setUserId(userIdFromCookie);
|
||||
}
|
||||
}, []);
|
||||
|
||||
function handleTextChange(event: React.ChangeEvent<HTMLTextAreaElement>) { // Обработчик изменения текста отзыва
|
||||
setReview({ ...review, text: event.target.value });
|
||||
}
|
||||
const handleTextChange = useCallback((event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setReview(prev => ({ ...prev, text: event.target.value }));
|
||||
}, []);
|
||||
|
||||
function handleRatingChange(event: React.ChangeEvent<HTMLInputElement>) { // Обработчик изменения оценки отзыва
|
||||
setReview({ ...review, rating: Number(event.target.value) });
|
||||
}
|
||||
const handleRatingChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setReview(prev => ({ ...prev, rating: Number(event.target.value) }));
|
||||
}, []);
|
||||
|
||||
function handleImageChange(event: React.ChangeEvent<HTMLInputElement>) { // Обработчик изменения изображения
|
||||
const handleImageChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (event.target.files && event.target.files[0]) {
|
||||
const file = event.target.files[0];
|
||||
setImageName(file.name);
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
setReview({ ...review, image: reader.result });
|
||||
setReview(prev => ({ ...prev, image: reader.result }));
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
async function handleSubmit(event: React.FormEvent) { // Обработчик отправки формы
|
||||
const handleSubmit = useCallback(async (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
if (!userId) {
|
||||
console.error('ID пользователя не найден!');
|
||||
return;
|
||||
}
|
||||
|
||||
const baseUrl = window.location.origin; // Получаем текущий домен сайта
|
||||
const baseUrl = window.location.origin;
|
||||
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
@@ -73,13 +73,12 @@ export default function ReviewForm({ productId }: { productId: string }) {
|
||||
} catch (error) {
|
||||
console.error('Ошибка при отправке отзыва:', error);
|
||||
}
|
||||
}
|
||||
}, [review, userId, productId]);
|
||||
|
||||
return (
|
||||
<form className='product-page__review-form' onSubmit={handleSubmit}>
|
||||
<h5 className='review-form__heading'>Оставить отзыв</h5>
|
||||
<div className="review-form__stars-container">
|
||||
{/* Создание радиокнопок для выбора оценки */}
|
||||
{[...Array(5)].map((_, index) => (
|
||||
<input
|
||||
key={index}
|
||||
@@ -108,7 +107,6 @@ export default function ReviewForm({ productId }: { productId: string }) {
|
||||
<input
|
||||
className='review-form__image-input'
|
||||
type="file"
|
||||
name="review image"
|
||||
id="review-image"
|
||||
accept='.png, .jpg, .jpeg'
|
||||
onChange={handleImageChange}
|
||||
@@ -124,5 +122,7 @@ export default function ReviewForm({ productId }: { productId: string }) {
|
||||
Отправить отзыв
|
||||
</motion.button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export default ReviewForm;
|
||||
@@ -9,7 +9,7 @@ type HomePageProps = {
|
||||
products: Product[];
|
||||
}
|
||||
|
||||
export default function HomePage({ products }: HomePageProps) {
|
||||
function HomePage({ products }: HomePageProps) {
|
||||
return(
|
||||
<section className="home-page">
|
||||
<Banner />
|
||||
@@ -30,4 +30,6 @@ export default function HomePage({ products }: HomePageProps) {
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default HomePage;
|
||||
@@ -4,7 +4,7 @@ import "../InfoPageStyle.scss";
|
||||
import RailTHAvatar from "../assets/img/info-page__railth-avatar.png";
|
||||
import NoKesspenAvatar from "../assets/img/info-page__no-kesspen-avatar.png";
|
||||
|
||||
export default function InfoPage() {
|
||||
function InfoPage() {
|
||||
return (
|
||||
<section className="info-page">
|
||||
<DevCard
|
||||
@@ -21,4 +21,6 @@ export default function InfoPage() {
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default InfoPage;
|
||||
@@ -1,59 +1,67 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useState, useCallback } from "react";
|
||||
import '../PaymentStyle.scss';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
export default function PaymentPage() {
|
||||
function PaymentPage() {
|
||||
const [ccNumber, setCcNumber] = useState(""); // Состояние для номера карты
|
||||
const [valueDate, setValueDate] = useState<number | ''>(''); // Состояние для даты истечения срока действия карты
|
||||
const [valueCode, setValueCode] = useState<number | ''>(''); // Состояние для кода карты
|
||||
const [expiryDate, setExpiryDate] = useState(""); // Состояние для даты истечения срока действия карты
|
||||
const [cvv, setCvv] = useState(""); // Состояние для кода карты
|
||||
const location = useLocation(); // Получение параметров из URL
|
||||
const queryParams = new URLSearchParams(location.search);
|
||||
const price = queryParams.get('price'); // Получение стоимости из URL
|
||||
|
||||
const formatAndSetCcNumber = (e: React.ChangeEvent<HTMLInputElement>) => { // Обработчик изменения номера карты
|
||||
const inputVal = e.target.value.replace(/ /g, ""); // Удаление пробелов из введенного значения
|
||||
let inputNumbersOnly = inputVal.replace(/\D/g, ""); // Удаление всех символов, кроме цифр
|
||||
const formatAndSetCcNumber = useCallback((e: React.ChangeEvent<HTMLInputElement>) => { // Обработчик изменения номера карты
|
||||
const inputVal = e.target.value.replace(/\D/g, ""); // Удаление всех символов, кроме цифр
|
||||
|
||||
if (inputNumbersOnly.length > 16) { // Если введенное значение превышает 16 символов
|
||||
inputNumbersOnly = inputNumbersOnly.substr(0, 16); // Усекаем его до 16 символов
|
||||
}
|
||||
const formattedNumber = inputVal.match(/.{1,4}/g)?.join(" ") || ""; // Разделяем введенное значение на группы по 4 символа и добавляем пробелы между ними
|
||||
setCcNumber(formattedNumber); // Устанавливаем введенное значение в состояние
|
||||
}, []);
|
||||
|
||||
const splits = inputNumbersOnly.match(/.{1,4}/g); // Разделяем введенное значение на группы по 4 символа
|
||||
const handleChangeExpiryDate = useCallback((event: React.ChangeEvent<HTMLInputElement>) => { // Обработчик изменения даты истечения срока действия карты
|
||||
const inputVal = event.target.value.replace(/\D/g, ""); // Удаление всех символов, кроме цифр
|
||||
setExpiryDate(inputVal.slice(0, 4)); // Ограничиваем значение до 4 символов
|
||||
}, []);
|
||||
|
||||
let spacedNumber = ""; // Строка для хранения введенного значения с разделителями
|
||||
if (splits) { // Если разделение прошло успешно
|
||||
spacedNumber = splits.join(" "); // Добавляем пробелы между группами по 4 символа
|
||||
}
|
||||
|
||||
setCcNumber(spacedNumber); // Устанавливаем введенное значение в состояние
|
||||
};
|
||||
|
||||
const handleChangeDate = (event: React.ChangeEvent<HTMLInputElement>) => { // Обработчик изменения даты истечения срока действия карты
|
||||
const inputValue = event.target.value; // Получаем введенное значение
|
||||
if (inputValue.length <= 4) { // Если введенное значение содержит не больше 4 символов
|
||||
setValueDate(inputValue === '' ? '' : Number(inputValue)); // Устанавливаем значение в состояние
|
||||
}
|
||||
};
|
||||
|
||||
const handleChangeCode = (event: React.ChangeEvent<HTMLInputElement>) => { // Обработчик изменения кода карты
|
||||
const inputValue = event.target.value; // Получаем введенное значение
|
||||
if (inputValue.length <= 3) { // Если введенное значение содержит не больше 3 символов
|
||||
setValueCode(inputValue === '' ? '' : Number(inputValue)); // Устанавливаем значение в состояние
|
||||
}
|
||||
};
|
||||
const handleChangeCvv = useCallback((event: React.ChangeEvent<HTMLInputElement>) => { // Обработчик изменения кода карты
|
||||
const inputVal = event.target.value.replace(/\D/g, ""); // Удаление всех символов, кроме цифр
|
||||
setCvv(inputVal.slice(0, 3)); // Ограничиваем значение до 3 символов
|
||||
}, []);
|
||||
|
||||
return(
|
||||
<section className="payment-page">
|
||||
<h2 className="payment-page__price">{price} ₽ </h2>
|
||||
<h2 className="payment-page__price">{price} ₽</h2>
|
||||
<div className="payment-page__payment-card">
|
||||
<h3 className="payment-card__heading"> Оплата картой </h3>
|
||||
<input className="payment-card__input" type="text" placeholder="Номер" value={ccNumber} onChange={formatAndSetCcNumber}/>
|
||||
<h3 className="payment-card__heading">Оплата картой</h3>
|
||||
<input
|
||||
className="payment-card__input"
|
||||
type="text"
|
||||
placeholder="Номер"
|
||||
value={ccNumber}
|
||||
onChange={formatAndSetCcNumber}
|
||||
maxLength={19} // Максимальная длина ввода (16 цифр + 3 пробела)
|
||||
/>
|
||||
<div className="payment-card__inputs-group">
|
||||
<input className="payment-card__input" type="number" placeholder="ММ/ГГ" value={valueDate} onChange={handleChangeDate}/>
|
||||
<input className="payment-card__input" type="number" placeholder="CVC/CVV" value={valueCode} onChange={handleChangeCode}/>
|
||||
<input
|
||||
className="payment-card__input"
|
||||
type="text"
|
||||
placeholder="ММГГ"
|
||||
value={expiryDate}
|
||||
onChange={handleChangeExpiryDate}
|
||||
maxLength={4} // Максимальная длина ввода
|
||||
/>
|
||||
<input
|
||||
className="payment-card__input"
|
||||
type="text"
|
||||
placeholder="CVC/CVV"
|
||||
value={cvv}
|
||||
onChange={handleChangeCvv}
|
||||
maxLength={3} // Максимальная длина ввода
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<a href="scam" className="payment-page__pay-link"> Оплатить </a>
|
||||
<a href="scam" className="payment-page__pay-link">Оплатить</a>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default PaymentPage;
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { Product, Reviews } from '../utils/types';
|
||||
import Review from '../components/Review';
|
||||
import axios from 'axios';
|
||||
@@ -7,67 +7,51 @@ import ShareIcon from "../assets/icons/share-icon.svg";
|
||||
import ReviewForm from '../components/ReviewForm';
|
||||
import { Link, useParams } from 'react-router-dom';
|
||||
|
||||
export default function ProductPage() {
|
||||
function ProductPage() {
|
||||
const { id } = useParams(); // Получение id из URL-параметров
|
||||
|
||||
// Состояние для продукта и рецензий
|
||||
const [product, setProduct] = useState<Product | null>(null);
|
||||
const [reviews, setReviews] = useState<Reviews[]>([]);
|
||||
|
||||
// Состояние для среднего рейтинга и флага для отслеживания получения данных
|
||||
// Состояние для среднего рейтинга
|
||||
const [averageRating, setAverageRating] = useState<number>(0);
|
||||
const [isDataFetched, setIsDataFetched] = useState(false);
|
||||
|
||||
const totalReviews = reviews.length; // Количество рецензий
|
||||
const fetchProductAndReviews = useCallback(async () => {
|
||||
try {
|
||||
const baseUrl = window.location.origin;
|
||||
const [productResponse, reviewsResponse] = await Promise.all([
|
||||
axios.get(`${baseUrl}/api/get/products`),
|
||||
axios.get(`${baseUrl}/api/get/reviews/${id}`)
|
||||
]);
|
||||
|
||||
const trimText = (text: string, limit: number): string => { // Функция для усечения текста
|
||||
return text.length > limit ? text.substring(0, limit) + '...' : text;
|
||||
};
|
||||
const productData = productResponse.data.products.find((item: Product) => item.id.toString() === id);
|
||||
setProduct(productData);
|
||||
|
||||
const countReviewsByRate = (rate: number): number => { // Функция для подсчета рецензий по рейтингу
|
||||
return reviews.filter(review => review.rate === rate).length;
|
||||
};
|
||||
const reviewsData = reviewsResponse.data.review;
|
||||
setReviews(reviewsData);
|
||||
|
||||
const percentageOfRate = (rate: number): number => { // Функция для вычисления процента рецензий по рейтингу
|
||||
const count = countReviewsByRate(rate);
|
||||
return (count / totalReviews) * 100;
|
||||
};
|
||||
|
||||
useEffect(() => { // Получение продукта по его id
|
||||
const baseUrl = window.location.origin; // Получаем текущий домен сайта
|
||||
|
||||
axios.get(`${baseUrl}/api/get/products`)
|
||||
.then(response => {
|
||||
const productData = response.data.products.find(
|
||||
(item: Product) => item.id.toString() === id
|
||||
);
|
||||
setProduct(productData);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Ошибка при получении продукта:', error);
|
||||
});
|
||||
const totalRating = reviewsData.reduce((acc: number, review: Reviews) => acc + review.rate, 0);
|
||||
setAverageRating(reviewsData.length > 0 ? totalRating / reviewsData.length : 0);
|
||||
} catch (error) {
|
||||
console.error('Ошибка при получении данных:', error);
|
||||
}
|
||||
}, [id]);
|
||||
|
||||
useEffect(() => { // Получение рецензий по id продукта
|
||||
if (!isDataFetched) {
|
||||
const baseUrl = window.location.origin;
|
||||
|
||||
axios.get(`${baseUrl}/api/get/reviews/${id}`)
|
||||
.then(response => {
|
||||
const reviewsData = response.data.review;
|
||||
setReviews(reviewsData);
|
||||
const totalRating = reviewsData.reduce((acc: number, review: Reviews) => acc + review.rate, 0);
|
||||
const average = totalRating / reviewsData.length;
|
||||
setAverageRating(reviewsData.length > 0 ? average : 0);
|
||||
setIsDataFetched(true);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Ошибка при получении рецензий:', error);
|
||||
});
|
||||
}
|
||||
}, [id, isDataFetched]);
|
||||
useEffect(() => {
|
||||
fetchProductAndReviews();
|
||||
}, [fetchProductAndReviews]);
|
||||
|
||||
if (!product) { // Отображение загрузки, если продукт не загружен
|
||||
const trimText = (text: string, limit: number): string =>
|
||||
text.length > limit ? text.substring(0, limit) + '...' : text;
|
||||
|
||||
const countReviewsByRate = useCallback((rate: number): number =>
|
||||
reviews.filter(review => review.rate === rate).length, [reviews]);
|
||||
|
||||
const percentageOfRate = useCallback((rate: number): number =>
|
||||
(countReviewsByRate(rate) / reviews.length) * 100, [countReviewsByRate, reviews.length]);
|
||||
|
||||
if (!product) {
|
||||
return <div>Загрузка...</div>;
|
||||
}
|
||||
|
||||
@@ -106,16 +90,12 @@ export default function ProductPage() {
|
||||
<div className='rate-block__rating'>
|
||||
<span className='rate-block__rate-number'>{averageRating.toFixed(1)}</span>
|
||||
<div className="rate-block__star-rating">
|
||||
{/* Контейнер для отображения звезд, занимающий 100% ширины */}
|
||||
<div className="star-rating__back-stars">
|
||||
{/* Отображение звезд, которые не должны быть закрашены */}
|
||||
{'★★★★★'.split('').map((star, i) => (
|
||||
<span key={`back-star-${i}`}>{star}</span>
|
||||
))}
|
||||
{/* Контейнер для отображения звезд, которые должны быть закрашены */}
|
||||
<div className="star-rating__front-stars"
|
||||
style={{ width: `${(averageRating / 5) * 100}%` }}>
|
||||
{/* Отображение звезд, которые должны быть закрашены */}
|
||||
{'★★★★★'.split('').map((star, i) => (
|
||||
<span key={`front-star-${i}`}>{star}</span>
|
||||
))}
|
||||
@@ -143,4 +123,6 @@ export default function ProductPage() {
|
||||
</section>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ProductPage;
|
||||
@@ -8,7 +8,7 @@ import ProfilePurchases from "../components/ProfilePurchases";
|
||||
import LogoutIcon from "../assets/icons/logout-icon.svg";
|
||||
import Cookies from "js-cookie";
|
||||
|
||||
export default function ProfilePage() {
|
||||
function ProfilePage() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleLogout = () => {
|
||||
@@ -34,4 +34,6 @@ export default function ProfilePage() {
|
||||
</motion.button>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default ProfilePage;
|
||||
@@ -2,10 +2,12 @@ import React from "react";
|
||||
import '../ScamStyle.scss';
|
||||
import ScamImage from "../assets/img/scam-image.png";
|
||||
|
||||
export default function ScamPage() {
|
||||
function ScamPage() {
|
||||
return(
|
||||
<section className="scam-page">
|
||||
<img src={ScamImage} alt="scam mammoth" className="scam-page__image"/>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default ScamPage;
|
||||
Reference in New Issue
Block a user