integrated login page for vue app

This commit is contained in:
Sascha Kühl 2025-03-23 18:54:24 +01:00
parent 751953ae70
commit 6eb42c4fce
11 changed files with 465 additions and 77 deletions

193
app/package-lock.json generated
View File

@ -18,8 +18,11 @@
"@ionic/vue-router": "^8.0.0", "@ionic/vue-router": "^8.0.0",
"axios": "^1.8.1", "axios": "^1.8.1",
"ionicons": "^7.0.0", "ionicons": "^7.0.0",
"vee-validate": "^4.15.0",
"vue": "^3.3.0", "vue": "^3.3.0",
"vue-router": "^4.2.0" "vue-router": "^4.2.0",
"vuex": "^4.1.0",
"yup": "^1.6.1"
}, },
"devDependencies": { "devDependencies": {
"@capacitor/cli": "6.1.2", "@capacitor/cli": "6.1.2",
@ -3743,6 +3746,30 @@
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@vue/devtools-kit": {
"version": "7.7.2",
"resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.2.tgz",
"integrity": "sha512-CY0I1JH3Z8PECbn6k3TqM1Bk9ASWxeMtTCvZr7vb+CHi+X/QwQm5F1/fPagraamKMAHVfuuCbdcnNg1A4CYVWQ==",
"license": "MIT",
"dependencies": {
"@vue/devtools-shared": "^7.7.2",
"birpc": "^0.2.19",
"hookable": "^5.5.3",
"mitt": "^3.0.1",
"perfect-debounce": "^1.0.0",
"speakingurl": "^14.0.1",
"superjson": "^2.2.1"
}
},
"node_modules/@vue/devtools-shared": {
"version": "7.7.2",
"resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.2.tgz",
"integrity": "sha512-uBFxnp8gwW2vD6FrJB8JZLUzVb6PNRG0B0jBnHsOH8uKyva2qINY8PTF5Te4QlTbMDqU5K6qtJDr6cNsKWhbOA==",
"license": "MIT",
"dependencies": {
"rfdc": "^1.4.1"
}
},
"node_modules/@vue/eslint-config-typescript": { "node_modules/@vue/eslint-config-typescript": {
"version": "12.0.0", "version": "12.0.0",
"resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-12.0.0.tgz", "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-12.0.0.tgz",
@ -4246,6 +4273,15 @@
"node": ">=0.6" "node": ">=0.6"
} }
}, },
"node_modules/birpc": {
"version": "0.2.19",
"resolved": "https://registry.npmjs.org/birpc/-/birpc-0.2.19.tgz",
"integrity": "sha512-5WeXXAvTmitV1RqJFppT5QtUiz2p1mRSYU000Jkft5ZUCLJIk4uQriYNO50HknxKwM6jd8utNc66K1qGIwwWBQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/blob-util": { "node_modules/blob-util": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz",
@ -4727,6 +4763,21 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/copy-anything": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz",
"integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==",
"license": "MIT",
"dependencies": {
"is-what": "^4.1.8"
},
"engines": {
"node": ">=12.13"
},
"funding": {
"url": "https://github.com/sponsors/mesqueeb"
}
},
"node_modules/core-js": { "node_modules/core-js": {
"version": "3.39.0", "version": "3.39.0",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.39.0.tgz", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.39.0.tgz",
@ -6233,6 +6284,12 @@
"he": "bin/he" "he": "bin/he"
} }
}, },
"node_modules/hookable": {
"version": "5.5.3",
"resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
"license": "MIT"
},
"node_modules/html-encoding-sniffer": { "node_modules/html-encoding-sniffer": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
@ -6574,6 +6631,18 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/is-what": {
"version": "4.1.16",
"resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz",
"integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==",
"license": "MIT",
"engines": {
"node": ">=12.13"
},
"funding": {
"url": "https://github.com/sponsors/mesqueeb"
}
},
"node_modules/is-wsl": { "node_modules/is-wsl": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
@ -7225,6 +7294,12 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/mitt": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
"license": "MIT"
},
"node_modules/mkdirp": { "node_modules/mkdirp": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
@ -7647,6 +7722,12 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/perfect-debounce": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
"integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==",
"license": "MIT"
},
"node_modules/performance-now": { "node_modules/performance-now": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
@ -7837,6 +7918,12 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/property-expr": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz",
"integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==",
"license": "MIT"
},
"node_modules/proto-list": { "node_modules/proto-list": {
"version": "1.2.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
@ -8094,7 +8181,6 @@
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/rimraf": { "node_modules/rimraf": {
@ -8433,6 +8519,15 @@
"source-map": "^0.6.0" "source-map": "^0.6.0"
} }
}, },
"node_modules/speakingurl": {
"version": "14.0.1",
"resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz",
"integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/split2": { "node_modules/split2": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
@ -8587,6 +8682,18 @@
"url": "https://github.com/sponsors/antfu" "url": "https://github.com/sponsors/antfu"
} }
}, },
"node_modules/superjson": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.2.tgz",
"integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==",
"license": "MIT",
"dependencies": {
"copy-anything": "^3.0.2"
},
"engines": {
"node": ">=16"
}
},
"node_modules/supports-color": { "node_modules/supports-color": {
"version": "8.1.1", "version": "8.1.1",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
@ -8725,6 +8832,12 @@
"readable-stream": "3" "readable-stream": "3"
} }
}, },
"node_modules/tiny-case": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz",
"integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==",
"license": "MIT"
},
"node_modules/tinybench": { "node_modules/tinybench": {
"version": "2.9.0", "version": "2.9.0",
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
@ -8795,6 +8908,12 @@
"node": ">=8.0" "node": ">=8.0"
} }
}, },
"node_modules/toposort": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
"integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==",
"license": "MIT"
},
"node_modules/tough-cookie": { "node_modules/tough-cookie": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz",
@ -9067,6 +9186,40 @@
"uuid": "dist/bin/uuid" "uuid": "dist/bin/uuid"
} }
}, },
"node_modules/vee-validate": {
"version": "4.15.0",
"resolved": "https://registry.npmjs.org/vee-validate/-/vee-validate-4.15.0.tgz",
"integrity": "sha512-PGJh1QCFwCBjbHu5aN6vB8macYVWrajbDvgo1Y/8fz9n/RVIkLmZCJDpUgu7+mUmCOPMxeyq7vXUOhbwAqdXcA==",
"license": "MIT",
"dependencies": {
"@vue/devtools-api": "^7.5.2",
"type-fest": "^4.8.3"
},
"peerDependencies": {
"vue": "^3.4.26"
}
},
"node_modules/vee-validate/node_modules/@vue/devtools-api": {
"version": "7.7.2",
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.2.tgz",
"integrity": "sha512-1syn558KhyN+chO5SjlZIwJ8bV/bQ1nOVTG66t2RbG66ZGekyiYNmRO7X9BJCXQqPsFHlnksqvPhce2qpzxFnA==",
"license": "MIT",
"dependencies": {
"@vue/devtools-kit": "^7.7.2"
}
},
"node_modules/vee-validate/node_modules/type-fest": {
"version": "4.37.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.37.0.tgz",
"integrity": "sha512-S/5/0kFftkq27FPNye0XM1e2NsnoD/3FS+pBmbjmmtLT6I+i344KoOf7pvXreaFsDamWeaJX55nczA1m5PsBDg==",
"license": "(MIT OR CC0-1.0)",
"engines": {
"node": ">=16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/verror": { "node_modules/verror": {
"version": "1.10.0", "version": "1.10.0",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
@ -9359,6 +9512,18 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/vuex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-4.1.0.tgz",
"integrity": "sha512-hmV6UerDrPcgbSy9ORAtNXDr9M4wlNP4pEFKye4ujJF8oqgFFuxDCdOLS3eNoRTtq5O3hoBDh9Doj1bQMYHRbQ==",
"license": "MIT",
"dependencies": {
"@vue/devtools-api": "^6.0.0-beta.11"
},
"peerDependencies": {
"vue": "^3.2.0"
}
},
"node_modules/w3c-xmlserializer": { "node_modules/w3c-xmlserializer": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",
@ -9676,6 +9841,30 @@
"funding": { "funding": {
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
},
"node_modules/yup": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/yup/-/yup-1.6.1.tgz",
"integrity": "sha512-JED8pB50qbA4FOkDol0bYF/p60qSEDQqBD0/qeIrUCG1KbPBIQ776fCUNb9ldbPcSTxA69g/47XTo4TqWiuXOA==",
"license": "MIT",
"dependencies": {
"property-expr": "^2.0.5",
"tiny-case": "^1.0.3",
"toposort": "^2.0.2",
"type-fest": "^2.19.0"
}
},
"node_modules/yup/node_modules/type-fest": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
"integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
"license": "(MIT OR CC0-1.0)",
"engines": {
"node": ">=12.20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
} }
} }
} }

