chore: improve the ui

This commit is contained in:
Muhsin
2026-07-11 17:02:12 +04:00
parent 86582569ee
commit a25113cea5
10 changed files with 117 additions and 153 deletions
@@ -66,7 +66,6 @@ class Api::V1::Accounts::Whatsapp::ManualSetupController < Api::V1::Accounts::Ba
number_access: true,
template_access: true,
webhook_setup: setup.webhook_setup?,
webhook_verified: channel.webhook_verified_at.present?,
webhook_error: setup.webhook_error
}
end
@@ -9,7 +9,6 @@ module MetaTokenVerifyConcern
def verify
service = is_a?(Webhooks::WhatsappController) ? 'whatsapp' : 'instagram'
if valid_token?(params['hub.verify_token'])
mark_webhook_verified if respond_to?(:mark_webhook_verified, true)
Rails.logger.info("#{service.capitalize} webhook verified")
render json: params['hub.challenge']
else
@@ -22,14 +22,6 @@ class Webhooks::WhatsappController < ActionController::API
token == whatsapp_webhook_verify_token if whatsapp_webhook_verify_token.present?
end
def mark_webhook_verified
channel = Channel::Whatsapp.find_by(phone_number: params[:phone_number])
# The verification callback must not trigger remote provider validation.
# rubocop:disable Rails/SkipsModelValidations
channel&.update_column(:webhook_verified_at, Time.current)
# rubocop:enable Rails/SkipsModelValidations
end
def meta_app_secrets
[
*channel_meta_app_secrets(whatsapp_channel),
@@ -304,7 +304,7 @@
"1": { "LABEL": "Create Meta app" },
"2": { "LABEL": "Add phone number and get IDs" },
"3": { "LABEL": "Generate access token" },
"4": { "LABEL": "Review and connect" },
"4": { "LABEL": "Create inbox" },
"5": { "LABEL": "Verify connection" }
},
"APP": {
@@ -346,18 +346,15 @@
"DETAILS": {
"WABA_LABEL": "WhatsApp Business Account ID",
"WABA_PLACEHOLDER": "Enter WABA ID",
"WABA_HELP": "Copy this from the API Setup page in your Meta app.",
"PHONE_ID_LABEL": "Phone Number ID",
"PHONE_ID_PLACEHOLDER": "Enter Phone Number ID",
"PHONE_ID_HELP": "Copy the ID shown beside your production phone number in Meta.",
"TOKEN_LABEL": "Permanent access token",
"TOKEN_PLACEHOLDER": "Paste access token",
"TOKEN_HELP": "Use a permanent system-user token with WhatsApp messaging and management permissions."
"TOKEN_PLACEHOLDER": "Paste access token"
},
"REVIEW": {
"TITLE": "Review and connect your number",
"DESCRIPTION": "These details were retrieved directly from Meta.",
"VERIFIED": "The number and token were verified with Meta.",
"TITLE": "Create your WhatsApp inbox",
"DESCRIPTION": "Review the number details and choose a name for the inbox.",
"VERIFIED": "Your Meta number and access token are valid.",
"BUSINESS_NAME": "Business name",
"PHONE_NUMBER": "Phone number",
"PHONE_ID": "Phone Number ID",
@@ -389,7 +386,7 @@
"SHOW_TOKEN": "Show token",
"HIDE_TOKEN": "Hide token",
"VERIFY_DETAILS": "Verify details",
"CONNECT": "Connect number",
"CONNECT": "Create inbox",
"RETRY_WEBHOOK": "Retry webhook setup",
"CONTINUE": "Continue to add agents"
},
@@ -1,5 +1,5 @@
<script setup>
import { computed, onBeforeUnmount, reactive, ref } from 'vue';
import { computed, nextTick, onBeforeUnmount, reactive, ref } from 'vue';
import { I18nT, useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
import { useStore } from 'vuex';
@@ -41,6 +41,7 @@ const errorMessage = ref('');
const inboxId = ref(null);
const pollTimer = ref(null);
const showAccessToken = ref(false);
const setupRoot = ref(null);
const form = reactive({
wabaId: '',
@@ -147,6 +148,7 @@ const apiErrorMessage = error =>
const setStep = step => {
errorMessage.value = '';
currentStep.value = step;
nextTick(() => setupRoot.value?.scrollIntoView({ block: 'start' }));
};
const returnToProviders = () => {
@@ -291,9 +293,12 @@ onBeforeUnmount(() => {
</script>
<template>
<div class="mx-auto flex w-full max-w-6xl flex-col gap-6 py-2">
<div class="flex flex-wrap items-start justify-between gap-4 px-1">
<div class="max-w-3xl">
<div
ref="setupRoot"
class="mx-auto flex w-full max-w-6xl flex-col gap-4 py-2"
>
<div class="flex items-start justify-between gap-4 px-1">
<div class="min-w-0 flex-1">
<h1 class="text-2xl font-semibold text-n-slate-12">
{{ $t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.HEADER.TITLE') }}
</h1>
@@ -312,7 +317,7 @@ onBeforeUnmount(() => {
</div>
<div
class="rounded-2xl border border-n-weak bg-n-background p-6 shadow-sm sm:p-8"
class="rounded-2xl border border-n-weak bg-n-background p-5 shadow-sm sm:p-6"
>
<div class="flex flex-col gap-3">
<div class="flex items-center justify-between text-sm">
@@ -345,8 +350,8 @@ onBeforeUnmount(() => {
{{ errorMessage }}
</div>
<section v-if="currentStep === 1" class="mt-8 flex flex-col gap-7">
<div>
<section v-if="currentStep === 1" class="mt-6 grid gap-5 lg:grid-cols-2">
<div class="lg:col-span-2">
<h2 class="text-2xl font-semibold text-n-slate-12">
{{ $t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.APP.TITLE') }}
</h2>
@@ -388,7 +393,9 @@ onBeforeUnmount(() => {
</li>
</ol>
<figure class="overflow-hidden rounded-xl border border-n-weak">
<figure
class="self-start overflow-hidden rounded-xl border border-n-weak"
>
<video
class="block w-full bg-n-solid-1"
controls
@@ -406,16 +413,11 @@ onBeforeUnmount(() => {
<p class="text-sm font-medium text-n-slate-12">
{{ $t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.APP.VIDEO_TITLE') }}
</p>
<p class="mt-1 text-sm text-n-slate-11">
{{
$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.APP.VIDEO_DESCRIPTION')
}}
</p>
</figcaption>
</figure>
<div
class="flex items-center justify-between border-t border-n-weak pt-5"
class="flex items-center justify-between border-t border-n-weak pt-5 lg:col-span-2"
>
<Button
:label="$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.ACTIONS.BACK')"
@@ -434,8 +436,11 @@ onBeforeUnmount(() => {
</div>
</section>
<section v-else-if="currentStep === 2" class="mt-8 flex flex-col gap-7">
<div>
<section
v-else-if="currentStep === 2"
class="mt-6 grid gap-5 lg:grid-cols-2"
>
<div class="lg:col-span-2">
<h2 class="text-2xl font-semibold text-n-slate-12">
{{ $t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.NUMBER.TITLE') }}
</h2>
@@ -456,50 +461,45 @@ onBeforeUnmount(() => {
</li>
</ol>
<figure class="overflow-hidden rounded-xl border border-n-weak">
<video
class="block w-full bg-n-solid-1"
controls
muted
playsinline
preload="metadata"
:poster="ADD_NUMBER_VIDEO_POSTER_URL"
:aria-label="
$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.NUMBER.VIDEO_TITLE')
"
>
<source :src="ADD_NUMBER_VIDEO_URL" type="video/mp4" />
</video>
<figcaption class="border-t border-n-weak bg-n-alpha-1 px-4 py-3">
<p class="text-sm font-medium text-n-slate-12">
{{
<div class="flex min-w-0 flex-col gap-4">
<figure class="overflow-hidden rounded-xl border border-n-weak">
<video
class="block w-full bg-n-solid-1"
controls
muted
playsinline
preload="metadata"
:poster="ADD_NUMBER_VIDEO_POSTER_URL"
:aria-label="
$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.NUMBER.VIDEO_TITLE')
}}
</p>
<p class="mt-1 text-sm text-n-slate-11">
{{
$t(
'INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.NUMBER.VIDEO_DESCRIPTION'
)
}}
</p>
</figcaption>
</figure>
"
>
<source :src="ADD_NUMBER_VIDEO_URL" type="video/mp4" />
</video>
<figcaption class="border-t border-n-weak bg-n-alpha-1 px-4 py-3">
<p class="text-sm font-medium text-n-slate-12">
{{
$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.NUMBER.VIDEO_TITLE')
}}
</p>
</figcaption>
</figure>
<a
:href="META_APPS_URL"
target="_blank"
rel="noopener noreferrer"
class="inline-flex w-fit items-center gap-2 text-sm font-medium text-n-brand hover:underline"
>
{{
$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.ACTIONS.OPEN_META_APPS')
}}
<Icon icon="i-lucide-external-link" />
</a>
<a
:href="META_APPS_URL"
target="_blank"
rel="noopener noreferrer"
class="inline-flex w-fit items-center gap-2 text-sm font-medium text-n-brand hover:underline"
>
{{
$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.ACTIONS.OPEN_META_APPS')
}}
<Icon icon="i-lucide-external-link" />
</a>
</div>
<div
class="grid gap-5 rounded-xl border border-n-weak p-5 lg:grid-cols-2"
class="grid gap-5 rounded-xl border border-n-weak p-5 lg:col-span-2 lg:grid-cols-2"
>
<Input
v-model="form.phoneNumberId"
@@ -511,9 +511,6 @@ onBeforeUnmount(() => {
'INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.DETAILS.PHONE_ID_PLACEHOLDER'
)
"
:message="
$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.DETAILS.PHONE_ID_HELP')
"
/>
<Input
v-model="form.wabaId"
@@ -525,14 +522,11 @@ onBeforeUnmount(() => {
'INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.DETAILS.WABA_PLACEHOLDER'
)
"
:message="
$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.DETAILS.WABA_HELP')
"
/>
</div>
<div
class="flex items-center justify-between border-t border-n-weak pt-5"
class="flex items-center justify-between border-t border-n-weak pt-5 lg:col-span-2"
>
<Button
:label="$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.ACTIONS.BACK')"
@@ -549,8 +543,11 @@ onBeforeUnmount(() => {
</div>
</section>
<section v-else-if="currentStep === 3" class="mt-8 flex flex-col gap-7">
<div>
<section
v-else-if="currentStep === 3"
class="mt-6 grid gap-5 lg:grid-cols-2"
>
<div class="lg:col-span-2">
<h2 class="text-2xl font-semibold text-n-slate-12">
{{ $t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.TOKEN.TITLE') }}
</h2>
@@ -571,50 +568,47 @@ onBeforeUnmount(() => {
</li>
</ol>
<figure class="overflow-hidden rounded-xl border border-n-weak">
<video
class="block w-full bg-n-solid-1"
controls
muted
playsinline
preload="metadata"
:poster="GENERATE_TOKEN_VIDEO_POSTER_URL"
:aria-label="
$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.TOKEN.VIDEO_TITLE')
"
<div class="flex min-w-0 flex-col gap-4">
<figure class="overflow-hidden rounded-xl border border-n-weak">
<video
class="block w-full bg-n-solid-1"
controls
muted
playsinline
preload="metadata"
:poster="GENERATE_TOKEN_VIDEO_POSTER_URL"
:aria-label="
$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.TOKEN.VIDEO_TITLE')
"
>
<source :src="GENERATE_TOKEN_VIDEO_URL" type="video/mp4" />
</video>
<figcaption class="border-t border-n-weak bg-n-alpha-1 px-4 py-3">
<p class="text-sm font-medium text-n-slate-12">
{{
$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.TOKEN.VIDEO_TITLE')
}}
</p>
</figcaption>
</figure>
<a
:href="META_BUSINESS_SETTINGS_URL"
target="_blank"
rel="noopener noreferrer"
class="inline-flex w-fit items-center gap-2 text-sm font-medium text-n-brand hover:underline"
>
<source :src="GENERATE_TOKEN_VIDEO_URL" type="video/mp4" />
</video>
<figcaption class="border-t border-n-weak bg-n-alpha-1 px-4 py-3">
<p class="text-sm font-medium text-n-slate-12">
{{ $t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.TOKEN.VIDEO_TITLE') }}
</p>
<p class="mt-1 text-sm text-n-slate-11">
{{
$t(
'INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.TOKEN.VIDEO_DESCRIPTION'
)
}}
</p>
</figcaption>
</figure>
{{
$t(
'INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.ACTIONS.OPEN_BUSINESS_SETTINGS'
)
}}
<Icon icon="i-lucide-external-link" />
</a>
</div>
<a
:href="META_BUSINESS_SETTINGS_URL"
target="_blank"
rel="noopener noreferrer"
class="inline-flex w-fit items-center gap-2 text-sm font-medium text-n-brand hover:underline"
>
{{
$t(
'INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.ACTIONS.OPEN_BUSINESS_SETTINGS'
)
}}
<Icon icon="i-lucide-external-link" />
</a>
<div class="rounded-xl border border-n-weak p-5">
<div class="grid items-end gap-3 sm:grid-cols-[1fr_auto]">
<div class="rounded-xl border border-n-weak p-5 lg:col-span-2">
<div class="grid items-start gap-3 sm:grid-cols-[1fr_auto]">
<Input
v-model="form.accessToken"
:type="showAccessToken ? 'text' : 'password'"
@@ -627,9 +621,6 @@ onBeforeUnmount(() => {
'INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.DETAILS.TOKEN_PLACEHOLDER'
)
"
:message="
$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.DETAILS.TOKEN_HELP')
"
/>
<Button
:label="
@@ -643,17 +634,18 @@ onBeforeUnmount(() => {
"
variant="outline"
color="slate"
class="sm:mt-7"
@click="showAccessToken = !showAccessToken"
/>
</div>
<p class="mt-4 flex items-start gap-2 text-sm text-n-amber-11">
<p class="mt-3 flex items-start gap-2 text-sm text-n-amber-11">
<Icon icon="i-lucide-triangle-alert" class="mt-0.5 shrink-0" />
{{ $t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.TOKEN.WARNING') }}
</p>
</div>
<div
class="flex items-center justify-between border-t border-n-weak pt-5"
class="flex items-center justify-between border-t border-n-weak pt-5 lg:col-span-2"
>
<Button
:label="$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.ACTIONS.BACK')"
@@ -672,7 +664,7 @@ onBeforeUnmount(() => {
</div>
</section>
<section v-else-if="currentStep === 4" class="mt-8 flex flex-col gap-7">
<section v-else-if="currentStep === 4" class="mt-6 flex flex-col gap-5">
<div>
<h2 class="text-2xl font-semibold text-n-slate-12">
{{ $t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.REVIEW.TITLE') }}
@@ -756,7 +748,7 @@ onBeforeUnmount(() => {
</div>
</section>
<section v-else class="mt-8 flex flex-col gap-7">
<section v-else class="mt-6 flex flex-col gap-5">
<div>
<h2 class="text-2xl font-semibold text-n-slate-12">
{{ $t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.VERIFY.TITLE') }}
-1
View File
@@ -8,7 +8,6 @@
# phone_number :string not null
# provider :string default("default")
# provider_config :jsonb
# webhook_verified_at :datetime
# created_at :datetime not null
# updated_at :datetime not null
# account_id :integer not null
@@ -8,7 +8,7 @@ class Whatsapp::ManualWebhookStatusService
callback_configured = callback_configured?
{
callback_verified: @channel.webhook_verified_at.present? || callback_configured,
callback_verified: callback_configured,
callback_configured: callback_configured,
callback_url: callback_url,
subscription_verified: subscription_verified?
@@ -1,5 +0,0 @@
class AddWebhookVerifiedAtToChannelWhatsapp < ActiveRecord::Migration[7.1]
def change
add_column :channel_whatsapp, :webhook_verified_at, :datetime
end
end
+1 -2
View File
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.1].define(version: 2026_07_11_090000) do
ActiveRecord::Schema[7.1].define(version: 2026_07_06_215758) do
# These extensions should be enabled to support this database
enable_extension "pg_stat_statements"
enable_extension "pg_trgm"
@@ -623,7 +623,6 @@ ActiveRecord::Schema[7.1].define(version: 2026_07_11_090000) do
t.datetime "updated_at", null: false
t.jsonb "message_templates", default: {}
t.datetime "message_templates_last_updated", precision: nil
t.datetime "webhook_verified_at"
t.index ["phone_number"], name: "index_channel_whatsapp_on_phone_number", unique: true
end
+3 -11
View File
@@ -102,7 +102,7 @@ Inbox naming:
- Allow the administrator to edit the inbox name here.
- Do not introduce a separate `WhatsApp number name` field.
Primary action: `Connect number`.
Primary action: `Create inbox`.
### Step 5: Verify Connection
@@ -256,16 +256,9 @@ Return:
- Required subscribed fields configured.
- Last setup error in a sanitized form, if available.
## Recording Webhook Verification
## Checking Webhook Verification
Add `webhook_verified_at` to `channel_whatsapp`.
When `Webhooks::WhatsappController#verify` receives the correct verify token:
1. Return Meta's challenge as it does today.
2. Record `webhook_verified_at` for the matched channel without triggering remote provider validation.
The connection screen should not mark the callback as verified until this timestamp is present. A successful outbound Graph API call by itself is not enough evidence that Meta reached the Chatwoot endpoint.
Do not persist additional webhook verification state. Read the phone-level webhook configuration from Meta and compare its callback URL with the URL generated for the inbox.
Use the existing WABA subscription and phone-level callback APIs rather than asking the user to configure the callback manually in the normal path.
@@ -293,7 +286,6 @@ Expected files or responsibilities:
- Reuse `Whatsapp::WebhookSetupService` for the actual subscription and callback override.
- Extend `Whatsapp::FacebookApiClient` for paginated phone lookup and webhook-status reads where required.
- Add account-scoped routes.
- Add `webhook_verified_at` to the WhatsApp channel schema.
- Check Enterprise overlays for affected controllers, serializers, routes, and inbox behavior before editing shared code.
## Out Of Scope