@canva/cli 1.19.0 → 1.20.0

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 (231) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +1 -9
  3. package/cli.js +408 -409
  4. package/package.json +1 -2
  5. package/templates/base/backend/base_backend/create.ts +0 -114
  6. package/templates/base/backend/database/database.ts +0 -42
  7. package/templates/base/backend/routers/auth.ts +0 -288
  8. package/templates/base/declarations/declarations.d.ts +0 -29
  9. package/templates/base/eslint.config.mjs +0 -14
  10. package/templates/base/package.json +0 -91
  11. package/templates/base/scripts/copy_env.ts +0 -13
  12. package/templates/base/scripts/ssl/ssl.ts +0 -131
  13. package/templates/base/scripts/start/app_runner.ts +0 -223
  14. package/templates/base/scripts/start/context.ts +0 -171
  15. package/templates/base/scripts/start/start.ts +0 -46
  16. package/templates/base/scripts/start.tests.ts +0 -61
  17. package/templates/base/styles/components.css +0 -38
  18. package/templates/base/tsconfig.json +0 -56
  19. package/templates/base/webpack.config.ts +0 -247
  20. package/templates/common/.env.template +0 -6
  21. package/templates/common/.gitignore.template +0 -8
  22. package/templates/common/.nvmrc +0 -1
  23. package/templates/common/.prettierrc +0 -21
  24. package/templates/common/LICENSE.md +0 -48
  25. package/templates/common/README.md +0 -179
  26. package/templates/common/jest.config.mjs +0 -35
  27. package/templates/common/jest.setup.ts +0 -35
  28. package/templates/content_publisher/README.md +0 -58
  29. package/templates/content_publisher/canva-app.json +0 -17
  30. package/templates/content_publisher/declarations/declarations.d.ts +0 -29
  31. package/templates/content_publisher/eslint.config.mjs +0 -14
  32. package/templates/content_publisher/package.json +0 -90
  33. package/templates/content_publisher/scripts/copy_env.ts +0 -13
  34. package/templates/content_publisher/scripts/ssl/ssl.ts +0 -131
  35. package/templates/content_publisher/scripts/start/app_runner.ts +0 -223
  36. package/templates/content_publisher/scripts/start/context.ts +0 -171
  37. package/templates/content_publisher/scripts/start/start.ts +0 -46
  38. package/templates/content_publisher/src/index.tsx +0 -4
  39. package/templates/content_publisher/src/intents/content_publisher/index.tsx +0 -107
  40. package/templates/content_publisher/src/intents/content_publisher/post_preview.tsx +0 -240
  41. package/templates/content_publisher/src/intents/content_publisher/preview_ui.tsx +0 -62
  42. package/templates/content_publisher/src/intents/content_publisher/settings_ui.tsx +0 -81
  43. package/templates/content_publisher/src/intents/content_publisher/types.ts +0 -29
  44. package/templates/content_publisher/styles/components.css +0 -38
  45. package/templates/content_publisher/styles/preview_ui.css +0 -49
  46. package/templates/content_publisher/tsconfig.json +0 -56
  47. package/templates/content_publisher/webpack.config.ts +0 -247
  48. package/templates/dam/backend/routers/dam.ts +0 -108
  49. package/templates/dam/backend/server.ts +0 -65
  50. package/templates/dam/canva-app.json +0 -25
  51. package/templates/dam/declarations/declarations.d.ts +0 -29
  52. package/templates/dam/eslint.config.mjs +0 -14
  53. package/templates/dam/package.json +0 -97
  54. package/templates/dam/scripts/copy_env.ts +0 -13
  55. package/templates/dam/scripts/ssl/ssl.ts +0 -131
  56. package/templates/dam/scripts/start/app_runner.ts +0 -223
  57. package/templates/dam/scripts/start/context.ts +0 -171
  58. package/templates/dam/scripts/start/start.ts +0 -46
  59. package/templates/dam/src/index.tsx +0 -4
  60. package/templates/dam/src/intents/design_editor/adapter.ts +0 -44
  61. package/templates/dam/src/intents/design_editor/app.tsx +0 -35
  62. package/templates/dam/src/intents/design_editor/config.ts +0 -220
  63. package/templates/dam/src/intents/design_editor/index.css +0 -3
  64. package/templates/dam/src/intents/design_editor/index.tsx +0 -25
  65. package/templates/dam/tsconfig.json +0 -56
  66. package/templates/dam/utils/backend/base_backend/create.ts +0 -114
  67. package/templates/dam/webpack.config.ts +0 -247
  68. package/templates/data_connector/README.md +0 -84
  69. package/templates/data_connector/canva-app.json +0 -21
  70. package/templates/data_connector/declarations/declarations.d.ts +0 -29
  71. package/templates/data_connector/eslint.config.mjs +0 -14
  72. package/templates/data_connector/package.json +0 -92
  73. package/templates/data_connector/scripts/copy_env.ts +0 -13
  74. package/templates/data_connector/scripts/ssl/ssl.ts +0 -131
  75. package/templates/data_connector/scripts/start/app_runner.ts +0 -223
  76. package/templates/data_connector/scripts/start/context.ts +0 -171
  77. package/templates/data_connector/scripts/start/start.ts +0 -46
  78. package/templates/data_connector/src/api/connect_client.ts +0 -6
  79. package/templates/data_connector/src/api/data_source.ts +0 -97
  80. package/templates/data_connector/src/api/data_sources/designs.tsx +0 -296
  81. package/templates/data_connector/src/api/data_sources/index.ts +0 -4
  82. package/templates/data_connector/src/api/data_sources/templates.tsx +0 -328
  83. package/templates/data_connector/src/api/fetch_data_table.ts +0 -55
  84. package/templates/data_connector/src/api/index.ts +0 -4
  85. package/templates/data_connector/src/api/oauth.ts +0 -8
  86. package/templates/data_connector/src/api/tests/data_source.test.tsx +0 -99
  87. package/templates/data_connector/src/components/app_error.tsx +0 -15
  88. package/templates/data_connector/src/components/footer.tsx +0 -26
  89. package/templates/data_connector/src/components/header.tsx +0 -40
  90. package/templates/data_connector/src/components/index.ts +0 -3
  91. package/templates/data_connector/src/components/inputs/messages.tsx +0 -95
  92. package/templates/data_connector/src/components/inputs/search_filter.tsx +0 -109
  93. package/templates/data_connector/src/components/inputs/select_field.tsx +0 -26
  94. package/templates/data_connector/src/context/app_context.tsx +0 -125
  95. package/templates/data_connector/src/context/index.ts +0 -2
  96. package/templates/data_connector/src/context/use_app_context.ts +0 -17
  97. package/templates/data_connector/src/index.tsx +0 -4
  98. package/templates/data_connector/src/intents/data_connector/app.tsx +0 -20
  99. package/templates/data_connector/src/intents/data_connector/entrypoint.tsx +0 -70
  100. package/templates/data_connector/src/intents/data_connector/home.tsx +0 -21
  101. package/templates/data_connector/src/intents/data_connector/index.tsx +0 -56
  102. package/templates/data_connector/src/pages/data_source_config.tsx +0 -9
  103. package/templates/data_connector/src/pages/error.tsx +0 -37
  104. package/templates/data_connector/src/pages/index.ts +0 -4
  105. package/templates/data_connector/src/pages/login.tsx +0 -145
  106. package/templates/data_connector/src/pages/select_source.tsx +0 -24
  107. package/templates/data_connector/src/routes/index.ts +0 -2
  108. package/templates/data_connector/src/routes/paths.ts +0 -7
  109. package/templates/data_connector/src/routes/protected_route.tsx +0 -26
  110. package/templates/data_connector/src/routes/routes.tsx +0 -42
  111. package/templates/data_connector/src/utils/data_params.ts +0 -17
  112. package/templates/data_connector/src/utils/data_table.ts +0 -116
  113. package/templates/data_connector/src/utils/fetch_result.ts +0 -36
  114. package/templates/data_connector/src/utils/index.ts +0 -2
  115. package/templates/data_connector/src/utils/tests/data_table.test.ts +0 -133
  116. package/templates/data_connector/styles/components.css +0 -38
  117. package/templates/data_connector/tsconfig.json +0 -56
  118. package/templates/data_connector/webpack.config.ts +0 -247
  119. package/templates/gen_ai/README.md +0 -27
  120. package/templates/gen_ai/backend/routers/image.ts +0 -232
  121. package/templates/gen_ai/backend/server.ts +0 -65
  122. package/templates/gen_ai/canva-app.json +0 -25
  123. package/templates/gen_ai/declarations/declarations.d.ts +0 -29
  124. package/templates/gen_ai/eslint.config.mjs +0 -14
  125. package/templates/gen_ai/package.json +0 -101
  126. package/templates/gen_ai/scripts/copy_env.ts +0 -13
  127. package/templates/gen_ai/scripts/ssl/ssl.ts +0 -131
  128. package/templates/gen_ai/scripts/start/app_runner.ts +0 -223
  129. package/templates/gen_ai/scripts/start/context.ts +0 -171
  130. package/templates/gen_ai/scripts/start/start.ts +0 -46
  131. package/templates/gen_ai/src/api/api.ts +0 -194
  132. package/templates/gen_ai/src/api/index.ts +0 -1
  133. package/templates/gen_ai/src/components/app_error.tsx +0 -18
  134. package/templates/gen_ai/src/components/footer.messages.ts +0 -48
  135. package/templates/gen_ai/src/components/footer.tsx +0 -156
  136. package/templates/gen_ai/src/components/image_grid.tsx +0 -103
  137. package/templates/gen_ai/src/components/index.ts +0 -7
  138. package/templates/gen_ai/src/components/loading_results.tsx +0 -169
  139. package/templates/gen_ai/src/components/prompt_input.messages.ts +0 -14
  140. package/templates/gen_ai/src/components/prompt_input.tsx +0 -154
  141. package/templates/gen_ai/src/components/remaining_credits.tsx +0 -84
  142. package/templates/gen_ai/src/components/report_box.tsx +0 -54
  143. package/templates/gen_ai/src/components/tests/remaining_credit.tests.tsx +0 -47
  144. package/templates/gen_ai/src/config.ts +0 -21
  145. package/templates/gen_ai/src/context/app_context.tsx +0 -153
  146. package/templates/gen_ai/src/context/context.messages.ts +0 -30
  147. package/templates/gen_ai/src/context/index.ts +0 -2
  148. package/templates/gen_ai/src/context/use_app_context.ts +0 -17
  149. package/templates/gen_ai/src/index.tsx +0 -4
  150. package/templates/gen_ai/src/intents/design_editor/app.tsx +0 -19
  151. package/templates/gen_ai/src/intents/design_editor/home.tsx +0 -13
  152. package/templates/gen_ai/src/intents/design_editor/index.tsx +0 -17
  153. package/templates/gen_ai/src/pages/error.tsx +0 -41
  154. package/templates/gen_ai/src/pages/generate.tsx +0 -9
  155. package/templates/gen_ai/src/pages/index.ts +0 -3
  156. package/templates/gen_ai/src/pages/results.tsx +0 -31
  157. package/templates/gen_ai/src/routes/index.ts +0 -1
  158. package/templates/gen_ai/src/routes/paths.ts +0 -4
  159. package/templates/gen_ai/src/routes/routes.tsx +0 -24
  160. package/templates/gen_ai/src/utils/index.ts +0 -1
  161. package/templates/gen_ai/src/utils/obscenity_filter.ts +0 -33
  162. package/templates/gen_ai/styles/components.css +0 -38
  163. package/templates/gen_ai/styles/utils.css +0 -3
  164. package/templates/gen_ai/tsconfig.json +0 -56
  165. package/templates/gen_ai/utils/backend/base_backend/create.ts +0 -114
  166. package/templates/gen_ai/webpack.config.ts +0 -247
  167. package/templates/hello_world/canva-app.json +0 -21
  168. package/templates/hello_world/declarations/declarations.d.ts +0 -29
  169. package/templates/hello_world/eslint.config.mjs +0 -14
  170. package/templates/hello_world/package.json +0 -90
  171. package/templates/hello_world/scripts/copy_env.ts +0 -13
  172. package/templates/hello_world/scripts/ssl/ssl.ts +0 -131
  173. package/templates/hello_world/scripts/start/app_runner.ts +0 -223
  174. package/templates/hello_world/scripts/start/context.ts +0 -171
  175. package/templates/hello_world/scripts/start/start.ts +0 -46
  176. package/templates/hello_world/src/index.tsx +0 -4
  177. package/templates/hello_world/src/intents/design_editor/app.tsx +0 -86
  178. package/templates/hello_world/src/intents/design_editor/index.tsx +0 -25
  179. package/templates/hello_world/src/intents/design_editor/tests/__snapshots__/app.tests.tsx.snap +0 -45
  180. package/templates/hello_world/src/intents/design_editor/tests/app.tests.tsx +0 -92
  181. package/templates/hello_world/styles/components.css +0 -38
  182. package/templates/hello_world/tsconfig.json +0 -56
  183. package/templates/hello_world/webpack.config.ts +0 -247
  184. package/templates/mls/README.md +0 -81
  185. package/templates/mls/canva-app.json +0 -25
  186. package/templates/mls/declarations/declarations.d.ts +0 -29
  187. package/templates/mls/eslint.config.mjs +0 -14
  188. package/templates/mls/jest.config.mjs +0 -36
  189. package/templates/mls/jest.setup.ts +0 -39
  190. package/templates/mls/package.json +0 -117
  191. package/templates/mls/scripts/copy_env.ts +0 -13
  192. package/templates/mls/scripts/ssl/ssl.ts +0 -131
  193. package/templates/mls/scripts/start/app_runner.ts +0 -223
  194. package/templates/mls/scripts/start/context.ts +0 -171
  195. package/templates/mls/scripts/start/start.ts +0 -46
  196. package/templates/mls/src/__tests__/app.tests.tsx +0 -11
  197. package/templates/mls/src/__tests__/office_selection_page.tests.tsx +0 -72
  198. package/templates/mls/src/__tests__/utils.tsx +0 -19
  199. package/templates/mls/src/adapter.ts +0 -126
  200. package/templates/mls/src/components/agent/agent_card.tsx +0 -57
  201. package/templates/mls/src/components/agent/agent_grid.tsx +0 -37
  202. package/templates/mls/src/components/agent/agent_list.tsx +0 -17
  203. package/templates/mls/src/components/agent/agent_search_filters.tsx +0 -88
  204. package/templates/mls/src/components/breadcrumb/breadcrumb.tsx +0 -40
  205. package/templates/mls/src/components/listing/listing_card.tsx +0 -64
  206. package/templates/mls/src/components/listing/listing_grid.tsx +0 -37
  207. package/templates/mls/src/components/listing/listing_list.tsx +0 -21
  208. package/templates/mls/src/components/listing/listing_search_filters.tsx +0 -145
  209. package/templates/mls/src/components/placeholders/placeholders.tsx +0 -65
  210. package/templates/mls/src/data.ts +0 -359
  211. package/templates/mls/src/index.tsx +0 -4
  212. package/templates/mls/src/intents/design_editor/app.tsx +0 -44
  213. package/templates/mls/src/intents/design_editor/index.tsx +0 -25
  214. package/templates/mls/src/pages/agent_details_page/agent_details_page.tsx +0 -175
  215. package/templates/mls/src/pages/list_page/agent_tab_panel.tsx +0 -126
  216. package/templates/mls/src/pages/list_page/list_page.tsx +0 -67
  217. package/templates/mls/src/pages/list_page/listing_tab_panel.tsx +0 -135
  218. package/templates/mls/src/pages/listing_details_page/listing_details_page.tsx +0 -418
  219. package/templates/mls/src/pages/loading_page/loading_page.tsx +0 -152
  220. package/templates/mls/src/pages/office_selection_page/office_selection_page.tsx +0 -144
  221. package/templates/mls/src/real_estate.type.ts +0 -44
  222. package/templates/mls/src/util/use_add_element.tsx +0 -62
  223. package/templates/mls/src/util/use_drag_element.tsx +0 -68
  224. package/templates/mls/styles/components.css +0 -38
  225. package/templates/mls/tsconfig.json +0 -54
  226. package/templates/mls/webpack.config.ts +0 -248
  227. package/templates/optional/.cursor/mcp.json +0 -8
  228. package/templates/optional/.vscode/extensions.json +0 -6
  229. package/templates/optional/.vscode/mcp.json +0 -9
  230. package/templates/optional/AGENTS.md +0 -154
  231. package/templates/optional/CLAUDE.md +0 -154
