@liiift-studio/sales-portal 1.2.1

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.
Files changed (52) hide show
  1. package/README.md +461 -0
  2. package/SETUP.md +230 -0
  3. package/api/getAnalytics.d.ts +38 -0
  4. package/api/getAnalytics.js +346 -0
  5. package/api/getBalanceTransactions.d.ts +29 -0
  6. package/api/getBalanceTransactions.js +125 -0
  7. package/api/getDesignerInfo.d.ts +37 -0
  8. package/api/getDesignerInfo.js +98 -0
  9. package/api/getDesigners.d.ts +28 -0
  10. package/api/getDesigners.js +63 -0
  11. package/api/getPreviousSales.d.ts +23 -0
  12. package/api/getPreviousSales.js +82 -0
  13. package/api/getSales.d.ts +29 -0
  14. package/api/getSales.js +50 -0
  15. package/api/getSalesRange.d.ts +23 -0
  16. package/api/getSalesRange.js +58 -0
  17. package/api/utils/authMiddleware.js +84 -0
  18. package/api/utils/dateUtils.js +69 -0
  19. package/api/utils/feeCalculator.js +148 -0
  20. package/api/utils/processors/invoiceProcessor.js +337 -0
  21. package/api/utils/processors/paymentProcessor.js +462 -0
  22. package/api/utils/salesDataProcessing.js +596 -0
  23. package/api/utils/salesDataProcessor.js +224 -0
  24. package/api/utils/stripeFetcher.js +248 -0
  25. package/components/DateRangeSalesTable.js +1072 -0
  26. package/components/DebugValues.js +48 -0
  27. package/components/LicenseTypeList.js +193 -0
  28. package/components/LoginForm.js +219 -0
  29. package/components/PeriodComparison.js +501 -0
  30. package/components/Sales.js +773 -0
  31. package/components/SalesChart.js +307 -0
  32. package/components/SalesPortalPage.js +147 -0
  33. package/components/SalesTable.js +677 -0
  34. package/components/SummaryCards.js +345 -0
  35. package/components/TopPerformers.js +331 -0
  36. package/components/TypefaceList.js +154 -0
  37. package/components/table-columns.js +70 -0
  38. package/components/table-row-cells.js +295 -0
  39. package/data/countryCode.json +318 -0
  40. package/hooks/useSalesDateQuery.d.ts +20 -0
  41. package/hooks/useSalesDateQuery.js +71 -0
  42. package/index.d.ts +172 -0
  43. package/index.js +33 -0
  44. package/package.json +87 -0
  45. package/styles/sales-portal.module.scss +383 -0
  46. package/styles/sales-portal.theme.d.ts +5 -0
  47. package/styles/sales-portal.theme.js +799 -0
  48. package/utils/currencyUtils.d.ts +20 -0
  49. package/utils/currencyUtils.js +79 -0
  50. package/utils/salesDataProcessing.d.ts +44 -0
  51. package/utils/salesDataProcessing.js +596 -0
  52. package/utils/useSalesDateQuery.js +71 -0
