@canva/cli 1.0.0 → 1.1.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 (40) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +1 -1
  3. package/cli.js +473 -463
  4. package/lib/cjs/index.cjs +3 -3
  5. package/lib/esm/index.mjs +3 -3
  6. package/lib/index.d.ts +56 -0
  7. package/package.json +5 -2
  8. package/templates/base/package.json +9 -9
  9. package/templates/base/scripts/start/app_runner.ts +1 -1
  10. package/templates/common/README.md +2 -2
  11. package/templates/common/jest.setup.ts +17 -4
  12. package/templates/dam/package.json +10 -10
  13. package/templates/dam/scripts/start/app_runner.ts +1 -1
  14. package/templates/data_connector/README.md +1 -1
  15. package/templates/data_connector/package.json +11 -11
  16. package/templates/data_connector/scripts/start/app_runner.ts +1 -1
  17. package/templates/data_connector/src/api/data_sources/designs.tsx +1 -1
  18. package/templates/data_connector/src/api/data_sources/templates.tsx +1 -1
  19. package/templates/data_connector/src/components/header.tsx +1 -1
  20. package/templates/data_connector/src/context/use_app_context.ts +2 -2
  21. package/templates/data_connector/src/entrypoint.tsx +1 -1
  22. package/templates/data_connector/src/pages/error.tsx +1 -1
  23. package/templates/data_connector/src/pages/login.tsx +1 -1
  24. package/templates/data_connector/src/paths.ts +7 -0
  25. package/templates/data_connector/src/routes/protected_route.tsx +1 -1
  26. package/templates/data_connector/src/routes/routes.tsx +5 -9
  27. package/templates/gen_ai/package.json +12 -12
  28. package/templates/gen_ai/scripts/start/app_runner.ts +1 -1
  29. package/templates/gen_ai/src/components/footer.tsx +16 -3
  30. package/templates/gen_ai/src/components/loading_results.tsx +1 -1
  31. package/templates/gen_ai/src/components/prompt_input.tsx +1 -1
  32. package/templates/gen_ai/src/components/remaining_credits.tsx +28 -20
  33. package/templates/gen_ai/src/components/report_box.tsx +1 -1
  34. package/templates/gen_ai/src/components/tests/remaining_credit.tests.tsx +5 -1
  35. package/templates/gen_ai/src/context/use_app_context.ts +2 -2
  36. package/templates/gen_ai/src/pages/error.tsx +1 -1
  37. package/templates/gen_ai/src/paths.ts +4 -0
  38. package/templates/gen_ai/src/routes/routes.tsx +4 -6
  39. package/templates/hello_world/package.json +10 -10
  40. package/templates/hello_world/scripts/start/app_runner.ts +1 -1
@@ -33,23 +33,23 @@
33
33
  "react-intl": "6.8.7"
34
34
  },
