feat(ui): integrate login/signup into navbar, enhance login flow, and set dynamic titles
- Removed outdated comments and unused <LoggedinPage> component - Cleaned up login.css header comment - Updated Home.vue navigation to use <router-link> for Login/Sign Up - Enhanced UserLogin.vue: • Added loginSuccess state, success styling, and green “Success! Redirecting” feedback • Redirect to homepage (/) after sign-in • Persist user data & simple auth token in local/session storage • Updated API call URL to /auth/login - Added route definitions for /signup in router - Added meta.title to Home/Login/Sign Up routes and global afterEach hook to update document.title - Simplified unused methods and cleaned up commented code in Home.vue
This commit is contained in:
		
							parent
							
								
									6422189f6c
								
							
						
					
					
						commit
						aa8dad9809
					
				
					 7 changed files with 924 additions and 73 deletions
				
			
		|  | @ -1,4 +1,3 @@ | ||||||
| /* login.css */ |  | ||||||
| * { | * { | ||||||
|   margin: 0; |   margin: 0; | ||||||
|   padding: 0; |   padding: 0; | ||||||
|  |  | ||||||
							
								
								
									
										517
									
								
								src/assets/css/components/signup.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										517
									
								
								src/assets/css/components/signup.css
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,517 @@ | ||||||
|  | * { | ||||||
|  |   margin: 0; | ||||||
|  |   padding: 0; | ||||||
|  |   box-sizing: border-box; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .signup-page { | ||||||
|  |   min-height: 100vh; | ||||||
|  |   background: linear-gradient(135deg, #0c0c0c 0%, #1a1a2e 50%, #16213e 100%); | ||||||
|  |   display: flex; | ||||||
|  |   align-items: center; | ||||||
|  |   justify-content: center; | ||||||
|  |   position: relative; | ||||||
|  |   overflow: hidden; | ||||||
|  |   font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .signup-bg { | ||||||
|  |   position: absolute; | ||||||
|  |   top: 0; | ||||||
|  |   left: 0; | ||||||
|  |   width: 100%; | ||||||
|  |   height: 100%; | ||||||
|  |   background: radial-gradient(circle at 20% 80%, rgba(0, 212, 255, 0.05) 0%, transparent 50%), | ||||||
|  |               radial-gradient(circle at 80% 20%, rgba(124, 58, 237, 0.05) 0%, transparent 50%); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .floating-elements { | ||||||
|  |   position: absolute; | ||||||
|  |   width: 100%; | ||||||
|  |   height: 100%; | ||||||
|  |   pointer-events: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .floating-element { | ||||||
|  |   position: absolute; | ||||||
|  |   width: 3px; | ||||||
|  |   height: 3px; | ||||||
|  |   background: linear-gradient(45deg, #00d4ff, #7c3aed); | ||||||
|  |   border-radius: 50%; | ||||||
|  |   animation: float 8s ease-in-out infinite; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @keyframes float { | ||||||
|  |   0%, 100% {  | ||||||
|  |     transform: translateY(0px) rotate(0deg);  | ||||||
|  |     opacity: 0.3;  | ||||||
|  |   } | ||||||
|  |   50% {  | ||||||
|  |     transform: translateY(-30px) rotate(180deg);  | ||||||
|  |     opacity: 0.8;  | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .signup-container { | ||||||
|  |   position: relative; | ||||||
|  |   z-index: 2; | ||||||
|  |   width: 100%; | ||||||
|  |   max-width: 480px; | ||||||
|  |   padding: 2rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .signup-card { | ||||||
|  |   background: rgba(255, 255, 255, 0.08); | ||||||
|  |   backdrop-filter: blur(20px); | ||||||
|  |   border: 1px solid rgba(255, 255, 255, 0.1); | ||||||
|  |   border-radius: 24px; | ||||||
|  |   padding: 3rem; | ||||||
|  |   box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); | ||||||
|  |   animation: slideInUp 0.8s ease-out; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @keyframes slideInUp { | ||||||
|  |   from { | ||||||
|  |     opacity: 0; | ||||||
|  |     transform: translateY(50px); | ||||||
|  |   } | ||||||
|  |   to { | ||||||
|  |     opacity: 1; | ||||||
|  |     transform: translateY(0); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .signup-header { | ||||||
|  |   text-align: center; | ||||||
|  |   margin-bottom: 2.5rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .logo { | ||||||
|  |   font-size: 2rem; | ||||||
|  |   font-weight: 800; | ||||||
|  |   color: #ffffff; | ||||||
|  |   margin-bottom: 1rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .logo-accent { | ||||||
|  |   background: linear-gradient(45deg, #00d4ff, #7c3aed); | ||||||
|  |   background-clip: text; | ||||||
|  |   -webkit-background-clip: text; | ||||||
|  |   color: transparent; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .signup-title { | ||||||
|  |   font-size: 1.75rem; | ||||||
|  |   font-weight: 700; | ||||||
|  |   color: #ffffff; | ||||||
|  |   margin-bottom: 0.5rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .signup-subtitle { | ||||||
|  |   color: #a0a0a0; | ||||||
|  |   font-size: 0.95rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .signup-form { | ||||||
|  |   display: flex; | ||||||
|  |   flex-direction: column; | ||||||
|  |   gap: 1.5rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .form-row { | ||||||
|  |   display: flex; | ||||||
|  |   gap: 1rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .form-group { | ||||||
|  |   display: flex; | ||||||
|  |   flex-direction: column; | ||||||
|  |   gap: 0.5rem; | ||||||
|  |   margin-bottom: 1rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .form-group.half-width { | ||||||
|  |   flex: 1; | ||||||
|  |   margin-bottom: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .form-label { | ||||||
|  |   font-weight: 600; | ||||||
|  |   color: #ffffff; | ||||||
|  |   font-size: 0.9rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .input-wrapper { | ||||||
|  |   position: relative; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .input-icon { | ||||||
|  |   position: absolute; | ||||||
|  |   left: 1rem; | ||||||
|  |   top: 50%; | ||||||
|  |   transform: translateY(-50%); | ||||||
|  |   color: #888; | ||||||
|  |   font-size: 0.9rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .form-input { | ||||||
|  |   width: 100%; | ||||||
|  |   padding: 1rem 1rem 1rem 2.75rem; | ||||||
|  |   background: rgba(255, 255, 255, 0.05); | ||||||
|  |   border: 2px solid rgba(255, 255, 255, 0.1); | ||||||
|  |   border-radius: 12px; | ||||||
|  |   color: #ffffff; | ||||||
|  |   font-size: 1rem; | ||||||
|  |   transition: all 0.3s ease; | ||||||
|  |   backdrop-filter: blur(10px); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .form-input:focus { | ||||||
|  |   outline: none; | ||||||
|  |   border-color: #00d4ff; | ||||||
|  |   box-shadow: 0 0 20px rgba(0, 212, 255, 0.2); | ||||||
|  |   background: rgba(255, 255, 255, 0.08); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .form-input.error { | ||||||
|  |   border-color: #ff4757; | ||||||
|  |   box-shadow: 0 0 20px rgba(255, 71, 87, 0.2); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .form-input::placeholder { | ||||||
|  |   color: #666; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .password-toggle { | ||||||
|  |   position: absolute; | ||||||
|  |   right: 1rem; | ||||||
|  |   top: 50%; | ||||||
|  |   transform: translateY(-50%); | ||||||
|  |   background: none; | ||||||
|  |   border: none; | ||||||
|  |   color: #888; | ||||||
|  |   cursor: pointer; | ||||||
|  |   padding: 0.25rem; | ||||||
|  |   transition: color 0.3s ease; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .password-toggle:hover { | ||||||
|  |   color: #00d4ff; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .password-strength { | ||||||
|  |   margin-top: 0.5rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .strength-bar { | ||||||
|  |   width: 100%; | ||||||
|  |   height: 4px; | ||||||
|  |   background-color: rgba(255, 255, 255, 0.1); | ||||||
|  |   border-radius: 2px; | ||||||
|  |   overflow: hidden; | ||||||
|  |   margin-bottom: 0.25rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .strength-fill { | ||||||
|  |   height: 100%; | ||||||
|  |   transition: width 0.3s ease, background-color 0.3s ease; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .strength-fill.weak { | ||||||
|  |   background: linear-gradient(45deg, #ff4757, #ff6b7a); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .strength-fill.fair { | ||||||
|  |   background: linear-gradient(45deg, #ffa502, #ffb347); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .strength-fill.good { | ||||||
|  |   background: linear-gradient(45deg, #2ed573, #7bed9f); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .strength-fill.strong { | ||||||
|  |   background: linear-gradient(45deg, #00d4ff, #7c3aed); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .strength-text { | ||||||
|  |   font-size: 0.75rem; | ||||||
|  |   font-weight: 500; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .strength-text.weak { | ||||||
|  |   color: #ff4757; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .strength-text.fair { | ||||||
|  |   color: #ffa502; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .strength-text.good { | ||||||
|  |   color: #2ed573; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .strength-text.strong { | ||||||
|  |   background: linear-gradient(45deg, #00d4ff, #7c3aed); | ||||||
|  |   background-clip: text; | ||||||
|  |   -webkit-background-clip: text; | ||||||
|  |   color: transparent; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .password-mismatch { | ||||||
|  |   display: flex; | ||||||
|  |   align-items: center; | ||||||
|  |   gap: 0.5rem; | ||||||
|  |   margin-top: 0.5rem; | ||||||
|  |   font-size: 0.75rem; | ||||||
|  |   color: #ff4757; | ||||||
|  |   animation: shake 0.5s ease-in-out; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .form-options { | ||||||
|  |   display: flex; | ||||||
|  |   flex-direction: column; | ||||||
|  |   gap: 1rem; | ||||||
|  |   margin-bottom: 0.5rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .checkbox-wrapper { | ||||||
|  |   display: flex; | ||||||
|  |   align-items: flex-start; | ||||||
|  |   cursor: pointer; | ||||||
|  |   gap: 0.75rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .checkbox-input { | ||||||
|  |   display: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .checkbox-custom { | ||||||
|  |   width: 18px; | ||||||
|  |   height: 18px; | ||||||
|  |   border: 2px solid rgba(255, 255, 255, 0.3); | ||||||
|  |   border-radius: 4px; | ||||||
|  |   display: flex; | ||||||
|  |   align-items: center; | ||||||
|  |   justify-content: center; | ||||||
|  |   transition: all 0.3s ease; | ||||||
|  |   flex-shrink: 0; | ||||||
|  |   margin-top: 1px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .checkbox-input:checked + .checkbox-custom { | ||||||
|  |   background: linear-gradient(45deg, #00d4ff, #7c3aed); | ||||||
|  |   border-color: transparent; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .checkbox-input:checked + .checkbox-custom::after { | ||||||
|  |   content: '✓'; | ||||||
|  |   color: white; | ||||||
|  |   font-size: 12px; | ||||||
|  |   font-weight: bold; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .checkbox-label { | ||||||
|  |   color: #a0a0a0; | ||||||
|  |   font-size: 0.9rem; | ||||||
|  |   line-height: 1.4; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .link { | ||||||
|  |   color: #00d4ff; | ||||||
|  |   text-decoration: none; | ||||||
|  |   transition: color 0.3s ease; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .link:hover { | ||||||
|  |   color: #7c3aed; | ||||||
|  |   text-decoration: underline; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .signup-button { | ||||||
|  |   width: 100%; | ||||||
|  |   padding: 1rem; | ||||||
|  |   background: linear-gradient(45deg, #00d4ff, #7c3aed); | ||||||
|  |   color: white; | ||||||
|  |   border: none; | ||||||
|  |   border-radius: 12px; | ||||||
|  |   font-weight: 600; | ||||||
|  |   font-size: 1.1rem; | ||||||
|  |   cursor: pointer; | ||||||
|  |   transition: all 0.3s ease; | ||||||
|  |   margin-top: 0.5rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .signup-button:hover:not(:disabled) { | ||||||
|  |   transform: translateY(-2px); | ||||||
|  |   box-shadow: 0 15px 40px rgba(0, 212, 255, 0.4); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .signup-button:disabled { | ||||||
|  |   opacity: 0.5; | ||||||
|  |   cursor: not-allowed; | ||||||
|  |   transform: none; | ||||||
|  |   background: rgba(255, 255, 255, 0.1); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .loading-text { | ||||||
|  |   display: flex; | ||||||
|  |   align-items: center; | ||||||
|  |   justify-content: center; | ||||||
|  |   gap: 0.5rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .divider { | ||||||
|  |   position: relative; | ||||||
|  |   text-align: center; | ||||||
|  |   margin: 1.5rem 0; | ||||||
|  |   color: #666; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .divider::before { | ||||||
|  |   content: ''; | ||||||
|  |   position: absolute; | ||||||
|  |   top: 50%; | ||||||
|  |   left: 0; | ||||||
|  |   right: 0; | ||||||
|  |   height: 1px; | ||||||
|  |   background: rgba(255, 255, 255, 0.1); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .divider span { | ||||||
|  |   background: rgba(255, 255, 255, 0.08); | ||||||
|  |   padding: 0 1rem; | ||||||
|  |   position: relative; | ||||||
|  |   z-index: 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .social-signup { | ||||||
|  |   width: 100%; | ||||||
|  |   padding: 0.875rem; | ||||||
|  |   background: rgba(255, 255, 255, 0.05); | ||||||
|  |   border: 2px solid rgba(255, 255, 255, 0.1); | ||||||
|  |   border-radius: 12px; | ||||||
|  |   color: #ffffff; | ||||||
|  |   font-weight: 600; | ||||||
|  |   cursor: pointer; | ||||||
|  |   transition: all 0.3s ease; | ||||||
|  |   display: flex; | ||||||
|  |   align-items: center; | ||||||
|  |   justify-content: center; | ||||||
|  |   gap: 0.75rem; | ||||||
|  |   margin-bottom: 0.75rem; | ||||||
|  |   backdrop-filter: blur(10px); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .social-signup:hover { | ||||||
|  |   background: rgba(255, 255, 255, 0.08); | ||||||
|  |   border-color: rgba(255, 255, 255, 0.2); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .social-signup.google:hover { | ||||||
|  |   border-color: rgba(219, 68, 55, 0.5); | ||||||
|  |   box-shadow: 0 0 20px rgba(219, 68, 55, 0.1); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .social-signup.github:hover { | ||||||
|  |   border-color: rgba(255, 255, 255, 0.5); | ||||||
|  |   box-shadow: 0 0 20px rgba(255, 255, 255, 0.1); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .error-message { | ||||||
|  |   background: rgba(255, 71, 87, 0.1); | ||||||
|  |   border: 1px solid rgba(255, 71, 87, 0.3); | ||||||
|  |   border-radius: 12px; | ||||||
|  |   padding: 1rem; | ||||||
|  |   color: #ff4757; | ||||||
|  |   font-size: 0.9rem; | ||||||
|  |   display: flex; | ||||||
|  |   align-items: center; | ||||||
|  |   gap: 0.5rem; | ||||||
|  |   margin-top: 1rem; | ||||||
|  |   animation: shake 0.5s ease-in-out; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .success-message { | ||||||
|  |   background: rgba(46, 213, 115, 0.1); | ||||||
|  |   border: 1px solid rgba(46, 213, 115, 0.3); | ||||||
|  |   border-radius: 12px; | ||||||
|  |   padding: 1rem; | ||||||
|  |   color: #2ed573; | ||||||
|  |   font-size: 0.9rem; | ||||||
|  |   display: flex; | ||||||
|  |   align-items: center; | ||||||
|  |   gap: 0.5rem; | ||||||
|  |   margin-top: 1rem; | ||||||
|  |   animation: slideInUp 0.5s ease-out; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @keyframes shake { | ||||||
|  |   0%, 100% { transform: translateX(0); } | ||||||
|  |   25% { transform: translateX(-5px); } | ||||||
|  |   75% { transform: translateX(5px); } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .login-prompt { | ||||||
|  |   text-align: center; | ||||||
|  |   margin-top: 2rem; | ||||||
|  |   padding-top: 2rem; | ||||||
|  |   border-top: 1px solid rgba(255, 255, 255, 0.1); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .login-prompt p { | ||||||
|  |   color: #a0a0a0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .login-link { | ||||||
|  |   color: #00d4ff; | ||||||
|  |   text-decoration: none; | ||||||
|  |   font-weight: 600; | ||||||
|  |   transition: color 0.3s ease; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .login-link:hover { | ||||||
|  |   color: #7c3aed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Responsive */ | ||||||
|  | @media (max-width: 640px) { | ||||||
|  |   .signup-container { | ||||||
|  |     padding: 1rem; | ||||||
|  |     max-width: 100%; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   .signup-card { | ||||||
|  |     padding: 2rem; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   .form-row { | ||||||
|  |     flex-direction: column; | ||||||
|  |     gap: 0; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   .form-group.half-width { | ||||||
|  |     margin-bottom: 1rem; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   .checkbox-wrapper { | ||||||
|  |     align-items: flex-start; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   .checkbox-label { | ||||||
|  |     font-size: 0.85rem; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @media (max-width: 480px) { | ||||||
|  |   .signup-card { | ||||||
|  |     padding: 1.5rem; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   .signup-title { | ||||||
|  |     font-size: 1.5rem; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   .logo { | ||||||
|  |     font-size: 1.75rem; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -1,19 +1,24 @@ | ||||||
| <template> | <template> | ||||||
|   <div class="homepage"> |   <div class="homepage"> | ||||||
|     <!-- Navigation --> |  | ||||||
|     <nav class="navbar"> |     <nav class="navbar"> | ||||||
|       <div class="nav-content"> |       <div class="nav-content"> | ||||||
|         <div class="logo" @click="scrollTo('hero')"> |         <div class="logo" @click="scrollTo('hero')"> | ||||||
|           Ride<span class="logo-accent">Aware</span> |           Ride<span class="logo-accent">Aware</span> | ||||||
|         </div> |         </div> | ||||||
|         <ul class="nav-links"> |         <ul class="nav-links"> | ||||||
|           <li><a href="#" @click.prevent="scrollTo('features')">Features</a></li> |           <li> | ||||||
|           <li><a href="/newsletters" @click.prevent="goToNewsletters">Newsletters</a></li> |             <a href="#" @click.prevent="scrollTo('features')">Features</a> | ||||||
|  |           </li> | ||||||
|  |           <li> | ||||||
|  |             <router-link to="/login" class="login-link">Login</router-link> | ||||||
|  |           </li> | ||||||
|  |           <li> | ||||||
|  |             <router-link to="/signup" class="signup-btn">Sign Up</router-link> | ||||||
|  |           </li> | ||||||
|         </ul> |         </ul> | ||||||
|       </div> |       </div> | ||||||
|     </nav> |     </nav> | ||||||
| 
 | 
 | ||||||
|     <!-- Hero Section --> |  | ||||||
|     <section id="hero" class="hero"> |     <section id="hero" class="hero"> | ||||||
|       <div class="hero-bg"></div> |       <div class="hero-bg"></div> | ||||||
|       <div class="floating-elements"> |       <div class="floating-elements"> | ||||||
|  | @ -72,7 +77,6 @@ | ||||||
|       </div> |       </div> | ||||||
|     </section> |     </section> | ||||||
| 
 | 
 | ||||||
|     <!-- Features Section --> |  | ||||||
|     <section id="features" class="features"> |     <section id="features" class="features"> | ||||||
|       <div class="section-header"> |       <div class="section-header"> | ||||||
|         <h2 class="section-title">{{ featuresTitle }}</h2> |         <h2 class="section-title">{{ featuresTitle }}</h2> | ||||||
|  | @ -101,7 +105,6 @@ | ||||||
|       </div> |       </div> | ||||||
|     </section> |     </section> | ||||||
| 
 | 
 | ||||||
|     <!-- Stats Section --> |  | ||||||
|     <section class="stats"> |     <section class="stats"> | ||||||
|       <div class="container"> |       <div class="container"> | ||||||
|         <div class="stats-grid"> |         <div class="stats-grid"> | ||||||
|  | @ -113,7 +116,6 @@ | ||||||
|       </div> |       </div> | ||||||
|     </section> |     </section> | ||||||
| 
 | 
 | ||||||
|     <!-- Footer --> |  | ||||||
|     <footer class="footer"> |     <footer class="footer"> | ||||||
|       <div class="container"> |       <div class="container"> | ||||||
|         <div class="footer-content"> |         <div class="footer-content"> | ||||||
|  | @ -225,7 +227,6 @@ export default { | ||||||
|     const animatedStats = reactive({}) |     const animatedStats = reactive({}) | ||||||
|     const floatingElements = reactive([]) |     const floatingElements = reactive([]) | ||||||
| 
 | 
 | ||||||
|     // Generate floating elements |  | ||||||
|     const generateFloatingElements = () => { |     const generateFloatingElements = () => { | ||||||
|       for (let i = 0; i < 15; i++) { |       for (let i = 0; i < 15; i++) { | ||||||
|         floatingElements.push({ |         floatingElements.push({ | ||||||
|  | @ -239,7 +240,6 @@ export default { | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Animate stats on scroll |  | ||||||
|     const animateStats = () => { |     const animateStats = () => { | ||||||
|       platformStats.forEach(stat => { |       platformStats.forEach(stat => { | ||||||
|         let current = 0 |         let current = 0 | ||||||
|  | @ -264,14 +264,11 @@ export default { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const goToNewsletters = () => { |     const goToNewsletters = () => { | ||||||
|       // Handle navigation to newsletters page |  | ||||||
|       // This could be a router push in a real Vue app |  | ||||||
|       window.location.href = '/newsletters' |       window.location.href = '/newsletters' | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const handleNotifyMe = () => { |     const handleNotifyMe = () => { | ||||||
|       if (isValidEmail.value) { |       if (isValidEmail.value) { | ||||||
|         // Here you would typically send the email to your backend |  | ||||||
|         alert(`Thanks! We'll notify you at ${emailInput.value} when RideAware launches.`) |         alert(`Thanks! We'll notify you at ${emailInput.value} when RideAware launches.`) | ||||||
|         emailInput.value = '' |         emailInput.value = '' | ||||||
|       } else { |       } else { | ||||||
|  | @ -293,7 +290,6 @@ export default { | ||||||
|     onMounted(() => { |     onMounted(() => { | ||||||
|       generateFloatingElements() |       generateFloatingElements() | ||||||
|        |        | ||||||
|       // Intersection Observer for stats animation |  | ||||||
|       const observer = new IntersectionObserver((entries) => { |       const observer = new IntersectionObserver((entries) => { | ||||||
|         entries.forEach(entry => { |         entries.forEach(entry => { | ||||||
|           if (entry.isIntersecting) { |           if (entry.isIntersecting) { | ||||||
|  | @ -308,7 +304,6 @@ export default { | ||||||
|         observer.observe(statsSection) |         observer.observe(statsSection) | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       // Parallax effect for hero background |  | ||||||
|       window.addEventListener('scroll', () => { |       window.addEventListener('scroll', () => { | ||||||
|         const scrolled = window.pageYOffset |         const scrolled = window.pageYOffset | ||||||
|         const parallax = document.querySelector('.hero-bg') |         const parallax = document.querySelector('.hero-bg') | ||||||
|  | @ -318,7 +313,6 @@ export default { | ||||||
|         } |         } | ||||||
|       }) |       }) | ||||||
| 
 | 
 | ||||||
|       // Animate mockup stats |  | ||||||
|       let statIndex = 0 |       let statIndex = 0 | ||||||
|       setInterval(() => { |       setInterval(() => { | ||||||
|         activeStat.value = mockupStats[statIndex].id |         activeStat.value = mockupStats[statIndex].id | ||||||
|  |  | ||||||
|  | @ -1,19 +0,0 @@ | ||||||
| <template> |  | ||||||
|     <div class="logged-in"> |  | ||||||
|       <h2>You have successfully logged in!</h2> |  | ||||||
|       <p>Welcome back to RideAware!</p> |  | ||||||
|     </div> |  | ||||||
|   </template> |  | ||||||
|    |  | ||||||
|   <script> |  | ||||||
|   export default { |  | ||||||
|     name: 'LoggedInPage', |  | ||||||
|   }; |  | ||||||
|   </script> |  | ||||||
|    |  | ||||||
|   <style scoped> |  | ||||||
|   .logged-in { |  | ||||||
|     text-align: center; |  | ||||||
|   } |  | ||||||
|   </style> |  | ||||||
|    |  | ||||||
|  | @ -1,6 +1,5 @@ | ||||||
| <template> | <template> | ||||||
|   <div class="login-page"> |   <div class="login-page"> | ||||||
|     <!-- Background Elements --> |  | ||||||
|     <div class="login-bg"> |     <div class="login-bg"> | ||||||
|       <div class="floating-elements"> |       <div class="floating-elements"> | ||||||
|         <div  |         <div  | ||||||
|  | @ -12,10 +11,8 @@ | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
| 
 | 
 | ||||||
|     <!-- Login Container --> |  | ||||||
|     <div class="login-container"> |     <div class="login-container"> | ||||||
|       <div class="login-card"> |       <div class="login-card"> | ||||||
|         <!-- Logo/Header --> |  | ||||||
|         <div class="login-header"> |         <div class="login-header"> | ||||||
|           <div class="logo"> |           <div class="logo"> | ||||||
|             Ride<span class="logo-accent">Aware</span> |             Ride<span class="logo-accent">Aware</span> | ||||||
|  | @ -24,7 +21,6 @@ | ||||||
|           <p class="login-subtitle">Sign in to continue your cycling journey</p> |           <p class="login-subtitle">Sign in to continue your cycling journey</p> | ||||||
|         </div> |         </div> | ||||||
| 
 | 
 | ||||||
|         <!-- Login Form --> |  | ||||||
|         <form @submit.prevent="login" class="login-form"> |         <form @submit.prevent="login" class="login-form"> | ||||||
|           <div class="form-group"> |           <div class="form-group"> | ||||||
|             <label for="username" class="form-label">Username</label> |             <label for="username" class="form-label">Username</label> | ||||||
|  | @ -77,14 +73,18 @@ | ||||||
|           <button  |           <button  | ||||||
|             type="submit"  |             type="submit"  | ||||||
|             class="login-button" |             class="login-button" | ||||||
|             :class="{ 'loading': isLoading }" |             :class="{ 'loading': isLoading, 'success': loginSuccess }" | ||||||
|             :disabled="isLoading" |             :disabled="isLoading" | ||||||
|           > |           > | ||||||
|             <span v-if="!isLoading">Sign In</span> |             <span v-if="!isLoading && !loginSuccess">Sign In</span> | ||||||
|             <span v-else class="loading-text"> |             <span v-else-if="isLoading" class="loading-text"> | ||||||
|               <i class="fas fa-spinner fa-spin"></i> |               <i class="fas fa-spinner fa-spin"></i> | ||||||
|               Signing In... |               Signing In... | ||||||
|             </span> |             </span> | ||||||
|  |             <span v-else-if="loginSuccess" class="success-text"> | ||||||
|  |               <i class="fas fa-check"></i> | ||||||
|  |               Success! Redirecting... | ||||||
|  |             </span> | ||||||
|           </button> |           </button> | ||||||
| 
 | 
 | ||||||
|           <div class="divider"> |           <div class="divider"> | ||||||
|  | @ -102,13 +102,16 @@ | ||||||
|           </button> |           </button> | ||||||
|         </form> |         </form> | ||||||
| 
 | 
 | ||||||
|         <!-- Error Message --> |  | ||||||
|         <div v-if="error" class="error-message"> |         <div v-if="error" class="error-message"> | ||||||
|           <i class="fas fa-exclamation-triangle"></i> |           <i class="fas fa-exclamation-triangle"></i> | ||||||
|           {{ error }} |           {{ error }} | ||||||
|         </div> |         </div> | ||||||
| 
 | 
 | ||||||
|         <!-- Sign Up Link --> |         <div v-if="loginSuccess" class="success-message"> | ||||||
|  |           <i class="fas fa-check-circle"></i> | ||||||
|  |           Login successful! Redirecting to homepage... | ||||||
|  |         </div> | ||||||
|  | 
 | ||||||
|         <div class="signup-prompt"> |         <div class="signup-prompt"> | ||||||
|           <p>Don't have an account? <a href="/signup" class="signup-link">Sign up</a></p> |           <p>Don't have an account? <a href="/signup" class="signup-link">Sign up</a></p> | ||||||
|         </div> |         </div> | ||||||
|  | @ -129,6 +132,7 @@ export default { | ||||||
|       password: '', |       password: '', | ||||||
|       error: null, |       error: null, | ||||||
|       isLoading: false, |       isLoading: false, | ||||||
|  |       loginSuccess: false, | ||||||
|       showPassword: false, |       showPassword: false, | ||||||
|       rememberMe: false, |       rememberMe: false, | ||||||
|       floatingElements: [] |       floatingElements: [] | ||||||
|  | @ -146,26 +150,35 @@ export default { | ||||||
| 
 | 
 | ||||||
|       this.isLoading = true; |       this.isLoading = true; | ||||||
|       this.error = null; |       this.error = null; | ||||||
|  |       this.loginSuccess = false; | ||||||
| 
 | 
 | ||||||
|       try { |       try { | ||||||
|         const response = await axios.post('http://127.0.0.1:5000/login', { |         const response = await axios.post('http://localhost:5000/auth/login', { | ||||||
|           username: this.username, |           username: this.username, | ||||||
|           password: this.password, |           password: this.password, | ||||||
|         }); |         }); | ||||||
|          |          | ||||||
|         console.log('Login successful:', response.data); |         console.log('Login successful:', response.data); | ||||||
|          |          | ||||||
|         // Store user data if remember me is checked |         const userData = { | ||||||
|  |           user_id: response.data.user_id, | ||||||
|  |           username: this.username, | ||||||
|  |           loginTime: new Date().toISOString() | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|         if (this.rememberMe) { |         if (this.rememberMe) { | ||||||
|           localStorage.setItem('user', JSON.stringify(response.data)); |           localStorage.setItem('user', JSON.stringify(userData)); | ||||||
|  |           localStorage.setItem('authToken', 'authenticated'); | ||||||
|         } else { |         } else { | ||||||
|           sessionStorage.setItem('user', JSON.stringify(response.data)); |           sessionStorage.setItem('user', JSON.stringify(userData)); | ||||||
|  |           sessionStorage.setItem('authToken', 'authenticated'); | ||||||
|         } |         } | ||||||
|          |          | ||||||
|         // Add success animation before redirect |         this.loginSuccess = true; | ||||||
|  |          | ||||||
|         setTimeout(() => { |         setTimeout(() => { | ||||||
|           this.$router.push('/logged-in'); |           this.$router.push('/'); | ||||||
|         }, 500); |         }, 1500); | ||||||
|          |          | ||||||
|       } catch (error) { |       } catch (error) { | ||||||
|         console.error('Login failed:', error.response?.data); |         console.error('Login failed:', error.response?.data); | ||||||
|  |  | ||||||
							
								
								
									
										344
									
								
								src/components/UserSignup.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										344
									
								
								src/components/UserSignup.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,344 @@ | ||||||
|  | <template> | ||||||
|  |   <div class="signup-page"> | ||||||
|  |     <div class="signup-bg"> | ||||||
|  |       <div class="floating-elements"> | ||||||
|  |         <div  | ||||||
|  |           v-for="(element, index) in floatingElements"  | ||||||
|  |           :key="index" | ||||||
|  |           class="floating-element" | ||||||
|  |           :style="element.style"> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  |     <div class="signup-container"> | ||||||
|  |       <div class="signup-card"> | ||||||
|  |         <div class="signup-header"> | ||||||
|  |           <div class="logo"> | ||||||
|  |             Ride<span class="logo-accent">Aware</span> | ||||||
|  |           </div> | ||||||
|  |           <h2 class="signup-title">Join RideAware</h2> | ||||||
|  |           <p class="signup-subtitle">Create your account to start tracking your cycling journey</p> | ||||||
|  |         </div> | ||||||
|  | 
 | ||||||
|  |         <form @submit.prevent="signup" class="signup-form"> | ||||||
|  |           <div class="form-row"> | ||||||
|  |             <div class="form-group half-width"> | ||||||
|  |               <label for="firstName" class="form-label">First Name</label> | ||||||
|  |               <div class="input-wrapper"> | ||||||
|  |                 <i class="fas fa-user input-icon"></i> | ||||||
|  |                 <input  | ||||||
|  |                   type="text"  | ||||||
|  |                   id="firstName"  | ||||||
|  |                   v-model="firstName"  | ||||||
|  |                   class="form-input" | ||||||
|  |                   :class="{ 'error': error && !firstName }" | ||||||
|  |                   placeholder="First name" | ||||||
|  |                   required  | ||||||
|  |                 /> | ||||||
|  |               </div> | ||||||
|  |             </div> | ||||||
|  | 
 | ||||||
|  |             <div class="form-group half-width"> | ||||||
|  |               <label for="lastName" class="form-label">Last Name</label> | ||||||
|  |               <div class="input-wrapper"> | ||||||
|  |                 <i class="fas fa-user input-icon"></i> | ||||||
|  |                 <input  | ||||||
|  |                   type="text"  | ||||||
|  |                   id="lastName"  | ||||||
|  |                   v-model="lastName"  | ||||||
|  |                   class="form-input" | ||||||
|  |                   :class="{ 'error': error && !lastName }" | ||||||
|  |                   placeholder="Last name" | ||||||
|  |                   required  | ||||||
|  |                 /> | ||||||
|  |               </div> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  | 
 | ||||||
|  |           <div class="form-group"> | ||||||
|  |             <label for="username" class="form-label">Username</label> | ||||||
|  |             <div class="input-wrapper"> | ||||||
|  |               <i class="fas fa-at input-icon"></i> | ||||||
|  |               <input  | ||||||
|  |                 type="text"  | ||||||
|  |                 id="username"  | ||||||
|  |                 v-model="username"  | ||||||
|  |                 class="form-input" | ||||||
|  |                 :class="{ 'error': error && !username }" | ||||||
|  |                 placeholder="Choose a username" | ||||||
|  |                 required  | ||||||
|  |               /> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  | 
 | ||||||
|  |           <div class="form-group"> | ||||||
|  |             <label for="email" class="form-label">Email</label> | ||||||
|  |             <div class="input-wrapper"> | ||||||
|  |               <i class="fas fa-envelope input-icon"></i> | ||||||
|  |               <input  | ||||||
|  |                 type="email"  | ||||||
|  |                 id="email"  | ||||||
|  |                 v-model="email"  | ||||||
|  |                 class="form-input" | ||||||
|  |                 :class="{ 'error': error && !email }" | ||||||
|  |                 placeholder="Enter your email" | ||||||
|  |                 required  | ||||||
|  |               /> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  | 
 | ||||||
|  |           <div class="form-group"> | ||||||
|  |             <label for="password" class="form-label">Password</label> | ||||||
|  |             <div class="input-wrapper"> | ||||||
|  |               <i class="fas fa-lock input-icon"></i> | ||||||
|  |               <input  | ||||||
|  |                 :type="showPassword ? 'text' : 'password'"  | ||||||
|  |                 id="password"  | ||||||
|  |                 v-model="password"  | ||||||
|  |                 class="form-input" | ||||||
|  |                 :class="{ 'error': error && !password }" | ||||||
|  |                 placeholder="Create a password" | ||||||
|  |                 required  | ||||||
|  |               /> | ||||||
|  |               <button  | ||||||
|  |                 type="button"  | ||||||
|  |                 class="password-toggle" | ||||||
|  |                 @click="showPassword = !showPassword" | ||||||
|  |               > | ||||||
|  |                 <i :class="showPassword ? 'fas fa-eye-slash' : 'fas fa-eye'"></i> | ||||||
|  |               </button> | ||||||
|  |             </div> | ||||||
|  |             <div class="password-strength" v-if="password"> | ||||||
|  |               <div class="strength-bar"> | ||||||
|  |                 <div  | ||||||
|  |                   class="strength-fill"  | ||||||
|  |                   :class="passwordStrength.class" | ||||||
|  |                   :style="{ width: passwordStrength.width }" | ||||||
|  |                 ></div> | ||||||
|  |               </div> | ||||||
|  |               <span class="strength-text" :class="passwordStrength.class"> | ||||||
|  |                 {{ passwordStrength.text }} | ||||||
|  |               </span> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  | 
 | ||||||
|  |           <div class="form-group"> | ||||||
|  |             <label for="confirmPassword" class="form-label">Confirm Password</label> | ||||||
|  |             <div class="input-wrapper"> | ||||||
|  |               <i class="fas fa-lock input-icon"></i> | ||||||
|  |               <input  | ||||||
|  |                 :type="showConfirmPassword ? 'text' : 'password'"  | ||||||
|  |                 id="confirmPassword"  | ||||||
|  |                 v-model="confirmPassword"  | ||||||
|  |                 class="form-input" | ||||||
|  |                 :class="{ 'error': error && !confirmPassword || passwordMismatch }" | ||||||
|  |                 placeholder="Confirm your password" | ||||||
|  |                 required  | ||||||
|  |               /> | ||||||
|  |               <button  | ||||||
|  |                 type="button"  | ||||||
|  |                 class="password-toggle" | ||||||
|  |                 @click="showConfirmPassword = !showConfirmPassword" | ||||||
|  |               > | ||||||
|  |                 <i :class="showConfirmPassword ? 'fas fa-eye-slash' : 'fas fa-eye'"></i> | ||||||
|  |               </button> | ||||||
|  |             </div> | ||||||
|  |             <div v-if="passwordMismatch" class="password-mismatch"> | ||||||
|  |               <i class="fas fa-exclamation-triangle"></i> | ||||||
|  |               Passwords do not match | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  | 
 | ||||||
|  |           <div class="form-options"> | ||||||
|  |             <label class="checkbox-wrapper"> | ||||||
|  |               <input type="checkbox" v-model="agreeToTerms" class="checkbox-input" required> | ||||||
|  |               <span class="checkbox-custom"></span> | ||||||
|  |               <span class="checkbox-label"> | ||||||
|  |                 I agree to the <a href="/terms" class="link">Terms of Service</a>  | ||||||
|  |                 and <a href="/privacy" class="link">Privacy Policy</a> | ||||||
|  |               </span> | ||||||
|  |             </label> | ||||||
|  |           </div> | ||||||
|  | 
 | ||||||
|  |           <div class="form-options"> | ||||||
|  |             <label class="checkbox-wrapper"> | ||||||
|  |               <input type="checkbox" v-model="subscribeNewsletter" class="checkbox-input"> | ||||||
|  |               <span class="checkbox-custom"></span> | ||||||
|  |               <span class="checkbox-label"> | ||||||
|  |                 Subscribe to newsletter for cycling tips and updates | ||||||
|  |               </span> | ||||||
|  |             </label> | ||||||
|  |           </div> | ||||||
|  | 
 | ||||||
|  |           <button  | ||||||
|  |             type="submit"  | ||||||
|  |             class="signup-button" | ||||||
|  |             :class="{ 'loading': isLoading }" | ||||||
|  |             :disabled="isLoading || !isFormValid" | ||||||
|  |           > | ||||||
|  |             <span v-if="!isLoading">Create Account</span> | ||||||
|  |             <span v-else class="loading-text"> | ||||||
|  |               <i class="fas fa-spinner fa-spin"></i> | ||||||
|  |               Creating Account... | ||||||
|  |             </span> | ||||||
|  |           </button> | ||||||
|  | 
 | ||||||
|  |           <div class="divider"> | ||||||
|  |             <span>or</span> | ||||||
|  |           </div> | ||||||
|  | 
 | ||||||
|  |           <button type="button" class="social-signup google"> | ||||||
|  |             <i class="fab fa-google"></i> | ||||||
|  |             Sign up with Google | ||||||
|  |           </button> | ||||||
|  | 
 | ||||||
|  |           <button type="button" class="social-signup github"> | ||||||
|  |             <i class="fab fa-github"></i> | ||||||
|  |             Sign up with GitHub | ||||||
|  |           </button> | ||||||
|  |         </form> | ||||||
|  | 
 | ||||||
|  |         <div v-if="error" class="error-message"> | ||||||
|  |           <i class="fas fa-exclamation-triangle"></i> | ||||||
|  |           {{ error }} | ||||||
|  |         </div> | ||||||
|  | 
 | ||||||
|  |         <div v-if="successMessage" class="success-message"> | ||||||
|  |           <i class="fas fa-check-circle"></i> | ||||||
|  |           {{ successMessage }} | ||||||
|  |         </div> | ||||||
|  | 
 | ||||||
|  |         <div class="login-prompt"> | ||||||
|  |           <p>Already have an account? <a href="/login" class="login-link">Sign in</a></p> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script> | ||||||
|  | import axios from 'axios'; | ||||||
|  | import '@/assets/css/components/signup.css'; | ||||||
|  | 
 | ||||||
|  | export default { | ||||||
|  |   name: 'UserSignup', | ||||||
|  |   data() { | ||||||
|  |     return { | ||||||
|  |       firstName: '', | ||||||
|  |       lastName: '', | ||||||
|  |       username: '', | ||||||
|  |       email: '', | ||||||
|  |       password: '', | ||||||
|  |       confirmPassword: '', | ||||||
|  |       error: null, | ||||||
|  |       successMessage: null, | ||||||
|  |       isLoading: false, | ||||||
|  |       showPassword: false, | ||||||
|  |       showConfirmPassword: false, | ||||||
|  |       agreeToTerms: false, | ||||||
|  |       subscribeNewsletter: false, | ||||||
|  |       floatingElements: [] | ||||||
|  |     }; | ||||||
|  |   }, | ||||||
|  |   computed: { | ||||||
|  |     passwordMismatch() { | ||||||
|  |       return this.confirmPassword && this.password !== this.confirmPassword; | ||||||
|  |     }, | ||||||
|  |     passwordStrength() { | ||||||
|  |       if (!this.password) return { width: '0%', class: '', text: '' }; | ||||||
|  |        | ||||||
|  |       let score = 0; | ||||||
|  |       let feedback = []; | ||||||
|  |        | ||||||
|  |       if (this.password.length >= 8) score += 1; | ||||||
|  |       else feedback.push('at least 8 characters'); | ||||||
|  |        | ||||||
|  |       if (/[A-Z]/.test(this.password)) score += 1; | ||||||
|  |       else feedback.push('uppercase letter'); | ||||||
|  |        | ||||||
|  |       if (/[a-z]/.test(this.password)) score += 1; | ||||||
|  |       else feedback.push('lowercase letter'); | ||||||
|  | 
 | ||||||
|  |       if (/\d/.test(this.password)) score += 1; | ||||||
|  |       else feedback.push('number'); | ||||||
|  |        | ||||||
|  |       if (/[!@#$%^&*(),.?":{}|<>]/.test(this.password)) score += 1; | ||||||
|  |       else feedback.push('special character'); | ||||||
|  |        | ||||||
|  |       if (score < 2) { | ||||||
|  |         return { width: '25%', class: 'weak', text: 'Weak' }; | ||||||
|  |       } else if (score < 4) { | ||||||
|  |         return { width: '50%', class: 'fair', text: 'Fair' }; | ||||||
|  |       } else if (score < 5) { | ||||||
|  |         return { width: '75%', class: 'good', text: 'Good' }; | ||||||
|  |       } else { | ||||||
|  |         return { width: '100%', class: 'strong', text: 'Strong' }; | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     isFormValid() { | ||||||
|  |       return this.firstName &&  | ||||||
|  |              this.lastName &&  | ||||||
|  |              this.username &&  | ||||||
|  |              this.email &&  | ||||||
|  |              this.password &&  | ||||||
|  |              this.confirmPassword &&  | ||||||
|  |              !this.passwordMismatch &&  | ||||||
|  |              this.agreeToTerms; | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   mounted() { | ||||||
|  |     this.generateFloatingElements(); | ||||||
|  |   }, | ||||||
|  |   methods: { | ||||||
|  |     async signup() { | ||||||
|  |       if (!this.isFormValid) { | ||||||
|  |         this.error = 'Please fill in all required fields correctly'; | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       this.isLoading = true; | ||||||
|  |       this.error = null; | ||||||
|  |       this.successMessage = null; | ||||||
|  | 
 | ||||||
|  |       try { | ||||||
|  |         const response = await axios.post('http://localhost:5000/auth/signup', { | ||||||
|  |           firstName: this.firstName, | ||||||
|  |           lastName: this.lastName, | ||||||
|  |           username: this.username, | ||||||
|  |           email: this.email, | ||||||
|  |           password: this.password, | ||||||
|  |           subscribeNewsletter: this.subscribeNewsletter | ||||||
|  |         }); | ||||||
|  |          | ||||||
|  |         console.log('Signup successful:', response.data); | ||||||
|  |          | ||||||
|  |         this.successMessage = 'Account created successfully! Please check your email to verify your account.'; | ||||||
|  |          | ||||||
|  |         setTimeout(() => { | ||||||
|  |           this.$router.push('/login'); | ||||||
|  |         }, 2000); | ||||||
|  |          | ||||||
|  |       } catch (error) { | ||||||
|  |         console.error('Signup failed:', error.response?.data); | ||||||
|  |         this.error = error.response?.data?.error || 'Account creation failed. Please try again.'; | ||||||
|  |       } finally { | ||||||
|  |         this.isLoading = false; | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |      | ||||||
|  |     generateFloatingElements() { | ||||||
|  |       for (let i = 0; i < 12; i++) { | ||||||
|  |         this.floatingElements.push({ | ||||||
|  |           style: { | ||||||
|  |             left: Math.random() * 100 + '%', | ||||||
|  |             top: Math.random() * 100 + '%', | ||||||
|  |             animationDelay: Math.random() * 8 + 's', | ||||||
|  |             animationDuration: (6 + Math.random() * 4) + 's' | ||||||
|  |           } | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | </script> | ||||||
|  | @ -1,43 +1,46 @@ | ||||||
| import { createRouter, createWebHistory } from 'vue-router'; | import { createRouter, createWebHistory } from 'vue-router' | ||||||
| 
 | 
 | ||||||
| // Lazy load components for better performance
 |  | ||||||
| const routes = [ | const routes = [ | ||||||
|   {  |   {  | ||||||
|     path: '/',  |     path: '/',  | ||||||
|     name: 'Home', |     name: 'Home', | ||||||
|     component: () => import('../components/Home.vue')  |     component: () => import('../components/Home.vue'), | ||||||
|  |     meta: { title: 'RideAware – Home' }  | ||||||
|   }, |   }, | ||||||
|   {  |   {  | ||||||
|     path: '/login',  |     path: '/login',  | ||||||
|     name: 'Login', |     name: 'Login', | ||||||
|     component: () => import('../components/UserLogin.vue')  |     component: () => import('../components/UserLogin.vue'), | ||||||
|  |     meta: { title: 'RideAware – Sign In' }  | ||||||
|   }, |   }, | ||||||
|   {  |   { | ||||||
|     path: '/logged-in',  |     path: '/signup', | ||||||
|     name: 'LoggedIn', |     name: 'SignUp', | ||||||
|     component: () => import('../components/LoggedinPage.vue'), |     component: () => import('../components/UserSignup.vue'), | ||||||
|     // Optional: Add route guard to check if user is actually logged in
 |     meta: { title: 'RideAware – Sign Up' } | ||||||
|     meta: { requiresAuth: true } |   }, | ||||||
|   } |    | ||||||
| ]; | ] | ||||||
| 
 | 
 | ||||||
| const router = createRouter({ | const router = createRouter({ | ||||||
|   history: createWebHistory(), |   history: createWebHistory(), | ||||||
|   routes, |   routes, | ||||||
| }); | }) | ||||||
| 
 | 
 | ||||||
| // Optional: Navigation guard example
 |  | ||||||
| router.beforeEach((to, from, next) => { | router.beforeEach((to, from, next) => { | ||||||
|   // Check if route requires authentication
 |  | ||||||
|   if (to.meta.requiresAuth) { |   if (to.meta.requiresAuth) { | ||||||
|     // Check if user is logged in (implement your auth logic)
 |     const isLoggedIn = !!(localStorage.getItem('user') || sessionStorage.getItem('user')) | ||||||
|     const isLoggedIn = localStorage.getItem('user') || sessionStorage.getItem('user'); |  | ||||||
|     if (!isLoggedIn) { |     if (!isLoggedIn) { | ||||||
|       next('/login'); |       next({ name: 'Login' }) | ||||||
|       return; |       return | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   next(); |   next() | ||||||
| }); | }) | ||||||
| 
 | 
 | ||||||
| export default router; | router.afterEach((to) => { | ||||||
|  |   const defaultTitle = 'RideAware' | ||||||
|  |   document.title = to.meta.title || defaultTitle | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | export default router | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Cipher Vance
						Cipher Vance