feat(frontend): add apiFetch wrapper for automatic auth token injection
Centralizes auth token management into a reusable apiFetch() helper and getAuthHeaders() utility, eliminating inline Bearer token logic across all frontend files. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { SvelteDate } from 'svelte/reactivity';
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { apiFetch } from '$lib/utils/api';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { sanitizeText } from '$lib/utils/toast-safe';
|
||||
import { formatDateTime, formatDuration } from '$lib/utils/format';
|
||||
@@ -140,11 +140,10 @@
|
||||
if (!booking?.id) return;
|
||||
loadingOverlaps = true;
|
||||
try {
|
||||
const response = await fetch(`/api/admin/bookings/${booking.id}/overlapping`, {
|
||||
const response = await apiFetch(`/api/admin/bookings/${booking.id}/overlapping`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
if (response.ok) {
|
||||
@@ -322,11 +321,10 @@
|
||||
serviceOverrides: overrides.length > 0 ? overrides : undefined
|
||||
};
|
||||
|
||||
const response = await fetch(`/api/admin/bookings/${booking.id}/confirm`, {
|
||||
const response = await apiFetch(`/api/admin/bookings/${booking.id}/confirm`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
@@ -351,11 +349,10 @@
|
||||
const loadingToast = toast.loading('Declining booking...');
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/admin/bookings/${booking.id}/cancel`, {
|
||||
const response = await apiFetch(`/api/admin/bookings/${booking.id}/cancel`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { apiFetch } from '$lib/utils/api';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { SvelteDate, SvelteSet, SvelteURLSearchParams } from 'svelte/reactivity';
|
||||
import { CalendarDate, getLocalTimeZone, type DateValue } from '@internationalized/date';
|
||||
@@ -473,11 +473,8 @@
|
||||
async function fetchUsers() {
|
||||
loadingUsers = true;
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/admin/users?page=1&per_page=4&q=${encodeURIComponent(userQuery)}`,
|
||||
{
|
||||
headers: { Authorization: `Bearer ${authStore.currentToken}` }
|
||||
}
|
||||
const response = await apiFetch(
|
||||
`/api/admin/users?page=1&per_page=4&q=${encodeURIComponent(userQuery)}`
|
||||
);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
@@ -503,9 +500,7 @@
|
||||
if (selectedUserId) {
|
||||
url = `/api/services/eligible-for/${selectedUserId}`;
|
||||
}
|
||||
const response = await fetch(url, {
|
||||
headers: { Authorization: `Bearer ${authStore.currentToken}` }
|
||||
});
|
||||
const response = await apiFetch(url);
|
||||
if (response.ok) {
|
||||
services = await response.json();
|
||||
}
|
||||
@@ -538,17 +533,11 @@
|
||||
|
||||
try {
|
||||
const [whRes, ahRes] = await Promise.all([
|
||||
fetch(
|
||||
`/api/scheduling/working-hours?start=${startStr}&end=${endStr}${outOfHours ? '&out_of_hours=true' : ''}`,
|
||||
{
|
||||
headers: { Authorization: `Bearer ${authStore.currentToken}` }
|
||||
}
|
||||
apiFetch(
|
||||
`/api/scheduling/working-hours?start=${startStr}&end=${endStr}${outOfHours ? '&out_of_hours=true' : ''}`
|
||||
),
|
||||
fetch(
|
||||
`/api/scheduling/available-hours?start=${startStr}&end=${endStr}${outOfHours ? '&out_of_hours=true' : ''}`,
|
||||
{
|
||||
headers: { Authorization: `Bearer ${authStore.currentToken}` }
|
||||
}
|
||||
apiFetch(
|
||||
`/api/scheduling/available-hours?start=${startStr}&end=${endStr}${outOfHours ? '&out_of_hours=true' : ''}`
|
||||
)
|
||||
]);
|
||||
|
||||
@@ -620,17 +609,11 @@
|
||||
);
|
||||
|
||||
const [whRes, ahRes] = await Promise.all([
|
||||
fetch(
|
||||
`/api/scheduling/working-hours?start=${startOfMonth}&end=${endOfMonth}${outOfHours ? '&out_of_hours=true' : ''}`,
|
||||
{
|
||||
headers: { Authorization: `Bearer ${authStore.currentToken}` }
|
||||
}
|
||||
apiFetch(
|
||||
`/api/scheduling/working-hours?start=${startOfMonth}&end=${endOfMonth}${outOfHours ? '&out_of_hours=true' : ''}`
|
||||
),
|
||||
fetch(
|
||||
`/api/scheduling/available-hours?start=${startOfMonth}&end=${endOfMonth}${outOfHours ? '&out_of_hours=true' : ''}`,
|
||||
{
|
||||
headers: { Authorization: `Bearer ${authStore.currentToken}` }
|
||||
}
|
||||
apiFetch(
|
||||
`/api/scheduling/available-hours?start=${startOfMonth}&end=${endOfMonth}${outOfHours ? '&out_of_hours=true' : ''}`
|
||||
)
|
||||
]);
|
||||
|
||||
@@ -713,11 +696,10 @@
|
||||
payload.custom_service_ids = customServiceIds;
|
||||
}
|
||||
|
||||
const response = await fetch('/api/admin/bookings/reserve', {
|
||||
const response = await apiFetch('/api/admin/bookings/reserve', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
@@ -818,9 +800,7 @@
|
||||
} else {
|
||||
params.set('popular', '3');
|
||||
}
|
||||
const response = await fetch(`/api/admin/custom-services?${params}`, {
|
||||
headers: { Authorization: `Bearer ${authStore.currentToken}` }
|
||||
});
|
||||
const response = await apiFetch(`/api/admin/custom-services?${params}`);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
const list = data.services || data;
|
||||
@@ -841,11 +821,10 @@
|
||||
}
|
||||
creatingCustomService = true;
|
||||
try {
|
||||
const response = await fetch('/api/admin/custom-services', {
|
||||
const response = await apiFetch('/api/admin/custom-services', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: (newCustomService.name ?? '').trim(),
|
||||
@@ -955,11 +934,10 @@
|
||||
return;
|
||||
}
|
||||
const phone = toE164UK(guestPhone)!;
|
||||
const createRes = await fetch('/api/users/guest', {
|
||||
const createRes = await apiFetch('/api/users/guest', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
firstName: guestName.trim().split(' ')[0] || 'Guest',
|
||||
@@ -1025,11 +1003,10 @@
|
||||
out_of_hours: outOfHours
|
||||
};
|
||||
|
||||
const res = await fetch('/api/admin/bookings', {
|
||||
const res = await apiFetch('/api/admin/bookings', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { apiFetch } from '$lib/utils/api';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { SvelteDate } from 'svelte/reactivity';
|
||||
import * as Modal from '$lib/components/ui/dialog';
|
||||
@@ -70,11 +71,10 @@
|
||||
const body: Record<string, unknown> = {};
|
||||
if (forgiveFeesCancel) body.forgive_fees = true;
|
||||
if (forgiveNoShowCancel) body.forgive_noshow = true;
|
||||
const response = await fetch(`/api/admin/bookings/${selectedBooking.id}/cancel`, {
|
||||
const response = await apiFetch(`/api/admin/bookings/${selectedBooking.id}/cancel`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
@@ -108,11 +108,10 @@
|
||||
if (!bookingId) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/admin/bookings/${bookingId}`, {
|
||||
const response = await apiFetch(`/api/admin/bookings/${bookingId}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { apiFetch } from '$lib/utils/api';
|
||||
import { SvelteDate, SvelteURLSearchParams } from 'svelte/reactivity';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
@@ -44,11 +44,10 @@
|
||||
url = '/api/admin/bookings/search';
|
||||
}
|
||||
|
||||
const response = await fetch(`${url}?${params}`, {
|
||||
const response = await apiFetch(`${url}?${params}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
if (response.ok) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { apiFetch } from '$lib/utils/api';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
@@ -36,9 +37,7 @@
|
||||
async function fetchSettings() {
|
||||
loading = true;
|
||||
try {
|
||||
const res = await fetch('/api/admin/settings', {
|
||||
headers: { Authorization: `Bearer ${authStore.currentToken}` }
|
||||
});
|
||||
const res = await apiFetch('/api/admin/settings');
|
||||
if (res.ok) {
|
||||
settings = await res.json();
|
||||
} else {
|
||||
@@ -237,11 +236,10 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await fetch('/api/admin/settings', {
|
||||
const res = await apiFetch('/api/admin/settings', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(patch)
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { apiFetch } from '$lib/utils/api';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { sanitizeText } from '$lib/utils/toast-safe';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
@@ -112,9 +112,7 @@
|
||||
per_page: perPage.toString()
|
||||
});
|
||||
if (searchQuery.trim()) params.set('q', searchQuery.trim());
|
||||
const response = await fetch(`/api/admin/custom-services?${params}`, {
|
||||
headers: { Authorization: `Bearer ${authStore.currentToken}` }
|
||||
});
|
||||
const response = await apiFetch(`/api/admin/custom-services?${params}`);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
services = data.services;
|
||||
@@ -137,11 +135,10 @@
|
||||
}
|
||||
creating = true;
|
||||
try {
|
||||
const response = await fetch('/api/admin/custom-services', {
|
||||
const response = await apiFetch('/api/admin/custom-services', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: (newService.name ?? '').trim(),
|
||||
@@ -175,9 +172,8 @@
|
||||
)
|
||||
return;
|
||||
try {
|
||||
const response = await fetch(`/api/admin/custom-services/${id}/promote`, {
|
||||
method: 'POST',
|
||||
headers: { Authorization: `Bearer ${authStore.currentToken}` }
|
||||
const response = await apiFetch(`/api/admin/custom-services/${id}/promote`, {
|
||||
method: 'POST'
|
||||
});
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
@@ -197,9 +193,8 @@
|
||||
async function deleteService(id: string) {
|
||||
if (!confirm('Delete this custom service?')) return;
|
||||
try {
|
||||
const response = await fetch(`/api/admin/custom-services/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: { Authorization: `Bearer ${authStore.currentToken}` }
|
||||
const response = await apiFetch(`/api/admin/custom-services/${id}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
if (response.ok) {
|
||||
toast.success('Deleted');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { SvelteDate } from 'svelte/reactivity';
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { apiFetch } from '$lib/utils/api';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
@@ -101,9 +101,7 @@
|
||||
async function loadCampaigns() {
|
||||
loading = true;
|
||||
try {
|
||||
const res = await fetch('/api/admin/discount-campaigns', {
|
||||
headers: { Authorization: `Bearer ${authStore.currentToken}` }
|
||||
});
|
||||
const res = await apiFetch('/api/admin/discount-campaigns');
|
||||
if (res.ok) campaigns = await res.json();
|
||||
else toast.error('Failed to load campaigns');
|
||||
} catch {
|
||||
@@ -173,11 +171,10 @@
|
||||
: '/api/admin/discount-campaigns';
|
||||
const method = editingCampaign ? 'PUT' : 'POST';
|
||||
|
||||
const res = await fetch(url, {
|
||||
const res = await apiFetch(url, {
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
@@ -200,11 +197,10 @@
|
||||
async function updateStatus(c: Campaign, status: string) {
|
||||
actionInProgress = c.id;
|
||||
try {
|
||||
const res = await fetch(`/api/admin/discount-campaigns/${c.id}`, {
|
||||
const res = await apiFetch(`/api/admin/discount-campaigns/${c.id}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ status })
|
||||
});
|
||||
@@ -222,9 +218,8 @@
|
||||
async function cancelCampaign(c: Campaign) {
|
||||
actionInProgress = c.id;
|
||||
try {
|
||||
const res = await fetch(`/api/admin/discount-campaigns/${c.id}`, {
|
||||
method: 'DELETE',
|
||||
headers: { Authorization: `Bearer ${authStore.currentToken}` }
|
||||
const res = await apiFetch(`/api/admin/discount-campaigns/${c.id}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
if (res.ok) {
|
||||
toast.success('Campaign cancelled');
|
||||
@@ -241,9 +236,7 @@
|
||||
statsLoading = true;
|
||||
showStatsModal = true;
|
||||
try {
|
||||
const res = await fetch(`/api/admin/discount-campaigns/${c.id}/stats`, {
|
||||
headers: { Authorization: `Bearer ${authStore.currentToken}` }
|
||||
});
|
||||
const res = await apiFetch(`/api/admin/discount-campaigns/${c.id}/stats`);
|
||||
if (res.ok) statsData = await res.json();
|
||||
else toast.error('Failed to load stats');
|
||||
} catch {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { apiFetch } from '$lib/utils/api';
|
||||
import { SvelteDate } from 'svelte/reactivity';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { formatDuration } from '$lib/utils/format';
|
||||
@@ -59,11 +59,10 @@
|
||||
async function fetchBooking() {
|
||||
loading = true;
|
||||
try {
|
||||
const response = await fetch(`/api/admin/bookings/${bookingId}`, {
|
||||
const response = await apiFetch(`/api/admin/bookings/${bookingId}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -241,11 +240,7 @@
|
||||
async function fetchAvailableServices() {
|
||||
loadingServices = true;
|
||||
try {
|
||||
const response = await fetch('/api/services', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
}
|
||||
});
|
||||
const response = await apiFetch('/api/services');
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
@@ -322,11 +317,10 @@
|
||||
payload.notes = notes || undefined;
|
||||
}
|
||||
|
||||
const response = await fetch(`/api/admin/bookings/${bookingId}`, {
|
||||
const response = await apiFetch(`/api/admin/bookings/${bookingId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
@@ -368,11 +362,10 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await fetch(`/api/admin/payments/${refundPaymentId}/refund`, {
|
||||
const response = await apiFetch(`/api/admin/payments/${refundPaymentId}/refund`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
amount: amountPence,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { apiFetch } from '$lib/utils/api';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import * as Modal from '$lib/components/ui/dialog';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
@@ -112,13 +112,12 @@
|
||||
const loadingToast = toast.loading('Approving change request...');
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
const response = await apiFetch(
|
||||
`/api/admin/bookings/${editRequest.booking_id}/edit-requests/${editRequest.id}/approve`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -144,13 +143,12 @@
|
||||
const loadingToast = toast.loading('Denying change request...');
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
const response = await apiFetch(
|
||||
`/api/admin/bookings/${editRequest.booking_id}/edit-requests/${editRequest.id}/deny`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { apiFetch } from '$lib/utils/api';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
@@ -259,11 +260,7 @@
|
||||
});
|
||||
if (search.trim()) params.append('q', search.trim());
|
||||
|
||||
const res = await fetch(`/api/admin/gift-cards?${params}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
}
|
||||
});
|
||||
const res = await apiFetch(`/api/admin/gift-cards?${params}`);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
summary.total_unclaimed = data.total_unclaimed;
|
||||
@@ -293,11 +290,7 @@
|
||||
async function fetchExpiredBalances() {
|
||||
loadingExpired = true;
|
||||
try {
|
||||
const res = await fetch('/api/admin/gift-cards/expired-balances', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
}
|
||||
});
|
||||
const res = await apiFetch('/api/admin/gift-cards/expired-balances');
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
expiredBalances = data.expired_balances || [];
|
||||
@@ -314,11 +307,10 @@
|
||||
async function claimExpiredBalance(balanceId: string) {
|
||||
claimingId = balanceId;
|
||||
try {
|
||||
const res = await fetch('/api/admin/gift-cards/expired-balances/claim', {
|
||||
const res = await apiFetch('/api/admin/gift-cards/expired-balances/claim', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ balance_id: balanceId })
|
||||
});
|
||||
@@ -352,9 +344,7 @@
|
||||
loadingCurrentCustomer = true;
|
||||
currentCustomerInfo = null;
|
||||
try {
|
||||
const res = await fetch('/api/admin/today/current-next', {
|
||||
headers: { Authorization: `Bearer ${authStore.currentToken}` }
|
||||
});
|
||||
const res = await apiFetch('/api/admin/today/current-next');
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
const appointment = data.current || data.next;
|
||||
@@ -385,9 +375,8 @@
|
||||
if (!generateUserQuery.trim()) return;
|
||||
generateLoadingUsers = true;
|
||||
try {
|
||||
const res = await fetch(
|
||||
`/api/admin/users?page=1&per_page=5&q=${encodeURIComponent(generateUserQuery)}`,
|
||||
{ headers: { Authorization: `Bearer ${authStore.currentToken}` } }
|
||||
const res = await apiFetch(
|
||||
`/api/admin/users?page=1&per_page=5&q=${encodeURIComponent(generateUserQuery)}`
|
||||
);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
@@ -406,11 +395,10 @@
|
||||
async function generateInventoryCard() {
|
||||
creating = true;
|
||||
try {
|
||||
const res = await fetch('/api/admin/gift-cards', {
|
||||
const res = await apiFetch('/api/admin/gift-cards', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
amount: 0,
|
||||
@@ -449,11 +437,10 @@
|
||||
if (!isTransferValid || !selectedCardId) return;
|
||||
transferring = true;
|
||||
try {
|
||||
const res = await fetch(`/api/admin/gift-cards/${selectedCardId}/transfer`, {
|
||||
const res = await apiFetch(`/api/admin/gift-cards/${selectedCardId}/transfer`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
to_card_id: transferToCode,
|
||||
@@ -570,11 +557,10 @@
|
||||
if (actionType === 'create' && generateType === 'account' && selectedCustomer)
|
||||
body.redeem_to_user_id = selectedCustomer.id;
|
||||
|
||||
const res = await fetch('/api/admin/till/sale', {
|
||||
const res = await apiFetch('/api/admin/till/sale', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
@@ -610,11 +596,10 @@
|
||||
if (actionType === 'create' && generateType === 'account' && selectedCustomer)
|
||||
body.redeem_to_user_id = selectedCustomer.id;
|
||||
|
||||
const res = await fetch('/api/admin/till/sale', {
|
||||
const res = await apiFetch('/api/admin/till/sale', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
@@ -650,9 +635,7 @@
|
||||
await new Promise((r) => setTimeout(r, 2000));
|
||||
attempts++;
|
||||
try {
|
||||
const res = await fetch(`/api/admin/till/sale/checkout/${ckId}/status`, {
|
||||
headers: { Authorization: `Bearer ${authStore.currentToken}` }
|
||||
});
|
||||
const res = await apiFetch(`/api/admin/till/sale/checkout/${ckId}/status`);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
if (data.status === 'COMPLETED') {
|
||||
@@ -700,11 +683,10 @@
|
||||
if (actionType === 'create' && generateType === 'account' && selectedCustomer)
|
||||
body.redeem_to_user_id = selectedCustomer.id;
|
||||
|
||||
const res = await fetch('/api/admin/till/sale', {
|
||||
const res = await apiFetch('/api/admin/till/sale', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
@@ -736,11 +718,10 @@
|
||||
idempotency_key: 'till-on-the-house-' + gcId + '-' + Date.now()
|
||||
};
|
||||
|
||||
const res = await fetch('/api/admin/till/sale', {
|
||||
const res = await apiFetch('/api/admin/till/sale', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { SvelteDate } from 'svelte/reactivity';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { sanitizeText } from '$lib/utils/toast-safe';
|
||||
import { apiFetch } from '$lib/utils/api';
|
||||
|
||||
// shadcn-svelte components
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
@@ -138,11 +138,9 @@
|
||||
async function fetchExceptionGroups() {
|
||||
exceptionGroupsLoading = true;
|
||||
try {
|
||||
const response = await fetch('/api/scheduling/exceptional-groups', {
|
||||
method: 'GET',
|
||||
const response = await apiFetch('/api/scheduling/exceptional-groups', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -210,11 +208,10 @@
|
||||
}))
|
||||
};
|
||||
|
||||
const response = await fetch('/api/scheduling/exceptional-groups', {
|
||||
const response = await apiFetch('/api/scheduling/exceptional-groups', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
@@ -242,12 +239,12 @@
|
||||
const loadingToast = toast.loading('Deleting exception group...');
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/scheduling/exceptional-groups?id=${exceptionToDelete}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
const response = await apiFetch(
|
||||
`/api/scheduling/exceptional-groups?id=${exceptionToDelete}`,
|
||||
{
|
||||
method: 'DELETE'
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
if (response.ok || response.status === 204) {
|
||||
toast.success('Exception group deleted successfully!', { id: loadingToast });
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import FileDropZone from '$lib/components/ui/file-drop-zone.svelte';
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import * as AlertDialog from '$lib/components/ui/alert-dialog';
|
||||
import { apiFetch } from '$lib/utils/api';
|
||||
|
||||
// =============== Image Upload ===============
|
||||
let uploading = $state(false);
|
||||
@@ -581,11 +581,8 @@
|
||||
|
||||
/* -------- 5. Call the API ------------------------------------- */
|
||||
uploadStatus[fileKey] = 'Uploading...';
|
||||
const response = await fetch('/api/portfolio/images', {
|
||||
const response = await apiFetch('/api/portfolio/images', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
},
|
||||
body: fd
|
||||
});
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import * as Modal from '$lib/components/ui/dialog';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { apiFetch } from '$lib/utils/api';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
@@ -28,11 +28,9 @@
|
||||
if (!userId) return;
|
||||
loading = true;
|
||||
try {
|
||||
const response = await fetch(`/api/admin/users/${userId}/patch-tests/eligible`, {
|
||||
method: 'GET',
|
||||
const response = await apiFetch(`/api/admin/users/${userId}/patch-tests/eligible`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -63,11 +61,10 @@
|
||||
const loadingToast = toast.loading('Adding patch test record...');
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/admin/users/${userId}/patch-tests`, {
|
||||
const response = await apiFetch(`/api/admin/users/${userId}/patch-tests`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
service_id: selectedServiceId
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { apiFetch } from '$lib/utils/api';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Checkbox } from '$lib/components/ui/checkbox';
|
||||
@@ -105,9 +105,7 @@
|
||||
async function fetchData() {
|
||||
loading = true;
|
||||
try {
|
||||
const ptRes = await fetch('/api/admin/patch-tests', {
|
||||
headers: { Authorization: `Bearer ${authStore.currentToken}` }
|
||||
});
|
||||
const ptRes = await apiFetch('/api/admin/patch-tests');
|
||||
|
||||
if (ptRes.ok) {
|
||||
patchTests = await ptRes.json();
|
||||
@@ -117,9 +115,7 @@
|
||||
|
||||
// Only fetch services if not provided via prop
|
||||
if (!servicesProp) {
|
||||
const svcRes = await fetch('/api/admin/services', {
|
||||
headers: { Authorization: `Bearer ${authStore.currentToken}` }
|
||||
});
|
||||
const svcRes = await apiFetch('/api/admin/services');
|
||||
if (svcRes.ok) {
|
||||
const svcData = await svcRes.json();
|
||||
const uniqueSvc = new SvelteMap<string, Service>();
|
||||
@@ -205,11 +201,10 @@
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
const res = await apiFetch(url, {
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
@@ -234,9 +229,8 @@
|
||||
if (!confirm('Are you sure you want to delete this patch test requirement?')) return;
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/admin/patch-tests/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: { Authorization: `Bearer ${authStore.currentToken}` }
|
||||
const res = await apiFetch(`/api/admin/patch-tests/${id}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { SvelteDate, SvelteSet } from 'svelte/reactivity';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { apiFetch } from '$lib/utils/api';
|
||||
import { CalendarDate, getLocalTimeZone, type DateValue } from '@internationalized/date';
|
||||
import { Checkbox } from '$lib/components/ui/checkbox';
|
||||
import PolicyPopover from '$lib/components/ui/policyPopover.svelte';
|
||||
@@ -163,12 +163,8 @@
|
||||
|
||||
try {
|
||||
const [whRes, ahRes] = await Promise.all([
|
||||
fetch(`/api/scheduling/working-hours?start=${startStr}&end=${endStr}`, {
|
||||
headers: { Authorization: `Bearer ${authStore.currentToken}` }
|
||||
}),
|
||||
fetch(`/api/scheduling/available-hours?start=${startStr}&end=${endStr}`, {
|
||||
headers: { Authorization: `Bearer ${authStore.currentToken}` }
|
||||
})
|
||||
apiFetch(`/api/scheduling/working-hours?start=${startStr}&end=${endStr}`),
|
||||
apiFetch(`/api/scheduling/available-hours?start=${startStr}&end=${endStr}`)
|
||||
]);
|
||||
|
||||
if (whRes.ok && ahRes.ok) {
|
||||
@@ -249,12 +245,8 @@
|
||||
date.calendar.getDaysInMonth(date)
|
||||
);
|
||||
const [whRes, ahRes] = await Promise.all([
|
||||
fetch(`/api/scheduling/working-hours?start=${startOfMonth}&end=${endOfMonth}`, {
|
||||
headers: { Authorization: `Bearer ${authStore.currentToken}` }
|
||||
}),
|
||||
fetch(`/api/scheduling/available-hours?start=${startOfMonth}&end=${endOfMonth}`, {
|
||||
headers: { Authorization: `Bearer ${authStore.currentToken}` }
|
||||
})
|
||||
apiFetch(`/api/scheduling/working-hours?start=${startOfMonth}&end=${endOfMonth}`),
|
||||
apiFetch(`/api/scheduling/available-hours?start=${startOfMonth}&end=${endOfMonth}`)
|
||||
]);
|
||||
if (whRes.ok && ahRes.ok) {
|
||||
const whData: WorkingHoursDay[] = await whRes.json();
|
||||
@@ -317,11 +309,10 @@
|
||||
const body: Record<string, unknown> = { start_time: newStartTime };
|
||||
if (forgiveFees) body.forgive_fees = true;
|
||||
if (forgiveNoShow) body.forgive_noshow = true;
|
||||
const response = await fetch(`/api/admin/bookings/${booking.id}/reschedule`, {
|
||||
const response = await apiFetch(`/api/admin/bookings/${booking.id}/reschedule`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { apiFetch } from '$lib/utils/api';
|
||||
|
||||
// shadcn-svelte components
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
@@ -154,11 +154,9 @@
|
||||
|
||||
servicesLoading = true;
|
||||
try {
|
||||
const response = await fetch('/api/admin/services', {
|
||||
method: 'GET',
|
||||
const response = await apiFetch('/api/admin/services', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -184,11 +182,8 @@
|
||||
async function toggleService(serviceId: string) {
|
||||
servicesUpdating[serviceId] = true;
|
||||
try {
|
||||
const response = await fetch(`/api/admin/services/${serviceId}/toggle`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
}
|
||||
const response = await apiFetch(`/api/admin/services/${serviceId}/toggle`, {
|
||||
method: 'PUT'
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
@@ -214,11 +209,8 @@
|
||||
servicesUpdating[serviceId] = true;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/admin/services/${serviceId}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
}
|
||||
const response = await apiFetch(`/api/admin/services/${serviceId}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
@@ -262,11 +254,10 @@
|
||||
minimum_age_required: Number(newService.minimum_age_required)
|
||||
};
|
||||
|
||||
const response = await fetch('/api/admin/services', {
|
||||
const response = await apiFetch('/api/admin/services', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { SvelteDate } from 'svelte/reactivity';
|
||||
import { apiFetch } from '$lib/utils/api';
|
||||
import { CalendarDate } from '@internationalized/date';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { sanitizeText } from '$lib/utils/toast-safe';
|
||||
@@ -222,11 +222,9 @@
|
||||
async function fetchBlockers() {
|
||||
loading = true;
|
||||
try {
|
||||
const response = await fetch('/api/admin/time-blockers', {
|
||||
method: 'GET',
|
||||
const response = await apiFetch('/api/admin/time-blockers', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -247,11 +245,9 @@
|
||||
async function fetchDefaultHours() {
|
||||
hoursLoading = true;
|
||||
try {
|
||||
const response = await fetch('/api/scheduling/default-hours', {
|
||||
method: 'GET',
|
||||
const response = await apiFetch('/api/scheduling/default-hours', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -312,13 +308,11 @@
|
||||
checkingOverlap = true;
|
||||
try {
|
||||
const dateParam = newStartDate;
|
||||
const response = await fetch(
|
||||
const response = await apiFetch(
|
||||
`/api/admin/bookings/by-date-range?start=${encodeURIComponent(dateParam)}&end=${encodeURIComponent(dateParam)}`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -409,11 +403,10 @@
|
||||
if (overlappingBookings.length > 0) {
|
||||
for (const booking of overlappingBookings) {
|
||||
try {
|
||||
await fetch('/api/admin/time-blockers', {
|
||||
await apiFetch('/api/admin/time-blockers', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
start_time: startIso,
|
||||
@@ -428,11 +421,10 @@
|
||||
}
|
||||
|
||||
// Create the actual time blocker
|
||||
const response = await fetch('/api/admin/time-blockers', {
|
||||
const response = await apiFetch('/api/admin/time-blockers', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
start_time: startIso,
|
||||
@@ -464,11 +456,8 @@
|
||||
const loadingToast = toast.loading('Deleting time blocker...');
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/admin/time-blockers/${blockerToDelete.id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
}
|
||||
const response = await apiFetch(`/api/admin/time-blockers/${blockerToDelete.id}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
if (response.ok || response.status === 204) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { SvelteDate, SvelteURLSearchParams } from 'svelte/reactivity';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { apiFetch } from '$lib/utils/api';
|
||||
import * as Modal from '$lib/components/ui/dialog';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import PatchTestModal from './PatchTestModal.svelte';
|
||||
@@ -104,11 +104,9 @@
|
||||
if (!userId) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/admin/users/${userId}`, {
|
||||
method: 'GET',
|
||||
const response = await apiFetch(`/api/admin/users/${userId}`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -127,11 +125,9 @@
|
||||
async function fetchEligiblePatchTests() {
|
||||
if (!userId) return;
|
||||
try {
|
||||
const response = await fetch(`/api/admin/users/${userId}/patch-tests/eligible`, {
|
||||
method: 'GET',
|
||||
const response = await apiFetch(`/api/admin/users/${userId}/patch-tests/eligible`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
if (response.ok) {
|
||||
@@ -163,11 +159,9 @@
|
||||
params.set('cursor', cursor);
|
||||
}
|
||||
|
||||
const response = await fetch(`/api/admin/bookings/user/${userId}?${params}`, {
|
||||
method: 'GET',
|
||||
const response = await apiFetch(`/api/admin/bookings/user/${userId}?${params}`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -221,11 +215,9 @@
|
||||
|
||||
loadingRelationship = true;
|
||||
try {
|
||||
const response = await fetch(`/api/admin/users/${userId}/relationship`, {
|
||||
method: 'GET',
|
||||
const response = await apiFetch(`/api/admin/users/${userId}/relationship`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -245,9 +237,7 @@
|
||||
async function fetchGiftCardBalance() {
|
||||
if (!userId) return;
|
||||
try {
|
||||
const res = await fetch(`/api/admin/users/${userId}/giftcard-balance`, {
|
||||
headers: { Authorization: `Bearer ${authStore.currentToken}` }
|
||||
});
|
||||
const res = await apiFetch(`/api/admin/users/${userId}/giftcard-balance`);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
giftCardBalance = data.balance;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { apiFetch } from '$lib/utils/api';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
@@ -60,11 +60,9 @@
|
||||
params.append('q', search.trim());
|
||||
}
|
||||
|
||||
const response = await fetch(`/api/admin/users?${params}`, {
|
||||
method: 'GET',
|
||||
const response = await apiFetch(`/api/admin/users?${params}`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import WalkInCreateModal from '$lib/components/admin/WalkInCreateModal.svelte';
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { CalendarDate } from '@internationalized/date';
|
||||
import { apiFetch } from '$lib/utils/api';
|
||||
import { SvelteDate } from 'svelte/reactivity';
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
@@ -40,9 +40,7 @@
|
||||
|
||||
async function fetchShortestService() {
|
||||
try {
|
||||
const response = await fetch('/api/services', {
|
||||
headers: { Authorization: `Bearer ${authStore.currentToken}` }
|
||||
});
|
||||
const response = await apiFetch('/api/services');
|
||||
if (response.ok) {
|
||||
const services: Service[] = await response.json();
|
||||
const durations = services.map((s) => s.duration_minutes).filter((d) => d > 0);
|
||||
@@ -91,9 +89,8 @@
|
||||
clearInterval(window.__walkInCountdownInterval);
|
||||
}
|
||||
try {
|
||||
const res = await fetch('/api/admin/bookings/reserve', {
|
||||
method: 'DELETE',
|
||||
headers: { Authorization: `Bearer ${authStore.currentToken}` }
|
||||
const res = await apiFetch('/api/admin/bookings/reserve', {
|
||||
method: 'DELETE'
|
||||
});
|
||||
if (!res.ok && res.status !== 404) {
|
||||
console.warn('Failed to release walk-in reservation', idToRelease, res.status);
|
||||
@@ -185,9 +182,9 @@
|
||||
const [y, m, d] = londonDateStr.split('-').map(Number);
|
||||
const today = new CalendarDate(y, m, d);
|
||||
|
||||
const response = await fetch(`/api/scheduling/available-hours?start=${today}&end=${today}`, {
|
||||
headers: { Authorization: `Bearer ${authStore.currentToken}` }
|
||||
});
|
||||
const response = await apiFetch(
|
||||
`/api/scheduling/available-hours?start=${today}&end=${today}`
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
noSlotsToday = true;
|
||||
@@ -285,11 +282,10 @@
|
||||
const start = new SvelteDate(y, m - 1, d, hours, minutes, 0, 0);
|
||||
const startTimeISO = formatLocalDateTime(start);
|
||||
|
||||
const response = await fetch('/api/admin/bookings/reserve', {
|
||||
const response = await apiFetch('/api/admin/bookings/reserve', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
user_id: null,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { generateUUID } from '$lib/utils/uuid';
|
||||
import { apiFetch } from '$lib/utils/api';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { SvelteDate, SvelteURLSearchParams } from 'svelte/reactivity';
|
||||
import { isValidUKPhone, toE164UK } from '$lib/utils/phone';
|
||||
@@ -281,11 +281,8 @@
|
||||
async function fetchUsers() {
|
||||
loadingUsers = true;
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/admin/users?page=1&per_page=4&q=${encodeURIComponent(userQuery)}`,
|
||||
{
|
||||
headers: { Authorization: `Bearer ${authStore.currentToken}` }
|
||||
}
|
||||
const response = await apiFetch(
|
||||
`/api/admin/users?page=1&per_page=4&q=${encodeURIComponent(userQuery)}`
|
||||
);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
@@ -312,9 +309,7 @@
|
||||
if (selectedUserId) {
|
||||
url = `/api/services/eligible-for/${selectedUserId}`;
|
||||
}
|
||||
const response = await fetch(url, {
|
||||
headers: { Authorization: `Bearer ${authStore.currentToken}` }
|
||||
});
|
||||
const response = await apiFetch(url);
|
||||
if (response.ok) {
|
||||
services = await response.json();
|
||||
}
|
||||
@@ -356,9 +351,7 @@
|
||||
} else {
|
||||
params.set('popular', '3');
|
||||
}
|
||||
const response = await fetch(`/api/admin/custom-services?${params}`, {
|
||||
headers: { Authorization: `Bearer ${authStore.currentToken}` }
|
||||
});
|
||||
const response = await apiFetch(`/api/admin/custom-services?${params}`);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
const list = data.services || data;
|
||||
@@ -379,11 +372,10 @@
|
||||
}
|
||||
creatingCustomService = true;
|
||||
try {
|
||||
const response = await fetch('/api/admin/custom-services', {
|
||||
const response = await apiFetch('/api/admin/custom-services', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: (newCustomService.name ?? '').trim(),
|
||||
@@ -454,11 +446,10 @@
|
||||
return;
|
||||
}
|
||||
const phone = toE164UK(guestPhone)!;
|
||||
const createRes = await fetch('/api/users/guest', {
|
||||
const createRes = await apiFetch('/api/users/guest', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
firstName: guestName.trim().split(' ')[0] || 'Walk-in',
|
||||
@@ -549,12 +540,11 @@
|
||||
notes: notes.trim() || null
|
||||
};
|
||||
|
||||
const res = await fetch('/api/admin/bookings', {
|
||||
const res = await apiFetch('/api/admin/bookings', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Idempotency-Key': idempotencyKey,
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Idempotency-Key': idempotencyKey
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { browser } from '$app/environment';
|
||||
import { apiFetch } from '$lib/utils/api';
|
||||
|
||||
// shadcn-svelte components
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
@@ -109,11 +109,9 @@
|
||||
let error = null;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/scheduling/default-hours', {
|
||||
method: 'GET',
|
||||
const response = await apiFetch('/api/scheduling/default-hours', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -168,11 +166,10 @@
|
||||
isOpen: hour.is_open
|
||||
}));
|
||||
|
||||
const response = await fetch('/api/scheduling/default-hours', {
|
||||
const response = await apiFetch('/api/scheduling/default-hours', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user