17 Commits

Author SHA1 Message Date
sdarbinyan
1b2a5af2be test 2026-06-21 01:45:05 +04:00
sdarbinyan
6410321895 price 2026-06-20 15:16:25 +04:00
sdarbinyan
51445a7341 telegram desktop 2026-06-20 15:09:15 +04:00
sdarbinyan
56df8632cb styles 2026-06-20 15:08:10 +04:00
sdarbinyan
824bed199c version 2026-06-20 15:05:09 +04:00
sdarbinyan
b5728f1238 test 3 2026-06-20 14:50:16 +04:00
sdarbinyan
04814aeeda reset 2026-06-20 14:40:22 +04:00
sdarbinyan
9386fbc2f8 currency for market 2026-06-20 14:00:28 +04:00
sdarbinyan
a06b654103 logo fix 2026-06-20 13:33:52 +04:00
sdarbinyan
9aaff4d80a removed parazite 2026-06-19 16:13:54 +04:00
sdarbinyan
7a06843bf5 fixes 2026-06-19 15:01:54 +04:00
sdarbinyan
1decc08f77 userId 2026-06-19 12:43:25 +04:00
sdarbinyan
c0cfbcbcbb changed type 2026-06-19 02:00:34 +04:00
sdarbinyan
688c225911 removed parasite 2026-06-19 01:57:27 +04:00
sdarbinyan
3e79304e5c timer 2026-06-18 18:32:36 +04:00
sdarbinyan
e7d8ec8c63 chek 2026-06-18 18:30:20 +04:00
sdarbinyan
1e3cd99c69 redirect 2026-06-18 18:29:39 +04:00
18 changed files with 612 additions and 341 deletions

451
package-lock.json generated
View File

