Skip to content

Commit 84f9e9e

Browse files
committed
split out toolbar into v1 and v2
1 parent 12805f0 commit 84f9e9e

20 files changed

Lines changed: 437 additions & 31 deletions

File tree

examples/guide-example/src/App.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ function App() {
6565
readyToTarget={true}
6666
listenForUpdates={true}
6767
colorMode={colorMode}
68+
toolbar="v2"
6869
>
6970
<div style={{ padding: "1rem 2rem" }}>
7071
<h1>Knock In-App Guide Example</h1>

packages/client/src/clients/guide/client.ts

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ const select = (state: StoreState, filters: SelectFilterParams = {}) => {
185185
type PredicateOpts = {
186186
location?: string | undefined;
187187
filters?: SelectFilterParams | undefined;
188-
debug: DebugState;
188+
debug?: DebugState;
189189
};
190190

191191
const predicate = (
@@ -268,13 +268,15 @@ export class KnockGuideClient {
268268
) {
269269
const {
270270
trackLocationFromWindow = true,
271+
// TODO: Remove this option once we ship guide toolbar v2, and offload
272+
// as much debugging specific logic and responsibilities to toolbar.
273+
trackDebugParams = false,
271274
throttleCheckInterval = DEFAULT_COUNTER_INCREMENT_INTERVAL,
272275
} = options;
273276
const win = checkForWindow();
274277

275278
const location = trackLocationFromWindow ? win?.location?.href : undefined;
276-
277-
const debug = detectDebugParams();
279+
const debug = trackDebugParams ? detectDebugParams() : undefined;
278280

279281
this.store = new Store<StoreState>({
280282
guideGroups: [],
@@ -410,8 +412,8 @@ export class KnockGuideClient {
410412
const params = {
411413
...this.targetParams,
412414
user_id: this.knock.userId,
413-
force_all_guides: debugState.forcedGuideKey ? true : undefined,
414-
preview_session_id: debugState.previewSessionId || undefined,
415+
force_all_guides: debugState?.forcedGuideKey ? true : undefined,
416+
preview_session_id: debugState?.previewSessionId || undefined,
415417
};
416418

417419
const newChannel = this.socket.channel(this.socketChannelTopic, params);
@@ -557,6 +559,39 @@ export class KnockGuideClient {
557559
}
558560
}
559561

562+
setDebug(debugOpts?: Omit<DebugState, "debugging">) {
563+
this.knock.log("[Guide] .setDebug()");
564+
const shouldRefetch = !this.store.state.debug?.debugging;
565+
566+
this.store.setState((state) => ({
567+
...state,
568+
debug: { ...debugOpts, debugging: true },
569+
}));
570+
571+
if (shouldRefetch) {
572+
this.knock.log(
573+
`[Guide] Start debugging, refetching guides and resubscribing to the websocket channel`,
574+
);
575+
this.fetch();
576+
this.subscribe();
577+
}
578+
}
579+
580+
unsetDebug() {
581+
this.knock.log("[Guide] .unsetDebug()");
582+
const shouldRefetch = this.store.state.debug?.debugging;
583+
584+
this.store.setState((state) => ({ ...state, debug: undefined }));
585+
586+
if (shouldRefetch) {
587+
this.knock.log(
588+
`[Guide] Stop debugging, refetching guides and resubscribing to the websocket channel`,
589+
);
590+
this.fetch();
591+
this.subscribe();
592+
}
593+
}
594+
560595
//
561596
// Store selector
562597
//
@@ -920,7 +955,7 @@ export class KnockGuideClient {
920955
// Get the next unarchived step.
921956
getStep() {
922957
// If debugging this guide, return the first step regardless of archive status
923-
if (self.store.state.debug.forcedGuideKey === this.key) {
958+
if (self.store.state.debug?.forcedGuideKey === this.key) {
924959
return this.steps[0];
925960
}
926961

@@ -977,7 +1012,7 @@ export class KnockGuideClient {
9771012

9781013
// Append debug params
9791014
const debugState = this.store.state.debug;
980-
if (debugState.forcedGuideKey) {
1015+
if (debugState?.forcedGuideKey || debugState?.debugging) {
9811016
combinedParams.force_all_guides = true;
9821017
}
9831018

@@ -1146,8 +1181,15 @@ export class KnockGuideClient {
11461181

11471182
this.knock.log(`[Guide] Detected a location change: ${href}`);
11481183

1184+
if (!this.options.trackDebugParams) {
1185+
this.setLocation(href);
1186+
return;
1187+
}
1188+
1189+
// TODO: Remove below once we ship toolbar v2.
1190+
11491191
// If entering debug mode, fetch all guides.
1150-
const currentDebugParams = this.store.state.debug;
1192+
const currentDebugParams = this.store.state.debug || {};
11511193
const newDebugParams = detectDebugParams();
11521194

11531195
this.setLocation(href, { debug: newDebugParams });

packages/client/src/clients/guide/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ export type QueryStatus = {
202202
};
203203

204204
export type DebugState = {
205+
debugging?: boolean;
205206
forcedGuideKey?: string | null;
206207
previewSessionId?: string | null;
207208
};
@@ -218,7 +219,7 @@ export type StoreState = {
218219
queries: Record<QueryKey, QueryStatus>;
219220
location: string | undefined;
220221
counter: number;
221-
debug: DebugState;
222+
debug?: DebugState;
222223
};
223224

224225
export type QueryFilterParams = Pick<GetGuidesQueryParams, "type">;
@@ -241,6 +242,7 @@ export type TargetParams = {
241242

242243
export type ConstructorOpts = {
243244
trackLocationFromWindow?: boolean;
245+
trackDebugParams?: boolean;
244246
orderResolutionDuration?: number;
245247
throttleCheckInterval?: number;
246248
};

packages/client/test/clients/guide/guide.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ describe("KnockGuideClient", () => {
165165
queries: {},
166166
location: undefined,
167167
counter: 0,
168-
debug: { forcedGuideKey: null, previewSessionId: null },
168+
debug: undefined,
169169
});
170170
});
171171

@@ -194,7 +194,7 @@ describe("KnockGuideClient", () => {
194194
queries: {},
195195
location: "https://example.com",
196196
counter: 0,
197-
debug: { forcedGuideKey: null, previewSessionId: null },
197+
debug: undefined,
198198
});
199199
});
200200

@@ -211,7 +211,7 @@ describe("KnockGuideClient", () => {
211211
queries: {},
212212
location: undefined,
213213
counter: 0,
214-
debug: { forcedGuideKey: null, previewSessionId: null },
214+
debug: undefined,
215215
});
216216
});
217217

@@ -234,7 +234,7 @@ describe("KnockGuideClient", () => {
234234
});
235235

236236
expect(() => {
237-
new KnockGuideClient(mockKnock, channelId);
237+
new KnockGuideClient(mockKnock, channelId, {}, { trackDebugParams: true });
238238
}).not.toThrow();
239239

240240
expect(mockLocalStorageWithErrors.setItem).toHaveBeenCalled();
@@ -2837,7 +2837,7 @@ describe("KnockGuideClient", () => {
28372837
mockKnock,
28382838
channelId,
28392839
{},
2840-
{ trackLocationFromWindow: true },
2840+
{ trackLocationFromWindow: true, trackDebugParams: true },
28412841
);
28422842

28432843
client.store.state.debug = { forcedGuideKey: null };
@@ -2894,7 +2894,7 @@ describe("KnockGuideClient", () => {
28942894
mockKnock,
28952895
channelId,
28962896
{},
2897-
{ trackLocationFromWindow: true },
2897+
{ trackLocationFromWindow: true, trackDebugParams: true },
28982898
);
28992899

29002900
client.store.state.debug = { forcedGuideKey: "test_guide" };

packages/react-core/src/modules/guide/context/KnockGuideProvider.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export type KnockGuideProviderProps = {
2323
colorMode?: ColorMode;
2424
targetParams?: KnockGuideTargetParams;
2525
trackLocationFromWindow?: boolean;
26+
trackDebugParams?: boolean;
2627
orderResolutionDuration?: number; // in milliseconds
2728
throttleCheckInterval?: number; // in milliseconds
2829
};
@@ -36,6 +37,10 @@ export const KnockGuideProvider: React.FC<
3637
colorMode = "light",
3738
targetParams = {},
3839
trackLocationFromWindow = true,
40+
// Whether the guide client should look for debug params from url or local
41+
// storage to launch guide toolbar. Set to true when using toolbar v1, but
42+
// plan to phase this out going forward with v2.
43+
trackDebugParams = false,
3944
// Default to 0 which works well for react apps as this "yields" to react for
4045
// one render cyle first and close the group stage.
4146
orderResolutionDuration = 0,
@@ -55,6 +60,7 @@ export const KnockGuideProvider: React.FC<
5560
const knockGuideClient = React.useMemo(() => {
5661
return new KnockGuideClient(knock, channelId, stableTargetParams, {
5762
trackLocationFromWindow,
63+
trackDebugParams,
5864
orderResolutionDuration,
5965
throttleCheckInterval,
6066
});
@@ -63,6 +69,7 @@ export const KnockGuideProvider: React.FC<
6369
channelId,
6470
stableTargetParams,
6571
trackLocationFromWindow,
72+
trackDebugParams,
6673
orderResolutionDuration,
6774
throttleCheckInterval,
6875
]);
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// vite.config.mts
2+
import { codecovVitePlugin } from "file:///Users/tsyy/work/javascript/node_modules/@codecov/vite-plugin/dist/index.mjs";
3+
import react from "file:///Users/tsyy/work/javascript/node_modules/@vitejs/plugin-react/dist/index.mjs";
4+
import { resolve } from "path";
5+
import { defineConfig, loadEnv } from "file:///Users/tsyy/work/javascript/node_modules/vite/dist/node/index.js";
6+
import dts from "file:///Users/tsyy/work/javascript/node_modules/vite-plugin-dts/dist/index.mjs";
7+
import noBundlePlugin from "file:///Users/tsyy/work/javascript/node_modules/vite-plugin-no-bundle/dist/index.js";
8+
var __vite_injected_original_dirname = "/Users/tsyy/work/javascript/packages/react-core";
9+
var vite_config_default = defineConfig(({ mode }) => {
10+
const env = loadEnv(mode, process.cwd(), "");
11+
const CJS = env.BUILD_TARGET?.toLocaleLowerCase()?.match("cjs");
12+
const formats = CJS ? ["cjs"] : ["es"];
13+
return {
14+
plugins: [
15+
dts({
16+
outDir: "dist/types"
17+
}),
18+
react({
19+
jsxRuntime: "classic",
20+
babel: {
21+
plugins: ["react-require"]
22+
}
23+
}),
24+
noBundlePlugin({ root: "./src" }),
25+
codecovVitePlugin({
26+
enableBundleAnalysis: process.env.CODECOV_TOKEN !== void 0,
27+
bundleName: "@knocklabs/react-core",
28+
uploadToken: process.env.CODECOV_TOKEN
29+
})
30+
],
31+
build: {
32+
outDir: CJS ? "dist/cjs" : "dist/esm",
33+
sourcemap: true,
34+
lib: {
35+
entry: resolve(__vite_injected_original_dirname, "src"),
36+
fileName: `[name]`,
37+
name: "react-core",
38+
formats
39+
},
40+
rollupOptions: {
41+
// External packages that should not be bundled
42+
external: ["react"],
43+
output: {
44+
interop: "compat",
45+
globals: {
46+
react: "React"
47+
},
48+
entryFileNames: () => {
49+
return `[name].${CJS ? "js" : "mjs"}`;
50+
}
51+
}
52+
}
53+
},
54+
test: {
55+
global: true,
56+
environment: "jsdom",
57+
setupFiles: "./setupTest.ts"
58+
}
59+
};
60+
});
61+
export {
62+
vite_config_default as default
63+
};
64+
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcubXRzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyJjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZGlybmFtZSA9IFwiL1VzZXJzL3RzeXkvd29yay9qYXZhc2NyaXB0L3BhY2thZ2VzL3JlYWN0LWNvcmVcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIi9Vc2Vycy90c3l5L3dvcmsvamF2YXNjcmlwdC9wYWNrYWdlcy9yZWFjdC1jb3JlL3ZpdGUuY29uZmlnLm10c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vVXNlcnMvdHN5eS93b3JrL2phdmFzY3JpcHQvcGFja2FnZXMvcmVhY3QtY29yZS92aXRlLmNvbmZpZy5tdHNcIjsvLy8gPHJlZmVyZW5jZSB0eXBlcz1cInZpdGVzdFwiIC8+XG5pbXBvcnQgeyBjb2RlY292Vml0ZVBsdWdpbiB9IGZyb20gXCJAY29kZWNvdi92aXRlLXBsdWdpblwiO1xuaW1wb3J0IHJlYWN0IGZyb20gXCJAdml0ZWpzL3BsdWdpbi1yZWFjdFwiO1xuaW1wb3J0IHsgcmVzb2x2ZSB9IGZyb20gXCJwYXRoXCI7XG5pbXBvcnQgeyBMaWJyYXJ5Rm9ybWF0cywgZGVmaW5lQ29uZmlnLCBsb2FkRW52IH0gZnJvbSBcInZpdGVcIjtcbmltcG9ydCBkdHMgZnJvbSBcInZpdGUtcGx1Z2luLWR0c1wiO1xuaW1wb3J0IG5vQnVuZGxlUGx1Z2luIGZyb20gXCJ2aXRlLXBsdWdpbi1uby1idW5kbGVcIjtcblxuLy8gaHR0cHM6Ly92aXRlanMuZGV2L2NvbmZpZy9cbmV4cG9ydCBkZWZhdWx0IGRlZmluZUNvbmZpZygoeyBtb2RlIH0pID0+IHtcbiAgY29uc3QgZW52ID0gbG9hZEVudihtb2RlLCBwcm9jZXNzLmN3ZCgpLCBcIlwiKTtcbiAgY29uc3QgQ0pTID0gZW52LkJVSUxEX1RBUkdFVD8udG9Mb2NhbGVMb3dlckNhc2UoKT8ubWF0Y2goXCJjanNcIik7XG4gIGNvbnN0IGZvcm1hdHM6IExpYnJhcnlGb3JtYXRzW10gPSBDSlMgPyBbXCJjanNcIl0gOiBbXCJlc1wiXTtcblxuICByZXR1cm4ge1xuICAgIHBsdWdpbnM6IFtcbiAgICAgIGR0cyh7XG4gICAgICAgIG91dERpcjogXCJkaXN0L3R5cGVzXCIsXG4gICAgICB9KSxcbiAgICAgIHJlYWN0KHtcbiAgICAgICAganN4UnVudGltZTogXCJjbGFzc2ljXCIsXG4gICAgICAgIGJhYmVsOiB7XG4gICAgICAgICAgcGx1Z2luczogW1wicmVhY3QtcmVxdWlyZVwiXSxcbiAgICAgICAgfSxcbiAgICAgIH0pLFxuICAgICAgbm9CdW5kbGVQbHVnaW4oeyByb290OiBcIi4vc3JjXCIgfSksXG4gICAgICBjb2RlY292Vml0ZVBsdWdpbih7XG4gICAgICAgIGVuYWJsZUJ1bmRsZUFuYWx5c2lzOiBwcm9jZXNzLmVudi5DT0RFQ09WX1RPS0VOICE9PSB1bmRlZmluZWQsXG4gICAgICAgIGJ1bmRsZU5hbWU6IFwiQGtub2NrbGFicy9yZWFjdC1jb3JlXCIsXG4gICAgICAgIHVwbG9hZFRva2VuOiBwcm9jZXNzLmVudi5DT0RFQ09WX1RPS0VOLFxuICAgICAgfSksXG4gICAgXSxcbiAgICBidWlsZDoge1xuICAgICAgb3V0RGlyOiBDSlMgPyBcImRpc3QvY2pzXCIgOiBcImRpc3QvZXNtXCIsXG4gICAgICBzb3VyY2VtYXA6IHRydWUsXG4gICAgICBsaWI6IHtcbiAgICAgICAgZW50cnk6IHJlc29sdmUoX19kaXJuYW1lLCBcInNyY1wiKSxcbiAgICAgICAgZmlsZU5hbWU6IGBbbmFtZV1gLFxuICAgICAgICBuYW1lOiBcInJlYWN0LWNvcmVcIixcbiAgICAgICAgZm9ybWF0cyxcbiAgICAgIH0sXG4gICAgICByb2xsdXBPcHRpb25zOiB7XG4gICAgICAgIC8vIEV4dGVybmFsIHBhY2thZ2VzIHRoYXQgc2hvdWxkIG5vdCBiZSBidW5kbGVkXG4gICAgICAgIGV4dGVybmFsOiBbXCJyZWFjdFwiXSxcbiAgICAgICAgb3V0cHV0OiB7XG4gICAgICAgICAgaW50ZXJvcDogXCJjb21wYXRcIixcbiAgICAgICAgICBnbG9iYWxzOiB7XG4gICAgICAgICAgICByZWFjdDogXCJSZWFjdFwiLFxuICAgICAgICAgIH0sXG4gICAgICAgICAgZW50cnlGaWxlTmFtZXM6ICgpID0+IHtcbiAgICAgICAgICAgIHJldHVybiBgW25hbWVdLiR7Q0pTID8gXCJqc1wiIDogXCJtanNcIn1gO1xuICAgICAgICAgIH0sXG4gICAgICAgIH0sXG4gICAgICB9LFxuICAgIH0sXG4gICAgdGVzdDoge1xuICAgICAgZ2xvYmFsOiB0cnVlLFxuICAgICAgZW52aXJvbm1lbnQ6IFwianNkb21cIixcbiAgICAgIHNldHVwRmlsZXM6IFwiLi9zZXR1cFRlc3QudHNcIixcbiAgICB9LFxuICB9O1xufSk7XG4iXSwKICAibWFwcGluZ3MiOiAiO0FBQ0EsU0FBUyx5QkFBeUI7QUFDbEMsT0FBTyxXQUFXO0FBQ2xCLFNBQVMsZUFBZTtBQUN4QixTQUF5QixjQUFjLGVBQWU7QUFDdEQsT0FBTyxTQUFTO0FBQ2hCLE9BQU8sb0JBQW9CO0FBTjNCLElBQU0sbUNBQW1DO0FBU3pDLElBQU8sc0JBQVEsYUFBYSxDQUFDLEVBQUUsS0FBSyxNQUFNO0FBQ3hDLFFBQU0sTUFBTSxRQUFRLE1BQU0sUUFBUSxJQUFJLEdBQUcsRUFBRTtBQUMzQyxRQUFNLE1BQU0sSUFBSSxjQUFjLGtCQUFrQixHQUFHLE1BQU0sS0FBSztBQUM5RCxRQUFNLFVBQTRCLE1BQU0sQ0FBQyxLQUFLLElBQUksQ0FBQyxJQUFJO0FBRXZELFNBQU87QUFBQSxJQUNMLFNBQVM7QUFBQSxNQUNQLElBQUk7QUFBQSxRQUNGLFFBQVE7QUFBQSxNQUNWLENBQUM7QUFBQSxNQUNELE1BQU07QUFBQSxRQUNKLFlBQVk7QUFBQSxRQUNaLE9BQU87QUFBQSxVQUNMLFNBQVMsQ0FBQyxlQUFlO0FBQUEsUUFDM0I7QUFBQSxNQUNGLENBQUM7QUFBQSxNQUNELGVBQWUsRUFBRSxNQUFNLFFBQVEsQ0FBQztBQUFBLE1BQ2hDLGtCQUFrQjtBQUFBLFFBQ2hCLHNCQUFzQixRQUFRLElBQUksa0JBQWtCO0FBQUEsUUFDcEQsWUFBWTtBQUFBLFFBQ1osYUFBYSxRQUFRLElBQUk7QUFBQSxNQUMzQixDQUFDO0FBQUEsSUFDSDtBQUFBLElBQ0EsT0FBTztBQUFBLE1BQ0wsUUFBUSxNQUFNLGFBQWE7QUFBQSxNQUMzQixXQUFXO0FBQUEsTUFDWCxLQUFLO0FBQUEsUUFDSCxPQUFPLFFBQVEsa0NBQVcsS0FBSztBQUFBLFFBQy9CLFVBQVU7QUFBQSxRQUNWLE1BQU07QUFBQSxRQUNOO0FBQUEsTUFDRjtBQUFBLE1BQ0EsZUFBZTtBQUFBO0FBQUEsUUFFYixVQUFVLENBQUMsT0FBTztBQUFBLFFBQ2xCLFFBQVE7QUFBQSxVQUNOLFNBQVM7QUFBQSxVQUNULFNBQVM7QUFBQSxZQUNQLE9BQU87QUFBQSxVQUNUO0FBQUEsVUFDQSxnQkFBZ0IsTUFBTTtBQUNwQixtQkFBTyxVQUFVLE1BQU0sT0FBTyxLQUFLO0FBQUEsVUFDckM7QUFBQSxRQUNGO0FBQUEsTUFDRjtBQUFBLElBQ0Y7QUFBQSxJQUNBLE1BQU07QUFBQSxNQUNKLFFBQVE7QUFBQSxNQUNSLGFBQWE7QUFBQSxNQUNiLFlBQVk7QUFBQSxJQUNkO0FBQUEsRUFDRjtBQUNGLENBQUM7IiwKICAibmFtZXMiOiBbXQp9Cg==

packages/react/src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ export {
4343
Card,
4444
CardView,
4545
KnockGuideProvider,
46-
GuideToolbar as KnockGuideToolbar,
4746
Modal,
4847
ModalView,
4948
} from "./modules/guide";
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { Button } from "@telegraph/button";
2+
3+
import { MAX_Z_INDEX } from "./shared";
4+
import "./styles.css";
5+
6+
type Props = {
7+
onClick: () => void;
8+
};
9+
10+
export const KnockButton = ({ onClick }: Props) => {
11+
return (
12+
<Button
13+
onClick={onClick}
14+
position="fixed"
15+
top="4"
16+
right="4"
17+
bg="surface-2"
18+
shadow="3"
19+
rounded="3"
20+
w="10"
21+
h="10"
22+
variant="soft"
23+
data-tgph-appearance="dark"
24+
aria-label="Expand guide toolbar"
25+
style={{ zIndex: MAX_Z_INDEX }}
26+
>
27+
<svg
28+
width="40"
29+
height="40"
30+
viewBox="0 0 40 40"
31+
fill="none"
32+
xmlns="http://www.w3.org/2000/svg"
33+
style={{
34+
position: "absolute",
35+
top: "50%",
36+
left: "50%",
37+
transform: "translate(-50%, -50%)",
38+
}}
39+
>
40+
<path
41+
d="M11.6001 32.4V7.59998H16.6365V21.8219H16.7774L22.3067 14.8525H27.9418L21.8138 22.0696L28.4001 32.4H22.7996L18.8555 25.572L16.6365 28.0839V32.4H11.6001Z"
42+
fill="#EDEEEF"
43+
/>
44+
<path
45+
d="M28.4 10.4C28.4 11.9464 27.1467 13.2 25.6 13.2C24.0534 13.2 22.8 11.9464 22.8 10.4C22.8 8.85358 24.0534 7.59998 25.6 7.59998C27.1467 7.59998 28.4 8.85358 28.4 10.4Z"
46+
fill="#FF573A"
47+
/>
48+
</svg>
49+
</Button>
50+
);
51+
};

0 commit comments

Comments
 (0)