+
+
Login to RideAware
+
Train with Focus. Ride with Awareness
+
+
+
+
+ {{ auth.error }}
-
-
-
+
+
+ Don't have an account? Sign up
+ Forgot password?
-
-
-
{{ error }}
+
-
+
+
\ No newline at end of file
diff --git a/src/components/UserProfile.vue b/src/components/UserProfile.vue
new file mode 100644
index 0000000..04ab741
--- /dev/null
+++ b/src/components/UserProfile.vue
@@ -0,0 +1,282 @@
+
+
+
+
+
+
+
User Profile
+
+
+
+
+ {{ auth.error }}
+
+
+
+ {{ successMessage }}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/UserSignup.vue b/src/components/UserSignup.vue
new file mode 100644
index 0000000..c0a859e
--- /dev/null
+++ b/src/components/UserSignup.vue
@@ -0,0 +1,222 @@
+
+
+
+
Join RideAware
+
Train with Focus. Ride with Awareness
+
+
+
+
+ {{ auth.error }}
+
+
+
+
Already have an account? Login here
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/composables/useAuth.js b/src/composables/useAuth.js
new file mode 100644
index 0000000..39b5fb3
--- /dev/null
+++ b/src/composables/useAuth.js
@@ -0,0 +1,19 @@
+import { useAuthStore } from '@/stores/auth'
+
+export function useAuth() {
+ const authStore = useAuthStore()
+
+ return {
+ user: authStore.user,
+ isAuthenticated: authStore.isAuthenticated,
+ error: authStore.error,
+ loading: authStore.loading,
+ signup: authStore.signup,
+ login: authStore.login,
+ logout: authStore.logout,
+ requestPasswordReset: authStore.requestPasswordReset,
+ resetPassword: authStore.resetPassword,
+ fetchProfile: authStore.fetchProfile,
+ updateProfile: authStore.updateProfile,
+ }
+}
\ No newline at end of file
diff --git a/src/main.js b/src/main.js
index e69d4b7..33f7c05 100644
--- a/src/main.js
+++ b/src/main.js
@@ -1,7 +1,11 @@
-import { createApp } from 'vue';
-import App from './App.vue';
-import router from './router';
+import { createApp } from 'vue'
+import { createPinia } from 'pinia'
+import App from './App.vue'
+import router from './router'
-const app = createApp(App);
-app.use(router);
-app.mount('#app');
+const app = createApp(App)
+
+app.use(createPinia())
+app.use(router)
+
+app.mount('#app')
\ No newline at end of file
diff --git a/src/router/index.js b/src/router/index.js
index a5868bb..7586f0b 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -1,16 +1,66 @@
-import { createRouter, createWebHistory } from 'vue-router';
-import Login from '../components/UserLogin.vue';
-import LoggedinPage from '@/components/LoggedinPage.vue';
+import { createRouter, createWebHistory } from 'vue-router'
+import { useAuthStore } from '@/stores/auth'
+
+import UserLogin from '@/components/UserLogin.vue'
+import UserSignup from '@/components/UserSignup.vue'
+import UserDashboard from '@/components/UserDashboard.vue'
+import UserProfile from '@/components/UserProfile.vue'
+import PasswordReset from '@/components/PasswordReset.vue'
const routes = [
- { path: '/', component: Login },
- { path: '/logged-in', component: LoggedinPage}
- //{ path: '/dashboard', component: () => import('../components/Dashboard.vue') }, // Placeholder for a dashboard page
-];
+ {
+ path: '/login',
+ name: 'Login',
+ component: UserLogin,
+ meta: { requiresAuth: false },
+ },
+ {
+ path: '/signup',
+ name: 'Signup',
+ component: UserSignup,
+ meta: { requiresAuth: false },
+ },
+ {
+ path: '/password-reset',
+ name: 'PasswordReset',
+ component: PasswordReset,
+ meta: { requiresAuth: false },
+ },
+ {
+ path: '/dashboard',
+ name: 'Dashboard',
+ component: UserDashboard,
+ meta: { requiresAuth: true },
+ },
+ {
+ path: '/profile',
+ name: 'Profile',
+ component: UserProfile,
+ meta: { requiresAuth: true },
+ },
+ {
+ path: '/',
+ redirect: '/dashboard',
+ },
+]
const router = createRouter({
- history: createWebHistory(),
+ history: createWebHistory(process.env.BASE_URL),
routes,
-});
+})
-export default router;
+// Navigation guard
+router.beforeEach((to, from, next) => {
+ const authStore = useAuthStore()
+ const requiresAuth = to.meta.requiresAuth
+
+ if (requiresAuth && !authStore.isAuthenticated) {
+ next('/login')
+ } else if (!requiresAuth && authStore.isAuthenticated && (to.path === '/login' || to.path === '/signup')) {
+ next('/dashboard')
+ } else {
+ next()
+ }
+})
+
+export default router
\ No newline at end of file
diff --git a/src/services/api.js b/src/services/api.js
new file mode 100644
index 0000000..ebc0b28
--- /dev/null
+++ b/src/services/api.js
@@ -0,0 +1,61 @@
+import axios from 'axios'
+
+const API_BASE_URL = process.env.VUE_APP_API_URL || 'http://127.0.0.1:5000'
+
+const api = axios.create({
+ baseURL: API_BASE_URL,
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+})
+
+// Request interceptor - add auth token
+api.interceptors.request.use(
+ (config) => {
+ const token = localStorage.getItem('access_token')
+ if (token) {
+ config.headers.Authorization = `Bearer ${token}`
+ }
+ return config
+ },
+ (error) => Promise.reject(error)
+)
+
+// Response interceptor - handle token refresh
+api.interceptors.response.use(
+ (response) => response,
+ async (error) => {
+ const originalRequest = error.config
+
+ if (error.response?.status === 401 && !originalRequest._retry) {
+ originalRequest._retry = true
+
+ try {
+ const refreshToken = localStorage.getItem('refresh_token')
+ if (!refreshToken) {
+ throw new Error('No refresh token')
+ }
+
+ // Note: You'll need to implement this endpoint on the backend
+ const { data } = await axios.post(
+ `${API_BASE_URL}/api/refresh-token`,
+ { refresh_token: refreshToken }
+ )
+
+ localStorage.setItem('access_token', data.access_token)
+ originalRequest.headers.Authorization = `Bearer ${data.access_token}`
+
+ return api(originalRequest)
+ } catch (refreshError) {
+ localStorage.removeItem('access_token')
+ localStorage.removeItem('refresh_token')
+ window.location.href = '/login'
+ return Promise.reject(refreshError)
+ }
+ }
+
+ return Promise.reject(error)
+ }
+)
+
+export default api
\ No newline at end of file
diff --git a/src/stores/auth.js b/src/stores/auth.js
new file mode 100644
index 0000000..2028e57
--- /dev/null
+++ b/src/stores/auth.js
@@ -0,0 +1,173 @@
+import { defineStore } from 'pinia'
+import { ref, computed } from 'vue'
+import api from '@/services/api'
+
+export const useAuthStore = defineStore('auth', () => {
+ const user = ref(null)
+ const accessToken = ref(localStorage.getItem('access_token'))
+ const refreshToken = ref(localStorage.getItem('refresh_token'))
+ const error = ref(null)
+ const loading = ref(false)
+
+ const isAuthenticated = computed(() => !!accessToken.value)
+
+ const signup = async (username, password, email, firstName, lastName) => {
+ loading.value = true
+ error.value = null
+
+ try {
+ const { data } = await api.post('/api/signup', {
+ username,
+ password,
+ email,
+ first_name: firstName,
+ last_name: lastName,
+ })
+
+ accessToken.value = data.access_token
+ refreshToken.value = data.refresh_token
+ user.value = {
+ id: data.user_id,
+ username: data.username,
+ email: data.email,
+ }
+
+ localStorage.setItem('access_token', data.access_token)
+ localStorage.setItem('refresh_token', data.refresh_token)
+ localStorage.setItem('user', JSON.stringify(user.value))
+
+ return data
+ } catch (err) {
+ error.value = err.response?.data?.error || 'Signup failed'
+ throw error.value
+ } finally {
+ loading.value = false
+ }
+ }
+
+ const login = async (username, password) => {
+ loading.value = true
+ error.value = null
+
+ try {
+ const { data } = await api.post('/api/login', {
+ username,
+ password,
+ })
+
+ accessToken.value = data.access_token
+ refreshToken.value = data.refresh_token
+ user.value = {
+ id: data.user_id,
+ username: data.username,
+ email: data.email,
+ }
+
+ localStorage.setItem('access_token', data.access_token)
+ localStorage.setItem('refresh_token', data.refresh_token)
+ localStorage.setItem('user', JSON.stringify(user.value))
+
+ return data
+ } catch (err) {
+ error.value = err.response?.data?.error || 'Login failed'
+ throw error.value
+ } finally {
+ loading.value = false
+ }
+ }
+
+ const logout = () => {
+ user.value = null
+ accessToken.value = null
+ refreshToken.value = null
+ error.value = null
+
+ localStorage.removeItem('access_token')
+ localStorage.removeItem('refresh_token')
+ localStorage.removeItem('user')
+ }
+
+ const requestPasswordReset = async (email) => {
+ loading.value = true
+ error.value = null
+
+ try {
+ const { data } = await api.post('/api/password-reset/request', { email })
+ return data
+ } catch (err) {
+ error.value = err.response?.data?.error || 'Password reset request failed'
+ throw error.value
+ } finally {
+ loading.value = false
+ }
+ }
+
+ const resetPassword = async (token, newPassword) => {
+ loading.value = true
+ error.value = null
+
+ try {
+ const { data } = await api.post('/api/password-reset/confirm', {
+ token,
+ new_password: newPassword,
+ })
+ return data
+ } catch (err) {
+ error.value = err.response?.data?.error || 'Password reset failed'
+ throw error.value
+ } finally {
+ loading.value = false
+ }
+ }
+
+ const fetchProfile = async () => {
+ loading.value = true
+ error.value = null
+
+ try {
+ const { data } = await api.get('/api/protected/profile')
+ return data
+ } catch (err) {
+ error.value = err.response?.data?.error || 'Failed to fetch profile'
+ throw error.value
+ } finally {
+ loading.value = false
+ }
+ }
+
+ const updateProfile = async (profileData) => {
+ loading.value = true
+ error.value = null
+
+ try {
+ const { data } = await api.put('/api/protected/profile', profileData)
+ return data
+ } catch (err) {
+ error.value = err.response?.data?.error || 'Failed to update profile'
+ throw error.value
+ } finally {
+ loading.value = false
+ }
+ }
+
+ // Initialize from localStorage
+ if (!user.value && localStorage.getItem('user')) {
+ user.value = JSON.parse(localStorage.getItem('user'))
+ }
+
+ return {
+ user,
+ accessToken,
+ refreshToken,
+ error,
+ loading,
+ isAuthenticated,
+ signup,
+ login,
+ logout,
+ requestPasswordReset,
+ resetPassword,
+ fetchProfile,
+ updateProfile,
+ }
+})
\ No newline at end of file
diff --git a/vue.config.js b/vue.config.js
index 910e297..f97ee55 100644
--- a/vue.config.js
+++ b/vue.config.js
@@ -1,4 +1,15 @@
-const { defineConfig } = require('@vue/cli-service')
+const { defineConfig } = require('@vue/cli-service');
+
module.exports = defineConfig({
- transpileDependencies: true
-})
+ transpileDependencies: true,
+ productionSourceMap: false,
+ devServer: {
+ port: 3000,
+ proxy: {
+ '/api': {
+ target: process.env.VUE_APP_API_URL || 'http://127.0.0.1:5000',
+ changeOrigin: true,
+ },
+ },
+ },
+});
\ No newline at end of file