35
35
  "devDependencies": {
36
- "@canva/app-eslint-plugin": "^1.0.0-beta.3",
36
+ "@canva/app-eslint-plugin": "^1.0.0-beta.5",
37
37
  "@canva/cli": ">= 0.0.1-beta.13",
38
38
  "@formatjs/cli": "6.7.2",
39
39
  "@formatjs/ts-transformer": "3.14.0",
40
- "@ngrok/ngrok": "1.5.1",
40
+ "@ngrok/ngrok": "1.5.2",
41
41
  "@pmmmwh/react-refresh-webpack-plugin": "0.6.1",
42
42
  "@svgr/webpack": "8.1.0",
43
43
  "@testing-library/react": "16.3.0",
44
44
  "@types/cors": "2.8.19",
45
45
  "@types/debug": "4.1.12",
46
46
  "@types/express": "4.17.21",
47
- "@types/express-serve-static-core": "5.0.6",
47
+ "@types/express-serve-static-core": "5.0.7",
48
48
  "@types/jest": "29.5.14",
49
49
  "@types/jsonwebtoken": "9.0.10",
50
50
  "@types/node": "20.19.2",
51
- "@types/node-fetch": "2.6.12",
52
- "@types/node-forge": "1.3.11",
51
+ "@types/node-fetch": "2.6.13",
52
+ "@types/node-forge": "1.3.14",
53
53
  "@types/nodemon": "1.19.6",
54
54
  "@types/prompts": "2.4.9",
55
55
  "@types/react": "18.3.12",
@@ -59,7 +59,7 @@
59
59
  "cli-table3": "0.6.5",
60
60
  "css-loader": "7.1.2",
61
61
  "css-modules-typescript-loader": "4.0.1",
62
- "cssnano": "7.0.7",
62
+ "cssnano": "7.1.1",
63
63
  "debug": "4.4.1",
64
64
  "dotenv": "16.6.0",
65
65
  "exponential-backoff": "3.1.2",
@@ -70,7 +70,7 @@
70
70
  "jest-environment-jsdom": "29.7.0",
71
71
  "jsonwebtoken": "9.0.2",
72
72
  "jwks-rsa": "3.2.0",
73
- "mini-css-extract-plugin": "2.9.2",
73
+ "mini-css-extract-plugin": "2.9.4",
74
74
  "node-fetch": "3.3.2",
75
75
  "node-forge": "1.3.1",
76
76
  "nodemon": "3.0.1",
@@ -82,10 +82,10 @@
82
82
  "style-loader": "4.0.0",
83
83
  "terser-webpack-plugin": "5.3.14",
84
84
  "tree-kill": "1.2.2",
85
- "ts-jest": "29.4.0",
86
- "ts-loader": "9.5.2",
85
+ "ts-jest": "29.4.1",
86
+ "ts-loader": "9.5.4",
87
87
  "ts-node": "10.9.2",
88
- "typescript": "5.8.2",
88
+ "typescript": "5.9.2",
89
89
  "url-loader": "4.1.1",
90
90
  "webpack": "5.99.9",
91
91
  "webpack-cli": "6.0.1",
@@ -4,7 +4,7 @@ import * as ngrok from "@ngrok/ngrok";
4
4
  import * as chalk from "chalk";
5
5
  import * as Table from "cli-table3";
6
6
  import * as nodemon from "nodemon";
7
- import * as open from "open";
7
+ import open from "open";
8
8
  import * as webpack from "webpack";
9
9
  import * as WebpackDevServer from "webpack-dev-server";
10
10
  import { buildConfig } from "../../webpack.config";
@@ -45,7 +45,7 @@ To run it and authenticate with the Canva Connect API via OAuth you must first c
45
45
 
46
46
  - If not already handled by the Canva CLI, you need to create an app.
47
47
  Go to [Your Apps](https://www.canva.com/developers/apps) and create an app.
48
- - On the **Configuration** page, enable the `Data Connector` intent.
48
+ - On the **Compatibility** page, enable the `Data Connector` intent.
49
49
 
50
50
  ### 1. Set up a Connect API Integration
51
51
 
@@ -31,24 +31,24 @@
31
31
  "react-dom": "18.3.1",
32
32
  "react-error-boundary": "6.0.0",
33
33
  "react-intl": "6.8.7",
34
- "react-router-dom": "7.6.3"
34
+ "react-router-dom": "7.8.2"
35
35
  },
36
36
  "devDependencies": {
37
- "@canva/app-eslint-plugin": "^1.0.0-beta.3",
37
+ "@canva/app-eslint-plugin": "^1.0.0-beta.5",
38
38
  "@canva/cli": ">= 0.0.1-beta.13",
39
39
  "@formatjs/cli": "6.7.2",
40
40
  "@formatjs/ts-transformer": "3.14.0",
41
- "@ngrok/ngrok": "1.5.1",
41
+ "@ngrok/ngrok": "1.5.2",
42
42
  "@pmmmwh/react-refresh-webpack-plugin": "0.6.1",
43
43
  "@svgr/webpack": "8.1.0",
44
44
  "@testing-library/react": "16.3.0",
45
45
  "@types/express": "4.17.21",
46
- "@types/express-serve-static-core": "5.0.6",
46
+ "@types/express-serve-static-core": "5.0.7",
47
47
  "@types/jest": "29.5.14",
48
48
  "@types/jsonwebtoken": "9.0.10",
49
49
  "@types/node": "20.19.2",
50
- "@types/node-fetch": "2.6.12",
51
- "@types/node-forge": "1.3.11",
50
+ "@types/node-fetch": "2.6.13",
51
+ "@types/node-forge": "1.3.14",
52
52
  "@types/nodemon": "1.19.6",
53
53
  "@types/react": "18.3.12",
54
54
  "@types/react-dom": "18.3.1",
@@ -57,7 +57,7 @@
57
57
  "cli-table3": "0.6.5",
58
58
  "css-loader": "7.1.2",
59
59
  "css-modules-typescript-loader": "4.0.1",
60
- "cssnano": "7.0.7",
60
+ "cssnano": "7.1.1",
61
61
  "debug": "4.4.1",
62
62
  "dotenv": "16.6.0",
63
63
  "express": "4.21.2",
@@ -67,7 +67,7 @@
67
67
  "jest-environment-jsdom": "29.7.0",
68
68
  "jsonwebtoken": "9.0.2",
69
69
  "jwks-rsa": "3.2.0",
70
- "mini-css-extract-plugin": "2.9.2",
70
+ "mini-css-extract-plugin": "2.9.4",
71
71
  "node-fetch": "3.3.2",
72
72
  "node-forge": "1.3.1",
73
73
  "nodemon": "3.0.1",
@@ -78,10 +78,10 @@
78
78
  "style-loader": "4.0.0",
79
79
  "terser-webpack-plugin": "5.3.14",
80
80
  "tree-kill": "1.2.2",
81
- "ts-jest": "29.4.0",
82
- "ts-loader": "9.5.2",
81
+ "ts-jest": "29.4.1",
82
+ "ts-loader": "9.5.4",
83
83
  "ts-node": "10.9.2",
84
- "typescript": "5.8.2",
84
+ "typescript": "5.9.2",
85
85
  "url-loader": "4.1.1",
86
86
  "webpack": "5.99.9",
87
87
  "webpack-cli": "6.0.1",
@@ -4,7 +4,7 @@ import * as ngrok from "@ngrok/ngrok";
4
4
  import * as chalk from "chalk";
5
5
  import * as Table from "cli-table3";
6
6
  import * as nodemon from "nodemon";
7
- import * as open from "open";
7
+ import open from "open";
8
8
  import * as webpack from "webpack";
9
9
  import * as WebpackDevServer from "webpack-dev-server";
10
10
  import { buildConfig } from "../../webpack.config";
@@ -13,7 +13,7 @@ import {
13
13
  import { SearchFilter } from "src/components/inputs/search_filter";
14
14
  import { SelectField } from "src/components/inputs/select_field";
15
15
  import { useAppContext } from "src/context";
16
- import { Paths } from "src/routes";
16
+ import { Paths } from "src/paths";
17
17
  import { dateCell, numberCell, stringCell } from "src/utils";
18
18
  import type { CanvaItemResponse } from "../connect_client";
19
19
  import { DataAPIError, DataSourceHandler } from "../data_source";
@@ -18,7 +18,7 @@ import {
18
18
  import { SearchFilter } from "src/components/inputs/search_filter";
19
19
  import { SelectField } from "src/components/inputs/select_field";
20
20
  import { useAppContext } from "src/context";
21
- import { Paths } from "src/routes";
21
+ import { Paths } from "src/paths";
22
22
  import { dateCell, stringCell } from "src/utils";
23
23
  import type { CanvaItemResponse } from "../connect_client";
24
24
  import { DataAPIError, DataSourceHandler } from "../data_source";
@@ -7,7 +7,7 @@ import {
7
7
  Title,
8
8
  } from "@canva/app-ui-kit";
9
9
  import { useNavigate } from "react-router-dom";
10
- import { Paths } from "src/routes";
10
+ import { Paths } from "src/paths";
11
11
 
12
12
  export const Header = ({
13
13
  title,
@@ -1,6 +1,6 @@
1
1
  import { useContext } from "react";
2
- import type { AppContextType } from ".";
3
- import { AppContext } from ".";
2
+ import type { AppContextType } from "./app_context";
3
+ import { AppContext } from "./app_context";
4
4
 
5
5
  /**
6
6
  * A custom React hook to access the application-wide state and methods provided by the ContextProvider using React Context.
@@ -8,7 +8,7 @@ import type {
8
8
  } from "./api";
9
9
  import { DATA_SOURCES } from "./api/data_sources";
10
10
  import { useAppContext } from "./context";
11
- import { Paths } from "./routes";
11
+ import { Paths } from "./paths";
12
12
  import {
13
13
  isDataRefEmpty,
14
14
  isLaunchedWithError,
@@ -1,7 +1,7 @@
1
1
  import { Button, Rows, Text } from "@canva/app-ui-kit";
2
2
  import { FormattedMessage, useIntl } from "react-intl";
3
3
  import { useNavigate } from "react-router-dom";
4
- import { Paths } from "src/routes";
4
+ import { Paths } from "src/paths";
5
5
  import * as styles from "styles/components.css";
6
6
 
7
7
  /**
@@ -11,9 +11,9 @@ import { defineMessages, FormattedMessage, useIntl } from "react-intl";
11
11
  import { useNavigate } from "react-router-dom";
12
12
  import { scope } from "src/api";
13
13
  import { Header } from "src/components";
14
+ import { Paths } from "src/paths";
14
15
  import * as styles from "styles/components.css";
15
16
  import { useAppContext } from "../context";
16
- import { Paths } from "../routes";
17
17
 
18
18
  export const Login = () => {
19
19
  const intl = useIntl();
@@ -0,0 +1,7 @@
1
+ export enum Paths {
2
+ ENTRYPOINT = "/",
3
+ LOGIN = "/login",
4
+ DATA_SOURCE_SELECTION = "/data-source-selection",
5
+ DATA_SOURCE_CONFIG = "/data-source-config",
6
+ ERRORS = "/errors/:retry",
7
+ }
@@ -1,7 +1,7 @@
1
1
  import { useEffect } from "react";
2
2
  import { useNavigate } from "react-router-dom";
3
+ import { Paths } from "src/paths";
3
4
  import { useAppContext } from "../context";
4
- import { Paths } from "../routes";
5
5
 
6
6
  interface ProtectedRouteProps {
7
7
  children: React.ReactNode;
@@ -1,16 +1,12 @@
1
1
  import { Entrypoint } from "src/entrypoint";
2
2
  import { Home } from "src/home";
3
- import { DataSourceConfig, ErrorPage, Login, SelectSource } from "src/pages";
3
+ import { DataSourceConfig } from "src/pages/data_source_config";
4
+ import { ErrorPage } from "src/pages/error";
5
+ import { Login } from "src/pages/login";
6
+ import { SelectSource } from "src/pages/select_source";
7
+ import { Paths } from "src/paths";
4
8
  import { ProtectedRoute } from "./protected_route";
5
9
 
6
- export enum Paths {
7
- ENTRYPOINT = "/",
8
- LOGIN = "/login",
9
- DATA_SOURCE_SELECTION = "/data-source-selection",
10
- DATA_SOURCE_CONFIG = "/data-source-config",
11
- ERRORS = "/errors/:retry",
12
- }
13
-
14
10
  export const routes = [
15
11
  {
16
12
  path: Paths.ENTRYPOINT,
@@ -27,31 +27,31 @@
27
27
  "@canva/user": "^2.1.1",
28
28
  "cookie-parser": "1.4.7",
29
29
  "cors": "2.8.5",
30
- "html-react-parser": "5.2.5",
30
+ "html-react-parser": "5.2.6",
31
31
  "obscenity": "0.4.4",
32
32
  "react": "18.3.1",
33
33
  "react-dom": "18.3.1",
34
34
  "react-error-boundary": "6.0.0",
35
35
  "react-intl": "6.8.7",
36
- "react-router-dom": "7.6.3"
36
+ "react-router-dom": "7.8.2"
37
37
  },
38
38
  "devDependencies": {
39
- "@canva/app-eslint-plugin": "^1.0.0-beta.3",
39
+ "@canva/app-eslint-plugin": "^1.0.0-beta.5",
40
40
  "@canva/cli": ">= 0.0.1-beta.13",
41
41
  "@formatjs/cli": "6.7.2",
42
42
  "@formatjs/ts-transformer": "3.14.0",
43
- "@ngrok/ngrok": "1.5.1",
43
+ "@ngrok/ngrok": "1.5.2",
44
44
  "@pmmmwh/react-refresh-webpack-plugin": "0.6.1",
45
45
  "@svgr/webpack": "8.1.0",
46
46
  "@testing-library/react": "16.3.0",
47
47
  "@types/debug": "4.1.12",
48
48
  "@types/express": "4.17.21",
49
- "@types/express-serve-static-core": "5.0.6",
49
+ "@types/express-serve-static-core": "5.0.7",
50
50
  "@types/jest": "29.5.14",
51
51
  "@types/jsonwebtoken": "9.0.10",
52
52
  "@types/node": "20.19.2",
53
- "@types/node-fetch": "2.6.12",
54
- "@types/node-forge": "1.3.11",
53
+ "@types/node-fetch": "2.6.13",
54
+ "@types/node-forge": "1.3.14",
55
55
  "@types/nodemon": "1.19.6",
56
56
  "@types/prompts": "2.4.9",
57
57
  "@types/react": "18.3.12",
@@ -63,7 +63,7 @@
63
63
  "cli-table3": "0.6.5",
64
64
  "css-loader": "7.1.2",
65
65
  "css-modules-typescript-loader": "4.0.1",
66
- "cssnano": "7.0.7",
66
+ "cssnano": "7.1.1",
67
67
  "debug": "4.4.1",
68
68
  "dotenv": "16.6.0",
69
69
  "exponential-backoff": "3.1.2",
@@ -74,7 +74,7 @@
74
74
  "jest-environment-jsdom": "29.7.0",
75
75
  "jsonwebtoken": "9.0.2",
76
76
  "jwks-rsa": "3.2.0",
77
- "mini-css-extract-plugin": "2.9.2",
77
+ "mini-css-extract-plugin": "2.9.4",
78
78
  "node-fetch": "3.3.2",
79
79
  "node-forge": "1.3.1",
80
80
  "nodemon": "3.0.1",
@@ -86,10 +86,10 @@
86
86
  "style-loader": "4.0.0",
87
87
  "terser-webpack-plugin": "5.3.14",
88
88
  "tree-kill": "1.2.2",
89
- "ts-jest": "29.4.0",
90
- "ts-loader": "9.5.2",
89
+ "ts-jest": "29.4.1",
90
+ "ts-loader": "9.5.4",
91
91
  "ts-node": "10.9.2",
92
- "typescript": "5.8.2",
92
+ "typescript": "5.9.2",
93
93
  "url-loader": "4.1.1",
94
94
  "webpack": "5.99.9",
95
95
  "webpack-cli": "6.0.1",
@@ -4,7 +4,7 @@ import * as ngrok from "@ngrok/ngrok";
4
4
  import * as chalk from "chalk";
5
5
  import * as Table from "cli-table3";
6
6
  import * as nodemon from "nodemon";
7
- import * as open from "open";
7
+ import open from "open";
8
8
  import * as webpack from "webpack";
9
9
  import * as WebpackDevServer from "webpack-dev-server";
10
10
  import { buildConfig } from "../../webpack.config";
@@ -1,11 +1,12 @@
1
1
  import { Button, Rows } from "@canva/app-ui-kit";
2
+ import { getPlatformInfo } from "@canva/platform";
2
3
  import { useIntl } from "react-intl";
3
4
  import { useLocation, useNavigate } from "react-router-dom";
4
5
  import { purchaseCredits, queueImageGeneration } from "src/api";
5
6
  import { RemainingCredits } from "src/components";
6
7
  import { NUMBER_OF_IMAGES_TO_GENERATE } from "src/config";
7
8
  import { useAppContext } from "src/context";
8
- import { Paths } from "src/routes";
9
+ import { Paths } from "src/paths";
9
10
  import { getObsceneWords } from "src/utils";
10
11
  import { FooterMessages as Messages } from "./footer.messages";
11
12
 
@@ -13,6 +14,7 @@ export const Footer = () => {
13
14
  const navigate = useNavigate();
14
15
  const { pathname } = useLocation();
15
16
  const isRootRoute = pathname === Paths.HOME;
17
+ const platformInfo = getPlatformInfo();
16
18
  const {
17
19
  setAppError,
18
20
  promptInput,
@@ -84,8 +86,11 @@ export const Footer = () => {
84
86
  };
85
87
 
86
88
  const onPurchaseMoreCredits = async () => {
87
- const { credits } = await purchaseCredits();
89
+ if (!platformInfo.canAcceptPayments) {
90
+ return;
91
+ }
88
92
 
93
+ const { credits } = await purchaseCredits();
89
94
  setRemainingCredits(credits);
90
95
  };
91
96
 
@@ -108,6 +113,12 @@ export const Footer = () => {
108
113
  onClick: onPurchaseMoreCredits,
109
114
  value: intl.formatMessage(Messages.purchaseMoreCredits),
110
115
  visible: !hasRemainingCredits,
116
+ disabled: !platformInfo.canAcceptPayments,
117
+ tooltip: intl.formatMessage({
118
+ defaultMessage: "Payment not available on this platform.",
119
+ description:
120
+ "Tooltip text when payment functionality is not available on the current platform. ",
121
+ }),
111
122
  },
112
123
  {
113
124
  variant: "secondary" as const,
@@ -124,7 +135,7 @@ export const Footer = () => {
124
135
  return (
125
136
  <Rows spacing="1u">
126
137
  {footerButtons.map(
127
- ({ visible, variant, onClick, value }) =>
138
+ ({ visible, variant, onClick, value, disabled, tooltip }) =>
128
139
  visible && (
129
140
  <Button
130
141
  key={value}
@@ -132,6 +143,8 @@ export const Footer = () => {
132
143
  onClick={onClick}
133
144
  loading={loadingApp}
134
145
  stretch={true}
146
+ disabled={disabled}
147
+ tooltipLabel={tooltip}
135
148
  >
136
149
  {value}
137
150
  </Button>
@@ -12,7 +12,7 @@ import { FormattedMessage, useIntl } from "react-intl";
12
12
  import { useNavigate } from "react-router-dom";
13
13
  import { cancelImageGenerationJob, getImageGenerationJobStatus } from "src/api";
14
14
  import { useAppContext } from "src/context";
15
- import { Paths } from "src/routes";
15
+ import { Paths } from "src/paths";
16
16
 
17
17
  const INTERVAL_DURATION_IN_MS = 100;
18
18
  const TOTAL_PROGRESS_PERCENTAGE = 100;
@@ -9,7 +9,7 @@ import { useState } from "react";
9
9
  import { useIntl } from "react-intl";
10
10
  import { useLocation } from "react-router-dom";
11
11
  import { useAppContext } from "src/context";
12
- import { Paths } from "src/routes";
12
+ import { Paths } from "src/paths";
13
13
  import { PromptInputMessages as Messages } from "./prompt_input.messages";
14
14
 
15
15
  // @TODO: Adjust according to your specific requirements.
@@ -1,5 +1,5 @@
1
1
  import { Link, Rows, Text, TextPlaceholder } from "@canva/app-ui-kit";
2
- import { requestOpenExternalUrl } from "@canva/platform";
2
+ import { getPlatformInfo, requestOpenExternalUrl } from "@canva/platform";
3
3
  import { FormattedMessage, useIntl } from "react-intl";
4
4
  import { useAppContext } from "src/context";
5
5
 
@@ -8,6 +8,7 @@ const PURCHASE_URL = "https://example.com";
8
8
 
9
9
  export const RemainingCredits = (): JSX.Element | undefined => {
10
10
  const { remainingCredits, loadingApp } = useAppContext();
11
+ const platformInfo = getPlatformInfo();
11
12
 
12
13
  const RemainingCreditsText = () => {
13
14
  if (loadingApp) {
@@ -50,25 +51,32 @@ export const RemainingCredits = (): JSX.Element | undefined => {
50
51
  <Rows spacing="0">
51
52
  <RemainingCreditsText />
52
53
  <Text alignment="center" size="small">
53
- <FormattedMessage
54
- defaultMessage="Purchase more credits at <link>example.com</link>."
55
- description="A message to prompt the user to purchase more credits. Do not translate <link>example.com</link>."
56
- values={{
57
- link: (chunks) => (
58
- <Link
59
- href={PURCHASE_URL}
60
- requestOpenExternalUrl={() => openExternalUrl(PURCHASE_URL)}
61
- title={intl.formatMessage({
62
- defaultMessage: "Example Co. website",
63
- description:
64
- "A title for a link to the website of Example Co.",
65
- })}
66
- >
67
- {chunks}
68
- </Link>
69
- ),
70
- }}
71
- />
54
+ {platformInfo.canAcceptPayments ? (
55
+ <FormattedMessage
56
+ defaultMessage="Purchase more credits at <link>example.com</link>."
57
+ description="A message to prompt the user to purchase more credits. Do not translate <link>example.com</link>."
58
+ values={{
59
+ link: (chunks) => (
60
+ <Link
61
+ href={PURCHASE_URL}
62
+ requestOpenExternalUrl={() => openExternalUrl(PURCHASE_URL)}
63
+ tooltipLabel={intl.formatMessage({
64
+ defaultMessage: "Example Co. website",
65
+ description:
66
+ "A title for a link to the website of Example Co.",
67
+ })}
68
+ >
69
+ {chunks}
70
+ </Link>
71
+ ),
72
+ }}
73
+ />
74
+ ) : (
75
+ <FormattedMessage
76
+ defaultMessage="Open this app in a web browser to learn how to purchase more credits."
77
+ description="A message shown when platform doesn't allow external payment links"
78
+ />
79
+ )}
72
80
  </Text>
73
81
  </Rows>
74
82
  );
@@ -34,7 +34,7 @@ export const ReportBox = (): JSX.Element => {
34
34
  <Link
35
35
  href={REPORT_URL}
36
36
  requestOpenExternalUrl={() => openExternalUrl(REPORT_URL)}
37
- title={intl.formatMessage({
37
+ tooltipLabel={intl.formatMessage({
38
38
  defaultMessage: "Report content",
39
39
  description:
40
40
  "A title for a link to report AI generated content that is problematic",
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable formatjs/no-literal-string-in-jsx */
2
2
  import { TestAppI18nProvider } from "@canva/app-i18n-kit";
3
3
  import { TestAppUiProvider } from "@canva/app-ui-kit";
4
- import { requestOpenExternalUrl } from "@canva/platform";
4
+ import { getPlatformInfo, requestOpenExternalUrl } from "@canva/platform";
5
5
  import type { RenderResult } from "@testing-library/react";
6
6
  import { fireEvent, render } from "@testing-library/react";
7
7
  import React from "react";
@@ -20,9 +20,13 @@ function renderInTestProvider(node: React.ReactNode): RenderResult {
20
20
  // For more information on testing with the Canva Apps SDK, see https://www.canva.dev/docs/apps/testing/
21
21
  describe("Remaining Credit Tests", () => {
22
22
  const mockRequestOpenExternalUrl = jest.mocked(requestOpenExternalUrl);
23
+ const mockGetPlatformInfo = jest.mocked(getPlatformInfo);
23
24
 
24
25
  beforeEach(() => {
25
26
  jest.resetAllMocks();
27
+ mockGetPlatformInfo.mockReturnValue({
28
+ canAcceptPayments: true,
29
+ });
26
30
  });
27
31
 
28
32
  it("should call requestOpenExternalUrl when the link is clicked", () => {
@@ -1,6 +1,6 @@
1
1
  import { useContext } from "react";
2
- import type { AppContextType } from ".";
3
- import { AppContext } from ".";
2
+ import type { AppContextType } from "./app_context";
3
+ import { AppContext } from "./app_context";
4
4
 
5
5
  /**
6
6
  * A custom React hook to access the application-wide state and methods provided by the ContextProvider using React Context.
@@ -2,7 +2,7 @@ import { Button, Rows, Text } from "@canva/app-ui-kit";
2
2
  import { FormattedMessage, useIntl } from "react-intl";
3
3
  import { useNavigate } from "react-router-dom";
4
4
  import { useAppContext } from "src/context";
5
- import { Paths } from "src/routes";
5
+ import { Paths } from "src/paths";
6
6
  import * as styles from "styles/components.css";
7
7
 
8
8
  /**
@@ -0,0 +1,4 @@
1
+ export enum Paths {
2
+ HOME = "/",
3
+ RESULTS = "/results",
4
+ }
@@ -1,10 +1,8 @@
1
1
  import { Home } from "src/home";
2
- import { ErrorPage, GeneratePage, ResultsPage } from "src/pages";
3
-
4
- export enum Paths {
5
- HOME = "/",
6
- RESULTS = "/results",
7
- }
2
+ import { ErrorPage } from "src/pages/error";
3
+ import { GeneratePage } from "src/pages/generate";
4
+ import { ResultsPage } from "src/pages/results";
5
+ import { Paths } from "src/paths";
8
6
 
9
7
  export const routes = [
10
8
  {
@@ -31,21 +31,21 @@
31
31
  "react-intl": "6.8.7"
32
32
  },
33
33
  "devDependencies": {
34
- "@canva/app-eslint-plugin": "^1.0.0-beta.3",
34
+ "@canva/app-eslint-plugin": "^1.0.0-beta.5",
35
35
  "@canva/cli": ">= 0.0.1-beta.13",
36
36
  "@formatjs/cli": "6.7.2",
37
37
  "@formatjs/ts-transformer": "3.14.0",
38
- "@ngrok/ngrok": "1.5.1",
38
+ "@ngrok/ngrok": "1.5.2",
39
39
  "@pmmmwh/react-refresh-webpack-plugin": "0.6.1",
40
40
  "@svgr/webpack": "8.1.0",
41
41
  "@testing-library/react": "16.3.0",
42
42
  "@types/express": "4.17.21",
43
- "@types/express-serve-static-core": "5.0.6",
43
+ "@types/express-serve-static-core": "5.0.7",
44
44
  "@types/jest": "29.5.14",
45
45
  "@types/jsonwebtoken": "9.0.10",
46
46
  "@types/node": "20.19.2",
47
- "@types/node-fetch": "2.6.12",
48
- "@types/node-forge": "1.3.11",
47
+ "@types/node-fetch": "2.6.13",
48
+ "@types/node-forge": "1.3.14",
49
49
  "@types/nodemon": "1.19.6",
50
50
  "@types/react": "18.3.12",
51
51
  "@types/react-dom": "18.3.1",
@@ -54,7 +54,7 @@
54
54
  "cli-table3": "0.6.5",
55
55
  "css-loader": "7.1.2",
56
56
  "css-modules-typescript-loader": "4.0.1",
57
- "cssnano": "7.0.7",
57
+ "cssnano": "7.1.1",
58
58
  "debug": "4.4.1",
59
59
  "dotenv": "16.6.0",
60
60
  "express": "4.21.2",
@@ -64,7 +64,7 @@
64
64
  "jest-environment-jsdom": "29.7.0",
65
65
  "jsonwebtoken": "9.0.2",
66
66
  "jwks-rsa": "3.2.0",
67
- "mini-css-extract-plugin": "2.9.2",
67
+ "mini-css-extract-plugin": "2.9.4",
68
68
  "node-fetch": "3.3.2",
69
69
  "node-forge": "1.3.1",
70
70
  "nodemon": "3.0.1",
@@ -75,10 +75,10 @@
75
75
  "style-loader": "4.0.0",
76
76
  "terser-webpack-plugin": "5.3.14",
77
77
  "tree-kill": "1.2.2",
78
- "ts-jest": "29.4.0",
79
- "ts-loader": "9.5.2",
78
+ "ts-jest": "29.4.1",
79
+ "ts-loader": "9.5.4",
80
80
  "ts-node": "10.9.2",
81
- "typescript": "5.8.2",
81
+ "typescript": "5.9.2",
82
82
  "url-loader": "4.1.1",
83
83
  "webpack": "5.99.9",
84
84
  "webpack-cli": "6.0.1",