From fc01ee9f56d3affc55c5a5c692971d101554d2af Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sun, 14 Sep 2025 23:43:22 -0700 Subject: [PATCH] Get localized error messages. --- package.components.json | 2 +- src/app/(main)/admin/users/UserAddForm.tsx | 4 +-- .../admin/users/[userId]/UserEditForm.tsx | 4 +-- src/app/(main)/links/LinkDeleteButton.tsx | 4 +-- src/app/(main)/links/LinkEditForm.tsx | 4 +-- src/app/(main)/pixels/PixelDeleteButton.tsx | 4 +-- src/app/(main)/pixels/PixelEditForm.tsx | 4 +-- .../settings/profile/PasswordEditForm.tsx | 8 +++--- src/app/(main)/teams/TeamAddForm.tsx | 4 +-- src/app/(main)/teams/TeamJoinForm.tsx | 4 +-- src/app/(main)/teams/TeamLeaveForm.tsx | 4 +-- .../(main)/teams/[teamId]/TeamDeleteForm.tsx | 4 +-- .../(main)/teams/[teamId]/TeamEditForm.tsx | 4 +-- .../teams/[teamId]/TeamMemberEditForm.tsx | 4 +-- .../[websiteId]/WebsiteMetricsBar.tsx | 4 +-- .../[websiteId]/cohorts/CohortEditForm.tsx | 8 ++++-- .../[websiteId]/segments/SegmentEditForm.tsx | 8 ++++-- .../[websiteId]/settings/WebsiteEditForm.tsx | 4 +-- .../[websiteId]/settings/WebsiteShareForm.tsx | 4 +-- .../settings/WebsiteTransferForm.tsx | 4 +-- src/app/api/auth/login/route.ts | 3 ++- src/app/login/LoginForm.tsx | 7 ++--- src/app/login/LoginPage.tsx | 2 +- src/components/common/ConfirmationForm.tsx | 4 +-- src/components/common/LoadingPanel.tsx | 2 +- .../common/TypeConfirmationForm.tsx | 5 ++-- .../hooks/queries/useUpdateQuery.ts | 2 +- src/components/hooks/useApi.ts | 27 +++++-------------- src/components/hooks/useMessages.ts | 12 ++++++++- src/components/hooks/useNavigation.ts | 8 +++--- src/lib/fetch.ts | 9 +++---- src/lib/url.ts | 4 +-- 32 files changed, 90 insertions(+), 85 deletions(-) diff --git a/package.components.json b/package.components.json index 09c0cf52..e1679f20 100644 --- a/package.components.json +++ b/package.components.json @@ -1,6 +1,6 @@ { "name": "@umami/components", - "version": "0.121.0", + "version": "0.122.0", "description": "Umami React components.", "author": "Mike Cao ", "license": "MIT", diff --git a/src/app/(main)/admin/users/UserAddForm.tsx b/src/app/(main)/admin/users/UserAddForm.tsx index e02be0df..f6881a10 100644 --- a/src/app/(main)/admin/users/UserAddForm.tsx +++ b/src/app/(main)/admin/users/UserAddForm.tsx @@ -14,7 +14,7 @@ import { ROLES } from '@/lib/constants'; export function UserAddForm({ onSave, onClose }) { const { mutate, error, isPending } = useUpdateQuery(`/users`); - const { formatMessage, labels } = useMessages(); + const { formatMessage, labels, getErrorMessage } = useMessages(); const handleSubmit = async (data: any) => { mutate(data, { @@ -26,7 +26,7 @@ export function UserAddForm({ onSave, onClose }) { }; return ( -
+ + @@ -37,7 +37,7 @@ export function UserEditForm({ userId, onSave }: { userId: string; onSave?: () = name="password" label={formatMessage(labels.password)} rules={{ - minLength: { value: 8, message: formatMessage(messages.minPasswordLength, { n: 8 }) }, + minLength: { value: 8, message: formatMessage(messages.minPasswordLength, { n: '8' }) }, }} > diff --git a/src/app/(main)/links/LinkDeleteButton.tsx b/src/app/(main)/links/LinkDeleteButton.tsx index ba0eddbe..cfecb425 100644 --- a/src/app/(main)/links/LinkDeleteButton.tsx +++ b/src/app/(main)/links/LinkDeleteButton.tsx @@ -15,7 +15,7 @@ export function LinkDeleteButton({ name: string; onSave?: () => void; }) { - const { formatMessage, labels } = useMessages(); + const { formatMessage, labels, getErrorMessage } = useMessages(); const { mutate, isPending, error, touch } = useDeleteQuery(`/links/${linkId}`); const handleConfirm = (close: () => void) => { @@ -37,7 +37,7 @@ export function LinkDeleteButton({ target: name, })} isLoading={isPending} - error={error} + error={getErrorMessage(error)} onConfirm={handleConfirm.bind(null, close)} onClose={close} buttonLabel={formatMessage(labels.delete)} diff --git a/src/app/(main)/links/LinkEditForm.tsx b/src/app/(main)/links/LinkEditForm.tsx index 54237e43..16c65aec 100644 --- a/src/app/(main)/links/LinkEditForm.tsx +++ b/src/app/(main)/links/LinkEditForm.tsx @@ -32,7 +32,7 @@ export function LinkEditForm({ onSave?: () => void; onClose?: () => void; }) { - const { formatMessage, labels, messages } = useMessages(); + const { formatMessage, labels, messages, getErrorMessage } = useMessages(); const { mutate, error, isPending, touch, toast } = useUpdateQuery( linkId ? `/links/${linkId}` : '/links', { @@ -82,7 +82,7 @@ export function LinkEditForm({ } return ( - + {({ setValue }) => { return ( <> diff --git a/src/app/(main)/pixels/PixelDeleteButton.tsx b/src/app/(main)/pixels/PixelDeleteButton.tsx index 6bbb514f..0a81ab80 100644 --- a/src/app/(main)/pixels/PixelDeleteButton.tsx +++ b/src/app/(main)/pixels/PixelDeleteButton.tsx @@ -13,7 +13,7 @@ export function PixelDeleteButton({ name: string; onSave?: () => void; }) { - const { formatMessage, labels } = useMessages(); + const { formatMessage, labels, getErrorMessage } = useMessages(); const { mutate, isPending, error } = useDeleteQuery(`/pixels/${pixelId}`); const { touch } = useModified(); @@ -36,7 +36,7 @@ export function PixelDeleteButton({ target: name, })} isLoading={isPending} - error={error} + error={getErrorMessage(error)} onConfirm={handleConfirm.bind(null, close)} onClose={close} buttonLabel={formatMessage(labels.delete)} diff --git a/src/app/(main)/pixels/PixelEditForm.tsx b/src/app/(main)/pixels/PixelEditForm.tsx index 4d3def87..61971e79 100644 --- a/src/app/(main)/pixels/PixelEditForm.tsx +++ b/src/app/(main)/pixels/PixelEditForm.tsx @@ -31,7 +31,7 @@ export function PixelEditForm({ onSave?: () => void; onClose?: () => void; }) { - const { formatMessage, labels, messages } = useMessages(); + const { formatMessage, labels, messages, getErrorMessage } = useMessages(); const { mutate, error, isPending, touch, toast } = useUpdateQuery( pixelId ? `/pixels/${pixelId}` : '/pixels', { @@ -74,7 +74,7 @@ export function PixelEditForm({ } return ( - + {({ setValue }) => { return ( <> diff --git a/src/app/(main)/settings/profile/PasswordEditForm.tsx b/src/app/(main)/settings/profile/PasswordEditForm.tsx index c75840ae..f0df01df 100644 --- a/src/app/(main)/settings/profile/PasswordEditForm.tsx +++ b/src/app/(main)/settings/profile/PasswordEditForm.tsx @@ -9,7 +9,7 @@ import { import { useMessages, useUpdateQuery } from '@/components/hooks'; export function PasswordEditForm({ onSave, onClose }) { - const { formatMessage, labels, messages } = useMessages(); + const { formatMessage, labels, messages, getErrorMessage } = useMessages(); const { mutate, error, isPending } = useUpdateQuery('/me/password'); const handleSubmit = async (data: any) => { @@ -29,7 +29,7 @@ export function PasswordEditForm({ onSave, onClose }) { }; return ( - + @@ -52,7 +52,7 @@ export function PasswordEditForm({ onSave, onClose }) { label={formatMessage(labels.confirmPassword)} rules={{ required: formatMessage(labels.required), - minLength: { value: 8, message: formatMessage(messages.minPasswordLength, { n: 8 }) }, + minLength: { value: 8, message: formatMessage(messages.minPasswordLength, { n: '8' }) }, validate: samePassword, }} > diff --git a/src/app/(main)/teams/TeamAddForm.tsx b/src/app/(main)/teams/TeamAddForm.tsx index 1f80b2c2..68ff7978 100644 --- a/src/app/(main)/teams/TeamAddForm.tsx +++ b/src/app/(main)/teams/TeamAddForm.tsx @@ -9,7 +9,7 @@ import { } from '@umami/react-zen'; export function TeamAddForm({ onSave, onClose }: { onSave: () => void; onClose: () => void }) { - const { formatMessage, labels } = useMessages(); + const { formatMessage, labels, getErrorMessage } = useMessages(); const { mutate, error, isPending } = useUpdateQuery('/teams'); const handleSubmit = async (data: any) => { @@ -22,7 +22,7 @@ export function TeamAddForm({ onSave, onClose }: { onSave: () => void; onClose: }; return ( - + diff --git a/src/app/(main)/teams/TeamJoinForm.tsx b/src/app/(main)/teams/TeamJoinForm.tsx index ca2e96ef..965d82d1 100644 --- a/src/app/(main)/teams/TeamJoinForm.tsx +++ b/src/app/(main)/teams/TeamJoinForm.tsx @@ -9,7 +9,7 @@ import { import { useMessages, useModified, useUpdateQuery } from '@/components/hooks'; export function TeamJoinForm({ onSave, onClose }: { onSave: () => void; onClose: () => void }) { - const { formatMessage, labels } = useMessages(); + const { formatMessage, labels, getErrorMessage } = useMessages(); const { mutate, error } = useUpdateQuery('/teams/join'); const { touch } = useModified(); @@ -24,7 +24,7 @@ export function TeamJoinForm({ onSave, onClose }: { onSave: () => void; onClose: }; return ( - + void; onClose: () => void; }) { - const { formatMessage, labels, messages } = useMessages(); + const { formatMessage, labels, messages, getErrorMessage } = useMessages(); const { mutate, error, isPending } = useDeleteQuery(`/teams/${teamId}/users/${userId}`); const { touch } = useModified(); @@ -37,7 +37,7 @@ export function TeamLeaveForm({ onConfirm={handleConfirm} onClose={onClose} isLoading={isPending} - error={error} + error={getErrorMessage(error)} /> ); } diff --git a/src/app/(main)/teams/[teamId]/TeamDeleteForm.tsx b/src/app/(main)/teams/[teamId]/TeamDeleteForm.tsx index 94c51d87..3634af56 100644 --- a/src/app/(main)/teams/[teamId]/TeamDeleteForm.tsx +++ b/src/app/(main)/teams/[teamId]/TeamDeleteForm.tsx @@ -12,7 +12,7 @@ export function TeamDeleteForm({ onSave?: () => void; onClose?: () => void; }) { - const { labels, formatMessage } = useMessages(); + const { labels, formatMessage, getErrorMessage } = useMessages(); const { mutate, error, isPending, touch } = useDeleteQuery(`/teams/${teamId}`); const handleConfirm = async () => { @@ -31,7 +31,7 @@ export function TeamDeleteForm({ onConfirm={handleConfirm} onClose={onClose} isLoading={isPending} - error={error} + error={getErrorMessage(error)} buttonLabel={formatMessage(labels.delete)} buttonVariant="danger" /> diff --git a/src/app/(main)/teams/[teamId]/TeamEditForm.tsx b/src/app/(main)/teams/[teamId]/TeamEditForm.tsx index 8444dcb8..98c2fbf3 100644 --- a/src/app/(main)/teams/[teamId]/TeamEditForm.tsx +++ b/src/app/(main)/teams/[teamId]/TeamEditForm.tsx @@ -21,7 +21,7 @@ export function TeamEditForm({ onSave?: () => void; }) { const team = useTeam(); - const { formatMessage, labels, messages } = useMessages(); + const { formatMessage, labels, messages, getErrorMessage } = useMessages(); const { mutate, error, isPending, touch, toast } = useUpdateQuery(`/teams/${teamId}`); @@ -37,7 +37,7 @@ export function TeamEditForm({ }; return ( - + {({ setValue }) => { return ( <> diff --git a/src/app/(main)/teams/[teamId]/TeamMemberEditForm.tsx b/src/app/(main)/teams/[teamId]/TeamMemberEditForm.tsx index e2c264ab..e64a9b27 100644 --- a/src/app/(main)/teams/[teamId]/TeamMemberEditForm.tsx +++ b/src/app/(main)/teams/[teamId]/TeamMemberEditForm.tsx @@ -24,7 +24,7 @@ export function TeamMemberEditForm({ onClose?: () => void; }) { const { mutate, error, isPending } = useUpdateQuery(`/teams/${teamId}/users/${userId}`); - const { formatMessage, labels } = useMessages(); + const { formatMessage, labels, getErrorMessage } = useMessages(); const handleSubmit = async (data: any) => { mutate(data, { @@ -36,7 +36,7 @@ export function TeamMemberEditForm({ }; return ( - + diff --git a/src/app/(main)/websites/[websiteId]/cohorts/CohortEditForm.tsx b/src/app/(main)/websites/[websiteId]/cohorts/CohortEditForm.tsx index 88e3d50d..cfe55230 100644 --- a/src/app/(main)/websites/[websiteId]/cohorts/CohortEditForm.tsx +++ b/src/app/(main)/websites/[websiteId]/cohorts/CohortEditForm.tsx @@ -31,7 +31,7 @@ export function CohortEditForm({ onClose?: () => void; }) { const { data } = useWebsiteCohortQuery(websiteId, cohortId); - const { formatMessage, labels, messages } = useMessages(); + const { formatMessage, labels, messages, getErrorMessage } = useMessages(); const { mutate, error, isPending, touch, toast } = useUpdateQuery( `/websites/${websiteId}/segments${cohortId ? `/${cohortId}` : ''}`, @@ -60,7 +60,11 @@ export function CohortEditForm({ }; return ( - + {({ watch }) => { const type = watch('parameters.action.type'); diff --git a/src/app/(main)/websites/[websiteId]/segments/SegmentEditForm.tsx b/src/app/(main)/websites/[websiteId]/segments/SegmentEditForm.tsx index 3be668d8..32b7ee93 100644 --- a/src/app/(main)/websites/[websiteId]/segments/SegmentEditForm.tsx +++ b/src/app/(main)/websites/[websiteId]/segments/SegmentEditForm.tsx @@ -28,7 +28,7 @@ export function SegmentEditForm({ onClose?: () => void; }) { const { data } = useWebsiteSegmentQuery(websiteId, segmentId); - const { formatMessage, labels } = useMessages(); + const { formatMessage, labels, getErrorMessage } = useMessages(); const { mutate, error, isPending, touch, toast } = useUpdateQuery( `/websites/${websiteId}/segments${segmentId ? `/${segmentId}` : ''}`, @@ -53,7 +53,11 @@ export function SegmentEditForm({ } return ( - + void }) { const website = useWebsite(); - const { formatMessage, labels, messages } = useMessages(); + const { formatMessage, labels, messages, getErrorMessage } = useMessages(); const { mutate, error, touch, toast } = useUpdateQuery(`/websites/${websiteId}`); const handleSubmit = async (data: any) => { @@ -18,7 +18,7 @@ export function WebsiteEditForm({ websiteId, onSave }: { websiteId: string; onSa }; return ( - + diff --git a/src/app/(main)/websites/[websiteId]/settings/WebsiteShareForm.tsx b/src/app/(main)/websites/[websiteId]/settings/WebsiteShareForm.tsx index 613a16fa..f828d9e1 100644 --- a/src/app/(main)/websites/[websiteId]/settings/WebsiteShareForm.tsx +++ b/src/app/(main)/websites/[websiteId]/settings/WebsiteShareForm.tsx @@ -23,7 +23,7 @@ export interface WebsiteShareFormProps { } export function WebsiteShareForm({ websiteId, shareId, onSave, onClose }: WebsiteShareFormProps) { - const { formatMessage, labels, messages } = useMessages(); + const { formatMessage, labels, messages, getErrorMessage } = useMessages(); const [id, setId] = useState(shareId); const { mutate, error, isPending, touch, toast } = useUpdateQuery(`/websites/${websiteId}`); @@ -52,7 +52,7 @@ export function WebsiteShareForm({ websiteId, shareId, onSave, onClose }: Websit }; return ( - + {formatMessage(labels.enableShareUrl)} diff --git a/src/app/(main)/websites/[websiteId]/settings/WebsiteTransferForm.tsx b/src/app/(main)/websites/[websiteId]/settings/WebsiteTransferForm.tsx index 4576b05e..f7461a44 100644 --- a/src/app/(main)/websites/[websiteId]/settings/WebsiteTransferForm.tsx +++ b/src/app/(main)/websites/[websiteId]/settings/WebsiteTransferForm.tsx @@ -31,7 +31,7 @@ export function WebsiteTransferForm({ const { user } = useLoginQuery(); const website = useWebsite(); const [teamId, setTeamId] = useState(null); - const { formatMessage, labels, messages } = useMessages(); + const { formatMessage, labels, messages, getErrorMessage } = useMessages(); const { mutate, error, isPending } = useUpdateQuery(`/websites/${websiteId}/transfer`); const { data: teams, isLoading } = useUserTeamsQuery(user.id); const isTeamWebsite = !!website?.teamId; @@ -68,7 +68,7 @@ export function WebsiteTransferForm({ } return ( - + {formatMessage( isTeamWebsite ? messages.transferTeamWebsiteToUser : messages.transferUserWebsiteToTeam, diff --git a/src/app/api/auth/login/route.ts b/src/app/api/auth/login/route.ts index f70cfc8e..e8b0e850 100644 --- a/src/app/api/auth/login/route.ts +++ b/src/app/api/auth/login/route.ts @@ -4,7 +4,8 @@ import redis from '@/lib/redis'; import { getUserByUsername } from '@/queries'; import { json, unauthorized } from '@/lib/response'; import { parseRequest } from '@/lib/request'; -import { saveAuth, checkPassword } from '@/lib/auth'; +import { saveAuth } from '@/lib/auth'; +import { checkPassword } from '@/lib/password'; import { secret } from '@/lib/crypto'; import { ROLES } from '@/lib/constants'; diff --git a/src/app/login/LoginForm.tsx b/src/app/login/LoginForm.tsx index 8090580d..fe09dcb2 100644 --- a/src/app/login/LoginForm.tsx +++ b/src/app/login/LoginForm.tsx @@ -16,7 +16,7 @@ import { setClientAuthToken } from '@/lib/client'; import { Logo } from '@/components/icons'; export function LoginForm() { - const { formatMessage, labels } = useMessages(); + const { formatMessage, labels, getErrorMessage } = useMessages(); const router = useRouter(); const { mutate, error, isPending } = useUpdateQuery('/auth/login'); @@ -32,12 +32,12 @@ export function LoginForm() { }; return ( - + umami - + diff --git a/src/app/login/LoginPage.tsx b/src/app/login/LoginPage.tsx index 83d17bd4..6f485e3f 100644 --- a/src/app/login/LoginPage.tsx +++ b/src/app/login/LoginPage.tsx @@ -4,7 +4,7 @@ import { LoginForm } from './LoginForm'; export function LoginPage() { return ( - + ); diff --git a/src/components/common/ConfirmationForm.tsx b/src/components/common/ConfirmationForm.tsx index c84b176a..a93dcc7f 100644 --- a/src/components/common/ConfirmationForm.tsx +++ b/src/components/common/ConfirmationForm.tsx @@ -21,10 +21,10 @@ export function ConfirmationForm({ onConfirm, onClose, }: ConfirmationFormProps) { - const { formatMessage, labels } = useMessages(); + const { formatMessage, labels, getErrorMessage } = useMessages(); return ( - + {message} diff --git a/src/components/common/LoadingPanel.tsx b/src/components/common/LoadingPanel.tsx index d5abfef0..a0159a02 100644 --- a/src/components/common/LoadingPanel.tsx +++ b/src/components/common/LoadingPanel.tsx @@ -5,7 +5,7 @@ import { Empty } from '@/components/common/Empty'; export interface LoadingPanelProps extends ColumnProps { data?: any; - error?: Error; + error?: unknown; isEmpty?: boolean; isLoading?: boolean; isFetching?: boolean; diff --git a/src/components/common/TypeConfirmationForm.tsx b/src/components/common/TypeConfirmationForm.tsx index 5b82f436..a28dd3c1 100644 --- a/src/components/common/TypeConfirmationForm.tsx +++ b/src/components/common/TypeConfirmationForm.tsx @@ -25,14 +25,13 @@ export function TypeConfirmationForm({ onConfirm?: () => void; onClose?: () => void; }) { - const { formatMessage, labels, messages } = useMessages(); - + const { formatMessage, labels, messages, getErrorMessage } = useMessages(); if (!confirmationValue) { return null; } return ( - +

{formatMessage(messages.actionConfirmation, { confirmation: confirmationValue, diff --git a/src/components/hooks/queries/useUpdateQuery.ts b/src/components/hooks/queries/useUpdateQuery.ts index ac280d8d..9535c436 100644 --- a/src/components/hooks/queries/useUpdateQuery.ts +++ b/src/components/hooks/queries/useUpdateQuery.ts @@ -1,6 +1,6 @@ +import { useToast } from '@umami/react-zen'; import { useApi } from '../useApi'; import { useModified } from '../useModified'; -import { useToast } from '@umami/react-zen'; export function useUpdateQuery(path: string, params?: Record) { const { post, useMutation } = useApi(); diff --git a/src/components/hooks/useApi.ts b/src/components/hooks/useApi.ts index fd0e26c6..8a456054 100644 --- a/src/components/hooks/useApi.ts +++ b/src/components/hooks/useApi.ts @@ -2,23 +2,18 @@ import { useCallback } from 'react'; import { useQuery, useMutation } from '@tanstack/react-query'; import { getClientAuthToken } from '@/lib/client'; import { SHARE_TOKEN_HEADER } from '@/lib/constants'; -import { httpGet, httpPost, httpPut, httpDelete, FetchResponse } from '@/lib/fetch'; +import { httpGet, httpPost, httpPut, httpDelete, FetchResponse, ErrorResponse } from '@/lib/fetch'; import { useApp } from '@/store/app'; const selector = (state: { shareToken: { token?: string } }) => state.shareToken; async function handleResponse(res: FetchResponse): Promise { - if (res.error) { - const { message, code } = res?.error?.error || {}; - return Promise.reject(new Error(code || message || 'Unexpected error.')); + if (!res.ok) { + return Promise.reject(res.data?.error as ErrorResponse); } return Promise.resolve(res.data); } -function handleError(err: Error | string) { - return Promise.reject((err as Error)?.message || err || null); -} - export function useApi() { const shareToken = useApp(selector); @@ -39,36 +34,28 @@ export function useApi() { return { get: useCallback( async (url: string, params: object = {}, headers: object = {}) => { - return httpGet(getUrl(url), params, getHeaders(headers)) - .then(handleResponse) - .catch(handleError); + return httpGet(getUrl(url), params, getHeaders(headers)).then(handleResponse); }, [httpGet], ), post: useCallback( async (url: string, params: object = {}, headers: object = {}) => { - return httpPost(getUrl(url), params, getHeaders(headers)) - .then(handleResponse) - .catch(handleError); + return httpPost(getUrl(url), params, getHeaders(headers)).then(handleResponse); }, [httpPost], ), put: useCallback( async (url: string, params: object = {}, headers: object = {}) => { - return httpPut(getUrl(url), params, getHeaders(headers)) - .then(handleResponse) - .catch(handleError); + return httpPut(getUrl(url), params, getHeaders(headers)).then(handleResponse); }, [httpPut], ), del: useCallback( async (url: string, params: object = {}, headers: object = {}) => { - return httpDelete(getUrl(url), params, getHeaders(headers)) - .then(handleResponse) - .catch(handleError); + return httpDelete(getUrl(url), params, getHeaders(headers)).then(handleResponse); }, [httpDelete], ), diff --git a/src/components/hooks/useMessages.ts b/src/components/hooks/useMessages.ts index 99e9e5a5..48b1ba60 100644 --- a/src/components/hooks/useMessages.ts +++ b/src/components/hooks/useMessages.ts @@ -10,6 +10,16 @@ export function useMessages() { return message ? formatMessage(message) : id; }; + const getErrorMessage = (error: unknown) => { + if (!error) { + return undefined; + } + + const code = error?.['code']; + + return code ? getMessage(code) : error?.['message'] || 'Unknown error'; + }; + const formatMessage = ( descriptor: { id: string; @@ -21,5 +31,5 @@ export function useMessages() { return descriptor ? intl.formatMessage(descriptor, values, opts) : null; }; - return { formatMessage, messages, labels, getMessage }; + return { formatMessage, messages, labels, getMessage, getErrorMessage }; } diff --git a/src/components/hooks/useNavigation.ts b/src/components/hooks/useNavigation.ts index cdfb1fc6..1b725060 100644 --- a/src/components/hooks/useNavigation.ts +++ b/src/components/hooks/useNavigation.ts @@ -1,6 +1,6 @@ import { useState, useEffect } from 'react'; import { usePathname, useRouter, useSearchParams } from 'next/navigation'; -import { buildUrl } from '@/lib/url'; +import { buildPath } from '@/lib/url'; export function useNavigation() { const router = useRouter(); @@ -11,15 +11,15 @@ export function useNavigation() { const [queryParams, setQueryParams] = useState(Object.fromEntries(searchParams)); const updateParams = (params?: Record) => { - return buildUrl(pathname, { ...queryParams, ...params }); + return buildPath(pathname, { ...queryParams, ...params }); }; const replaceParams = (params?: Record) => { - return buildUrl(pathname, params); + return buildPath(pathname, params); }; const renderUrl = (path: string, params?: Record | false) => { - return buildUrl( + return buildPath( teamId ? `/teams/${teamId}${path}` : path, params === false ? {} : { ...queryParams, ...params }, ); diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index 06d94192..1086973f 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -1,4 +1,4 @@ -import { buildUrl } from '@/lib/url'; +import { buildPath } from '@/lib/url'; export interface ErrorResponse { error: { @@ -36,18 +36,17 @@ export async function request( return { ok: res.ok, status: res.status, - data: res.ok ? data : undefined, - error: res.ok ? undefined : data, + data, }; }); } export async function httpGet(path: string, params: object = {}, headers: object = {}) { - return request('GET', buildUrl(path, params), undefined, headers); + return request('GET', buildPath(path, params), undefined, headers); } export async function httpDelete(path: string, params: object = {}, headers: object = {}) { - return request('DELETE', buildUrl(path, params), undefined, headers); + return request('DELETE', buildPath(path, params), undefined, headers); } export async function httpPost(path: string, params: object = {}, headers: object = {}) { diff --git a/src/lib/url.ts b/src/lib/url.ts index f39e6061..f6772fee 100644 --- a/src/lib/url.ts +++ b/src/lib/url.ts @@ -10,9 +10,9 @@ export function getQueryString(params: object = {}): string { return searchParams.toString(); } -export function buildUrl(url: string, params: object = {}): string { +export function buildPath(path: string, params: object = {}): string { const queryString = getQueryString(params); - return `${url}${queryString && '?' + queryString}`; + return queryString ? `${path}?${queryString}` : path; } export function safeDecodeURI(s: string | undefined | null): string | undefined | null {