1import { useState, useEffect } from 'react';
2import { apiFetch } from '../../lib/api';
4export default function TabWordPress() {
5 const [sites, setSites] = useState([]);
6 const [loading, setLoading] = useState(true);
7 const [error, setError] = useState('');
8 const [showCreateForm, setShowCreateForm] = useState(false);
9 const [creating, setCreating] = useState(false);
10 const [customers, setCustomers] = useState([]);
13 const [formData, setFormData] = useState({
27 const fetchSites = async () => {
31 const res = await apiFetch('/wordpress/sites');
33 const data = await res.json();
34 setSites(data.sites || []);
36 const errorData = await res.json();
37 setError(errorData.error || 'Failed to fetch WordPress sites');
40 console.error('Error fetching sites:', err);
41 setError(err.message || 'Failed to load sites');
47 const fetchCustomers = async () => {
49 const res = await apiFetch('/customers');
51 const data = await res.json();
52 setCustomers(data.customers || []);
55 console.error('Error fetching customers:', err);
59 const handleInputChange = (e) => {
60 const { name, value } = e.target;
61 setFormData(prev => ({
67 const handleSubmit = async (e) => {
73 const res = await apiFetch('/wordpress/create', {
75 headers: { 'Content-Type': 'application/json' },
76 body: JSON.stringify(formData)
80 const data = await res.json();
81 alert(`WordPress site created successfully!\nApp ID: ${data.appId}\nURL: ${data.url || 'Deploying...'}`);
82 setShowCreateForm(false);
93 const errorData = await res.json();
94 setError(errorData.error || 'Failed to create WordPress site');
97 console.error('Error creating site:', err);
98 setError(err.message || 'Failed to create WordPress site');
104 const getStatusColor = (status) => {
106 'running': 'success',
110 'stopped': 'secondary'
112 return statusMap[status?.toLowerCase()] || 'secondary';
115 const formatTime = (date) => {
116 if (!date) return '';
117 return new Date(date).toLocaleString();
122 <div style={{ padding: '40px', textAlign: 'center' }}>
123 <div className="spinner"></div>
129 <div style={{ padding: '20px' }}>
130 <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px' }}>
132 <h2>WordPress Sites</h2>
133 <p style={{ color: 'var(--text-secondary)', marginTop: '8px' }}>
134 Create and manage WordPress sites on DigitalOcean
138 onClick={() => setShowCreateForm(!showCreateForm)}
139 className="btn btn-primary"
140 style={{ display: 'flex', alignItems: 'center', gap: '8px' }}
142 <span className="material-symbols-outlined">
143 {showCreateForm ? 'close' : 'add'}
145 {showCreateForm ? 'Cancel' : 'Create WordPress Site'}
150 <div className="card" style={{ padding: '16px', marginBottom: '20px', background: 'var(--error-bg)', border: '1px solid var(--error-border)' }}>
151 <div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
152 <span className="material-symbols-outlined" style={{ color: 'var(--error-text)' }}>
155 <span style={{ fontSize: '14px', color: 'var(--error-text)' }}>{error}</span>
162 <div className="card" style={{ padding: '24px', marginBottom: '20px' }}>
163 <h3 style={{ marginBottom: '20px' }}>Create New WordPress Site</h3>
164 <form onSubmit={handleSubmit}>
165 <div style={{ display: 'grid', gap: '16px', gridTemplateColumns: '1fr 1fr' }}>
167 <label style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
173 value={formData.siteName}
174 onChange={handleInputChange}
176 placeholder="my-awesome-site"
177 style={{ width: '100%', padding: '8px 12px', border: '1px solid var(--border)', borderRadius: '4px' }}
179 <small style={{ color: 'var(--text-secondary)', fontSize: '12px' }}>
180 Lowercase letters, numbers, and hyphens only
185 <label style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
190 value={formData.customerId}
191 onChange={handleInputChange}
192 style={{ width: '100%', padding: '8px 12px', border: '1px solid var(--border)', borderRadius: '4px' }}
194 <option value="">Select customer (optional)</option>
195 {customers.map(customer => (
196 <option key={customer.customer_id} value={customer.customer_id}>
204 <label style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
210 value={formData.domain}
211 onChange={handleInputChange}
212 placeholder="example.com"
213 style={{ width: '100%', padding: '8px 12px', border: '1px solid var(--border)', borderRadius: '4px' }}
215 <small style={{ color: 'var(--text-secondary)', fontSize: '12px' }}>
216 Leave empty to use DO App Platform domain
221 <label style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
227 value={formData.dbPassword}
228 onChange={handleInputChange}
230 placeholder="Strong password"
231 style={{ width: '100%', padding: '8px 12px', border: '1px solid var(--border)', borderRadius: '4px' }}
236 <label style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
237 DO Spaces Access Key *
242 value={formData.spacesKey}
243 onChange={handleInputChange}
245 placeholder="DO00..."
246 style={{ width: '100%', padding: '8px 12px', border: '1px solid var(--border)', borderRadius: '4px' }}
251 <label style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
252 DO Spaces Secret Key *
257 value={formData.spacesSecret}
258 onChange={handleInputChange}
260 placeholder="Secret key"
261 style={{ width: '100%', padding: '8px 12px', border: '1px solid var(--border)', borderRadius: '4px' }}
266 <div style={{ marginTop: '24px', display: 'flex', gap: '12px', justifyContent: 'flex-end' }}>
269 onClick={() => setShowCreateForm(false)}
277 className="btn btn-primary"
278 style={{ display: 'flex', alignItems: 'center', gap: '8px' }}
282 <span className="spinner" style={{ width: '16px', height: '16px' }}></span>
287 <span className="material-symbols-outlined">rocket_launch</span>
288 Create WordPress Site
298 {sites.length === 0 && !showCreateForm ? (
299 <div className="card" style={{ padding: '60px 20px', textAlign: 'center' }}>
300 <span className="material-symbols-outlined" style={{ fontSize: '64px', marginBottom: '16px', display: 'block', opacity: 0.5, color: 'var(--text-secondary)' }}>
303 <p style={{ color: 'var(--text-secondary)' }}>No WordPress sites found</p>
304 <p style={{ fontSize: '14px', color: 'var(--text-tertiary)', marginTop: '8px' }}>
305 Create your first WordPress site to get started
309 <div style={{ display: 'grid', gap: '20px', gridTemplateColumns: 'repeat(auto-fill, minmax(350px, 1fr))' }}>
310 {sites.map((site) => (
311 <div key={site.app_id || site.id} className="card">
312 <div style={{ padding: '16px', borderBottom: '1px solid var(--border)' }}>
313 <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', marginBottom: '12px' }}>
315 <h3 style={{ margin: 0, marginBottom: '4px' }}>{site.name}</h3>
316 {site.customer_name && (
317 <p style={{ fontSize: '13px', color: 'var(--text-secondary)', margin: 0 }}>
322 <span className={`badge badge-${getStatusColor(site.status)}`}>
323 {site.status || 'unknown'}
331 rel="noopener noreferrer"
334 color: 'var(--primary)',
335 textDecoration: 'none',
337 alignItems: 'center',
341 <span>{site.url}</span>
342 <span className="material-symbols-outlined" style={{ fontSize: '16px' }}>
349 <div style={{ padding: '16px' }}>
350 <div style={{ display: 'grid', gap: '8px', fontSize: '13px' }}>
351 <div style={{ display: 'flex', justifyContent: 'space-between' }}>
352 <span style={{ color: 'var(--text-secondary)' }}>Region:</span>
353 <span>{site.region || 'syd1'}</span>
355 <div style={{ display: 'flex', justifyContent: 'space-between' }}>
356 <span style={{ color: 'var(--text-secondary)' }}>Created:</span>
357 <span>{formatTime(site.created_at)}</span>
360 <div style={{ display: 'flex', justifyContent: 'space-between' }}>
361 <span style={{ color: 'var(--text-secondary)' }}>App ID:</span>
362 <span style={{ fontFamily: 'monospace', fontSize: '11px' }}>
369 <div style={{ marginTop: '16px', display: 'flex', gap: '8px' }}>
371 className="btn btn-sm"
372 onClick={() => window.open(`https://cloud.digitalocean.com/apps/${site.app_id}`, '_blank')}
373 disabled={!site.app_id}
374 style={{ flex: 1, fontSize: '12px' }}
376 <span className="material-symbols-outlined" style={{ fontSize: '16px' }}>
382 className="btn btn-sm"
383 onClick={() => fetchSites()}
384 style={{ fontSize: '12px' }}
386 <span className="material-symbols-outlined" style={{ fontSize: '16px' }}>