@canva/cli 0.0.1-beta.1 → 0.0.1-beta.10

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 (79) hide show
  1. package/README.md +184 -108
  2. package/cli.js +446 -359
  3. package/lib/cjs/index.cjs +392 -0
  4. package/lib/esm/index.esm +392 -0
  5. package/package.json +11 -2
  6. package/templates/base/backend/routers/oauth.ts +393 -0
  7. package/templates/base/eslint.config.mjs +5 -277
  8. package/templates/base/package.json +26 -23
  9. package/templates/base/scripts/copy-env.ts +10 -0
  10. package/templates/base/utils/backend/bearer_middleware/bearer_middleware.ts +101 -0
  11. package/templates/base/utils/backend/bearer_middleware/index.ts +1 -0
  12. package/templates/base/utils/backend/bearer_middleware/tests/bearer_middleware.tests.ts +192 -0
  13. package/templates/base/utils/use_add_element.ts +48 -0
  14. package/templates/base/utils/use_feature_support.ts +28 -0
  15. package/templates/base/webpack.config.cjs +3 -1
  16. package/templates/common/.gitignore.template +5 -6
  17. package/templates/common/.nvmrc +1 -0
  18. package/templates/common/.prettierrc +21 -0
  19. package/templates/common/.vscode/extensions.json +6 -0
  20. package/templates/common/README.md +4 -74
  21. package/templates/common/conf/eslint-general.mjs +303 -0
  22. package/templates/common/conf/eslint-i18n.mjs +41 -0
  23. package/templates/common/conf/eslint-local-i18n-rules/index.mjs +181 -0
  24. package/templates/common/jest.config.mjs +29 -2
  25. package/templates/common/jest.setup.ts +19 -0
  26. package/templates/common/utils/backend/base_backend/create.ts +104 -0
  27. package/templates/common/utils/table_wrapper.ts +477 -0
  28. package/templates/common/utils/tests/table_wrapper.tests.ts +252 -0
  29. package/templates/common/utils/use_add_element.ts +48 -0
  30. package/templates/common/utils/use_feature_support.ts +28 -0
  31. package/templates/common/utils/use_overlay_hook.ts +74 -0
  32. package/templates/common/utils/use_selection_hook.ts +37 -0
  33. package/templates/dam/backend/server.ts +0 -7
  34. package/templates/dam/eslint.config.mjs +6 -275
  35. package/templates/dam/package.json +43 -35
  36. package/templates/dam/scripts/copy-env.ts +10 -0
  37. package/templates/dam/src/app.tsx +2 -135
  38. package/templates/dam/webpack.config.cjs +3 -1
  39. package/templates/gen_ai/README.md +1 -1
  40. package/templates/gen_ai/backend/routers/image.ts +3 -3
  41. package/templates/gen_ai/backend/server.ts +0 -7
  42. package/templates/gen_ai/eslint.config.mjs +5 -277
  43. package/templates/gen_ai/package.json +46 -38
  44. package/templates/gen_ai/scripts/copy-env.ts +10 -0
  45. package/templates/gen_ai/src/api/api.ts +1 -39
  46. package/templates/gen_ai/src/app.tsx +16 -10
  47. package/templates/gen_ai/src/components/footer.messages.ts +0 -5
  48. package/templates/gen_ai/src/components/footer.tsx +2 -16
  49. package/templates/gen_ai/src/components/image_grid.tsx +9 -7
  50. package/templates/gen_ai/src/components/index.ts +0 -1
  51. package/templates/gen_ai/src/components/prompt_input.tsx +2 -0
  52. package/templates/gen_ai/src/components/tests/remaining_credit.tests.tsx +43 -0
  53. package/templates/gen_ai/src/context/app_context.tsx +4 -26
  54. package/templates/gen_ai/src/context/context.messages.ts +1 -12
  55. package/templates/gen_ai/src/home.tsx +13 -0
  56. package/templates/gen_ai/src/index.tsx +2 -18
  57. package/templates/gen_ai/src/routes/routes.tsx +2 -2
  58. package/templates/gen_ai/utils/backend/bearer_middleware/bearer_middleware.ts +101 -0
  59. package/templates/gen_ai/utils/backend/bearer_middleware/index.ts +1 -0
  60. package/templates/gen_ai/utils/backend/bearer_middleware/tests/bearer_middleware.tests.ts +192 -0
  61. package/templates/gen_ai/webpack.config.cjs +3 -1
  62. package/templates/hello_world/eslint.config.mjs +5 -277
  63. package/templates/hello_world/package.json +46 -28
  64. package/templates/hello_world/scripts/copy-env.ts +10 -0
  65. package/templates/hello_world/src/app.tsx +25 -3
  66. package/templates/hello_world/src/tests/__snapshots__/app.tests.tsx.snap +45 -0
  67. package/templates/hello_world/src/tests/app.tests.tsx +86 -0
  68. package/templates/hello_world/utils/use_add_element.ts +48 -0
  69. package/templates/hello_world/utils/use_feature_support.ts +28 -0
  70. package/templates/hello_world/webpack.config.cjs +3 -1
  71. package/templates/dam/backend/database/database.ts +0 -42
  72. package/templates/dam/backend/routers/auth.ts +0 -285
  73. package/templates/gen_ai/backend/routers/auth.ts +0 -285
  74. package/templates/gen_ai/src/components/logged_in_status.tsx +0 -44
  75. package/templates/gen_ai/src/services/auth.tsx +0 -31
  76. package/templates/gen_ai/src/services/index.ts +0 -1
  77. /package/templates/{gen_ai → common}/utils/backend/jwt_middleware/index.ts +0 -0
  78. /package/templates/{gen_ai → common}/utils/backend/jwt_middleware/jwt_middleware.ts +0 -0
  79. /package/templates/{gen_ai → common}/utils/backend/jwt_middleware/tests/jwt_middleware.tests.ts +0 -0