@@ -8,17 +8,17 @@
"name": "dexarmarket",
"version": "0.0.0",
"dependencies": {
"@angular/animations": "^21.1.5",
"@angular/cdk": "^21.1.5",
"@angular/common": "^21.0.6",
"@angular/compiler": "^21.0.6",
"@angular/core": "^21.0.6",
"@angular/forms": "^21.0.6",
"@angular/material": "^21.1.5",
"@angular/platform-browser": "^21.0.6",
"@angular/platform-browser-dynamic": "^21.1.5",
"@angular/router": "^21.0.6",
"@angular/service-worker": "^21.0.6",
"@angular/animations": "21.1.5",
"@angular/cdk": "21.1.5",
"@angular/common": "21.1.5",
"@angular/compiler": "21.1.5",
"@angular/core": "21.1.5",
"@angular/forms": "21.1.5",
"@angular/material": "21.1.5",
"@angular/platform-browser": "21.1.5",
"@angular/platform-browser-dynamic": "21.1.5",
"@angular/router": "21.1.5",
"@angular/service-worker": "21.1.5",
"primeicons": "^7.0.0",
"primeng": "^21.0.3",
"rxjs": "~7.8.0",
@@ -26,9 +26,9 @@
"zone.js": "~0.16.0"
},
"devDependencies": {
"@angular/build": "^21.0.6",
"@angular/cli": "^21.0.6",
"@angular/compiler-cli": "^21.0.6",
"@angular/build": "21.1.5",
"@angular/cli": "21.1.5",
"@angular/compiler-cli": "21.1.5",
"@types/jasmine": "~5.1.0",
"jasmine-core": "~5.13.0",
"karma": "~6.4.0",
@@ -263,13 +263,13 @@
}
},
"node_modules/@angular-devkit/architect": {
"version": "0.2101.0",
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.0.tgz",
"integrity": "sha512-vnNAzWXwSRGTHk2K7woIQsj7WDYZp69Z3DBdlxkK0H08ymkJ/ELbhN0/AnIJNNtYCqEb57AH7Ro98n422beDuw==",
"version": "0.2101.5",
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.5.tgz",
"integrity": "sha512-eTo6wWzUW5AyBBLTbaUTpBHhGbZhzteErtNGklWkhjicCr/soNH+2mVtvg8bqA8sNreYffK1VXKFsq5NyMh5qg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@angular-devkit/core": "21.1.0",
"@angular-devkit/core": "21.1.5",
"rxjs": "7.8.2"
},
"bin": {
@@ -282,13 +282,13 @@
}
},
"node_modules/@angular-devkit/core": {
"version": "21.1.0",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.0.tgz",
"integrity": "sha512-dPfVy0CictDjWffRv4pGTPOFjdlJL3ZkGUqxzaosUjMbJW+Ai9cNn1VNr7zxYZ4kem3BxLBh1thzDsCPrkXlZA==",
"version": "21.1.5",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.5.tgz",
"integrity": "sha512-KUKbllHvHefkAbTBjWNpRPyrpBqecW+6HBBAR+XNbKBuFTHkG+gxtuwMXNsvO5KECKwQphvQt5h3g05Xtaf0LQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ajv": "8.17.1",
"ajv": "8.18.0",
"ajv-formats": "3.0.1",
"jsonc-parser": "3.3.1",
"picomatch": "4.0.3",
@@ -310,13 +310,13 @@
}
},
"node_modules/@angular-devkit/schematics": {
"version": "21.1.0",
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-21.1.0.tgz",
"integrity": "sha512-sVgTntCZCOV7mOpHzj6V14KOAoy4B9Ur9yHNRFZVgL2yD77TYRrJ0qwq+l7Im9fSjMCar6csjboqCvyAEpfV1g==",
"version": "21.1.5",
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-21.1.5.tgz",
"integrity": "sha512-CGmoorQL5+mVCJEHwHWOrhSd1hFxB3h66i9wUDizJAEQUM3mSml5SiglHArpWY/G4GmFwi6XVe+Jm3U8J/mcFg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@angular-devkit/core": "21.1.0",
"@angular-devkit/core": "21.1.5",
"jsonc-parser": "3.3.1",
"magic-string": "0.30.21",
"ora": "9.0.0",
@@ -344,14 +344,14 @@
}
},
"node_modules/@angular/build": {
"version": "21.1.0",
"resolved": "https://registry.npmjs.org/@angular/build/-/build-21.1.0.tgz",
"integrity": "sha512-ftms4F/TlkRNhf/4ykFO12zTG0f9sIRZ4fGFnaOVGmnKYkPKZklWvMCPoaoIligHS2pqKye1a5JSiTgTeUDp9w==",
"version": "21.1.5",
"resolved": "https://registry.npmjs.org/@angular/build/-/build-21.1.5.tgz",
"integrity": "sha512-v2eDinWKlSKuk5pyMMY8j5TMFW8HA9B1l13TrDDpxsRGAAzekg7TFNyuh1x9Y6Rq4Vn+8/8pCjMUPZigzWbMhQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "2.3.0",
"@angular-devkit/architect": "0.2101.0",
"@angular-devkit/architect": "0.2101.5",
"@babel/core": "7.28.5",
"@babel/helper-annotate-as-pure": "7.27.3",
"@babel/helper-split-export-declaration": "7.24.7",
@@ -374,7 +374,7 @@
"semver": "7.7.3",
"source-map-support": "0.5.21",
"tinyglobby": "0.2.15",
"undici": "7.18.0",
"undici": "7.20.0",
"vite": "7.3.0",
"watchpack": "2.5.0"
},
@@ -394,7 +394,7 @@
"@angular/platform-browser": "^21.0.0",
"@angular/platform-server": "^21.0.0",
"@angular/service-worker": "^21.0.0",
"@angular/ssr": "^21.1.0",
"@angular/ssr": "^21.1.5",
"karma": "^6.4.0",
"less": "^4.2.0",
"ng-packagr": "^21.0.0",
@@ -443,54 +443,6 @@
}
}
},
"node_modules/@angular/build/node_modules/@babel/core": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5",
"@babel/helper-compilation-targets": "^7.27.2",
"@babel/helper-module-transforms": "^7.28.3",
"@babel/helpers": "^7.28.4",
"@babel/parser": "^7.28.5",
"@babel/template": "^7.27.2",
"@babel/traverse": "^7.28.5",
"@babel/types": "^7.28.5",
"@jridgewell/remapping": "^2.3.5",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
"json5": "^2.2.3",
"semver": "^6.3.1"
},
"engines": {
"node": ">=6.9.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/babel"
}
},
"node_modules/@angular/build/node_modules/@babel/core/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/@angular/build/node_modules/convert-source-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
"dev": true,
"license": "MIT"
},
"node_modules/@angular/cdk": {
"version": "21.1.5",
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-21.1.5.tgz",
@@ -508,19 +460,19 @@
}
},
"node_modules/@angular/cli": {
"version": "21.1.0",
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-21.1.0.tgz",
"integrity": "sha512-kzk8du388x6EBybJeq5AB27qGm8oGC9HhvBJDbu8o+aBIOY+JwVON9m4SYLCzeT+EVK8sKA1NMVYi2CDerk6hA==",
"version": "21.1.5",
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-21.1.5.tgz",
"integrity": "sha512-ljqvAzSk8FKMaYW/aZhR+SXjudbQViYYkMlJvJUClGpokjDM9KfJWPX+QZfr2J+piW5yaaHmFaIMddO9QxkUDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@angular-devkit/architect": "0.2101.0",
"@angular-devkit/core": "21.1.0",
"@angular-devkit/schematics": "21.1.0",
"@angular-devkit/architect": "0.2101.5",
"@angular-devkit/core": "21.1.5",
"@angular-devkit/schematics": "21.1.5",
"@inquirer/prompts": "7.10.1",
"@listr2/prompt-adapter-inquirer": "3.0.5",
"@modelcontextprotocol/sdk": "1.25.2",
"@schematics/angular": "21.1.0",
"@modelcontextprotocol/sdk": "1.26.0",
"@schematics/angular": "21.1.5",
"@yarnpkg/lockfile": "1.1.0",
"algoliasearch": "5.46.2",
"ini": "6.0.0",
@@ -544,9 +496,9 @@
}
},
"node_modules/@angular/common": {
"version": "21.0.6",
"resolved": "https://registry.npmjs.org/@angular/common/-/common-21.0.6.tgz",
"integrity": "sha512-Yd8PF0dR37FAzqEcBHAyVCiSGMJOezSJe6rV/4BC6AVLfaZ7oZLl8CNVxKsod2UHd6rKxt1hzx05QdVcVvYNeA==",
"version": "21.1.5",
"resolved": "https://registry.npmjs.org/@angular/common/-/common-21.1.5.tgz",
"integrity": "sha512-olO2F0b+H8YBfsuQFEwo9Hjf+B714xGcttDW37+4jnY2IRS2uYeMu2RGIpY7ps+0uZ017c4iK3CCgSPBgmbTcA==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
@@ -555,14 +507,14 @@
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
},
"peerDependencies": {
"@angular/core": "21.0.6",
"@angular/core": "21.1.5",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/@angular/compiler": {
"version": "21.0.6",
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-21.0.6.tgz",
"integrity": "sha512-rBMzG7WnQMouFfDST+daNSAOVYdtw560645PhlxyVeIeHMlCm0j1jjBgVPGTBNpVgKRdT/sqbi6W6JYkY9mERA==",
"version": "21.1.5",
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-21.1.5.tgz",
"integrity": "sha512-yRUdWlL+AWcTL4d7zD0jkNqsjvxXpWEihvOfD2gc65DO0+E80DsWIpHq9A8yWeLukbfLcmBGI2QbfW9+SXAlvg==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
@@ -572,15 +524,15 @@
}
},
"node_modules/@angular/compiler-cli": {
"version": "21.0.6",
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-21.0.6.tgz",
"integrity": "sha512-UcIUx+fbn0VLlCBCIYxntAzWG3zPRUo0K7wvuK0MC6ZFCWawgewx9SdLLZTqcaWe1g5FRQlQeVQcFgHAO5R2Mw==",
"version": "21.1.5",
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-21.1.5.tgz",
"integrity": "sha512-i2r2bQuWdjjFGTd2TA7FtCWNx5yJ3BMoyTGUC9lzSfmxWAfcH/NWR+6OdaEVwv6Zap3IXYYxs8S+REkx954EwA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/core": "7.28.4",
"@babel/core": "7.28.5",
"@jridgewell/sourcemap-codec": "^1.4.14",
"chokidar": "^4.0.0",
"chokidar": "^5.0.0",
"convert-source-map": "^1.5.1",
"reflect-metadata": "^0.2.0",
"semver": "^7.0.0",
@@ -595,7 +547,7 @@
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
},
"peerDependencies": {
"@angular/compiler": "21.0.6",
"@angular/compiler": "21.1.5",
"typescript": ">=5.9 <6.0"
},
"peerDependenciesMeta": {
@@ -604,10 +556,40 @@
}
}
},
"node_modules/@angular/compiler-cli/node_modules/chokidar": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz",
"integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
"dev": true,
"license": "MIT",
"dependencies": {
"readdirp": "^5.0.0"
},
"engines": {
"node": ">= 20.19.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@angular/compiler-cli/node_modules/readdirp": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz",
"integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 20.19.0"
},
"funding": {
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@angular/core": {
"version": "21.0.6",
"resolved": "https://registry.npmjs.org/@angular/core/-/core-21.0.6.tgz",
"integrity": "sha512-SvWbOkkrsqprYJSBmzQEWkWjfZB/jkRYyFp2ClMJBPqOLxP1a+i3Om2rolcNQjZPz87bs9FszwgRlXUy7sw5cQ==",
"version": "21.1.5",
"resolved": "https://registry.npmjs.org/@angular/core/-/core-21.1.5.tgz",
"integrity": "sha512-m61YHiyE+SIvS8UXcFLjYCucv6ShJJCwz9xxEk7ysYW9wOtHdfIf9tgyOsucZDAvrvpSyQLRj5jGBCGm1VIvXA==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
@@ -616,7 +598,7 @@
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
},
"peerDependencies": {
"@angular/compiler": "21.0.6",
"@angular/compiler": "21.1.5",
"rxjs": "^6.5.3 || ^7.4.0",
"zone.js": "~0.15.0 || ~0.16.0"
},
@@ -630,9 +612,9 @@
}
},
"node_modules/@angular/forms": {
"version": "21.0.6",
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-21.0.6.tgz",
"integrity": "sha512-aAkAAKuUrP8U7R4aH/HbmG/CXP90GlML77ECBI5b4qCSb+bvaTEYsaf85mCyTpr9jvGkia2LTe42hPcOuyzdsQ==",
"version": "21.1.5",
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-21.1.5.tgz",
"integrity": "sha512-Z8Vcgz5KYlCobRxLjyGGUBv0mA4nusuiD36GqYRn3sR780TLDcPFVwTCwVEWLdwID64oiHXG+x9jjU/Z3HzR6A==",
"license": "MIT",
"dependencies": {
"@standard-schema/spec": "^1.0.0",
@@ -642,9 +624,9 @@
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
},
"peerDependencies": {
"@angular/common": "21.0.6",
"@angular/core": "21.0.6",
"@angular/platform-browser": "21.0.6",
"@angular/common": "21.1.5",
"@angular/core": "21.1.5",
"@angular/platform-browser": "21.1.5",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
@@ -666,9 +648,9 @@
}
},
"node_modules/@angular/platform-browser": {
"version": "21.0.6",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-21.0.6.tgz",
"integrity": "sha512-tPk8rlUEBPXIUPRYq6Xu7QhJgKtnVr0dOHHuhyi70biKTupr5VikpZC5X9dy2Q3H3zYbK6MHC6384YMuwfU2kg==",
"version": "21.1.5",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-21.1.5.tgz",
"integrity": "sha512-rAN0cu05Pg7HHe9JMRd3g5JyyVCeFW8QiB/jG6klUrOTF4QzyCbmwlm7MX0uTx3CWAZraWCGbdahUkLyYtuqFA==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
@@ -677,9 +659,9 @@
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
},
"peerDependencies": {
"@angular/animations": "21.0.6",
"@angular/common": "21.0.6",
"@angular/core": "21.0.6"
"@angular/animations": "21.1.5",
"@angular/common": "21.1.5",
"@angular/core": "21.1.5"
},
"peerDependenciesMeta": {
"@angular/animations": {
@@ -706,9 +688,9 @@
}
},
"node_modules/@angular/router": {
"version": "21.0.6",
"resolved": "https://registry.npmjs.org/@angular/router/-/router-21.0.6.tgz",
"integrity": "sha512-HOfomKq7jRSgxt/uUvpdbB8RNaYuGB/FJQ3BfQCFfGw1O9L3B72b7Hilk6AcjCruul6cfv/kmT4EB6Vqi3dQtA==",
"version": "21.1.5",
"resolved": "https://registry.npmjs.org/@angular/router/-/router-21.1.5.tgz",
"integrity": "sha512-OjFn6Nw51CU712CMbl2U9TxlCkzOmjMLYPAfnV4+RdG7o+/eOS2nV0oapJ88RNCw7Yl04PA1amc3ql3agDFd4A==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
@@ -717,16 +699,16 @@
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
},
"peerDependencies": {
"@angular/common": "21.0.6",
"@angular/core": "21.0.6",
"@angular/platform-browser": "21.0.6",
"@angular/common": "21.1.5",
"@angular/core": "21.1.5",
"@angular/platform-browser": "21.1.5",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/@angular/service-worker": {
"version": "21.0.6",
"resolved": "https://registry.npmjs.org/@angular/service-worker/-/service-worker-21.0.6.tgz",
"integrity": "sha512-/T1aHc7ys3in7qTGO8MLIHvoXumMPxv7vU1C1sKbK14mw8ahwuqYo8m2Y+f6/ZcYwUZIbN3Ipd9sHEEB7VCz3A==",
"version": "21.1.5",
"resolved": "https://registry.npmjs.org/@angular/service-worker/-/service-worker-21.1.5.tgz",
"integrity": "sha512-hG+hlcPo3Qkz8LAweGTiN0gQyd24h/rX7gAJZ+R3/lpMJYTMrY76Qrxl7GRs9cN24WbawITMWlhNNFCVQEIQtg==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
@@ -738,7 +720,7 @@
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
},
"peerDependencies": {
"@angular/core": "21.0.6",
"@angular/core": "21.1.5",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
@@ -768,21 +750,21 @@
}
},
"node_modules/@babel/core": {
"version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz",
"integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.3",
"@babel/generator": "^7.28.5",
"@babel/helper-compilation-targets": "^7.27.2",
"@babel/helper-module-transforms": "^7.28.3",
"@babel/helpers": "^7.28.4",
"@babel/parser": "^7.28.4",
"@babel/parser": "^7.28.5",
"@babel/template": "^7.27.2",
"@babel/traverse": "^7.28.4",
"@babel/types": "^7.28.4",
"@babel/traverse": "^7.28.5",
"@babel/types": "^7.28.5",
"@jridgewell/remapping": "^2.3.5",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
@@ -1522,9 +1504,9 @@
}
},
"node_modules/@hono/node-server": {
"version": "1.19.9",
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz",
"integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==",
"version": "1.19.14",
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz",
"integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2096,13 +2078,13 @@
]
},
"node_modules/@modelcontextprotocol/sdk": {
"version": "1.25.2",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.25.2.tgz",
"integrity": "sha512-LZFeo4F9M5qOhC/Uc1aQSrBHxMrvxett+9KLHt7OhcExtoiRN9DKgbZffMP/nxjutWDQpfMDfP3nkHI4X9ijww==",
"version": "1.26.0",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz",
"integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@hono/node-server": "^1.19.7",
"@hono/node-server": "^1.19.9",
"ajv": "^8.17.1",
"ajv-formats": "^3.0.1",
"content-type": "^1.0.5",
@@ -2110,14 +2092,15 @@
"cross-spawn": "^7.0.5",
"eventsource": "^3.0.2",
"eventsource-parser": "^3.0.0",
"express": "^5.0.1",
"express-rate-limit": "^7.5.0",
"jose": "^6.1.1",
"express": "^5.2.1",
"express-rate-limit": "^8.2.1",
"hono": "^4.11.4",
"jose": "^6.1.3",
"json-schema-typed": "^8.0.2",
"pkce-challenge": "^5.0.0",
"raw-body": "^3.0.0",
"zod": "^3.25 || ^4.0",
"zod-to-json-schema": "^3.25.0"
"zod-to-json-schema": "^3.25.1"
},
"engines": {
"node": ">=18"
@@ -3777,14 +3760,14 @@
]
},
"node_modules/@schematics/angular": {
"version": "21.1.0",
"resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-21.1.0.tgz",
"integrity": "sha512-gXf3gO5SeU+tiPHxXeQvdbua4C4/V+KH43JH2PYPxaNCD2HGo1uV0pfyNSNgcVF21voKlbAQ13YRrNDh7z5Kig==",
"version": "21.1.5",
"resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-21.1.5.tgz",
"integrity": "sha512-AndJ17ePYUoqJqiIF9VaXbGAFfOqDcHuAxhwozsQlWDzwgQSOUC/WWeG9hKVCgMD6tE02Sxr2ova9DiBKsLQNg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@angular-devkit/core": "21.1.0",
"@angular-devkit/schematics": "21.1.0",
"@angular-devkit/core": "21.1.5",
"@angular-devkit/schematics": "21.1.5",
"jsonc-parser": "3.3.1"
},
"engines": {
@@ -4026,9 +4009,9 @@
}
},
"node_modules/ajv": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4216,21 +4199,21 @@
}
},
"node_modules/body-parser": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
"integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz",
"integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==",
"dev": true,
"license": "MIT",
"dependencies": {
"bytes": "^3.1.2",
"content-type": "^1.0.5",
"content-type": "^2.0.0",
"debug": "^4.4.3",
"http-errors": "^2.0.0",
"iconv-lite": "^0.7.0",
"http-errors": "^2.0.1",
"iconv-lite": "^0.7.2",
"on-finished": "^2.4.1",
"qs": "^6.14.1",
"raw-body": "^3.0.1",
"type-is": "^2.0.1"
"qs": "^6.15.2",
"raw-body": "^3.0.2",
"type-is": "^2.1.0"
},
"engines": {
"node": ">=18"
@@ -4240,6 +4223,36 @@
"url": "https://opencollective.com/express"
}
},
"node_modules/body-parser/node_modules/content-type": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz",
"integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/body-parser/node_modules/qs": {
"version": "6.15.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz",
"integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
@@ -4713,9 +4726,9 @@
}
},
"node_modules/content-disposition": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
"integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz",
"integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==",
"dev": true,
"license": "MIT",
"engines": {
@@ -5317,9 +5330,9 @@
}
},
"node_modules/eventsource-parser": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz",
"integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz",
"integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -5378,11 +5391,14 @@
}
},
"node_modules/express-rate-limit": {
"version": "7.5.1",
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz",
"integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==",
"version": "8.5.2",
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz",
"integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==",
"dev": true,
"license": "MIT",
"dependencies": {
"ip-address": "^10.2.0"
},
"engines": {
"node": ">= 16"
},
@@ -5408,9 +5424,9 @@
"license": "MIT"
},
"node_modules/fast-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
"integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz",
"integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==",
"dev": true,
"funding": [
{
@@ -5606,9 +5622,9 @@
}
},
"node_modules/get-east-asian-width": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz",
"integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==",
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz",
"integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -5771,6 +5787,16 @@
"node": ">= 0.4"
}
},
"node_modules/hono": {
"version": "4.12.26",
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.26.tgz",
"integrity": "sha512-uyZtpnYxM9CmQ7QsQknM4zN8EftNqhON1qYeIKM0Se67CCEe2c44xyGURwB0axX2fBDu1dqHrHAc1hmNT8ITkw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=16.9.0"
}
},
"node_modules/hosted-git-info": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz",
@@ -5998,9 +6024,9 @@
}
},
"node_modules/ip-address": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
"integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==",
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
"integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -6256,9 +6282,9 @@
"license": "MIT"
},
"node_modules/jose": {
"version": "6.1.3",
"resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz",
"integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==",
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz",
"integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==",
"dev": true,
"license": "MIT",
"funding": {
@@ -7684,14 +7710,14 @@
}
},
"node_modules/ora/node_modules/string-width": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz",
"integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==",
"version": "8.2.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz",
"integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==",
"dev": true,
"license": "MIT",
"dependencies": {
"get-east-asian-width": "^1.3.0",
"strip-ansi": "^7.1.0"
"get-east-asian-width": "^1.5.0",
"strip-ansi": "^7.1.2"
},
"engines": {
"node": ">=20"
@@ -7883,9 +7909,9 @@
}
},
"node_modules/path-to-regexp": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
"integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
"version": "8.4.2",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
"integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
"dev": true,
"license": "MIT",
"funding": {
@@ -9053,18 +9079,36 @@
}
},
"node_modules/type-is": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
"integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz",
"integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==",
"dev": true,
"license": "MIT",
"dependencies": {
"content-type": "^1.0.5",
"content-type": "^2.0.0",
"media-typer": "^1.1.0",
"mime-types": "^3.0.0"
},
"engines": {
"node": ">= 0.6"
"node": ">= 18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/type-is/node_modules/content-type": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz",
"integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/typescript": {
@@ -9109,9 +9153,9 @@
}
},
"node_modules/undici": {
"version": "7.18.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-7.18.0.tgz",
"integrity": "sha512-CfPufgPFHCYu0W4h1NiKW9+tNJ39o3kWm7Cm29ET1enSJx+AERfz7A2wAr26aY0SZbYzZlTBQtcHy15o60VZfQ==",
"version": "7.20.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-7.20.0.tgz",
"integrity": "sha512-MJZrkjyd7DeC+uPZh+5/YaMDxFiiEEaDgbUSVMXayofAkDWF1088CDo+2RPg7B1BuS1qf1vgNE7xqwPxE0DuSQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -9563,13 +9607,13 @@
}
},
"node_modules/zod-to-json-schema": {
"version": "3.25.1",
"resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz",
"integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==",
"version": "3.25.2",
"resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz",
"integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==",
"dev": true,
"license": "ISC",
"peerDependencies": {
"zod": "^3.25 || ^4"
"zod": "^3.25.28 || ^4"
}
},
"node_modules/zone.js": {
@@ -9580,4 +9624,3 @@
}
}
}

View File

@@ -19,17 +19,17 @@
},
"private": true,
"dependencies": {
"@angular/animations": "^21.1.5",
"@angular/cdk": "^21.1.5",
"@angular/common": "^21.0.6",
"@angular/compiler": "^21.0.6",
"@angular/core": "^21.0.6",
"@angular/forms": "^21.0.6",
"@angular/material": "^21.1.5",
"@angular/platform-browser": "^21.0.6",
"@angular/platform-browser-dynamic": "^21.1.5",
"@angular/router": "^21.0.6",
"@angular/service-worker": "^21.0.6",
"@angular/animations": "21.1.5",
"@angular/cdk": "21.1.5",
"@angular/common": "21.1.5",
"@angular/compiler": "21.1.5",
"@angular/core": "21.1.5",
"@angular/forms": "21.1.5",
"@angular/material": "21.1.5",
"@angular/platform-browser": "21.1.5",
"@angular/platform-browser-dynamic": "21.1.5",
"@angular/router": "21.1.5",
"@angular/service-worker": "21.1.5",
"primeicons": "^7.0.0",
"primeng": "^21.0.3",
"rxjs": "~7.8.0",
@@ -37,9 +37,9 @@
"zone.js": "~0.16.0"
},
"devDependencies": {
"@angular/build": "^21.0.6",
"@angular/cli": "^21.0.6",
"@angular/compiler-cli": "^21.0.6",
"@angular/build": "21.1.5",
"@angular/cli": "21.1.5",
"@angular/compiler-cli": "21.1.5",
"@types/jasmine": "~5.1.0",
"jasmine-core": "~5.13.0",
"karma": "~6.4.0",

View File

@@ -19,5 +19,5 @@
<router-outlet></router-outlet>
</main>
<app-footer></app-footer>
<app-telegram-login />
<!-- <app-telegram-login /> -->
}

