pi-kiosk-shared 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +85 -0
- package/dist/api.d.ts +20 -0
- package/dist/api.js +57 -0
- package/dist/components/ErrorDisplay.d.ts +12 -0
- package/dist/components/ErrorDisplay.js +8 -0
- package/dist/components/LoadingSpinner.d.ts +8 -0
- package/dist/components/LoadingSpinner.js +10 -0
- package/dist/config/deployment.d.ts +96 -0
- package/dist/config/deployment.js +153 -0
- package/dist/config/environments.d.ts +13 -0
- package/dist/config/environments.js +29 -0
- package/dist/config/environments.test.d.ts +1 -0
- package/dist/config/environments.test.js +49 -0
- package/dist/config/logger.d.ts +40 -0
- package/dist/config/logger.js +117 -0
- package/dist/constants.d.ts +66 -0
- package/dist/constants.js +85 -0
- package/dist/errors.d.ts +29 -0
- package/dist/errors.js +73 -0
- package/dist/hooks/useApi.d.ts +20 -0
- package/dist/hooks/useApi.js +51 -0
- package/dist/hooks/useAsyncOperation.d.ts +21 -0
- package/dist/hooks/useAsyncOperation.js +58 -0
- package/dist/hooks/useErrorHandler.d.ts +14 -0
- package/dist/hooks/useErrorHandler.js +54 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +15 -0
- package/dist/types.d.ts +56 -0
- package/dist/types.js +2 -0
- package/dist/utils/simple.test.d.ts +1 -0
- package/dist/utils/simple.test.js +45 -0
- package/dist/utils.d.ts +7 -0
- package/dist/utils.js +40 -0
- package/dist/validation.d.ts +31 -0
- package/dist/validation.js +113 -0
- package/package.json +40 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// Centralized logging system with environment-aware configuration
|
|
2
|
+
import { getEnvironmentConfig, getCurrentEnvironment } from './environments';
|
|
3
|
+
class Logger {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.config = getEnvironmentConfig();
|
|
6
|
+
this.sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
7
|
+
}
|
|
8
|
+
shouldLog(level) {
|
|
9
|
+
const levels = {
|
|
10
|
+
debug: 0,
|
|
11
|
+
info: 1,
|
|
12
|
+
warn: 2,
|
|
13
|
+
error: 3
|
|
14
|
+
};
|
|
15
|
+
// Default to 'info' level if not specified in config
|
|
16
|
+
const configLevel = this.config.showDebugInfo ? 'debug' : 'info';
|
|
17
|
+
return levels[level] >= levels[configLevel];
|
|
18
|
+
}
|
|
19
|
+
formatMessage(level, message, context, data) {
|
|
20
|
+
return {
|
|
21
|
+
level,
|
|
22
|
+
message,
|
|
23
|
+
context,
|
|
24
|
+
data,
|
|
25
|
+
timestamp: new Date().toISOString(),
|
|
26
|
+
environment: getCurrentEnvironment(),
|
|
27
|
+
sessionId: this.sessionId
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
log(level, message, context, data) {
|
|
31
|
+
if (!this.shouldLog(level))
|
|
32
|
+
return;
|
|
33
|
+
const logEntry = this.formatMessage(level, message, context, data);
|
|
34
|
+
// Console logging with appropriate method
|
|
35
|
+
const consoleMethod = level === 'debug' ? 'log' : level;
|
|
36
|
+
const prefix = `[${level.toUpperCase()}] ${logEntry.timestamp}`;
|
|
37
|
+
const contextStr = context ? ` [${context}]` : '';
|
|
38
|
+
console[consoleMethod](`${prefix}${contextStr}: ${message}`, data || '');
|
|
39
|
+
// Send to external logging service in production
|
|
40
|
+
if (!this.config.showDebugInfo && (level === 'error' || level === 'warn')) {
|
|
41
|
+
this.sendToExternalService(logEntry);
|
|
42
|
+
}
|
|
43
|
+
// Store in local storage for debugging (development only)
|
|
44
|
+
if (this.config.showDebugInfo) {
|
|
45
|
+
this.storeLogEntry(logEntry);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
sendToExternalService(logEntry) {
|
|
49
|
+
// TODO: Integrate with Sentry, LogRocket, or other monitoring service
|
|
50
|
+
// For now, just log to console in production
|
|
51
|
+
console.warn('External logging not implemented yet:', logEntry);
|
|
52
|
+
}
|
|
53
|
+
storeLogEntry(logEntry) {
|
|
54
|
+
try {
|
|
55
|
+
const storageKey = 'kiosk-logs';
|
|
56
|
+
const existingLogs = JSON.parse(localStorage.getItem(storageKey) || '[]');
|
|
57
|
+
const updatedLogs = [...existingLogs.slice(-99), logEntry]; // Keep last 100 entries
|
|
58
|
+
localStorage.setItem(storageKey, JSON.stringify(updatedLogs));
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
console.warn('Failed to store log entry:', error);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
debug(message, context, data) {
|
|
65
|
+
this.log('debug', message, context, data);
|
|
66
|
+
}
|
|
67
|
+
info(message, context, data) {
|
|
68
|
+
this.log('info', message, context, data);
|
|
69
|
+
}
|
|
70
|
+
warn(message, context, data) {
|
|
71
|
+
this.log('warn', message, context, data);
|
|
72
|
+
}
|
|
73
|
+
error(message, context, data) {
|
|
74
|
+
this.log('error', message, context, data);
|
|
75
|
+
}
|
|
76
|
+
// Kiosk-specific logging methods
|
|
77
|
+
kioskAction(action, kioskId, data) {
|
|
78
|
+
this.info(`Kiosk action: ${action}`, `kiosk-${kioskId}`, data);
|
|
79
|
+
}
|
|
80
|
+
paymentEvent(event, paymentId, data) {
|
|
81
|
+
this.info(`Payment event: ${event}`, `payment-${paymentId}`, data);
|
|
82
|
+
}
|
|
83
|
+
apiCall(method, endpoint, duration, error) {
|
|
84
|
+
const message = `API ${method} ${endpoint}${duration ? ` (${duration}ms)` : ''}`;
|
|
85
|
+
if (error) {
|
|
86
|
+
this.error(message, 'api-client', { error: error.message, stack: error.stack });
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
this.debug(message, 'api-client');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Get logs for debugging
|
|
93
|
+
getLogs() {
|
|
94
|
+
try {
|
|
95
|
+
return JSON.parse(localStorage.getItem('kiosk-logs') || '[]');
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Clear stored logs
|
|
102
|
+
clearLogs() {
|
|
103
|
+
localStorage.removeItem('kiosk-logs');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Export singleton instance
|
|
107
|
+
export const logger = new Logger();
|
|
108
|
+
// Export convenience functions
|
|
109
|
+
export const log = {
|
|
110
|
+
debug: (message, context, data) => logger.debug(message, context, data),
|
|
111
|
+
info: (message, context, data) => logger.info(message, context, data),
|
|
112
|
+
warn: (message, context, data) => logger.warn(message, context, data),
|
|
113
|
+
error: (message, context, data) => logger.error(message, context, data),
|
|
114
|
+
kioskAction: (action, kioskId, data) => logger.kioskAction(action, kioskId, data),
|
|
115
|
+
paymentEvent: (event, paymentId, data) => logger.paymentEvent(event, paymentId, data),
|
|
116
|
+
apiCall: (method, endpoint, duration, error) => logger.apiCall(method, endpoint, duration, error),
|
|
117
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export declare const APP_CONFIG: {
|
|
2
|
+
readonly DEFAULT_API_URL: "http://localhost:3015";
|
|
3
|
+
readonly DEFAULT_WS_URL: "ws://localhost:3015";
|
|
4
|
+
readonly API_TIMEOUT: 10000;
|
|
5
|
+
readonly RETRY_ATTEMPTS: 3;
|
|
6
|
+
readonly RETRY_DELAY: 1000;
|
|
7
|
+
readonly WS_RECONNECT_ATTEMPTS: 5;
|
|
8
|
+
readonly WS_RECONNECT_INTERVAL: 3000;
|
|
9
|
+
readonly WS_HEARTBEAT_INTERVAL: 30000;
|
|
10
|
+
readonly LOADING_DEBOUNCE: 300;
|
|
11
|
+
readonly ERROR_DISPLAY_DURATION: 5000;
|
|
12
|
+
readonly MIN_USERNAME_LENGTH: 3;
|
|
13
|
+
readonly MIN_PASSWORD_LENGTH: 6;
|
|
14
|
+
readonly MAX_PRODUCT_NAME_LENGTH: 100;
|
|
15
|
+
readonly MAX_DESCRIPTION_LENGTH: 500;
|
|
16
|
+
readonly DEFAULT_KIOSK_ID: 1;
|
|
17
|
+
readonly PAYMENT_POLLING_INTERVAL: 3000;
|
|
18
|
+
readonly PRODUCT_CACHE_TTL: 300000;
|
|
19
|
+
readonly PAYMENT_ACCOUNT_NUMBER: "1234567890";
|
|
20
|
+
readonly PAYMENT_CURRENCY: "CZK";
|
|
21
|
+
readonly QR_CODE_WIDTH: 300;
|
|
22
|
+
readonly QR_CODE_FORMAT: "SPD*1.0";
|
|
23
|
+
};
|
|
24
|
+
export declare const UI_MESSAGES: {
|
|
25
|
+
readonly LOADING_PRODUCTS: "Načítání produktů...";
|
|
26
|
+
readonly LOADING_PAYMENT: "Zpracovávám platbu...";
|
|
27
|
+
readonly GENERATING_QR: "Generuji QR kód...";
|
|
28
|
+
readonly PAYMENT_SUCCESS: "Platba byla úspěšně zpracována!";
|
|
29
|
+
readonly PRODUCT_SAVED: "Produkt byl úspěšně uložen!";
|
|
30
|
+
readonly NETWORK_ERROR: "Problém s připojením. Zkuste to znovu.";
|
|
31
|
+
readonly VALIDATION_ERROR: "Zkontrolujte zadané údaje.";
|
|
32
|
+
readonly UNKNOWN_ERROR: "Něco se pokazilo. Zkuste to znovu.";
|
|
33
|
+
readonly NO_PRODUCTS: "Žádné produkty nejsou k dispozici";
|
|
34
|
+
readonly NO_TRANSACTIONS: "Žádné transakce";
|
|
35
|
+
readonly COMING_SOON: "Připravujeme pro vás...";
|
|
36
|
+
readonly REQUIRED_FIELD: "Toto pole je povinné";
|
|
37
|
+
readonly INVALID_EMAIL: "Zadejte platnou emailovou adresu";
|
|
38
|
+
readonly INVALID_PRICE: "Cena musí být větší než 0";
|
|
39
|
+
};
|
|
40
|
+
export declare const CSS_CLASSES: {
|
|
41
|
+
readonly CONTAINER: "container";
|
|
42
|
+
readonly GRID: "grid";
|
|
43
|
+
readonly FLEX: "flex";
|
|
44
|
+
readonly LOADING: "loading";
|
|
45
|
+
readonly ERROR: "error";
|
|
46
|
+
readonly SUCCESS: "success";
|
|
47
|
+
readonly DISABLED: "disabled";
|
|
48
|
+
readonly ACTIVE: "active";
|
|
49
|
+
readonly BUTTON_PRIMARY: "btn-primary";
|
|
50
|
+
readonly BUTTON_SECONDARY: "btn-secondary";
|
|
51
|
+
readonly INPUT: "input";
|
|
52
|
+
readonly CARD: "card";
|
|
53
|
+
readonly ONLINE: "online";
|
|
54
|
+
readonly OFFLINE: "offline";
|
|
55
|
+
readonly CONNECTED: "connected";
|
|
56
|
+
readonly DISCONNECTED: "disconnected";
|
|
57
|
+
};
|
|
58
|
+
export declare const ROUTES: {
|
|
59
|
+
readonly KIOSK_HOME: "/";
|
|
60
|
+
readonly KIOSK_PAYMENT: "/payment";
|
|
61
|
+
readonly KIOSK_CONFIRMATION: "/confirmation";
|
|
62
|
+
readonly ADMIN_LOGIN: "/admin/login";
|
|
63
|
+
readonly ADMIN_DASHBOARD: "/admin";
|
|
64
|
+
readonly ADMIN_PRODUCTS: "/admin/products";
|
|
65
|
+
readonly ADMIN_KIOSKS: "/admin/kiosks";
|
|
66
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// Shared constants across all packages
|
|
2
|
+
export const APP_CONFIG = {
|
|
3
|
+
// API Configuration
|
|
4
|
+
DEFAULT_API_URL: 'http://localhost:3015',
|
|
5
|
+
DEFAULT_WS_URL: 'ws://localhost:3015',
|
|
6
|
+
// Timeouts
|
|
7
|
+
API_TIMEOUT: 10000, // 10 seconds
|
|
8
|
+
RETRY_ATTEMPTS: 3,
|
|
9
|
+
RETRY_DELAY: 1000, // 1 second
|
|
10
|
+
// WebSocket
|
|
11
|
+
WS_RECONNECT_ATTEMPTS: 5,
|
|
12
|
+
WS_RECONNECT_INTERVAL: 3000,
|
|
13
|
+
WS_HEARTBEAT_INTERVAL: 30000,
|
|
14
|
+
// UI
|
|
15
|
+
LOADING_DEBOUNCE: 300,
|
|
16
|
+
ERROR_DISPLAY_DURATION: 5000,
|
|
17
|
+
// Validation
|
|
18
|
+
MIN_USERNAME_LENGTH: 3,
|
|
19
|
+
MIN_PASSWORD_LENGTH: 6,
|
|
20
|
+
MAX_PRODUCT_NAME_LENGTH: 100,
|
|
21
|
+
MAX_DESCRIPTION_LENGTH: 500,
|
|
22
|
+
// Business Rules
|
|
23
|
+
DEFAULT_KIOSK_ID: 1,
|
|
24
|
+
PAYMENT_POLLING_INTERVAL: 3000,
|
|
25
|
+
PRODUCT_CACHE_TTL: 300000, // 5 minutes
|
|
26
|
+
// Payment Configuration
|
|
27
|
+
PAYMENT_ACCOUNT_NUMBER: '1234567890',
|
|
28
|
+
PAYMENT_CURRENCY: 'CZK',
|
|
29
|
+
QR_CODE_WIDTH: 300,
|
|
30
|
+
QR_CODE_FORMAT: 'SPD*1.0', // Czech QR payment standard
|
|
31
|
+
};
|
|
32
|
+
export const UI_MESSAGES = {
|
|
33
|
+
// Loading states
|
|
34
|
+
LOADING_PRODUCTS: 'Načítání produktů...',
|
|
35
|
+
LOADING_PAYMENT: 'Zpracovávám platbu...',
|
|
36
|
+
GENERATING_QR: 'Generuji QR kód...',
|
|
37
|
+
// Success messages
|
|
38
|
+
PAYMENT_SUCCESS: 'Platba byla úspěšně zpracována!',
|
|
39
|
+
PRODUCT_SAVED: 'Produkt byl úspěšně uložen!',
|
|
40
|
+
// Error messages
|
|
41
|
+
NETWORK_ERROR: 'Problém s připojením. Zkuste to znovu.',
|
|
42
|
+
VALIDATION_ERROR: 'Zkontrolujte zadané údaje.',
|
|
43
|
+
UNKNOWN_ERROR: 'Něco se pokazilo. Zkuste to znovu.',
|
|
44
|
+
// Empty states
|
|
45
|
+
NO_PRODUCTS: 'Žádné produkty nejsou k dispozici',
|
|
46
|
+
NO_TRANSACTIONS: 'Žádné transakce',
|
|
47
|
+
COMING_SOON: 'Připravujeme pro vás...',
|
|
48
|
+
// Form validation
|
|
49
|
+
REQUIRED_FIELD: 'Toto pole je povinné',
|
|
50
|
+
INVALID_EMAIL: 'Zadejte platnou emailovou adresu',
|
|
51
|
+
INVALID_PRICE: 'Cena musí být větší než 0',
|
|
52
|
+
};
|
|
53
|
+
export const CSS_CLASSES = {
|
|
54
|
+
// Layout
|
|
55
|
+
CONTAINER: 'container',
|
|
56
|
+
GRID: 'grid',
|
|
57
|
+
FLEX: 'flex',
|
|
58
|
+
// States
|
|
59
|
+
LOADING: 'loading',
|
|
60
|
+
ERROR: 'error',
|
|
61
|
+
SUCCESS: 'success',
|
|
62
|
+
DISABLED: 'disabled',
|
|
63
|
+
ACTIVE: 'active',
|
|
64
|
+
// Components
|
|
65
|
+
BUTTON_PRIMARY: 'btn-primary',
|
|
66
|
+
BUTTON_SECONDARY: 'btn-secondary',
|
|
67
|
+
INPUT: 'input',
|
|
68
|
+
CARD: 'card',
|
|
69
|
+
// Status indicators
|
|
70
|
+
ONLINE: 'online',
|
|
71
|
+
OFFLINE: 'offline',
|
|
72
|
+
CONNECTED: 'connected',
|
|
73
|
+
DISCONNECTED: 'disconnected',
|
|
74
|
+
};
|
|
75
|
+
export const ROUTES = {
|
|
76
|
+
// Kiosk routes
|
|
77
|
+
KIOSK_HOME: '/',
|
|
78
|
+
KIOSK_PAYMENT: '/payment',
|
|
79
|
+
KIOSK_CONFIRMATION: '/confirmation',
|
|
80
|
+
// Admin routes
|
|
81
|
+
ADMIN_LOGIN: '/admin/login',
|
|
82
|
+
ADMIN_DASHBOARD: '/admin',
|
|
83
|
+
ADMIN_PRODUCTS: '/admin/products',
|
|
84
|
+
ADMIN_KIOSKS: '/admin/kiosks',
|
|
85
|
+
};
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export declare class AppError extends Error {
|
|
2
|
+
readonly code: string;
|
|
3
|
+
readonly statusCode: number;
|
|
4
|
+
readonly isOperational: boolean;
|
|
5
|
+
constructor(message: string, code?: string, statusCode?: number, isOperational?: boolean);
|
|
6
|
+
}
|
|
7
|
+
export declare class ValidationError extends AppError {
|
|
8
|
+
constructor(message: string, _field?: string);
|
|
9
|
+
}
|
|
10
|
+
export declare class NetworkError extends AppError {
|
|
11
|
+
constructor(message?: string);
|
|
12
|
+
}
|
|
13
|
+
export declare class AuthenticationError extends AppError {
|
|
14
|
+
constructor(message?: string);
|
|
15
|
+
}
|
|
16
|
+
export declare class NotFoundError extends AppError {
|
|
17
|
+
constructor(resource?: string);
|
|
18
|
+
}
|
|
19
|
+
export interface ErrorResponse {
|
|
20
|
+
success: false;
|
|
21
|
+
error: {
|
|
22
|
+
code: string;
|
|
23
|
+
message: string;
|
|
24
|
+
timestamp: string;
|
|
25
|
+
details?: any;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export declare const formatError: (error: Error | AppError, details?: any) => ErrorResponse;
|
|
29
|
+
export declare const getErrorMessage: (error: Error | AppError) => string;
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Shared error handling system
|
|
2
|
+
export class AppError extends Error {
|
|
3
|
+
constructor(message, code = 'UNKNOWN_ERROR', statusCode = 500, isOperational = true) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = 'AppError';
|
|
6
|
+
this.code = code;
|
|
7
|
+
this.statusCode = statusCode;
|
|
8
|
+
this.isOperational = isOperational;
|
|
9
|
+
// Ensure the stack trace is captured
|
|
10
|
+
Error.captureStackTrace(this, this.constructor);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
// Predefined error types
|
|
14
|
+
export class ValidationError extends AppError {
|
|
15
|
+
constructor(message, _field) {
|
|
16
|
+
super(message, 'VALIDATION_ERROR', 400);
|
|
17
|
+
this.name = 'ValidationError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export class NetworkError extends AppError {
|
|
21
|
+
constructor(message = 'Chyba připojení k serveru') {
|
|
22
|
+
super(message, 'NETWORK_ERROR', 503);
|
|
23
|
+
this.name = 'NetworkError';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export class AuthenticationError extends AppError {
|
|
27
|
+
constructor(message = 'Neplatné přihlašovací údaje') {
|
|
28
|
+
super(message, 'AUTH_ERROR', 401);
|
|
29
|
+
this.name = 'AuthenticationError';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export class NotFoundError extends AppError {
|
|
33
|
+
constructor(resource = 'Zdroj') {
|
|
34
|
+
super(`${resource} nebyl nalezen`, 'NOT_FOUND', 404);
|
|
35
|
+
this.name = 'NotFoundError';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export const formatError = (error, details) => {
|
|
39
|
+
const isAppError = error instanceof AppError;
|
|
40
|
+
return {
|
|
41
|
+
success: false,
|
|
42
|
+
error: {
|
|
43
|
+
code: isAppError ? error.code : 'UNKNOWN_ERROR',
|
|
44
|
+
message: error.message || 'Došlo k neočekávané chybě',
|
|
45
|
+
timestamp: new Date().toISOString(),
|
|
46
|
+
...(details && { details })
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
// Error handler hook for React components
|
|
51
|
+
export const getErrorMessage = (error) => {
|
|
52
|
+
if (error instanceof NetworkError) {
|
|
53
|
+
return 'Problém s připojením. Zkuste to znovu.';
|
|
54
|
+
}
|
|
55
|
+
if (error instanceof ValidationError) {
|
|
56
|
+
return error.message;
|
|
57
|
+
}
|
|
58
|
+
if (error instanceof AuthenticationError) {
|
|
59
|
+
return 'Neplatné přihlašovací údaje.';
|
|
60
|
+
}
|
|
61
|
+
if (error instanceof NotFoundError) {
|
|
62
|
+
return error.message;
|
|
63
|
+
}
|
|
64
|
+
// Check for specific error messages
|
|
65
|
+
if (error.message.includes('Failed to fetch') || error.message.includes('fetch')) {
|
|
66
|
+
return 'Problém s připojením. Zkuste to znovu.';
|
|
67
|
+
}
|
|
68
|
+
if (error.message.includes('401') || error.message.includes('Unauthorized')) {
|
|
69
|
+
return 'Neplatné přihlašovací údaje.';
|
|
70
|
+
}
|
|
71
|
+
// Return original message if available, otherwise generic
|
|
72
|
+
return error.message || 'Něco se pokazilo. Zkuste to znovu.';
|
|
73
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { APIClient } from '../api';
|
|
2
|
+
export interface ApiState<T> {
|
|
3
|
+
data: T | null;
|
|
4
|
+
loading: boolean;
|
|
5
|
+
error: Error | null;
|
|
6
|
+
}
|
|
7
|
+
export declare function useApi<T = any>(_apiClient: APIClient): {
|
|
8
|
+
execute: (apiCall: () => Promise<T>, options?: {
|
|
9
|
+
onSuccess?: (data: T) => void;
|
|
10
|
+
onError?: (error: Error) => void;
|
|
11
|
+
skipLoading?: boolean;
|
|
12
|
+
}) => Promise<T>;
|
|
13
|
+
reset: () => void;
|
|
14
|
+
isLoading: boolean;
|
|
15
|
+
hasError: boolean;
|
|
16
|
+
hasData: boolean;
|
|
17
|
+
data: T | null;
|
|
18
|
+
loading: boolean;
|
|
19
|
+
error: Error | null;
|
|
20
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { useState, useCallback } from 'react';
|
|
2
|
+
import { NetworkError, AppError } from '../errors';
|
|
3
|
+
export function useApi(_apiClient) {
|
|
4
|
+
const [state, setState] = useState({
|
|
5
|
+
data: null,
|
|
6
|
+
loading: false,
|
|
7
|
+
error: null
|
|
8
|
+
});
|
|
9
|
+
const execute = useCallback(async (apiCall, options) => {
|
|
10
|
+
try {
|
|
11
|
+
if (!options?.skipLoading) {
|
|
12
|
+
setState(prev => ({ ...prev, loading: true, error: null }));
|
|
13
|
+
}
|
|
14
|
+
const data = await apiCall();
|
|
15
|
+
setState({
|
|
16
|
+
data,
|
|
17
|
+
loading: false,
|
|
18
|
+
error: null
|
|
19
|
+
});
|
|
20
|
+
options?.onSuccess?.(data);
|
|
21
|
+
return data;
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
const appError = error instanceof AppError
|
|
25
|
+
? error
|
|
26
|
+
: new NetworkError('Chyba při komunikaci se serverem');
|
|
27
|
+
setState(prev => ({
|
|
28
|
+
...prev,
|
|
29
|
+
loading: false,
|
|
30
|
+
error: appError
|
|
31
|
+
}));
|
|
32
|
+
options?.onError?.(appError);
|
|
33
|
+
throw appError;
|
|
34
|
+
}
|
|
35
|
+
}, []);
|
|
36
|
+
const reset = useCallback(() => {
|
|
37
|
+
setState({
|
|
38
|
+
data: null,
|
|
39
|
+
loading: false,
|
|
40
|
+
error: null
|
|
41
|
+
});
|
|
42
|
+
}, []);
|
|
43
|
+
return {
|
|
44
|
+
...state,
|
|
45
|
+
execute,
|
|
46
|
+
reset,
|
|
47
|
+
isLoading: state.loading,
|
|
48
|
+
hasError: !!state.error,
|
|
49
|
+
hasData: !!state.data
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
interface UseAsyncOperationOptions<T> {
|
|
2
|
+
onSuccess?: (data: T) => void;
|
|
3
|
+
onError?: (error: Error) => void;
|
|
4
|
+
initialData?: T;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Consistent hook for handling async operations with loading states and error handling
|
|
8
|
+
*/
|
|
9
|
+
export declare function useAsyncOperation<T = any>(options?: UseAsyncOperationOptions<T>): {
|
|
10
|
+
execute: (operation: () => Promise<T>, context?: string) => Promise<T | null>;
|
|
11
|
+
reset: () => void;
|
|
12
|
+
setData: (data: T) => void;
|
|
13
|
+
isLoading: boolean;
|
|
14
|
+
hasError: boolean;
|
|
15
|
+
hasData: boolean;
|
|
16
|
+
isIdle: boolean;
|
|
17
|
+
data: T | null;
|
|
18
|
+
loading: boolean;
|
|
19
|
+
error: Error | null;
|
|
20
|
+
};
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { useState, useCallback } from 'react';
|
|
2
|
+
import { useErrorHandler } from './useErrorHandler';
|
|
3
|
+
/**
|
|
4
|
+
* Consistent hook for handling async operations with loading states and error handling
|
|
5
|
+
*/
|
|
6
|
+
export function useAsyncOperation(options = {}) {
|
|
7
|
+
const [state, setState] = useState({
|
|
8
|
+
data: options.initialData || null,
|
|
9
|
+
loading: false,
|
|
10
|
+
error: null
|
|
11
|
+
});
|
|
12
|
+
const { handleError } = useErrorHandler();
|
|
13
|
+
const execute = useCallback(async (operation, context) => {
|
|
14
|
+
setState(prev => ({ ...prev, loading: true, error: null }));
|
|
15
|
+
try {
|
|
16
|
+
const result = await operation();
|
|
17
|
+
setState({
|
|
18
|
+
data: result,
|
|
19
|
+
loading: false,
|
|
20
|
+
error: null
|
|
21
|
+
});
|
|
22
|
+
options.onSuccess?.(result);
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
27
|
+
setState(prev => ({
|
|
28
|
+
...prev,
|
|
29
|
+
loading: false,
|
|
30
|
+
error: errorObj
|
|
31
|
+
}));
|
|
32
|
+
handleError(errorObj, context || 'useAsyncOperation');
|
|
33
|
+
options.onError?.(errorObj);
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}, [handleError, options]);
|
|
37
|
+
const reset = useCallback(() => {
|
|
38
|
+
setState({
|
|
39
|
+
data: options.initialData || null,
|
|
40
|
+
loading: false,
|
|
41
|
+
error: null
|
|
42
|
+
});
|
|
43
|
+
}, [options.initialData]);
|
|
44
|
+
const setData = useCallback((data) => {
|
|
45
|
+
setState(prev => ({ ...prev, data }));
|
|
46
|
+
}, []);
|
|
47
|
+
return {
|
|
48
|
+
...state,
|
|
49
|
+
execute,
|
|
50
|
+
reset,
|
|
51
|
+
setData,
|
|
52
|
+
// Computed properties
|
|
53
|
+
isLoading: state.loading,
|
|
54
|
+
hasError: !!state.error,
|
|
55
|
+
hasData: !!state.data,
|
|
56
|
+
isIdle: !state.loading && !state.error && !state.data
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { AppError } from '../errors';
|
|
2
|
+
export interface ErrorState {
|
|
3
|
+
error: AppError | Error | null;
|
|
4
|
+
message: string;
|
|
5
|
+
isVisible: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function useErrorHandler(): {
|
|
8
|
+
error: Error | AppError | null;
|
|
9
|
+
errorMessage: string;
|
|
10
|
+
isErrorVisible: boolean;
|
|
11
|
+
handleError: (error: Error | AppError, context?: string) => void;
|
|
12
|
+
clearError: () => void;
|
|
13
|
+
retryAction: (action: () => Promise<void> | void) => void;
|
|
14
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useCallback, useState } from 'react';
|
|
2
|
+
import { getErrorMessage, formatError } from '../errors';
|
|
3
|
+
export function useErrorHandler() {
|
|
4
|
+
const [errorState, setErrorState] = useState({
|
|
5
|
+
error: null,
|
|
6
|
+
message: '',
|
|
7
|
+
isVisible: false
|
|
8
|
+
});
|
|
9
|
+
const handleError = useCallback((error, context) => {
|
|
10
|
+
console.error(`Error in ${context || 'unknown context'}:`, error);
|
|
11
|
+
const userMessage = getErrorMessage(error);
|
|
12
|
+
setErrorState({
|
|
13
|
+
error,
|
|
14
|
+
message: userMessage,
|
|
15
|
+
isVisible: true
|
|
16
|
+
});
|
|
17
|
+
// Auto-hide error after 5 seconds
|
|
18
|
+
setTimeout(() => {
|
|
19
|
+
setErrorState(prev => ({ ...prev, isVisible: false }));
|
|
20
|
+
}, 5000);
|
|
21
|
+
// Send to monitoring service in production
|
|
22
|
+
if (process.env.NODE_ENV === 'production') {
|
|
23
|
+
// TODO: Integrate with monitoring service (Sentry, LogRocket, etc.)
|
|
24
|
+
console.warn('Production error logged:', formatError(error));
|
|
25
|
+
}
|
|
26
|
+
}, []);
|
|
27
|
+
const clearError = useCallback(() => {
|
|
28
|
+
setErrorState({
|
|
29
|
+
error: null,
|
|
30
|
+
message: '',
|
|
31
|
+
isVisible: false
|
|
32
|
+
});
|
|
33
|
+
}, []);
|
|
34
|
+
const retryAction = useCallback((action) => {
|
|
35
|
+
clearError();
|
|
36
|
+
try {
|
|
37
|
+
const result = action();
|
|
38
|
+
if (result instanceof Promise) {
|
|
39
|
+
result.catch(handleError);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
handleError(error);
|
|
44
|
+
}
|
|
45
|
+
}, [handleError, clearError]);
|
|
46
|
+
return {
|
|
47
|
+
error: errorState.error,
|
|
48
|
+
errorMessage: errorState.message,
|
|
49
|
+
isErrorVisible: errorState.isVisible,
|
|
50
|
+
handleError,
|
|
51
|
+
clearError,
|
|
52
|
+
retryAction
|
|
53
|
+
};
|
|
54
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export * from './types';
|
|
2
|
+
export * from './api';
|
|
3
|
+
export * from './constants';
|
|
4
|
+
export * from './validation';
|
|
5
|
+
export * from './errors';
|
|
6
|
+
export * from './utils';
|
|
7
|
+
export * from './hooks/useErrorHandler';
|
|
8
|
+
export * from './hooks/useApi';
|
|
9
|
+
export * from './hooks/useAsyncOperation';
|
|
10
|
+
export * from './components/LoadingSpinner';
|
|
11
|
+
export * from './components/ErrorDisplay';
|
|
12
|
+
export * from './config/environments';
|
|
13
|
+
export * from './config/logger';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Shared package entry point
|
|
2
|
+
export * from './types';
|
|
3
|
+
export * from './api';
|
|
4
|
+
export * from './constants';
|
|
5
|
+
export * from './validation';
|
|
6
|
+
export * from './errors';
|
|
7
|
+
export * from './utils';
|
|
8
|
+
// Note: ./utils exports getErrorMessage which shadows the one from ./errors
|
|
9
|
+
export * from './hooks/useErrorHandler';
|
|
10
|
+
export * from './hooks/useApi';
|
|
11
|
+
export * from './hooks/useAsyncOperation';
|
|
12
|
+
export * from './components/LoadingSpinner';
|
|
13
|
+
export * from './components/ErrorDisplay';
|
|
14
|
+
export * from './config/environments';
|
|
15
|
+
export * from './config/logger';
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export interface Product {
|
|
2
|
+
id: number;
|
|
3
|
+
name: string;
|
|
4
|
+
price: number;
|
|
5
|
+
description: string;
|
|
6
|
+
image?: string;
|
|
7
|
+
imageUrl?: string;
|
|
8
|
+
quantityInStock: number;
|
|
9
|
+
clickedOn: number;
|
|
10
|
+
numberOfPurchases: number;
|
|
11
|
+
}
|
|
12
|
+
export interface PaymentData {
|
|
13
|
+
productId: number;
|
|
14
|
+
productName: string;
|
|
15
|
+
amount: number;
|
|
16
|
+
customerEmail: string;
|
|
17
|
+
qrCode: string;
|
|
18
|
+
paymentId: string;
|
|
19
|
+
}
|
|
20
|
+
export interface AdminProduct {
|
|
21
|
+
id: number;
|
|
22
|
+
name: string;
|
|
23
|
+
price: number;
|
|
24
|
+
description: string;
|
|
25
|
+
image: string;
|
|
26
|
+
imageUrl?: string;
|
|
27
|
+
active: boolean;
|
|
28
|
+
clickedOn: number;
|
|
29
|
+
qrCodesGenerated: number;
|
|
30
|
+
numberOfPurchases: number;
|
|
31
|
+
createdAt: string;
|
|
32
|
+
updatedAt: string;
|
|
33
|
+
quantityInStock?: number;
|
|
34
|
+
}
|
|
35
|
+
export interface ApiResponse<T> {
|
|
36
|
+
success: boolean;
|
|
37
|
+
data?: T;
|
|
38
|
+
message?: string;
|
|
39
|
+
error?: string;
|
|
40
|
+
}
|
|
41
|
+
export interface KioskStatus {
|
|
42
|
+
id: number;
|
|
43
|
+
name: string;
|
|
44
|
+
location: string;
|
|
45
|
+
online: boolean;
|
|
46
|
+
lastSeen: Date;
|
|
47
|
+
salesToday: number;
|
|
48
|
+
}
|
|
49
|
+
export interface WebSocketMessage {
|
|
50
|
+
type: string;
|
|
51
|
+
updateType?: string;
|
|
52
|
+
data?: any;
|
|
53
|
+
kioskId?: number;
|
|
54
|
+
timestamp?: string;
|
|
55
|
+
}
|
|
56
|
+
export type ScreenType = 'products' | 'payment' | 'confirmation' | 'admin-login' | 'admin-dashboard';
|
package/dist/types.js
ADDED