@@ -1,11 +1,9 @@
1
- import typescriptEslint from "@typescript-eslint/eslint-plugin";
2
- import jest from "eslint-plugin-jest";
3
- import react from "eslint-plugin-react";
4
- import globals from "globals";
5
1
  import path from "node:path";
6
2
  import { fileURLToPath } from "node:url";
7
3
  import js from "@eslint/js";
8
4
  import { FlatCompat } from "@eslint/eslintrc";
5
+ import general from "./conf/eslint-general.mjs";
6
+ import i18n from "./conf/eslint-i18n.mjs";
9
7
 
10
8
  const __filename = fileURLToPath(import.meta.url);
11
9
  const __dirname = path.dirname(__filename);
@@ -22,8 +20,6 @@ export default [
22
20
  "**/dist",
23
21
  "**/*.d.ts",
24
22
  "**/*.d.tsx",
25
- "**/sdk",
26
- "**/internal",
27
23
  "**/*.config.*",
28
24
  ],
29
25
  },
@@ -34,276 +30,8 @@ export default [
34
30
  "plugin:@typescript-eslint/strict",
35
31
  "plugin:@typescript-eslint/stylistic",
36
32
  "plugin:react/recommended",
37
- "plugin:jest/recommended"
33
+ "plugin:jest/recommended",
38
34
  ),
