Code optimization & other

Memoization & other
This commit is contained in:
RailTH
2024-05-21 00:17:45 +11:00
parent c3700ad25a
commit dc028a0af0
55 changed files with 393 additions and 296 deletions

View File

@@ -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"
]
}

View File

@@ -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>

View 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*/

View 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":""}

View 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*/

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
.scam-page{display:flex;width:96%}.scam-page .scam-page__image{width:100%}
/*# sourceMappingURL=503.9bd9b336.chunk.css.map*/

View 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":""}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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*/

View 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":""}

View 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*/

View 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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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

File diff suppressed because one or more lines are too long

View 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

File diff suppressed because one or more lines are too long

View 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

View 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":""}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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

View 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":""}

View 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

View 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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

View File

@@ -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>
</>
);

View File

@@ -1,8 +1,10 @@
import React from "react";
export default function Banner() {
function Banner() {
return(
<div className="banner-div">
</div>
)
}
}
export default Banner;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;