Skip to content

Instantly share code, notes, and snippets.

@swdevbali
Created October 13, 2025 03:46
Show Gist options
  • Save swdevbali/a00060845e6149c2d694e3deb1852b60 to your computer and use it in GitHub Desktop.
Save swdevbali/a00060845e6149c2d694e3deb1852b60 to your computer and use it in GitHub Desktop.
'use client';
import { useState, useEffect, Suspense } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import {
Building2,
Rocket,
Users,
Video,
ArrowRight,
Loader2,
UserPlus,
Plus,
Mail,
ArrowLeft,
Clock,
AlertCircle
} from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { toast } from 'sonner';
import { OrganizationServiceClient } from '@/lib/services/organizationServiceClient';
import { createClient } from '@/lib/supabase/client';
function CreateNewTenantContent() {
const router = useRouter();
const searchParams = useSearchParams();
const [loading, setLoading] = useState(false);
const [checkingPermissions, setCheckingPermissions] = useState(true);
const [isSuperAdmin, setIsSuperAdmin] = useState(false);
// Create organization fields
const [organizationName, setOrganizationName] = useState('');
const [website, setWebsite] = useState('');
useEffect(() => {
checkSuperAdminStatus();
}, []);
const checkSuperAdminStatus = async () => {
try {
const supabase = createClient();
const { data: { user } } = await supabase.auth.getUser();
if (!user) {
// Not authenticated - redirect to login
router.push('/');
return;
}
// Check if user is superadmin
const response = await fetch('/api/users/roles/current');
if (response.ok) {
const data = await response.json();
const hasSuperAdmin = data.roles?.includes('superadmin') || false;
setIsSuperAdmin(hasSuperAdmin);
if (!hasSuperAdmin) {
// Non-superadmin cannot create tenants
toast.error('Only super administrators can create tenant organizations');
router.push('/dashboard');
return;
}
}
// Superadmin confirmed - ready to create tenant
setCheckingPermissions(false);
} catch (error) {
console.error('Error checking superadmin status:', error);
setCheckingPermissions(false);
}
};
const handleCreateOrganization = async () => {
if (!organizationName.trim()) {
toast.error('Please enter an organization name');
return;
}
setLoading(true);
try {
const response = await fetch('/api/organizations/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: organizationName,
website: website || null,
}),
});
const data = await response.json();
console.log('Create organization response:', response.status, data);
if (!response.ok || !data.success) {
throw new Error(data.message || 'Failed to create organization');
}
toast.success('Organization created successfully!');
// Set as current organization
if (data.organization?.id) {
console.log('Setting current organization:', data.organization.id);
OrganizationServiceClient.setCurrentOrganization(data.organization.id);
}
// Redirect to dashboard after successful creation
console.log('Redirecting to dashboard...');
setTimeout(() => {
router.push('/dashboard');
}, 1000);
} catch (error) {
console.error('Error creating organization:', error);
toast.error(error instanceof Error ? error.message : 'Failed to create organization');
} finally {
setLoading(false);
}
};
if (checkingPermissions) {
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center">
<div className="flex flex-col items-center">
<Loader2 className="h-8 w-8 animate-spin text-blue-600 mb-4" />
<p className="text-gray-600">Verifying permissions...</p>
</div>
</div>
);
}
// Only superadmins should reach this page
// Non-superadmins are redirected by middleware and checkSuperAdminStatus
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center p-4">
<div className="max-w-lg w-full">
<div className="text-center mb-8">
<div className="inline-flex items-center justify-center w-16 h-16 bg-blue-600 rounded-full mb-4">
<Building2 className="w-8 h-8 text-white" />
</div>
<h1 className="text-3xl font-bold text-gray-900 mb-2">Create Tenant</h1>
<p className="text-gray-600">Set up a new tenant workspace for digital signage management</p>
</div>
<div className="bg-white rounded-2xl shadow-xl p-8">
<div className="space-y-6">
<div>
<Label htmlFor="org-name" className="text-sm font-medium text-gray-700">
Tenant Organization Name *
</Label>
<Input
id="org-name"
type="text"
placeholder="e.g., Acme Corporation"
value={organizationName}
onChange={(e) => setOrganizationName(e.target.value)}
className="mt-1"
disabled={loading}
/>
<p className="mt-1 text-xs text-gray-500">
The name of the tenant organization you're creating
</p>
</div>
<div>
<Label htmlFor="website" className="text-sm font-medium text-gray-700">
Website (optional)
</Label>
<Input
id="website"
type="url"
placeholder="https://example.com"
value={website}
onChange={(e) => setWebsite(e.target.value)}
className="mt-1"
disabled={loading}
/>
<p className="mt-1 text-xs text-gray-500">
The tenant organization's website
</p>
</div>
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
<h3 className="font-semibold text-blue-900 mb-2">Tenant Organization Features:</h3>
<ul className="space-y-1 text-sm text-blue-800">
<li>• Isolated data and content</li>
<li>• Dedicated device management</li>
<li>• Separate user management</li>
<li>• Custom venue configurations</li>
<li>• Independent license allocation</li>
</ul>
</div>
<div className="flex gap-3">
<Button
variant="outline"
onClick={() => router.push('/dashboard')}
disabled={loading}
className="flex-1"
>
Cancel
</Button>
<Button
onClick={handleCreateOrganization}
disabled={loading || !organizationName.trim()}
className="flex-1 bg-blue-600 hover:bg-blue-700 text-white"
>
{loading ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Creating...
</>
) : (
<>
Create Tenant
<ArrowRight className="ml-2 h-4 w-4" />
</>
)}
</Button>
</div>
</div>
</div>
</div>
</div>
);
}
export default function CreateNewTenantPage() {
return (
<Suspense fallback={
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center">
<div className="flex flex-col items-center">
<Loader2 className="h-8 w-8 animate-spin text-blue-600 mb-4" />
<p className="text-gray-600">Loading...</p>
</div>
</div>
}>
<CreateNewTenantContent />
</Suspense>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment