diff --git a/package.components.json b/package.components.json index 8dda25887..09c0cf52c 100644 --- a/package.components.json +++ b/package.components.json @@ -1,6 +1,6 @@ { "name": "@umami/components", - "version": "0.123.0", + "version": "0.121.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 f6881a109..e02be0df4 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, getErrorMessage } = useMessages(); + const { formatMessage, labels } = 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 cfecb4259..ba0eddbef 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, getErrorMessage } = useMessages(); + const { formatMessage, labels } = 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={getErrorMessage(error)} + error={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 16c65aecb..54237e435 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, getErrorMessage } = useMessages(); + const { formatMessage, labels, messages } = 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 0a81ab802..6bbb514f7 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, getErrorMessage } = useMessages(); + const { formatMessage, labels } = useMessages(); const { mutate, isPending, error } = useDeleteQuery(`/pixels/${pixelId}`); const { touch } = useModified(); @@ -36,7 +36,7 @@ export function PixelDeleteButton({ target: name, })} isLoading={isPending} - error={getErrorMessage(error)} + error={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 61971e795..4d3def87f 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, getErrorMessage } = useMessages(); + const { formatMessage, labels, messages } = 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 f0df01df8..c75840ae1 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, getErrorMessage } = useMessages(); + const { formatMessage, labels, messages } = 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 68ff79786..1f80b2c2b 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, getErrorMessage } = useMessages(); + const { formatMessage, labels } = 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 965d82d1f..ca2e96eff 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, getErrorMessage } = useMessages(); + const { formatMessage, labels } = 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, getErrorMessage } = useMessages(); + const { formatMessage, labels, messages } = 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={getErrorMessage(error)} + error={error} /> ); } diff --git a/src/app/(main)/teams/[teamId]/TeamDeleteForm.tsx b/src/app/(main)/teams/[teamId]/TeamDeleteForm.tsx index 3634af568..94c51d87d 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, getErrorMessage } = useMessages(); + const { labels, formatMessage } = 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={getErrorMessage(error)} + error={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 98c2fbf34..8444dcb8d 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, getErrorMessage } = useMessages(); + const { formatMessage, labels, messages } = 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 e64a9b274..e2c264abf 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, getErrorMessage } = useMessages(); + const { formatMessage, labels } = 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 cfe552300..88e3d50de 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, getErrorMessage } = useMessages(); + const { formatMessage, labels, messages } = useMessages(); const { mutate, error, isPending, touch, toast } = useUpdateQuery( `/websites/${websiteId}/segments${cohortId ? `/${cohortId}` : ''}`, @@ -60,11 +60,7 @@ 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 32b7ee931..3be668d88 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, getErrorMessage } = useMessages(); + const { formatMessage, labels } = useMessages(); const { mutate, error, isPending, touch, toast } = useUpdateQuery( `/websites/${websiteId}/segments${segmentId ? `/${segmentId}` : ''}`, @@ -53,11 +53,7 @@ export function SegmentEditForm({ } return ( - + void }) { const website = useWebsite(); - const { formatMessage, labels, messages, getErrorMessage } = useMessages(); + const { formatMessage, labels, messages } = 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 f828d9e11..613a16fa2 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, getErrorMessage } = useMessages(); + const { formatMessage, labels, messages } = 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 f7461a445..4576b05e6 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, getErrorMessage } = useMessages(); + const { formatMessage, labels, messages } = 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 e8b0e8502..f70cfc8ea 100644 --- a/src/app/api/auth/login/route.ts +++ b/src/app/api/auth/login/route.ts @@ -4,8 +4,7 @@ import redis from '@/lib/redis'; import { getUserByUsername } from '@/queries'; import { json, unauthorized } from '@/lib/response'; import { parseRequest } from '@/lib/request'; -import { saveAuth } from '@/lib/auth'; -import { checkPassword } from '@/lib/password'; +import { saveAuth, checkPassword } from '@/lib/auth'; 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 fe09dcb23..8090580da 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, getErrorMessage } = useMessages(); + const { formatMessage, labels } = 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 6f485e3f7..83d17bd4e 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 a93dcc7f8..c84b176a0 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, getErrorMessage } = useMessages(); + const { formatMessage, labels } = useMessages(); return ( - + {message} diff --git a/src/components/common/LoadingPanel.tsx b/src/components/common/LoadingPanel.tsx index a0159a029..d5abfef02 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?: unknown; + error?: Error; isEmpty?: boolean; isLoading?: boolean; isFetching?: boolean; diff --git a/src/components/common/TypeConfirmationForm.tsx b/src/components/common/TypeConfirmationForm.tsx index a28dd3c15..5b82f4360 100644 --- a/src/components/common/TypeConfirmationForm.tsx +++ b/src/components/common/TypeConfirmationForm.tsx @@ -25,13 +25,14 @@ export function TypeConfirmationForm({ onConfirm?: () => void; onClose?: () => void; }) { - const { formatMessage, labels, messages, getErrorMessage } = useMessages(); + const { formatMessage, labels, messages } = 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 9535c4363..ac280d8d6 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 994163ade..fd0e26c6c 100644 --- a/src/components/hooks/useApi.ts +++ b/src/components/hooks/useApi.ts @@ -8,14 +8,17 @@ import { useApp } from '@/store/app'; const selector = (state: { shareToken: { token?: string } }) => state.shareToken; async function handleResponse(res: FetchResponse): Promise { - if (!res.ok) { - const { message, code, status } = res?.data?.error || {}; - - return Promise.reject(Object.assign(new Error(message), { code, status })); + if (res.error) { + const { message, code } = res?.error?.error || {}; + return Promise.reject(new Error(code || message || 'Unexpected error.')); } 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); @@ -36,28 +39,36 @@ export function useApi() { return { get: useCallback( async (url: string, params: object = {}, headers: object = {}) => { - return httpGet(getUrl(url), params, getHeaders(headers)).then(handleResponse); + return httpGet(getUrl(url), params, getHeaders(headers)) + .then(handleResponse) + .catch(handleError); }, [httpGet], ), post: useCallback( async (url: string, params: object = {}, headers: object = {}) => { - return httpPost(getUrl(url), params, getHeaders(headers)).then(handleResponse); + return httpPost(getUrl(url), params, getHeaders(headers)) + .then(handleResponse) + .catch(handleError); }, [httpPost], ), put: useCallback( async (url: string, params: object = {}, headers: object = {}) => { - return httpPut(getUrl(url), params, getHeaders(headers)).then(handleResponse); + return httpPut(getUrl(url), params, getHeaders(headers)) + .then(handleResponse) + .catch(handleError); }, [httpPut], ), del: useCallback( async (url: string, params: object = {}, headers: object = {}) => { - return httpDelete(getUrl(url), params, getHeaders(headers)).then(handleResponse); + return httpDelete(getUrl(url), params, getHeaders(headers)) + .then(handleResponse) + .catch(handleError); }, [httpDelete], ), diff --git a/src/components/hooks/useMessages.ts b/src/components/hooks/useMessages.ts index 48b1ba60f..99e9e5a5d 100644 --- a/src/components/hooks/useMessages.ts +++ b/src/components/hooks/useMessages.ts @@ -10,16 +10,6 @@ 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; @@ -31,5 +21,5 @@ export function useMessages() { return descriptor ? intl.formatMessage(descriptor, values, opts) : null; }; - return { formatMessage, messages, labels, getMessage, getErrorMessage }; + return { formatMessage, messages, labels, getMessage }; } diff --git a/src/components/hooks/useNavigation.ts b/src/components/hooks/useNavigation.ts index 1b7250602..cdfb1fc62 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 { buildPath } from '@/lib/url'; +import { buildUrl } 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 buildPath(pathname, { ...queryParams, ...params }); + return buildUrl(pathname, { ...queryParams, ...params }); }; const replaceParams = (params?: Record) => { - return buildPath(pathname, params); + return buildUrl(pathname, params); }; const renderUrl = (path: string, params?: Record | false) => { - return buildPath( + return buildUrl( teamId ? `/teams/${teamId}${path}` : path, params === false ? {} : { ...queryParams, ...params }, ); diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index 1086973f3..06d941924 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -1,4 +1,4 @@ -import { buildPath } from '@/lib/url'; +import { buildUrl } from '@/lib/url'; export interface ErrorResponse { error: { @@ -36,17 +36,18 @@ export async function request( return { ok: res.ok, status: res.status, - data, + data: res.ok ? data : undefined, + error: res.ok ? undefined : data, }; }); } export async function httpGet(path: string, params: object = {}, headers: object = {}) { - return request('GET', buildPath(path, params), undefined, headers); + return request('GET', buildUrl(path, params), undefined, headers); } export async function httpDelete(path: string, params: object = {}, headers: object = {}) { - return request('DELETE', buildPath(path, params), undefined, headers); + return request('DELETE', buildUrl(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 f6772feec..f39e60619 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 buildPath(path: string, params: object = {}): string { +export function buildUrl(url: string, params: object = {}): string { const queryString = getQueryString(params); - return queryString ? `${path}?${queryString}` : path; + return `${url}${queryString && '?' + queryString}`; } export function safeDecodeURI(s: string | undefined | null): string | undefined | null {