1+ /*
2+ Password rules:
3+ - Minimum length = 8
4+ - Must contain at least:
5+ - 1 number
6+ - 1 lowercase letter
7+ - 1 uppercase letter
8+ - 1 special character
9+ - Passwords must match
10+ */
11+
12+ // Helper: toggle success/danger styles for a rule item
13+ function setRuleState ( id , isMet ) {
14+ const el = document . getElementById ( id ) ;
15+ if ( ! el ) return ;
16+ el . classList . toggle ( 'list-group-item-success' , ! ! isMet ) ;
17+ el . classList . toggle ( 'list-group-item-danger' , ! isMet ) ;
18+ }
19+
20+ // Evaluate password and update UI
21+ function updateRules ( ) {
22+ const pw = document . getElementById ( 'passwordInput' ) ?. value || '' ;
23+ const pw2 = document . getElementById ( 'passwordVerify' ) ?. value || '' ;
24+
25+ const hasLen = pw . length >= 8 ;
26+ const hasUpper = / [ A - Z ] / . test ( pw ) ;
27+ const hasLower = / [ a - z ] / . test ( pw ) ;
28+ const hasDigit = / \d / . test ( pw ) ;
29+ const hasSpecial = / [ ! @ # $ % ^ & * ( ) \- _ = + \[ \] { } ; : \" ' < > . , ? \/ \\ | ` ~ ] / . test ( pw ) ;
30+ const matches = pw . length > 0 && pw === pw2 ;
31+
32+ setRuleState ( 'rule-length' , hasLen ) ;
33+ setRuleState ( 'rule-upper' , hasUpper ) ;
34+ setRuleState ( 'rule-lower' , hasLower ) ;
35+ setRuleState ( 'rule-number' , hasDigit ) ;
36+ setRuleState ( 'rule-special' , hasSpecial ) ;
37+ setRuleState ( 'rule-match' , matches ) ;
38+
39+ return { hasLen, hasUpper, hasLower, hasDigit, hasSpecial, matches } ;
40+ }
41+
42+ // Called by inline oninput handlers
43+ function inputPassword ( ) {
44+ updateRules ( ) ;
45+ }
46+
47+ // Called by the "Check Password" button
48+ function validatePassword ( ) {
49+ const { hasLen, hasUpper, hasLower, hasDigit, hasSpecial, matches } = updateRules ( ) ;
50+ const ok = hasLen && hasUpper && hasLower && hasDigit && hasSpecial && matches ;
51+ if ( ! ok ) {
52+ alert ( 'Password does not meet all requirements yet.' ) ;
53+ } else {
54+ alert ( 'Great! Your password meets all requirements.' ) ;
55+ }
56+ return ok ;
57+ }
58+
59+ // Save Password: copy current password to clipboard and alert
60+ function copyTextToClipboard ( text ) {
61+ if ( navigator . clipboard && window . isSecureContext ) {
62+ return navigator . clipboard . writeText ( text ) ;
63+ }
64+ // Fallback for insecure contexts/older browsers
65+ return new Promise ( ( resolve , reject ) => {
66+ try {
67+ const ta = document . createElement ( 'textarea' ) ;
68+ ta . value = text ;
69+ ta . setAttribute ( 'readonly' , '' ) ;
70+ ta . style . position = 'fixed' ;
71+ ta . style . left = '-9999px' ;
72+ document . body . appendChild ( ta ) ;
73+ ta . select ( ) ;
74+ const successful = document . execCommand ( 'copy' ) ;
75+ document . body . removeChild ( ta ) ;
76+ successful ? resolve ( ) : reject ( new Error ( 'Copy command was unsuccessful' ) ) ;
77+ } catch ( err ) {
78+ reject ( err ) ;
79+ }
80+ } ) ;
81+ }
82+
83+ // Show a Bootstrap-style alert in the page (fallback to alert())
84+ function showAlert ( message , type = 'success' , timeout = 4000 ) {
85+ const container = document . getElementById ( 'alertContainer' ) ;
86+ if ( ! container ) {
87+ alert ( message ) ;
88+ return ;
89+ }
90+ // Replace any existing alert
91+ container . innerHTML = '' ;
92+
93+ const wrapper = document . createElement ( 'div' ) ;
94+ wrapper . className = `alert alert-${ type } alert-dismissible fade show` ;
95+ wrapper . setAttribute ( 'role' , 'alert' ) ;
96+ wrapper . textContent = message ;
97+
98+ const btn = document . createElement ( 'button' ) ;
99+ btn . type = 'button' ;
100+ btn . className = 'btn-close' ;
101+ btn . setAttribute ( 'aria-label' , 'Close' ) ;
102+ btn . addEventListener ( 'click' , ( ) => wrapper . remove ( ) ) ;
103+
104+ wrapper . appendChild ( btn ) ;
105+ container . appendChild ( wrapper ) ;
106+
107+ if ( timeout && timeout > 0 ) {
108+ setTimeout ( ( ) => {
109+ try { wrapper . classList . remove ( 'show' ) ; } catch ( e ) { }
110+ // Remove after transition (~150ms), keep it simple
111+ setTimeout ( ( ) => wrapper . remove ( ) , 200 ) ;
112+ } , timeout ) ;
113+ }
114+ }
115+
116+ function savePassword ( ) {
117+ // Evaluate and sync UI
118+ const { hasLen, hasUpper, hasLower, hasDigit, hasSpecial, matches } = updateRules ( ) ;
119+ const pw = document . getElementById ( 'passwordInput' ) ?. value || '' ;
120+ const pw2 = document . getElementById ( 'passwordVerify' ) ?. value || '' ;
121+
122+ if ( ! pw ) {
123+ showAlert ( 'Please enter a password.' , 'warning' ) ;
124+ return false ;
125+ }
126+
127+ // Ensure both fields are filled and match
128+ if ( ! pw2 || ! matches ) {
129+ showAlert ( 'Passwords do not match and cannot be copied. Please make both entries identical.' , 'warning' ) ;
130+ return false ;
131+ }
132+
133+ // Ensure all complexity requirements are met
134+ if ( ! ( hasLen && hasUpper && hasLower && hasDigit && hasSpecial ) ) {
135+ showAlert ( 'Password does not meet all requirements and cannot be copied yet.' , 'warning' ) ;
136+ return false ;
137+ }
138+
139+ // Copy to clipboard
140+ copyTextToClipboard ( pw )
141+ . then ( ( ) => {
142+ showAlert ( 'Password has been saved to the clipboard.' , 'success' ) ;
143+ } )
144+ . catch ( ( err ) => {
145+ console . error ( 'Clipboard error:' , err ) ;
146+ showAlert ( 'Sorry, could not copy the password to the clipboard.' , 'danger' ) ;
147+ } ) ;
148+ return true ;
149+ }
150+
151+ // Measure the toggle button widths and adjust input padding so text/placeholder
152+ // appear centered relative to the entire input group.
153+ function adjustCentering ( ) {
154+ try {
155+ const pairs = [
156+ { inputId : 'passwordInput' , btnId : 'togglePassword' } ,
157+ { inputId : 'passwordVerify' , btnId : 'togglePasswordVerify' } ,
158+ ] ;
159+ pairs . forEach ( ( { inputId, btnId } ) => {
160+ const input = document . getElementById ( inputId ) ;
161+ const btn = document . getElementById ( btnId ) ;
162+ if ( ! input || ! btn ) return ;
163+ const btnRect = btn . getBoundingClientRect ( ) ;
164+ // Use measured width; minimal buffer to account for borders
165+ const offset = Math . ceil ( btnRect . width ) + 1 ;
166+ input . style . setProperty ( '--toggle-offset' , offset + 'px' ) ;
167+ } ) ;
168+ } catch ( _ ) {
169+ // no-op
170+ }
171+ }
172+
173+ // Initialize styles once DOM is ready (covers refresh / prefilled fields)
174+ function initUI ( ) {
175+ updateRules ( ) ;
176+ adjustCentering ( ) ;
177+ }
178+
179+ if ( document . readyState === 'loading' ) {
180+ document . addEventListener ( 'DOMContentLoaded' , initUI ) ;
181+ } else {
182+ initUI ( ) ;
183+ }
184+
185+ // Keep centering accurate on resize
186+ window . addEventListener ( 'resize' , adjustCentering ) ;
0 commit comments