View File

@@ -5,7 +5,6 @@ import { Title } from '@angular/platform-browser';
import { HeaderComponent } from './components/header/header.component';
import { FooterComponent } from './components/footer/footer.component';
import { BackButtonComponent } from './components/back-button/back-button.component';
import { TelegramLoginComponent } from './components/telegram-login/telegram-login.component';
import { ApiService } from './services';
import { interval, concat } from 'rxjs';
import { filter, first } from 'rxjs/operators';
@@ -17,7 +16,7 @@ import { TranslateService } from './i18n/translate.service';
@Component({
selector: 'app-root',
imports: [RouterOutlet, HeaderComponent, FooterComponent, BackButtonComponent, TelegramLoginComponent, TranslatePipe],
imports: [RouterOutlet, HeaderComponent, FooterComponent, BackButtonComponent, TranslatePipe],
templateUrl: './app.html',
styleUrl: './app.scss'
})
@@ -92,13 +91,5 @@ export class App implements OnInit {
console.error('Update check failed:', err);
}
});
this.swUpdate.versionUpdates
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(event => {
if (event.type === 'VERSION_READY') {
console.log('New app version ready');
}
});
}
}

View File

@@ -3,7 +3,7 @@
<header class="novo-header">
<div class="novo-header-container">
<div class="novo-left">
<a [routerLink]="'/' | langRoute" class="novo-logo" (click)="closeMenu()">
<a [attr.href]="homeUrl" class="novo-logo" (click)="navigateHome($event)">
<app-logo />
<!-- <span class="novo-brand">{{ brandName }}</span> -->
</a>
@@ -36,13 +36,18 @@
<app-language-selector />
<a [routerLink]="'/cart' | langRoute" routerLinkActive="novo-cart-active" class="novo-cart" (click)="closeMenu()" [attr.aria-label]="'header.cart' | translate">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<circle cx="9" cy="21" r="1"></circle>
<circle cx="20" cy="21" r="1"></circle>
<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path>
</svg>
<span class="novo-cart-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<circle cx="9" cy="21" r="1"></circle>
<circle cx="20" cy="21" r="1"></circle>
<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path>
</svg>
@if (cartItemCount() > 0) {
<span class="novo-cart-badge">{{ cartItemCount() }}</span>
}
</span>
@if (cartItemCount() > 0) {
<span class="novo-cart-badge">{{ cartItemCount() }}</span>
<span class="novo-cart-total">{{ formatCartTotal(cartTotal()) }}</span>
}
</a>
@@ -59,7 +64,7 @@
<header class="dexar-header">
<div class="dexar-header-container">
<!-- Logo -->
<a [routerLink]="'/' | langRoute" class="dexar-logo" (click)="closeMenu()">
<a [attr.href]="homeUrl" class="dexar-logo" (click)="navigateHome($event)">
<app-logo />
</a>
@@ -102,13 +107,18 @@
<div class="dexar-actions">
<!-- Cart Button -->
<a [routerLink]="'/cart' | langRoute" routerLinkActive="dexar-cart-active" class="dexar-cart-btn" (click)="closeMenu()">
<svg width="32" height="24" viewBox="0 0 48 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 0.5H36C42.3513 0.5 47.5 5.64873 47.5 12V20C47.5 26.3513 42.3513 31.5 36 31.5H12C5.64873 31.5 0.5 26.3513 0.5 20V12C0.5 5.64873 5.64873 0.5 12 0.5Z" fill="transparent" />
<path d="M12 0.5H36C42.3513 0.5 47.5 5.64873 47.5 12V20C47.5 26.3513 42.3513 31.5 36 31.5H12C5.64873 31.5 0.5 26.3513 0.5 20V12C0.5 5.64873 5.64873 0.5 12 0.5Z" stroke="#677B78" />
<path d="M10 3.9C10 3.40294 10.4029 3 10.9 3H13.6C14.013 3 14.373 3.28107 14.4731 3.68172L15.2027 6.6H36.1C36.3677 6.6 36.6216 6.7192 36.7925 6.92523C36.9635 7.13125 37.0339 7.40271 36.9846 7.66586L34.2846 22.0659C34.2048 22.4915 33.8331 22.8 33.4 22.8H31.6H19H17.2C16.7669 22.8 16.3952 22.4915 16.3154 22.0659L13.6204 7.69224L12.8973 4.8H10.9C10.4029 4.8 10 4.39706 10 3.9ZM15.5844 8.4L17.9469 21H32.6531L35.0156 8.4H15.5844ZM19 22.8C17.0118 22.8 15.4 24.4118 15.4 26.4C15.4 28.3882 17.0118 30 19 30C20.9882 30 22.6 28.3882 22.6 26.4C22.6 24.4118 20.9882 22.8 19 22.8ZM31.6 22.8C29.6118 22.8 28 24.4118 28 26.4C28 28.3882 29.6118 30 31.6 30C33.5882 30 35.2 28.3882 35.2 26.4C35.2 24.4118 33.5882 22.8 31.6 22.8ZM19 24.6C19.9941 24.6 20.8 25.4059 20.8 26.4C20.8 27.3941 19.9941 28.2 19 28.2C18.0059 28.2 17.2 27.3941 17.2 26.4C17.2 25.4059 18.0059 24.6 19 24.6ZM31.6 24.6C32.5941 24.6 33.4 25.4059 33.4 26.4C33.4 27.3941 32.5941 28.2 31.6 28.2C30.6059 28.2 29.8 27.3941 29.8 26.4C29.8 25.4059 30.6059 24.6 31.6 24.6Z" fill="#1E3C38" />
</svg>
<span class="dexar-cart-icon">
<svg width="32" height="24" viewBox="0 0 48 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 0.5H36C42.3513 0.5 47.5 5.64873 47.5 12V20C47.5 26.3513 42.3513 31.5 36 31.5H12C5.64873 31.5 0.5 26.3513 0.5 20V12C0.5 5.64873 5.64873 0.5 12 0.5Z" fill="transparent" />
<path d="M12 0.5H36C42.3513 0.5 47.5 5.64873 47.5 12V20C47.5 26.3513 42.3513 31.5 36 31.5H12C5.64873 31.5 0.5 26.3513 0.5 20V12C0.5 5.64873 5.64873 0.5 12 0.5Z" stroke="#677B78" />
<path d="M10 3.9C10 3.40294 10.4029 3 10.9 3H13.6C14.013 3 14.373 3.28107 14.4731 3.68172L15.2027 6.6H36.1C36.3677 6.6 36.6216 6.7192 36.7925 6.92523C36.9635 7.13125 37.0339 7.40271 36.9846 7.66586L34.2846 22.0659C34.2048 22.4915 33.8331 22.8 33.4 22.8H31.6H19H17.2C16.7669 22.8 16.3952 22.4915 16.3154 22.0659L13.6204 7.69224L12.8973 4.8H10.9C10.4029 4.8 10 4.39706 10 3.9ZM15.5844 8.4L17.9469 21H32.6531L35.0156 8.4H15.5844ZM19 22.8C17.0118 22.8 15.4 24.4118 15.4 26.4C15.4 28.3882 17.0118 30 19 30C20.9882 30 22.6 28.3882 22.6 26.4C22.6 24.4118 20.9882 22.8 19 22.8ZM31.6 22.8C29.6118 22.8 28 24.4118 28 26.4C28 28.3882 29.6118 30 31.6 30C33.5882 30 35.2 28.3882 35.2 26.4C35.2 24.4118 33.5882 22.8 31.6 22.8ZM19 24.6C19.9941 24.6 20.8 25.4059 20.8 26.4C20.8 27.3941 19.9941 28.2 19 28.2C18.0059 28.2 17.2 27.3941 17.2 26.4C17.2 25.4059 18.0059 24.6 19 24.6ZM31.6 24.6C32.5941 24.6 33.4 25.4059 33.4 26.4C33.4 27.3941 32.5941 28.2 31.6 28.2C30.6059 28.2 29.8 27.3941 29.8 26.4C29.8 25.4059 30.6059 24.6 31.6 24.6Z" fill="#1E3C38" />
</svg>
@if (cartItemCount() > 0) {
<span class="dexar-cart-badge">{{ cartItemCount() }}</span>
}
</span>
@if (cartItemCount() > 0) {
<span class="dexar-cart-badge">{{ cartItemCount() }}</span>
<span class="dexar-cart-total">{{ formatCartTotal(cartTotal()) }}</span>
}
</a>

