diff --git a/.gitignore b/.gitignore index a547bf3..0acd2d1 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,9 @@ node_modules dist dist-ssr *.local +.env +.env.* +.npm-cache/ # Editor directories and files .vscode/* diff --git a/src/App.tsx b/src/App.tsx index c91cff5..9deaabf 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,7 +8,7 @@ import { ProtectedRoute } from "@/components/auth/ProtectedRoute"; import { PageLoader } from "@/components/ui/PageLoader"; // Added import for PageLoader import Login from "./pages/Login"; import Dashboard from "./pages/Dashboard"; -import PartnerDirectory from "./features/partners/PartnerDirectory"; +import Partners from "./pages/Partners"; import PartnerProfile from "./features/partners/PartnerProfile"; import Events from "./pages/Events"; import Users from "./pages/Users"; @@ -45,7 +45,7 @@ const App = () => ( path="/partners" element={ - + } /> diff --git a/src/components/layout/TopBar.tsx b/src/components/layout/TopBar.tsx index 3aa8544..5853534 100644 --- a/src/components/layout/TopBar.tsx +++ b/src/components/layout/TopBar.tsx @@ -72,7 +72,7 @@ export function TopBar({ title, description }: TopBarProps) {

{getDisplayName()}

-

Admin

+

{user?.role || 'Admin'}

diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx index 19c0138..37aaab2 100644 --- a/src/contexts/AuthContext.tsx +++ b/src/contexts/AuthContext.tsx @@ -59,24 +59,17 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children setIsLoading(true); const response = await authLogin(username, password); - // Store auth data storeAuth(response); - // Set user state const authUser: AuthUser = { - username: response.username, + ...response.user, token: response.token, - first_name: response.user?.first_name, - last_name: response.user?.last_name, - email: response.user?.email, - profile_photo: response.user?.profile_photo, }; - setUser(authUser); toast({ title: 'Login Successful', - description: `Welcome back, ${username}!`, + description: `Welcome back, ${response.user.first_name || username}!`, }); } catch (error) { console.error('Login error:', error); @@ -97,7 +90,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children const logout = async () => { try { if (user) { - await authLogout(user.username, user.token); + await authLogout(); } } catch (error) { console.error('Logout error:', error); diff --git a/src/features/partners/PartnerDirectory.tsx b/src/features/partners/PartnerDirectory.tsx index 4835e3d..3ca00d5 100644 --- a/src/features/partners/PartnerDirectory.tsx +++ b/src/features/partners/PartnerDirectory.tsx @@ -40,6 +40,7 @@ import { } from 'lucide-react'; import { cn } from '@/lib/utils'; import { subDays } from 'date-fns'; +import { AddPartnerSheet } from './components/AddPartnerSheet'; function RiskGauge({ score }: { score: number }) { const level = getRiskLevel(score); @@ -173,9 +174,11 @@ export default function PartnerDirectory() { className="pl-9" /> - + + + {/* Tabs + Table */} diff --git a/src/features/partners/components/AddPartnerSheet.tsx b/src/features/partners/components/AddPartnerSheet.tsx index 424591a..8f8c144 100644 --- a/src/features/partners/components/AddPartnerSheet.tsx +++ b/src/features/partners/components/AddPartnerSheet.tsx @@ -3,7 +3,7 @@ import { useState } from "react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import * as z from "zod"; -import { Loader2, Upload } from "lucide-react"; +import { Loader2 } from "lucide-react"; import { Sheet, @@ -17,7 +17,6 @@ import { Button } from "@/components/ui/button"; import { Form, FormControl, - FormDescription, FormField, FormItem, FormLabel, @@ -32,16 +31,15 @@ import { SelectValue, } from "@/components/ui/select"; import { toast } from "sonner"; +import { createPartner } from "@/services/partnerApi"; const partnerFormSchema = z.object({ name: z.string().min(2, "Name must be at least 2 characters"), - type: z.enum(['Venue', 'Promoter', 'Sponsor', 'Vendor', 'Affiliate', 'Other']), - email: z.string().email("Invalid email address"), - phone: z.string().optional(), - website: z.string().url().optional().or(z.literal("")), - address: z.string().optional(), - contactName: z.string().min(2, "Contact name required"), - contactRole: z.string().optional(), + partner_type: z.enum(['Venue', 'Promoter', 'Sponsor', 'Vendor', 'Affiliate', 'Other']), + primary_contact_person_email: z.string().email("Invalid email address"), + primary_contact_person_phone: z.string().optional(), + website_url: z.string().url().optional().or(z.literal("")), + primary_contact_person_name: z.string().min(2, "Contact name required"), }); type FormValues = z.infer; @@ -58,27 +56,27 @@ export function AddPartnerSheet({ children }: AddPartnerSheetProps) { resolver: zodResolver(partnerFormSchema), defaultValues: { name: "", - type: "Venue", - email: "", - phone: "", - website: "", - address: "", - contactName: "", - contactRole: "", + partner_type: "Venue", + primary_contact_person_email: "", + primary_contact_person_phone: "", + website_url: "", + primary_contact_person_name: "", }, }); async function onSubmit(data: FormValues) { setIsSubmitting(true); - // Simulate API call - await new Promise((resolve) => setTimeout(resolve, 1500)); - - console.log("Partner created:", data); - toast.success("Partner added successfully"); - - setIsSubmitting(false); - setOpen(false); - form.reset(); + try { + await createPartner(data); + toast.success("Partner added successfully"); + setOpen(false); + form.reset(); + } catch (error) { + const message = error instanceof Error ? error.message : 'Failed to create partner'; + toast.error(message); + } finally { + setIsSubmitting(false); + } } return ( @@ -113,7 +111,7 @@ export function AddPartnerSheet({ children }: AddPartnerSheetProps) { ( Partner Type @@ -142,7 +140,7 @@ export function AddPartnerSheet({ children }: AddPartnerSheetProps) { ( Contact Name @@ -157,7 +155,7 @@ export function AddPartnerSheet({ children }: AddPartnerSheetProps) {
( Email @@ -170,7 +168,7 @@ export function AddPartnerSheet({ children }: AddPartnerSheetProps) { /> ( Phone @@ -186,7 +184,7 @@ export function AddPartnerSheet({ children }: AddPartnerSheetProps) { ( Website diff --git a/src/pages/Partners.tsx b/src/pages/Partners.tsx index 15bda71..a14737f 100644 --- a/src/pages/Partners.tsx +++ b/src/pages/Partners.tsx @@ -1,129 +1,264 @@ -import { Users, UserCheck, AlertTriangle, Search, Filter } from 'lucide-react'; +import { useState, useEffect, useMemo } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { Users, UserCheck, AlertTriangle, Search, Plus, Clock, MoreHorizontal, Eye, Ban, ShieldCheck, Loader2 } from 'lucide-react'; import { AppLayout } from '@/components/layout/AppLayout'; -import { cn } from '@/lib/utils'; -import { mockPartners, formatCurrency } from '@/data/mockData'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Badge } from '@/components/ui/badge'; +import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { + Table, TableBody, TableCell, TableHead, TableHeader, TableRow, +} from '@/components/ui/table'; +import { + DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import { AddPartnerSheet } from '@/features/partners/components/AddPartnerSheet'; +import { fetchPartners as fetchPartnersApi, Partner } from '@/services/partnerApi'; + +function KycBadge({ status }: { status: string }) { + const normalized = status?.toLowerCase(); + if (normalized === 'approved') return ( + + Approved + + ); + if (normalized === 'rejected') return ( + + Rejected + + ); + return ( + + Pending + + ); +} + +function StatusBadge({ status }: { status: string }) { + const normalized = status?.toLowerCase(); + if (normalized === 'active') return ( + Active + ); + if (normalized === 'suspended') return ( + Suspended + ); + return ( + {status} + ); +} export default function Partners() { - return ( - - {/* Quick Stats */} -
-
-
-
- -
-
-

156

-

Total Partners

-
-
-
-
-
-
- -
-
-

12

-

Pending KYC

-
-
-
-
-
-
- -
-
-

2

-

Stripe Issues

-
-
-
-
+ const navigate = useNavigate(); + const [partners, setPartners] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + const [searchQuery, setSearchQuery] = useState(''); + const [activeTab, setActiveTab] = useState('all'); - {/* Partners Table */} -
-
-

All Partners

-
-
- - -
- -
-
+ const loadPartners = async () => { + setIsLoading(true); + setError(null); + try { + const data = await fetchPartnersApi(); + setPartners(data); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to fetch partners'); + } finally { + setIsLoading(false); + } + }; -
- - - - - - - - - - - - - {mockPartners.map((partner) => ( - - - - - - - - - ))} - -
PartnerKYC StatusStripeRevenueEventsActions
-
-

{partner.name}

-

{partner.email}

+ useEffect(() => { + loadPartners(); + }, []); + + const filteredPartners = useMemo(() => { + let list = [...partners]; + + if (searchQuery.trim()) { + const q = searchQuery.toLowerCase(); + list = list.filter(p => + p.name.toLowerCase().includes(q) || + p.primary_contact_person_name.toLowerCase().includes(q) || + p.primary_contact_person_email.toLowerCase().includes(q) + ); + } + + switch (activeTab) { + case 'pending_kyc': + list = list.filter(p => p.kyc_compliance_status?.toLowerCase() === 'pending'); + break; + case 'active': + list = list.filter(p => p.status?.toLowerCase() === 'active'); + break; + } + + return list; + }, [partners, searchQuery, activeTab]); + + const stats = useMemo(() => ({ + total: partners.length, + active: partners.filter(p => p.status?.toLowerCase() === 'active').length, + pendingKyc: partners.filter(p => p.kyc_compliance_status?.toLowerCase() === 'pending').length, + kycApproved: partners.filter(p => p.is_kyc_compliant).length, + }), [partners]); + + return ( + + {/* Stats Row */} +
+
+
+ Total Partners
-
- - {partner.kycStatus.charAt(0).toUpperCase() + partner.kycStatus.slice(1)} - - - - {partner.stripeStatus.charAt(0).toUpperCase() + partner.stripeStatus.slice(1)} - - - {formatCurrency(partner.totalRevenue)} - - {partner.eventsCount} - - -
-
-
-
- ); +

{stats.total}

+
+
+
+ Active +
+

{stats.active}

+
+
+
+ Pending KYC +
+

{stats.pendingKyc}

+
+
+
+ KYC Approved +
+

{stats.kycApproved}

+
+ + + {/* Toolbar */} +
+
+ + setSearchQuery(e.target.value)} + className="pl-9" + /> +
+ + + +
+ + {/* Tabs + Table */} + + + + All {stats.total} + + + Active {stats.active} + + + Pending KYC {stats.pendingKyc} + + + +
+ {isLoading ? ( +
+ +
+ ) : error ? ( +
+ +

{error}

+ +
+ ) : ( + + + + Partner + Type + KYC Status + Location + Status + + + + + {filteredPartners.length > 0 ? ( + filteredPartners.map(partner => ( + navigate(`/partners/${partner.id}`)} + > + +
+
+ + {partner.name.substring(0, 2).toUpperCase()} + +
+
+

{partner.name}

+

{partner.primary_contact_person_email}

+
+
+
+ + + {partner.partner_type} + + + + + + +

{partner.city}

+

{partner.state}

+
+ + + + e.stopPropagation()}> + + + + + + navigate(`/partners/${partner.id}`)}> + View Details + + + + +
+ )) + ) : ( + + + +

No partners found

+

Try adjusting your search or filters

+
+
+ )} +
+
+ )} +
+
+ + ); } diff --git a/src/services/api.ts b/src/services/api.ts new file mode 100644 index 0000000..9a4f1d3 --- /dev/null +++ b/src/services/api.ts @@ -0,0 +1,45 @@ +import { AuthError, getStoredAuth } from './auth'; + +/** + * Centralized API client. Every backend call in the project goes through here. + * + * - Automatically injects `username` and `token` from localStorage + * - All requests are POST with JSON body (matches backend convention) + * - Handles error responses and token expiry uniformly + */ + +export async function apiPost( + url: string, + body: Record = {}, + { skipAuth = false }: { skipAuth?: boolean } = {}, +): Promise { + const headers: Record = { 'Content-Type': 'application/json' }; + + let authPayload: Record = {}; + if (!skipAuth) { + const stored = getStoredAuth(); + if (!stored) { + throw new AuthError('Not authenticated', true); + } + authPayload = { username: stored.username, token: stored.token }; + } + + const res = await fetch(url, { + method: 'POST', + headers, + body: JSON.stringify({ ...authPayload, ...body }), + }); + + const data = await res.json().catch(() => ({})); + + if (!res.ok) { + const message = data.message || data.error || data.errors || 'Request failed'; + const isTokenError = + res.status === 401 || + res.status === 403 || + (typeof message === 'string' && message.toLowerCase().includes('invalid token')); + throw new AuthError(message, isTokenError); + } + + return data as T; +} diff --git a/src/services/auth.ts b/src/services/auth.ts index 1ff3c49..d5baeca 100644 --- a/src/services/auth.ts +++ b/src/services/auth.ts @@ -1,26 +1,52 @@ -// Authentication service based on UAT admin panel pattern +import { apiPost } from './api'; -const AUTH_API_URL = import.meta.env.VITE_AUTH_API_URL || 'https://uat.eventifyplus.com/api/'; +const AUTH_BASE_URL = '/accounts/api/'; export interface AuthUser { + id: number; username: string; token: string; - first_name?: string; - last_name?: string; - email?: string; - profile_photo?: string; + first_name: string; + last_name: string; + email: string; + phone_number: string; + role: string; + is_staff: boolean; + is_customer: boolean; + is_user: boolean; + pincode: string | null; + district: string | null; + state: string | null; + country: string | null; + place: string | null; + latitude: number | null; + longitude: number | null; + profile_picture: string; } export interface LoginResponse { - username: string; + status: string; + message: string; token: string; - message?: string; - user?: { - first_name?: string; - last_name?: string; - email?: string; - phone_number?: string; - profile_photo?: string; + user: { + id: number; + username: string; + first_name: string; + last_name: string; + email: string; + phone_number: string; + role: string; + is_staff: boolean; + is_customer: boolean; + is_user: boolean; + pincode: string | null; + district: string | null; + state: string | null; + country: string | null; + place: string | null; + latitude: number | null; + longitude: number | null; + profile_picture: string; }; } @@ -34,132 +60,51 @@ export class AuthError extends Error { } } -/** - * Login with username and password - */ export const login = async (username: string, password: string): Promise => { - console.log('Bypassing auth for dev as requested'); + const data = await apiPost( + `${AUTH_BASE_URL}login/`, + { username, password }, + { skipAuth: true }, + ); - // Return mock successful response immediately - return { - username: username, - token: 'dev-bypass-token-' + Date.now(), - message: 'Login successful (Bypass)', - user: { - first_name: 'Admin', - last_name: 'User (Bypass)', - email: username, - profile_photo: '', // Placeholder or empty - }, - }; -}; - -/** - * Check user status with token - */ -export const checkUserStatus = async (username: string, token: string): Promise => { - // Support bypass token - if (token && token.startsWith('dev-bypass-token')) { - return { - status: 'active', - user: { - first_name: 'Admin', - last_name: 'User (Bypass)', - email: username || 'admin@example.com', - } - }; - } - - const statusUrl = `${AUTH_API_URL}user/status`; - - const res = await fetch(statusUrl, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ token }), - }); - - const data = await res.json().catch(() => ({})); - - if (!res.ok) { - const errorMessage = data.message || data.errors || data.error || 'Status check failed'; - const isTokenError = errorMessage.toLowerCase().includes('invalid token') || - errorMessage.toLowerCase().includes('token') && (res.status === 401 || res.status === 403); - - throw new AuthError(errorMessage, isTokenError); + if (data.status !== 'success') { + throw new AuthError(data.message || 'Login failed', false); } return data; }; -/** - * Logout user - */ -export const logout = async (username: string, token: string): Promise => { - // Handle bypass token logout locally - if (token && token.startsWith('dev-bypass-token')) { - return { message: 'Logged out successfully' }; - } - - const logoutUrl = `${AUTH_API_URL}user/logout`; - - const res = await fetch(logoutUrl, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ token }), - }); - - const data = await res.json().catch(() => ({})); - - if (!res.ok) { - throw new AuthError(data.message || data.errors || data.error || 'Logout failed'); - } - - return data; +export const logout = async (): Promise<{ message: string }> => { + return apiPost(`${AUTH_BASE_URL}logout/`); +}; + +export const checkUserStatus = async (_username: string, token: string): Promise<{ valid: true }> => { + const stored = getStoredAuth(); + if (stored && stored.token === token) { + return { valid: true }; + } + throw new AuthError('No valid session found', true); }; -/** - * Get stored authentication data from localStorage - */ export const getStoredAuth = (): AuthUser | null => { try { - const username = localStorage.getItem('username'); - const token = localStorage.getItem('token'); - const userData = localStorage.getItem('userData'); - - if (!username || !token) { - return null; - } - - const parsedUserData = userData ? JSON.parse(userData) : {}; - - return { - username, - token, - ...parsedUserData, - }; - } catch (error) { - console.error('Error reading stored auth:', error); + const raw = localStorage.getItem('authUser'); + if (!raw) return null; + return JSON.parse(raw) as AuthUser; + } catch { + console.error('Error reading stored auth'); return null; } }; -/** - * Store authentication data in localStorage - */ -export const storeAuth = (loginResponse: LoginResponse): void => { - localStorage.setItem('username', loginResponse.username); - localStorage.setItem('token', loginResponse.token); - - if (loginResponse.user) { - localStorage.setItem('userData', JSON.stringify(loginResponse.user)); - } +export const storeAuth = (response: LoginResponse): void => { + const authUser: AuthUser = { + ...response.user, + token: response.token, + }; + localStorage.setItem('authUser', JSON.stringify(authUser)); }; -/** - * Clear authentication data from localStorage - */ export const clearAuth = (): void => { - localStorage.removeItem('username'); - localStorage.removeItem('token'); - localStorage.removeItem('userData'); + localStorage.removeItem('authUser'); }; diff --git a/src/services/partnerApi.ts b/src/services/partnerApi.ts index dffff9d..423008d 100644 --- a/src/services/partnerApi.ts +++ b/src/services/partnerApi.ts @@ -1,207 +1,44 @@ -// API service for Partner Dashboard (partner.prototype.eventifyplus.com) - -import { AuthError } from './auth'; - -const API_URL = import.meta.env.VITE_PARTNER_APP_API_URL || 'https://partner.prototype.eventifyplus.com/api/'; +import { apiPost } from './api'; export interface Partner { id: number; name: string; - email: string; - phone?: string; - company_name?: string; - kyc_status: 'pending' | 'approved' | 'rejected'; - stripe_status: 'pending' | 'connected' | 'failed'; - stripe_account_id?: string; - total_revenue?: number; - events_count?: number; - created_at?: string; - kyc_documents?: { - id_proof?: string; - address_proof?: string; - business_registration?: string; - }; + partner_type: string; + primary_contact_person_name: string; + primary_contact_person_email: string; + primary_contact_person_phone: string; + status: string; + address: string; + city: string; + state: string; + country: string; + website_url: string | null; + pincode: string; + latitude: string; + longitude: string; + is_kyc_compliant: boolean; + kyc_compliance_status: string; + kyc_compliance_reason: string | null; + kyc_compliance_document_type: string | null; + kyc_compliance_document_other_type: string | null; + kyc_compliance_document_number: string | null; + kyc_compliance_document_file: string | null; } -export interface PartnerEvent { - id: number; - partner_id: number; - title: string; - description?: string; - date: string; - time?: string; - venue?: string; - ticket_price?: number; - total_tickets?: number; - tickets_sold?: number; - status: 'draft' | 'pending_approval' | 'approved' | 'live' | 'completed' | 'cancelled'; - revenue?: number; -} - -export interface Staff { - id: number; - partner_id: number; +export interface CreatePartnerPayload { name: string; - email: string; - role: string; - permissions?: string[]; - status: 'active' | 'inactive'; + partner_type: string; + primary_contact_person_name: string; + primary_contact_person_email: string; + primary_contact_person_phone?: string; + website_url?: string; } -/** - * Fetch all partners (admin only) - */ -export const fetchPartners = async (username: string, token: string): Promise => { - const res = await fetch(`${API_URL}partners/all/`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ username, token }), - }); - - const data = await res.json().catch(() => ({})); - - if (!res.ok) { - const errorMessage = data.message || data.errors || data.error || 'Failed to fetch partners'; - const isTokenError = errorMessage.toLowerCase().includes('invalid token') || (res.status === 401 || res.status === 403); - throw new AuthError(errorMessage, isTokenError); - } - +export const fetchPartners = async (): Promise => { + const data = await apiPost<{ status: string; partners: Partner[] }>('/partner/list/'); return data.partners || []; }; -/** - * Update partner KYC status (admin only) - */ -export const updatePartnerKYC = async ( - username: string, - token: string, - partnerId: number, - status: 'approved' | 'rejected', - notes?: string -): Promise => { - const res = await fetch(`${API_URL}partners/kyc/update/`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ username, token, partner_id: partnerId, status, notes }), - }); - - const data = await res.json().catch(() => ({})); - - if (!res.ok) { - const errorMessage = data.message || data.errors || data.error || 'Failed to update KYC status'; - const isTokenError = errorMessage.toLowerCase().includes('invalid token') || (res.status === 401 || res.status === 403); - throw new AuthError(errorMessage, isTokenError); - } - - return data; -}; - -/** - * Fetch partner events (admin can view all) - */ -export const fetchPartnerEvents = async ( - username: string, - token: string, - partnerId?: number -): Promise => { - const res = await fetch(`${API_URL}events/partner/`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ username, token, partner_id: partnerId }), - }); - - const data = await res.json().catch(() => ({})); - - if (!res.ok) { - const errorMessage = data.message || data.errors || data.error || 'Failed to fetch partner events'; - const isTokenError = errorMessage.toLowerCase().includes('invalid token') || (res.status === 401 || res.status === 403); - throw new AuthError(errorMessage, isTokenError); - } - - return data.events || []; -}; - -/** - * Approve or reject partner event (admin only) - */ -export const moderatePartnerEvent = async ( - username: string, - token: string, - eventId: number, - action: 'approve' | 'reject', - reason?: string -): Promise => { - const res = await fetch(`${API_URL}events/moderate/`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ username, token, event_id: eventId, action, reason }), - }); - - const data = await res.json().catch(() => ({})); - - if (!res.ok) { - const errorMessage = data.message || data.errors || data.error || 'Failed to moderate event'; - const isTokenError = errorMessage.toLowerCase().includes('invalid token') || (res.status === 401 || res.status === 403); - throw new AuthError(errorMessage, isTokenError); - } - - return data; -}; - -/** - * Fetch partner staff members - */ -export const fetchPartnerStaff = async ( - username: string, - token: string, - partnerId?: number -): Promise => { - const res = await fetch(`${API_URL}staff/all/`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ username, token, partner_id: partnerId }), - }); - - const data = await res.json().catch(() => ({})); - - if (!res.ok) { - const errorMessage = data.message || data.errors || data.error || 'Failed to fetch staff'; - const isTokenError = errorMessage.toLowerCase().includes('invalid token') || (res.status === 401 || res.status === 403); - throw new AuthError(errorMessage, isTokenError); - } - - return data.staff || []; -}; - -/** - * Update partner Stripe connection status - */ -export const updatePartnerStripe = async ( - username: string, - token: string, - partnerId: number, - stripeAccountId: string, - status: 'connected' | 'failed' -): Promise => { - const res = await fetch(`${API_URL}partners/stripe/update/`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - username, - token, - partner_id: partnerId, - stripe_account_id: stripeAccountId, - status - }), - }); - - const data = await res.json().catch(() => ({})); - - if (!res.ok) { - const errorMessage = data.message || data.errors || data.error || 'Failed to update Stripe status'; - const isTokenError = errorMessage.toLowerCase().includes('invalid token') || (res.status === 401 || res.status === 403); - throw new AuthError(errorMessage, isTokenError); - } - - return data; +export const createPartner = async (payload: CreatePartnerPayload): Promise => { + return apiPost('/partner/create/', payload); }; diff --git a/src/services/userAppApi.ts b/src/services/userAppApi.ts index d2886ca..b1ba1ad 100644 --- a/src/services/userAppApi.ts +++ b/src/services/userAppApi.ts @@ -1,7 +1,4 @@ -// API service for User App (mvnew.eventifyplus.com) -// Based on UAT admin panel API patterns - -import { AuthError } from './auth'; +import { apiPost } from './api'; const API_URL = import.meta.env.VITE_USER_APP_API_URL || 'https://uat.eventifyplus.com/api/'; @@ -54,167 +51,36 @@ export interface Booking { status?: string; } -/** - * Fetch all events - */ -export const fetchEvents = async (username: string, token: string): Promise => { - const res = await fetch(`${API_URL}events/all/`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ username, token }), - }); - - const data = await res.json().catch(() => ({})); - - if (!res.ok) { - const errorMessage = data.message || data.errors || data.error || 'Failed to fetch events'; - const isTokenError = errorMessage.toLowerCase().includes('invalid token') || (res.status === 401 || res.status === 403); - throw new AuthError(errorMessage, isTokenError); - } - +export const fetchEvents = async (): Promise => { + const data = await apiPost<{ events: Event[] }>(`${API_URL}events/all/`); return data.events || []; }; -/** - * Fetch calendar events for a specific month - */ -export const fetchCalendarEvents = async ( - username: string, - token: string, - month: number, - year: number -): Promise => { - const res = await fetch(`${API_URL}calendar/`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ username, token, month, year }), - }); - - const data = await res.json().catch(() => ({})); - - if (!res.ok) { - const errorMessage = data.message || data.errors || data.error || 'Failed to fetch calendar events'; - const isTokenError = errorMessage.toLowerCase().includes('invalid token') || (res.status === 401 || res.status === 403); - throw new AuthError(errorMessage, isTokenError); - } - +export const fetchCalendarEvents = async (month: number, year: number): Promise => { + const data = await apiPost<{ events: Event[] }>(`${API_URL}calendar/`, { month, year }); return data.events || []; }; -/** - * Fetch events by date - */ -export const fetchEventsByDate = async ( - username: string, - token: string, - date_of_event: string -): Promise => { - const res = await fetch(`${API_URL}events/by-date/`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ username, token, date_of_event }), - }); - - const data = await res.json().catch(() => ({})); - - if (!res.ok) { - const errorMessage = data.message || data.errors || data.error || 'Failed to fetch events by date'; - const isTokenError = errorMessage.toLowerCase().includes('invalid token') || (res.status === 401 || res.status === 403); - throw new AuthError(errorMessage, isTokenError); - } - +export const fetchEventsByDate = async (date_of_event: string): Promise => { + const data = await apiPost<{ events: Event[] }>(`${API_URL}events/by-date/`, { date_of_event }); return data.events || []; }; -/** - * Fetch all users (admin only) - */ -export const fetchUsers = async (username: string, token: string): Promise => { - const res = await fetch(`${API_URL}users/all/`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ username, token }), - }); - - const data = await res.json().catch(() => ({})); - - if (!res.ok) { - const errorMessage = data.message || data.errors || data.error || 'Failed to fetch users'; - const isTokenError = errorMessage.toLowerCase().includes('invalid token') || (res.status === 401 || res.status === 403); - throw new AuthError(errorMessage, isTokenError); - } - +export const fetchUsers = async (): Promise => { + const data = await apiPost<{ users: User[] }>(`${API_URL}users/all/`); return data.users || []; }; -/** - * Fetch event categories - */ -export const fetchCategories = async (username: string, token: string): Promise => { - const res = await fetch(`${API_URL}categories/`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ username, token }), - }); - - const data = await res.json().catch(() => ({})); - - if (!res.ok) { - const errorMessage = data.message || data.errors || data.error || 'Failed to fetch categories'; - const isTokenError = errorMessage.toLowerCase().includes('invalid token') || (res.status === 401 || res.status === 403); - throw new AuthError(errorMessage, isTokenError); - } - +export const fetchCategories = async (): Promise => { + const data = await apiPost<{ categories: Category[] }>(`${API_URL}categories/`); return data.categories || []; }; -/** - * Fetch user bookings - */ -export const fetchUserBookings = async ( - username: string, - token: string, - userId: number -): Promise => { - const res = await fetch(`${API_URL}bookings/user/`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ username, token, user_id: userId }), - }); - - const data = await res.json().catch(() => ({})); - - if (!res.ok) { - const errorMessage = data.message || data.errors || data.error || 'Failed to fetch bookings'; - const isTokenError = errorMessage.toLowerCase().includes('invalid token') || (res.status === 401 || res.status === 403); - throw new AuthError(errorMessage, isTokenError); - } - +export const fetchUserBookings = async (userId: number): Promise => { + const data = await apiPost<{ bookings: Booking[] }>(`${API_URL}bookings/user/`, { user_id: userId }); return data.bookings || []; }; -/** - * Update event status (admin only) - */ -export const updateEventStatus = async ( - username: string, - token: string, - eventId: number, - status: string -): Promise => { - const res = await fetch(`${API_URL}events/update-status/`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ username, token, event_id: eventId, status }), - }); - - const data = await res.json().catch(() => ({})); - - if (!res.ok) { - const errorMessage = data.message || data.errors || data.error || 'Failed to update event'; - const isTokenError = errorMessage.toLowerCase().includes('invalid token') || (res.status === 401 || res.status === 403); - throw new AuthError(errorMessage, isTokenError); - } - - return data; +export const updateEventStatus = async (eventId: number, status: string): Promise => { + return apiPost(`${API_URL}events/update-status/`, { event_id: eventId, status }); }; diff --git a/vite.config.ts b/vite.config.ts index dccbc04..e1b37bb 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -12,12 +12,20 @@ export default defineConfig(({ mode }) => ({ overlay: false, }, proxy: { - // Proxy API requests to bypass CORS during development + '/accounts/api': { + target: 'http://0.0.0.0:8000', + changeOrigin: true, + secure: false, + }, + '/partner': { + target: 'http://0.0.0.0:8000', + changeOrigin: true, + secure: false, + }, '/api': { target: 'https://uat.eventifyplus.com', changeOrigin: true, secure: false, - rewrite: (path) => path, }, }, },