refactor(tenant): replace tier+username with email-first creation
- Remove tier from create tenant form (always defaults to STARTER, controlled via license minting) - Admin email is now the primary identity field - Username auto-derived from email local part, optionally overridable - Set primaryEmail on Logto user at creation (prevents invalid accounts) - Async tenant delete: PG/ClickHouse cleanup runs after commit instead of blocking the HTTP response - Remove legacy /server/* OIDC redirect URIs from bootstrap Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
48
ui/src/pages/vendor/CreateTenantPage.tsx
vendored
48
ui/src/pages/vendor/CreateTenantPage.tsx
vendored
@@ -5,8 +5,6 @@ import { useCreateTenant } from '../../api/vendor-hooks';
|
||||
import { errorMessage } from '../../api/client';
|
||||
import { toSlug } from '../../utils/slug';
|
||||
|
||||
const TIERS = ['STARTER', 'TEAM', 'BUSINESS', 'ENTERPRISE'];
|
||||
|
||||
export function CreateTenantPage() {
|
||||
const navigate = useNavigate();
|
||||
const { toast } = useToast();
|
||||
@@ -15,8 +13,9 @@ export function CreateTenantPage() {
|
||||
const [name, setName] = useState('');
|
||||
const [slug, setSlug] = useState('');
|
||||
const [slugTouched, setSlugTouched] = useState(false);
|
||||
const [tier, setTier] = useState('STARTER');
|
||||
const [adminEmail, setAdminEmail] = useState('');
|
||||
const [adminUsername, setAdminUsername] = useState('');
|
||||
const [usernameTouched, setUsernameTouched] = useState(false);
|
||||
const [adminPassword, setAdminPassword] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
@@ -25,11 +24,18 @@ export function CreateTenantPage() {
|
||||
}
|
||||
}, [name, slugTouched]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!usernameTouched && adminEmail.includes('@')) {
|
||||
setAdminUsername(adminEmail.substring(0, adminEmail.indexOf('@')).replace(/[^a-zA-Z0-9]/g, ''));
|
||||
}
|
||||
}, [adminEmail, usernameTouched]);
|
||||
|
||||
async function handleSubmit(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
try {
|
||||
const result = await createTenant.mutateAsync({
|
||||
name, slug, tier,
|
||||
name, slug,
|
||||
adminEmail: adminEmail || undefined,
|
||||
adminUsername: adminUsername || undefined,
|
||||
adminPassword: adminPassword || undefined,
|
||||
});
|
||||
@@ -71,32 +77,24 @@ export function CreateTenantPage() {
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField label="Tier" htmlFor="tenant-tier" required>
|
||||
<select
|
||||
id="tenant-tier"
|
||||
value={tier}
|
||||
onChange={(e) => setTier(e.target.value)}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '8px 12px',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 6,
|
||||
background: 'var(--bg-surface)',
|
||||
color: 'var(--text-primary)',
|
||||
fontSize: '0.875rem',
|
||||
}}
|
||||
>
|
||||
{TIERS.map((t) => (
|
||||
<option key={t} value={t}>{t}</option>
|
||||
))}
|
||||
</select>
|
||||
<FormField label="Admin Email" htmlFor="admin-email" hint="Initial tenant admin (owner role)">
|
||||
<Input
|
||||
id="admin-email"
|
||||
type="email"
|
||||
value={adminEmail}
|
||||
onChange={(e) => setAdminEmail(e.target.value)}
|
||||
placeholder="admin@acme.com"
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField label="Admin Username" htmlFor="admin-user" hint="Initial tenant admin (owner role). Alphanumeric only, no hyphens.">
|
||||
<FormField label="Username" htmlFor="admin-user" hint="Auto-generated from email, override if needed">
|
||||
<Input
|
||||
id="admin-user"
|
||||
value={adminUsername}
|
||||
onChange={(e) => setAdminUsername(e.target.value.replace(/[^a-zA-Z0-9]/g, ''))}
|
||||
onChange={(e) => {
|
||||
setAdminUsername(e.target.value.replace(/[^a-zA-Z0-9]/g, ''));
|
||||
setUsernameTouched(true);
|
||||
}}
|
||||
placeholder="admin"
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
@@ -95,7 +95,7 @@ export interface VendorTenantDetail {
|
||||
export interface CreateTenantRequest {
|
||||
name: string;
|
||||
slug: string;
|
||||
tier?: string;
|
||||
adminEmail?: string;
|
||||
adminUsername?: string;
|
||||
adminPassword?: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user