View File

@@ -333,42 +333,60 @@
}
.novo-cart {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.2rem;
color: var(--text-primary);
text-decoration: none;
padding: 0.5rem;
border-radius: var(--radius-md);
transition: all 0.3s;
svg {
display: block;
}
&:hover,
&.novo-cart-active {
color: var(--primary-color);
background: var(--bg-secondary);
}
.novo-cart-badge {
position: absolute;
top: -4px;
right: -4px;
background: var(--primary-color);
color: white;
font-size: 0.7rem;
font-weight: 700;
min-width: 18px;
height: 18px;
border-radius: 9px;
display: flex;
align-items: center;
justify-content: center;
padding: 0 5px;
box-shadow: var(--shadow-sm);
}
.novo-cart-icon {
position: relative;
display: flex;
align-items: center;
justify-content: center;
line-height: 0;
svg {
display: block;
}
}
.novo-cart-badge {
position: absolute;
top: -4px;
right: -4px;
background: var(--primary-color);
color: white;
font-size: 0.7rem;
font-weight: 700;
min-width: 18px;
height: 18px;
border-radius: 9px;
display: flex;
align-items: center;
justify-content: center;
padding: 0 5px;
box-shadow: var(--shadow-sm);
}
.novo-cart-total {
font-size: 0.68rem;
font-weight: 700;
line-height: 1;
white-space: nowrap;
}
.novo-menu-toggle {
display: none;
flex-direction: column;
@@ -492,7 +510,7 @@
display: flex;
align-items: center;
gap: 32px;
height: 48px;
min-height: 48px;
}
.dexar-logo {
@@ -615,15 +633,15 @@
}
.dexar-cart-btn {
position: relative;
width: 36px;
height: 28px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-width: 36px;
text-decoration: none;
cursor: pointer;
transition: opacity 0.3s ease;
gap: 2px;
svg {
width: 36px;
@@ -639,6 +657,15 @@
}
}
.dexar-cart-icon {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 28px;
}
.dexar-cart-badge {
position: absolute;
top: -8px;
@@ -658,6 +685,15 @@
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
}
.dexar-cart-total {
font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
font-weight: 700;
font-size: 10px;
line-height: 1;
color: #1e3c38;
white-space: nowrap;
}
.dexar-lang-selector {
display: flex;
align-items: center;

View File

@@ -17,6 +17,7 @@ import { TranslatePipe } from '../../i18n/translate.pipe';
})
export class HeaderComponent {
cartItemCount;
cartTotal;
menuOpen = false;
brandName = environment.brandFullName;
logo = environment.logo;
@@ -28,6 +29,11 @@ export class HeaderComponent {
constructor(private cartService: CartService, private router: Router) {
this.cartItemCount = this.cartService.itemCount;
this.cartTotal = this.cartService.totalPrice;
}
get homeUrl(): string {
return `/${this.langService.currentLanguage()}`;
}
toggleMenu(): void {
@@ -44,6 +50,23 @@ export class HeaderComponent {
this.renderer.removeClass(this.document.body, 'dexar-menu-open');
}
navigateHome(event?: Event): void {
event?.preventDefault();
this.closeMenu();
const homeUrl = this.homeUrl;
const currentUrl = this.router.url.split('?')[0].split('#')[0];
if (currentUrl === homeUrl || currentUrl === `${homeUrl}/`) {
this.document.defaultView?.scrollTo({ top: 0, behavior: 'smooth' });
return;
}
this.router.navigateByUrl(homeUrl).then(() => {
this.document.defaultView?.scrollTo({ top: 0, behavior: 'auto' });
});
}
navigateToSearch(): void {
const lang = this.langService.currentLanguage();
this.router.navigate([`/${lang}/search`]);
@@ -58,4 +81,20 @@ export class HeaderComponent {
}, 100);
});
}
formatCartTotal(total: number): string {
const locale = this.langService.currentLanguage() === 'en'
? 'en-US'
: this.langService.currentLanguage() === 'hy'
? 'hy-AM'
: 'ru-RU';
const fractionDigits = Number.isInteger(total) ? 0 : 2;
const amount = new Intl.NumberFormat(locale, {
minimumFractionDigits: fractionDigits,
maximumFractionDigits: 2,
}).format(total);
const currencySymbol = this.langService.getCurrentCurrency()?.symbol ?? this.langService.currentCurrency();
return `${amount} ${currencySymbol}`;
}
}

