diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 0000000..e74df74
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,18 @@
+{
+ "root": true,
+ "extends": [
+ "plugin:@typescript-eslint/recommended",
+ "prettier"
+ ],
+ "parser": "@typescript-eslint/parser",
+ "plugins": [
+ "@typescript-eslint",
+ "prettier"
+ ],
+ "rules": {
+ "@typescript-eslint/no-explicit-any": "off",
+ "@typescript-eslint/no-empty-object-type": "off",
+ "@typescript-eslint/no-unused-vars": "off",
+ "prettier/prettier": "off"
+ }
+}
diff --git a/.github/workflows/manual.yml b/.github/workflows/manual.yml
new file mode 100644
index 0000000..90402fc
--- /dev/null
+++ b/.github/workflows/manual.yml
@@ -0,0 +1,48 @@
+name: Kotyari Build
+
+on:
+ push:
+ branches: [main]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Install Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: 16
+
+ - name: Checkout code
+ run: actions/checkout@v4
+
+ - name: Install deps
+ run: npm ci
+
+ - name: Build
+ run: npm run build
+
+ - name: Upload build result
+ uses: actions/upload-artifact@v1
+ with:
+ name: dist
+ path: ./dist
+
+ deploy:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Download build
+ uses: actions/download-artifact@v1
+ with:
+ name: dist
+ - name: Transfer build files to server
+ uses: appleboy/scp-action@master
+ with:
+ host: 94.139.246.241
+ username: ubuntu
+ key: ${{ secrets.PRIVATE_KEY }}
+ source: "dist/*"
+ target: "/home/ubuntu/dist"
+ strip_components: 1
diff --git a/.gitignore b/.gitignore
index 9113e7a..96b006f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+key-yandex.ts
./dist/
node_modules
diff --git a/.husky/pre-commit b/.husky/pre-commit
new file mode 100755
index 0000000..ab10b9b
--- /dev/null
+++ b/.husky/pre-commit
@@ -0,0 +1,4 @@
+# .husky/pre-commit
+
+prettier $(git diff --cached --name-only --diff-filter=ACMR | sed 's| |\\ |g') --write --ignore-unknown
+git update-index --again
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..04c01ba
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,2 @@
+node_modules/
+dist/
\ No newline at end of file
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..4f6b534
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,9 @@
+{
+ "trailingComma": "all",
+ "tabWidth": 2,
+ "semi": true,
+ "singleQuote": true,
+ "printWidth": 120,
+ "bracketSpacing": true,
+ "endOfLine": "lf"
+}
diff --git a/eslint.config.js b/eslint.config.js
deleted file mode 100644
index 719b029..0000000
--- a/eslint.config.js
+++ /dev/null
@@ -1,4 +0,0 @@
-import globals from 'globals';
-import pluginJs from '@eslint/js';
-
-export default [{ languageOptions: { globals: globals.browser } }, pluginJs.configs.recommended];
diff --git a/eslint.config.mjs b/eslint.config.mjs
deleted file mode 100644
index e363415..0000000
--- a/eslint.config.mjs
+++ /dev/null
@@ -1,27 +0,0 @@
-import globals from "globals";
-import pluginJs from "@eslint/js";
-import prettier from 'eslint-plugin-prettier';
-
-export default [
- {
- languageOptions: {
- globals: {
- ...globals.browser,
- Handlebars: 'readonly',
- },
- },
-
- plugins: {
- prettier,
- },
- rules: {
- 'prettier/prettier': 'error',
- 'semi': ['warn', 'always'],
- },
- files: ['**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx'],
- ignores: [
- 'node_modules/**',
- ],
- },
- pluginJs.configs.recommended,
-];
diff --git a/package-lock.json b/package-lock.json
index 7b5dc53..d2c17bd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,7 +14,9 @@
},
"devDependencies": {
"@types/handlebars": "^4.0.40",
- "@typescript-eslint/eslint-plugin": "^8.10.0",
+ "@types/node": "^22.10.2",
+ "@typescript-eslint/eslint-plugin": "^8.18.0",
+ "@typescript-eslint/parser": "^8.18.0",
"eslint": "^8.0.1",
"eslint-config-prettier": "^9.1.0",
"eslint-config-standard": "^17.1.0",
@@ -24,7 +26,8 @@
"lint-staged": "^15.2.10",
"prettier": "^3.3.3",
"sass": "^1.81.0",
- "vite": "^5.4.9",
+ "typescript": "^5.7.2",
+ "vite": "^5.4.11",
"vite-plugin-handlebars": "^2.0.0",
"vite-plugin-pwa": "^0.20.5",
"vite-plugin-string": "^1.2.3"
@@ -3026,6 +3029,16 @@
"license": "MIT",
"peer": true
},
+ "node_modules/@types/node": {
+ "version": "22.10.2",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz",
+ "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.20.0"
+ }
+ },
"node_modules/@types/resolve": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
@@ -3041,17 +3054,17 @@
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.12.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.12.2.tgz",
- "integrity": "sha512-gQxbxM8mcxBwaEmWdtLCIGLfixBMHhQjBqR8sVWNTPpcj45WlYL2IObS/DNMLH1DBP0n8qz+aiiLTGfopPEebw==",
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.0.tgz",
+ "integrity": "sha512-NR2yS7qUqCL7AIxdJUQf2MKKNDVNaig/dEB0GBLU7D+ZdHgK1NoH/3wsgO3OnPVipn51tG3MAwaODEGil70WEw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
- "@typescript-eslint/scope-manager": "8.12.2",
- "@typescript-eslint/type-utils": "8.12.2",
- "@typescript-eslint/utils": "8.12.2",
- "@typescript-eslint/visitor-keys": "8.12.2",
+ "@typescript-eslint/scope-manager": "8.18.0",
+ "@typescript-eslint/type-utils": "8.18.0",
+ "@typescript-eslint/utils": "8.18.0",
+ "@typescript-eslint/visitor-keys": "8.18.0",
"graphemer": "^1.4.0",
"ignore": "^5.3.1",
"natural-compare": "^1.4.0",
@@ -3066,26 +3079,21 @@
},
"peerDependencies": {
"@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0",
- "eslint": "^8.57.0 || ^9.0.0"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.8.0"
}
},
"node_modules/@typescript-eslint/parser": {
- "version": "8.12.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.12.2.tgz",
- "integrity": "sha512-MrvlXNfGPLH3Z+r7Tk+Z5moZAc0dzdVjTgUgwsdGweH7lydysQsnSww3nAmsq8blFuRD5VRlAr9YdEFw3e6PBw==",
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.18.0.tgz",
+ "integrity": "sha512-hgUZ3kTEpVzKaK3uNibExUYm6SKKOmTU2BOxBSvOYwtJEPdVQ70kZJpPjstlnhCHcuc2WGfSbpKlb/69ttyN5Q==",
"dev": true,
- "license": "BSD-2-Clause",
- "peer": true,
+ "license": "MITClause",
"dependencies": {
- "@typescript-eslint/scope-manager": "8.12.2",
- "@typescript-eslint/types": "8.12.2",
- "@typescript-eslint/typescript-estree": "8.12.2",
- "@typescript-eslint/visitor-keys": "8.12.2",
+ "@typescript-eslint/scope-manager": "8.18.0",
+ "@typescript-eslint/types": "8.18.0",
+ "@typescript-eslint/typescript-estree": "8.18.0",
+ "@typescript-eslint/visitor-keys": "8.18.0",
"debug": "^4.3.4"
},
"engines": {
@@ -3096,23 +3104,19 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.8.0"
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "8.12.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.12.2.tgz",
- "integrity": "sha512-gPLpLtrj9aMHOvxJkSbDBmbRuYdtiEbnvO25bCMza3DhMjTQw0u7Y1M+YR5JPbMsXXnSPuCf5hfq0nEkQDL/JQ==",
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.18.0.tgz",
+ "integrity": "sha512-PNGcHop0jkK2WVYGotk/hxj+UFLhXtGPiGtiaWgVBVP1jhMoMCHlTyJA+hEj4rszoSdLTK3fN4oOatrL0Cp+Xw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.12.2",
- "@typescript-eslint/visitor-keys": "8.12.2"
+ "@typescript-eslint/types": "8.18.0",
+ "@typescript-eslint/visitor-keys": "8.18.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3123,14 +3127,14 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "8.12.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.12.2.tgz",
- "integrity": "sha512-bwuU4TAogPI+1q/IJSKuD4shBLc/d2vGcRT588q+jzayQyjVK2X6v/fbR4InY2U2sgf8MEvVCqEWUzYzgBNcGQ==",
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.18.0.tgz",
+ "integrity": "sha512-er224jRepVAVLnMF2Q7MZJCq5CsdH2oqjP4dT7K6ij09Kyd+R21r7UVJrF0buMVdZS5QRhDzpvzAxHxabQadow==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/typescript-estree": "8.12.2",
- "@typescript-eslint/utils": "8.12.2",
+ "@typescript-eslint/typescript-estree": "8.18.0",
+ "@typescript-eslint/utils": "8.18.0",
"debug": "^4.3.4",
"ts-api-utils": "^1.3.0"
},
@@ -3141,16 +3145,15 @@
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.8.0"
}
},
"node_modules/@typescript-eslint/types": {
- "version": "8.12.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.12.2.tgz",
- "integrity": "sha512-VwDwMF1SZ7wPBUZwmMdnDJ6sIFk4K4s+ALKLP6aIQsISkPv8jhiw65sAK6SuWODN/ix+m+HgbYDkH+zLjrzvOA==",
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.18.0.tgz",
+ "integrity": "sha512-FNYxgyTCAnFwTrzpBGq+zrnoTO4x0c1CKYY5MuUTzpScqmY5fmsh2o3+57lqdI3NZucBDCzDgdEbIaNfAjAHQA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -3162,14 +3165,14 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.12.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.12.2.tgz",
- "integrity": "sha512-mME5MDwGe30Pq9zKPvyduyU86PH7aixwqYR2grTglAdB+AN8xXQ1vFGpYaUSJ5o5P/5znsSBeNcs5g5/2aQwow==",
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.18.0.tgz",
+ "integrity": "sha512-rqQgFRu6yPkauz+ms3nQpohwejS8bvgbPyIDq13cgEDbkXt4LH4OkDMT0/fN1RUtzG8e8AKJyDBoocuQh8qNeg==",
"dev": true,
- "license": "BSD-2-Clause",
+ "license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.12.2",
- "@typescript-eslint/visitor-keys": "8.12.2",
+ "@typescript-eslint/types": "8.18.0",
+ "@typescript-eslint/visitor-keys": "8.18.0",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
@@ -3184,23 +3187,21 @@
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <5.8.0"
}
},
"node_modules/@typescript-eslint/utils": {
- "version": "8.12.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.12.2.tgz",
- "integrity": "sha512-UTTuDIX3fkfAz6iSVa5rTuSfWIYZ6ATtEocQ/umkRSyC9O919lbZ8dcH7mysshrCdrAM03skJOEYaBugxN+M6A==",
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.18.0.tgz",
+ "integrity": "sha512-p6GLdY383i7h5b0Qrfbix3Vc3+J2k6QWw6UMUeY5JGfm3C5LbZ4QIZzJNoNOfgyRe0uuYKjvVOsO/jD4SJO+xg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
- "@typescript-eslint/scope-manager": "8.12.2",
- "@typescript-eslint/types": "8.12.2",
- "@typescript-eslint/typescript-estree": "8.12.2"
+ "@typescript-eslint/scope-manager": "8.18.0",
+ "@typescript-eslint/types": "8.18.0",
+ "@typescript-eslint/typescript-estree": "8.18.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3210,18 +3211,19 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0"
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.8.0"
}
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.12.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.12.2.tgz",
- "integrity": "sha512-PChz8UaKQAVNHghsHcPyx1OMHoFRUEA7rJSK/mDhdq85bk+PLsUHUBqTQTFt18VJZbmxBovM65fezlheQRsSDA==",
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.18.0.tgz",
+ "integrity": "sha512-pCh/qEA8Lb1wVIqNvBke8UaRjJ6wrAWkJO5yyIbs8Yx6TNGYyfNjOo61tLv+WwLvoLPp4BQ8B7AHKijl8NGUfw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.12.2",
- "eslint-visitor-keys": "^3.4.3"
+ "@typescript-eslint/types": "8.18.0",
+ "eslint-visitor-keys": "^4.2.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3231,6 +3233,19 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
+ "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
+ "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
"node_modules/@ungap/structured-clone": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
@@ -8470,12 +8485,11 @@
}
},
"node_modules/typescript": {
- "version": "5.6.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
- "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz",
+ "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==",
"dev": true,
"license": "Apache-2.0",
- "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -8513,6 +8527,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/undici-types": {
+ "version": "6.20.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
+ "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/unicode-canonical-property-names-ecmascript": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz",
@@ -8660,11 +8681,10 @@
}
},
"node_modules/vite": {
- "version": "5.4.10",
- "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz",
- "integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==",
+ "version": "5.4.11",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz",
+ "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==",
"dev": true,
- "license": "MIT",
"dependencies": {
"esbuild": "^0.21.3",
"postcss": "^8.4.43",
diff --git a/package.json b/package.json
index 7132edc..30abc7e 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,9 @@
"version": "1.0.0",
"devDependencies": {
"@types/handlebars": "^4.0.40",
- "@typescript-eslint/eslint-plugin": "^8.10.0",
+ "@types/node": "^22.10.2",
+ "@typescript-eslint/eslint-plugin": "^8.18.0",
+ "@typescript-eslint/parser": "^8.18.0",
"eslint": "^8.0.1",
"eslint-config-prettier": "^9.1.0",
"eslint-config-standard": "^17.1.0",
@@ -13,7 +15,8 @@
"lint-staged": "^15.2.10",
"prettier": "^3.3.3",
"sass": "^1.81.0",
- "vite": "^5.4.9",
+ "typescript": "^5.7.2",
+ "vite": "^5.4.11",
"vite-plugin-handlebars": "^2.0.0",
"vite-plugin-pwa": "^0.20.5",
"vite-plugin-string": "^1.2.3"
@@ -28,12 +31,13 @@
"server": "node server/server.js",
"prepare": "husky",
"format": "prettier --write '**/*.{js,jsx,ts,tsx}'",
- "lint": "eslint '**/*.{js,jsx,ts,tsx}'",
- "lint:fix": "npm run format && npm run lint -- --fix",
+ "lint": "eslint . --ext .js,.jsx,.ts,.tsx",
+ "lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix",
"dev": "vite",
"build": "vite build",
"start": "vite preview",
- "scss": "sass public/index.scss public/index.css"
+ "scss": "sass public/index.scss public/index.css",
+ "precommit": "lint-staged"
},
"husky": {
"hooks": {
@@ -41,9 +45,10 @@
}
},
"lint-staged": {
- "*.{js,jsx,ts,tsx,json,css,scss}": [
+ "*.{js,ts,tsx}": [
"prettier --write",
- "eslint --fix"
+ "eslint --fix",
+ "git add"
]
}
}
diff --git a/public/assets/img/static/crying-man.svg b/public/assets/img/static/crying-man.svg
new file mode 100644
index 0000000..a338f39
--- /dev/null
+++ b/public/assets/img/static/crying-man.svg
@@ -0,0 +1,39 @@
+
diff --git a/public/css/parametrs/basic-mixins.scss b/public/css/parametrs/basic-mixins.scss
index 05df585..de001f3 100644
--- a/public/css/parametrs/basic-mixins.scss
+++ b/public/css/parametrs/basic-mixins.scss
@@ -44,15 +44,16 @@
font-size: 20px;
font-weight: bold;
color: white;
- border: 2px solid #7a16d5;
+ border: 2px solid rgba(122, 22, 213, 0);
background-color: #7a16d5;
border-radius: parameters.$default-border-radius;
cursor: pointer;
- transition: transform 0.2s ease, margin 0.2s ease, background-color 0.3s ease;
+ transition: transform 0.2s ease, margin 0.2s ease, background-color 0.3s ease, border-color 0.3s ease;
&:hover {
background-color: #5e11b0;
- transform: scale(1.05); /* Увеличение кнопки */
+ border-color: #5e11b0;
+ transform: scale(1.1);
}
}
@@ -126,3 +127,38 @@
}
}
}
+
+@mixin placeholder() {
+ &__placeholder {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 30px;
+ text-align: center;
+ font-size: 20px;
+ font-weight: bold;
+ color: parameters.$default-text-color;
+ background-color: #f9f9f9;
+ border-radius: 12px;
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
+ animation: fadeIn 1s ease-in-out;
+ }
+
+ &__icon {
+ margin-bottom: 15px;
+ opacity: 0.7;
+ transition: transform 0.3s ease;
+ }
+
+ &__placeholder-text,
+ &__placeholder-suggestion {
+ font-size: 18px;
+ color: #555;
+ }
+
+ &__placeholder:hover .cart-item__icon {
+ transform: scale(1.1);
+ opacity: 1;
+ }
+}
diff --git a/public/css/parametrs/parameters.scss b/public/css/parametrs/parameters.scss
index e43d976..e9472e0 100644
--- a/public/css/parametrs/parameters.scss
+++ b/public/css/parametrs/parameters.scss
@@ -283,7 +283,7 @@ $catalog-justify-content: center;
$card-justify-content: space-between;
$card-allign-items: center;
$card-text-align: center;
-$card-z-index_on: 5000;
+$card-z-index_on: 4000;
$card-z-index: 1;
$header-z-index: 333;
$before-card-z-index: -1;
diff --git a/public/index.css b/public/index.css
index e69de29..29c01ab 100644
--- a/public/index.css
+++ b/public/index.css
@@ -0,0 +1,17 @@
+/* Error: Can't find stylesheet to import.
+ * ,
+ * 2 | @use 'src/scripts/components/dropdown-btn/view/dropdown.scss';
+ * | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ * '
+ * public\index.scss 2:1 root stylesheet */
+
+body::before {
+ font-family: "Source Code Pro", "SF Mono", Monaco, Inconsolata, "Fira Mono",
+ "Droid Sans Mono", monospace, monospace;
+ white-space: pre;
+ display: block;
+ padding: 1em;
+ margin-bottom: 1em;
+ border-bottom: 2px solid black;
+ content: "Error: Can't find stylesheet to import.\a \2577 \a 2 \2502 @use 'src/scripts/components/dropdown-btn/view/dropdown.scss';\d\a \2502 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\a \2575 \a public\\index.scss 2:1 root stylesheet";
+}
diff --git a/public/index.scss b/public/index.scss
index e32f2e3..eb2f357 100644
--- a/public/index.scss
+++ b/public/index.scss
@@ -24,6 +24,12 @@
@use "src/scripts/components/personal-account/views/personal-account__mobile";
@use 'src/scripts/components/personal-account/views/personal-account__middle';
@use "src/scripts/layouts/tab-bar.scss";
+@use "src/scripts/components/searcher/view/suggestions.scss";
+@use 'src/scripts/components/wish-list/presenter/wish-list';
+@use 'src/scripts/components/wish-list/presenter/all-wishlists';
+@use '../src/scripts/components/wish-list/presenter/wishlist-modal';
+@use "src/scripts/components/notice/view/csat.scss";
+
@font-face {
font-family: 'UbuntuSans';
src: url('/public/assets/fonts/UbuntuSans.ttf');
diff --git a/server/server.js b/server/server.js
index 4795dbe..f2a76f6 100644
--- a/server/server.js
+++ b/server/server.js
@@ -4,7 +4,7 @@ import { fileURLToPath } from 'url';
import path from 'path';
const app = express();
-const PORT = 3000;
+const PORT = 3001;
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -101,5 +101,5 @@ app.get('/', (req, res) => {
* @param {number} PORT - Порт, на котором будет запущен сервер
*/
app.listen(PORT, () => {
- console.log(`Server is running on port ${PORT}`);
+ //// console.log(`Server is running on port ${PORT}`);
});
diff --git a/src/scripts/components/auth-menu/api/auth.ts b/src/scripts/components/auth-menu/api/auth.ts
index 957cfd1..275ad8e 100644
--- a/src/scripts/components/auth-menu/api/auth.ts
+++ b/src/scripts/components/auth-menu/api/auth.ts
@@ -48,7 +48,7 @@ export default class AuthAPI {
throw Error(`ошибка сервера ${res.status} - ${res.body.error_message}`);
})
.catch((err) => {
- console.error(err);
+ //// console.error(err);
return false;
});
};
diff --git a/src/scripts/components/auth-menu/presenters/login.ts b/src/scripts/components/auth-menu/presenters/login.ts
index 75f7ab9..bd778c9 100644
--- a/src/scripts/components/auth-menu/presenters/login.ts
+++ b/src/scripts/components/auth-menu/presenters/login.ts
@@ -55,9 +55,9 @@ export class LoginPresenter {
this.router.clearHistory();
this.router.navigate('/');
})
- .catch((err) => {
- console.error(err);
- });
+ // .catch((err) => {
+ // // console.error(err);
+ // });
};
private handleLogin = (event: SubmitEvent) => {
@@ -90,12 +90,12 @@ export class LoginPresenter {
}
const error = response.body as ErrorResponse;
- console.error('Login error:', error.error_message);
+ //// console.error('Login error:', error.error_message);
this.errorView.displayBackError(error.error_message ?? 'неизвестная ошибка');
})
.catch((error: authResponse) => {
const errorBody = error.body as ErrorResponse;
- console.error('Ошибка при логине:', errorBody.error_message);
+ //// console.error('Ошибка при логине:', errorBody.error_message);
this.errorView.displayBackError(errorBody.error_message);
});
};
diff --git a/src/scripts/components/auth-menu/presenters/signup.ts b/src/scripts/components/auth-menu/presenters/signup.ts
index b9ccab2..c41b043 100644
--- a/src/scripts/components/auth-menu/presenters/signup.ts
+++ b/src/scripts/components/auth-menu/presenters/signup.ts
@@ -43,7 +43,7 @@ export class SignUpPresenter {
if (signUpForm) {
signUpForm.addEventListener('submit', this.handleSignUp);
} else {
- console.warn('Форма для регистрации не найдена!');
+ //// console.warn('Форма для регистрации не найдена!');
}
};
@@ -84,12 +84,12 @@ export class SignUpPresenter {
}
const error = response.body as ErrorResponse;
- console.error('Login error:', error.error_message);
+ //// console.error('Login error:', error.error_message);
this.errorView.displayBackError(error.error_message ?? 'неизвестная ошибка');
})
.catch((error: authResponse) => {
const errorBody = error.body as ErrorResponse;
- console.error('Ошибка при логине:', errorBody.error_message);
+ //// console.error('Ошибка при логине:', errorBody.error_message);
this.errorView.displayBackError(errorBody.error_message);
});
};
diff --git a/src/scripts/components/auth-menu/types/mixins.scss b/src/scripts/components/auth-menu/types/mixins.scss
index 728178c..58d7496 100644
--- a/src/scripts/components/auth-menu/types/mixins.scss
+++ b/src/scripts/components/auth-menu/types/mixins.scss
@@ -6,10 +6,9 @@
border-radius: $border-radius;
font-size: $font-size;
padding: $padding;
- border-color: parameters.$darkest-background-color;
- border-width: 0;
cursor: pointer;
transition: background-color parameters.$middle-duration ease, color parameters.$middle-duration ease;
+ border: 2px solid rgba(0, 0, 0, 0);
}
@mixin flex-container($direction: column, $align-items: center, $justify-content: center) {
diff --git a/src/scripts/components/auth-menu/views/auth.ts b/src/scripts/components/auth-menu/views/auth.ts
index 1872e95..80fcbf1 100644
--- a/src/scripts/components/auth-menu/views/auth.ts
+++ b/src/scripts/components/auth-menu/views/auth.ts
@@ -17,7 +17,8 @@ export default class AuthView implements AuthViewInterface {
const rootElement = document.getElementById(rootId);
if (!rootElement) {
- return console.error(`Элемент ID = ${rootId} не найден`);
+ //// console.error(`Элемент ID = ${rootId} не найден`);
+ return new Error(`Элемент ID = ${rootId} не найден`)
}
rootElement.innerHTML = '';
diff --git a/src/scripts/components/auth-menu/views/configs.ts b/src/scripts/components/auth-menu/views/configs.ts
index d4d3f31..afe57e9 100644
--- a/src/scripts/components/auth-menu/views/configs.ts
+++ b/src/scripts/components/auth-menu/views/configs.ts
@@ -60,7 +60,7 @@ export const menuSignUp = {
],
submitText: 'Регистрация',
link: {
- href: 'login',
+ href: '/login',
text: 'Есть аккаунт?',
label: 'Войти',
},
@@ -114,7 +114,7 @@ export const menuSignIn = {
],
submitText: 'Войти',
link: {
- href: 'signup',
+ href: '/signup',
text: 'Нет аккаунта?',
label: 'Зарегистрироваться',
},
diff --git a/src/scripts/components/auth-menu/views/form.scss b/src/scripts/components/auth-menu/views/form.scss
index 82e0802..3c79ecd 100644
--- a/src/scripts/components/auth-menu/views/form.scss
+++ b/src/scripts/components/auth-menu/views/form.scss
@@ -53,12 +53,6 @@
font-weight: bold;
margin-bottom: parameters.$midle-button-margin-bottom;
- &:hover {
- background-color: parameters.$darker-background-color;
- border: 2px solid parameters.$darker-background-color;
- transform: scale(parameters.$big-scale);
- }
-
&:active {
background-color: parameters.$darkest-background-color;
border: 2px solid parameters.$darkest-background-color;
diff --git a/src/scripts/components/card/presenter/card.ts b/src/scripts/components/card/presenter/card.ts
index a37d232..0a28fb7 100644
--- a/src/scripts/components/card/presenter/card.ts
+++ b/src/scripts/components/card/presenter/card.ts
@@ -30,9 +30,9 @@ export class CardPresenter {
this.view.render({ products: cardsData });
})
- .catch((err) => {
- console.error(err);
- });
+ // .catch((err) => {
+ // // console.error(err);
+ // });
};
private attachCardClickHandlers() {
diff --git a/src/scripts/components/card/view/card.hbs b/src/scripts/components/card/view/card.hbs
index b5cb44a..d158e9d 100644
--- a/src/scripts/components/card/view/card.hbs
+++ b/src/scripts/components/card/view/card.hbs
@@ -9,7 +9,7 @@
{{page_title}}
-
+ {{#if sorting}}{{/if}}
@@ -27,7 +27,7 @@
-
+
{{costFormat price}}₽
{{#if original_price}}
@@ -36,6 +36,7 @@
{{#if discount}}
-{{discount}}%
{{/if}}
+
-
{{title}}
@@ -64,4 +64,8 @@
{{/each}}
+
+ {{#if more}}
+
+ {{/if}}
\ No newline at end of file
diff --git a/src/scripts/components/card/view/card.ts b/src/scripts/components/card/view/card.ts
index 597f128..3328105 100644
--- a/src/scripts/components/card/view/card.ts
+++ b/src/scripts/components/card/view/card.ts
@@ -1,9 +1,10 @@
import card from './card.hbs?raw';
import { rootId } from '@/services/app/config';
import Handlebars from 'handlebars';
+import {b} from "vite/dist/node/types.d-aGj9QkWt";
export interface CardViewInterface {
- render(data: { products: any[], page_title?: string }, title?: string): void;
+ render(data: { products: any[], page_title?: string }, title?: string, newRootId?: string, sorting?: boolean, flag?: boolean, url?: string): void;
}
export class CardView implements CardViewInterface {
@@ -13,15 +14,17 @@ export class CardView implements CardViewInterface {
this.compiled = Handlebars.compile(card);
}
- render = (data: { products: any[], page_title?: string }, title?: string): void => {
- const rootElement = document.getElementById(rootId);
+ render = (data: { products: any[], page_title?: string }, title?: string, newRootId: string = rootId, sorting: boolean = true, flag: boolean = false, url: string = ''): void => {
+
+ data.sorting = sorting;
+
+ const rootElement = document.getElementById(newRootId);
+
if (!rootElement) {
- console.log(`ошибка rootElement ${rootElement} -- rootId ${rootId}`);
+ // console.log(`ошибка rootElement ${rootElement} -- rootId ${rootId}`);
return;
}
- console.log(data.page_title, title);
-
rootElement.innerHTML = '';
const templateElement = document.createElement('div');
@@ -34,5 +37,18 @@ export class CardView implements CardViewInterface {
templateElement.innerHTML = this.compiled(data);
rootElement.appendChild(templateElement);
+
+ if (flag) {
+ this.initButton(flag, url);
+ }
+ };
+
+ initButton = (flag: boolean, url: string) => {
+ const button = document.getElementById('show-more')
+
+ if (flag && button) {
+ button.setAttribute('href', url);
+ button.setAttribute('router', 'changed-active');
+ }
};
}
diff --git a/src/scripts/components/card/view/catalog.scss b/src/scripts/components/card/view/catalog.scss
index 594f773..b262f17 100644
--- a/src/scripts/components/card/view/catalog.scss
+++ b/src/scripts/components/card/view/catalog.scss
@@ -33,7 +33,6 @@
padding: parameters.$padding;
display: flex;
flex-direction: column;
- justify-content: space-between;
align-items: center;
text-align: center;
position: relative;
@@ -86,9 +85,22 @@
}
}
- &__card-info {
- width: 100%;
+ .product-page__rating {
+ margin-bottom: 0;
+ display: flex;
+ gap: 1px;
+ }
+
+ &__wrap {
display: flex;
+ margin-bottom: 10px;
+ gap: 15px;
+ width: 100%;
+ }
+
+ &__card-info {
+ width: 70%;
+ display: inline-block;
flex-direction: column;
align-items: center;
@@ -116,7 +128,6 @@
font-size: 16px;
color: parameters.$color-discount;
margin-left: 8px;
- margin-right: 40px;
}
}
@@ -158,6 +169,12 @@
&:hover {
transform: none;
}
+
+ &__show-more {
+ @include mixins.checkout-btn();
+ margin: 10px;
+ width: calc(100% - 20px);
+ }
}
/* Центрирование и стили для сообщения ожидания */
@@ -182,6 +199,12 @@
}
}
+.reviews-header-scroll {
+ display: inline-flex;
+ align-items: center;
+ width: 68px;
+}
+
@keyframes spin {
to {
transform: rotate(360deg);
@@ -191,7 +214,7 @@
/* Медиазапросы для адаптивности */
@media (max-width: parameters.$phone-smallest-header) {
.catalog {
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));;
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));;
width: 100%;
padding: parameters.$padding 10px;
@@ -208,6 +231,10 @@
width: 100%;
text-align: center;
}
+
+ &__wrap {
+ display: block;
+ }
}
}
@@ -223,7 +250,7 @@
}
&__card-image {
- height: 300px;
+ height: 200px;
}
&__buy-button {
@@ -231,4 +258,4 @@
text-align: center;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/scripts/components/cart/api/cart-api.ts b/src/scripts/components/cart/api/cart-api.ts
index 33b29fb..8f431c6 100644
--- a/src/scripts/components/cart/api/cart-api.ts
+++ b/src/scripts/components/cart/api/cart-api.ts
@@ -31,7 +31,7 @@ export class CartApiInterface {
static async updateProductQuantity(productId: string, count: number): Promise {
return csrf.patch(`${backurl}${CART_URLS.updateProductQuantity.route}${productId}`, { count })
.then(res => {
- if (res.status !== 204) {
+ if (res.status !== 200) {
throw new Error(`Ошибка при обновлении количества: ${res.status} - ${res.body.error_message}`);
}
})
@@ -54,13 +54,13 @@ export class CartApiInterface {
csrf.refreshToken();
}
- console.error(`ошибка при выборе: ${res.status} - ${res.body}`);
+ //// console.error(`ошибка при выборе: ${res.status} - ${res.body}`);
throw new Error(`${res.body}`);
})
- .catch(err => {
- console.error(err);
- })
+ /*.catch(err => {
+ // console.error(err);
+ })*/
}
/**
@@ -79,13 +79,13 @@ export class CartApiInterface {
csrf.refreshToken();
}
- console.error(`ошибка при выборе: ${res.status} - ${res.body}`);
+ //// console.error(`ошибка при выборе: ${res.status} - ${res.body}`);
throw new Error(`${res.body}`);
})
- .catch(err => {
- console.error(err);
- })
+ /*.catch(err => {
+ // console.error(err);
+ })*/
}
/**
diff --git a/src/scripts/components/cart/elements/data-sampling/presenter/data-sampling.ts b/src/scripts/components/cart/elements/data-sampling/presenter/data-sampling.ts
index cbd1a2c..a03643f 100644
--- a/src/scripts/components/cart/elements/data-sampling/presenter/data-sampling.ts
+++ b/src/scripts/components/cart/elements/data-sampling/presenter/data-sampling.ts
@@ -72,7 +72,7 @@ export class DataSamplingPresenter {
this.cartView.initializeCheckboxes(this.cartData.products);
this.updateSelectedCount();});
} catch (error) {
- console.error("Ошибка при изменении состояния 'Выбрать всё':", error);
+ //// console.error("Ошибка при изменении состояния 'Выбрать всё':", error);
}
}
@@ -126,7 +126,7 @@ export class DataSamplingPresenter {
}
})
} catch (error) {
- console.error("Ошибка при изменении состояния 'Удалить выбранное':", error);
+ //// console.error("Ошибка при изменении состояния 'Удалить выбранное':", error);
}
}
@@ -151,7 +151,7 @@ export class DataSamplingPresenter {
this.cartView.updateSelectAllCheckbox(allChecked, isIndeterminate);
} catch (error) {
- console.error("Ошибка при изменении состояния 'Выбрать всё':", error);
+ //// console.error("Ошибка при изменении состояния 'Выбрать всё':", error);
}
}
}
diff --git a/src/scripts/components/cart/elements/left-cards/presenter/left-cards.ts b/src/scripts/components/cart/elements/left-cards/presenter/left-cards.ts
index 2526482..f1f9e50 100644
--- a/src/scripts/components/cart/elements/left-cards/presenter/left-cards.ts
+++ b/src/scripts/components/cart/elements/left-cards/presenter/left-cards.ts
@@ -67,6 +67,10 @@ export class LeftCardsPresenter {
this.view.updateSelectedCount(selectedCount);
this.rightCartPresenter.calculateCartTotals(this.cartData);
+ if (this.cartData.products.length === 0) {
+ LeftCardsView.displayEmptyCartMessage();
+ }
+
// Обновляем состояние чекбокса "Выбрать все"
this.updateSelectAllCheckbox();
}
@@ -125,7 +129,7 @@ export class LeftCardsPresenter {
await this.rightCartPresenter.calculateCartTotals(this.cartData);
})
} catch (error) {
- console.error('Ошибка при обновлении количества товара:', error);
+ //// console.error('Ошибка при обновлении количества товара:', error);
}
}
}
@@ -149,7 +153,7 @@ export class LeftCardsPresenter {
this.checkIfCartIsEmpty();
})
} catch (error) {
- console.error('Ошибка при удалении товара из корзины:', error);
+ //// console.error('Ошибка при удалении товара из корзины:', error);
}
}
@@ -170,7 +174,7 @@ export class LeftCardsPresenter {
}
})
} catch (error) {
- console.error('Ошибка при выборе товара в корзине:', error);
+ //// console.error('Ошибка при выборе товара в корзине:', error);
}
}
diff --git a/src/scripts/components/cart/elements/left-cards/view/left-cards.hbs b/src/scripts/components/cart/elements/left-cards/view/left-cards.hbs
index 59b6cb3..8565bfa 100644
--- a/src/scripts/components/cart/elements/left-cards/view/left-cards.hbs
+++ b/src/scripts/components/cart/elements/left-cards/view/left-cards.hbs
@@ -40,13 +40,10 @@
{{/each}}
-{{else}}
-
-
-
Ваша корзина пуста
-
Добавьте товары, чтобы увидеть их здесь!
-
{{/if}}
+
+
+
+
Ваша корзина пуста
+
Добавьте товары, чтобы увидеть их здесь!
+
diff --git a/src/scripts/components/cart/elements/left-cards/view/left-cards.scss b/src/scripts/components/cart/elements/left-cards/view/left-cards.scss
index 09930a8..a4501b7 100644
--- a/src/scripts/components/cart/elements/left-cards/view/left-cards.scss
+++ b/src/scripts/components/cart/elements/left-cards/view/left-cards.scss
@@ -16,9 +16,8 @@
}
&__image {
- width: parameters.$card-image-width;
padding: parameters.$micro-padding;
- min-width: parameters.$min-card-image-width;
+ width: parameters.$min-card-image-width;
img {
width: parameters.$width-max;
@@ -146,38 +145,7 @@
@include basic-mixins.checkbox;
- &__placeholder {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- padding: 30px;
- text-align: center;
- font-size: 20px;
- font-weight: bold;
- color: parameters.$default-text-color;
- background-color: #f9f9f9;
- border-radius: 12px;
- box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
- animation: fadeIn 1s ease-in-out;
- }
-
- &__icon {
- margin-bottom: 15px;
- opacity: 0.7;
- transition: transform 0.3s ease;
- }
-
- &__placeholder-text,
- &__placeholder-suggestion {
- font-size: 18px;
- color: #555;
- }
-
- &__placeholder:hover .cart-item__icon {
- transform: scale(1.1);
- opacity: 1;
- }
+ @include basic-mixins.placeholder;
&__left {
display: flex;
diff --git a/src/scripts/components/cart/elements/left-cards/view/left-cards.ts b/src/scripts/components/cart/elements/left-cards/view/left-cards.ts
index 5bedef1..00b8a6a 100644
--- a/src/scripts/components/cart/elements/left-cards/view/left-cards.ts
+++ b/src/scripts/components/cart/elements/left-cards/view/left-cards.ts
@@ -195,16 +195,10 @@ export class LeftCardsView {
* Отображает сообщение о том, что корзина пуста.
*/
public static displayEmptyCartMessage(): void {
- const cartContainer = document.querySelector('.cart-items');
- if (cartContainer) {
- cartContainer.innerHTML = '\n' +
- '
\n' +
- '
Ваша корзина пуста
\n' +
- '
Добавьте товары, чтобы увидеть их здесь!
\n' +
- '
';
+ const placeholder = document.getElementById('empty-cart-placeholder');
+
+ if (placeholder) {
+ placeholder.style.display = "flex";
}
}
}
diff --git a/src/scripts/components/cart/elements/right-element-of-cart/presenter/calculate-cart-totals.ts b/src/scripts/components/cart/elements/right-element-of-cart/presenter/calculate-cart-totals.ts
index 0577606..219fdc6 100644
--- a/src/scripts/components/cart/elements/right-element-of-cart/presenter/calculate-cart-totals.ts
+++ b/src/scripts/components/cart/elements/right-element-of-cart/presenter/calculate-cart-totals.ts
@@ -42,7 +42,7 @@ export class RightCartPresenter {
* @returns {Promise} Промис, разрешающийся после обновления данных корзины.
*/
async calculateCartTotals(cartData : CartData) {
- console.log(cartData);
+ // console.log(cartData);
this.cartData = cartData;
const selectedItems = this.cartData.products.filter((product: CartProduct) => product.isSelected);
diff --git a/src/scripts/components/cart/elements/right-element-of-cart/view/calculate-cart-totals.ts b/src/scripts/components/cart/elements/right-element-of-cart/view/calculate-cart-totals.ts
index d602b1e..9d584c1 100644
--- a/src/scripts/components/cart/elements/right-element-of-cart/view/calculate-cart-totals.ts
+++ b/src/scripts/components/cart/elements/right-element-of-cart/view/calculate-cart-totals.ts
@@ -72,7 +72,7 @@ export class RightCartView {
) {
if (this.totalItemsElement && this.benefitPriceElement && this.finalPriceElement) {
this.totalItemsElement.innerHTML = `
- Всего: ${totalItems} ${Helper.pluralize(totalItems, 'товар', 'товара', 'товаров')}, ${Math.round(totalWeight * 10) / 10}кг
+ Всего: ${totalItems} ${Helper.pluralize(totalItems, 'товар', 'товара', 'товаров')}
${totalPrice} ${currency ? Helper.isNotUndefined(currency) : '₽' }
`;
@@ -121,7 +121,7 @@ export class RightCartView {
// Обработчик клика для вывода в консоль выбранных товаров
this.checkoutButton.addEventListener('click', () => {
- console.log('Selected products:', selectedItems);
+ // console.log('Selected products:', selectedItems);
});
}
}
diff --git a/src/scripts/components/cart/elements/right-element-of-cart/view/right-element-of-cart.hbs b/src/scripts/components/cart/elements/right-element-of-cart/view/right-element-of-cart.hbs
index fde7428..3857cd9 100644
--- a/src/scripts/components/cart/elements/right-element-of-cart/view/right-element-of-cart.hbs
+++ b/src/scripts/components/cart/elements/right-element-of-cart/view/right-element-of-cart.hbs
@@ -3,7 +3,7 @@
- Всего: {{totalItems}} {{pluralize totalItems 'товар' 'товара' 'товаров'}}, {{totalWeight}}кг
+ Всего: {{totalItems}} {{pluralize totalItems 'товар' 'товара' 'товаров'}}
{{totalPrice}}{{#if (isNotUndefined currency)}}{{currency}}{{else}}₽{{/if}}
diff --git a/src/scripts/components/cart/presenter/cart.ts b/src/scripts/components/cart/presenter/cart.ts
index 30857af..aaa5bea 100644
--- a/src/scripts/components/cart/presenter/cart.ts
+++ b/src/scripts/components/cart/presenter/cart.ts
@@ -73,15 +73,19 @@ export class CartPresenter {
*/
async initializeCart() {
csrf.fetchToken().then(() =>{
- // Инициализируем чекбоксы на основе данных корзины
- this.cartView.initializeCheckboxes(this.cartData.products);
+ try {
+ // Инициализируем чекбоксы на основе данных корзины
+ this.cartView.initializeCheckboxes(this.cartData.products);
- // Инициализируем работу с выборкой данных.
- this.dataSamplingPresenter.initializeDataSampling();
+ // Инициализируем работу с выборкой данных.
+ this.dataSamplingPresenter.initializeDataSampling();
- // Выполняем дополнительные настройки.
- this.leftCardsPresenter.updateSelectedCount();
- this.rightCartPresenter.calculateCartTotals(this.cartData);
+ // Выполняем дополнительные настройки.
+ this.leftCardsPresenter.updateSelectedCount();
+ this.rightCartPresenter.calculateCartTotals(this.cartData);
+ } catch (err) {
+
+ }
});
}
}
diff --git a/src/scripts/components/cart/view/cart-builder.ts b/src/scripts/components/cart/view/cart-builder.ts
index 03f42f1..f537395 100644
--- a/src/scripts/components/cart/view/cart-builder.ts
+++ b/src/scripts/components/cart/view/cart-builder.ts
@@ -93,7 +93,7 @@ export class CartBuilder {
await this.renderCart();
this.initializeCartPresenter();
} catch (error) {
- console.error(error);
+ // console.error(error);
}
}
diff --git a/src/scripts/components/category/api/category.ts b/src/scripts/components/category/api/category.ts
index 3511456..9b99a10 100644
--- a/src/scripts/components/category/api/category.ts
+++ b/src/scripts/components/category/api/category.ts
@@ -40,7 +40,7 @@ export class CategoryApi implements CategoryApiInterface {
return fetch(`${backurl}/categories`)
.then((response) => response.json())
.then((data) => {
- console.log(data);
+ // console.log(data);
return data;
})
.then((data: ApiResponse
) => {
@@ -65,7 +65,7 @@ export class CategoryApi implements CategoryApiInterface {
return data as ProductError;
})
.catch(error =>{
- console.log(error);
+ // console.log(error);
return error;
})
diff --git a/src/scripts/components/category/presenter/category.ts b/src/scripts/components/category/presenter/category.ts
index aa7670a..51d3a42 100644
--- a/src/scripts/components/category/presenter/category.ts
+++ b/src/scripts/components/category/presenter/category.ts
@@ -31,7 +31,7 @@ export class CategoryPresenter {
this.api.getCategories()
.then((data) => {
if (typeof data === 'string') {
- console.error(data);
+ // console.error(data);
} else {
data.forEach((card) => {
card.picture = `${backurl}/${card.picture}`;
@@ -93,7 +93,7 @@ export class CategoryPresenter {
if (order) urlParams.append('order', order);
const categoryUrl = `${backurl}/category/${categoryLink}${urlParams.toString() ? `?${urlParams.toString()}` : ''}`;
- console.log(categoryUrl);
+ // console.log(categoryUrl);
this.api.getCategoryProducts(categoryUrl)
.then((products) => {
if (Array.isArray(products)) {
@@ -105,7 +105,7 @@ export class CategoryPresenter {
this.categoryView.renderCategoryProducts({ products }, categoryLink);
this.updateBreadcrumbs(`Категория: ${categoryLink}`, `/category/${categoryLink}${urlParams.toString() ? `?${urlParams.toString()}` : ''}`);
} else {
- console.error('No products found in category');
+ // console.error('No products found in category');
}
})
.then(() => {
@@ -128,11 +128,11 @@ export class CategoryPresenter {
this.dropdownPresenter.initView();
} else {
- console.error('Dropdown container not found');
+ // console.error('Dropdown container not found');
}
})
.catch((e) => {
- console.error('Error fetching category products:', e);
+ // console.error('Error fetching category products:', e);
});
}
diff --git a/src/scripts/components/category/view/category.ts b/src/scripts/components/category/view/category.ts
index 95a1e95..808d512 100644
--- a/src/scripts/components/category/view/category.ts
+++ b/src/scripts/components/category/view/category.ts
@@ -32,16 +32,40 @@ export class CategoryView implements CategoryViewInterface {
root.innerHTML = htmlContent;
}
+ private transform (link: string): string {
+ let name = ''
+
+ switch (link) {
+ case 'perfumery':
+ name = 'Парфюмерия'
+ break;
+ case 'appliances':
+ name = 'Бытовая техника'
+ break;
+ case 'clothes':
+ name = 'Одежда'
+ break;
+ case 'electronics':
+ name = 'Электроника'
+ break;
+ default:
+ name = 'Другое'
+ break;
+ }
+
+ return name;
+ }
+
renderCategoryProducts = (products: { products: any[] , page_title?: string }, link: string): void => {
- products.page_title = 'Категория: ';
+ products.page_title = `Категория: `;
- this.cardView.render(products, link);
+ this.cardView.render(products, this.transform(link));
}
renderBreadcrumbs = (breadcrumbs: { title: string, link: string }[]) => {
// const breadcrumbContainer = document.getElementById('breadcrumbs');
// if (!breadcrumbContainer) {
- // console.error('Не найден контейнер для хлебных крошек');
+ // // console.error('Не найден контейнер для хлебных крошек');
// return;
// }
//
diff --git a/src/scripts/components/custom-messages/error/error.hbs b/src/scripts/components/custom-messages/error/error.hbs
index 1d6ca7d..8c90ac8 100644
--- a/src/scripts/components/custom-messages/error/error.hbs
+++ b/src/scripts/components/custom-messages/error/error.hbs
@@ -1,5 +1,5 @@
Ошибка {{name}}
{{description}}
-
Нажмите, сюда, чтобы вернуться на главную!
+
Нажмите сюда, чтобы вернуться на главную!
\ No newline at end of file
diff --git a/src/scripts/components/custom-messages/error/error.ts b/src/scripts/components/custom-messages/error/error.ts
index c085b84..ea5a6de 100644
--- a/src/scripts/components/custom-messages/error/error.ts
+++ b/src/scripts/components/custom-messages/error/error.ts
@@ -15,7 +15,7 @@ const returnPage = '/catalog';
* @returns {Promise} Промис, который разрешается после успешного отображения страницы ошибки.
*/
export function errorPage(name: string): void {
- let config = {
+ const config = {
name: name,
description: errorsDescriptions[name],
return: returnPage,
@@ -25,7 +25,7 @@ export function errorPage(name: string): void {
const rootElement = document.getElementById(rootId) as HTMLElement;
if (!rootElement) {
- console.error(`Element ID = ${rootId} not found`);
+ // console.error(`Element ID = ${rootId} not found`);
}
rootElement.innerHTML = '';
diff --git a/src/scripts/components/custom-messages/error/errors.ts b/src/scripts/components/custom-messages/error/errors.ts
index 37926f3..8a675d5 100644
--- a/src/scripts/components/custom-messages/error/errors.ts
+++ b/src/scripts/components/custom-messages/error/errors.ts
@@ -5,5 +5,5 @@
* @property {string} '404' - Описание ошибки 404 (Не найдено), отображаемое при попытке обращения к несуществующему адресу.
*/
export const errorsDescriptions: { [key: string]: string } = {
- 404: 'Вы обращаетесь на несуществующему адресу.',
+ 404: 'Вы обращаетесь к несуществующему адресу.',
};
diff --git a/src/scripts/components/custom-messages/soon/soon.hbs b/src/scripts/components/custom-messages/soon/soon.hbs
index 6ba1d4a..c16d771 100644
--- a/src/scripts/components/custom-messages/soon/soon.hbs
+++ b/src/scripts/components/custom-messages/soon/soon.hbs
@@ -1,5 +1,5 @@
Скоро
Будет добавлено позже.
-
Нажмите, сюда, чтобы вернуться назад
+
Нажмите сюда, чтобы вернуться назад
\ No newline at end of file
diff --git a/src/scripts/components/dropdown-btn/api/dropdown.ts b/src/scripts/components/dropdown-btn/api/dropdown.ts
index b315f96..5e292eb 100644
--- a/src/scripts/components/dropdown-btn/api/dropdown.ts
+++ b/src/scripts/components/dropdown-btn/api/dropdown.ts
@@ -16,14 +16,23 @@ export class DropdownAPI {
}
- static sortProducts = (endpoint: string, sort: string, order: string): Promise => {
+ static sortProducts = async (endpoint: string, sort: string, order: string): Promise | undefined => {
const params = new URLSearchParams({
sort,
order,
});
- console.log(123, `${endpoint}&${params.toString()}`);
-
- return get(`${endpoint}&${params.toString()}`)
+ try {
+ const response = await get(`${endpoint}&${params.toString()}`);
+ if (response === undefined) {
+ console.log("Response is undefined", response);
+ return undefined; // Либо можно обработать это как-то по-другому
+ } else {
+ return response;
+ }
+ } catch {
+ return undefined;
+ }
+ // console.log(123, `${endpoint}&${params.toString()}`);
};
}
diff --git a/src/scripts/components/dropdown-btn/presenter/dropdown.ts b/src/scripts/components/dropdown-btn/presenter/dropdown.ts
index 3f0735f..04fa193 100644
--- a/src/scripts/components/dropdown-btn/presenter/dropdown.ts
+++ b/src/scripts/components/dropdown-btn/presenter/dropdown.ts
@@ -22,10 +22,10 @@ export class DropdownPresenter {
initView(): void {
const container = document.getElementById(this.config.containerId);
- console.log('init view');
+ // console.log('init view');
if (!container) {
- console.error(`Контейнер с ID ${this.config.containerId} не найден`);
+ // console.error(`Контейнер с ID ${this.config.containerId} не найден`);
return;
}
diff --git a/src/scripts/components/modal/presenters/base-modal.ts b/src/scripts/components/modal/presenters/base-modal.ts
index 6687c15..8f7722a 100644
--- a/src/scripts/components/modal/presenters/base-modal.ts
+++ b/src/scripts/components/modal/presenters/base-modal.ts
@@ -29,7 +29,7 @@ export abstract class BaseModal {
//
// const closeButton = this.modalElement.querySelector(this.config.btnClose);
// if (!closeButton) {
- // console.error(closeButton, this.config.btnClose);
+ // // console.error(closeButton, this.config.btnClose);
// return;
// }
//
@@ -48,19 +48,26 @@ export abstract class BaseModal {
}, 300);
}
}
-
private handleOutsideClick(event: MouseEvent) {
+ const modalContent = this.modalElement?.querySelector('.modal__container');
+ const suggestionsList = document.querySelector('.suggestions');
+ const suggestionItem = event.target && (event.target as HTMLElement).closest('.suggestions__item');
+
if (!this.modalElement) {
- console.error(this.modalElement, ' not found');
+ // console.error(this.modalElement, ' not found');
return;
}
- const modalContent = this.modalElement.querySelector('.modal__container');
- if (modalContent && !modalContent.contains(event.target as Node)) {
+ const isClickInsideModal = modalContent && modalContent.contains(event.target as Node);
+ const isClickInsideSuggestions = suggestionsList && (suggestionsList.contains(event.target as Node) || suggestionItem);
+
+ // Close the modal only if the click is outside both modal content and suggestions
+ if (!isClickInsideModal && !isClickInsideSuggestions) {
this.close();
}
}
+
protected abstract renderContent(): void;
protected handleFormSubmission(
diff --git a/src/scripts/components/modal/presenters/change-password.ts b/src/scripts/components/modal/presenters/change-password.ts
new file mode 100644
index 0000000..6ec414b
--- /dev/null
+++ b/src/scripts/components/modal/presenters/change-password.ts
@@ -0,0 +1,134 @@
+import { BaseModal } from './base-modal';
+import { changePasswordConfig, ModalControllerParams } from '../views/types';
+import { ModalRenderer } from '../views/modal-render';
+import { backurl } from '../../../../services/app/config';
+import { csrf } from '../../../../services/api/CSRFService';
+
+export class ChangePasswordModal extends BaseModal {
+ private readonly onSubmitCallback: () => void;
+
+ constructor(
+ config: ModalControllerParams,
+ onSubmit: () => void,
+ ) {
+ super(config, changePasswordConfig, {});
+ this.onSubmitCallback = onSubmit;
+ }
+
+ protected renderContent(): void {
+ changePasswordConfig.fields.forEach((field) => {
+ field.value = '';
+ });
+
+ this.modalElement = ModalRenderer.render(this.config.rootId, changePasswordConfig);
+ this.setupListeners();
+ }
+
+ protected setupListeners() {
+ if (!this.modalElement) return;
+ const form = this.modalElement.querySelector(`#${changePasswordConfig.formId}`) as HTMLFormElement;
+ if (!form) return;
+
+ form.addEventListener('submit', async (event) => {
+ event.preventDefault();
+
+ if (!this.validateForm()) {
+ return;
+ }
+
+ const formData = new FormData(form);
+ const updatedPassword: Record = {};
+
+ formData.forEach((value, key) => {
+ updatedPassword[key] = value as string;
+ });
+
+ // Отправка данных на сервер для смены пароля
+ return csrf.put(`${backurl}/change_password`, updatedPassword)
+ .then(res => {
+ switch (res.status) {
+ case 200:
+ this.onSubmitCallback();
+ this.close();
+ return;
+ case 403:
+ csrf.refreshToken();
+ throw new Error('протух csrf токен, попробуйте еще раз');
+ default:
+ throw new Error(`${res.status} - ${res.body.error_message}`);
+ }
+ })
+ .catch((err) => {
+ // console.error('Ошибка смены пароля:', err);
+ });
+ });
+
+ if (!this.modalElement) {
+ return;
+ }
+
+ const closeButton = this.modalElement.querySelector('.btn__close');
+ closeButton?.addEventListener('click', this.close.bind(this));
+ }
+
+ private validateForm(): boolean {
+ let isValid = true;
+
+ changePasswordConfig.fields.forEach((field) => {
+ const inputElement = this.modalElement?.querySelector(`[name="${field.name}"]`) as HTMLInputElement;
+
+ const regex = /^[a-zA-Z0-9!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]+$/u;
+ this.removeInputError(inputElement);
+ isValid = true;
+
+ if (inputElement && !inputElement.value.trim()) {
+ this.addInputError(inputElement, `Поле "${field.label}" не должно быть пустым`);
+ isValid = false;
+ } else if (field.name === 'repeatPassword' && inputElement && inputElement.value !== this.modalElement?.querySelector('[name="new-password"]')?.value) {
+ this.addInputError(inputElement, 'Пароли не совпадают');
+ isValid = false;
+ } else if (!regex.test(inputElement.value)) {
+ this.addInputError(inputElement, 'Пароль должен содержать только допустимые символы');
+ isValid = false;
+ }
+ });
+
+ return isValid;
+ }
+
+ private addInputError(element: HTMLElement, message: string) {
+ const errorId = element.getAttribute('data-error-id') || `${element.name}Error`;
+ let errorElement = document.getElementById(errorId);
+
+ if (!errorElement) {
+ errorElement = document.createElement('div');
+ errorElement.id = errorId;
+ errorElement.className = 'form__error';
+ element.parentElement?.appendChild(errorElement);
+ }
+
+ errorElement.textContent = message;
+ errorElement.style.display = 'block';
+ element.classList.add('form__input_invalid');
+ element.style.backgroundColor = '#ffe6e6';
+ element.style.borderColor = 'red';
+ }
+
+ private removeInputError(element: HTMLElement) {
+ const errorId = element.getAttribute('data-error-id') || `${element.name}Error`;
+ const errorElement = document.getElementById(errorId);
+
+ if (errorElement) {
+ errorElement.style.display = 'none';
+ errorElement.textContent = '';
+ element.classList.remove('form__input_invalid');
+ element.style.borderColor = '';
+ element.style.backgroundColor = '';
+ }
+ }
+
+ public open() {
+ this.renderContent();
+ super.open();
+ }
+}
diff --git a/src/scripts/components/modal/presenters/modal-address.ts b/src/scripts/components/modal/presenters/modal-address.ts
index d0bf1de..9a3b9d9 100644
--- a/src/scripts/components/modal/presenters/modal-address.ts
+++ b/src/scripts/components/modal/presenters/modal-address.ts
@@ -3,11 +3,17 @@ import { editAddressConfig, ModalControllerParams } from '../views/types';
import { ModalRenderer } from '../views/modal-render';
import { backurl } from '../../../../services/app/config';
import { csrf } from '../../../../services/api/CSRFService';
+import { debounce } from '../../../utils/debounce';
+import { API_KEY } from './key-yandex';
export class AddressModal extends BaseModal {
private readonly onSubmitCallback: (updatedAddress: Record) => void;
- constructor(config: ModalControllerParams, userAddress: Record, onSubmit: (updatedAddress: Record) => void) {
+ constructor(
+ config: ModalControllerParams,
+ userAddress: Record,
+ onSubmit: (updatedAddress: Record) => void,
+ ) {
super(config, editAddressConfig, userAddress);
this.onSubmitCallback = onSubmit;
}
@@ -26,46 +32,101 @@ export class AddressModal extends BaseModal {
const form = this.modalElement.querySelector(`#${editAddressConfig.formId}`) as HTMLFormElement;
if (!form) return;
- form.addEventListener('submit', async (event) => {
- event.preventDefault();
+ const addressInput = this.modalElement.querySelector(`[name="address"]`) as HTMLInputElement;
+ const suggestionsList = this.modalElement.querySelector('#suggestions-list') as HTMLElement;
+
+ if (addressInput && suggestionsList) {
+ addressInput.addEventListener(
+ 'input',
+ debounce(async () => {
+ const query = addressInput.value.trim();
+ if (query.length < 3) {
+ suggestionsList.innerHTML = ''; // Очистка списка предложений
+ return;
+ }
- if (!this.validateForm()) {
- console.log("Validation failed: Some required fields are empty.");
- return;
- }
+ const suggestions = await this.fetchAddressSuggestions(query);
- const formData = new FormData(form);
- const updatedAddress: Record = {};
+ // Обновляем UI для показа подсказок
+ this.updateSuggestionsList(suggestionsList, suggestions, addressInput);
+ }, 300),
+ );
- formData.forEach((value, key) => {
- updatedAddress[key] = value as string;
- });
+ form.addEventListener('submit', async (event) => {
+ event.preventDefault();
+
+ if (!this.validateForm()) {
+ // console.log('Validation failed: Some required fields are empty.');
+ return;
+ }
+
+ const formData = new FormData(form);
+ const updatedAddress: Record = {};
+
+ formData.forEach((value, key) => {
+ updatedAddress[key] = value as string;
+ });
return csrf.put(`${backurl}/address`, updatedAddress)
.then(res => {
- switch (res.status){
+ switch (res.status) {
case 200:
this.onSubmitCallback(updatedAddress);
this.close();
return;
case 403:
csrf.refreshToken();
- throw new Error('протух csrf токен, попробуйте еще раз')
+ throw new Error('протух csrf токен, попробуйте еще раз');
default:
throw new Error(`${res.status} - ${res.body.error_message}`);
}
})
.catch((err) => {
- console.error('Error updating address:', err);
- })
+ // console.error('Error updating address:', err);
+ });
});
- if (!this.modalElement) {
- return;
+ if (!this.modalElement) {
+ return;
+ }
+
+ const closeButton = this.modalElement.querySelector('.btn__close');
+ closeButton?.addEventListener('click', this.close.bind(this));
}
+ }
+
+ private updateSuggestionsList(suggestionsList: HTMLElement, suggestions: any[], addressInput: HTMLInputElement) {
+ suggestionsList.innerHTML = ''; // Очищаем текущие подсказки
- const closeButton = this.modalElement.querySelector('.btn__close');
- closeButton?.addEventListener('click', this.close.bind(this));
+ suggestions.forEach((suggestion) => {
+ const option = document.createElement('div');
+ option.className = 'suggestions__item';
+ option.textContent = suggestion.address.formatted_address;
+
+ option.addEventListener('click', () => {
+ addressInput.value = suggestion.address.formatted_address;
+ suggestionsList.innerHTML = '';
+ });
+
+ suggestionsList.appendChild(option);
+ });
+ }
+
+ private async fetchAddressSuggestions(query: string): Promise {
+ const url = `https://suggest-maps.yandex.ru/v1/suggest?apikey=${API_KEY}&text=${encodeURIComponent(query)}&print_address=1&attrs=uri`;
+
+ try {
+ const response = await fetch(url);
+ if (!response.ok) {
+ throw new Error(`Ошибка API Яндекса: ${response.status}`);
+ }
+
+ const data = await response.json();
+ return data.results || [];
+ } catch (error) {
+ console.error('Ошибка получения подсказок адреса:', error);
+ return [];
+ }
}
private validateForm(): boolean {
@@ -74,11 +135,16 @@ export class AddressModal extends BaseModal {
editAddressConfig.fields.forEach((field) => {
const inputElement = this.modalElement?.querySelector(`[name="${field.name}"]`) as HTMLInputElement;
- if (inputElement && field.name !== 'flat' && !inputElement.value.trim()) {
+ const regex = /^[a-zA-Zа-яА-Я0-9№\s.,\/-]+$/u;
+ this.removeInputError(inputElement);
+ isValid = true;
+
+ if (inputElement && !inputElement.value.trim()) {
this.addInputError(inputElement, `Поле "${field.label}" не должно быть пустым`);
isValid = false;
- } else {
- this.removeInputError(inputElement);
+ } else if (!regex.test(inputElement.value)) {
+ this.addInputError(inputElement, 'Поле должно содержать буквы цифры, точки, запятые, слэши и дефисы')
+ isValid = false;
}
});
@@ -120,4 +186,4 @@ export class AddressModal extends BaseModal {
this.renderContent();
super.open();
}
-}
\ No newline at end of file
+}
diff --git a/src/scripts/components/modal/presenters/modal-factory.ts b/src/scripts/components/modal/presenters/modal-factory.ts
index 2e23605..7ea25e8 100644
--- a/src/scripts/components/modal/presenters/modal-factory.ts
+++ b/src/scripts/components/modal/presenters/modal-factory.ts
@@ -26,7 +26,7 @@ export class ModalFactory {
const ModalClass = modalRegistry[modalType];
if (!ModalClass) {
- console.warn(`Unknown modal type: ${modalType}`);
+ // console.warn(`Unknown modal type: ${modalType}`);
return null;
}
diff --git a/src/scripts/components/modal/presenters/modal-personal.ts b/src/scripts/components/modal/presenters/modal-personal.ts
index ee2560c..96162d7 100644
--- a/src/scripts/components/modal/presenters/modal-personal.ts
+++ b/src/scripts/components/modal/presenters/modal-personal.ts
@@ -40,9 +40,9 @@ export class PersonalDataModal extends BaseModal {
updatedUser[key] = value as string;
});
- this.handleSubmit(updatedUser)
- .then(() => console.log("Submit successful"))
- .catch((error) => console.error("Submit failed:", error));
+ this.handleSubmit(updatedUser).finally()
+ // .then(() => console.log("Submit successful"))
+ // .catch((error) => console.error("Submit failed:", error));
});
const inputs = form.querySelectorAll('input, select') as HTMLInputElement[];
@@ -91,7 +91,7 @@ export class PersonalDataModal extends BaseModal {
}
})
.catch(err => {
- console.error('Error updating profile:', err);
+ // console.error('Error updating profile:', err);
this.displayBackError(err || 'ошибка, попробуйте еще раз');
});
}
@@ -104,7 +104,7 @@ export class PersonalDataModal extends BaseModal {
globalErrorMessage.innerText = message;
errorElement.style.display = 'block';
} else {
- console.error('Error elements not found in the modal.');
+ // console.error('Error elements not found in the modal.');
}
}
@@ -162,7 +162,7 @@ export class PersonalDataModal extends BaseModal {
}
private addInputError(element: HTMLElement, errorElement: HTMLElement | null, message: string) {
- console.log('Adding input error for:', element.id, 'with message:', message);
+ // console.log('Adding input error for:', element.id, 'with message:', message);
if (errorElement) {
errorElement.textContent = message;
@@ -174,7 +174,7 @@ export class PersonalDataModal extends BaseModal {
}
private removeInputError(element: HTMLElement, errorElement: HTMLElement | null) {
- console.log('Removing input error for:', element.id);
+ // console.log('Removing input error for:', element.id);
if (errorElement) {
errorElement.textContent = '';
diff --git a/src/scripts/components/modal/views/modal-render.ts b/src/scripts/components/modal/views/modal-render.ts
index 594ede8..379e77c 100644
--- a/src/scripts/components/modal/views/modal-render.ts
+++ b/src/scripts/components/modal/views/modal-render.ts
@@ -7,7 +7,7 @@ export class ModalRenderer {
public static render(rootId: string, data: any): HTMLElement | null {
const root = document.getElementById(rootId) as HTMLElement | null;
if (!root) {
- console.error(`Root element with id ${rootId} not found.`);
+ // console.error(`Root element with id ${rootId} not found.`);
return null;
}
diff --git a/src/scripts/components/modal/views/modal.hbs b/src/scripts/components/modal/views/modal.hbs
index 705376b..b5cae7b 100644
--- a/src/scripts/components/modal/views/modal.hbs
+++ b/src/scripts/components/modal/views/modal.hbs
@@ -22,7 +22,8 @@
{{/each}}
{{else}}
-
+
+
{{/if}}
diff --git a/src/scripts/components/modal/views/modal.scss b/src/scripts/components/modal/views/modal.scss
index 02a90ca..e30628c 100644
--- a/src/scripts/components/modal/views/modal.scss
+++ b/src/scripts/components/modal/views/modal.scss
@@ -1,5 +1,5 @@
@use "../../../../../public/css/parametrs/parameters";
-
+@use "src/scripts/components/searcher/view/suggestions.scss";
.modal {
position: fixed;
top: 0;
@@ -12,7 +12,6 @@
&__main {
font-family: parameters.$mega-font-family, sans-serif;
font-weight: parameters.$litle-font-weight;
- display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
@@ -28,6 +27,11 @@
cursor: default;
padding: parameters.$big-padding;
z-index:5000;
+
+
+ @media (max-width: 1000px) {
+ display: flex;
+ }
}
&__container {
@@ -78,6 +82,7 @@
&:focus {
outline: none;
}
+
}
&__submit-button {
diff --git a/src/scripts/components/modal/views/types.ts b/src/scripts/components/modal/views/types.ts
index 0c41d43..112af8c 100644
--- a/src/scripts/components/modal/views/types.ts
+++ b/src/scripts/components/modal/views/types.ts
@@ -48,43 +48,54 @@ export const editNameGenderEmailConfig = {
submitText: 'Сохранить изменения',
};
-export const editAddressConfig = {
- id: 'edit_address',
+export const changePasswordConfig = {
+ id: 'edit_password',
title: 'Редактировать',
- formId: 'address-edit-form',
+ formId: 'user-edit-form',
fields: [
{
- id: 'user-city',
- label: 'Город',
- type: 'text',
- name: 'city',
+ id: 'old-pass',
+ label: 'Старый пароль',
+ type: 'password',
+ name: 'old-password',
value: '',
- error_id: 'city-error',
+ error_id: 'passwordError',
},
{
- id: 'user-street',
- label: 'Улица',
- type: 'text',
- name: 'street',
+ id: 'new-pass',
+ label: 'Новый пароль',
+ type: 'newPassword',
+ name: 'old-password',
value: '',
- error_id: 'street-error',
+ error_id: 'newPasswordError',
},
{
- id: 'user-house',
- label: 'Дом',
- type: 'text',
- name: 'house',
+ id: 'repeat-pass',
+ label: 'Повторите',
+ type: 'password',
+ name: 'repeatPassword',
value: '',
- error_id: 'house-error',
+ error_id: 'repeatPasswordError',
},
+ ] as ModalField[],
+ submitText: 'Сохранить изменения',
+};
+
+
+
+export const editAddressConfig = {
+ id: 'edit_address',
+ title: 'Редактировать',
+ formId: 'address-edit-form',
+ fields: [
{
- id: 'user-flat',
- label: 'Квартира',
+ id: 'user-address',
+ label: 'Введите ваш адрес',
type: 'text',
- name: 'flat',
+ name: 'address',
value: '',
- error_id: 'flat-error',
- }
+ error_id: 'city-error',
+ },
],
submitText: 'Сохранить изменения',
};
diff --git a/src/scripts/components/notice/api/csat.ts b/src/scripts/components/notice/api/csat.ts
new file mode 100644
index 0000000..baace92
--- /dev/null
+++ b/src/scripts/components/notice/api/csat.ts
@@ -0,0 +1,16 @@
+import {getWithCred} from "../../../../services/api/without-csrf";
+import {backurl} from "../../../../services/app/config";
+
+export interface CsatApiInterface {
+ getData(): object | null;
+}
+
+export class CsatApi {
+ static async getData(): object | null {
+ try {
+ return await getWithCred(backurl + '/orders/updates');
+ } catch {
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/scripts/components/notice/presenters/csat.ts b/src/scripts/components/notice/presenters/csat.ts
new file mode 100644
index 0000000..b2928a3
--- /dev/null
+++ b/src/scripts/components/notice/presenters/csat.ts
@@ -0,0 +1,42 @@
+import Handlebars from 'handlebars';
+import csatTemplate from './csat.hbs';
+import {csatView, csatViewInterface} from "../view/csat";
+import {CsatApi} from "../api/csat";
+
+export interface csatPresenterInterface {
+ render(): void;
+}
+
+export class csatPresenter implements csatPresenterInterface {
+ private readonly compile: HandlebarsTemplates;
+ private readonly title: HTMLElement;
+ private readonly text: HTMLElement;
+ private readonly view: any;
+
+ constructor(containerId: string) {
+ this.view = new csatView(containerId);
+
+ this.startPolling();
+ }
+
+ private startPolling() {
+ this.updateData();
+ setInterval(() => this.updateData(), 60 * 1000); // Раз в минут
+ }
+
+ private async updateData() {
+ try {
+ const data = await CsatApi.getData();
+
+ if (data && data.body.orders_updates) {
+ this.render(data.body.orders_updates);
+ }
+ } catch {
+ return;
+ }
+ }
+
+ public render(data: any) {
+ this.view.render(data);
+ }
+}
diff --git a/src/scripts/components/notice/view/csat.hbs b/src/scripts/components/notice/view/csat.hbs
new file mode 100644
index 0000000..8a82a6c
--- /dev/null
+++ b/src/scripts/components/notice/view/csat.hbs
@@ -0,0 +1,12 @@
+
\ No newline at end of file
diff --git a/src/scripts/components/notice/view/csat.scss b/src/scripts/components/notice/view/csat.scss
new file mode 100644
index 0000000..2b793e8
--- /dev/null
+++ b/src/scripts/components/notice/view/csat.scss
@@ -0,0 +1,141 @@
+@use '../../../../../public/css/parametrs/basic-mixins';
+@use '../../../../../public/css/parametrs/parameters';
+
+$voting-frame-bg-color: #f4f4f4;
+$voting-frame-border-color: #cccccc;
+$voting-frame-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+$voting-frame-transition: 0.3s ease-in-out;
+$voting-frame-color-primary: #6a0dad;
+$voting-frame-color-gray: #8c8c8c;
+
+#voting-frame__title__text {
+ font-family: 'Ubuntu', sans-serif;
+ font-weight: 700;
+}
+
+.voting-frame {
+ position: fixed;
+ bottom: 1rem;
+ left: 1rem;
+ background: $voting-frame-bg-color;
+ border: 1px solid $voting-frame-border-color;
+ box-shadow: $voting-frame-shadow;
+ border-radius: 0.5rem;
+ overflow: hidden;
+ width: 450px;
+ max-width: 90%;
+ display: flex;
+ flex-direction: column;
+ z-index: 900;
+ transition: transform $voting-frame-transition, opacity $voting-frame-transition;
+ opacity: 1;
+ justify-content: space-between;
+ align-items: center;
+
+ &__title {
+ font-size: 1rem;
+ margin: 0;
+ font-weight: 400;
+ align-self: flex-start;
+ justify-content: space-between;
+ margin-bottom: 5px;
+
+ &__close {
+ position: absolute;
+ top: 10px;
+ right: 15px;
+ cursor: pointer;
+ font-size: 24px;
+ color: parameters.$text-color;
+ transition: color parameters.$short-duration ease;
+
+ &:hover {
+ color: black;
+ }
+ }
+ }
+
+ &__content {
+ padding: 1rem;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ min-width: 306px;
+ opacity: 1;
+ transition: opacity 0.3s ease, transform 0.3s ease;
+
+ &--hidden {
+ opacity: 0;
+ transform: scale(0.95);
+ pointer-events: none;
+ }
+ }
+
+ &__vote-button {
+ @include basic-mixins.checkout-btn();
+ width: 262px;
+ text-align: center;
+ font-size: 14px;
+ padding: 15px 0;
+ transition: background-color 0.2s ease;
+
+ &:hover {
+ background-color: $voting-frame-color-primary;
+ }
+ }
+
+ #back {
+ flex: 0 0 auto;
+ }
+}
+
+@for $i from 0 through 9 {
+ .rating-circles {
+ &__circle-#{$i} {
+ background: hsl((120 / 10) * $i, 100%, 50%);
+ }
+ }
+}
+
+.order-status {
+ margin-bottom: 10px;
+ font-size: 14px;
+
+ &__highlight {
+ color: $voting-frame-color-primary;
+ font-weight: bold;
+ }
+}
+
+@media (max-width: 350px) {
+ .voting-frame {
+ bottom: 0.5rem;
+ width: calc(100% - 20px);
+ max-width: none;
+
+ margin: 0 auto;
+ left: 0;
+ right: 0;
+ }
+
+ .voting-frame__title {
+ font-size: 0.9rem;
+ }
+
+ .rating-circles__labels span {
+ font-size: 12px;
+ }
+
+ .voting-frame__vote-button {
+ font-size: 12px;
+ padding: 10px 0;
+ }
+ .voting-frame__content {
+ min-width: calc(100% - 20px);
+ margin-top: 7px;
+ }
+
+ .order-status {
+ font-size: 12px;
+ }
+}
\ No newline at end of file
diff --git a/src/scripts/components/notice/view/csat.ts b/src/scripts/components/notice/view/csat.ts
new file mode 100644
index 0000000..d1b1cd1
--- /dev/null
+++ b/src/scripts/components/notice/view/csat.ts
@@ -0,0 +1,88 @@
+import Handlebars from 'handlebars';
+import csatTemplate from './csat.hbs'
+
+export interface csatViewInterface {
+ render(id: string, status: string): void;
+ switchOrderStatus(status: string): string;
+}
+
+export class csatView implements csatViewInterface {
+ private readonly compile: HandlebarsTemplates;
+ private readonly title: HTMLElement;
+ private readonly text: HTMLElement;
+ private container: HTMLElement | null;
+
+ constructor(containerId: string) {
+ this.compile = Handlebars.compile(csatTemplate)
+
+ this.container = document.getElementById(containerId);
+
+ this.container.innerHTML = this.compile({title: '', text: ''})
+
+ this.title = document.getElementById('voting-frame__title__text');
+ this.text = document.getElementById('content');
+
+ const backButton = document.getElementById('voting-frame__back');
+ const closeButton = document.getElementById('frame__title__close');
+
+ backButton.addEventListener('click', (data) => {
+ this.container.style.display = 'none';
+ });
+
+ closeButton?.addEventListener('click', () => {
+ this.container.style.display = 'none';
+ });
+ }
+
+ public switchOrderStatus(status: string): string {
+ switch (status) {
+ case 'awaiting_payment':
+ return 'Ожидает оплаты';
+ case 'paid':
+ return 'Оплачен';
+ case 'delivered':
+ return 'Доставлен';
+ case 'cancelled':
+ return 'Отменен';
+ default:
+ return '';
+ }
+ }
+
+ public render = (data_in: any) => {
+ let text: string = '';
+ let count: number = 0;
+
+ for (const key in data_in) {
+ text += `\nСтатус заказа ${data_in[key].order_id} изменился на "${this.switchOrderStatus(data_in[key].new_status)}"
`;
+ count += 1;
+ }
+
+ const data = {
+ "title": (count === 1) ? `Обновление заказа` : `Обновления заказов`,
+ "text": text.trim(),
+ };
+
+ // Style container for mobile responsiveness
+ this.container.style.display = 'flex';
+ this.container.style.maxHeight = '400px';
+ this.container.style.overflowY = 'auto';
+ this.container.style.borderRadius = '10px';
+ this.container.style.padding = '10px';
+
+ // Adjust for mobile screens
+ if (window.innerWidth <= 768) {
+
+ }
+
+ this.title.innerHTML = data.title;
+ this.text.innerHTML = data.text;
+
+ // Ensure content does not overflow and IDs break correctly
+ this.text.style.wordWrap = 'break-word';
+ const orderIds = this.text.querySelectorAll('.order-id');
+ orderIds.forEach((id) => {
+ id.style.wordBreak = 'break-all';
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/scripts/components/order-list/presenters/order-list.ts b/src/scripts/components/order-list/presenters/order-list.ts
index 3ae6624..341a039 100644
--- a/src/scripts/components/order-list/presenters/order-list.ts
+++ b/src/scripts/components/order-list/presenters/order-list.ts
@@ -17,7 +17,7 @@ export class OrderListPresenter {
this.orderData = await this.orderListApi.getOrderData()
this.orderListView.render(this.orderData);
} catch (error) {
- console.error('Не удалось инициализировать заказ', error)
+ // console.error('Не удалось инициализировать заказ', error)
}
}
}
\ No newline at end of file
diff --git a/src/scripts/components/order-list/views/order-list.hbs b/src/scripts/components/order-list/views/order-list.hbs
index d2de55b..bc419ae 100644
--- a/src/scripts/components/order-list/views/order-list.hbs
+++ b/src/scripts/components/order-list/views/order-list.hbs
@@ -1,25 +1,33 @@
Мои заказы
- {{#each orders}}
-
-
diff --git a/src/scripts/components/order-list/views/order-list.scss b/src/scripts/components/order-list/views/order-list.scss
index 4390a0a..25bee20 100644
--- a/src/scripts/components/order-list/views/order-list.scss
+++ b/src/scripts/components/order-list/views/order-list.scss
@@ -1,9 +1,18 @@
@use "../../../../../public/css/parametrs/parameters";
+@use "../../../../../public/css/parametrs/basic-mixins";
.order-list {
max-width: 1081px;
margin: auto;
+ @include basic-mixins.placeholder;
+
+ &__placeholder__icon {
+ width: 90px;
+ height: auto;
+ margin-bottom: 20px;
+ }
+
&__title {
font-size: parameters.$big-font-size;
text-align: left;
diff --git a/src/scripts/components/order-list/views/order-list.ts b/src/scripts/components/order-list/views/order-list.ts
index 88dd02d..71ec629 100644
--- a/src/scripts/components/order-list/views/order-list.ts
+++ b/src/scripts/components/order-list/views/order-list.ts
@@ -17,7 +17,7 @@ export class OrderListView {
public render(data: Order[]) {
this.rootElement = document.getElementById(this.rootId);
if (!this.rootElement) {
- console.error(`Root element with id ${this.rootId} not found`);
+ // console.error(`Root element with id ${this.rootId} not found`);
return;
}
diff --git a/src/scripts/components/order-placement/api/config.ts b/src/scripts/components/order-placement/api/config.ts
index 166bb1d..05ff5e4 100644
--- a/src/scripts/components/order-placement/api/config.ts
+++ b/src/scripts/components/order-placement/api/config.ts
@@ -27,6 +27,13 @@ export const ORDER_PLACEMENT_URLS = {
'Content-Type': 'application/json',
}
},
+ getCartProductsWithPromocode: {
+ route: '/cart/select/products?promocode=',
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ }
+ },
updatePaymentMethod: {
route: '/cart/pay-method',
method: 'PATCH',
diff --git a/src/scripts/components/order-placement/api/order-placement.ts b/src/scripts/components/order-placement/api/order-placement.ts
index 84a3e4d..bad4a1c 100644
--- a/src/scripts/components/order-placement/api/order-placement.ts
+++ b/src/scripts/components/order-placement/api/order-placement.ts
@@ -21,11 +21,32 @@ export class OrderPlacementApiInterface {
}
})
.catch(err => {
- console.error('Ошибка получения данных: ', err);
+ // console.error('Ошибка получения данных: ', err);
throw err;
});
}
+ /**
+ * Получение данных о продуктах в корзине с применением промокода.
+ *
+ * @returns {Promise
} Возвращает данные корзины.
+ */
+ static async getCartProductsWithPromocode(promocode: string): Promise {
+ return getWithCred(`${backurl}${ORDER_PLACEMENT_URLS.getCartProductsWithPromocode.route}${promocode}`)
+ .then(res => {
+ switch (res.status) {
+ case 200:
+ return this.transformCartData(res.body) as OrderData;
+ default:
+ throw new Error(`${res.status} - ${res.body.error_message}`);
+ }
+ })
+ .catch(err => {
+ // console.error('Ошибка получения данных: ', err);
+ throw err;
+ });
+ }
+
/**
* Обновление выбранного метода оплаты.
*
@@ -43,7 +64,7 @@ export class OrderPlacementApiInterface {
}
})
.catch(error => {
- console.error('Ошибка:', error);
+ // console.error('Ошибка:', error);
throw error;
});
}
@@ -54,12 +75,12 @@ export class OrderPlacementApiInterface {
* @async
* @returns {Promise} Промис без возвращаемого значения.
*/
- public static async placeOrder(address: string): Promise {
- console.log(address);
+ public static async placeOrder(address: string, promo: string): Promise {
+ // console.log(address);
- return csrf.post(`${backurl}${ORDER_PLACEMENT_URLS.placeOrder.route}`, { address: address })
+ return csrf.post(`${backurl}${ORDER_PLACEMENT_URLS.placeOrder.route}`, { address: address, promocode: promo })
.then(res => {
- console.log(res.status, res.body);
+ // console.log(res.status, res.body);
switch (res.status) {
case 200:
@@ -69,7 +90,7 @@ export class OrderPlacementApiInterface {
}
})
.catch(err => {
- console.error('Ошибка при отправке заказа:', err);
+ // console.error('Ошибка при отправке заказа:', err);
});
}
@@ -107,6 +128,7 @@ export class OrderPlacementApiInterface {
url: item.url,
})),
})),
+ promoStatus: data.promo_status
};
}
}
diff --git a/src/scripts/components/order-placement/view/elements/right-element-of-order-placement/right-element-of-order-placement.hbs b/src/scripts/components/order-placement/view/elements/right-element-of-order-placement/right-element-of-order-placement.hbs
index 41e92aa..c5db476 100644
--- a/src/scripts/components/order-placement/view/elements/right-element-of-order-placement/right-element-of-order-placement.hbs
+++ b/src/scripts/components/order-placement/view/elements/right-element-of-order-placement/right-element-of-order-placement.hbs
@@ -4,10 +4,16 @@
Всего: {{totalItems}} {{pluralize totalItems 'товар' 'товара' 'товаров'}} {{totalWeight}} кг
-
+
+
+
+
+
+
+ Промокод не найден. Проверьте правильность ввода.
К оплате
- {{finalPrice}} {{currency}}
+ {{finalPrice}} {{currency}}
diff --git a/src/scripts/components/order-placement/view/elements/right-element-of-order-placement/right-element-of-order-placement.scss b/src/scripts/components/order-placement/view/elements/right-element-of-order-placement/right-element-of-order-placement.scss
index 6ce118f..d8b55aa 100644
--- a/src/scripts/components/order-placement/view/elements/right-element-of-order-placement/right-element-of-order-placement.scss
+++ b/src/scripts/components/order-placement/view/elements/right-element-of-order-placement/right-element-of-order-placement.scss
@@ -1,4 +1,6 @@
@use "../../../../../../../public/css/parametrs/parameters";
+@use "../../../../../../../public/css/parametrs/basic-mixins";
+@use "../../../../../../../public/css/parametrs/animations";
// Блок для карточки (корневая обертка)
.right-element-card {
@@ -46,7 +48,7 @@
border-radius: parameters.$card-border-radius;
color: parameters.$text-color;
padding: parameters.$chrckout-padding;
- width: parameters.$width-max;
+ width: 78%;
height: parameters.$right-order-height;
font-size: parameters.$input-font-size;
padding-left: parameters.$padding15;
@@ -54,6 +56,14 @@
&::placeholder {
color: parameters.$delete-color;
}
+
+ @media (max-width: 600px) {
+ width: 100%;
+ }
+
+ @media (min-width: 1350px) {
+ width: 100%;
+ }
}
&__payment-method {
@@ -104,6 +114,65 @@
font-weight: normal;
margin-bottom: parameters.$mini-gap;
}
+
+ &__promo-container {
+ display: flex;
+ align-items: center;
+ gap: parameters.$main-gap;
+
+ @media (max-width: 600px) {
+ flex-direction: column;
+ align-items: stretch;
+ }
+
+ @media (min-width: 1350px) {
+ flex-direction: column;
+ align-items: stretch;
+ }
+ }
+
+ &__apply-promo-btn {
+ @include basic-mixins.checkout-btn;
+ flex-shrink: 0;
+ width: 20%;
+ height: 40px;
+ padding: 0;
+
+ @media (max-width: 600px) {
+ width: 100%;
+ margin-top: parameters.$mini-gap;
+ }
+
+ @media (min-width: 1350px) {
+ width: 100%;
+ margin-top: parameters.$mini-gap;
+ }
+ }
+
+ &__promo {
+ justify-content: space-around;
+ display: flex;
+
+ @media (max-width: 600px) {
+ display: contents;
+ }
+
+ @media (min-width: 1350px) {
+ display: contents;
+ }
+ }
+
+ &__promo-error {
+ display: none;
+ color: parameters.$error-color;
+ font-size: parameters.$error-font-size;
+ margin-top: parameters.$mini-gap;
+ animation: fade-in 0.3s ease-in-out;
+
+ &.visible {
+ display: block;
+ }
+ }
}
// Блок для кнопки
@@ -139,3 +208,16 @@
}
}
}
+
+#original-price {
+ position: absolute;
+ top: 0;
+ left: 0;
+ transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out;
+ white-space: nowrap; /* Чтобы текст не переносился */
+}
+
+.right-element-card__promo-title {
+ margin-bottom: 10px;
+ font-size: 16px;
+}
diff --git a/src/scripts/components/order-placement/view/elements/right-element-of-order-placement/right-element-of-order-placement.ts b/src/scripts/components/order-placement/view/elements/right-element-of-order-placement/right-element-of-order-placement.ts
index 828cb06..68b51ea 100644
--- a/src/scripts/components/order-placement/view/elements/right-element-of-order-placement/right-element-of-order-placement.ts
+++ b/src/scripts/components/order-placement/view/elements/right-element-of-order-placement/right-element-of-order-placement.ts
@@ -1,6 +1,7 @@
import {OrderPlacementApiInterface} from "../../../api/order-placement";
import { router } from '../../../../../../services/app/init';
import { add } from 'husky';
+import {OrderPlacementBuilder} from "../../order-placement-builder";
/**
* Класс для управления элементами правой части страницы оформления заказа.
@@ -16,6 +17,8 @@ export class RightElementOfOrderPlacementView {
*/
private paymentMethods: NodeListOf
;
+ private builder: OrderPlacementBuilder;
+
/**
* Конструктор класса RightElementOfOrderPlacementView.
* Инициализирует список способов оплаты и запускает процессы инициализации.
@@ -23,7 +26,9 @@ export class RightElementOfOrderPlacementView {
constructor(address: string) {
// Инициализация: получаем все элементы способов оплаты
this.paymentMethods = document.querySelectorAll('.right-element-card__payment-method');
- console.log(address);
+ // console.log(address);
+
+ this.builder = new OrderPlacementBuilder();
this.init(address);
}
@@ -80,15 +85,93 @@ export class RightElementOfOrderPlacementView {
this.addEventListeners();
document.getElementById('order-button')?.addEventListener('click', async () => {
- console.log(address);
+ // console.log(address);
- OrderPlacementApiInterface.placeOrder(address)
+ OrderPlacementApiInterface.placeOrder(address, document.getElementById('apply-promo-input')?.value)
.then(() => {
router.navigate('/order_list');
})
.catch(err => {
- console.error('что-то пошло не так', err);
+ // console.error('что-то пошло не так', err);
})
});
+
+ document.getElementById('apply-promo-button')?.addEventListener('click', async () => {
+
+ const promo = document.getElementById('apply-promo-input')?.value;
+
+ if (promo) {
+ const prev = await document.getElementById('final-price')?.textContent;
+ const data = await OrderPlacementApiInterface.getCartProductsWithPromocode(promo);
+
+ await this.reconstructRightPart(data);
+
+ if (data.promoStatus !== '') {
+ this.showError();
+ } else {
+ this.applyCorrectPromoAnimation(data, prev);
+ }
+ } else {
+ this.showError();
+ }
+ })
+ }
+
+ private debounceTimeout: number;
+
+ private showError () {
+ const err = document.getElementById('promo-error');
+
+ clearTimeout(this.debounceTimeout); // Очистка предыдущего таймера
+
+ err.style.display = "flex";
+
+ this.debounceTimeout = setTimeout(() => {
+ err.style.display = "none";
+ }, 3000);
+ }
+
+ private async reconstructRightPart (data: any): Promise {
+ const finalPrice = document.getElementById('final-price');
+ finalPrice.innerHTML = data.finalPrice + ' ' + data.currency;
+ }
+
+ private applyCorrectPromoAnimation(data: any, prev: number): void {
+ const finalPrice = document.getElementById('final-price');
+ const originalPrice = document.createElement('span');
+
+ originalPrice.id = "original-price";
+ originalPrice.textContent = prev;
+ originalPrice.style.textDecoration = "line-through";
+ originalPrice.style.marginRight = "10px";
+ originalPrice.style.opacity = "1";
+ originalPrice.style.transition = "opacity 0.3s ease-in-out, transform 0.3s ease-in-out";
+ originalPrice.style.position = "absolute";
+ originalPrice.style.transform = "translateX(0)";
+
+ const parent = finalPrice.parentElement;
+ parent.style.position = "relative";
+
+ parent.insertBefore(originalPrice, finalPrice);
+
+ const finalPriceRect = finalPrice.getBoundingClientRect();
+ originalPrice.style.left = `${finalPriceRect.left - parent.getBoundingClientRect().left - originalPrice.offsetWidth - 10}px`;
+ originalPrice.style.top = "0";
+
+ setTimeout(() => {
+ originalPrice.style.opacity = "0";
+ originalPrice.style.transform = "translateX(-20px)";
+
+ setTimeout(() => {
+ originalPrice.remove();
+ finalPrice.innerHTML = data.finalPrice + ' ' + data.currency;
+ finalPrice.style.opacity = "0";
+ finalPrice.style.transition = "opacity 0.3s ease-in-out";
+
+ setTimeout(() => {
+ finalPrice.style.opacity = "1";
+ }, 50);
+ }, 300);
+ }, 3000);
}
}
diff --git a/src/scripts/components/order-placement/view/order-placement-builder.ts b/src/scripts/components/order-placement/view/order-placement-builder.ts
index ba0a033..7f2043e 100644
--- a/src/scripts/components/order-placement/view/order-placement-builder.ts
+++ b/src/scripts/components/order-placement/view/order-placement-builder.ts
@@ -140,7 +140,7 @@ export class OrderPlacementBuilder {
await this.renderLeftPart();
this.initializeOrderPlacement();
} catch (error) {
- console.error(error);
+ // console.error(error);
}
}
@@ -222,7 +222,7 @@ export class OrderPlacementBuilder {
this.rightElementsView = new RightElementOfOrderPlacementView(this.orderData.recipient.address);
} catch {
- console.error('что-то не так');
+ // console.error('что-то не так');
}
}
}
diff --git a/src/scripts/components/personal-account/api/personal-account.ts b/src/scripts/components/personal-account/api/personal-account.ts
index d58f5e2..c4d0309 100644
--- a/src/scripts/components/personal-account/api/personal-account.ts
+++ b/src/scripts/components/personal-account/api/personal-account.ts
@@ -10,12 +10,7 @@ export interface UserData {
age: number;
avatar_url: string;
Address: {
- id: number;
- city: string;
- street: string;
- house: string;
- flat: string;
- profile_id: number;
+ address: string;
};
}
@@ -54,7 +49,7 @@ export class AccountAPI {
const data = response.body;
- console.log(data, data.delivery_date);
+ // console.log(data, data.delivery_date);
const deliveryDate = new Date(data.delivery_date);
return `Ожидаемая дата доставки: ${deliveryDate.toLocaleDateString('ru-RU', {
diff --git a/src/scripts/components/personal-account/presenters/account.ts b/src/scripts/components/personal-account/presenters/account.ts
index c4ad7f3..09d868c 100644
--- a/src/scripts/components/personal-account/presenters/account.ts
+++ b/src/scripts/components/personal-account/presenters/account.ts
@@ -6,14 +6,14 @@ import { backurl } from '../../../../services/app/config';
import { storageUser } from '../../../../services/storage/user';
import { updateAfterAuth } from '../../../layouts/body';
import { csrf } from '../../../../services/api/CSRFService';
-
+import { ChangePasswordModal } from '../../modal/presenters/change-password';
export class AccountPresenter {
private accountAPI: AccountAPI;
private view: AccountView;
- private userData: UserData;
- private deliveryInfo: Array;
- private rightColumnInfo: Array;
+ private userData: UserData | undefined;
+ private deliveryInfo: Array | undefined;
+ private rightColumnInfo: Array | undefined;
constructor(apiBaseUrl: string, rootId: string) {
this.accountAPI = new AccountAPI(apiBaseUrl);
@@ -22,6 +22,15 @@ export class AccountPresenter {
this.view.onEditAvatarClick = this.handleEditAvatar.bind(this);
this.view.onEditUserInfoClick = this.handleEditUserInfo.bind(this);
this.view.onEditAddressClick = this.handleEditAddress.bind(this);
+ this.view.onChangePasswordClick = this.changePassword.bind(this);
+ }
+
+
+ private changePassword() {
+ const changePasswordModal = new ChangePasswordModal(
+ { modal: 'change-password-modal', rootId: 'modal-render', btnOpen: 'change-password' },
+ console.log,
+ );
}
public async initialize() {
@@ -46,16 +55,14 @@ export class AccountPresenter {
}
private async buildDeliveryInfo(userData: UserData) {
- const addressText = [
- userData.Address?.city?.trim(),
- userData.Address?.street?.trim(),
- userData.Address?.house?.trim(),
- userData.Address?.flat?.trim()
- ].filter(Boolean).join(', ') || 'Добавьте адресс';
+ const addressText =
+ [
+ userData.Address?.address?.trim(),
+ ]
+ .filter(Boolean)
+ .join(', ') || 'Добавьте адрес';
- let msg: string;
-
- msg = await this.accountAPI.getNearestDeliveryDate()
+ const msg = await this.accountAPI.getNearestDeliveryDate();
return [
{
@@ -92,9 +99,9 @@ export class AccountPresenter {
detailsClass: 'account__favorites-details',
titleClass: 'account__favorites-title',
textClass: 'account__favorites-text',
- title: 'Избранное',
- text: 'скоро',
- href: '/soon'
+ title: 'Вишлисты',
+ text: 'Смотреть',
+ href: '/wishlists',
},
{
class: 'account__purchases-info',
@@ -106,7 +113,7 @@ export class AccountPresenter {
title: 'Заказы',
text: 'Смотреть',
href: '/order_list',
- }
+ },
];
}
@@ -122,10 +129,9 @@ export class AccountPresenter {
let newAvatarUrl = await this.accountAPI.updateAvatar(file);
newAvatarUrl = `${backurl}/${newAvatarUrl}`;
- this.userData.avatar_url = `${backurl}/${newAvatarUrl}`;
+ this.userData!.avatar_url = `${backurl}/${newAvatarUrl}`;
this.view.updateAvatar(newAvatarUrl);
} catch (error) {
-
const errorMessage = this.parseError(error);
this.view.displayErrorMessage(errorMessage);
@@ -142,9 +148,9 @@ export class AccountPresenter {
private async handleEditUserInfo() {
const userDataRecord: Record = {
- username: this.userData.username,
- gender: this.userData.gender,
- email: this.userData.email,
+ username: this.userData!.username,
+ gender: this.userData!.gender,
+ email: this.userData!.email,
};
const userInfoModal = new PersonalDataModal(
@@ -160,7 +166,7 @@ export class AccountPresenter {
storageUser.changeUsername(this.userData.username);
updateAfterAuth(storageUser.getUserData());
- }
+ },
);
userInfoModal.open();
@@ -168,23 +174,25 @@ export class AccountPresenter {
private async handleEditAddress() {
const addressRecord: Record = {
- city: this.userData.Address.city,
- street: this.userData.Address.street,
- house: this.userData.Address.house,
- flat: this.userData.Address.flat,
+ address: this.userData!.Address.address,
};
const addressModal = new AddressModal(
{ modal: 'edit-address-modal', rootId: 'modal-render', btnOpen: 'edit-user-address' },
addressRecord,
(updatedAddress) => {
- this.userData.Address = { ...this.userData.Address, ...updatedAddress };
- this.view.updateAddress(this.userData.Address);
+ this.userData!.Address = { ...this.userData!.Address, ...updatedAddress };
+
+ console.log(this.userData!.Address );
+
+ this.view.updateAddress(this.userData!.Address.address);
- storageUser.changeCity(this.userData.Address.city);
+ storageUser.changeCity(this.userData!.Address.address);
updateAfterAuth(storageUser.getUserData());
- }
+ },
);
addressModal.open();
}
-}
\ No newline at end of file
+
+
+}
diff --git a/src/scripts/components/personal-account/views/account.ts b/src/scripts/components/personal-account/views/account.ts
index 1e60b06..9399df3 100644
--- a/src/scripts/components/personal-account/views/account.ts
+++ b/src/scripts/components/personal-account/views/account.ts
@@ -10,6 +10,7 @@ export class AccountView {
public onEditAvatarClick?: () => void;
public onEditUserInfoClick?: () => void;
public onEditAddressClick?: () => void;
+ public onChangePasswordClick?: () => void;
constructor(rootId: string) {
this.rootId = rootId;
@@ -17,10 +18,10 @@ export class AccountView {
this.compiledTemplate = Handlebars.compile(accountTemplate);
}
- public render(data: UserData & { deliveryInfo: Array; rightColumnInfo: Array }) {
+ public render(data: UserData & { deliveryInfo: Array; rightColumnInfo: Array }) {
this.rootElement = document.getElementById(this.rootId);
if (!this.rootElement) {
- console.error(`Root element with id ${this.rootId} not found`);
+ // console.error(`Root element with id ${this.rootId} not found`);
return;
}
this.rootElement.innerHTML = this.compiledTemplate(data);
@@ -29,8 +30,8 @@ export class AccountView {
public updateAvatar(newAvatarUrl: string) {
this.rootElement = document.getElementById(this.rootId);
- if (!this.rootElement){
- console.error(this.rootElement, 'not found');
+ if (!this.rootElement) {
+ // console.error(this.rootElement, 'not found');
return;
}
@@ -46,46 +47,42 @@ export class AccountView {
errorContainer.textContent = errorMessage;
if (!this.rootElement) {
- console.error('root element with id ${this.rootId} not found');
+ // console.error('root element with id ${this.rootId} not found');
return;
}
const avatarContainer = this.rootElement.querySelector('.account__user-name') as HTMLElement | null;
if (!avatarContainer) {
- console.error('avatar container not found');
+ // console.error('avatar container not found');
return;
}
- console.log(avatarContainer,errorContainer);
+ // console.log(avatarContainer,errorContainer);
avatarContainer.appendChild(errorContainer);
- console.log(avatarContainer);
+ // console.log(avatarContainer);
setTimeout(() => {
errorContainer.remove();
}, 20000);
}
- public updateAddress(address: UserData['Address']) {
+
+ public updateAddress(address: string) {
this.rootElement = document.getElementById(this.rootId);
- if (!this.rootElement){
- console.error(this.rootElement, 'not found');
+ if (!this.rootElement) {
+ // console.error(this.rootElement, 'not found');
return;
}
const addressElement = this.rootElement.querySelector('.account__address-details .account__address-text');
if (addressElement) {
- addressElement.textContent = `${address.city}, ${address.street}, ${address.house}`;
- if (address.flat) {
- console.log(address.flat);
-
- addressElement.textContent += `, ${address.flat}`;
- }
+ addressElement.textContent = `${address}`;
}
}
private setupListeners() {
this.rootElement = document.getElementById(this.rootId);
- if (!this.rootElement){
- console.error(this.rootElement, 'not found');
+ if (!this.rootElement) {
+ // console.error(this.rootElement, 'not found');
return;
}
@@ -109,5 +106,12 @@ export class AccountView {
if (this.onEditAddressClick) this.onEditAddressClick();
});
}
+
+ const changePasswordBtn = this.rootElement.querySelector('#change-password');
+ if (changePasswordBtn) {
+ changePasswordBtn.addEventListener('click', () => {
+ if (this.onChangePasswordClick) this.onChangePasswordClick();
+ });
+ }
}
}
\ No newline at end of file
diff --git a/src/scripts/components/personal-account/views/personal-account.hbs b/src/scripts/components/personal-account/views/personal-account.hbs
index 2ff43e6..257bbba 100644
--- a/src/scripts/components/personal-account/views/personal-account.hbs
+++ b/src/scripts/components/personal-account/views/personal-account.hbs
@@ -32,7 +32,6 @@
-
diff --git a/src/scripts/components/personal-account/views/personal-account.scss b/src/scripts/components/personal-account/views/personal-account.scss
index 7d94219..8ca0a9d 100644
--- a/src/scripts/components/personal-account/views/personal-account.scss
+++ b/src/scripts/components/personal-account/views/personal-account.scss
@@ -131,7 +131,7 @@
}
&__address_update{
- margin-bottom: 60px;
+ height: 100%;
}
&__notification {
@@ -139,12 +139,18 @@
margin-right: -170px;
}
+ &__user-info {
+ display: flex;
+ flex-direction: row;
+ }
+
&__user-name {
white-space: nowrap;
overflow: visible;
text-overflow: ellipsis;
margin: 0;
flex: 4;
+ font-size: 18px;
}
&__icons-personal-account {
@@ -262,6 +268,8 @@
&__edit{
color: parameters.$base-background-color;
+ font-size: 18px;
+ margin-left: 5px;
}
}
}
diff --git a/src/scripts/components/personal-account/views/personal-account__middle.scss b/src/scripts/components/personal-account/views/personal-account__middle.scss
index 4e68797..c8ea4e4 100644
--- a/src/scripts/components/personal-account/views/personal-account__middle.scss
+++ b/src/scripts/components/personal-account/views/personal-account__middle.scss
@@ -3,7 +3,6 @@
@media (max-width: 1000px) {
.account {
- // Уменьшаем размеры фотографий и текста для уменьшенных экранов
&__avatar-container {
width: 120px;
height: 120px;
diff --git a/src/scripts/components/personal-account/views/personal-account__mobile.scss b/src/scripts/components/personal-account/views/personal-account__mobile.scss
index 4267081..f7a9071 100644
--- a/src/scripts/components/personal-account/views/personal-account__mobile.scss
+++ b/src/scripts/components/personal-account/views/personal-account__mobile.scss
@@ -55,7 +55,7 @@
}
&__avatar-container:hover .account__avatar-overlay {
- display: block; // Показываем подсказку при наведении
+ display: block;
}
.product-page__description-wrapper {
@@ -63,7 +63,7 @@
}
.product-page__description-item {
- display: block; // Делает каждый элемент блока в одну строку
+ display: block;
margin-bottom: 6px;
}
@@ -99,13 +99,12 @@
padding-top: 10px;
}
- // Показываем
только на мобильных устройствах
br {
- display: none; // Скрываем по умолчанию
+ display: none;
}
br.mobile-break {
- display: block; // Показываем только на мобильных устройствах
+ display: block;
}
}
@@ -115,11 +114,11 @@
&__pin-drop,
&__favorite,
&__shopping-basket {
- font-size: 24px; // Уменьшаем иконки
+ font-size: 24px;
}
&__edit {
- font-size: 20px; // Уменьшаем размер иконки редактирования
+ font-size: 20px;
}
}
}
diff --git a/src/scripts/components/product-page/api/product-page.ts b/src/scripts/components/product-page/api/product-page.ts
index 63eb052..b3dcc69 100644
--- a/src/scripts/components/product-page/api/product-page.ts
+++ b/src/scripts/components/product-page/api/product-page.ts
@@ -1,5 +1,5 @@
import { backurl } from '../../../../services/app/config';
-import { getWithCred } from '../../../../services/api/without-csrf';
+import {get, getWithCred} from '../../../../services/api/without-csrf';
import { csrf } from '../../../../services/api/CSRFService';
interface ProductOption {
@@ -43,8 +43,8 @@ interface ProductData {
}
export class ProductPageApi {
- getProductData = (productId: string): Promise< { ok: boolean, body: ProductData }> => {
- return getWithCred(`${backurl}/product/${productId}`)
+ getProductData = async (productId: string): Promise< { ok: boolean, body: ProductData }> => {
+ return await getWithCred(`${backurl}/product/${productId}`)
.then((res) => {
switch (res.status) {
case 200:
@@ -54,22 +54,22 @@ export class ProductPageApi {
}
})
.catch(e => {
- console.error('Error fetching product data:', e);
+ // console.error('Error fetching product data:', e);
return { ok: false };
})
}
- addToCart = (productId: string): Promise<{ ok: boolean; unauthorized?: boolean }> =>{
+ addToCart = async (productId: string): Promise<{ ok: boolean; unauthorized?: boolean; res?: any }> =>{
return csrf.post(`${backurl}/cart/product/${productId}`)
.then((res) => {
switch (res.status) {
case 204:
- return { ok: true };
+ return { ok: true, unauthorized: false, res: res };
case 401:
return { ok: false, unauthorized: true };
case 403:
csrf.refreshToken()
- .catch(err => console.log(err));
+ .catch(err => {/*console.log(err)*/});
return { ok: false };
case 409:
@@ -79,7 +79,7 @@ export class ProductPageApi {
}
})
.catch((error) => {
- console.error('Error adding to cart:', error);
+ // console.error('Error adding to cart:', error);
return { ok: false };
});
}
@@ -95,7 +95,7 @@ export class ProductPageApi {
return { ok: false, unauthorized: true };
case 403:
csrf.refreshToken()
- .catch(err => console.log(err));
+ .catch(err => {/*console.log(err)*/});
return { ok: false };
default:
@@ -103,7 +103,7 @@ export class ProductPageApi {
}
})
.catch((error) => {
- console.error('Error removing from cart:', error);
+ // console.error('Error removing from cart:', error);
return { ok: false };
});
}
@@ -112,13 +112,13 @@ export class ProductPageApi {
return csrf.patch(`${backurl}/cart/product/${productId}`, { count })
.then(res =>{
switch (res.status) {
- case 204:
- return;
+ case 200:
+ return res.body;
case 403:
csrf.refreshToken()
- .catch(err => console.log(err));
+ .catch(err => {/*console.log(err)*/});
- return;
+ return res.body;
default:
throw new Error(`${res.status} - ${res.body.error_message}`);
}
diff --git a/src/scripts/components/product-page/presenters/product-page.ts b/src/scripts/components/product-page/presenters/product-page.ts
index 086ecb0..420b9d9 100644
--- a/src/scripts/components/product-page/presenters/product-page.ts
+++ b/src/scripts/components/product-page/presenters/product-page.ts
@@ -6,16 +6,30 @@ import { router } from '../../../../services/app/init';
import { ProductData } from '../types/types';
import { isAuth } from '../../../../services/storage/user';
import { csrf } from '../../../../services/api/CSRFService';
+import { ReviewsView } from '../../reviews/views/reviews';
+import { ReviewsPresenter } from '../../reviews/presenters/reviews';
+import { ReviewsApi } from '../../reviews/api/api';
+import { WishlistApi } from '../../wish-list/api/wish-list';
import {ReviewsView} from "../../reviews/views/reviews";
import {ReviewsPresenter} from "../../reviews/presenters/reviews";
import {ReviewsApi} from "../../reviews/api/api";
+import {Recommendations} from "../../recomendations/presenter/recommendations";
+import {RecommendationsView} from "../../recomendations/view/recomendations";
+import {CardView} from "../../card/view/card";
+import {RecommendationsApi} from "../../recomendations/api/recommendations";
+import {productData} from "./data";
export class ProductPageBuilder {
private readonly reviewsId = 'reviews';
+ private readonly recommendationsId = 'recommendations-page';
private productPage: ProductPage;
private api = new ProductPageApi();
+ private reviewsPresenter: ReviewsPresenter;
+ private wishlistModal: HTMLElement | null = null;
+ private wishlistCheckboxes: NodeListOf | null = null;
private reviewsPresenter: ReviewsPresenter
+ private recommendations: Recommendations;
constructor() {
this.productPage = new ProductPage();
@@ -23,18 +37,24 @@ export class ProductPageBuilder {
new ReviewsApi();
const reviewsView = new ReviewsView(this.reviewsId);
this.reviewsPresenter = new ReviewsPresenter(reviewsView);
+ const cardView = new CardView();
+ const recommendationsView = new RecommendationsView(cardView);
+ const recommendationsApi = new RecommendationsApi();
+ this.recommendations = new Recommendations(recommendationsApi, recommendationsView);
}
async build({ hash }: { hash?: string }) {
try {
- console.log(hash);
-
const id = this.getProductId();
- await csrf.refreshToken();
+ try {
+ await csrf.refreshToken();
+ } catch {
+
+ }
- if (id === ''){
- router.navigate('/')
+ if (id === '') {
+ router.navigate('/');
return;
}
@@ -58,22 +78,153 @@ export class ProductPageBuilder {
this.initializeConditionButtons();
// this.initializeOptionButtons();
- this.initializeCartButtons(productData.in_cart);
+ this.initializeCartButtons(productData.in_cart, productData.count);
// this.initializeFavoriteIcon();
new Carousel();
- this.reviewsPresenter.init(id, hash);
+ this.recommendations.render(productData.id, productData.title, this.recommendationsId);
+ this.reviewsPresenter.init(id, hash);
+ this.initializeFavoriteButton();
} catch (error) {
- console.error('Error building product page:', error);
+ // console.error('Error building product page:', error);
+ }
+ }
+
+ private initializeFavoriteButton() {
+ const favoriteButton = document.querySelector('.product-page__favorite-button') as HTMLElement;
+
+ favoriteButton?.addEventListener('click', () => {
+ this.openWishlistModal();
+ });
+ }
+
+ private openWishlistModal() {
+ this.wishlistModal = document.querySelector('.modal-add-wish') as HTMLElement;
+ this.wishlistCheckboxes = this.wishlistModal?.querySelectorAll('.modal-add-wish__checkbox') as NodeListOf;
+
+ if (this.wishlistModal) {
+ this.wishlistModal.classList.add('modal-add-wish--open');
+ console.log('Modal opened');
+ this.loadUserWishlists()
+ .then(() => this.attachModalCloseEvent())
+ .catch(()=>this.attachModalCloseEvent());
+ return;
+ }
+
+
+ this.attachModalCloseEvent();
+
+ }
+
+ private closeWishlistModal() {
+ if (this.wishlistModal) {
+ this.wishlistModal.classList.remove('modal-add-wish--open');
}
}
+ private async loadUserWishlists() {
+ const response = await WishlistApi.getWishlists();
+
+ this.populateWishlistCheckboxes(response);
+
+ const submitButton = this.wishlistModal?.querySelector('.modal-add-wish__submit-button') as HTMLElement;
+ submitButton?.addEventListener('click', (event) => {
+ event.preventDefault();
+ this.saveSelectedWishlists();
+ });
+ }
+
+
+ private populateWishlistCheckboxes(wishlists: any[]) {
+ const wishlistContainer = document.getElementById('wishlistCheckboxes');
+ if (wishlistContainer) {
+ wishlistContainer.innerHTML = ''; // очищаем контейнер
+
+ wishlists.forEach((wishlist) => {
+ const checkbox = document.createElement('input');
+ checkbox.type = 'checkbox';
+ checkbox.classList.add('modal-add-wish__checkbox');
+ checkbox.setAttribute('data-wishlist-link', wishlist.link); // сохраняем ссылку
+
+ const label = document.createElement('label');
+ label.textContent = wishlist.name;
+
+ const checkboxWrapper = document.createElement('div');
+ checkboxWrapper.appendChild(checkbox);
+ checkboxWrapper.appendChild(label);
+
+ wishlistContainer.appendChild(checkboxWrapper);
+ });
+ } else {
+ console.error('Wishlist container not found');
+ }
+ }
+
+
+ private saveSelectedWishlists() {
+ const wishlistContainer = document.getElementById('wishlistCheckboxes');
+ if (!wishlistContainer) {
+ console.error('Wishlist container not found');
+ return;
+ }
+
+ // Получаем все выбранные чекбоксы и извлекаем их ссылки (data-wishlist-link)
+ const selectedWishlists = Array.from(wishlistContainer.querySelectorAll('.modal-add-wish__checkbox:checked'))
+ .map((checkbox) => (checkbox as HTMLInputElement).getAttribute('data-wishlist-link')); // извлекаем ссылку
+
+ if (selectedWishlists.length === 0) {
+ console.log('zero');
+ return;
+ }
+
+ const productId = this.getProductId();
+ const numId = Number(productId);
+
+ if (!numId) {
+ console.error('Invalid product ID');
+ return;
+ }
+
+ WishlistApi.addProductToWishlist(numId, selectedWishlists)
+ .then((result) => {
+ if (result.status === 201) {
+ console.log('Product added to wishlist');
+ this.closeWishlistModal();
+ } else {
+ console.error('Failed to add product to wishlist');
+ }
+ })
+ .catch((error) => {
+ console.error('Error saving wishlists:', error);
+ });
+ }
+
+ private handleDocumentClick = (event: MouseEvent) => {
+ const target = event.target as HTMLElement;
+
+ if (this.wishlistModal && !this.wishlistModal.contains(target)) {
+ this.closeWishlistModal();
+ document.removeEventListener('click', this.handleDocumentClick); // Убираем обработчик после закрытия
+ }
+ };
+
+
+ private attachModalCloseEvent() {
+ const closeButton = this.wishlistModal?.querySelector('.modal-add-wish__close-button') as HTMLElement;
+
+ closeButton?.addEventListener('click', () => {
+ this.closeWishlistModal();
+ });
+
+ document.addEventListener('click', this.handleDocumentClick);
+ }
+
private initializeConditionButtons() {
const conditionButtons = Array.from(document.querySelectorAll('.product-page__condition-buttons button')).filter(
- (el): el is HTMLButtonElement => el instanceof HTMLButtonElement
+ (el): el is HTMLButtonElement => el instanceof HTMLButtonElement,
);
const currentPriceElement = document.querySelector('.product-page__current-price-product-page') as HTMLElement;
@@ -89,7 +240,7 @@ export class ProductPageBuilder {
private initializeOptionButtons() {
// Обработка цветовых кнопок
const colorButtons = Array.from(document.querySelectorAll('.product-page__color-button')).filter(
- (el): el is HTMLButtonElement => el instanceof HTMLButtonElement
+ (el): el is HTMLButtonElement => el instanceof HTMLButtonElement,
);
colorButtons.forEach((button) => {
@@ -103,7 +254,7 @@ export class ProductPageBuilder {
});
const sizeButtons = Array.from(document.querySelectorAll('.product-page__size-button')).filter(
- (el): el is HTMLButtonElement => el instanceof HTMLButtonElement
+ (el): el is HTMLButtonElement => el instanceof HTMLButtonElement,
);
sizeButtons.forEach((button) => {
@@ -117,7 +268,7 @@ export class ProductPageBuilder {
});
const textOptions = Array.from(document.querySelectorAll('.product-page__text-option')).filter(
- (el): el is HTMLAnchorElement => el instanceof HTMLAnchorElement
+ (el): el is HTMLAnchorElement => el instanceof HTMLAnchorElement,
);
textOptions.forEach((link) => {
@@ -132,16 +283,16 @@ export class ProductPageBuilder {
});
}
- private getProductId = (): string =>{
+ private getProductId = (): string => {
const keys = router.getRouteParams();
if (keys === null) {
return '';
}
return keys['id'];
- }
+ };
- private initializeCartButtons(isInCart: boolean) {
+ private initializeCartButtons(isInCart: boolean, count: number = 0) {
const cartButton = document.querySelector('.product-page__cart-button') as HTMLButtonElement;
const incrementButton = document.createElement('button');
incrementButton.textContent = '+';
@@ -149,7 +300,7 @@ export class ProductPageBuilder {
incrementButton.style.display = isInCart ? 'inline-block' : 'none';
if (isInCart) {
- cartButton.textContent = 'Удалить из корзины';
+ cartButton.textContent = `Удалить из корзины (${count})`;
this.productPage.setButtonPressedState(cartButton);
} else {
if (!isAuth()) {
@@ -180,38 +331,37 @@ export class ProductPageBuilder {
private async addToCart(cartButton: HTMLElement, incrementButton: HTMLElement) {
const id = this.getProductId();
- if (id === ''){
- router.navigate('/')
+ if (id === '') {
+ router.navigate('/');
return;
}
- this.api.addToCart(id)
- .then((result) => {
- if (result.unauthorized) {
- cartButton.textContent = 'Войдите в аккаунт';
- cartButton.setAttribute('router', 'changed-active');
- cartButton.setAttribute('href', '/login');
- return;
- }
+ const result = await this.api.addToCart(id)
- if (result.ok) {
- cartButton.textContent = 'Удалить из корзины';
- incrementButton.style.display = 'inline-block';
- this.productPage.setButtonPressedState(cartButton);
- }
- });
+ if (result.unauthorized) {
+ cartButton.textContent = 'Войдите в аккаунт';
+ cartButton.setAttribute('router', 'changed-active');
+ cartButton.setAttribute('href', '/login');
+ return;
+ }
+
+ if (result.ok) {
+ cartButton.textContent = `Удалить из корзины (1)`;
+ incrementButton.style.display = 'inline-block';
+ this.productPage.setButtonPressedState(cartButton);
+ }
}
private async removeFromCart(cartButton: HTMLElement, incrementButton: HTMLElement) {
const id = this.getProductId();
- if (id === ''){
- router.navigate('/')
+ if (id === '') {
+ router.navigate('/');
return;
}
this.api.rmFromCart(id)
.then((result) => {
- console.log(result);
+ // console.log(result);
if (result.unauthorized) {
cartButton.textContent = 'Войдите в аккаунт';
@@ -225,7 +375,7 @@ export class ProductPageBuilder {
incrementButton.style.display = 'none';
this.productPage.setButtonDefaultState(cartButton);
}
- });
+ });
}
private async increaseCartCount(cartButton: HTMLElement, incrementButton: HTMLElement) {
@@ -235,10 +385,11 @@ export class ProductPageBuilder {
}
try {
- await ProductPageApi.updateProductQuantity(id);
- cartButton.textContent = 'Удалить из корзины';
+ const count = await ProductPageApi.updateProductQuantity(id);
+
+ cartButton.textContent = `Удалить из корзины (${count.count})`;
} catch (error) {
- console.error('Ошибка при обновлении количества:', error);
+ // console.error('Ошибка при обновлении количества:', error);
}
}
diff --git a/src/scripts/components/product-page/views/new-pr-page.hbs b/src/scripts/components/product-page/views/new-pr-page.hbs
index 8ecdaab..ffa4bf6 100644
--- a/src/scripts/components/product-page/views/new-pr-page.hbs
+++ b/src/scripts/components/product-page/views/new-pr-page.hbs
@@ -1,5 +1,19 @@
+
+
+
- {{> carousel-slider images=images }}
+ {{> carousel-slider images=images }}
{{title}}
@@ -16,77 +30,53 @@
-
-
-
+
{{seller.name}}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
{{price}} ₽
+
+
{{price}} ₽
+
+
+
{{original_price}} ₽
-
-
-
+
-
-
-
-
-
-
-
-
-
{{description}}
+
+
{{description}}
+
+
-
\ No newline at end of file
+
+
+
diff --git a/src/scripts/components/product-page/views/product-page.scss b/src/scripts/components/product-page/views/product-page.scss
index a25aa13..333af63 100644
--- a/src/scripts/components/product-page/views/product-page.scss
+++ b/src/scripts/components/product-page/views/product-page.scss
@@ -136,7 +136,7 @@
}
& .text-review {
- margin-left: parameters.$margin-left-material-icons;
+ margin-left: 45px;
}
& span {
@@ -247,8 +247,8 @@
margin-bottom: parameters.$big-margin-bottom;
font-size: 18px;
font-weight: 440;
- margin-top: parameters.$small-margin;
- margin-left: 10px;
+ margin-top: 10px;
+ margin-left: 20px;
}
&__description-item {
@@ -287,6 +287,7 @@
word-break: normal;
overflow-wrap: break-word;
hyphens: auto;
+ margin-left: 0.5px;
&__header {
font-size: 25px;
@@ -298,7 +299,7 @@
&__purchase-info {
flex: 0.75;
- width: 20%;
+ width: 300px;
margin-left: parameters.$big-margin;
}
@@ -314,6 +315,7 @@
padding: parameters.$big-margin;
margin-bottom: parameters.$big-margin;
position: relative;
+ width: 300px;
}
&__current-price-product-page {
@@ -421,7 +423,12 @@
width: 2/3 * 100%;
}
-@media screen and (max-width: 768px) {
+.recommendations-page {
+ width: 2/3 * 100%;
+ margin-left: 20px;
+}
+
+@media screen and (max-width: 1150px) {
.product {
flex-direction: column;
padding: 10px;
@@ -470,6 +477,7 @@
margin-bottom: 10px;
}
+ margin-left: 0.5px;
font-size: 14px;
padding: 10px;
}
@@ -525,11 +533,16 @@
&__slider {
width: 100% !important;
}
+
+ &__price-box {
+ width: 100% !important;
+ }
}
.product-page__description-product-page {
font-size: 14px;
- margin-top: 15px;
+ margin-top: 10px;
+ margin-left: 20px;
padding: 10px;
}
@@ -540,8 +553,209 @@
#reviews {
width: 100%;
}
+
+ .recommendations-page {
+ width: 100%;
+ margin-right: 0;
+ margin-left: 0;
+ }
+}
+
+.recommendations__catalog {
+ margin-right: 10px;
+ margin-left: 10px;
+}
+
+.cards-view-title {
+ transition: max-height 0.3s ease-in-out, white-space 0.5s ease-in-out;
+ max-height: 1.5em;
+
+ &:hover {
+ white-space: normal;
+ max-height: 10em;
+ }
+}
+
+@media screen and (max-width: parameters.$smallest-phone) {
+ .cards-view-title {
+ transition: max-height 0.3s ease-in-out, white-space 0.5s ease-in-out;
+ max-height: 10em;
+ white-space: normal !important;
+
+ &:hover {
+ white-space: normal;
+ max-height: 10em;
+ }
+ }
+}
+.modal-add-wish {
+ display: none; /* Скрыть модалку по умолчанию */
+ position: fixed;
+ z-index: 100;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0, 0, 0, 0.4);
+ padding-top: 60px;
+
+ &__content {
+ background-color: #fefefe;
+ margin: 5% auto;
+ padding: 20px;
+ border: 1px solid #888;
+ width: 55%;
+ border-radius: 20px;
+ }
+
+ &__header {
+ font-size: 1.5rem;
+ margin-bottom: 20px;
+ }
+
+ &__checkboxes {
+ display: flex;
+ flex-direction: column;
+ margin-bottom: 20px;
+
+ label{
+ font-size: 20px;
+ padding-left: 12px;
+ margin-bottom: 5px;
+ }
+ }
+
+ &__form {
+ display: flex;
+ flex-direction: column;
+ }
+
+ &__submit-button,
+ &__close-button {
+ padding: 10px;
+ margin: 10px;
+ cursor: pointer;
+ background-color: parameters.$base-background-color;
+ border-radius: 20px;
+ color: white;
+ border: none;
+ font-size: 1rem;
+
+ }
+
+ &__close-button {
+ background-color: #ccc;
+ color: black;
+ font-weight: 600;
+ }
+
+ &__submit-button:hover {
+ background-color: #4b0e8c;
+ font-weight: 600;
+ }
+
+ &__close-button:hover {
+ background-color: #999;
+ }
+
+ &--active {
+ display: block;
+ }
+}
+
+
+.product-page__price-and-favorite {
+ display: flex; // Используем flexbox для размещения элементов в одну строку
+ align-items: center; // Центрируем элементы по вертикали
+ gap: 10px; // Расстояние между ценой и кнопкой
+}
+
+.product-page__current-price-product-page {
+ font-size: 1.5rem;
+ font-weight: bold;
+}
+
+.product-page__favorite-button {
+ background: none;
+ border: none;
+ cursor: pointer;
+ padding: 0;
+ display: flex;
+ align-items: center; // Центрируем SVG внутри кнопки
+}
+
+.product-page__favorite-button svg {
+ width: 24px;
+ height: 24px;
+ stroke: #ff4040;
}
+.modal-add-wish {
+ display: none;
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.5);
+ align-items: center;
+ justify-content: center;
+}
+.modal-add-wish--open {
+ display: flex;
+}
+
+.modal-add-wish__checkbox {
+ position: relative;
+ width: 24px; /* Увеличенный размер чекбокса */
+ height: 24px;
+ appearance: none; /* Убираем стандартный чекбокс */
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ border: 2px solid #6a5acd; /* Фиолетовая рамка */
+ border-radius: 5px; /* Скругленные края */
+ outline: none;
+ cursor: pointer;
+ background: #fff;
+ transition: all 0.3s ease; /* Плавный переход */
+}
+.modal-add-wish__checkbox:checked {
+ background: #6a5acd; /* Заполнение фиолетовым цветом */
+ border-color: #6a5acd;
+}
+
+.modal-add-wish__checkbox:checked::after {
+ content: '✔'; /* Галочка */
+ position: absolute;
+ top: 2px;
+ left: 4px;
+ font-size: 16px;
+ color: #fff; /* Цвет галочки */
+ transition: all 0.2s ease; /* Плавное появление */
+}
+
+/* Эффект при наведении */
+.modal-add-wish__checkbox:hover {
+ border-color: #9370db; /* Светлее фиолетовый */
+ box-shadow: 0 0 10px rgba(106, 90, 205, 0.5); /* Тень вокруг */
+}
+
+/* Анимация при клике */
+.modal-add-wish__checkbox:active {
+ transform: scale(0.9); /* Небольшое сжатие */
+}
+
+.recommendations__catalog {
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)) !important;
+}
+
+.recommendations__catalog .product-page__rating {
+ justify-content: center;
+}
+
+.recommendations__catalog .catalog__wrap {
+ display: contents;
+}
diff --git a/src/scripts/components/product-page/views/product-view.ts b/src/scripts/components/product-page/views/product-view.ts
index b62b33d..6a0bc3d 100644
--- a/src/scripts/components/product-page/views/product-view.ts
+++ b/src/scripts/components/product-page/views/product-view.ts
@@ -11,9 +11,10 @@ export class ProductPage {
this.rootElement = document.getElementById(rootId);
if (!this.rootElement) {
- console.error(`Элемент root ${rootId} не найден`);
+ // console.error(`Элемент root ${rootId} не найден`);
return;
}
+
Handlebars.registerPartial('carousel-slider', carouselSliderTemplate);
this.rootElement.innerHTML = '';
const templateElement = document.createElement('div');
diff --git a/src/scripts/components/recomendations/api/config.ts b/src/scripts/components/recomendations/api/config.ts
new file mode 100644
index 0000000..fb77694
--- /dev/null
+++ b/src/scripts/components/recomendations/api/config.ts
@@ -0,0 +1,7 @@
+export const RECOMMENDATIONS_URLS = {
+ GET_PRODUCTS: {
+ route: '/product/',
+ name: 'Главная oxic',
+ REG_EXP:new RegExp(`^/product/$`),
+ },
+};
diff --git a/src/scripts/components/recomendations/api/recommendations.ts b/src/scripts/components/recomendations/api/recommendations.ts
new file mode 100644
index 0000000..c4348f1
--- /dev/null
+++ b/src/scripts/components/recomendations/api/recommendations.ts
@@ -0,0 +1,38 @@
+import { getWithCred } from '../../../../services/api/without-csrf';
+import { backurl } from '../../../../services/app/config';
+import { Product } from '../../card/api/card';
+import {RECOMMENDATIONS_URLS} from "./config";
+
+export interface RecommendationsApiInterface {
+ getProducts(id: number): Promise;
+}
+
+export class RecommendationsApi implements RecommendationsApiInterface {
+ constructor() {
+
+ }
+
+ getProducts = (id: number): Promise => {
+ return getWithCred(backurl + RECOMMENDATIONS_URLS.GET_PRODUCTS.route + id + '/recommendations')
+ .then((res) => {
+ return res.body as Product[];
+ })
+ .catch(() => {
+ return null;
+ });
+ };
+
+ // getProducts = async (id: number): Promise => {
+ // return Array.from({ length: 20 }, (_, index) => ({
+ // id: index + 1,
+ // title: `Product ${index + 1}`,
+ // description: `Description for product ${index + 1}`,
+ // price: 1000 + index * 10,
+ // originalPrice: 1200 + index * 10,
+ // discount: 200,
+ // count: 100,
+ // rating: 4.5,
+ // image_url: `https://via.placeholder.com/150?text=Product+${index + 1}`,
+ // }));
+ // };
+}
\ No newline at end of file
diff --git a/src/scripts/components/recomendations/presenter/recommendations.ts b/src/scripts/components/recomendations/presenter/recommendations.ts
new file mode 100644
index 0000000..f60be0c
--- /dev/null
+++ b/src/scripts/components/recomendations/presenter/recommendations.ts
@@ -0,0 +1,97 @@
+import { RecommendationsApiInterface } from '../api/recommendations';
+import { RecommendationsViewInterface } from '../view/recomendations';
+import {backurl, rootId} from '../../../../services/app/config';
+import { router } from '../../../../services/app/init';
+import { DropdownConfig, DropdownPresenter } from '../../dropdown-btn/presenter/dropdown';
+import { DropdownAPI } from '../../dropdown-btn/api/dropdown';
+
+
+export class Recommendations {
+ private api: RecommendationsApiInterface;
+ private view: RecommendationsViewInterface;
+ private dropdownPresenter: DropdownPresenter;
+ private apiEndpoint = '/product/';
+ private readonly config: DropdownConfig;
+ private readonly id: number | null = null;
+ private readonly name: string | null = null;
+
+ constructor(api: RecommendationsApiInterface, view: RecommendationsViewInterface) {
+ this.api = api;
+ this.view = view;
+
+ this.config = {
+ containerId: 'sort-container',
+ sortOptions: [
+ { value: 'price_asc', text: 'Сначала дешевле' },
+ { value: 'price_desc', text: 'Сначала дороже'},
+ { value: 'rating', text: 'По рейтингу' },
+ ],
+ apiEndpoint: this.apiEndpoint,
+ defaultSort: 'cost',
+ defaultOrder: 'asc',
+ onSortChange: this.handleSortChange,
+ };
+ }
+
+ private handleSortChange = (sortOrder: string): void => {
+ if (!this.id || !this.name) return;
+
+ // Маппинг значений sortOrder в параметры sort и order
+ let sort: string;
+ let order: string;
+
+ switch (sortOrder) {
+ case 'price_asc':
+ sort = 'price';
+ order = 'asc';
+ break;
+ case 'price_desc':
+ sort = 'price';
+ order = 'desc';
+ break;
+ default:
+ sort = 'rating';
+ order = 'desc';
+ break;
+ }
+
+ router.navigate(`${this.apiEndpoint}/${this.id}/recommendations?sort=${sort}&order=${order}`);
+
+ this.recommendationsProducts(this.id, this.name, sort, order);
+ };
+
+ async render(id: number, name: string, rootId: string) {
+ this.id = id;
+ this.name = name;
+
+ const products = {products: await this.api.getProducts(this.id)};
+
+ if (products.products.length > 6) {
+ //products.more = true;
+ products.products = products.products.slice(0, 6);
+ }
+
+ this.view.renderProducts(products, 'Похожее на ' + this.name, this.config, rootId, false, false, this.apiEndpoint + this.id + '/recommendations' + `?id=${this.id}&title=${this.name}`);
+ }
+
+ public recommendationsProducts(id: number, name: string, sort: string, order: string) {
+ DropdownAPI.sortProducts(`${backurl}${this.apiEndpoint}/${id}/recommendations`, sort, order)
+ .then(async (productsApi) => {
+ let products;
+
+ if (productsApi !== undefined) {
+ products = productsApi.body;
+ products.forEach((card: any) => {
+ card.image_url = `${backurl}/${card.image_url}`;
+ });
+ } else {
+ products = {};
+ }
+
+ await this.view.renderProducts({products: products}, 'Похожее на ' + name, this.config, rootId, false, false);
+ })
+ .catch(() => {
+ return
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/scripts/components/recomendations/view/recomendations.ts b/src/scripts/components/recomendations/view/recomendations.ts
new file mode 100644
index 0000000..eb92e12
--- /dev/null
+++ b/src/scripts/components/recomendations/view/recomendations.ts
@@ -0,0 +1,35 @@
+import Handlebars from 'handlebars';
+import { Product } from '../../card/api/card';
+import { CardViewInterface } from '../../card/view/card';
+import { DropdownConfig, DropdownPresenter } from '../../dropdown-btn/presenter/dropdown';
+import {backurl, rootId} from "../../../../services/app/config";
+import {b} from "vite/dist/node/types.d-aGj9QkWt";
+
+export interface RecommendationsViewInterface {
+ renderProducts(data: { products: Product[] , page_title?: string}, query: string, config: DropdownConfig, rootId?: string, show?: boolean, flag?: boolean, url?: string): void;
+}
+
+export class RecommendationsView implements RecommendationsViewInterface {
+ private readonly cardView: CardViewInterface;
+
+ constructor(cardView: CardViewInterface) {
+ this.cardView = cardView;
+ }
+
+ public renderProducts = (data: { products: Product[] , page_title?: string}, query: string, config: DropdownConfig, newRootId: string = rootId, show: boolean = true, flag: boolean = false, url: string = '') => {
+ data.page_title = '';
+
+ data.products.forEach((pr) => {
+ pr.image_url = `${backurl}/${pr.image_url}`;
+ });
+
+ this.cardView.render(data, query, newRootId, show, flag, url);
+
+ document.querySelectorAll('.catalog').forEach((catalog) => {
+ catalog.classList.add('recommendations__catalog');
+ });
+
+ const dropdownPresenter = new DropdownPresenter(config);
+ dropdownPresenter.initView();
+ };
+}
\ No newline at end of file
diff --git a/src/scripts/components/reviews/presenters/reviews.ts b/src/scripts/components/reviews/presenters/reviews.ts
index de52749..8652af7 100644
--- a/src/scripts/components/reviews/presenters/reviews.ts
+++ b/src/scripts/components/reviews/presenters/reviews.ts
@@ -33,10 +33,10 @@ export class ReviewsPresenter {
this.loadReviews(id)
.then(() => {
if (hash) {
- console.log(hash);
+ // console.log(hash);
const targetElement = document.getElementById(hash);
- console.log(targetElement);
+ // console.log(targetElement);
if (targetElement) {
targetElement.scrollIntoView({ behavior: 'smooth' });
@@ -67,28 +67,24 @@ export class ReviewsPresenter {
}))
};
+ console.log(reviewsData);
+
this.view.render(id, formattedReviews);
const view = new AddReviewView();
new AddReviewPresenter(id, view, this.loadReviews);
})
.catch((err: Error) => {
- console.error(123, err);
-
- if (err.message === 'Ошибка при загрузке отзывов: 404'){
- const formattedReviews = {
- total_review_count: 0,
- total_review_rating: 0,
- reviews: []
- };
-
- this.view.render(id, formattedReviews);
- const view = new AddReviewView();
- new AddReviewPresenter(id, view, this.loadReviews);
+ const formattedReviews = {
+ total_review_count: 0,
+ total_review_rating: 0,
+ reviews: []
+ };
- return;
- }
+ this.view.render(id, formattedReviews);
+ const view = new AddReviewView();
+ new AddReviewPresenter(id, view, this.loadReviews);
- this.view.renderError('Не удалось загрузить отзывы. Попробуйте позже.');
+ return;
});
};
}
diff --git a/src/scripts/components/reviews/views/reviews-card.hbs b/src/scripts/components/reviews/views/reviews-card.hbs
index d877420..6685d15 100644
--- a/src/scripts/components/reviews/views/reviews-card.hbs
+++ b/src/scripts/components/reviews/views/reviews-card.hbs
@@ -2,7 +2,7 @@