39
- {
40
- plugins: {
41
- "@typescript-eslint": typescriptEslint,
42
- jest,
43
- react,
44
- },
45
- languageOptions: {
46
- globals: {
47
- ...globals.serviceworker,
48
- ...globals.browser,
49
- },
50
- },
51
- settings: {
52
- react: {
53
- version: "detect",
54
- },
55
- },
56
- rules: {
57
- "@typescript-eslint/no-non-null-assertion": "warn",
58
- "@typescript-eslint/no-empty-function": "off",
59
- "@typescript-eslint/consistent-type-imports": "error",
60
- "@typescript-eslint/no-explicit-any": "warn",
61
- "@typescript-eslint/no-empty-interface": "warn",
62
- "@typescript-eslint/consistent-type-definitions": "off",
63
- "@typescript-eslint/explicit-member-accessibility": [
64
- "error",
65
- {
66
- accessibility: "no-public",
67
- overrides: {
68
- parameterProperties: "off",
69
- },
70
- },
71
- ],
72
- "@typescript-eslint/naming-convention": [
73
- "error",
74
- {
75
- selector: "typeLike",
76
- format: ["PascalCase"],
77
- leadingUnderscore: "allow",
78
- },
79
- ],
80
- "no-invalid-this": "off",
81
- "@typescript-eslint/no-invalid-this": "error",
82
- "@typescript-eslint/no-unused-expressions": [
83
- "error",
84
- {
85
- allowShortCircuit: true,
86
- allowTernary: true,
87
- },
88
- ],
89
- "no-unused-vars": "off",
90
- "@typescript-eslint/no-unused-vars": [
91
- "error",
92
- {
93
- vars: "all",
94
- varsIgnorePattern: "^_",
95
- args: "none",
96
- ignoreRestSiblings: true,
97
- },
98
- ],
99
- "@typescript-eslint/no-require-imports": "error",
100
- "jest/no-restricted-matchers": [
101
- "error",
102
- {
103
- toContainElement:
104
- "toContainElement is not recommended as it encourages testing the internals of the components",
105
- toContainHTML:
106
- "toContainHTML is not recommended as it encourages testing the internals of the components",
107
- toHaveAttribute:
108
- "toHaveAttribute is not recommended as it encourages testing the internals of the components",
109
- toHaveClass:
110
- "toHaveClass is not recommended as it encourages testing the internals of the components",
111
- toHaveStyle:
112
- "toHaveStyle is not recommended as it encourages testing the internals of the components",
113
- },
114
- ],
115
- "react/jsx-curly-brace-presence": [
116
- "error",
117
- {
118
- props: "never",
119
- children: "never",
120
- },
121
- ],
122
- "react/jsx-tag-spacing": [
123
- "error",
124
- {
125
- closingSlash: "never",
126
- beforeSelfClosing: "allow",
127
- afterOpening: "never",
128
- beforeClosing: "allow",
129
- },
130
- ],
131
- "react/self-closing-comp": "error",
132
- "react/no-unescaped-entities": "off",
133
- "react/jsx-uses-react": "off",
134
- "react/react-in-jsx-scope": "off",
135
- "default-case": "error",
136
- eqeqeq: [
137
- "error",
138
- "always",
139
- {
140
- null: "never",
141
- },
142
- ],
143
- "no-caller": "error",
144
- "no-console": "error",
145
- "no-eval": "error",
146
- "no-inner-declarations": "error",
147
- "no-new-wrappers": "error",
148
- "no-restricted-globals": [
149
- "error",
150
- {
151
- name: "fit",
152
- message: "Don't focus tests",
153
- },
154
- {
155
- name: "fdescribe",
156
- message: "Don't focus tests",
157
- },
158
- {
159
- name: "length",
160
- message:
161
- "Undefined length - Did you mean to use window.length instead?",
162
- },
163
- {
164
- name: "name",
165
- message: "Undefined name - Did you mean to use window.name instead?",
166
- },
167
- {
168
- name: "status",
169
- message:
170
- "Undefined status - Did you mean to use window.status instead?",
171
- },
172
- {
173
- name: "spyOn",
174
- message: "Don't use spyOn directly, use jest.spyOn",
175
- },
176
- ],
177
- "no-restricted-properties": [
178
- "error",
179
- {
180
- property: "bind",
181
- message: "Don't o.f.bind(o, ...), use () => o.f(...)",
182
- },
183
- {
184
- object: "ReactDOM",
185
- property: "findDOMNode",
186
- message: "Don't use ReactDOM.findDOMNode() as it is deprecated",
187
- },
188
- ],
189
- "no-restricted-syntax": [
190
- "error",
191
- {
192
- selector: "AccessorProperty, TSAbstractAccessorProperty",
193
- message:
194
- "Accessor property syntax is not allowed, use getter and setters.",
195
- },
196
- {
197
- selector: "PrivateIdentifier",
198
- message:
199
- "Private identifiers are not allowed, use TypeScript private fields.",
200
- },
201
- {
202
- selector:
203
- "JSXOpeningElement[name.name = /^[A-Z]/] > JSXAttribute[name.name = /-/]",
204
- message:
205
- "Passing hyphenated props to custom components is not type-safe. Prefer a camelCased equivalent if available. (See https://github.com/microsoft/TypeScript/issues/55182)",
206
- },
207
- {
208
- selector:
209
- "CallExpression[callee.object.name='window'][callee.property.name='open']",
210
- message:
211
- "Apps are currently not allowed to open popups, or new tabs via browser APIs. Please use `requestOpenExternalUrl` from `@canva/platform` to link to external URLs. To learn more, see https://www.canva.dev/docs/apps/api/platform-request-open-external-url/",
212
- },
213
- ],
214
- "no-return-await": "error",
215
- "no-throw-literal": "error",
216
- "no-undef-init": "error",
217
- "no-var": "error",
218
- "object-shorthand": "error",
219
- "prefer-const": [
220
- "error",
221
- {
222
- destructuring: "all",
223
- },
224
- ],
225
- "prefer-object-spread": "error",
226
- "prefer-rest-params": "error",
227
- "prefer-spread": "error",
228
- radix: "error",
229
- },
230
- },
231
- {
232
- files: ["**/*.tsx"],
233
- rules: {
234
- "react/no-deprecated": "error",
235
- "react/forbid-elements": [
236
- "error",
237
- {
238
- forbid: [
239
- {
240
- element: "video",
241
- message:
242
- "Don't use HTML video directly. Instead, use the App UI Kit <VideoCard /> as this respects users' auto-playing preferences",
243
- },
244
- {
245
- element: "em",
246
- message:
247
- "Don't use <em> to italicize text. Canva's UI fonts don't support italic font style.",
248
- },
249
- {
250
- element: "i",
251
- message:
252
- "Don't use <i> to italicize text. Canva's UI fonts don't support italic font style.",
253
- },
254
- {
255
- element: "iframe",
256
- message:
257
- "Canva Apps aren't allowed to contain iframes. You should either recreate the UI you want to show in the iframe in the app directly, or link to your page via a `<Link>` tag. For more info see https://www.canva.dev/docs/apps/content-security-policy/#what-is-and-isnt-allowed",
258
- },
259
- {
260
- element: "script",
261
- message:
262
- "Script tags are not allowed in Canva SDK Apps. You should import JavaScript modules instead. For more info see https://www.canva.dev/docs/apps/content-security-policy/#what-is-and-isnt-allowed",
263
- },
264
- {
265
- element: "a",
266
- message:
267
- "Don't use <a> tags. Instead, use the <Link> component from the App UI Kit, and remember to open the url via the requestOpenExternalUrl method from @canva/platform.",
268
- },
269
- {
270
- element: "img",
271
- message:
272
- "Have you considered using <ImageCard /> from the App UI Kit instead?",
273
- },
274
- {
275
- element: "embed",
276
- message:
277
- "Have you considered using <EmbedCard /> from the App UI Kit instead?",
278
- },
279
- {
280
- element: "audio",
281
- message:
282
- "Have you considered using <AudioCard /> from the App UI Kit instead?",
283
- },
284
- {
285
- element: "button",
286
- message:
287
- "Rather than using the native HTML <button> element, use the <Button> component from the App UI Kit for consistency and accessibility.",
288
- },
289
- {
290
- element: "input",
291
- message:
292
- "Wherever possible, prefer using the form inputs from the App UI Kit for consistency and accessibility (TextInput, Checkbox, FileInput, etc).",
293
- },
294
- {
295
- element: "base",
296
- message:
297
- "The <base> tag is not supported in Canva Apps. We recommend using hash-based routing. For more on what is and isn't allowed in Canva Apps see https://www.canva.dev/docs/apps/content-security-policy/#what-is-and-isnt-allowed",
298
- },
299
- {
300
- element: "link",
301
- message:
302
- "If you're trying to include a css stylesheet, we recommend importing css using React, or using embedded stylesheets. For more on what is and isn't allowed in Canva Apps see https://www.canva.dev/docs/apps/content-security-policy/#what-is-and-isnt-allowed",
303
- },
304
- ],
305
- },
306
- ],
307
- },
308
- },
35
+ ...general,
36
+ ...i18n,
309
37
  ];
