gmoonc 0.0.14 → 0.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -63,6 +63,12 @@ The logo is installed at `src/gmoonc/assets/gmoonc-logo.png`. You can replace it
|
|
|
63
63
|
|
|
64
64
|
## Changelog
|
|
65
65
|
|
|
66
|
+
### 0.0.15
|
|
67
|
+
- Feature: Complete Account page implementation (equivalent to Sicoop reference)
|
|
68
|
+
- Feature: Personal Information, Account Security, and Change Email sections
|
|
69
|
+
- Feature: Password strength indicator and recommendations
|
|
70
|
+
- Feature: Responsive grid layout (desktop: 1+2 columns, mobile: stack)
|
|
71
|
+
|
|
66
72
|
### 0.0.14
|
|
67
73
|
- Fix: Auth pages now have proper theme scoping (gmoonc-root class added)
|
|
68
74
|
- Fix: Logo uses Vite-friendly import instead of public path
|
package/dist/index.cjs
CHANGED
|
@@ -701,7 +701,7 @@ function patchBrowserRouter(consumerDir, basePath, dryRun) {
|
|
|
701
701
|
|
|
702
702
|
// src/cli/index.ts
|
|
703
703
|
var program = new import_commander.Command();
|
|
704
|
-
program.name("gmoonc").description("Goalmoon Ctrl (gmoonc): Install complete dashboard into your React project").version("0.0.
|
|
704
|
+
program.name("gmoonc").description("Goalmoon Ctrl (gmoonc): Install complete dashboard into your React project").version("0.0.15").option("--base <path>", "Base path for dashboard routes", "/app").option("--skip-router-patch", "Skip automatic router integration (only copy files and inject CSS)").option("--dry-run", "Show what would be done without making changes").action(async (options) => {
|
|
705
705
|
try {
|
|
706
706
|
logInfo("\u{1F680} Starting gmoonc installer...");
|
|
707
707
|
logInfo("\u{1F4E6} Installing complete dashboard into your React project\n");
|
package/package.json
CHANGED
|
@@ -1,172 +1,480 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useState, useCallback } from 'react';
|
|
2
|
+
import { useNavigate } from 'react-router-dom';
|
|
2
3
|
import { useGMooncSession } from '../../../session/GMooncSessionContext';
|
|
3
|
-
import { User, Mail,
|
|
4
|
+
import { User, Mail, Lock, ArrowLeft, Save, Key } from 'lucide-react';
|
|
4
5
|
|
|
5
6
|
export function GMooncOfficeAccountPage() {
|
|
6
7
|
const { user } = useGMooncSession();
|
|
8
|
+
const navigate = useNavigate();
|
|
9
|
+
|
|
10
|
+
const [formData, setFormData] = useState({
|
|
11
|
+
name: user?.name || '',
|
|
12
|
+
email: user?.email || ''
|
|
13
|
+
});
|
|
14
|
+
const [newEmail, setNewEmail] = useState('');
|
|
15
|
+
const [passwordData, setPasswordData] = useState({
|
|
16
|
+
currentPassword: '',
|
|
17
|
+
newPassword: '',
|
|
18
|
+
confirmPassword: ''
|
|
19
|
+
});
|
|
20
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
21
|
+
const [isPasswordLoading, setIsPasswordLoading] = useState(false);
|
|
22
|
+
const [message, setMessage] = useState('');
|
|
23
|
+
const [error, setError] = useState('');
|
|
24
|
+
const [passwordError, setPasswordError] = useState('');
|
|
25
|
+
const [passwordMessage, setPasswordMessage] = useState('');
|
|
26
|
+
const [passwordStrength, setPasswordStrength] = useState({ score: 0, label: '', color: '' });
|
|
27
|
+
|
|
28
|
+
const handleBack = useCallback(() => {
|
|
29
|
+
if (window.history.length > 1) {
|
|
30
|
+
navigate(-1);
|
|
31
|
+
} else {
|
|
32
|
+
navigate('/app');
|
|
33
|
+
}
|
|
34
|
+
}, [navigate]);
|
|
35
|
+
|
|
36
|
+
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
37
|
+
const { name, value } = e.target;
|
|
38
|
+
setFormData(prev => ({ ...prev, [name]: value }));
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const calculatePasswordStrength = (password: string) => {
|
|
42
|
+
if (!password) {
|
|
43
|
+
return { score: 0, label: '', color: '' };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let score = 0;
|
|
47
|
+
|
|
48
|
+
// Criterion 1: Length
|
|
49
|
+
if (password.length >= 8) score += 1;
|
|
50
|
+
if (password.length >= 12) score += 1;
|
|
51
|
+
|
|
52
|
+
// Criterion 2: Uppercase
|
|
53
|
+
if (/[A-Z]/.test(password)) score += 1;
|
|
54
|
+
|
|
55
|
+
// Criterion 3: Lowercase
|
|
56
|
+
if (/[a-z]/.test(password)) score += 1;
|
|
57
|
+
|
|
58
|
+
// Criterion 4: Numbers
|
|
59
|
+
if (/[0-9]/.test(password)) score += 1;
|
|
60
|
+
|
|
61
|
+
// Criterion 5: Symbols
|
|
62
|
+
if (/[^A-Za-z0-9]/.test(password)) score += 1;
|
|
63
|
+
|
|
64
|
+
// Penalize obvious sequences
|
|
65
|
+
if (/(.)\1{2,}/.test(password) || /123|abc|qwerty|password/i.test(password)) {
|
|
66
|
+
score = Math.max(0, score - 2);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Define strength based on score
|
|
70
|
+
if (score <= 2) return { score, label: 'Weak', color: 'var(--gmoonc-color-error, #ef4444)' };
|
|
71
|
+
if (score <= 4) return { score, label: 'Medium', color: 'var(--gmoonc-color-accent, #f59e0b)' };
|
|
72
|
+
return { score, label: 'Strong', color: 'var(--gmoonc-color-accent, #71b399)' };
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const handlePasswordChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
76
|
+
const { name, value } = e.target;
|
|
77
|
+
setPasswordData(prev => ({ ...prev, [name]: value }));
|
|
78
|
+
|
|
79
|
+
// Calculate strength only for new password field
|
|
80
|
+
if (name === 'newPassword') {
|
|
81
|
+
setPasswordStrength(calculatePasswordStrength(value));
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
86
|
+
e.preventDefault();
|
|
87
|
+
if (isLoading) return;
|
|
88
|
+
|
|
89
|
+
setIsLoading(true);
|
|
90
|
+
setError('');
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
// Placeholder: Update profile name
|
|
94
|
+
// In a real implementation, this would call an API
|
|
95
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
96
|
+
|
|
97
|
+
setMessage('✅ Profile updated successfully!');
|
|
98
|
+
setTimeout(() => setMessage(''), 3000);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error('Error updating profile:', error);
|
|
101
|
+
setError('Error updating profile. Please try again.');
|
|
102
|
+
} finally {
|
|
103
|
+
setIsLoading(false);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const handlePasswordSubmit = async (e: React.FormEvent) => {
|
|
108
|
+
e.preventDefault();
|
|
109
|
+
if (isPasswordLoading) return;
|
|
110
|
+
|
|
111
|
+
setIsPasswordLoading(true);
|
|
112
|
+
setPasswordError('');
|
|
113
|
+
setPasswordMessage('');
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
// Validations
|
|
117
|
+
if (!passwordData.currentPassword) {
|
|
118
|
+
setPasswordError('Please enter your current password.');
|
|
119
|
+
setIsPasswordLoading(false);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (passwordData.newPassword.length < 8) {
|
|
124
|
+
setPasswordError('New password must be at least 8 characters.');
|
|
125
|
+
setIsPasswordLoading(false);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (passwordData.newPassword !== passwordData.confirmPassword) {
|
|
130
|
+
setPasswordError('Passwords do not match.');
|
|
131
|
+
setIsPasswordLoading(false);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Validate password strength
|
|
136
|
+
if (passwordStrength.score < 3) {
|
|
137
|
+
setPasswordError('Please choose a stronger password following the recommendations.');
|
|
138
|
+
setIsPasswordLoading(false);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Placeholder: Update password
|
|
143
|
+
// In a real implementation, this would call an API
|
|
144
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
145
|
+
|
|
146
|
+
setPasswordMessage('✅ Password updated successfully!');
|
|
147
|
+
setPasswordData({
|
|
148
|
+
currentPassword: '',
|
|
149
|
+
newPassword: '',
|
|
150
|
+
confirmPassword: ''
|
|
151
|
+
});
|
|
152
|
+
setPasswordStrength({ score: 0, label: '', color: '' });
|
|
153
|
+
|
|
154
|
+
setTimeout(() => setPasswordMessage(''), 3000);
|
|
155
|
+
} catch (error) {
|
|
156
|
+
console.error('Error updating password:', error);
|
|
157
|
+
setPasswordError('Error updating password. Please try again.');
|
|
158
|
+
} finally {
|
|
159
|
+
setIsPasswordLoading(false);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const handleEmailChange = useCallback(async () => {
|
|
164
|
+
if (!newEmail || newEmail === user?.email) {
|
|
165
|
+
setError('Please enter a new email different from the current one.');
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Email format validation
|
|
170
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
171
|
+
if (!emailRegex.test(newEmail)) {
|
|
172
|
+
setError('Please enter a valid email address.');
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
// Placeholder: Request email change
|
|
178
|
+
// In a real implementation, this would call an API
|
|
179
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
180
|
+
|
|
181
|
+
setMessage('✅ A confirmation email will be sent to the new address.');
|
|
182
|
+
setNewEmail('');
|
|
183
|
+
setTimeout(() => setMessage(''), 3000);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
console.error('Error changing email:', error);
|
|
186
|
+
setError(error instanceof Error ? error.message : 'Unexpected error changing email.');
|
|
187
|
+
}
|
|
188
|
+
}, [newEmail, user?.email]);
|
|
189
|
+
|
|
190
|
+
if (!user) {
|
|
191
|
+
return (
|
|
192
|
+
<div className="gmoonc-account-page">
|
|
193
|
+
<div className="gmoonc-account-header">
|
|
194
|
+
<button onClick={handleBack} className="back-button" aria-label="Back">
|
|
195
|
+
<ArrowLeft size={20} />
|
|
196
|
+
<span>Back</span>
|
|
197
|
+
</button>
|
|
198
|
+
<div className="header-content">
|
|
199
|
+
<h1 className="page-title">Manage Account</h1>
|
|
200
|
+
<p className="page-subtitle">Configure your personal information and security preferences</p>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
<div className="gmoonc-account-grid">
|
|
204
|
+
<div className="gmoonc-account-card">
|
|
205
|
+
<p>No user information available.</p>
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
7
211
|
|
|
8
212
|
return (
|
|
9
|
-
<div className="gmoonc-
|
|
10
|
-
|
|
11
|
-
|
|
213
|
+
<div className="gmoonc-account-page">
|
|
214
|
+
{/* Header */}
|
|
215
|
+
<div className="gmoonc-account-header">
|
|
216
|
+
<button onClick={handleBack} className="back-button" aria-label="Back">
|
|
217
|
+
<ArrowLeft size={20} />
|
|
218
|
+
<span>Back</span>
|
|
219
|
+
</button>
|
|
220
|
+
<div className="header-content">
|
|
221
|
+
<h1 className="page-title">Manage Account</h1>
|
|
222
|
+
<p className="page-subtitle">Configure your personal information and security preferences</p>
|
|
223
|
+
</div>
|
|
12
224
|
</div>
|
|
13
|
-
<div className="gmoonc-content-body">
|
|
14
|
-
<div style={{ maxWidth: '800px', margin: '0 auto' }}>
|
|
15
|
-
{/* Profile Card */}
|
|
16
|
-
<div style={{
|
|
17
|
-
backgroundColor: '#ffffff',
|
|
18
|
-
borderRadius: '12px',
|
|
19
|
-
border: '1px solid #dbe2ea',
|
|
20
|
-
padding: '32px',
|
|
21
|
-
marginBottom: '24px',
|
|
22
|
-
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.05)'
|
|
23
|
-
}}>
|
|
24
|
-
<h3 style={{
|
|
25
|
-
fontSize: '20px',
|
|
26
|
-
fontWeight: 600,
|
|
27
|
-
color: 'var(--gmoonc-color-text-primary, #374161)',
|
|
28
|
-
marginBottom: '24px',
|
|
29
|
-
display: 'flex',
|
|
30
|
-
alignItems: 'center',
|
|
31
|
-
gap: '12px',
|
|
32
|
-
fontFamily: 'var(--gmoonc-font-family)'
|
|
33
|
-
}}>
|
|
34
|
-
<User size={24} style={{ color: 'var(--gmoonc-color-primary-2, #6374AD)' }} />
|
|
35
|
-
Profile Information
|
|
36
|
-
</h3>
|
|
37
|
-
|
|
38
|
-
{user ? (
|
|
39
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: '20px' }}>
|
|
40
|
-
<div style={{
|
|
41
|
-
display: 'flex',
|
|
42
|
-
alignItems: 'center',
|
|
43
|
-
gap: '16px',
|
|
44
|
-
padding: '16px',
|
|
45
|
-
backgroundColor: '#f8f9fa',
|
|
46
|
-
borderRadius: '8px'
|
|
47
|
-
}}>
|
|
48
|
-
<div style={{
|
|
49
|
-
width: '64px',
|
|
50
|
-
height: '64px',
|
|
51
|
-
borderRadius: '50%',
|
|
52
|
-
backgroundColor: '#6374AD',
|
|
53
|
-
color: 'white',
|
|
54
|
-
display: 'flex',
|
|
55
|
-
alignItems: 'center',
|
|
56
|
-
justifyContent: 'center',
|
|
57
|
-
fontSize: '24px',
|
|
58
|
-
fontWeight: 'bold'
|
|
59
|
-
}}>
|
|
60
|
-
{user.name ? user.name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2) : user.email.charAt(0).toUpperCase()}
|
|
61
|
-
</div>
|
|
62
|
-
<div style={{ flex: 1 }}>
|
|
63
|
-
<div style={{
|
|
64
|
-
fontSize: '18px',
|
|
65
|
-
fontWeight: 600,
|
|
66
|
-
color: 'var(--gmoonc-color-text-primary, #374161)',
|
|
67
|
-
marginBottom: '4px',
|
|
68
|
-
fontFamily: 'var(--gmoonc-font-family)'
|
|
69
|
-
}}>
|
|
70
|
-
{user.name || 'No name'}
|
|
71
|
-
</div>
|
|
72
|
-
<div style={{
|
|
73
|
-
fontSize: '14px',
|
|
74
|
-
color: 'var(--gmoonc-color-primary-2, #6374AD)',
|
|
75
|
-
fontFamily: 'var(--gmoonc-font-family)'
|
|
76
|
-
}}>
|
|
77
|
-
{user.email}
|
|
78
|
-
</div>
|
|
79
|
-
</div>
|
|
80
|
-
</div>
|
|
81
225
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
226
|
+
{/* Grid of Cards */}
|
|
227
|
+
<div className="gmoonc-account-grid">
|
|
228
|
+
{/* Card: Personal Information */}
|
|
229
|
+
<div className="gmoonc-account-card">
|
|
230
|
+
<div className="card-header">
|
|
231
|
+
<div className="card-icon">
|
|
232
|
+
<User size={24} />
|
|
233
|
+
</div>
|
|
234
|
+
<div>
|
|
235
|
+
<h2 className="card-title">Personal Information</h2>
|
|
236
|
+
<p className="card-description">Update your name and basic information</p>
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
|
|
240
|
+
{message && (
|
|
241
|
+
<div className="alert alert-success">
|
|
242
|
+
{message}
|
|
243
|
+
</div>
|
|
244
|
+
)}
|
|
245
|
+
|
|
246
|
+
{error && (
|
|
247
|
+
<div className="alert alert-error">
|
|
248
|
+
{error}
|
|
249
|
+
</div>
|
|
250
|
+
)}
|
|
251
|
+
|
|
252
|
+
<form onSubmit={handleSubmit} className="card-form">
|
|
253
|
+
<div className="form-field">
|
|
254
|
+
<label htmlFor="name" className="form-label">
|
|
255
|
+
<User size={16} />
|
|
256
|
+
Full Name
|
|
257
|
+
</label>
|
|
258
|
+
<input
|
|
259
|
+
type="text"
|
|
260
|
+
id="name"
|
|
261
|
+
name="name"
|
|
262
|
+
value={formData.name}
|
|
263
|
+
onChange={handleInputChange}
|
|
264
|
+
className="form-input"
|
|
265
|
+
placeholder="Your full name"
|
|
266
|
+
required
|
|
267
|
+
minLength={2}
|
|
268
|
+
/>
|
|
269
|
+
</div>
|
|
270
|
+
|
|
271
|
+
<div className="form-field">
|
|
272
|
+
<label htmlFor="email" className="form-label">
|
|
273
|
+
<Mail size={16} />
|
|
274
|
+
Current Email
|
|
275
|
+
</label>
|
|
276
|
+
<input
|
|
277
|
+
type="email"
|
|
278
|
+
id="email"
|
|
279
|
+
value={formData.email}
|
|
280
|
+
className="form-input disabled"
|
|
281
|
+
disabled
|
|
282
|
+
/>
|
|
283
|
+
<small className="form-hint">
|
|
284
|
+
The current email cannot be edited directly. Use the "Change Email" section below.
|
|
285
|
+
</small>
|
|
286
|
+
</div>
|
|
287
|
+
|
|
288
|
+
<button
|
|
289
|
+
type="submit"
|
|
290
|
+
className="btn btn-primary"
|
|
291
|
+
disabled={isLoading}
|
|
292
|
+
>
|
|
293
|
+
<Save size={18} />
|
|
294
|
+
{isLoading ? 'Saving...' : 'Save Changes'}
|
|
295
|
+
</button>
|
|
296
|
+
</form>
|
|
297
|
+
</div>
|
|
108
298
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
299
|
+
{/* Card: Account Security */}
|
|
300
|
+
<div className="gmoonc-account-card">
|
|
301
|
+
<div className="card-header">
|
|
302
|
+
<div className="card-icon">
|
|
303
|
+
<Lock size={24} />
|
|
304
|
+
</div>
|
|
305
|
+
<div>
|
|
306
|
+
<h2 className="card-title">Account Security</h2>
|
|
307
|
+
<p className="card-description">Change your password to keep your account secure</p>
|
|
308
|
+
</div>
|
|
309
|
+
</div>
|
|
310
|
+
|
|
311
|
+
{passwordMessage && (
|
|
312
|
+
<div className="alert alert-success">
|
|
313
|
+
{passwordMessage}
|
|
314
|
+
</div>
|
|
315
|
+
)}
|
|
316
|
+
|
|
317
|
+
{passwordError && (
|
|
318
|
+
<div className="alert alert-error">
|
|
319
|
+
{passwordError}
|
|
320
|
+
</div>
|
|
321
|
+
)}
|
|
322
|
+
|
|
323
|
+
<form onSubmit={handlePasswordSubmit} className="card-form">
|
|
324
|
+
<div className="form-field">
|
|
325
|
+
<label htmlFor="currentPassword" className="form-label">
|
|
326
|
+
<Lock size={16} />
|
|
327
|
+
Current Password
|
|
328
|
+
</label>
|
|
329
|
+
<input
|
|
330
|
+
type="password"
|
|
331
|
+
id="currentPassword"
|
|
332
|
+
name="currentPassword"
|
|
333
|
+
value={passwordData.currentPassword}
|
|
334
|
+
onChange={handlePasswordChange}
|
|
335
|
+
className="form-input"
|
|
336
|
+
placeholder="Enter your current password"
|
|
337
|
+
required
|
|
338
|
+
disabled={isPasswordLoading}
|
|
339
|
+
/>
|
|
340
|
+
</div>
|
|
341
|
+
|
|
342
|
+
<div className="form-field">
|
|
343
|
+
<label htmlFor="newPassword" className="form-label">
|
|
344
|
+
<Key size={16} />
|
|
345
|
+
New Password
|
|
346
|
+
</label>
|
|
347
|
+
<input
|
|
348
|
+
type="password"
|
|
349
|
+
id="newPassword"
|
|
350
|
+
name="newPassword"
|
|
351
|
+
value={passwordData.newPassword}
|
|
352
|
+
onChange={handlePasswordChange}
|
|
353
|
+
className="form-input"
|
|
354
|
+
placeholder="Enter your new password"
|
|
355
|
+
required
|
|
356
|
+
minLength={8}
|
|
357
|
+
disabled={isPasswordLoading}
|
|
358
|
+
/>
|
|
359
|
+
|
|
360
|
+
{/* Password Strength Indicator */}
|
|
361
|
+
{passwordData.newPassword && (
|
|
362
|
+
<div className="password-strength-container">
|
|
363
|
+
<div className="password-strength-bar">
|
|
364
|
+
<div
|
|
365
|
+
className="password-strength-fill"
|
|
366
|
+
style={{
|
|
367
|
+
width: `${(passwordStrength.score / 6) * 100}%`,
|
|
368
|
+
backgroundColor: passwordStrength.color
|
|
369
|
+
}}
|
|
370
|
+
/>
|
|
130
371
|
</div>
|
|
372
|
+
<span
|
|
373
|
+
className="password-strength-label"
|
|
374
|
+
style={{ color: passwordStrength.color }}
|
|
375
|
+
>
|
|
376
|
+
{passwordStrength.label}
|
|
377
|
+
</span>
|
|
131
378
|
</div>
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
379
|
+
)}
|
|
380
|
+
</div>
|
|
381
|
+
|
|
382
|
+
<div className="form-field">
|
|
383
|
+
<label htmlFor="confirmPassword" className="form-label">
|
|
384
|
+
<Lock size={16} />
|
|
385
|
+
Confirm New Password
|
|
386
|
+
</label>
|
|
387
|
+
<input
|
|
388
|
+
type="password"
|
|
389
|
+
id="confirmPassword"
|
|
390
|
+
name="confirmPassword"
|
|
391
|
+
value={passwordData.confirmPassword}
|
|
392
|
+
onChange={handlePasswordChange}
|
|
393
|
+
className="form-input"
|
|
394
|
+
placeholder="Confirm your new password"
|
|
395
|
+
required
|
|
396
|
+
minLength={8}
|
|
397
|
+
disabled={isPasswordLoading}
|
|
398
|
+
/>
|
|
399
|
+
</div>
|
|
143
400
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
401
|
+
{/* Password Tips */}
|
|
402
|
+
<div className="password-tips">
|
|
403
|
+
<h4 className="password-tips-title">Password Recommendations:</h4>
|
|
404
|
+
<ul className="password-tips-list">
|
|
405
|
+
<li className={passwordData.newPassword.length >= 8 ? 'tip-met' : ''}>
|
|
406
|
+
Minimum: 8 characters (recommended: 12+)
|
|
407
|
+
</li>
|
|
408
|
+
<li className={/[A-Z]/.test(passwordData.newPassword) && /[a-z]/.test(passwordData.newPassword) && /[0-9]/.test(passwordData.newPassword) && /[^A-Za-z0-9]/.test(passwordData.newPassword) ? 'tip-met' : ''}>
|
|
409
|
+
Combine: uppercase, lowercase, numbers and symbols
|
|
410
|
+
</li>
|
|
411
|
+
<li>
|
|
412
|
+
Avoid: personal information and obvious sequences
|
|
413
|
+
</li>
|
|
414
|
+
<li>
|
|
415
|
+
Use: unique password for this account
|
|
416
|
+
</li>
|
|
417
|
+
</ul>
|
|
418
|
+
</div>
|
|
419
|
+
|
|
420
|
+
<button
|
|
421
|
+
type="submit"
|
|
422
|
+
className="btn btn-primary"
|
|
423
|
+
disabled={isPasswordLoading}
|
|
424
|
+
>
|
|
425
|
+
<Lock size={18} />
|
|
426
|
+
{isPasswordLoading ? 'Updating...' : 'Update Password'}
|
|
427
|
+
</button>
|
|
428
|
+
</form>
|
|
429
|
+
</div>
|
|
430
|
+
|
|
431
|
+
{/* Card: Change Email */}
|
|
432
|
+
<div className="gmoonc-account-card">
|
|
433
|
+
<div className="card-header">
|
|
434
|
+
<div className="card-icon">
|
|
435
|
+
<Mail size={24} />
|
|
436
|
+
</div>
|
|
437
|
+
<div>
|
|
438
|
+
<h2 className="card-title">Change Email</h2>
|
|
439
|
+
<p className="card-description">Update your primary email address</p>
|
|
440
|
+
</div>
|
|
169
441
|
</div>
|
|
442
|
+
|
|
443
|
+
{error && (
|
|
444
|
+
<div className="alert alert-error">
|
|
445
|
+
{error}
|
|
446
|
+
</div>
|
|
447
|
+
)}
|
|
448
|
+
|
|
449
|
+
<form onSubmit={(e) => { e.preventDefault(); handleEmailChange(); }} className="card-form">
|
|
450
|
+
<div className="form-field">
|
|
451
|
+
<label htmlFor="newEmail" className="form-label">
|
|
452
|
+
<Mail size={16} />
|
|
453
|
+
New Email
|
|
454
|
+
</label>
|
|
455
|
+
<input
|
|
456
|
+
type="email"
|
|
457
|
+
id="newEmail"
|
|
458
|
+
value={newEmail}
|
|
459
|
+
onChange={(e) => setNewEmail(e.target.value)}
|
|
460
|
+
className="form-input"
|
|
461
|
+
placeholder="Enter the new email"
|
|
462
|
+
required
|
|
463
|
+
/>
|
|
464
|
+
<small className="form-hint">
|
|
465
|
+
A confirmation email will be sent to the new address.
|
|
466
|
+
</small>
|
|
467
|
+
</div>
|
|
468
|
+
|
|
469
|
+
<button
|
|
470
|
+
type="submit"
|
|
471
|
+
className="btn btn-secondary"
|
|
472
|
+
disabled={!newEmail || newEmail === user.email}
|
|
473
|
+
>
|
|
474
|
+
<Mail size={18} />
|
|
475
|
+
Request Change
|
|
476
|
+
</button>
|
|
477
|
+
</form>
|
|
170
478
|
</div>
|
|
171
479
|
</div>
|
|
172
480
|
</div>
|
|
@@ -779,4 +779,392 @@
|
|
|
779
779
|
.user-edit-card .btn {
|
|
780
780
|
width: 100%;
|
|
781
781
|
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
/* Account Page Styles (gmoonc-specific) */
|
|
785
|
+
.gmoonc-account-page {
|
|
786
|
+
max-width: 1200px;
|
|
787
|
+
margin: 0 auto;
|
|
788
|
+
padding: 2rem;
|
|
789
|
+
background: var(--gmoonc-color-bg-light, #eaf0f5);
|
|
790
|
+
min-height: 100vh;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
/* Header */
|
|
794
|
+
.gmoonc-account-header {
|
|
795
|
+
margin-bottom: 2.5rem;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
.gmoonc-account-header .back-button {
|
|
799
|
+
display: inline-flex;
|
|
800
|
+
align-items: center;
|
|
801
|
+
gap: 0.5rem;
|
|
802
|
+
background: transparent;
|
|
803
|
+
border: none;
|
|
804
|
+
color: var(--gmoonc-color-text-primary, #374161);
|
|
805
|
+
font-size: 0.95rem;
|
|
806
|
+
font-weight: 500;
|
|
807
|
+
cursor: pointer;
|
|
808
|
+
padding: 0.5rem 0;
|
|
809
|
+
margin-bottom: 1.5rem;
|
|
810
|
+
transition: all 0.2s ease;
|
|
811
|
+
font-family: var(--gmoonc-font-family);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
.gmoonc-account-header .back-button:hover {
|
|
815
|
+
color: var(--gmoonc-color-accent, #71b399);
|
|
816
|
+
transform: translateX(-4px);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
.gmoonc-account-header .header-content {
|
|
820
|
+
text-align: left;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
.gmoonc-account-header .page-title {
|
|
824
|
+
font-family: var(--gmoonc-font-family);
|
|
825
|
+
font-weight: var(--gmoonc-font-weight-title, 700);
|
|
826
|
+
font-size: var(--gmoonc-font-size-2xl, 2rem);
|
|
827
|
+
color: var(--gmoonc-color-text-primary, #374161);
|
|
828
|
+
margin: 0 0 0.5rem 0;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
.gmoonc-account-header .page-subtitle {
|
|
832
|
+
font-size: var(--gmoonc-font-size-base, 1rem);
|
|
833
|
+
color: var(--gmoonc-color-primary-2, #6374AD);
|
|
834
|
+
margin: 0;
|
|
835
|
+
font-family: var(--gmoonc-font-family);
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
/* Grid Layout */
|
|
839
|
+
.gmoonc-account-grid {
|
|
840
|
+
display: grid;
|
|
841
|
+
grid-template-columns: 1fr;
|
|
842
|
+
gap: 1.5rem;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/* Card */
|
|
846
|
+
.gmoonc-account-card {
|
|
847
|
+
background: var(--gmoonc-color-surface-white, white);
|
|
848
|
+
border-radius: var(--gmoonc-radius, 12px);
|
|
849
|
+
padding: 2rem;
|
|
850
|
+
box-shadow: var(--gmoonc-shadow-md, 0 2px 12px rgba(55, 65, 97, 0.08));
|
|
851
|
+
transition: all 0.3s ease;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
.gmoonc-account-card:hover {
|
|
855
|
+
box-shadow: var(--gmoonc-shadow-lg, 0 4px 20px rgba(55, 65, 97, 0.12));
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
/* Card Header */
|
|
859
|
+
.gmoonc-account-card .card-header {
|
|
860
|
+
display: flex;
|
|
861
|
+
align-items: flex-start;
|
|
862
|
+
gap: 1rem;
|
|
863
|
+
margin-bottom: 1.5rem;
|
|
864
|
+
padding-bottom: 1.5rem;
|
|
865
|
+
border-bottom: 2px solid var(--gmoonc-color-bg-light, #eaf0f5);
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
.gmoonc-account-card .card-icon {
|
|
869
|
+
display: flex;
|
|
870
|
+
align-items: center;
|
|
871
|
+
justify-content: center;
|
|
872
|
+
width: 48px;
|
|
873
|
+
height: 48px;
|
|
874
|
+
border-radius: 10px;
|
|
875
|
+
background: linear-gradient(135deg, var(--gmoonc-color-primary-2, #6374AD) 0%, var(--gmoonc-color-primary, #879FED) 100%);
|
|
876
|
+
color: white;
|
|
877
|
+
flex-shrink: 0;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
.gmoonc-account-card .card-title {
|
|
881
|
+
font-family: var(--gmoonc-font-family);
|
|
882
|
+
font-weight: var(--gmoonc-font-weight-title, 700);
|
|
883
|
+
font-size: 1.3rem;
|
|
884
|
+
color: var(--gmoonc-color-text-primary, #374161);
|
|
885
|
+
margin: 0 0 0.25rem 0;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
.gmoonc-account-card .card-description {
|
|
889
|
+
font-size: 0.9rem;
|
|
890
|
+
color: var(--gmoonc-color-primary-2, #6374AD);
|
|
891
|
+
margin: 0;
|
|
892
|
+
font-family: var(--gmoonc-font-family);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
/* Form */
|
|
896
|
+
.gmoonc-account-card .card-form {
|
|
897
|
+
display: flex;
|
|
898
|
+
flex-direction: column;
|
|
899
|
+
gap: 1.5rem;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
.gmoonc-account-card .form-field {
|
|
903
|
+
display: flex;
|
|
904
|
+
flex-direction: column;
|
|
905
|
+
gap: 0.5rem;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
.gmoonc-account-card .form-label {
|
|
909
|
+
display: flex;
|
|
910
|
+
align-items: center;
|
|
911
|
+
gap: 0.5rem;
|
|
912
|
+
font-family: var(--gmoonc-font-family);
|
|
913
|
+
font-weight: var(--gmoonc-font-weight-heading, 600);
|
|
914
|
+
font-size: 0.9rem;
|
|
915
|
+
color: var(--gmoonc-color-text-primary, #374161);
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
.gmoonc-account-card .form-input {
|
|
919
|
+
width: 100%;
|
|
920
|
+
padding: 0.875rem 1rem;
|
|
921
|
+
border: 2px solid var(--gmoonc-color-border, #dbe2ea);
|
|
922
|
+
border-radius: var(--gmoonc-radius-sm, 8px);
|
|
923
|
+
font-size: var(--gmoonc-font-size-base, 1rem);
|
|
924
|
+
color: var(--gmoonc-color-text-primary, #374161);
|
|
925
|
+
transition: all 0.2s ease;
|
|
926
|
+
background: var(--gmoonc-color-surface-white, white);
|
|
927
|
+
font-family: var(--gmoonc-font-family);
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
.gmoonc-account-card .form-input:focus {
|
|
931
|
+
outline: none;
|
|
932
|
+
border-color: var(--gmoonc-color-accent, #71b399);
|
|
933
|
+
box-shadow: 0 0 0 3px rgba(113, 179, 153, 0.1);
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
.gmoonc-account-card .form-input.disabled,
|
|
937
|
+
.gmoonc-account-card .form-input:disabled {
|
|
938
|
+
background: var(--gmoonc-color-surface-light, #f8f9fa);
|
|
939
|
+
color: var(--gmoonc-color-primary, #879FED);
|
|
940
|
+
cursor: not-allowed;
|
|
941
|
+
border-color: var(--gmoonc-color-bg-light, #eaf0f5);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
.gmoonc-account-card .form-hint {
|
|
945
|
+
font-size: 0.85rem;
|
|
946
|
+
color: var(--gmoonc-color-primary-2, #6374AD);
|
|
947
|
+
margin-top: 0.25rem;
|
|
948
|
+
font-family: var(--gmoonc-font-family);
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
/* Buttons */
|
|
952
|
+
.gmoonc-account-card .btn {
|
|
953
|
+
display: inline-flex;
|
|
954
|
+
align-items: center;
|
|
955
|
+
justify-content: center;
|
|
956
|
+
gap: 0.5rem;
|
|
957
|
+
padding: 0.875rem 1.5rem;
|
|
958
|
+
border: none;
|
|
959
|
+
border-radius: var(--gmoonc-radius-sm, 0.5rem);
|
|
960
|
+
font-family: var(--gmoonc-font-family);
|
|
961
|
+
font-weight: var(--gmoonc-font-weight-heading, 600);
|
|
962
|
+
font-size: var(--gmoonc-font-size-base, 1rem);
|
|
963
|
+
cursor: pointer;
|
|
964
|
+
transition: all 0.2s ease;
|
|
965
|
+
text-decoration: none;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
.gmoonc-account-card .btn-primary {
|
|
969
|
+
background: var(--gmoonc-color-accent, #71b399);
|
|
970
|
+
color: var(--gmoonc-color-surface-white, white);
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
.gmoonc-account-card .btn-primary:hover:not(:disabled) {
|
|
974
|
+
background: #5d9b82;
|
|
975
|
+
transform: translateY(-2px);
|
|
976
|
+
box-shadow: 0 4px 12px rgba(113, 179, 153, 0.3);
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
.gmoonc-account-card .btn-secondary {
|
|
980
|
+
background: var(--gmoonc-color-primary-2, #6374AD);
|
|
981
|
+
color: var(--gmoonc-color-surface-white, white);
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
.gmoonc-account-card .btn-secondary:hover:not(:disabled) {
|
|
985
|
+
background: var(--gmoonc-color-primary-dark, #374161);
|
|
986
|
+
transform: translateY(-2px);
|
|
987
|
+
box-shadow: 0 4px 12px rgba(55, 65, 97, 0.3);
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
.gmoonc-account-card .btn:disabled {
|
|
991
|
+
background: var(--gmoonc-color-border, #dbe2ea);
|
|
992
|
+
color: var(--gmoonc-color-primary, #879FED);
|
|
993
|
+
cursor: not-allowed;
|
|
994
|
+
transform: none;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
/* Alerts */
|
|
998
|
+
.gmoonc-account-card .alert {
|
|
999
|
+
padding: 1rem 1.25rem;
|
|
1000
|
+
border-radius: var(--gmoonc-radius-sm, 8px);
|
|
1001
|
+
font-size: 0.95rem;
|
|
1002
|
+
margin-bottom: 1rem;
|
|
1003
|
+
font-family: var(--gmoonc-font-family);
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
.gmoonc-account-card .alert-success {
|
|
1007
|
+
background: var(--gmoonc-color-success-bg, #f0fdf4);
|
|
1008
|
+
color: var(--gmoonc-color-success, #166534);
|
|
1009
|
+
border-left: 4px solid var(--gmoonc-color-accent, #71b399);
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
.gmoonc-account-card .alert-error {
|
|
1013
|
+
background: var(--gmoonc-color-error-bg, #fef2f2);
|
|
1014
|
+
color: var(--gmoonc-color-error, #dc2626);
|
|
1015
|
+
border-left: 4px solid var(--gmoonc-color-error, #dc2626);
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
/* Password Strength Indicator */
|
|
1019
|
+
.gmoonc-account-card .password-strength-container {
|
|
1020
|
+
margin-top: 8px;
|
|
1021
|
+
display: flex;
|
|
1022
|
+
align-items: center;
|
|
1023
|
+
gap: 12px;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
.gmoonc-account-card .password-strength-bar {
|
|
1027
|
+
flex: 1;
|
|
1028
|
+
height: 6px;
|
|
1029
|
+
background-color: var(--gmoonc-color-border, #dbe2ea);
|
|
1030
|
+
border-radius: 3px;
|
|
1031
|
+
overflow: hidden;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
.gmoonc-account-card .password-strength-fill {
|
|
1035
|
+
height: 100%;
|
|
1036
|
+
transition: width 0.3s ease, background-color 0.3s ease;
|
|
1037
|
+
border-radius: 3px;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
.gmoonc-account-card .password-strength-label {
|
|
1041
|
+
font-size: 13px;
|
|
1042
|
+
font-weight: var(--gmoonc-font-weight-heading, 600);
|
|
1043
|
+
min-width: 60px;
|
|
1044
|
+
text-align: right;
|
|
1045
|
+
font-family: var(--gmoonc-font-family);
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
/* Password Tips */
|
|
1049
|
+
.gmoonc-account-card .password-tips {
|
|
1050
|
+
background: linear-gradient(135deg, rgba(99, 116, 173, 0.08), rgba(113, 179, 153, 0.08));
|
|
1051
|
+
border: 1px solid rgba(99, 116, 173, 0.2);
|
|
1052
|
+
border-radius: var(--gmoonc-radius, 12px);
|
|
1053
|
+
padding: 16px 20px;
|
|
1054
|
+
margin: 16px 0;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
.gmoonc-account-card .password-tips-title {
|
|
1058
|
+
color: var(--gmoonc-color-text-primary, #374161);
|
|
1059
|
+
font-size: 14px;
|
|
1060
|
+
font-weight: var(--gmoonc-font-weight-heading, 600);
|
|
1061
|
+
margin: 0 0 12px 0;
|
|
1062
|
+
display: flex;
|
|
1063
|
+
align-items: center;
|
|
1064
|
+
gap: 6px;
|
|
1065
|
+
font-family: var(--gmoonc-font-family);
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
.gmoonc-account-card .password-tips-list {
|
|
1069
|
+
list-style: none;
|
|
1070
|
+
padding: 0;
|
|
1071
|
+
margin: 0;
|
|
1072
|
+
display: flex;
|
|
1073
|
+
flex-direction: column;
|
|
1074
|
+
gap: 8px;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
.gmoonc-account-card .password-tips-list li {
|
|
1078
|
+
color: var(--gmoonc-color-primary-2, #6374AD);
|
|
1079
|
+
font-size: 13px;
|
|
1080
|
+
padding-left: 24px;
|
|
1081
|
+
position: relative;
|
|
1082
|
+
transition: color 0.3s ease;
|
|
1083
|
+
font-family: var(--gmoonc-font-family);
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
.gmoonc-account-card .password-tips-list li::before {
|
|
1087
|
+
content: "○";
|
|
1088
|
+
position: absolute;
|
|
1089
|
+
left: 8px;
|
|
1090
|
+
color: var(--gmoonc-color-primary-2, #6374AD);
|
|
1091
|
+
font-size: 16px;
|
|
1092
|
+
transition: all 0.3s ease;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
.gmoonc-account-card .password-tips-list li.tip-met {
|
|
1096
|
+
color: var(--gmoonc-color-accent, #71b399);
|
|
1097
|
+
font-weight: var(--gmoonc-font-weight-medium, 500);
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
.gmoonc-account-card .password-tips-list li.tip-met::before {
|
|
1101
|
+
content: "✓";
|
|
1102
|
+
color: var(--gmoonc-color-accent, #71b399);
|
|
1103
|
+
font-weight: bold;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
/* Responsive Design */
|
|
1107
|
+
@media (min-width: 768px) {
|
|
1108
|
+
.gmoonc-account-page {
|
|
1109
|
+
padding: 3rem 2rem;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
.gmoonc-account-header .page-title {
|
|
1113
|
+
font-size: 2.5rem;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
.gmoonc-account-grid {
|
|
1117
|
+
grid-template-columns: repeat(2, 1fr);
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
.gmoonc-account-card:first-child {
|
|
1121
|
+
grid-column: 1 / -1;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
.gmoonc-account-card .btn {
|
|
1125
|
+
max-width: 280px;
|
|
1126
|
+
margin-left: auto;
|
|
1127
|
+
margin-right: auto;
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
@media (min-width: 1024px) {
|
|
1132
|
+
.gmoonc-account-page {
|
|
1133
|
+
padding: 3rem;
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
@media (max-width: 767px) {
|
|
1138
|
+
.gmoonc-account-page {
|
|
1139
|
+
padding: 1.5rem 1rem;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
.gmoonc-account-header .page-title {
|
|
1143
|
+
font-size: 1.75rem;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
.gmoonc-account-header .page-subtitle {
|
|
1147
|
+
font-size: 0.9rem;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
.gmoonc-account-card {
|
|
1151
|
+
padding: 1.5rem;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
.gmoonc-account-card .card-icon {
|
|
1155
|
+
width: 40px;
|
|
1156
|
+
height: 40px;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
.gmoonc-account-card .card-title {
|
|
1160
|
+
font-size: 1.1rem;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
.gmoonc-account-card .card-description {
|
|
1164
|
+
font-size: 0.85rem;
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
.gmoonc-account-card .btn {
|
|
1168
|
+
width: 100%;
|
|
1169
|
+
}
|
|
782
1170
|
}
|