Skip to content

Commit d09d4d8

Browse files
bchapuisclaude
andcommitted
Improve WhatsApp setup with multi-step wizard and consistent UX
Convert the account creation dialog from a single form to a 4-step wizard (like Discord), with each step mapping to a section of the Meta Developer Portal. Add contextual help text, token expiration warning, and consistent field ordering across create/edit dialogs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 2cefb38 commit d09d4d8

4 files changed

Lines changed: 172 additions & 46 deletions

File tree

apps/app/src/components/workflow/widgets/input/whatsapp-account-create-dialog.tsx

Lines changed: 112 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,27 @@ import { createWhatsAppAccount } from "@/services/whatsapp-account-service";
1616

1717
import { WhatsAppSetupInfo } from "./whatsapp-setup-info";
1818

19-
type Step = "credentials" | "setup";
19+
type Step = "name" | "app-secret" | "api-credentials" | "setup";
2020

2121
const STEP_TITLES: Record<Step, string> = {
22-
credentials: "Add WhatsApp Account",
22+
name: "Add WhatsApp Account",
23+
"app-secret": "App Secret",
24+
"api-credentials": "WhatsApp API Credentials",
2325
setup: "Account Created",
2426
};
2527

2628
const STEP_DESCRIPTIONS: Record<Step, string> = {
27-
credentials:
28-
"Enter your WhatsApp Business API credentials from the Meta Developer Portal.",
29+
name: "Choose a display name to identify this WhatsApp account in Dafthunk.",
30+
"app-secret":
31+
"Copy the App Secret from your Meta app. Navigate to App Settings > Basic in the Meta Developer Portal.",
32+
"api-credentials":
33+
"Copy your Access Token and Phone Number ID from the WhatsApp API Setup page in the Meta Developer Portal.",
2934
setup:
30-
"Your account is ready. Configure the webhook URL in the Meta Developer Portal.",
35+
"Your account is ready. Follow these steps to complete the setup.",
3136
};
3237

