wizards 0.4.1 → 0.4.2
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/apps/runtime/dist/docs/dev/plugins/reloading.md +3 -3
- package/apps/runtime/dist/docs/dev/plugins/runners.md +9 -12
- package/apps/runtime/dist/docs/user/test-and-share.md +22 -58
- package/apps/web/dist/assets/{AboutDialog-CXEpsI6L.js → AboutDialog-CH1rQdia.js} +1 -1
- package/apps/web/dist/assets/{ChangelogDialog-Cc2APUGI.js → ChangelogDialog-78MQLHfA.js} +1 -1
- package/apps/web/dist/assets/{ChatView-B2r4dW_m.js → ChatView-DLJfy69U.js} +1 -1
- package/apps/web/dist/assets/{EditorPage-CNAYlSms.js → EditorPage-BYrZokVk.js} +1 -1
- package/apps/web/dist/assets/{FlowPreview-DzU1nGDb.js → FlowPreview-xIWPdOCV.js} +1 -1
- package/apps/web/dist/assets/{Harness-ALQ9Csm6.js → Harness-CsDhNLSW.js} +1 -1
- package/apps/web/dist/assets/{NewWizardChannels-DMPqHkCF.js → NewWizardChannels-JB3V6DCL.js} +1 -1
- package/apps/web/dist/assets/{NewWizardPage-pi053e8N.js → NewWizardPage-Bkh-hjSe.js} +1 -1
- package/apps/web/dist/assets/{OnboardingPage-TYNX6Umd.js → OnboardingPage-Diw1ILjC.js} +1 -1
- package/apps/web/dist/assets/{ProjectPage-CvEkx_Ux.js → ProjectPage-FgoZDzPI.js} +1 -1
- package/apps/web/dist/assets/{PublicRunner-BrivKNHo.js → PublicRunner-ClT6B7t5.js} +1 -1
- package/apps/web/dist/assets/{RunDrawer-3osugizk.js → RunDrawer-DAwTEyjM.js} +1 -1
- package/apps/web/dist/assets/{RunnerView-Ci4_WzVv.js → RunnerView-B_tJroFM.js} +1 -1
- package/apps/web/dist/assets/{SettingsPage-DKntf7-9.js → SettingsPage-BsMN-XQs.js} +1 -1
- package/apps/web/dist/assets/{SetupPage-DZQxEQyk.js → SetupPage-C7FuIpSt.js} +1 -1
- package/apps/web/dist/assets/{SharePage-B9l4KthU.js → SharePage-CbFe83L9.js} +1 -1
- package/apps/web/dist/assets/changelog-mYclku73.js +1 -0
- package/apps/web/dist/assets/{esm-CuqXcPS3.js → esm-BhIeKBJY.js} +1 -1
- package/apps/web/dist/assets/{index-j09nLtEs.js → index-fflYi8P1.js} +3 -3
- package/apps/web/dist/assets/{settings-menu-C7VsRpe9.js → settings-menu-C5Y8SOhG.js} +1 -1
- package/apps/web/dist/index.html +1 -1
- package/package.json +1 -1
- package/apps/web/dist/assets/changelog-CxdXZo3V.js +0 -1
- package/modules/calls/dist/client.css +0 -3
- package/modules/calls/dist/client.js +0 -1
- package/modules/calls/engenty.plugin.json +0 -8
- package/modules/calls/migrations/0001_init.sql +0 -26
- package/modules/calls/package.json +0 -25
- package/modules/calls/src/openai.ts +0 -155
- package/modules/calls/src/plugin.ts +0 -407
- package/modules/calls/src/rules.ts +0 -226
- package/modules/calls/src/schema.ts +0 -39
- package/modules/calls/src/session.ts +0 -353
- package/modules/calls/src/store.ts +0 -124
- package/modules/calls/src/twilio.ts +0 -90
- package/modules/calls/tsconfig.json +0 -10
- package/modules/calls/ui/Settings.tsx +0 -137
- package/modules/calls/ui/app.ts +0 -50
- package/modules/calls/ui/messages.ts +0 -81
- package/modules/calls/ui/parts.tsx +0 -36
- package/modules/calls/ui/plugin.tsx +0 -15
- package/modules/sms/dist/client.css +0 -3
- package/modules/sms/dist/client.js +0 -1
- package/modules/sms/engenty.plugin.json +0 -8
- package/modules/sms/migrations/0001_init.sql +0 -26
- package/modules/sms/package.json +0 -24
- package/modules/sms/src/plugin.ts +0 -329
- package/modules/sms/src/schema.ts +0 -26
- package/modules/sms/src/store.ts +0 -140
- package/modules/sms/src/surface.ts +0 -70
- package/modules/sms/src/twilio.ts +0 -130
- package/modules/sms/tsconfig.json +0 -10
- package/modules/sms/ui/Settings.tsx +0 -113
- package/modules/sms/ui/Share.tsx +0 -60
- package/modules/sms/ui/app.ts +0 -51
- package/modules/sms/ui/messages.ts +0 -70
- package/modules/sms/ui/parts.tsx +0 -36
- package/modules/sms/ui/plugin.tsx +0 -20
- package/modules/whatsapp/dist/client.css +0 -3
- package/modules/whatsapp/dist/client.js +0 -1
- package/modules/whatsapp/engenty.plugin.json +0 -8
- package/modules/whatsapp/migrations/0001_init.sql +0 -30
- package/modules/whatsapp/package.json +0 -24
- package/modules/whatsapp/src/graph.ts +0 -271
- package/modules/whatsapp/src/inbound.ts +0 -143
- package/modules/whatsapp/src/plugin.ts +0 -374
- package/modules/whatsapp/src/schema.ts +0 -32
- package/modules/whatsapp/src/store.ts +0 -156
- package/modules/whatsapp/src/surface.ts +0 -127
- package/modules/whatsapp/tsconfig.json +0 -10
- package/modules/whatsapp/ui/Settings.tsx +0 -141
- package/modules/whatsapp/ui/Share.tsx +0 -60
- package/modules/whatsapp/ui/app.ts +0 -54
- package/modules/whatsapp/ui/messages.ts +0 -80
- package/modules/whatsapp/ui/parts.tsx +0 -36
- package/modules/whatsapp/ui/plugin.tsx +0 -20
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import { Button, Card, Input, Label, Spinner } from "@engenty-wizards/web/ui";
|
|
2
|
-
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
3
|
-
import { useEffect, useState } from "react";
|
|
4
|
-
import { type AccountView, api, errorText, KEY, t } from "./app";
|
|
5
|
-
import { Shown } from "./parts";
|
|
6
|
-
|
|
7
|
-
/** Settings → WhatsApp: the number, the token, and what to enter in the Meta app. */
|
|
8
|
-
export function SettingsSection() {
|
|
9
|
-
const cache = useQueryClient();
|
|
10
|
-
const account = useQuery({
|
|
11
|
-
queryKey: [KEY, "account"],
|
|
12
|
-
queryFn: () => api().get<AccountView>("/account"),
|
|
13
|
-
});
|
|
14
|
-
const [form, setForm] = useState({
|
|
15
|
-
phoneNumberId: "",
|
|
16
|
-
number: "",
|
|
17
|
-
accessToken: "",
|
|
18
|
-
appSecret: "",
|
|
19
|
-
template: "",
|
|
20
|
-
});
|
|
21
|
-
const [to, setTo] = useState("");
|
|
22
|
-
useEffect(() => {
|
|
23
|
-
if (account.data) {
|
|
24
|
-
setForm((f) => ({
|
|
25
|
-
...f,
|
|
26
|
-
phoneNumberId: account.data.phoneNumberId,
|
|
27
|
-
number: account.data.number,
|
|
28
|
-
template: account.data.template,
|
|
29
|
-
}));
|
|
30
|
-
}
|
|
31
|
-
}, [account.data]);
|
|
32
|
-
const refresh = () => cache.invalidateQueries({ queryKey: [KEY] });
|
|
33
|
-
const save = useMutation({
|
|
34
|
-
mutationFn: () =>
|
|
35
|
-
api().put<AccountView>("/account", {
|
|
36
|
-
phoneNumberId: form.phoneNumberId,
|
|
37
|
-
number: form.number,
|
|
38
|
-
accessToken: form.accessToken || undefined,
|
|
39
|
-
appSecret: form.appSecret || undefined,
|
|
40
|
-
template: form.template || null,
|
|
41
|
-
}),
|
|
42
|
-
onSuccess: async () => {
|
|
43
|
-
setForm((f) => ({ ...f, accessToken: "", appSecret: "" }));
|
|
44
|
-
await refresh();
|
|
45
|
-
},
|
|
46
|
-
});
|
|
47
|
-
const remove = useMutation({
|
|
48
|
-
mutationFn: () => api().del<AccountView>("/account"),
|
|
49
|
-
onSuccess: refresh,
|
|
50
|
-
});
|
|
51
|
-
const test = useMutation({
|
|
52
|
-
mutationFn: () => api().post<{ ok: boolean }>("/account/test", { to }),
|
|
53
|
-
});
|
|
54
|
-
const set = (key: keyof typeof form) => (e: React.ChangeEvent<HTMLInputElement>) =>
|
|
55
|
-
setForm((f) => ({ ...f, [key]: e.target.value }));
|
|
56
|
-
const data = account.data;
|
|
57
|
-
return (
|
|
58
|
-
<section className="flex flex-col gap-2.5">
|
|
59
|
-
<h2 className="px-1 font-display font-semibold text-lg leading-tight">{t("title")}</h2>
|
|
60
|
-
<Card className="flex flex-col gap-4 p-5 text-[0.875rem]">
|
|
61
|
-
<p className="text-[0.8125rem] text-ink-3">{t("intro")}</p>
|
|
62
|
-
{account.isLoading ? <Spinner className="mx-auto" /> : null}
|
|
63
|
-
<div>
|
|
64
|
-
<Label hint={t("phoneNumberIdHint")}>{t("phoneNumberId")}</Label>
|
|
65
|
-
<Input value={form.phoneNumberId} onChange={set("phoneNumberId")} autoComplete="off" />
|
|
66
|
-
</div>
|
|
67
|
-
<div>
|
|
68
|
-
<Label hint={t("numberHint")}>{t("number")}</Label>
|
|
69
|
-
<Input value={form.number} onChange={set("number")} inputMode="numeric" />
|
|
70
|
-
</div>
|
|
71
|
-
<div>
|
|
72
|
-
<Label hint={data?.hasToken ? t("tokenKept") : t("tokenHint")}>{t("token")}</Label>
|
|
73
|
-
<Input
|
|
74
|
-
type="password"
|
|
75
|
-
value={form.accessToken}
|
|
76
|
-
onChange={set("accessToken")}
|
|
77
|
-
placeholder={data?.hasToken ? "••••••••" : ""}
|
|
78
|
-
autoComplete="new-password"
|
|
79
|
-
/>
|
|
80
|
-
</div>
|
|
81
|
-
<div>
|
|
82
|
-
<Label hint={t("secretHint")}>{t("secret")}</Label>
|
|
83
|
-
<Input
|
|
84
|
-
type="password"
|
|
85
|
-
value={form.appSecret}
|
|
86
|
-
onChange={set("appSecret")}
|
|
87
|
-
placeholder={data?.hasSecret ? "••••••••" : ""}
|
|
88
|
-
autoComplete="new-password"
|
|
89
|
-
/>
|
|
90
|
-
</div>
|
|
91
|
-
<div>
|
|
92
|
-
<Label hint={t("templateHint")}>{t("template")}</Label>
|
|
93
|
-
<Input value={form.template} onChange={set("template")} autoComplete="off" />
|
|
94
|
-
</div>
|
|
95
|
-
<div className="flex items-center gap-3">
|
|
96
|
-
<Button onClick={() => save.mutate()} busy={save.isPending}>
|
|
97
|
-
{t("save")}
|
|
98
|
-
</Button>
|
|
99
|
-
{data?.connected ? (
|
|
100
|
-
<Button variant="ghost" onClick={() => remove.mutate()} busy={remove.isPending}>
|
|
101
|
-
{t("disconnect")}
|
|
102
|
-
</Button>
|
|
103
|
-
) : null}
|
|
104
|
-
{save.isSuccess ? (
|
|
105
|
-
<span className="text-[0.8125rem] text-ink-3">{t("saved")}</span>
|
|
106
|
-
) : null}
|
|
107
|
-
</div>
|
|
108
|
-
{save.error ? <p className="text-[0.8125rem] text-rose">{errorText(save.error)}</p> : null}
|
|
109
|
-
</Card>
|
|
110
|
-
{data?.connected ? (
|
|
111
|
-
<Card className="flex flex-col gap-4 p-5 text-[0.875rem]">
|
|
112
|
-
<div className="font-medium">{t("meta")}</div>
|
|
113
|
-
<p className="text-[0.8125rem] text-ink-3">{t("metaHint")}</p>
|
|
114
|
-
<Shown label={t("webhook")} value={data.webhook} />
|
|
115
|
-
{data.verifyToken ? <Shown label={t("verifyToken")} value={data.verifyToken} /> : null}
|
|
116
|
-
<div>
|
|
117
|
-
<Label>{t("testTo")}</Label>
|
|
118
|
-
<div className="flex items-center gap-2">
|
|
119
|
-
<Input
|
|
120
|
-
value={to}
|
|
121
|
-
onChange={(e) => setTo(e.target.value)}
|
|
122
|
-
placeholder="+43 660 …"
|
|
123
|
-
inputMode="tel"
|
|
124
|
-
className="max-w-xs"
|
|
125
|
-
/>
|
|
126
|
-
<Button variant="secondary" onClick={() => test.mutate()} busy={test.isPending}>
|
|
127
|
-
{t("sendTest")}
|
|
128
|
-
</Button>
|
|
129
|
-
</div>
|
|
130
|
-
{test.isSuccess ? (
|
|
131
|
-
<p className="mt-2 text-[0.8125rem] text-ink-3">{t("testSent")}</p>
|
|
132
|
-
) : null}
|
|
133
|
-
{test.error ? (
|
|
134
|
-
<p className="mt-2 text-[0.8125rem] text-rose">{errorText(test.error)}</p>
|
|
135
|
-
) : null}
|
|
136
|
-
</div>
|
|
137
|
-
</Card>
|
|
138
|
-
) : null}
|
|
139
|
-
</section>
|
|
140
|
-
);
|
|
141
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import type { StudioShareSectionProps } from "@engenty-wizards/plugin-sdk/studio";
|
|
2
|
-
import { Input } from "@engenty-wizards/web/ui";
|
|
3
|
-
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
4
|
-
import { useState } from "react";
|
|
5
|
-
import { api, type Binding, errorText, KEY, t } from "./app";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* In the share dialog's settings, while WhatsApp is on: the keyword that picks the wizard. Its
|
|
9
|
-
* number, its link and the QR code the dialog shows itself, from the runner's address.
|
|
10
|
-
*/
|
|
11
|
-
export function ShareSection({ wizard }: StudioShareSectionProps) {
|
|
12
|
-
const cache = useQueryClient();
|
|
13
|
-
const binding = useQuery({
|
|
14
|
-
queryKey: [KEY, "binding", wizard.id],
|
|
15
|
-
queryFn: () => api().get<Binding>(`/binding/${wizard.id}`),
|
|
16
|
-
});
|
|
17
|
-
const [draft, setDraft] = useState<string | null>(null);
|
|
18
|
-
const save = useMutation({
|
|
19
|
-
mutationFn: (keyword: string) => api().put<Binding>(`/binding/${wizard.id}`, { keyword }),
|
|
20
|
-
onSuccess: async () => {
|
|
21
|
-
setDraft(null);
|
|
22
|
-
// The dialog's row shows the keyword and the link that writes it.
|
|
23
|
-
await Promise.all([
|
|
24
|
-
cache.invalidateQueries({ queryKey: [KEY, "binding", wizard.id] }),
|
|
25
|
-
cache.invalidateQueries({ queryKey: ["wizard-runners", wizard.id] }),
|
|
26
|
-
]);
|
|
27
|
-
},
|
|
28
|
-
});
|
|
29
|
-
if (!binding.data) {
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
const keyword = draft ?? binding.data.keyword;
|
|
33
|
-
const commit = () => {
|
|
34
|
-
if (draft?.trim() && draft.trim() !== binding.data?.keyword) {
|
|
35
|
-
save.mutate(draft);
|
|
36
|
-
} else {
|
|
37
|
-
setDraft(null);
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
return (
|
|
41
|
-
<div className="flex flex-col gap-1.5">
|
|
42
|
-
<label className="flex flex-col gap-1.5">
|
|
43
|
-
<span className="font-medium text-[0.875rem]">{t("keyword")}</span>
|
|
44
|
-
<Input
|
|
45
|
-
value={keyword}
|
|
46
|
-
onChange={(e) => setDraft(e.target.value)}
|
|
47
|
-
onBlur={commit}
|
|
48
|
-
onKeyDown={(e) => {
|
|
49
|
-
if (e.key === "Enter") {
|
|
50
|
-
e.currentTarget.blur();
|
|
51
|
-
}
|
|
52
|
-
}}
|
|
53
|
-
className="max-w-xs"
|
|
54
|
-
/>
|
|
55
|
-
</label>
|
|
56
|
-
<p className="text-[0.8125rem] text-ink-3">{t("keywordHint")}</p>
|
|
57
|
-
{save.error ? <p className="text-[0.8125rem] text-rose">{errorText(save.error)}</p> : null}
|
|
58
|
-
</div>
|
|
59
|
-
);
|
|
60
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import type { StudioPluginContext } from "@engenty-wizards/plugin-sdk/studio";
|
|
2
|
-
import { messages } from "./messages";
|
|
3
|
-
|
|
4
|
-
/** The studio this half was handed, and its words. */
|
|
5
|
-
|
|
6
|
-
let current: StudioPluginContext | null = null;
|
|
7
|
-
let translate: ((key: string, vars?: Record<string, string | number>) => string) | null = null;
|
|
8
|
-
|
|
9
|
-
export function setStudio(studio: StudioPluginContext) {
|
|
10
|
-
current = studio;
|
|
11
|
-
translate = studio.i18n.register(messages) as typeof translate;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function studio(): StudioPluginContext {
|
|
15
|
-
if (!current) {
|
|
16
|
-
throw new Error("The WhatsApp plugin is not loaded.");
|
|
17
|
-
}
|
|
18
|
-
return current;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export type Key = keyof (typeof messages)["de"];
|
|
22
|
-
export const t = (key: Key, vars?: Record<string, string | number>) =>
|
|
23
|
-
translate?.(key, vars) ?? key;
|
|
24
|
-
export const api = () => studio().api;
|
|
25
|
-
|
|
26
|
-
export const KEY = "whatsapp";
|
|
27
|
-
|
|
28
|
-
/** What a failed request says, in the person's language. */
|
|
29
|
-
export function errorText(error: unknown): string {
|
|
30
|
-
const body = (error as { body?: { code?: string; error?: string } } | null)?.body;
|
|
31
|
-
const code = body?.code;
|
|
32
|
-
const message = body?.error ?? (error instanceof Error ? error.message : String(error));
|
|
33
|
-
if (code && `error_${code}` in messages.de) {
|
|
34
|
-
return t(`error_${code}` as Key, { message });
|
|
35
|
-
}
|
|
36
|
-
return t("error_other", { message });
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface AccountView {
|
|
40
|
-
connected: boolean;
|
|
41
|
-
phoneNumberId: string;
|
|
42
|
-
number: string;
|
|
43
|
-
hasToken: boolean;
|
|
44
|
-
hasSecret: boolean;
|
|
45
|
-
template: string;
|
|
46
|
-
verifyToken: string | null;
|
|
47
|
-
webhook: string;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export interface Binding {
|
|
51
|
-
keyword: string;
|
|
52
|
-
link: string | null;
|
|
53
|
-
number: string | null;
|
|
54
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
/** The studio half's words. `de` is the source. */
|
|
2
|
-
export const messages = {
|
|
3
|
-
de: {
|
|
4
|
-
title: "WhatsApp",
|
|
5
|
-
intro:
|
|
6
|
-
"Wizards laufen in einem WhatsApp-Thread auf der eigenen Business-Nummer. Dafür braucht es eine Meta-App mit dem Produkt WhatsApp: die ID der Telefonnummer und einen dauerhaften Access Token aus dem Meta-Dashboard.",
|
|
7
|
-
phoneNumberId: "Phone number ID",
|
|
8
|
-
phoneNumberIdHint: "Im Meta-Dashboard unter WhatsApp → API-Setup.",
|
|
9
|
-
number: "Nummer",
|
|
10
|
-
numberHint: "Mit Ländervorwahl, nur Ziffern: 43660…",
|
|
11
|
-
token: "Access Token",
|
|
12
|
-
tokenHint: "Ein dauerhafter Token eines System-Users mit whatsapp_business_messaging.",
|
|
13
|
-
tokenKept: "Ist hinterlegt. Leer lassen, um ihn zu behalten.",
|
|
14
|
-
secret: "App Secret",
|
|
15
|
-
secretHint: "Freiwillig. Damit wird jede Nachricht von Meta auf ihre Signatur geprüft.",
|
|
16
|
-
template: "Template",
|
|
17
|
-
templateHint:
|
|
18
|
-
"Freiwillig. Der Name eines genehmigten Utility-Templates mit einem Platzhalter im Text: damit meldet sich der Wizard nach einem Tag Stille mit dem Link.",
|
|
19
|
-
save: "Speichern",
|
|
20
|
-
saved: "Gespeichert.",
|
|
21
|
-
disconnect: "Trennen",
|
|
22
|
-
meta: "In der Meta-App eintragen",
|
|
23
|
-
metaHint:
|
|
24
|
-
"Unter WhatsApp → Konfiguration: die Webhook-Adresse und den Verify Token eintragen, dann das Feld „messages“ abonnieren.",
|
|
25
|
-
webhook: "Webhook-Adresse",
|
|
26
|
-
verifyToken: "Verify Token",
|
|
27
|
-
copy: "Kopieren",
|
|
28
|
-
copied: "Kopiert",
|
|
29
|
-
testTo: "Testnachricht an",
|
|
30
|
-
sendTest: "Senden",
|
|
31
|
-
testSent: "Angekommen? Dann passt alles.",
|
|
32
|
-
keyword: "Stichwort",
|
|
33
|
-
keywordHint:
|
|
34
|
-
"Wer dieses Wort an die Nummer schreibt, startet den Wizard. Der Link und der QR-Code schreiben es vor.",
|
|
35
|
-
error_bad_keyword:
|
|
36
|
-
"Ein Stichwort besteht aus Buchstaben, Ziffern, Leerzeichen und Bindestrichen, bis zu 40.",
|
|
37
|
-
error_keyword_taken: "Ein anderer Wizard hat dieses Stichwort schon.",
|
|
38
|
-
error_no_token: "Es braucht einen Access Token.",
|
|
39
|
-
error_provider: "WhatsApp hat die Nachricht nicht angenommen: {message}",
|
|
40
|
-
error_other: "{message}",
|
|
41
|
-
},
|
|
42
|
-
en: {
|
|
43
|
-
title: "WhatsApp",
|
|
44
|
-
intro:
|
|
45
|
-
"Wizards run in a WhatsApp thread on your own business number. It takes a Meta app with the WhatsApp product: the phone number's ID and a permanent access token from the Meta dashboard.",
|
|
46
|
-
phoneNumberId: "Phone number ID",
|
|
47
|
-
phoneNumberIdHint: "In the Meta dashboard under WhatsApp → API setup.",
|
|
48
|
-
number: "Number",
|
|
49
|
-
numberHint: "With the country code, digits only: 43660…",
|
|
50
|
-
token: "Access token",
|
|
51
|
-
tokenHint: "A permanent token of a system user with whatsapp_business_messaging.",
|
|
52
|
-
tokenKept: "Is set. Leave empty to keep it.",
|
|
53
|
-
secret: "App secret",
|
|
54
|
-
secretHint: "Optional. With it, every message from Meta is checked for its signature.",
|
|
55
|
-
template: "Template",
|
|
56
|
-
templateHint:
|
|
57
|
-
"Optional. The name of an approved utility template with one placeholder in its text: the wizard reaches back with the link after a day of silence.",
|
|
58
|
-
save: "Save",
|
|
59
|
-
saved: "Saved.",
|
|
60
|
-
disconnect: "Disconnect",
|
|
61
|
-
meta: "Enter in the Meta app",
|
|
62
|
-
metaHint:
|
|
63
|
-
"Under WhatsApp → Configuration: enter the webhook address and the verify token, then subscribe to the “messages” field.",
|
|
64
|
-
webhook: "Webhook address",
|
|
65
|
-
verifyToken: "Verify token",
|
|
66
|
-
copy: "Copy",
|
|
67
|
-
copied: "Copied",
|
|
68
|
-
testTo: "Test message to",
|
|
69
|
-
sendTest: "Send",
|
|
70
|
-
testSent: "Arrived? Then everything is set.",
|
|
71
|
-
keyword: "Keyword",
|
|
72
|
-
keywordHint:
|
|
73
|
-
"Whoever writes this word to the number starts the wizard. The link and the QR code write it for them.",
|
|
74
|
-
error_bad_keyword: "A keyword is letters, digits, spaces and dashes, up to 40.",
|
|
75
|
-
error_keyword_taken: "Another wizard has this keyword already.",
|
|
76
|
-
error_no_token: "An access token is needed.",
|
|
77
|
-
error_provider: "WhatsApp did not take the message: {message}",
|
|
78
|
-
error_other: "{message}",
|
|
79
|
-
},
|
|
80
|
-
} as const;
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { IconButton } from "@engenty-wizards/web/ui";
|
|
2
|
-
import { Check, Copy } from "lucide-react";
|
|
3
|
-
import { useState } from "react";
|
|
4
|
-
import { t } from "./app";
|
|
5
|
-
|
|
6
|
-
/** A value to take along: the webhook address, the verify token, the link. */
|
|
7
|
-
export function CopyButton({ text }: { text: string }) {
|
|
8
|
-
const [done, setDone] = useState(false);
|
|
9
|
-
return (
|
|
10
|
-
<IconButton
|
|
11
|
-
label={t(done ? "copied" : "copy")}
|
|
12
|
-
className="size-8"
|
|
13
|
-
onClick={() => {
|
|
14
|
-
void navigator.clipboard.writeText(text).then(() => {
|
|
15
|
-
setDone(true);
|
|
16
|
-
setTimeout(() => setDone(false), 1500);
|
|
17
|
-
});
|
|
18
|
-
}}
|
|
19
|
-
>
|
|
20
|
-
{done ? <Check className="size-4" /> : <Copy className="size-4" />}
|
|
21
|
-
</IconButton>
|
|
22
|
-
);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** A value shown as it is, with the button that copies it. */
|
|
26
|
-
export function Shown({ label, value }: { label: string; value: string }) {
|
|
27
|
-
return (
|
|
28
|
-
<div className="flex items-center gap-2">
|
|
29
|
-
<div className="min-w-0 flex-1">
|
|
30
|
-
<div className="text-[0.8125rem] text-ink-3">{label}</div>
|
|
31
|
-
<div className="select-all truncate font-mono text-[0.8125rem]">{value}</div>
|
|
32
|
-
</div>
|
|
33
|
-
<CopyButton text={value} />
|
|
34
|
-
</div>
|
|
35
|
-
);
|
|
36
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { defineStudioPlugin } from "@engenty-wizards/plugin-sdk/studio";
|
|
2
|
-
import { MessageCircle } from "lucide-react";
|
|
3
|
-
import { setStudio, t } from "./app";
|
|
4
|
-
import { SettingsSection } from "./Settings";
|
|
5
|
-
import { ShareSection } from "./Share";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* The studio half: the number and the token in the settings, and the keyword with its link
|
|
9
|
-
* under the WhatsApp row of the share dialog. Everything talks to src/plugin.ts.
|
|
10
|
-
*/
|
|
11
|
-
export default defineStudioPlugin((studio) => {
|
|
12
|
-
setStudio(studio);
|
|
13
|
-
studio.registerSettingsSection({
|
|
14
|
-
id: "whatsapp",
|
|
15
|
-
label: () => t("title"),
|
|
16
|
-
icon: MessageCircle,
|
|
17
|
-
component: SettingsSection,
|
|
18
|
-
});
|
|
19
|
-
studio.registerShareSection({ runner: "whatsapp", component: ShareSection });
|
|
20
|
-
});
|