@canva/cli 1.11.0 → 1.12.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 (71) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +2 -0
  3. package/cli.js +593 -580
  4. package/package.json +7 -2
  5. package/templates/base/package.json +6 -6
  6. package/templates/common/jest.config.mjs +1 -1
  7. package/templates/dam/backend/server.ts +8 -0
  8. package/templates/dam/canva-app.json +4 -0
  9. package/templates/dam/package.json +6 -6
  10. package/templates/data_connector/README.md +1 -1
  11. package/templates/data_connector/package.json +6 -6
  12. package/templates/gen_ai/backend/server.ts +17 -0
  13. package/templates/gen_ai/package.json +6 -6
  14. package/templates/gen_ai/src/api/api.ts +4 -0
  15. package/templates/hello_world/package.json +6 -6
  16. package/templates/mls/README.md +81 -0
  17. package/templates/mls/canva-app.json +25 -0
  18. package/templates/mls/declarations/declarations.d.ts +29 -0
  19. package/templates/mls/eslint.config.mjs +14 -0
  20. package/templates/mls/jest.config.mjs +36 -0
  21. package/templates/mls/jest.setup.ts +37 -0
  22. package/templates/mls/package.json +117 -0
  23. package/templates/mls/scripts/copy_env.ts +13 -0
  24. package/templates/mls/scripts/ssl/ssl.ts +131 -0
  25. package/templates/mls/scripts/start/app_runner.ts +223 -0
  26. package/templates/mls/scripts/start/context.ts +171 -0
  27. package/templates/mls/scripts/start/start.ts +46 -0
  28. package/templates/mls/src/__tests__/app.tests.tsx +11 -0
  29. package/templates/mls/src/__tests__/office_selection_page.tests.tsx +72 -0
  30. package/templates/mls/src/__tests__/utils.tsx +19 -0
  31. package/templates/mls/src/adapter.ts +126 -0
  32. package/templates/mls/src/components/agent/agent_card.tsx +57 -0
  33. package/templates/mls/src/components/agent/agent_grid.tsx +37 -0
  34. package/templates/mls/src/components/agent/agent_list.tsx +17 -0
  35. package/templates/mls/src/components/agent/agent_search_filters.tsx +88 -0
  36. package/templates/mls/src/components/breadcrumb/breadcrumb.tsx +40 -0
  37. package/templates/mls/src/components/listing/listing_card.tsx +64 -0
  38. package/templates/mls/src/components/listing/listing_grid.tsx +37 -0
  39. package/templates/mls/src/components/listing/listing_list.tsx +21 -0
  40. package/templates/mls/src/components/listing/listing_search_filters.tsx +145 -0
  41. package/templates/mls/src/components/placeholders/placeholders.tsx +65 -0
  42. package/templates/mls/src/data.ts +359 -0
  43. package/templates/mls/src/index.tsx +4 -0
  44. package/templates/mls/src/intents/design_editor/app.tsx +44 -0
  45. package/templates/mls/src/intents/design_editor/index.tsx +25 -0
  46. package/templates/mls/src/pages/agent_details_page/agent_details_page.tsx +175 -0
  47. package/templates/mls/src/pages/list_page/agent_tab_panel.tsx +126 -0
  48. package/templates/mls/src/pages/list_page/list_page.tsx +67 -0
  49. package/templates/mls/src/pages/list_page/listing_tab_panel.tsx +135 -0
  50. package/templates/mls/src/pages/listing_details_page/listing_details_page.tsx +418 -0
  51. package/templates/mls/src/pages/loading_page/loading_page.tsx +152 -0
  52. package/templates/mls/src/pages/office_selection_page/office_selection_page.tsx +144 -0
  53. package/templates/mls/src/real_estate.type.ts +44 -0
  54. package/templates/mls/src/util/use_add_element.tsx +62 -0
  55. package/templates/mls/src/util/use_drag_element.tsx +68 -0
  56. package/templates/mls/styles/components.css +56 -0
  57. package/templates/mls/tsconfig.json +55 -0
  58. package/templates/mls/webpack.config.ts +254 -0
  59. package/templates/base/backend/routers/oauth.ts +0 -393
  60. package/templates/base/utils/backend/bearer_middleware/bearer_middleware.ts +0 -99
  61. package/templates/base/utils/backend/bearer_middleware/index.ts +0 -1
  62. package/templates/base/utils/backend/bearer_middleware/tests/bearer_middleware.tests.ts +0 -192
  63. package/templates/common/utils/backend/base_backend/create.ts +0 -104
  64. package/templates/gen_ai/backend/database/database.ts +0 -42
  65. package/templates/gen_ai/utils/backend/bearer_middleware/bearer_middleware.ts +0 -99
  66. package/templates/gen_ai/utils/backend/bearer_middleware/index.ts +0 -1
  67. /package/templates/base/{utils/backend → backend}/base_backend/create.ts +0 -0
  68. /package/templates/base/{utils/backend → backend}/jwt_middleware/index.ts +0 -0
  69. /package/templates/base/{utils/backend → backend}/jwt_middleware/jwt_middleware.ts +0 -0
  70. /package/templates/{common → gen_ai}/utils/backend/jwt_middleware/index.ts +0 -0
  71. /package/templates/{common → gen_ai}/utils/backend/jwt_middleware/jwt_middleware.ts +0 -0
