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.14").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) => {
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,6 +1,6 @@
1
1
  {
2
2
  "name": "gmoonc",
3
- "version": "0.0.14",
3
+ "version": "0.0.15",
4
4
  "description": "Goalmoon Ctrl (gmoonc): Complete dashboard installer for React projects",
5
5
  "license": "MIT",
6
6
  "homepage": "https://gmoonc.com",
@@ -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, Calendar } from 'lucide-react';
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-content-wrapper">
10
- <div className="gmoonc-content-header">
11
- <h2>Account</h2>
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
- <div style={{
83
- display: 'grid',
84
- gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))',
85
- gap: '16px'
86
- }}>
87
- <div style={{
88
- padding: '16px',
89
- backgroundColor: '#f8f9fa',
90
- borderRadius: '8px',
91
- border: '1px solid #e9ecef'
92
- }}>
93
- <div style={{
94
- display: 'flex',
95
- alignItems: 'center',
96
- gap: '8px',
97
- marginBottom: '8px',
98
- color: '#6374AD',
99
- fontSize: '14px'
100
- }}>
101
- <Mail size={16} />
102
- <span style={{ fontWeight: 500 }}>Email</span>
103
- </div>
104
- <div style={{ color: 'var(--gmoonc-color-text-primary, #374161)', fontSize: '16px', fontFamily: 'var(--gmoonc-font-family)' }}>
105
- {user.email}
106
- </div>
107
- </div>
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
- <div style={{
110
- padding: '16px',
111
- backgroundColor: 'var(--gmoonc-color-surface-light, #f8f9fa)',
112
- borderRadius: '8px',
113
- border: '1px solid var(--gmoonc-color-border-light, #e9ecef)'
114
- }}>
115
- <div style={{
116
- display: 'flex',
117
- alignItems: 'center',
118
- gap: '8px',
119
- marginBottom: '8px',
120
- color: 'var(--gmoonc-color-primary-2, #6374AD)',
121
- fontSize: '14px',
122
- fontFamily: 'var(--gmoonc-font-family)'
123
- }}>
124
- <User size={16} />
125
- <span style={{ fontWeight: 500 }}>Role</span>
126
- </div>
127
- <div style={{ color: 'var(--gmoonc-color-text-primary, #374161)', fontSize: '16px', fontFamily: 'var(--gmoonc-font-family)' }}>
128
- {user.role || 'No role assigned'}
129
- </div>
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
- </div>
133
- ) : (
134
- <div style={{
135
- padding: '24px',
136
- textAlign: 'center',
137
- color: '#6374AD'
138
- }}>
139
- <p>No user information available.</p>
140
- </div>
141
- )}
142
- </div>
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
- {/* Placeholder for future features */}
145
- <div style={{
146
- backgroundColor: '#ffffff',
147
- borderRadius: '12px',
148
- border: '1px solid #dbe2ea',
149
- padding: '32px',
150
- boxShadow: '0 2px 8px rgba(0, 0, 0, 0.05)'
151
- }}>
152
- <h3 style={{
153
- fontSize: '20px',
154
- fontWeight: 600,
155
- color: 'var(--gmoonc-color-text-primary, #374161)',
156
- marginBottom: '16px',
157
- fontFamily: 'var(--gmoonc-font-family)'
158
- }}>
159
- Account Settings
160
- </h3>
161
- <p style={{
162
- color: 'var(--gmoonc-color-primary-2, #6374AD)',
163
- fontSize: '14px',
164
- lineHeight: '1.6',
165
- fontFamily: 'var(--gmoonc-font-family)'
166
- }}>
167
- Additional account settings and preferences will be available here in a future update.
168
- </p>
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
  }