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:
309
src/lib/api/client.ts
Normal file
309
src/lib/api/client.ts
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
import { PUBLIC_API_URL } from '$env/static/public';
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
import type {
|
||||||
|
ApiResponse,
|
||||||
|
LoginRequest,
|
||||||
|
LoginResponse,
|
||||||
|
RegisterRequest,
|
||||||
|
User,
|
||||||
|
Company,
|
||||||
|
CreateCompanyRequest,
|
||||||
|
UpdateCompanyRequest,
|
||||||
|
Listing,
|
||||||
|
CreateListingRequest,
|
||||||
|
UpdateListingRequest,
|
||||||
|
County,
|
||||||
|
ServiceCategory
|
||||||
|
} from './types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle 401 unauthorized responses - clear user data and redirect to login
|
||||||
|
* Note: We only clear localStorage user data, the httpOnly cookie is cleared server-side
|
||||||
|
*/
|
||||||
|
function handleUnauthorized(): void {
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
localStorage.removeItem('user');
|
||||||
|
goto('/login');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Core fetch wrapper with auth and error handling
|
||||||
|
* Uses credentials: 'include' for cookie-based authentication
|
||||||
|
*/
|
||||||
|
async function request<T>(
|
||||||
|
endpoint: string,
|
||||||
|
options: RequestInit = {},
|
||||||
|
requiresAuth: boolean = false
|
||||||
|
): Promise<ApiResponse<T>> {
|
||||||
|
const headers: HeadersInit = {
|
||||||
|
...options.headers,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add Content-Type for JSON body
|
||||||
|
if (options.body && typeof options.body === 'string') {
|
||||||
|
(headers as Record<string, string>)['Content-Type'] = 'application/json';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we have a user in localStorage for auth-required requests
|
||||||
|
if (requiresAuth) {
|
||||||
|
const user = authApi.getStoredUser();
|
||||||
|
if (!user) {
|
||||||
|
handleUnauthorized();
|
||||||
|
return { data: null, error: 'Not authenticated' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${PUBLIC_API_URL}${endpoint}`, {
|
||||||
|
...options,
|
||||||
|
headers,
|
||||||
|
// Include credentials (cookies) in all requests for httpOnly cookie auth
|
||||||
|
credentials: 'include',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle 401 Unauthorized
|
||||||
|
if (response.status === 401) {
|
||||||
|
handleUnauthorized();
|
||||||
|
return { data: null, error: 'Session expired. Please log in again.' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle 404 Not Found - return null data without error for some cases
|
||||||
|
if (response.status === 404) {
|
||||||
|
return { data: null, error: 'Not found' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle other error responses
|
||||||
|
if (!response.ok) {
|
||||||
|
try {
|
||||||
|
const errorData = await response.json();
|
||||||
|
return { data: null, error: errorData.error || `Request failed with status ${response.status}` };
|
||||||
|
} catch {
|
||||||
|
return { data: null, error: `Request failed with status ${response.status}` };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle empty responses (204 No Content, DELETE responses, etc.)
|
||||||
|
const contentType = response.headers.get('content-type');
|
||||||
|
if (!contentType || !contentType.includes('application/json')) {
|
||||||
|
return { data: null as T, error: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return { data, error: null };
|
||||||
|
} catch (err) {
|
||||||
|
const message = err instanceof Error ? err.message : 'Network error';
|
||||||
|
return { data: null, error: message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auth API methods
|
||||||
|
*/
|
||||||
|
export const authApi = {
|
||||||
|
/**
|
||||||
|
* Login with username and password
|
||||||
|
* Server sets JWT as httpOnly cookie, we store user info in localStorage
|
||||||
|
*/
|
||||||
|
async login(credentials: LoginRequest): Promise<ApiResponse<LoginResponse>> {
|
||||||
|
const result = await request<LoginResponse>('/auth/login', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(credentials),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Store user on successful login (token is now in httpOnly cookie)
|
||||||
|
if (result.data) {
|
||||||
|
localStorage.setItem('user', JSON.stringify(result.data.user));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a new user
|
||||||
|
*/
|
||||||
|
async register(data: RegisterRequest): Promise<ApiResponse<{ message: string }>> {
|
||||||
|
return request('/auth/register', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logout - calls server to clear httpOnly cookie and clears local storage
|
||||||
|
*/
|
||||||
|
async logout(): Promise<void> {
|
||||||
|
// Call server to clear the httpOnly cookie
|
||||||
|
await request('/auth/logout', {
|
||||||
|
method: 'POST',
|
||||||
|
});
|
||||||
|
// Clear local storage
|
||||||
|
localStorage.removeItem('user');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the currently logged-in user from localStorage
|
||||||
|
*/
|
||||||
|
getStoredUser(): User | null {
|
||||||
|
if (typeof window === 'undefined') return null;
|
||||||
|
const userStr = localStorage.getItem('user');
|
||||||
|
if (!userStr) return null;
|
||||||
|
try {
|
||||||
|
return JSON.parse(userStr);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if user is authenticated
|
||||||
|
* Since httpOnly cookies can't be read via JS, we check localStorage user presence
|
||||||
|
* The actual auth is verified server-side when making requests
|
||||||
|
*/
|
||||||
|
isAuthenticated(): boolean {
|
||||||
|
return !!this.getStoredUser();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Company API methods
|
||||||
|
*/
|
||||||
|
export const companyApi = {
|
||||||
|
/**
|
||||||
|
* Get the current user's company
|
||||||
|
*/
|
||||||
|
async get(): Promise<ApiResponse<Company>> {
|
||||||
|
return request<Company>('/company', {}, true);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new company
|
||||||
|
*/
|
||||||
|
async create(data: CreateCompanyRequest): Promise<ApiResponse<Company>> {
|
||||||
|
return request<Company>('/company', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
}, true);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the current user's company
|
||||||
|
*/
|
||||||
|
async update(data: UpdateCompanyRequest): Promise<ApiResponse<Company>> {
|
||||||
|
return request<Company>('/company', {
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
}, true);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete (soft-delete) the current user's company
|
||||||
|
*/
|
||||||
|
async delete(): Promise<ApiResponse<null>> {
|
||||||
|
return request<null>('/company', {
|
||||||
|
method: 'DELETE',
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listing API methods (for authenticated user's listings)
|
||||||
|
*/
|
||||||
|
export const listingApi = {
|
||||||
|
/**
|
||||||
|
* Get all listings for the current user
|
||||||
|
*/
|
||||||
|
async getAll(): Promise<ApiResponse<Listing[]>> {
|
||||||
|
return request<Listing[]>('/listing', {}, true);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a specific listing by ID
|
||||||
|
*/
|
||||||
|
async getById(id: number): Promise<ApiResponse<Listing>> {
|
||||||
|
return request<Listing>(`/listing/${id}`, {}, true);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new listing
|
||||||
|
*/
|
||||||
|
async create(data: CreateListingRequest): Promise<ApiResponse<Listing>> {
|
||||||
|
return request<Listing>('/listing', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
}, true);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a listing
|
||||||
|
*/
|
||||||
|
async update(id: number, data: UpdateListingRequest): Promise<ApiResponse<Listing>> {
|
||||||
|
return request<Listing>(`/listing/${id}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
}, true);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a listing
|
||||||
|
*/
|
||||||
|
async delete(id: number): Promise<ApiResponse<null>> {
|
||||||
|
return request<null>(`/listing/${id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public listings API methods
|
||||||
|
*/
|
||||||
|
export const listingsApi = {
|
||||||
|
/**
|
||||||
|
* Get all listings for a county (public)
|
||||||
|
*/
|
||||||
|
async getByCounty(countyId: number): Promise<ApiResponse<Listing[]>> {
|
||||||
|
return request<Listing[]>(`/listings/county/${countyId}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* State/County API methods
|
||||||
|
*/
|
||||||
|
export const stateApi = {
|
||||||
|
/**
|
||||||
|
* Get all counties for a state
|
||||||
|
*/
|
||||||
|
async getCounties(stateAbbr: string): Promise<ApiResponse<County[]>> {
|
||||||
|
return request<County[]>(`/state/${stateAbbr.toUpperCase()}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a specific county by ID within a state
|
||||||
|
*/
|
||||||
|
async getCounty(stateAbbr: string, countyId: string | number): Promise<ApiResponse<County>> {
|
||||||
|
return request<County>(`/state/${stateAbbr.toUpperCase()}/${countyId}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Categories API methods
|
||||||
|
*/
|
||||||
|
export const categoriesApi = {
|
||||||
|
/**
|
||||||
|
* Get all service categories
|
||||||
|
*/
|
||||||
|
async getAll(): Promise<ApiResponse<ServiceCategory[]>> {
|
||||||
|
return request<ServiceCategory[]>('/categories');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unified API object for convenient imports
|
||||||
|
*/
|
||||||
|
export const api = {
|
||||||
|
auth: authApi,
|
||||||
|
company: companyApi,
|
||||||
|
listing: listingApi,
|
||||||
|
listings: listingsApi,
|
||||||
|
state: stateApi,
|
||||||
|
categories: categoriesApi
|
||||||
|
};
|
||||||
3
src/lib/api/index.ts
Normal file
3
src/lib/api/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// API Client exports
|
||||||
|
export { api, authApi, companyApi, listingApi, listingsApi, stateApi, categoriesApi } from './client';
|
||||||
|
export * from './types';
|
||||||
116
src/lib/api/types.ts
Normal file
116
src/lib/api/types.ts
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
// API Response Types
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic API response wrapper
|
||||||
|
* Success: { data: T, error: null }
|
||||||
|
* Error: { data: null, error: string }
|
||||||
|
*/
|
||||||
|
export type ApiResponse<T> =
|
||||||
|
| { data: T; error: null }
|
||||||
|
| { data: null; error: string };
|
||||||
|
|
||||||
|
// Auth Types
|
||||||
|
export interface LoginRequest {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RegisterRequest {
|
||||||
|
email: string;
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LoginResponse {
|
||||||
|
token: string;
|
||||||
|
user: User;
|
||||||
|
}
|
||||||
|
|
||||||
|
// User Types
|
||||||
|
export interface User {
|
||||||
|
id: number;
|
||||||
|
username: string;
|
||||||
|
email: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Company Types
|
||||||
|
export interface Company {
|
||||||
|
id: number;
|
||||||
|
active: boolean;
|
||||||
|
created: string;
|
||||||
|
name: string;
|
||||||
|
address?: string | null;
|
||||||
|
town?: string | null;
|
||||||
|
state?: string | null;
|
||||||
|
phone?: string | null;
|
||||||
|
owner_name?: string | null;
|
||||||
|
owner_phone_number?: string | null;
|
||||||
|
email?: string | null;
|
||||||
|
user_id?: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateCompanyRequest {
|
||||||
|
name: string;
|
||||||
|
address?: string | null;
|
||||||
|
town?: string | null;
|
||||||
|
state?: string | null;
|
||||||
|
phone?: string | null;
|
||||||
|
owner_name?: string | null;
|
||||||
|
owner_phone_number?: string | null;
|
||||||
|
email?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UpdateCompanyRequest = Partial<CreateCompanyRequest>;
|
||||||
|
|
||||||
|
// Listing Types
|
||||||
|
export 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;
|
||||||
|
created_at?: string;
|
||||||
|
last_edited?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateListingRequest {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UpdateListingRequest = Partial<CreateListingRequest>;
|
||||||
|
|
||||||
|
// State/County Types
|
||||||
|
export interface County {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
state: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Service Category Types
|
||||||
|
export interface ServiceCategory {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
clicks_total: number;
|
||||||
|
total_companies: number;
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
import type { Writable } from 'svelte/store';
|
import type { Writable } from 'svelte/store';
|
||||||
import '../../app.postcss'; // Import Tailwind CSS
|
import '../../app.postcss'; // Import Tailwind CSS
|
||||||
import { user, darkMode, type User } from '$lib/states';
|
import { user, darkMode, type User } from '$lib/states';
|
||||||
|
import { authApi } from '$lib/api';
|
||||||
|
|
||||||
// Initialize dark mode on mount to ensure data-theme is set
|
// Initialize dark mode on mount to ensure data-theme is set
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
@@ -18,17 +19,15 @@
|
|||||||
// Placeholder for user store - in a real app, this would be managed by an auth library or context
|
// Placeholder for user store - in a real app, this would be managed by an auth library or context
|
||||||
let storedUser: User | null = null;
|
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(() => {
|
onMount(() => {
|
||||||
const storedUserString = localStorage.getItem('user');
|
const storedUserString = localStorage.getItem('user');
|
||||||
const token = localStorage.getItem('auth_token');
|
if (storedUserString) {
|
||||||
if (storedUserString && token) {
|
|
||||||
storedUser = JSON.parse(storedUserString);
|
storedUser = JSON.parse(storedUserString);
|
||||||
user.set(storedUser);
|
user.set(storedUser);
|
||||||
} else {
|
} else {
|
||||||
// Clear if inconsistent
|
// No user stored
|
||||||
localStorage.removeItem('user');
|
localStorage.removeItem('user');
|
||||||
localStorage.removeItem('auth_token');
|
|
||||||
user.set(null);
|
user.set(null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -41,11 +40,10 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Logout function
|
// Logout function - now async to call API to clear httpOnly cookie
|
||||||
const logout = () => {
|
const logout = async () => {
|
||||||
|
await authApi.logout();
|
||||||
user.set(null);
|
user.set(null);
|
||||||
localStorage.removeItem('user');
|
|
||||||
localStorage.removeItem('auth_token');
|
|
||||||
window.location.href = '/';
|
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 { onMount } from 'svelte';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
|
import { api } from '$lib/api';
|
||||||
|
import type { County } from '$lib/api';
|
||||||
|
|
||||||
const { stateSlug } = $page.params as { stateSlug: string };
|
const { stateSlug } = $page.params as { stateSlug: string };
|
||||||
|
|
||||||
let stateData: NewEnglandState | undefined;
|
let stateData: NewEnglandState | undefined;
|
||||||
let stateCounties: NewEnglandState[] = [];
|
let stateCounties: NewEnglandState[] = [];
|
||||||
let filteredCounties: any[] = [];
|
let filteredCounties: County[] = [];
|
||||||
let loading = false;
|
let loading = false;
|
||||||
let error: string | null = null;
|
let error: string | null = null;
|
||||||
let hoveredCounty: string | null = null;
|
let hoveredCounty: string | null = null;
|
||||||
@@ -41,7 +43,7 @@
|
|||||||
function handleCountyClick(county: NewEnglandState) {
|
function handleCountyClick(county: NewEnglandState) {
|
||||||
// Match county names between map data and API data
|
// Match county names between map data and API data
|
||||||
const cleanMapName = county.name.toLowerCase().replace(/\s+county$/, '').replace(/[^a-z]/g, '');
|
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, '')
|
cleanMapName === c.name.toLowerCase().replace(/[^a-z]/g, '')
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -65,16 +67,16 @@
|
|||||||
if (stateData) {
|
if (stateData) {
|
||||||
// Load API county data
|
// Load API county data
|
||||||
loading = true;
|
loading = true;
|
||||||
try {
|
|
||||||
const token = localStorage.getItem('auth_token');
|
const result = await api.state.getCounties(stateSlug);
|
||||||
const response = await fetch(`http://localhost:9552/state/${stateSlug.toUpperCase()}`, {
|
|
||||||
headers: token ? { Authorization: `Bearer ${token}` } : {}
|
if (result.error) {
|
||||||
});
|
error = result.error;
|
||||||
filteredCounties = response.ok ? await response.json() : [];
|
|
||||||
} catch (err) {
|
|
||||||
error = err instanceof Error ? err.message : 'Failed to fetch counties';
|
|
||||||
filteredCounties = [];
|
filteredCounties = [];
|
||||||
|
} else {
|
||||||
|
filteredCounties = result.data || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
loading = false;
|
loading = false;
|
||||||
|
|
||||||
// Load map county data
|
// Load map county data
|
||||||
|
|||||||
@@ -3,27 +3,11 @@
|
|||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { newEnglandStates } from '../../../../lib/states';
|
import { newEnglandStates } from '../../../../lib/states';
|
||||||
|
import { api } from '$lib/api';
|
||||||
interface Listing {
|
import type { Listing, County } from '$lib/api';
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { stateSlug, countySlug } = $page.params as { stateSlug: string; countySlug: string };
|
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 listings: Listing[] = [];
|
||||||
let loading = true;
|
let loading = true;
|
||||||
let listingsLoading = false;
|
let listingsLoading = false;
|
||||||
@@ -33,29 +17,18 @@
|
|||||||
let sortDirection = 'asc'; // 'asc' or 'desc' - lowest price first
|
let sortDirection = 'asc'; // 'asc' or 'desc' - lowest price first
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
try {
|
const result = await api.state.getCounty(stateSlug, countySlug);
|
||||||
// Ensure API URL matches the Docker port forwarding for API (9552)
|
|
||||||
const token = localStorage.getItem('auth_token');
|
if (result.error) {
|
||||||
const headers: Record<string, string> = {};
|
error = result.error;
|
||||||
if (token) {
|
countyData = null;
|
||||||
headers['Authorization'] = `Bearer ${token}`;
|
} else {
|
||||||
}
|
countyData = result.data;
|
||||||
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();
|
|
||||||
|
|
||||||
// Fetch listings for this county
|
// Fetch listings for this county
|
||||||
await fetchListings();
|
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() {
|
async function fetchListings() {
|
||||||
@@ -63,19 +36,17 @@
|
|||||||
|
|
||||||
listingsLoading = true;
|
listingsLoading = true;
|
||||||
listingsError = null;
|
listingsError = null;
|
||||||
try {
|
|
||||||
const response = await fetch(`http://localhost:9552/listings/county/${countyData.id}`);
|
const result = await api.listings.getByCounty(countyData.id);
|
||||||
if (response.ok) {
|
|
||||||
listings = await response.json();
|
if (result.error) {
|
||||||
sortListings();
|
listingsError = 'Failed to load listings';
|
||||||
} else {
|
} else {
|
||||||
listingsError = 'Failed to load listings';
|
listings = result.data || [];
|
||||||
}
|
sortListings();
|
||||||
} catch (err) {
|
|
||||||
listingsError = 'Network error loading listings';
|
|
||||||
} finally {
|
|
||||||
listingsLoading = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
listingsLoading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortListings() {
|
function sortListings() {
|
||||||
@@ -260,7 +231,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>{new Date(listing.last_edited).toLocaleDateString()}</td>
|
<td>{listing.last_edited ? new Date(listing.last_edited).toLocaleDateString() : 'N/A'}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -319,7 +290,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span class="font-semibold">Last Updated:</span><br>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { user } from '$lib/states';
|
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 username = '';
|
||||||
let password = '';
|
let password = '';
|
||||||
@@ -17,39 +18,18 @@
|
|||||||
isLoading = true;
|
isLoading = true;
|
||||||
errorMessage = '';
|
errorMessage = '';
|
||||||
|
|
||||||
try {
|
const result = await api.auth.login(loginData);
|
||||||
const response = await fetch('http://localhost:9552/auth/login', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify(loginData)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (result.error) {
|
||||||
const errorData = await response.json();
|
errorMessage = result.error;
|
||||||
errorMessage = errorData.error || 'Login failed';
|
} else if (result.data) {
|
||||||
} else {
|
// Update the user store
|
||||||
const data = await response.json();
|
user.set(result.data.user);
|
||||||
// Assuming the backend returns a token or some success indicator
|
// Redirect to vendor page after successful login
|
||||||
// Store the token in localStorage for authentication checks
|
goto('/vendor');
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isLoading = false;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { api } from '$lib/api';
|
||||||
import type { RegisterRequest } from '$lib/types/types';
|
import type { RegisterRequest } from '$lib/api';
|
||||||
|
|
||||||
let email = '';
|
let email = '';
|
||||||
let username = '';
|
let username = '';
|
||||||
@@ -25,33 +25,16 @@
|
|||||||
isLoading = true;
|
isLoading = true;
|
||||||
errorMessage = '';
|
errorMessage = '';
|
||||||
|
|
||||||
try {
|
const result = await api.auth.register(registerData);
|
||||||
const response = await fetch('http://localhost:9552/auth/register', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify(registerData)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (result.error) {
|
||||||
const errorData = await response.json();
|
errorMessage = result.error;
|
||||||
errorMessage = errorData.error || 'Registration failed';
|
} else {
|
||||||
} else {
|
// Redirect to login page after successful registration
|
||||||
const data = await response.json();
|
goto('/login');
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isLoading = false;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
18
src/routes/(app)/vendor/+layout.svelte
vendored
18
src/routes/(app)/vendor/+layout.svelte
vendored
@@ -1,15 +1,21 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
import { authApi } from '$lib/api';
|
||||||
|
|
||||||
onMount(() => {
|
onMount(async () => {
|
||||||
// Check if the user is logged in by looking for a token in localStorage or a cookie
|
// Check if the user is logged in by checking localStorage user
|
||||||
// This assumes a token is stored upon successful login
|
// and verifying with the API (which checks the httpOnly cookie)
|
||||||
const token = localStorage.getItem('auth_token');
|
const storedUser = authApi.getStoredUser();
|
||||||
if (!token) {
|
if (!storedUser) {
|
||||||
// Redirect to home page if not logged in
|
// No user stored, redirect to home
|
||||||
goto('/');
|
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>
|
</script>
|
||||||
|
|
||||||
|
|||||||
229
src/routes/(app)/vendor/+page.svelte
vendored
229
src/routes/(app)/vendor/+page.svelte
vendored
@@ -1,38 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
import { api } from '$lib/api';
|
||||||
interface Listing {
|
import type { Listing, Company } from '$lib/api';
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
let listings: Listing[] = [];
|
let listings: Listing[] = [];
|
||||||
let company: Company | null = null;
|
let company: Company | null = null;
|
||||||
@@ -48,96 +17,44 @@
|
|||||||
let editingValue: string = '';
|
let editingValue: string = '';
|
||||||
|
|
||||||
async function fetchCompany() {
|
async function fetchCompany() {
|
||||||
try {
|
const result = await api.company.get();
|
||||||
const token = localStorage.getItem('auth_token');
|
|
||||||
|
if (result.error) {
|
||||||
if (!token) {
|
if (result.error === 'Not found') {
|
||||||
// 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) {
|
|
||||||
// Company not found - that's okay, user can create one
|
// Company not found - that's okay, user can create one
|
||||||
company = null;
|
company = null;
|
||||||
} else {
|
} else if (result.error !== 'Session expired. Please log in again.') {
|
||||||
companyError = 'Failed to load company information';
|
companyError = 'Failed to load company information';
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} else {
|
||||||
companyError = 'Network error loading company information';
|
company = result.data;
|
||||||
} finally {
|
|
||||||
companyLoading = false;
|
|
||||||
}
|
}
|
||||||
|
companyLoading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchListings() {
|
async function fetchListings() {
|
||||||
try {
|
const result = await api.listing.getAll();
|
||||||
const token = localStorage.getItem('auth_token');
|
|
||||||
|
if (result.error) {
|
||||||
if (!token) {
|
if (result.error !== 'Session expired. Please log in again.') {
|
||||||
// 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 {
|
|
||||||
error = 'Failed to load listings';
|
error = 'Failed to load listings';
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} else {
|
||||||
error = 'Network error loading listings';
|
listings = result.data || [];
|
||||||
} finally {
|
|
||||||
isLoading = false;
|
|
||||||
}
|
}
|
||||||
|
isLoading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteListing(id: number) {
|
async function deleteListing(id: number) {
|
||||||
if (!confirm('Are you sure you want to delete this listing?')) return;
|
if (!confirm('Are you sure you want to delete this listing?')) return;
|
||||||
|
|
||||||
try {
|
const result = await api.listing.delete(id);
|
||||||
const token = localStorage.getItem('auth_token');
|
|
||||||
const response = await fetch(`http://localhost:9552/listing/${id}`, {
|
if (result.error) {
|
||||||
method: 'DELETE',
|
alert('Failed to delete listing');
|
||||||
headers: {
|
} else {
|
||||||
'Authorization': token ? `Bearer ${token}` : ''
|
// Remove from local state
|
||||||
}
|
listings = listings.filter(listing => listing.id !== id);
|
||||||
});
|
|
||||||
|
|
||||||
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');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,62 +88,50 @@
|
|||||||
async function saveEditing(listing: Listing) {
|
async function saveEditing(listing: Listing) {
|
||||||
if (!editingField) return;
|
if (!editingField) return;
|
||||||
|
|
||||||
let updateData: any = {};
|
let updateData: Record<string, unknown> = {};
|
||||||
let newValue: any;
|
let newValue: unknown;
|
||||||
|
|
||||||
try {
|
if (editingField === 'price_per_gallon' || editingField === 'price_per_gallon_cash') {
|
||||||
if (editingField === 'price_per_gallon' || editingField === 'price_per_gallon_cash') {
|
newValue = editingValue ? parseFloat(editingValue) : null;
|
||||||
newValue = editingValue ? parseFloat(editingValue) : null;
|
if (editingField === 'price_per_gallon' && (!newValue || (newValue as number) <= 0)) {
|
||||||
if (editingField === 'price_per_gallon' && (!newValue || newValue <= 0)) {
|
alert('Please enter a valid price greater than 0');
|
||||||
alert('Please enter a valid price greater than 0');
|
return;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
updateData[editingField] = newValue;
|
||||||
const token = localStorage.getItem('auth_token');
|
} else if (editingField === 'minimum_order') {
|
||||||
const response = await fetch(`http://localhost:9552/listing/${listing.id}`, {
|
newValue = editingValue ? parseInt(editingValue) : null;
|
||||||
method: 'PUT',
|
if (newValue && ((newValue as number) < 1 || (newValue as number) > 200)) {
|
||||||
headers: {
|
alert('Minimum order must be between 1 and 200');
|
||||||
'Content-Type': 'application/json',
|
return;
|
||||||
'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}`);
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
updateData[editingField] = newValue;
|
||||||
alert('Network error updating field');
|
} 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(() => {
|
onMount(() => {
|
||||||
|
if (!api.auth.isAuthenticated()) {
|
||||||
|
window.location.href = '/login';
|
||||||
|
return;
|
||||||
|
}
|
||||||
fetchCompany();
|
fetchCompany();
|
||||||
fetchListings();
|
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">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { newEnglandStates } from '$lib/states';
|
import { newEnglandStates } from '$lib/states';
|
||||||
|
import { api } from '$lib/api';
|
||||||
interface County {
|
import type { County } from '$lib/api';
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
state: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Form data
|
// Form data
|
||||||
let formData = {
|
let formData = {
|
||||||
@@ -137,45 +132,30 @@
|
|||||||
isSubmitting = true;
|
isSubmitting = true;
|
||||||
submitMessage = '';
|
submitMessage = '';
|
||||||
|
|
||||||
try {
|
const result = await api.listing.create({
|
||||||
const token = localStorage.getItem('token');
|
company_name: formData.companyName,
|
||||||
const response = await fetch('http://localhost:9552/listing', {
|
is_active: isActive,
|
||||||
method: 'POST',
|
price_per_gallon: formData.pricePerGallon,
|
||||||
headers: {
|
price_per_gallon_cash: formData.pricePerGallonCash,
|
||||||
'Content-Type': 'application/json',
|
minimum_order: formData.minimumOrder,
|
||||||
'Authorization': token ? `Bearer ${token}` : ''
|
service: formData.labelService,
|
||||||
},
|
bio_percent: formData.bioPercent,
|
||||||
body: JSON.stringify({
|
phone: formData.phone,
|
||||||
company_name: formData.companyName,
|
online_ordering: onlineOrdering,
|
||||||
is_active: isActive,
|
county_id: formData.countyId,
|
||||||
price_per_gallon: formData.pricePerGallon,
|
town: formData.town.trim() || null
|
||||||
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) {
|
if (result.error) {
|
||||||
// Redirect to vendor dashboard on success
|
submitMessage = result.error === 'Session expired. Please log in again.'
|
||||||
goto('/vendor');
|
? result.error
|
||||||
} else {
|
: 'Failed to create listing. Please try again.';
|
||||||
submitMessage = 'Failed to create listing. Please try again.';
|
} else {
|
||||||
}
|
// Redirect to vendor dashboard on success
|
||||||
} catch (error) {
|
goto('/vendor');
|
||||||
submitMessage = 'Network error. Please check your connection and try again.';
|
|
||||||
} finally {
|
|
||||||
isSubmitting = false;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Format price for display
|
isSubmitting = false;
|
||||||
function formatPrice(value: number): string {
|
|
||||||
return value.toFixed(2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle state change
|
// Handle state change
|
||||||
@@ -187,22 +167,19 @@
|
|||||||
|
|
||||||
if (formData.state) {
|
if (formData.state) {
|
||||||
isLoadingCounties = true;
|
isLoadingCounties = true;
|
||||||
try {
|
|
||||||
const response = await fetch(`http://localhost:9552/state/${formData.state}`);
|
const result = await api.state.getCounties(formData.state);
|
||||||
if (response.ok) {
|
|
||||||
counties = await response.json();
|
if (result.error) {
|
||||||
if (counties.length === 0) {
|
errors.countyId = result.error;
|
||||||
errors.countyId = 'No counties found for selected state';
|
} else {
|
||||||
}
|
counties = result.data || [];
|
||||||
} else {
|
if (counties.length === 0) {
|
||||||
const errorData = await response.json();
|
errors.countyId = 'No counties found for selected state';
|
||||||
errors.countyId = errorData.error || 'Failed to load counties';
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
errors.countyId = 'Network error loading counties';
|
|
||||||
} finally {
|
|
||||||
isLoadingCounties = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isLoadingCounties = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</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 { goto } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { newEnglandStates } from '$lib/states';
|
import { newEnglandStates } from '$lib/states';
|
||||||
|
import { api } from '$lib/api';
|
||||||
interface County {
|
import type { County, Listing } from '$lib/api';
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get listing ID from URL params
|
// Get listing ID from URL params
|
||||||
let listingId: string;
|
let listingId: string;
|
||||||
@@ -79,40 +57,33 @@
|
|||||||
|
|
||||||
// Load existing listing data
|
// Load existing listing data
|
||||||
async function loadListing() {
|
async function loadListing() {
|
||||||
try {
|
const result = await api.listing.getById(parseInt(listingId));
|
||||||
const token = localStorage.getItem('auth_token');
|
|
||||||
const response = await fetch(`http://localhost:9552/listing/${listingId}`, {
|
|
||||||
headers: {
|
|
||||||
'Authorization': token ? `Bearer ${token}` : ''
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.ok) {
|
if (result.error) {
|
||||||
const listing: Listing = await response.json();
|
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
|
// Pre-populate form with existing data
|
||||||
formData.companyName = listing.company_name;
|
formData.companyName = listing.company_name;
|
||||||
isActive = listing.is_active;
|
isActive = listing.is_active;
|
||||||
formData.pricePerGallon = listing.price_per_gallon;
|
formData.pricePerGallon = listing.price_per_gallon;
|
||||||
formData.pricePerGallonCash = listing.price_per_gallon_cash;
|
formData.pricePerGallonCash = listing.price_per_gallon_cash;
|
||||||
formData.minimumOrder = listing.minimum_order;
|
formData.minimumOrder = listing.minimum_order;
|
||||||
formData.labelService = listing.service;
|
formData.labelService = listing.service;
|
||||||
formData.bioPercent = listing.bio_percent;
|
formData.bioPercent = listing.bio_percent;
|
||||||
formData.phone = listing.phone || '';
|
formData.phone = listing.phone || '';
|
||||||
onlineOrdering = listing.online_ordering;
|
onlineOrdering = listing.online_ordering;
|
||||||
formData.countyId = listing.county_id;
|
formData.countyId = listing.county_id;
|
||||||
formData.town = listing.town || '';
|
formData.town = listing.town || '';
|
||||||
|
|
||||||
// Load the state for this county
|
// Load the state for this county
|
||||||
await loadStateForCounty(listing.county_id);
|
await loadStateForCounty(listing.county_id);
|
||||||
} else {
|
|
||||||
submitMessage = 'Failed to load listing data';
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
submitMessage = 'Network error loading listing data';
|
|
||||||
} finally {
|
|
||||||
isLoading = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isLoading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load state information for a given county
|
// Load state information for a given county
|
||||||
@@ -120,19 +91,16 @@
|
|||||||
// We need to find which state this county belongs to
|
// 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
|
// Since we don't have a reverse lookup, we'll load counties for each state until we find it
|
||||||
for (const state of newEnglandStates) {
|
for (const state of newEnglandStates) {
|
||||||
try {
|
const result = await api.state.getCounties(state.id);
|
||||||
const response = await fetch(`http://localhost:9552/state/${state.id}`);
|
|
||||||
if (response.ok) {
|
if (!result.error && result.data) {
|
||||||
const stateCounties: County[] = await response.json();
|
const stateCounties = result.data;
|
||||||
const county = stateCounties.find(c => c.id === countyId);
|
const county = stateCounties.find(c => c.id === countyId);
|
||||||
if (county) {
|
if (county) {
|
||||||
formData.state = state.id;
|
formData.state = state.id;
|
||||||
counties = stateCounties;
|
counties = stateCounties;
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
// Continue to next state
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -221,45 +189,30 @@
|
|||||||
isSubmitting = true;
|
isSubmitting = true;
|
||||||
submitMessage = '';
|
submitMessage = '';
|
||||||
|
|
||||||
try {
|
const result = await api.listing.update(parseInt(listingId), {
|
||||||
const token = localStorage.getItem('auth_token');
|
company_name: formData.companyName,
|
||||||
const response = await fetch(`http://localhost:9552/listing/${listingId}`, {
|
is_active: isActive,
|
||||||
method: 'PUT',
|
price_per_gallon: formData.pricePerGallon,
|
||||||
headers: {
|
price_per_gallon_cash: formData.pricePerGallonCash,
|
||||||
'Content-Type': 'application/json',
|
minimum_order: formData.minimumOrder,
|
||||||
'Authorization': token ? `Bearer ${token}` : ''
|
service: formData.labelService,
|
||||||
},
|
bio_percent: formData.bioPercent,
|
||||||
body: JSON.stringify({
|
phone: formData.phone,
|
||||||
company_name: formData.companyName,
|
online_ordering: onlineOrdering,
|
||||||
is_active: isActive,
|
county_id: formData.countyId,
|
||||||
price_per_gallon: formData.pricePerGallon,
|
town: formData.town.trim() || null
|
||||||
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) {
|
if (result.error) {
|
||||||
// Redirect to vendor dashboard on success
|
submitMessage = result.error === 'Session expired. Please log in again.'
|
||||||
goto('/vendor');
|
? result.error
|
||||||
} else {
|
: 'Failed to update listing. Please try again.';
|
||||||
submitMessage = 'Failed to update listing. Please try again.';
|
} else {
|
||||||
}
|
// Redirect to vendor dashboard on success
|
||||||
} catch (error) {
|
goto('/vendor');
|
||||||
submitMessage = 'Network error. Please check your connection and try again.';
|
|
||||||
} finally {
|
|
||||||
isSubmitting = false;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Format price for display
|
isSubmitting = false;
|
||||||
function formatPrice(value: number): string {
|
|
||||||
return value.toFixed(2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle state change
|
// Handle state change
|
||||||
@@ -271,22 +224,19 @@
|
|||||||
|
|
||||||
if (formData.state) {
|
if (formData.state) {
|
||||||
isLoadingCounties = true;
|
isLoadingCounties = true;
|
||||||
try {
|
|
||||||
const response = await fetch(`http://localhost:9552/state/${formData.state}`);
|
const result = await api.state.getCounties(formData.state);
|
||||||
if (response.ok) {
|
|
||||||
counties = await response.json();
|
if (result.error) {
|
||||||
if (counties.length === 0) {
|
errors.countyId = result.error;
|
||||||
errors.countyId = 'No counties found for selected state';
|
} else {
|
||||||
}
|
counties = result.data || [];
|
||||||
} else {
|
if (counties.length === 0) {
|
||||||
const errorData = await response.json();
|
errors.countyId = 'No counties found for selected state';
|
||||||
errors.countyId = errorData.error || 'Failed to load counties';
|
|
||||||
}
|
}
|
||||||
} 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">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
import { api } from '$lib/api';
|
||||||
|
|
||||||
const states = ['ME', 'NH', 'VT', 'MA', 'RI', 'CT'];
|
const states = ['ME', 'NH', 'VT', 'MA', 'RI', 'CT'];
|
||||||
|
|
||||||
@@ -20,79 +21,54 @@
|
|||||||
let deleting = false;
|
let deleting = false;
|
||||||
|
|
||||||
async function fetchCompany() {
|
async function fetchCompany() {
|
||||||
try {
|
const result = await api.company.get();
|
||||||
const response = await fetch('http://localhost:9552/company', {
|
|
||||||
headers: {
|
if (result.data) {
|
||||||
'Authorization': `Bearer ${localStorage.getItem('auth_token')}`
|
company = {
|
||||||
}
|
name: result.data.name || '',
|
||||||
});
|
address: result.data.address || '',
|
||||||
|
town: result.data.town || '',
|
||||||
if (response.ok) {
|
state: result.data.state || '',
|
||||||
const data = await response.json();
|
phone: result.data.phone ? result.data.phone.toString() : '',
|
||||||
company = {
|
owner_name: result.data.owner_name || '',
|
||||||
name: data.name || '',
|
owner_phone_number: result.data.owner_phone_number ? result.data.owner_phone_number.toString() : '',
|
||||||
address: data.address || '',
|
email: result.data.email || ''
|
||||||
town: data.town || '',
|
};
|
||||||
state: data.state || '',
|
} else if (result.error && result.error !== 'Not found') {
|
||||||
phone: data.phone ? data.phone.toString() : '',
|
console.error('Error fetching company:', result.error);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveCompany() {
|
async function saveCompany() {
|
||||||
saving = true;
|
saving = true;
|
||||||
try {
|
|
||||||
const payload = {
|
const payload = {
|
||||||
name: company.name,
|
name: company.name,
|
||||||
address: company.address || null,
|
address: company.address || null,
|
||||||
town: company.town || null,
|
town: company.town || null,
|
||||||
state: company.state || null,
|
state: company.state || null,
|
||||||
phone: company.phone || null,
|
phone: company.phone || null,
|
||||||
owner_name: company.owner_name || null,
|
owner_name: company.owner_name || null,
|
||||||
owner_phone_number: company.owner_phone_number || null,
|
owner_phone_number: company.owner_phone_number || null,
|
||||||
email: company.email || null
|
email: company.email || null
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('Sending payload:', payload);
|
console.log('Sending payload:', payload);
|
||||||
|
|
||||||
const response = await fetch('http://localhost:9552/company', {
|
const result = await api.company.update(payload);
|
||||||
method: 'PUT',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': `Bearer ${localStorage.getItem('auth_token')}`
|
|
||||||
},
|
|
||||||
body: JSON.stringify(payload)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.ok) {
|
if (result.error) {
|
||||||
alert('Company profile saved successfully!');
|
if (result.error === 'Session expired. Please log in again.') {
|
||||||
} else if (response.status === 401) {
|
|
||||||
alert('Your session has expired. Please log in again.');
|
alert('Your session has expired. Please log in again.');
|
||||||
// Could redirect to login: goto('/login');
|
|
||||||
} else {
|
} else {
|
||||||
const errorText = await response.text();
|
alert('Error saving company: ' + result.error);
|
||||||
console.log('Error response:', errorText);
|
|
||||||
try {
|
|
||||||
const error = JSON.parse(errorText);
|
|
||||||
alert('Error saving company: ' + error.error);
|
|
||||||
} catch (e) {
|
|
||||||
alert('Error saving company: ' + errorText);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} else {
|
||||||
console.error('Error saving company:', error);
|
alert('Company profile saved successfully!');
|
||||||
alert('Error saving company');
|
|
||||||
} finally {
|
|
||||||
saving = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
saving = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteCompany() {
|
async function deleteCompany() {
|
||||||
@@ -101,41 +77,28 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
deleting = true;
|
deleting = true;
|
||||||
try {
|
|
||||||
const response = await fetch('http://localhost:9552/company', {
|
const result = await api.company.delete();
|
||||||
method: 'DELETE',
|
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${localStorage.getItem('auth_token')}`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.ok) {
|
if (result.error) {
|
||||||
alert('Company profile deleted successfully!');
|
if (result.error === 'Session expired. Please log in again.') {
|
||||||
goto('/vendor');
|
|
||||||
} else if (response.status === 401) {
|
|
||||||
alert('Your session has expired. Please log in again.');
|
alert('Your session has expired. Please log in again.');
|
||||||
// Could redirect to login: goto('/login');
|
|
||||||
} else {
|
} else {
|
||||||
const error = await response.json();
|
alert('Error deleting company: ' + result.error);
|
||||||
alert('Error deleting company: ' + error.error);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} else {
|
||||||
console.error('Error deleting company:', error);
|
alert('Company profile deleted successfully!');
|
||||||
alert('Error deleting company');
|
goto('/vendor');
|
||||||
} finally {
|
|
||||||
deleting = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleting = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const token = localStorage.getItem('auth_token');
|
if (api.auth.isAuthenticated()) {
|
||||||
if (token) {
|
|
||||||
fetchCompany();
|
fetchCompany();
|
||||||
} else {
|
} else {
|
||||||
loading = false;
|
loading = false;
|
||||||
// Could optionally show a login prompt or redirect
|
|
||||||
// alert('Please log in to access your company profile.');
|
|
||||||
// goto('/login');
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user