Skip to content

Commit 91b3f02

Browse files
committed
fix(web): surface API error messages
Preserve server-provided entitlement failures and keep toast notifications above open dialogs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: dfc75c3c-5f63-4c2e-8a00-765e45f24c67
1 parent 9f964b0 commit 91b3f02

6 files changed

Lines changed: 73 additions & 39 deletions

File tree

web/app/components/AppToast.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const notificationActive = computed({
1717
variant="tonal"
1818
:color="notificationsStore.notification.type"
1919
:timeout="notificationsStore.notification.timeout"
20+
:z-index="10000"
2021
>
2122
<v-icon
2223
v-if="notificationsStore.notification.type === 'success'"

web/app/pages/billing/index.vue

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type {
1414
} from '~~/shared/types/api'
1515
1616
import { countries, getStateOptions } from '~/utils/countries'
17+
import { getApiErrorMessage } from '~/utils/api-error'
1718
1819
type SubscriptionPayment =
1920
ResponsesUserSubscriptionPaymentsResponse['data'][number]
@@ -197,9 +198,12 @@ async function updateDetails() {
197198
loading.value = true
198199
try {
199200
window.location.href = await billingStore.getSubscriptionUpdateLink()
200-
} catch {
201+
} catch (error: unknown) {
201202
notificationsStore.addNotification({
202-
message: 'We could not redirect you to the subscription update portal.',
203+
message: getApiErrorMessage(
204+
error,
205+
'We could not redirect you to the subscription update portal.',
206+
),
203207
type: 'error',
204208
})
205209
loading.value = false
@@ -215,9 +219,12 @@ async function cancelPlan() {
215219
type: 'success',
216220
})
217221
navigateTo('/')
218-
} catch {
222+
} catch (error: unknown) {
219223
notificationsStore.addNotification({
220-
message: 'We could not cancel your subscription.',
224+
message: getApiErrorMessage(
225+
error,
226+
'We could not cancel your subscription.',
227+
),
221228
type: 'error',
222229
})
223230
loading.value = false

web/app/pages/messages/index.vue

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
parsePhoneNumber,
77
type CountryCode,
88
} from 'libphonenumber-js'
9-
import { toApiError } from '~/utils/api-error'
9+
import { getApiErrorMessage, toApiError } from '~/utils/api-error'
1010
1111
definePageMeta({
1212
middleware: ['auth'],
@@ -91,6 +91,11 @@ async function sendMessage() {
9191
})
9292
}
9393
errors.value = newErrors
94+
} else {
95+
notificationsStore.addNotification({
96+
message: getApiErrorMessage(err, 'Failed to send message'),
97+
type: 'error',
98+
})
9499
}
95100
} finally {
96101
sending.value = false

web/app/pages/phone-api-keys/index.vue

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import QRCode from 'qrcode'
44
import Pusher from 'pusher-js'
55
import type { Channel } from 'pusher-js'
66
import { ErrorMessages } from '~/utils/errors'
7-
import { toApiError } from '~/utils/api-error'
7+
import { getApiErrorMessage, toApiError } from '~/utils/api-error'
88
import type { EntitiesPhoneAPIKey } from '~~/shared/types/api'
99
1010
definePageMeta({
@@ -58,9 +58,9 @@ async function loadPhoneApiKeys() {
5858
{ query: { limit: 100 } },
5959
)
6060
phoneApiKeys.value = response.data ?? []
61-
} catch {
61+
} catch (error: unknown) {
6262
notificationsStore.addNotification({
63-
message: 'Failed to load Phone API Keys',
63+
message: getApiErrorMessage(error, 'Failed to load Phone API Keys'),
6464
type: 'error',
6565
})
6666
} finally {
@@ -88,7 +88,7 @@ async function createPhoneApiKey() {
8888
errorMessages.value = parseErrors(error)
8989
if (errorMessages.value.size() === 0) {
9090
notificationsStore.addNotification({
91-
message: 'Failed to create Phone API Key',
91+
message: getApiErrorMessage(error, 'Failed to create Phone API Key'),
9292
type: 'error',
9393
})
9494
}
@@ -155,9 +155,9 @@ async function deleteApiKey() {
155155
})
156156
deleteApiKeyDialog.value = false
157157
await loadPhoneApiKeys()
158-
} catch {
158+
} catch (error: unknown) {
159159
notificationsStore.addNotification({
160-
message: 'Failed to delete Phone API Key',
160+
message: getApiErrorMessage(error, 'Failed to delete Phone API Key'),
161161
type: 'error',
162162
})
163163
loading.value = false
@@ -191,9 +191,12 @@ async function removePhoneFromPhoneKey() {
191191
})
192192
removePhoneFromApiKeyDialog.value = false
193193
await loadPhoneApiKeys()
194-
} catch {
194+
} catch (error: unknown) {
195195
notificationsStore.addNotification({
196-
message: 'Failed to remove the phone from the Phone API Key',
196+
message: getApiErrorMessage(
197+
error,
198+
'Failed to remove the phone from the Phone API Key',
199+
),
197200
type: 'error',
198201
})
199202
loading.value = false

web/app/pages/search-messages/index.vue

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
import type { EntitiesMessage, EntitiesPhone } from '~~/shared/types/api'
1717
import type { SearchMessagesRequest } from '~~/shared/types/message'
1818
import { ErrorMessages } from '~/utils/errors'
19-
import { toApiError } from '~/utils/api-error'
19+
import { getApiErrorMessage, toApiError } from '~/utils/api-error'
2020
2121
interface Turnstile {
2222
ready(callback: () => void): void
@@ -261,9 +261,12 @@ async function deleteMessages() {
261261
type: 'success',
262262
})
263263
selectedIds.value = []
264-
} catch {
264+
} catch (error: unknown) {
265265
notificationsStore.addNotification({
266-
message: 'Error while deleting the selected messages',
266+
message: getApiErrorMessage(
267+
error,
268+
'Error while deleting the selected messages',
269+
),
267270
type: 'error',
268271
})
269272
} finally {
@@ -300,8 +303,15 @@ async function resendMessages() {
300303
})
301304
selectedIds.value = []
302305
} else if (failed.length === results.length) {
306+
const firstFailure = failed[0]
303307
notificationsStore.addNotification({
304-
message: 'Error while resending the selected messages',
308+
message:
309+
firstFailure?.status === 'rejected'
310+
? getApiErrorMessage(
311+
firstFailure.reason,
312+
'Error while resending the selected messages',
313+
)
314+
: 'Error while resending the selected messages',
305315
type: 'error',
306316
})
307317
} else {

web/app/pages/settings/index.vue

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
} from 'firebase/auth'
2424
import QRCode from 'qrcode'
2525
import { ErrorMessages } from '~/utils/errors'
26-
import { toApiError } from '~/utils/api-error'
26+
import { getApiErrorMessage, toApiError } from '~/utils/api-error'
2727
import type {
2828
EntitiesPhone,
2929
EntitiesWebhook,
@@ -157,9 +157,9 @@ async function rotateApiKey() {
157157
message: 'API Key rotated successfully',
158158
type: 'success',
159159
})
160-
} catch {
160+
} catch (error: unknown) {
161161
notificationsStore.addNotification({
162-
message: 'Failed to rotate API Key',
162+
message: getApiErrorMessage(error, 'Failed to rotate API Key'),
163163
type: 'error',
164164
})
165165
} finally {
@@ -198,9 +198,9 @@ async function updateTimezone(timezone: string) {
198198
message: 'Timezone updated successfully',
199199
type: 'success',
200200
})
201-
} catch {
201+
} catch (error: unknown) {
202202
notificationsStore.addNotification({
203-
message: 'Failed to update timezone',
203+
message: getApiErrorMessage(error, 'Failed to update timezone'),
204204
type: 'error',
205205
})
206206
}
@@ -298,7 +298,7 @@ async function saveWebhook() {
298298
errorMessages.value = parseErrors(error)
299299
if (errorMessages.value.size() === 0) {
300300
notificationsStore.addNotification({
301-
message: 'Failed to save webhook',
301+
message: getApiErrorMessage(error, 'Failed to save webhook'),
302302
type: 'error',
303303
})
304304
}
@@ -317,9 +317,9 @@ async function deleteWebhook(id: string) {
317317
})
318318
showWebhookEdit.value = false
319319
await loadWebhooks()
320-
} catch {
320+
} catch (error: unknown) {
321321
notificationsStore.addNotification({
322-
message: 'Failed to delete webhook',
322+
message: getApiErrorMessage(error, 'Failed to delete webhook'),
323323
type: 'error',
324324
})
325325
} finally {
@@ -406,7 +406,10 @@ async function saveDiscord() {
406406
errorMessages.value = parseErrors(error)
407407
if (errorMessages.value.size() === 0) {
408408
notificationsStore.addNotification({
409-
message: 'Failed to save discord integration',
409+
message: getApiErrorMessage(
410+
error,
411+
'Failed to save discord integration',
412+
),
410413
type: 'error',
411414
})
412415
}
@@ -425,9 +428,12 @@ async function deleteDiscord(id: string) {
425428
})
426429
showDiscordEdit.value = false
427430
await loadDiscordIntegrations()
428-
} catch {
431+
} catch (error: unknown) {
429432
notificationsStore.addNotification({
430-
message: 'Failed to delete discord integration',
433+
message: getApiErrorMessage(
434+
error,
435+
'Failed to delete discord integration',
436+
),
431437
type: 'error',
432438
})
433439
} finally {
@@ -472,9 +478,9 @@ async function deletePhone(phoneId: string) {
472478
})
473479
showPhoneEdit.value = false
474480
activePhone.value = null
475-
} catch {
481+
} catch (error: unknown) {
476482
notificationsStore.addNotification({
477-
message: 'Failed to delete phone',
483+
message: getApiErrorMessage(error, 'Failed to delete phone'),
478484
type: 'error',
479485
})
480486
} finally {
@@ -675,10 +681,10 @@ async function saveSchedule() {
675681
await loadSendSchedules()
676682
} catch (error: unknown) {
677683
errorMessages.value = parseErrors(error)
678-
if (errorMessages.value.size() != 0) {
684+
if (errorMessages.value.size() === 0) {
679685
notificationsStore.addNotification({
680686
type: 'error',
681-
message: 'Failed to save send schedule',
687+
message: getApiErrorMessage(error, 'Failed to save send schedule'),
682688
})
683689
}
684690
} finally {
@@ -702,10 +708,10 @@ async function deleteSchedule() {
702708
showScheduleDelete.value = false
703709
showScheduleEdit.value = false
704710
await loadSendSchedules()
705-
} catch {
711+
} catch (error: unknown) {
706712
notificationsStore.addNotification({
707713
type: 'error',
708-
message: 'Failed to delete send schedule',
714+
message: getApiErrorMessage(error, 'Failed to delete send schedule'),
709715
})
710716
} finally {
711717
savingSchedule.value = false
@@ -746,9 +752,9 @@ async function saveEmailNotifications() {
746752
type: 'success',
747753
})
748754
syncEmailNotifications()
749-
} catch {
755+
} catch (error: unknown) {
750756
notificationsStore.addNotification({
751-
message: 'Failed to save email notifications',
757+
message: getApiErrorMessage(error, 'Failed to save email notifications'),
752758
type: 'error',
753759
})
754760
} finally {
@@ -780,10 +786,12 @@ async function deleteUserAccount() {
780786
message: 'You have successfully logged out',
781787
})
782788
await router.push({ name: 'index' })
783-
} catch {
789+
} catch (error: unknown) {
784790
notificationsStore.addNotification({
785-
message:
786-
'We ran into an internal error while deleteing your account please contact us.',
791+
message: getApiErrorMessage(
792+
error,
793+
'We ran into an internal error while deleting your account. Please contact us.',
794+
),
787795
type: 'error',
788796
})
789797
} finally {

0 commit comments

Comments
 (0)