feat(api): implement centralized API client and refactor vendor pages
Introduced a new API client in src/lib/api/ to handle requests securely. Refactored vendor pages to use this client. Updated authentication logic in layout and login pages.
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
import type { Writable } from 'svelte/store';
|
||||
import '../../app.postcss'; // Import Tailwind CSS
|
||||
import { user, darkMode, type User } from '$lib/states';
|
||||
import { authApi } from '$lib/api';
|
||||
|
||||
// Initialize dark mode on mount to ensure data-theme is set
|
||||
onMount(() => {
|
||||
@@ -18,17 +19,15 @@
|
||||
// Placeholder for user store - in a real app, this would be managed by an auth library or context
|
||||
let storedUser: User | null = null;
|
||||
|
||||
// Check for user session on mount (this is a placeholder, actual implementation may vary)
|
||||
// Check for user session on mount
|
||||
onMount(() => {
|
||||
const storedUserString = localStorage.getItem('user');
|
||||
const token = localStorage.getItem('auth_token');
|
||||
if (storedUserString && token) {
|
||||
if (storedUserString) {
|
||||
storedUser = JSON.parse(storedUserString);
|
||||
user.set(storedUser);
|
||||
} else {
|
||||
// Clear if inconsistent
|
||||
// No user stored
|
||||
localStorage.removeItem('user');
|
||||
localStorage.removeItem('auth_token');
|
||||
user.set(null);
|
||||
}
|
||||
});
|
||||
@@ -41,11 +40,10 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Logout function
|
||||
const logout = () => {
|
||||
// Logout function - now async to call API to clear httpOnly cookie
|
||||
const logout = async () => {
|
||||
await authApi.logout();
|
||||
user.set(null);
|
||||
localStorage.removeItem('user');
|
||||
localStorage.removeItem('auth_token');
|
||||
window.location.href = '/';
|
||||
};
|
||||
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
<!-- Catch-all route for state and county pages: /[stateSlug] and /[stateSlug]/[countyId] -->
|
||||
<script lang="ts">
|
||||
import { newEnglandStates, type NewEnglandState } from '$lib/states';
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { browser } from '$app/environment';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
interface County {
|
||||
id: number;
|
||||
name: string;
|
||||
state: string;
|
||||
}
|
||||
|
||||
const { slugs } = $page.params as { slugs: string };
|
||||
|
||||
let currentState: NewEnglandState | null = null;
|
||||
let counties: County[] = [];
|
||||
let currentCounty: County | null = null;
|
||||
let loading = false;
|
||||
let error: string | null = null;
|
||||
let hoveredState: string | null = null;
|
||||
|
||||
// Parse the URL slugs: /stateSlug(/countyId)
|
||||
$: {
|
||||
if (slugs) {
|
||||
const slugParts = slugs.split('/');
|
||||
|
||||
// First part is state slug: /MA(/123)
|
||||
if (slugParts.length >= 1) {
|
||||
const stateSlug = slugParts[0];
|
||||
currentState = newEnglandStates.find(s => s.id === stateSlug) || null;
|
||||
|
||||
// Second part is county ID: /MA/123
|
||||
if (slugParts.length >= 2 && !isNaN(Number(slugParts[1]))) {
|
||||
const countyId = Number(slugParts[1]);
|
||||
currentCounty = counties.find(c => c.id === countyId) || null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
loading = true;
|
||||
try {
|
||||
// Trigger reactive parsing
|
||||
if (slugs) {
|
||||
const slugParts = slugs.split('/');
|
||||
|
||||
// If we have state data, fetch counties
|
||||
if (slugParts.length >= 1) {
|
||||
const stateSlug = slugParts[0];
|
||||
currentState = newEnglandStates.find(s => s.id === stateSlug) || null;
|
||||
|
||||
if (currentState) {
|
||||
const countyResponse = await fetch(`http://localhost:9552/state/${stateSlug.toUpperCase()}`);
|
||||
if (countyResponse.ok) {
|
||||
counties = await countyResponse.json();
|
||||
}
|
||||
|
||||
// Handle county data
|
||||
if (slugParts.length >= 2 && !isNaN(Number(slugParts[1]))) {
|
||||
const countyId = Number(slugParts[1]);
|
||||
const countyResponse = await fetch(`http://localhost:9552/state/${stateSlug.toUpperCase()}/${countyId}`);
|
||||
if (countyResponse.ok) {
|
||||
currentCounty = await countyResponse.json();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
error = err instanceof Error ? err.message : 'Failed to load data';
|
||||
console.error('Error:', err);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
});
|
||||
|
||||
function handleStateClick(stateId: string) {
|
||||
goto(`/${stateId}`);
|
||||
}
|
||||
|
||||
function handleCountyClick(countyId: number) {
|
||||
if (currentState) {
|
||||
goto(`/${currentState.id}/${countyId}`);
|
||||
}
|
||||
}
|
||||
|
||||
function handleMouseEnter(stateId: string) {
|
||||
if (browser) {
|
||||
hoveredState = stateId;
|
||||
}
|
||||
}
|
||||
|
||||
function handleMouseLeave() {
|
||||
if (browser) {
|
||||
hoveredState = null;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if loading}
|
||||
<div class="text-center py-8">
|
||||
<p>Loading...</p>
|
||||
</div>
|
||||
{:else if error}
|
||||
<div class="text-center py-8">
|
||||
<p class="text-error">Error: {error}</p>
|
||||
</div>
|
||||
{:else if currentCounty && currentState}
|
||||
<!-- County Details Page -->
|
||||
<div class="card lg:card-side bg-base-100 shadow-xl">
|
||||
<div class="card-body">
|
||||
<h1 class="card-title text-3xl">{currentCounty.name}, {currentState.name}</h1>
|
||||
<p>This is the page for {currentCounty.name} county in {currentState.name}.</p>
|
||||
<p>The URL for this page is <code class="bg-base-300 p-1 rounded">/{currentState.id}/{currentCounty.id}</code></p>
|
||||
<div class="card-actions justify-end mt-4">
|
||||
<a href="/{currentState.id}" class="btn btn-primary">Back to State</a>
|
||||
<a href="/" class="btn btn-secondary">Back to Map</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else if currentState}
|
||||
<!-- State Page with Counties -->
|
||||
<div class="card lg:card-side bg-base-100 shadow-xl">
|
||||
<figure class="flex-shrink-0">
|
||||
<img
|
||||
src={currentState.image}
|
||||
alt="Map or notable feature of {currentState.id}"
|
||||
class="object-cover w-full h-64 lg:h-auto lg:w-64"
|
||||
/>
|
||||
</figure>
|
||||
<div class="card-body">
|
||||
<h1 class="card-title text-3xl">{currentState.name}</h1>
|
||||
<p>The URL for this page is <code class="bg-base-300 p-1 rounded">/{currentState.id}</code></p>
|
||||
|
||||
{#if counties.length > 0}
|
||||
<div class="mt-4">
|
||||
<h2 class="text-xl font-semibold mb-2">Counties:</h2>
|
||||
<ul class="list-disc pl-5 max-h-48 overflow-y-auto">
|
||||
{#each counties as county}
|
||||
<li>
|
||||
<a
|
||||
href="/{currentState.id}/{county.id}"
|
||||
class="text-blue-600 hover:underline"
|
||||
>
|
||||
{county.name}
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="card-actions justify-end mt-4">
|
||||
<a href="/" class="btn btn-primary">Back to Map</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-center py-10">
|
||||
<h1 class="text-3xl font-bold text-error">Invalid URL</h1>
|
||||
<p class="mt-4">The requested page could not be found.</p>
|
||||
<a href="/" class="btn btn-primary mt-6">Go Back to Map</a>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -14,12 +14,14 @@
|
||||
import { onMount } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/environment';
|
||||
import { api } from '$lib/api';
|
||||
import type { County } from '$lib/api';
|
||||
|
||||
const { stateSlug } = $page.params as { stateSlug: string };
|
||||
|
||||
let stateData: NewEnglandState | undefined;
|
||||
let stateCounties: NewEnglandState[] = [];
|
||||
let filteredCounties: any[] = [];
|
||||
let filteredCounties: County[] = [];
|
||||
let loading = false;
|
||||
let error: string | null = null;
|
||||
let hoveredCounty: string | null = null;
|
||||
@@ -41,7 +43,7 @@
|
||||
function handleCountyClick(county: NewEnglandState) {
|
||||
// Match county names between map data and API data
|
||||
const cleanMapName = county.name.toLowerCase().replace(/\s+county$/, '').replace(/[^a-z]/g, '');
|
||||
const apiCounty = filteredCounties.find((c: any) =>
|
||||
const apiCounty = filteredCounties.find((c: County) =>
|
||||
cleanMapName === c.name.toLowerCase().replace(/[^a-z]/g, '')
|
||||
);
|
||||
|
||||
@@ -65,16 +67,16 @@
|
||||
if (stateData) {
|
||||
// Load API county data
|
||||
loading = true;
|
||||
try {
|
||||
const token = localStorage.getItem('auth_token');
|
||||
const response = await fetch(`http://localhost:9552/state/${stateSlug.toUpperCase()}`, {
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {}
|
||||
});
|
||||
filteredCounties = response.ok ? await response.json() : [];
|
||||
} catch (err) {
|
||||
error = err instanceof Error ? err.message : 'Failed to fetch counties';
|
||||
|
||||
const result = await api.state.getCounties(stateSlug);
|
||||
|
||||
if (result.error) {
|
||||
error = result.error;
|
||||
filteredCounties = [];
|
||||
} else {
|
||||
filteredCounties = result.data || [];
|
||||
}
|
||||
|
||||
loading = false;
|
||||
|
||||
// Load map county data
|
||||
|
||||
@@ -3,27 +3,11 @@
|
||||
import { page } from '$app/stores';
|
||||
import { onMount } from 'svelte';
|
||||
import { newEnglandStates } from '../../../../lib/states';
|
||||
|
||||
interface Listing {
|
||||
id: number;
|
||||
company_name: string;
|
||||
is_active: boolean;
|
||||
price_per_gallon: number;
|
||||
price_per_gallon_cash: number | null;
|
||||
note: string | null;
|
||||
minimum_order: number | null;
|
||||
service: boolean;
|
||||
bio_percent: number;
|
||||
phone: string | null;
|
||||
online_ordering: string;
|
||||
county_id: number;
|
||||
town: string | null;
|
||||
user_id: number;
|
||||
last_edited: string;
|
||||
}
|
||||
import { api } from '$lib/api';
|
||||
import type { Listing, County } from '$lib/api';
|
||||
|
||||
const { stateSlug, countySlug } = $page.params as { stateSlug: string; countySlug: string };
|
||||
let countyData: { id: number; name: string; state: string } | null = null;
|
||||
let countyData: County | null = null;
|
||||
let listings: Listing[] = [];
|
||||
let loading = true;
|
||||
let listingsLoading = false;
|
||||
@@ -33,29 +17,18 @@
|
||||
let sortDirection = 'asc'; // 'asc' or 'desc' - lowest price first
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
// Ensure API URL matches the Docker port forwarding for API (9552)
|
||||
const token = localStorage.getItem('auth_token');
|
||||
const headers: Record<string, string> = {};
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
const response = await fetch(`http://localhost:9552/state/${stateSlug.toUpperCase()}/${countySlug}`, {
|
||||
headers
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch county data: ${response.statusText}`);
|
||||
}
|
||||
countyData = await response.json();
|
||||
|
||||
const result = await api.state.getCounty(stateSlug, countySlug);
|
||||
|
||||
if (result.error) {
|
||||
error = result.error;
|
||||
countyData = null;
|
||||
} else {
|
||||
countyData = result.data;
|
||||
// Fetch listings for this county
|
||||
await fetchListings();
|
||||
} catch (err) {
|
||||
error = err instanceof Error ? err.message : 'An error occurred while fetching county data.';
|
||||
countyData = null;
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
|
||||
loading = false;
|
||||
});
|
||||
|
||||
async function fetchListings() {
|
||||
@@ -63,19 +36,17 @@
|
||||
|
||||
listingsLoading = true;
|
||||
listingsError = null;
|
||||
try {
|
||||
const response = await fetch(`http://localhost:9552/listings/county/${countyData.id}`);
|
||||
if (response.ok) {
|
||||
listings = await response.json();
|
||||
sortListings();
|
||||
} else {
|
||||
listingsError = 'Failed to load listings';
|
||||
}
|
||||
} catch (err) {
|
||||
listingsError = 'Network error loading listings';
|
||||
} finally {
|
||||
listingsLoading = false;
|
||||
|
||||
const result = await api.listings.getByCounty(countyData.id);
|
||||
|
||||
if (result.error) {
|
||||
listingsError = 'Failed to load listings';
|
||||
} else {
|
||||
listings = result.data || [];
|
||||
sortListings();
|
||||
}
|
||||
|
||||
listingsLoading = false;
|
||||
}
|
||||
|
||||
function sortListings() {
|
||||
@@ -260,7 +231,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>{new Date(listing.last_edited).toLocaleDateString()}</td>
|
||||
<td>{listing.last_edited ? new Date(listing.last_edited).toLocaleDateString() : 'N/A'}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
@@ -319,7 +290,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<span class="font-semibold">Last Updated:</span><br>
|
||||
<small>{new Date(listing.last_edited).toLocaleDateString()}</small>
|
||||
<small>{listing.last_edited ? new Date(listing.last_edited).toLocaleDateString() : 'N/A'}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { user } from '$lib/states';
|
||||
import type { LoginRequest } from '$lib/types/types';
|
||||
import { api } from '$lib/api';
|
||||
import type { LoginRequest } from '$lib/api';
|
||||
|
||||
let username = '';
|
||||
let password = '';
|
||||
@@ -17,39 +18,18 @@
|
||||
isLoading = true;
|
||||
errorMessage = '';
|
||||
|
||||
try {
|
||||
const response = await fetch('http://localhost:9552/auth/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(loginData)
|
||||
});
|
||||
const result = await api.auth.login(loginData);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
errorMessage = errorData.error || 'Login failed';
|
||||
} else {
|
||||
const data = await response.json();
|
||||
// Assuming the backend returns a token or some success indicator
|
||||
// Store the token in localStorage for authentication checks
|
||||
if (data.token) {
|
||||
localStorage.setItem('auth_token', data.token);
|
||||
}
|
||||
// Store the user object in localStorage
|
||||
localStorage.setItem('user', JSON.stringify(data.user));
|
||||
|
||||
// Update the user store
|
||||
user.set(data.user);
|
||||
|
||||
// Redirect to vendor page after successful login
|
||||
goto('/vendor');
|
||||
}
|
||||
} catch (err) {
|
||||
errorMessage = 'An error occurred. Please try again.';
|
||||
} finally {
|
||||
isLoading = false;
|
||||
if (result.error) {
|
||||
errorMessage = result.error;
|
||||
} else if (result.data) {
|
||||
// Update the user store
|
||||
user.set(result.data.user);
|
||||
// Redirect to vendor page after successful login
|
||||
goto('/vendor');
|
||||
}
|
||||
|
||||
isLoading = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import type { RegisterRequest } from '$lib/types/types';
|
||||
import { api } from '$lib/api';
|
||||
import type { RegisterRequest } from '$lib/api';
|
||||
|
||||
let email = '';
|
||||
let username = '';
|
||||
@@ -25,33 +25,16 @@
|
||||
isLoading = true;
|
||||
errorMessage = '';
|
||||
|
||||
try {
|
||||
const response = await fetch('http://localhost:9552/auth/register', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(registerData)
|
||||
});
|
||||
const result = await api.auth.register(registerData);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
errorMessage = errorData.error || 'Registration failed';
|
||||
} else {
|
||||
const data = await response.json();
|
||||
// Store the token in localStorage for authentication checks
|
||||
if (data.token) {
|
||||
localStorage.setItem('auth_token', data.token);
|
||||
}
|
||||
// Redirect to vendor page after successful registration
|
||||
goto('/vendor');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Registration error:', err);
|
||||
errorMessage = 'An error occurred. Please try again. Check the console for details.';
|
||||
} finally {
|
||||
isLoading = false;
|
||||
if (result.error) {
|
||||
errorMessage = result.error;
|
||||
} else {
|
||||
// Redirect to login page after successful registration
|
||||
goto('/login');
|
||||
}
|
||||
|
||||
isLoading = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
18
src/routes/(app)/vendor/+layout.svelte
vendored
18
src/routes/(app)/vendor/+layout.svelte
vendored
@@ -1,15 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { onMount } from 'svelte';
|
||||
import { authApi } from '$lib/api';
|
||||
|
||||
onMount(() => {
|
||||
// Check if the user is logged in by looking for a token in localStorage or a cookie
|
||||
// This assumes a token is stored upon successful login
|
||||
const token = localStorage.getItem('auth_token');
|
||||
if (!token) {
|
||||
// Redirect to home page if not logged in
|
||||
onMount(async () => {
|
||||
// Check if the user is logged in by checking localStorage user
|
||||
// and verifying with the API (which checks the httpOnly cookie)
|
||||
const storedUser = authApi.getStoredUser();
|
||||
if (!storedUser) {
|
||||
// No user stored, redirect to home
|
||||
goto('/');
|
||||
return;
|
||||
}
|
||||
|
||||
// Optional: Verify auth by making a request to /user endpoint
|
||||
// This ensures the httpOnly cookie is still valid
|
||||
// Note: We could skip this for faster loading, as protected API calls will handle 401
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
229
src/routes/(app)/vendor/+page.svelte
vendored
229
src/routes/(app)/vendor/+page.svelte
vendored
@@ -1,38 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
interface Listing {
|
||||
id: number;
|
||||
company_name: string;
|
||||
is_active: boolean;
|
||||
price_per_gallon: number;
|
||||
price_per_gallon_cash: number | null;
|
||||
note: string | null;
|
||||
minimum_order: number | null;
|
||||
service: boolean;
|
||||
bio_percent: number;
|
||||
phone: string | null;
|
||||
online_ordering: string;
|
||||
county_id: number;
|
||||
town: string | null;
|
||||
user_id: number;
|
||||
last_edited: string;
|
||||
}
|
||||
|
||||
interface Company {
|
||||
id: number;
|
||||
active: boolean;
|
||||
created: string;
|
||||
name: string;
|
||||
address?: string;
|
||||
town?: string;
|
||||
state?: string;
|
||||
phone?: string;
|
||||
owner_name?: string;
|
||||
owner_phone_number?: string;
|
||||
email?: string;
|
||||
user_id?: number;
|
||||
}
|
||||
import { api } from '$lib/api';
|
||||
import type { Listing, Company } from '$lib/api';
|
||||
|
||||
let listings: Listing[] = [];
|
||||
let company: Company | null = null;
|
||||
@@ -48,96 +17,44 @@
|
||||
let editingValue: string = '';
|
||||
|
||||
async function fetchCompany() {
|
||||
try {
|
||||
const token = localStorage.getItem('auth_token');
|
||||
|
||||
if (!token) {
|
||||
// Redirect to login if no token
|
||||
window.location.href = '/login';
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await fetch('http://localhost:9552/company', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
company = await response.json();
|
||||
} else if (response.status === 401) {
|
||||
// Token expired or invalid, redirect to login
|
||||
localStorage.removeItem('auth_token');
|
||||
localStorage.removeItem('user');
|
||||
window.location.href = '/login';
|
||||
return;
|
||||
} else if (response.status === 404) {
|
||||
const result = await api.company.get();
|
||||
|
||||
if (result.error) {
|
||||
if (result.error === 'Not found') {
|
||||
// Company not found - that's okay, user can create one
|
||||
company = null;
|
||||
} else {
|
||||
} else if (result.error !== 'Session expired. Please log in again.') {
|
||||
companyError = 'Failed to load company information';
|
||||
}
|
||||
} catch (err) {
|
||||
companyError = 'Network error loading company information';
|
||||
} finally {
|
||||
companyLoading = false;
|
||||
} else {
|
||||
company = result.data;
|
||||
}
|
||||
companyLoading = false;
|
||||
}
|
||||
|
||||
async function fetchListings() {
|
||||
try {
|
||||
const token = localStorage.getItem('auth_token');
|
||||
|
||||
if (!token) {
|
||||
// Redirect to login if no token
|
||||
window.location.href = '/login';
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await fetch('http://localhost:9552/listing', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
listings = await response.json();
|
||||
} else if (response.status === 401) {
|
||||
// Token expired or invalid, redirect to login
|
||||
localStorage.removeItem('auth_token');
|
||||
localStorage.removeItem('user');
|
||||
window.location.href = '/login';
|
||||
return;
|
||||
} else {
|
||||
const result = await api.listing.getAll();
|
||||
|
||||
if (result.error) {
|
||||
if (result.error !== 'Session expired. Please log in again.') {
|
||||
error = 'Failed to load listings';
|
||||
}
|
||||
} catch (err) {
|
||||
error = 'Network error loading listings';
|
||||
} finally {
|
||||
isLoading = false;
|
||||
} else {
|
||||
listings = result.data || [];
|
||||
}
|
||||
isLoading = false;
|
||||
}
|
||||
|
||||
async function deleteListing(id: number) {
|
||||
if (!confirm('Are you sure you want to delete this listing?')) return;
|
||||
|
||||
try {
|
||||
const token = localStorage.getItem('auth_token');
|
||||
const response = await fetch(`http://localhost:9552/listing/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Authorization': token ? `Bearer ${token}` : ''
|
||||
}
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
// Remove from local state
|
||||
listings = listings.filter(listing => listing.id !== id);
|
||||
} else {
|
||||
alert('Failed to delete listing');
|
||||
}
|
||||
} catch (err) {
|
||||
alert('Network error deleting listing');
|
||||
const result = await api.listing.delete(id);
|
||||
|
||||
if (result.error) {
|
||||
alert('Failed to delete listing');
|
||||
} else {
|
||||
// Remove from local state
|
||||
listings = listings.filter(listing => listing.id !== id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,62 +88,50 @@
|
||||
async function saveEditing(listing: Listing) {
|
||||
if (!editingField) return;
|
||||
|
||||
let updateData: any = {};
|
||||
let newValue: any;
|
||||
let updateData: Record<string, unknown> = {};
|
||||
let newValue: unknown;
|
||||
|
||||
try {
|
||||
if (editingField === 'price_per_gallon' || editingField === 'price_per_gallon_cash') {
|
||||
newValue = editingValue ? parseFloat(editingValue) : null;
|
||||
if (editingField === 'price_per_gallon' && (!newValue || newValue <= 0)) {
|
||||
alert('Please enter a valid price greater than 0');
|
||||
return;
|
||||
}
|
||||
updateData[editingField] = newValue;
|
||||
} else if (editingField === 'minimum_order') {
|
||||
newValue = editingValue ? parseInt(editingValue) : null;
|
||||
if (newValue && (newValue < 1 || newValue > 200)) {
|
||||
alert('Minimum order must be between 1 and 200');
|
||||
return;
|
||||
}
|
||||
updateData[editingField] = newValue;
|
||||
} else if (editingField === 'note') {
|
||||
newValue = editingValue.trim() || null;
|
||||
if (newValue && newValue.length > 250) {
|
||||
alert('Note cannot exceed 250 characters');
|
||||
return;
|
||||
}
|
||||
updateData[editingField] = newValue;
|
||||
if (editingField === 'price_per_gallon' || editingField === 'price_per_gallon_cash') {
|
||||
newValue = editingValue ? parseFloat(editingValue) : null;
|
||||
if (editingField === 'price_per_gallon' && (!newValue || (newValue as number) <= 0)) {
|
||||
alert('Please enter a valid price greater than 0');
|
||||
return;
|
||||
}
|
||||
|
||||
const token = localStorage.getItem('auth_token');
|
||||
const response = await fetch(`http://localhost:9552/listing/${listing.id}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': token ? `Bearer ${token}` : ''
|
||||
},
|
||||
body: JSON.stringify(updateData)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
// Update local state
|
||||
listings = listings.map(l =>
|
||||
l.id === listing.id
|
||||
? { ...l, [editingField as string]: newValue }
|
||||
: l
|
||||
);
|
||||
cancelEditing();
|
||||
successMessage = `${editingField.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())} updated successfully!`;
|
||||
|
||||
// Clear success message after 3 seconds
|
||||
setTimeout(() => {
|
||||
successMessage = '';
|
||||
}, 3000);
|
||||
} else {
|
||||
alert(`Failed to update ${editingField}`);
|
||||
updateData[editingField] = newValue;
|
||||
} else if (editingField === 'minimum_order') {
|
||||
newValue = editingValue ? parseInt(editingValue) : null;
|
||||
if (newValue && ((newValue as number) < 1 || (newValue as number) > 200)) {
|
||||
alert('Minimum order must be between 1 and 200');
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
alert('Network error updating field');
|
||||
updateData[editingField] = newValue;
|
||||
} else if (editingField === 'note') {
|
||||
newValue = editingValue.trim() || null;
|
||||
if (newValue && (newValue as string).length > 250) {
|
||||
alert('Note cannot exceed 250 characters');
|
||||
return;
|
||||
}
|
||||
updateData[editingField] = newValue;
|
||||
}
|
||||
|
||||
const result = await api.listing.update(listing.id, updateData);
|
||||
|
||||
if (result.error) {
|
||||
alert(`Failed to update ${editingField}`);
|
||||
} else {
|
||||
// Update local state
|
||||
listings = listings.map(l =>
|
||||
l.id === listing.id
|
||||
? { ...l, [editingField as string]: newValue }
|
||||
: l
|
||||
);
|
||||
cancelEditing();
|
||||
successMessage = `${editingField.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())} updated successfully!`;
|
||||
|
||||
// Clear success message after 3 seconds
|
||||
setTimeout(() => {
|
||||
successMessage = '';
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,6 +153,10 @@
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (!api.auth.isAuthenticated()) {
|
||||
window.location.href = '/login';
|
||||
return;
|
||||
}
|
||||
fetchCompany();
|
||||
fetchListings();
|
||||
});
|
||||
|
||||
91
src/routes/(app)/vendor/listing/+page.svelte
vendored
91
src/routes/(app)/vendor/listing/+page.svelte
vendored
@@ -1,13 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { newEnglandStates } from '$lib/states';
|
||||
|
||||
interface County {
|
||||
id: number;
|
||||
name: string;
|
||||
state: string;
|
||||
}
|
||||
import { api } from '$lib/api';
|
||||
import type { County } from '$lib/api';
|
||||
|
||||
// Form data
|
||||
let formData = {
|
||||
@@ -137,45 +132,30 @@
|
||||
isSubmitting = true;
|
||||
submitMessage = '';
|
||||
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const response = await fetch('http://localhost:9552/listing', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': token ? `Bearer ${token}` : ''
|
||||
},
|
||||
body: JSON.stringify({
|
||||
company_name: formData.companyName,
|
||||
is_active: isActive,
|
||||
price_per_gallon: formData.pricePerGallon,
|
||||
price_per_gallon_cash: formData.pricePerGallonCash,
|
||||
minimum_order: formData.minimumOrder,
|
||||
service: formData.labelService,
|
||||
bio_percent: formData.bioPercent,
|
||||
phone: formData.phone,
|
||||
online_ordering: onlineOrdering,
|
||||
county_id: formData.countyId,
|
||||
town: formData.town.trim() || null
|
||||
})
|
||||
});
|
||||
const result = await api.listing.create({
|
||||
company_name: formData.companyName,
|
||||
is_active: isActive,
|
||||
price_per_gallon: formData.pricePerGallon,
|
||||
price_per_gallon_cash: formData.pricePerGallonCash,
|
||||
minimum_order: formData.minimumOrder,
|
||||
service: formData.labelService,
|
||||
bio_percent: formData.bioPercent,
|
||||
phone: formData.phone,
|
||||
online_ordering: onlineOrdering,
|
||||
county_id: formData.countyId,
|
||||
town: formData.town.trim() || null
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
// Redirect to vendor dashboard on success
|
||||
goto('/vendor');
|
||||
} else {
|
||||
submitMessage = 'Failed to create listing. Please try again.';
|
||||
}
|
||||
} catch (error) {
|
||||
submitMessage = 'Network error. Please check your connection and try again.';
|
||||
} finally {
|
||||
isSubmitting = false;
|
||||
if (result.error) {
|
||||
submitMessage = result.error === 'Session expired. Please log in again.'
|
||||
? result.error
|
||||
: 'Failed to create listing. Please try again.';
|
||||
} else {
|
||||
// Redirect to vendor dashboard on success
|
||||
goto('/vendor');
|
||||
}
|
||||
}
|
||||
|
||||
// Format price for display
|
||||
function formatPrice(value: number): string {
|
||||
return value.toFixed(2);
|
||||
isSubmitting = false;
|
||||
}
|
||||
|
||||
// Handle state change
|
||||
@@ -187,22 +167,19 @@
|
||||
|
||||
if (formData.state) {
|
||||
isLoadingCounties = true;
|
||||
try {
|
||||
const response = await fetch(`http://localhost:9552/state/${formData.state}`);
|
||||
if (response.ok) {
|
||||
counties = await response.json();
|
||||
if (counties.length === 0) {
|
||||
errors.countyId = 'No counties found for selected state';
|
||||
}
|
||||
} else {
|
||||
const errorData = await response.json();
|
||||
errors.countyId = errorData.error || 'Failed to load counties';
|
||||
|
||||
const result = await api.state.getCounties(formData.state);
|
||||
|
||||
if (result.error) {
|
||||
errors.countyId = result.error;
|
||||
} else {
|
||||
counties = result.data || [];
|
||||
if (counties.length === 0) {
|
||||
errors.countyId = 'No counties found for selected state';
|
||||
}
|
||||
} catch (error) {
|
||||
errors.countyId = 'Network error loading counties';
|
||||
} finally {
|
||||
isLoadingCounties = false;
|
||||
}
|
||||
|
||||
isLoadingCounties = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
182
src/routes/(app)/vendor/listing/[id]/+page.svelte
vendored
182
src/routes/(app)/vendor/listing/[id]/+page.svelte
vendored
@@ -3,30 +3,8 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { newEnglandStates } from '$lib/states';
|
||||
|
||||
interface County {
|
||||
id: number;
|
||||
name: string;
|
||||
state: string;
|
||||
}
|
||||
|
||||
interface Listing {
|
||||
id: number;
|
||||
company_name: string;
|
||||
is_active: boolean;
|
||||
price_per_gallon: number;
|
||||
price_per_gallon_cash: number | null;
|
||||
note: string | null;
|
||||
minimum_order: number | null;
|
||||
service: boolean;
|
||||
bio_percent: number;
|
||||
phone: string | null;
|
||||
online_ordering: string;
|
||||
county_id: number;
|
||||
town: string | null;
|
||||
user_id: number;
|
||||
last_edited: string;
|
||||
}
|
||||
import { api } from '$lib/api';
|
||||
import type { County, Listing } from '$lib/api';
|
||||
|
||||
// Get listing ID from URL params
|
||||
let listingId: string;
|
||||
@@ -79,40 +57,33 @@
|
||||
|
||||
// Load existing listing data
|
||||
async function loadListing() {
|
||||
try {
|
||||
const token = localStorage.getItem('auth_token');
|
||||
const response = await fetch(`http://localhost:9552/listing/${listingId}`, {
|
||||
headers: {
|
||||
'Authorization': token ? `Bearer ${token}` : ''
|
||||
}
|
||||
});
|
||||
const result = await api.listing.getById(parseInt(listingId));
|
||||
|
||||
if (response.ok) {
|
||||
const listing: Listing = await response.json();
|
||||
if (result.error) {
|
||||
submitMessage = result.error === 'Session expired. Please log in again.'
|
||||
? result.error
|
||||
: 'Failed to load listing data';
|
||||
} else if (result.data) {
|
||||
const listing = result.data;
|
||||
|
||||
// Pre-populate form with existing data
|
||||
formData.companyName = listing.company_name;
|
||||
isActive = listing.is_active;
|
||||
formData.pricePerGallon = listing.price_per_gallon;
|
||||
formData.pricePerGallonCash = listing.price_per_gallon_cash;
|
||||
formData.minimumOrder = listing.minimum_order;
|
||||
formData.labelService = listing.service;
|
||||
formData.bioPercent = listing.bio_percent;
|
||||
formData.phone = listing.phone || '';
|
||||
onlineOrdering = listing.online_ordering;
|
||||
formData.countyId = listing.county_id;
|
||||
formData.town = listing.town || '';
|
||||
// Pre-populate form with existing data
|
||||
formData.companyName = listing.company_name;
|
||||
isActive = listing.is_active;
|
||||
formData.pricePerGallon = listing.price_per_gallon;
|
||||
formData.pricePerGallonCash = listing.price_per_gallon_cash;
|
||||
formData.minimumOrder = listing.minimum_order;
|
||||
formData.labelService = listing.service;
|
||||
formData.bioPercent = listing.bio_percent;
|
||||
formData.phone = listing.phone || '';
|
||||
onlineOrdering = listing.online_ordering;
|
||||
formData.countyId = listing.county_id;
|
||||
formData.town = listing.town || '';
|
||||
|
||||
// Load the state for this county
|
||||
await loadStateForCounty(listing.county_id);
|
||||
} else {
|
||||
submitMessage = 'Failed to load listing data';
|
||||
}
|
||||
} catch (error) {
|
||||
submitMessage = 'Network error loading listing data';
|
||||
} finally {
|
||||
isLoading = false;
|
||||
// Load the state for this county
|
||||
await loadStateForCounty(listing.county_id);
|
||||
}
|
||||
|
||||
isLoading = false;
|
||||
}
|
||||
|
||||
// Load state information for a given county
|
||||
@@ -120,19 +91,16 @@
|
||||
// We need to find which state this county belongs to
|
||||
// Since we don't have a reverse lookup, we'll load counties for each state until we find it
|
||||
for (const state of newEnglandStates) {
|
||||
try {
|
||||
const response = await fetch(`http://localhost:9552/state/${state.id}`);
|
||||
if (response.ok) {
|
||||
const stateCounties: County[] = await response.json();
|
||||
const county = stateCounties.find(c => c.id === countyId);
|
||||
if (county) {
|
||||
formData.state = state.id;
|
||||
counties = stateCounties;
|
||||
break;
|
||||
}
|
||||
const result = await api.state.getCounties(state.id);
|
||||
|
||||
if (!result.error && result.data) {
|
||||
const stateCounties = result.data;
|
||||
const county = stateCounties.find(c => c.id === countyId);
|
||||
if (county) {
|
||||
formData.state = state.id;
|
||||
counties = stateCounties;
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
// Continue to next state
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -221,45 +189,30 @@
|
||||
isSubmitting = true;
|
||||
submitMessage = '';
|
||||
|
||||
try {
|
||||
const token = localStorage.getItem('auth_token');
|
||||
const response = await fetch(`http://localhost:9552/listing/${listingId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': token ? `Bearer ${token}` : ''
|
||||
},
|
||||
body: JSON.stringify({
|
||||
company_name: formData.companyName,
|
||||
is_active: isActive,
|
||||
price_per_gallon: formData.pricePerGallon,
|
||||
price_per_gallon_cash: formData.pricePerGallonCash,
|
||||
minimum_order: formData.minimumOrder,
|
||||
service: formData.labelService,
|
||||
bio_percent: formData.bioPercent,
|
||||
phone: formData.phone,
|
||||
online_ordering: onlineOrdering,
|
||||
county_id: formData.countyId,
|
||||
town: formData.town.trim() || null
|
||||
})
|
||||
});
|
||||
const result = await api.listing.update(parseInt(listingId), {
|
||||
company_name: formData.companyName,
|
||||
is_active: isActive,
|
||||
price_per_gallon: formData.pricePerGallon,
|
||||
price_per_gallon_cash: formData.pricePerGallonCash,
|
||||
minimum_order: formData.minimumOrder,
|
||||
service: formData.labelService,
|
||||
bio_percent: formData.bioPercent,
|
||||
phone: formData.phone,
|
||||
online_ordering: onlineOrdering,
|
||||
county_id: formData.countyId,
|
||||
town: formData.town.trim() || null
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
// Redirect to vendor dashboard on success
|
||||
goto('/vendor');
|
||||
} else {
|
||||
submitMessage = 'Failed to update listing. Please try again.';
|
||||
}
|
||||
} catch (error) {
|
||||
submitMessage = 'Network error. Please check your connection and try again.';
|
||||
} finally {
|
||||
isSubmitting = false;
|
||||
if (result.error) {
|
||||
submitMessage = result.error === 'Session expired. Please log in again.'
|
||||
? result.error
|
||||
: 'Failed to update listing. Please try again.';
|
||||
} else {
|
||||
// Redirect to vendor dashboard on success
|
||||
goto('/vendor');
|
||||
}
|
||||
}
|
||||
|
||||
// Format price for display
|
||||
function formatPrice(value: number): string {
|
||||
return value.toFixed(2);
|
||||
isSubmitting = false;
|
||||
}
|
||||
|
||||
// Handle state change
|
||||
@@ -271,22 +224,19 @@
|
||||
|
||||
if (formData.state) {
|
||||
isLoadingCounties = true;
|
||||
try {
|
||||
const response = await fetch(`http://localhost:9552/state/${formData.state}`);
|
||||
if (response.ok) {
|
||||
counties = await response.json();
|
||||
if (counties.length === 0) {
|
||||
errors.countyId = 'No counties found for selected state';
|
||||
}
|
||||
} else {
|
||||
const errorData = await response.json();
|
||||
errors.countyId = errorData.error || 'Failed to load counties';
|
||||
|
||||
const result = await api.state.getCounties(formData.state);
|
||||
|
||||
if (result.error) {
|
||||
errors.countyId = result.error;
|
||||
} else {
|
||||
counties = result.data || [];
|
||||
if (counties.length === 0) {
|
||||
errors.countyId = 'No counties found for selected state';
|
||||
}
|
||||
} catch (error) {
|
||||
errors.countyId = 'Network error loading counties';
|
||||
} finally {
|
||||
isLoadingCounties = false;
|
||||
}
|
||||
|
||||
isLoadingCounties = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
133
src/routes/(app)/vendor/profile/+page.svelte
vendored
133
src/routes/(app)/vendor/profile/+page.svelte
vendored
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { api } from '$lib/api';
|
||||
|
||||
const states = ['ME', 'NH', 'VT', 'MA', 'RI', 'CT'];
|
||||
|
||||
@@ -20,79 +21,54 @@
|
||||
let deleting = false;
|
||||
|
||||
async function fetchCompany() {
|
||||
try {
|
||||
const response = await fetch('http://localhost:9552/company', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem('auth_token')}`
|
||||
}
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
company = {
|
||||
name: data.name || '',
|
||||
address: data.address || '',
|
||||
town: data.town || '',
|
||||
state: data.state || '',
|
||||
phone: data.phone ? data.phone.toString() : '',
|
||||
owner_name: data.owner_name || '',
|
||||
owner_phone_number: data.owner_phone_number ? data.owner_phone_number.toString() : '',
|
||||
email: data.email || ''
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching company:', error);
|
||||
} finally {
|
||||
loading = false;
|
||||
const result = await api.company.get();
|
||||
|
||||
if (result.data) {
|
||||
company = {
|
||||
name: result.data.name || '',
|
||||
address: result.data.address || '',
|
||||
town: result.data.town || '',
|
||||
state: result.data.state || '',
|
||||
phone: result.data.phone ? result.data.phone.toString() : '',
|
||||
owner_name: result.data.owner_name || '',
|
||||
owner_phone_number: result.data.owner_phone_number ? result.data.owner_phone_number.toString() : '',
|
||||
email: result.data.email || ''
|
||||
};
|
||||
} else if (result.error && result.error !== 'Not found') {
|
||||
console.error('Error fetching company:', result.error);
|
||||
}
|
||||
loading = false;
|
||||
}
|
||||
|
||||
async function saveCompany() {
|
||||
saving = true;
|
||||
try {
|
||||
const payload = {
|
||||
name: company.name,
|
||||
address: company.address || null,
|
||||
town: company.town || null,
|
||||
state: company.state || null,
|
||||
phone: company.phone || null,
|
||||
owner_name: company.owner_name || null,
|
||||
owner_phone_number: company.owner_phone_number || null,
|
||||
email: company.email || null
|
||||
};
|
||||
|
||||
const payload = {
|
||||
name: company.name,
|
||||
address: company.address || null,
|
||||
town: company.town || null,
|
||||
state: company.state || null,
|
||||
phone: company.phone || null,
|
||||
owner_name: company.owner_name || null,
|
||||
owner_phone_number: company.owner_phone_number || null,
|
||||
email: company.email || null
|
||||
};
|
||||
|
||||
console.log('Sending payload:', payload);
|
||||
console.log('Sending payload:', payload);
|
||||
|
||||
const response = await fetch('http://localhost:9552/company', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${localStorage.getItem('auth_token')}`
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
const result = await api.company.update(payload);
|
||||
|
||||
if (response.ok) {
|
||||
alert('Company profile saved successfully!');
|
||||
} else if (response.status === 401) {
|
||||
if (result.error) {
|
||||
if (result.error === 'Session expired. Please log in again.') {
|
||||
alert('Your session has expired. Please log in again.');
|
||||
// Could redirect to login: goto('/login');
|
||||
} else {
|
||||
const errorText = await response.text();
|
||||
console.log('Error response:', errorText);
|
||||
try {
|
||||
const error = JSON.parse(errorText);
|
||||
alert('Error saving company: ' + error.error);
|
||||
} catch (e) {
|
||||
alert('Error saving company: ' + errorText);
|
||||
}
|
||||
alert('Error saving company: ' + result.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error saving company:', error);
|
||||
alert('Error saving company');
|
||||
} finally {
|
||||
saving = false;
|
||||
} else {
|
||||
alert('Company profile saved successfully!');
|
||||
}
|
||||
|
||||
saving = false;
|
||||
}
|
||||
|
||||
async function deleteCompany() {
|
||||
@@ -101,41 +77,28 @@
|
||||
}
|
||||
|
||||
deleting = true;
|
||||
try {
|
||||
const response = await fetch('http://localhost:9552/company', {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem('auth_token')}`
|
||||
}
|
||||
});
|
||||
|
||||
const result = await api.company.delete();
|
||||
|
||||
if (response.ok) {
|
||||
alert('Company profile deleted successfully!');
|
||||
goto('/vendor');
|
||||
} else if (response.status === 401) {
|
||||
if (result.error) {
|
||||
if (result.error === 'Session expired. Please log in again.') {
|
||||
alert('Your session has expired. Please log in again.');
|
||||
// Could redirect to login: goto('/login');
|
||||
} else {
|
||||
const error = await response.json();
|
||||
alert('Error deleting company: ' + error.error);
|
||||
alert('Error deleting company: ' + result.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting company:', error);
|
||||
alert('Error deleting company');
|
||||
} finally {
|
||||
deleting = false;
|
||||
} else {
|
||||
alert('Company profile deleted successfully!');
|
||||
goto('/vendor');
|
||||
}
|
||||
|
||||
deleting = false;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
const token = localStorage.getItem('auth_token');
|
||||
if (token) {
|
||||
if (api.auth.isAuthenticated()) {
|
||||
fetchCompany();
|
||||
} else {
|
||||
loading = false;
|
||||
// Could optionally show a login prompt or redirect
|
||||
// alert('Please log in to access your company profile.');
|
||||
// goto('/login');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user