@promakeai/cli 0.5.6 → 0.5.7
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 +71 -71
- package/dist/index.js +132 -132
- package/dist/registry/about-page.json +3 -3
- package/dist/registry/about-section.json +4 -4
- package/dist/registry/animations.json +2 -2
- package/dist/registry/announcement-bar.json +4 -4
- package/dist/registry/api.json +1 -1
- package/dist/registry/auth-core.json +3 -3
- package/dist/registry/bento-grid-section.json +4 -4
- package/dist/registry/blog-core.json +5 -5
- package/dist/registry/blog-list-page.json +4 -4
- package/dist/registry/blog-section.json +4 -4
- package/dist/registry/cards-carousel-section.json +4 -4
- package/dist/registry/cart-drawer.json +4 -4
- package/dist/registry/cart-page.json +4 -4
- package/dist/registry/case-study-page.json +3 -3
- package/dist/registry/category-section.json +4 -4
- package/dist/registry/checkout-page.json +4 -4
- package/dist/registry/coming-soon-page-minimal.json +4 -4
- package/dist/registry/coming-soon-page.json +4 -4
- package/dist/registry/contact-info-grid.json +4 -4
- package/dist/registry/contact-page-centered.json +4 -4
- package/dist/registry/contact-page-map-overlay.json +3 -3
- package/dist/registry/contact-page-map-split.json +3 -3
- package/dist/registry/contact-page-split.json +4 -4
- package/dist/registry/contact-page.json +4 -4
- package/dist/registry/content-section.json +4 -4
- package/dist/registry/cookie-consent.json +4 -4
- package/dist/registry/cookies-page.json +3 -3
- package/dist/registry/cta-section.json +3 -3
- package/dist/registry/ecommerce-core.json +8 -8
- package/dist/registry/empty-page.json +3 -3
- package/dist/registry/faq-categorized.json +4 -4
- package/dist/registry/faq-simple.json +4 -4
- package/dist/registry/favorites-blog-block.json +1 -1
- package/dist/registry/favorites-blog-page.json +4 -4
- package/dist/registry/favorites-ecommerce-block.json +1 -1
- package/dist/registry/favorites-ecommerce-page.json +4 -4
- package/dist/registry/feature-section.json +3 -3
- package/dist/registry/featured-products.json +4 -4
- package/dist/registry/footer-detailed.json +4 -4
- package/dist/registry/footer-minimal.json +3 -3
- package/dist/registry/footer.json +3 -3
- package/dist/registry/forgot-password-page-split.json +4 -4
- package/dist/registry/forgot-password-page.json +4 -4
- package/dist/registry/google-adsense.json +4 -4
- package/dist/registry/google-map.json +2 -2
- package/dist/registry/header-centered-pill.json +4 -4
- package/dist/registry/header-ecommerce.json +4 -4
- package/dist/registry/header-mega.json +4 -4
- package/dist/registry/header-minimal.json +4 -4
- package/dist/registry/header-simple.json +3 -3
- package/dist/registry/hero-carousel.json +3 -3
- package/dist/registry/hero-cta.json +4 -4
- package/dist/registry/hero-gradient.json +4 -4
- package/dist/registry/hero-grid.json +4 -4
- package/dist/registry/hero-profile.json +3 -3
- package/dist/registry/hero.json +3 -3
- package/dist/registry/index.json +103 -103
- package/dist/registry/landing-page-app.json +3 -3
- package/dist/registry/landing-page-saas.json +3 -3
- package/dist/registry/login-page-split.json +4 -4
- package/dist/registry/login-page.json +4 -4
- package/dist/registry/logo-cloud.json +4 -4
- package/dist/registry/masonry-grid.json +3 -3
- package/dist/registry/my-orders-page.json +4 -4
- package/dist/registry/newsletter-section.json +4 -4
- package/dist/registry/order-card-compact.json +1 -1
- package/dist/registry/order-confirmation-page.json +4 -4
- package/dist/registry/order-detail-block.json +1 -1
- package/dist/registry/orders-list-block.json +1 -1
- package/dist/registry/payment-success-block.json +2 -2
- package/dist/registry/portfolio-page.json +4 -4
- package/dist/registry/post-card.json +4 -4
- package/dist/registry/post-detail-block.json +2 -2
- package/dist/registry/post-detail-page.json +4 -4
- package/dist/registry/pricing-card.json +3 -3
- package/dist/registry/pricing-page.json +4 -4
- package/dist/registry/pricing-section.json +4 -4
- package/dist/registry/privacy-page.json +3 -3
- package/dist/registry/product-card-detailed.json +4 -4
- package/dist/registry/product-card-hover.json +4 -4
- package/dist/registry/product-card.json +4 -4
- package/dist/registry/product-detail-block.json +2 -2
- package/dist/registry/product-detail-page.json +4 -4
- package/dist/registry/product-detail-section.json +4 -4
- package/dist/registry/product-quick-view.json +4 -4
- package/dist/registry/products-page.json +4 -4
- package/dist/registry/reading-progress.json +4 -4
- package/dist/registry/register-page-split.json +4 -4
- package/dist/registry/register-page.json +4 -4
- package/dist/registry/related-posts-block.json +1 -1
- package/dist/registry/related-products-block.json +2 -2
- package/dist/registry/reset-password-page-split.json +4 -4
- package/dist/registry/reset-password-page.json +4 -4
- package/dist/registry/service-card.json +1 -1
- package/dist/registry/share-buttons.json +4 -4
- package/dist/registry/skill-card.json +1 -1
- package/dist/registry/team-page.json +4 -4
- package/dist/registry/terms-page.json +3 -3
- package/dist/registry/testimonials-carousel.json +4 -4
- package/dist/registry/testimonials-grid.json +4 -4
- package/dist/registry/timeline-section.json +4 -4
- package/dist/registry/video-hero.json +4 -4
- package/dist/registry/youtube-embed.json +4 -4
- package/package.json +56 -56
- package/template/.env +6 -6
- package/template/README.md +54 -54
- package/template/eslint.config.js +41 -41
- package/template/package.json +95 -95
- package/template/public/_redirects +1 -1
- package/template/public/robots.txt +14 -14
- package/template/scripts/init-db.ts +18 -18
- package/template/src/App.tsx +19 -19
- package/template/src/components/FormField.tsx +48 -48
- package/template/src/components/FormFileInput.tsx +75 -75
- package/template/src/components/GoogleAnalytics.tsx +34 -34
- package/template/src/components/LanguageSwitcher.tsx +53 -53
- package/template/src/components/PasswordInput.tsx +60 -60
- package/template/src/components/ScriptInjector.tsx +62 -62
- package/template/src/components/Stack.tsx +39 -39
- package/template/src/constants/constants.json +67 -67
- package/template/src/db/index.ts +20 -20
- package/template/src/db/provider.tsx +78 -78
- package/template/src/db/schema.json +258 -258
- package/template/src/db/types.ts +195 -195
- package/template/src/hooks/use-debounced-value.ts +12 -12
- package/template/src/lang/index.ts +90 -90
- package/template/src/lib/api.ts +345 -345
- package/template/src/lib/env.ts +19 -19
- package/template/src/router.tsx +14 -14
- package/template/src/vite-env.d.ts +1 -1
- package/template/vite.config.ts +64 -64
package/template/src/lib/api.ts
CHANGED
|
@@ -1,345 +1,345 @@
|
|
|
1
|
-
import axios, { type AxiosInstance, type AxiosResponse } from "axios";
|
|
2
|
-
import constants from "@/constants/constants.json";
|
|
3
|
-
|
|
4
|
-
// API service class
|
|
5
|
-
class ApiService {
|
|
6
|
-
private axiosInstance: AxiosInstance;
|
|
7
|
-
private settings: any = null;
|
|
8
|
-
|
|
9
|
-
constructor() {
|
|
10
|
-
// Default configuration - will be updated when settings load
|
|
11
|
-
this.axiosInstance = axios.create({
|
|
12
|
-
baseURL: "https://backend.promake.ai/api",
|
|
13
|
-
timeout: 30000,
|
|
14
|
-
headers: {
|
|
15
|
-
"Content-Type": "application/json",
|
|
16
|
-
},
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
// Request interceptor to add site slug
|
|
20
|
-
this.axiosInstance.interceptors.request.use(
|
|
21
|
-
(config) => {
|
|
22
|
-
if (this.settings?.site?.slug) {
|
|
23
|
-
config.headers["X-Site-Slug"] = this.settings.site.slug;
|
|
24
|
-
}
|
|
25
|
-
return config;
|
|
26
|
-
},
|
|
27
|
-
(error) => {
|
|
28
|
-
return Promise.reject(error);
|
|
29
|
-
},
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
// Response interceptor for error handling
|
|
33
|
-
this.axiosInstance.interceptors.response.use(
|
|
34
|
-
(response: AxiosResponse) => response,
|
|
35
|
-
(error) => {
|
|
36
|
-
console.error("API Error:", {
|
|
37
|
-
status: error.response?.status,
|
|
38
|
-
statusText: error.response?.statusText,
|
|
39
|
-
data: error.response?.data,
|
|
40
|
-
url: error.config?.url,
|
|
41
|
-
});
|
|
42
|
-
return Promise.reject(error);
|
|
43
|
-
},
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Update settings for API configuration
|
|
48
|
-
updateSettings(settings: any) {
|
|
49
|
-
this.settings = settings;
|
|
50
|
-
if (settings?.api?.baseUrl) {
|
|
51
|
-
this.axiosInstance.defaults.baseURL = settings.api.baseUrl;
|
|
52
|
-
}
|
|
53
|
-
if (settings?.api?.timeout) {
|
|
54
|
-
this.axiosInstance.defaults.timeout = settings.api.timeout;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Get endpoint from settings
|
|
59
|
-
private getEndpoint(key: string): string {
|
|
60
|
-
return this.settings?.api?.endpoints?.[key] || `/${key}`;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Order API methods
|
|
64
|
-
async createOrder(orderData: {
|
|
65
|
-
user_id: string;
|
|
66
|
-
total_price: number;
|
|
67
|
-
status: string;
|
|
68
|
-
payment_method: string;
|
|
69
|
-
shipping_address: any;
|
|
70
|
-
notes?: string | null;
|
|
71
|
-
items: Array<{
|
|
72
|
-
product_id: number;
|
|
73
|
-
quantity: number;
|
|
74
|
-
price: number;
|
|
75
|
-
}>;
|
|
76
|
-
}): Promise<{ orderId: number; success: boolean }> {
|
|
77
|
-
try {
|
|
78
|
-
const response = await this.axiosInstance.post(
|
|
79
|
-
this.getEndpoint("orders"),
|
|
80
|
-
{
|
|
81
|
-
site_slug: this.settings?.site?.slug || "promake",
|
|
82
|
-
order: {
|
|
83
|
-
user_id: orderData.user_id,
|
|
84
|
-
total_price: orderData.total_price,
|
|
85
|
-
status: orderData.status,
|
|
86
|
-
payment_method: orderData.payment_method,
|
|
87
|
-
shipping_address: orderData.shipping_address,
|
|
88
|
-
notes: orderData.notes,
|
|
89
|
-
},
|
|
90
|
-
items: orderData.items,
|
|
91
|
-
},
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
return {
|
|
95
|
-
orderId: response.data.order_id,
|
|
96
|
-
success: true,
|
|
97
|
-
};
|
|
98
|
-
} catch (error: any) {
|
|
99
|
-
console.error("Order creation failed:", error);
|
|
100
|
-
throw new Error(error.response?.data?.message || "Order creation failed");
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
// Generic form submission with file upload to mail service
|
|
106
|
-
async submitFormWithFile(
|
|
107
|
-
formData: Record<string, any>,
|
|
108
|
-
formConfig: {
|
|
109
|
-
email_subject1: string;
|
|
110
|
-
email_subject2: string;
|
|
111
|
-
fields: Array<{ name: string; required: boolean }>;
|
|
112
|
-
},
|
|
113
|
-
currentLanguage: string = "en",
|
|
114
|
-
): Promise<{ success: boolean; message: string }> {
|
|
115
|
-
try {
|
|
116
|
-
const customerEmail = formData["email"];
|
|
117
|
-
if (!customerEmail) {
|
|
118
|
-
throw new Error("Email field is required in form data");
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const tenantEmail = constants.email || import.meta.env.VITE_TENANT_MAIL;
|
|
122
|
-
if (!tenantEmail) {
|
|
123
|
-
throw new Error("VITE_TENANT_MAIL environment variable is not set");
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const mailServiceUrl =
|
|
127
|
-
import.meta.env.VITE_MAIL_SERVICE_URL ||
|
|
128
|
-
"https://mail.promake.ai/api/v1/send-mail";
|
|
129
|
-
|
|
130
|
-
// Check if there are attachments to determine request type
|
|
131
|
-
const hasAttachments = formData.attachments &&
|
|
132
|
-
Array.isArray(formData.attachments) &&
|
|
133
|
-
formData.attachments.length > 0;
|
|
134
|
-
|
|
135
|
-
if (hasAttachments) {
|
|
136
|
-
// Use FormData for file uploads
|
|
137
|
-
const formDataObj = new FormData();
|
|
138
|
-
|
|
139
|
-
formDataObj.append('target_email1', customerEmail);
|
|
140
|
-
formDataObj.append('target_email2', tenantEmail);
|
|
141
|
-
formDataObj.append('email_subject1', formConfig.email_subject1);
|
|
142
|
-
formDataObj.append('email_subject2', formConfig.email_subject2);
|
|
143
|
-
|
|
144
|
-
// Add form fields
|
|
145
|
-
formConfig.fields.forEach((field) => {
|
|
146
|
-
if (field.name !== 'attachments') {
|
|
147
|
-
formDataObj.append(`data[${field.name}][required]`, field.required.toString());
|
|
148
|
-
formDataObj.append(`data[${field.name}][value]`, formData[field.name] || '');
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
// Add attachments
|
|
153
|
-
formData.attachments.forEach((file: File) => {
|
|
154
|
-
formDataObj.append('attachments[]', file);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
const response = await axios.post(mailServiceUrl, formDataObj, {
|
|
158
|
-
headers: {
|
|
159
|
-
'Accept-Language': currentLanguage,
|
|
160
|
-
'Content-Type': 'multipart/form-data',
|
|
161
|
-
},
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
return {
|
|
165
|
-
success: true,
|
|
166
|
-
message: response.data.message || "Form submitted successfully",
|
|
167
|
-
};
|
|
168
|
-
} else {
|
|
169
|
-
// Use JSON for simple forms (backward compatible)
|
|
170
|
-
const data: Record<string, { required: boolean; value: any }> = {};
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
formConfig.fields.forEach((field) => {
|
|
174
|
-
if (field.name !== 'attachments') {
|
|
175
|
-
data[field.name] = {
|
|
176
|
-
required: field.required,
|
|
177
|
-
value: formData[field.name] || null,
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
const response = await axios.post(
|
|
183
|
-
mailServiceUrl,
|
|
184
|
-
{
|
|
185
|
-
target_email1: customerEmail,
|
|
186
|
-
target_email2: tenantEmail,
|
|
187
|
-
email_subject1: formConfig.email_subject1,
|
|
188
|
-
email_subject2: formConfig.email_subject2,
|
|
189
|
-
data: data,
|
|
190
|
-
},
|
|
191
|
-
{
|
|
192
|
-
headers: {
|
|
193
|
-
"Accept-Language": currentLanguage,
|
|
194
|
-
"Content-Type": "application/json",
|
|
195
|
-
},
|
|
196
|
-
},
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
return {
|
|
200
|
-
success: true,
|
|
201
|
-
message: response.data.message || "Form submitted successfully",
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
} catch (error: any) {
|
|
205
|
-
console.error("Form submission failed:", error);
|
|
206
|
-
throw new Error(error.response?.data?.message || "Failed to submit form");
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
// DEPRECATED: Use submitFormWithFile instead
|
|
212
|
-
// Keeping for backward compatibility
|
|
213
|
-
// Generic form submission to mail service
|
|
214
|
-
async submitForm(
|
|
215
|
-
formData: Record<string, any>,
|
|
216
|
-
formConfig: {
|
|
217
|
-
email_subject1: string;
|
|
218
|
-
email_subject2: string;
|
|
219
|
-
fields: Array<{ name: string; required: boolean }>;
|
|
220
|
-
},
|
|
221
|
-
currentLanguage: string = "en",
|
|
222
|
-
): Promise<{ success: boolean; message: string }> {
|
|
223
|
-
try {
|
|
224
|
-
// Extract customer email from form data
|
|
225
|
-
const customerEmail = formData["email"];
|
|
226
|
-
if (!customerEmail) {
|
|
227
|
-
throw new Error("Email field is required in form data");
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// Get site owner email from ENV
|
|
231
|
-
const tenantEmail = constants.email || import.meta.env.VITE_TENANT_MAIL;
|
|
232
|
-
if (!tenantEmail) {
|
|
233
|
-
throw new Error("VITE_TENANT_MAIL environment variable is not set");
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Convert formData to backend expected format with required info from config
|
|
237
|
-
const data: Record<string, { required: boolean; value: any }> = {};
|
|
238
|
-
|
|
239
|
-
formConfig.fields.forEach((field) => {
|
|
240
|
-
data[field.name] = {
|
|
241
|
-
required: field.required,
|
|
242
|
-
value: formData[field.name] || null,
|
|
243
|
-
};
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
// Get mail service URL from ENV
|
|
247
|
-
const mailServiceUrl =
|
|
248
|
-
import.meta.env.VITE_MAIL_SERVICE_URL ||
|
|
249
|
-
"https://mail.promake.ai/api/v1/send-mail";
|
|
250
|
-
|
|
251
|
-
// Make request to mail service
|
|
252
|
-
const response = await axios.post(
|
|
253
|
-
mailServiceUrl,
|
|
254
|
-
{
|
|
255
|
-
target_email1: customerEmail,
|
|
256
|
-
target_email2: tenantEmail,
|
|
257
|
-
email_subject1: formConfig.email_subject1,
|
|
258
|
-
email_subject2: formConfig.email_subject2,
|
|
259
|
-
data: data,
|
|
260
|
-
},
|
|
261
|
-
{
|
|
262
|
-
headers: {
|
|
263
|
-
"Accept-Language": currentLanguage,
|
|
264
|
-
"Content-Type": "application/json",
|
|
265
|
-
},
|
|
266
|
-
},
|
|
267
|
-
);
|
|
268
|
-
|
|
269
|
-
return {
|
|
270
|
-
success: true,
|
|
271
|
-
message: response.data.message || "Form submitted successfully",
|
|
272
|
-
};
|
|
273
|
-
} catch (error: any) {
|
|
274
|
-
console.error("Form submission failed:", error);
|
|
275
|
-
throw new Error(error.response?.data?.message || "Failed to submit form");
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// DEPRECATED: Use submitForm instead
|
|
280
|
-
// Keeping for backward compatibility
|
|
281
|
-
async submitContactForm(contactData: {
|
|
282
|
-
name: string;
|
|
283
|
-
email: string;
|
|
284
|
-
subject?: string;
|
|
285
|
-
message: string;
|
|
286
|
-
}): Promise<{ success: boolean; message: string }> {
|
|
287
|
-
console.warn("submitContactForm is deprecated. Use submitForm instead.");
|
|
288
|
-
return this.submitForm(
|
|
289
|
-
contactData,
|
|
290
|
-
{
|
|
291
|
-
email_subject1: "Contact Form Submission",
|
|
292
|
-
email_subject2: "New Contact Form",
|
|
293
|
-
fields: [
|
|
294
|
-
{ name: "name", required: true },
|
|
295
|
-
{ name: "email", required: true },
|
|
296
|
-
{ name: "subject", required: false },
|
|
297
|
-
{ name: "message", required: true },
|
|
298
|
-
],
|
|
299
|
-
},
|
|
300
|
-
"en",
|
|
301
|
-
);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// Get order by ID (for order confirmation page)
|
|
305
|
-
async getOrderById(orderId: number): Promise<any | null> {
|
|
306
|
-
try {
|
|
307
|
-
const response = await this.axiosInstance.get(
|
|
308
|
-
`${this.getEndpoint("orders")}/${orderId}?site_slug=${this.settings?.site?.slug
|
|
309
|
-
}`,
|
|
310
|
-
);
|
|
311
|
-
return response.data.order || null;
|
|
312
|
-
} catch (error: any) {
|
|
313
|
-
console.error("Failed to fetch order:", error);
|
|
314
|
-
return null;
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// Create singleton instance
|
|
320
|
-
const apiService = new ApiService();
|
|
321
|
-
|
|
322
|
-
// Hook to use API service with settings
|
|
323
|
-
export const useApiService = () => {
|
|
324
|
-
// Reconstruct settings object for ApiService.updateSettings
|
|
325
|
-
const settings = {
|
|
326
|
-
site: {
|
|
327
|
-
slug: constants.site.slug,
|
|
328
|
-
},
|
|
329
|
-
api: {
|
|
330
|
-
baseUrl: constants.api.baseUrl,
|
|
331
|
-
timeout: constants.api.timeout,
|
|
332
|
-
endpoints: constants.api.endpoints,
|
|
333
|
-
},
|
|
334
|
-
};
|
|
335
|
-
|
|
336
|
-
// Update API service settings when they change
|
|
337
|
-
if (settings && apiService) {
|
|
338
|
-
apiService.updateSettings(settings);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
return apiService;
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
// Export the service for direct use
|
|
345
|
-
export default apiService;
|
|
1
|
+
import axios, { type AxiosInstance, type AxiosResponse } from "axios";
|
|
2
|
+
import constants from "@/constants/constants.json";
|
|
3
|
+
|
|
4
|
+
// API service class
|
|
5
|
+
class ApiService {
|
|
6
|
+
private axiosInstance: AxiosInstance;
|
|
7
|
+
private settings: any = null;
|
|
8
|
+
|
|
9
|
+
constructor() {
|
|
10
|
+
// Default configuration - will be updated when settings load
|
|
11
|
+
this.axiosInstance = axios.create({
|
|
12
|
+
baseURL: "https://backend.promake.ai/api",
|
|
13
|
+
timeout: 30000,
|
|
14
|
+
headers: {
|
|
15
|
+
"Content-Type": "application/json",
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Request interceptor to add site slug
|
|
20
|
+
this.axiosInstance.interceptors.request.use(
|
|
21
|
+
(config) => {
|
|
22
|
+
if (this.settings?.site?.slug) {
|
|
23
|
+
config.headers["X-Site-Slug"] = this.settings.site.slug;
|
|
24
|
+
}
|
|
25
|
+
return config;
|
|
26
|
+
},
|
|
27
|
+
(error) => {
|
|
28
|
+
return Promise.reject(error);
|
|
29
|
+
},
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
// Response interceptor for error handling
|
|
33
|
+
this.axiosInstance.interceptors.response.use(
|
|
34
|
+
(response: AxiosResponse) => response,
|
|
35
|
+
(error) => {
|
|
36
|
+
console.error("API Error:", {
|
|
37
|
+
status: error.response?.status,
|
|
38
|
+
statusText: error.response?.statusText,
|
|
39
|
+
data: error.response?.data,
|
|
40
|
+
url: error.config?.url,
|
|
41
|
+
});
|
|
42
|
+
return Promise.reject(error);
|
|
43
|
+
},
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Update settings for API configuration
|
|
48
|
+
updateSettings(settings: any) {
|
|
49
|
+
this.settings = settings;
|
|
50
|
+
if (settings?.api?.baseUrl) {
|
|
51
|
+
this.axiosInstance.defaults.baseURL = settings.api.baseUrl;
|
|
52
|
+
}
|
|
53
|
+
if (settings?.api?.timeout) {
|
|
54
|
+
this.axiosInstance.defaults.timeout = settings.api.timeout;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Get endpoint from settings
|
|
59
|
+
private getEndpoint(key: string): string {
|
|
60
|
+
return this.settings?.api?.endpoints?.[key] || `/${key}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Order API methods
|
|
64
|
+
async createOrder(orderData: {
|
|
65
|
+
user_id: string;
|
|
66
|
+
total_price: number;
|
|
67
|
+
status: string;
|
|
68
|
+
payment_method: string;
|
|
69
|
+
shipping_address: any;
|
|
70
|
+
notes?: string | null;
|
|
71
|
+
items: Array<{
|
|
72
|
+
product_id: number;
|
|
73
|
+
quantity: number;
|
|
74
|
+
price: number;
|
|
75
|
+
}>;
|
|
76
|
+
}): Promise<{ orderId: number; success: boolean }> {
|
|
77
|
+
try {
|
|
78
|
+
const response = await this.axiosInstance.post(
|
|
79
|
+
this.getEndpoint("orders"),
|
|
80
|
+
{
|
|
81
|
+
site_slug: this.settings?.site?.slug || "promake",
|
|
82
|
+
order: {
|
|
83
|
+
user_id: orderData.user_id,
|
|
84
|
+
total_price: orderData.total_price,
|
|
85
|
+
status: orderData.status,
|
|
86
|
+
payment_method: orderData.payment_method,
|
|
87
|
+
shipping_address: orderData.shipping_address,
|
|
88
|
+
notes: orderData.notes,
|
|
89
|
+
},
|
|
90
|
+
items: orderData.items,
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
orderId: response.data.order_id,
|
|
96
|
+
success: true,
|
|
97
|
+
};
|
|
98
|
+
} catch (error: any) {
|
|
99
|
+
console.error("Order creation failed:", error);
|
|
100
|
+
throw new Error(error.response?.data?.message || "Order creation failed");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
// Generic form submission with file upload to mail service
|
|
106
|
+
async submitFormWithFile(
|
|
107
|
+
formData: Record<string, any>,
|
|
108
|
+
formConfig: {
|
|
109
|
+
email_subject1: string;
|
|
110
|
+
email_subject2: string;
|
|
111
|
+
fields: Array<{ name: string; required: boolean }>;
|
|
112
|
+
},
|
|
113
|
+
currentLanguage: string = "en",
|
|
114
|
+
): Promise<{ success: boolean; message: string }> {
|
|
115
|
+
try {
|
|
116
|
+
const customerEmail = formData["email"];
|
|
117
|
+
if (!customerEmail) {
|
|
118
|
+
throw new Error("Email field is required in form data");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const tenantEmail = constants.email || import.meta.env.VITE_TENANT_MAIL;
|
|
122
|
+
if (!tenantEmail) {
|
|
123
|
+
throw new Error("VITE_TENANT_MAIL environment variable is not set");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const mailServiceUrl =
|
|
127
|
+
import.meta.env.VITE_MAIL_SERVICE_URL ||
|
|
128
|
+
"https://mail.promake.ai/api/v1/send-mail";
|
|
129
|
+
|
|
130
|
+
// Check if there are attachments to determine request type
|
|
131
|
+
const hasAttachments = formData.attachments &&
|
|
132
|
+
Array.isArray(formData.attachments) &&
|
|
133
|
+
formData.attachments.length > 0;
|
|
134
|
+
|
|
135
|
+
if (hasAttachments) {
|
|
136
|
+
// Use FormData for file uploads
|
|
137
|
+
const formDataObj = new FormData();
|
|
138
|
+
|
|
139
|
+
formDataObj.append('target_email1', customerEmail);
|
|
140
|
+
formDataObj.append('target_email2', tenantEmail);
|
|
141
|
+
formDataObj.append('email_subject1', formConfig.email_subject1);
|
|
142
|
+
formDataObj.append('email_subject2', formConfig.email_subject2);
|
|
143
|
+
|
|
144
|
+
// Add form fields
|
|
145
|
+
formConfig.fields.forEach((field) => {
|
|
146
|
+
if (field.name !== 'attachments') {
|
|
147
|
+
formDataObj.append(`data[${field.name}][required]`, field.required.toString());
|
|
148
|
+
formDataObj.append(`data[${field.name}][value]`, formData[field.name] || '');
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Add attachments
|
|
153
|
+
formData.attachments.forEach((file: File) => {
|
|
154
|
+
formDataObj.append('attachments[]', file);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const response = await axios.post(mailServiceUrl, formDataObj, {
|
|
158
|
+
headers: {
|
|
159
|
+
'Accept-Language': currentLanguage,
|
|
160
|
+
'Content-Type': 'multipart/form-data',
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
success: true,
|
|
166
|
+
message: response.data.message || "Form submitted successfully",
|
|
167
|
+
};
|
|
168
|
+
} else {
|
|
169
|
+
// Use JSON for simple forms (backward compatible)
|
|
170
|
+
const data: Record<string, { required: boolean; value: any }> = {};
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
formConfig.fields.forEach((field) => {
|
|
174
|
+
if (field.name !== 'attachments') {
|
|
175
|
+
data[field.name] = {
|
|
176
|
+
required: field.required,
|
|
177
|
+
value: formData[field.name] || null,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const response = await axios.post(
|
|
183
|
+
mailServiceUrl,
|
|
184
|
+
{
|
|
185
|
+
target_email1: customerEmail,
|
|
186
|
+
target_email2: tenantEmail,
|
|
187
|
+
email_subject1: formConfig.email_subject1,
|
|
188
|
+
email_subject2: formConfig.email_subject2,
|
|
189
|
+
data: data,
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
headers: {
|
|
193
|
+
"Accept-Language": currentLanguage,
|
|
194
|
+
"Content-Type": "application/json",
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
success: true,
|
|
201
|
+
message: response.data.message || "Form submitted successfully",
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
} catch (error: any) {
|
|
205
|
+
console.error("Form submission failed:", error);
|
|
206
|
+
throw new Error(error.response?.data?.message || "Failed to submit form");
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
// DEPRECATED: Use submitFormWithFile instead
|
|
212
|
+
// Keeping for backward compatibility
|
|
213
|
+
// Generic form submission to mail service
|
|
214
|
+
async submitForm(
|
|
215
|
+
formData: Record<string, any>,
|
|
216
|
+
formConfig: {
|
|
217
|
+
email_subject1: string;
|
|
218
|
+
email_subject2: string;
|
|
219
|
+
fields: Array<{ name: string; required: boolean }>;
|
|
220
|
+
},
|
|
221
|
+
currentLanguage: string = "en",
|
|
222
|
+
): Promise<{ success: boolean; message: string }> {
|
|
223
|
+
try {
|
|
224
|
+
// Extract customer email from form data
|
|
225
|
+
const customerEmail = formData["email"];
|
|
226
|
+
if (!customerEmail) {
|
|
227
|
+
throw new Error("Email field is required in form data");
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Get site owner email from ENV
|
|
231
|
+
const tenantEmail = constants.email || import.meta.env.VITE_TENANT_MAIL;
|
|
232
|
+
if (!tenantEmail) {
|
|
233
|
+
throw new Error("VITE_TENANT_MAIL environment variable is not set");
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Convert formData to backend expected format with required info from config
|
|
237
|
+
const data: Record<string, { required: boolean; value: any }> = {};
|
|
238
|
+
|
|
239
|
+
formConfig.fields.forEach((field) => {
|
|
240
|
+
data[field.name] = {
|
|
241
|
+
required: field.required,
|
|
242
|
+
value: formData[field.name] || null,
|
|
243
|
+
};
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Get mail service URL from ENV
|
|
247
|
+
const mailServiceUrl =
|
|
248
|
+
import.meta.env.VITE_MAIL_SERVICE_URL ||
|
|
249
|
+
"https://mail.promake.ai/api/v1/send-mail";
|
|
250
|
+
|
|
251
|
+
// Make request to mail service
|
|
252
|
+
const response = await axios.post(
|
|
253
|
+
mailServiceUrl,
|
|
254
|
+
{
|
|
255
|
+
target_email1: customerEmail,
|
|
256
|
+
target_email2: tenantEmail,
|
|
257
|
+
email_subject1: formConfig.email_subject1,
|
|
258
|
+
email_subject2: formConfig.email_subject2,
|
|
259
|
+
data: data,
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
headers: {
|
|
263
|
+
"Accept-Language": currentLanguage,
|
|
264
|
+
"Content-Type": "application/json",
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
success: true,
|
|
271
|
+
message: response.data.message || "Form submitted successfully",
|
|
272
|
+
};
|
|
273
|
+
} catch (error: any) {
|
|
274
|
+
console.error("Form submission failed:", error);
|
|
275
|
+
throw new Error(error.response?.data?.message || "Failed to submit form");
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// DEPRECATED: Use submitForm instead
|
|
280
|
+
// Keeping for backward compatibility
|
|
281
|
+
async submitContactForm(contactData: {
|
|
282
|
+
name: string;
|
|
283
|
+
email: string;
|
|
284
|
+
subject?: string;
|
|
285
|
+
message: string;
|
|
286
|
+
}): Promise<{ success: boolean; message: string }> {
|
|
287
|
+
console.warn("submitContactForm is deprecated. Use submitForm instead.");
|
|
288
|
+
return this.submitForm(
|
|
289
|
+
contactData,
|
|
290
|
+
{
|
|
291
|
+
email_subject1: "Contact Form Submission",
|
|
292
|
+
email_subject2: "New Contact Form",
|
|
293
|
+
fields: [
|
|
294
|
+
{ name: "name", required: true },
|
|
295
|
+
{ name: "email", required: true },
|
|
296
|
+
{ name: "subject", required: false },
|
|
297
|
+
{ name: "message", required: true },
|
|
298
|
+
],
|
|
299
|
+
},
|
|
300
|
+
"en",
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Get order by ID (for order confirmation page)
|
|
305
|
+
async getOrderById(orderId: number): Promise<any | null> {
|
|
306
|
+
try {
|
|
307
|
+
const response = await this.axiosInstance.get(
|
|
308
|
+
`${this.getEndpoint("orders")}/${orderId}?site_slug=${this.settings?.site?.slug
|
|
309
|
+
}`,
|
|
310
|
+
);
|
|
311
|
+
return response.data.order || null;
|
|
312
|
+
} catch (error: any) {
|
|
313
|
+
console.error("Failed to fetch order:", error);
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Create singleton instance
|
|
320
|
+
const apiService = new ApiService();
|
|
321
|
+
|
|
322
|
+
// Hook to use API service with settings
|
|
323
|
+
export const useApiService = () => {
|
|
324
|
+
// Reconstruct settings object for ApiService.updateSettings
|
|
325
|
+
const settings = {
|
|
326
|
+
site: {
|
|
327
|
+
slug: constants.site.slug,
|
|
328
|
+
},
|
|
329
|
+
api: {
|
|
330
|
+
baseUrl: constants.api.baseUrl,
|
|
331
|
+
timeout: constants.api.timeout,
|
|
332
|
+
endpoints: constants.api.endpoints,
|
|
333
|
+
},
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
// Update API service settings when they change
|
|
337
|
+
if (settings && apiService) {
|
|
338
|
+
apiService.updateSettings(settings);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return apiService;
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
// Export the service for direct use
|
|
345
|
+
export default apiService;
|