@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,307 @@
1
+ // Sales chart component displaying various sales metrics and data visualization
2
+ import React from 'react';
3
+ import { FormGroup, FormControlLabel, Checkbox } from '@mui/material';
4
+ import { ResponsiveChartContainer } from '@mui/x-charts/ResponsiveChartContainer';
5
+ import { BarPlot } from '@mui/x-charts/BarChart';
6
+ import { LinePlot, MarkPlot } from '@mui/x-charts/LineChart';
7
+ import { ChartsXAxis } from '@mui/x-charts/ChartsXAxis';
8
+ import { ChartsYAxis } from '@mui/x-charts/ChartsYAxis';
9
+ import { ChartsAxisHighlight } from '@mui/x-charts/ChartsAxisHighlight';
10
+ import { ChartsTooltip } from '@mui/x-charts/ChartsTooltip';
11
+ import styles from '../styles/sales-portal.module.scss';
12
+
13
+ const currencyFormatterCentless = new Intl.NumberFormat('en-US', {
14
+ style: 'currency',
15
+ currency: 'USD',
16
+ minimumFractionDigits: 0,
17
+ maximumFractionDigits: 0,
18
+ }).format;
19
+
20
+ // CurrentDate in UTC
21
+ const currentDate = new Date();
22
+
23
+ /**
24
+ * Line mark styles for chart tooltips
25
+ * @param {string} color - Color for the line mark
26
+ * @returns {Object} Styles object for line marks
27
+ */
28
+ function lineMarkStyles(color) {
29
+ return {
30
+ background: color,
31
+ position: 'relative',
32
+ '&::before': {
33
+ content: '""',
34
+ background: 'var(--white, white)',
35
+ position: 'absolute',
36
+ width: 'calc(100% - 4px)',
37
+ height: 'calc(100% - 4px)',
38
+ top: '2px',
39
+ left: '2px',
40
+ borderRadius: '50%',
41
+ zIndex: "2",
42
+ },
43
+ '&::after': {
44
+ content: '""',
45
+ position: 'absolute',
46
+ top: 'calc(50% - 1px)',
47
+ left: '-2px',
48
+ width: 'calc(100% + 4px)',
49
+ height: '2px',
50
+ background: 'inherit',
51
+ }
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Sales chart component for visualizing sales data
57
+ */
58
+ export default function SalesChart({
59
+ sales,
60
+ chartState,
61
+ seriesData,
62
+ displayLosses,
63
+ setDisplayLosses,
64
+ date,
65
+ loading
66
+ }) {
67
+ return (
68
+ <div data-loading={loading}>
69
+
70
+ {/* SVG Patterns */}
71
+ <svg style={{ height: "0", display: "block" }}>
72
+ <pattern id="discountHatch" width="4" height="4" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse">
73
+ <line x1="0" y1="0" x2="0" y2="4" style={{ stroke: "black", strokeWidth: "2" }} />
74
+ </pattern>
75
+ <pattern id="discountFirstOrderHatch" width="4" height="4" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse">
76
+ <rect x="0" y="0" width="4" height="4" fill="rgba(var(--blueRGB, 0,0,255), .25)" />
77
+ <line x1="0" y1="0" x2="0" y2="4" style={{ stroke: "black", strokeWidth: "2" }} />
78
+ </pattern>
79
+ <pattern id="firstOrderHatch" width="4" height="4" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse">
80
+ <rect x="0" y="0" width="4" height="4" fill="rgba(var(--blueRGB, 0,0,255), .25)" />
81
+ </pattern>
82
+ </svg>
83
+
84
+ {/* Display Losses Checkbox */}
85
+ <div className={styles.displayLossContainer}>
86
+ <FormGroup>
87
+ <FormControlLabel
88
+ control={<Checkbox onChange={(e) => setDisplayLosses(e.target.checked)} size="small" />}
89
+ label="Show potential losses"
90
+ sx={{
91
+ mr: 0,
92
+ }}
93
+ componentsProps={{
94
+ typography: {
95
+ variant: 'body2',
96
+ fontSize: "12px",
97
+ }
98
+ }}
99
+ />
100
+ </FormGroup>
101
+ </div>
102
+
103
+ {/* Sales Chart */}
104
+ <ResponsiveChartContainer
105
+ xAxis={[{
106
+ data: (() => {
107
+ return chartState.xAxis;
108
+ })(),
109
+ valueFormatter: (v, context) => {
110
+ if (context?.location === "tick") return `${v}`;
111
+
112
+ // Format as "UTC: Month Day"
113
+ return `UTC: ${new Date(date).toLocaleString("en-US", { timeZone: 'UTC', month: "long" })} ${v}`;
114
+ },
115
+ min: chartState.xAxis[0],
116
+ max: (() => {
117
+ const lastDayOfMonth = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, 0)).getUTCDate();
118
+ const currentUTCDate = new Date(Date.UTC(currentDate.getUTCFullYear(), currentDate.getUTCMonth(), currentDate.getUTCDate()));
119
+
120
+ if (date.getUTCMonth() === currentUTCDate.getUTCMonth() && date.getUTCFullYear() === currentUTCDate.getUTCFullYear()) {
121
+ return Math.min(currentUTCDate.getUTCDate(), lastDayOfMonth);
122
+ }
123
+ return date.getUTCMonth() < currentUTCDate.getUTCMonth() || date.getUTCFullYear() < currentUTCDate.getUTCFullYear()
124
+ ? lastDayOfMonth
125
+ : 1;
126
+ })(),
127
+ scaleType: 'band',
128
+ id: 'x-axis-id',
129
+ tickLabelPlacement: 'middle',
130
+ }]}
131
+ yAxis={[{
132
+ stacked: true,
133
+ scaleType: 'linear',
134
+ valueFormatter: currencyFormatterCentless,
135
+ id: 'y-axis-id',
136
+ }]}
137
+ series={seriesData.length ? seriesData.map(series => ({
138
+ ...series,
139
+ data: series.data.slice(0, (() => {
140
+ // If this is the current month, only show data up to the current day (UTC)
141
+ const lastDayOfMonth = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, 0)).getUTCDate();
142
+ const currentUTCDate = new Date(Date.UTC(currentDate.getUTCFullYear(), currentDate.getUTCMonth(), currentDate.getUTCDate()));
143
+ const endDate = (date.getUTCMonth() === currentUTCDate.getUTCMonth() && date.getUTCFullYear() === currentUTCDate.getUTCFullYear())
144
+ ? Math.min(currentUTCDate.getUTCDate(), lastDayOfMonth)
145
+ : lastDayOfMonth;
146
+ return endDate;
147
+ })())
148
+ })) : []}
149
+ height={!!sales.length ? 300 : 75}
150
+ className={'chart'}
151
+ skipAnimation
152
+ disableAxisListener={false}
153
+ >
154
+ <BarPlot
155
+ borderRadius={4}
156
+ sx={{
157
+ '& .MuiBarElement-root': {
158
+ transition: 'all 0.2s ease-in-out',
159
+ }
160
+ }}
161
+ />
162
+ <LinePlot
163
+ sx={{
164
+ '& .MuiLineElement-root': {
165
+ transition: 'all 0.2s ease-in-out',
166
+ }
167
+ }}
168
+ />
169
+ <ChartsYAxis axisId="y-axis-id" />
170
+ <ChartsXAxis axisId="x-axis-id" />
171
+ <ChartsAxisHighlight x='line' />
172
+ <ChartsTooltip
173
+ slotProps={{
174
+ popper: {
175
+ sx: {
176
+ // The wrap
177
+ ["& .MuiChartsTooltip-root"]: {
178
+ boxShadow: "0 0 10px rgba(var(--blackRGB, 0,0,0), 0.1)",
179
+ padding: "10px",
180
+ },
181
+ ["& .MuiChartsTooltip-paper::after"]: {
182
+ content: '"non-applicable fields are hidden"',
183
+ fontSize: "10px",
184
+ maxWidth: "100%",
185
+ textAlign: "center",
186
+ background: "rgba(var(--blackRGB, 0,0,0), 0.1)",
187
+ display: "block",
188
+ color: "rgba(var(--blackRGB, 0,0,0), 0.5)",
189
+ padding: "5px",
190
+ },
191
+
192
+ // The title
193
+ ["& .MuiChartsTooltip-paper thead"]: {
194
+ background: "var(--black, black)",
195
+ },
196
+ ["& .MuiChartsTooltip-paper thead *"]: {
197
+ color: "var(--white, white)",
198
+ textAlign: "center",
199
+ fontWeight: "900",
200
+ fontVariationSettings: '"wght" 900, "wdth" 500',
201
+ fontSize: "14px",
202
+ letterSpacing: "0.01em",
203
+ textTransform: "uppercase",
204
+ },
205
+ // The body
206
+ ["& .MuiChartsTooltip-paper tbody tr:nth-of-type(odd)"]: {
207
+ background: "rgba(var(--blackRGB, 0,0,0), 0.1)",
208
+ padding: "10px",
209
+ },
210
+ ["& .MuiChartsTooltip-paper tbody tr * "]: {
211
+ fontSize: "14px",
212
+ letterSpacing: "0.01em",
213
+ textTransform: "uppercase",
214
+ lineHeight: "1",
215
+ padding: "5px",
216
+ },
217
+ // The mark
218
+ ["& .MuiChartsTooltip-mark"]: {
219
+ border: "none",
220
+ outline: "none",
221
+ boxShadow: "none",
222
+ marginLeft: "5px",
223
+ mixBlendMode: "multiply",
224
+ },
225
+ // Mark styling for discounts
226
+ ["& .MuiChartsTooltip-row:first-of-type .MuiChartsTooltip-mark"]: {
227
+ background:
228
+ (
229
+ (chartState.discountData?.length > 0 && chartState.discountData.some(value => value !== 0)) &&
230
+ (chartState.discountFirstOrderData?.length > 0 && chartState.discountFirstOrderData.some(value => value !== 0))
231
+ ) ?
232
+ 'repeating-linear-gradient( -45deg, transparent, transparent 2px, black 0px, black 3px, transparent 0px, transparent 4px), rgba(0,0,0,0.05)'
233
+ : '',
234
+ },
235
+ ["& .MuiChartsTooltip-row:nth-of-type(2) .MuiChartsTooltip-mark"]: {
236
+ background:
237
+ (
238
+ (chartState.discountData?.length > 0 && chartState.discountData.some(value => value !== 0)) &&
239
+ (chartState.discountFirstOrderData?.length > 0 && chartState.discountFirstOrderData.some(value => value !== 0))
240
+ ) ?
241
+ 'repeating-linear-gradient( -45deg, transparent, transparent 2px, black 0px, black 3px, transparent 0px, transparent 4px), rgba(var(--blueRGB, 0,0,255), .25)'
242
+ : '',
243
+ },
244
+ ["& .MuiChartsTooltip-row:first-of-type .MuiChartsTooltip-mark"]: {
245
+ background:
246
+ (
247
+ (chartState.discountData?.length > 0 && chartState.discountData.some(value => value !== 0)) &&
248
+ !(!!chartState.discountFirstOrderData?.length > 0 && !!chartState.discountFirstOrderData.some(value => value !== 0))
249
+ ) ?
250
+ 'repeating-linear-gradient( -45deg, transparent, transparent 2px, black 0px, black 3px, transparent 0px, transparent 4px), rgba(0,0,0,0.05)'
251
+ : (
252
+ !(chartState.discountData?.length > 0 && chartState.discountData.some(value => value !== 0)) &&
253
+ (!!chartState.discountFirstOrderData?.length > 0 && !!chartState.discountFirstOrderData.some(value => value !== 0))
254
+ ) ?
255
+ 'repeating-linear-gradient( -45deg, transparent, transparent 2px, black 0px, black 3px, transparent 0px, transparent 4px), rgba(var(--blueRGB, 0,0,255), .25)'
256
+ : '',
257
+ },
258
+ // Mark styling for lines
259
+ [`& .MuiChartsTooltip-row:nth-last-of-type(${
260
+ displayLosses && !!(chartState.taxData?.length > 0 && chartState.taxData.some(value => value !== 0)) && !!(chartState.shippingData?.length > 0 && chartState.shippingData.some(value => value !== 0)) ?
261
+ 5
262
+ :
263
+ (displayLosses || !!(chartState.taxData?.length > 0 && chartState.taxData.some(value => value !== 0))) && !!(chartState.shippingData?.length > 0 && chartState.shippingData.some(value => value !== 0)) ?
264
+ 4
265
+ :
266
+ !!(chartState.shippingData?.length > 0 && chartState.shippingData.some(value => value !== 0)) ?
267
+ 3
268
+ :
269
+ -1
270
+ }) .MuiChartsTooltip-mark`]: {
271
+ ...lineMarkStyles('var(--orange, orange)')
272
+ },
273
+ [`& .MuiChartsTooltip-row:nth-last-of-type(${
274
+ displayLosses && !!(chartState.taxData?.length > 0 && chartState.taxData.some(value => value !== 0)) ?
275
+ 4
276
+ :
277
+ !!(chartState.taxData?.length > 0 && chartState.taxData.some(value => value !== 0)) ?
278
+ 3
279
+ :
280
+ -1
281
+ }) .MuiChartsTooltip-mark`]: {
282
+ ...lineMarkStyles('var(--red, red)')
283
+ },
284
+ [`& .MuiChartsTooltip-row:nth-last-of-type(${
285
+ displayLosses ? 3 : 2
286
+ }) .MuiChartsTooltip-mark`]: {
287
+ ...lineMarkStyles('var(--green, green)')
288
+ },
289
+ [`& .MuiChartsTooltip-row:nth-last-of-type(${
290
+ displayLosses ? 2 : 1
291
+ }) .MuiChartsTooltip-mark`]: {
292
+ ...lineMarkStyles('var(--blue, blue)')
293
+ },
294
+ [`& .MuiChartsTooltip-row:nth-last-of-type(${
295
+ displayLosses ? 1 : -1
296
+ }) .MuiChartsTooltip-mark`]: {
297
+ ...lineMarkStyles('var(--black, black)')
298
+ },
299
+ },
300
+ },
301
+ }}
302
+ />
303
+ <MarkPlot />
304
+ </ResponsiveChartContainer>
305
+ </div>
306
+ );
307
+ }
@@ -0,0 +1,147 @@
1
+ // Complete sales portal page component with login and dashboard views
2
+ import React, { useState } from 'react';
3
+ import { Grid, Typography } from '@mui/material';
4
+ import { ThemeProvider } from '@mui/system';
5
+ import { salesTheme } from '../styles/sales-portal.theme.js';
6
+ import Sales from './Sales.js';
7
+ import LoginForm from './LoginForm.js';
8
+
9
+ /**
10
+ * SalesPortalPage - Complete sales portal page with authentication and dashboard
11
+ *
12
+ * This component provides the full sales portal experience:
13
+ * - Login form for designer authentication
14
+ * - Post-login dashboard with admin and designer views
15
+ * - Automatic theme wrapping
16
+ *
17
+ * @param {Object} props Component props
18
+ * @param {Function} props.renderLoader Optional render prop for custom loader (receives { fill, position, height })
19
+ * @param {Object} props.texts Optional text customization for login form
20
+ * @param {boolean} props.includeTheme Whether to wrap with salesTheme (default: true)
21
+ * @param {Object} props.pageStyles Optional custom styles for the page container
22
+ * @param {Object} props.dashboardStyles Optional custom styles for the dashboard container
23
+ */
24
+ export default function SalesPortalPage({
25
+ renderLoader,
26
+ texts = {},
27
+ includeTheme = true,
28
+ pageStyles = {},
29
+ dashboardStyles = {}
30
+ }) {
31
+ // Authentication state
32
+ const [designers, setDesigners] = useState(null);
33
+ const [admin, setAdmin] = useState(false);
34
+ const [credentials, setCredentials] = useState({ user: '', password: '' });
35
+
36
+ // Date state for syncing across Sales components
37
+ const [month, setMonth] = useState(new Date().getUTCMonth());
38
+ const [year, setYear] = useState(new Date().getUTCFullYear());
39
+
40
+ const handleLoginSuccess = ({ designers, admin, user, password }) => {
41
+ setDesigners(designers);
42
+ setAdmin(admin);
43
+ setCredentials({ user, password });
44
+ };
45
+
46
+ const updateDate = (newDate) => {
47
+ setMonth(new Date(newDate).getUTCMonth());
48
+ setYear(new Date(newDate).getUTCFullYear());
49
+ };
50
+
51
+ // Default page title text
52
+ const {
53
+ dashboardTitle = 'Monthly Sales',
54
+ dashboardSubtitle = 'by designer'
55
+ } = texts;
56
+
57
+ const content = (
58
+ <>
59
+ {designers ? (
60
+ <>
61
+ {/* Admin View - Shows foundry-wide data */}
62
+ {admin && (
63
+ <Grid container columnSpacing={10} sx={{
64
+ position: 'relative',
65
+ display: 'flex',
66
+ alignContent: 'flex-start',
67
+ background: 'rgba(0,0,0,.06)',
68
+ ...dashboardStyles
69
+ }}>
70
+ <Grid item xs={12}>
71
+ <Sales
72
+ key={'sales-admin'}
73
+ month={month}
74
+ year={year}
75
+ updateDate={updateDate}
76
+ designer={{
77
+ admin: true,
78
+ firstName: '',
79
+ lastName: 'The Foundry',
80
+ user: credentials.user,
81
+ password: credentials.password
82
+ }}
83
+ categories={true}
84
+ admin={true}
85
+ />
86
+ </Grid>
87
+ </Grid>
88
+ )}
89
+
90
+ {/* Designer Sales View */}
91
+ <Grid container columnSpacing={10} sx={{
92
+ pb: 20,
93
+ position: 'relative',
94
+ display: 'flex',
95
+ alignContent: 'flex-start',
96
+ minHeight: '80vh',
97
+ ...pageStyles
98
+ }}>
99
+ <Grid
100
+ id='titleContainer'
101
+ item
102
+ xs={12}
103
+ sx={{
104
+ pt: 10,
105
+ mx: { xs: 'var(--marginXMobile)', md: 'var(--marginX)' },
106
+ }}
107
+ >
108
+ <Typography variant='h1'>{dashboardTitle}</Typography>
109
+ {admin && <Typography variant='h5'>{dashboardSubtitle}</Typography>}
110
+ </Grid>
111
+
112
+ {designers.map((designer, i) => (
113
+ <Sales
114
+ key={`sales-${i}`}
115
+ designer={{
116
+ ...designer,
117
+ user: designer.user || credentials.user,
118
+ password: designer.password || credentials.password
119
+ }}
120
+ month={month}
121
+ year={year}
122
+ updateDate={updateDate}
123
+ />
124
+ ))}
125
+ </Grid>
126
+ </>
127
+ ) : (
128
+ <LoginForm
129
+ onLoginSuccess={handleLoginSuccess}
130
+ renderLoader={renderLoader}
131
+ texts={texts}
132
+ />
133
+ )}
134
+ </>
135
+ );
136
+
137
+ // Optionally wrap with theme provider
138
+ if (includeTheme) {
139
+ return (
140
+ <ThemeProvider theme={salesTheme}>
141
+ {content}
142
+ </ThemeProvider>
143
+ );
144
+ }
145
+
146
+ return content;
147
+ }