fix(campaigns): preview and review steps showed empty values

Form.useWatch([], form) tracks getFieldsValue(), which returns {} on a step
with no mounted Form.Item. The Preview and Review steps have none, so on
entering Preview the creative memo recomputed to empty and previewed a blank
ad pointing at example.com, and the Review step rendered "-" for every field.
The submitted payload was always correct — submit() reads the preserved store
via getFieldsValue(true) — so this was a display and preview defect, not data
loss. Pass preserve: true so the watch tracks the whole store.

Also document VITE_META_AD_ACCOUNT_ID. Unset, it silently disables the
billing panel, the missing-payment-method warning, ad preview, and the
account-minimum budget check, with no diagnostic anywhere.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
AFFAANh
2026-08-02 19:45:59 +05:30
co-authored by Claude Opus 5
parent 39119163c1
commit 66cfa38076
7 changed files with 45 additions and 1 deletions
+5
View File
@@ -1,2 +1,7 @@
VITE_MASKANX_API_URL=https://api-dev.example.com
VITE_MASKANX_PROXY_TARGET=http://127.0.0.1:8088
# Meta ad account used by the Campaigns page (act_<id>).
# Without it the billing panel, the "no payment method" warning, ad preview,
# and the account-minimum budget check are all silently unavailable.
VITE_META_AD_ACCOUNT_ID=
+5
View File
@@ -1,2 +1,7 @@
VITE_MASKANX_API_URL=http://127.0.0.1:8088
VITE_MASKANX_PROXY_TARGET=http://127.0.0.1:8088
# Meta ad account used by the Campaigns page (act_<id>).
# Without it the billing panel, the "no payment method" warning, ad preview,
# and the account-minimum budget check are all silently unavailable.
VITE_META_AD_ACCOUNT_ID=
+5
View File
@@ -1,3 +1,8 @@
# Leave the public URL empty to use the Vite /api proxy locally.
VITE_MASKANX_API_URL=
VITE_MASKANX_PROXY_TARGET=http://127.0.0.1:8088
# Meta ad account used by the Campaigns page (act_<id>).
# Without it the billing panel, the "no payment method" warning, ad preview,
# and the account-minimum budget check are all silently unavailable.
VITE_META_AD_ACCOUNT_ID=
+5
View File
@@ -1 +1,6 @@
VITE_MASKANX_API_URL=https://api.maskanx.example.com
# Meta ad account used by the Campaigns page (act_<id>).
# Without it the billing panel, the "no payment method" warning, ad preview,
# and the account-minimum budget check are all silently unavailable.
VITE_META_AD_ACCOUNT_ID=
+5
View File
@@ -1,2 +1,7 @@
VITE_MASKANX_API_URL=http://127.0.0.1:8089
VITE_MASKANX_PROXY_TARGET=http://127.0.0.1:8089
# Meta ad account used by the Campaigns page (act_<id>).
# Without it the billing panel, the "no payment method" warning, ad preview,
# and the account-minimum budget check are all silently unavailable.
VITE_META_AD_ACCOUNT_ID=
+14
View File
@@ -19,6 +19,20 @@ npm run local
Start `maskanx-backend` on port `8088` before using the full UI.
## Campaigns page
The Campaigns page needs `VITE_META_AD_ACCOUNT_ID` set to the Meta ad account
id (`act_<digits>`) in your `.env.*` file.
If it is unset the page still loads and lists campaigns, but four things are
silently unavailable: the ad-account billing panel, the warning shown when no
payment method is attached, ad preview in the wizard, and the account-minimum
budget check (the backend cannot look up `min_daily_budget` without an
account, so it skips that validation and logs a warning).
Ad preview also requires `META_ADS_ACCESS_TOKEN` to be configured on the
backend, in Settings > Environments.
## Commands
| Command | Purpose |
+6 -1
View File
@@ -74,7 +74,12 @@ export default function CampaignWizard({
const [step, setStep] = useState(0);
const [saving, setSaving] = useState(false);
const values: WizardValues = Form.useWatch([], form) ?? EMPTY_VALUES;
// `preserve: true` is required. Only the active step is mounted, so a bare
// useWatch tracks getFieldsValue() — which returns {} on a step with no
// Form.Item (Preview, Review). Without this the Preview step would render a
// blank creative and the Review step would show "-" for every field.
const values: WizardValues =
Form.useWatch([], { form, preserve: true }) ?? EMPTY_VALUES;
// Memoised so PreviewStep's effect does not refire on every parent render.
const creative = useMemo(