14 Commits
Author SHA1 Message Date
azeem 64e3371a6b Merge pull request 'added the env' (#7) from ribai-patch-1 into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/docqube_frontend/pulls/7
2026-09-10 10:28:14 +00:00
momorew 796f807539 added the env 2026-09-10 15:55:05 +05:30
azeem b4e887bc60 Merge pull request 'ribai-patch-1' (#6) from ribai-patch-1 into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/docqube_frontend/pulls/6
2026-09-10 10:17:39 +00:00
momorew ad47431581 Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/docqube_frontend into ribai-patch-1 2026-09-10 15:45:52 +05:30
momorew 136644966b edit and delete handle in the saas 2026-09-10 15:45:36 +05:30
maskantech 99c6487c38 Merge pull request 'super amdin loign role creation in the saas' (#5) from ribai-patch-1 into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/docqube_frontend/pulls/5
2026-09-09 18:18:10 +00:00
momorew 2192206e43 super amdin loign role creation in the saas 2026-09-09 19:20:53 +05:30
azeem 32df114ee5 Merge pull request 'upload modal ui' (#4) from ribai-patch-1 into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/docqube_frontend/pulls/4
2026-09-08 13:06:06 +00:00
momorew 3ef3200221 upload modal ui 2026-09-08 18:35:07 +05:30
azeem 3f9cdb4a43 Merge pull request 'super amdin issue' (#3) from ribai-patch-1 into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/docqube_frontend/pulls/3
2026-09-08 11:22:06 +00:00
momorew 6b5ab22609 super amdin issue 2026-09-08 16:40:17 +05:30
azeem ddb29f25e4 Merge pull request 'email template assets' (#2) from ameenah into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/docqube_frontend/pulls/2
2026-09-08 09:52:50 +00:00
azeem 2f0150c92b Merge pull request 'Update .env.test' (#1) from ribai-patch-1 into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/docqube_frontend/pulls/1
2026-09-08 09:16:19 +00:00
ribai d64bd3d7f7 Update .env.test 2026-09-08 09:14:49 +00:00
10 changed files with 170 additions and 95 deletions
+2 -1
View File
@@ -4,4 +4,5 @@ VITE_PRODUCT_HUB_API_URL=https://product-hub.maskantech.in/api/v1
VITE_DOCUSEAL_URL=https://docuseal.com
VITE_DOCUSEAL_SCRIPT_URL=https://cdn.docuseal.com/js/form.js
VITE_PDF_EDITOR_DOCKER_URL=https://pdf-dev.maskantech.in/
VITE_SAAS_API_URL=https://saas-test-api.maskantech.in
VITE_SAAS_API_URL=https://saas-test.maskantech.in
VITE_LANDING_PAGE_URL=https://docqube-test.maskantech.in
+20 -1
View File
@@ -36,12 +36,31 @@ import ConversionWorkflow from '@/features/conversion/ConversionWorkflow';
import UpgradePlan from '@/features/subscription/SubscriptionPage';
import { useAuth } from '@/features/auth/context';
const RouteFallback = () => (
<div className="flex min-h-[60vh] items-center justify-center" role="status" aria-live="polite">
<div className="h-8 w-8 animate-spin rounded-full border-2 border-gray-300 border-t-transparent" />
<span className="sr-only">Loading</span>
</div>
);
const TenantDashboardRoute = ({ children }: { children: React.ReactElement }) => {
const { user, isLoading } = useAuth();
if (isLoading) {
return <RouteFallback />;
}
const isSuperadmin = Boolean(
user?.is_superadmin ||
user?.role === 'superadmin' ||
user?.accesses?.includes('superadmin.main.view')
);
if (isSuperadmin) {
return <Navigate to="/admin/dashboard" replace />;
}
return children;
};
// Scroll to top component
const ScrollToTop = () => {
const { pathname } = useLocation();
@@ -157,7 +176,7 @@ function AppContent() {
</RequireAuth>
}
>
<Route path="/dashboard" element={<DashboardMainView />} />
<Route path="/dashboard" element={<TenantDashboardRoute><DashboardMainView /></TenantDashboardRoute>} />
<Route path="/documents" element={<RequireAccess accessCode="document.read"><MyDocumentsView /></RequireAccess>} />
<Route
path="/new-conversion"
+5 -1
View File
@@ -14,7 +14,11 @@ const PublicRoute: React.FC<{ children: React.ReactElement }> = ({ children }) =
}
if (isAuthenticated) {
if (user?.accesses?.includes('superadmin.main.view')) {
if (
user?.is_superadmin ||
user?.role === 'superadmin' ||
user?.accesses?.includes('superadmin.main.view')
) {
return <Navigate to="/admin/dashboard" replace />;
}
return <Navigate to="/dashboard" replace />;
+1
View File
@@ -76,6 +76,7 @@ export const authAPI = {
saasSignInRes = await fetch(`${saasApiUrl}/api/auth/signin`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ email: credentials.email.trim(), password: credentials.password }),
});
} catch (e: any) {
+5 -14
View File
@@ -38,20 +38,11 @@ const LoginPage: React.FC = () => {
// Instant auth state update without hard page reload
await checkAuthStatus();
let isSuperadmin = Boolean(ssoSuccess.user?.is_superadmin);
try {
const profileRes = await fetch('/api/me/profile', { credentials: 'include' });
if (profileRes.ok) {
const profile = await profileRes.json();
if (
profile.accesses?.includes('superadmin.main.view') ||
profile.role === 'superadmin' ||
profile.is_superadmin
) {
isSuperadmin = true;
}
}
} catch {}
let isSuperadmin = Boolean(
ssoSuccess.user?.is_superadmin ||
ssoSuccess.user?.role === 'superadmin' ||
(Array.isArray(ssoSuccess.user?.permissions) && ssoSuccess.user.permissions.includes('superadmin.main.view'))
);
if (isSuperadmin) {
navigate('/admin/dashboard', { replace: true });
+3
View File
@@ -21,6 +21,7 @@ interface User {
role: string | null;
roles: RoleRef[];
accesses: string[];
is_superadmin?: boolean;
subscription_details?: {
plan_id?: string | null;
plan_name?: string | null;
@@ -143,6 +144,7 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
role: profile.role || null,
roles: profile.roles || [],
accesses: profile.accesses || [],
is_superadmin: Boolean(profile.is_superadmin || profile.role === 'superadmin' || profile.accesses?.includes('superadmin.main.view')),
subscription_details: profile.subscription_details || null,
preferred_language: getSafeLanguage(profile.preferred_language),
terms_accepted: profile.terms_accepted ?? false,
@@ -174,6 +176,7 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
role: profile.role || null,
roles: profile.roles || [],
accesses: profile.accesses || [],
is_superadmin: Boolean(profile.is_superadmin || profile.role === 'superadmin' || profile.accesses?.includes('superadmin.main.view')),
subscription_details: profile.subscription_details || null,
preferred_language: getSafeLanguage(profile.preferred_language),
terms_accepted: profile.terms_accepted ?? false,
+49 -67
View File
@@ -93,22 +93,22 @@ export const UploadModal: React.FC<UploadModalProps> = ({ isOpen, onClose, folde
if (!isOpen) return null;
return (
<div className="fixed inset-0 z-[150] flex items-center justify-center p-4 sm:p-6 animate-fade-in">
{}
<div className="fixed inset-0 z-[150] flex items-center justify-center p-3 sm:p-4 animate-fade-in">
{/* Backdrop */}
<div
className="absolute inset-0 bg-slate-950/40 backdrop-blur-md transition-all duration-500"
onClick={onClose}
/>
{}
{/* Modal Card */}
<div
className="relative w-full max-w-lg bg-white rounded-3xl shadow-[0_32px_64px_-12px_rgba(0,0,0,0.14)] overflow-hidden flex flex-col transform transition-all duration-300 scale-100 border border-slate-100/50"
style={{ maxHeight: '80vh' }}
className="relative w-full max-w-lg bg-white rounded-3xl shadow-[0_32px_64px_-12px_rgba(0,0,0,0.14)] overflow-hidden flex flex-col transform transition-all duration-300 scale-100 border border-slate-100/50 my-auto"
style={{ maxHeight: 'calc(100vh - 2rem)' }}
>
{}
<div className="flex items-center justify-between px-8 py-5 border-b border-slate-50 bg-white/80 backdrop-blur-md z-10">
{/* Header */}
<div className="flex items-center justify-between px-6 py-3.5 border-b border-slate-100 bg-white/80 backdrop-blur-md z-10 shrink-0">
<div>
<h3 className="text-lg font-bold text-slate-900 tracking-tight">Upload Files</h3>
<h3 className="text-base font-bold text-slate-900 tracking-tight">Upload Files</h3>
<div className="flex items-center gap-2 mt-0.5">
<p className="text-[10px] text-slate-400 font-bold uppercase tracking-widest">Secure Document Gateway</p>
<span className="w-1 h-1 bg-slate-300 rounded-full" />
@@ -117,89 +117,70 @@ export const UploadModal: React.FC<UploadModalProps> = ({ isOpen, onClose, folde
</div>
<button
onClick={onClose}
className={`group p-2 rounded-xl transition-all duration-300 hover:bg-slate-50 text-slate-400 hover:text-slate-900`}
className="group p-1.5 rounded-xl transition-all duration-300 hover:bg-slate-50 text-slate-400 hover:text-slate-900"
>
<X className="w-5 h-5" />
</button>
</div>
{}
<div className="flex-1 overflow-y-auto px-8 py-6 space-y-6 scrollbar-hide">
{}
{/* Content */}
<div className="flex-1 overflow-y-auto px-6 py-3.5 space-y-3 min-h-0">
{/* Dropzone */}
<div
onDragOver={(e) => { e.preventDefault(); setIsDragging(true); }}
onDragLeave={() => setIsDragging(false)}
onDrop={onDrop}
className={`group p-8 rounded-xl border-2 border-dashed transition-all duration-500 flex flex-col items-center justify-center gap-4 relative overflow-hidden
className={`group p-4 rounded-2xl border-2 border-dashed transition-all duration-300 flex flex-col items-center justify-center gap-2 relative overflow-hidden
${isDragging
? 'border-[#3b82f6] bg-blue-50/30 scale-[0.99] shadow-inner'
: 'border-[#e2e8f0] bg-white hover:bg-slate-50/50 hover:border-[#cbd5e1]'}
`}
>
<div className="flex justify-center -space-x-3 mb-2">
<div className="w-12 h-14 bg-blue-100 rounded border-2 border-white flex flex-col items-center justify-center rotate-[-15deg] shadow-sm">
<div className="w-6 h-0.5 bg-blue-300 mb-1"></div>
<div className="w-6 h-0.5 bg-blue-300 mb-1"></div>
<div className="w-4 h-0.5 bg-blue-300"></div>
<div className="flex justify-center -space-x-2 mb-0.5">
<div className="w-8 h-10 bg-blue-100 rounded border-2 border-white flex flex-col items-center justify-center rotate-[-15deg] shadow-sm">
<div className="w-4 h-0.5 bg-blue-300 mb-1"></div>
<div className="w-4 h-0.5 bg-blue-300 mb-1"></div>
<div className="w-3 h-0.5 bg-blue-300"></div>
</div>
<div className="w-14 h-16 bg-blue-500 rounded border-2 border-white flex flex-col items-center justify-center z-10 shadow-md">
<div className="w-8 h-0.5 bg-blue-200 mb-1.5"></div>
<div className="w-8 h-0.5 bg-blue-200 mb-1.5"></div>
<div className="w-6 h-0.5 bg-blue-200"></div>
<div className="w-9 h-11 bg-blue-500 rounded border-2 border-white flex flex-col items-center justify-center z-10 shadow-md">
<div className="w-5 h-0.5 bg-blue-200 mb-1"></div>
<div className="w-5 h-0.5 bg-blue-200 mb-1"></div>
<div className="w-3.5 h-0.5 bg-blue-200"></div>
</div>
<div className="w-12 h-14 bg-blue-100 rounded border-2 border-white flex flex-col items-center justify-center rotate-[15deg] shadow-sm">
<div className="w-6 h-0.5 bg-blue-300 mb-1"></div>
<div className="w-6 h-0.5 bg-blue-300 mb-1"></div>
<div className="w-4 h-0.5 bg-blue-300"></div>
<div className="w-8 h-10 bg-blue-100 rounded border-2 border-white flex flex-col items-center justify-center rotate-[15deg] shadow-sm">
<div className="w-4 h-0.5 bg-blue-300 mb-1"></div>
<div className="w-4 h-0.5 bg-blue-300 mb-1"></div>
<div className="w-3 h-0.5 bg-blue-300"></div>
</div>
</div>
<div className="text-center space-y-1">
<h3 className="text-[16px] font-bold text-slate-800">Drag and drop files here</h3>
<p className="text-[12px] text-slate-400 font-medium">Supported formats: .pdf, .doc, .docx, .xls, .xlsx, .txt, .csv, .jpg, .jpeg, .png, .gif, .webp, .svg</p>
<p className="text-[12px] text-slate-400 font-medium">Maximum file size: 30MB</p>
<div className="text-center space-y-0.5">
<h3 className="text-sm font-bold text-slate-800">Drag and drop files here</h3>
<p className="text-[11px] text-slate-400 font-medium max-w-xs leading-relaxed">Supported formats: .pdf, .doc, .docx, .xls, .xlsx, .txt, .csv, .jpg, .jpeg, .png, .gif, .webp, .svg</p>
<p className="text-[10px] text-slate-400 font-semibold">Maximum file size: 30MB</p>
</div>
<div className="w-full relative flex items-center justify-center my-4">
<div className="w-full relative flex items-center justify-center my-0.5">
<div className="absolute w-full border-t border-dashed border-slate-200"></div>
<span className="bg-white px-3 text-xs text-slate-400 relative z-10">or</span>
<span className="bg-white px-3 text-[10px] uppercase font-bold text-slate-400 relative z-10">or</span>
</div>
<div className="flex gap-4 w-full justify-center">
<div className="flex gap-3 w-full justify-center">
{/* My Computer */}
<button
type="button"
onClick={() => fileInputRef.current?.click()}
className="flex flex-col items-center justify-center gap-3 w-28 h-24 rounded-lg hover:bg-slate-50 transition-colors group"
className="flex items-center justify-center gap-2 px-4 py-2 rounded-xl border border-slate-200/80 bg-slate-50 hover:bg-blue-50 hover:border-blue-300 transition-all duration-200 group cursor-pointer shadow-xs"
>
<div className="w-12 h-12 flex items-center justify-center group-hover:scale-105 transition-transform">
<svg viewBox="0 0 24 24" fill="none" className="w-10 h-10 text-blue-500">
<path d="M4 6h16v10H4z" fill="#3b82f6" />
<path d="M3 5a2 2 0 012-2h14a2 2 0 012 2v12a2 2 0 01-2 2H3a2 2 0 01-2-2V5z" fill="#1e40af" />
<path d="M8 21h8v-2H8v2z" fill="#94a3b8" />
<path d="M4 6h16v10H4z" fill="#bfdbfe" />
<div className="w-6 h-6 flex items-center justify-center group-hover:scale-110 transition-transform">
<svg viewBox="0 0 24 24" fill="none" className="w-5 h-5 text-blue-600" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"/>
<line x1="8" y1="21" x2="16" y2="21"/>
<line x1="12" y1="17" x2="12" y2="21"/>
</svg>
</div>
<span className="text-[11px] font-semibold text-slate-700">My Computer</span>
<span className="text-xs font-bold text-slate-700 group-hover:text-blue-600 transition-colors">Browse from Computer</span>
</button>
{/* Google Drive button (Commented out) */}
{/*
<button
onClick={() => openDrivePicker()}
disabled={isDownloading}
className={`flex flex-col items-center justify-center gap-3 w-28 h-24 rounded-lg hover:bg-slate-50 transition-colors group ${isDownloading ? 'opacity-50 cursor-not-allowed' : ''}`}
>
<div className="w-12 h-12 flex items-center justify-center group-hover:scale-105 transition-transform">
{isDownloading ? (
<Loader2 className="w-8 h-8 animate-spin text-blue-500" />
) : (
<img src={GoogleDriveIcon} alt="Google Drive" className="w-9 h-9" />
)}
</div>
<span className="text-[11px] font-semibold text-slate-700">Google Drive</span>
</button>
*/}
</div>
<input
@@ -212,7 +193,7 @@ export const UploadModal: React.FC<UploadModalProps> = ({ isOpen, onClose, folde
/>
</div>
{}
{/* Upload Queue Section */}
{uploads.length > 0 && (
<div className="space-y-4 animate-in slide-in-from-bottom-4 duration-500">
<div className="flex items-center justify-between px-2">
@@ -288,8 +269,8 @@ export const UploadModal: React.FC<UploadModalProps> = ({ isOpen, onClose, folde
)}
</div>
{}
<div className="px-8 py-5 border-t border-slate-50 bg-slate-50/30 flex items-center justify-between">
{/* Footer */}
<div className="px-6 py-3.5 border-t border-slate-100 bg-slate-50/50 flex items-center justify-between shrink-0">
<div className="flex flex-col">
<span className="text-[9px] text-slate-400 font-black uppercase tracking-widest mb-0.5">Status Report</span>
<div className="flex items-center gap-1.5">
@@ -298,18 +279,19 @@ export const UploadModal: React.FC<UploadModalProps> = ({ isOpen, onClose, folde
{uploads.length === 0 ? 'Awaiting Input' : `${uploads.filter(u => u.status === 'completed' || u.status === 'ready').length} of ${uploads.length} Secured`}
</span>
</div>
</div> <button
</div>
<button
onClick={() => uploads.length > 0 && handleFinish()}
disabled={activeCount > 0 || hasVirus}
className={`px-6 py-2.5 text-[10px] font-black rounded-xl transition-all duration-500 tracking-[0.1em] flex items-center gap-2
className={`px-5 py-2 text-[10px] font-black rounded-xl transition-all duration-500 tracking-[0.1em] flex items-center gap-2
${(uploads.length > 0 && activeCount === 0)
? 'bg-emerald-600 text-white hover:bg-emerald-700 hover:-translate-y-1 shadow-2xl shadow-emerald-200'
? 'bg-emerald-600 text-white hover:bg-emerald-700 hover:-translate-y-0.5 shadow-lg shadow-emerald-200'
: activeCount > 0
? 'bg-slate-100 text-slate-300 cursor-not-allowed scale-95'
: hasVirus
? 'bg-rose-100 text-rose-400 cursor-not-allowed border border-rose-200'
: uploads.length > 0
? 'bg-blue-600 text-white hover:bg-blue-700 shadow-2xl shadow-blue-200 hover:-translate-y-1'
? 'bg-blue-600 text-white hover:bg-blue-700 shadow-lg shadow-blue-200 hover:-translate-y-0.5'
: 'bg-slate-200 text-slate-500 hover:bg-slate-300'}
`}
>
@@ -13,6 +13,7 @@ import ConfirmationModal from '@/components/custom/CustomConfirmationModal';
import { tenantApi, Tenant } from '@/services/tenant';
import { useAuth } from '@/features/auth/context';
import { useTranslation } from 'react-i18next';
import { getSaasFrontendUrl } from '@/lib/saasConfig';
const RoleManagement = () => {
const [roles, setRoles] = useState<Role[]>([]);
@@ -77,12 +78,17 @@ const RoleManagement = () => {
}, []);
const handleOpenCreate = () => {
if (isTenantAdmin) {
const saasUrl = getSaasFrontendUrl();
window.open(`${saasUrl}/roles/add`, '_blank');
return;
}
setIsEditing(false);
setIsViewModalOpen(false);
setRoleName('');
setRoleDescription('');
setSelectedAccessIds([]);
setSelectedTenantId(isTenantAdmin ? user!.tenant_id! : '');
setSelectedTenantId('');
setIsSuperadminRole(false);
setIsDefaultRole(false);
setIsModalOpen(true);
@@ -261,7 +267,7 @@ const RoleManagement = () => {
const isDefault = row.is_default;
const isGlobalSystemRole = !row.tenant_id;
const canModify = isCurrentUserSuperadmin || (!isDefault && !isGlobalSystemRole);
const canModify = !isTenantAdmin && (isCurrentUserSuperadmin || (!isDefault && !isGlobalSystemRole));
return (
<div className="flex gap-1">
@@ -274,7 +280,7 @@ const RoleManagement = () => {
>
<Eye className="w-4 h-4" />
</CustomButton>
{!isSuperadminRole && (
{!isTenantAdmin && !isSuperadminRole && (
<>
<CustomButton
variant="text"
@@ -321,6 +327,22 @@ const RoleManagement = () => {
</CustomButton>
</div>
{isTenantAdmin && (
<div className="mb-4 p-3 bg-blue-50 border border-blue-200 rounded-lg flex flex-wrap items-center justify-between gap-2 text-sm text-blue-800">
<div className="flex items-center gap-2">
<span>Role creation, editing, and deletion are centrally managed in SaaS Hub.</span>
</div>
<a
href={`${getSaasFrontendUrl()}/roles`}
target="_blank"
rel="noopener noreferrer"
className="font-semibold underline hover:text-blue-900 flex items-center gap-1"
>
Manage in SaaS Hub &rarr;
</a>
</div>
)}
<div className="flex-1 flex flex-col min-h-0">
<CustomTable
data={roles}
@@ -19,6 +19,7 @@ import { tenantApi, Tenant } from '@/services/tenant';
import { ConfigurationApi, LimitsSnapshot } from '@/features/superAdmin/settings/ConfigurationApi';
import { useAuth } from '@/features/auth/context';
import { useTranslation } from 'react-i18next';
import { getSaasFrontendUrl } from '@/lib/saasConfig';
export default function UserManagement() {
const [users, setUsers] = useState<UserListItem[]>([]);
@@ -166,6 +167,12 @@ export default function UserManagement() {
const openCreateModal = async () => {
if (isTenantAdmin) {
const saasUrl = getSaasFrontendUrl();
window.open(`${saasUrl}/users/add`, '_blank');
return;
}
setIsCreating(true);
setSelectedUser(null);
setFormName('');
@@ -180,14 +187,10 @@ export default function UserManagement() {
refreshLimits();
if (isTenantAdmin) {
await onTenantChanged(user!.tenant_id!);
} else {
setFormTenantId('');
setSelectedTenantActiveUsers(null);
setSelectedTenantUserLimit(null);
setAvailableRoles([]);
}
setFormTenantId('');
setSelectedTenantActiveUsers(null);
setSelectedTenantUserLimit(null);
setAvailableRoles([]);
setIsModalOpen(true);
};
@@ -702,6 +705,22 @@ export default function UserManagement() {
</div>
</div>
{isTenantAdmin && (
<div className="mb-6 p-3 bg-blue-50 border border-blue-200 rounded-lg flex flex-wrap items-center justify-between gap-2 text-sm text-blue-800">
<div className="flex items-center gap-2">
<span>User creation, editing, and deletion are centrally managed in SaaS Hub.</span>
</div>
<a
href={`${getSaasFrontendUrl()}/users`}
target="_blank"
rel="noopener noreferrer"
className="font-semibold underline hover:text-blue-900 flex items-center gap-1"
>
Manage in SaaS Hub &rarr;
</a>
</div>
)}
{(orgCreditStats || isLoading) && (
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8">
{isLoading && !orgCreditStats ? (
+33
View File
@@ -0,0 +1,33 @@
/**
* Helper to dynamically resolve the Central SaaS Management Frontend URL
* Supports local development, test environments, and production dynamically.
*/
export function getSaasFrontendUrl(): string {
const envSaasFrontend = (import.meta.env.VITE_SAAS_FRONTEND_URL as string | undefined);
if (envSaasFrontend && envSaasFrontend.trim()) {
return envSaasFrontend.trim().replace(/\/+$/, '');
}
if (typeof window !== 'undefined') {
const hostname = window.location.hostname;
if (hostname.includes('maskantech.in')) {
if (hostname.includes('test')) {
return 'https://saas-test.maskantech.in';
}
return 'https://saas.maskantech.in';
}
if (hostname === 'localhost' || hostname === '127.0.0.1') {
return 'http://localhost:5174';
}
}
const saasApi = (import.meta.env.VITE_SAAS_API_URL as string | undefined) || '';
if (saasApi.includes('8000') || saasApi.includes('localhost')) {
return 'http://localhost:5174';
}
if (saasApi.trim()) {
return saasApi.trim().replace(/\/+$/, '');
}
return 'http://localhost:5173';
}