View File

@ -22,8 +22,11 @@
"@ionic/vue-router": "^8.0.0", "@ionic/vue-router": "^8.0.0",
"axios": "^1.8.1", "axios": "^1.8.1",
"ionicons": "^7.0.0", "ionicons": "^7.0.0",
"vee-validate": "^4.15.0",
"vue": "^3.3.0", "vue": "^3.3.0",
"vue-router": "^4.2.0" "vue-router": "^4.2.0",
"vuex": "^4.1.0",
"yup": "^1.6.1"
}, },
"devDependencies": { "devDependencies": {
"@capacitor/cli": "6.1.2", "@capacitor/cli": "6.1.2",
@ -41,5 +44,5 @@
"vitest": "^0.34.6", "vitest": "^0.34.6",
"vue-tsc": "^2.0.22" "vue-tsc": "^2.0.22"
}, },
"description": "An Ionic project" "description": "Bullpen session app"
} }

View File

@ -1,6 +1,7 @@
import { createApp } from 'vue' import { createApp } from 'vue'
import App from './App.vue' import App from './App.vue'
import router from './router'; import router from './router';
import Vuex from 'vuex'
import { IonicVue } from '@ionic/vue'; import { IonicVue } from '@ionic/vue';
@ -34,9 +35,12 @@ import '@ionic/vue/css/palettes/dark.system.css';
/* Theme variables */ /* Theme variables */
import './theme/variables.css'; import './theme/variables.css';
import store from './store';
const app = createApp(App) const app = createApp(App)
.use(IonicVue) .use(store)
.use(router); .use(IonicVue)
.use(router);
router.isReady().then(() => { router.isReady().then(() => {
app.mount('#app'); app.mount('#app');

View File

@ -0,0 +1,12 @@
export default class AuthHeader {
public header(): any {
const user = JSON.parse(localStorage.getItem('user') || '""');
if (user && user.accessToken) {
// return { Authorization: 'Bearer ' + user.accessToken }; // for Spring Boot back-end
return { 'x-access-token': user.accessToken }; // for Node.js Express back-end
} else {
return {};
}
}
}

View File

@ -0,0 +1,34 @@
import axios from 'axios';
const API_URL = 'http://localhost:8080/api/auth/';
class AuthService {
public login(email: string, password: string) {
return axios
.post(API_URL + 'login', {
email,
password
})
.then(response => {
if (response.data.accessToken) {
localStorage.setItem('user', JSON.stringify(response.data));
}
return response.data;
});
}
public logout() {
localStorage.removeItem('user');
}
public register(username: string, email: string, password: string) {
return axios.post(API_URL + 'register', {
username,
email,
password
});
}
}
export default new AuthService();

76
app/src/store/auth.ts Normal file
View File

@ -0,0 +1,76 @@
import AuthService from '@/services/AuthService'
import Pitcher from "@/types/Pitcher";
import { Module, ActionContext } from 'vuex';
import { RootState } from './index';
export interface AuthState {
isAuthenticated: boolean;
user: Pitcher | null;
}
const user = JSON.parse(localStorage.getItem('user') || '""');
const initialState = user
? { isAuthenticated: true, user }
: { isAuthenticated: false, user: null };
type AuthActionContext = ActionContext<AuthState, RootState>;
const auth: Module<AuthState, RootState> = {
namespaced: true,
state: initialState,
actions: {
login({ commit }: AuthActionContext, user) {
return AuthService.login(user.email, user.password).then(
user => {
commit('loginSuccess', user);
return Promise.resolve(user);
},
error => {
commit('loginFailure');
return Promise.reject(error);
}
);
},
logout({ commit }: AuthActionContext) {
AuthService.logout();
commit('logout');
},
register({ commit }: AuthActionContext, user) {
return AuthService.register(user.firstName, user.email, user.password).then(
response => {
commit('registerSuccess');
return Promise.resolve(response.data);
},
error => {
commit('registerFailure');
return Promise.reject(error);
}
);
}
},
mutations: {
loginSuccess(state, user) {
state.isAuthenticated = true;
state.user = user;
},
loginFailure(state) {
state.isAuthenticated = false;
state.user = null;
},
logout(state) {
state.isAuthenticated = false;
state.user = null;
},
registerSuccess(state) {
state.isAuthenticated = false;
},
registerFailure(state) {
state.isAuthenticated = false;
}
}
};
export default auth;

19
app/src/store/index.ts Normal file
View File

@ -0,0 +1,19 @@
import { createStore, Store } from 'vuex';
import auth, {AuthState} from './auth';
import pitchTypes, {PitchTypeState} from './pitchType';
// Root state type
export interface RootState {
auth: AuthState;
pitchTypes: PitchTypeState;
}
const store = createStore<RootState>({
modules: {
auth,
pitchTypes
}
});
export default store;
export type AppStore = Store<RootState>;

View File

@ -0,0 +1,36 @@
import {pitchTypeService} from '@/services/PitchTypeService'
import PitchType from "@/types/PitchType";
import { Module, ActionContext } from 'vuex';
import { RootState } from './index';
export interface PitchTypeState {
pitchTypes: PitchType[];
}
type PitchTypeActionContext = ActionContext<PitchTypeState, RootState>;
const pitchTypes: Module<PitchTypeState, RootState> = {
namespaced: true,
state: {pitchTypes: []},
actions: {
initialize({ commit }: PitchTypeActionContext) {
return pitchTypeService.getAllPitchTypes().then(
(pitchTypeList: PitchType[]) => {
commit('initializedPitchTypes', pitchTypeList);
return Promise.resolve(pitchTypeList);
},
error => {
return Promise.reject(error);
});
}
},
mutations: {
initializedPitchTypes(state, pitchTypeList: PitchType[]) {
state.pitchTypes = pitchTypeList;
}
}
};
export default pitchTypes;

View File

@ -10,59 +10,37 @@
</ion-header> </ion-header>
<ion-content class="ion-padding"> <ion-content class="ion-padding">
<div class="login-logo"> <form @submit="submit">
<img src="./../../public/favicon.png" alt="Ionic logo" /> <ion-row>
</div>
<form novalidate @submit.prevent="onLogin">
<ion-list>
<ion-item>
<ion-input
label="Username"
labelPlacement="stacked"
v-model="username"
name="username"
type="text"
:spellcheck="false"
autocapitalize="off"
required
></ion-input>
</ion-item>
<ion-item>
<ion-input
labelPlacement="stacked"
label="Password"
v-model="password"
name="password"
type="password"
required
></ion-input>
</ion-item>
</ion-list>
<ion-row responsive-sm class="ion-padding">
<ion-col> <ion-col>
<ion-button :disabled="!canSubmit" type="submit" expand="block" <ion-list>
>Login</ion-button
> <ion-item>
<ion-icon name="person" class="icon-login"></ion-icon>
<ion-input name="user" v-model="email" v-bind="emailAttrs" type="text" required placeholder="Username"></ion-input>
</ion-item>
<br />
<ion-item>
<ion-icon name="lock" class="icon-login"></ion-icon>
<ion-input name="password" v-model="password" v-bind="passwordAttrs" type="password" required placeholder="Password"></ion-input>
</ion-item>
</ion-list>
</ion-col> </ion-col>
</ion-row>
<ion-row>
<ion-col> <ion-col>
<ion-button <ion-button :disabled="!meta.valid" type="submit" fill="solid" expand="full">
:disabled="!canSubmit" Login
@click="onSignup" </ion-button>
color="light" </ion-col>
expand="block" </ion-row>
>Signup</ion-button <ion-row>
> <ion-col>
<pre>errors: {{ errors }}</pre>
</ion-col> </ion-col>
</ion-row> </ion-row>
</form> </form>
<ion-toast
:is-open="showToast"
:message="toastMessage"
:duration="2000"
></ion-toast>
</ion-content> </ion-content>
</ion-page> </ion-page>
</template> </template>
@ -84,7 +62,7 @@
</style> </style>
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref } from "vue"; import { computed, ref, onMounted } from "vue";
import { import {
IonPage, IonPage,
IonHeader, IonHeader,
@ -99,35 +77,55 @@ import {
IonRow, IonRow,
IonCol, IonCol,
IonInput, IonInput,
IonToast, IonIcon,
} from "@ionic/vue"; } from "@ionic/vue";
import { useForm } from 'vee-validate';
import { useRouter } from 'vue-router';
import { useStore } from 'vuex'
import { AppStore } from '@/store';
import * as yup from 'yup';
const username = ref(""); const loading = ref(false);
const password = ref("");
const submitted = ref(false);
const usernameValid = true; const { meta, errors, handleSubmit, defineField } = useForm({
const passwordValid = true; validationSchema: yup.object({
email: yup.string().email().required('No email provided.'),
password: yup.string().required('No password provided.').min(8)
})
});
const showToast = ref(false); const [email, emailAttrs] = defineField('email');
const toastMessage = ref(""); const [password, passwordAttrs] = defineField('password');
const canSubmit = computed( const router = useRouter();
() => username.value.trim() !== "" && password.value.trim() !== "" const store = useStore<AppStore>();
);
const onLogin = () => { const loggedIn = computed(() => store.state.auth.isAuthenticated);
submitted.value = true;
if (usernameValid && passwordValid) { onMounted(() => {
if (loggedIn.value) {
router.push('/prepare');
} }
}; });
const onSignup = () => { const submit = handleSubmit(values => {
toastMessage.value = "Successfully logged in!"; store.dispatch('auth/login', {
email: values.email,
password: values.password,
}).then(
() => {
router.push('/prepare');
},
error => {
loading.value = false;
// message.value =
// (error.response && error.response.data && error.response.data.message) ||
// error.message ||
// error.toString();
}
);
}, ({errors}) => {
loading.value = false;
});
showToast.value = true;
username.value = "";
password.value = "";
};
</script> </script>

17
app/src/vuex.d.ts vendored Normal file
View File

@ -0,0 +1,17 @@
import { Store } from 'vuex'
import Pitcher from 'types/Pitcher'
declare module 'vue' {
// declare your own auth states
interface State {
status: {
loggedIn: boolean
},
user: Pitcher | null
}
// provide typings for `this.$auth`
interface ComponentCustomProperties {
$store: Store<State>
}
}

View File

@ -5,7 +5,7 @@ const bcrypt = require("bcryptjs");
const app = express(); const app = express();
const corsOptions = { const corsOptions = {
origin: "http://localhost:8080" origin: "http://localhost:5173"
}; };
app.use(cors(corsOptions)); app.use(cors(corsOptions));