@wix/ditto-codegen-public 1.0.179 → 1.0.181

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,116 @@
1
+ import { items } from '@wix/data';
2
+ import type { SurveyQuestion, SurveyResponse, QuestionStats } from './types';
3
+
4
+ /**
5
+ * Creates a new survey question
6
+ */
7
+ export async function createQuestion(
8
+ questionData: Omit<SurveyQuestion, '_id' | '_createdDate' | '_updatedDate' | '_owner'>
9
+ ): Promise<SurveyQuestion> {
10
+ try {
11
+ const result = await items.insert('survey-questions', questionData);
12
+ return result as SurveyQuestion;
13
+ } catch (error) {
14
+ console.error('Error creating survey question:', error);
15
+ throw new Error(
16
+ error instanceof Error ? error.message : 'Failed to create survey question'
17
+ );
18
+ }
19
+ }
20
+
21
+ /**
22
+ * Retrieves all survey questions
23
+ */
24
+ export async function getAllQuestions(): Promise<items.WixDataResult<SurveyQuestion>> {
25
+ try {
26
+ const result = await items.query('survey-questions')
27
+ .descending('_createdDate')
28
+ .find();
29
+ return result as items.WixDataResult<SurveyQuestion>;
30
+ } catch (error) {
31
+ console.error('Error fetching survey questions:', error);
32
+ throw new Error(
33
+ error instanceof Error ? error.message : 'Failed to fetch survey questions'
34
+ );
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Updates an existing survey question
40
+ */
41
+ export async function updateQuestion(questionData: SurveyQuestion): Promise<SurveyQuestion> {
42
+ try {
43
+ if (!questionData._id) {
44
+ throw new Error('Question ID is required for update');
45
+ }
46
+ const result = await items.update('survey-questions', questionData);
47
+ return result as SurveyQuestion;
48
+ } catch (error) {
49
+ console.error('Error updating survey question:', error);
50
+ throw new Error(
51
+ error instanceof Error ? error.message : 'Failed to update survey question'
52
+ );
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Deletes a survey question by ID
58
+ */
59
+ export async function deleteQuestion(questionId: string): Promise<SurveyQuestion> {
60
+ try {
61
+ if (!questionId) {
62
+ throw new Error('Question ID is required for deletion');
63
+ }
64
+ const result = await items.remove('survey-questions', questionId);
65
+ return result as SurveyQuestion;
66
+ } catch (error) {
67
+ console.error('Error deleting survey question:', error);
68
+ throw new Error(
69
+ error instanceof Error ? error.message : 'Failed to delete survey question'
70
+ );
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Gets statistics for a specific question
76
+ */
77
+ export async function getQuestionStats(questionId: string): Promise<QuestionStats> {
78
+ try {
79
+ const responses = await items.query('survey-responses')
80
+ .eq('questionId', questionId)
81
+ .find();
82
+
83
+ const responseItems = responses.items as SurveyResponse[];
84
+ const totalResponses = responseItems.length;
85
+
86
+ if (totalResponses === 0) {
87
+ return {
88
+ totalResponses: 0,
89
+ averageRating: 0,
90
+ ratingDistribution: {}
91
+ };
92
+ }
93
+
94
+ const ratings = responseItems.map(r => r.rating);
95
+ const averageRating = ratings.reduce((sum, rating) => sum + rating, 0) / totalResponses;
96
+
97
+ const ratingDistribution: { [rating: number]: number } = {};
98
+ ratings.forEach(rating => {
99
+ ratingDistribution[rating] = (ratingDistribution[rating] || 0) + 1;
100
+ });
101
+
102
+ return {
103
+ totalResponses,
104
+ averageRating: Math.round(averageRating * 100) / 100,
105
+ ratingDistribution
106
+ };
107
+ } catch (error) {
108
+ console.error('Error fetching question stats:', error);
109
+ return {
110
+ totalResponses: 0,
111
+ averageRating: 0,
112
+ ratingDistribution: {}
113
+ };
114
+ }
115
+ }
116
+
@@ -0,0 +1,23 @@
1
+ import React, { type FC } from 'react';
2
+ import { Page, WixDesignSystemProvider, Box, Loader } from '@wix/design-system';
3
+
4
+ interface PageLoaderProps {
5
+ title: string;
6
+ message?: string;
7
+ }
8
+
9
+ export const PageLoader: FC<PageLoaderProps> = ({ title, message = 'Loading...' }) => {
10
+ return (
11
+ <WixDesignSystemProvider features={{ newColorsBranding: true }}>
12
+ <Page height="100vh">
13
+ <Page.Header title={title} />
14
+ <Page.Content>
15
+ <Box align="center" verticalAlign="middle" height="50vh">
16
+ <Loader text={message} />
17
+ </Box>
18
+ </Page.Content>
19
+ </Page>
20
+ </WixDesignSystemProvider>
21
+ );
22
+ };
23
+
@@ -0,0 +1,84 @@
1
+ import React, { type FC } from 'react';
2
+ import {
3
+ Modal,
4
+ CustomModalLayout,
5
+ FormField,
6
+ Input,
7
+ ToggleSwitch,
8
+ Layout,
9
+ Cell,
10
+ } from '@wix/design-system';
11
+ import type { SurveyQuestion, QuestionFormData } from '../types';
12
+
13
+ interface QuestionModalProps {
14
+ isOpen: boolean;
15
+ editingQuestion: SurveyQuestion | null;
16
+ formData: QuestionFormData;
17
+ onFormChange: (data: QuestionFormData) => void;
18
+ onSave: () => void;
19
+ onClose: () => void;
20
+ }
21
+
22
+ export const QuestionModal: FC<QuestionModalProps> = ({
23
+ isOpen,
24
+ editingQuestion,
25
+ formData,
26
+ onFormChange,
27
+ onSave,
28
+ onClose,
29
+ }) => {
30
+ return (
31
+ <Modal isOpen={isOpen} onRequestClose={onClose}>
32
+ <CustomModalLayout
33
+ primaryButtonText="Save"
34
+ secondaryButtonText="Cancel"
35
+ onCloseButtonClick={onClose}
36
+ primaryButtonOnClick={onSave}
37
+ secondaryButtonOnClick={onClose}
38
+ title={`${editingQuestion ? 'Edit' : 'Add'} Survey Question`}
39
+ content={
40
+ <Layout gap="24px">
41
+ <Cell span={12}>
42
+ <FormField label="Question Text" required>
43
+ <Input
44
+ value={formData.questionText}
45
+ onChange={(e) =>
46
+ onFormChange({ ...formData, questionText: e.target.value })
47
+ }
48
+ placeholder="How would you rate our service?"
49
+ />
50
+ </FormField>
51
+ </Cell>
52
+ <Cell span={6}>
53
+ <FormField label="Maximum Rating">
54
+ <Input
55
+ type="number"
56
+ value={formData.maxRating.toString()}
57
+ onChange={(e) =>
58
+ onFormChange({
59
+ ...formData,
60
+ maxRating: Math.max(1, Math.min(10, Number(e.target.value))),
61
+ })
62
+ }
63
+ min={1}
64
+ max={10}
65
+ />
66
+ </FormField>
67
+ </Cell>
68
+ <Cell span={6}>
69
+ <FormField label="Active" labelPlacement="right" stretchContent={false}>
70
+ <ToggleSwitch
71
+ checked={formData.isActive}
72
+ onChange={() =>
73
+ onFormChange({ ...formData, isActive: !formData.isActive })
74
+ }
75
+ />
76
+ </FormField>
77
+ </Cell>
78
+ </Layout>
79
+ }
80
+ />
81
+ </Modal>
82
+ );
83
+ };
84
+
@@ -0,0 +1,139 @@
1
+ import React, { type FC } from 'react';
2
+ import {
3
+ Table,
4
+ TableActionCell,
5
+ TableToolbar,
6
+ Badge,
7
+ Box,
8
+ Text,
9
+ } from '@wix/design-system';
10
+ import * as Icons from '@wix/wix-ui-icons-common';
11
+ import type { SurveyQuestion, QuestionStats } from '../types';
12
+
13
+ interface QuestionsTableProps {
14
+ questions: SurveyQuestion[];
15
+ questionStats: Record<string, QuestionStats>;
16
+ onEdit: (question: SurveyQuestion) => void;
17
+ onDelete: (questionId: string, questionText: string) => void;
18
+ onToggleStatus: (question: SurveyQuestion) => void;
19
+ }
20
+
21
+ export const QuestionsTable: FC<QuestionsTableProps> = ({
22
+ questions,
23
+ questionStats,
24
+ onEdit,
25
+ onDelete,
26
+ onToggleStatus,
27
+ }) => {
28
+ const columns = [
29
+ {
30
+ title: 'Question',
31
+ render: (question: SurveyQuestion) => (
32
+ <Box direction="vertical" gap="3px">
33
+ <Text size="medium" weight="normal">
34
+ {question.questionText}
35
+ </Text>
36
+ <Box gap="6px">
37
+ <Badge
38
+ skin={question.isActive ? 'success' : 'neutral'}
39
+ size="small"
40
+ >
41
+ {question.isActive ? 'Active' : 'Inactive'}
42
+ </Badge>
43
+ <Text size="tiny" secondary>
44
+ Max Rating: {question.maxRating}
45
+ </Text>
46
+ </Box>
47
+ </Box>
48
+ ),
49
+ width: 'auto',
50
+ },
51
+ {
52
+ title: 'Responses',
53
+ render: (question: SurveyQuestion) => {
54
+ const stats = question._id ? questionStats[question._id] : null;
55
+ return (
56
+ <Box direction="vertical" gap="3px">
57
+ <Text size="medium" weight="bold">
58
+ {stats?.totalResponses || 0}
59
+ </Text>
60
+ <Text size="tiny" secondary>
61
+ Total responses
62
+ </Text>
63
+ </Box>
64
+ );
65
+ },
66
+ width: '120px',
67
+ },
68
+ {
69
+ title: 'Average Rating',
70
+ render: (question: SurveyQuestion) => {
71
+ const stats = question._id ? questionStats[question._id] : null;
72
+ return (
73
+ <Box direction="vertical" gap="3px">
74
+ <Text size="medium" weight="bold">
75
+ {stats?.averageRating || 0}
76
+ </Text>
77
+ <Text size="tiny" secondary>
78
+ out of {question.maxRating}
79
+ </Text>
80
+ </Box>
81
+ );
82
+ },
83
+ width: '120px',
84
+ },
85
+ {
86
+ title: 'Created',
87
+ render: (question: SurveyQuestion) => (
88
+ <Text size="small">
89
+ {question._createdDate
90
+ ? new Date(question._createdDate).toLocaleDateString()
91
+ : ''}
92
+ </Text>
93
+ ),
94
+ width: '100px',
95
+ },
96
+ {
97
+ title: 'Actions',
98
+ render: (question: SurveyQuestion) => (
99
+ <TableActionCell
100
+ primaryAction={{
101
+ text: 'Edit',
102
+ onClick: () => onEdit(question),
103
+ }}
104
+ secondaryActions={[
105
+ {
106
+ text: question.isActive ? 'Deactivate' : 'Activate',
107
+ icon: question.isActive ? <Icons.Hidden /> : <Icons.Visible />,
108
+ onClick: () => onToggleStatus(question),
109
+ },
110
+ {
111
+ text: 'Delete',
112
+ icon: <Icons.Delete />,
113
+ onClick: () => {
114
+ if (question._id) {
115
+ onDelete(question._id, question.questionText);
116
+ }
117
+ },
118
+ },
119
+ ]}
120
+ />
121
+ ),
122
+ width: '120px',
123
+ },
124
+ ];
125
+
126
+ return (
127
+ <Table data={questions} columns={columns}>
128
+ <TableToolbar>
129
+ <TableToolbar.ItemGroup position="start">
130
+ <TableToolbar.Item>
131
+ <TableToolbar.Title>Survey Questions</TableToolbar.Title>
132
+ </TableToolbar.Item>
133
+ </TableToolbar.ItemGroup>
134
+ </TableToolbar>
135
+ <Table.Content />
136
+ </Table>
137
+ );
138
+ };
139
+
@@ -0,0 +1,99 @@
1
+ import React, { type FC } from 'react';
2
+ import {
3
+ Button,
4
+ Page,
5
+ WixDesignSystemProvider,
6
+ EmptyState,
7
+ } from '@wix/design-system';
8
+ import * as Icons from '@wix/wix-ui-icons-common';
9
+
10
+ import type { SurveyQuestion, QuestionStats, QuestionFormData } from '../types';
11
+ import { SurveyStats } from './SurveyStats';
12
+ import { QuestionModal } from './QuestionModal';
13
+ import { QuestionsTable } from './QuestionsTable';
14
+
15
+ interface SurveyPageLayoutProps {
16
+ questions: SurveyQuestion[];
17
+ questionStats: Record<string, QuestionStats>;
18
+ activeQuestions: number;
19
+ totalResponses: number;
20
+ isModalOpen: boolean;
21
+ editingQuestion: SurveyQuestion | null;
22
+ formData: QuestionFormData;
23
+ onOpenModal: (question?: SurveyQuestion) => void;
24
+ onCloseModal: () => void;
25
+ onFormChange: (data: QuestionFormData) => void;
26
+ onSave: () => void;
27
+ onDelete: (questionId: string, questionText: string) => void;
28
+ onToggleStatus: (question: SurveyQuestion) => void;
29
+ }
30
+
31
+ export const SurveyPageLayout: FC<SurveyPageLayoutProps> = ({
32
+ questions,
33
+ questionStats,
34
+ activeQuestions,
35
+ totalResponses,
36
+ isModalOpen,
37
+ editingQuestion,
38
+ formData,
39
+ onOpenModal,
40
+ onCloseModal,
41
+ onFormChange,
42
+ onSave,
43
+ onDelete,
44
+ onToggleStatus,
45
+ }) => {
46
+ return (
47
+ <WixDesignSystemProvider features={{ newColorsBranding: true }}>
48
+ <Page height="100vh">
49
+ <Page.Header
50
+ title="Survey Manager"
51
+ subtitle="Create and manage rating survey questions, view aggregated results"
52
+ actionsBar={
53
+ <Button onClick={() => onOpenModal()} prefixIcon={<Icons.Add />}>
54
+ Add Question
55
+ </Button>
56
+ }
57
+ />
58
+ <Page.Content>
59
+ {questions.length === 0 ? (
60
+ <EmptyState
61
+ title="No survey questions yet"
62
+ subtitle="Create your first rating question to start collecting feedback from visitors"
63
+ skin="page"
64
+ >
65
+ <Button onClick={() => onOpenModal()} prefixIcon={<Icons.Add />}>
66
+ Create First Question
67
+ </Button>
68
+ </EmptyState>
69
+ ) : (
70
+ <>
71
+ <SurveyStats
72
+ totalQuestions={questions.length}
73
+ activeQuestions={activeQuestions}
74
+ totalResponses={totalResponses}
75
+ />
76
+ <QuestionsTable
77
+ questions={questions}
78
+ questionStats={questionStats}
79
+ onEdit={onOpenModal}
80
+ onDelete={onDelete}
81
+ onToggleStatus={onToggleStatus}
82
+ />
83
+ </>
84
+ )}
85
+ </Page.Content>
86
+ </Page>
87
+
88
+ <QuestionModal
89
+ isOpen={isModalOpen}
90
+ editingQuestion={editingQuestion}
91
+ formData={formData}
92
+ onFormChange={onFormChange}
93
+ onSave={onSave}
94
+ onClose={onCloseModal}
95
+ />
96
+ </WixDesignSystemProvider>
97
+ );
98
+ };
99
+
@@ -0,0 +1,50 @@
1
+ import React, { type FC } from 'react';
2
+ import { Box, Card, Heading, Text } from '@wix/design-system';
3
+
4
+ interface SurveyStatsProps {
5
+ totalQuestions: number;
6
+ activeQuestions: number;
7
+ totalResponses: number;
8
+ }
9
+
10
+ export const SurveyStats: FC<SurveyStatsProps> = ({
11
+ totalQuestions,
12
+ activeQuestions,
13
+ totalResponses,
14
+ }) => {
15
+ return (
16
+ <Box gap="24px" marginBottom="24px">
17
+ <Card>
18
+ <Card.Content>
19
+ <Box direction="vertical" gap="6px">
20
+ <Heading size="small">Total Questions</Heading>
21
+ <Text size="medium" weight="bold">
22
+ {totalQuestions}
23
+ </Text>
24
+ </Box>
25
+ </Card.Content>
26
+ </Card>
27
+ <Card>
28
+ <Card.Content>
29
+ <Box direction="vertical" gap="6px">
30
+ <Heading size="small">Active Questions</Heading>
31
+ <Text size="medium" weight="bold">
32
+ {activeQuestions}
33
+ </Text>
34
+ </Box>
35
+ </Card.Content>
36
+ </Card>
37
+ <Card>
38
+ <Card.Content>
39
+ <Box direction="vertical" gap="6px">
40
+ <Heading size="small">Total Responses</Heading>
41
+ <Text size="medium" weight="bold">
42
+ {totalResponses}
43
+ </Text>
44
+ </Box>
45
+ </Card.Content>
46
+ </Card>
47
+ </Box>
48
+ );
49
+ };
50
+
@@ -1,184 +1,39 @@
1
1
  import React, { useState, useEffect, type FC } from 'react';
2
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
3
  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
4
 
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
- }
5
+ import type { SurveyQuestion, QuestionStats, QuestionFormData } from './types';
6
+ import {
7
+ createQuestion,
8
+ getAllQuestions,
9
+ updateQuestion,
10
+ deleteQuestion,
11
+ getQuestionStats,
12
+ } from './apis';
13
+ import { PageLoader } from './components/PageLoader';
14
+ import { SurveyPageLayout } from './components/SurveyPageLayout';
15
+
16
+ const DEFAULT_FORM_DATA: QuestionFormData = {
17
+ questionText: '',
18
+ maxRating: 5,
19
+ isActive: true,
20
+ };
160
21
 
161
22
  const SurveyManager: FC = () => {
162
23
  const [questions, setQuestions] = useState<SurveyQuestion[]>([]);
163
- const [questionStats, setQuestionStats] = useState<{ [questionId: string]: QuestionStats }>({});
24
+ const [questionStats, setQuestionStats] = useState<Record<string, QuestionStats>>({});
164
25
  const [loading, setLoading] = useState(true);
165
26
  const [isModalOpen, setIsModalOpen] = useState(false);
166
27
  const [editingQuestion, setEditingQuestion] = useState<SurveyQuestion | null>(null);
167
- const [formData, setFormData] = useState({
168
- questionText: '',
169
- maxRating: 5,
170
- isActive: true
171
- });
28
+ const [formData, setFormData] = useState<QuestionFormData>(DEFAULT_FORM_DATA);
172
29
 
173
- // Load questions and their stats
174
30
  const loadQuestions = async () => {
175
31
  try {
176
32
  setLoading(true);
177
33
  const result = await getAllQuestions();
178
34
  setQuestions(result.items);
179
-
180
- // Load stats for each question
181
- const stats: { [questionId: string]: QuestionStats } = {};
35
+
36
+ const stats: Record<string, QuestionStats> = {};
182
37
  for (const question of result.items) {
183
38
  if (question._id) {
184
39
  stats[question._id] = await getQuestionStats(question._id);
@@ -188,7 +43,7 @@ const SurveyManager: FC = () => {
188
43
  } catch (error) {
189
44
  dashboard.showToast({
190
45
  message: 'Failed to load survey questions',
191
- type: 'error'
46
+ type: 'error',
192
47
  });
193
48
  } finally {
194
49
  setLoading(false);
@@ -201,76 +56,53 @@ const SurveyManager: FC = () => {
201
56
 
202
57
  const openModal = (question?: SurveyQuestion) => {
203
58
  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
-
59
+ setFormData(
60
+ question
61
+ ? {
62
+ questionText: question.questionText,
63
+ maxRating: question.maxRating,
64
+ isActive: question.isActive,
65
+ }
66
+ : DEFAULT_FORM_DATA
67
+ );
219
68
  setIsModalOpen(true);
220
69
  };
221
70
 
222
71
  const closeModal = () => {
223
72
  setIsModalOpen(false);
224
73
  setEditingQuestion(null);
225
- setFormData({
226
- questionText: '',
227
- maxRating: 5,
228
- isActive: true
229
- });
74
+ setFormData(DEFAULT_FORM_DATA);
230
75
  };
231
76
 
232
77
  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
- }
78
+ if (!formData.questionText.trim()) {
79
+ dashboard.showToast({
80
+ message: 'Question text is required',
81
+ type: 'error',
82
+ });
83
+ return;
84
+ }
241
85
 
86
+ try {
242
87
  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);
88
+ await updateQuestion({ ...editingQuestion, ...formData });
251
89
  dashboard.showToast({
252
90
  message: 'Question updated successfully',
253
- type: 'success'
91
+ type: 'success',
254
92
  });
255
93
  } else {
256
- // Create new question
257
- await createQuestion({
258
- questionText: formData.questionText,
259
- maxRating: formData.maxRating,
260
- isActive: formData.isActive
261
- });
94
+ await createQuestion(formData);
262
95
  dashboard.showToast({
263
96
  message: 'Question created successfully',
264
- type: 'success'
97
+ type: 'success',
265
98
  });
266
99
  }
267
-
268
100
  closeModal();
269
101
  loadQuestions();
270
102
  } catch (error) {
271
103
  dashboard.showToast({
272
104
  message: editingQuestion ? 'Failed to update question' : 'Failed to create question',
273
- type: 'error'
105
+ type: 'error',
274
106
  });
275
107
  }
276
108
  };
@@ -280,272 +112,60 @@ const SurveyManager: FC = () => {
280
112
  await deleteQuestion(questionId);
281
113
  dashboard.showToast({
282
114
  message: `"${questionText}" deleted successfully`,
283
- type: 'success'
115
+ type: 'success',
284
116
  });
285
117
  loadQuestions();
286
118
  } catch (error) {
287
119
  dashboard.showToast({
288
120
  message: 'Failed to delete question',
289
- type: 'error'
121
+ type: 'error',
290
122
  });
291
123
  }
292
124
  };
293
125
 
294
- const toggleQuestionStatus = async (question: SurveyQuestion) => {
126
+ const handleToggleStatus = async (question: SurveyQuestion) => {
295
127
  try {
296
- const updatedQuestion: SurveyQuestion = {
297
- ...question,
298
- isActive: !question.isActive
299
- };
300
- await updateQuestion(updatedQuestion);
128
+ await updateQuestion({ ...question, isActive: !question.isActive });
301
129
  dashboard.showToast({
302
- message: `Question ${updatedQuestion.isActive ? 'activated' : 'deactivated'}`,
303
- type: 'success'
130
+ message: `Question ${!question.isActive ? 'activated' : 'deactivated'}`,
131
+ type: 'success',
304
132
  });
305
133
  loadQuestions();
306
134
  } catch (error) {
307
135
  dashboard.showToast({
308
136
  message: 'Failed to update question status',
309
- type: 'error'
137
+ type: 'error',
310
138
  });
311
139
  }
312
140
  };
313
141
 
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
- ];
142
+ const totalResponses = Object.values(questionStats).reduce(
143
+ (sum, stats) => sum + stats.totalResponses,
144
+ 0
145
+ );
146
+ const activeQuestions = questions.filter((q) => q.isActive).length;
409
147
 
410
148
  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
- );
149
+ return <PageLoader title="Survey Manager" message="Loading survey questions..." />;
423
150
  }
424
151
 
425
152
  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>
153
+ <SurveyPageLayout
154
+ questions={questions}
155
+ questionStats={questionStats}
156
+ activeQuestions={activeQuestions}
157
+ totalResponses={totalResponses}
158
+ isModalOpen={isModalOpen}
159
+ editingQuestion={editingQuestion}
160
+ formData={formData}
161
+ onOpenModal={openModal}
162
+ onCloseModal={closeModal}
163
+ onFormChange={setFormData}
164
+ onSave={handleSave}
165
+ onDelete={handleDelete}
166
+ onToggleStatus={handleToggleStatus}
167
+ />
548
168
  );
549
169
  };
550
170
 
551
- export default SurveyManager;
171
+ export default SurveyManager;
@@ -0,0 +1,28 @@
1
+ import type { items } from '@wix/data';
2
+
3
+ export type WixDataItem = items.WixDataItem;
4
+
5
+ export interface SurveyQuestion extends WixDataItem {
6
+ questionText: string;
7
+ maxRating: number;
8
+ isActive: boolean;
9
+ }
10
+
11
+ export interface SurveyResponse extends WixDataItem {
12
+ questionId: string;
13
+ rating: number;
14
+ submittedAt: Date;
15
+ }
16
+
17
+ export interface QuestionStats {
18
+ totalResponses: number;
19
+ averageRating: number;
20
+ ratingDistribution: { [rating: number]: number };
21
+ }
22
+
23
+ export interface QuestionFormData {
24
+ questionText: string;
25
+ maxRating: number;
26
+ isActive: boolean;
27
+ }
28
+
package/dist/out.js CHANGED
@@ -36567,6 +36567,12 @@ var require_CodegenAIProxyService = __commonJS({
36567
36567
  maxRetries: 3,
36568
36568
  temperature: 0
36569
36569
  });
36570
+ if (response.finishReason === "length") {
36571
+ throw new ditto_codegen_types_12.GenerateObjectError(`Output truncated due to max output tokens limit. Agent: ${payload.agentName}`, {
36572
+ status: 413,
36573
+ statusText: "Output truncated - max tokens reached"
36574
+ });
36575
+ }
36570
36576
  return response;
36571
36577
  };
36572
36578
  }
@@ -64620,7 +64626,14 @@ var require_load_examples = __commonJS({
64620
64626
  description: "A dashboard app that allows administrators to create and manage rating (1-5) survey questions and view aggregated results",
64621
64627
  files: {
64622
64628
  [types_1.ExtensionType.DASHBOARD_PAGE]: [
64623
- "survey-manager/src/dashboard/pages/page.tsx"
64629
+ "survey-manager/src/dashboard/pages/page.tsx",
64630
+ "survey-manager/src/dashboard/pages/components/PageLoader.tsx",
64631
+ "survey-manager/src/dashboard/pages/components/QuestionModal.tsx",
64632
+ "survey-manager/src/dashboard/pages/components/QuestionsTable.tsx",
64633
+ "survey-manager/src/dashboard/pages/components/SurveyPageLayout.tsx",
64634
+ "survey-manager/src/dashboard/pages/components/SurveyStats.tsx",
64635
+ "survey-manager/src/dashboard/pages/apis.ts",
64636
+ "survey-manager/src/dashboard/pages/types.ts"
64624
64637
  ]
64625
64638
  }
64626
64639
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/ditto-codegen-public",
3
- "version": "1.0.179",
3
+ "version": "1.0.181",
4
4
  "description": "AI-powered Wix CLI app generator - standalone executable",
5
5
  "scripts": {
6
6
  "build": "node build.mjs",
@@ -24,5 +24,5 @@
24
24
  "@wix/ditto-codegen": "1.0.0",
25
25
  "esbuild": "^0.25.9"
26
26
  },
27
- "falconPackageHash": "600908bee71034cf80b10a10f6bcd2217c1f7b4f901a769ef4c58757"
27
+ "falconPackageHash": "151737a032f3a8f5f4f0c4f06b5f3327f6ae413df19d0c5acb41af5d"
28
28
  }
@@ -1,18 +0,0 @@
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>
@@ -1,10 +0,0 @@
1
- import { app, extensions } from '@wix/astro/builders';
2
-
3
- export default app().use(
4
- extensions.dashboardPage({
5
- component: './dashboard/pages/page.tsx',
6
- id: '4344c4a2-1864-4587-87e9-5e4109b16999',
7
- routePath: 'survey-manager',
8
- title: 'Survey Manager',
9
- })
10
- );