@canva/cli 0.0.1-beta.9 → 1.0.1
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.
- package/README.md +131 -31
- package/cli.js +841 -437
- package/lib/cjs/index.cjs +11 -0
- package/lib/esm/index.mjs +11 -0
- package/package.json +19 -4
- package/templates/base/backend/routers/auth.ts +4 -4
- package/templates/base/backend/routers/oauth.ts +3 -3
- package/templates/base/eslint.config.mjs +2 -25
- package/templates/base/package.json +26 -23
- package/templates/base/scripts/ssl/ssl.ts +1 -1
- package/templates/base/scripts/start/app_runner.ts +42 -5
- package/templates/base/scripts/start/context.ts +12 -6
- package/templates/base/scripts/start/start.ts +12 -1
- package/templates/base/scripts/start/tests/start.tests.ts +61 -0
- package/templates/base/utils/backend/base_backend/create.ts +3 -3
- package/templates/base/utils/backend/bearer_middleware/bearer_middleware.ts +2 -2
- package/templates/base/utils/backend/jwt_middleware/jwt_middleware.ts +4 -4
- package/templates/base/utils/backend/jwt_middleware/tests/jwt_middleware.tests.ts +1 -1
- package/templates/base/utils/use_add_element.ts +1 -1
- package/templates/base/utils/use_feature_support.ts +1 -1
- package/templates/{gen_ai/webpack.config.cjs → base/webpack.config.ts} +50 -68
- package/templates/common/.env.template +3 -3
- package/templates/common/README.md +16 -17
- package/templates/common/jest.config.mjs +1 -1
- package/templates/common/jest.setup.ts +1 -0
- package/templates/common/utils/backend/base_backend/create.ts +3 -3
- package/templates/common/utils/backend/jwt_middleware/jwt_middleware.ts +4 -4
- package/templates/common/utils/backend/jwt_middleware/tests/jwt_middleware.tests.ts +1 -1
- package/templates/common/utils/tests/table_wrapper.tests.ts +1 -1
- package/templates/common/utils/use_add_element.ts +1 -1
- package/templates/common/utils/use_feature_support.ts +1 -1
- package/templates/dam/backend/routers/dam.ts +24 -20
- package/templates/dam/backend/server.ts +1 -1
- package/templates/dam/canva-app.json +16 -0
- package/templates/dam/eslint.config.mjs +2 -28
- package/templates/dam/package.json +38 -41
- package/templates/dam/scripts/ssl/ssl.ts +1 -1
- package/templates/dam/scripts/start/app_runner.ts +42 -5
- package/templates/dam/scripts/start/context.ts +12 -6
- package/templates/dam/scripts/start/start.ts +12 -1
- package/templates/dam/scripts/start/tests/start.tests.ts +61 -0
- package/templates/dam/src/app.tsx +24 -3
- package/templates/dam/src/config.ts +212 -87
- package/templates/dam/src/index.tsx +1 -1
- package/templates/dam/utils/backend/base_backend/create.ts +3 -3
- package/templates/dam/utils/backend/jwt_middleware/jwt_middleware.ts +4 -4
- package/templates/dam/utils/backend/jwt_middleware/tests/jwt_middleware.tests.ts +1 -1
- package/templates/{base/webpack.config.cjs → dam/webpack.config.ts} +50 -68
- package/templates/data_connector/README.md +84 -0
- package/templates/data_connector/canva-app.json +21 -0
- package/templates/data_connector/declarations/declarations.d.ts +29 -0
- package/templates/data_connector/eslint.config.mjs +14 -0
- package/templates/data_connector/package.json +91 -0
- package/templates/data_connector/scripts/ssl/ssl.ts +131 -0
- package/templates/data_connector/scripts/start/app_runner.ts +201 -0
- package/templates/data_connector/scripts/start/context.ts +171 -0
- package/templates/data_connector/scripts/start/start.ts +46 -0
- package/templates/data_connector/scripts/start/tests/start.tests.ts +61 -0
- package/templates/data_connector/src/api/connect_client.ts +6 -0
- package/templates/data_connector/src/api/data_source.ts +96 -0
- package/templates/data_connector/src/api/data_sources/designs.tsx +296 -0
- package/templates/data_connector/src/api/data_sources/index.ts +4 -0
- package/templates/data_connector/src/api/data_sources/templates.tsx +329 -0
- package/templates/data_connector/src/api/fetch_data_table.ts +55 -0
- package/templates/data_connector/src/api/index.ts +4 -0
- package/templates/data_connector/src/api/oauth.ts +8 -0
- package/templates/data_connector/src/api/tests/data_source.test.tsx +99 -0
- package/templates/data_connector/src/app.tsx +20 -0
- package/templates/data_connector/src/components/app_error.tsx +15 -0
- package/templates/data_connector/src/components/footer.tsx +26 -0
- package/templates/data_connector/src/components/header.tsx +40 -0
- package/templates/data_connector/src/components/index.ts +3 -0
- package/templates/data_connector/src/components/inputs/messages.tsx +99 -0
- package/templates/data_connector/src/components/inputs/search_filter.tsx +108 -0
- package/templates/data_connector/src/components/inputs/select_field.tsx +26 -0
- package/templates/data_connector/src/context/app_context.tsx +124 -0
- package/templates/data_connector/src/context/index.ts +2 -0
- package/templates/data_connector/src/context/use_app_context.ts +17 -0
- package/templates/data_connector/src/entrypoint.tsx +70 -0
- package/templates/data_connector/src/home.tsx +21 -0
- package/templates/data_connector/src/index.tsx +68 -0
- package/templates/data_connector/src/pages/data_source_config.tsx +9 -0
- package/templates/data_connector/src/pages/error.tsx +37 -0
- package/templates/data_connector/src/pages/index.ts +4 -0
- package/templates/data_connector/src/pages/login.tsx +145 -0
- package/templates/data_connector/src/pages/select_source.tsx +24 -0
- package/templates/data_connector/src/paths.ts +7 -0
- package/templates/data_connector/src/routes/index.ts +2 -0
- package/templates/data_connector/src/routes/protected_route.tsx +25 -0
- package/templates/data_connector/src/routes/routes.tsx +42 -0
- package/templates/data_connector/src/utils/data_params.ts +17 -0
- package/templates/data_connector/src/utils/data_table.ts +115 -0
- package/templates/data_connector/src/utils/fetch_result.ts +36 -0
- package/templates/data_connector/src/utils/index.ts +2 -0
- package/templates/data_connector/src/utils/tests/data_table.test.ts +133 -0
- package/templates/data_connector/styles/components.css +38 -0
- package/templates/data_connector/tsconfig.json +54 -0
- package/templates/{dam/webpack.config.cjs → data_connector/webpack.config.ts} +50 -68
- package/templates/gen_ai/backend/routers/image.ts +8 -8
- package/templates/gen_ai/backend/server.ts +1 -1
- package/templates/gen_ai/canva-app.json +20 -0
- package/templates/gen_ai/eslint.config.mjs +2 -25
- package/templates/gen_ai/package.json +42 -43
- package/templates/gen_ai/scripts/ssl/ssl.ts +1 -1
- package/templates/gen_ai/scripts/start/app_runner.ts +42 -5
- package/templates/gen_ai/scripts/start/context.ts +12 -6
- package/templates/gen_ai/scripts/start/start.ts +12 -1
- package/templates/gen_ai/scripts/start/tests/start.tests.ts +61 -0
- package/templates/gen_ai/src/app.tsx +3 -3
- package/templates/gen_ai/src/components/footer.tsx +20 -7
- package/templates/gen_ai/src/components/image_grid.tsx +1 -1
- package/templates/gen_ai/src/components/loading_results.tsx +8 -8
- package/templates/gen_ai/src/components/prompt_input.tsx +4 -4
- package/templates/gen_ai/src/components/remaining_credits.tsx +29 -21
- package/templates/gen_ai/src/components/report_box.tsx +2 -2
- package/templates/gen_ai/src/components/tests/remaining_credit.tests.tsx +6 -2
- package/templates/gen_ai/src/context/use_app_context.ts +2 -2
- package/templates/gen_ai/src/home.tsx +2 -2
- package/templates/gen_ai/src/pages/error.tsx +3 -3
- package/templates/gen_ai/src/pages/results.tsx +1 -1
- package/templates/gen_ai/src/paths.ts +4 -0
- package/templates/gen_ai/src/routes/routes.tsx +4 -6
- package/templates/gen_ai/utils/backend/base_backend/create.ts +3 -3
- package/templates/gen_ai/utils/backend/bearer_middleware/bearer_middleware.ts +2 -2
- package/templates/{hello_world/webpack.config.cjs → gen_ai/webpack.config.ts} +50 -68
- package/templates/hello_world/canva-app.json +16 -0
- package/templates/hello_world/eslint.config.mjs +2 -25
- package/templates/hello_world/package.json +35 -38
- package/templates/hello_world/scripts/copy_env.ts +10 -0
- package/templates/hello_world/scripts/ssl/ssl.ts +1 -1
- package/templates/hello_world/scripts/start/app_runner.ts +42 -5
- package/templates/hello_world/scripts/start/context.ts +12 -6
- package/templates/hello_world/scripts/start/start.ts +12 -1
- package/templates/hello_world/scripts/start/tests/start.tests.ts +61 -0
- package/templates/hello_world/src/app.tsx +1 -1
- package/templates/hello_world/src/index.tsx +1 -1
- package/templates/hello_world/src/tests/__snapshots__/app.tests.tsx.snap +5 -5
- package/templates/hello_world/src/tests/app.tests.tsx +3 -3
- package/templates/hello_world/utils/use_add_element.ts +1 -1
- package/templates/hello_world/utils/use_feature_support.ts +1 -1
- package/templates/hello_world/webpack.config.ts +254 -0
- package/templates/optional/.cursor/mcp.json +8 -0
- package/templates/optional/.vscode/mcp.json +9 -0
- package/templates/optional/AGENTS.md +76 -0
- package/templates/optional/CLAUDE.md +76 -0
- package/templates/common/conf/eslint-general.mjs +0 -303
- package/templates/common/conf/eslint-i18n.mjs +0 -41
- package/templates/common/conf/eslint-local-i18n-rules/index.mjs +0 -181
- /package/templates/base/scripts/{copy-env.ts → copy_env.ts} +0 -0
- /package/templates/dam/scripts/{copy-env.ts → copy_env.ts} +0 -0
- /package/templates/{gen_ai/scripts/copy-env.ts → data_connector/scripts/copy_env.ts} +0 -0
- /package/templates/{hello_world/scripts/copy-env.ts → gen_ai/scripts/copy_env.ts} +0 -0
- /package/templates/{common → optional}/.vscode/extensions.json +0 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
|
|
3
|
+
import type { ChildProcess } from "child_process";
|
|
4
|
+
import { spawn } from "child_process";
|
|
5
|
+
import * as treeKill from "tree-kill";
|
|
6
|
+
|
|
7
|
+
describe("start script", () => {
|
|
8
|
+
let serverProcess: ChildProcess;
|
|
9
|
+
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
if (serverProcess?.pid && !serverProcess.exitCode) {
|
|
12
|
+
treeKill(serverProcess.pid);
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("should execute 'npm run start' and start a dev server", async () => {
|
|
17
|
+
const testServerPort = 8089;
|
|
18
|
+
serverProcess = await spawn(
|
|
19
|
+
`npm run start -- -p ${testServerPort} --no-preview`,
|
|
20
|
+
{
|
|
21
|
+
shell: true,
|
|
22
|
+
},
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
if (!serverProcess.pid) {
|
|
26
|
+
throw new Error("Unable to start server");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// wait for the server to start and collect output until it reports the running URL
|
|
30
|
+
let output = "";
|
|
31
|
+
await new Promise<void>((resolve, reject) => {
|
|
32
|
+
serverProcess.stdout?.on("data", (data) => {
|
|
33
|
+
output += data.toString();
|
|
34
|
+
if (output.includes("Development URL")) {
|
|
35
|
+
resolve();
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
serverProcess.stderr?.on("data", (data) => {
|
|
40
|
+
console.error("Server process error: ", data.toString());
|
|
41
|
+
reject();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// timeout and fail test if the server hasn't correctly started in 10 seconds
|
|
45
|
+
setTimeout(() => {
|
|
46
|
+
if (serverProcess?.pid && !serverProcess.exitCode) {
|
|
47
|
+
treeKill(serverProcess.pid);
|
|
48
|
+
}
|
|
49
|
+
reject(new Error("Test timed out"));
|
|
50
|
+
}, 10000);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// check that the server is running and accessible
|
|
54
|
+
const expectedServerURL = `http://localhost:${testServerPort}`;
|
|
55
|
+
expect(output).toContain(expectedServerURL);
|
|
56
|
+
expect(serverProcess.exitCode).toBeNull();
|
|
57
|
+
|
|
58
|
+
// clean up
|
|
59
|
+
treeKill(serverProcess.pid);
|
|
60
|
+
}, 15000); // 15 seconds timeout to allow for the server to start
|
|
61
|
+
});
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { createHashRouter, RouterProvider } from "react-router-dom";
|
|
2
|
-
import { ContextProvider } from "./context";
|
|
3
|
-
import { routes } from "./routes";
|
|
4
1
|
import { AppI18nProvider } from "@canva/app-i18n-kit";
|
|
5
2
|
import { AppUiProvider } from "@canva/app-ui-kit";
|
|
6
3
|
import { ErrorBoundary } from "react-error-boundary";
|
|
4
|
+
import { createHashRouter, RouterProvider } from "react-router-dom";
|
|
5
|
+
import { ContextProvider } from "./context";
|
|
7
6
|
import { ErrorPage } from "./pages";
|
|
7
|
+
import { routes } from "./routes";
|
|
8
8
|
|
|
9
9
|
export const App = () => (
|
|
10
10
|
<AppI18nProvider>
|
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { Button, Rows } from "@canva/app-ui-kit";
|
|
2
|
+
import { getPlatformInfo } from "@canva/platform";
|
|
3
|
+
import { useIntl } from "react-intl";
|
|
4
|
+
import { useLocation, useNavigate } from "react-router-dom";
|
|
5
|
+
import { purchaseCredits, queueImageGeneration } from "src/api";
|
|
4
6
|
import { RemainingCredits } from "src/components";
|
|
5
7
|
import { NUMBER_OF_IMAGES_TO_GENERATE } from "src/config";
|
|
6
8
|
import { useAppContext } from "src/context";
|
|
7
|
-
import { Paths } from "src/
|
|
9
|
+
import { Paths } from "src/paths";
|
|
8
10
|
import { getObsceneWords } from "src/utils";
|
|
9
|
-
import { useIntl } from "react-intl";
|
|
10
11
|
import { FooterMessages as Messages } from "./footer.messages";
|
|
11
12
|
|
|
12
13
|
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
|
-
|
|
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>
|
|
@@ -2,10 +2,10 @@ 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
4
|
import { addElementAtPoint, ui } from "@canva/design";
|
|
5
|
+
import { FormattedMessage, useIntl } from "react-intl";
|
|
5
6
|
import type { ImageType } from "src/api";
|
|
6
7
|
import { useAppContext } from "src/context";
|
|
7
8
|
import * as styles from "styles/utils.css";
|
|
8
|
-
import { FormattedMessage, useIntl } from "react-intl";
|
|
9
9
|
|
|
10
10
|
const THUMBNAIL_HEIGHT = 150;
|
|
11
11
|
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import { useEffect, useState } from "react";
|
|
2
|
-
import { useNavigate } from "react-router-dom";
|
|
3
1
|
import {
|
|
4
|
-
|
|
2
|
+
Box,
|
|
3
|
+
Button,
|
|
5
4
|
Grid,
|
|
6
5
|
Placeholder,
|
|
7
6
|
ProgressBar,
|
|
7
|
+
Rows,
|
|
8
8
|
Text,
|
|
9
|
-
Button,
|
|
10
|
-
Box,
|
|
11
9
|
} from "@canva/app-ui-kit";
|
|
12
|
-
import {
|
|
13
|
-
import { useAppContext } from "src/context";
|
|
14
|
-
import { cancelImageGenerationJob, getImageGenerationJobStatus } from "src/api";
|
|
10
|
+
import { useEffect, useState } from "react";
|
|
15
11
|
import { FormattedMessage, useIntl } from "react-intl";
|
|
12
|
+
import { useNavigate } from "react-router-dom";
|
|
13
|
+
import { cancelImageGenerationJob, getImageGenerationJobStatus } from "src/api";
|
|
14
|
+
import { useAppContext } from "src/context";
|
|
15
|
+
import { Paths } from "src/paths";
|
|
16
16
|
|
|
17
17
|
const INTERVAL_DURATION_IN_MS = 100;
|
|
18
18
|
const TOTAL_PROGRESS_PERCENTAGE = 100;
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
2
|
-
import { useLocation } from "react-router-dom";
|
|
3
1
|
import {
|
|
4
2
|
Box,
|
|
5
3
|
Button,
|
|
@@ -7,10 +5,12 @@ import {
|
|
|
7
5
|
LightBulbIcon,
|
|
8
6
|
MultilineInput,
|
|
9
7
|
} from "@canva/app-ui-kit";
|
|
8
|
+
import { useState } from "react";
|
|
9
|
+
import { useIntl } from "react-intl";
|
|
10
|
+
import { useLocation } from "react-router-dom";
|
|
10
11
|
import { useAppContext } from "src/context";
|
|
11
|
-
import { Paths } from "src/
|
|
12
|
+
import { Paths } from "src/paths";
|
|
12
13
|
import { PromptInputMessages as Messages } from "./prompt_input.messages";
|
|
13
|
-
import { useIntl } from "react-intl";
|
|
14
14
|
|
|
15
15
|
// @TODO: Adjust according to your specific requirements.
|
|
16
16
|
const MAX_INPUT_LENGTH = 280;
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { Link, Rows, Text, TextPlaceholder } from "@canva/app-ui-kit";
|
|
2
|
-
import { requestOpenExternalUrl } from "@canva/platform";
|
|
3
|
-
import { useAppContext } from "src/context";
|
|
2
|
+
import { getPlatformInfo, requestOpenExternalUrl } from "@canva/platform";
|
|
4
3
|
import { FormattedMessage, useIntl } from "react-intl";
|
|
4
|
+
import { useAppContext } from "src/context";
|
|
5
5
|
|
|
6
6
|
// @TODO: Replace this URL with your custom upselling link.
|
|
7
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Box,
|
|
1
|
+
import { Box, Column, Columns, FlagIcon, Link, Text } from "@canva/app-ui-kit";
|
|
2
2
|
import { requestOpenExternalUrl } from "@canva/platform";
|
|
3
3
|
import { FormattedMessage, useIntl } from "react-intl";
|
|
4
4
|
import { APP_NAME } from "src/config";
|
|
@@ -34,7 +34,7 @@ export const ReportBox = (): JSX.Element => {
|
|
|
34
34
|
<Link
|
|
35
35
|
href={REPORT_URL}
|
|
36
36
|
requestOpenExternalUrl={() => openExternalUrl(REPORT_URL)}
|
|
37
|
-
|
|
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,11 +1,11 @@
|
|
|
1
1
|
/* eslint-disable formatjs/no-literal-string-in-jsx */
|
|
2
|
-
import { TestAppUiProvider } from "@canva/app-ui-kit";
|
|
3
2
|
import { TestAppI18nProvider } from "@canva/app-i18n-kit";
|
|
3
|
+
import { TestAppUiProvider } from "@canva/app-ui-kit";
|
|
4
|
+
import { getPlatformInfo, requestOpenExternalUrl } from "@canva/platform";
|
|
4
5
|
import type { RenderResult } from "@testing-library/react";
|
|
5
6
|
import { fireEvent, render } from "@testing-library/react";
|
|
6
7
|
import React from "react";
|
|
7
8
|
import { RemainingCredits } from "../remaining_credits";
|
|
8
|
-
import { requestOpenExternalUrl } from "@canva/platform";
|
|
9
9
|
|
|
10
10
|
function renderInTestProvider(node: React.ReactNode): RenderResult {
|
|
11
11
|
return render(
|
|
@@ -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.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Outlet } from "react-router-dom";
|
|
2
1
|
import { Rows } from "@canva/app-ui-kit";
|
|
3
|
-
import {
|
|
2
|
+
import { Outlet } from "react-router-dom";
|
|
4
3
|
import * as styles from "styles/components.css";
|
|
4
|
+
import { Footer } from "./components";
|
|
5
5
|
|
|
6
6
|
export const Home = () => (
|
|
7
7
|
<div className={styles.scrollContainer}>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { useNavigate } from "react-router-dom";
|
|
2
1
|
import { Button, Rows, Text } from "@canva/app-ui-kit";
|
|
2
|
+
import { FormattedMessage, useIntl } from "react-intl";
|
|
3
|
+
import { useNavigate } from "react-router-dom";
|
|
3
4
|
import { useAppContext } from "src/context";
|
|
4
|
-
import { Paths } from "src/
|
|
5
|
+
import { Paths } from "src/paths";
|
|
5
6
|
import * as styles from "styles/components.css";
|
|
6
|
-
import { FormattedMessage, useIntl } from "react-intl";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Bare bones Error Page, please add relevant information and behavior that your app requires.
|
|
@@ -6,8 +6,8 @@ import {
|
|
|
6
6
|
PromptInput,
|
|
7
7
|
ReportBox,
|
|
8
8
|
} from "src/components";
|
|
9
|
-
import { useAppContext } from "src/context";
|
|
10
9
|
import { EXPECTED_LOADING_TIME_IN_SECONDS } from "src/config";
|
|
10
|
+
import { useAppContext } from "src/context";
|
|
11
11
|
|
|
12
12
|
export const ResultsPage = () => {
|
|
13
13
|
const { isLoadingImages } = useAppContext();
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { Home } from "src/home";
|
|
2
|
-
import { ErrorPage
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
{
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
|
+
import debug from "debug";
|
|
2
3
|
import * as express from "express";
|
|
4
|
+
import type { NextFunction, Request, Response } from "express";
|
|
5
|
+
import * as fs from "fs";
|
|
3
6
|
import * as http from "http";
|
|
4
7
|
import * as https from "https";
|
|
5
|
-
import * as fs from "fs";
|
|
6
|
-
import type { Request, Response, NextFunction } from "express";
|
|
7
|
-
import debug from "debug";
|
|
8
8
|
|
|
9
9
|
const serverDebug = debug("server");
|
|
10
10
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
2
|
import * as debug from "debug";
|
|
3
|
-
import type { Request, Response
|
|
3
|
+
import type { NextFunction, Request, Response } from "express";
|
|
4
4
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
5
5
|
import Express from "express-serve-static-core";
|
|
6
6
|
|
|
@@ -45,7 +45,7 @@ export function createBearerMiddleware(
|
|
|
45
45
|
throw new AuthorizationError("Token is invalid");
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
req
|
|
48
|
+
req["user_id"] = user;
|
|
49
49
|
|
|
50
50
|
next();
|
|
51
51
|
} catch (e) {
|
|
@@ -1,31 +1,36 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import type { Configuration } from "webpack";
|
|
2
|
+
import { DefinePlugin, optimize } from "webpack";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import * as TerserPlugin from "terser-webpack-plugin";
|
|
5
|
+
import { transform } from "@formatjs/ts-transformer";
|
|
6
|
+
import * as chalk from "chalk";
|
|
7
|
+
import { config } from "dotenv";
|
|
8
|
+
import { Configuration as DevServerConfiguration } from "webpack-dev-server";
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
* @returns {Object}
|
|
23
|
-
*/
|
|
24
|
-
function buildConfig({
|
|
10
|
+
config();
|
|
11
|
+
|
|
12
|
+
type DevConfig = {
|
|
13
|
+
port: number;
|
|
14
|
+
enableHmr: boolean;
|
|
15
|
+
enableHttps: boolean;
|
|
16
|
+
appOrigin?: string;
|
|
17
|
+
appId?: string; // Deprecated in favour of appOrigin
|
|
18
|
+
certFile?: string;
|
|
19
|
+
keyFile?: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export function buildConfig({
|
|
25
23
|
devConfig,
|
|
26
24
|
appEntry = path.join(process.cwd(), "src", "index.tsx"),
|
|
27
25
|
backendHost = process.env.CANVA_BACKEND_HOST,
|
|
28
|
-
|
|
26
|
+
// For IN_HARNESS, refer to the following docs for more information: https://www.canva.dev/docs/apps/test-harness/
|
|
27
|
+
inHarness = process.env.IN_HARNESS?.toLowerCase() === "true",
|
|
28
|
+
}: {
|
|
29
|
+
devConfig?: DevConfig;
|
|
30
|
+
appEntry?: string;
|
|
31
|
+
backendHost?: string;
|
|
32
|
+
inHarness?: boolean;
|
|
33
|
+
} = {}): Configuration & DevServerConfiguration {
|
|
29
34
|
const mode = devConfig ? "development" : "production";
|
|
30
35
|
|
|
31
36
|
if (!backendHost) {
|
|
@@ -46,9 +51,14 @@ function buildConfig({
|
|
|
46
51
|
return {
|
|
47
52
|
mode,
|
|
48
53
|
context: path.resolve(process.cwd(), "./"),
|
|
49
|
-
entry:
|
|
50
|
-
|
|
51
|
-
|
|
54
|
+
entry: inHarness
|
|
55
|
+
? {
|
|
56
|
+
harness: path.join(process.cwd(), "harness", "harness.tsx"),
|
|
57
|
+
init: path.join(process.cwd(), "harness", "init.ts"),
|
|
58
|
+
}
|
|
59
|
+
: {
|
|
60
|
+
app: appEntry,
|
|
61
|
+
},
|
|
52
62
|
target: "web",
|
|
53
63
|
resolve: {
|
|
54
64
|
alias: {
|
|
@@ -60,7 +70,7 @@ function buildConfig({
|
|
|
60
70
|
extensions: [".ts", ".tsx", ".js", ".css", ".svg", ".woff", ".woff2"],
|
|
61
71
|
},
|
|
62
72
|
infrastructureLogging: {
|
|
63
|
-
level: "none",
|
|
73
|
+
level: inHarness ? "info" : "none",
|
|
64
74
|
},
|
|
65
75
|
module: {
|
|
66
76
|
rules: [
|
|
@@ -172,35 +182,26 @@ function buildConfig({
|
|
|
172
182
|
new DefinePlugin({
|
|
173
183
|
BACKEND_HOST: JSON.stringify(backendHost),
|
|
174
184
|
}),
|
|
175
|
-
// Apps can only submit a single JS file via the
|
|
185
|
+
// Apps can only submit a single JS file via the Developer Portal
|
|
176
186
|
new optimize.LimitChunkCountPlugin({ maxChunks: 1 }),
|
|
177
|
-
mode === "development" && new ReactRefreshWebpackPlugin(),
|
|
178
187
|
].filter(Boolean),
|
|
179
188
|
...buildDevConfig(devConfig),
|
|
180
189
|
};
|
|
181
190
|
}
|
|
182
191
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
* @param {boolean} [options.enableHmr]
|
|
188
|
-
* @param {boolean} [options.enableHttps]
|
|
189
|
-
* @param {string} [options.appOrigin]
|
|
190
|
-
* @param {string} [options.appId] - Deprecated in favour of appOrigin
|
|
191
|
-
* @param {string} [options.certFile]
|
|
192
|
-
* @param {string} [options.keyFile]
|
|
193
|
-
* @returns {Object|null}
|
|
194
|
-
*/
|
|
195
|
-
function buildDevConfig(options) {
|
|
192
|
+
function buildDevConfig(options?: DevConfig): {
|
|
193
|
+
devtool?: string;
|
|
194
|
+
devServer?: DevServerConfiguration;
|
|
195
|
+
} {
|
|
196
196
|
if (!options) {
|
|
197
|
-
return
|
|
197
|
+
return {};
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
const { port, enableHmr, appOrigin, appId, enableHttps, certFile, keyFile } =
|
|
201
201
|
options;
|
|
202
|
+
const host = "localhost";
|
|
202
203
|
|
|
203
|
-
let devServer = {
|
|
204
|
+
let devServer: DevServerConfiguration = {
|
|
204
205
|
server: enableHttps
|
|
205
206
|
? {
|
|
206
207
|
type: "https",
|
|
@@ -210,7 +211,8 @@ function buildDevConfig(options) {
|
|
|
210
211
|
},
|
|
211
212
|
}
|
|
212
213
|
: "http",
|
|
213
|
-
host
|
|
214
|
+
host,
|
|
215
|
+
allowedHosts: [host],
|
|
214
216
|
historyApiFallback: {
|
|
215
217
|
rewrites: [{ from: /^\/$/, to: "/app.js" }],
|
|
216
218
|
},
|
|
@@ -227,31 +229,13 @@ function buildDevConfig(options) {
|
|
|
227
229
|
if (enableHmr && appOrigin) {
|
|
228
230
|
devServer = {
|
|
229
231
|
...devServer,
|
|
230
|
-
allowedHosts: new URL(appOrigin).hostname,
|
|
232
|
+
allowedHosts: [host, new URL(appOrigin).hostname],
|
|
231
233
|
headers: {
|
|
232
234
|
"Access-Control-Allow-Origin": appOrigin,
|
|
233
235
|
"Access-Control-Allow-Credentials": "true",
|
|
234
236
|
"Access-Control-Allow-Private-Network": "true",
|
|
235
237
|
},
|
|
236
238
|
};
|
|
237
|
-
} else if (enableHmr && appId) {
|
|
238
|
-
// Deprecated - App ID should not be used to configure HMR in the future and can be safely removed
|
|
239
|
-
// after a few months.
|
|
240
|
-
|
|
241
|
-
console.warn(
|
|
242
|
-
"Enabling Hot Module Replacement (HMR) with an App ID is deprecated, please see the README.md on how to update.",
|
|
243
|
-
);
|
|
244
|
-
|
|
245
|
-
const appDomain = `app-${appId.toLowerCase().trim()}.canva-apps.com`;
|
|
246
|
-
devServer = {
|
|
247
|
-
...devServer,
|
|
248
|
-
allowedHosts: appDomain,
|
|
249
|
-
headers: {
|
|
250
|
-
"Access-Control-Allow-Origin": `https://${appDomain}`,
|
|
251
|
-
"Access-Control-Allow-Credentials": "true",
|
|
252
|
-
"Access-Control-Allow-Private-Network": "true",
|
|
253
|
-
},
|
|
254
|
-
};
|
|
255
239
|
} else {
|
|
256
240
|
if (enableHmr && !appOrigin) {
|
|
257
241
|
console.warn(
|
|
@@ -267,6 +251,4 @@ function buildDevConfig(options) {
|
|
|
267
251
|
};
|
|
268
252
|
}
|
|
269
253
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
module.exports.buildConfig = buildConfig;
|
|
254
|
+
export default buildConfig;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://www.canva.dev/schemas/app/v1/manifest-schema.json",
|
|
3
|
+
"manifest_schema_version": 1,
|
|
4
|
+
"runtime": {
|
|
5
|
+
"permissions": [
|
|
6
|
+
{
|
|
7
|
+
"name": "canva:design:content:read",
|
|
8
|
+
"type": "mandatory"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"name": "canva:design:content:write",
|
|
12
|
+
"type": "mandatory"
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -1,17 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { fileURLToPath } from "node:url";
|
|
3
|
-
import js from "@eslint/js";
|
|
4
|
-
import { FlatCompat } from "@eslint/eslintrc";
|
|
5
|
-
import general from "./conf/eslint-general.mjs";
|
|
6
|
-
import i18n from "./conf/eslint-i18n.mjs";
|
|
7
|
-
|
|
8
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
-
const __dirname = path.dirname(__filename);
|
|
10
|
-
const compat = new FlatCompat({
|
|
11
|
-
baseDirectory: __dirname,
|
|
12
|
-
recommendedConfig: js.configs.recommended,
|
|
13
|
-
allConfig: js.configs.all,
|
|
14
|
-
});
|
|
1
|
+
import canvaPlugin from "@canva/app-eslint-plugin";
|
|
15
2
|
|
|
16
3
|
export default [
|
|
17
4
|
{
|
|
@@ -23,15 +10,5 @@ export default [
|
|
|
23
10
|
"**/*.config.*",
|
|
24
11
|
],
|
|
25
12
|
},
|
|
26
|
-
...
|
|
27
|
-
"eslint:recommended",
|
|
28
|
-
"plugin:@typescript-eslint/recommended",
|
|
29
|
-
"plugin:@typescript-eslint/eslint-recommended",
|
|
30
|
-
"plugin:@typescript-eslint/strict",
|
|
31
|
-
"plugin:@typescript-eslint/stylistic",
|
|
32
|
-
"plugin:react/recommended",
|
|
33
|
-
"plugin:jest/recommended",
|
|
34
|
-
),
|
|
35
|
-
...general,
|
|
36
|
-
...i18n,
|
|
13
|
+
...canvaPlugin.configs.apps,
|
|
37
14
|
];
|