Files
interview-demo-code/.cursor/rules/javascript.md
Nikita Kiselev 9a93cc7342 feat: add Telegram customers management system with admin panel
Implement comprehensive Telegram customers storage and management functionality:

Backend:
- Add database migration for telecart_customers table with indexes
- Create TelegramCustomer model with CRUD operations
- Implement TelegramCustomerService for business logic
- Add TelegramCustomerHandler for API endpoint (saveOrUpdate)
- Add TelegramCustomersHandler for admin API (getCustomers with pagination, filtering, sorting)
- Add SendMessageHandler for sending messages to customers via Telegram
- Create custom exceptions: TelegramCustomerNotFoundException, TelegramCustomerWriteNotAllowedException
- Refactor TelegramInitDataDecoder to separate decoding logic
- Add TelegramHeader enum for header constants
- Update SignatureValidator to use TelegramInitDataDecoder
- Register new routes in bastion/routes.php and src/routes.php

Frontend (Admin):
- Add CustomersView.vue component with PrimeVue DataTable
- Implement advanced filtering (text, date, boolean filters)
- Add column visibility toggle functionality
- Add global search with debounce
- Implement message sending dialog with validation
- Add Russian locale for PrimeVue components
- Add navigation link in App.vue
- Register route in router

Frontend (SPA):
- Add saveTelegramCustomer utility function
- Integrate automatic customer data saving on app initialization
- Extract user data from Telegram.WebApp.initDataUnsafe

The system automatically saves/updates customer data when users access the Telegram Mini App,
and provides admin interface for viewing, filtering, and messaging customers.

BREAKING CHANGE: None
2025-11-23 21:30:51 +03:00

333 lines
6.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# JavaScript/TypeScript Code Style Rules
## JavaScript Version
- ES2020+ features
- Modern async/await
- Optional chaining (`?.`)
- Nullish coalescing (`??`)
- Template literals
## Code Style
### Variable Declarations
```javascript
// ✅ Используй const по умолчанию
const customers = [];
const totalRecords = 0;
// ✅ let только когда нужно переназначение
let currentPage = 1;
currentPage = 2;
// ❌ Не используй var
var oldVariable = 'bad';
```
### Arrow Functions
```javascript
// ✅ Предпочтительно для коротких функций
const filtered = items.filter(item => item.isActive);
// ✅ Для методов объектов
const api = {
get: async (url) => {
return await fetch(url);
}
};
// ✅ Для сложной логики - обычные функции
function complexCalculation(data) {
// много строк кода
return result;
}
```
### Template Literals
```javascript
// ✅ Предпочтительно
const message = `User ${userId} not found`;
const url = `${baseUrl}/api/${endpoint}`;
// ❌ Не используй конкатенацию
const message = 'User ' + userId + ' not found';
```
### Optional Chaining
```javascript
// ✅ Используй optional chaining
const name = user?.profile?.name;
const count = data?.items?.length ?? 0;
// ❌ Избегай длинных проверок
const name = user && user.profile && user.profile.name;
```
### Nullish Coalescing
```javascript
// ✅ Используй ?? для значений по умолчанию
const page = params.page ?? 1;
const name = user.name ?? 'Unknown';
// ❌ Не используй || для чисел/булевых
const page = params.page || 1; // 0 будет заменено на 1
```
### Destructuring
```javascript
// ✅ Используй деструктуризацию
const { data, totalRecords } = response.data;
const [first, second] = items;
// ✅ В параметрах функций
function processUser({ id, name, email }) {
// ...
}
// ✅ С значениями по умолчанию
const { page = 1, limit = 20 } = params;
```
### Async/Await
```javascript
// ✅ Предпочтительно
async function loadCustomers() {
try {
const response = await apiGet('getCustomers', params);
return response.data;
} catch (error) {
console.error('Error:', error);
throw error;
}
}
// ❌ Избегай .then() цепочек
function loadCustomers() {
return apiGet('getCustomers', params)
.then(response => response.data)
.catch(error => console.error(error));
}
```
## Vue.js 3 Composition API
### Script Setup
```vue
<script setup>
// ✅ Используй <script setup>
import { ref, computed, onMounted } from 'vue';
import { apiGet } from '@/utils/http.js';
const customers = ref([]);
const loading = ref(false);
const totalRecords = computed(() => customers.value.length);
async function loadCustomers() {
loading.value = true;
try {
const result = await apiGet('getCustomers');
customers.value = result.data || [];
} finally {
loading.value = false;
}
}
onMounted(() => {
loadCustomers();
});
</script>
```
### Reactive State
```javascript
// ✅ Используй ref для примитивов
const count = ref(0);
const name = ref('');
// ✅ Используй reactive для объектов
import { reactive } from 'vue';
const state = reactive({
customers: [],
loading: false
});
// ✅ Или ref для объектов (предпочтительно)
const state = ref({
customers: [],
loading: false
});
```
### Computed Properties
```javascript
// ✅ Используй computed для производных значений
const filteredCustomers = computed(() => {
return customers.value.filter(c => c.isActive);
});
// ❌ Не используй методы для вычислений
function filteredCustomers() {
return customers.value.filter(c => c.isActive);
}
```
### Props
```vue
<script setup>
// ✅ Определяй props с типами
const props = defineProps({
customerId: {
type: Number,
required: true
},
showDetails: {
type: Boolean,
default: false
}
});
</script>
```
### Emits
```vue
<script setup>
// ✅ Определяй emits
const emit = defineEmits(['update', 'delete']);
function handleUpdate() {
emit('update', data);
}
</script>
```
## Pinia Stores
```javascript
// ✅ Используй setup stores
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
export const useCustomersStore = defineStore('customers', () => {
const customers = ref([]);
const loading = ref(false);
const totalRecords = computed(() => customers.value.length);
async function loadCustomers() {
loading.value = true;
try {
const result = await apiGet('getCustomers');
customers.value = result.data || [];
} finally {
loading.value = false;
}
}
return {
customers,
loading,
totalRecords,
loadCustomers
};
});
```
## Error Handling
```javascript
// ✅ Всегда обрабатывай ошибки
async function loadData() {
try {
const result = await apiGet('endpoint');
if (result.success) {
return result.data;
} else {
throw new Error(result.error);
}
} catch (error) {
console.error('Failed to load data:', error);
toast.error('Не удалось загрузить данные');
throw error;
}
}
```
## Naming Conventions
### Variables and Functions
```javascript
// ✅ camelCase
const customerData = {};
const totalRecords = 0;
function loadCustomers() {}
// ✅ Константы UPPER_SNAKE_CASE
const MAX_RETRIES = 3;
const API_BASE_URL = '/api';
```
### Components
```vue
<!-- PascalCase для компонентов -->
<CustomerCard />
<ProductsList />
```
### Files
```javascript
// ✅ kebab-case для файлов
// customers-view.vue
// http-utils.js
// customer-service.js
```
## Imports
```javascript
// ✅ Группируй импорты
// 1. Vue core
import { ref, computed, onMounted } from 'vue';
// 2. Third-party
import { apiGet } from '@/utils/http.js';
import { useToast } from 'primevue';
// 3. Local components
import CustomerCard from '@/components/CustomerCard.vue';
// 4. Types (если TypeScript)
import type { Customer } from '@/types';
```
## TypeScript (где используется)
```typescript
// ✅ Используй типы
interface Customer {
id: number;
name: string;
email?: string;
}
function getCustomer(id: number): Promise<Customer> {
return apiGet(`customers/${id}`);
}
```