refactor(frontend): Remove '/api' prefix from all API calls to align with backend URL convention
- Updated all API endpoint strings in 'frontend/src' (via sed and manual fixes) to remove the '/api/' prefix. - Manually fixed 'Timeline.tsx' absolute URLs to use the 'api' subdomain and correct path. - Manually fixed 'useAuth.ts' logout fetch URLs. - Updated 'HelpApiDocs.tsx' sandbox URL. - This change, combined with the backend URL update, fully transitions the application to use subdomain-based routing (e.g., 'http://api.lvh.me:8000/resource/') instead of path-prefix routing (e.g., 'http://api.lvh.me:8000/api/resource/').
This commit is contained in:
@@ -252,9 +252,10 @@ const AppContent: React.FC = () => {
|
|||||||
const isRootDomainForUnauthUser = currentHostname === baseDomain || currentHostname === 'localhost';
|
const isRootDomainForUnauthUser = currentHostname === baseDomain || currentHostname === 'localhost';
|
||||||
|
|
||||||
if (!isRootDomainForUnauthUser) {
|
if (!isRootDomainForUnauthUser) {
|
||||||
// Redirect to root domain login
|
// Redirect to root domain login (preserve port)
|
||||||
const protocol = window.location.protocol;
|
const protocol = window.location.protocol;
|
||||||
window.location.href = `${protocol}//${baseDomain}/login`;
|
const port = window.location.port ? `:${window.location.port}` : '';
|
||||||
|
window.location.href = `${protocol}//${baseDomain}${port}/login`;
|
||||||
return <LoadingScreen />;
|
return <LoadingScreen />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ const CreateTaskModal: React.FC<CreateTaskModalProps> = ({ isOpen, onClose, onSu
|
|||||||
const { data: plugins = [], isLoading: pluginsLoading } = useQuery<PluginInstallation[]>({
|
const { data: plugins = [], isLoading: pluginsLoading } = useQuery<PluginInstallation[]>({
|
||||||
queryKey: ['plugin-installations'],
|
queryKey: ['plugin-installations'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data } = await axios.get('/api/plugin-installations/');
|
const { data } = await axios.get('/plugin-installations/');
|
||||||
// Filter out plugins that already have scheduled tasks
|
// Filter out plugins that already have scheduled tasks
|
||||||
return data.filter((p: PluginInstallation) => !p.scheduled_task);
|
return data.filter((p: PluginInstallation) => !p.scheduled_task);
|
||||||
},
|
},
|
||||||
@@ -209,7 +209,7 @@ const CreateTaskModal: React.FC<CreateTaskModalProps> = ({ isOpen, onClose, onSu
|
|||||||
apply_to_existing: applyToExisting,
|
apply_to_existing: applyToExisting,
|
||||||
};
|
};
|
||||||
|
|
||||||
await axios.post('/api/global-event-plugins/', payload);
|
await axios.post('/global-event-plugins/', payload);
|
||||||
queryClient.invalidateQueries({ queryKey: ['global-event-plugins'] });
|
queryClient.invalidateQueries({ queryKey: ['global-event-plugins'] });
|
||||||
toast.success(applyToExisting ? 'Plugin attached to all events' : 'Plugin will apply to future events');
|
toast.success(applyToExisting ? 'Plugin attached to all events' : 'Plugin will apply to future events');
|
||||||
} else {
|
} else {
|
||||||
@@ -240,7 +240,7 @@ const CreateTaskModal: React.FC<CreateTaskModalProps> = ({ isOpen, onClose, onSu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await axios.post('/api/scheduled-tasks/', payload);
|
await axios.post('/scheduled-tasks/', payload);
|
||||||
toast.success('Scheduled task created');
|
toast.success('Scheduled task created');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ export function DevQuickLogin({ embedded = false }: DevQuickLoginProps) {
|
|||||||
setLoading(user.username);
|
setLoading(user.username);
|
||||||
try {
|
try {
|
||||||
// Call token auth API
|
// Call token auth API
|
||||||
const response = await apiClient.post('/api/auth-token/', {
|
const response = await apiClient.post('/auth-token/', {
|
||||||
username: user.username,
|
username: user.username,
|
||||||
password: user.password,
|
password: user.password,
|
||||||
});
|
});
|
||||||
@@ -98,7 +98,7 @@ export function DevQuickLogin({ embedded = false }: DevQuickLoginProps) {
|
|||||||
setCookie('access_token', response.data.token, 7);
|
setCookie('access_token', response.data.token, 7);
|
||||||
|
|
||||||
// Fetch user data to determine redirect
|
// Fetch user data to determine redirect
|
||||||
const userResponse = await apiClient.get('/api/auth/me/');
|
const userResponse = await apiClient.get('/auth/me/');
|
||||||
const userData = userResponse.data;
|
const userData = userResponse.data;
|
||||||
|
|
||||||
// Determine the correct subdomain based on user role
|
// Determine the correct subdomain based on user role
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ const EditTaskModal: React.FC<EditTaskModalProps> = ({ task, isOpen, onClose, on
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await axios.patch(`/api/scheduled-tasks/${task.id}/`, payload);
|
await axios.patch(`/scheduled-tasks/${task.id}/`, payload);
|
||||||
onSuccess();
|
onSuccess();
|
||||||
handleClose();
|
handleClose();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ const EmailTemplateForm: React.FC<EmailTemplateFormProps> = ({
|
|||||||
const { data: variablesData } = useQuery<{ variables: EmailTemplateVariableGroup[] }>({
|
const { data: variablesData } = useQuery<{ variables: EmailTemplateVariableGroup[] }>({
|
||||||
queryKey: ['email-template-variables'],
|
queryKey: ['email-template-variables'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data } = await api.get('/api/email-templates/variables/');
|
const { data } = await api.get('/email-templates/variables/');
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -57,7 +57,7 @@ const EmailTemplateForm: React.FC<EmailTemplateFormProps> = ({
|
|||||||
// Preview mutation
|
// Preview mutation
|
||||||
const previewMutation = useMutation({
|
const previewMutation = useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
const { data } = await api.post('/api/email-templates/preview/', {
|
const { data } = await api.post('/email-templates/preview/', {
|
||||||
subject,
|
subject,
|
||||||
html_content: htmlContent,
|
html_content: htmlContent,
|
||||||
text_content: textContent,
|
text_content: textContent,
|
||||||
@@ -80,10 +80,10 @@ const EmailTemplateForm: React.FC<EmailTemplateFormProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (isEditing && template) {
|
if (isEditing && template) {
|
||||||
const { data } = await api.patch(`/api/email-templates/${template.id}/`, payload);
|
const { data } = await api.patch(`/email-templates/${template.id}/`, payload);
|
||||||
return data;
|
return data;
|
||||||
} else {
|
} else {
|
||||||
const { data } = await api.post('/api/email-templates/', payload);
|
const { data } = await api.post('/email-templates/', payload);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ const EmailTemplateSelector: React.FC<EmailTemplateSelectorProps> = ({
|
|||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
if (category) params.append('category', category);
|
if (category) params.append('category', category);
|
||||||
const { data } = await api.get(`/api/email-templates/?${params.toString()}`);
|
const { data } = await api.get(`/email-templates/?${params.toString()}`);
|
||||||
return data.map((t: any) => ({
|
return data.map((t: any) => ({
|
||||||
id: String(t.id),
|
id: String(t.id),
|
||||||
name: t.name,
|
name: t.name,
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ const EventAutomations: React.FC<EventAutomationsProps> = ({ eventId, compact =
|
|||||||
const { data: plugins = [] } = useQuery<PluginInstallation[]>({
|
const { data: plugins = [] } = useQuery<PluginInstallation[]>({
|
||||||
queryKey: ['plugin-installations'],
|
queryKey: ['plugin-installations'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data } = await axios.get('/api/plugin-installations/');
|
const { data } = await axios.get('/plugin-installations/');
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -81,7 +81,7 @@ const EventAutomations: React.FC<EventAutomationsProps> = ({ eventId, compact =
|
|||||||
const { data: eventPlugins = [], isLoading } = useQuery<EventPlugin[]>({
|
const { data: eventPlugins = [], isLoading } = useQuery<EventPlugin[]>({
|
||||||
queryKey: ['event-plugins', eventId],
|
queryKey: ['event-plugins', eventId],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data } = await axios.get(`/api/event-plugins/?event_id=${eventId}`);
|
const { data } = await axios.get(`/event-plugins/?event_id=${eventId}`);
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
enabled: !!eventId,
|
enabled: !!eventId,
|
||||||
@@ -90,7 +90,7 @@ const EventAutomations: React.FC<EventAutomationsProps> = ({ eventId, compact =
|
|||||||
// Add plugin mutation
|
// Add plugin mutation
|
||||||
const addMutation = useMutation({
|
const addMutation = useMutation({
|
||||||
mutationFn: async (data: { plugin_installation: string; trigger: string; offset_minutes: number }) => {
|
mutationFn: async (data: { plugin_installation: string; trigger: string; offset_minutes: number }) => {
|
||||||
return axios.post('/api/event-plugins/', {
|
return axios.post('/event-plugins/', {
|
||||||
event: eventId,
|
event: eventId,
|
||||||
...data,
|
...data,
|
||||||
});
|
});
|
||||||
@@ -111,7 +111,7 @@ const EventAutomations: React.FC<EventAutomationsProps> = ({ eventId, compact =
|
|||||||
// Toggle mutation
|
// Toggle mutation
|
||||||
const toggleMutation = useMutation({
|
const toggleMutation = useMutation({
|
||||||
mutationFn: async (pluginId: string) => {
|
mutationFn: async (pluginId: string) => {
|
||||||
return axios.post(`/api/event-plugins/${pluginId}/toggle/`);
|
return axios.post(`/event-plugins/${pluginId}/toggle/`);
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['event-plugins', eventId] });
|
queryClient.invalidateQueries({ queryKey: ['event-plugins', eventId] });
|
||||||
@@ -121,7 +121,7 @@ const EventAutomations: React.FC<EventAutomationsProps> = ({ eventId, compact =
|
|||||||
// Delete mutation
|
// Delete mutation
|
||||||
const deleteMutation = useMutation({
|
const deleteMutation = useMutation({
|
||||||
mutationFn: async (pluginId: string) => {
|
mutationFn: async (pluginId: string) => {
|
||||||
return axios.delete(`/api/event-plugins/${pluginId}/`);
|
return axios.delete(`/event-plugins/${pluginId}/`);
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['event-plugins', eventId] });
|
queryClient.invalidateQueries({ queryKey: ['event-plugins', eventId] });
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export const Timeline: React.FC = () => {
|
|||||||
const { data: resources = [] } = useQuery({
|
const { data: resources = [] } = useQuery({
|
||||||
queryKey: ['resources'],
|
queryKey: ['resources'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await axios.get('http://lvh.me:8000/api/resources/');
|
const response = await axios.get('http://api.lvh.me:8000/resources/');
|
||||||
return adaptResources(response.data);
|
return adaptResources(response.data);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -47,7 +47,7 @@ export const Timeline: React.FC = () => {
|
|||||||
const { data: backendAppointments = [] } = useQuery({ // Renamed to backendAppointments to avoid conflict with localEvents
|
const { data: backendAppointments = [] } = useQuery({ // Renamed to backendAppointments to avoid conflict with localEvents
|
||||||
queryKey: ['appointments'],
|
queryKey: ['appointments'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await axios.get('http://lvh.me:8000/api/appointments/');
|
const response = await axios.get('http://api.lvh.me:8000/appointments/');
|
||||||
return response.data; // Still return raw data, adapt in useEffect
|
return response.data; // Still return raw data, adapt in useEffect
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -95,8 +95,11 @@ export const useLogout = () => {
|
|||||||
queryClient.removeQueries({ queryKey: ['currentUser'] });
|
queryClient.removeQueries({ queryKey: ['currentUser'] });
|
||||||
queryClient.clear();
|
queryClient.clear();
|
||||||
|
|
||||||
// Redirect to login page
|
// Redirect to login page on root domain
|
||||||
window.location.href = '/login';
|
const protocol = window.location.protocol;
|
||||||
|
const baseDomain = getBaseDomain();
|
||||||
|
const port = window.location.port ? `:${window.location.port}` : '';
|
||||||
|
window.location.href = `${protocol}//${baseDomain}${port}/login`;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -150,7 +153,7 @@ export const useMasquerade = () => {
|
|||||||
// Call logout API to clear HttpOnly sessionid cookie
|
// Call logout API to clear HttpOnly sessionid cookie
|
||||||
try {
|
try {
|
||||||
const apiUrl = import.meta.env.VITE_API_URL || `${window.location.protocol}//${baseDomain}`;
|
const apiUrl = import.meta.env.VITE_API_URL || `${window.location.protocol}//${baseDomain}`;
|
||||||
await fetch(`${apiUrl}/api/auth/logout/`, {
|
await fetch(`${apiUrl}/auth/logout/`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
});
|
});
|
||||||
@@ -222,7 +225,7 @@ export const useStopMasquerade = () => {
|
|||||||
// CRITICAL: Clear the session cookie BEFORE redirect
|
// CRITICAL: Clear the session cookie BEFORE redirect
|
||||||
try {
|
try {
|
||||||
const apiUrl = import.meta.env.VITE_API_URL || `${window.location.protocol}//${baseDomain}`;
|
const apiUrl = import.meta.env.VITE_API_URL || `${window.location.protocol}//${baseDomain}`;
|
||||||
await fetch(`${apiUrl}/api/auth/logout/`, {
|
await fetch(`${apiUrl}/auth/logout/`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export const useCurrentBusiness = () => {
|
|||||||
return null; // No token, return null instead of making request
|
return null; // No token, return null instead of making request
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data } = await apiClient.get('/api/business/current/');
|
const { data } = await apiClient.get('/business/current/');
|
||||||
|
|
||||||
// Transform backend format to frontend format
|
// Transform backend format to frontend format
|
||||||
return {
|
return {
|
||||||
@@ -96,7 +96,7 @@ export const useUpdateBusiness = () => {
|
|||||||
backendData.customer_dashboard_content = updates.customerDashboardContent;
|
backendData.customer_dashboard_content = updates.customerDashboardContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data } = await apiClient.patch('/api/business/current/update/', backendData);
|
const { data } = await apiClient.patch('/business/current/update/', backendData);
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
@@ -112,7 +112,7 @@ export const useResources = () => {
|
|||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ['resources'],
|
queryKey: ['resources'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data } = await apiClient.get('/api/resources/');
|
const { data } = await apiClient.get('/resources/');
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||||
@@ -127,7 +127,7 @@ export const useCreateResource = () => {
|
|||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async (resourceData: { name: string; type: string; user_id?: string }) => {
|
mutationFn: async (resourceData: { name: string; type: string; user_id?: string }) => {
|
||||||
const { data } = await apiClient.post('/api/resources/', resourceData);
|
const { data } = await apiClient.post('/resources/', resourceData);
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
@@ -143,7 +143,7 @@ export const useBusinessUsers = () => {
|
|||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ['businessUsers'],
|
queryKey: ['businessUsers'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data } = await apiClient.get('/api/staff/');
|
const { data } = await apiClient.get('/staff/');
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export const useStaff = (filters?: StaffFilters) => {
|
|||||||
if (filters?.search) params.append('search', filters.search);
|
if (filters?.search) params.append('search', filters.search);
|
||||||
params.append('show_inactive', 'true'); // Always fetch inactive staff too
|
params.append('show_inactive', 'true'); // Always fetch inactive staff too
|
||||||
|
|
||||||
const { data } = await apiClient.get(`/api/staff/?${params}`);
|
const { data } = await apiClient.get(`/staff/?${params}`);
|
||||||
|
|
||||||
// Transform backend format to frontend format
|
// Transform backend format to frontend format
|
||||||
return data.map((s: any) => ({
|
return data.map((s: any) => ({
|
||||||
@@ -68,7 +68,7 @@ export const useUpdateStaff = () => {
|
|||||||
id: string;
|
id: string;
|
||||||
updates: { is_active?: boolean; permissions?: StaffPermissions };
|
updates: { is_active?: boolean; permissions?: StaffPermissions };
|
||||||
}) => {
|
}) => {
|
||||||
const { data } = await apiClient.patch(`/api/staff/${id}/`, updates);
|
const { data } = await apiClient.patch(`/staff/${id}/`, updates);
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
@@ -86,7 +86,7 @@ export const useToggleStaffActive = () => {
|
|||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async (id: string) => {
|
mutationFn: async (id: string) => {
|
||||||
const { data } = await apiClient.post(`/api/staff/${id}/toggle_active/`);
|
const { data } = await apiClient.post(`/staff/${id}/toggle_active/`);
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const PlatformLayout: React.FC<PlatformLayoutProps> = ({ user, darkMode, toggleT
|
|||||||
useScrollToTop();
|
useScrollToTop();
|
||||||
|
|
||||||
// Fetch ticket data when modal is opened from notification
|
// Fetch ticket data when modal is opened from notification
|
||||||
const { data: ticketFromNotification } = useTicket(ticketModalId || undefined);
|
const { data: ticketFromNotification } = useTicket(ticketModalId && ticketModalId !== 'undefined' ? ticketModalId : undefined);
|
||||||
|
|
||||||
const handleTicketClick = (ticketId: string) => {
|
const handleTicketClick = (ticketId: string) => {
|
||||||
setTicketModalId(ticketId);
|
setTicketModalId(ticketId);
|
||||||
@@ -38,7 +38,7 @@ const PlatformLayout: React.FC<PlatformLayoutProps> = ({ user, darkMode, toggleT
|
|||||||
<div className="flex h-screen bg-gray-100 dark:bg-gray-900">
|
<div className="flex h-screen bg-gray-100 dark:bg-gray-900">
|
||||||
{/* Mobile menu */}
|
{/* Mobile menu */}
|
||||||
<div className={`fixed inset-y-0 left-0 z-40 transform ${isMobileMenuOpen ? 'translate-x-0' : '-translate-x-full'} transition-transform duration-300 ease-in-out md:hidden`}>
|
<div className={`fixed inset-y-0 left-0 z-40 transform ${isMobileMenuOpen ? 'translate-x-0' : '-translate-x-full'} transition-transform duration-300 ease-in-out md:hidden`}>
|
||||||
<PlatformSidebar user={user} isCollapsed={false} toggleCollapse={() => {}} />
|
<PlatformSidebar user={user} isCollapsed={false} toggleCollapse={() => { }} />
|
||||||
</div>
|
</div>
|
||||||
{isMobileMenuOpen && <div className="fixed inset-0 z-30 bg-black/50 md:hidden" onClick={() => setIsMobileMenuOpen(false)}></div>}
|
{isMobileMenuOpen && <div className="fixed inset-0 z-30 bg-black/50 md:hidden" onClick={() => setIsMobileMenuOpen(false)}></div>}
|
||||||
|
|
||||||
@@ -51,32 +51,32 @@ const PlatformLayout: React.FC<PlatformLayoutProps> = ({ user, darkMode, toggleT
|
|||||||
<div className="flex flex-col flex-1 min-w-0 overflow-hidden">
|
<div className="flex flex-col flex-1 min-w-0 overflow-hidden">
|
||||||
{/* Platform Top Bar */}
|
{/* Platform Top Bar */}
|
||||||
<header className="flex items-center justify-between h-16 px-4 sm:px-8 bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
|
<header className="flex items-center justify-between h-16 px-4 sm:px-8 bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsMobileMenuOpen(true)}
|
onClick={() => setIsMobileMenuOpen(true)}
|
||||||
className="p-2 -ml-2 text-gray-500 rounded-md md:hidden hover:bg-gray-100 dark:hover:bg-gray-700"
|
className="p-2 -ml-2 text-gray-500 rounded-md md:hidden hover:bg-gray-100 dark:hover:bg-gray-700"
|
||||||
aria-label="Open sidebar"
|
aria-label="Open sidebar"
|
||||||
>
|
>
|
||||||
<Menu size={24} />
|
<Menu size={24} />
|
||||||
</button>
|
</button>
|
||||||
<div className="hidden md:flex items-center text-gray-500 dark:text-gray-400 text-sm gap-2">
|
<div className="hidden md:flex items-center text-gray-500 dark:text-gray-400 text-sm gap-2">
|
||||||
<Globe size={16} />
|
<Globe size={16} />
|
||||||
<span>smoothschedule.com</span>
|
<span>smoothschedule.com</span>
|
||||||
<span className="mx-2 text-gray-300">/</span>
|
<span className="mx-2 text-gray-300">/</span>
|
||||||
<span className="text-gray-900 dark:text-white font-medium">Admin Console</span>
|
<span className="text-gray-900 dark:text-white font-medium">Admin Console</span>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<button
|
<button
|
||||||
onClick={toggleTheme}
|
onClick={toggleTheme}
|
||||||
className="p-2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 transition-colors"
|
className="p-2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 transition-colors"
|
||||||
>
|
>
|
||||||
{darkMode ? <Sun size={20} /> : <Moon size={20} />}
|
{darkMode ? <Sun size={20} /> : <Moon size={20} />}
|
||||||
</button>
|
</button>
|
||||||
<NotificationDropdown onTicketClick={handleTicketClick} />
|
<NotificationDropdown onTicketClick={handleTicketClick} />
|
||||||
<UserProfileDropdown user={user} />
|
<UserProfileDropdown user={user} />
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main className="flex-1 overflow-auto bg-gray-50 dark:bg-gray-900 p-8">
|
<main className="flex-1 overflow-auto bg-gray-50 dark:bg-gray-900 p-8">
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ const EmailTemplates: React.FC = () => {
|
|||||||
const { data: templates = [], isLoading, error } = useQuery<EmailTemplate[]>({
|
const { data: templates = [], isLoading, error } = useQuery<EmailTemplate[]>({
|
||||||
queryKey: ['email-templates'],
|
queryKey: ['email-templates'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data } = await api.get('/api/email-templates/');
|
const { data } = await api.get('/email-templates/');
|
||||||
return data.map((t: any) => ({
|
return data.map((t: any) => ({
|
||||||
id: String(t.id),
|
id: String(t.id),
|
||||||
name: t.name,
|
name: t.name,
|
||||||
@@ -85,7 +85,7 @@ const EmailTemplates: React.FC = () => {
|
|||||||
// Delete template mutation
|
// Delete template mutation
|
||||||
const deleteMutation = useMutation({
|
const deleteMutation = useMutation({
|
||||||
mutationFn: async (templateId: string) => {
|
mutationFn: async (templateId: string) => {
|
||||||
await api.delete(`/api/email-templates/${templateId}/`);
|
await api.delete(`/email-templates/${templateId}/`);
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['email-templates'] });
|
queryClient.invalidateQueries({ queryKey: ['email-templates'] });
|
||||||
@@ -97,7 +97,7 @@ const EmailTemplates: React.FC = () => {
|
|||||||
// Duplicate template mutation
|
// Duplicate template mutation
|
||||||
const duplicateMutation = useMutation({
|
const duplicateMutation = useMutation({
|
||||||
mutationFn: async (templateId: string) => {
|
mutationFn: async (templateId: string) => {
|
||||||
const { data } = await api.post(`/api/email-templates/${templateId}/duplicate/`);
|
const { data } = await api.post(`/email-templates/${templateId}/duplicate/`);
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ const LANGUAGES: Record<CodeLanguage, LanguageConfig> = {
|
|||||||
// Default test credentials (used when no tokens are available)
|
// Default test credentials (used when no tokens are available)
|
||||||
const DEFAULT_TEST_API_KEY = 'ss_test_<your_test_token_here>';
|
const DEFAULT_TEST_API_KEY = 'ss_test_<your_test_token_here>';
|
||||||
const DEFAULT_TEST_WEBHOOK_SECRET = 'whsec_test_abc123def456ghi789jkl012mno345pqr678';
|
const DEFAULT_TEST_WEBHOOK_SECRET = 'whsec_test_abc123def456ghi789jkl012mno345pqr678';
|
||||||
const SANDBOX_URL = 'https://sandbox.smoothschedule.com/api/v1';
|
const SANDBOX_URL = 'https://sandbox.smoothschedule.com/v1';
|
||||||
|
|
||||||
// Multi-language code interface
|
// Multi-language code interface
|
||||||
interface MultiLangCode {
|
interface MultiLangCode {
|
||||||
@@ -1111,7 +1111,7 @@ my $response = $ua->get('${SANDBOX_URL}/services/',
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<a
|
<a
|
||||||
href="/api/v1/docs/"
|
href="/v1/docs/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="flex items-center gap-2 px-4 py-2 text-sm font-medium text-purple-600 dark:text-purple-400 hover:bg-purple-50 dark:hover:bg-purple-900/20 rounded-lg transition-colors"
|
className="flex items-center gap-2 px-4 py-2 text-sm font-medium text-purple-600 dark:text-purple-400 hover:bg-purple-50 dark:hover:bg-purple-900/20 rounded-lg transition-colors"
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ const MyPlugins: React.FC = () => {
|
|||||||
const { data: plugins = [], isLoading, error } = useQuery<PluginInstallation[]>({
|
const { data: plugins = [], isLoading, error } = useQuery<PluginInstallation[]>({
|
||||||
queryKey: ['plugin-installations'],
|
queryKey: ['plugin-installations'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data } = await api.get('/api/plugin-installations/');
|
const { data } = await api.get('/plugin-installations/');
|
||||||
return data.map((p: any) => ({
|
return data.map((p: any) => ({
|
||||||
id: String(p.id),
|
id: String(p.id),
|
||||||
template: String(p.template),
|
template: String(p.template),
|
||||||
@@ -88,7 +88,7 @@ const MyPlugins: React.FC = () => {
|
|||||||
// Uninstall plugin mutation
|
// Uninstall plugin mutation
|
||||||
const uninstallMutation = useMutation({
|
const uninstallMutation = useMutation({
|
||||||
mutationFn: async (pluginId: string) => {
|
mutationFn: async (pluginId: string) => {
|
||||||
await api.delete(`/api/plugin-installations/${pluginId}/`);
|
await api.delete(`/plugin-installations/${pluginId}/`);
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['plugin-installations'] });
|
queryClient.invalidateQueries({ queryKey: ['plugin-installations'] });
|
||||||
@@ -100,7 +100,7 @@ const MyPlugins: React.FC = () => {
|
|||||||
// Rate plugin mutation
|
// Rate plugin mutation
|
||||||
const rateMutation = useMutation({
|
const rateMutation = useMutation({
|
||||||
mutationFn: async ({ pluginId, rating, review }: { pluginId: string; rating: number; review: string }) => {
|
mutationFn: async ({ pluginId, rating, review }: { pluginId: string; rating: number; review: string }) => {
|
||||||
const { data } = await api.post(`/api/plugin-installations/${pluginId}/rate/`, {
|
const { data } = await api.post(`/plugin-installations/${pluginId}/rate/`, {
|
||||||
rating,
|
rating,
|
||||||
review,
|
review,
|
||||||
});
|
});
|
||||||
@@ -118,7 +118,7 @@ const MyPlugins: React.FC = () => {
|
|||||||
// Update plugin mutation
|
// Update plugin mutation
|
||||||
const updateMutation = useMutation({
|
const updateMutation = useMutation({
|
||||||
mutationFn: async (pluginId: string) => {
|
mutationFn: async (pluginId: string) => {
|
||||||
const { data } = await api.post(`/api/plugin-installations/${pluginId}/update/`);
|
const { data } = await api.post(`/plugin-installations/${pluginId}/update/`);
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
@@ -129,7 +129,7 @@ const MyPlugins: React.FC = () => {
|
|||||||
// Edit config mutation
|
// Edit config mutation
|
||||||
const editConfigMutation = useMutation({
|
const editConfigMutation = useMutation({
|
||||||
mutationFn: async ({ pluginId, configValues }: { pluginId: string; configValues: Record<string, any> }) => {
|
mutationFn: async ({ pluginId, configValues }: { pluginId: string; configValues: Record<string, any> }) => {
|
||||||
const { data } = await api.patch(`/api/plugin-installations/${pluginId}/`, {
|
const { data } = await api.patch(`/plugin-installations/${pluginId}/`, {
|
||||||
config_values: configValues,
|
config_values: configValues,
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ const PluginMarketplace: React.FC = () => {
|
|||||||
const { data: plugins = [], isLoading, error } = useQuery<PluginTemplate[]>({
|
const { data: plugins = [], isLoading, error } = useQuery<PluginTemplate[]>({
|
||||||
queryKey: ['plugin-templates', 'marketplace'],
|
queryKey: ['plugin-templates', 'marketplace'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data } = await api.get('/api/plugin-templates/?view=marketplace');
|
const { data } = await api.get('/plugin-templates/?view=marketplace');
|
||||||
return data.map((p: any) => ({
|
return data.map((p: any) => ({
|
||||||
id: String(p.id),
|
id: String(p.id),
|
||||||
name: p.name,
|
name: p.name,
|
||||||
@@ -120,7 +120,7 @@ const PluginMarketplace: React.FC = () => {
|
|||||||
const { data: installedPlugins = [] } = useQuery<{ template: number }[]>({
|
const { data: installedPlugins = [] } = useQuery<{ template: number }[]>({
|
||||||
queryKey: ['plugin-installations'],
|
queryKey: ['plugin-installations'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data } = await api.get('/api/plugin-installations/');
|
const { data } = await api.get('/plugin-installations/');
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -133,7 +133,7 @@ const PluginMarketplace: React.FC = () => {
|
|||||||
// Install plugin mutation
|
// Install plugin mutation
|
||||||
const installMutation = useMutation({
|
const installMutation = useMutation({
|
||||||
mutationFn: async (templateId: string) => {
|
mutationFn: async (templateId: string) => {
|
||||||
const { data } = await api.post('/api/plugin-installations/', {
|
const { data } = await api.post('/plugin-installations/', {
|
||||||
template: templateId,
|
template: templateId,
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
@@ -187,7 +187,7 @@ const PluginMarketplace: React.FC = () => {
|
|||||||
// Fetch full plugin details including plugin_code
|
// Fetch full plugin details including plugin_code
|
||||||
setIsLoadingDetails(true);
|
setIsLoadingDetails(true);
|
||||||
try {
|
try {
|
||||||
const { data } = await api.get(`/api/plugin-templates/${plugin.id}/`);
|
const { data } = await api.get(`/plugin-templates/${plugin.id}/`);
|
||||||
setSelectedPlugin({
|
setSelectedPlugin({
|
||||||
...plugin,
|
...plugin,
|
||||||
pluginCode: data.plugin_code,
|
pluginCode: data.plugin_code,
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ const Tasks: React.FC = () => {
|
|||||||
const { data: scheduledTasks = [], isLoading: tasksLoading } = useQuery<ScheduledTask[]>({
|
const { data: scheduledTasks = [], isLoading: tasksLoading } = useQuery<ScheduledTask[]>({
|
||||||
queryKey: ['scheduled-tasks'],
|
queryKey: ['scheduled-tasks'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data } = await axios.get('/api/scheduled-tasks/');
|
const { data } = await axios.get('/scheduled-tasks/');
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -108,7 +108,7 @@ const Tasks: React.FC = () => {
|
|||||||
const { data: eventAutomations = [], isLoading: automationsLoading } = useQuery<GlobalEventPlugin[]>({
|
const { data: eventAutomations = [], isLoading: automationsLoading } = useQuery<GlobalEventPlugin[]>({
|
||||||
queryKey: ['global-event-plugins'],
|
queryKey: ['global-event-plugins'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data } = await axios.get('/api/global-event-plugins/');
|
const { data } = await axios.get('/global-event-plugins/');
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -129,7 +129,7 @@ const Tasks: React.FC = () => {
|
|||||||
// Delete task
|
// Delete task
|
||||||
const deleteMutation = useMutation({
|
const deleteMutation = useMutation({
|
||||||
mutationFn: async (taskId: string) => {
|
mutationFn: async (taskId: string) => {
|
||||||
await axios.delete(`/api/scheduled-tasks/${taskId}/`);
|
await axios.delete(`/scheduled-tasks/${taskId}/`);
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['scheduled-tasks'] });
|
queryClient.invalidateQueries({ queryKey: ['scheduled-tasks'] });
|
||||||
@@ -143,7 +143,7 @@ const Tasks: React.FC = () => {
|
|||||||
// Toggle task active status
|
// Toggle task active status
|
||||||
const toggleActiveMutation = useMutation({
|
const toggleActiveMutation = useMutation({
|
||||||
mutationFn: async ({ taskId, status }: { taskId: string; status: 'ACTIVE' | 'PAUSED' }) => {
|
mutationFn: async ({ taskId, status }: { taskId: string; status: 'ACTIVE' | 'PAUSED' }) => {
|
||||||
await axios.patch(`/api/scheduled-tasks/${taskId}/`, { status });
|
await axios.patch(`/scheduled-tasks/${taskId}/`, { status });
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['scheduled-tasks'] });
|
queryClient.invalidateQueries({ queryKey: ['scheduled-tasks'] });
|
||||||
@@ -157,7 +157,7 @@ const Tasks: React.FC = () => {
|
|||||||
// Trigger task manually
|
// Trigger task manually
|
||||||
const triggerMutation = useMutation({
|
const triggerMutation = useMutation({
|
||||||
mutationFn: async (taskId: string) => {
|
mutationFn: async (taskId: string) => {
|
||||||
await axios.post(`/api/scheduled-tasks/${taskId}/trigger/`);
|
await axios.post(`/scheduled-tasks/${taskId}/trigger/`);
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success('Task triggered successfully');
|
toast.success('Task triggered successfully');
|
||||||
@@ -170,7 +170,7 @@ const Tasks: React.FC = () => {
|
|||||||
// Delete event automation
|
// Delete event automation
|
||||||
const deleteEventAutomationMutation = useMutation({
|
const deleteEventAutomationMutation = useMutation({
|
||||||
mutationFn: async (automationId: string) => {
|
mutationFn: async (automationId: string) => {
|
||||||
await axios.delete(`/api/global-event-plugins/${automationId}/`);
|
await axios.delete(`/global-event-plugins/${automationId}/`);
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['global-event-plugins'] });
|
queryClient.invalidateQueries({ queryKey: ['global-event-plugins'] });
|
||||||
@@ -184,7 +184,7 @@ const Tasks: React.FC = () => {
|
|||||||
// Toggle event automation active status
|
// Toggle event automation active status
|
||||||
const toggleEventAutomationMutation = useMutation({
|
const toggleEventAutomationMutation = useMutation({
|
||||||
mutationFn: async (automationId: string) => {
|
mutationFn: async (automationId: string) => {
|
||||||
await axios.post(`/api/global-event-plugins/${automationId}/toggle/`);
|
await axios.post(`/global-event-plugins/${automationId}/toggle/`);
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['global-event-plugins'] });
|
queryClient.invalidateQueries({ queryKey: ['global-event-plugins'] });
|
||||||
@@ -198,7 +198,7 @@ const Tasks: React.FC = () => {
|
|||||||
// Update event automation
|
// Update event automation
|
||||||
const updateEventAutomationMutation = useMutation({
|
const updateEventAutomationMutation = useMutation({
|
||||||
mutationFn: async ({ id, data }: { id: string; data: Partial<GlobalEventPlugin> }) => {
|
mutationFn: async ({ id, data }: { id: string; data: Partial<GlobalEventPlugin> }) => {
|
||||||
await axios.patch(`/api/global-event-plugins/${id}/`, data);
|
await axios.patch(`/global-event-plugins/${id}/`, data);
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['global-event-plugins'] });
|
queryClient.invalidateQueries({ queryKey: ['global-event-plugins'] });
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useSearchParams, useNavigate } from 'react-router-dom';
|
|||||||
import { CheckCircle, XCircle, Loader2, Mail, ShieldCheck } from 'lucide-react';
|
import { CheckCircle, XCircle, Loader2, Mail, ShieldCheck } from 'lucide-react';
|
||||||
import apiClient from '../api/client';
|
import apiClient from '../api/client';
|
||||||
import { deleteCookie } from '../utils/cookies';
|
import { deleteCookie } from '../utils/cookies';
|
||||||
|
import { getBaseDomain } from '../utils/domain';
|
||||||
|
|
||||||
type VerificationStatus = 'pending' | 'loading' | 'success' | 'error' | 'already_verified';
|
type VerificationStatus = 'pending' | 'loading' | 'success' | 'error' | 'already_verified';
|
||||||
|
|
||||||
@@ -115,7 +116,12 @@ const VerifyEmail: React.FC = () => {
|
|||||||
Your email address has been successfully verified. You can now sign in to your account.
|
Your email address has been successfully verified. You can now sign in to your account.
|
||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
onClick={() => window.location.href = '/login'}
|
onClick={() => {
|
||||||
|
const protocol = window.location.protocol;
|
||||||
|
const baseDomain = getBaseDomain();
|
||||||
|
const port = window.location.port ? `:${window.location.port}` : '';
|
||||||
|
window.location.href = `${protocol}//${baseDomain}${port}/login`;
|
||||||
|
}}
|
||||||
className="w-full px-4 py-3 bg-brand-500 text-white rounded-lg hover:bg-brand-600 transition-colors font-medium"
|
className="w-full px-4 py-3 bg-brand-500 text-white rounded-lg hover:bg-brand-600 transition-colors font-medium"
|
||||||
>
|
>
|
||||||
Go to Login
|
Go to Login
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ const EditPlatformUserModal: React.FC<EditPlatformUserModalProps> = ({
|
|||||||
// Update mutation
|
// Update mutation
|
||||||
const updateMutation = useMutation({
|
const updateMutation = useMutation({
|
||||||
mutationFn: async (data: any) => {
|
mutationFn: async (data: any) => {
|
||||||
const response = await apiClient.patch(`/api/platform/users/${user.id}/`, data);
|
const response = await apiClient.patch(`/platform/users/${user.id}/`, data);
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user