@@ -0,0 +1,38 @@
1
+ // Type definitions for getAnalytics API handler
2
+ import { NextApiRequest, NextApiResponse } from 'next';
3
+
4
+ export interface GetAnalyticsRequest extends NextApiRequest {
5
+ query: {
6
+ month: string;
7
+ year: string;
8
+ user?: string;
9
+ password?: string;
10
+ };
11
+ }
12
+
13
+ export interface AnalyticsData {
14
+ totalRevenue: number;
15
+ orderCount: number;
16
+ averageOrderValue: number;
17
+ topTypefaces?: any[];
18
+ topLicenseTypes?: any[];
19
+ geographicDistribution?: any[];
20
+ }
21
+
22
+ export interface AnalyticsResponse {
23
+ success: boolean;
24
+ data?: AnalyticsData;
25
+ error?: string;
26
+ message?: string;
27
+ }
28
+
29
+ export const config: {
30
+ maxDuration: number;
31
+ };
32
+
33
+ declare function handler(
34
+ req: GetAnalyticsRequest,
35
+ res: NextApiResponse<AnalyticsResponse>
36
+ ): Promise<void>;
37
+
38
+ export default handler;
@@ -0,0 +1,346 @@
1
+ // API endpoint for fetching sales analytics data
2
+ import { authMiddleware } from './utils/authMiddleware';
3
+
4
+ /**
5
+ * API handler for generating analytics from sales data
6
+ * @param {Object} req - HTTP request object
7
+ * @param {Object} res - HTTP response object
8
+ */
9
+ export default async function handler(req, res) {
10
+ // Only allow POST requests
11
+ if (req.method !== 'POST') {
12
+ return res.status(405).json({ success: false, message: 'Method not allowed' });
13
+ }
14
+
15
+ try {
16
+ // Authenticate request
17
+ const { authorized, designer, error } = await authMiddleware(req);
18
+
19
+ if (!authorized) {
20
+ return res.status(401).json({ success: false, message: error || 'Unauthorized' });
21
+ }
22
+
23
+ // Extract request parameters
24
+ const {
25
+ date,
26
+ compareWith = 'previous_month', // 'previous_month', 'previous_year', 'custom'
27
+ customStartDate,
28
+ customEndDate,
29
+ admin = false
30
+ } = req.body;
31
+
32
+ if (!date) {
33
+ return res.status(400).json({ success: false, message: 'Date is required' });
34
+ }
35
+
36
+ // Generate analytics based on current sales data
37
+ try {
38
+ // Call your existing sales data retrieval function to get current period data
39
+ const { getSalesData } = require('./getSales');
40
+ const currentPeriodSales = await getSalesData(date, designer, admin);
41
+
42
+ // Get comparison period data
43
+ let comparisonPeriodDate;
44
+ let comparisonLabel;
45
+
46
+ if (compareWith === 'previous_month') {
47
+ comparisonPeriodDate = new Date(date);
48
+ comparisonPeriodDate.setUTCMonth(comparisonPeriodDate.getUTCMonth() - 1);
49
+ comparisonLabel = 'Previous Month';
50
+ } else if (compareWith === 'previous_year') {
51
+ comparisonPeriodDate = new Date(date);
52
+ comparisonPeriodDate.setUTCFullYear(comparisonPeriodDate.getUTCFullYear() - 1);
53
+ comparisonLabel = 'Previous Year';
54
+ } else if (compareWith === 'custom' && customStartDate && customEndDate) {
55
+ // Handle custom date range
56
+ comparisonPeriodDate = {
57
+ start: new Date(customStartDate),
58
+ end: new Date(customEndDate)
59
+ };
60
+ comparisonLabel = 'Custom Range';
61
+ } else {
62
+ // Default to previous month
63
+ comparisonPeriodDate = new Date(date);
64
+ comparisonPeriodDate.setUTCMonth(comparisonPeriodDate.getUTCMonth() - 1);
65
+ comparisonLabel = 'Previous Month';
66
+ }
67
+
68
+ // Fetch comparison period data
69
+ const comparisonPeriodSales = await getSalesData(comparisonPeriodDate, designer, admin);
70
+
71
+ // Calculate analytics
72
+ const analytics = generateAnalytics(currentPeriodSales, comparisonPeriodSales);
73
+
74
+ // Return the analytics data
75
+ return res.status(200).json({
76
+ success: true,
77
+ data: analytics,
78
+ metadata: {
79
+ currentPeriod: new Date(date).toISOString(),
80
+ comparisonPeriod: comparisonPeriodDate instanceof Date
81
+ ? comparisonPeriodDate.toISOString()
82
+ : {
83
+ start: comparisonPeriodDate.start.toISOString(),
84
+ end: comparisonPeriodDate.end.toISOString()
85
+ },
86
+ comparisonLabel
87
+ }
88
+ });
89
+ } catch (error) {
90
+ console.error('Error generating analytics:', error);
91
+ return res.status(500).json({ success: false, message: 'Failed to generate analytics' });
92
+ }
93
+ } catch (error) {
94
+ console.error('Error in getAnalytics API:', error);
95
+ return res.status(500).json({ success: false, message: 'Internal server error' });
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Generate analytics from sales data
101
+ * @param {Array} currentSales - Current period sales data
102
+ * @param {Array} comparisonSales - Comparison period sales data
103
+ * @returns {Object} Analytics data
104
+ */
105
+ function generateAnalytics(currentSales, comparisonSales) {
106
+ // Initialize analytics object with proper documentation
107
+ /**
108
+ * Analytics data structure
109
+ * Note: All currency values are stored in dollars (not cents)
110
+ */
111
+ const analytics = {
112
+ summary: {
113
+ totalRevenue: 0,
114
+ totalOrders: 0,
115
+ averageOrderValue: 0,
116
+ totalTax: 0,
117
+ totalShipping: 0,
118
+ totalDiscounts: 0,
119
+ totalRefunds: 0
120
+ },
121
+ comparison: {
122
+ revenueChange: 0,
123
+ revenueChangePercent: 0,
124
+ orderChange: 0,
125
+ orderChangePercent: 0,
126
+ aovChange: 0,
127
+ aovChangePercent: 0
128
+ },
129
+ typefaces: [],
130
+ bestSellingTypefaces: [],
131
+ worstSellingTypefaces: [],
132
+ designers: [],
133
+ topDesigners: [],
134
+ salesByDay: {},
135
+ salesByWeekday: {
136
+ "Sunday": 0,
137
+ "Monday": 0,
138
+ "Tuesday": 0,
139
+ "Wednesday": 0,
140
+ "Thursday": 0,
141
+ "Friday": 0,
142
+ "Saturday": 0
143
+ }
144
+ };
145
+
146
+ // Skip processing if no sales data
147
+ if (!currentSales || currentSales.length === 0) {
148
+ return analytics;
149
+ }
150
+
151
+ // Process current period sales data
152
+ let totalRevenue = 0;
153
+ let totalTax = 0;
154
+ let totalShipping = 0;
155
+ let totalDiscounts = 0;
156
+ let totalRefunds = 0;
157
+
158
+ // Typeface and designer tracking
159
+ const typefaceMap = new Map();
160
+ const designerMap = new Map();
161
+ const dailySalesMap = new Map();
162
+ const weekdaySalesMap = new Map();
163
+
164
+ // Initialize weekday totals
165
+ for (let i = 0; i < 7; i++) {
166
+ weekdaySalesMap.set(i, 0);
167
+ }
168
+
169
+ // Process each sale
170
+ currentSales.forEach(sale => {
171
+ // Skip shipping provisions
172
+ if (sale.shippingProvision) {
173
+ totalShipping += sale.total || 0;
174
+ return;
175
+ }
176
+
177
+ // Basic metrics
178
+ totalRevenue += sale.total || 0;
179
+
180
+ // Tax totals
181
+ if (sale.taxAmounts && sale.taxAmounts.length) {
182
+ sale.taxAmounts.forEach(tax => {
183
+ totalTax += tax.amount || 0;
184
+ });
185
+ }
186
+
187
+ // Discount totals
188
+ if (sale.discountAmounts && sale.discountAmounts.length) {
189
+ sale.discountAmounts.forEach(discount => {
190
+ totalDiscounts += discount.amount || 0;
191
+ });
192
+ }
193
+
194
+ // Refund totals
195
+ if (sale.refunds && sale.refunds.length) {
196
+ sale.refunds.forEach(refund => {
197
+ totalRefunds += refund.adjustedTotal || 0;
198
+ });
199
+ }
200
+
201
+ // Process by typeface
202
+ const typefaceTitle = getTypefaceTitle(sale);
203
+ if (typefaceTitle) {
204
+ const current = typefaceMap.get(typefaceTitle) || { revenue: 0, orders: 0, author: sale.author };
205
+ current.revenue += sale.total || 0;
206
+ current.orders += 1;
207
+ typefaceMap.set(typefaceTitle, current);
208
+ }
209
+
210
+ // Process by designer
211
+ if (sale.author) {
212
+ const designerId = sale.author._id || sale.author;
213
+ const designerName = sale.author.firstName && sale.author.lastName
214
+ ? `${sale.author.firstName} ${sale.author.lastName}`
215
+ : 'Unknown Designer';
216
+
217
+ const current = designerMap.get(designerId) || { id: designerId, name: designerName, revenue: 0, orders: 0 };
218
+ current.revenue += sale.total || 0;
219
+ current.orders += 1;
220
+ designerMap.set(designerId, current);
221
+ }
222
+
223
+ // Process by day
224
+ const saleDate = new Date(sale.date || sale.created * 1000);
225
+ const dayKey = saleDate.toISOString().split('T')[0];
226
+ const weekdayIndex = saleDate.getUTCDay(); // 0 = Sunday, 6 = Saturday
227
+
228
+ const dailyTotal = dailySalesMap.get(dayKey) || 0;
229
+ dailySalesMap.set(dayKey, dailyTotal + (sale.total || 0));
230
+
231
+ const weekdayTotal = weekdaySalesMap.get(weekdayIndex) || 0;
232
+ weekdaySalesMap.set(weekdayIndex, weekdayTotal + (sale.total || 0));
233
+ });
234
+
235
+ // Process comparison data if available
236
+ let comparisonRevenue = 0;
237
+ let comparisonOrders = 0;
238
+
239
+ if (comparisonSales && comparisonSales.length > 0) {
240
+ comparisonSales.forEach(sale => {
241
+ if (!sale.shippingProvision) {
242
+ comparisonRevenue += sale.total || 0;
243
+ comparisonOrders += 1;
244
+ }
245
+ });
246
+ }
247
+
248
+ // Set summary data
249
+ analytics.summary.totalRevenue = totalRevenue;
250
+ analytics.summary.totalOrders = currentSales.filter(sale => !sale.shippingProvision).length;
251
+ analytics.summary.averageOrderValue = analytics.summary.totalOrders > 0
252
+ ? totalRevenue / analytics.summary.totalOrders
253
+ : 0;
254
+ analytics.summary.totalTax = totalTax;
255
+ analytics.summary.totalShipping = totalShipping;
256
+ analytics.summary.totalDiscounts = totalDiscounts;
257
+ analytics.summary.totalRefunds = totalRefunds;
258
+
259
+ // Set comparison data
260
+ analytics.comparison.revenueChange = totalRevenue - comparisonRevenue;
261
+ analytics.comparison.revenueChangePercent = comparisonRevenue > 0
262
+ ? (totalRevenue - comparisonRevenue) / comparisonRevenue * 100
263
+ : 0;
264
+ analytics.comparison.orderChange = analytics.summary.totalOrders - comparisonOrders;
265
+ analytics.comparison.orderChangePercent = comparisonOrders > 0
266
+ ? (analytics.summary.totalOrders - comparisonOrders) / comparisonOrders * 100
267
+ : 0;
268
+
269
+ const comparisonAOV = comparisonOrders > 0 ? comparisonRevenue / comparisonOrders : 0;
270
+ analytics.comparison.aovChange = analytics.summary.averageOrderValue - comparisonAOV;
271
+ analytics.comparison.aovChangePercent = comparisonAOV > 0
272
+ ? (analytics.summary.averageOrderValue - comparisonAOV) / comparisonAOV * 100
273
+ : 0;
274
+
275
+ // Set typeface data
276
+ analytics.typefaces = Array.from(typefaceMap.entries()).map(([title, data]) => ({
277
+ title,
278
+ revenue: data.revenue,
279
+ orders: data.orders,
280
+ author: data.author
281
+ }));
282
+
283
+ // Best and worst selling typefaces
284
+ analytics.bestSellingTypefaces = [...analytics.typefaces]
285
+ .sort((a, b) => b.revenue - a.revenue)
286
+ .slice(0, 5);
287
+
288
+ analytics.worstSellingTypefaces = [...analytics.typefaces]
289
+ .sort((a, b) => a.revenue - b.revenue)
290
+ .slice(0, 5);
291
+
292
+ // Set designer data
293
+ analytics.designers = Array.from(designerMap.values());
294
+
295
+ // Top designers
296
+ analytics.topDesigners = [...analytics.designers]
297
+ .sort((a, b) => b.revenue - a.revenue)
298
+ .slice(0, 5);
299
+
300
+ // Sales by day
301
+ analytics.salesByDay = Object.fromEntries(dailySalesMap);
302
+
303
+ // Sales by weekday
304
+ analytics.salesByWeekday = {
305
+ "Sunday": weekdaySalesMap.get(0) || 0,
306
+ "Monday": weekdaySalesMap.get(1) || 0,
307
+ "Tuesday": weekdaySalesMap.get(2) || 0,
308
+ "Wednesday": weekdaySalesMap.get(3) || 0,
309
+ "Thursday": weekdaySalesMap.get(4) || 0,
310
+ "Friday": weekdaySalesMap.get(5) || 0,
311
+ "Saturday": weekdaySalesMap.get(6) || 0
312
+ };
313
+
314
+ return analytics;
315
+ }
316
+
317
+ /**
318
+ * Extract typeface title from sale description
319
+ * @param {Object} sale - Sale data object
320
+ * @returns {string} Typeface title
321
+ */
322
+ function getTypefaceTitle(sale) {
323
+ if (!sale || !sale.description) return 'Unknown Typeface';
324
+
325
+ let title = sale.description;
326
+
327
+ // Extract title before parentheses
328
+ const parenIndex = title.indexOf(' (');
329
+ if (parenIndex !== -1) {
330
+ title = title.substring(0, parenIndex);
331
+ }
332
+
333
+ // Extract title before em dash
334
+ const dashIndex = title.indexOf(' —');
335
+ if (dashIndex !== -1) {
336
+ title = title.substring(0, dashIndex);
337
+ }
338
+
339
+ // Extract title before period
340
+ const periodIndex = title.indexOf('.');
341
+ if (periodIndex !== -1) {
342
+ title = title.substring(0, periodIndex);
343
+ }
344
+
345
+ return title.trim() || 'Unknown Typeface';
346
+ }
@@ -0,0 +1,29 @@
1
+ // Type definitions for getBalanceTransactions API handler
2
+ import { NextApiRequest, NextApiResponse } from 'next';
3
+
4
+ export interface GetBalanceTransactionsRequest extends NextApiRequest {
5
+ query: {
6
+ month: string;
7
+ year: string;
8
+ user?: string;
9
+ password?: string;
10
+ };
11
+ }
12
+
13
+ export interface BalanceTransactionsResponse {
14
+ success: boolean;
15
+ data?: any[];
16
+ error?: string;
17
+ message?: string;
18
+ }
19
+
20
+ export const config: {
21
+ maxDuration: number;
22
+ };
23
+
24
+ declare function handler(
25
+ req: GetBalanceTransactionsRequest,
26
+ res: NextApiResponse<BalanceTransactionsResponse>
27
+ ): Promise<void>;
28
+
29
+ export default handler;
@@ -0,0 +1,125 @@
1
+ // API endpoint to fetch Stripe balance transactions for reconciliation
2
+ const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
3
+
4
+ /**
5
+ * Fetches Stripe balance transactions for a specific month
6
+ * @param {Object} req - HTTP request object
7
+ * @param {Object} res - HTTP response object
8
+ */
9
+ export default async function handler(req, res) {
10
+ // Only allow POST requests
11
+ if (req.method !== 'POST') {
12
+ return res.status(405).json({ success: false, message: 'Method not allowed' });
13
+ }
14
+
15
+ try {
16
+ // Get date or dateRange from request body
17
+ const { date, dateRange, admin } = req.body;
18
+
19
+ let timeRange;
20
+
21
+ if (dateRange) {
22
+ // If dateRange is provided, use it directly
23
+ if (!dateRange.start || !dateRange.end) {
24
+ return res.status(400).json({ success: false, message: 'dateRange must include start and end timestamps' });
25
+ }
26
+
27
+ // Convert millisecond timestamps to seconds for Stripe API
28
+ timeRange = {
29
+ gte: Math.floor(dateRange.start / 1000),
30
+ lte: Math.floor(dateRange.end / 1000)
31
+ };
32
+
33
+ console.log('Using date range:', {
34
+ start: new Date(dateRange.start).toISOString(),
35
+ end: new Date(dateRange.end).toISOString()
36
+ });
37
+ } else if (date) {
38
+ // If only date is provided, create a month range
39
+ const targetDate = new Date(date);
40
+ const startOfMonth = new Date(Date.UTC(targetDate.getUTCFullYear(), targetDate.getUTCMonth(), 1, 0, 0, 0));
41
+ const endOfMonth = new Date(Date.UTC(targetDate.getUTCFullYear(), targetDate.getUTCMonth() + 1, 0, 23, 59, 59));
42
+
43
+ timeRange = {
44
+ gte: Math.floor(startOfMonth.getTime() / 1000),
45
+ lte: Math.floor(endOfMonth.getTime() / 1000)
46
+ };
47
+
48
+ console.log('Using month range:', {
49
+ start: startOfMonth.toISOString(),
50
+ end: endOfMonth.toISOString()
51
+ });
52
+ } else {
53
+ return res.status(400).json({ success: false, message: 'Either date or dateRange is required' });
54
+ }
55
+
56
+ // Fetch all balance transactions for the month
57
+ const balanceTransactions = await fetchAllBalanceTransactions(timeRange);
58
+
59
+ // Calculate total balance change for the month (ignoring payouts and pending transactions)
60
+ const totalBalanceChange = balanceTransactions.reduce((total, transaction) => {
61
+ // Skip payout, stripe_fee and pending transactions
62
+ if (transaction.type === 'payout' || transaction.type === 'stripe_fee' || transaction.status === 'pending') {
63
+ return total;
64
+ }
65
+ return total + transaction.amount;
66
+ }, 0);
67
+
68
+ return res.status(200).json({
69
+ success: true,
70
+ data: {
71
+ totalBalanceChange,
72
+ transactions: balanceTransactions
73
+ }
74
+ });
75
+ } catch (error) {
76
+ console.error('Error fetching balance transactions:', error);
77
+ return res.status(500).json({
78
+ success: false,
79
+ message: 'Failed to fetch balance transactions',
80
+ error: error.message
81
+ });
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Fetches all pages of Stripe balance transactions for a given date range
87
+ * @param {Object} timeRange - Stripe-compatible date range
88
+ * @returns {Promise<Array>} Array of all fetched balance transactions
89
+ */
90
+ async function fetchAllBalanceTransactions(timeRange) {
91
+ const fetchOptions = {
92
+ created: timeRange,
93
+ limit: 100,
94
+ };
95
+
96
+ let allTransactions = [];
97
+ let hasMore = true;
98
+ let lastId = null;
99
+ let pageCount = 0;
100
+
101
+ while (hasMore) {
102
+ try {
103
+ const fetchParams = { ...fetchOptions };
104
+ if (lastId) {
105
+ fetchParams.starting_after = lastId;
106
+ }
107
+
108
+ console.log(`Fetching balance transaction page ${pageCount + 1}...`);
109
+ const response = await stripe.balanceTransactions.list(fetchParams);
110
+
111
+ if (response.data.length > 0) {
112
+ allTransactions = allTransactions.concat(response.data);
113
+ lastId = response.data[response.data.length - 1].id;
114
+ hasMore = response.has_more;
115
+ pageCount++;
116
+ } else {
117
+ hasMore = false;
118
+ }
119
+ } catch (error) {
120
+ console.error('Error fetching balance transaction page:', error);
121
+ throw new Error(`Failed to fetch balance transaction page ${pageCount + 1}: ${error.message}`);
122
+ }
123
+ }
124
+ return allTransactions;
125
+ }
@@ -0,0 +1,37 @@
1
+ // Type definitions for getDesignerInfo API handler
2
+ import { NextApiRequest, NextApiResponse } from 'next';
3
+
4
+ export interface Designer {
5
+ _id: string;
6
+ firstName: string;
7
+ lastName: string;
8
+ user: string;
9
+ password?: string;
10
+ }
11
+
12
+ export interface GetDesignerInfoRequest extends NextApiRequest {
13
+ body: {
14
+ user: string;
15
+ password: string;
16
+ };
17
+ }
18
+
19
+ export interface DesignerInfoResponse {
20
+ success: boolean;
21
+ designer?: Designer;
22
+ designers?: Designer[];
23
+ admin?: boolean;
24
+ error?: string;
25
+ message?: string;
26
+ }
27
+
28
+ export const config: {
29
+ maxDuration: number;
30
+ };
31
+
32
+ declare function handler(
33
+ req: GetDesignerInfoRequest,
34
+ res: NextApiResponse<DesignerInfoResponse>
35
+ ): Promise<void>;
36
+
37
+ export default handler;
@@ -0,0 +1,98 @@
1
+ // API endpoint for retrieving designer information and authentication
2
+
3
+ // Configure extended timeout for Vercel serverless function
4
+ export const config = { maxDuration: 300 };
5
+
6
+ // Import required dependencies
7
+ const { createClient } = require('@sanity/client');
8
+ const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
9
+
10
+ /**
11
+ * Sanity client configuration for CMS interactions
12
+ * Uses environment variables for secure configuration
13
+ */
14
+ const client = createClient({
15
+ projectId: process.env.SANITY_STUDIO_PROJECT_ID,
16
+ dataset: process.env.SANITY_STUDIO_DATASET,
17
+ apiVersion: process.env.SANITY_STUDIO_API_VERSION,
18
+ token: process.env.SANITY_STUDIO_TOKEN,
19
+ useCdn: false, // Ensures fresh data by bypassing CDN
20
+ });
21
+
22
+ /**
23
+ * API route handler for designer information
24
+ * Handles authentication and retrieval of designer data
25
+ * @param {Object} req - HTTP request object containing user credentials
26
+ * @param {Object} res - HTTP response object
27
+ */
28
+ export default async function handler(req, res) {
29
+ const { method } = req;
30
+
31
+ // Check if sales portal is enabled
32
+ if (process.env.SALES_PORTAL_ENABLED === 'false') {
33
+ return res.status(503).json({
34
+ success: false,
35
+ message: 'Sales portal is currently disabled',
36
+ disabled: true
37
+ });
38
+ }
39
+
40
+ // Only handle POST requests
41
+ if (method === 'POST') {
42
+ const { user, password } = req.body;
43
+
44
+ // Authenticate designer using Sanity
45
+ const designer = await client.fetch(
46
+ `*[_type == "account" && email == '${user}' && password == '${password}' && isDesigner][0]`
47
+ );
48
+
49
+ if (designer) {
50
+ // Handle admin user case
51
+ if (designer.isAdmin) {
52
+ // Fetch all designers for admin view
53
+ const designers = await client.fetch(`*[_type == "account" && isDesigner]`);
54
+
55
+ if (designers) {
56
+ res.status(200).json({
57
+ success: true,
58
+ admin: true,
59
+ data: designers.map(designer => ({
60
+ _id: designer._id,
61
+ firstName: designer?.firstName || (designer?.name || 'Nameless'),
62
+ lastName: designer?.lastName || '',
63
+ user: designer.email,
64
+ password: designer.password,
65
+ })).sort((a, b) =>
66
+ a?.firstName
67
+ ? a.firstName.localeCompare(b.firstName)
68
+ : a.name.localeCompare(b.name)
69
+ ),
70
+ });
71
+ } else {
72
+ res.status(200).json({
73
+ success: false,
74
+ message: 'No designers... thats weird.'
75
+ });
76
+ }
77
+ } else {
78
+ // Handle regular designer case
79
+ res.status(200).json({
80
+ success: true,
81
+ data: [{
82
+ _id: designer._id,
83
+ firstName: designer?.firstName || (designer?.name || 'Nameless'),
84
+ lastName: designer?.lastName || '',
85
+ user: designer.email,
86
+ password: designer.password
87
+ }],
88
+ });
89
+ }
90
+ } else {
91
+ // Handle authentication failure
92
+ res.status(200).json({
93
+ success: false,
94
+ message: 'Incorrect email or password.'
95
+ });
96
+ }
97
+ }
98
+ }
@@ -0,0 +1,28 @@
1
+ // Type definitions for getDesigners API handler
2
+ import { NextApiRequest, NextApiResponse } from 'next';
3
+ import { Designer } from './getDesignerInfo';
4
+
5
+ export interface GetDesignersRequest extends NextApiRequest {
6
+ body: {
7
+ user: string;
8
+ password: string;
9
+ };
10
+ }
11
+
12
+ export interface DesignersResponse {
13
+ success: boolean;
14
+ designers?: Designer[];
15
+ error?: string;
16
+ message?: string;
17
+ }
18
+
19
+ export const config: {
20
+ maxDuration: number;
21
+ };
22
+
23
+ declare function handler(
24
+ req: GetDesignersRequest,
25
+ res: NextApiResponse<DesignersResponse>
26
+ ): Promise<void>;
27
+
28
+ export default handler;