@@ -1,296 +0,0 @@
1
- /* eslint-disable formatjs/no-literal-string-in-object */
2
-
3
- import type { SelectOption } from "@canva/app-ui-kit";
4
- import { Button, DatabaseIcon, HorizontalCard, Rows } from "@canva/app-ui-kit";
5
- import { useEffect, useState } from "react";
6
- import { useIntl } from "react-intl";
7
- import { useNavigate } from "react-router-dom";
8
- import { Header } from "src/components";
9
- import {
10
- ownershipFilter,
11
- sortOrderField,
12
- } from "src/components/inputs/messages";
13
- import { SearchFilter } from "src/components/inputs/search_filter";
14
- import { SelectField } from "src/components/inputs/select_field";
15
- import { useAppContext } from "src/context";
16
- import { Paths } from "src/routes/paths";
17
- import { dateCell, numberCell, stringCell } from "src/utils";
18
- import type { CanvaItemResponse } from "../connect_client";
19
- import { DataAPIError, DataSourceHandler } from "../data_source";
20
- import type { APIResponseItem, DataSourceConfig } from "../data_source";
21
-
22
- export interface DesignsDataSource extends DataSourceConfig {
23
- query: string;
24
- ownership: "any" | "owned" | "shared";
25
- sort_by:
26
- | "relevance"
27
- | "modified_descending"
28
- | "modified_ascending"
29
- | "title_descending"
30
- | "title_ascending";
31
- }
32
-
33
- export interface CanvaDesign extends APIResponseItem {
34
- title: string;
35
- created_at: number;
36
- updated_at: number;
37
- page_count: number;
38
- }
39
-
40
- export const designsSource = new DataSourceHandler<
41
- DesignsDataSource,
42
- CanvaDesign
43
- >(
44
- {
45
- schema: "designs/v1",
46
- query: "",
47
- ownership: "any",
48
- sort_by: "relevance",
49
- },
50
- [
51
- {
52
- label: "ID",
53
- getValue: (design: CanvaDesign) => `ID ${design.id}`,
54
- toCell: stringCell,
55
- },
56
- {
57
- label: "Title",
58
- getValue: "title",
59
- toCell: stringCell,
60
- },
61
- {
62
- label: "Created At",
63
- getValue: "created_at",
64
- toCell: dateCell,
65
- },
66
- {
67
- label: "Updated At",
68
- getValue: "updated_at",
69
- toCell: dateCell,
70
- },
71
- {
72
- label: "Page Count",
73
- getValue: "page_count",
74
- toCell: numberCell,
75
- },
76
- ],
77
- (
78
- source: DesignsDataSource,
79
- authToken: string,
80
- rowLimit: number,
81
- signal: AbortSignal | undefined,
82
- ) =>
83
- getDesigns(
84
- authToken,
85
- rowLimit,
86
- signal,
87
- source.query,
88
- source.ownership,
89
- source.sort_by,
90
- ),
91
- DesignSelection,
92
- DesignsSourceConfig,
93
- );
94
-
95
- export async function getDesigns(
96
- authToken: string,
97
- rowLimit: number,
98
- signal: AbortSignal | undefined,
99
- query: string,
100
- ownership: string,
101
- sort_by: string,
102
- continuation?: string,
103
- allItems: CanvaDesign[] = [],
104
- ): Promise<CanvaDesign[]> {
105
- const baseUrl = `https://api.canva.com/rest/v1/designs`;
106
- const params = new URLSearchParams();
107
- if (continuation) {
108
- params.set("continuation", continuation);
109
- } else {
110
- if (query) {
111
- params.set("query", query);
112
- }
113
- params.set("ownership", ownership);
114
- params.set("sort_by", sort_by);
115
- }
116
- const url = `${baseUrl}?${params.toString()}`;
117
-
118
- return fetch(url, {
119
- headers: {
120
- Authorization: `Bearer ${authToken}`,
121
- },
122
- signal,
123
- })
124
- .then((response) => {
125
- if (!response.ok) {
126
- throw new DataAPIError(
127
- `Canva Connect response was not ok: ${response.statusText || response.status}`,
128
- );
129
- }
130
- return response.json();
131
- })
132
- .then((data: CanvaItemResponse<CanvaDesign>) => {
133
- const updatedItems = [...allItems, ...data.items];
134
-
135
- if (data.continuation && updatedItems.length < rowLimit) {
136
- return getDesigns(
137
- authToken,
138
- rowLimit,
139
- signal,
140
- query,
141
- ownership,
142
- sort_by,
143
- data.continuation,
144
- updatedItems,
145
- );
146
- }
147
-
148
- return updatedItems;
149
- });
150
- }
151
-
152
- function DesignSelection() {
153
- const intl = useIntl();
154
- const { setDataSourceHandler } = useAppContext();
155
- const navigate = useNavigate();
156
-
157
- const title = intl.formatMessage({
158
- defaultMessage: "Designs",
159
- description:
160
- "Main heading on the designs button displayed when selecting the import type.",
161
- });
162
-
163
- const description = intl.formatMessage({
164
- defaultMessage: "Query designs",
165
- description:
166
- "Subtext on the designs selection button displayed when selecting the import type.",
167
- });
168
-
169
- const handleClick = () => {
170
- setDataSourceHandler(
171
- designsSource as unknown as DataSourceHandler<
172
- DataSourceConfig,
173
- APIResponseItem
174
- >,
175
- );
176
- navigate(Paths.DATA_SOURCE_CONFIG);
177
- };
178
-
179
- return (
180
- <HorizontalCard
181
- key="designs"
182
- title={title}
183
- thumbnail={{ icon: () => <DatabaseIcon /> }}
184
- onClick={handleClick}
185
- description={description}
186
- ariaLabel={description}
187
- />
188
- );
189
- }
190
-
191
- function DesignsSourceConfig(sourceConfig: DesignsDataSource) {
192
- const intl = useIntl();
193
- const { loadDataSource } = useAppContext();
194
- const [query, setQuery] = useState<string>(sourceConfig.query);
195
- const [ownership, setOwnership] = useState<string>(sourceConfig.ownership);
196
- const [sortOrder, setSortOrder] = useState<string>(sourceConfig.sort_by);
197
- const [isLoading, setIsLoading] = useState(false);
198
-
199
- const [filterCount, setFilterCount] = useState(0);
200
- useEffect(() => {
201
- // Update the filter count based on the selected filters
202
- // consider a filter to be applied if not set to the default value
203
- setFilterCount(
204
- (ownership !== "any" ? 1 : 0) + (sortOrder !== "relevance" ? 1 : 0),
205
- );
206
- }, [ownership, sortOrder]);
207
- const resetFilters = () => {
208
- setOwnership("any");
209
- setSortOrder("relevance");
210
- };
211
-
212
- const loadDesigns = async () => {
213
- setIsLoading(true);
214
- loadDataSource("Canva Designs", {
215
- schema: "designs/v1",
216
- query,
217
- ownership,
218
- sort_by: sortOrder,
219
- } as DesignsDataSource).then(() => {
220
- setIsLoading(false);
221
- });
222
- };
223
-
224
- const ownershipOptions: SelectOption<string>[] = [
225
- { value: "any", label: intl.formatMessage(ownershipFilter.any) },
226
- { value: "owned", label: intl.formatMessage(ownershipFilter.owned) },
227
- { value: "shared", label: intl.formatMessage(ownershipFilter.shared) },
228
- ];
229
-
230
- const sortOrderOptions: SelectOption<string>[] = [
231
- { value: "relevance", label: intl.formatMessage(sortOrderField.relevance) },
232
- {
233
- value: "modified_descending",
234
- label: intl.formatMessage(sortOrderField.modifiedDesc),
235
- },
236
- {
237
- value: "modified_ascending",
238
- label: intl.formatMessage(sortOrderField.modifiedAsc),
239
- },
240
- {
241
- value: "title_descending",
242
- label: intl.formatMessage(sortOrderField.titleDesc),
243
- },
244
- {
245
- value: "title_ascending",
246
- label: intl.formatMessage(sortOrderField.titleAsc),
247
- },
248
- ];
249
-
250
- return (
251
- <div>
252
- <Rows spacing="2u">
253
- <Header
254
- title={intl.formatMessage({
255
- defaultMessage: "Canva Designs",
256
- description: "The header text for the designs data source",
257
- })}
258
- showBack={true}
259
- />
260
-
261
- <SearchFilter
262
- value={query}
263
- onChange={setQuery}
264
- filterCount={filterCount}
265
- resetFilters={resetFilters}
266
- >
267
- <SelectField
268
- label={intl.formatMessage(ownershipFilter.label)}
269
- options={ownershipOptions}
270
- value={ownership}
271
- onChange={setOwnership}
272
- />
273
- <SelectField
274
- label={intl.formatMessage(sortOrderField.label)}
275
- options={sortOrderOptions}
276
- value={sortOrder}
277
- onChange={setSortOrder}
278
- />
279
- </SearchFilter>
280
-
281
- <Button
282
- variant="primary"
283
- loading={isLoading}
284
- onClick={async () => {
285
- loadDesigns();
286
- }}
287
- >
288
- {intl.formatMessage({
289
- defaultMessage: "Load Designs",
290
- description: "Button for saving and applying the query filter",
291
- })}
292
- </Button>
293
- </Rows>
294
- </div>
295
- );
296
- }
@@ -1,4 +0,0 @@
1
- import { designsSource } from "./designs";
2
- import { brandTemplatesSource } from "./templates";
3
-
4
- export const DATA_SOURCES = [designsSource, brandTemplatesSource];
@@ -1,328 +0,0 @@
1
- /* eslint-disable formatjs/no-literal-string-in-object */
2
- import type { SelectOption } from "@canva/app-ui-kit";
3
- import {
4
- Button,
5
- HorizontalCard,
6
- Rows,
7
- TableMergedHeaderCellsIcon,
8
- } from "@canva/app-ui-kit";
9
- import { useEffect, useState } from "react";
10
- import { useIntl } from "react-intl";
11
- import { useNavigate } from "react-router-dom";
12
- import { Header } from "src/components";
13
- import {
14
- datasetFilter,
15
- ownershipFilter,
16
- sortOrderField,
17
- } from "src/components/inputs/messages";
18
- import { SearchFilter } from "src/components/inputs/search_filter";
19
- import { SelectField } from "src/components/inputs/select_field";
20
- import { useAppContext } from "src/context";
21
- import { Paths } from "src/routes/paths";
22
- import { dateCell, stringCell } from "src/utils";
23
- import type { CanvaItemResponse } from "../connect_client";
24
- import { DataAPIError, DataSourceHandler } from "../data_source";
25
- import type { APIResponseItem, DataSourceConfig } from "../data_source";
26
-
27
- export interface BrandTemplatesDataSource extends DataSourceConfig {
28
- query: string;
29
- dataset: "any" | "non_empty";
30
- ownership: "any" | "owned" | "shared";
31
- sort_by:
32
- | "relevance"
33
- | "modified_descending"
34
- | "modified_ascending"
35
- | "title_descending"
36
- | "title_ascending";
37
- }
38
-
39
- export interface CanvaBrandTemplate extends APIResponseItem {
40
- title: string;
41
- created_at: number;
42
- updated_at: number;
43
- view_url: string;
44
- create_url: string;
45
- }
46
-
47
- export const brandTemplatesSource = new DataSourceHandler<
48
- BrandTemplatesDataSource,
49
- CanvaBrandTemplate
50
- >(
51
- {
52
- schema: "brand_templates/v1",
53
- query: "",
54
- dataset: "any",
55
- ownership: "any",
56
- sort_by: "relevance",
57
- },
58
- [
59
- {
60
- label: "ID",
61
- getValue: (template: CanvaBrandTemplate) => `ID ${template.id}`,
62
- toCell: stringCell,
63
- },
64
- {
65
- label: "Title",
66
- getValue: "title",
67
- toCell: stringCell,
68
- },
69
- {
70
- label: "Created At",
71
- getValue: "created_at",
72
- toCell: dateCell,
73
- },
74
- {
75
- label: "Updated At",
76
- getValue: "updated_at",
77
- toCell: dateCell,
78
- },
79
- {
80
- label: "View URL",
81
- getValue: "view_url",
82
- toCell: stringCell,
83
- },
84
- {
85
- label: "Create URL",
86
- getValue: "create_url",
87
- toCell: stringCell,
88
- },
89
- ],
90
- (
91
- source: BrandTemplatesDataSource,
92
- authToken: string,
93
- rowLimit: number,
94
- signal: AbortSignal | undefined,
95
- ) =>
96
- getBrandTemplates(
97
- authToken,
98
- rowLimit,
99
- signal,
100
- source.query,
101
- source.ownership,
102
- source.dataset,
103
- source.sort_by,
104
- ),
105
- BrandTemplatesSelection,
106
- BrandTemplatesSourceConfig,
107
- );
108
-
109
- export async function getBrandTemplates(
110
- authToken: string,
111
- rowLimit: number,
112
- signal: AbortSignal | undefined,
113
- query: string,
114
- ownership: string,
115
- dataset: string,
116
- sort_by: string,
117
- continuation?: string,
118
- allItems: CanvaBrandTemplate[] = [],
119
- ): Promise<CanvaBrandTemplate[]> {
120
- const baseUrl = `https://api.canva.com/rest/v1/brand-templates`;
121
-
122
- const params = new URLSearchParams();
123
- if (continuation) {
124
- params.set("continuation", continuation);
125
- } else {
126
- if (query) {
127
- params.set("query", query);
128
- }
129
- params.set("ownership", ownership);
130
- params.set("dataset", dataset);
131
- params.set("sort_by", sort_by);
132
- }
133
- const url = `${baseUrl}?${params.toString()}`;
134
-
135
- return fetch(url, {
136
- headers: {
137
- Authorization: `Bearer ${authToken}`,
138
- },
139
- signal,
140
- })
141
- .then((response) => {
142
- if (!response.ok) {
143
- throw new DataAPIError(
144
- `Canva Connect response was not ok: ${response.statusText || response.status}`,
145
- );
146
- }
147
- return response.json();
148
- })
149
- .then((data: CanvaItemResponse<CanvaBrandTemplate>) => {
150
- const updatedItems = [...allItems, ...data.items];
151
-
152
- if (data.continuation && updatedItems.length < rowLimit) {
153
- return getBrandTemplates(
154
- authToken,
155
- rowLimit,
156
- signal,
157
- query,
158
- ownership,
159
- dataset,
160
- sort_by,
161
- data.continuation,
162
- updatedItems,
163
- );
164
- }
165
-
166
- return updatedItems;
167
- });
168
- }
169
-
170
- function BrandTemplatesSelection() {
171
- const intl = useIntl();
172
- const { setDataSourceHandler } = useAppContext();
173
- const navigate = useNavigate();
174
-
175
- const title = intl.formatMessage({
176
- defaultMessage: "Brand Templates",
177
- description:
178
- "Main heading on the brand templates button displayed when selecting the import type.",
179
- });
180
-
181
- const description = intl.formatMessage({
182
- defaultMessage: "Query brand templates",
183
- description:
184
- "Subtext on the brand templates button displayed when selecting the import type.",
185
- });
186
-
187
- const handleClick = () => {
188
- setDataSourceHandler(
189
- brandTemplatesSource as unknown as DataSourceHandler<
190
- DataSourceConfig,
191
- APIResponseItem
192
- >,
193
- );
194
- navigate(Paths.DATA_SOURCE_CONFIG);
195
- };
196
-
197
- return (
198
- <HorizontalCard
199
- key="brand-templates"
200
- title={title}
201
- thumbnail={{ icon: () => <TableMergedHeaderCellsIcon /> }}
202
- onClick={handleClick}
203
- description={description}
204
- ariaLabel={description}
205
- />
206
- );
207
- }
208
-
209
- function BrandTemplatesSourceConfig(sourceConfig: BrandTemplatesDataSource) {
210
- const intl = useIntl();
211
- const { loadDataSource } = useAppContext();
212
- const [query, setQuery] = useState<string>(sourceConfig.query);
213
- const [ownership, setOwnership] = useState<string>(sourceConfig.ownership);
214
- const [sortOrder, setSortOrder] = useState<string>(sourceConfig.sort_by);
215
- const [dataset, setDataset] = useState<string>(sourceConfig.dataset);
216
- const [isLoading, setIsLoading] = useState(false);
217
-
218
- const [filterCount, setFilterCount] = useState(0);
219
- useEffect(() => {
220
- // Update the filter count based on the selected filters
221
- // consider a filter to be applied if not set to the default value
222
- setFilterCount(
223
- (ownership !== "any" ? 1 : 0) +
224
- (dataset !== "any" ? 1 : 0) +
225
- (sortOrder !== "relevance" ? 1 : 0),
226
- );
227
- }, [ownership, dataset, sortOrder]);
228
- const resetFilters = () => {
229
- setOwnership("any");
230
- setDataset("any");
231
- setSortOrder("relevance");
232
- };
233
-
234
- const loadTemplates = async () => {
235
- loadDataSource("Canva Brand Templates", {
236
- schema: "brand_templates/v1",
237
- query,
238
- ownership,
239
- dataset,
240
- sort_by: sortOrder,
241
- } as BrandTemplatesDataSource).then(() => {
242
- setIsLoading(false);
243
- });
244
- };
245
-
246
- const ownershipOptions: SelectOption<string>[] = [
247
- { value: "any", label: intl.formatMessage(ownershipFilter.any) },
248
- { value: "owned", label: intl.formatMessage(ownershipFilter.owned) },
249
- { value: "shared", label: intl.formatMessage(ownershipFilter.shared) },
250
- ];
251
-
252
- const sortOrderOptions: SelectOption<string>[] = [
253
- { value: "relevance", label: intl.formatMessage(sortOrderField.relevance) },
254
- {
255
- value: "modified_descending",
256
- label: intl.formatMessage(sortOrderField.modifiedDesc),
257
- },
258
- {
259
- value: "modified_ascending",
260
- label: intl.formatMessage(sortOrderField.modifiedAsc),
261
- },
262
- {
263
- value: "title_descending",
264
- label: intl.formatMessage(sortOrderField.titleDesc),
265
- },
266
- {
267
- value: "title_ascending",
268
- label: intl.formatMessage(sortOrderField.titleAsc),
269
- },
270
- ];
271
-
272
- const datasetOptions: SelectOption<string>[] = [
273
- { value: "any", label: intl.formatMessage(datasetFilter.any) },
274
- { value: "non_empty", label: intl.formatMessage(datasetFilter.nonEmpty) },
275
- ];
276
-
277
- return (
278
- <div>
279
- <Header
280
- title={intl.formatMessage({
281
- defaultMessage: "Canva Brand Templates",
282
- description: "The header text for the brand templates data source",
283
- })}
284
- showBack={true}
285
- />
286
- <Rows spacing="2u">
287
- <SearchFilter
288
- value={query}
289
- onChange={setQuery}
290
- filterCount={filterCount}
291
- resetFilters={resetFilters}
292
- >
293
- <SelectField
294
- label={intl.formatMessage(ownershipFilter.label)}
295
- options={ownershipOptions}
296
- value={ownership}
297
- onChange={setOwnership}
298
- />
299
- <SelectField
300
- label={intl.formatMessage(datasetFilter.label)}
301
- options={datasetOptions}
302
- value={dataset}
303
- onChange={setDataset}
304
- />
305
- <SelectField
306
- label={intl.formatMessage(sortOrderField.label)}
307
- options={sortOrderOptions}
308
- value={sortOrder}
309
- onChange={setSortOrder}
310
- />
311
- </SearchFilter>
312
-
313
- <Button
314
- variant="primary"
315
- loading={isLoading}
316
- onClick={async () => {
317
- loadTemplates();
318
- }}
319
- >
320
- {intl.formatMessage({
321
- defaultMessage: "Load Templates",
322
- description: "Button for saving and applying the query filter",
323
- })}
324
- </Button>
325
- </Rows>
326
- </div>
327
- );
328
- }
@@ -1,55 +0,0 @@
1
- import type {
2
- GetDataTableRequest,
3
- GetDataTableResponse,
4
- } from "@canva/intents/data";
5
- import {
6
- appError,
7
- completeDataTable,
8
- outdatedSourceRef,
9
- remoteRequestFailed,
10
- } from "src/utils/fetch_result";
11
- import { DATA_SOURCES } from "./data_sources";
12
- import { DataAPIError } from ".";
13
-
14
- /**
15
- * This function handles parsing the data fetch parameters and calling the appropriate handler for the data source.
16
- * @param request
17
- * @param authToken
18
- * @returns
19
- */
20
- export const buildDataTableResult = async (
21
- request: GetDataTableRequest,
22
- authToken?: string,
23
- ): Promise<GetDataTableResponse> => {
24
- const source = JSON.parse(request.dataSourceRef.source);
25
- const rowLimit = request.limit.row - 1; // -1 for the header row
26
-
27
- const dataHandler = DATA_SOURCES.find((handler) =>
28
- handler.matchSource(source),
29
- );
30
- if (!dataHandler) {
31
- // the configured source does not match the expected data source type
32
- // return an error result which will prompt the user to reconfigure the data source
33
- return outdatedSourceRef();
34
- }
35
- try {
36
- const dataTable = await dataHandler.fetchAndBuildTable(
37
- source,
38
- authToken || "",
39
- rowLimit,
40
- request.signal,
41
- );
42
- if (dataTable.rows.length === 0) {
43
- // if the data table is empty, return an error to prompt the user to reconfigure the data source
44
- return appError("No results found.");
45
- }
46
- return completeDataTable(dataTable);
47
- } catch (error) {
48
- if (error instanceof DataAPIError) {
49
- return remoteRequestFailed();
50
- }
51
- return appError(
52
- error instanceof Error ? error.message : "An unknown error occurred",
53
- );
54
- }
55
- };
@@ -1,4 +0,0 @@
1
- export * from "./connect_client";
2
- export * from "./fetch_data_table";
3
- export * from "./data_source";
4
- export * from "./oauth";
@@ -1,8 +0,0 @@
1
- /**
2
- * The scopes to request as part of the OAuth flow.
3
- * These scopes are required to read the user's designs and brand templates.
4
- *
5
- * @see https://www.canva.dev/docs/apps/authenticating-users/oauth/#overview - for more information about using the @canva/user package to support OAuth login.
6
- * @see https://www.canva.dev/docs/connect/appendix/scopes/ - for a full list of Canva Connect API scopes.
7
- */
8
- export const scope = new Set(["design:meta:read", "brandtemplate:meta:read"]);