EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
CustomerForm.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';
4// import removed: TenantSelector
5import '../styles/forms.css';
6import './CustomerForm.css';
7import { apiFetch } from '../lib/api';
8import { notifySuccess, notifyError } from '../utils/notifications';
9
10function CustomerForm() {
11 const navigate = useNavigate();
12 const { id } = useParams();
13 const [selectedTenantId, setSelectedTenantId] = useState('');
14 const [isRootTenant, setIsRootTenant] = useState(false);
15 const [tenants, setTenants] = useState([]);
16 const defaultForm = {
17 name: '',
18 contact_name: '',
19 email: '',
20 phone: '',
21 address: '',
22 city: '',
23 state: '',
24 postal_code: '',
25 country: '',
26 notes: '',
27 status: 'active',
28 billing_email: '',
29 tenant_id: ''
30 };
31 const [form, setForm] = useState(defaultForm);
32 const [loading, setLoading] = useState(false);
33 const [error, setError] = useState('');
34
35 useEffect(() => {
36 // Check if user is root tenant
37 const token = localStorage.getItem('token');
38 if (token) {
39 try {
40 const payload = token.split('.')[1];
41 const decoded = JSON.parse(atob(payload.replace(/-/g, '+').replace(/_/g, '/')));
42 const userTenantId = decoded.tenant_id || decoded.tenantId || '';
43 setIsRootTenant(userTenantId === '00000000-0000-0000-0000-000000000001');
44 } catch (e) {
45 console.error('Error decoding token:', e);
46 }
47 }
48
49 async function fetchCustomer() {
50 if (!id) return;
51 setLoading(true);
52 setError('');
53 try {
54 const res = await apiFetch(`/customers/${id}`, { method: 'GET', credentials: 'include' });
55 if (!res.ok) throw new Error('Failed to load customer');
56 const data = await res.json();
57 const cust = data.customer || data || {};
58 if (cust.tenant_id && !selectedTenantId) {
59 setSelectedTenantId(String(cust.tenant_id));
60 }
61 const normalized = Object.keys(defaultForm).reduce((acc, key) => {
62 acc[key] = cust[key] ?? defaultForm[key];
63 return acc;
64 }, {});
65 setForm(normalized);
66 } catch (err) {
67 console.error(err);
68 setError(err.message || 'Failed to load customer');
69 } finally {
70 setLoading(false);
71 }
72 }
73 fetchCustomer();
74 }, [id]);
75
76 useEffect(() => {
77 // Fetch tenants list for root tenant
78 if (isRootTenant) {
79 (async () => {
80 try {
81 const res = await apiFetch('/tenants', { method: 'GET' });
82 if (res.ok) {
83 const data = await res.json();
84 setTenants(data.tenants || []);
85 }
86 } catch (err) {
87 console.error('Error fetching tenants:', err);
88 }
89 })();
90 }
91 }, [isRootTenant]);
92
93 const handleSubmit = async (e) => {
94 e.preventDefault();
95 setLoading(true);
96 setError('');
97
98 if (!form.name?.trim()) {
99 setError('Company name is required');
100 setLoading(false);
101 return;
102 }
103
104 try {
105 const res = await apiFetch(`/customers${id ? `/${id}` : ''}`, {
106 method: id ? 'PUT' : 'POST',
107 headers: { 'Content-Type': 'application/json' },
108 credentials: 'include',
109 body: JSON.stringify(form),
110 });
111
112 const data = await res.json();
113
114 if (!res.ok) {
115 const errorMsg = data.error || 'Failed to save customer';
116 await notifyError('Save Failed', errorMsg);
117 throw new Error(errorMsg);
118 }
119
120 await notifySuccess(id ? 'Customer Updated' : 'Customer Created', `Customer has been ${id ? 'updated' : 'created'} successfully`);
121 navigate('/customers');
122 } catch (err) {
123 console.error(err);
124 setError(err.message || 'Failed to save customer');
125 } finally {
126 setLoading(false);
127 }
128 };
129
130 return (
131 <MainLayout>
132 <div className="page-content">
133 <div className="page-header">
134 <h2>{id ? 'Edit Customer' : 'Create Customer'}</h2>
135 </div>
136
137 {error && <div className="error-message">{error}</div>}
138
139
140 <form onSubmit={handleSubmit} className="form-container">
141 <div className="form-grid">
142 <div className="form-section">
143 <h3>Basic Information</h3>
144
145 {isRootTenant && (
146 <div className="form-row">
147 <div className="form-field">
148 <label>Tenant</label>
149 <select
150 value={form.tenant_id}
151 onChange={(e) => setForm({ ...form, tenant_id: e.target.value })}
152 >
153 <option value="">Select Tenant</option>
154 {tenants.map(tenant => (
155 <option key={tenant.tenant_id} value={tenant.tenant_id}>
156 {tenant.name}
157 </option>
158 ))}
159 </select>
160 <span className="field-hint">Assign this customer to a specific tenant (Root only)</span>
161 </div>
162 </div>
163 )}
164
165 <div className="form-row">
166 <div className="form-field">
167 <label>Company Name<span className="required">*</span></label>
168 <input
169 type="text"
170 value={form.name}
171 onChange={(e) => setForm({ ...form, name: e.target.value })}
172 required
173 />
174 <span className="field-hint">Required - The name of the company or organization</span>
175 </div>
176 <div className="form-field">
177 <label>Status</label>
178 <select value={form.status} onChange={(e) => setForm({ ...form, status: e.target.value })}>
179 <option value="active">Active</option>
180 <option value="inactive">Inactive</option>
181 </select>
182 <span className="field-hint">Defaults to Active if not specified</span>
183 </div>
184 </div>
185 </div>
186
187 <div className="form-section">
188 <h3>Contact Information</h3>
189 <div className="form-row">
190 <div className="form-field">
191 <label>Contact Name</label>
192 <input
193 type="text"
194 value={form.contact_name}
195 onChange={(e) => setForm({ ...form, contact_name: e.target.value })}
196 />
197 <span className="field-hint">Primary contact person's name</span>
198 </div>
199 <div className="form-field">
200 <label>Email</label>
201 <input
202 type="email"
203 value={form.email}
204 onChange={(e) => setForm({ ...form, email: e.target.value })}
205 />
206 </div>
207 <div className="form-field">
208 <label>Phone</label>
209 <input
210 type="tel"
211 value={form.phone}
212 onChange={(e) => setForm({ ...form, phone: e.target.value })}
213 />
214 </div>
215 </div>
216 <div className="form-row">
217 <div className="form-field">
218 <label>Billing Email</label>
219 <input
220 type="email"
221 value={form.billing_email}
222 onChange={(e) => setForm({ ...form, billing_email: e.target.value })}
223 />
224 <span className="field-hint">Email address for invoices and billing communications</span>
225 </div>
226 </div>
227 </div>
228
229 <div className="form-section">
230 <h3>Address</h3>
231 <div className="form-row">
232 <div className="form-field">
233 <label>Street Address</label>
234 <input
235 type="text"
236 value={form.address}
237 onChange={(e) => setForm({ ...form, address: e.target.value })}
238 />
239 </div>
240 </div>
241 <div className="form-row">
242 <div className="form-field">
243 <label>City</label>
244 <input
245 type="text"
246 value={form.city}
247 onChange={(e) => setForm({ ...form, city: e.target.value })}
248 />
249 </div>
250 <div className="form-field">
251 <label>State/Province</label>
252 <input
253 type="text"
254 value={form.state}
255 onChange={(e) => setForm({ ...form, state: e.target.value })}
256 />
257 </div>
258 <div className="form-field">
259 <label>Postal Code</label>
260 <input
261 type="text"
262 value={form.postal_code}
263 onChange={(e) => setForm({ ...form, postal_code: e.target.value })}
264 />
265 </div>
266 </div>
267 <div className="form-row">
268 <div className="form-field">
269 <label>Country</label>
270 <input
271 type="text"
272 value={form.country}
273 onChange={(e) => setForm({ ...form, country: e.target.value })}
274 />
275 </div>
276 </div>
277 </div>
278
279 <div className="form-section">
280 <h3>Additional Information</h3>
281 <div className="form-field">
282 <label>Notes</label>
283 <textarea
284 value={form.notes}
285 onChange={(e) => setForm({ ...form, notes: e.target.value })}
286 rows={4}
287 />
288 <span className="field-hint">Any additional notes or comments about the customer</span>
289 </div>
290 </div>
291 </div>
292
293 <div className="form-actions">
294 <button type="submit" disabled={loading} className="btn">
295 {loading ? 'Saving...' : (<><span className="material-symbols-outlined">save</span> Save</>)}
296 </button>
297 <button type="button" className="btn" onClick={() => navigate('/customers')}>
298 <span className="material-symbols-outlined">close</span> Cancel
299 </button>
300 </div>
301 </form>
302 </div>
303 </MainLayout>
304 );
305}
306
307export default CustomerForm;