diff --git a/app/package-lock.json b/app/package-lock.json index 3e3acdc..e324e94 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -18,8 +18,11 @@ "@ionic/vue-router": "^8.0.0", "axios": "^1.8.1", "ionicons": "^7.0.0", + "vee-validate": "^4.15.0", "vue": "^3.3.0", - "vue-router": "^4.2.0" + "vue-router": "^4.2.0", + "vuex": "^4.1.0", + "yup": "^1.6.1" }, "devDependencies": { "@capacitor/cli": "6.1.2", @@ -3743,6 +3746,30 @@ "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", "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": { "version": "12.0.0", "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-12.0.0.tgz", @@ -4246,6 +4273,15 @@ "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": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", @@ -4727,6 +4763,21 @@ "dev": true, "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": { "version": "3.39.0", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.39.0.tgz", @@ -6233,6 +6284,12 @@ "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": { "version": "3.0.0", "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" } }, + "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": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -7225,6 +7294,12 @@ "dev": true, "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": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -7647,6 +7722,12 @@ "dev": true, "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": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -7837,6 +7918,12 @@ "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": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", @@ -8094,7 +8181,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "dev": true, "license": "MIT" }, "node_modules/rimraf": { @@ -8433,6 +8519,15 @@ "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": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", @@ -8587,6 +8682,18 @@ "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": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -8725,6 +8832,12 @@ "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": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -8795,6 +8908,12 @@ "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": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz", @@ -9067,6 +9186,40 @@ "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": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", @@ -9359,6 +9512,18 @@ "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": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", @@ -9676,6 +9841,30 @@ "funding": { "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" + } } } } diff --git a/app/package.json b/app/package.json index 05eb686..3aa79e8 100644 --- a/app/package.json +++ b/app/package.json @@ -22,8 +22,11 @@ "@ionic/vue-router": "^8.0.0", "axios": "^1.8.1", "ionicons": "^7.0.0", + "vee-validate": "^4.15.0", "vue": "^3.3.0", - "vue-router": "^4.2.0" + "vue-router": "^4.2.0", + "vuex": "^4.1.0", + "yup": "^1.6.1" }, "devDependencies": { "@capacitor/cli": "6.1.2", @@ -41,5 +44,5 @@ "vitest": "^0.34.6", "vue-tsc": "^2.0.22" }, - "description": "An Ionic project" + "description": "Bullpen session app" } diff --git a/app/src/main.ts b/app/src/main.ts index 923c91c..1c2cc67 100644 --- a/app/src/main.ts +++ b/app/src/main.ts @@ -1,6 +1,7 @@ import { createApp } from 'vue' import App from './App.vue' import router from './router'; +import Vuex from 'vuex' import { IonicVue } from '@ionic/vue'; @@ -34,9 +35,12 @@ import '@ionic/vue/css/palettes/dark.system.css'; /* Theme variables */ import './theme/variables.css'; +import store from './store'; + const app = createApp(App) - .use(IonicVue) - .use(router); + .use(store) + .use(IonicVue) + .use(router); router.isReady().then(() => { app.mount('#app'); diff --git a/app/src/services/AuthHeader.ts b/app/src/services/AuthHeader.ts new file mode 100644 index 0000000..185ca7c --- /dev/null +++ b/app/src/services/AuthHeader.ts @@ -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 {}; + } + } +} diff --git a/app/src/services/AuthService.ts b/app/src/services/AuthService.ts new file mode 100644 index 0000000..0931541 --- /dev/null +++ b/app/src/services/AuthService.ts @@ -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(); \ No newline at end of file diff --git a/app/src/store/auth.ts b/app/src/store/auth.ts new file mode 100644 index 0000000..087fe13 --- /dev/null +++ b/app/src/store/auth.ts @@ -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; + +const auth: Module = { + 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; diff --git a/app/src/store/index.ts b/app/src/store/index.ts new file mode 100644 index 0000000..76e55f1 --- /dev/null +++ b/app/src/store/index.ts @@ -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({ + modules: { + auth, + pitchTypes + } +}); + +export default store; +export type AppStore = Store; diff --git a/app/src/store/pitchType.ts b/app/src/store/pitchType.ts new file mode 100644 index 0000000..7878459 --- /dev/null +++ b/app/src/store/pitchType.ts @@ -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; + +const pitchTypes: Module = { + 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; diff --git a/app/src/views/Login.vue b/app/src/views/Login.vue index 56ccac8..8fc941c 100644 --- a/app/src/views/Login.vue +++ b/app/src/views/Login.vue @@ -10,59 +10,37 @@ - - -
- - - - - - - - - - - + + - Login + + + + + + +
+ + + + + +
+
+ - Signup + + Login + + + + + +
errors: {{ errors }}
-
@@ -84,7 +62,7 @@ \ No newline at end of file diff --git a/app/src/vuex.d.ts b/app/src/vuex.d.ts new file mode 100644 index 0000000..bfe60a4 --- /dev/null +++ b/app/src/vuex.d.ts @@ -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 + } +} \ No newline at end of file diff --git a/backend/app.js b/backend/app.js index 496f035..8e9ac84 100644 --- a/backend/app.js +++ b/backend/app.js @@ -5,7 +5,7 @@ const bcrypt = require("bcryptjs"); const app = express(); const corsOptions = { - origin: "http://localhost:8080" + origin: "http://localhost:5173" }; app.use(cors(corsOptions));