@@ -3,7 +3,7 @@
3
3
  "name": "gen_ai",
4
4
  "description": "An example app demonstrating common patterns that you might use in a generative AI app.",
5
5
  "scripts": {
6
- "extract": "formatjs extract 'src/**/*.{ts,tsx}' --out-file dist/messages_en.json",
6
+ "extract": "formatjs extract \"src/**/*.{ts,tsx}\" --out-file dist/messages_en.json",
7
7
  "build": "webpack --config webpack.config.cjs --mode production && npm run extract",
8
8
  "format": "prettier '**/*.{css,ts,tsx}' --no-config --write",
9
9
  "format:check": "prettier '**/*.{css,ts,tsx}' --no-config --check --ignore-path",
@@ -13,80 +13,88 @@
13
13
  "lint:types": "tsc",
14
14
  "start": "ts-node ./scripts/start/start.ts",
15
15
  "test": "jest --no-cache",
16
- "test:watch": "jest --watchAll"
16
+ "test:watch": "jest --watchAll",
17
+ "postinstall": "ts-node ./scripts/copy-env.ts"
17
18
  },
18
19
  "dependencies": {
19
- "@canva/app-i18n-kit": "^0.0.1-beta.5",
20
- "@canva/app-ui-kit": "^3.8.0",
21
- "@canva/asset": "^1.7.1",
22
- "@canva/design": "^1.10.0",
23
- "@canva/platform": "^1.1.0",
24
- "@canva/user": "^1.0.0",
25
- "cookie-parser": "1.4.6",
20
+ "@canva/app-i18n-kit": "^1.0.2",
21
+ "@canva/app-ui-kit": "^4.4.0",
22
+ "@canva/asset": "^2.1.0",
23
+ "@canva/design": "^2.3.0",
24
+ "@canva/error": "^2.1.0",
25
+ "@canva/platform": "^2.1.0",
26
+ "@canva/user": "^2.1.0",
27
+ "cookie-parser": "1.4.7",
26
28
  "cors": "2.8.5",
27
- "html-react-parser": "5.1.12",
28
- "obscenity": "0.4.0",
29
+ "html-react-parser": "5.2.0",
30
+ "obscenity": "0.4.1",
29
31
  "react": "18.3.1",
30
32
  "react-dom": "18.3.1",
31
- "react-error-boundary": "4.0.13",
32
- "react-intl": "6.6.8",
33
- "react-router-dom": "6.26.1"
33
+ "react-error-boundary": "4.1.2",
34
+ "react-intl": "6.8.7",
35
+ "react-router-dom": "6.28.0"
34
36
  },
