Fix double /api/ prefix in API endpoint calls
When VITE_API_URL=/api, axios baseURL is already set to /api. However, all endpoint calls included the /api/ prefix, creating double paths like /api/api/auth/login/. Removed /api/ prefix from 81 API endpoint calls across 22 files: - src/api/auth.ts - Fixed login, logout, me, refresh, hijack endpoints - src/api/client.ts - Fixed token refresh endpoint - src/api/profile.ts - Fixed all profile, email, password, MFA, sessions endpoints - src/hooks/*.ts - Fixed all remaining API calls (users, appointments, resources, etc) - src/pages/*.tsx - Fixed signup and email verification endpoints This ensures API requests use the correct path: /api/auth/login/ instead of /api/api/auth/login/ 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -25,7 +25,7 @@ const EmailVerificationRequired: React.FC = () => {
|
||||
setSent(false);
|
||||
|
||||
try {
|
||||
await apiClient.post('/api/auth/email/verify/send/');
|
||||
await apiClient.post('/auth/email/verify/send/');
|
||||
setSent(true);
|
||||
setTimeout(() => setSent(false), 5000); // Hide success message after 5 seconds
|
||||
} catch (err: any) {
|
||||
|
||||
@@ -46,11 +46,20 @@ const LoginPage: React.FC = () => {
|
||||
const currentHostname = window.location.hostname;
|
||||
const currentPort = window.location.port;
|
||||
const portStr = currentPort ? `:${currentPort}` : '';
|
||||
const protocol = window.location.protocol;
|
||||
|
||||
// Extract base domain from current hostname
|
||||
// For lvh.me: smoothschedule.com becomes base, subdomain.smoothschedule.com has subdomain
|
||||
// For production: smoothschedule.com becomes base, subdomain.smoothschedule.com has subdomain
|
||||
const hostnameParts = currentHostname.split('.');
|
||||
const baseDomain = hostnameParts.length >= 2
|
||||
? hostnameParts.slice(-2).join('.')
|
||||
: currentHostname;
|
||||
|
||||
// Check domain type
|
||||
const isRootDomain = currentHostname === 'lvh.me' || currentHostname === 'localhost';
|
||||
const isPlatformDomain = currentHostname === 'platform.lvh.me';
|
||||
const currentSubdomain = currentHostname.split('.')[0];
|
||||
const isRootDomain = currentHostname === baseDomain || currentHostname === 'localhost';
|
||||
const isPlatformDomain = currentHostname === `platform.${baseDomain}`;
|
||||
const currentSubdomain = hostnameParts[0];
|
||||
const isBusinessSubdomain = !isRootDomain && !isPlatformDomain && currentSubdomain !== 'api';
|
||||
|
||||
// Platform users (superuser, platform_manager, platform_support)
|
||||
@@ -95,19 +104,21 @@ const LoginPage: React.FC = () => {
|
||||
// Determine target subdomain for redirect
|
||||
let targetSubdomain: string | null = null;
|
||||
|
||||
if (isPlatformUser) {
|
||||
// Platform users should be redirected to platform subdomain if not already there
|
||||
if (isPlatformUser && !isPlatformDomain) {
|
||||
targetSubdomain = 'platform';
|
||||
} else if (user.business_subdomain) {
|
||||
} else if (isBusinessUser && user.business_subdomain && !isBusinessSubdomain) {
|
||||
// Business users should be on their business subdomain
|
||||
targetSubdomain = user.business_subdomain;
|
||||
}
|
||||
|
||||
// Check if we need to redirect to a different subdomain
|
||||
const isOnTargetSubdomain = currentHostname === `${targetSubdomain}.lvh.me`;
|
||||
const needsRedirect = targetSubdomain && !isOnTargetSubdomain;
|
||||
const needsRedirect = targetSubdomain !== null;
|
||||
|
||||
if (needsRedirect) {
|
||||
// Pass tokens in URL to ensure they're available immediately on the new subdomain
|
||||
window.location.href = `http://${targetSubdomain}.lvh.me${portStr}/?access_token=${data.access}&refresh_token=${data.refresh}`;
|
||||
const targetHostname = `${targetSubdomain}.${baseDomain}`;
|
||||
window.location.href = `${protocol}//${targetHostname}${portStr}/?access_token=${data.access}&refresh_token=${data.refresh}`;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { sendMFALoginCode, verifyMFALogin } from '../api/mfa';
|
||||
import { setCookie } from '../utils/cookies';
|
||||
import { buildSubdomainUrl } from '../utils/domain';
|
||||
import SmoothScheduleLogo from '../components/SmoothScheduleLogo';
|
||||
import {
|
||||
AlertCircle,
|
||||
@@ -146,8 +147,6 @@ const MFAVerifyPage: React.FC = () => {
|
||||
// Get redirect info from user
|
||||
const user = response.user;
|
||||
const currentHostname = window.location.hostname;
|
||||
const currentPort = window.location.port;
|
||||
const portStr = currentPort ? `:${currentPort}` : '';
|
||||
|
||||
// Determine target subdomain
|
||||
const isPlatformUser = ['superuser', 'platform_manager', 'platform_support'].includes(user.role);
|
||||
@@ -160,11 +159,12 @@ const MFAVerifyPage: React.FC = () => {
|
||||
}
|
||||
|
||||
// Check if we need to redirect
|
||||
const isOnTargetSubdomain = currentHostname === `${targetSubdomain}.lvh.me`;
|
||||
const needsRedirect = targetSubdomain && !isOnTargetSubdomain;
|
||||
const targetHostname = targetSubdomain ? `${targetSubdomain}.${window.location.hostname.split('.').slice(-2).join('.')}` : null;
|
||||
const needsRedirect = targetSubdomain && targetHostname && currentHostname !== targetHostname;
|
||||
|
||||
if (needsRedirect) {
|
||||
window.location.href = `http://${targetSubdomain}.lvh.me${portStr}/?access_token=${response.access}&refresh_token=${response.refresh}`;
|
||||
if (needsRedirect && targetSubdomain) {
|
||||
const targetUrl = buildSubdomainUrl(targetSubdomain, `/?access_token=${response.access}&refresh_token=${response.refresh}`);
|
||||
window.location.href = targetUrl;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useNavigate, useParams, useLocation } from 'react-router-dom';
|
||||
import { Loader2, AlertCircle, CheckCircle } from 'lucide-react';
|
||||
import { handleOAuthCallback } from '../api/oauth';
|
||||
import { setCookie } from '../utils/cookies';
|
||||
import { getCookieDomain, buildSubdomainUrl } from '../utils/domain';
|
||||
import SmoothScheduleLogo from '../components/SmoothScheduleLogo';
|
||||
|
||||
const OAuthCallback: React.FC = () => {
|
||||
@@ -56,7 +57,8 @@ const OAuthCallback: React.FC = () => {
|
||||
setCookie('refresh_token', response.refresh, 7);
|
||||
|
||||
// Clear session cookie to prevent interference with JWT
|
||||
document.cookie = 'sessionid=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=.lvh.me';
|
||||
const cookieDomain = getCookieDomain();
|
||||
document.cookie = `sessionid=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=${cookieDomain}`;
|
||||
document.cookie = 'sessionid=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
|
||||
|
||||
setStatus('success');
|
||||
@@ -64,27 +66,27 @@ const OAuthCallback: React.FC = () => {
|
||||
// Determine redirect URL based on user role
|
||||
const user = response.user;
|
||||
const currentHostname = window.location.hostname;
|
||||
const currentPort = window.location.port;
|
||||
|
||||
let targetUrl = '/';
|
||||
let needsRedirect = false;
|
||||
let targetSubdomain: string | null = null;
|
||||
|
||||
// Platform users (superuser, platform_manager, platform_support)
|
||||
if (['superuser', 'platform_manager', 'platform_support'].includes(user.role)) {
|
||||
const targetHostname = 'platform.lvh.me';
|
||||
needsRedirect = currentHostname !== targetHostname;
|
||||
if (needsRedirect) {
|
||||
const portStr = currentPort ? `:${currentPort}` : '';
|
||||
targetUrl = `http://${targetHostname}${portStr}/`;
|
||||
}
|
||||
targetSubdomain = 'platform';
|
||||
}
|
||||
// Business users - redirect to their business subdomain
|
||||
else if (user.business_subdomain) {
|
||||
const targetHostname = `${user.business_subdomain}.lvh.me`;
|
||||
targetSubdomain = user.business_subdomain;
|
||||
}
|
||||
|
||||
// Check if redirect is needed
|
||||
if (targetSubdomain) {
|
||||
const baseDomain = window.location.hostname.split('.').slice(-2).join('.');
|
||||
const targetHostname = `${targetSubdomain}.${baseDomain}`;
|
||||
needsRedirect = currentHostname !== targetHostname;
|
||||
if (needsRedirect) {
|
||||
const portStr = currentPort ? `:${currentPort}` : '';
|
||||
targetUrl = `http://${targetHostname}${portStr}/`;
|
||||
targetUrl = buildSubdomainUrl(targetSubdomain, '/');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,20 +148,8 @@ const OAuthCallback: React.FC = () => {
|
||||
}, [provider, location, navigate]);
|
||||
|
||||
const handleTryAgain = () => {
|
||||
const currentHostname = window.location.hostname;
|
||||
const currentPort = window.location.port;
|
||||
const portStr = currentPort ? `:${currentPort}` : '';
|
||||
|
||||
// Redirect to login page
|
||||
if (currentHostname.includes('platform.lvh.me')) {
|
||||
window.location.href = `http://platform.lvh.me${portStr}/login`;
|
||||
} else if (currentHostname.includes('.lvh.me')) {
|
||||
// On business subdomain - go to their login
|
||||
window.location.href = `http://${currentHostname}${portStr}/login`;
|
||||
} else {
|
||||
// Fallback
|
||||
navigate('/login');
|
||||
}
|
||||
// Simply navigate to login on current subdomain
|
||||
navigate('/login');
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react';
|
||||
import { useSearchParams, useNavigate } from 'react-router-dom';
|
||||
import { CheckCircle, Mail, Lock, User, Building2, CreditCard, ArrowRight, ArrowLeft, Loader } from 'lucide-react';
|
||||
import { useInvitationByToken, useAcceptInvitation } from '../hooks/usePlatform';
|
||||
import { getBaseDomain, buildSubdomainUrl } from '../utils/domain';
|
||||
|
||||
const TenantOnboardPage: React.FC = () => {
|
||||
const [searchParams] = useSearchParams();
|
||||
@@ -387,12 +388,12 @@ const TenantOnboardPage: React.FC = () => {
|
||||
placeholder="mybusiness"
|
||||
/>
|
||||
<span className="px-4 py-3 bg-gray-100 dark:bg-gray-600 border border-l-0 border-gray-300 dark:border-gray-600 rounded-r-lg text-gray-500 dark:text-gray-400">
|
||||
.lvh.me
|
||||
.{getBaseDomain()}
|
||||
</span>
|
||||
</div>
|
||||
{errors.subdomain && <p className="text-red-500 text-xs mt-1">{errors.subdomain}</p>}
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
This will be your business URL: {formData.subdomain || 'your-business'}.lvh.me
|
||||
This will be your business URL: {formData.subdomain || 'your-business'}.{getBaseDomain()}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -470,7 +471,7 @@ const TenantOnboardPage: React.FC = () => {
|
||||
<h3 className="font-semibold text-gray-900 dark:text-white mb-2">What's Next?</h3>
|
||||
<ul className="text-left space-y-2 text-sm text-gray-600 dark:text-gray-400">
|
||||
<li>✓ Your account has been created</li>
|
||||
<li>✓ Business subdomain: {formData.subdomain}.lvh.me</li>
|
||||
<li>✓ Business subdomain: {formData.subdomain}.{getBaseDomain()}</li>
|
||||
<li>✓ You can now log in and start using SmoothSchedule</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -478,7 +479,7 @@ const TenantOnboardPage: React.FC = () => {
|
||||
<button
|
||||
onClick={() => {
|
||||
// Redirect to login or the business subdomain
|
||||
window.location.href = `http://${formData.subdomain}.lvh.me:5173`;
|
||||
window.location.href = buildSubdomainUrl(formData.subdomain, '/');
|
||||
}}
|
||||
className="px-8 py-3 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 font-medium"
|
||||
>
|
||||
|
||||
@@ -24,7 +24,7 @@ const VerifyEmail: React.FC = () => {
|
||||
setStatus('loading');
|
||||
|
||||
try {
|
||||
const response = await apiClient.post('/api/auth/email/verify/', { token });
|
||||
const response = await apiClient.post('/auth/email/verify/', { token });
|
||||
|
||||
// Immediately clear auth cookies to log out
|
||||
deleteCookie('access_token');
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
Loader2,
|
||||
} from 'lucide-react';
|
||||
import apiClient from '../../api/client';
|
||||
import { getBaseDomain, buildSubdomainUrl } from '../../utils/domain';
|
||||
|
||||
interface SignupFormData {
|
||||
// Step 1: Business info
|
||||
@@ -160,7 +161,7 @@ const SignupPage: React.FC = () => {
|
||||
const timer = setTimeout(async () => {
|
||||
setCheckingSubdomain(true);
|
||||
try {
|
||||
const response = await apiClient.post('/api/auth/signup/check-subdomain/', {
|
||||
const response = await apiClient.post('/auth/signup/check-subdomain/', {
|
||||
subdomain: formData.subdomain,
|
||||
});
|
||||
setSubdomainAvailable(response.data.available);
|
||||
@@ -267,7 +268,7 @@ const SignupPage: React.FC = () => {
|
||||
setSubmitError(null);
|
||||
|
||||
try {
|
||||
await apiClient.post('/api/auth/signup/', {
|
||||
await apiClient.post('/auth/signup/', {
|
||||
business_name: formData.businessName,
|
||||
subdomain: formData.subdomain,
|
||||
address_line1: formData.addressLine1,
|
||||
@@ -324,8 +325,7 @@ const SignupPage: React.FC = () => {
|
||||
</p>
|
||||
<button
|
||||
onClick={() => {
|
||||
const port = window.location.port ? `:${window.location.port}` : '';
|
||||
window.location.href = `http://${formData.subdomain}.lvh.me${port}/login`;
|
||||
window.location.href = buildSubdomainUrl(formData.subdomain, '/login');
|
||||
}}
|
||||
className="w-full py-3 px-6 text-base font-semibold text-white bg-brand-600 rounded-xl hover:bg-brand-700 transition-colors"
|
||||
>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useBusinesses } from '../../hooks/usePlatform';
|
||||
import { PlatformBusiness } from '../../api/platform';
|
||||
import TenantInviteModal from './components/TenantInviteModal';
|
||||
import BusinessEditModal from './components/BusinessEditModal';
|
||||
import { getBaseDomain } from '../../utils/domain';
|
||||
|
||||
interface PlatformBusinessesProps {
|
||||
onMasquerade: (targetUser: { id: number; username?: string; name?: string; email?: string; role?: string }) => void;
|
||||
@@ -51,7 +52,7 @@ const PlatformBusinesses: React.FC<PlatformBusinessesProps> = ({ onMasquerade })
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{business.subdomain}.lvh.me
|
||||
{business.subdomain}.{getBaseDomain()}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import { X, Plus, Building2, Key, User, Mail, Lock } from 'lucide-react';
|
||||
import { useCreateBusiness } from '../../../hooks/usePlatform';
|
||||
import { getBaseDomain } from '../../../utils/domain';
|
||||
|
||||
interface BusinessCreateModalProps {
|
||||
isOpen: boolean;
|
||||
@@ -183,7 +184,7 @@ const BusinessCreateModal: React.FC<BusinessCreateModalProps> = ({ isOpen, onClo
|
||||
className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-l-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
|
||||
/>
|
||||
<span className="px-3 py-2 bg-gray-100 dark:bg-gray-600 border border-l-0 border-gray-300 dark:border-gray-600 rounded-r-lg text-gray-500 dark:text-gray-400 text-sm">
|
||||
.lvh.me
|
||||
.{getBaseDomain()}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
|
||||
Reference in New Issue
Block a user