EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
TenantForm.jsx
Go to the documentation of this file.
1import { useState, useEffect } from 'react';
2import { useNavigate, useParams } from 'react-router-dom';
3import MainLayout from '../components/Layout/MainLayout';
4import '../styles/forms.css';
5import { apiFetch } from '../lib/api';
6
7function TenantForm() {
8 const navigate = useNavigate();
9 const { id } = useParams();
10 const [form, setForm] = useState({
11 name: '',
12 subdomain: '',
13 status: 'active'
14 });
15 const [loading, setLoading] = useState(false);
16 const [error, setError] = useState('');
17
18 useEffect(() => {
19 if (id) {
20 fetchTenant();
21 }
22 }, [id]);
23
24 async function fetchTenant() {
25 setLoading(true);
26 setError('');
27 try {
28 const token = localStorage.getItem('token');
29 const res = await apiFetch(`/tenants/${id}`, {
30 headers: {
31 Authorization: `Bearer ${token}`
32 }
33 });
34
35 if (!res.ok) throw new Error('Failed to load tenant');
36
37 const data = await res.json();
38 setForm({
39 name: data.tenant.name || '',
40 subdomain: data.tenant.subdomain || '',
41 status: data.tenant.status || 'active'
42 });
43 } catch (err) {
44 console.error(err);
45 setError(err.message || 'Failed to load tenant');
46 } finally {
47 setLoading(false);
48 }
49 }
50
51 async function handleSubmit(e) {
52 e.preventDefault();
53 setLoading(true);
54 setError('');
55
56 if (!form.name?.trim()) {
57 setError('Tenant name is required');
58 setLoading(false);
59 return;
60 }
61
62 if (!id && !form.subdomain?.trim()) {
63 setError('Subdomain is required');
64 setLoading(false);
65 return;
66 }
67
68 try {
69 const token = localStorage.getItem('token');
70 const url = id
71 ? `/tenants/${id}`
72 : '/tenants';
73
74 const body = id
75 ? { name: form.name, status: form.status }
76 : { name: form.name, subdomain: form.subdomain };
77
78 const res = await apiFetch(url, {
79 method: id ? 'PUT' : 'POST',
80 headers: {
81 'Content-Type': 'application/json',
82 Authorization: `Bearer ${token}`
83 },
84 body: JSON.stringify(body)
85 });
86
87 const data = await res.json();
88
89 if (!res.ok) {
90 throw new Error(data.error || 'Failed to save tenant');
91 }
92
93 // After creation or update, go back to tenants list
94 navigate('/tenants');
95 } catch (err) {
96 console.error(err);
97 setError(err.message || 'Failed to save tenant');
98 } finally {
99 setLoading(false);
100 }
101 }
102
103 return (
104 <MainLayout>
105 <div className="page-content">
106 <div className="page-header">
107 <h2>{id ? 'Edit Tenant' : 'Create Tenant'}</h2>
108 </div>
109
110 {error && <div className="error-message">{error}</div>}
111
112 <form onSubmit={handleSubmit} className="form-container">
113 <div className="form-grid">
114 <div className="form-section">
115 <h3>Tenant Information</h3>
116
117 <div className="form-row">
118 <div className="form-field">
119 <label>Tenant Name<span className="required">*</span></label>
120 <input
121 id="tenant-name"
122 name="tenant-name"
123 type="text"
124 value={form.name}
125 onChange={(e) => setForm({ ...form, name: e.target.value })}
126 required
127 placeholder="e.g., Acme Corporation"
128 />
129 <span className="field-hint">The name of the organization/company</span>
130 </div>
131 </div>
132
133 <div className="form-row">
134 <div className="form-field">
135 <label>
136 Tenant Code<span className="required">*</span>
137 {id && <span style={{ color: 'var(--text-muted)', fontWeight: 'normal' }}> (Cannot be changed)</span>}
138 </label>
139 <input
140 id="tenant-code"
141 name="tenant-code"
142 type="text"
143 value={form.subdomain}
144 onChange={(e) => setForm({ ...form, subdomain: e.target.value.toLowerCase() })}
145 required={!id}
146 disabled={!!id}
147 placeholder="e.g., acme"
148 pattern="[a-z0-9][a-z0-9-]*[a-z0-9]"
149 />
150 <span className="field-hint">
151 Lowercase letters, numbers, and hyphens only.
152 {!id && ' This will be used as a unique tenant code.'}
153 </span>
154 </div>
155 </div>
156
157 <div className="form-row">
158 <div className="form-field">
159 <label>Status</label>
160 <select
161 value={form.status}
162 onChange={(e) => setForm({ ...form, status: e.target.value })}
163 >
164 <option value="active">Active</option>
165 <option value="suspended">Suspended</option>
166 <option value="inactive">Inactive</option>
167 </select>
168 <span className="field-hint">
169 Active: Tenant can access the system.
170 Suspended: Temporary block.
171 Inactive: Permanently disabled.
172 </span>
173 </div>
174 </div>
175
176 {!id && (
177 <div className="info-box" style={{
178 padding: '16px',
179 borderRadius: '8px',
180 backgroundColor: 'var(--bg-secondary)',
181 border: '1px solid var(--border)',
182 marginTop: '16px'
183 }}>
184 <h4 style={{ marginTop: 0, display: 'flex', alignItems: 'center', gap: '8px' }}>
185 <span className="material-symbols-outlined">info</span>
186 Next Steps After Creation
187 </h4>
188 <ul style={{ marginBottom: 0, paddingLeft: '20px' }}>
189 <li>Create an owner user for this tenant</li>
190 <li>Configure tenant-specific settings</li>
191 <li>Send invitation to the tenant admin</li>
192 </ul>
193 </div>
194 )}
195 </div>
196 </div>
197
198 <div className="form-actions">
199 <button type="submit" disabled={loading} className="btn">
200 {loading ? 'Saving...' : (
201 <>
202 <span className="material-symbols-outlined">save</span>
203 {id ? 'Update' : 'Create'} Tenant
204 </>
205 )}
206 </button>
207 <button type="button" className="btn" onClick={() => navigate('/tenants')}>
208 <span className="material-symbols-outlined">close</span> Cancel
209 </button>
210 </div>
211 </form>
212 </div>
213 </MainLayout>
214 );
215}
216
217export default TenantForm;