35
37
  "devDependencies": {
36
- "@eslint/eslintrc": "3.1.0",
37
- "@eslint/js": "9.9.0",
38
- "@formatjs/cli": "6.2.12",
39
- "@formatjs/ts-transformer": "3.13.14",
38
+ "@eslint/eslintrc": "3.2.0",
39
+ "@eslint/js": "9.16.0",
40
+ "@formatjs/cli": "6.3.14",
41
+ "@formatjs/ts-transformer": "3.13.26",
40
42
  "@ngrok/ngrok": "1.4.1",
43
+ "@pmmmwh/react-refresh-webpack-plugin": "0.5.15",
41
44
  "@svgr/webpack": "8.1.0",
45
+ "@testing-library/react": "16.1.0",
42
46
  "@types/debug": "4.1.12",
43
47
  "@types/express": "4.17.21",
44
- "@types/express-serve-static-core": "4.19.5",
45
- "@types/jest": "29.5.12",
46
- "@types/jsonwebtoken": "9.0.6",
48
+ "@types/express-serve-static-core": "4.19.6",
49
+ "@types/jest": "29.5.14",
50
+ "@types/jsonwebtoken": "9.0.7",
47
51
  "@types/node": "20.10.0",
48
- "@types/node-fetch": "2.6.11",
52
+ "@types/node-fetch": "2.6.12",
49
53
  "@types/node-forge": "1.3.11",
50
54
  "@types/nodemon": "1.19.6",
51
55
  "@types/prompts": "2.4.9",
52
- "@types/react": "18.3.4",
53
- "@types/react-dom": "18.3.0",
56
+ "@types/react": "18.3.12",
57
+ "@types/react-dom": "18.3.1",
54
58
  "@types/webpack-env": "1.18.5",
55
- "@typescript-eslint/eslint-plugin": "8.2.0",
56
- "@typescript-eslint/parser": "8.2.0",
59
+ "@typescript-eslint/eslint-plugin": "8.18.0",
60
+ "@typescript-eslint/parser": "8.18.0",
57
61
  "chalk": "4.1.2",
58
62
  "cli-table3": "0.6.5",
59
63
  "css-loader": "7.1.2",
60
64
  "css-modules-typescript-loader": "4.0.1",
61
- "cssnano": "7.0.5",
62
- "debug": "4.3.6",
63
- "dotenv": "16.4.5",
64
- "eslint": "9.9.0",
65
- "eslint-plugin-jest": "28.8.0",
66
- "eslint-plugin-react": "7.35.0",
65
+ "cssnano": "7.0.6",
66
+ "debug": "4.4.0",
67
+ "dotenv": "16.4.7",
68
+ "eslint": "9.16.0",
69
+ "eslint-plugin-formatjs": "5.2.8",
70
+ "eslint-plugin-jest": "28.9.0",
71
+ "eslint-plugin-react": "7.37.2",
67
72
  "exponential-backoff": "3.1.1",
68
- "express": "4.19.2",
73
+ "express": "4.21.2",
69
74
  "express-basic-auth": "1.2.1",
70
75
  "jest": "29.7.0",
76
+ "jest-css-modules-transform": "4.4.2",
77
+ "jest-environment-jsdom": "29.7.0",
71
78
  "jsonwebtoken": "9.0.2",
72
79
  "jwks-rsa": "3.1.0",
73
- "mini-css-extract-plugin": "2.9.1",
80
+ "mini-css-extract-plugin": "2.9.2",
74
81
  "node-fetch": "3.3.2",
75
82
  "node-forge": "1.3.1",
76
83
  "nodemon": "3.0.1",
77
84
  "postcss-loader": "8.1.1",
78
- "prettier": "3.3.3",
85
+ "prettier": "3.4.2",
79
86
  "prompts": "2.4.2",
87
+ "react-refresh": "0.16.0",
80
88
  "style-loader": "4.0.0",
81
89
  "terser-webpack-plugin": "5.3.10",
82
- "ts-jest": "29.2.4",
90
+ "ts-jest": "29.2.5",
83
91
  "ts-loader": "9.5.1",
84
92
  "ts-node": "10.9.2",
85
93
  "typescript": "5.5.4",
86
94
  "url-loader": "4.1.1",
87
- "webpack": "5.94.0",
95
+ "webpack": "5.97.1",
88
96
  "webpack-cli": "5.1.4",
89
- "webpack-dev-server": "5.0.4",
97
+ "webpack-dev-server": "5.1.0",
90
98
  "yargs": "17.7.2"
91
99
  }
92
100
  }
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+
5
+ const envPath = path.resolve(__dirname, "..", ".env");
6
+ const templatePath = path.resolve(__dirname, "..", ".env.template");
7
+
8
+ if (!fs.existsSync(envPath)) {
9
+ fs.copyFileSync(templatePath, envPath);
10
+ }
@@ -1,4 +1,3 @@
1
- import { auth } from "@canva/user";
2
1
  import { POLLING_INTERVAL_IN_SECONDS } from "src/config";
3
2
 
4
3
  /**
@@ -41,22 +40,12 @@ interface RemainingCreditsResult {
41
40
  credits: number;
42
41
  }
43
42
 
44
- /**
45
- * Represents the result of retrieving the logged in status of the user.
46
- */
47
- export type LoggedInState =
48
- | "authenticated"
49
- | "not_authenticated"
50
- | "checking"
51
- | "error";
52
-
53
43
  const endpoints = {
54
44
  queueImageGeneration: "/api/queue-image-generation",
55
45
  getImageGenerationJobStatus: "/api/job-status",
56
46
  cancelImageGenerationJob: "/api/job-status/cancel",
57
47
  getRemainingCredits: "/api/credits",
58
48
  purchaseCredits: "/api/purchase-credits",
59
- getLoggedInStatus: "/api/authentication/status",
60
49
  };
61
50
 
