Compare commits
110 Commits
reorganize
...
3208ef2b3b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3208ef2b3b | ||
|
|
6037216899 | ||
|
|
b9be1ff007 | ||
|
|
9386fbc2f8 | ||
|
|
a06b654103 | ||
|
|
9aaff4d80a | ||
|
|
7a06843bf5 | ||
|
|
1decc08f77 | ||
|
|
c0cfbcbcbb | ||
|
|
688c225911 | ||
|
|
3e79304e5c | ||
|
|
e7d8ec8c63 | ||
|
|
1e3cd99c69 | ||
|
|
3ab67cbe2d | ||
|
|
b3c056980d | ||
|
|
fb3bb6c77c | ||
|
|
bdc330c885 | ||
|
|
31da7f85cf | ||
|
|
69e63fc5f3 | ||
|
|
fe6fc2cb74 | ||
|
|
80cc90d347 | ||
|
|
9b5c2dd95c | ||
|
|
58e0869916 | ||
|
|
14bdd3bcd0 | ||
|
|
a10216a392 | ||
|
|
e53c8230e6 | ||
|
|
c6bc05560e | ||
|
|
63b0e18396 | ||
|
|
1bec150822 | ||
|
|
4d8dc6b59c | ||
|
|
b0a744034b | ||
|
|
49f69f6af0 | ||
|
|
5017b62059 | ||
|
|
ea80f90d0f | ||
|
|
dd74432dd7 | ||
|
|
4aef4881e1 | ||
|
|
7bc3eb10c1 | ||
|
|
55957df00c | ||
|
|
cb2666177a | ||
|
|
6e5fb3b86a | ||
|
|
a15f2bca6a | ||
|
|
1897cbe7a6 | ||
|
|
ab1732d74b | ||
|
|
7df15a4243 | ||
|
|
abb74390e8 | ||
|
|
06a7568386 | ||
|
|
77737f0ba9 | ||
|
|
6de461473e | ||
|
|
db781fd871 | ||
|
|
ce301e9c70 | ||
|
|
64288b5ce1 | ||
|
|
a8bb725f78 | ||
|
|
df2208ab53 | ||
|
|
72deb8d5e3 | ||
|
|
5566e011b7 | ||
|
|
ee23fd2d3c | ||
|
|
2a41062769 | ||
|
|
6624de7a32 | ||
|
|
44553f5bd4 | ||
|
|
5ed255dddb | ||
|
|
650bf137f2 | ||
|
|
3a8bc2f893 | ||
|
|
d29de100c6 | ||
|
|
97214c3a90 | ||
|
|
56f4c56b9e | ||
|
|
0b3b2ee463 | ||
|
|
c3e4e695eb | ||
|
|
c112aded47 | ||
|
|
75f029b872 | ||
|
|
f823df7e15 | ||
|
|
af78c053ba | ||
|
|
4ef4223367 | ||
|
|
7b18376d28 | ||
|
|
c64b9cfee8 | ||
|
|
712281d2e8 | ||
|
|
0626dcbe46 | ||
|
|
d288a5fb3c | ||
|
|
3445f55758 | ||
|
|
350581cbe9 | ||
|
|
377da22761 | ||
|
|
6689acbe57 | ||
|
|
421346d957 | ||
|
|
86d11364f0 | ||
|
|
dcb75b8f4e | ||
|
|
0cb32a22d9 | ||
|
|
caf14eeae1 | ||
|
|
e4206d8abc | ||
|
|
a4765ffe98 | ||
|
|
10b4974719 | ||
|
|
7a00a8f1e3 | ||
|
|
d6097e2b5d | ||
|
|
369af40f20 | ||
|
|
75b45abe4f | ||
|
|
2baa72a022 | ||
|
|
18df968b7a | ||
|
|
e3efb270dd | ||
|
|
2bd98b29eb | ||
|
|
0692cc6360 | ||
|
|
82cbf07120 | ||
|
|
61f441f6b2 | ||
|
|
e07356a700 | ||
|
|
5068a3a114 | ||
|
|
9154660a01 | ||
|
|
4238d59fc6 | ||
|
|
751ad48489 | ||
|
|
88ac37ebc4 | ||
|
|
333ea45c38 | ||
|
|
39290ef776 | ||
|
|
b22390f3eb | ||
|
|
3f285ca15f |
4
.gitignore
vendored
@@ -5,6 +5,8 @@
|
||||
/tmp
|
||||
/out-tsc
|
||||
/bazel-out
|
||||
/files
|
||||
changes.txt
|
||||
|
||||
# Node
|
||||
/node_modules
|
||||
@@ -36,7 +38,7 @@ yarn-error.log
|
||||
/libpeerconnection.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
/public/images/
|
||||
# System files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
116
angular.json
@@ -54,8 +54,8 @@
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "25kB",
|
||||
"maximumError": "35kB"
|
||||
"maximumWarning": "40kB",
|
||||
"maximumError": "50kB"
|
||||
}
|
||||
],
|
||||
"outputHashing": "all",
|
||||
@@ -129,8 +129,8 @@
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "25kB",
|
||||
"maximumError": "35kB"
|
||||
"maximumWarning": "40kB",
|
||||
"maximumError": "50kB"
|
||||
}
|
||||
],
|
||||
"outputHashing": "all",
|
||||
@@ -146,14 +146,88 @@
|
||||
},
|
||||
"sourceMap": false,
|
||||
"namedChunks": false,
|
||||
"extractLicenses": true
|
||||
"extractLicenses": true,
|
||||
"serviceWorker": "ngsw-config.json"
|
||||
},
|
||||
"lavero": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.lavero.ts"
|
||||
},
|
||||
{
|
||||
"replace": "src/app/brands/brand-routes.ts",
|
||||
"with": "src/app/brands/brand-routes.lavero.ts"
|
||||
}
|
||||
],
|
||||
"index": "src/index.lavero.html",
|
||||
"styles": [
|
||||
"src/styles.scss",
|
||||
"src/styles/themes/lavero.theme.scss"
|
||||
],
|
||||
"outputPath": "dist/laveromarket",
|
||||
"optimization": false,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true
|
||||
},
|
||||
"lavero-production": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.lavero.production.ts"
|
||||
},
|
||||
{
|
||||
"replace": "src/app/brands/brand-routes.ts",
|
||||
"with": "src/app/brands/brand-routes.lavero.ts"
|
||||
}
|
||||
],
|
||||
"index": "src/index.lavero.html",
|
||||
"styles": [
|
||||
"src/styles.scss",
|
||||
"src/styles/themes/lavero.theme.scss"
|
||||
],
|
||||
"outputPath": "dist/laveromarket",
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "500kB",
|
||||
"maximumError": "1MB"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "40kB",
|
||||
"maximumError": "50kB"
|
||||
}
|
||||
],
|
||||
"outputHashing": "all",
|
||||
"optimization": {
|
||||
"scripts": true,
|
||||
"styles": {
|
||||
"minify": true,
|
||||
"inlineCritical": true
|
||||
},
|
||||
"fonts": {
|
||||
"inline": true
|
||||
}
|
||||
},
|
||||
"sourceMap": false,
|
||||
"namedChunks": false,
|
||||
"extractLicenses": true,
|
||||
"serviceWorker": "ngsw-config.json"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"options": {
|
||||
"allowedHosts": ["novo.market", "dexarmarket.ru", "localhost"]
|
||||
"allowedHosts": [
|
||||
"novo.market",
|
||||
"dexarmarket.ru",
|
||||
"dexar.market",
|
||||
"localhost",
|
||||
"lovero.store"
|
||||
],
|
||||
"proxyConfig": "proxy.conf.json"
|
||||
},
|
||||
"builder": "@angular/build:dev-server",
|
||||
"configurations": {
|
||||
@@ -164,36 +238,24 @@
|
||||
"buildTarget": "Dexarmarket:build:development"
|
||||
},
|
||||
"novo": {
|
||||
"buildTarget": "Dexarmarket:build:novo"
|
||||
"buildTarget": "Dexarmarket:build:novo",
|
||||
"proxyConfig": "proxy.conf.novo.json"
|
||||
},
|
||||
"novo-production": {
|
||||
"buildTarget": "Dexarmarket:build:novo-production"
|
||||
},
|
||||
"lavero": {
|
||||
"buildTarget": "Dexarmarket:build:lavero",
|
||||
"proxyConfig": "proxy.conf.lavero.json"
|
||||
},
|
||||
"lavero-production": {
|
||||
"buildTarget": "Dexarmarket:build:lavero-production"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular/build:extract-i18n"
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular/build:karma",
|
||||
"options": {
|
||||
"polyfills": [
|
||||
"zone.js",
|
||||
"zone.js/testing"
|
||||
],
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "public"
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.scss"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,168 +1,726 @@
|
||||
# Backend API Changes Required
|
||||
# Complete Backend API Documentation
|
||||
|
||||
## Cart Quantity Support
|
||||
|
||||
### 1. Add Quantity to Cart Items
|
||||
|
||||
**Current GET /cart Response:**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"itemID": 123,
|
||||
"name": "Product Name",
|
||||
"price": 100,
|
||||
"currency": "RUB",
|
||||
...other item fields
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**NEW Required Response:**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"itemID": 123,
|
||||
"name": "Product Name",
|
||||
"price": 100,
|
||||
"currency": "RUB",
|
||||
"quantity": 2, // <-- ADD THIS FIELD
|
||||
...other item fields
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### 2. POST /cart - Add Item to Cart
|
||||
|
||||
**Current Request:**
|
||||
```json
|
||||
{
|
||||
"itemID": 123
|
||||
}
|
||||
```
|
||||
|
||||
**NEW Request (with optional quantity):**
|
||||
```json
|
||||
{
|
||||
"itemID": 123,
|
||||
"quantity": 1 // Optional, defaults to 1 if not provided
|
||||
}
|
||||
```
|
||||
|
||||
**Behavior:**
|
||||
- If item already exists in cart, **increment** the quantity by the provided amount
|
||||
- If item doesn't exist, add it with the specified quantity
|
||||
|
||||
### 3. PATCH /cart - Update Item Quantity (NEW ENDPOINT)
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"itemID": 123,
|
||||
"quantity": 5 // New quantity value (not increment, but absolute value)
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"message": "Cart updated successfully"
|
||||
}
|
||||
```
|
||||
|
||||
**Behavior:**
|
||||
- Set the quantity to the exact value provided
|
||||
- If quantity is 0 or negative, remove the item from cart
|
||||
|
||||
### 4. Payment Endpoints - Include Quantity
|
||||
|
||||
**POST /payment/create**
|
||||
|
||||
Update the items array to include quantity:
|
||||
|
||||
**Current:**
|
||||
```json
|
||||
{
|
||||
"amount": 1000,
|
||||
"currency": "RUB",
|
||||
"items": [
|
||||
{
|
||||
"itemID": 123,
|
||||
"price": 500,
|
||||
"name": "Product Name"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**NEW:**
|
||||
```json
|
||||
{
|
||||
"amount": 1000,
|
||||
"currency": "RUB",
|
||||
"items": [
|
||||
{
|
||||
"itemID": 123,
|
||||
"price": 500,
|
||||
"name": "Product Name",
|
||||
"quantity": 2 // <-- ADD THIS FIELD
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Email Purchase Confirmation
|
||||
|
||||
**POST /purchase-email**
|
||||
|
||||
Update items to include quantity:
|
||||
|
||||
**NEW:**
|
||||
```json
|
||||
{
|
||||
"email": "user@example.com",
|
||||
"telegramUserId": "123456",
|
||||
"items": [
|
||||
{
|
||||
"itemID": 123,
|
||||
"name": "Product Name",
|
||||
"price": 500,
|
||||
"currency": "RUB",
|
||||
"quantity": 2 // <-- ADD THIS FIELD
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Future: Filters & Sorting (To Be Discussed)
|
||||
|
||||
### GET /category/{categoryID}
|
||||
|
||||
Add query parameters for filtering and sorting:
|
||||
|
||||
**Proposed Query Parameters:**
|
||||
- `sort`: Sort order (e.g., `price_asc`, `price_desc`, `rating_desc`, `name_asc`)
|
||||
- `minPrice`: Minimum price filter
|
||||
- `maxPrice`: Maximum price filter
|
||||
- `minRating`: Minimum rating filter (1-5)
|
||||
- `count`: Number of items per page (already exists)
|
||||
- `skip`: Offset for pagination (already exists)
|
||||
|
||||
**Example:**
|
||||
```
|
||||
GET /category/5?sort=price_asc&minPrice=100&maxPrice=500&minRating=4&count=20&skip=0
|
||||
```
|
||||
|
||||
**Response:** Same as current (array of items)
|
||||
> **Last updated:** February 2026
|
||||
> **Frontend:** Angular 21 · Dual-brand (Dexar + Novo)
|
||||
> **Covers:** Catalog, Cart, Payments, Reviews, Regions, Auth, i18n, BackOffice
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
## Base URLs
|
||||
|
||||
**Required NOW:**
|
||||
1. Add `quantity` field to cart item responses
|
||||
2. Support `quantity` parameter in POST /cart
|
||||
3. Create new PATCH /cart endpoint for updating quantities
|
||||
4. Include `quantity` in payment and email endpoints
|
||||
| Brand | Dev | Production |
|
||||
|--------|----------------------------------|----------------------------------|
|
||||
| Dexar | `https://api.dexarmarket.ru:445` | `https://api.dexarmarket.ru:445` |
|
||||
| Novo | `https://api.novo.market:444` | `https://api.novo.market:444` |
|
||||
|
||||
**Future (After Discussion):**
|
||||
- Sorting and filtering query parameters for category items endpoint
|
||||
---
|
||||
|
||||
## Global HTTP Headers
|
||||
|
||||
The frontend **automatically attaches** two custom headers to **every API request** via an interceptor. The backend should read these headers and use them to filter/translate responses accordingly.
|
||||
|
||||
| Header | Example Value | Description |
|
||||
|---------------|---------------|------------------------------------------------------------|
|
||||
| `X-Region` | `moscow` | Region ID selected by the user. **Absent** = global (all). |
|
||||
| `X-Language` | `ru` | Active UI language: `ru`, `en`, or `hy`. |
|
||||
|
||||
### Backend behavior
|
||||
|
||||
- **`X-Region`**: If present, filter items/categories to only those available in that region. If absent, return everything (global catalog).
|
||||
- **`X-Language`**: If present, return translated `name`, `description`, etc. for categories/items when translations exist. If absent or `ru`, use russians defaults.
|
||||
|
||||
### CORS requirements for these headers
|
||||
|
||||
```
|
||||
Access-Control-Allow-Headers: Content-Type, X-Region, X-Language
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. Health Check
|
||||
|
||||
### `GET /ping`
|
||||
|
||||
Simple health check.
|
||||
|
||||
**Response `200`:**
|
||||
```json
|
||||
{ "message": "pong" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Catalog — Categories
|
||||
|
||||
### `GET /category`
|
||||
|
||||
Returns all top-level categories. Respects `X-Region` and `X-Language` headers.
|
||||
|
||||
**Response `200`:**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"categoryID": 1,
|
||||
"name": "Электроника",
|
||||
"parentID": 0,
|
||||
"icon": "https://...",
|
||||
"wideBanner": "https://...",
|
||||
"itemCount": 42,
|
||||
"priority": 10,
|
||||
|
||||
"id": "cat_abc123",
|
||||
"visible": true,
|
||||
"img": "https://...",
|
||||
"projectId": "proj_xyz",
|
||||
"subcategories": [
|
||||
{
|
||||
"id": "sub_001",
|
||||
"name": "Смартфоны",
|
||||
"visible": true,
|
||||
"priority": 5,
|
||||
"img": "https://...",
|
||||
"categoryId": "cat_abc123",
|
||||
"parentId": "cat_abc123",
|
||||
"itemCount": 20,
|
||||
"hasItems": true,
|
||||
"subcategories": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**Category object:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|------------------|---------------|----------|----------------------------------------------------|
|
||||
| `categoryID` | number | yes | Legacy numeric ID |
|
||||
| `name` | string | yes | Category display name (translated if `X-Language`) |
|
||||
| `parentID` | number | yes | Parent category ID (`0` = top-level) |
|
||||
| `icon` | string | no | Category icon URL |
|
||||
| `wideBanner` | string | no | Wide banner image URL |
|
||||
| `itemCount` | number | no | Number of items in category |
|
||||
| `priority` | number | no | Sort priority (higher = first) |
|
||||
| `id` | string | no | BackOffice string ID |
|
||||
| `visible` | boolean | no | Whether category is shown (`true` default) |
|
||||
| `img` | string | no | BackOffice image URL (maps to `icon`) |
|
||||
| `projectId` | string | no | BackOffice project reference |
|
||||
| `subcategories` | Subcategory[] | no | Nested subcategories |
|
||||
|
||||
**Subcategory object:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|------------------|---------------|----------|------------------------------------|
|
||||
| `id` | string | yes | Subcategory ID |
|
||||
| `name` | string | yes | Display name |
|
||||
| `visible` | boolean | no | Whether visible |
|
||||
| `priority` | number | no | Sort priority |
|
||||
| `img` | string | no | Image URL |
|
||||
| `categoryId` | string | yes | Parent category ID |
|
||||
| `parentId` | string | yes | Direct parent ID |
|
||||
| `itemCount` | number | no | Number of items |
|
||||
| `hasItems` | boolean | no | Whether has any items |
|
||||
| `subcategories` | Subcategory[] | no | Nested children |
|
||||
|
||||
---
|
||||
|
||||
### `GET /category/:categoryID`
|
||||
|
||||
Returns items in a specific category. Respects `X-Region` and `X-Language` headers.
|
||||
|
||||
**Query params:**
|
||||
|
||||
| Param | Type | Default | Description |
|
||||
|----------|--------|---------|--------------------|
|
||||
| `count` | number | `50` | Items per page |
|
||||
| `skip` | number | `0` | Offset for paging |
|
||||
|
||||
**Response `200`:** Array of [Item](#item-object) objects.
|
||||
|
||||
---
|
||||
|
||||
## 3. Items
|
||||
|
||||
### `GET /item/:itemID`
|
||||
|
||||
Returns a single item. Respects `X-Region` and `X-Language` headers.
|
||||
|
||||
**Response `200`:** A single [Item](#item-object) object.
|
||||
|
||||
---
|
||||
|
||||
### `GET /searchitems`
|
||||
|
||||
Full-text search across items. Respects `X-Region` and `X-Language` headers.
|
||||
|
||||
**Query params:**
|
||||
|
||||
| Param | Type | Default | Description |
|
||||
|----------|--------|---------|----------------------|
|
||||
| `search` | string | — | Search query (required) |
|
||||
| `count` | number | `50` | Items per page |
|
||||
| `skip` | number | `0` | Offset for paging |
|
||||
|
||||
**Response `200`:**
|
||||
```json
|
||||
{
|
||||
"items": [ /* Item objects */ ],
|
||||
"total": 128,
|
||||
"count": 50,
|
||||
"skip": 0
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `GET /randomitems`
|
||||
|
||||
Returns random items for carousel/recommendations. Respects `X-Region` and `X-Language` headers.
|
||||
|
||||
**Query params:**
|
||||
|
||||
| Param | Type | Default | Description |
|
||||
|------------|--------|---------|------------------------------------|
|
||||
| `count` | number | `5` | Number of items to return |
|
||||
| `category` | number | — | Optional: limit to this category |
|
||||
|
||||
**Response `200`:** Array of [Item](#item-object) objects.
|
||||
|
||||
---
|
||||
|
||||
### Item Object
|
||||
|
||||
The backend can return items in **either** legacy format or BackOffice format. The frontend normalizes both.
|
||||
|
||||
```json
|
||||
{
|
||||
"categoryID": 1,
|
||||
"itemID": 123,
|
||||
"name": "iPhone 15 Pro",
|
||||
"photos": [{ "url": "https://..." }],
|
||||
"description": "Описание товара",
|
||||
"currency": "RUB",
|
||||
"price": 89990,
|
||||
"discount": 10,
|
||||
"remainings": "high",
|
||||
"rating": 4.5,
|
||||
"callbacks": [
|
||||
{
|
||||
"rating": 5,
|
||||
"content": "Отличный товар!",
|
||||
"userID": "user_123",
|
||||
"timestamp": "2026-02-01T12:00:00Z"
|
||||
}
|
||||
],
|
||||
"questions": [
|
||||
{
|
||||
"question": "Есть ли гарантия?",
|
||||
"answer": "Да, 12 месяцев",
|
||||
"upvotes": 5,
|
||||
"downvotes": 0
|
||||
}
|
||||
],
|
||||
|
||||
"id": "item_abc123",
|
||||
"visible": true,
|
||||
"priority": 10,
|
||||
"imgs": ["https://img1.jpg", "https://img2.jpg"],
|
||||
"tags": ["new", "popular"],
|
||||
"badges": ["bestseller", "sale"],
|
||||
"simpleDescription": "Краткое описание",
|
||||
"descriptionFields": [
|
||||
{ "key": "Процессор", "value": "A17 Pro" },
|
||||
{ "key": "Память", "value": "256 GB" }
|
||||
],
|
||||
"subcategoryId": "sub_001",
|
||||
"translations": {
|
||||
"en": {
|
||||
"name": "iPhone 15 Pro",
|
||||
"simpleDescription": "Short description",
|
||||
"description": [
|
||||
{ "key": "Processor", "value": "A17 Pro" }
|
||||
]
|
||||
},
|
||||
"hy": {
|
||||
"name": "iPhone 15 Pro",
|
||||
"simpleDescription": "Կարcheck check check"
|
||||
}
|
||||
},
|
||||
"comments": [
|
||||
{
|
||||
"id": "cmt_001",
|
||||
"text": "Отличный товар!",
|
||||
"author": "user_123",
|
||||
"stars": 5,
|
||||
"createdAt": "2026-02-01T12:00:00Z"
|
||||
}
|
||||
],
|
||||
"quantity": 50
|
||||
}
|
||||
```
|
||||
|
||||
**Full Item fields:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|---------------------|-------------------|----------|------------------------------------------------------------|
|
||||
| `categoryID` | number | yes | Category this item belongs to |
|
||||
| `itemID` | number | yes | Legacy numeric item ID |
|
||||
| `name` | string | yes | Item display name |
|
||||
| `photos` | Photo[] | no | Legacy photo array `[{ url }]` |
|
||||
| `description` | string | yes | Text description |
|
||||
| `currency` | string | yes | Currency code (default: `RUB`) |
|
||||
| `price` | number | yes | Price in the currency's smallest display unit |
|
||||
| `discount` | number | yes | Discount percentage (`0`–`100`) |
|
||||
| `remainings` | string | no | Stock level: `high`, `medium`, `low`, `out` |
|
||||
| `rating` | number | yes | Average rating (`0`–`5`) |
|
||||
| `callbacks` | Review[] | no | Legacy reviews (alias for reviews) |
|
||||
| `questions` | Question[] | no | Q&A entries |
|
||||
| `id` | string | no | BackOffice string ID |
|
||||
| `visible` | boolean | no | Whether item is visible (`true` default) |
|
||||
| `priority` | number | no | Sort priority (higher = first) |
|
||||
| `imgs` | string[] | no | BackOffice image URLs (maps to `photos`) |
|
||||
| `tags` | string[] | no | Item tags for filtering |
|
||||
| `badges` | string[] | no | Display badges (`bestseller`, `sale`, etc.) |
|
||||
| `simpleDescription` | string | no | Short plain-text description |
|
||||
| `descriptionFields` | DescriptionField[]| no | Structured `[{ key, value }]` descriptions |
|
||||
| `subcategoryId` | string | no | BackOffice subcategory reference |
|
||||
| `translations` | Record | no | Translations keyed by lang code (see below) |
|
||||
| `comments` | Comment[] | no | BackOffice comments format |
|
||||
| `quantity` | number | no | Numeric stock count (maps to `remainings` on frontend) |
|
||||
|
||||
**Nested types:**
|
||||
|
||||
| Type | Fields |
|
||||
|--------------------|-----------------------------------------------------------------|
|
||||
| `Photo` | `url: string`, `photo?: string`, `video?: string`, `type?: string` |
|
||||
| `DescriptionField` | `key: string`, `value: string` |
|
||||
| `Comment` | `id?: string`, `text: string`, `author?: string`, `stars?: number`, `createdAt?: string` |
|
||||
| `Review` | `rating?: number`, `content?: string`, `userID?: string`, `answer?: string`, `timestamp?: string` |
|
||||
| `Question` | `question: string`, `answer: string`, `upvotes: number`, `downvotes: number` |
|
||||
| `ItemTranslation` | `name?: string`, `simpleDescription?: string`, `description?: DescriptionField[]` |
|
||||
|
||||
---
|
||||
|
||||
## 4. Cart
|
||||
|
||||
### `POST /cart` — Add item to cart
|
||||
|
||||
**Request body:**
|
||||
```json
|
||||
{ "itemID": 123, "quantity": 1 }
|
||||
```
|
||||
|
||||
**Response `200`:**
|
||||
```json
|
||||
{ "message": "Added to cart" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `PATCH /cart` — Update item quantity
|
||||
|
||||
**Request body:**
|
||||
```json
|
||||
{ "itemID": 123, "quantity": 3 }
|
||||
```
|
||||
|
||||
**Response `200`:**
|
||||
```json
|
||||
{ "message": "Updated" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `DELETE /cart` — Remove items from cart
|
||||
|
||||
**Request body:** Array of item IDs
|
||||
```json
|
||||
[123, 456]
|
||||
```
|
||||
|
||||
**Response `200`:**
|
||||
```json
|
||||
{ "message": "Removed" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `GET /cart` — Get cart contents
|
||||
|
||||
**Response `200`:** Array of [Item](#item-object) objects (each with `quantity` field).
|
||||
|
||||
---
|
||||
|
||||
## 5. Payments (SBP / QR)
|
||||
|
||||
### `POST /cart` — Create payment (SBP QR)
|
||||
|
||||
> Note: Same endpoint as add-to-cart but with different body schema.
|
||||
|
||||
**Request body:**
|
||||
```json
|
||||
{
|
||||
"amount": 89990,
|
||||
"currency": "RUB",
|
||||
"siteuserID": "tg_123456789",
|
||||
"siteorderID": "order_abc123",
|
||||
"redirectUrl": "",
|
||||
"telegramUsername": "john_doe",
|
||||
"items": [
|
||||
{ "itemID": 123, "price": 89990, "name": "iPhone 15 Pro" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Response `200`:**
|
||||
```json
|
||||
{
|
||||
"qrId": "qr_abc123",
|
||||
"qrStatus": "CREATED",
|
||||
"qrExpirationDate": "2026-02-28T13:00:00Z",
|
||||
"payload": "https://qr.nspk.ru/...",
|
||||
"qrUrl": "https://qr.nspk.ru/..."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `GET /qr/payment/:qrId` — Check payment status
|
||||
|
||||
**Response `200`:**
|
||||
```json
|
||||
{
|
||||
"additionalInfo": "",
|
||||
"paymentPurpose": "Order #order_abc123",
|
||||
"amount": 89990,
|
||||
"code": "SUCCESS",
|
||||
"createDate": "2026-02-28T12:00:00Z",
|
||||
"currency": "RUB",
|
||||
"order": "order_abc123",
|
||||
"paymentStatus": "COMPLETED",
|
||||
"qrId": "qr_abc123",
|
||||
"transactionDate": "2026-02-28T12:01:00Z",
|
||||
"transactionId": 999,
|
||||
"qrExpirationDate": "2026-02-28T13:00:00Z",
|
||||
"phoneNumber": "+7XXXXXXXXXX"
|
||||
}
|
||||
```
|
||||
|
||||
| `paymentStatus` values | Meaning |
|
||||
|------------------------|---------------------------|
|
||||
| `CREATED` | QR generated, not paid |
|
||||
| `WAITING` | Payment in progress |
|
||||
| `COMPLETED` | Payment successful |
|
||||
| `EXPIRED` | QR code expired |
|
||||
| `CANCELLED` | Payment cancelled |
|
||||
|
||||
---
|
||||
|
||||
### `POST /purchase-email` — Submit email after payment
|
||||
|
||||
**Request body:**
|
||||
```json
|
||||
{
|
||||
"email": "user@example.com",
|
||||
"telegramUserId": "123456789",
|
||||
"items": [
|
||||
{ "itemID": 123, "name": "iPhone 15 Pro", "price": 89990, "currency": "RUB" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Response `200`:**
|
||||
```json
|
||||
{ "message": "Email sent" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Reviews / Comments
|
||||
|
||||
### `POST /comment` — Submit a review
|
||||
|
||||
**Request body:**
|
||||
```json
|
||||
{
|
||||
"itemID": 123,
|
||||
"rating": 5,
|
||||
"comment": "Great product!",
|
||||
"username": "john_doe",
|
||||
"userId": 123456789,
|
||||
"timestamp": "2026-02-28T12:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Response `200`:**
|
||||
```json
|
||||
{ "message": "Review submitted" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Regions
|
||||
|
||||
### `GET /regions` — List available regions
|
||||
|
||||
Returns regions where the marketplace operates.
|
||||
|
||||
**Response `200`:**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "moscow",
|
||||
"city": "Москва",
|
||||
"country": "Россия",
|
||||
"countryCode": "RU",
|
||||
"timezone": "Europe/Moscow"
|
||||
},
|
||||
{
|
||||
"id": "spb",
|
||||
"city": "Санкт-Петербург",
|
||||
"country": "Россия",
|
||||
"countryCode": "RU",
|
||||
"timezone": "Europe/Moscow"
|
||||
},
|
||||
{
|
||||
"id": "yerevan",
|
||||
"city": "Ереван",
|
||||
"country": "Армения",
|
||||
"countryCode": "AM",
|
||||
"timezone": "Asia/Yerevan"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**Region object:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|---------------|--------|----------|--------------------------|
|
||||
| `id` | string | yes | Unique region identifier |
|
||||
| `city` | string | yes | City name (display) |
|
||||
| `country` | string | yes | Country name |
|
||||
| `countryCode` | string | yes | ISO 3166-1 alpha-2 |
|
||||
| `timezone` | string | no | IANA timezone |
|
||||
|
||||
> **Fallback:** If this endpoint is down, the frontend uses 6 hardcoded defaults: Moscow, SPB, Yerevan, Minsk, Almaty, Tbilisi.
|
||||
|
||||
---
|
||||
|
||||
## 8. Authentication (Telegram Login)
|
||||
|
||||
Authentication is **Telegram-based** with **cookie sessions** (HttpOnly, Secure, SameSite=None).
|
||||
|
||||
All auth endpoints must include `withCredentials: true` CORS support.
|
||||
|
||||
### Auth flow
|
||||
|
||||
```
|
||||
1. User clicks "Checkout" → not authenticated → login dialog shown
|
||||
2. User clicks "Log in with Telegram" → opens https://t.me/{bot}?start=auth_{callback}
|
||||
3. User starts the bot in Telegram
|
||||
4. Bot sends user data → backend /auth/telegram/callback
|
||||
5. Backend creates session → sets Set-Cookie
|
||||
6. Frontend polls GET /auth/session every 3s
|
||||
7. Session detected → dialog closes → checkout proceeds
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `GET /auth/session` — Check current session
|
||||
|
||||
**Request:** Cookies only (session cookie set by backend).
|
||||
|
||||
**Response `200`** (authenticated):
|
||||
```json
|
||||
{
|
||||
"sessionId": "sess_abc123",
|
||||
"telegramUserId": 123456789,
|
||||
"username": "john_doe",
|
||||
"displayName": "John Doe",
|
||||
"active": true,
|
||||
"expiresAt": "2026-03-01T12:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Response `200`** (expired):
|
||||
```json
|
||||
{
|
||||
"sessionId": "sess_abc123",
|
||||
"telegramUserId": 123456789,
|
||||
"username": "john_doe",
|
||||
"displayName": "John Doe",
|
||||
"active": false,
|
||||
"expiresAt": "2026-02-27T12:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Response `401`** (no session):
|
||||
```json
|
||||
{ "error": "No active session" }
|
||||
```
|
||||
|
||||
**AuthSession object:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|------------------|---------|----------|--------------------------------------------|
|
||||
| `sessionId` | string | yes | Unique session ID |
|
||||
| `telegramUserId` | number | yes | Telegram user ID |
|
||||
| `username` | string? | no | Telegram @username (can be null) |
|
||||
| `displayName` | string | yes | User display name (first + last) |
|
||||
| `active` | boolean | yes | Whether session is valid |
|
||||
| `expiresAt` | string | yes | ISO 8601 expiration datetime |
|
||||
|
||||
---
|
||||
|
||||
### `GET /auth/telegram/callback` — Telegram bot auth callback
|
||||
|
||||
Called by the Telegram bot after user authenticates.
|
||||
|
||||
**Request body (from bot):**
|
||||
```json
|
||||
{
|
||||
"id": 123456789,
|
||||
"first_name": "John",
|
||||
"last_name": "Doe",
|
||||
"username": "john_doe",
|
||||
"photo_url": "https://t.me/i/userpic/...",
|
||||
"auth_date": 1709100000,
|
||||
"hash": "abc123def456..."
|
||||
}
|
||||
```
|
||||
|
||||
**Response:** Must set a session cookie and return:
|
||||
```json
|
||||
{
|
||||
"sessionId": "sess_abc123",
|
||||
"message": "Authenticated successfully"
|
||||
}
|
||||
```
|
||||
|
||||
**Cookie requirements:**
|
||||
|
||||
| Attribute | Value | Notes |
|
||||
|------------|----------------|--------------------------------------------|
|
||||
| `HttpOnly` | `true` | Not accessible via JS |
|
||||
| `Secure` | `true` | HTTPS only |
|
||||
| `SameSite` | `None` | Required for cross-origin (API ≠ frontend) |
|
||||
| `Path` | `/` | |
|
||||
| `Max-Age` | `86400` (24h) | Or as needed |
|
||||
|
||||
---
|
||||
|
||||
### `POST /auth/logout` — End session
|
||||
|
||||
**Request:** Cookies only, empty body `{}`
|
||||
|
||||
**Response `200`:**
|
||||
```json
|
||||
{ "message": "Logged out" }
|
||||
```
|
||||
|
||||
Must clear/invalidate the session cookie.
|
||||
|
||||
---
|
||||
|
||||
### Session refresh
|
||||
|
||||
The frontend re-checks the session **60 seconds before `expiresAt`**. If the backend supports sliding expiration, it can reset the cookie's `Max-Age` on each `GET /auth/session`.
|
||||
|
||||
---
|
||||
|
||||
## 9. i18n / Translations
|
||||
|
||||
The frontend supports 3 languages: **Russian (ru)**, **English (en)**, **Armenian (hy)**.
|
||||
|
||||
The active language is sent via the `X-Language` HTTP header on every request.
|
||||
|
||||
### What the backend should do with `X-Language`
|
||||
|
||||
1. **Categories & items**: If `translations` field exists for the requested language, return the translated `name`, `description`, etc. OR the backend can apply translations server-side and return already-translated fields.
|
||||
|
||||
2. **The `translations` field** on items (optional approach):
|
||||
```json
|
||||
{
|
||||
"translations": {
|
||||
"en": {
|
||||
"name": "iPhone 15 Pro",
|
||||
"simpleDescription": "Short desc in English",
|
||||
"description": [{ "key": "Processor", "value": "A17 Pro" }]
|
||||
},
|
||||
"hy": {
|
||||
"name": "iPhone 15 Pro",
|
||||
"simpleDescription": "Կarcheck check"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. **Recommended approach**: Read `X-Language` header and return the `name`/`description` in that language directly. If no translation exists, return the Russian default.
|
||||
|
||||
---
|
||||
|
||||
## 10. CORS Configuration
|
||||
|
||||
For auth cookies and custom headers to work, the backend CORS config must include:
|
||||
|
||||
```
|
||||
Access-Control-Allow-Origin: https://dexarmarket.ru (NOT wildcard *)
|
||||
Access-Control-Allow-Credentials: true
|
||||
Access-Control-Allow-Headers: Content-Type, X-Region, X-Language
|
||||
Access-Control-Allow-Methods: GET, POST, PATCH, DELETE, OPTIONS
|
||||
```
|
||||
|
||||
> **Important:** `Access-Control-Allow-Origin` cannot be `*` when `Allow-Credentials: true`. Must be the exact frontend origin.
|
||||
|
||||
**Allowed origins:**
|
||||
- `https://dexarmarket.ru`
|
||||
- `https://novo.market`
|
||||
- `http://localhost:4200` (dev)
|
||||
- `http://localhost:4201` (dev, Novo)
|
||||
|
||||
---
|
||||
|
||||
## 11. Telegram Bot Setup
|
||||
|
||||
Each brand needs its own bot:
|
||||
- **Dexar:** `@dexarmarket_bot`
|
||||
- **Novo:** `@novomarket_bot`
|
||||
|
||||
The bot should:
|
||||
1. Listen for `/start auth_{callbackUrl}` command
|
||||
2. Extract the callback URL
|
||||
3. Send the user's Telegram data (`id`, `first_name`, `username`, etc.) to that callback URL
|
||||
4. The callback URL is `{apiUrl}/auth/telegram/callback`
|
||||
|
||||
---
|
||||
|
||||
## Complete Endpoint Reference
|
||||
|
||||
### New endpoints
|
||||
|
||||
| Method | Path | Description | Auth |
|
||||
|--------|---------------------------|----------------------------|----------|
|
||||
| `GET` | `/regions` | List available regions | No |
|
||||
| `GET` | `/auth/session` | Check current session | Cookie |
|
||||
| `GET` | `/auth/telegram/callback` | Telegram bot auth callback | No (bot) |
|
||||
| `POST` | `/auth/logout` | End session | Cookie |
|
||||
|
||||
### Existing endpoints
|
||||
|
||||
| Method | Path | Description | Auth | Headers |
|
||||
|----------|-----------------------|-------------------------|------|--------------------|
|
||||
| `GET` | `/ping` | Health check | No | — |
|
||||
| `GET` | `/category` | List categories | No | X-Region, X-Language |
|
||||
| `GET` | `/category/:id` | Items in category | No | X-Region, X-Language |
|
||||
| `GET` | `/item/:id` | Single item | No | X-Region, X-Language |
|
||||
| `GET` | `/searchitems` | Search items | No | X-Region, X-Language |
|
||||
| `GET` | `/randomitems` | Random items | No | X-Region, X-Language |
|
||||
| `POST` | `/cart` | Add to cart / Payment | No* | — |
|
||||
| `PATCH` | `/cart` | Update cart quantity | No* | — |
|
||||
| `DELETE` | `/cart` | Remove from cart | No* | — |
|
||||
| `GET` | `/cart` | Get cart contents | No* | — |
|
||||
| `POST` | `/comment` | Submit review | No | — |
|
||||
| `GET` | `/qr/payment/:qrId` | Check payment status | No | — |
|
||||
| `POST` | `/purchase-email` | Submit email after pay | No | — |
|
||||
|
||||
> \* Cart/payment endpoints may use the session cookie if available for order association, but don't strictly require auth. The frontend enforces auth before checkout.
|
||||
|
||||
726
docs/API_DOCS_RU.md
Normal file
@@ -0,0 +1,726 @@
|
||||
# Полная документация Backend API
|
||||
|
||||
> **Последнее обновление:** Февраль 2026
|
||||
> **Фронтенд:** Angular 21 · Два бренда (Dexar + Novo)
|
||||
> **Охватывает:** Каталог, Корзина, Оплата, Отзывы, Регионы, Авторизация, i18n, BackOffice
|
||||
|
||||
---
|
||||
|
||||
## Базовые URL
|
||||
|
||||
| Бренд | Dev | Production |
|
||||
|--------|----------------------------------|----------------------------------|
|
||||
| Dexar | `https://api.dexarmarket.ru:445` | `https://api.dexarmarket.ru:445` |
|
||||
| Novo | `https://api.novo.market:444` | `https://api.novo.market:444` |
|
||||
|
||||
---
|
||||
|
||||
## Глобальные HTTP-заголовки
|
||||
|
||||
Фронтенд **автоматически добавляет** два кастомных заголовка к **каждому API-запросу** через interceptor. Бэкенд должен читать эти заголовки и использовать для фильтрации/перевода ответов.
|
||||
|
||||
| Заголовок | Пример значения | Описание |
|
||||
|---------------|-----------------|-------------------------------------------------------------------|
|
||||
| `X-Region` | `moscow` | ID региона, выбранного пользователем. **Отсутствует** = все регионы. |
|
||||
| `X-Language` | `ru` | Активный язык интерфейса: `ru`, `en` или `hy`. |
|
||||
|
||||
### Поведение бэкенда
|
||||
|
||||
- **`X-Region`**: Если присутствует — фильтровать товары/категории только по этому региону. Если отсутствует — возвращать всё (глобальный каталог).
|
||||
- **`X-Language`**: Если присутствует — возвращать переведённые `name`, `description` и т.д., если переводы существуют. Если отсутствует или `ru` — возвращать на русском (по умолчанию).
|
||||
|
||||
### Требования CORS для этих заголовков
|
||||
|
||||
```
|
||||
Access-Control-Allow-Headers: Content-Type, X-Region, X-Language
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. Проверка состояния
|
||||
|
||||
### `GET /ping`
|
||||
|
||||
Простая проверка работоспособности.
|
||||
|
||||
**Ответ `200`:**
|
||||
```json
|
||||
{ "message": "pong" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Каталог — Категории
|
||||
|
||||
### `GET /category`
|
||||
|
||||
Возвращает все категории верхнего уровня. Учитывает заголовки `X-Region` и `X-Language`.
|
||||
|
||||
**Ответ `200`:**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"categoryID": 1,
|
||||
"name": "Электроника",
|
||||
"parentID": 0,
|
||||
"icon": "https://...",
|
||||
"wideBanner": "https://...",
|
||||
"itemCount": 42,
|
||||
"priority": 10,
|
||||
|
||||
"id": "cat_abc123",
|
||||
"visible": true,
|
||||
"img": "https://...",
|
||||
"projectId": "proj_xyz",
|
||||
"subcategories": [
|
||||
{
|
||||
"id": "sub_001",
|
||||
"name": "Смартфоны",
|
||||
"visible": true,
|
||||
"priority": 5,
|
||||
"img": "https://...",
|
||||
"categoryId": "cat_abc123",
|
||||
"parentId": "cat_abc123",
|
||||
"itemCount": 20,
|
||||
"hasItems": true,
|
||||
"subcategories": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**Объект Category:**
|
||||
|
||||
| Поле | Тип | Обязат. | Описание |
|
||||
|------------------|---------------|---------|-----------------------------------------------------|
|
||||
| `categoryID` | number | да | Числовой ID (legacy) |
|
||||
| `name` | string | да | Название категории (переведённое если `X-Language`) |
|
||||
| `parentID` | number | да | ID родительской категории (`0` = верхний уровень) |
|
||||
| `icon` | string | нет | URL иконки категории |
|
||||
| `wideBanner` | string | нет | URL широкого баннера |
|
||||
| `itemCount` | number | нет | Количество товаров в категории |
|
||||
| `priority` | number | нет | Приоритет сортировки (больше = выше) |
|
||||
| `id` | string | нет | Строковый ID из BackOffice |
|
||||
| `visible` | boolean | нет | Видима ли категория (по умолч. `true`) |
|
||||
| `img` | string | нет | URL изображения из BackOffice (маппится на `icon`) |
|
||||
| `projectId` | string | нет | Ссылка на проект в BackOffice |
|
||||
| `subcategories` | Subcategory[] | нет | Вложенные подкатегории |
|
||||
|
||||
**Объект Subcategory:**
|
||||
|
||||
| Поле | Тип | Обязат. | Описание |
|
||||
|------------------|---------------|---------|----------------------------------|
|
||||
| `id` | string | да | ID подкатегории |
|
||||
| `name` | string | да | Отображаемое название |
|
||||
| `visible` | boolean | нет | Видима ли |
|
||||
| `priority` | number | нет | Приоритет сортировки |
|
||||
| `img` | string | нет | URL изображения |
|
||||
| `categoryId` | string | да | ID родительской категории |
|
||||
| `parentId` | string | да | ID прямого родителя |
|
||||
| `itemCount` | number | нет | Количество товаров |
|
||||
| `hasItems` | boolean | нет | Есть ли товары |
|
||||
| `subcategories` | Subcategory[] | нет | Вложенные дочерние подкатегории |
|
||||
|
||||
---
|
||||
|
||||
### `GET /category/:categoryID`
|
||||
|
||||
Возвращает товары определённой категории. Учитывает заголовки `X-Region` и `X-Language`.
|
||||
|
||||
**Query-параметры:**
|
||||
|
||||
| Параметр | Тип | По умолч. | Описание |
|
||||
|----------|--------|-----------|-----------------------|
|
||||
| `count` | number | `50` | Товаров на страницу |
|
||||
| `skip` | number | `0` | Смещение для пагинации |
|
||||
|
||||
**Ответ `200`:** Массив объектов [Item](#объект-item).
|
||||
|
||||
---
|
||||
|
||||
## 3. Товары
|
||||
|
||||
### `GET /item/:itemID`
|
||||
|
||||
Возвращает один товар. Учитывает заголовки `X-Region` и `X-Language`.
|
||||
|
||||
**Ответ `200`:** Один объект [Item](#объект-item).
|
||||
|
||||
---
|
||||
|
||||
### `GET /searchitems`
|
||||
|
||||
Полнотекстовый поиск по товарам. Учитывает заголовки `X-Region` и `X-Language`.
|
||||
|
||||
**Query-параметры:**
|
||||
|
||||
| Параметр | Тип | По умолч. | Описание |
|
||||
|----------|--------|-----------|-------------------------------|
|
||||
| `search` | string | — | Поисковый запрос (обязателен) |
|
||||
| `count` | number | `50` | Товаров на страницу |
|
||||
| `skip` | number | `0` | Смещение для пагинации |
|
||||
|
||||
**Ответ `200`:**
|
||||
```json
|
||||
{
|
||||
"items": [ /* объекты Item */ ],
|
||||
"total": 128,
|
||||
"count": 50,
|
||||
"skip": 0
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `GET /randomitems`
|
||||
|
||||
Возвращает случайные товары для карусели/рекомендаций. Учитывает заголовки `X-Region` и `X-Language`.
|
||||
|
||||
**Query-параметры:**
|
||||
|
||||
| Параметр | Тип | По умолч. | Описание |
|
||||
|------------|--------|-----------|--------------------------------------|
|
||||
| `count` | number | `5` | Количество товаров |
|
||||
| `category` | number | — | Ограничить данной категорией (опц.) |
|
||||
|
||||
**Ответ `200`:** Массив объектов [Item](#объект-item).
|
||||
|
||||
---
|
||||
|
||||
### Объект Item
|
||||
|
||||
Бэкенд может возвращать товары в **любом** из двух форматов — legacy или BackOffice. Фронтенд нормализует оба варианта.
|
||||
|
||||
```json
|
||||
{
|
||||
"categoryID": 1,
|
||||
"itemID": 123,
|
||||
"name": "iPhone 15 Pro",
|
||||
"photos": [{ "url": "https://..." }],
|
||||
"description": "Описание товара",
|
||||
"currency": "RUB",
|
||||
"price": 89990,
|
||||
"discount": 10,
|
||||
"remainings": "high",
|
||||
"rating": 4.5,
|
||||
"callbacks": [
|
||||
{
|
||||
"rating": 5,
|
||||
"content": "Отличный товар!",
|
||||
"userID": "user_123",
|
||||
"timestamp": "2026-02-01T12:00:00Z"
|
||||
}
|
||||
],
|
||||
"questions": [
|
||||
{
|
||||
"question": "Есть ли гарантия?",
|
||||
"answer": "Да, 12 месяцев",
|
||||
"upvotes": 5,
|
||||
"downvotes": 0
|
||||
}
|
||||
],
|
||||
|
||||
"id": "item_abc123",
|
||||
"visible": true,
|
||||
"priority": 10,
|
||||
"imgs": ["https://img1.jpg", "https://img2.jpg"],
|
||||
"tags": ["new", "popular"],
|
||||
"badges": ["bestseller", "sale"],
|
||||
"simpleDescription": "Краткое описание",
|
||||
"descriptionFields": [
|
||||
{ "key": "Процессор", "value": "A17 Pro" },
|
||||
{ "key": "Память", "value": "256 GB" }
|
||||
],
|
||||
"subcategoryId": "sub_001",
|
||||
"translations": {
|
||||
"en": {
|
||||
"name": "iPhone 15 Pro",
|
||||
"simpleDescription": "Short description",
|
||||
"description": [
|
||||
{ "key": "Processor", "value": "A17 Pro" }
|
||||
]
|
||||
},
|
||||
"hy": {
|
||||
"name": "iPhone 15 Pro",
|
||||
"simpleDescription": "Կարcheck check"
|
||||
}
|
||||
},
|
||||
"comments": [
|
||||
{
|
||||
"id": "cmt_001",
|
||||
"text": "Отличный товар!",
|
||||
"author": "user_123",
|
||||
"stars": 5,
|
||||
"createdAt": "2026-02-01T12:00:00Z"
|
||||
}
|
||||
],
|
||||
"quantity": 50
|
||||
}
|
||||
```
|
||||
|
||||
**Все поля Item:**
|
||||
|
||||
| Поле | Тип | Обязат. | Описание |
|
||||
|---------------------|-------------------|---------|-----------------------------------------------------------|
|
||||
| `categoryID` | number | да | Категория, к которой принадлежит товар |
|
||||
| `itemID` | number | да | Числовой ID товара (legacy) |
|
||||
| `name` | string | да | Название товара |
|
||||
| `photos` | Photo[] | нет | Массив фотографий `[{ url }]` (legacy) |
|
||||
| `description` | string | да | Текстовое описание |
|
||||
| `currency` | string | да | Код валюты (по умолч. `RUB`) |
|
||||
| `price` | number | да | Цена |
|
||||
| `discount` | number | да | Процент скидки (`0`–`100`) |
|
||||
| `remainings` | string | нет | Уровень остатка: `high`, `medium`, `low`, `out` |
|
||||
| `rating` | number | да | Средний рейтинг (`0`–`5`) |
|
||||
| `callbacks` | Review[] | нет | Отзывы (legacy формат) |
|
||||
| `questions` | Question[] | нет | Вопросы и ответы |
|
||||
| `id` | string | нет | Строковый ID из BackOffice |
|
||||
| `visible` | boolean | нет | Виден ли товар (по умолч. `true`) |
|
||||
| `priority` | number | нет | Приоритет сортировки (больше = выше) |
|
||||
| `imgs` | string[] | нет | URL картинок из BackOffice (маппится на `photos`) |
|
||||
| `tags` | string[] | нет | Теги для фильтрации |
|
||||
| `badges` | string[] | нет | Бейджи (`bestseller`, `sale` и т.д.) |
|
||||
| `simpleDescription` | string | нет | Краткое текстовое описание |
|
||||
| `descriptionFields` | DescriptionField[]| нет | Структурированное описание `[{ key, value }]` |
|
||||
| `subcategoryId` | string | нет | Ссылка на подкатегорию из BackOffice |
|
||||
| `translations` | Record | нет | Переводы по ключу языка (см. ниже) |
|
||||
| `comments` | Comment[] | нет | Комментарии в формате BackOffice |
|
||||
| `quantity` | number | нет | Числовое кол-во на складе (маппится на `remainings`) |
|
||||
|
||||
**Вложенные типы:**
|
||||
|
||||
| Тип | Поля |
|
||||
|--------------------|-----------------------------------------------------------------|
|
||||
| `Photo` | `url: string`, `photo?: string`, `video?: string`, `type?: string` |
|
||||
| `DescriptionField` | `key: string`, `value: string` |
|
||||
| `Comment` | `id?: string`, `text: string`, `author?: string`, `stars?: number`, `createdAt?: string` |
|
||||
| `Review` | `rating?: number`, `content?: string`, `userID?: string`, `answer?: string`, `timestamp?: string` |
|
||||
| `Question` | `question: string`, `answer: string`, `upvotes: number`, `downvotes: number` |
|
||||
| `ItemTranslation` | `name?: string`, `simpleDescription?: string`, `description?: DescriptionField[]` |
|
||||
|
||||
---
|
||||
|
||||
## 4. Корзина
|
||||
|
||||
### `POST /cart` — Добавить товар в корзину
|
||||
|
||||
**Тело запроса:**
|
||||
```json
|
||||
{ "itemID": 123, "quantity": 1 }
|
||||
```
|
||||
|
||||
**Ответ `200`:**
|
||||
```json
|
||||
{ "message": "Added to cart" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `PATCH /cart` — Обновить количество товара
|
||||
|
||||
**Тело запроса:**
|
||||
```json
|
||||
{ "itemID": 123, "quantity": 3 }
|
||||
```
|
||||
|
||||
**Ответ `200`:**
|
||||
```json
|
||||
{ "message": "Updated" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `DELETE /cart` — Удалить товары из корзины
|
||||
|
||||
**Тело запроса:** Массив ID товаров
|
||||
```json
|
||||
[123, 456]
|
||||
```
|
||||
|
||||
**Ответ `200`:**
|
||||
```json
|
||||
{ "message": "Removed" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `GET /cart` — Получить содержимое корзины
|
||||
|
||||
**Ответ `200`:** Массив объектов [Item](#объект-item) (каждый с полем `quantity`).
|
||||
|
||||
---
|
||||
|
||||
## 5. Оплата (СБП / QR)
|
||||
|
||||
### `POST /cart` — Создать платёж (СБП QR)
|
||||
|
||||
> Примечание: Тот же эндпоинт что и добавление в корзину, но с другой схемой тела запроса.
|
||||
|
||||
**Тело запроса:**
|
||||
```json
|
||||
{
|
||||
"amount": 89990,
|
||||
"currency": "RUB",
|
||||
"siteuserID": "tg_123456789",
|
||||
"siteorderID": "order_abc123",
|
||||
"redirectUrl": "",
|
||||
"telegramUsername": "john_doe",
|
||||
"items": [
|
||||
{ "itemID": 123, "price": 89990, "name": "iPhone 15 Pro" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Ответ `200`:**
|
||||
```json
|
||||
{
|
||||
"qrId": "qr_abc123",
|
||||
"qrStatus": "CREATED",
|
||||
"qrExpirationDate": "2026-02-28T13:00:00Z",
|
||||
"payload": "https://qr.nspk.ru/...",
|
||||
"qrUrl": "https://qr.nspk.ru/..."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `GET /qr/payment/:qrId` — Проверить статус оплаты
|
||||
|
||||
**Ответ `200`:**
|
||||
```json
|
||||
{
|
||||
"additionalInfo": "",
|
||||
"paymentPurpose": "Order #order_abc123",
|
||||
"amount": 89990,
|
||||
"code": "SUCCESS",
|
||||
"createDate": "2026-02-28T12:00:00Z",
|
||||
"currency": "RUB",
|
||||
"order": "order_abc123",
|
||||
"paymentStatus": "COMPLETED",
|
||||
"qrId": "qr_abc123",
|
||||
"transactionDate": "2026-02-28T12:01:00Z",
|
||||
"transactionId": 999,
|
||||
"qrExpirationDate": "2026-02-28T13:00:00Z",
|
||||
"phoneNumber": "+7XXXXXXXXXX"
|
||||
}
|
||||
```
|
||||
|
||||
| Значение `paymentStatus` | Значение |
|
||||
|--------------------------|------------------------------|
|
||||
| `CREATED` | QR создан, не оплачен |
|
||||
| `WAITING` | Оплата в процессе |
|
||||
| `COMPLETED` | Оплата успешна |
|
||||
| `EXPIRED` | QR-код истёк |
|
||||
| `CANCELLED` | Оплата отменена |
|
||||
|
||||
---
|
||||
|
||||
### `POST /purchase-email` — Отправить email после оплаты
|
||||
|
||||
**Тело запроса:**
|
||||
```json
|
||||
{
|
||||
"email": "user@example.com",
|
||||
"telegramUserId": "123456789",
|
||||
"items": [
|
||||
{ "itemID": 123, "name": "iPhone 15 Pro", "price": 89990, "currency": "RUB" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Ответ `200`:**
|
||||
```json
|
||||
{ "message": "Email sent" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Отзывы / Комментарии
|
||||
|
||||
### `POST /comment` — Оставить отзыв
|
||||
|
||||
**Тело запроса:**
|
||||
```json
|
||||
{
|
||||
"itemID": 123,
|
||||
"rating": 5,
|
||||
"comment": "Отличный товар!",
|
||||
"username": "john_doe",
|
||||
"userId": 123456789,
|
||||
"timestamp": "2026-02-28T12:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Ответ `200`:**
|
||||
```json
|
||||
{ "message": "Review submitted" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Регионы
|
||||
|
||||
### `GET /regions` — Список доступных регионов
|
||||
|
||||
Возвращает регионы, в которых работает маркетплейс.
|
||||
|
||||
**Ответ `200`:**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "moscow",
|
||||
"city": "Москва",
|
||||
"country": "Россия",
|
||||
"countryCode": "RU",
|
||||
"timezone": "Europe/Moscow"
|
||||
},
|
||||
{
|
||||
"id": "spb",
|
||||
"city": "Санкт-Петербург",
|
||||
"country": "Россия",
|
||||
"countryCode": "RU",
|
||||
"timezone": "Europe/Moscow"
|
||||
},
|
||||
{
|
||||
"id": "yerevan",
|
||||
"city": "Ереван",
|
||||
"country": "Армения",
|
||||
"countryCode": "AM",
|
||||
"timezone": "Asia/Yerevan"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**Объект Region:**
|
||||
|
||||
| Поле | Тип | Обязат. | Описание |
|
||||
|---------------|--------|---------|----------------------------------|
|
||||
| `id` | string | да | Уникальный идентификатор региона |
|
||||
| `city` | string | да | Название города |
|
||||
| `country` | string | да | Название страны |
|
||||
| `countryCode` | string | да | Код страны ISO 3166-1 alpha-2 |
|
||||
| `timezone` | string | нет | Часовой пояс IANA |
|
||||
|
||||
> **Фоллбэк:** Если эндпоинт недоступен, фронтенд использует 6 захардкоженных значений: Москва, СПб, Ереван, Минск, Алматы, Тбилиси.
|
||||
|
||||
---
|
||||
|
||||
## 8. Авторизация (вход через Telegram)
|
||||
|
||||
Авторизация **через Telegram** с **cookie-сессиями** (HttpOnly, Secure, SameSite=None).
|
||||
|
||||
Все auth-эндпоинты должны поддерживать CORS с `credentials: true`.
|
||||
|
||||
### Процесс авторизации
|
||||
|
||||
```
|
||||
1. Пользователь нажимает «Оформить заказ» → не авторизован → показывается диалог входа
|
||||
2. Нажимает «Войти через Telegram» → открывается https://t.me/{bot}?start=auth_{callback}
|
||||
3. Пользователь запускает бота в Telegram
|
||||
4. Бот отправляет данные пользователя → бэкенд /auth/telegram/callback
|
||||
5. Бэкенд создаёт сессию → устанавливает Set-Cookie
|
||||
6. Фронтенд опрашивает GET /auth/session каждые 3 секунды
|
||||
7. Сессия обнаружена → диалог закрывается → оформление заказа продолжается
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `GET /auth/session` — Проверить текущую сессию
|
||||
|
||||
**Запрос:** Только cookie (сессионная cookie, установленная бэкендом).
|
||||
|
||||
**Ответ `200`** (авторизован):
|
||||
```json
|
||||
{
|
||||
"sessionId": "sess_abc123",
|
||||
"telegramUserId": 123456789,
|
||||
"username": "john_doe",
|
||||
"displayName": "John Doe",
|
||||
"active": true,
|
||||
"expiresAt": "2026-03-01T12:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Ответ `200`** (сессия истекла):
|
||||
```json
|
||||
{
|
||||
"sessionId": "sess_abc123",
|
||||
"telegramUserId": 123456789,
|
||||
"username": "john_doe",
|
||||
"displayName": "John Doe",
|
||||
"active": false,
|
||||
"expiresAt": "2026-02-27T12:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Ответ `401`** (нет сессии):
|
||||
```json
|
||||
{ "error": "No active session" }
|
||||
```
|
||||
|
||||
**Объект AuthSession:**
|
||||
|
||||
| Поле | Тип | Обязат. | Описание |
|
||||
|------------------|---------|---------|-------------------------------------------|
|
||||
| `sessionId` | string | да | Уникальный ID сессии |
|
||||
| `telegramUserId` | number | да | ID пользователя в Telegram |
|
||||
| `username` | string? | нет | @username в Telegram (может быть null) |
|
||||
| `displayName` | string | да | Отображаемое имя (имя + фамилия) |
|
||||
| `active` | boolean | да | Действительна ли сессия |
|
||||
| `expiresAt` | string | да | Дата истечения в формате ISO 8601 |
|
||||
|
||||
---
|
||||
|
||||
### `GET /auth/telegram/callback` — Callback авторизации Telegram-бота
|
||||
|
||||
Вызывается Telegram-ботом после авторизации пользователя.
|
||||
|
||||
**Тело запроса (от бота):**
|
||||
```json
|
||||
{
|
||||
"id": 123456789,
|
||||
"first_name": "John",
|
||||
"last_name": "Doe",
|
||||
"username": "john_doe",
|
||||
"photo_url": "https://t.me/i/userpic/...",
|
||||
"auth_date": 1709100000,
|
||||
"hash": "abc123def456..."
|
||||
}
|
||||
```
|
||||
|
||||
**Ответ:** Должен установить cookie сессии и вернуть:
|
||||
```json
|
||||
{
|
||||
"sessionId": "sess_abc123",
|
||||
"message": "Authenticated successfully"
|
||||
}
|
||||
```
|
||||
|
||||
**Требования к cookie:**
|
||||
|
||||
| Атрибут | Значение | Примечание |
|
||||
|------------|----------------|-----------------------------------------------------|
|
||||
| `HttpOnly` | `true` | Недоступна из JavaScript |
|
||||
| `Secure` | `true` | Только HTTPS |
|
||||
| `SameSite` | `None` | Обязательно для cross-origin (API ≠ фронтенд) |
|
||||
| `Path` | `/` | |
|
||||
| `Max-Age` | `86400` (24ч) | Или по необходимости |
|
||||
|
||||
---
|
||||
|
||||
### `POST /auth/logout` — Завершить сессию
|
||||
|
||||
**Запрос:** Только cookie, пустое тело `{}`
|
||||
|
||||
**Ответ `200`:**
|
||||
```json
|
||||
{ "message": "Logged out" }
|
||||
```
|
||||
|
||||
Должен очистить/инвалидировать cookie сессии.
|
||||
|
||||
---
|
||||
|
||||
### Обновление сессии
|
||||
|
||||
Фронтенд повторно проверяет сессию за **60 секунд до `expiresAt`**. Если бэкенд поддерживает скользящий срок действия (sliding expiration), можно обновлять `Max-Age` cookie при каждом вызове `GET /auth/session`.
|
||||
|
||||
---
|
||||
|
||||
## 9. i18n / Переводы
|
||||
|
||||
Фронтенд поддерживает 3 языка: **Русский (ru)**, **Английский (en)**, **Армянский (hy)**.
|
||||
|
||||
Активный язык отправляется через HTTP-заголовок `X-Language` с каждым запросом.
|
||||
|
||||
### Что бэкенд должен делать с `X-Language`
|
||||
|
||||
1. **Категории и товары**: Если для запрошенного языка есть поле `translations`, вернуть переведённые `name`, `description` и т.д. ИЛИ бэкенд может применять переводы на стороне сервера и возвращать уже переведённые поля.
|
||||
|
||||
2. **Поле `translations`** на товарах (опциональный подход):
|
||||
```json
|
||||
{
|
||||
"translations": {
|
||||
"en": {
|
||||
"name": "iPhone 15 Pro",
|
||||
"simpleDescription": "Short desc in English",
|
||||
"description": [{ "key": "Processor", "value": "A17 Pro" }]
|
||||
},
|
||||
"hy": {
|
||||
"name": "iPhone 15 Pro",
|
||||
"simpleDescription": "Կarcheck check"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. **Рекомендуемый подход**: Читать заголовок `X-Language` и возвращать `name`/`description` на этом языке напрямую. Если перевода нет — возвращать русский вариант по умолчанию.
|
||||
|
||||
---
|
||||
|
||||
## 10. Настройка CORS
|
||||
|
||||
Для работы auth-cookie и кастомных заголовков конфигурация CORS бэкенда должна включать:
|
||||
|
||||
```
|
||||
Access-Control-Allow-Origin: https://dexarmarket.ru (НЕ wildcard *)
|
||||
Access-Control-Allow-Credentials: true
|
||||
Access-Control-Allow-Headers: Content-Type, X-Region, X-Language
|
||||
Access-Control-Allow-Methods: GET, POST, PATCH, DELETE, OPTIONS
|
||||
```
|
||||
|
||||
> **Важно:** `Access-Control-Allow-Origin` не может быть `*` при `Allow-Credentials: true`. Должен быть точный origin фронтенда.
|
||||
|
||||
**Разрешённые origins:**
|
||||
- `https://dexarmarket.ru`
|
||||
- `https://novo.market`
|
||||
- `http://localhost:4200` (dev)
|
||||
- `http://localhost:4201` (dev, Novo)
|
||||
|
||||
---
|
||||
|
||||
## 11. Настройка Telegram-бота
|
||||
|
||||
Каждому бренду нужен свой бот:
|
||||
- **Dexar:** `@dexarmarket_bot`
|
||||
- **Novo:** `@novomarket_bot`
|
||||
|
||||
Бот должен:
|
||||
1. Слушать команду `/start auth_{callbackUrl}`
|
||||
2. Извлечь callback URL
|
||||
3. Отправить данные пользователя (`id`, `first_name`, `username` и т.д.) на этот callback URL
|
||||
4. Callback URL: `{apiUrl}/auth/telegram/callback`
|
||||
|
||||
---
|
||||
|
||||
## Полный справочник эндпоинтов
|
||||
|
||||
### Новые эндпоинты
|
||||
|
||||
| Метод | Путь | Описание | Авторизация |
|
||||
|--------|---------------------------|---------------------------------|-------------|
|
||||
| `GET` | `/regions` | Список доступных регионов | Нет |
|
||||
| `GET` | `/auth/session` | Проверка текущей сессии | Cookie |
|
||||
| `GET` | `/auth/telegram/callback` | Callback авторизации через бота | Нет (бот) |
|
||||
| `POST` | `/auth/logout` | Завершение сессии | Cookie |
|
||||
|
||||
### Существующие эндпоинты
|
||||
|
||||
| Метод | Путь | Описание | Авт. | Заголовки |
|
||||
|----------|-----------------------|---------------------------|------|--------------------|
|
||||
| `GET` | `/ping` | Проверка состояния | Нет | — |
|
||||
| `GET` | `/category` | Список категорий | Нет | X-Region, X-Language |
|
||||
| `GET` | `/category/:id` | Товары категории | Нет | X-Region, X-Language |
|
||||
| `GET` | `/item/:id` | Один товар | Нет | X-Region, X-Language |
|
||||
| `GET` | `/searchitems` | Поиск товаров | Нет | X-Region, X-Language |
|
||||
| `GET` | `/randomitems` | Случайные товары | Нет | X-Region, X-Language |
|
||||
| `POST` | `/cart` | Добавить в корзину / Оплата | Нет* | — |
|
||||
| `PATCH` | `/cart` | Обновить кол-во | Нет* | — |
|
||||
| `DELETE` | `/cart` | Удалить из корзины | Нет* | — |
|
||||
| `GET` | `/cart` | Содержимое корзины | Нет* | — |
|
||||
| `POST` | `/comment` | Оставить отзыв | Нет | — |
|
||||
| `GET` | `/qr/payment/:qrId` | Статус оплаты | Нет | — |
|
||||
| `POST` | `/purchase-email` | Отправить email после оплаты | Нет | — |
|
||||
|
||||
> \* Эндпоинты корзины/оплаты могут использовать cookie сессии (если есть) для привязки к заказу, но не требуют авторизации строго. Фронтенд проверяет авторизацию перед оформлением заказа.
|
||||
824
docs/BACKEND_AUTH_INTEGRATION.md
Normal file
@@ -0,0 +1,824 @@
|
||||
# Авторизация через Telegram — Backend & Bot
|
||||
|
||||
> Всё что нужно Go-разработчику для реализации авторизации.
|
||||
> Фронтенд **полностью готов** и ждёт эти эндпоинты.
|
||||
|
||||
---
|
||||
|
||||
## Статус
|
||||
|
||||
| Компонент | Готов? |
|
||||
|-----------|--------|
|
||||
| Frontend (Angular) — диалог, QR, поллинг, корзина | ✅ Готов |
|
||||
| Telegram бот (обработка `/start`) | ❌ Нужно |
|
||||
| Backend — 6 HTTP-эндпоинтов | ❌ Нужно |
|
||||
| Хранилище сессий + QR-токенов | ❌ Нужно |
|
||||
| CORS для cookie-based запросов | ❌ Нужно |
|
||||
|
||||
---
|
||||
|
||||
## Архитектура
|
||||
|
||||
Два сценария авторизации:
|
||||
|
||||
### Сценарий 1: Прямой вход (кнопка "Войти через Telegram")
|
||||
|
||||
Пользователь нажимает кнопку → открывается Telegram → бот выдаёт кнопку "Войти на сайт" → callback ставит cookie → фронтенд поллит `/auth/session`.
|
||||
|
||||
### Сценарий 2: QR-логин с десктопа (основной)
|
||||
|
||||
```
|
||||
ДЕСКТОП БРАУЗЕР СЕРВЕР (Go) TELEGRAM
|
||||
│ │ │
|
||||
│ 1. POST /auth/qr/create │ │
|
||||
│ ─────────────────────────────> │ │
|
||||
│ { token: "abc", url: "..." } │ │
|
||||
│ <───────────────────────────── │ │
|
||||
│ │ │
|
||||
│ 2. Показать QR: │ │
|
||||
│ t.me/Bot?start=login_abc │ │
|
||||
│ │ │
|
||||
│ ПОЛЬЗОВАТЕЛЬ СКАНИРУЕТ ТЕЛЕФОНОМ │
|
||||
│ │ │
|
||||
│ │ 3. /start login_abc │
|
||||
│ │ <────────────────────────│
|
||||
│ │ │
|
||||
│ │ Бот → POST /auth/qr/confirm
|
||||
│ │ Бот → "✅ Вы вошли!" │
|
||||
│ │ ────────────────────────>│
|
||||
│ │ │
|
||||
│ 4. GET /auth/qr/poll?token=abc │ │
|
||||
│ (каждые 3 сек) │ │
|
||||
│ ─────────────────────────────> │ │
|
||||
│ { status: "confirmed", │ │
|
||||
│ session: {...} } │ │
|
||||
│ + Set-Cookie: dx_session=... │ │
|
||||
│ <───────────────────────────── │ │
|
||||
│ │ │
|
||||
│ 5. POST /websession/{sessionId} │ │
|
||||
│ [{ itemID, quantity, ... }] │ ← корзина │
|
||||
│ ─────────────────────────────> │ │
|
||||
│ │ │
|
||||
│ 6. Готово! Авторизован + корзина│ │
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Бренды и боты
|
||||
|
||||
| Бренд | Username бота | Домен фронтенда | API сервер | Cookie Domain |
|
||||
|-------|---------------|------------------|------------|---------------|
|
||||
| Dexar | `DexarSupport_bot` | `dexarmarket.ru` | `api.dexarmarket.ru:445` | `.dexarmarket.ru` |
|
||||
| Novo | `novomarket_bot` | `novo.market` | `api.novo.market:444` | `.novo.market` |
|
||||
|
||||
Бот создаётся через https://t.me/BotFather → `/newbot`. Сохранить `BOT_TOKEN`.
|
||||
|
||||
---
|
||||
|
||||
## Хранилище
|
||||
|
||||
### Структура: Сессия
|
||||
|
||||
```go
|
||||
type Session struct {
|
||||
SessionID string `json:"sessionId"`
|
||||
TelegramUserID int64 `json:"telegramUserId"`
|
||||
Username *string `json:"username"` // может быть null
|
||||
DisplayName string `json:"displayName"`
|
||||
Active bool `json:"active"`
|
||||
ExpiresAt time.Time `json:"expiresAt"`
|
||||
}
|
||||
```
|
||||
|
||||
**TTL:** 24 часа.
|
||||
|
||||
### Структура: QR-токен (одноразовый)
|
||||
|
||||
```go
|
||||
type AuthToken struct {
|
||||
Token string `json:"token"`
|
||||
Status string `json:"status"` // "pending" | "confirmed" | "expired"
|
||||
SessionID string `json:"sessionId"` // заполняется после подтверждения ботом
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
ExpiresAt time.Time `json:"expiresAt"`
|
||||
}
|
||||
```
|
||||
|
||||
**TTL:** 5 минут.
|
||||
|
||||
### Варианты хранения
|
||||
|
||||
**Redis (рекомендуется):**
|
||||
```go
|
||||
// Сессия
|
||||
redisClient.Set(ctx, "session:"+s.SessionID, json, 24*time.Hour)
|
||||
|
||||
// QR-токен
|
||||
redisClient.Set(ctx, "auth_token:"+t.Token, json, 5*time.Minute)
|
||||
```
|
||||
|
||||
**sync.Map (для MVP):**
|
||||
```go
|
||||
var sessions sync.Map
|
||||
var authTokens sync.Map
|
||||
|
||||
// Очистка устаревших токенов — запустить горутину при старте
|
||||
func cleanupExpiredTokens() {
|
||||
ticker := time.NewTicker(1 * time.Minute)
|
||||
for range ticker.C {
|
||||
authTokens.Range(func(key, value any) bool {
|
||||
t := value.(AuthToken)
|
||||
if time.Now().After(t.ExpiresAt) {
|
||||
authTokens.Delete(key)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## HTTP-эндпоинты
|
||||
|
||||
### 1. `POST /auth/qr/create`
|
||||
|
||||
Фронтенд вызывает при открытии диалога логина. Создаёт одноразовый QR-токен.
|
||||
|
||||
```go
|
||||
func handleQrCreate(w http.ResponseWriter, r *http.Request) {
|
||||
// 1. Сгенерировать криптографически безопасный токен
|
||||
tokenBytes := make([]byte, 32)
|
||||
if _, err := rand.Read(tokenBytes); err != nil {
|
||||
http.Error(w, "internal error", 500)
|
||||
return
|
||||
}
|
||||
token := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(tokenBytes)
|
||||
|
||||
// 2. Определить бота по origin
|
||||
botUsername := getBotForOrigin(r.Header.Get("Origin"))
|
||||
|
||||
// 3. Сохранить токен
|
||||
authToken := AuthToken{
|
||||
Token: token,
|
||||
Status: "pending",
|
||||
CreatedAt: time.Now(),
|
||||
ExpiresAt: time.Now().Add(5 * time.Minute),
|
||||
}
|
||||
saveAuthToken(authToken)
|
||||
|
||||
// 4. Ответить
|
||||
qrURL := fmt.Sprintf("https://t.me/%s?start=login_%s", botUsername, token)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"token": token,
|
||||
"url": qrURL,
|
||||
})
|
||||
}
|
||||
|
||||
func getBotForOrigin(origin string) string {
|
||||
if strings.Contains(origin, "novo.market") {
|
||||
return "novomarket_bot"
|
||||
}
|
||||
return "DexarSupport_bot"
|
||||
}
|
||||
```
|
||||
|
||||
**Ответ:**
|
||||
```json
|
||||
{ "token": "dG9rZW4tYWJj....", "url": "https://t.me/DexarSupport_bot?start=login_dG9rZW4tYWJj...." }
|
||||
```
|
||||
|
||||
> Telegram ограничивает `start` до 64 символов. `login_` (6) + base64url из 32 байт (43) = 49 ✅
|
||||
|
||||
---
|
||||
|
||||
### 2. `GET /auth/qr/poll?token={token}`
|
||||
|
||||
Фронтенд вызывает каждые 3 секунды. Когда бот подтвердил — возвращает сессию и ставит cookie.
|
||||
|
||||
```go
|
||||
func handleQrPoll(w http.ResponseWriter, r *http.Request) {
|
||||
tokenStr := r.URL.Query().Get("token")
|
||||
if tokenStr == "" {
|
||||
http.Error(w, "missing token", 400)
|
||||
return
|
||||
}
|
||||
|
||||
authToken, ok := getAuthToken(tokenStr)
|
||||
if !ok {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]string{"status": "expired"})
|
||||
return
|
||||
}
|
||||
|
||||
switch authToken.Status {
|
||||
case "pending":
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]string{"status": "pending"})
|
||||
|
||||
case "confirmed":
|
||||
session, err := getSession(authToken.SessionID)
|
||||
if err != nil {
|
||||
http.Error(w, "session not found", 500)
|
||||
return
|
||||
}
|
||||
|
||||
// Cookie в ДЕСКТОПНЫЙ браузер
|
||||
domain := getDomainForOrigin(r.Header.Get("Origin"))
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "dx_session",
|
||||
Value: session.SessionID,
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
Secure: true,
|
||||
SameSite: http.SameSiteNoneMode,
|
||||
MaxAge: 86400,
|
||||
Domain: domain,
|
||||
})
|
||||
|
||||
// Удалить использованный токен
|
||||
deleteAuthToken(tokenStr)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "confirmed",
|
||||
"session": session,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func getDomainForOrigin(origin string) string {
|
||||
if strings.Contains(origin, "novo.market") {
|
||||
return ".novo.market"
|
||||
}
|
||||
return ".dexarmarket.ru"
|
||||
}
|
||||
```
|
||||
|
||||
**Ответы:**
|
||||
|
||||
| Статус | JSON |
|
||||
|--------|------|
|
||||
| Ждём | `{ "status": "pending" }` |
|
||||
| Подтверждено | `{ "status": "confirmed", "session": { sessionId, telegramUserId, username, displayName, active, expiresAt } }` + `Set-Cookie` |
|
||||
| Истекло | `{ "status": "expired" }` |
|
||||
|
||||
---
|
||||
|
||||
### 3. `POST /auth/qr/confirm` (внутренний, для бота)
|
||||
|
||||
Бот вызывает когда пользователь отсканировал QR. Привязывает сессию к токену.
|
||||
|
||||
```go
|
||||
func handleQrConfirm(w http.ResponseWriter, r *http.Request) {
|
||||
// Проверить секрет бота
|
||||
if r.Header.Get("X-Bot-Secret") != os.Getenv("BOT_INTERNAL_SECRET") {
|
||||
http.Error(w, "forbidden", 403)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Token string `json:"token"`
|
||||
User struct {
|
||||
ID int64 `json:"id"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
Username string `json:"username"`
|
||||
} `json:"telegram_user"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, "bad request", 400)
|
||||
return
|
||||
}
|
||||
|
||||
authToken, ok := getAuthToken(req.Token)
|
||||
if !ok || authToken.Status != "pending" {
|
||||
http.Error(w, "token not found or already used", 404)
|
||||
return
|
||||
}
|
||||
|
||||
// Создать сессию
|
||||
displayName := req.User.FirstName
|
||||
if req.User.LastName != "" {
|
||||
displayName += " " + req.User.LastName
|
||||
}
|
||||
var username *string
|
||||
if req.User.Username != "" {
|
||||
username = &req.User.Username
|
||||
}
|
||||
|
||||
session := Session{
|
||||
SessionID: uuid.New().String(),
|
||||
TelegramUserID: req.User.ID,
|
||||
Username: username,
|
||||
DisplayName: displayName,
|
||||
Active: true,
|
||||
ExpiresAt: time.Now().Add(24 * time.Hour),
|
||||
}
|
||||
saveSession(session)
|
||||
|
||||
// Привязать сессию к токену
|
||||
authToken.Status = "confirmed"
|
||||
authToken.SessionID = session.SessionID
|
||||
saveAuthToken(*authToken)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
|
||||
}
|
||||
```
|
||||
|
||||
**Запрос от бота:**
|
||||
```json
|
||||
{
|
||||
"token": "dG9rZW4tYWJj...",
|
||||
"telegram_user": {
|
||||
"id": 123456789,
|
||||
"first_name": "Иван",
|
||||
"last_name": "Петров",
|
||||
"username": "ivan_petrov"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. `GET /auth/session`
|
||||
|
||||
Фронтенд вызывает для проверки текущей сессии. Читает cookie.
|
||||
|
||||
```go
|
||||
func handleGetSession(w http.ResponseWriter, r *http.Request) {
|
||||
cookie, err := r.Cookie("dx_session")
|
||||
if err != nil {
|
||||
http.Error(w, "unauthorized", 401)
|
||||
return
|
||||
}
|
||||
|
||||
session, err := getSession(cookie.Value)
|
||||
if err != nil {
|
||||
http.Error(w, "unauthorized", 401)
|
||||
return
|
||||
}
|
||||
|
||||
if time.Now().After(session.ExpiresAt) {
|
||||
session.Active = false
|
||||
saveSession(session)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(session)
|
||||
}
|
||||
```
|
||||
|
||||
**Формат ответа (200)** — фронтенд ожидает **точно эти поля**:
|
||||
|
||||
```json
|
||||
{
|
||||
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"telegramUserId": 123456789,
|
||||
"username": "ivan_petrov",
|
||||
"displayName": "Иван Петров",
|
||||
"active": true,
|
||||
"expiresAt": "2026-03-25T14:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
| Поле | Тип | Обязательно | Примечание |
|
||||
|------|-----|-------------|------------|
|
||||
| `sessionId` | string (UUID) | да | Используется для `/websession/{sessionId}` |
|
||||
| `telegramUserId` | number | да | Telegram user ID |
|
||||
| `username` | string / null | нет | Telegram @username |
|
||||
| `displayName` | string | да | "Имя Фамилия" — показывается в UI |
|
||||
| `active` | boolean | да | `false` = истекла |
|
||||
| `expiresAt` | string (ISO 8601) | да | Фронтенд перепроверяет за 60 сек до |
|
||||
|
||||
**Ошибка:** любой HTTP не-200 → фронтенд считает "не авторизован".
|
||||
|
||||
---
|
||||
|
||||
### 5. `GET /auth/telegram/callback`
|
||||
|
||||
Для прямого входа (по кнопке в Telegram, не через QR). Открывается в браузере.
|
||||
|
||||
```go
|
||||
func handleTelegramCallback(w http.ResponseWriter, r *http.Request) {
|
||||
token := r.URL.Query().Get("token")
|
||||
if token == "" {
|
||||
http.Error(w, "missing token", 400)
|
||||
return
|
||||
}
|
||||
|
||||
session, err := getSession(token)
|
||||
if err != nil || !session.Active {
|
||||
http.Error(w, "invalid or expired token", 401)
|
||||
return
|
||||
}
|
||||
|
||||
domain := getDomainForOrigin(r.Header.Get("Origin"))
|
||||
if domain == "" {
|
||||
domain = ".dexarmarket.ru" // fallback для прямого перехода
|
||||
}
|
||||
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "dx_session",
|
||||
Value: session.SessionID,
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
Secure: true,
|
||||
SameSite: http.SameSiteNoneMode,
|
||||
MaxAge: 86400,
|
||||
Domain: domain,
|
||||
})
|
||||
|
||||
// Редирект на сайт
|
||||
http.Redirect(w, r, "https://dexarmarket.ru", http.StatusFound)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6. `POST /auth/logout`
|
||||
|
||||
```go
|
||||
func handleLogout(w http.ResponseWriter, r *http.Request) {
|
||||
cookie, err := r.Cookie("dx_session")
|
||||
if err == nil {
|
||||
deleteSession(cookie.Value)
|
||||
}
|
||||
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "dx_session",
|
||||
Value: "",
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
Secure: true,
|
||||
SameSite: http.SameSiteNoneMode,
|
||||
MaxAge: -1,
|
||||
Domain: ".dexarmarket.ru",
|
||||
})
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(`{"message":"ok"}`))
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Cookie-параметры
|
||||
|
||||
| Параметр | Значение | Почему |
|
||||
|----------|----------|--------|
|
||||
| `Name` | `dx_session` | |
|
||||
| `SameSite` | `None` | Фронтенд на `dexarmarket.ru`, API на `api.dexarmarket.ru:445` — разные origins |
|
||||
| `Secure` | `true` | Обязательно при `SameSite=None` |
|
||||
| `Domain` | `.dexarmarket.ru` | Доступна и на `dexarmarket.ru` и на `api.dexarmarket.ru` |
|
||||
| `HttpOnly` | `true` | Недоступна из JS — защита от XSS |
|
||||
| `MaxAge` | `86400` | 24 часа |
|
||||
|
||||
---
|
||||
|
||||
## CORS
|
||||
|
||||
Фронтенд шлёт `withCredentials: true`. Бэкенд обязан вернуть правильные заголовки.
|
||||
|
||||
```go
|
||||
func corsMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
origin := r.Header.Get("Origin")
|
||||
|
||||
allowed := map[string]bool{
|
||||
"https://dexarmarket.ru": true,
|
||||
"https://www.dexarmarket.ru": true,
|
||||
"https://novo.market": true,
|
||||
"https://www.novo.market": true,
|
||||
"http://localhost:4200": true,
|
||||
}
|
||||
|
||||
if allowed[origin] {
|
||||
w.Header().Set("Access-Control-Allow-Origin", origin) // НЕ "*"
|
||||
w.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
|
||||
}
|
||||
|
||||
if r.Method == "OPTIONS" {
|
||||
w.WriteHeader(200)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
> **Критично:** `Access-Control-Allow-Origin` не может быть `"*"` при `withCredentials`. Вернуть конкретный origin.
|
||||
|
||||
---
|
||||
|
||||
## Роутинг
|
||||
|
||||
```go
|
||||
mux := http.NewServeMux()
|
||||
|
||||
// Существующие
|
||||
mux.HandleFunc("GET /items/{id}", handleGetItem)
|
||||
mux.HandleFunc("GET /category", handleGetCategories)
|
||||
mux.HandleFunc("POST /websession/{id}", handleWebSession)
|
||||
mux.HandleFunc("POST /websession/{id}/qr", handleCreateQR)
|
||||
mux.HandleFunc("GET /websession/{id}/{qrId}", handleCheckPayment)
|
||||
|
||||
// Auth — прямой вход
|
||||
mux.HandleFunc("GET /auth/session", handleGetSession)
|
||||
mux.HandleFunc("GET /auth/telegram/callback", handleTelegramCallback)
|
||||
mux.HandleFunc("POST /auth/logout", handleLogout)
|
||||
|
||||
// Auth — QR-логин
|
||||
mux.HandleFunc("POST /auth/qr/create", handleQrCreate)
|
||||
mux.HandleFunc("GET /auth/qr/poll", handleQrPoll)
|
||||
mux.HandleFunc("POST /auth/qr/confirm", handleQrConfirm)
|
||||
|
||||
handler := corsMiddleware(mux)
|
||||
http.ListenAndServeTLS(":445", "cert.pem", "key.pem", handler)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Telegram-бот
|
||||
|
||||
### Обработчик `/start`
|
||||
|
||||
```go
|
||||
const (
|
||||
confirmURL = "http://localhost:8080/auth/qr/confirm"
|
||||
botInternalSecret = os.Getenv("BOT_INTERNAL_SECRET")
|
||||
)
|
||||
|
||||
func handleStart(update tgbotapi.Update) {
|
||||
text := update.Message.Text
|
||||
user := update.Message.From
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(text, "/start login_"):
|
||||
handleQrLogin(update, user, strings.TrimPrefix(text, "/start login_"))
|
||||
|
||||
case strings.HasPrefix(text, "/start auth"):
|
||||
handleDirectAuth(update, user)
|
||||
|
||||
default:
|
||||
sendWelcome(update)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### QR-логин (основной)
|
||||
|
||||
```go
|
||||
func handleQrLogin(update tgbotapi.Update, user *tgbotapi.User, token string) {
|
||||
reqBody := map[string]interface{}{
|
||||
"token": token,
|
||||
"telegram_user": map[string]interface{}{
|
||||
"id": user.ID,
|
||||
"first_name": user.FirstName,
|
||||
"last_name": user.LastName,
|
||||
"username": user.UserName,
|
||||
},
|
||||
}
|
||||
bodyBytes, _ := json.Marshal(reqBody)
|
||||
|
||||
req, _ := http.NewRequest("POST", confirmURL, bytes.NewReader(bodyBytes))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-Bot-Secret", botInternalSecret)
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil || resp.StatusCode != 200 {
|
||||
msg := tgbotapi.NewMessage(update.Message.Chat.ID,
|
||||
"❌ Не удалось войти. QR-код мог устареть. Попробуйте обновить страницу и отсканировать новый QR.")
|
||||
bot.Send(msg)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
displayName := buildDisplayName(user)
|
||||
msg := tgbotapi.NewMessage(update.Message.Chat.ID,
|
||||
fmt.Sprintf("✅ Вы вошли на сайт как %s!\n\nМожете вернуться в браузер — страница обновится автоматически.", displayName))
|
||||
bot.Send(msg)
|
||||
}
|
||||
|
||||
func buildDisplayName(user *tgbotapi.User) string {
|
||||
name := user.FirstName
|
||||
if user.LastName != "" {
|
||||
name += " " + user.LastName
|
||||
}
|
||||
return name
|
||||
}
|
||||
```
|
||||
|
||||
### Прямой вход (кнопка, для обратной совместимости)
|
||||
|
||||
```go
|
||||
func handleDirectAuth(update tgbotapi.Update, user *tgbotapi.User) {
|
||||
session := Session{
|
||||
SessionID: uuid.New().String(),
|
||||
TelegramUserID: user.ID,
|
||||
Username: stringPtr(user.UserName),
|
||||
DisplayName: buildDisplayName(user),
|
||||
Active: true,
|
||||
ExpiresAt: time.Now().Add(24 * time.Hour),
|
||||
}
|
||||
saveSession(session)
|
||||
|
||||
callbackURL := "https://api.dexarmarket.ru:445/auth/telegram/callback"
|
||||
loginURL := callbackURL + "?token=" + session.SessionID
|
||||
|
||||
msg := tgbotapi.NewMessage(update.Message.Chat.ID, "Нажмите кнопку чтобы войти:")
|
||||
msg.ReplyMarkup = tgbotapi.NewInlineKeyboardMarkup(
|
||||
tgbotapi.NewInlineKeyboardRow(
|
||||
tgbotapi.NewInlineKeyboardButtonURL("🔐 Войти на сайт", loginURL),
|
||||
),
|
||||
)
|
||||
bot.Send(msg)
|
||||
}
|
||||
```
|
||||
|
||||
### Запуск бота (long polling)
|
||||
|
||||
```go
|
||||
func main() {
|
||||
bot, _ := tgbotapi.NewBotAPI(os.Getenv("BOT_TOKEN"))
|
||||
|
||||
u := tgbotapi.NewUpdate(0)
|
||||
u.Timeout = 60
|
||||
updates := bot.GetUpdatesChan(u)
|
||||
|
||||
for update := range updates {
|
||||
if update.Message != nil && strings.HasPrefix(update.Message.Text, "/start") {
|
||||
handleStart(update)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Синхронизация корзины
|
||||
|
||||
Сразу после QR-логина фронтенд автоматически отправляет корзину:
|
||||
|
||||
```
|
||||
POST /websession/{sessionId}
|
||||
```
|
||||
|
||||
Тело — массив:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"itemID": 123,
|
||||
"quantity": 2,
|
||||
"colour": "#ff0000",
|
||||
"size": "XL",
|
||||
"price": 1500
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
| Поле | Тип | Примечание |
|
||||
|------|-----|------------|
|
||||
| `itemID` | number | ID товара |
|
||||
| `quantity` | number | Количество |
|
||||
| `colour` | string | CSS hex (`#ff0000`). Бэкенд отдаёт `0xff0000`, фронтенд конвертирует |
|
||||
| `size` | string | `"default"` если размер один |
|
||||
| `price` | number | Финальная цена **с учётом скидки** |
|
||||
|
||||
> Этот эндпоинт (`POST /websession/{id}`) уже существует. Ничего менять не нужно, просто учитывать что он вызывается сразу после успешного логина.
|
||||
|
||||
---
|
||||
|
||||
## Безопасность
|
||||
|
||||
### Криптографический токен
|
||||
```go
|
||||
tokenBytes := make([]byte, 32) // 256 бит
|
||||
crypto/rand.Read(tokenBytes)
|
||||
token := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(tokenBytes)
|
||||
```
|
||||
**НЕ использовать:** `math/rand`, UUID, timestamp.
|
||||
|
||||
### Токен одноразовый
|
||||
- После `confirmed` → удалить при первом успешном `poll`
|
||||
- После 5 минут → автоудаление (TTL)
|
||||
- Повторный `poll` → `"expired"`
|
||||
|
||||
### Защита `/auth/qr/confirm`
|
||||
```go
|
||||
if r.Header.Get("X-Bot-Secret") != os.Getenv("BOT_INTERNAL_SECRET") {
|
||||
http.Error(w, "forbidden", 403)
|
||||
return
|
||||
}
|
||||
```
|
||||
Дополнительно: можно ограничить по IP (`127.0.0.1`) если бот на том же сервере.
|
||||
|
||||
### Rate limiting для `/auth/qr/create`
|
||||
Не более **5 токенов в минуту** с одного IP:
|
||||
```go
|
||||
var ipCounts sync.Map
|
||||
|
||||
func rateLimitQrCreate(ip string) bool {
|
||||
key := ip + ":" + time.Now().Format("2006-01-02T15:04")
|
||||
val, _ := ipCounts.LoadOrStore(key, new(int32))
|
||||
count := atomic.AddInt32(val.(*int32), 1)
|
||||
return count <= 5
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Переменные окружения
|
||||
|
||||
```env
|
||||
BOT_TOKEN=123456:ABC-DEF...
|
||||
BOT_INTERNAL_SECRET=случайная-строка-минимум-32-символа
|
||||
FRONTEND_URL=https://dexarmarket.ru
|
||||
SESSION_TTL=24h
|
||||
REDIS_URL=localhost:6379
|
||||
```
|
||||
|
||||
`BOT_INTERNAL_SECRET` должен совпадать в env сервера и env бота.
|
||||
|
||||
---
|
||||
|
||||
## Тестирование
|
||||
|
||||
### curl-тесты
|
||||
|
||||
**1. Создание токена:**
|
||||
```bash
|
||||
curl -X POST https://api.dexarmarket.ru:445/auth/qr/create \
|
||||
-H "Origin: https://dexarmarket.ru"
|
||||
# → { "token": "dG9r...", "url": "https://t.me/DexarSupport_bot?start=login_dG9r..." }
|
||||
```
|
||||
|
||||
**2. Поллинг (до подтверждения):**
|
||||
```bash
|
||||
curl "https://api.dexarmarket.ru:445/auth/qr/poll?token=dG9r..."
|
||||
# → { "status": "pending" }
|
||||
```
|
||||
|
||||
**3. Подтверждение (имитация бота):**
|
||||
```bash
|
||||
curl -X POST https://api.dexarmarket.ru:445/auth/qr/confirm \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Bot-Secret: ваш-секрет" \
|
||||
-d '{"token":"dG9r...","telegram_user":{"id":123,"first_name":"Тест","last_name":"","username":"testuser"}}'
|
||||
# → { "status": "ok" }
|
||||
```
|
||||
|
||||
**4. Поллинг (после подтверждения):**
|
||||
```bash
|
||||
curl -v "https://api.dexarmarket.ru:445/auth/qr/poll?token=dG9r..."
|
||||
# → { "status": "confirmed", "session": {...} } + Set-Cookie: dx_session=...
|
||||
```
|
||||
|
||||
**5. E2E:**
|
||||
1. Открыть маркетплейс → добавить товар в корзину
|
||||
2. Нажать "Оформить заказ" → появляется диалог с QR
|
||||
3. Отсканировать QR телефоном → Telegram → бот: "✅ Вы вошли!"
|
||||
4. Через 3 сек диалог закрывается → авторизован
|
||||
5. Корзина синхронизирована (`POST /websession/{sessionId}`)
|
||||
|
||||
### Отладка
|
||||
|
||||
| Проблема | Где смотреть |
|
||||
|----------|-------------|
|
||||
| QR не показывается | `POST /auth/qr/create` — ошибка? CORS? |
|
||||
| QR отсканирован, ничего не происходит | Бот получил `/start login_...`? Бот вызвал `confirm`? |
|
||||
| Бот пишет "❌ QR устарел" | Токен expired? 5 минут прошло? |
|
||||
| Поллинг "pending" бесконечно | Бот не вызвал `confirm`. Логи бота |
|
||||
| Поллинг "confirmed" но cookie нет | `SameSite`, `Secure`, `Domain`, CORS |
|
||||
|
||||
---
|
||||
|
||||
## Чеклист
|
||||
|
||||
### Бэкенд (Go)
|
||||
|
||||
- [ ] Структура `Session` + `AuthToken`, функции save/get/delete
|
||||
- [ ] `POST /auth/qr/create` — генерация токена
|
||||
- [ ] `GET /auth/qr/poll?token=...` — статус + cookie при confirmed
|
||||
- [ ] `POST /auth/qr/confirm` — приём от бота с `X-Bot-Secret`
|
||||
- [ ] `GET /auth/session` — чтение cookie, JSON сессии
|
||||
- [ ] `GET /auth/telegram/callback?token=...` — cookie + редирект
|
||||
- [ ] `POST /auth/logout` — удаление сессии и cookie
|
||||
- [ ] TTL 5 мин для токенов, 24ч для сессий
|
||||
- [ ] Rate limiting `/auth/qr/create` (5/мин/IP)
|
||||
- [ ] Очистка устаревших токенов
|
||||
- [ ] CORS middleware
|
||||
- [ ] `BOT_INTERNAL_SECRET` в env
|
||||
|
||||
### Telegram бот
|
||||
|
||||
- [ ] Обработка `/start login_{token}` → `POST /auth/qr/confirm`
|
||||
- [ ] Обработка `/start auth` → создание сессии + кнопка "Войти"
|
||||
- [ ] Сообщения: "✅ Вы вошли" / "❌ QR устарел"
|
||||
- [ ] `BOT_INTERNAL_SECRET` в env (совпадает с сервером)
|
||||
- [ ] `BOT_TOKEN` в env
|
||||
327
docs/TELEGRAM_USERAUTH_BACKEND.md
Normal file
@@ -0,0 +1,327 @@
|
||||
# Telegram UserAuth Backend Contract
|
||||
|
||||
This document extracts the existing Telegram login flow into a repo-neutral contract for reuse in other projects.
|
||||
|
||||
The UI behavior, payloads, polling cadence, and session model stay the same. Only route names and cookie naming are generalized.
|
||||
|
||||
## Endpoint Renaming
|
||||
|
||||
| Current app contract | Reusable contract |
|
||||
|---|---|
|
||||
| `GET /auth/session` | `GET /userauth/session` |
|
||||
| `POST /auth/qr/create` | `POST /userauth/qr/create` |
|
||||
| `GET /auth/qr/poll?token=...` | `GET /userauth/qr/poll?token=...` |
|
||||
| `POST /auth/qr/confirm` | `POST /userauth/qr/confirm` |
|
||||
| `GET /auth/telegram/callback` | `GET /userauth/telegram/callback` |
|
||||
| `POST /auth/logout` | `POST /userauth/logout` |
|
||||
| `POST /websession/{sessionId}` | `POST /usersession/{sessionId}` |
|
||||
| Cookie `dx_session` | Cookie `userauth_session` |
|
||||
|
||||
## Flow Summary
|
||||
|
||||
There are two supported flows.
|
||||
|
||||
### 1. Direct login from button
|
||||
|
||||
1. Frontend opens `https://t.me/{botUsername}?start=auth_{callbackUrl}`.
|
||||
2. Telegram bot creates a session and sends the user a login button.
|
||||
3. The button points to `GET /userauth/telegram/callback?token={sessionId}`.
|
||||
4. Backend sets `userauth_session` cookie and redirects back to the storefront.
|
||||
5. Frontend calls `GET /userauth/session` and becomes authenticated.
|
||||
|
||||
### 2. QR login from desktop
|
||||
|
||||
1. Frontend opens dialog.
|
||||
2. Frontend calls `POST /userauth/qr/create`.
|
||||
3. Backend returns `{ token, url }` where `url` is a Telegram deep link.
|
||||
4. Frontend renders a QR from that URL.
|
||||
5. User scans QR and bot calls `POST /userauth/qr/confirm`.
|
||||
6. Frontend polls `GET /userauth/qr/poll?token=...` every 3 seconds.
|
||||
7. When status becomes `confirmed`, backend returns session payload and sets the cookie.
|
||||
8. Frontend syncs local cart using `POST /usersession/{sessionId}`.
|
||||
|
||||
## Session Shape
|
||||
|
||||
The frontend expects this exact response shape for the authenticated session.
|
||||
|
||||
```json
|
||||
{
|
||||
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"telegramUserId": 123456789,
|
||||
"username": "ivan_petrov",
|
||||
"displayName": "Ivan Petrov",
|
||||
"active": true,
|
||||
"expiresAt": "2026-05-21T14:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Notes |
|
||||
|---|---|---|---|
|
||||
| `sessionId` | string | yes | Session identifier used in cart sync |
|
||||
| `telegramUserId` | number | yes | Telegram user ID |
|
||||
| `username` | string or null | no | Telegram username |
|
||||
| `displayName` | string | yes | User-facing full name |
|
||||
| `active` | boolean | yes | `false` means expired session |
|
||||
| `expiresAt` | ISO 8601 string | yes | Used by frontend refresh scheduling |
|
||||
|
||||
Recommended TTL:
|
||||
|
||||
- Session TTL: 24 hours
|
||||
- QR token TTL: 5 minutes
|
||||
|
||||
## HTTP Contract
|
||||
|
||||
### `POST /userauth/qr/create`
|
||||
|
||||
Creates a one-time QR login token when the dialog opens.
|
||||
|
||||
Request body:
|
||||
|
||||
```json
|
||||
{}
|
||||
```
|
||||
|
||||
Response `200`:
|
||||
|
||||
```json
|
||||
{
|
||||
"token": "dG9rZW4tYWJjMTIz",
|
||||
"url": "https://t.me/userauth_bot?start=login_dG9rZW4tYWJjMTIz"
|
||||
}
|
||||
```
|
||||
|
||||
Requirements:
|
||||
|
||||
- Generate a cryptographically secure token.
|
||||
- Save token with status `pending`.
|
||||
- Return a Telegram deep link in `url`.
|
||||
- Rate limit to 5 requests per minute per IP.
|
||||
|
||||
### `GET /userauth/qr/poll?token={token}`
|
||||
|
||||
Called every 3 seconds until confirmation or expiration.
|
||||
|
||||
Possible responses:
|
||||
|
||||
Pending:
|
||||
|
||||
```json
|
||||
{ "status": "pending" }
|
||||
```
|
||||
|
||||
Confirmed:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "confirmed",
|
||||
"session": {
|
||||
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"telegramUserId": 123456789,
|
||||
"username": "ivan_petrov",
|
||||
"displayName": "Ivan Petrov",
|
||||
"active": true,
|
||||
"expiresAt": "2026-05-21T14:30:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Expired:
|
||||
|
||||
```json
|
||||
{ "status": "expired" }
|
||||
```
|
||||
|
||||
Behavior:
|
||||
|
||||
- If confirmed, set cookie `userauth_session` in the response.
|
||||
- Delete or invalidate the QR token after the first successful confirmed poll.
|
||||
- If token is unknown or expired, return `status: "expired"`.
|
||||
|
||||
### `POST /userauth/qr/confirm`
|
||||
|
||||
Internal endpoint called by the Telegram bot after the user scans the QR code.
|
||||
|
||||
Required header:
|
||||
|
||||
```text
|
||||
X-Bot-Secret: <shared secret between bot and backend>
|
||||
```
|
||||
|
||||
Request body:
|
||||
|
||||
```json
|
||||
{
|
||||
"token": "dG9rZW4tYWJjMTIz",
|
||||
"telegram_user": {
|
||||
"id": 123456789,
|
||||
"first_name": "Ivan",
|
||||
"last_name": "Petrov",
|
||||
"username": "ivan_petrov"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Response `200`:
|
||||
|
||||
```json
|
||||
{ "status": "ok" }
|
||||
```
|
||||
|
||||
Behavior:
|
||||
|
||||
- Validate `X-Bot-Secret`.
|
||||
- Validate token exists and is still `pending`.
|
||||
- Create a user session.
|
||||
- Store session ID on the QR token.
|
||||
- Mark QR token as `confirmed`.
|
||||
|
||||
### `GET /userauth/session`
|
||||
|
||||
Returns the currently active session based on the cookie.
|
||||
|
||||
Frontend behavior depends on this endpoint in two places:
|
||||
|
||||
- initial auth check on app startup
|
||||
- fallback polling if QR token creation fails
|
||||
|
||||
Response `200`:
|
||||
|
||||
```json
|
||||
{
|
||||
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"telegramUserId": 123456789,
|
||||
"username": "ivan_petrov",
|
||||
"displayName": "Ivan Petrov",
|
||||
"active": true,
|
||||
"expiresAt": "2026-05-21T14:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
Error handling:
|
||||
|
||||
- Any non-200 response is treated by the frontend as unauthenticated.
|
||||
|
||||
### `GET /userauth/telegram/callback?token={sessionId}`
|
||||
|
||||
Used for direct Telegram login from the primary button flow.
|
||||
|
||||
Behavior:
|
||||
|
||||
- Read the `token` query param.
|
||||
- Resolve it to a valid active session.
|
||||
- Set cookie `userauth_session`.
|
||||
- Redirect user to the storefront URL.
|
||||
|
||||
### `POST /userauth/logout`
|
||||
|
||||
Clears the backend session and expires the cookie.
|
||||
|
||||
Request body:
|
||||
|
||||
```json
|
||||
{}
|
||||
```
|
||||
|
||||
Response `200`:
|
||||
|
||||
```json
|
||||
{ "message": "ok" }
|
||||
```
|
||||
|
||||
### `POST /usersession/{sessionId}`
|
||||
|
||||
Synchronizes local cart immediately after successful login.
|
||||
|
||||
Request body:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"itemID": 123,
|
||||
"quantity": 2,
|
||||
"colour": "#ff0000",
|
||||
"size": "XL",
|
||||
"price": 1500
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- This payload is unchanged from the existing implementation.
|
||||
- `price` is already discounted on the frontend side.
|
||||
- The frontend skips the call if cart is empty.
|
||||
|
||||
## Telegram Deep Link Format
|
||||
|
||||
Direct login link format:
|
||||
|
||||
```text
|
||||
https://t.me/{botUsername}?start=auth_{urlEncodedCallbackUrl}
|
||||
```
|
||||
|
||||
QR login link format:
|
||||
|
||||
```text
|
||||
https://t.me/{botUsername}?start=login_{qrToken}
|
||||
```
|
||||
|
||||
Important limit:
|
||||
|
||||
- Telegram limits the `start` payload to 64 characters.
|
||||
- A base64url encoding of 32 random bytes plus `login_` fits safely.
|
||||
|
||||
## Cookie Requirements
|
||||
|
||||
Use these cookie settings for the frontend to work correctly across site and API origins.
|
||||
|
||||
| Property | Value |
|
||||
|---|---|
|
||||
| Name | `userauth_session` |
|
||||
| Path | `/` |
|
||||
| HttpOnly | `true` |
|
||||
| Secure | `true` |
|
||||
| SameSite | `None` |
|
||||
| MaxAge | `86400` |
|
||||
| Domain | your shared parent domain, for example `.example.com` |
|
||||
|
||||
## CORS Requirements
|
||||
|
||||
Because the frontend sends credentials, backend must return an explicit origin.
|
||||
|
||||
Required headers:
|
||||
|
||||
```text
|
||||
Access-Control-Allow-Origin: https://your-frontend.example
|
||||
Access-Control-Allow-Credentials: true
|
||||
Access-Control-Allow-Methods: GET, POST, OPTIONS
|
||||
Access-Control-Allow-Headers: Content-Type
|
||||
```
|
||||
|
||||
Do not use `*` for `Access-Control-Allow-Origin` together with credentials.
|
||||
|
||||
## Frontend Runtime Expectations
|
||||
|
||||
The current dialog behavior is fixed and should be preserved by backend responses.
|
||||
|
||||
- QR polling interval: every 3 seconds
|
||||
- QR expiration on frontend: after 100 checks
|
||||
- If QR creation fails, frontend falls back to direct login URL and session polling
|
||||
- After login, frontend closes the dialog and re-checks session
|
||||
|
||||
## Minimal Backend Checklist
|
||||
|
||||
- Implement all six `userauth` endpoints and the `usersession` sync endpoint.
|
||||
- Store sessions for 24 hours.
|
||||
- Store QR tokens for 5 minutes.
|
||||
- Protect `POST /userauth/qr/confirm` with `X-Bot-Secret`.
|
||||
- Set `userauth_session` cookie on confirmed QR poll and direct callback.
|
||||
- Return the exact session JSON shape.
|
||||
- Support credentialed CORS.
|
||||
|
||||
## Bot Checklist
|
||||
|
||||
- Handle `/start login_{token}` and call `POST /userauth/qr/confirm`.
|
||||
- Handle `/start auth_{callbackUrl}` and provide a button that opens the callback URL.
|
||||
- Send success and expiration messages back to the user.
|
||||
- Share the same `X-Bot-Secret` value with backend.
|
||||
551
docs/telegram-login-dialog.html
Normal file
@@ -0,0 +1,551 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Telegram Login Dialog</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg-page: linear-gradient(135deg, #f4f7fb 0%, #e8eef4 100%);
|
||||
--bg-card: #ffffff;
|
||||
--bg-hover: #f0f0f0;
|
||||
--text-primary: #1a1a1a;
|
||||
--text-secondary: #666666;
|
||||
--accent-color: #497671;
|
||||
--accent-light: rgba(73, 118, 113, 0.1);
|
||||
--telegram: #2aabee;
|
||||
--telegram-hover: #229ed9;
|
||||
--border: #e8e8e8;
|
||||
--shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-page);
|
||||
}
|
||||
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(320px, 448px) minmax(320px, 560px);
|
||||
gap: 32px;
|
||||
padding: 40px 32px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.panel {
|
||||
background: rgba(255, 255, 255, 0.72);
|
||||
border: 1px solid rgba(255, 255, 255, 0.8);
|
||||
border-radius: 28px;
|
||||
padding: 24px;
|
||||
box-shadow: 0 18px 50px rgba(38, 52, 73, 0.12);
|
||||
backdrop-filter: blur(14px);
|
||||
}
|
||||
|
||||
.info h1 {
|
||||
margin: 0 0 12px;
|
||||
font-size: 32px;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.info p {
|
||||
margin: 0 0 18px;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.state-switcher {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin: 20px 0 24px;
|
||||
}
|
||||
|
||||
.state-switcher button {
|
||||
border: 1px solid #cfd8e3;
|
||||
border-radius: 999px;
|
||||
background: #fff;
|
||||
color: var(--text-primary);
|
||||
padding: 10px 14px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: 0.2s ease;
|
||||
}
|
||||
|
||||
.state-switcher button.active {
|
||||
border-color: var(--accent-color);
|
||||
background: var(--accent-light);
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.api-grid {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.api-card {
|
||||
background: #fff;
|
||||
border: 1px solid #eef2f7;
|
||||
border-radius: 16px;
|
||||
padding: 14px 16px;
|
||||
}
|
||||
|
||||
.api-card strong {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.api-card code {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 999px;
|
||||
background: #f3f7fb;
|
||||
color: #21425f;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.api-card p {
|
||||
margin: 8px 0 0;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.login-overlay {
|
||||
position: relative;
|
||||
min-height: 700px;
|
||||
border-radius: 28px;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(4px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
animation: fadeIn 0.2s ease;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.login-dialog {
|
||||
position: relative;
|
||||
background: var(--bg-card);
|
||||
border-radius: 20px;
|
||||
padding: 32px 28px;
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
box-shadow: var(--shadow);
|
||||
animation: scaleIn 0.25s ease;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
background: #e0e0e0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.login-icon {
|
||||
margin: 0 auto 16px;
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
border-radius: 50%;
|
||||
background: var(--accent-light);
|
||||
color: var(--accent-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.login-dialog h2 {
|
||||
margin: 0 0 8px;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.login-desc {
|
||||
margin: 0 0 24px;
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.telegram-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
padding: 14px 24px;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
background: var(--telegram);
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.telegram-btn:hover {
|
||||
background: var(--telegram-hover);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(42, 171, 238, 0.3);
|
||||
}
|
||||
|
||||
.telegram-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.tg-icon {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.qr-section {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.qr-hint {
|
||||
margin: 0 0 12px;
|
||||
font-size: 13px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.qr-container {
|
||||
display: inline-flex;
|
||||
padding: 12px;
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.qr-container img {
|
||||
display: block;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.qr-loading {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 204px;
|
||||
height: 204px;
|
||||
}
|
||||
|
||||
.qr-loading .spinner,
|
||||
.login-status .spinner {
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
.qr-loading .spinner {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 3px solid #e0e0e0;
|
||||
border-top-color: var(--accent-color);
|
||||
}
|
||||
|
||||
.qr-expired {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
width: 204px;
|
||||
height: 204px;
|
||||
cursor: pointer;
|
||||
color: #999;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.qr-expired:hover {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.qr-expired span {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.login-note {
|
||||
margin: 16px 0 0;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.login-status {
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
padding: 16px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.login-status .spinner {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid #e0e0e0;
|
||||
border-top-color: var(--accent-color);
|
||||
}
|
||||
|
||||
.dialog-content[data-state="checking"] .login-status {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.dialog-content[data-state="checking"] .action-block,
|
||||
.dialog-content[data-state="loading"] .qr-ready,
|
||||
.dialog-content[data-state="loading"] .qr-expired,
|
||||
.dialog-content[data-state="expired"] .qr-ready,
|
||||
.dialog-content[data-state="expired"] .qr-loading,
|
||||
.dialog-content[data-state="error"] .qr-loading,
|
||||
.dialog-content[data-state="error"] .qr-expired,
|
||||
.dialog-content[data-state="checking"] .qr-section,
|
||||
.dialog-content[data-state="checking"] .login-note {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dialog-content[data-state="ready"] .qr-loading,
|
||||
.dialog-content[data-state="ready"] .qr-expired,
|
||||
.dialog-content[data-state="ready"] .qr-error,
|
||||
.dialog-content[data-state="loading"] .qr-ready,
|
||||
.dialog-content[data-state="loading"] .qr-expired,
|
||||
.dialog-content[data-state="loading"] .qr-error,
|
||||
.dialog-content[data-state="expired"] .qr-loading,
|
||||
.dialog-content[data-state="expired"] .qr-ready,
|
||||
.dialog-content[data-state="expired"] .qr-error,
|
||||
.dialog-content[data-state="error"] .qr-loading,
|
||||
.dialog-content[data-state="error"] .qr-expired {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dialog-content[data-state="error"] .qr-ready {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.metadata {
|
||||
margin-top: 22px;
|
||||
padding-top: 18px;
|
||||
border-top: 1px solid #e9edf2;
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.metadata ul {
|
||||
margin: 10px 0 0;
|
||||
padding-left: 18px;
|
||||
}
|
||||
|
||||
.metadata li + li {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes scaleIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@media (max-width: 980px) {
|
||||
.page {
|
||||
grid-template-columns: 1fr;
|
||||
padding: 24px 16px 32px;
|
||||
}
|
||||
|
||||
.login-overlay {
|
||||
min-height: 560px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.panel {
|
||||
border-radius: 22px;
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
.login-dialog {
|
||||
padding: 24px 20px;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.qr-container img {
|
||||
width: 140px;
|
||||
height: 140px;
|
||||
}
|
||||
|
||||
.qr-loading,
|
||||
.qr-expired {
|
||||
width: 164px;
|
||||
height: 164px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="page">
|
||||
<section class="panel info">
|
||||
<h1>Telegram Login Dialog</h1>
|
||||
<p>
|
||||
Standalone extraction of the current login popup: same layout, same visual states,
|
||||
same QR flow, but with reusable neutral endpoint names for moving into a separate repo.
|
||||
</p>
|
||||
|
||||
<div class="state-switcher" aria-label="Dialog state switcher">
|
||||
<button class="active" data-state-btn="ready" type="button">Ready</button>
|
||||
<button data-state-btn="loading" type="button">QR Loading</button>
|
||||
<button data-state-btn="checking" type="button">Checking</button>
|
||||
<button data-state-btn="expired" type="button">Expired</button>
|
||||
<button data-state-btn="error" type="button">Fallback</button>
|
||||
</div>
|
||||
|
||||
<div class="api-grid">
|
||||
<div class="api-card">
|
||||
<strong>Start QR session</strong>
|
||||
<code>POST /userauth/qr/create</code>
|
||||
<p>Returns a one-time token and Telegram deeplink for the QR image.</p>
|
||||
</div>
|
||||
<div class="api-card">
|
||||
<strong>Poll QR confirmation</strong>
|
||||
<code>GET /userauth/qr/poll?token=...</code>
|
||||
<p>Returns pending, confirmed, or expired. On confirmed, also returns the user session.</p>
|
||||
</div>
|
||||
<div class="api-card">
|
||||
<strong>Read current session</strong>
|
||||
<code>GET /userauth/session</code>
|
||||
<p>Cookie-based session lookup used for initial auth check and fallback polling.</p>
|
||||
</div>
|
||||
<div class="api-card">
|
||||
<strong>Sync cart after login</strong>
|
||||
<code>POST /usersession/{sessionId}</code>
|
||||
<p>Existing cart payload is preserved. Only the namespace is generalized for reuse.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="metadata">
|
||||
<strong>Behavior kept intact</strong>
|
||||
<ul>
|
||||
<li>Open Telegram directly from the primary button.</li>
|
||||
<li>Show QR immediately and poll every 3 seconds.</li>
|
||||
<li>Expire the QR after 100 checks and allow manual refresh.</li>
|
||||
<li>Re-check cookie session if QR creation fails.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<div class="login-overlay">
|
||||
<div class="login-dialog">
|
||||
<button class="close-btn" type="button" aria-label="Close dialog">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M18 6L6 18M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div class="dialog-content" data-state="ready" id="dialog-content">
|
||||
<div class="login-icon">
|
||||
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<h2>Login required</h2>
|
||||
<p class="login-desc">Please log in via Telegram to proceed with your order.</p>
|
||||
|
||||
<div class="login-status checking">
|
||||
<div class="spinner"></div>
|
||||
<span>Checking...</span>
|
||||
</div>
|
||||
|
||||
<div class="action-block">
|
||||
<button class="telegram-btn" type="button">
|
||||
<svg class="tg-icon" width="22" height="22" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z"></path>
|
||||
</svg>
|
||||
Log in with Telegram
|
||||
</button>
|
||||
|
||||
<div class="qr-section">
|
||||
<p class="qr-hint">Or scan the QR code</p>
|
||||
|
||||
<div class="qr-container qr-loading">
|
||||
<div class="spinner"></div>
|
||||
</div>
|
||||
|
||||
<div class="qr-container qr-ready">
|
||||
<img
|
||||
src="https://api.qrserver.com/v1/create-qr-code/?size=180x180&data=https%3A%2F%2Ft.me%2Fuserauth_bot%3Fstart%3Dlogin_sample_token"
|
||||
alt="QR Code"
|
||||
width="180"
|
||||
height="180"
|
||||
loading="eager"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="qr-container qr-expired" role="button" tabindex="0" aria-label="Refresh QR code">
|
||||
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M1 4v6h6M23 20v-6h-6"></path>
|
||||
<path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15"></path>
|
||||
</svg>
|
||||
<span>QR code expired. Click to refresh</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="login-note">You will be redirected back after login.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
const content = document.getElementById('dialog-content');
|
||||
const buttons = document.querySelectorAll('[data-state-btn]');
|
||||
|
||||
buttons.forEach((button) => {
|
||||
button.addEventListener('click', () => {
|
||||
const state = button.getAttribute('data-state-btn');
|
||||
content.setAttribute('data-state', state);
|
||||
|
||||
buttons.forEach((candidate) => candidate.classList.remove('active'));
|
||||
button.classList.add('active');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
24
files/changes.txt
Normal file
@@ -0,0 +1,24 @@
|
||||
bro please read carefully.
|
||||
this must be for all projects.
|
||||
At first here is the API for only auth process:
|
||||
https://users.vitanova.network:456/ping
|
||||
|
||||
and here are other stuff regarding it:
|
||||
//Logout user by sessionID
|
||||
r.DELETE("/users/sessions/:webSessionID", Logout)
|
||||
|
||||
//creates new session for user and send code for activation
|
||||
r.POST("/users/sessions", newWebSession)
|
||||
|
||||
r.GET("/users/sessions/:webSessionID", getWebSession)
|
||||
|
||||
As you got all the info, keep all the structure of api above.
|
||||
Now when the user clicks on login, we must show the QR and the link of the telegram bot, which we already have (btw sho me, so i see wheter it is true or not)
|
||||
and add a query param "?start=GUID" and generate a guid there.
|
||||
after we post it ad a websession, we have to get it like this " r.GET("/users/sessions/:webSessionID", getWebSession)" every 5 secs untill we get a status true
|
||||
if we will be loged in, we have to keep that webSessionID in the cookies for an hour
|
||||
1. if we open our website, we have to check the cookies and do a request for that websession
|
||||
2. if we are not loged in, then we will loge in one more time
|
||||
|
||||
|
||||
any questions?
|
||||
15
nginx.conf
@@ -36,9 +36,24 @@ server {
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
|
||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://telegram.org; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self' https:; frame-src https://telegram.org;" always;
|
||||
|
||||
# Brotli compression (if available)
|
||||
# brotli on;
|
||||
# brotli_comp_level 6;
|
||||
# brotli_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name lovero.store www.lovero.store;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:4202;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
"resources": {
|
||||
"files": [
|
||||
"/favicon.ico",
|
||||
"/index.csr.html",
|
||||
"/index.html",
|
||||
"/manifest.webmanifest",
|
||||
"/*.css",
|
||||
@@ -31,7 +30,9 @@
|
||||
{
|
||||
"name": "api-cache",
|
||||
"urls": [
|
||||
"/api/**"
|
||||
"/api/**",
|
||||
"https://api.dexarmarket.ru:445/**",
|
||||
"https://api.novo.market:444/**"
|
||||
],
|
||||
"cacheConfig": {
|
||||
"maxSize": 100,
|
||||
@@ -48,7 +49,7 @@
|
||||
"https://**/*.webp"
|
||||
],
|
||||
"cacheConfig": {
|
||||
"maxSize": 50,
|
||||
"maxSize": 200,
|
||||
"maxAge": "7d",
|
||||
"strategy": "performance"
|
||||
}
|
||||
|
||||
73
package-lock.json
generated
@@ -8,11 +8,15 @@
|
||||
"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",
|
||||
"primeicons": "^7.0.0",
|
||||
@@ -324,6 +328,21 @@
|
||||
"yarn": ">= 1.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/animations": {
|
||||
"version": "21.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-21.1.5.tgz",
|
||||
"integrity": "sha512-gsqHX8lCYV8cgVtHs0iLwrX8SVlmcjUF44l/xCc/jBC/TeKWRl2e6Jqrn1Wcd0NDlGiNsm+mYNyqMyy5/I7kjw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/core": "21.1.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/build": {
|
||||
"version": "21.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@angular/build/-/build-21.1.0.tgz",
|
||||
@@ -472,6 +491,22 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@angular/cdk": {
|
||||
"version": "21.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-21.1.5.tgz",
|
||||
"integrity": "sha512-AlQPgqe3LLwXCyrDwYSX3m/WKnl2ppCMW7Gb+7bJpIcpMdWYEpSOSQF318jXGYIysKg43YbdJ1tWhJWY/cbn3w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"parse5": "^8.0.0",
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": "^21.0.0 || ^22.0.0",
|
||||
"@angular/core": "^21.0.0 || ^22.0.0",
|
||||
"@angular/platform-browser": "^21.0.0 || ^22.0.0",
|
||||
"rxjs": "^6.5.3 || ^7.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/cli": {
|
||||
"version": "21.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-21.1.0.tgz",
|
||||
@@ -613,6 +648,23 @@
|
||||
"rxjs": "^6.5.3 || ^7.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/material": {
|
||||
"version": "21.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@angular/material/-/material-21.1.5.tgz",
|
||||
"integrity": "sha512-D6JvFulPvIKhPJ52prMV7DxwYMzcUpHar11ZcMb7r9WQzUfCS3FDPXfMAce5n3h+3kFccfmmGpnyBwqTlLPSig==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/cdk": "21.1.5",
|
||||
"@angular/common": "^21.0.0 || ^22.0.0",
|
||||
"@angular/core": "^21.0.0 || ^22.0.0",
|
||||
"@angular/forms": "^21.0.0 || ^22.0.0",
|
||||
"@angular/platform-browser": "^21.0.0 || ^22.0.0",
|
||||
"rxjs": "^6.5.3 || ^7.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/platform-browser": {
|
||||
"version": "21.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-21.0.6.tgz",
|
||||
@@ -635,6 +687,24 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/platform-browser-dynamic": {
|
||||
"version": "21.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-21.1.5.tgz",
|
||||
"integrity": "sha512-Pd8nPbJSIONnze1WS9wLBAtaFw4TYIH+ZGjKHS9G1E9l09tDWtHWyB7dY82Sc//Nc8iR4V7dcsbUmFjOJHThww==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": "21.1.5",
|
||||
"@angular/compiler": "21.1.5",
|
||||
"@angular/core": "21.1.5",
|
||||
"@angular/platform-browser": "21.1.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/router": {
|
||||
"version": "21.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@angular/router/-/router-21.0.6.tgz",
|
||||
@@ -7687,7 +7757,6 @@
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz",
|
||||
"integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"entities": "^6.0.0"
|
||||
@@ -7741,7 +7810,6 @@
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
|
||||
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
@@ -9512,3 +9580,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
13
package.json
@@ -5,22 +5,29 @@
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"dexar": "ng serve --configuration=development --port 4200",
|
||||
"novo": "ng serve --configuration=novo --port 4201",
|
||||
"novo": "ng serve --configuration=novo --port 4201 --proxy-config proxy.conf.novo.json",
|
||||
"start:dexar": "ng serve --configuration=development --port 4200",
|
||||
"start:novo": "ng serve --configuration=novo --port 4201",
|
||||
"build": "ng build",
|
||||
"build:dexar": "ng build --configuration=production",
|
||||
"build:novo": "ng build --configuration=novo-production",
|
||||
"watch": "ng build --watch --configuration development",
|
||||
"test": "ng test"
|
||||
"test": "ng test",
|
||||
"lavero": "ng serve --configuration=lavero --port 4202 --proxy-config proxy.conf.lavero.json",
|
||||
"start:lavero": "ng serve --configuration=lavero --port 4202",
|
||||
"build:lavero": "ng build --configuration=lavero-production"
|
||||
},
|
||||
"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",
|
||||
"primeicons": "^7.0.0",
|
||||
@@ -42,4 +49,4 @@
|
||||
"karma-jasmine-html-reporter": "~2.1.0",
|
||||
"typescript": "~5.9.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
11
proxy.conf.lavero.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"/api": {
|
||||
"target": "https://api.lovero.store:555",
|
||||
"secure": false,
|
||||
"changeOrigin": true,
|
||||
"pathRewrite": {
|
||||
"^/api": ""
|
||||
},
|
||||
"logLevel": "debug"
|
||||
}
|
||||
}
|
||||
11
proxy.conf.lavero.json.bak
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"/api": {
|
||||
"target": "https://api.lavero.store:555",
|
||||
"secure": false,
|
||||
"changeOrigin": true,
|
||||
"pathRewrite": {
|
||||
"^/api": ""
|
||||
},
|
||||
"logLevel": "debug"
|
||||
}
|
||||
}
|
||||
11
proxy.conf.novo.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"/api": {
|
||||
"target": "https://api.novo.market:444",
|
||||
"secure": false,
|
||||
"changeOrigin": true,
|
||||
"pathRewrite": {
|
||||
"^/api": ""
|
||||
},
|
||||
"logLevel": "debug"
|
||||
}
|
||||
}
|
||||
1
public/assets/images/dexar-logo-small.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg data-name="Слой 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 108.43 100.53"><path d="M101.66 15.71c-4.16-.3-8.34-.35-12.51-.46-3.85-.1-7.69-.15-11.54-.21-9.14-.15-18.29-.32-27.44-.44-7.84-.11-15.68-.18-23.53-.21-.83 0-1.17-.3-1.33-1.01-.81-3.51-1.64-7.02-2.44-10.53-.31-1.33-1.42-2.36-2.68-2.41C18.6.37 17.01.27 15.42.23 11.37.13 7.31.06 3.25 0 1.27-.03 0 1.13 0 2.92 0 4.7 1.38 6.06 3.26 6.09c4.28.08 8.56.17 12.84.2.89 0 1.34.26 1.56 1.17 1.2 4.99 2.47 9.95 3.69 14.93 2.3 9.38 4.58 18.77 6.88 28.15 1.11 4.54 2.21 9.07 3.36 13.6.28 1.11.15 1.73-1.02 2.31-3.76 1.85-5.33 5.91-4.45 9.93.91 4.11 4.58 6.95 9.07 7.02h1.38c-2.97 1.75-4.68 4.13-4.95 7.42-.27 3.32 1.42 5.8 3.95 7.96-4.85.74-6.27.75-9.41 1.23.8.23 1.31.11 1.98.12 4.46.05 8.92.17 13.37.01 4.94-.17 8.86-5.16 7.57-10.63-.63-2.66-2.21-4.7-5.04-5.9h39.73c-2.87 1.74-4.53 4.14-4.85 7.36-.32 3.29 1.08 5.9 3.89 8.11-9.01.38-17.71.47-26.34 1.09l30.02.35c1.84-.07 3.73.03 5.49-.97 4.82-2.75 6.23-8.3 3.26-12.73-.84-1.26-2.17-2.19-3.21-3.2 1.3 0 2.83.03 4.35 0 1.66-.04 2.81-1.34 2.78-3.08-.02-1.56-1.25-2.77-2.82-2.79-6.68-.07-13.36-.18-20.04-.2-9.37-.04-18.74-.01-28.11-.02H35.44c-2.17 0-3.72-1.47-3.62-3.37.09-1.79 1.73-3.16 3.83-3.15 8.39.04 16.77.1 25.16.13 8.61.04 17.21.06 25.82.07.97 0 1.94-.09 2.9-.21 3.83-.52 6.67-3.16 7.69-6.89 1.84-6.75 3.76-13.47 5.65-20.21 1.36-4.84 2.79-9.66 4.08-14.52.59-2.2 1.13-4.45 1.32-6.7.29-3.53-2.89-6.7-6.6-6.96Zm-13.8 71.86c2.2-.07 4.11 1.95 4.1 4.15-.18 2.67-1.84 3.97-4.24 4.07-2.17.08-4.06-1.98-4.03-4.18.03-2.3 1.72-3.96 4.17-4.04m-47.43-.03c2.45-.06 4.19 1.8 4.15 4.03-.05 2.63-2.02 3.98-4.06 4.02-2.23.04-4.05-1.86-4.15-4.07-.1-2.22 2.05-4.07 4.06-3.98m30.45-67.01v12.33c-1.89 0-3.69.02-5.48 0-3.15-.05-6.3-.18-9.45-.18-.98 0-1.2-.35-1.27-1.24-.22-2.76-.55-5.5-.82-8.25-.09-.93-.15-1.86-.21-2.66zm-.14 17.64v12.64c-4.47 0-8.88.02-13.29-.04-.26 0-.71-.63-.75-1.01-.35-3.18-.62-6.37-.91-9.55v-.11c-.15-1.98-.15-1.95 1.83-1.94 4.35.02 8.69 0 13.13 0Zm-41.31-8.1c-.62-2.71-1.26-5.41-1.88-8.12-.15-.65-.27-1.32-.43-2.1 7.05.12 13.97.24 21.04.37.41 4.15.81 8.23 1.19 12.14H32.48c-.11 0-.22-.02-.32-.03-2.25-.14-2.24-.14-2.73-2.26m5.02 20.67c-1.01-4.24-2.02-8.49-3.03-12.7h18.64c.47 4.3.93 8.46 1.39 12.7H34.44Zm57.74 8.57c-.3 1.1-.54 2.23-.89 3.31-.51 1.58-1.87 2.54-3.47 2.54-16.08-.01-32.17-.04-48.25 0-1.26 0-1.71-.36-1.95-1.57-.44-2.27-1.1-4.5-1.65-6.75-.04-.17 0-.35 0-.67l56.99.39c-.29 1.03-.53 1.89-.77 2.76Zm4.75-16.54c-.7 2.51-1.41 5.02-2.17 7.51-.09.29-.56.65-.85.65q-8.385.06-16.77 0c-.29 0-.83-.42-.84-.64-.05-3.87-.04-7.75-.04-11.6h21.71c-.38 1.5-.69 2.8-1.05 4.08Zm5.38-19.31c-.83 2.95-1.7 5.89-2.49 8.85-.19.73-.47 1.01-1.23.99-6.45-.16-12.91-.28-19.36-.41-.94-.02-1.88 0-2.97 0 0-3.91.01-7.67 0-11.43 0-.76.45-.78 1-.77 2.83.08 5.65.17 8.48.22 4.93.09 9.86.15 14.79.22 1.49.02 2.18.94 1.78 2.34Z" style="fill:#477470;stroke-width:0"/></svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 727 B After Width: | Height: | Size: 6.3 KiB |
BIN
public/assets/images/footer_bg.webp
Normal file
|
After Width: | Height: | Size: 9.9 KiB |
BIN
public/assets/images/footer_bg_mobile.webp
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
public/assets/images/hero_background_desktop.webp
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
public/assets/images/hero_background_mobile.webp
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
public/assets/images/lavero/lavero-logo.png
Normal file
|
After Width: | Height: | Size: 306 KiB |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><linearGradient id="a" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" style="stop-color:#667eea;stop-opacity:1"/><stop offset="100%" style="stop-color:#764ba2;stop-opacity:1"/></linearGradient></defs><path d="m20 35-5 50q0 10 10 10h50q10 0 10-10l-5-50Z" fill="url(#a)" stroke="#4a5cd6" stroke-width="2"/><path d="M30 35q0-20 20-20t20 20" fill="none" stroke="#4a5cd6" stroke-width="3" stroke-linecap="round"/><circle cx="70" cy="25" r="4" fill="gold"/><circle cx="30" cy="70" r="3" fill="#fff" opacity=".7"/></svg>
|
||||
<svg data-name="Слой 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 108.43 100.53"><path d="M101.66 15.71c-4.16-.3-8.34-.35-12.51-.46-3.85-.1-7.69-.15-11.54-.21-9.14-.15-18.29-.32-27.44-.44-7.84-.11-15.68-.18-23.53-.21-.83 0-1.17-.3-1.33-1.01-.81-3.51-1.64-7.02-2.44-10.53-.31-1.33-1.42-2.36-2.68-2.41C18.6.37 17.01.27 15.42.23 11.37.13 7.31.06 3.25 0 1.27-.03 0 1.13 0 2.92 0 4.7 1.38 6.06 3.26 6.09c4.28.08 8.56.17 12.84.2.89 0 1.34.26 1.56 1.17 1.2 4.99 2.47 9.95 3.69 14.93 2.3 9.38 4.58 18.77 6.88 28.15 1.11 4.54 2.21 9.07 3.36 13.6.28 1.11.15 1.73-1.02 2.31-3.76 1.85-5.33 5.91-4.45 9.93.91 4.11 4.58 6.95 9.07 7.02h1.38c-2.97 1.75-4.68 4.13-4.95 7.42-.27 3.32 1.42 5.8 3.95 7.96-4.85.74-6.27.75-9.41 1.23.8.23 1.31.11 1.98.12 4.46.05 8.92.17 13.37.01 4.94-.17 8.86-5.16 7.57-10.63-.63-2.66-2.21-4.7-5.04-5.9h39.73c-2.87 1.74-4.53 4.14-4.85 7.36-.32 3.29 1.08 5.9 3.89 8.11-9.01.38-17.71.47-26.34 1.09l30.02.35c1.84-.07 3.73.03 5.49-.97 4.82-2.75 6.23-8.3 3.26-12.73-.84-1.26-2.17-2.19-3.21-3.2 1.3 0 2.83.03 4.35 0 1.66-.04 2.81-1.34 2.78-3.08-.02-1.56-1.25-2.77-2.82-2.79-6.68-.07-13.36-.18-20.04-.2-9.37-.04-18.74-.01-28.11-.02H35.44c-2.17 0-3.72-1.47-3.62-3.37.09-1.79 1.73-3.16 3.83-3.15 8.39.04 16.77.1 25.16.13 8.61.04 17.21.06 25.82.07.97 0 1.94-.09 2.9-.21 3.83-.52 6.67-3.16 7.69-6.89 1.84-6.75 3.76-13.47 5.65-20.21 1.36-4.84 2.79-9.66 4.08-14.52.59-2.2 1.13-4.45 1.32-6.7.29-3.53-2.89-6.7-6.6-6.96Zm-13.8 71.86c2.2-.07 4.11 1.95 4.1 4.15-.18 2.67-1.84 3.97-4.24 4.07-2.17.08-4.06-1.98-4.03-4.18.03-2.3 1.72-3.96 4.17-4.04m-47.43-.03c2.45-.06 4.19 1.8 4.15 4.03-.05 2.63-2.02 3.98-4.06 4.02-2.23.04-4.05-1.86-4.15-4.07-.1-2.22 2.05-4.07 4.06-3.98m30.45-67.01v12.33c-1.89 0-3.69.02-5.48 0-3.15-.05-6.3-.18-9.45-.18-.98 0-1.2-.35-1.27-1.24-.22-2.76-.55-5.5-.82-8.25-.09-.93-.15-1.86-.21-2.66zm-.14 17.64v12.64c-4.47 0-8.88.02-13.29-.04-.26 0-.71-.63-.75-1.01-.35-3.18-.62-6.37-.91-9.55v-.11c-.15-1.98-.15-1.95 1.83-1.94 4.35.02 8.69 0 13.13 0Zm-41.31-8.1c-.62-2.71-1.26-5.41-1.88-8.12-.15-.65-.27-1.32-.43-2.1 7.05.12 13.97.24 21.04.37.41 4.15.81 8.23 1.19 12.14H32.48c-.11 0-.22-.02-.32-.03-2.25-.14-2.24-.14-2.73-2.26m5.02 20.67c-1.01-4.24-2.02-8.49-3.03-12.7h18.64c.47 4.3.93 8.46 1.39 12.7H34.44Zm57.74 8.57c-.3 1.1-.54 2.23-.89 3.31-.51 1.58-1.87 2.54-3.47 2.54-16.08-.01-32.17-.04-48.25 0-1.26 0-1.71-.36-1.95-1.57-.44-2.27-1.1-4.5-1.65-6.75-.04-.17 0-.35 0-.67l56.99.39c-.29 1.03-.53 1.89-.77 2.76Zm4.75-16.54c-.7 2.51-1.41 5.02-2.17 7.51-.09.29-.56.65-.85.65q-8.385.06-16.77 0c-.29 0-.83-.42-.84-.64-.05-3.87-.04-7.75-.04-11.6h21.71c-.38 1.5-.69 2.8-1.05 4.08Zm5.38-19.31c-.83 2.95-1.7 5.89-2.49 8.85-.19.73-.47 1.01-1.23.99-6.45-.16-12.91-.28-19.36-.41-.94-.02-1.88 0-2.97 0 0-3.91.01-7.67 0-11.43 0-.76.45-.78 1-.77 2.83.08 5.65.17 8.48.22 4.93.09 9.86.15 14.79.22 1.49.02 2.18.94 1.78 2.34Z" style="fill:#477470;stroke-width:0"/></svg>
|
||||
|
Before Width: | Height: | Size: 588 B After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 547 B |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/service-worker/config/schema.json",
|
||||
"name": "Novo Market - Интернет-магазин",
|
||||
"short_name": "Novo",
|
||||
"description": "Novo Market - ваш онлайн магазин качественных товаров с доставкой",
|
||||
@@ -12,34 +11,10 @@
|
||||
"categories": ["shopping", "lifestyle"],
|
||||
"icons": [
|
||||
{
|
||||
"src": "icons/icon-72x72.png",
|
||||
"sizes": "72x72",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable any"
|
||||
},
|
||||
{
|
||||
"src": "icons/icon-96x96.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable any"
|
||||
},
|
||||
{
|
||||
"src": "icons/icon-128x128.png",
|
||||
"sizes": "128x128",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable any"
|
||||
},
|
||||
{
|
||||
"src": "icons/icon-144x144.png",
|
||||
"sizes": "144x144",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable any"
|
||||
},
|
||||
{
|
||||
"src": "icons/icon-152x152.png",
|
||||
"sizes": "152x152",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable any"
|
||||
"src": "assets/images/novo-favicon.svg",
|
||||
"sizes": "any",
|
||||
"type": "image/svg+xml",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "icons/icon-192x192.png",
|
||||
@@ -47,12 +22,6 @@
|
||||
"type": "image/png",
|
||||
"purpose": "maskable any"
|
||||
},
|
||||
{
|
||||
"src": "icons/icon-384x384.png",
|
||||
"sizes": "384x384",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable any"
|
||||
},
|
||||
{
|
||||
"src": "icons/icon-512x512.png",
|
||||
"sizes": "512x512",
|
||||
|
||||
@@ -11,34 +11,10 @@
|
||||
"categories": ["shopping", "marketplace"],
|
||||
"icons": [
|
||||
{
|
||||
"src": "icons/icon-72x72.png",
|
||||
"sizes": "72x72",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable any"
|
||||
},
|
||||
{
|
||||
"src": "icons/icon-96x96.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable any"
|
||||
},
|
||||
{
|
||||
"src": "icons/icon-128x128.png",
|
||||
"sizes": "128x128",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable any"
|
||||
},
|
||||
{
|
||||
"src": "icons/icon-144x144.png",
|
||||
"sizes": "144x144",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable any"
|
||||
},
|
||||
{
|
||||
"src": "icons/icon-152x152.png",
|
||||
"sizes": "152x152",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable any"
|
||||
"src": "assets/images/dexar-favicon.svg",
|
||||
"sizes": "any",
|
||||
"type": "image/svg+xml",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "icons/icon-192x192.png",
|
||||
@@ -46,12 +22,6 @@
|
||||
"type": "image/png",
|
||||
"purpose": "maskable any"
|
||||
},
|
||||
{
|
||||
"src": "icons/icon-384x384.png",
|
||||
"sizes": "384x384",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable any"
|
||||
},
|
||||
{
|
||||
"src": "icons/icon-512x512.png",
|
||||
"sizes": "512x512",
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection, isDevMode } from '@angular/core';
|
||||
import { PreloadAllModules, provideRouter, withPreloading, withInMemoryScrolling } from '@angular/router';
|
||||
import { provideRouter, withInMemoryScrolling } from '@angular/router';
|
||||
import { provideHttpClient, withInterceptors } from '@angular/common/http';
|
||||
|
||||
import { routes } from './app.routes';
|
||||
import { cacheInterceptor } from './interceptors/cache.interceptor';
|
||||
import { apiHeadersInterceptor } from './interceptors/api-headers.interceptor';
|
||||
import { mockDataInterceptor } from './interceptors/mock-data.interceptor';
|
||||
import { provideServiceWorker } from '@angular/service-worker';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
@@ -11,15 +13,15 @@ export const appConfig: ApplicationConfig = {
|
||||
provideBrowserGlobalErrorListeners(),
|
||||
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||
provideRouter(
|
||||
routes,
|
||||
withPreloading(PreloadAllModules),
|
||||
routes,
|
||||
withInMemoryScrolling({ scrollPositionRestoration: 'top' })
|
||||
),
|
||||
provideHttpClient(
|
||||
withInterceptors([cacheInterceptor])
|
||||
), provideServiceWorker('ngsw-worker.js', {
|
||||
enabled: !isDevMode(),
|
||||
registrationStrategy: 'registerWhenStable:30000'
|
||||
})
|
||||
withInterceptors([mockDataInterceptor, apiHeadersInterceptor, cacheInterceptor])
|
||||
),
|
||||
provideServiceWorker('ngsw-worker.js', {
|
||||
enabled: !isDevMode(),
|
||||
registrationStrategy: 'registerWhenStable:30000'
|
||||
})
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
@if (checkingServer()) {
|
||||
<div class="server-check-overlay">
|
||||
<div class="server-check-content">
|
||||
<div class="spinner-large"></div>
|
||||
<h2>Проверка соединения с сервером...</h2>
|
||||
</div>
|
||||
<div class="spinner-large"></div>
|
||||
<p>{{ 'app.connecting' | translate }}</p>
|
||||
</div>
|
||||
} @else if (!serverAvailable()) {
|
||||
<div class="server-error-overlay">
|
||||
<div class="server-error-content">
|
||||
<div class="error-icon">⚠️</div>
|
||||
<h1>Извините, возникла проблема</h1>
|
||||
<p>Не удается подключиться к серверу. Пожалуйста, проверьте подключение к интернету или попробуйте позже.</p>
|
||||
<button class="retry-btn" (click)="retryConnection()">Попробовать снова</button>
|
||||
</div>
|
||||
<div class="error-icon">⚠️</div>
|
||||
<h2>{{ 'app.serverUnavailable' | translate }}</h2>
|
||||
<p>{{ 'app.serverError' | translate }}</p>
|
||||
<button class="retry-btn" (click)="retryConnection()">{{ 'app.retryConnection' | translate }}</button>
|
||||
</div>
|
||||
} @else {
|
||||
} @else {
|
||||
<app-header></app-header>
|
||||
<main class="main-content">
|
||||
@if (!isHomePage()) {
|
||||
<app-back-button />
|
||||
}
|
||||
<router-outlet></router-outlet>
|
||||
</main>
|
||||
<app-footer></app-footer>
|
||||
<!-- <app-telegram-login /> -->
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Routes } from '@angular/router';
|
||||
import { brandInfoRoutes, brandLegalRoutes } from './brands/brand-routes';
|
||||
import { languageGuard } from './guards/language.guard';
|
||||
|
||||
// Core routes (same across all brands)
|
||||
const coreRoutes: Routes = [
|
||||
@@ -29,13 +30,18 @@ const coreRoutes: Routes = [
|
||||
}
|
||||
];
|
||||
|
||||
// Combine core routes with brand-specific routes
|
||||
// All routes sit under a :lang prefix (e.g. /ru/cart, /en/item/5)
|
||||
export const routes: Routes = [
|
||||
...coreRoutes,
|
||||
...brandInfoRoutes,
|
||||
...brandLegalRoutes,
|
||||
{
|
||||
path: '**',
|
||||
redirectTo: ''
|
||||
}
|
||||
path: ':lang',
|
||||
canActivate: [languageGuard],
|
||||
children: [
|
||||
...coreRoutes,
|
||||
...brandInfoRoutes,
|
||||
...brandLegalRoutes,
|
||||
{ path: '**', redirectTo: '' }
|
||||
]
|
||||
},
|
||||
// URLs without a language prefix → redirect to default language
|
||||
{ path: '**', redirectTo: 'ru' }
|
||||
];
|
||||
@@ -7,81 +7,58 @@
|
||||
|
||||
.server-check-overlay,
|
||||
.server-error-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: #f8f9fa;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.server-check-content,
|
||||
.server-error-content {
|
||||
min-height: 100vh;
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
max-width: 500px;
|
||||
padding: 2rem;
|
||||
background: var(--surface-ground, #f8f9fa);
|
||||
color: var(--text-color, #333);
|
||||
}
|
||||
|
||||
.spinner-large {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border: 6px solid #f3f3f3;
|
||||
border-top: 6px solid var(--primary-color);
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border: 4px solid var(--surface-border, #dee2e6);
|
||||
border-top-color: var(--primary-color, #007bff);
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 24px;
|
||||
animation: spin 0.8s linear infinite;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.server-check-content h2 {
|
||||
color: #333;
|
||||
font-size: 1.5rem;
|
||||
margin: 0;
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.error-icon {
|
||||
font-size: 5rem;
|
||||
margin-bottom: 20px;
|
||||
font-size: 3rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.server-error-content h1 {
|
||||
font-size: 2rem;
|
||||
color: #333;
|
||||
margin: 0 0 16px 0;
|
||||
.server-error-overlay h2 {
|
||||
margin: 0 0 0.5rem;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.server-error-content p {
|
||||
font-size: 1.1rem;
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
margin: 0 0 32px 0;
|
||||
.server-error-overlay p {
|
||||
margin: 0 0 1.5rem;
|
||||
opacity: 0.7;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.retry-btn {
|
||||
padding: 14px 32px;
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
padding: 0.75rem 2rem;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
background: var(--primary-color, #007bff);
|
||||
color: #fff;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: var(--primary-hover);
|
||||
}
|
||||
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { App } from './app';
|
||||
import { provideRouter } from '@angular/router';
|
||||
|
||||
describe('App', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [App],
|
||||
providers: [provideRouter([])]
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
it('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(App);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
});
|
||||
120
src/app/app.ts
@@ -1,93 +1,95 @@
|
||||
|
||||
import { Component, OnInit, OnDestroy, signal, ApplicationRef } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { RouterOutlet } from '@angular/router';
|
||||
import { Component, OnInit, signal, ApplicationRef, inject, DestroyRef } from '@angular/core';
|
||||
import { Router, RouterOutlet, NavigationEnd } from '@angular/router';
|
||||
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 { ApiService } from './services';
|
||||
import { Subscription, interval, concat } from 'rxjs';
|
||||
import { first } from 'rxjs/operators';
|
||||
import { interval, concat } from 'rxjs';
|
||||
import { filter, first } from 'rxjs/operators';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { environment } from '../environments/environment';
|
||||
import { SwUpdate } from '@angular/service-worker';
|
||||
import { TranslatePipe } from './i18n/translate.pipe';
|
||||
import { TranslateService } from './i18n/translate.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
imports: [RouterOutlet, HeaderComponent, FooterComponent, CommonModule],
|
||||
imports: [RouterOutlet, HeaderComponent, FooterComponent, BackButtonComponent, TranslatePipe],
|
||||
templateUrl: './app.html',
|
||||
styleUrl: './app.scss'
|
||||
})
|
||||
export class App implements OnInit, OnDestroy {
|
||||
export class App implements OnInit {
|
||||
protected title = environment.brandName;
|
||||
serverAvailable = signal(true);
|
||||
isHomePage = signal(true);
|
||||
checkingServer = signal(true);
|
||||
private pingSubscription?: Subscription;
|
||||
private updateSubscription?: Subscription;
|
||||
serverAvailable = signal(false);
|
||||
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private titleService: Title,
|
||||
private swUpdate: SwUpdate,
|
||||
private appRef: ApplicationRef
|
||||
) {}
|
||||
private destroyRef = inject(DestroyRef);
|
||||
private apiService = inject(ApiService);
|
||||
private titleService = inject(Title);
|
||||
private swUpdate = inject(SwUpdate);
|
||||
private appRef = inject(ApplicationRef);
|
||||
private router = inject(Router);
|
||||
private i18n = inject(TranslateService);
|
||||
|
||||
ngOnInit(): void {
|
||||
// Устанавливаем заголовок страницы в зависимости от бренда
|
||||
this.titleService.setTitle(`${environment.brandFullName} - Маркетплейс товаров и услуг`);
|
||||
this.titleService.setTitle(`${environment.brandFullName} - ${this.i18n.t('app.pageTitle')}`);
|
||||
this.checkServerHealth();
|
||||
this.setupAutoUpdates();
|
||||
|
||||
// Track route changes to show/hide back button
|
||||
this.router.events
|
||||
.pipe(
|
||||
filter(event => event instanceof NavigationEnd),
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
)
|
||||
.subscribe((event) => {
|
||||
const navEnd = event as NavigationEnd;
|
||||
const url = navEnd.urlAfterRedirects || navEnd.url;
|
||||
// Home pages: /ru, /en, /hy (with or without trailing slash)
|
||||
this.isHomePage.set(/^\/[a-z]{2}\/?$/.test(url) || url === '/' || url === '');
|
||||
});
|
||||
}
|
||||
|
||||
checkServerHealth(): void {
|
||||
this.pingSubscription = this.apiService.ping().subscribe({
|
||||
next: (response) => {
|
||||
// Server is available
|
||||
this.serverAvailable.set(true);
|
||||
this.checkingServer.set(false);
|
||||
},
|
||||
error: (err) => {
|
||||
console.error('Server health check failed:', err);
|
||||
// Allow app to continue even if server is unreachable
|
||||
this.serverAvailable.set(true);
|
||||
this.checkingServer.set(false);
|
||||
}
|
||||
});
|
||||
private checkServerHealth(): void {
|
||||
this.checkingServer.set(true);
|
||||
this.apiService.ping()
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe({
|
||||
next: () => {
|
||||
this.serverAvailable.set(true);
|
||||
this.checkingServer.set(false);
|
||||
},
|
||||
error: () => {
|
||||
this.serverAvailable.set(false);
|
||||
this.checkingServer.set(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setupAutoUpdates(): void {
|
||||
retryConnection(): void {
|
||||
this.checkServerHealth();
|
||||
}
|
||||
|
||||
private setupAutoUpdates(): void {
|
||||
if (!this.swUpdate.isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for updates every 6 hours
|
||||
const appIsStable$ = this.appRef.isStable.pipe(first(isStable => isStable === true));
|
||||
const every6Hours$ = interval(6 * 60 * 60 * 1000);
|
||||
const checkInterval$ = concat(appIsStable$, every6Hours$);
|
||||
|
||||
this.updateSubscription = checkInterval$.subscribe(async () => {
|
||||
try {
|
||||
await this.swUpdate.checkForUpdate();
|
||||
} catch (err) {
|
||||
console.error('Update check failed:', err);
|
||||
}
|
||||
});
|
||||
|
||||
// Silently activate updates when ready
|
||||
this.swUpdate.versionUpdates.subscribe(event => {
|
||||
if (event.type === 'VERSION_READY') {
|
||||
// Update will activate on next navigation/reload automatically
|
||||
console.log('New app version ready');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.pingSubscription?.unsubscribe();
|
||||
this.updateSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
retryConnection(): void {
|
||||
this.checkingServer.set(true);
|
||||
this.checkServerHealth();
|
||||
checkInterval$
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe(async () => {
|
||||
try {
|
||||
await this.swUpdate.checkForUpdate();
|
||||
} catch (err) {
|
||||
console.error('Update check failed:', err);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
49
src/app/brands/brand-routes.lavero.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
// Lavero brand routes
|
||||
// Loaded via angular.json fileReplacements when building for novo
|
||||
import { Routes } from '@angular/router';
|
||||
|
||||
export const brandInfoRoutes: Routes = [
|
||||
{
|
||||
path: 'about',
|
||||
loadComponent: () => import('./lavero/pages/info/about/about.component').then(m => m.AboutLaveroComponent)
|
||||
},
|
||||
{
|
||||
path: 'contacts',
|
||||
loadComponent: () => import('./lavero/pages/info/contacts/contacts.component').then(m => m.ContactsLaveroComponent)
|
||||
},
|
||||
{
|
||||
path: 'faq',
|
||||
loadComponent: () => import('./lavero/pages/info/faq/faq.component').then(m => m.FaqLaveroComponent)
|
||||
},
|
||||
{
|
||||
path: 'delivery',
|
||||
loadComponent: () => import('./lavero/pages/info/delivery/delivery.component').then(m => m.DeliveryLaveroComponent)
|
||||
},
|
||||
{
|
||||
path: 'guarantee',
|
||||
loadComponent: () => import('./lavero/pages/info/guarantee/guarantee.component').then(m => m.GuaranteeLaveroComponent)
|
||||
}
|
||||
];
|
||||
|
||||
export const brandLegalRoutes: Routes = [
|
||||
{
|
||||
path: 'company-details',
|
||||
loadComponent: () => import('./lavero/pages/legal/company-details/company-details.component').then(m => m.CompanyDetailsLaveroComponent)
|
||||
},
|
||||
{
|
||||
path: 'payment-terms',
|
||||
loadComponent: () => import('./lavero/pages/legal/payment-terms/payment-terms.component').then(m => m.PaymentTermsLaveroComponent)
|
||||
},
|
||||
{
|
||||
path: 'return-policy',
|
||||
loadComponent: () => import('./lavero/pages/legal/return-policy/return-policy.component').then(m => m.ReturnPolicyLaveroComponent)
|
||||
},
|
||||
{
|
||||
path: 'public-offer',
|
||||
loadComponent: () => import('./lavero/pages/legal/public-offer/public-offer.component').then(m => m.PublicOfferLaveroComponent)
|
||||
},
|
||||
{
|
||||
path: 'privacy-policy',
|
||||
loadComponent: () => import('./lavero/pages/legal/privacy-policy/privacy-policy.component').then(m => m.PrivacyPolicyLaveroComponent)
|
||||
}
|
||||
];
|
||||
@@ -1,258 +0,0 @@
|
||||
<div class="legal-page">
|
||||
<div class="legal-container">
|
||||
<h1>ПОЛИТИКА В ОТНОШЕНИИ ОБРАБОТКИ ПЕРСОНАЛЬНЫХ ДАННЫХ</h1>
|
||||
|
||||
<section class="legal-section">
|
||||
<h2>1. ОБЩИЕ ПОЛОЖЕНИЯ</h2>
|
||||
|
||||
<p>1.1. Настоящая политика ООО "ИНТ ФИН ЛОГИСТИК" (ИНН 9909697628), именуемого далее как «Оператор», описывает порядок обработки персональных данных и направлена на защиту прав и законных интересов субъектов данных. Документ разработан в соответствии с Федеральным законом №152-ФЗ от 27 июля 2006 года «О персональных данных».</p>
|
||||
|
||||
<p>1.2. Политика определяет порядок и меры обеспечения безопасности обработки персональных данных на сайте <a href="https://dexarmarket.ru">https://dexarmarket.ru</a>, ставя своей задачей защитить права и свободы человека и гражданина, включая право на неприкосновенность частной жизни, личную и семейную тайны.</p>
|
||||
|
||||
<p>1.3. Документ охватывает все процессы, осуществляемые Оператором, касающиеся обработки персональных данных.</p>
|
||||
|
||||
<p>1.4. Политика обязательна для изучения и выполнения всеми лицами, допущенными к обработке персональных данных.</p>
|
||||
|
||||
<p>1.5. Действует в отношении всех действий, связанных с обработкой персональных данных на сайте <a href="https://dexarmarket.ru">https://dexarmarket.ru</a> и в информационных системах Оператора.</p>
|
||||
|
||||
<p>1.6. Пользователь, оформляющий заказ, открывающий личный кабинет или иным образом взаимодействующий с Оператором, выражает согласие на обработку своих персональных данных в соответствии с Политикой и законодательством РФ. Продолжительное использование сайта свидетельствует о согласии с Положениями Политики. Пользователь, не готовый согласиться с условиями, должен воздержаться от использования ресурса.</p>
|
||||
|
||||
<p><strong>Дополнительно:</strong> Настоящая Политика распространяется на персональные данные, собранные как до вступления документа в силу, так и после.</p>
|
||||
</section>
|
||||
|
||||
<section class="legal-section">
|
||||
<h2>2. ТЕРМИНЫ И ОПРЕДЕЛЕНИЯ</h2>
|
||||
|
||||
<p>В данной Политике используются следующие основные термины и определения:</p>
|
||||
|
||||
<p><strong>Персональные данные (ПДн)</strong> — любая информация, прямо или косвенно относящаяся к определённому физическому лицу (субъекту персональных данных).</p>
|
||||
|
||||
<p><strong>Информационная система персональных данных (ИСПДн)</strong> — совокупность персональных данных, хранимых в базах данных, а также технологий и средств для их обработки.</p>
|
||||
|
||||
<p><strong>Автоматизированная обработка ПДн</strong> — обработка данных с использованием компьютерных средств.</p>
|
||||
|
||||
<p><strong>Блокировка ПДн</strong> — временная приостановка обработки данных (за исключением случаев уточнения данных).</p>
|
||||
|
||||
<p><strong>Обезличивание ПДн</strong> — действия, ведущие к невозможности установления принадлежности данных конкретному лицу без дополнительной информации.</p>
|
||||
|
||||
<p><strong>Интернет-сайт (Сайт)</strong> — автоматизированная информационная система, доступная в сети Интернет по адресу: <a href="https://dexarmarket.ru">https://dexarmarket.ru</a>.</p>
|
||||
|
||||
<p><strong>Обработка ПДн</strong> — любые действия с персональными данными, включая сбор, запись, хранение, обновление, использование, передачу, уничтожение и другие операции.</p>
|
||||
|
||||
<p><strong>Оператор</strong> — государственный или частный орган, самостоятельно или совместно организующий обработку персональных данных.</p>
|
||||
|
||||
<p><strong>Предоставление ПДн</strong> — передача данных определённому лицу или группе лиц.</p>
|
||||
|
||||
<p><strong>Распространение ПДн</strong> — открытие данных неопределённому кругу лиц, включая публикацию в СМИ или сети Интернет.</p>
|
||||
|
||||
<p><strong>Трансграничная передача ПДн</strong> — передача данных зарубежным властям, компаниям или физическим лицам.</p>
|
||||
|
||||
<p><strong>Уничтожение ПДн</strong> — действия, приводящие к утрате возможности восстановления данных или уничтожения материальных носителей.</p>
|
||||
|
||||
<p><strong>Субъект ПДн</strong> — физическое лицо, чья информация обрабатывается.</p>
|
||||
|
||||
<p><strong>Конфиденциальность ПДн</strong> — обязанность Оператора защищать данные от распространения без согласия субъекта или законного основания.</p>
|
||||
|
||||
<p><strong>Продавец (Исполнитель)</strong> — лицо, предлагающее товары или услуги на сайте <a href="https://dexarmarket.ru">https://dexarmarket.ru</a>.</p>
|
||||
|
||||
<p><strong>Пользователь</strong> — лицо, посещающее или использующее ресурсы, управляемые Оператором, включая сайт <a href="https://dexarmarket.ru">https://dexarmarket.ru</a>.</p>
|
||||
|
||||
<p><strong>Заказ</strong> — оформленный Пользователем заказ товаров или услуг на сайте.</p>
|
||||
|
||||
<p><strong>Файлы cookie</strong> — небольшие файлы, сохраняемые на устройстве пользователя для запоминания предпочтений и действий при последующих посещениях сайта.</p>
|
||||
</section>
|
||||
|
||||
<section class="legal-section">
|
||||
<h2>3. ПРАВОВЫЕ ОСНОВАНИЯ ОБРАБОТКИ ПЕРСОНАЛЬНЫХ ДАННЫХ</h2>
|
||||
|
||||
<p>3.1. Правовым основанием Обработки ПДн в зависимости от целей процесса, предусматривающего Обработку ПДн может являться:</p>
|
||||
|
||||
<h3>3.1.1. Конституция Российской Федерации, а также совокупность правовых актов:</h3>
|
||||
<ul>
|
||||
<li>Налоговый кодекс Российской Федерации;</li>
|
||||
<li>Гражданский Кодекс Российской Федерации;</li>
|
||||
<li>ст. 86 - 90 Трудового кодекса Российской Федерации;</li>
|
||||
<li>Федеральный закон от 07.08.2001 № 115-ФЗ «О противодействии легализации (отмыванию) доходов, полученных преступным путем, и финансированию терроризма»;</li>
|
||||
<li>Федеральный закон от 27.07.2006 г. № 152-ФЗ «О персональных данных»;</li>
|
||||
<li>Федеральный закон от 22.04.1996 № 39-ФЗ «О рынке ценных бумаг»;</li>
|
||||
<li>Федеральный закон от 26.12.1995 №208-ФЗ «Об акционерных обществах»;</li>
|
||||
<li>Федеральный закон от 27.07.2006 № 149-ФЗ «Об информации, информационных технологиях и о защите информации»;</li>
|
||||
<li>Федеральный закон от 01.04.1996 № 27-ФЗ «Об индивидуальном (персонифицированном) учете в системе обязательного пенсионного страхования»;</li>
|
||||
<li>Федеральный закон от 06.04.2011 № 63-ФЗ «Об электронной подписи»;</li>
|
||||
<li>Федеральный закон от 06.12.2011 № 402-ФЗ «О бухгалтерском учете»;</li>
|
||||
<li>Федеральный закон от 27.06.2011 № 161-ФЗ «О национальной платежной системе»;</li>
|
||||
<li>Постановление Правительства Российской Федерации от 15.09.2008 № 687 «Об утверждении положения об особенностях обработки персональных данных, осуществляемой без использования средств автоматизации»;</li>
|
||||
<li>Постановлением Правительства РФ от 01.11.2012 № 1119 "Об утверждении требований к защите персональных данных при их обработке в информационных системах персональных данных"</li>
|
||||
<li>иные нормативные правовые акты Российской Федерации и нормативные документы исполнительных органов государственной власти.</li>
|
||||
</ul>
|
||||
|
||||
<p>3.1.2. Устав Оператора.</p>
|
||||
|
||||
<p>3.1.3. Договоры, заключаемые между Оператором и Субъектом Персональных данных, в том числе в случае реализации Оператором своего права на уступку прав (требований) по таким договорам, между Оператором и иным лицом, поручившим Оператору Обработку ПДн, а также для заключения договоров, стороной которых являются Субъекты Персональных данных.</p>
|
||||
|
||||
<p>3.1.4. Согласие на Обработку ПДн (в случаях, прямо не предусмотренных законодательством Российской Федерации, но соответствующих полномочиям Оператора), в т.ч. согласие соискателей на замещение вакантных должностей на Обработку ПДн, согласие практиканта на Обработку ПДн, согласие работников на Обработку ПДн; согласие клиентов на Обработку ПДн, согласие Пользователей соответствующего Сайта, согласие иных Субъектов Персональных данных.</p>
|
||||
|
||||
<p>3.1.5. Договор между оператором и третьим лицом, где последнее поручает Оператору обработку персональных данных Субъекта Персональных данных или передает на основании заключенного договора персональные данные Субъекта Персональных данных.</p>
|
||||
</section>
|
||||
|
||||
<section class="legal-section">
|
||||
<h2>4. КАТЕГОРИИ СУБЪЕКТОВ ПЕРСОНАЛЬНЫХ ДАННЫХ</h2>
|
||||
|
||||
<p>4.1. Оператором осуществляется Обработка полученных в установленном законом порядке ПДн, принадлежащих:</p>
|
||||
<ul>
|
||||
<li>Кандидатам на работу и работникам Оператора;</li>
|
||||
<li>Уволенным работникам Оператора;</li>
|
||||
<li>Близким родственникам/членам семьи работников Оператора, практикантам;</li>
|
||||
<li>Потенциальным клиентам, клиентам - физическим лицам, клиентам - индивидуальным предпринимателям;</li>
|
||||
<li>Физическим лицам, заключившим с Оператором гражданско-правовые договоры;</li>
|
||||
<li>Пользователям Сайтов Оператора, получателям Заказов;</li>
|
||||
<li>Клиентам других юридических лиц;</li>
|
||||
<li>Собственникам Оператора;</li>
|
||||
<li>Иным субъектам, вступившим или намеревающимися вступить в договорные отношения с Оператором.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="legal-section">
|
||||
<h2>5. КАТЕГОРИИ ОБРАБАТЫВАЕМЫХ ПЕРСОНАЛЬНЫХ ДАННЫХ</h2>
|
||||
|
||||
<p>5.1. Оператор обрабатывает следующие категории ПДн Пользователей:</p>
|
||||
|
||||
<ul>
|
||||
<li>Сведения, полученные при регистрации и/или оформлении Заказа (фамилию, имя, фактический адрес, номер телефона, адрес электронной почты, cookie);</li>
|
||||
<li>Сведения, полученные при взаимодействии с Пользователями (фамилия, имя, отчество, пол, место рождения, дата рождения, паспортные данные, адрес, контакты);</li>
|
||||
<li>Сведения о способе доставки и оплаты Заказа;</li>
|
||||
<li>Сведения о претензиях Пользователя;</li>
|
||||
<li>Сведения о геолокации (местонахождении).</li>
|
||||
</ul>
|
||||
|
||||
<p>5.2. Персональные данные могут быть получены Оператором путем:</p>
|
||||
<ul>
|
||||
<li>Предоставления Субъектами через формы на Сайте или по email;</li>
|
||||
<li>Получения от третьих лиц (Продавцов, контрагентов) в рамках законодательства РФ.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="legal-section">
|
||||
<h2>6. ПРИНЦИПЫ, ПОРЯДОК И УСЛОВИЯ ОБРАБОТКИ</h2>
|
||||
|
||||
<h3>6.1. Принципы обработки Персональных данных</h3>
|
||||
<p>Обработка ПДн у Оператора осуществляется на основе следующих принципов:</p>
|
||||
<ul>
|
||||
<li>законности и справедливой основы;</li>
|
||||
<li>ограничения Обработки ПДн достижением конкретных, заранее определенных и законных целей;</li>
|
||||
<li>недопущения Обработки ПДн, несовместимой с целями сбора ПДн;</li>
|
||||
<li>обеспечения точности, достаточности и актуальности ПДн;</li>
|
||||
<li>хранение ПДн не дольше, чем этого требуют цели Обработки;</li>
|
||||
<li>уничтожения либо обезличивания ПДн по достижении целей их Обработки.</li>
|
||||
</ul>
|
||||
|
||||
<h3>6.2. Обязанности работников Оператора</h3>
|
||||
<p>Работники Оператора, допущенные к Обработке Персональных данных, обязаны:</p>
|
||||
<ul>
|
||||
<li>Знать и выполнять положения законодательства РФ в области ПДн;</li>
|
||||
<li>Знать и выполнять положения настоящей Политики;</li>
|
||||
<li>Обрабатывать ПДн только в рамках должностных обязанностей;</li>
|
||||
<li>Не разглашать ПДн, обрабатываемые Оператором;</li>
|
||||
<li>Сообщать о действиях, которые могут привести к нарушению Политики.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="legal-section">
|
||||
<h2>7. ПРАВА СУБЪЕКТА ПЕРСОНАЛЬНЫХ ДАННЫХ</h2>
|
||||
|
||||
<h3>7.1. Согласие Субъекта</h3>
|
||||
<p>Субъект ПДн дает согласие на Обработку свободно, своей волей и в своем интересе.</p>
|
||||
|
||||
<h3>7.2. Права Субъекта</h3>
|
||||
<p>Субъект ПДн имеет право на получение информации о:</p>
|
||||
<ul>
|
||||
<li>подтверждение факта Обработки ПДн;</li>
|
||||
<li>правовые основания и цели Обработки;</li>
|
||||
<li>применяемые способы Обработки;</li>
|
||||
<li>сроки Обработки и хранения данных;</li>
|
||||
<li>уточнение, блокирование или уничтожение данных.</li>
|
||||
</ul>
|
||||
|
||||
<p><strong>Субъект ПДн имеет право отозвать свое Согласие</strong> и потребовать удалить свои ПДн, направив сообщение на: <a href="mailto:info@dexarmarket.ru">info@dexarmarket.ru</a></p>
|
||||
|
||||
<p>Субъект также имеет право на защиту своих прав, возмещение убытков и компенсацию морального вреда.</p>
|
||||
</section>
|
||||
|
||||
<section class="legal-section">
|
||||
<h2>8. ОБЯЗАННОСТИ ОПЕРАТОРА</h2>
|
||||
|
||||
<p>8.1. Оператор обязан предоставить Субъекту информацию о обработке его данных при обращении.</p>
|
||||
|
||||
<p>8.2. Оператор обеспечивает хранение данных граждан РФ на территории Российской Федерации.</p>
|
||||
|
||||
<p>8.3. Оператор несет иные обязанности, установленные ФЗ-152.</p>
|
||||
</section>
|
||||
|
||||
<section class="legal-section">
|
||||
<h2>9. ОБЕСПЕЧЕНИЕ БЕЗОПАСНОСТИ</h2>
|
||||
|
||||
<p>9.1-9.3. Безопасность обеспечивается комплексом организационных и технических мер:</p>
|
||||
<ul>
|
||||
<li>назначение ответственных лиц;</li>
|
||||
<li>ограничение доступа к ПДн;</li>
|
||||
<li>обучение сотрудников;</li>
|
||||
<li>учет и хранение носителей с ПДн;</li>
|
||||
<li>использование антивирусных средств;</li>
|
||||
<li>средства шифрования и межсетевого экранирования;</li>
|
||||
<li>физическая защита помещений;</li>
|
||||
<li>контроль мер безопасности.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="legal-section">
|
||||
<h2>10. ОТВЕТСТВЕННОСТЬ</h2>
|
||||
|
||||
<p>10.1. Лица, виновные в нарушении норм обработки и защиты Персональных данных, несут ответственность согласно законодательству РФ.</p>
|
||||
</section>
|
||||
|
||||
<section class="legal-section">
|
||||
<h2>11. ЦЕЛИ ОБРАБОТКИ</h2>
|
||||
|
||||
<p>11.1. Оператор обрабатывает ПДн для следующих целей:</p>
|
||||
<ul>
|
||||
<li>Предоставления доступа к Сайту и личному кабинету;</li>
|
||||
<li>Исполнения договоров купли-продажи и оказания услуг;</li>
|
||||
<li>Доставки товаров;</li>
|
||||
<li>Урегулирования претензий;</li>
|
||||
<li>Обработки платежей;</li>
|
||||
<li>Улучшения качества обслуживания;</li>
|
||||
<li>Получения обратной связи;</li>
|
||||
<li>Проведения маркетинговых исследований;</li>
|
||||
<li>Направления рекламных сообщений;</li>
|
||||
<li>В иных случаях, предусмотренных законодательством РФ.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="legal-section">
|
||||
<h2>12. АВТОМАТИЧЕСКИ СОБИРАЕМАЯ ИНФОРМАЦИЯ</h2>
|
||||
|
||||
<p>12.1. Оператор собирает и обрабатывает:</p>
|
||||
<ul>
|
||||
<li>информацию об интересах на основе поисковых запросов;</li>
|
||||
<li>информацию для формирования рейтинга (отзывы, данные об исполнении Заказов);</li>
|
||||
<li>статистику об использовании Сайта.</li>
|
||||
</ul>
|
||||
|
||||
<p>12.2. Оператор использует cookies, веб-отметки и другие технологии мониторинга. Эти технологии не дают возможность автоматически получать ПДн.</p>
|
||||
|
||||
<p>12.3. Если собранные сведения можно соотнести с личным кабинетом Пользователя, они могут обрабатываться совместно с ПДн.</p>
|
||||
</section>
|
||||
|
||||
<section class="legal-section">
|
||||
<h2>13. КОНТАКТНАЯ ИНФОРМАЦИЯ</h2>
|
||||
|
||||
<p>По всем вопросам обработки персональных данных обращайтесь:</p>
|
||||
<ul>
|
||||
<li><strong>Email:</strong> <a href="mailto:info@dexarmarket.ru">info@dexarmarket.ru</a></li>
|
||||
<li><strong>Телефон (Россия):</strong> <a href="tel:+79264593157">+7 (926) 459-31-57</a></li>
|
||||
<li><strong>Телефон (Армения):</strong> <a href="tel:+37494861816">+374 94 86 18 16</a></li>
|
||||
</ul>
|
||||
<p>Мы ответим в течение 30 дней согласно законодательству РФ.</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,5 @@
|
||||
@switch (lang()) {
|
||||
@case ('ru') { <app-about-lavero-ru /> }
|
||||
@case ('en') { <app-about-lavero-en /> }
|
||||
@case ('hy') { <app-about-lavero-hy /> }
|
||||
}
|
||||
16
src/app/brands/lavero/pages/info/about/about.component.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Component, ChangeDetectionStrategy, inject } from '@angular/core';
|
||||
import { LanguageService } from '../../../../../services/language.service';
|
||||
import { AboutLaveroRuComponent } from './ru/about-ru.component';
|
||||
import { AboutLaveroEnComponent } from './en/about-en.component';
|
||||
import { AboutLaveroHyComponent } from './hy/about-hy.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-about-lavero',
|
||||
imports: [AboutLaveroRuComponent, AboutLaveroEnComponent, AboutLaveroHyComponent],
|
||||
templateUrl: './about.component.html',
|
||||
styleUrls: ['../../../../../pages/info/about/about.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class AboutLaveroComponent {
|
||||
lang = inject(LanguageService).currentLanguage;
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<div class="legal-page">
|
||||
<div class="legal-container">
|
||||
<div class="lavero-header">
|
||||
<h1>About Us</h1>
|
||||
<p class="subtitle">A modern marketplace for your convenience</p>
|
||||
</div>
|
||||
|
||||
<div class="lavero-cards">
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🚀</div>
|
||||
<h3>Who We Are</h3>
|
||||
<p>We are a rapidly growing marketplace connecting sellers and buyers from different countries. Our platform creates convenient conditions for safe trading of various goods and services.</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🎯</div>
|
||||
<h3>Our Mission</h3>
|
||||
<p>To create a simple and profitable ecosystem for businesses and buyers, where everyone finds the best deals.</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🌍</div>
|
||||
<h3>Geography</h3>
|
||||
<p>We operate in Russia, Armenia, UAE, Turkey, China, Kazakhstan, Kyrgyzstan, and other countries.</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">💼</div>
|
||||
<h3>For Business</h3>
|
||||
<ul class="compact-list">
|
||||
<li>Easy product listing</li>
|
||||
<li>Ready-made audience</li>
|
||||
<li>Convenient tools</li>
|
||||
<li>Technical support</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🛍️</div>
|
||||
<h3>For Buyers</h3>
|
||||
<ul class="compact-list">
|
||||
<li>Wide selection of products</li>
|
||||
<li>Competitive prices</li>
|
||||
<li>Safe purchases</li>
|
||||
<li>Fast delivery</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🔒</div>
|
||||
<h3>Our Values</h3>
|
||||
<div class="features-list">
|
||||
<div class="feature">✓ Transparency</div>
|
||||
<div class="feature">✓ Reliability</div>
|
||||
<div class="feature">✓ Innovation</div>
|
||||
<div class="feature">✓ Customer Service</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">📈</div>
|
||||
<h3>Our Journey</h3>
|
||||
<div class="timeline">
|
||||
<div class="timeline-item">
|
||||
<strong>2024</strong>
|
||||
<p>Platform launch in Armenia</p>
|
||||
</div>
|
||||
<div class="timeline-item">
|
||||
<strong>2025</strong>
|
||||
<p>Expansion to the Russian market</p>
|
||||
</div>
|
||||
<div class="timeline-item">
|
||||
<strong>Today</strong>
|
||||
<p>International expansion</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🏢</div>
|
||||
<h3>Company Details</h3>
|
||||
<p><strong>Company:</strong> «LAVERO» LLC</p>
|
||||
<p><strong>Director:</strong> GEVORG MATEVOSYAN</p>
|
||||
<p><strong>TIN (ՀVHH):</strong> 03590442</p>
|
||||
<p><strong>Registration No.:</strong> 999.110.1583686</p>
|
||||
<p><strong>Address:</strong> ARMENIA, KOTAYK, ABOVYAN, VERIN PTGHNI, 3rd Street, 28</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">📞</div>
|
||||
<h3>Contact Us</h3>
|
||||
<a href="mailto:info@lavero.store" class="contact-email">info@lavero.store</a>
|
||||
<p><a [href]="env.phoneTel">{{ env.phones.support }}</a></p>
|
||||
<p class="support-note">We are always in touch</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { environment } from '../../../../../../../environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-about-lavero-en',
|
||||
templateUrl: './about-en.component.html',
|
||||
styleUrls: ['../../../../../../pages/info/about/about.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class AboutLaveroEnComponent {
|
||||
protected readonly env = environment;
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<div class="legal-page">
|
||||
<div class="legal-container">
|
||||
<div class="lavero-header">
|
||||
<h1>Մեր մասին</h1>
|
||||
<p class="subtitle">Զամանակակից մարկեթփլեյս ձեր հարմարության համար</p>
|
||||
</div>
|
||||
|
||||
<div class="lavero-cards">
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🚀</div>
|
||||
<h3>Ովքեր ենք</h3>
|
||||
<p>Մենք դինամիկ զարգացող մարկեթփլեյս ենք, որը միավորում է վաճառողներին և գնորդներին տարբեր երկրներից։ Մեր հարթակը ստեղծում է հարմար պայմաններ տարբեր ապրանքների և ծառայությունների անվտանգ առևտրի համար։</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🎯</div>
|
||||
<h3>Մեր առաքելությունը</h3>
|
||||
<p>Ստեղծել պարզ և շահավետ էկոհամակարգ բիզնեսի և գնորդների համար, որտեղ բոլորը գտնեն լավագույն առաջարկները։</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🌍</div>
|
||||
<h3>Աշխարհագրություն</h3>
|
||||
<p>Մենք աշխատում ենք Ռուսաստանում, Հայաստանում, ԱՀԷ-ում, Թուրքիայում, Չինաստանում, Ղազախստանում, Ղրղզստանում և այլ երկրներում։</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">💼</div>
|
||||
<h3>Բիզնեսի համար</h3>
|
||||
<ul class="compact-list">
|
||||
<li>Ապրանքների հեշտ տեղադրում</li>
|
||||
<li>Պատրաստ լսարան</li>
|
||||
<li>Հարմար գործիքներ</li>
|
||||
<li>Տեխնիկական աջակցություն</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🛍️</div>
|
||||
<h3>Գնորդների համար</h3>
|
||||
<ul class="compact-list">
|
||||
<li>Ապրանքների լայն ընտրություն</li>
|
||||
<li>Մրցունակելի գներ</li>
|
||||
<li>Անվտանգ գնումներ</li>
|
||||
<li>Արագ առաքում</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🔒</div>
|
||||
<h3>Մեր արժեքները</h3>
|
||||
<div class="features-list">
|
||||
<div class="feature">✓ Թափանցիկություն</div>
|
||||
<div class="feature">✓ Հուսալիություն</div>
|
||||
<div class="feature">✓ Նորարարություն</div>
|
||||
<div class="feature">✓ Հաճախորդային սպասարկում</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">📈</div>
|
||||
<h3>Մեր ճանապարհը</h3>
|
||||
<div class="timeline">
|
||||
<div class="timeline-item">
|
||||
<strong>2024</strong>
|
||||
<p>Հարթակի գործարկումը Հայաստանում</p>
|
||||
</div>
|
||||
<div class="timeline-item">
|
||||
<strong>2025</strong>
|
||||
<p>Մուտք ռուսական շուկա</p>
|
||||
</div>
|
||||
<div class="timeline-item">
|
||||
<strong>Այսօր</strong>
|
||||
<p>Միջազգային ընդլայնում</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🏢</div>
|
||||
<h3>Ինկերության տվյալները</h3>
|
||||
<p><strong>Ընկерություն՝</strong> «ԼАВЕРО» ՍՊԸ</p>
|
||||
<p><strong>Տнօрен՝</strong> ГЕВORG МАТЕВОСЯН (GEVORG MATEVOSYAN)</p>
|
||||
<p><strong>ՀВHH՝</strong> 03590442</p>
|
||||
<p><strong>Гранцман h/h՝</strong> 999.110.1583686</p>
|
||||
<p><strong>Hasцe՝</strong> ՀАЙАСТАН, КОТАЙК, АБОВЯН, ВЕРИН ПТГНИ, 3-рд ПOЛOC, 28</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">📞</div>
|
||||
<h3>Կապվել մեզ հետ</h3>
|
||||
<a href="mailto:info@lavero.store" class="contact-email">info@lavero.store</a>
|
||||
<p><a [href]="env.phoneTel">{{ env.phones.support }}</a></p>
|
||||
<p class="support-note">Մենք միշտ կապի մեջ ենք</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { environment } from '../../../../../../../environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-about-lavero-hy',
|
||||
templateUrl: './about-hy.component.html',
|
||||
styleUrls: ['../../../../../../pages/info/about/about.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class AboutLaveroHyComponent {
|
||||
protected readonly env = environment;
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<div class="legal-page">
|
||||
<div class="legal-container">
|
||||
<div class="lavero-header">
|
||||
<h1>О нас</h1>
|
||||
<p class="subtitle">Современный маркетплейс для вашего удобства</p>
|
||||
</div>
|
||||
|
||||
<div class="lavero-cards">
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🚀</div>
|
||||
<h3>Кто мы</h3>
|
||||
<p>Мы - динамично развивающийся маркетплейс, объединяющий продавцов и покупателей из разных стран. Наша платформа создает удобные условия для безопасной торговли различными товарами и услугами.</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🎯</div>
|
||||
<h3>Наша миссия</h3>
|
||||
<p>Создавать простую и выгодную экосистему для бизнеса и покупателей, где каждый находит лучшие предложения.</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🌍</div>
|
||||
<h3>География</h3>
|
||||
<p>Мы работаем в России, Армении, ОАЭ, Турции, Китае, Казахстане, Кыргызстане и других странах.</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">💼</div>
|
||||
<h3>Для бизнеса</h3>
|
||||
<ul class="compact-list">
|
||||
<li>Простое размещение товаров</li>
|
||||
<li>Готовая аудитория</li>
|
||||
<li>Удобные инструменты</li>
|
||||
<li>Техническая поддержка</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🛍️</div>
|
||||
<h3>Для покупателей</h3>
|
||||
<ul class="compact-list">
|
||||
<li>Широкий выбор товаров</li>
|
||||
<li>Выгодные цены</li>
|
||||
<li>Безопасные покупки</li>
|
||||
<li>Быстрая доставка</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🔒</div>
|
||||
<h3>Наши ценности</h3>
|
||||
<div class="features-list">
|
||||
<div class="feature">✓ Прозрачность</div>
|
||||
<div class="feature">✓ Надежность</div>
|
||||
<div class="feature">✓ Инновации</div>
|
||||
<div class="feature">✓ Клиентский сервис</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">📈</div>
|
||||
<h3>Наш путь</h3>
|
||||
<div class="timeline">
|
||||
<div class="timeline-item">
|
||||
<strong>2024</strong>
|
||||
<p>Запуск платформы в Армении</p>
|
||||
</div>
|
||||
<div class="timeline-item">
|
||||
<strong>2025</strong>
|
||||
<p>Выход на российский рынок</p>
|
||||
</div>
|
||||
<div class="timeline-item">
|
||||
<strong>Сегодня</strong>
|
||||
<p>Международная экспансия</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🏢</div>
|
||||
<h3>Реквизиты компании</h3>
|
||||
<p><strong>Компания:</strong> «ЛАВЕРО» ООО</p>
|
||||
<p><strong>Директор:</strong> ГЕВОРГ МАТЕВОСЯН</p>
|
||||
<p><strong>ՀВՀՀ (ИНН):</strong> 03590442</p>
|
||||
<p><strong>Рег. номер:</strong> 999.110.1583686</p>
|
||||
<p><strong>Адрес:</strong> АРМЕНИЯ, КОТАЙК, АБОВЯН, ВЕРИН ПТГНИ, ул. 3-я, 28</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">📞</div>
|
||||
<h3>Связаться с нами</h3>
|
||||
<a href="mailto:info@lavero.store" class="contact-email">info@lavero.store</a>
|
||||
<p><a [href]="env.phoneTel">{{ env.phones.support }}</a></p>
|
||||
<p class="support-note">Мы всегда на связи</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { environment } from '../../../../../../../environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-about-lavero-ru',
|
||||
templateUrl: './about-ru.component.html',
|
||||
styleUrls: ['../../../../../../pages/info/about/about.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class AboutLaveroRuComponent {
|
||||
protected readonly env = environment;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
@switch (lang()) {
|
||||
@case ('ru') { <app-contacts-lavero-ru /> }
|
||||
@case ('en') { <app-contacts-lavero-en /> }
|
||||
@case ('hy') { <app-contacts-lavero-hy /> }
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Component, ChangeDetectionStrategy, inject } from '@angular/core';
|
||||
import { LanguageService } from '../../../../../services/language.service';
|
||||
import { ContactsLaveroRuComponent } from './ru/contacts-ru.component';
|
||||
import { ContactsLaveroEnComponent } from './en/contacts-en.component';
|
||||
import { ContactsLaveroHyComponent } from './hy/contacts-hy.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-contacts-lavero',
|
||||
imports: [ContactsLaveroRuComponent, ContactsLaveroEnComponent, ContactsLaveroHyComponent],
|
||||
templateUrl: './contacts.component.html',
|
||||
styleUrls: ['../../../../../pages/info/contacts/contacts.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ContactsLaveroComponent {
|
||||
lang = inject(LanguageService).currentLanguage;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<div class="legal-page">
|
||||
<div class="legal-container">
|
||||
<div class="lavero-header">
|
||||
<h1>Contacts</h1>
|
||||
<p class="subtitle">Get in touch with us</p>
|
||||
</div>
|
||||
|
||||
<div class="lavero-cards">
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🏢</div>
|
||||
<h3>Organization</h3>
|
||||
<p class="org-name">LLC «ELECTROMOTORS»</p>
|
||||
<p><strong>TIN:</strong> 03590442</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">📞</div>
|
||||
<h3>Phone</h3>
|
||||
<p><a [href]="env.phoneTel">{{ env.phones.support }}</a></p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">✉️</div>
|
||||
<h3>Email</h3>
|
||||
<p><a href="mailto:info@lavero.store">info@lavero.store</a></p>
|
||||
<p class="note">Response within 24 hours</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">📍</div>
|
||||
<h3>Address</h3>
|
||||
<p>Armenia, 0501, Aragatsotn region, Talin, 12 Gaya St.</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">⏰</div>
|
||||
<h3>Working Hours</h3>
|
||||
<p><strong>Support:</strong> 9:00 - 21:00</p>
|
||||
<p><strong>Days off:</strong> Saturday - Sunday</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">💬</div>
|
||||
<h3>Contact Us</h3>
|
||||
<p>If you experience technical issues with the website or have questions about placing an order, please contact us at <a href="mailto:info@lavero.store">info@lavero.store</a> with a detailed description of the problem.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { environment } from '../../../../../../../environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-contacts-lavero-en',
|
||||
templateUrl: './contacts-en.component.html',
|
||||
styleUrls: ['../../../../../../pages/info/contacts/contacts.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ContactsLaveroEnComponent {
|
||||
protected readonly env = environment;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<div class="legal-page">
|
||||
<div class="legal-container">
|
||||
<div class="lavero-header">
|
||||
<h1>Կապ</h1>
|
||||
<p class="subtitle">Կապվեք մեզ հետ</p>
|
||||
</div>
|
||||
|
||||
<div class="lavero-cards">
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🏢</div>
|
||||
<h3>Կազմակերպություն</h3>
|
||||
<p class="org-name">ՍՊԸ «ԷԼԵԿՏՌՈՄՈՏՈՌՍ»</p>
|
||||
<p><strong>ՀՎՀՀ՝</strong> 03590442</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">📞</div>
|
||||
<h3>Հեռախոս</h3>
|
||||
<p><a [href]="env.phoneTel">{{ env.phones.support }}</a></p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">✉️</div>
|
||||
<h3>Էլ. փոստ</h3>
|
||||
<p><a href="mailto:info@lavero.store">info@lavero.store</a></p>
|
||||
<p class="note">Պատասխանը 24 ժամվա ընթացքում</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">📍</div>
|
||||
<h3>Հասցե</h3>
|
||||
<p>Հայաստան, 0501, Արագածոտնի մարզ, ք. Տալին, Գայայի փող. 12</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">⏰</div>
|
||||
<h3>Աշխատանքային ժամեր</h3>
|
||||
<p><strong>Աջակցություն՝</strong> 9:00 - 21:00</p>
|
||||
<p><strong>Հանգստյան օրեր՝</strong> Շաբաթ - Կիրակի</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">💬</div>
|
||||
<h3>Կապվել մեզ հետ</h3>
|
||||
<p>Կայքի տեխնիկական խնդիրների դեպքում կամ պատվերի ձևակերպման հարցերի դեպքում դիմեք <a href="mailto:info@lavero.store">info@lavero.store</a> խնդրի մանրամասն նկարագրությամբ։</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { environment } from '../../../../../../../environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-contacts-lavero-hy',
|
||||
templateUrl: './contacts-hy.component.html',
|
||||
styleUrls: ['../../../../../../pages/info/contacts/contacts.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ContactsLaveroHyComponent {
|
||||
protected readonly env = environment;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<div class="legal-page">
|
||||
<div class="legal-container">
|
||||
<div class="lavero-header">
|
||||
<h1>Контакты</h1>
|
||||
<p class="subtitle">Свяжитесь с нами</p>
|
||||
</div>
|
||||
|
||||
<div class="lavero-cards">
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🏢</div>
|
||||
<h3>Организация</h3>
|
||||
<p class="org-name">ООО «ЛАВЕРО»</p>
|
||||
<p><strong>ИНН:</strong> 03590442</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">📞</div>
|
||||
<h3>Телефон</h3>
|
||||
<p><a [href]="env.phoneTel">{{ env.phones.support }}</a></p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">✉️</div>
|
||||
<h3>Email</h3>
|
||||
<p><a href="mailto:info@lavero.store">info@lavero.store</a></p>
|
||||
<p class="note">Ответ в течение 24 часов</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">📍</div>
|
||||
<h3>Адрес</h3>
|
||||
<p>АРМЕНИЯ, КОТАЙК, АБОВЯН, ВЕРИН ПТГНИ, ул. 3-я, 28</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">⏰</div>
|
||||
<h3>Часы работы</h3>
|
||||
<p><strong>Поддержка:</strong> 9:00 - 21:00</p>
|
||||
<p><strong>Выходные:</strong> Суббота - Воскресенье</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">💬</div>
|
||||
<h3>Связаться с нами</h3>
|
||||
<p>При возникновении технических проблем с работой сайта или вопросов по оформлению заказа обращайтесь на <a href="mailto:info@lavero.store">info@lavero.store</a> с подробным описанием проблемы.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { environment } from '../../../../../../../environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-contacts-lavero-ru',
|
||||
templateUrl: './contacts-ru.component.html',
|
||||
styleUrls: ['../../../../../../pages/info/contacts/contacts.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ContactsLaveroRuComponent {
|
||||
protected readonly env = environment;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
@switch (lang()) {
|
||||
@case ('ru') { <app-delivery-lavero-ru /> }
|
||||
@case ('en') { <app-delivery-lavero-en /> }
|
||||
@case ('hy') { <app-delivery-lavero-hy /> }
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Component, ChangeDetectionStrategy, inject } from '@angular/core';
|
||||
import { LanguageService } from '../../../../../services/language.service';
|
||||
import { DeliveryLaveroRuComponent } from './ru/delivery-ru.component';
|
||||
import { DeliveryLaveroEnComponent } from './en/delivery-en.component';
|
||||
import { DeliveryLaveroHyComponent } from './hy/delivery-hy.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-delivery-lavero',
|
||||
imports: [DeliveryLaveroRuComponent, DeliveryLaveroEnComponent, DeliveryLaveroHyComponent],
|
||||
templateUrl: './delivery.component.html',
|
||||
styleUrls: ['../../../../../pages/info/delivery/delivery.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class DeliveryLaveroComponent {
|
||||
lang = inject(LanguageService).currentLanguage;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<div class="legal-page">
|
||||
<div class="legal-container">
|
||||
<div class="lavero-header">
|
||||
<h1>Delivery</h1>
|
||||
<p class="subtitle">Fast and convenient to your door</p>
|
||||
</div>
|
||||
|
||||
<div class="lavero-cards">
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">📧</div>
|
||||
<h3>Digital Products</h3>
|
||||
<div class="features-list">
|
||||
<div class="feature">⚡ Instant delivery</div>
|
||||
<div class="feature">📨 To your email</div>
|
||||
<div class="feature">💰 Free</div>
|
||||
<div class="feature">🔒 Secure</div>
|
||||
</div>
|
||||
<p class="note important" style="margin-top: 12px;">⚠️ The platform is not responsible for digital products. The seller is responsible for quality and functionality.</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">📦</div>
|
||||
<h3>Physical Products</h3>
|
||||
<ul class="compact-list">
|
||||
<li>СДЭК (2-7 days)</li>
|
||||
<li>Почта России (5-14 days)</li>
|
||||
<li>Boxberry (2-5 days)</li>
|
||||
<li>DPD (1-3 days)</li>
|
||||
<li>Яндекс.Доставка (same day*)</li>
|
||||
</ul>
|
||||
<p class="note">*If available in your city</p>
|
||||
<p class="note important" style="margin-top: 12px;">⚠️ The platform is not responsible for the actions of shipping companies. Delivery is handled by СДЭК, Почта России, Boxberry, DPD, and other carriers.</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">💰</div>
|
||||
<h3>Delivery Cost</h3>
|
||||
<div class="delivery-cost">
|
||||
<div class="cost-item">
|
||||
<strong>Digital Products</strong>
|
||||
<span class="free">Free</span>
|
||||
</div>
|
||||
<div class="cost-item">
|
||||
<strong>Physical Products</strong>
|
||||
<span>Depends on weight and region</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="note">Exact cost is calculated at checkout</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🔍</div>
|
||||
<h3>Tracking</h3>
|
||||
<p>After shipping, you will receive a tracking number by email. Track your package on the delivery service website or in your account.</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">✓</div>
|
||||
<h3>Check upon receipt</h3>
|
||||
<div class="check-grid">
|
||||
<div class="check-item">✓ Packaging integrity</div>
|
||||
<div class="check-item">✓ Product match</div>
|
||||
<div class="check-item">✓ Completeness</div>
|
||||
<div class="check-item">✓ No damage</div>
|
||||
</div>
|
||||
<p class="note important">If there are issues - file a report with the courier</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">📞</div>
|
||||
<h3>Questions about delivery?</h3>
|
||||
<p>Contact the seller or us:</p>
|
||||
<a href="mailto:info@lavero.store" class="contact-email">info@lavero.store</a>
|
||||
<p><a [href]="env.phoneTel">{{ env.phones.support }}</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { environment } from '../../../../../../../environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-delivery-lavero-en',
|
||||
templateUrl: './delivery-en.component.html',
|
||||
styleUrls: ['../../../../../../pages/info/delivery/delivery.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class DeliveryLaveroEnComponent {
|
||||
protected readonly env = environment;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<div class="legal-page">
|
||||
<div class="legal-container">
|
||||
<div class="lavero-header">
|
||||
<h1>Առաքում</h1>
|
||||
<p class="subtitle">Արագ և հարմար՝ մինչև ձեր դուռը</p>
|
||||
</div>
|
||||
|
||||
<div class="lavero-cards">
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">📧</div>
|
||||
<h3>Թվային ապրանքներ</h3>
|
||||
<div class="features-list">
|
||||
<div class="feature">⚡ Ակնթարթային առաքում</div>
|
||||
<div class="feature">📨 Ձեր email-ին</div>
|
||||
<div class="feature">💰 Անվճար</div>
|
||||
<div class="feature">🔒 Անվտանգ</div>
|
||||
</div>
|
||||
<p class="note important" style="margin-top: 12px;">⚠️ Հարթակը պատասխանատվություն չի կրում թվային ապրանքների համար։ Որակի և գործունակության համար պատասխանատու է վաճառողը։</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">📦</div>
|
||||
<h3>Ֆիզիկական ապրանքներ</h3>
|
||||
<ul class="compact-list">
|
||||
<li>СДЭК (2-7 օր)</li>
|
||||
<li>Почта России (5-14 օր)</li>
|
||||
<li>Boxberry (2-5 օր)</li>
|
||||
<li>DPD (1-3 օր)</li>
|
||||
<li>Яндекс.Доставка (պատվերի օրը*)</li>
|
||||
</ul>
|
||||
<p class="note">*Եթե հասանելի է ձեր քաղաքում</p>
|
||||
<p class="note important" style="margin-top: 12px;">⚠️ Հարթակը պատասխանատվություն չի կրում տրանսպորտային ընկերությունների գործողությունների համար։ Առաքման համար պատասխանատու են СДЭК, Почта России, Boxberry, DPD և այլ փոխադրողներ։</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">💰</div>
|
||||
<h3>Առաքման արժեքը</h3>
|
||||
<div class="delivery-cost">
|
||||
<div class="cost-item">
|
||||
<strong>Թվային ապրանքներ</strong>
|
||||
<span class="free">Անվճար</span>
|
||||
</div>
|
||||
<div class="cost-item">
|
||||
<strong>Ֆիզիկական ապրանքներ</strong>
|
||||
<span>Կախված է քաշից և տարածաշրջանից</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="note">Ճիշտ արժեքը հաշվարկվում է ձևակերպման ժամանակ</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🔍</div>
|
||||
<h3>Հետագծում</h3>
|
||||
<p>Ուղարկմանից հետո դուք կստանաք թրեք-համար email-ով։ Հետևեք ծանրութը առաքման ծառայության կայքում կամ անձնական էջում։</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">✓</div>
|
||||
<h3>Ստանալիս ստուգեք</h3>
|
||||
<div class="check-grid">
|
||||
<div class="check-item">✓ Փաթեթավորման ամբողջականությունը</div>
|
||||
<div class="check-item">✓ Ապրանքի համապատասխանությունը</div>
|
||||
<div class="check-item">✓ Լրիվությունը</div>
|
||||
<div class="check-item">✓ Վնասվածների բացակայությունը</div>
|
||||
</div>
|
||||
<p class="note important">Եթե խնդիրներ կան - կազմեք ակտ սուրհանդեսի հետ</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">📞</div>
|
||||
<h3>Առաքման հարցեր՞</h3>
|
||||
<p>Կապվեք վաճառողի կամ մեզ հետ՝</p>
|
||||
<a href="mailto:info@lavero.store" class="contact-email">info@lavero.store</a>
|
||||
<p><a [href]="env.phoneTel">{{ env.phones.support }}</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { environment } from '../../../../../../../environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-delivery-lavero-hy',
|
||||
templateUrl: './delivery-hy.component.html',
|
||||
styleUrls: ['../../../../../../pages/info/delivery/delivery.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class DeliveryLaveroHyComponent {
|
||||
protected readonly env = environment;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<div class="legal-page">
|
||||
<div class="legal-container">
|
||||
<div class="lavero-header">
|
||||
<h1>Доставка</h1>
|
||||
<p class="subtitle">Быстро и удобно до вашей двери</p>
|
||||
</div>
|
||||
|
||||
<div class="lavero-cards">
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">📧</div>
|
||||
<h3>Цифровые товары</h3>
|
||||
<div class="features-list">
|
||||
<div class="feature">⚡ Мгновенная доставка</div>
|
||||
<div class="feature">📨 На ваш email</div>
|
||||
<div class="feature">💰 Бесплатно</div>
|
||||
<div class="feature">🔒 Безопасно</div>
|
||||
</div>
|
||||
<p class="note important" style="margin-top: 12px;">⚠️ Платформа не несет ответственности за цифровые товары. За качество и работоспособность отвечает продавец.</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">📦</div>
|
||||
<h3>Физические товары</h3>
|
||||
<ul class="compact-list">
|
||||
<li>СДЭК (2-7 дней)</li>
|
||||
<li>Почта России (5-14 дней)</li>
|
||||
<li>Boxberry (2-5 дней)</li>
|
||||
<li>DPD (1-3 дня)</li>
|
||||
<li>Яндекс.Доставка (в день заказа*)</li>
|
||||
</ul>
|
||||
<p class="note">*При наличии в вашем городе</p>
|
||||
<p class="note important" style="margin-top: 12px;">⚠️ Платформа не несет ответственности за действия транспортных компаний. За доставку отвечают СДЭК, Почта России, Boxberry, DPD и другие перевозчики.</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">💰</div>
|
||||
<h3>Стоимость доставки</h3>
|
||||
<div class="delivery-cost">
|
||||
<div class="cost-item">
|
||||
<strong>Цифровые товары</strong>
|
||||
<span class="free">Бесплатно</span>
|
||||
</div>
|
||||
<div class="cost-item">
|
||||
<strong>Физические товары</strong>
|
||||
<span>Зависит от веса и региона</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="note">Точная стоимость рассчитывается при оформлении</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🔍</div>
|
||||
<h3>Отслеживание</h3>
|
||||
<p>После отправки вы получите трек-номер на email. Отслеживайте посылку на сайте службы доставки или в личном кабинете.</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">✓</div>
|
||||
<h3>При получении проверьте</h3>
|
||||
<div class="check-grid">
|
||||
<div class="check-item">✓ Целостность упаковки</div>
|
||||
<div class="check-item">✓ Соответствие товара</div>
|
||||
<div class="check-item">✓ Комплектность</div>
|
||||
<div class="check-item">✓ Отсутствие повреждений</div>
|
||||
</div>
|
||||
<p class="note important">Если есть проблемы - составьте акт с курьером</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">📞</div>
|
||||
<h3>Вопросы по доставке?</h3>
|
||||
<p>Свяжитесь с продавцом или нами:</p>
|
||||
<a href="mailto:info@lavero.store" class="contact-email">info@lavero.store</a>
|
||||
<p><a [href]="env.phoneTel">{{ env.phones.support }}</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { environment } from '../../../../../../../environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-delivery-lavero-ru',
|
||||
templateUrl: './delivery-ru.component.html',
|
||||
styleUrls: ['../../../../../../pages/info/delivery/delivery.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class DeliveryLaveroRuComponent {
|
||||
protected readonly env = environment;
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<div class="legal-page">
|
||||
<div class="legal-container">
|
||||
<div class="lavero-header">
|
||||
<h1>Frequently Asked Questions</h1>
|
||||
<p class="subtitle">Quick answers to popular questions</p>
|
||||
</div>
|
||||
|
||||
<div class="lavero-cards">
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🛍️</div>
|
||||
<h3>How to place an order?</h3>
|
||||
<div class="process-steps-compact">
|
||||
<p>1. Add the product to your cart</p>
|
||||
<p>2. Enter your delivery details</p>
|
||||
<p>3. Choose a payment method</p>
|
||||
<p>4. Confirm your order</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">💳</div>
|
||||
<h3>Payment Methods</h3>
|
||||
<ul class="compact-list">
|
||||
<li>Bank cards</li>
|
||||
<li>SBP</li>
|
||||
<li>E-wallets</li>
|
||||
<li>Cash on delivery*</li>
|
||||
</ul>
|
||||
<p class="note">*Depends on the seller</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🚚</div>
|
||||
<h3>Delivery</h3>
|
||||
<div class="delivery-info">
|
||||
<div class="delivery-item">
|
||||
<strong>Digital products</strong>
|
||||
<p>Instantly via email</p>
|
||||
</div>
|
||||
<div class="delivery-item">
|
||||
<strong>Physical products</strong>
|
||||
<p>СДЭК, Почта России, DPD</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">↩️</div>
|
||||
<h3>Product Returns</h3>
|
||||
<p>You can return a product in good condition within 7 days, provided it has not been used and the packaging is intact.</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🔒</div>
|
||||
<h3>Security</h3>
|
||||
<div class="features-list">
|
||||
<div class="feature">✓ Secure payments</div>
|
||||
<div class="feature">✓ Verified sellers</div>
|
||||
<div class="feature">✓ Return guarantee</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">⏱️</div>
|
||||
<h3>Order Processing</h3>
|
||||
<p>Your order is processed immediately after payment. The seller ships the product within 1-3 business days.</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">💬</div>
|
||||
<h3>Customer Support</h3>
|
||||
<div class="contacts-grid">
|
||||
<div class="contact-item">
|
||||
<strong>Email</strong>
|
||||
<a href="mailto:info@lavero.store">info@lavero.store</a>
|
||||
</div>
|
||||
<div class="contact-item">
|
||||
<strong>Phone</strong>
|
||||
<a [href]="env.phoneTel">{{ env.phones.support }}</a>
|
||||
</div>
|
||||
<div class="contact-item">
|
||||
<strong>Working Hours</strong>
|
||||
<p>24/7 support</p>
|
||||
</div>
|
||||
<div class="contact-item">
|
||||
<strong>Response Time</strong>
|
||||
<p>Up to 2 hours during business hours</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
12
src/app/brands/lavero/pages/info/faq/en/faq-en.component.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { environment } from '../../../../../../../environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-faq-lavero-en',
|
||||
templateUrl: './faq-en.component.html',
|
||||
styleUrls: ['../../../../../../pages/info/faq/faq.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class FaqLaveroEnComponent {
|
||||
protected readonly env = environment;
|
||||
}
|
||||
5
src/app/brands/lavero/pages/info/faq/faq.component.html
Normal file
@@ -0,0 +1,5 @@
|
||||
@switch (lang()) {
|
||||
@case ('ru') { <app-faq-lavero-ru /> }
|
||||
@case ('en') { <app-faq-lavero-en /> }
|
||||
@case ('hy') { <app-faq-lavero-hy /> }
|
||||
}
|
||||
16
src/app/brands/lavero/pages/info/faq/faq.component.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Component, ChangeDetectionStrategy, inject } from '@angular/core';
|
||||
import { LanguageService } from '../../../../../services/language.service';
|
||||
import { FaqLaveroRuComponent } from './ru/faq-ru.component';
|
||||
import { FaqLaveroEnComponent } from './en/faq-en.component';
|
||||
import { FaqLaveroHyComponent } from './hy/faq-hy.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-faq-lavero',
|
||||
imports: [FaqLaveroRuComponent, FaqLaveroEnComponent, FaqLaveroHyComponent],
|
||||
templateUrl: './faq.component.html',
|
||||
styleUrls: ['../../../../../pages/info/faq/faq.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class FaqLaveroComponent {
|
||||
lang = inject(LanguageService).currentLanguage;
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<div class="legal-page">
|
||||
<div class="legal-container">
|
||||
<div class="lavero-header">
|
||||
<h1>Հաճախ տրվող հարցեր</h1>
|
||||
<p class="subtitle">Արագ պատասխաններ տարածված հարցերին</p>
|
||||
</div>
|
||||
|
||||
<div class="lavero-cards">
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🛍️</div>
|
||||
<h3>Ինչպես կատարել պատվեր՞</h3>
|
||||
<div class="process-steps-compact">
|
||||
<p>1. Ավելացրեք ապրանքը զամբյուղին</p>
|
||||
<p>2. Նշեց առաքման տվյալները</p>
|
||||
<p>3. Ընտրեք վճարման եղանակը</p>
|
||||
<p>4. Հաստատեք պատվերը</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">💳</div>
|
||||
<h3>Վճարման եղանակներ</h3>
|
||||
<ul class="compact-list">
|
||||
<li>Բանկային քարտեր</li>
|
||||
<li>ՍԲՊ</li>
|
||||
<li>Էլեկտրոնային դրամապանակներ</li>
|
||||
<li>Կանխիկ ստանալիս*</li>
|
||||
</ul>
|
||||
<p class="note">*Կախված է վաճառողից</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🚚</div>
|
||||
<h3>Առաքում</h3>
|
||||
<div class="delivery-info">
|
||||
<div class="delivery-item">
|
||||
<strong>Թվային ապրանքներ</strong>
|
||||
<p>Ակնթարթային email-ին</p>
|
||||
</div>
|
||||
<div class="delivery-item">
|
||||
<strong>Ֆիզիկական ապրանքներ</strong>
|
||||
<p>СДЭК, Почта России, DPD</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">↩️</div>
|
||||
<h3>Ապրանքի վերադարձ</h3>
|
||||
<p>Կարելի է վերադարձել որակյալ ապրանքը 7 օրվա ընթացքում, եթե այն չի օգտագործվել և փաթեթավորումը պահպանված է։</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🔒</div>
|
||||
<h3>Անվտանգություն</h3>
|
||||
<div class="features-list">
|
||||
<div class="feature">✓ Պաշտպանված վճարումներ</div>
|
||||
<div class="feature">✓ Ստուգված վաճառողներ</div>
|
||||
<div class="feature">✓ Վերադարձի երաշխիք</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">⏱️</div>
|
||||
<h3>Պատվերի մշակում</h3>
|
||||
<p>Պատվերը մշակվում է վճարումից անմիջապես հետո։ Վաճառողը ապրանքը ուղարկում է 1-3 աշխատանքային օրվա ընթացքում։</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">💬</div>
|
||||
<h3>Աջակցության ծառայություն</h3>
|
||||
<div class="contacts-grid">
|
||||
<div class="contact-item">
|
||||
<strong>Email</strong>
|
||||
<a href="mailto:info@lavero.store">info@lavero.store</a>
|
||||
</div>
|
||||
<div class="contact-item">
|
||||
<strong>Հեռախոս</strong>
|
||||
<a [href]="env.phoneTel">{{ env.phones.support }}</a>
|
||||
</div>
|
||||
<div class="contact-item">
|
||||
<strong>Աշխատանքային ժամեր</strong>
|
||||
<p>24/7 տեխնիկական աջակցություն</p>
|
||||
</div>
|
||||
<div class="contact-item">
|
||||
<strong>Պատասխանի ժամանակ</strong>
|
||||
<p>Մինչև 2 ժամ աշխատանքային ժամերին</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
12
src/app/brands/lavero/pages/info/faq/hy/faq-hy.component.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { environment } from '../../../../../../../environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-faq-lavero-hy',
|
||||
templateUrl: './faq-hy.component.html',
|
||||
styleUrls: ['../../../../../../pages/info/faq/faq.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class FaqLaveroHyComponent {
|
||||
protected readonly env = environment;
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<div class="legal-page">
|
||||
<div class="legal-container">
|
||||
<div class="lavero-header">
|
||||
<h1>Частые вопросы</h1>
|
||||
<p class="subtitle">Быстрые ответы на популярные вопросы</p>
|
||||
</div>
|
||||
|
||||
<div class="lavero-cards">
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🛍️</div>
|
||||
<h3>Как сделать заказ?</h3>
|
||||
<div class="process-steps-compact">
|
||||
<p>1. Добавьте товар в корзину</p>
|
||||
<p>2. Укажите данные для доставки</p>
|
||||
<p>3. Выберите способ оплаты</p>
|
||||
<p>4. Подтвердите заказ</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">💳</div>
|
||||
<h3>Способы оплаты</h3>
|
||||
<ul class="compact-list">
|
||||
<li>Банковские карты</li>
|
||||
<li>СБП</li>
|
||||
<li>Электронные кошельки</li>
|
||||
<li>Наличные при получении*</li>
|
||||
</ul>
|
||||
<p class="note">*Зависит от продавца</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🚚</div>
|
||||
<h3>Доставка</h3>
|
||||
<div class="delivery-info">
|
||||
<div class="delivery-item">
|
||||
<strong>Цифровые товары</strong>
|
||||
<p>Мгновенно на email</p>
|
||||
</div>
|
||||
<div class="delivery-item">
|
||||
<strong>Физические товары</strong>
|
||||
<p>СДЭК, Почта России, DPD</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">↩️</div>
|
||||
<h3>Возврат товара</h3>
|
||||
<p>Можно вернуть качественный товар в течение 7 дней, если он не использовался и сохранена упаковка.</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🔒</div>
|
||||
<h3>Безопасность</h3>
|
||||
<div class="features-list">
|
||||
<div class="feature">✓ Защищенные платежи</div>
|
||||
<div class="feature">✓ Проверка продавцов</div>
|
||||
<div class="feature">✓ Гарантия возврата</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">⏱️</div>
|
||||
<h3>Обработка заказа</h3>
|
||||
<p>Заказ обрабатывается сразу после оплаты. Продавец отправляет товар в течение 1-3 рабочих дней.</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">💬</div>
|
||||
<h3>Служба поддержки</h3>
|
||||
<div class="contacts-grid">
|
||||
<div class="contact-item">
|
||||
<strong>Email</strong>
|
||||
<a href="mailto:info@lavero.store">info@lavero.store</a>
|
||||
</div>
|
||||
<div class="contact-item">
|
||||
<strong>Телефон</strong>
|
||||
<a [href]="env.phoneTel">{{ env.phones.support }}</a>
|
||||
</div>
|
||||
<div class="contact-item">
|
||||
<strong>Время работы</strong>
|
||||
<p>24/7 техподдержка</p>
|
||||
</div>
|
||||
<div class="contact-item">
|
||||
<strong>Время ответа</strong>
|
||||
<p>До 2 часов в рабочее время</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
12
src/app/brands/lavero/pages/info/faq/ru/faq-ru.component.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { environment } from '../../../../../../../environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-faq-lavero-ru',
|
||||
templateUrl: './faq-ru.component.html',
|
||||
styleUrls: ['../../../../../../pages/info/faq/faq.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class FaqLaveroRuComponent {
|
||||
protected readonly env = environment;
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<div class="legal-page">
|
||||
<div class="legal-container">
|
||||
<div class="lavero-header">
|
||||
<h1>Guarantee</h1>
|
||||
<p class="subtitle">Protecting your purchases</p>
|
||||
</div>
|
||||
|
||||
<div class="lavero-cards">
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🏷️</div>
|
||||
<h3>Warranty Periods</h3>
|
||||
<div class="warranty-periods">
|
||||
<div class="warranty-item">
|
||||
<strong>Electronics</strong>
|
||||
<span>12-24 months</span>
|
||||
</div>
|
||||
<div class="warranty-item">
|
||||
<strong>Computer Equipment</strong>
|
||||
<span>12-36 months</span>
|
||||
</div>
|
||||
<div class="warranty-item">
|
||||
<strong>Clothing and Footwear</strong>
|
||||
<span>30 days - 6 months</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">✓</div>
|
||||
<h3>Warranty Conditions</h3>
|
||||
<ul class="compact-list">
|
||||
<li>Used as intended</li>
|
||||
<li>No unauthorized repairs</li>
|
||||
<li>Seals preserved</li>
|
||||
<li>No mechanical damage</li>
|
||||
<li>Warranty card available</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🛠️</div>
|
||||
<h3>Your Rights for Defects</h3>
|
||||
<ul class="compact-list">
|
||||
<li>Free repair</li>
|
||||
<li>Product replacement</li>
|
||||
<li>Money refund</li>
|
||||
<li>Price reduction</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">⏱️</div>
|
||||
<h3>Repair Timeframe</h3>
|
||||
<p>Maximum 45 days by law. If the deadline is violated, you can request a replacement or a refund.</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🚫</div>
|
||||
<h3>Warranty Does Not Apply</h3>
|
||||
<div class="noguar-grid">
|
||||
<div class="noguar-item">Mechanical damage</div>
|
||||
<div class="noguar-item">Improper use</div>
|
||||
<div class="noguar-item">Liquid exposure</div>
|
||||
<div class="noguar-item">Unauthorized repair</div>
|
||||
<div class="noguar-item">Force majeure</div>
|
||||
<div class="noguar-item">Natural wear and tear</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">📝</div>
|
||||
<h3>How to File a Claim</h3>
|
||||
<div class="process-steps-compact">
|
||||
<p>1. Contact the seller</p>
|
||||
<p>2. Describe the issue</p>
|
||||
<p>3. Get the service address</p>
|
||||
<p>4. Send the product</p>
|
||||
<p>5. Receive the acceptance report</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">📞</div>
|
||||
<h3>Need Help?</h3>
|
||||
<p>In case of disputes:</p>
|
||||
<a href="mailto:info@lavero.store" class="contact-email">info@lavero.store</a>
|
||||
<p><a [href]="env.phoneTel">{{ env.phones.support }}</a></p>
|
||||
<p class="note">Subject: "Warranty Issue - Order #..."</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { environment } from '../../../../../../../environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-guarantee-lavero-en',
|
||||
templateUrl: './guarantee-en.component.html',
|
||||
styleUrls: ['../../../../../../pages/info/guarantee/guarantee.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class GuaranteeLaveroEnComponent {
|
||||
protected readonly env = environment;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
@switch (lang()) {
|
||||
@case ('ru') { <app-guarantee-lavero-ru /> }
|
||||
@case ('en') { <app-guarantee-lavero-en /> }
|
||||
@case ('hy') { <app-guarantee-lavero-hy /> }
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Component, ChangeDetectionStrategy, inject } from '@angular/core';
|
||||
import { LanguageService } from '../../../../../services/language.service';
|
||||
import { GuaranteeLaveroRuComponent } from './ru/guarantee-ru.component';
|
||||
import { GuaranteeLaveroEnComponent } from './en/guarantee-en.component';
|
||||
import { GuaranteeLaveroHyComponent } from './hy/guarantee-hy.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-guarantee-lavero',
|
||||
imports: [GuaranteeLaveroRuComponent, GuaranteeLaveroEnComponent, GuaranteeLaveroHyComponent],
|
||||
templateUrl: './guarantee.component.html',
|
||||
styleUrls: ['../../../../../pages/info/guarantee/guarantee.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class GuaranteeLaveroComponent {
|
||||
lang = inject(LanguageService).currentLanguage;
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<div class="legal-page">
|
||||
<div class="legal-container">
|
||||
<div class="lavero-header">
|
||||
<h1>Երաշխիք</h1>
|
||||
<p class="subtitle">Ձեր գնումների պաշտպանություն</p>
|
||||
</div>
|
||||
|
||||
<div class="lavero-cards">
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🏷️</div>
|
||||
<h3>Երաշխիքի ժամկետներ</h3>
|
||||
<div class="warranty-periods">
|
||||
<div class="warranty-item">
|
||||
<strong>Էլեկտրոնիկա</strong>
|
||||
<span>12-24 ամիս</span>
|
||||
</div>
|
||||
<div class="warranty-item">
|
||||
<strong>Համակարգչային տեխնիկա</strong>
|
||||
<span>12-36 ամիս</span>
|
||||
</div>
|
||||
<div class="warranty-item">
|
||||
<strong>Հագուստ և կոշիկ</strong>
|
||||
<span>30 օր - 6 ամիս</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">✓</div>
|
||||
<h3>Երաշխիքի պայմաններ</h3>
|
||||
<ul class="compact-list">
|
||||
<li>Օգտագործում ըստ նշանակության</li>
|
||||
<li>Առանց ինքնուրույն նորոգման</li>
|
||||
<li>Կապարանները պահպանված են</li>
|
||||
<li>Մեխանիկական վնասներ չկան</li>
|
||||
<li>Երաշխիքային տոմսը առկա է</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🛠️</div>
|
||||
<h3>Ձեր իրավունքները թերության դեպքում</h3>
|
||||
<ul class="compact-list">
|
||||
<li>Անվճար նորոգում</li>
|
||||
<li>Ապրանքի փոխարինում</li>
|
||||
<li>Գումարի վերադարձ</li>
|
||||
<li>Գնի նվազեցում</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">⏱️</div>
|
||||
<h3>Նորոգման ժամկետ</h3>
|
||||
<p>Օրենքով առավելագույնը 45 օր։ Եթե ժամկետը խախտվի ՝ կարող եք պահանջել փոխարինում կամ գումարի վերադարձ։</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🚫</div>
|
||||
<h3>Երաշխիքը չի գործում</h3>
|
||||
<div class="noguar-grid">
|
||||
<div class="noguar-item">Մեխանիկական վնասներ</div>
|
||||
<div class="noguar-item">Սխալ շահագործում</div>
|
||||
<div class="noguar-item">Հեղուկի ներթափանցում</div>
|
||||
<div class="noguar-item">Ինքնուրույն նորոգում</div>
|
||||
<div class="noguar-item">Ֆորս-մաժոր</div>
|
||||
<div class="noguar-item">Բնական մաշվածություն</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">📝</div>
|
||||
<h3>Ինչպես դիմել հայտ</h3>
|
||||
<div class="process-steps-compact">
|
||||
<p>1. Կապվեք վաճառողի հետ</p>
|
||||
<p>2. Նկարագրեք խնդիրը</p>
|
||||
<p>3. Ստացեք սերվիսի հասցեը</p>
|
||||
<p>4. Ուղարկեք ապրանքը</p>
|
||||
<p>5. Ստացեք ընդունման ակտը</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">📞</div>
|
||||
<h3>Օգնությու՞ն պետք է՞</h3>
|
||||
<p>Վեճերի դեպքում՝</p>
|
||||
<a href="mailto:info@lavero.store" class="contact-email">info@lavero.store</a>
|
||||
<p><a [href]="env.phoneTel">{{ env.phones.support }}</a></p>
|
||||
<p class="note">Թեմա՝ “Երաշխիքային հարց - Պատվեր №...”</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { environment } from '../../../../../../../environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-guarantee-lavero-hy',
|
||||
templateUrl: './guarantee-hy.component.html',
|
||||
styleUrls: ['../../../../../../pages/info/guarantee/guarantee.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class GuaranteeLaveroHyComponent {
|
||||
protected readonly env = environment;
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<div class="legal-page">
|
||||
<div class="legal-container">
|
||||
<div class="lavero-header">
|
||||
<h1>Гарантия</h1>
|
||||
<p class="subtitle">Защита ваших покупок</p>
|
||||
</div>
|
||||
|
||||
<div class="lavero-cards">
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🏷️</div>
|
||||
<h3>Сроки гарантии</h3>
|
||||
<div class="warranty-periods">
|
||||
<div class="warranty-item">
|
||||
<strong>Электроника</strong>
|
||||
<span>12-24 месяца</span>
|
||||
</div>
|
||||
<div class="warranty-item">
|
||||
<strong>Компьютерная техника</strong>
|
||||
<span>12-36 месяцев</span>
|
||||
</div>
|
||||
<div class="warranty-item">
|
||||
<strong>Одежда и обувь</strong>
|
||||
<span>30 дней - 6 месяцев</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">✓</div>
|
||||
<h3>Условия гарантии</h3>
|
||||
<ul class="compact-list">
|
||||
<li>Использование по назначению</li>
|
||||
<li>Без самостоятельного ремонта</li>
|
||||
<li>Сохранены пломбы</li>
|
||||
<li>Нет механических повреждений</li>
|
||||
<li>Есть гарантийный талон</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🛠️</div>
|
||||
<h3>Ваши права при браке</h3>
|
||||
<ul class="compact-list">
|
||||
<li>Бесплатный ремонт</li>
|
||||
<li>Замена товара</li>
|
||||
<li>Возврат денег</li>
|
||||
<li>Снижение цены</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">⏱️</div>
|
||||
<h3>Срок ремонта</h3>
|
||||
<p>Максимум 45 дней по закону. Если срок нарушен - можно требовать замену или возврат денег.</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">🚫</div>
|
||||
<h3>Гарантия не действует</h3>
|
||||
<div class="noguar-grid">
|
||||
<div class="noguar-item">Механические повреждения</div>
|
||||
<div class="noguar-item">Неправильная эксплуатация</div>
|
||||
<div class="noguar-item">Попадание жидкости</div>
|
||||
<div class="noguar-item">Самостоятельный ремонт</div>
|
||||
<div class="noguar-item">Форс-мажор</div>
|
||||
<div class="noguar-item">Естественный износ</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">📝</div>
|
||||
<h3>Как подать заявку</h3>
|
||||
<div class="process-steps-compact">
|
||||
<p>1. Свяжитесь с продавцом</p>
|
||||
<p>2. Опишите проблему</p>
|
||||
<p>3. Получите адрес сервиса</p>
|
||||
<p>4. Отправьте товар</p>
|
||||
<p>5. Получите акт приема</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-card wide">
|
||||
<div class="card-icon">📞</div>
|
||||
<h3>Нужна помощь?</h3>
|
||||
<p>При возникновении споров:</p>
|
||||
<a href="mailto:info@lavero.store" class="contact-email">info@lavero.store</a>
|
||||
<p><a [href]="env.phoneTel">{{ env.phones.support }}</a></p>
|
||||
<p class="note">Тема: "Гарантийный вопрос - Заказ №..."</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { environment } from '../../../../../../../environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-guarantee-lavero-ru',
|
||||
templateUrl: './guarantee-ru.component.html',
|
||||
styleUrls: ['../../../../../../pages/info/guarantee/guarantee.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class GuaranteeLaveroRuComponent {
|
||||
protected readonly env = environment;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
@switch (lang()) {
|
||||
@case ('ru') { <app-company-details-lavero-ru /> }
|
||||
@case ('en') { <app-company-details-lavero-en /> }
|
||||
@case ('hy') { <app-company-details-lavero-hy /> }
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Component, ChangeDetectionStrategy, inject } from '@angular/core';
|
||||
import { LanguageService } from '../../../../../services/language.service';
|
||||
import { CompanyDetailsLaveroRuComponent } from './ru/company-details-ru.component';
|
||||
import { CompanyDetailsLaveroEnComponent } from './en/company-details-en.component';
|
||||
import { CompanyDetailsLaveroHyComponent } from './hy/company-details-hy.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-company-details-lavero',
|
||||
imports: [CompanyDetailsLaveroRuComponent, CompanyDetailsLaveroEnComponent, CompanyDetailsLaveroHyComponent],
|
||||
templateUrl: './company-details.component.html',
|
||||
styleUrls: ['../../../../../pages/legal/company-details/company-details.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class CompanyDetailsLaveroComponent {
|
||||
lang = inject(LanguageService).currentLanguage;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<div class="legal-page">
|
||||
<div class="legal-container">
|
||||
<h1>Реквизиты организации / Company Details</h1>
|
||||
|
||||
<section class="legal-section">
|
||||
<h2>Полное наименование / Full Name</h2>
|
||||
<p>«ԼԱՎԵՐՈ» Սահմանափակ պատասխանատվությամբ ընկերություն</p>
|
||||
<p>«ЛАВЕРО» Общество с Ограниченной Ответственностью</p>
|
||||
<p>«LAVERO» LIMITED LIABILITY COMPANY</p>
|
||||
</section>
|
||||
|
||||
<section class="legal-section">
|
||||
<h2>Юридический адрес / Legal Address</h2>
|
||||
<p>ՀԱՅԱՍՏԱՆ, ԿՈՏԱՅՔ, ԱԲՈՎՅԱՆ, ՎԵՐԻՆ ՊՏՂՆԻ, 3-րդ փողոց, 28, Տ Փ/Դ՝ 2228</p>
|
||||
</section>
|
||||
|
||||
<section class="legal-section">
|
||||
<h2>Основные реквизиты / Core Details</h2>
|
||||
<div class="details-grid">
|
||||
<div class="detail-item">
|
||||
<strong>ՀՎՀՀ (ИНН/TIN):</strong>
|
||||
<span>03590442</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<strong>Գրանցման համար (Registration Number):</strong>
|
||||
<span>999.110.1583686</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<strong>Գրանցման ամսաթիվ (Date of Registration):</strong>
|
||||
<span>18.05.2026</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="legal-section">
|
||||
<h2>Контактная информация / Contact Information</h2>
|
||||
<div class="details-grid">
|
||||
<div class="detail-item">
|
||||
<strong>Email:</strong>
|
||||
<span><a href="mailto:info@lavero.store">info@lavero.store</a></span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<strong>Сайт / Website:</strong>
|
||||
<span><a href="https://lavero.store" target="_blank">lavero.store</a></span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="legal-section">
|
||||
<h2>Руководство / Management</h2>
|
||||
<p><strong>Տնօրեն (Director):</strong> ԳԵՎՈՐԳ ՄԱԹԵՎՈՍՅԱՆ (GEVORG MATEVOSYAN)</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { environment } from '../../../../../../../environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-company-details-lavero-en',
|
||||
templateUrl: './company-details-en.component.html',
|
||||
styleUrls: ['../../../../../../pages/legal/company-details/company-details.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class CompanyDetailsLaveroEnComponent {
|
||||
protected readonly env = environment;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<div class="legal-page">
|
||||
<div class="legal-container">
|
||||
<h1>Реквизиты организации / Company Details</h1>
|
||||
|
||||
<section class="legal-section">
|
||||
<h2>Полное наименование / Full Name</h2>
|
||||
<p>«ԼԱՎԵՐՈ» Սահմանափակ պատասխանատվությամբ ընկերություն</p>
|
||||
<p>«ЛАВЕРО» Общество с Ограниченной Ответственностью</p>
|
||||
<p>«LAVERO» LIMITED LIABILITY COMPANY</p>
|
||||
</section>
|
||||
|
||||
<section class="legal-section">
|
||||
<h2>Юридический адрес / Legal Address</h2>
|
||||
<p>ՀԱՅԱՍՏԱՆ, ԿՈՏԱՅՔ, ԱԲՈՎՅԱՆ, ՎԵՐԻՆ ՊՏՂՆԻ, 3-րդ փողոց, 28, Տ Փ/Դ՝ 2228</p>
|
||||
</section>
|
||||
|
||||
<section class="legal-section">
|
||||
<h2>Основные реквизиты / Core Details</h2>
|
||||
<div class="details-grid">
|
||||
<div class="detail-item">
|
||||
<strong>ՀՎՀՀ (ИНН/TIN):</strong>
|
||||
<span>03590442</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<strong>Գրանցման համար (Registration Number):</strong>
|
||||
<span>999.110.1583686</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<strong>Գրանցման ամսաթիվ (Date of Registration):</strong>
|
||||
<span>18.05.2026</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="legal-section">
|
||||
<h2>Контактная информация / Contact Information</h2>
|
||||
<div class="details-grid">
|
||||
<div class="detail-item">
|
||||
<strong>Email:</strong>
|
||||
<span><a href="mailto:info@lavero.store">info@lavero.store</a></span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<strong>Сайт / Website:</strong>
|
||||
<span><a href="https://lavero.store" target="_blank">lavero.store</a></span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="legal-section">
|
||||
<h2>Руководство / Management</h2>
|
||||
<p><strong>Տնօրեն (Director):</strong> ԳԵՎՈՐԳ ՄԱԹԵՎՈՍՅԱՆ (GEVORG MATEVOSYAN)</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { environment } from '../../../../../../../environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-company-details-lavero-hy',
|
||||
templateUrl: './company-details-hy.component.html',
|
||||
styleUrls: ['../../../../../../pages/legal/company-details/company-details.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class CompanyDetailsLaveroHyComponent {
|
||||
protected readonly env = environment;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<div class="legal-page">
|
||||
<div class="legal-container">
|
||||
<h1>Реквизиты организации / Company Details</h1>
|
||||
|
||||
<section class="legal-section">
|
||||
<h2>Полное наименование / Full Name</h2>
|
||||
<p>«ԼԱՎԵՐՈ» Սահմանափակ պատասխանատվությամբ ընկերություն</p>
|
||||
<p>«ЛАВЕРО» Общество с Ограниченной Ответственностью</p>
|
||||
<p>«LAVERO» LIMITED LIABILITY COMPANY</p>
|
||||
</section>
|
||||
|
||||
<section class="legal-section">
|
||||
<h2>Юридический адрес / Legal Address</h2>
|
||||
<p>ՀԱՅԱՍՏԱՆ, ԿՈՏԱՅՔ, ԱԲՈՎՅԱՆ, ՎԵՐԻՆ ՊՏՂՆԻ, 3-րդ փողոց, 28, Տ Փ/Դ՝ 2228</p>
|
||||
</section>
|
||||
|
||||
<section class="legal-section">
|
||||
<h2>Основные реквизиты / Core Details</h2>
|
||||
<div class="details-grid">
|
||||
<div class="detail-item">
|
||||
<strong>ՀՎՀՀ (ИНН/TIN):</strong>
|
||||
<span>03590442</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<strong>Գրանցման համար (Registration Number):</strong>
|
||||
<span>999.110.1583686</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<strong>Գրանցման ամսաթիվ (Date of Registration):</strong>
|
||||
<span>18.05.2026</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="legal-section">
|
||||
<h2>Контактная информация / Contact Information</h2>
|
||||
<div class="details-grid">
|
||||
<div class="detail-item">
|
||||
<strong>Email:</strong>
|
||||
<span><a href="mailto:info@lavero.store">info@lavero.store</a></span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<strong>Сайт / Website:</strong>
|
||||
<span><a href="https://lavero.store" target="_blank">lavero.store</a></span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="legal-section">
|
||||
<h2>Руководство / Management</h2>
|
||||
<p><strong>Տնօրեն (Director):</strong> ԳԵՎՈՐԳ ՄԱԹԵՎՈՍՅԱՆ (GEVORG MATEVOSYAN)</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { environment } from '../../../../../../../environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-company-details-lavero-ru',
|
||||
templateUrl: './company-details-ru.component.html',
|
||||
styleUrls: ['../../../../../../pages/legal/company-details/company-details.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class CompanyDetailsLaveroRuComponent {
|
||||
protected readonly env = environment;
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
<div class="legal-page">
|
||||
<div class="legal-container">
|
||||
<div class="lavero-header">
|
||||
<h1>Payment Terms</h1>
|
||||
<p class="subtitle">All payment methods and transaction conditions</p>
|
||||
</div>
|
||||
|
||||
<div class="lavero-cards">
|
||||
<section class="info-card wide">
|
||||
<div class="card-icon">📋</div>
|
||||
<h2>1. General Provisions</h2>
|
||||
<p>1.1. These Terms define the payment procedure for Goods and Services purchased by Buyers through the Lavero Store Marketplace.</p>
|
||||
<p>1.2. Payment is made for Goods/Services listed by independent Sellers. The Marketplace acts as an information intermediary and provides the technical infrastructure for processing payments.</p>
|
||||
<p>1.3. Payment for goods and services on the Marketplace is made in Russian rubles (RUB).</p>
|
||||
<p>1.4. Prices for Goods/Services are set by Sellers independently and are indicated on the corresponding Goods/Services page.</p>
|
||||
</section>
|
||||
|
||||
<section class="info-card wide">
|
||||
<div class="card-icon">💳</div>
|
||||
<h2>2. Payment Methods</h2>
|
||||
<p>2.1. The Marketplace supports the following payment methods:</p>
|
||||
<div class="payment-methods-grid">
|
||||
<div class="method-item">
|
||||
<span class="method-icon">🏦</span>
|
||||
<div>
|
||||
<strong>Bank Cards</strong>
|
||||
<p>Visa, Mastercard, МИР</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="method-item">
|
||||
<span class="method-icon">⚡</span>
|
||||
<div>
|
||||
<strong>SBP</strong>
|
||||
<p>Fast Payment System - instant transfer via mobile banking app</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="method-item">
|
||||
<span class="method-icon">👛</span>
|
||||
<div>
|
||||
<strong>E-Wallets</strong>
|
||||
<p>ЮMoney, QIWI (if available)</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="method-item">
|
||||
<span class="method-icon">🔗</span>
|
||||
<div>
|
||||
<strong>Payment by Link</strong>
|
||||
<p>Generation of a unique payment link for each order</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p>2.2. Available payment methods may vary depending on the Seller and the type of Goods/Services.</p>
|
||||
<p>2.3. All payments are processed through certified payment systems in compliance with PCI DSS security standards.</p>
|
||||
</section>
|
||||
|
||||
<section class="info-card wide">
|
||||
<div class="card-icon">⚙️</div>
|
||||
<h2>3. Payment Process</h2>
|
||||
<p>3.1. The order payment procedure includes the following steps:</p>
|
||||
<ol class="compact-list">
|
||||
<li>Selection of Goods/Services and adding them to the cart</li>
|
||||
<li>Placing an order with contact details and delivery method</li>
|
||||
<li>Choosing a payment method from the available options</li>
|
||||
<li>Redirect to the secure payment system page</li>
|
||||
<li>Entering payment details and confirming the payment</li>
|
||||
<li>Receiving a successful payment notification</li>
|
||||
</ol>
|
||||
<p>3.2. When paying by bank card, the Buyer may be redirected to the issuing bank's page for additional authentication (3D-Secure).</p>
|
||||
<p>3.3. The Buyer's payment obligation is considered fulfilled from the moment the funds are received in the payment system account.</p>
|
||||
</section>
|
||||
|
||||
<section class="info-card wide">
|
||||
<div class="card-icon">🛡️</div>
|
||||
<h2>4. Payment Security</h2>
|
||||
<p>4.1. All payments are processed through a secure HTTPS connection using TLS 1.2 protocol and above.</p>
|
||||
<p>4.2. The Marketplace does not store full bank card data of Buyers. Payment data processing is carried out by certified payment aggregators.</p>
|
||||
<div class="features-list">
|
||||
<div class="feature">✓ TLS 1.2+ Encryption</div>
|
||||
<div class="feature">✓ 3D-Secure Technology</div>
|
||||
<div class="feature">✓ Fraud Protection</div>
|
||||
<div class="feature">✓ Data Confidentiality</div>
|
||||
</div>
|
||||
<p>4.3. To protect against fraud, 3D-Secure technology is used, requiring payment confirmation via SMS code or push notification from the bank.</p>
|
||||
<p>4.4. In case of suspicious activity, the payment system has the right to request additional identity verification of the Buyer.</p>
|
||||
</section>
|
||||
|
||||
<section class="info-card wide">
|
||||
<div class="card-icon">✅</div>
|
||||
<h2>5. Payment Confirmation</h2>
|
||||
<p>5.1. After successful payment, the Buyer receives a confirmation to the email address provided during checkout.</p>
|
||||
<p>5.2. The confirmation contains the following information:</p>
|
||||
<ul class="compact-list">
|
||||
<li>Order number</li>
|
||||
<li>Payment date and time</li>
|
||||
<li>Payment amount</li>
|
||||
<li>Order contents</li>
|
||||
<li>Seller contact details</li>
|
||||
</ul>
|
||||
<p>5.3. Order information is also displayed in the Buyer's personal account on the Marketplace (if registered).</p>
|
||||
<p>5.4. A fiscal receipt is sent by the Seller in accordance with the requirements of the legislation of the Russian Federation.</p>
|
||||
</section>
|
||||
|
||||
<section class="info-card wide">
|
||||
<div class="card-icon">↩️</div>
|
||||
<h2>6. Refunds</h2>
|
||||
<p>6.1. The refund procedure is governed by the <a [routerLink]="'/return-policy' | langRoute">Return Policy</a> and depends on the type of purchased Goods/Services.</p>
|
||||
<p>6.2. Refunds are made to the same payment instrument from which the payment was made.</p>
|
||||
<p>6.3. The refund processing time is:</p>
|
||||
<div class="refund-times">
|
||||
<div class="refund-item">
|
||||
<strong>Bank Card</strong>
|
||||
<span>3-30 days</span>
|
||||
</div>
|
||||
<div class="refund-item">
|
||||
<strong>E-Wallet</strong>
|
||||
<span>1-5 days</span>
|
||||
</div>
|
||||
<div class="refund-item">
|
||||
<strong>SBP</strong>
|
||||
<span>1-3 days</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="note">Refunds are made to the same payment instrument used for the original payment</p>
|
||||
<p>6.4. The Marketplace does not charge a fee for processing refunds. Payment system and bank fees may apply in accordance with their tariffs.</p>
|
||||
</section>
|
||||
|
||||
<section class="info-card wide">
|
||||
<div class="card-icon">❌</div>
|
||||
<h2>7. Failed Payments</h2>
|
||||
<p>7.1. A payment may be declined for the following reasons:</p>
|
||||
<ul class="compact-list">
|
||||
<li>Insufficient funds in the account</li>
|
||||
<li>Incorrectly entered payment details</li>
|
||||
<li>Card is blocked or expired</li>
|
||||
<li>Transaction limits set by the bank have been exceeded</li>
|
||||
<li>Transaction rejected by the security system</li>
|
||||
</ul>
|
||||
<p>7.2. In case of an unsuccessful payment, the Buyer receives a notification indicating the reason for the decline.</p>
|
||||
<p>7.3. If you experience payment issues, it is recommended to:</p>
|
||||
<ul class="compact-list">
|
||||
<li>Check the accuracy of the entered data</li>
|
||||
<li>Contact the card issuing bank to clarify the reason for the decline</li>
|
||||
<li>Try an alternative payment method</li>
|
||||
<li>Contact support: <a href="mailto:info@lavero.store">info@lavero.store</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="info-card wide">
|
||||
<div class="card-icon">📧</div>
|
||||
<h2>8. Payment Inquiries Contact</h2>
|
||||
<p>For questions related to order payments, you can contact us:</p>
|
||||
<ul class="compact-list">
|
||||
<li><strong>Email:</strong> <a href="mailto:info@lavero.store" class="contact-email">info@lavero.store</a></li>
|
||||
<li><strong>Phone:</strong> <a [href]="env.phoneTel">{{ env.phones.support }}</a></li>
|
||||
<li><strong>Working hours:</strong> 24/7 (technical support)</li>
|
||||
<li><strong>Average response time:</strong> Up to 24 hours on business days</li>
|
||||
</ul>
|
||||
<p>When contacting us, please include your order number and a brief description of the issue for faster resolution.</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { environment } from '../../../../../../../environments/environment';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { LangRoutePipe } from '../../../../../../pipes/lang-route.pipe';
|
||||
|
||||
@Component({
|
||||
selector: 'app-payment-terms-lavero-en',
|
||||
imports: [RouterLink, LangRoutePipe],
|
||||
templateUrl: './payment-terms-en.component.html',
|
||||
styleUrls: ['../../../../../../pages/legal/payment-terms/payment-terms.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class PaymentTermsLaveroEnComponent {
|
||||
protected readonly env = environment;
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
<div class="legal-page">
|
||||
<div class="legal-container">
|
||||
<div class="lavero-header">
|
||||
<h1>Վճարման պայմաններ</h1>
|
||||
<p class="subtitle">Վճարման բոլոր եղանակները և գործարքների իրականացման պայմանները</p>
|
||||
</div>
|
||||
|
||||
<div class="lavero-cards">
|
||||
<section class="info-card wide">
|
||||
<div class="card-icon">📋</div>
|
||||
<h2>1. Ընդհանուր դրույթներ</h2>
|
||||
<p>1.1. Սույն Կանոնները սահմանում են Lavero Store Մարկեթպլեյսի միջոցով Գնորդների կողմից ձեռք բերվող Ապրանքների և Ծառայությունների վճարման կարգը։</p>
|
||||
<p>1.2. Վճարումը կատարվում է անկախ Վաճառողների կողմից տեղադրված Ապրանքների/Ծառայությունների համար։ Մարկեթպլեյսը հանդես է գալիս որպես տեղեկատվական միջնորդ և ապահովում է տեխնիկական ենթակառուցվածք վճարումների իրականացման համար։</p>
|
||||
<p>1.3. Մարկեթպլեյսում ապրանքների և ծառայությունների վճարումը կատարվում է ռուսական ռուբլով (RUB)։</p>
|
||||
<p>1.4. Ապրանքների/Ծառայությունների գները սահմանվում են Վաճառողների կողմից ինքնուրույն և նշված են համապատասխան Ապրանքի/Ծառայության էջում։</p>
|
||||
</section>
|
||||
|
||||
<section class="info-card wide">
|
||||
<div class="card-icon">💳</div>
|
||||
<h2>2. Վճարման եղանակներ</h2>
|
||||
<p>2.1. Մարկեթպլեյսը աջակցում է վճարման հետևյալ եղանակները՝</p>
|
||||
<div class="payment-methods-grid">
|
||||
<div class="method-item">
|
||||
<span class="method-icon">🏦</span>
|
||||
<div>
|
||||
<strong>Բանկային քարտեր</strong>
|
||||
<p>Visa, Mastercard, ՄИР</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="method-item">
|
||||
<span class="method-icon">⚡</span>
|
||||
<div>
|
||||
<strong>Արագ վճարման համակարգ (ՍБП)</strong>
|
||||
<p>Արագ վճարման համակարգ - ակնթարծային փոխանցում բանկի բջջային հավելվածի միջոցով</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="method-item">
|
||||
<span class="method-icon">👛</span>
|
||||
<div>
|
||||
<strong>Էլեկտրոնային դրամապանակներ</strong>
|
||||
<p>ЮMoney, QIWI (առկայության դեպքում)</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="method-item">
|
||||
<span class="method-icon">🔗</span>
|
||||
<div>
|
||||
<strong>Վճարում հղման միջոցով</strong>
|
||||
<p>Յուրաքանչյուր պատվերի համար յուրահատուկ վճարման հղման ստեղծում</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p>2.2. Հասանելի վճարման եղանակները կարող են տարբերվել կախված Վաճառողից և Ապրանքի/Ծառայության տեսակից։</p>
|
||||
<p>2.3. Բոլոր վճարումները մշակվում են սերտիֆիկացված վճարման համակարգերի միջոցով՝ PCI DSS անվտանգության ստանդարտներին համապատասխան։</p>
|
||||
</section>
|
||||
|
||||
<section class="info-card wide">
|
||||
<div class="card-icon">⚙️</div>
|
||||
<h2>3. Վճարման գործընթացը</h2>
|
||||
<p>3.1. Պատվերի վճարման գործընթացը ներառում է հետևյալ քայլերը՝</p>
|
||||
<ol class="compact-list">
|
||||
<li>Ապրանքների/Ծառայությունների ընտրություն և դրանց զամբյուղին ավելացնել</li>
|
||||
<li>Պատվերի ձևակերպում՝ կոնտակտային տվյալների և առաքման եղանակի նշումով</li>
|
||||
<li>Վճարման եղանակի ընտրությունը հասանելի տարբերակներից</li>
|
||||
<li>Վերահղում վճարման համակարգի պաշտպանված էջին</li>
|
||||
<li>Վճարման տվյալների մուտքագրում և վճարման հաստատում</li>
|
||||
<li>Հաջող վճարման մասին ծանուցման ստացում</li>
|
||||
</ol>
|
||||
<p>3.2. Բանկային քարտով վճարելիս Գնորդը կարող է վերահղվել թողարկող բանկի էջ լրացուցիչ նույնականացման համար (3D-Secure)։</p>
|
||||
<p>3.3. Գնորդի վճարման պարտավորությունը համարվում է կատարված վճարման համակարգի հաշվին դրամական միջոցների մուտքագրման պահից։</p>
|
||||
</section>
|
||||
|
||||
<section class="info-card wide">
|
||||
<div class="card-icon">🛡️</div>
|
||||
<h2>4. Վճարումների անվտանգություն</h2>
|
||||
<p>4.1. Բոլոր վճարումները մշակվում են պաշտպանված HTTPS կապակցով՝ TLS 1.2 և ավելի բարձր արթանագրի օգտագործմամբ։</p>
|
||||
<p>4.2. Մարկեթպլեյսը չի պահպանում Գնորդների բանկային քարտերի լիարժեք տվյալները։ Վճարման տվյալների մշակումը իրականացվում է սերտիֆիկացված վճարման ագրեգատորների կողմից։</p>
|
||||
<div class="features-list">
|
||||
<div class="feature">✓ TLS 1.2+ գաղտնագրում</div>
|
||||
<div class="feature">✓ 3D-Secure տեխնոլոգիա</div>
|
||||
<div class="feature">✓ Խաբեությունից պաշտպանություն</div>
|
||||
<div class="feature">✓ Տվյալների գաղտնիություն</div>
|
||||
</div>
|
||||
<p>4.3. Խաբեությունից պաշտպանության համար կիրառվում է 3D-Secure տեխնոլոգիան՝ պահանջելով վճարման հաստատումը SMS կոդի կամ բանկի push ծանուցման միջոցով։</p>
|
||||
<p>4.4. Կասկածելի գործունեության դեպքում վճարման համակարգը իրավունք ունի պահանջել Գնորդի ինքնության լրացուցիչ ստուգում։</p>
|
||||
</section>
|
||||
|
||||
<section class="info-card wide">
|
||||
<div class="card-icon">✅</div>
|
||||
<h2>5. Վճարման հաստատում</h2>
|
||||
<p>5.1. Հաջող վճարմանից հետո Գնորդը ստանում է հաստատում պատվերի ձևակերպման թելադրված էլ. փոստի հասցեին։</p>
|
||||
<p>5.2. Հաստատումը պարունակում է հետևյալ տեղեկությունը՝</p>
|
||||
<ul class="compact-list">
|
||||
<li>Պատվերի համար</li>
|
||||
<li>Վճարման ամսաթիվ և ժամ</li>
|
||||
<li>Վճարման գումար</li>
|
||||
<li>Պատվերի կազմը</li>
|
||||
<li>Վաճառողի կոնտակտային տվյալներ</li>
|
||||
</ul>
|
||||
<p>5.3. Պատվերի տեղեկությունը նաև ցուցադրվում է Գնորդի անձնական ընթացում Մարկեթպլեյսում (գրանցման դեպքում)։</p>
|
||||
<p>5.4. Հարկային կտրոնը ուղարկվում է Վաճառողի կողմից՝ Ռուսաստանի Դաշնության օրենսդրության պահանջներին համապատասխան։</p>
|
||||
</section>
|
||||
|
||||
<section class="info-card wide">
|
||||
<div class="card-icon">↩️</div>
|
||||
<h2>6. Միջոցների վերադարձ</h2>
|
||||
<p>6.1. Դրամական միջոցների վերադարձի կարգը կարգավորվում է <a [routerLink]="'/return-policy' | langRoute">Վերադարձի քաղաքականությամբ</a> և կախված է ձեռք բերված Ապրանքի/Ծառայության տեսակից։</p>
|
||||
<p>6.2. Միջոցների վերադարձը կատարվում է նույն վճարման գործիքին՝ որից կատարվել էր վճարումը։</p>
|
||||
<p>6.3. Դրամական միջոցների վերադարձի ժամկետը կազմում է՝</p>
|
||||
<div class="refund-times">
|
||||
<div class="refund-item">
|
||||
<strong>Բանկային քարտ</strong>
|
||||
<span>3-30 օր</span>
|
||||
</div>
|
||||
<div class="refund-item">
|
||||
<strong>Էլեկտրոնային դրամապանակ</strong>
|
||||
<span>1-5 օր</span>
|
||||
</div>
|
||||
<div class="refund-item">
|
||||
<strong>Արագ վճարման համակարգ (ՍБП)</strong>
|
||||
<span>1-3 օր</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="note">Վերադարձը կատարվում է նույն վճարման գործիքին՝ որը օգտագործվել է վճարման ժամանակ</p>
|
||||
<p>6.4. Միջոցների վերադարձի մշակման համար Մարկեթպլեյսը միջնորդավճար չի գանձում։ Վճարման համակարգերի և բանկերի միջնորդավճարները կարող են կիրառվել դրանց սակագներին համապատասխան։</p>
|
||||
</section>
|
||||
|
||||
<section class="info-card wide">
|
||||
<div class="card-icon">❌</div>
|
||||
<h2>7. Անհաջող վճարումներ</h2>
|
||||
<p>7.1. Վճարումը կարող է մերժվել հետևյալ պատճառներով՝</p>
|
||||
<ul class="compact-list">
|
||||
<li>Հաշվին անբավարար միջոցներ</li>
|
||||
<li>Վճարման տվյալները սխալ են մուտքագրված</li>
|
||||
<li>Քարտը արգելափակված է կամ ժամկետը լրացել է</li>
|
||||
<li>Բանկի կողմից սահմանված գործարքների սահմանաչափերը գերազանցված է</li>
|
||||
<li>Անվտանգության համակարգի կողմից գործարքի մերժում</li>
|
||||
</ul>
|
||||
<p>7.2. Անհաջող վճարման դեպքում Գնորդը ստանում է ծանուցում՝ մերժման պատճառի նշումով։</p>
|
||||
<p>7.3. Վճարման խնդիրների դեպքում խորհուրդ է տրվում՝</p>
|
||||
<ul class="compact-list">
|
||||
<li>Ստուգել մուտքագրված տվյալների ճշտությունը</li>
|
||||
<li>Կապվել քարտ թողարկող բանկի հետ մերժման պատճառը պարզելու համար</li>
|
||||
<li>Փորձել այլընտրանքային վճարման եղանակ</li>
|
||||
<li>Դիմել աջակցության ծառայությանը՝ <a href="mailto:info@lavero.store">info@lavero.store</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="info-card wide">
|
||||
<div class="card-icon">📧</div>
|
||||
<h2>8. Վճարման հարցերի կապակցության կոնտակտներ</h2>
|
||||
<p>Պատվերների վճարման հետ կապված հարցերի համար կարող եք դիմել՝</p>
|
||||
<ul class="compact-list">
|
||||
<li><strong>Email:</strong> <a href="mailto:info@lavero.store" class="contact-email">info@lavero.store</a></li>
|
||||
<li><strong>Հեռախոս՝</strong> <a [href]="env.phoneTel">{{ env.phones.support }}</a></li>
|
||||
<li><strong>Աշխատանքի ժամերը՝</strong> Հստակետ (տեխնիկական աջակցություն)</li>
|
||||
<li><strong>Պատասխանի միջին ժամանակը՝</strong> Մինչև 24 ժամ աշխատանքային օրերին</li>
|
||||
</ul>
|
||||
<p>Դիմելիս նշեք պատվերի համարը և խնդրի հակիրճ նկարագրությունը՝ հարցի ավելի արագ լուծման համար։</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { environment } from '../../../../../../../environments/environment';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { LangRoutePipe } from '../../../../../../pipes/lang-route.pipe';
|
||||
|
||||
@Component({
|
||||
selector: 'app-payment-terms-lavero-hy',
|
||||
imports: [RouterLink, LangRoutePipe],
|
||||
templateUrl: './payment-terms-hy.component.html',
|
||||
styleUrls: ['../../../../../../pages/legal/payment-terms/payment-terms.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class PaymentTermsLaveroHyComponent {
|
||||
protected readonly env = environment;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
@switch (lang()) {
|
||||
@case ('ru') { <app-payment-terms-lavero-ru /> }
|
||||
@case ('en') { <app-payment-terms-lavero-en /> }
|
||||
@case ('hy') { <app-payment-terms-lavero-hy /> }
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Component, ChangeDetectionStrategy, inject } from '@angular/core';
|
||||
import { LanguageService } from '../../../../../services/language.service';
|
||||
import { PaymentTermsLaveroRuComponent } from './ru/payment-terms-ru.component';
|
||||
import { PaymentTermsLaveroEnComponent } from './en/payment-terms-en.component';
|
||||
import { PaymentTermsLaveroHyComponent } from './hy/payment-terms-hy.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-payment-terms-lavero',
|
||||
imports: [PaymentTermsLaveroRuComponent, PaymentTermsLaveroEnComponent, PaymentTermsLaveroHyComponent],
|
||||
templateUrl: './payment-terms.component.html',
|
||||
styleUrls: ['../../../../../pages/legal/payment-terms/payment-terms.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class PaymentTermsLaveroComponent {
|
||||
lang = inject(LanguageService).currentLanguage;
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
<div class="legal-page">
|
||||
<div class="legal-container">
|
||||
<div class="lavero-header">
|
||||
<h1>Правила оплаты</h1>
|
||||
<p class="subtitle">Все способы оплаты и условия проведения платежей</p>
|
||||
</div>
|
||||
|
||||
<div class="lavero-cards">
|
||||
<section class="info-card wide">
|
||||
<div class="card-icon">📋</div>
|
||||
<h2>1. Общие положения</h2>
|
||||
<p>1.1. Настоящие Правила определяют порядок оплаты Товаров и Услуг, приобретаемых Покупателями через Маркетплейс Lavero Store.</p>
|
||||
<p>1.2. Оплата производится за Товары/Услуги, размещенные независимыми Продавцами. Маркетплейс выступает в качестве информационного посредника и обеспечивает техническую инфраструктуру для проведения платежей.</p>
|
||||
<p>1.3. Оплата товаров и услуг на Маркетплейсе осуществляется в российских рублях (RUB).</p>
|
||||
<p>1.4. Цены на Товары/Услуги устанавливаются Продавцами самостоятельно и указываются на странице соответствующего Товара/Услуги.</p>
|
||||
</section>
|
||||
|
||||
<section class="info-card wide">
|
||||
<div class="card-icon">💳</div>
|
||||
<h2>2. Способы оплаты</h2>
|
||||
<p>2.1. Маркетплейс поддерживает следующие способы оплаты:</p>
|
||||
<div class="payment-methods-grid">
|
||||
<div class="method-item">
|
||||
<span class="method-icon">🏦</span>
|
||||
<div>
|
||||
<strong>Банковские карты</strong>
|
||||
<p>Visa, Mastercard, МИР</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="method-item">
|
||||
<span class="method-icon">⚡</span>
|
||||
<div>
|
||||
<strong>СБП</strong>
|
||||
<p>Система быстрых платежей - мгновенный перевод через мобильное приложение банка</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="method-item">
|
||||
<span class="method-icon">👛</span>
|
||||
<div>
|
||||
<strong>Электронные кошельки</strong>
|
||||
<p>ЮMoney, QIWI (при наличии)</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="method-item">
|
||||
<span class="method-icon">🔗</span>
|
||||
<div>
|
||||
<strong>Оплата по ссылке</strong>
|
||||
<p>Генерация уникальной платежной ссылки для каждого заказа</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p>2.2. Доступные способы оплаты могут различаться в зависимости от Продавца и типа Товара/Услуги.</p>
|
||||
<p>2.3. Все платежи обрабатываются через сертифицированные платежные системы с соблюдением стандартов безопасности PCI DSS.</p>
|
||||
</section>
|
||||
|
||||
<section class="info-card wide">
|
||||
<div class="card-icon">⚙️</div>
|
||||
<h2>3. Процесс оплаты</h2>
|
||||
<p>3.1. Процедура оплаты заказа включает следующие этапы:</p>
|
||||
<ol class="compact-list">
|
||||
<li>Выбор Товаров/Услуг и добавление их в корзину</li>
|
||||
<li>Оформление заказа с указанием контактных данных и способа доставки</li>
|
||||
<li>Выбор способа оплаты из доступных вариантов</li>
|
||||
<li>Перенаправление на защищенную страницу платежной системы</li>
|
||||
<li>Ввод платежных данных и подтверждение оплаты</li>
|
||||
<li>Получение уведомления об успешной оплате</li>
|
||||
</ol>
|
||||
<p>3.2. При оплате банковской картой Покупатель может быть перенаправлен на страницу банка-эмитента для прохождения дополнительной аутентификации (3D-Secure).</p>
|
||||
<p>3.3. Обязательство Покупателя по оплате считается исполненным с момента поступления денежных средств на счет платежной системы.</p>
|
||||
</section>
|
||||
|
||||
<section class="info-card wide">
|
||||
<div class="card-icon">🛡️</div>
|
||||
<h2>4. Безопасность платежей</h2>
|
||||
<p>4.1. Все платежи обрабатываются через защищенное HTTPS-соединение с использованием протокола TLS 1.2 и выше.</p>
|
||||
<p>4.2. Маркетплейс не хранит полные данные банковских карт Покупателей. Обработка платежных данных осуществляется сертифицированными платежными агрегаторами.</p>
|
||||
<div class="features-list">
|
||||
<div class="feature">✓ Шифрование TLS 1.2+</div>
|
||||
<div class="feature">✓ Технология 3D-Secure</div>
|
||||
<div class="feature">✓ Защита от мошенничества</div>
|
||||
<div class="feature">✓ Конфиденциальность данных</div>
|
||||
</div>
|
||||
<p>4.3. Для защиты от мошенничества применяется технология 3D-Secure, требующая подтверждения платежа через SMS-код или push-уведомление от банка.</p>
|
||||
<p>4.4. В случае подозрительной активности платежная система имеет право запросить дополнительную верификацию личности Покупателя.</p>
|
||||
</section>
|
||||
|
||||
<section class="info-card wide">
|
||||
<div class="card-icon">✅</div>
|
||||
<h2>5. Подтверждение оплаты</h2>
|
||||
<p>5.1. После успешной оплаты Покупатель получает подтверждение на указанный при оформлении заказа адрес электронной почты.</p>
|
||||
<p>5.2. Подтверждение содержит следующую информацию:</p>
|
||||
<ul class="compact-list">
|
||||
<li>Номер заказа</li>
|
||||
<li>Дата и время оплаты</li>
|
||||
<li>Сумма платежа</li>
|
||||
<li>Состав заказа</li>
|
||||
<li>Контактные данные Продавца</li>
|
||||
</ul>
|
||||
<p>5.3. Информация о заказе также отображается в личном кабинете Покупателя на Маркетплейсе (при наличии регистрации).</p>
|
||||
<p>5.4. Фискальный чек направляется Продавцом в соответствии с требованиями законодательства РФ.</p>
|
||||
</section>
|
||||
|
||||
<section class="info-card wide">
|
||||
<div class="card-icon">↩️</div>
|
||||
<h2>6. Возврат средств</h2>
|
||||
<p>6.1. Порядок возврата денежных средств регулируется <a [routerLink]="'/return-policy' | langRoute">Политикой возврата</a> и зависит от типа приобретенного Товара/Услуги.</p>
|
||||
<p>6.2. Возврат средств производится на тот же платежный инструмент, с которого была произведена оплата.</p>
|
||||
<p>6.3. Срок возврата денежных средств составляет:</p>
|
||||
<div class="refund-times">
|
||||
<div class="refund-item">
|
||||
<strong>Банковская карта</strong>
|
||||
<span>3-30 дней</span>
|
||||
</div>
|
||||
<div class="refund-item">
|
||||
<strong>Электронный кошелек</strong>
|
||||
<span>1-5 дней</span>
|
||||
</div>
|
||||
<div class="refund-item">
|
||||
<strong>СБП</strong>
|
||||
<span>1-3 дня</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="note">Возврат производится на тот же платежный инструмент, который использовался при оплате</p>
|
||||
<p>6.4. За обработку возврата средств Маркетплейс комиссию не взимает. Комиссии платежных систем и банков могут применяться в соответствии с их тарифами.</p>
|
||||
</section>
|
||||
|
||||
<section class="info-card wide">
|
||||
<div class="card-icon">❌</div>
|
||||
<h2>7. Неуспешные платежи</h2>
|
||||
<p>7.1. Платеж может быть отклонен по следующим причинам:</p>
|
||||
<ul class="compact-list">
|
||||
<li>Недостаточно средств на счете</li>
|
||||
<li>Неверно введены платежные данные</li>
|
||||
<li>Карта заблокирована или просрочена</li>
|
||||
<li>Превышены лимиты на операции, установленные банком</li>
|
||||
<li>Отказ в проведении транзакции системой безопасности</li>
|
||||
</ul>
|
||||
<p>7.2. В случае неуспешной оплаты Покупатель получает уведомление с указанием причины отказа.</p>
|
||||
<p>7.3. При возникновении проблем с оплатой рекомендуется:</p>
|
||||
<ul class="compact-list">
|
||||
<li>Проверить правильность введенных данных</li>
|
||||
<li>Связаться с банком-эмитентом карты для уточнения причины отказа</li>
|
||||
<li>Попробовать альтернативный способ оплаты</li>
|
||||
<li>Обратиться в службу поддержки: <a href="mailto:info@lavero.store">info@lavero.store</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="info-card wide">
|
||||
<div class="card-icon">📧</div>
|
||||
<h2>8. Контакты для вопросов по оплате</h2>
|
||||
<p>По вопросам, связанным с оплатой заказов, вы можете обратиться:</p>
|
||||
<ul class="compact-list">
|
||||
<li><strong>Email:</strong> <a href="mailto:info@lavero.store" class="contact-email">info@lavero.store</a></li>
|
||||
<li><strong>Телефон:</strong> <a [href]="env.phoneTel">{{ env.phones.support }}</a></li>
|
||||
<li><strong>Время работы:</strong> Круглосуточно (техническая поддержка)</li>
|
||||
<li><strong>Среднее время ответа:</strong> До 24 часов в рабочие дни</li>
|
||||
</ul>
|
||||
<p>При обращении указывайте номер заказа и краткое описание проблемы для более быстрого решения вопроса.</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { environment } from '../../../../../../../environments/environment';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { LangRoutePipe } from '../../../../../../pipes/lang-route.pipe';
|
||||
|
||||
@Component({
|
||||
selector: 'app-payment-terms-lavero-ru',
|
||||
imports: [RouterLink, LangRoutePipe],
|
||||
templateUrl: './payment-terms-ru.component.html',
|
||||
styleUrls: ['../../../../../../pages/legal/payment-terms/payment-terms.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class PaymentTermsLaveroRuComponent {
|
||||
protected readonly env = environment;
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
<div class="legal-page">
|
||||
<div class="legal-container">
|
||||
<div class="lavero-header">
|
||||
<h1>Privacy Policy</h1>
|
||||
<p class="subtitle">Protection of your personal data</p>
|
||||
</div>
|
||||
|
||||
<div class="lavero-cards">
|
||||
<section class="info-card wide">
|
||||
<div class="card-icon">📋</div>
|
||||
<h2>1. GENERAL PROVISIONS</h2>
|
||||
|
||||
<p>1.1. This Privacy Policy (hereinafter — the Policy) defines the obligations of the Limited Liability Company «ЛАВЕРО» (hereinafter — the Operator) regarding the processing, use, storage, and protection of personal data (hereinafter PD, Personal Data) of users (hereinafter — the User).</p>
|
||||
|
||||
<p>1.2. The Operator undertakes to strictly observe the rights and freedoms of individuals when processing their personal data, including the protection of the right to privacy, personal and family confidentiality.</p>
|
||||
|
||||
<p>1.3. The Personal Data Processing Policy (hereinafter — the Policy) has been developed in accordance with Federal Law No. 152-FZ dated July 27, 2006 «On Personal Data» (hereinafter — FZ-152).</p>
|
||||
|
||||
<p>1.4. The Policy can be reviewed on the Operator's website on the Internet at: <a href="https://lavero.store">https://lavero.store</a>.</p>
|
||||
|
||||
<p>1.5. Applies to all activities related to the processing of personal data on the website <a href="https://lavero.store">https://lavero.store</a> and in the Operator's information systems.</p>
|
||||
|
||||
<p>1.6. A User who places an order, opens a personal account, or otherwise interacts with the Operator expresses consent to the processing of their personal data in accordance with the Policy and the legislation of the Russian Federation. Continued use of the website indicates agreement with the provisions of the Policy. A User who is not willing to agree to the terms should refrain from using the resource.</p>
|
||||
|
||||
<p><strong>Additionally:</strong> This Policy applies to personal data collected both before and after the document comes into effect.</p>
|
||||
</section>
|
||||
|
||||
<section class="info-card wide">
|
||||
<div class="card-icon">📖</div>
|
||||
<h2>2. TERMS AND DEFINITIONS</h2>
|
||||
|
||||
<p>The following main terms and definitions are used in this Policy:</p>
|
||||
|
||||
<div class="compact-list">
|
||||
<p><strong>Personal Data (PD)</strong> — any information directly or indirectly related to a specific individual (personal data subject).</p>
|
||||
|
||||
<p><strong>Personal Data Information System (PDIS)</strong> — a set of personal data stored in databases, as well as technologies and tools for their processing.</p>
|
||||
|
||||
<p><strong>Automated PD Processing</strong> — data processing using computer tools.</p>
|
||||
|
||||
<p><strong>PD Blocking</strong> — temporary suspension of data processing (except in cases of data clarification).</p>
|
||||
|
||||
<p><strong>PD Anonymization</strong> — actions that make it impossible to determine the ownership of data to a specific person without additional information.</p>
|
||||
|
||||
<p><strong>Website (Site)</strong> — an automated information system available on the Internet at: <a href="https://lavero.store">https://lavero.store</a>.</p>
|
||||
|
||||
<p><strong>PD Processing</strong> — any actions with personal data, including collection, recording, storage, updating, use, transfer, destruction, and other operations.</p>
|
||||
|
||||
<p><strong>Operator</strong> — a state or private body that independently or jointly organizes the processing of personal data.</p>
|
||||
|
||||
<p><strong>PD Provision</strong> — transfer of data to a specific person or group of persons.</p>
|
||||
|
||||
<p><strong>PD Distribution</strong> — disclosure of data to an indefinite number of persons, including publication in the media or on the Internet.</p>
|
||||
|
||||
<p><strong>Cross-border PD Transfer</strong> — transfer of data to foreign authorities, companies, or individuals.</p>
|
||||
|
||||
<p><strong>PD Destruction</strong> — actions leading to the loss of the ability to recover data or the destruction of physical media.</p>
|
||||
|
||||
<p><strong>PD Subject</strong> — an individual whose information is being processed.</p>
|
||||
|
||||
<p><strong>PD Confidentiality</strong> — the Operator's obligation to protect data from distribution without the consent of the subject or a legal basis.</p>
|
||||
|
||||
<p><strong>Seller (Contractor)</strong> — a person offering goods or services on the website <a href="https://lavero.store">https://lavero.store</a>.</p>
|
||||
|
||||
<p><strong>User</strong> — a person visiting or using resources managed by the Operator, including the website <a href="https://lavero.store">https://lavero.store</a>.</p>
|
||||
|
||||
<p><strong>Order</strong> — an order for goods or services placed by the User on the website.</p>
|
||||
|
||||
<p><strong>Cookies</strong> — small files saved on the user's device to remember preferences and actions during subsequent visits to the website.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="info-card wide">
|
||||
<div class="card-icon">⚖️</div>
|
||||
<h2>3. LEGAL BASIS FOR PERSONAL DATA PROCESSING</h2>
|
||||
|
||||
<p>3.1. The legal basis for PD Processing, depending on the purposes of the process involving PD Processing, may be:</p>
|
||||
|
||||
<h3>3.1.1. The Constitution of the Russian Federation, as well as a set of legal acts:</h3>
|
||||
<ul class="compact-list">
|
||||
<li>Tax Code of the Russian Federation;</li>
|
||||
<li>Civil Code of the Russian Federation;</li>
|
||||
<li>Articles 86–90 of the Labor Code of the Russian Federation;</li>
|
||||
<li>Federal Law No. 115-FZ dated August 7, 2001 «On Countering the Legalization (Laundering) of Proceeds from Crime and the Financing of Terrorism»;</li>
|
||||
<li>Federal Law No. 152-FZ dated July 27, 2006 «On Personal Data»;</li>
|
||||
<li>Federal Law No. 39-FZ dated April 22, 1996 «On the Securities Market»;</li>
|
||||
<li>Federal Law No. 208-FZ dated December 26, 1995 «On Joint-Stock Companies»;</li>
|
||||
<li>Federal Law No. 149-FZ dated July 27, 2006 «On Information, Information Technologies and Information Protection»;</li>
|
||||
<li>Federal Law No. 27-FZ dated April 1, 1996 «On Individual (Personalized) Accounting in the Mandatory Pension Insurance System»;</li>
|
||||
<li>Federal Law No. 63-FZ dated April 6, 2011 «On Electronic Signatures»;</li>
|
||||
<li>Federal Law No. 402-FZ dated December 6, 2011 «On Accounting»;</li>
|
||||
<li>Federal Law No. 161-FZ dated June 27, 2011 «On the National Payment System»;</li>
|
||||
<li>Decree of the Government of the Russian Federation No. 687 dated September 15, 2008 «On Approval of the Regulation on the Specifics of Personal Data Processing Carried Out Without the Use of Automation Tools»;</li>
|
||||
<li>Decree of the Government of the Russian Federation No. 1119 dated November 1, 2012 "On Approval of Requirements for the Protection of Personal Data During Their Processing in Personal Data Information Systems"</li>
|
||||
<li>other regulatory legal acts of the Russian Federation and regulatory documents of executive government bodies.</li>
|
||||
</ul>
|
||||
|
||||
<p>3.1.2. The Operator's Charter.</p>
|
||||
|
||||
<p>3.1.3. Agreements concluded between the Operator and the Personal Data Subject, including in the case of the Operator exercising its right to assign rights (claims) under such agreements, between the Operator and another person who has entrusted the Operator with PD Processing, as well as for concluding agreements in which Personal Data Subjects are parties.</p>
|
||||
|
||||
<p>3.1.4. Consent to PD Processing (in cases not directly provided for by the legislation of the Russian Federation, but corresponding to the Operator's authority), including consent of applicants for vacant positions to PD Processing, consent of interns to PD Processing, consent of employees to PD Processing; consent of clients to PD Processing, consent of Users of the respective Website, consent of other Personal Data Subjects.</p>
|
||||
|
||||
<p>3.1.5. An agreement between the Operator and a third party, where the latter entrusts the Operator with the processing of personal data of the Personal Data Subject or transfers personal data of the Personal Data Subject based on the concluded agreement.</p>
|
||||
</section>
|
||||
|
||||
<section class="info-card wide">
|
||||
<div class="card-icon">👥</div>
|
||||
<h2>4. CATEGORIES OF PERSONAL DATA SUBJECTS</h2>
|
||||
|
||||
<p>4.1. The Operator processes PD obtained in accordance with the law, belonging to:</p>
|
||||
<ul class="compact-list">
|
||||
<li>Job candidates and employees of the Operator;</li>
|
||||
<li>Former employees of the Operator;</li>
|
||||
<li>Close relatives/family members of the Operator's employees, interns;</li>
|
||||
<li>Potential clients, individual clients, individual entrepreneur clients;</li>
|
||||
<li>Individuals who have entered into civil law contracts with the Operator;</li>
|
||||
<li>Users of the Operator's Websites, Order recipients;</li>
|
||||
<li>Clients of other legal entities;</li>
|
||||
<li>Owners of the Operator;</li>
|
||||
<li>Other subjects who have entered into or intend to enter into contractual relationships with the Operator.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="info-card wide">
|
||||
<div class="card-icon">📝</div>
|
||||
<h2>5. CATEGORIES OF PROCESSED PERSONAL DATA</h2>
|
||||
|
||||
<p>5.1. The Operator processes the following categories of User PD:</p>
|
||||
|
||||
<ul class="compact-list">
|
||||
<li>Information obtained during registration and/or placing an Order (last name, first name, actual address, phone number, email address, cookies);</li>
|
||||
<li>Information obtained during interaction with Users (last name, first name, patronymic, gender, place of birth, date of birth, passport data, address, contacts);</li>
|
||||
<li>Information about the method of delivery and payment of the Order;</li>
|
||||
<li>Information about User complaints;</li>
|
||||
<li>Geolocation (location) information.</li>
|
||||
</ul>
|
||||
|
||||
<p>5.2. Personal data may be obtained by the Operator through:</p>
|
||||
<ul class="compact-list">
|
||||
<li>Submission by Subjects through forms on the Website or by email;</li>
|
||||
<li>Receipt from third parties (Sellers, counterparties) in accordance with the legislation of the Russian Federation.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="info-card wide">
|
||||
<div class="card-icon">⚙️</div>
|
||||
<h2>6. PRINCIPLES, PROCEDURES, AND CONDITIONS OF PROCESSING</h2>
|
||||
|
||||
<h3>6.1. Principles of Personal Data Processing</h3>
|
||||
<p>PD Processing by the Operator is carried out based on the following principles:</p>
|
||||
<ul class="compact-list">
|
||||
<li>legality and fair basis;</li>
|
||||
<li>limiting PD Processing to achieving specific, predetermined, and legitimate purposes;</li>
|
||||
<li>preventing PD Processing incompatible with the purposes of PD collection;</li>
|
||||
<li>ensuring accuracy, sufficiency, and relevance of PD;</li>
|
||||
<li>storing PD no longer than required by the purposes of Processing;</li>
|
||||
<li>destroying or anonymizing PD upon achieving the purposes of their Processing.</li>
|
||||
</ul>
|
||||
|
||||
<h3>6.2. Obligations of the Operator's Employees</h3>
|
||||
<p>Employees of the Operator authorized to process Personal Data are required to:</p>
|
||||
<ul class="compact-list">
|
||||
<li>Know and comply with the provisions of the legislation of the Russian Federation in the field of PD;</li>
|
||||
<li>Know and comply with the provisions of this Policy;</li>
|
||||
<li>Process PD only within the scope of their job responsibilities;</li>
|
||||
<li>Not disclose PD processed by the Operator;</li>
|
||||
<li>Report actions that may lead to violations of the Policy.</li>
|
||||
</ul>
|
||||
|
||||
<h3>6.3-6.24. Additional Conditions</h3>
|
||||
<p>Include conditions for processing, storage, cross-border data transfer, work with publicly available sources, special and biometric data, as well as the procedure for obtaining clarifications in accordance with the legislation of the Russian Federation.</p>
|
||||
</section>
|
||||
|
||||
<section class="info-card wide">
|
||||
<div class="card-icon">✅</div>
|
||||
<h2>7. RIGHTS OF THE PERSONAL DATA SUBJECT</h2>
|
||||
|
||||
<h3>7.1. Subject's Consent</h3>
|
||||
<p>The PD Subject gives consent to Processing freely, of their own will and in their own interest.</p>
|
||||
|
||||
<h3>7.2. Subject's Rights</h3>
|
||||
<p>The PD Subject has the right to receive information about:</p>
|
||||
<ul class="compact-list">
|
||||
<li>confirmation of the fact of PD Processing;</li>
|
||||
<li>legal grounds and purposes of Processing;</li>
|
||||
<li>methods of Processing used;</li>
|
||||
<li>terms of Processing and data storage;</li>
|
||||
<li>clarification, blocking, or destruction of data;</li>
|
||||
</ul>
|
||||
|
||||
<p><strong>The PD Subject has the right to withdraw their Consent</strong> and request the deletion of their PD by sending a message to: <a href="mailto:info@lavero.store" class="contact-email">info@lavero.store</a></p>
|
||||
|
||||
<p>The Subject also has the right to protect their rights, recover damages, and receive compensation for moral harm.</p>
|
||||
</section>
|
||||
|
||||
<section class="info-card wide">
|
||||
<div class="card-icon">📋</div>
|
||||
<h2>8. OPERATOR'S OBLIGATIONS</h2>
|
||||
|
||||
<p>8.1. The Operator is obliged to provide the Subject with information about the processing of their data upon request.</p>
|
||||
|
||||
<p>8.2. The Operator ensures the storage of data of citizens of the Russian Federation on the territory of the Russian Federation.</p>
|
||||
|
||||
<p>8.3. The Operator bears other obligations established by FZ-152.</p>
|
||||
</section>
|
||||
|
||||
<section class="info-card wide">
|
||||
<div class="card-icon">🛡️</div>
|
||||
<h2>9. SECURITY ASSURANCE</h2>
|
||||
|
||||
<p>9.1-9.3. Security is ensured through a set of organizational and technical measures:</p>
|
||||
<ul class="compact-list">
|
||||
<li>appointment of responsible persons;</li>
|
||||
<li>restriction of access to PD;</li>
|
||||
<li>employee training;</li>
|
||||
<li>accounting and storage of PD media;</li>
|
||||
<li>use of antivirus tools;</li>
|
||||
<li>encryption and firewall tools;</li>
|
||||
<li>physical protection of premises;</li>
|
||||
<li>security measures control.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="info-card wide">
|
||||
<div class="card-icon">⚠️</div>
|
||||
<h2>10. LIABILITY</h2>
|
||||
|
||||
<p>10.1. Persons guilty of violating the norms of processing and protection of Personal Data bear liability in accordance with the legislation of the Russian Federation.</p>
|
||||
</section>
|
||||
|
||||
<section class="info-card wide">
|
||||
<div class="card-icon">🎯</div>
|
||||
<h2>11. PURPOSES OF PROCESSING</h2>
|
||||
|
||||
<p>11.1. The Operator processes PD for the following purposes:</p>
|
||||
<ul class="compact-list">
|
||||
<li>Providing access to the Website and personal account;</li>
|
||||
<li>Fulfillment of purchase and sale agreements and provision of services;</li>
|
||||
<li>Delivery of goods;</li>
|
||||
<li>Resolution of complaints;</li>
|
||||
<li>Payment processing;</li>
|
||||
<li>Improvement of service quality;</li>
|
||||
<li>Obtaining feedback;</li>
|
||||
<li>Conducting marketing research;</li>
|
||||
<li>Sending promotional messages;</li>
|
||||
<li>In other cases provided for by the legislation of the Russian Federation.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="info-card wide">
|
||||
<div class="card-icon">🍪</div>
|
||||
<h2>12. AUTOMATICALLY COLLECTED INFORMATION</h2>
|
||||
|
||||
<p>12.1. The Operator collects and processes:</p>
|
||||
<ul class="compact-list">
|
||||
<li>information about interests based on search queries;</li>
|
||||
<li>information for rating formation (reviews, data on Order fulfillment);</li>
|
||||
<li>statistics on Website usage.</li>
|
||||
</ul>
|
||||
|
||||
<p>12.2. The Operator uses cookies, web beacons, and other monitoring technologies. These technologies do not allow automatic collection of PD.</p>
|
||||
|
||||
<p>12.3. If the collected information can be associated with the User's personal account, it may be processed together with PD.</p>
|
||||
</section>
|
||||
|
||||
<div class="info-card wide contact-card">
|
||||
<div class="card-icon">📧</div>
|
||||
<h3>Contact Information</h3>
|
||||
<p>For all questions regarding personal data processing, please contact:</p>
|
||||
<a href="mailto:info@lavero.store" class="contact-email">info@lavero.store</a>
|
||||
<p><a [href]="env.phoneTel">{{ env.phones.support }}</a></p>
|
||||
<p class="note">We will respond within 30 days in accordance with the legislation of the Russian Federation</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { environment } from '../../../../../../../environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-privacy-policy-lavero-en',
|
||||
templateUrl: './privacy-policy-en.component.html',
|
||||
styleUrls: ['../../../../../../pages/legal/privacy-policy/privacy-policy.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class PrivacyPolicyLaveroEnComponent {
|
||||
protected readonly env = environment;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<div class="legal-page">
|
||||
<div class="legal-container">
|
||||
<div class="lavero-header">
|
||||
<h1>Գաղdelays delays delays delays</h1>
|
||||
<p class="subtitle"> Delays delays delays delays delays delays</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||