@@ -0,0 +1,126 @@
1
+ import { Box, Rows, Scrollable, Text } from "@canva/app-ui-kit";
2
+ import { useInfiniteQuery } from "@tanstack/react-query";
3
+ import { useState } from "react";
4
+ import InfiniteScroll from "react-infinite-scroller";
5
+ import { useIntl } from "react-intl";
6
+ import { useNavigate } from "react-router-dom";
7
+ import { fetchAgents } from "../../adapter";
8
+ import { AgentGrid } from "../../components/agent/agent_grid";
9
+ import { AgentList } from "../../components/agent/agent_list";
10
+ import { AgentSearchFilters } from "../../components/agent/agent_search_filters";
11
+ import {
12
+ GridPlaceholder,
13
+ ListPlaceholder,
14
+ } from "../../components/placeholders/placeholders";
15
+ import type { Agent, Office } from "../../real_estate.type";
16
+
17
+ type Layout = "grid" | "list";
18
+
19
+ interface AgentTabContentProps {
20
+ office: Office;
21
+ }
22
+
23
+ export const AgentTabPanel = ({ office }: AgentTabContentProps) => {
24
+ const intl = useIntl();
25
+ const [layout, setLayout] = useState<Layout>("grid");
26
+ const [query, setQuery] = useState<string>("");
27
+ const [sort, setSort] = useState<string>("");
28
+
29
+ const toggleLayout = () => {
30
+ setLayout(layout === "grid" ? "list" : "grid");
31
+ };
32
+
33
+ const {
34
+ data: agentItems,
35
+ hasNextPage,
36
+ isLoading,
37
+ fetchNextPage,
38
+ isFetchingNextPage,
39
+ isError,
40
+ } = useInfiniteQuery({
41
+ queryKey: ["agents", query, sort, office],
42
+ queryFn: async ({ pageParam }: { pageParam: string | undefined }) => {
43
+ return fetchAgents(office, query, pageParam, sort);
44
+ },
45
+ getNextPageParam: (lastPage) => lastPage?.continuation,
46
+ initialPageParam: undefined,
47
+ });
48
+
49
+ const agents = agentItems?.pages?.flatMap((page) => page.agents) || [];
50
+ const navigate = useNavigate();
51
+ const handleAgentClick = (item: Agent) => {
52
+ navigate(`/details/agent`, { state: { office, agent: item } });
53
+ };
54
+
55
+ return (
56
+ <Scrollable>
57
+ <InfiniteScroll
58
+ loadMore={() => fetchNextPage()}
59
+ hasMore={hasNextPage}
60
+ useWindow={false}
61
+ >
62
+ <Box height="full">
63
+ <Rows spacing="2u">
64
+ {((!isError && !isLoading && agents.length > 0) ||
65
+ query?.length) && (
66
+ <AgentSearchFilters
67
+ query={query}
68
+ onQueryChange={setQuery}
69
+ layout={layout}
70
+ onLayoutToggle={toggleLayout}
71
+ sort={sort}
72
+ onSortChange={setSort}
73
+ />
74
+ )}
75
+ {isError && (
76
+ <Text tone="critical">
77
+ {intl.formatMessage({
78
+ defaultMessage: "Error loading agents. Please try again.",
79
+ description: "Error message when agents fail to load",
80
+ })}
81
+ </Text>
82
+ )}
83
+ {isLoading && (
84
+ <Box width="full" paddingBottom="2u">
85
+ {layout === "grid" ? <GridPlaceholder /> : <ListPlaceholder />}
86
+ </Box>
87
+ )}
88
+ {!isError && !isLoading && agents.length === 0 && (
89
+ <Box
90
+ height="full"
91
+ display="flex"
92
+ alignItems="center"
93
+ justifyContent="center"
94
+ >
95
+ <Text>
96
+ {intl.formatMessage({
97
+ defaultMessage: "No agents found in this office.",
98
+ description: "Message shown when no agents are found",
99
+ })}
100
+ </Text>
101
+ </Box>
102
+ )}
103
+ {!isError && !isLoading && agents.length > 0 && (
104
+ <Rows spacing={layout === "grid" ? "2u" : "0"}>
105
+ {layout === "grid" ? (
106
+ <AgentGrid agents={agents} onAgentClick={handleAgentClick} />
107
+ ) : (
108
+ <AgentList agents={agents} onAgentClick={handleAgentClick} />
109
+ )}
110
+ {isFetchingNextPage && (
111
+ <Box width="full" paddingBottom="2u">
112
+ {layout === "grid" ? (
113
+ <GridPlaceholder />
114
+ ) : (
115
+ <ListPlaceholder />
116
+ )}
117
+ </Box>
118
+ )}
119
+ </Rows>
120
+ )}
121
+ </Rows>
122
+ </Box>
123
+ </InfiniteScroll>
124
+ </Scrollable>
125
+ );
126
+ };
@@ -0,0 +1,67 @@
1
+ import {
2
+ Box,
3
+ Tab,
4
+ TabList,
5
+ TabPanel,
6
+ TabPanels,
7
+ Tabs,
8
+ } from "@canva/app-ui-kit";
9
+ import { useIntl } from "react-intl";
10
+ import { useLocation, useNavigate, useParams } from "react-router-dom";
11
+ import { Breadcrumb } from "../../components/breadcrumb/breadcrumb";
12
+ import type { Office } from "../../real_estate.type";
13
+ import { AgentTabPanel } from "./agent_tab_panel";
14
+ import { ListingTabPanel } from "./listing_tab_panel";
15
+
16
+ export const ListPage = () => {
17
+ const navigate = useNavigate();
18
+ const { tab = "listings" } = useParams<{
19
+ tab?: string;
20
+ }>();
21
+ const { office } = useLocation().state as { office: Office };
22
+ const intl = useIntl();
23
+
24
+ return (
25
+ <Tabs
26
+ onSelect={(value) => navigate(`/list/${value}`, { state: { office } })}
27
+ activeId={tab}
28
+ height="fill"
29
+ >
30
+ <Box height="full" display="flex" flexDirection="column" paddingTop="2u">
31
+ <Breadcrumb />
32
+ <Box paddingBottom="1u">
33
+ <TabList>
34
+ <Tab
35
+ id="listings"
36
+ active={tab === "listings"}
37
+ onClick={() => navigate(`/list/listings`, { state: { office } })}
38
+ >
39
+ {intl.formatMessage({
40
+ defaultMessage: "Listings",
41
+ description: "Tab label for property listings",
42
+ })}
43
+ </Tab>
44
+ <Tab
45
+ id="agents"
46
+ active={tab === "agents"}
47
+ onClick={() => navigate(`/list/agents`, { state: { office } })}
48
+ >
49
+ {intl.formatMessage({
50
+ defaultMessage: "Agents",
51
+ description: "Tab label for real estate agents",
52
+ })}
53
+ </Tab>
54
+ </TabList>
55
+ </Box>
56
+ <TabPanels>
57
+ <TabPanel id="listings">
58
+ <ListingTabPanel office={office} />
59
+ </TabPanel>
60
+ <TabPanel id="agents">
61
+ <AgentTabPanel office={office} />
62
+ </TabPanel>
63
+ </TabPanels>
64
+ </Box>
65
+ </Tabs>
66
+ );
67
+ };
@@ -0,0 +1,135 @@
1
+ import { Box, Rows, Scrollable, Text } from "@canva/app-ui-kit";
2
+ import { useInfiniteQuery } from "@tanstack/react-query";
3
+ import { useState } from "react";
4
+ import InfiniteScroll from "react-infinite-scroller";
5
+ import { useIntl } from "react-intl";
6
+ import { useNavigate } from "react-router-dom";
7
+ import { fetchListings } from "../../adapter";
8
+ import { ListingGrid } from "../../components/listing/listing_grid";
9
+ import { ListingList } from "../../components/listing/listing_list";
10
+ import { ListingSearchFilters } from "../../components/listing/listing_search_filters";
11
+ import {
12
+ GridPlaceholder,
13
+ ListPlaceholder,
14
+ } from "../../components/placeholders/placeholders";
15
+ import type { Office, Property } from "../../real_estate.type";
16
+
17
+ type Layout = "grid" | "list";
18
+
19
+ interface ListingTabContentProps {
20
+ office: Office;
21
+ }
22
+
23
+ export const ListingTabPanel = ({ office }: ListingTabContentProps) => {
24
+ const intl = useIntl();
25
+ const [layout, setLayout] = useState<Layout>("grid");
26
+ const [query, setQuery] = useState<string>("");
27
+ const [propertyType, setPropertyType] = useState<string>("");
28
+ const [sort, setSort] = useState<string>("");
29
+
30
+ const toggleLayout = () => {
31
+ setLayout(layout === "grid" ? "list" : "grid");
32
+ };
33
+
34
+ const {
35
+ data: listingItems,
36
+ hasNextPage,
37
+ isLoading,
38
+ fetchNextPage,
39
+ isFetchingNextPage,
40
+ isError,
41
+ } = useInfiniteQuery({
42
+ queryKey: ["listings", query, propertyType, sort, office],
43
+ queryFn: async ({ pageParam }: { pageParam: string | undefined }) => {
44
+ return fetchListings(office, query, propertyType, sort, pageParam);
45
+ },
46
+ getNextPageParam: (lastPage) => lastPage?.continuation,
47
+ initialPageParam: undefined,
48
+ });
49
+ const navigate = useNavigate();
50
+
51
+ const listings = listingItems?.pages?.flatMap((page) => page.listings) || [];
52
+ const handleListingClick = (item: Property) => {
53
+ navigate(`/details/listing`, { state: { office, listing: item } });
54
+ };
55
+
56
+ return (
57
+ <Scrollable>
58
+ <InfiniteScroll
59
+ loadMore={() => fetchNextPage()}
60
+ hasMore={hasNextPage}
61
+ useWindow={false}
62
+ >
63
+ <Box height="full">
64
+ <Rows spacing="2u">
65
+ {((!isError && !isLoading && !!listings.length) ||
66
+ !!query?.length) && (
67
+ <ListingSearchFilters
68
+ query={query}
69
+ onQueryChange={setQuery}
70
+ layout={layout}
71
+ onLayoutToggle={toggleLayout}
72
+ propertyType={propertyType}
73
+ onPropertyTypeChange={setPropertyType}
74
+ sort={sort}
75
+ onSortChange={setSort}
76
+ />
77
+ )}
78
+ {isError && (
79
+ <Text tone="critical">
80
+ {intl.formatMessage({
81
+ defaultMessage: "Error loading listings. Please try again.",
82
+ description: "Error message when listings fail to load",
83
+ })}
84
+ </Text>
85
+ )}
86
+ {isLoading && (
87
+ <Box width="full" paddingBottom="2u">
88
+ {layout === "grid" ? <GridPlaceholder /> : <ListPlaceholder />}
89
+ </Box>
90
+ )}
91
+ {!isError && !isLoading && !listings.length && (
92
+ <Box
93
+ height="full"
94
+ display="flex"
95
+ alignItems="center"
96
+ justifyContent="center"
97
+ >
98
+ <Text>
99
+ {intl.formatMessage({
100
+ defaultMessage: "No listings found.",
101
+ description: "Message shown when no listings are found",
102
+ })}
103
+ </Text>
104
+ </Box>
105
+ )}
106
+ {!isError && !isLoading && !!listings.length && (
107
+ <Rows spacing={layout === "grid" ? "2u" : "0"}>
108
+ {layout === "grid" ? (
109
+ <ListingGrid
110
+ listings={listings}
111
+ onListingClick={handleListingClick}
112
+ />
113
+ ) : (
114
+ <ListingList
115
+ listings={listings}
116
+ onListingClick={handleListingClick}
117
+ />
118
+ )}
119
+ {isFetchingNextPage && (
120
+ <Box width="full" paddingBottom="2u">
121
+ {layout === "grid" ? (
122
+ <GridPlaceholder />
123
+ ) : (
124
+ <ListPlaceholder />
125
+ )}
126
+ </Box>
127
+ )}
128
+ </Rows>
129
+ )}
130
+ </Rows>
131
+ </Box>
132
+ </InfiniteScroll>
133
+ </Scrollable>
134
+ );
135
+ };