62
51
  /**
@@ -172,42 +161,15 @@ export const purchaseCredits = async (): Promise<RemainingCreditsResult> => {
172
161
  };
173
162
 
174
163
  /**
175
- * Send a request to an endpoint that checks if the user is authenticated.
176
- * This is example code, intended to convey the basic idea. When implementing this in your app, you might want more advanced checks.
177
- *
178
- * Note: You must register the provided endpoint via the Developer Portal.
179
- */
180
- export const checkAuthenticationStatus = async (): Promise<LoggedInState> => {
181
- try {
182
- const url = new URL(endpoints.getLoggedInStatus, BACKEND_HOST);
183
- const result: { isAuthenticated: string } = await sendRequest(url, {
184
- method: "POST",
185
- });
186
-
187
- if (result?.isAuthenticated) {
188
- return "authenticated";
189
- } else {
190
- return "not_authenticated";
191
- }
192
- } catch (error) {
193
- // eslint-disable-next-line no-console
194
- console.error(error);
195
- return "error";
196
- }
197
- };
198
-
199
- /**
200
- * Sends a request to the specified URL with authorization headers.
164
+ * Sends a request to the specified URL.
201
165
  * @param {URL} url - The URL to send the request to.
202
166
  * @param {RequestInit} [options] - Optional fetch options to be passed to the fetch function.
203
167
  * @returns {Promise<Object>} - A promise that resolves to the response body.
204
168
  */