38+
const META_PORTAL_URL = "https://developers.facebook.com/apps/";
39+
3340
interface WhatsAppAccountCreateDialogProps {
3441
isOpen: boolean;
3542
onClose: () => void;
@@ -42,22 +49,22 @@ export function WhatsAppAccountCreateDialog({
4249
onCreated,
4350
}: WhatsAppAccountCreateDialogProps) {
4451
const { organization } = useAuth();
45-
const [step, setStep] = useState<Step>("credentials");
52+
const [step, setStep] = useState<Step>("name");
4653
const [name, setName] = useState("");
54+
const [appSecret, setAppSecret] = useState("");
4755
const [accessToken, setAccessToken] = useState("");
4856
const [phoneNumberId, setPhoneNumberId] = useState("");
4957
const [wabaId, setWabaId] = useState("");
50-
const [appSecret, setAppSecret] = useState("");
5158
const [isSubmitting, setIsSubmitting] = useState(false);
5259
const [error, setError] = useState<string | null>(null);
5360

5461
const resetForm = () => {
55-
setStep("credentials");
62+
setStep("name");
5663
setName("");
64+
setAppSecret("");
5765
setAccessToken("");
5866
setPhoneNumberId("");
5967
setWabaId("");
60-
setAppSecret("");
6168
setError(null);
6269
};
6370

@@ -101,11 +108,11 @@ export function WhatsAppAccountCreateDialog({
101108
</DialogTitle>
102109
<DialogDescription className="text-sm text-muted-foreground mt-1">
103110
{STEP_DESCRIPTIONS[step]}
104-
{step === "credentials" && (
111+
{(step === "app-secret" || step === "api-credentials") && (
105112
<>
106113
{" "}
107114
<a
108-
href="https://developers.facebook.com/apps/"
115+
href={META_PORTAL_URL}
109116
target="_blank"
110117
rel="noopener noreferrer"
111118
className="text-primary hover:underline inline-flex items-center gap-0.5"
@@ -118,33 +125,104 @@ export function WhatsAppAccountCreateDialog({
118125
</DialogDescription>
119126
</div>
120127

121-
{step === "credentials" && (
128+
{step === "name" && (
122129
<div className="space-y-3">
123130
<div className="space-y-1.5">
124131
<Label htmlFor="whatsapp-name">Name</Label>
125132
<Input
126133
id="whatsapp-name"
127134
value={name}
128135
onChange={(e) => setName(e.target.value)}
129-
placeholder="My WhatsApp Account"
136+
placeholder="My WhatsApp Bot"
137+
/>
138+
<p className="text-xs text-muted-foreground">
139+
A display name for this account in Dafthunk. This is not visible
140+
to your WhatsApp users.
141+
</p>
142+
</div>
143+
144+
<div className="flex justify-end gap-2 pt-1">
145+
<Button type="button" variant="outline" onClick={handleClose}>
146+
Cancel
147+
</Button>
148+
<Button
149+
onClick={() => setStep("app-secret")}
150+
disabled={name.trim() === ""}
151+
>
152+
Next
153+
</Button>
154+
</div>
155+
</div>
156+
)}
157+
158+
{step === "app-secret" && (
159+
<div className="space-y-3">
160+
<div className="space-y-1.5">
161+
<Label htmlFor="whatsapp-app-secret">App Secret</Label>
162+
<Input
163+
id="whatsapp-app-secret"
164+
type="password"
165+
value={appSecret}
166+
onChange={(e) => setAppSecret(e.target.value)}
167+
placeholder="Paste your App Secret here"
130168
/>
131169
<p className="text-xs text-muted-foreground">
132-
A display name for this account in Dafthunk.
170+
Find this at{" "}
171+
<span className="font-medium text-foreground">
172+
App Settings &gt; Basic
173+
</span>{" "}
174+
in the Meta Developer Portal. Click{" "}
175+
<span className="font-medium text-foreground">Show</span> next
176+
to the App Secret field. Used to verify that incoming webhook
177+
messages are genuinely from Meta.
133178
</p>
134179
</div>
135180

181+
<div className="flex justify-end gap-2 pt-1">
182+
<Button
183+
type="button"
184+
variant="outline"
185+
onClick={() => setStep("name")}
186+
>
187+
Back
188+
</Button>
189+
<Button
190+
onClick={() => setStep("api-credentials")}
191+
disabled={appSecret.trim() === ""}
192+
>
193+
Next
194+
</Button>
195+
</div>
196+
</div>
197+
)}
198+
199+
{step === "api-credentials" && (
200+
<div className="space-y-3">
136201
<div className="space-y-1.5">
137202
<Label htmlFor="whatsapp-token">Access Token</Label>
138203
<Input
139204
id="whatsapp-token"
140205
type="password"
141206
value={accessToken}
142207
onChange={(e) => setAccessToken(e.target.value)}
143-
placeholder="••••••••"
208+
placeholder="Paste your access token here"
144209
/>
145210
<p className="text-xs text-muted-foreground">
146-
Your WhatsApp Business API access token from the Meta Developer
147-
Portal.
211+
Find this at{" "}
212+
<span className="font-medium text-foreground">
213+
WhatsApp &gt; API Setup
214+
</span>{" "}
215+
under the temporary access token section, or generate a
216+
permanent token via{" "}
217+
<span className="font-medium text-foreground">
218+
Business Settings &gt; System Users
219+
</span>
220+
.
221+
</p>
222+
<p className="text-xs text-amber-600 dark:text-amber-400 bg-amber-50 dark:bg-amber-900/20 px-3 py-2 rounded-md">
223+
Temporary tokens from API Setup expire in 24 hours. For
224+
production use, generate a permanent token from a System User in
225+
Business Settings.
148226
</p>
149227
</div>
150228

@@ -157,35 +235,31 @@ export function WhatsAppAccountCreateDialog({
157235
placeholder="123456789012345"
158236
/>
159237
<p className="text-xs text-muted-foreground">
160-
The Phone Number ID from your WhatsApp Business account.
238+
Find this at{" "}
239+
<span className="font-medium text-foreground">
240+
WhatsApp &gt; API Setup
241+
</span>
242+
. Select your phone number from the dropdown — the numeric ID
243+
appears below it. This is not the phone number itself.
161244
</p>
162245
</div>
163246

164247
<div className="space-y-1.5">
165248
<Label htmlFor="whatsapp-waba-id">
166249
WABA ID{" "}
167-
<span className="text-muted-foreground">(optional)</span>
250+
<span className="text-muted-foreground font-normal">
251+
(optional)
252+
</span>
168253
</Label>
169254
<Input
170255
id="whatsapp-waba-id"
171256
value={wabaId}
172257
onChange={(e) => setWabaId(e.target.value)}
173258
placeholder="WhatsApp Business Account ID"
174259
/>
175-
</div>
176-
177-
<div className="space-y-1.5">
178-
<Label htmlFor="whatsapp-app-secret">App Secret</Label>
179-
<Input
180-
id="whatsapp-app-secret"
181-
type="password"
182-
value={appSecret}
183-
onChange={(e) => setAppSecret(e.target.value)}
184-
placeholder="••••••••"
185-
/>
186260
<p className="text-xs text-muted-foreground">
187-
Required to verify incoming webhook signatures. Find it in App
188-
Settings &gt; Basic in the Meta Developer Portal.
261+
The WhatsApp Business Account ID, found on the same page. Stored
262+
for reference only.
189263
</p>
190264
</div>
191265

@@ -199,19 +273,20 @@ export function WhatsAppAccountCreateDialog({
199273
<Button
200274
type="button"
201275
variant="outline"
202-
onClick={handleClose}
276+
onClick={() => {
277+
setError(null);
278+
setStep("app-secret");
279+
}}
203280
disabled={isSubmitting}
204281
>
205-
Cancel
282+
Back
206283
</Button>
207284
<Button
208285
onClick={handleSubmit}
209286
disabled={
210287
isSubmitting ||
211-
name.trim() === "" ||
212288
accessToken.trim() === "" ||
213-
phoneNumberId.trim() === "" ||
214-
appSecret.trim() === ""
289+
phoneNumberId.trim() === ""
215290
}
216291
>
217292
{isSubmitting ? (

apps/app/src/components/workflow/widgets/input/whatsapp-setup-info.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,17 @@ export function WhatsAppSetupInfo() {
1212
trigger and select this account.
1313
</li>
1414
<li>
15-
Go to the account details page to find the{" "}
15+
After creating the workflow, the{" "}
1616
<span className="font-medium text-foreground">Callback URL</span>{" "}
1717
and{" "}
18-
<span className="font-medium text-foreground">Verify Token</span>,
19-
then configure them in the{" "}
18+
<span className="font-medium text-foreground">Verify Token</span>{" "}
19+
will appear on the account details page. Copy them into the{" "}
2020
<span className="font-medium text-foreground">
2121
Meta Developer Portal
2222
</span>{" "}
23-
webhook settings.
23+
webhook settings and subscribe to the{" "}
24+
<span className="font-medium text-foreground">messages</span>{" "}
25+
field.
2426
</li>
2527
<li>
2628
Enable the workflow, then send a WhatsApp message to your business

apps/app/src/pages/bot-whatsapp-detail-page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ export function BotWhatsAppDetailPage() {
8989
mono
9090
/>
9191
<DetailRow
92-
label="Token"
92+
label="Access Token"
9393
value={`****${whatsappAccount.tokenLastFour}`}
9494
mono
9595
/>

apps/app/src/pages/bot-whatsapp-edit-dialog.tsx

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ export function BotWhatsAppEditDialog({
3232
const [name, setName] = useState(account.name);
3333
const [accessToken, setAccessToken] = useState("");
3434
const [phoneNumberId, setPhoneNumberId] = useState(account.phoneNumberId);
35+
const [appSecret, setAppSecret] = useState("");
36+
const [wabaId, setWabaId] = useState(account.wabaId || "");
3537
const [isSubmitting, setIsSubmitting] = useState(false);
3638
const [error, setError] = useState<string | null>(null);
3739

@@ -49,6 +51,8 @@ export function BotWhatsAppEditDialog({
4951
accessToken: accessToken.trim() !== "" ? accessToken : undefined,
5052
phoneNumberId:
5153
phoneNumberId !== account.phoneNumberId ? phoneNumberId : undefined,
54+
appSecret: appSecret.trim() !== "" ? appSecret : undefined,
55+
wabaId: wabaId !== (account.wabaId || "") ? wabaId || undefined : undefined,
5256
},
5357
organization.id
5458
);
@@ -66,6 +70,8 @@ export function BotWhatsAppEditDialog({
6670
setName(account.name);
6771
setAccessToken("");
6872
setPhoneNumberId(account.phoneNumberId);
73+
setAppSecret("");
74+
setWabaId(account.wabaId || "");
6975
setError(null);
7076
}
7177
onOpenChange(value);
@@ -89,15 +95,23 @@ export function BotWhatsAppEditDialog({
8995
value={name}
9096
onChange={(e) => setName(e.target.value)}
9197
/>
98+
<p className="text-xs text-muted-foreground">
99+
A display name for this account in Dafthunk.
100+
</p>
92101
</div>
93102

94103
<div className="space-y-1.5">
95-
<Label htmlFor="edit-phone-number-id">Phone Number ID</Label>
104+
<Label htmlFor="edit-app-secret">App Secret</Label>
96105
<Input
97-
id="edit-phone-number-id"
98-
value={phoneNumberId}
99-
onChange={(e) => setPhoneNumberId(e.target.value)}
106+
id="edit-app-secret"
107+
type="password"
108+
value={appSecret}
109+
onChange={(e) => setAppSecret(e.target.value)}
110+
placeholder="Leave empty to keep current secret"
100111
/>
112+
<p className="text-xs text-muted-foreground">
113+
Found at App Settings &gt; Basic in the Meta Developer Portal.
114+
</p>
101115
</div>
102116

103117
<div className="space-y-1.5">
@@ -109,6 +123,41 @@ export function BotWhatsAppEditDialog({
109123
onChange={(e) => setAccessToken(e.target.value)}
110124
placeholder="Leave empty to keep current token"
111125
/>
126+
<p className="text-xs text-muted-foreground">
127+
Found at WhatsApp &gt; API Setup, or via Business Settings &gt;
128+
System Users for a permanent token.
129+
</p>
130+
</div>
131+
132+
<div className="space-y-1.5">
133+
<Label htmlFor="edit-phone-number-id">Phone Number ID</Label>
134+
<Input
135+
id="edit-phone-number-id"
136+
value={phoneNumberId}
137+
onChange={(e) => setPhoneNumberId(e.target.value)}
138+
/>
139+
<p className="text-xs text-muted-foreground">
140+
Found at WhatsApp &gt; API Setup under the phone number dropdown.
141+
</p>
142+
</div>
143+
144+
<div className="space-y-1.5">
145+
<Label htmlFor="edit-waba-id">
146+
WABA ID{" "}
147+
<span className="text-muted-foreground font-normal">
148+
(optional)
149+
</span>
150+
</Label>
151+
<Input
152+
id="edit-waba-id"
153+
value={wabaId}
154+
onChange={(e) => setWabaId(e.target.value)}
155+
placeholder="WhatsApp Business Account ID"
156+
/>
157+
<p className="text-xs text-muted-foreground">
158+
The WhatsApp Business Account ID, found on the same page. Stored
159+
for reference only.
160+
</p>
112161
</div>
113162

114163
{error && (

0 commit comments

Comments
 (0)