Skip to content

Commit 79c20da

Browse files
committed
feat(profile): enhance profile editing with auto-save and UI improvements
- Add auto-save toggle with visual indicator - Improve phone number formatting with country code - Add last edit timestamp display - Add character counter for tagline input - Enhance save button with visual feedback - Use separate Google Script URL for edit operations - Add spellcheck dictionary for "checkmark"
1 parent 81655b2 commit 79c20da

File tree

6 files changed

+359
-179
lines changed

6 files changed

+359
-179
lines changed

.vscode/settings.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
{
2-
"liveServer.settings.port": 5501
2+
"liveServer.settings.port": 5501,
3+
"cSpell.words": [
4+
"checkmark"
5+
]
36
}

Assets/css/main.css

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,16 @@
117117
0%, 100% { transform: translateX(0); }
118118
20%, 60% { transform: translateX(-5px); }
119119
40%, 80% { transform: translateX(5px);}
120+
}.save-success {
121+
background-color: #10B981 !important;
122+
transition: background-color 0.3s ease;
123+
}
124+
.save-checkmark {
125+
margin-left: 8px;
126+
color: white;
127+
animation: fadeInScale 0.5s ease;
128+
}
129+
@keyframes fadeInScale {
130+
from { opacity: 0; transform: scale(0.5); }
131+
to { opacity: 1; transform: scale(1); }
120132
}

Assets/js/auth.js

Lines changed: 78 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -4,79 +4,88 @@ import { showAlert } from './utils.js';
44
// Enhanced OTP input setup with better visual feedback
55
export function setupOtpInputs() {
66
const inputs = document.querySelectorAll('.otp-inputs input');
7-
7+
const ANIMATION_DURATION = 200;
8+
const INPUT_CLASSES = {
9+
base: ['transition-all', 'duration-200', 'ease-in-out', 'text-center', 'text-xl', 'font-medium', 'bg-gray-800'],
10+
focus: ['ring-2', 'ring-purple-300', 'border-purple-400', 'shadow-lg'],
11+
success: ['border-green-400', 'bg-green-50', 'transform', 'scale-105'],
12+
paste: ['bg-blue-50', 'border-blue-400', 'transform', 'scale-105'],
13+
disabled: ['verifying', 'cursor-not-allowed']
14+
};
15+
16+
const applyClasses = (element, classes, remove = false) => {
17+
element.classList[remove ? 'remove' : 'add'](...classes);
18+
};
19+
20+
const animateInput = (input, classes) => {
21+
applyClasses(input, classes);
22+
setTimeout(() => applyClasses(input, classes, true), ANIMATION_DURATION);
23+
};
24+
825
inputs.forEach((input, index) => {
9-
// Improved visual styling
10-
input.classList.add(
11-
'transition-all', 'duration-200', 'ease-in-out',
12-
'text-center', 'text-xl', 'font-medium',
13-
'bg-gray-800' // Initial background color
14-
);
15-
16-
// Enhanced paste handling
26+
// Apply base styling
27+
applyClasses(input, INPUT_CLASSES.base);
28+
29+
// Enhanced paste handling with validation
1730
input.addEventListener('paste', (e) => {
1831
e.preventDefault();
1932
const pasteData = e.clipboardData.getData('text').trim();
33+
2034
if (/^\d{6}$/.test(pasteData)) {
2135
inputs.forEach((inp, i) => {
2236
inp.value = pasteData[i] || '';
23-
inp.classList.remove('border-green-400', 'bg-green-50');
24-
inp.classList.add('bg-blue-50', 'border-blue-400', 'transform','scale-105');
25-
setTimeout(() => {
26-
inp.classList.remove('bg-blue-50', 'border-blue-400', 'transform','scale-105');
27-
}, 200);
37+
animateInput(inp, INPUT_CLASSES.paste);
2838
});
29-
inputs[5].focus();
39+
inputs[inputs.length - 1].focus();
3040
}
3141
});
3242

33-
// Improved input handling with better feedback
43+
// Smart input handling with validation and auto-focus
3444
input.addEventListener('input', (e) => {
35-
input.value = input.value.replace(/\D/g, '').slice(0, 1);
36-
37-
if (input.value.length === 1) {
38-
input.classList.add('border-green-400', 'bg-green-50', 'transform', 'scale-105');
39-
setTimeout(() => input.classList.remove(
40-
'border-green-400', 'bg-green-50', 'transform', 'scale-105'
41-
), 200);
45+
const sanitizedValue = input.value.replace(/\D/g, '').slice(0, 1);
46+
input.value = sanitizedValue;
47+
48+
if (sanitizedValue) {
49+
animateInput(input, INPUT_CLASSES.success);
4250

4351
if (index < inputs.length - 1) {
4452
inputs[index + 1].focus();
45-
}
46-
}
47-
48-
// Auto-submit when complete
49-
if (index === inputs.length - 1 && input.value.length === 1) {
50-
const allFilled = Array.from(inputs).every(i => i.value.length === 1);
51-
if (allFilled) {
52-
inputs.forEach(i => {
53-
i.classList.add('verifying', 'cursor-not-allowed');
54-
i.disabled = true;
55-
});
56-
DOM.verifyOtpBtn.click();
53+
} else {
54+
const isComplete = Array.from(inputs).every(i => i.value.length === 1);
55+
if (isComplete) {
56+
inputs.forEach(i => {
57+
applyClasses(i, INPUT_CLASSES.disabled);
58+
i.disabled = true;
59+
});
60+
requestAnimationFrame(() => DOM.verifyOtpBtn.click());
61+
}
5762
}
5863
}
5964
});
6065

61-
// Enhanced keyboard navigation
66+
// Advanced keyboard navigation
6267
input.addEventListener('keydown', (e) => {
63-
if (e.key === 'Backspace' && !input.value && index > 0) {
64-
inputs[index - 1].focus();
65-
} else if (e.key === 'ArrowLeft' && index > 0) {
66-
inputs[index - 1].focus();
67-
} else if (e.key === 'ArrowRight' && index < inputs.length - 1) {
68-
inputs[index + 1].focus();
69-
}
70-
});
71-
72-
// Improved focus states
73-
input.addEventListener('focus', () => {
74-
input.classList.add('ring-2', 'ring-purple-300', 'border-purple-400', 'shadow-md');
75-
});
76-
77-
input.addEventListener('blur', () => {
78-
input.classList.remove('ring-2', 'ring-purple-300', 'border-purple-400', 'shadow-md');
68+
const keyHandlers = {
69+
'Backspace': () => !input.value && index > 0 && inputs[index - 1].focus(),
70+
'ArrowLeft': () => index > 0 && inputs[index - 1].focus(),
71+
'ArrowRight': () => index < inputs.length - 1 && inputs[index + 1].focus(),
72+
'Tab': (e) => {
73+
if (e.shiftKey && index > 0) {
74+
e.preventDefault();
75+
inputs[index - 1].focus();
76+
} else if (!e.shiftKey && index < inputs.length - 1) {
77+
e.preventDefault();
78+
inputs[index + 1].focus();
79+
}
80+
}
81+
};
82+
83+
keyHandlers[e.key]?.(e);
7984
});
85+
86+
// Enhanced focus management
87+
input.addEventListener('focus', () => applyClasses(input, INPUT_CLASSES.focus));
88+
input.addEventListener('blur', () => applyClasses(input, INPUT_CLASSES.focus, true));
8089
});
8190
}
8291

@@ -159,7 +168,7 @@ async function handleOtpRequestError(message) {
159168
await showAlert('error', 'Error', errorMessages[message] || message);
160169

161170
// Restore original button content
162-
DOM.requestOtpBtn.innerHTML = `<i class="fas fa-paper-plane"></i> Send OTP`;
171+
DOM.requestOtpBtn.innerHTML = `<i class="fas fa-paper-plane"></i> Resend OTP`;
163172
}
164173
// Enhanced OTP verification with better UI states
165174
export async function verifyOtp() {
@@ -207,24 +216,31 @@ export async function verifyOtp() {
207216

208217
// Helper function: Start OTP countdown timer
209218
function startOtpCountdown() {
210-
let secondsLeft = 30;
219+
let secondsLeft = 60;
211220
const originalBtnContent = DOM.requestOtpBtn.innerHTML;
221+
let countdownInterval;
222+
223+
// Clear any existing interval before starting new one
224+
if (countdownInterval) {
225+
clearInterval(countdownInterval);
226+
}
212227

213-
const countdownInterval = setInterval(() => {
228+
countdownInterval = setInterval(() => {
229+
if (secondsLeft <= 0) {
230+
clearInterval(countdownInterval);
231+
DOM.requestOtpBtn.disabled = false;
232+
DOM.requestOtpBtn.classList.remove('opacity-75', 'cursor-not-allowed');
233+
DOM.requestOtpBtn.innerHTML = originalBtnContent;
234+
return;
235+
}
236+
214237
DOM.requestOtpBtn.innerHTML = `
215238
<span class="flex items-center justify-center gap-2">
216239
<i class="far fa-clock"></i>
217240
<span>Resend in ${secondsLeft}s</span>
218241
</span>
219242
`;
220243
secondsLeft--;
221-
222-
if (secondsLeft < 0) {
223-
clearInterval(countdownInterval);
224-
DOM.requestOtpBtn.disabled = false;
225-
DOM.requestOtpBtn.classList.remove('opacity-75', 'cursor-not-allowed');
226-
DOM.requestOtpBtn.innerHTML = originalBtnContent;
227-
}
228244
}, 1000);
229245
}
230246

Assets/js/config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Configuration constants
22
export const CONFIG = {
33
googleScriptUrl: 'https://script.google.com/macros/s/AKfycbzngNDUtUYoCcSx2IBGNAc5JkQoBSYekWIdSG14wMTAUDfW52Zpfqyv7T05NcCe839n/exec', // auth
4+
googleEditUrl: 'https://script.google.com/macros/s/AKfycbws2bb7yExCPf9n46RDV6BNivkjXbVp-3ghYJc25hceqFDSxnV4_4FcoUczhuywnqKI/exec', // edit
45
sessionExpiryHours: 1, // Matches GAS session duration
56
otpExpiryMinutes: 5, // Matches GAS OTP duration
67

0 commit comments

Comments
 (0)