205
169
  const sendRequest = async <T>(url: URL, options?: RequestInit): Promise<T> => {
206
- const token = await auth.getCanvaUserToken();
207
170
  const res = await fetch(url, {
208
171
  headers: {
209
172
  ...options?.headers,
210
- Authorization: `Bearer ${token}`,
211
173
  },
212
174
  ...options,
213
175
  });
@@ -1,13 +1,19 @@
1
- import { Outlet } from "react-router-dom";
2
- import { Rows } from "@canva/app-ui-kit";
3
- import { Footer } from "./components";
4
- import * as styles from "styles/components.css";
1
+ import { createHashRouter, RouterProvider } from "react-router-dom";
2
+ import { ContextProvider } from "./context";
3
+ import { routes } from "./routes";
4
+ import { AppI18nProvider } from "@canva/app-i18n-kit";
5
+ import { AppUiProvider } from "@canva/app-ui-kit";
6
+ import { ErrorBoundary } from "react-error-boundary";
7
+ import { ErrorPage } from "./pages";
5
8
 
6
9
  export const App = () => (
7
- <div className={styles.scrollContainer}>
8
- <Rows spacing="3u">
9
- <Outlet />
10
- <Footer />
11
- </Rows>
12
- </div>
10
+ <AppI18nProvider>
11
+ <AppUiProvider>
12
+ <ErrorBoundary fallback={<ErrorPage />}>
13
+ <ContextProvider>
14
+ <RouterProvider router={createHashRouter(routes)} />
15
+ </ContextProvider>
16
+ </ErrorBoundary>
17
+ </AppUiProvider>
18
+ </AppI18nProvider>
13
19
  );
@@ -11,11 +11,6 @@ export const FooterMessages = defineMessages({
11
11
  defaultMessage: "Generate image",
12
12
  description: "A button label to generate an image from a prompt",
13
13
  },
14
- signUpOrLogin: {
15
- defaultMessage: "Sign up or log in to generate",
16
- description:
17
- "A button label to sign up or log in, before the user is able to generate an image",
18
- },
19
14
  purchaseMoreCredits: {
20
15
  defaultMessage: "Purchase more credits",
21
16
  description:
@@ -1,11 +1,10 @@
1
1
  import { useNavigate, useLocation } from "react-router-dom";
2
2
  import { Rows, Button } from "@canva/app-ui-kit";
3
3
  import { queueImageGeneration, purchaseCredits } from "src/api";
4
- import { LoggedInStatus, RemainingCredits } from "src/components";
4
+ import { RemainingCredits } from "src/components";
5
5
  import { NUMBER_OF_IMAGES_TO_GENERATE } from "src/config";
6
6
  import { useAppContext } from "src/context";
7
7
  import { Paths } from "src/routes";
8
- import { useAuth } from "src/services";
9
8
  import { getObsceneWords } from "src/utils";
10
9
  import { useIntl } from "react-intl";
11
10
  import { FooterMessages as Messages } from "./footer.messages";
@@ -13,10 +12,8 @@ import { FooterMessages as Messages } from "./footer.messages";
13
12
  export const Footer = () => {
14
13
  const navigate = useNavigate();
15
14
  const { pathname } = useLocation();
16
- const { requestAuthentication } = useAuth();
17
15
  const isRootRoute = pathname === Paths.HOME;
18
16
  const {
19
- loggedInState,
20
17
  setAppError,
21
18
  promptInput,
22
19
  setPromptInput,
@@ -86,10 +83,6 @@ export const Footer = () => {
86
83
  navigate(Paths.RESULTS);
87
84
  };
88
85
 
89
- const onSignUpOrLogInClick = async () => {
90
- await requestAuthentication();
91
- };
92
-
93
86
  const onPurchaseMoreCredits = async () => {
94
87
  const { credits } = await purchaseCredits();
95
88
 
@@ -110,17 +103,11 @@ export const Footer = () => {
110
103
  : intl.formatMessage(Messages.generateAgain),
111
104
  visible: hasRemainingCredits,
112
105
  },
113
- {
114
- variant: "primary" as const,
115
- onClick: onSignUpOrLogInClick,
116
- value: intl.formatMessage(Messages.signUpOrLogin),
117
- visible: loggedInState === "not_authenticated" && !hasRemainingCredits,
118
- },
119
106
  {
120
107
  variant: "primary" as const,
121
108
  onClick: onPurchaseMoreCredits,
122
109
  value: intl.formatMessage(Messages.purchaseMoreCredits),
123
- visible: loggedInState === "authenticated" && !hasRemainingCredits,
110
+ visible: !hasRemainingCredits,
124
111
  },
125
112
  {
126
113
  variant: "secondary" as const,
@@ -151,7 +138,6 @@ export const Footer = () => {
151
138
  ),
152
139
  )}
153
140
  <RemainingCredits />
154
- <LoggedInStatus />
155
141
  </Rows>
156
142
  );
157
143
  };
@@ -1,23 +1,24 @@
1
1
  import { Grid, ImageCard, Rows, Text } from "@canva/app-ui-kit";
2
2
  import type { QueuedImage } from "@canva/asset";
3
3
  import { upload } from "@canva/asset";
4
- import { addNativeElement, ui } from "@canva/design";
4
+ import { addElementAtPoint, ui } from "@canva/design";
5
5
  import type { ImageType } from "src/api";
6
6
  import { useAppContext } from "src/context";
7
7
  import * as styles from "styles/utils.css";
8
- import { FormattedMessage } from "react-intl";
8
+ import { FormattedMessage, useIntl } from "react-intl";
9
9
 
10
10
  const THUMBNAIL_HEIGHT = 150;
11
11
 
12
12
  const uploadImage = async (image: ImageType): Promise<QueuedImage> => {
13
13
  // Upload the image using @canva/asset.
14
14
  const queuedImage = await upload({
15
- type: "IMAGE",
15
+ type: "image",
16
16
  mimeType: "image/jpeg",
17
17
  thumbnailUrl: image.thumbnail.url,
18
18
  url: image.fullsize.url,
19
19
  width: image.fullsize.width,
20
20
  height: image.fullsize.height,
21
+ aiDisclosure: "app_generated",
21
22
  });
22
23
 
23
24
  return queuedImage;
@@ -34,8 +35,8 @@ export const ImageGrid = () => {
34
35
  try {
35
36
  parentNode?.classList.add(styles.hidden);
36
37
 
37
- await ui.startDrag(event, {
38
- type: "IMAGE",
38
+ await ui.startDragToPoint(event, {
39
+ type: "image",
39
40
  resolveImageRef: () => uploadImage(image),
40
41
  previewUrl: image.thumbnail.url,
41
42
  previewSize: {
@@ -55,8 +56,9 @@ export const ImageGrid = () => {
55
56
  const onImageClick = async (image: ImageType) => {
56
57
  const queuedImage = await uploadImage(image);
57
58
 
58
- await addNativeElement({
59
- type: "IMAGE",
59
+ await addElementAtPoint({
60
+ type: "image",
61
+ altText: { text: image.label, decorative: false },
60
62
  ref: queuedImage.ref,
61
63
  });
62
64
  };