View File

@@ -3,12 +3,15 @@ import { environment } from '../../../environments/environment';
@Component({
selector: 'app-logo',
template: `<img [src]="logoPath" [alt]="brandName + ' logo'" class="logo-img" fetchpriority="high" />`,
template: `<img [src]="logoPath" [alt]="brandName + ' logo'" class="logo-img" fetchpriority="high" draggable="false" />`,
styles: [`
.logo-img {
display: block;
width: 100%;
height: 100%;
object-fit: contain;
pointer-events: none;
user-select: none;
}
`],
changeDetection: ChangeDetectionStrategy.OnPush

View File

@@ -1,8 +1,6 @@
import { Component, ChangeDetectionStrategy, inject, signal, computed, effect, OnDestroy } from '@angular/core';
import { AuthService } from '../../services/auth.service';
import { CartService } from '../../services/cart.service';
import { TranslatePipe } from '../../i18n/translate.pipe';
import { getDiscountedPrice } from '../../utils/item.utils';
@Component({
selector: 'app-telegram-login',
@@ -13,7 +11,6 @@ import { getDiscountedPrice } from '../../utils/item.utils';
})
export class TelegramLoginComponent implements OnDestroy {
private authService = inject(AuthService);
private cartService = inject(CartService);
showDialog = this.authService.showLoginDialog;
status = this.authService.status;
@@ -22,48 +19,77 @@ export class TelegramLoginComponent implements OnDestroy {
webSessionID = signal('');
qrStatus = signal<'loading' | 'ready' | 'expired' | 'error'>('loading');
encodedQrUrl = computed(() => encodeURIComponent(this.loginUrl()));
awaitingTelegramReturn = signal(false);
private readonly pollIntervalMs = 5000;
private pollTimer?: ReturnType<typeof setInterval>;
private telegramFallbackTimer?: ReturnType<typeof setTimeout>;
private readonly handleVisibilityChange = () => {
if (typeof document !== 'undefined' && document.visibilityState === 'visible') {
this.checkLoginAfterReturn();
}
};
private readonly handleWindowFocus = () => {
this.checkLoginAfterReturn();
};
private readonly handlePageShow = () => {
this.checkLoginAfterReturn();
};
constructor() {
effect(() => {
if (this.showDialog()) {
this.initQrLogin();
} else {
this.awaitingTelegramReturn.set(false);
this.stopPolling();
}
});
if (typeof window !== 'undefined') {
document.addEventListener('visibilitychange', this.handleVisibilityChange);
window.addEventListener('focus', this.handleWindowFocus);
window.addEventListener('pageshow', this.handlePageShow);
}
}
ngOnDestroy(): void {
this.awaitingTelegramReturn.set(false);
this.stopPolling();
if (typeof window !== 'undefined') {
document.removeEventListener('visibilitychange', this.handleVisibilityChange);
window.removeEventListener('focus', this.handleWindowFocus);
window.removeEventListener('pageshow', this.handlePageShow);
}
}
close(): void {
this.awaitingTelegramReturn.set(false);
this.authService.hideLogin();
this.stopPolling();
}
openTelegramLogin(): void {
const url = this.loginUrl();
if (!url) return;
const webSessionID = this.webSessionID();
if (!webSessionID || typeof window === 'undefined') return;
window.open(url, '_blank');
if (!this.pollTimer) {
const webSessionID = this.webSessionID();
if (webSessionID) {
this.startPolling(webSessionID);
}
this.startPolling(webSessionID);
}
this.awaitingTelegramReturn.set(true);
window.location.href = this.authService.getTelegramAppLoginUrl(webSessionID);
}
refreshQr(): void {
this.awaitingTelegramReturn.set(false);
this.stopPolling();
this.initQrLogin();
}
private initQrLogin(): void {
this.awaitingTelegramReturn.set(false);
this.qrStatus.set('loading');
this.loginUrl.set('');
this.webSessionID.set('');
@@ -97,8 +123,9 @@ export class TelegramLoginComponent implements OnDestroy {
this.authService.checkSessionOnce(webSessionID).subscribe({
next: (session) => {
if (session?.active) {
this.awaitingTelegramReturn.set(false);
this.stopPolling();
this.syncCartAndComplete(session.sessionId);
this.authService.onTelegramLoginComplete();
}
},
error: () => {
@@ -108,26 +135,34 @@ export class TelegramLoginComponent implements OnDestroy {
}, this.pollIntervalMs);
}
private syncCartAndComplete(sessionId: string): void {
const cartItems = this.cartService.items().map(item => ({
itemID: item.itemID,
quantity: item.quantity,
colour: item.colour || '',
size: item.size || '',
price: item.discount > 0
? item.price * (1 - item.discount / 100)
: item.price,
}));
this.authService.syncCart(sessionId, cartItems).subscribe(() => {
this.authService.onTelegramLoginComplete();
});
}
private stopPolling(): void {
if (this.pollTimer) {
clearInterval(this.pollTimer);
this.pollTimer = undefined;
}
}
private checkLoginAfterReturn(): void {
if (!this.showDialog() || !this.awaitingTelegramReturn()) {
return;
}
const webSessionID = this.webSessionID();
if (!webSessionID) {
this.awaitingTelegramReturn.set(false);
return;
}
if (!this.pollTimer) {
this.startPolling(webSessionID);
}
this.authService.checkSessionOnce(webSessionID).subscribe(session => {
if (session?.active) {
this.awaitingTelegramReturn.set(false);
this.stopPolling();
this.authService.onTelegramLoginComplete();
}
});
}
}

View File

@@ -780,8 +780,8 @@ export const mockDataInterceptor: HttpInterceptorFn = (req, next) => {
return respond([]);
}
// ── POST /websession/:id (add to cart)
if (url.match(/\/websession\/[^/]+$/) && req.method === 'POST') {
// ── POST /usersession/:id or /websession/:id (sync cart)
if (url.match(/\/(?:user|web)session\/[^/]+$/) && req.method === 'POST') {
return respond({
sessionId: 'mock-session',
Status: true,

View File

@@ -1,10 +1,10 @@
export interface AuthSession {
sessionId: string;
telegramUserId: number;
userId: number | null;
username: string | null;
displayName: string;
active: boolean;
expiresAt: string;
expires: string;
}
export interface WebSessionStart {

View File

@@ -68,6 +68,7 @@ export interface ItemDetail {
colour?: string;
size?: string;
price: number;
deliveryPrice?: number;
currency: string;
remaining: number;
}
@@ -80,6 +81,7 @@ export interface Item {
description: string;
currency: string;
price: number;
deliveryPrice?: number;
discount: number;
remainings?: string;
rating: number;

View File

@@ -78,6 +78,12 @@
} @else {
<span class="current-price">{{ item.price }} {{ item.currency }}</span>
}
@if (item.deliveryPrice != null) {
<span class="delivery-price">
{{ 'cart.deliveryLabel' | translate }}: {{ item.deliveryPrice | number:'1.2-2' }} {{ item.currency }}
</span>
}
</div>
<div class="quantity-controls">
@@ -116,14 +122,16 @@
<span class="value">{{ totalPrice() | number:'1.2-2' }} {{ currentCurrency }}</span>
</div>
<div class="summary-row delivery">
<span>{{ 'cart.deliveryLabel' | translate }}</span>
<span>0 {{ currentCurrency }}</span>
</div>
@if (hasDeliveryPrice()) {
<div class="summary-row delivery">
<span>{{ 'cart.deliveryLabel' | translate }}</span>
<span class="value">{{ totalDeliveryPrice() | number:'1.2-2' }} {{ currentCurrency }}</span>
</div>
}
<div class="summary-row total">
<span>{{ 'cart.toPay' | translate }}</span>
<span class="total-price">{{ totalPrice() | number:'1.2-2' }} {{ currentCurrency }}</span>
<span class="total-price">{{ totalWithDelivery() | number:'1.2-2' }} {{ currentCurrency }}</span>
</div>
<div class="terms-agreement">
@@ -245,7 +253,7 @@
<div class="payment-status-screen success">
<div class="success-icon"></div>
<h2>{{ 'cart.paymentSuccess' | translate }}</h2>
<p class="success-text">{{ 'cart.paymentSuccessDesc' | translate }}</p>
<!-- <p class="success-text">{{ 'cart.paymentSuccessDesc' | translate }}</p> -->
<!-- <div class="email-form">
<div class="input-group">

View File

@@ -225,14 +225,14 @@
.cart-content {
display: grid;
grid-template-columns: 1fr 350px;
grid-template-columns: minmax(0, 1fr) 350px;
gap: 24px;
align-items: start;
}
// Novo wider summary
.cart-container.novo .cart-content {
grid-template-columns: 1fr 400px;
grid-template-columns: minmax(0, 1fr) 400px;
gap: 32px;
}
@@ -240,6 +240,7 @@
display: flex;
flex-direction: column;
gap: 16px;
min-width: 0;
}
// Novo larger gap
@@ -554,6 +555,12 @@
font-weight: 700;
color: #497671;
}
.delivery-price {
font-size: 0.85rem;
font-weight: 500;
color: #697777;
}
}
// Dexar quantity controls
@@ -837,7 +844,7 @@
color: #6b7280;
&.delivery {
display: none; // Hide delivery in Novo
display: flex;
}
&.total {
@@ -1527,11 +1534,31 @@
// Mobile responsive
@media (max-width: 768px) {
.cart-content {
grid-template-columns: 1fr;
grid-template-columns: minmax(0, 1fr);
gap: 20px;
}
.cart-container.novo .cart-content {
grid-template-columns: minmax(0, 1fr);
gap: 20px;
}
.cart-summary {
position: static;
width: 100%;
max-width: 100%;
min-width: 0;
}
.cart-container.novo,
.cart-container.dexar {
padding: 16px;
}
.cart-container.novo .cart-header,
.cart-container.dexar .cart-header {
flex-wrap: wrap;
gap: 12px;
}
.remove-btn-desktop {
@@ -1809,6 +1836,7 @@
margin: 0;
line-height: 1.5;
display: -webkit-box;
line-clamp: 2;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;

View File

@@ -1,4 +1,4 @@
import { Component, computed, ChangeDetectionStrategy, signal, OnDestroy, inject } from '@angular/core';
import { Component, ChangeDetectionStrategy, signal, OnDestroy, inject } from '@angular/core';
import { DecimalPipe } from '@angular/common';
import { Router, RouterLink } from '@angular/router';
import { FormsModule } from '@angular/forms';
@@ -26,6 +26,9 @@ export class CartComponent implements OnDestroy {
items;
itemCount;
totalPrice;
totalDeliveryPrice;
totalWithDelivery;
hasDeliveryPrice;
termsAccepted = false;
isnovo = environment.theme === 'novo';
@@ -39,7 +42,7 @@ export class CartComponent implements OnDestroy {
// Payment popup states
showPaymentPopup = signal<boolean>(false);
paymentStatus = signal<'creating' | 'waiting' | 'success' | 'timeout' | 'error'>('creating');
paymentStatus = signal<'creating' | 'waiting' | 'success' | 'timeout' | 'error' | null>('creating');
qrCodeUrl = signal<string>('');
paymentUrl = signal<string>('');
paymentId = signal<string>('');
@@ -68,6 +71,9 @@ export class CartComponent implements OnDestroy {
this.items = this.cartService.items;
this.itemCount = this.cartService.itemCount;
this.totalPrice = this.cartService.totalPrice;
this.totalDeliveryPrice = this.cartService.totalDeliveryPrice;
this.totalWithDelivery = this.cartService.totalWithDelivery;
this.hasDeliveryPrice = this.cartService.hasDeliveryPrice;
}
requestLogin(): void {
@@ -195,7 +201,7 @@ export class CartComponent implements OnDestroy {
createPayment(): void {
const orderId = this.generateOrderId();
const paymentPayload = {
amount: Number(this.totalPrice()),
amount: Number(this.totalWithDelivery()),
currency: 'RUB' as const,
siteuserID: this.getPaymentUserId(),
siteorderID: orderId,
@@ -274,13 +280,14 @@ export class CartComponent implements OnDestroy {
if (paymentStatus === 'COMPLETED' || paymentStatus === 'APPROVED' || paymentStatus === 'PAID' || paymentCode === 'SUCCESS') {
this.paymentStatus.set('success');
this.stopPolling();
this.cartService.clearCart();
// Auto-submit purchase after 5 seconds
// Auto-submit purchase after 5 seconds
if (this.closeTimeout) clearTimeout(this.closeTimeout);
this.closeTimeout = setTimeout(() => {
this.autoSubmitPurchase();
}, 5000);
this.cartService.clearCart();
}
// Continue checking for 3 minutes regardless of other statuses
},
@@ -316,6 +323,9 @@ export class CartComponent implements OnDestroy {
}
private autoSubmitPurchase(): void {
setTimeout(() => {
const lang = this.langService.currentLanguage();
this.router.navigate([`/${lang}`]);}, 0);
const telegramUserId = this.getTelegramUserId();
// Telegram ID is mandatory
@@ -358,6 +368,9 @@ export class CartComponent implements OnDestroy {
this.router.navigate([`/${lang}`]);
}
});
this.paymentStatus.set(null);
}
copyPaymentLink(): void {
@@ -428,8 +441,8 @@ export class CartComponent implements OnDestroy {
}
private getTelegramUserId(): string | null {
const sessionTelegramUserId = this.authService.session()?.telegramUserId;
if (sessionTelegramUserId) {
const sessionTelegramUserId = this.authService.session()?.userId;
if (sessionTelegramUserId !== null && sessionTelegramUserId !== undefined) {
return sessionTelegramUserId.toString();
}

View File

@@ -86,6 +86,18 @@ export class ApiService {
return c.startsWith('0x') ? '#' + c.slice(2) : c;
}
private normalizeOptionalNumber(value: unknown): number | undefined {
if (value === null || value === undefined || value === '') {
return undefined;
}
const normalized = typeof value === 'number'
? value
: Number(String(value).replace(',', '.'));
return Number.isFinite(normalized) ? normalized : undefined;
}
/** Resolve relative image URLs (e.g. ./images/x.webp) against site origin */
private resolveImageUrl(url: string): string {
if (!url) return '';
@@ -102,6 +114,13 @@ export class ApiService {
private normalizeItem(raw: any): Item {
const { partnerID, ...rest } = raw;
const item: Item = { ...rest };
const topLevelDeliveryPrice = this.normalizeOptionalNumber(
raw.deliveryPrice ?? raw.delivery_price ?? raw.deliveryprice
);
if (topLevelDeliveryPrice !== undefined) {
item.deliveryPrice = topLevelDeliveryPrice;
}
// Extract price/currency/remaining/colour/size from itemDetails[]
// Note: Go struct tag is "itemdetails" but actual API may send "itemDetails"
@@ -112,11 +131,23 @@ export class ApiService {
...d,
colour: this.normalizeColor(d.colour || d.color || ''),
color: undefined,
deliveryPrice: this.normalizeOptionalNumber(
d.deliveryPrice ?? d.delivery_price ?? d.deliveryprice
),
}));
if (item.price == null || item.price === 0) item.price = detail.price;
if (!item.currency) item.currency = detail.currency;
if (!item.colour) item.colour = this.normalizeColor(detail.colour || detail.color || '');
if (!item.size) item.size = detail.size || '';
if (item.deliveryPrice == null) {
const detailDeliveryPrice = this.normalizeOptionalNumber(
detail.deliveryPrice ?? detail.delivery_price ?? detail.deliveryprice
);
if (detailDeliveryPrice !== undefined) {
item.deliveryPrice = detailDeliveryPrice;
}
}
// Use remaining from detail for stock level
if (raw.remaining == null && detail.remaining != null) {
(raw as any).remaining = detail.remaining;

View File

@@ -26,7 +26,6 @@ export class AuthService {
/** Display name of authenticated user */
readonly displayName = computed(() => this.sessionSignal()?.displayName ?? null);
private readonly apiUrl = environment.apiUrl;
private readonly authApiUrl = environment.authApiUrl;
private sessionCheckTimer?: ReturnType<typeof setTimeout>;
@@ -85,10 +84,16 @@ export class AuthService {
/** Generate the Telegram login URL for bot-based auth */
getTelegramLoginUrl(webSessionID = this.generateGuid()): string {
const botUsername = (environment as Record<string, unknown>)['telegramBot'] as string || 'DexarSupport_bot';
const botUsername = this.getTelegramBotUsername();
return `https://t.me/${botUsername}?start=${encodeURIComponent(webSessionID)}`;
}
/** Generate a Telegram app deep link for mobile login without opening a browser tab. */
getTelegramAppLoginUrl(webSessionID: string): string {
const botUsername = this.getTelegramBotUsername();
return `tg://resolve?domain=${encodeURIComponent(botUsername)}&start=${encodeURIComponent(webSessionID)}`;
}
/** Get QR code data URL for Telegram login */
getTelegramQrUrl(): string {
return this.getTelegramLoginUrl();
@@ -113,14 +118,6 @@ export class AuthService {
);
}
/** Sync local cart to the backend session after login */
syncCart(sessionId: string, items: Array<{ itemID: number; quantity: number; colour?: string; size?: string; price?: number }>): Observable<unknown> {
if (!items.length) return of(null);
return this.http.post(`${this.apiUrl}/websession/${sessionId}`, items, {
withCredentials: true,
}).pipe(catchError(() => of(null)));
}
/** Show login dialog (called when user tries to pay without being logged in) */
requestLogin(): void {
this.showLoginSignal.set(true);
@@ -153,7 +150,7 @@ export class AuthService {
this.sessionSignal.set(session);
this.statusSignal.set('authenticated');
this.setStoredWebSessionID(session.sessionId);
this.scheduleSessionRefresh(session.expiresAt);
this.scheduleSessionRefresh(session.expires);
}
private clearAuthState(status: AuthStatus): void {
@@ -214,19 +211,19 @@ export class AuthService {
const explicitDisplayName = this.readString(this.readFirst(response, ['displayName', 'DisplayName', 'name', 'Name']))
?? this.readString(this.readFirst(user, ['displayName', 'DisplayName', 'name', 'Name']))
const displayName = explicitDisplayName ?? username ?? (fullName || 'Telegram User');
const telegramUserId = this.readNumber(this.readFirst(user, ['telegramUserId', 'telegramUserID', 'TelegramUserID', 'id', 'ID']))
?? this.readNumber(this.readFirst(response, ['telegramUserId', 'telegramUserID', 'TelegramUserID', 'userID', 'UserID']))
?? 0;
const telegramUserId = this.readNumber(this.readFirst(user, ['userId','telegramUserId', 'telegramUserID', 'TelegramUserID', 'id', 'ID']))
?? this.readNumber(this.readFirst(response, ['userId', 'telegramUserId', 'telegramUserID', 'TelegramUserID', 'userID', 'UserID', 'UserId']))
?? null;
const expiresAt = this.readString(this.readFirst(response, ['expiresAt', 'ExpiresAt', 'expires', 'Expires']))
?? new Date(Date.now() + WEB_SESSION_COOKIE_MAX_AGE_SECONDS * 1000).toISOString();
return {
sessionId,
telegramUserId,
userId: telegramUserId,
username,
displayName,
active,
expiresAt,
expires: expiresAt,
};
}
@@ -357,4 +354,8 @@ export class AuthService {
document.cookie = `${WEB_SESSION_COOKIE}=; Max-Age=0; Path=/; SameSite=Lax`;
}
private getTelegramBotUsername(): string {
return (environment as Record<string, unknown>)['telegramBot'] as string || 'DexarSupport_bot';
}
}

View File

@@ -28,6 +28,17 @@ export class CartService {
return total + (getDiscountedPrice(item) * item.quantity);
}, 0);
});
totalDeliveryPrice = computed(() => {
const items = this.cartItems();
if (!Array.isArray(items)) return 0;
return items.reduce((total, item) => total + ((item.deliveryPrice ?? 0) * item.quantity), 0);
});
totalWithDelivery = computed(() => this.totalPrice() + this.totalDeliveryPrice());
hasDeliveryPrice = computed(() => {
const items = this.cartItems();
if (!Array.isArray(items)) return false;
return items.some(item => item.deliveryPrice !== undefined && item.deliveryPrice !== null);
});
constructor(private apiService: ApiService) {
this.loadCart();
@@ -41,6 +52,29 @@ export class CartService {
});
}
private normalizeOptionalNumber(value: unknown): number | undefined {
if (value === null || value === undefined || value === '') {
return undefined;
}
const normalized = typeof value === 'number'
? value
: Number(String(value).replace(',', '.'));
return Number.isFinite(normalized) ? normalized : undefined;
}
private normalizeCartItem(item: CartItem): CartItem {
const { deliveryPrice, ...rest } = item;
const normalizedDeliveryPrice = this.normalizeOptionalNumber(deliveryPrice);
return {
...rest,
quantity: item.quantity || 1,
...(normalizedDeliveryPrice !== undefined ? { deliveryPrice: normalizedDeliveryPrice } : {}),
};
}
private saveToStorage(items: CartItem[]): void {
const data = JSON.stringify(items);
@@ -90,10 +124,7 @@ export class CartService {
try {
const items = JSON.parse(json);
if (Array.isArray(items)) {
this.cartItems.set(items.map(item => ({
...item,
quantity: item.quantity || 1
})));
this.cartItems.set(items.map(item => this.normalizeCartItem(item)));
return true;
}
} catch (err) {
@@ -118,14 +149,14 @@ export class CartService {
this.addingItems.add(itemID);
this.apiService.getItem(itemID).subscribe({
next: (item) => {
const cartItem: CartItem = {
const cartItem = this.normalizeCartItem({
...item,
quantity,
...(variant?.colour != null && { colour: variant.colour }),
...(variant?.size != null && { size: variant.size }),
...(variant?.price != null && { price: variant.price }),
...(variant?.currency != null && { currency: variant.currency }),
};
});
this.cartItems.set([...this.cartItems(), cartItem]);
this.addingItems.delete(itemID);
},