@wix/ditto-codegen-public 1.0.20 → 1.0.21

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.
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "wix-astro-blank",
3
+ "type": "module",
4
+ "version": "0.0.1",
5
+ "scripts": {
6
+ "astro": "astro",
7
+ "dev": "wix dev",
8
+ "build": "wix build",
9
+ "wix": "wix",
10
+ "preview": "wix preview",
11
+ "release": "wix release",
12
+ "generate": "wix generate",
13
+ "env": "wix env"
14
+ },
15
+ "dependencies": {
16
+ "@wix/astro": "^2.2.0",
17
+ "@wix/ecom": "^1.0.1392",
18
+ "@wix/data": "^1.0.291",
19
+ "@wix/design-system": "^1.219.0",
20
+ "@wix/wix-ui-icons-common": "^3.92.0",
21
+ "@wix/dashboard": "^1.3.36",
22
+ "@wix/essentials": "^0.1.23",
23
+ "@wix/sdk": "^1.15.23",
24
+ "astro": "^5.8.0",
25
+ "typescript": "^5.8.3"
26
+ },
27
+ "devDependencies": {
28
+ "@astrojs/cloudflare": "^12.5.3",
29
+ "@astrojs/react": "^4.3.0",
30
+ "@types/react": "^18.3.1",
31
+ "@types/react-dom": "^18.3.1",
32
+ "@wix/cli": "^1.1.92",
33
+ "react": "18.3.1",
34
+ "react-dom": "18.3.1"
35
+ }
36
+ }
@@ -0,0 +1,551 @@
1
+ import React, { useState, useEffect, type FC } from 'react';
2
+ import { dashboard } from '@wix/dashboard';
3
+ import { items } from '@wix/data';
4
+ import {
5
+ Button,
6
+ Page,
7
+ WixDesignSystemProvider,
8
+ Table,
9
+ TableActionCell,
10
+ TableToolbar,
11
+ Modal,
12
+ CustomModalLayout,
13
+ FormField,
14
+ Input,
15
+ ToggleSwitch,
16
+ Layout,
17
+ Cell,
18
+ Badge,
19
+ Box,
20
+ Text,
21
+ EmptyState,
22
+ Loader,
23
+ Card,
24
+ Heading,
25
+ } from '@wix/design-system';
26
+ import '@wix/design-system/styles.global.css';
27
+ import * as Icons from '@wix/wix-ui-icons-common';
28
+
29
+ export type WixDataItem = items.WixDataItem;
30
+
31
+ // Type definitions based on the CMS collections
32
+ interface SurveyQuestion extends WixDataItem {
33
+ questionText: string;
34
+ maxRating: number;
35
+ isActive: boolean;
36
+ }
37
+
38
+ interface SurveyResponse extends WixDataItem {
39
+ questionId: string;
40
+ rating: number;
41
+ submittedAt: Date;
42
+ }
43
+
44
+ interface QuestionStats {
45
+ totalResponses: number;
46
+ averageRating: number;
47
+ ratingDistribution: { [rating: number]: number };
48
+ }
49
+
50
+ /**
51
+ * Creates a new survey question
52
+ */
53
+ async function createQuestion(questionData: Omit<SurveyQuestion, '_id' | '_createdDate' | '_updatedDate' | '_owner'>): Promise<SurveyQuestion> {
54
+ try {
55
+ const result = await items.insert('survey-questions', questionData);
56
+ return result as SurveyQuestion;
57
+ } catch (error) {
58
+ console.error('Error creating survey question:', error);
59
+ throw new Error(
60
+ error instanceof Error ? error.message : 'Failed to create survey question'
61
+ );
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Retrieves all survey questions
67
+ */
68
+ async function getAllQuestions(): Promise<items.WixDataResult<SurveyQuestion>> {
69
+ try {
70
+ const result = await items.query('survey-questions')
71
+ .descending('_createdDate')
72
+ .find();
73
+ return result as items.WixDataResult<SurveyQuestion>;
74
+ } catch (error) {
75
+ console.error('Error fetching survey questions:', error);
76
+ throw new Error(
77
+ error instanceof Error ? error.message : 'Failed to fetch survey questions'
78
+ );
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Updates an existing survey question
84
+ */
85
+ async function updateQuestion(questionData: SurveyQuestion): Promise<SurveyQuestion> {
86
+ try {
87
+ if (!questionData._id) {
88
+ throw new Error('Question ID is required for update');
89
+ }
90
+ const result = await items.update('survey-questions', questionData);
91
+ return result as SurveyQuestion;
92
+ } catch (error) {
93
+ console.error('Error updating survey question:', error);
94
+ throw new Error(
95
+ error instanceof Error ? error.message : 'Failed to update survey question'
96
+ );
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Deletes a survey question by ID
102
+ */
103
+ async function deleteQuestion(questionId: string): Promise<SurveyQuestion> {
104
+ try {
105
+ if (!questionId) {
106
+ throw new Error('Question ID is required for deletion');
107
+ }
108
+ const result = await items.remove('survey-questions', questionId);
109
+ return result as SurveyQuestion;
110
+ } catch (error) {
111
+ console.error('Error deleting survey question:', error);
112
+ throw new Error(
113
+ error instanceof Error ? error.message : 'Failed to delete survey question'
114
+ );
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Gets statistics for a specific question
120
+ */
121
+ async function getQuestionStats(questionId: string): Promise<QuestionStats> {
122
+ try {
123
+ const responses = await items.query('survey-responses')
124
+ .eq('questionId', questionId)
125
+ .find();
126
+
127
+ const responseItems = responses.items as SurveyResponse[];
128
+ const totalResponses = responseItems.length;
129
+
130
+ if (totalResponses === 0) {
131
+ return {
132
+ totalResponses: 0,
133
+ averageRating: 0,
134
+ ratingDistribution: {}
135
+ };
136
+ }
137
+
138
+ const ratings = responseItems.map(r => r.rating);
139
+ const averageRating = ratings.reduce((sum, rating) => sum + rating, 0) / totalResponses;
140
+
141
+ const ratingDistribution: { [rating: number]: number } = {};
142
+ ratings.forEach(rating => {
143
+ ratingDistribution[rating] = (ratingDistribution[rating] || 0) + 1;
144
+ });
145
+
146
+ return {
147
+ totalResponses,
148
+ averageRating: Math.round(averageRating * 100) / 100,
149
+ ratingDistribution
150
+ };
151
+ } catch (error) {
152
+ console.error('Error fetching question stats:', error);
153
+ return {
154
+ totalResponses: 0,
155
+ averageRating: 0,
156
+ ratingDistribution: {}
157
+ };
158
+ }
159
+ }
160
+
161
+ const SurveyManager: FC = () => {
162
+ const [questions, setQuestions] = useState<SurveyQuestion[]>([]);
163
+ const [questionStats, setQuestionStats] = useState<{ [questionId: string]: QuestionStats }>({});
164
+ const [loading, setLoading] = useState(true);
165
+ const [isModalOpen, setIsModalOpen] = useState(false);
166
+ const [editingQuestion, setEditingQuestion] = useState<SurveyQuestion | null>(null);
167
+ const [formData, setFormData] = useState({
168
+ questionText: '',
169
+ maxRating: 5,
170
+ isActive: true
171
+ });
172
+
173
+ // Load questions and their stats
174
+ const loadQuestions = async () => {
175
+ try {
176
+ setLoading(true);
177
+ const result = await getAllQuestions();
178
+ setQuestions(result.items);
179
+
180
+ // Load stats for each question
181
+ const stats: { [questionId: string]: QuestionStats } = {};
182
+ for (const question of result.items) {
183
+ if (question._id) {
184
+ stats[question._id] = await getQuestionStats(question._id);
185
+ }
186
+ }
187
+ setQuestionStats(stats);
188
+ } catch (error) {
189
+ dashboard.showToast({
190
+ message: 'Failed to load survey questions',
191
+ type: 'error'
192
+ });
193
+ } finally {
194
+ setLoading(false);
195
+ }
196
+ };
197
+
198
+ useEffect(() => {
199
+ loadQuestions();
200
+ }, []);
201
+
202
+ const openModal = (question?: SurveyQuestion) => {
203
+ setEditingQuestion(question || null);
204
+
205
+ if (question) {
206
+ setFormData({
207
+ questionText: question.questionText,
208
+ maxRating: question.maxRating,
209
+ isActive: question.isActive
210
+ });
211
+ } else {
212
+ setFormData({
213
+ questionText: '',
214
+ maxRating: 5,
215
+ isActive: true
216
+ });
217
+ }
218
+
219
+ setIsModalOpen(true);
220
+ };
221
+
222
+ const closeModal = () => {
223
+ setIsModalOpen(false);
224
+ setEditingQuestion(null);
225
+ setFormData({
226
+ questionText: '',
227
+ maxRating: 5,
228
+ isActive: true
229
+ });
230
+ };
231
+
232
+ const handleSave = async () => {
233
+ try {
234
+ if (!formData.questionText.trim()) {
235
+ dashboard.showToast({
236
+ message: 'Question text is required',
237
+ type: 'error'
238
+ });
239
+ return;
240
+ }
241
+
242
+ if (editingQuestion) {
243
+ // Update existing question
244
+ const updatedQuestion: SurveyQuestion = {
245
+ ...editingQuestion,
246
+ questionText: formData.questionText,
247
+ maxRating: formData.maxRating,
248
+ isActive: formData.isActive
249
+ };
250
+ await updateQuestion(updatedQuestion);
251
+ dashboard.showToast({
252
+ message: 'Question updated successfully',
253
+ type: 'success'
254
+ });
255
+ } else {
256
+ // Create new question
257
+ await createQuestion({
258
+ questionText: formData.questionText,
259
+ maxRating: formData.maxRating,
260
+ isActive: formData.isActive
261
+ });
262
+ dashboard.showToast({
263
+ message: 'Question created successfully',
264
+ type: 'success'
265
+ });
266
+ }
267
+
268
+ closeModal();
269
+ loadQuestions();
270
+ } catch (error) {
271
+ dashboard.showToast({
272
+ message: editingQuestion ? 'Failed to update question' : 'Failed to create question',
273
+ type: 'error'
274
+ });
275
+ }
276
+ };
277
+
278
+ const handleDelete = async (questionId: string, questionText: string) => {
279
+ try {
280
+ await deleteQuestion(questionId);
281
+ dashboard.showToast({
282
+ message: `"${questionText}" deleted successfully`,
283
+ type: 'success'
284
+ });
285
+ loadQuestions();
286
+ } catch (error) {
287
+ dashboard.showToast({
288
+ message: 'Failed to delete question',
289
+ type: 'error'
290
+ });
291
+ }
292
+ };
293
+
294
+ const toggleQuestionStatus = async (question: SurveyQuestion) => {
295
+ try {
296
+ const updatedQuestion: SurveyQuestion = {
297
+ ...question,
298
+ isActive: !question.isActive
299
+ };
300
+ await updateQuestion(updatedQuestion);
301
+ dashboard.showToast({
302
+ message: `Question ${updatedQuestion.isActive ? 'activated' : 'deactivated'}`,
303
+ type: 'success'
304
+ });
305
+ loadQuestions();
306
+ } catch (error) {
307
+ dashboard.showToast({
308
+ message: 'Failed to update question status',
309
+ type: 'error'
310
+ });
311
+ }
312
+ };
313
+
314
+ const columns = [
315
+ {
316
+ title: 'Question',
317
+ render: (question: SurveyQuestion) => (
318
+ <Box direction="vertical" gap="3px">
319
+ <Text size="medium" weight="normal">
320
+ {question.questionText}
321
+ </Text>
322
+ <Box gap="6px">
323
+ <Badge
324
+ skin={question.isActive ? 'success' : 'neutral'}
325
+ size="small"
326
+ >
327
+ {question.isActive ? 'Active' : 'Inactive'}
328
+ </Badge>
329
+ <Text size="tiny" secondary>
330
+ Max Rating: {question.maxRating}
331
+ </Text>
332
+ </Box>
333
+ </Box>
334
+ ),
335
+ width: 'auto'
336
+ },
337
+ {
338
+ title: 'Responses',
339
+ render: (question: SurveyQuestion) => {
340
+ const stats = question._id ? questionStats[question._id] : null;
341
+ return (
342
+ <Box direction="vertical" gap="3px">
343
+ <Text size="medium" weight="bold">
344
+ {stats?.totalResponses || 0}
345
+ </Text>
346
+ <Text size="tiny" secondary>
347
+ Total responses
348
+ </Text>
349
+ </Box>
350
+ );
351
+ },
352
+ width: '120px'
353
+ },
354
+ {
355
+ title: 'Average Rating',
356
+ render: (question: SurveyQuestion) => {
357
+ const stats = question._id ? questionStats[question._id] : null;
358
+ return (
359
+ <Box direction="vertical" gap="3px">
360
+ <Text size="medium" weight="bold">
361
+ {stats?.averageRating || 0}
362
+ </Text>
363
+ <Text size="tiny" secondary>
364
+ out of {question.maxRating}
365
+ </Text>
366
+ </Box>
367
+ );
368
+ },
369
+ width: '120px'
370
+ },
371
+ {
372
+ title: 'Created',
373
+ render: (question: SurveyQuestion) => (
374
+ <Text size="small">
375
+ {question._createdDate ? new Date(question._createdDate).toLocaleDateString() : ''}
376
+ </Text>
377
+ ),
378
+ width: '100px'
379
+ },
380
+ {
381
+ title: 'Actions',
382
+ render: (question: SurveyQuestion) => (
383
+ <TableActionCell
384
+ primaryAction={{
385
+ text: 'Edit',
386
+ onClick: () => openModal(question)
387
+ }}
388
+ secondaryActions={[
389
+ {
390
+ text: question.isActive ? 'Deactivate' : 'Activate',
391
+ icon: question.isActive ? <Icons.Hidden /> : <Icons.Visible />,
392
+ onClick: () => toggleQuestionStatus(question)
393
+ },
394
+ {
395
+ text: 'Delete',
396
+ icon: <Icons.Delete />,
397
+ onClick: () => {
398
+ if (question._id) {
399
+ handleDelete(question._id, question.questionText);
400
+ }
401
+ }
402
+ }
403
+ ]}
404
+ />
405
+ ),
406
+ width: '120px'
407
+ }
408
+ ];
409
+
410
+ if (loading) {
411
+ return (
412
+ <WixDesignSystemProvider features={{ newColorsBranding: true }}>
413
+ <Page height="100vh">
414
+ <Page.Header title="Survey Manager" />
415
+ <Page.Content>
416
+ <Box align="center" verticalAlign="middle" height="50vh">
417
+ <Loader text="Loading survey questions..." />
418
+ </Box>
419
+ </Page.Content>
420
+ </Page>
421
+ </WixDesignSystemProvider>
422
+ );
423
+ }
424
+
425
+ return (
426
+ <WixDesignSystemProvider features={{ newColorsBranding: true }}>
427
+ <Page height="100vh">
428
+ <Page.Header
429
+ title="Survey Manager"
430
+ subtitle="Create and manage rating survey questions, view aggregated results"
431
+ actionsBar={
432
+ <Button
433
+ onClick={() => openModal()}
434
+ prefixIcon={<Icons.Add />}
435
+ >
436
+ Add Question
437
+ </Button>
438
+ }
439
+ />
440
+ <Page.Content>
441
+ {questions.length === 0 ? (
442
+ <EmptyState
443
+ title="No survey questions yet"
444
+ subtitle="Create your first rating question to start collecting feedback from visitors"
445
+ skin="page"
446
+ >
447
+ <Button
448
+ onClick={() => openModal()}
449
+ prefixIcon={<Icons.Add />}
450
+ >
451
+ Create First Question
452
+ </Button>
453
+ </EmptyState>
454
+ ) : (
455
+ <>
456
+ {/* Summary Cards */}
457
+ <Box gap="24px" marginBottom="24px">
458
+ <Card>
459
+ <Card.Content>
460
+ <Box direction="vertical" gap="6px">
461
+ <Heading size="small">Total Questions</Heading>
462
+ <Text size="medium" weight="bold">{questions.length}</Text>
463
+ </Box>
464
+ </Card.Content>
465
+ </Card>
466
+ <Card>
467
+ <Card.Content>
468
+ <Box direction="vertical" gap="6px">
469
+ <Heading size="small">Active Questions</Heading>
470
+ <Text size="medium" weight="bold">
471
+ {questions.filter(q => q.isActive).length}
472
+ </Text>
473
+ </Box>
474
+ </Card.Content>
475
+ </Card>
476
+ <Card>
477
+ <Card.Content>
478
+ <Box direction="vertical" gap="6px">
479
+ <Heading size="small">Total Responses</Heading>
480
+ <Text size="medium" weight="bold">
481
+ {Object.values(questionStats).reduce((sum, stats) => sum + stats.totalResponses, 0)}
482
+ </Text>
483
+ </Box>
484
+ </Card.Content>
485
+ </Card>
486
+ </Box>
487
+
488
+ {/* Questions Table */}
489
+ <Table data={questions} columns={columns}>
490
+ <TableToolbar>
491
+ <TableToolbar.ItemGroup position="start">
492
+ <TableToolbar.Item>
493
+ <TableToolbar.Title>Survey Questions</TableToolbar.Title>
494
+ </TableToolbar.Item>
495
+ </TableToolbar.ItemGroup>
496
+ </TableToolbar>
497
+ <Table.Content />
498
+ </Table>
499
+ </>
500
+ )}
501
+ </Page.Content>
502
+ </Page>
503
+
504
+ {/* Add/Edit Question Modal */}
505
+ <Modal isOpen={isModalOpen} onRequestClose={closeModal}>
506
+ <CustomModalLayout
507
+ primaryButtonText="Save"
508
+ secondaryButtonText="Cancel"
509
+ onCloseButtonClick={closeModal}
510
+ primaryButtonOnClick={handleSave}
511
+ secondaryButtonOnClick={closeModal}
512
+ title={`${editingQuestion ? 'Edit' : 'Add'} Survey Question`}
513
+ content={
514
+ <Layout gap="24px">
515
+ <Cell span={12}>
516
+ <FormField label="Question Text" required>
517
+ <Input
518
+ value={formData.questionText}
519
+ onChange={(e) => setFormData({ ...formData, questionText: e.target.value })}
520
+ placeholder="How would you rate our service?"
521
+ />
522
+ </FormField>
523
+ </Cell>
524
+ <Cell span={6}>
525
+ <FormField label="Maximum Rating">
526
+ <Input
527
+ type="number"
528
+ value={formData.maxRating.toString()}
529
+ onChange={(e) => setFormData({ ...formData, maxRating: Math.max(1, Math.min(10, Number(e.target.value))) })}
530
+ min={1}
531
+ max={10}
532
+ />
533
+ </FormField>
534
+ </Cell>
535
+ <Cell span={6}>
536
+ <FormField label="Active" labelPlacement="right" stretchContent={false}>
537
+ <ToggleSwitch
538
+ checked={formData.isActive}
539
+ onChange={() => setFormData({ ...formData, isActive: !formData.isActive })}
540
+ />
541
+ </FormField>
542
+ </Cell>
543
+ </Layout>
544
+ }
545
+ />
546
+ </Modal>
547
+ </WixDesignSystemProvider>
548
+ );
549
+ };
550
+
551
+ export default SurveyManager;
@@ -0,0 +1,18 @@
1
+ <svg width="166" height="64" viewBox="0 0 166 64" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <g clip-path="url(#clip0_14809_9463)">
3
+ <path
4
+ d="M165.304 0H156.173C153.64 0 151.273 1.25433 149.85 3.35199L137.639 21.3662C137.313 21.844 136.608 21.844 136.283 21.3662L124.071 3.35199C122.651 1.25433 120.281 0 117.748 0H108.617L130.371 32.0894L108.737 64H117.868C120.401 64 122.769 62.7457 124.192 60.648L136.283 42.8126C136.608 42.3349 137.313 42.3349 137.639 42.8126L149.73 60.648C151.15 62.7457 153.52 64 156.053 64H165.184L143.551 32.0894L165.304 0Z"
5
+ fill="black" />
6
+ <path
7
+ d="M89.8281 6.54652V64H94.1922C97.8088 64 100.74 61.0697 100.74 57.4535V0H96.3755C92.7588 0 89.8281 2.93032 89.8281 6.54652Z"
8
+ fill="black" />
9
+ <path
10
+ d="M81.8276 0H77.944C73.6681 0 69.9633 2.95701 69.0158 7.12564L60.3278 45.3185L52.7234 9.66632C51.3168 3.0771 44.5559 -1.36641 37.6375 0.544431C33.2307 1.76139 29.9637 5.48434 29.0108 9.95455L21.4839 45.2705L12.8118 7.12831C11.8616 2.95968 8.15687 0 3.88092 0H0L14.5548 63.9973H20.0692C25.0738 63.9973 29.3978 60.4986 30.4415 55.604L39.7461 11.9401C39.8608 11.3984 40.3466 11.006 40.8991 11.006C41.4516 11.006 41.9374 11.3984 42.0522 11.9401L51.3648 55.6067C52.4084 60.5012 56.7324 63.9973 61.7371 63.9973H67.2702L81.8276 0Z"
11
+ fill="black" />
12
+ </g>
13
+ <defs>
14
+ <clipPath id="clip0_14809_9463">
15
+ <rect width="165.305" height="64" fill="white" />
16
+ </clipPath>
17
+ </defs>
18
+ </svg>
@@ -0,0 +1,12 @@
1
+ import { app } from '@wix/astro/builders';
2
+ import * as extensions from '@wix/astro/builders';
3
+
4
+ export default app()
5
+ .use(
6
+ extensions.backofficePage({
7
+ component: './dashboard/pages/page.tsx',
8
+ id: '4344c4a2-1864-4587-87e9-5e4109b16999',
9
+ routePath: 'survey-manager',
10
+ title: 'Survey Manager',
11
+ })
12
+ );
@@ -0,0 +1,8 @@
1
+ {
2
+ "compilerOptions": {
3
+ "jsx": "react"
4
+ },
5
+ "extends": "astro/tsconfigs/strict",
6
+ "include": [".astro/types.d.ts", "**/*"],
7
+ "exclude": ["dist"]
8
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "appId": "f24d09af-4473-430b-b9bc-6550bc08b268",
3
+ "siteId": "2593d2c4-5fee-42a5-b81c-f487d4e8ed64"
4
+ }