renamed login and home view, restructured login view and router, added logo
This commit is contained in:
parent
a18559a77a
commit
9071886991
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
|
|
@ -1,45 +1,24 @@
|
||||||
import { createRouter, createWebHistory } from '@ionic/vue-router';
|
import { createRouter, createWebHistory } from '@ionic/vue-router';
|
||||||
|
import { useStore } from 'vuex'
|
||||||
import { RouteRecordRaw } from 'vue-router';
|
import { RouteRecordRaw } from 'vue-router';
|
||||||
import PitcherList from '../views/PitcherList.vue'
|
import PitcherList from '../views/PitcherList.vue'
|
||||||
import Login from '../views/Login.vue'
|
import LoginView from '../views/LoginView.vue'
|
||||||
import Home from '../views/Home.vue'
|
import HomeView from '../views/HomeView.vue'
|
||||||
import PreparePitch from "@/views/PreparePitch.vue";
|
import PreparePitch from "@/views/PreparePitch.vue";
|
||||||
import FinalizePitch from "@/views/FinalizePitch.vue";
|
import FinalizePitch from "@/views/FinalizePitch.vue";
|
||||||
import BullpenStats from "@/views/BullpenStats.vue";
|
import BullpenStats from "@/views/BullpenStats.vue";
|
||||||
|
import SetupView from '@/views/SetupView.vue';
|
||||||
|
|
||||||
const routes: Array<RouteRecordRaw> = [
|
const routes: Array<RouteRecordRaw> = [
|
||||||
{
|
{ path: '/', redirect: '/login' },
|
||||||
path: '/',
|
{ path: '/login', component: LoginView },
|
||||||
redirect: '/login'
|
{ path: '/setup', component: SetupView },
|
||||||
}, {
|
{ path: '/home', component: HomeView },
|
||||||
path: '/login',
|
{ path: '/pitchers', component: PitcherList },
|
||||||
name: 'Login',
|
{ path: '/bullpen', component: PreparePitch },
|
||||||
component: Login
|
{ path: '/prepare', component: PreparePitch },
|
||||||
}, {
|
{ path: '/finalize', component: FinalizePitch },
|
||||||
path: '/home',
|
{ path: '/stats', component: BullpenStats }
|
||||||
name: 'Home',
|
|
||||||
component: Home
|
|
||||||
}, {
|
|
||||||
path: '/pitchers',
|
|
||||||
name: 'Pitchers',
|
|
||||||
component: PitcherList
|
|
||||||
}, {
|
|
||||||
path: '/bullpen',
|
|
||||||
name: 'PreparePitch',
|
|
||||||
component: PreparePitch
|
|
||||||
}, {
|
|
||||||
path: '/prepare',
|
|
||||||
name: 'PreparePitch',
|
|
||||||
component: PreparePitch
|
|
||||||
}, {
|
|
||||||
path: '/finalize',
|
|
||||||
name: 'FinalizePitch',
|
|
||||||
component: FinalizePitch
|
|
||||||
}, {
|
|
||||||
path: '/stats',
|
|
||||||
name: 'BullpenStats',
|
|
||||||
component: BullpenStats
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
|
|
@ -47,4 +26,24 @@ const router = createRouter({
|
||||||
routes
|
routes
|
||||||
})
|
})
|
||||||
|
|
||||||
|
router.beforeEach((to, from, next) => {
|
||||||
|
const store = useStore();
|
||||||
|
const serverAddress = localStorage.getItem('serverAddress');
|
||||||
|
const isAuthenticated = store.state.auth.isAuthenticated;
|
||||||
|
|
||||||
|
if (to.meta.requiresAuth && !isAuthenticated) {
|
||||||
|
return next('/login');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!serverAddress && to.path !== '/setup') {
|
||||||
|
return next('/setup');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serverAddress && to.path === '/setup') {
|
||||||
|
return next('/login');
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,50 @@
|
||||||
/* For information on how to create your own theme, please see:
|
/* For information on how to create your own theme, please see:
|
||||||
http://ionicframework.com/docs/theming/ */
|
http://ionicframework.com/docs/theming/ */
|
||||||
|
/*:root {*/
|
||||||
|
/* --ion-background-color: #7c3b6a;*/
|
||||||
|
/* --ion-text-color: white;*/
|
||||||
|
/* --ion-tab-bar-background: #7c3b6a;*/
|
||||||
|
/* --ion-toolbar-background: #7c3b6a;;*/
|
||||||
|
/* --ion-toolbar-color: white;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
.custom-back {
|
||||||
|
|
||||||
|
background-color: rgba(255, 255, 255, 0.1) !important;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
padding-left: 0.75rem;
|
||||||
|
padding-right: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-button {
|
||||||
|
|
||||||
|
--border-radius: 5px !important;
|
||||||
|
--background: rgb(236, 149, 35);
|
||||||
|
--background-activated: rgb(192, 125, 38);
|
||||||
|
--background-focused: rgb(192, 125, 38);
|
||||||
|
font-size: 1.2rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-input {
|
||||||
|
|
||||||
|
--background: #834e76;
|
||||||
|
--padding-bottom: 1rem;
|
||||||
|
--padding-top: 1rem;
|
||||||
|
--padding-start: 1rem;
|
||||||
|
--padding-end: 1rem;
|
||||||
|
border-radius: 10px !important;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
transition: all 0.2s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ion-label {
|
||||||
|
padding-left: 0.2rem;
|
||||||
|
padding-right: 0.5rem;
|
||||||
|
color: #d3a6c7;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,146 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { computed, ref, onMounted } from "vue";
|
|
||||||
import {
|
|
||||||
IonPage,
|
|
||||||
IonHeader,
|
|
||||||
IonToolbar,
|
|
||||||
IonButtons,
|
|
||||||
IonMenuButton,
|
|
||||||
IonButton,
|
|
||||||
IonContent,
|
|
||||||
IonList,
|
|
||||||
IonItem,
|
|
||||||
IonTitle,
|
|
||||||
IonRow,
|
|
||||||
IonCol,
|
|
||||||
IonInput,
|
|
||||||
IonIcon,
|
|
||||||
} from "@ionic/vue";
|
|
||||||
|
|
||||||
// Todo: https://github.com/alanmontgomery/ionic-react-login
|
|
||||||
|
|
||||||
import { lockClosedOutline, personOutline } from 'ionicons/icons';
|
|
||||||
import { useForm } from 'vee-validate';
|
|
||||||
import { useRouter } from 'vue-router';
|
|
||||||
import { useStore } from 'vuex'
|
|
||||||
import * as yup from 'yup';
|
|
||||||
|
|
||||||
const loading = ref(false);
|
|
||||||
|
|
||||||
const { meta, errors, handleSubmit, defineField } = useForm({
|
|
||||||
validationSchema: yup.object({
|
|
||||||
email: yup.string().email().required('No email provided.'),
|
|
||||||
password: yup.string().required('No password provided.').min(8)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
const [email, emailAttrs] = defineField('email');
|
|
||||||
const [password, passwordAttrs] = defineField('password');
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
const store = useStore();
|
|
||||||
|
|
||||||
const loggedIn = computed(() => store.state.auth.isAuthenticated);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (loggedIn.value) {
|
|
||||||
onLogin();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const submit = handleSubmit((values, { resetForm }) => {
|
|
||||||
store.dispatch('auth/login', {
|
|
||||||
email: values.email,
|
|
||||||
password: values.password,
|
|
||||||
}).then(
|
|
||||||
() => {
|
|
||||||
resetForm();
|
|
||||||
onLogin();
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
loading.value = false;
|
|
||||||
console.log(error);
|
|
||||||
// message.value =
|
|
||||||
// (error.response && error.response.data && error.response.data.message) ||
|
|
||||||
// error.message ||
|
|
||||||
// error.toString();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}, ({errors}) => {
|
|
||||||
console.log(errors);
|
|
||||||
loading.value = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
const onLogin = () => {
|
|
||||||
console.log('check if pitch types are available.');
|
|
||||||
|
|
||||||
store.dispatch('pitchTypes/initialize').then(() => {
|
|
||||||
router.push({ path: '/home'});
|
|
||||||
}, error => {
|
|
||||||
console.log(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<ion-page>
|
|
||||||
<ion-header>
|
|
||||||
<ion-toolbar>
|
|
||||||
<ion-buttons slot="start">
|
|
||||||
<ion-menu-button></ion-menu-button>
|
|
||||||
</ion-buttons>
|
|
||||||
<ion-title>Login</ion-title>
|
|
||||||
</ion-toolbar>
|
|
||||||
</ion-header>
|
|
||||||
|
|
||||||
<ion-content class="ion-padding">
|
|
||||||
<form @submit="submit">
|
|
||||||
<ion-row>
|
|
||||||
<ion-col>
|
|
||||||
<ion-list>
|
|
||||||
|
|
||||||
<ion-item>
|
|
||||||
<ion-icon :icon="personOutline" 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 :icon="lockClosedOutline" 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-row>
|
|
||||||
<ion-row>
|
|
||||||
<ion-col>
|
|
||||||
<ion-button :disabled="!meta.valid" type="submit" fill="solid" expand="full">
|
|
||||||
Login
|
|
||||||
</ion-button>
|
|
||||||
</ion-col>
|
|
||||||
</ion-row>
|
|
||||||
<ion-row>
|
|
||||||
<ion-col>
|
|
||||||
<pre>errors: {{ errors }}</pre>
|
|
||||||
</ion-col>
|
|
||||||
</ion-row>
|
|
||||||
</form>
|
|
||||||
</ion-content>
|
|
||||||
</ion-page>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.login-logo {
|
|
||||||
padding: 20px 0;
|
|
||||||
min-height: 200px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-logo img {
|
|
||||||
max-width: 150px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -0,0 +1,240 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref, onMounted } from "vue";
|
||||||
|
import {
|
||||||
|
IonPage,
|
||||||
|
IonIcon,
|
||||||
|
IonButton,
|
||||||
|
IonContent,
|
||||||
|
IonInput
|
||||||
|
} from "@ionic/vue";
|
||||||
|
|
||||||
|
// Todo: https://github.com/alanmontgomery/ionic-react-login
|
||||||
|
|
||||||
|
import {
|
||||||
|
lockClosedOutline,
|
||||||
|
personOutline,
|
||||||
|
// arrowBack,
|
||||||
|
// shapesOutline
|
||||||
|
} from 'ionicons/icons';
|
||||||
|
import {Field, useForm} from 'vee-validate';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import { useStore } from 'vuex'
|
||||||
|
import * as yup from 'yup';
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
|
const schema = yup.object({
|
||||||
|
email: yup.string().email('Invalid email address').required('Email is required'),
|
||||||
|
password: yup
|
||||||
|
.string()
|
||||||
|
.min(8, 'Minimum 8 characters')
|
||||||
|
.matches(/\d/, 'Must include a number')
|
||||||
|
.matches(/[^A-Za-z\d]/, 'Must include a special character')
|
||||||
|
.required('Password is required'),
|
||||||
|
});
|
||||||
|
|
||||||
|
const { meta, errors, handleSubmit, defineField } = useForm({
|
||||||
|
validationSchema: schema
|
||||||
|
});
|
||||||
|
|
||||||
|
const [email, emailAttrs] = defineField('email');
|
||||||
|
const [password, passwordAttrs] = defineField('password');
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const store = useStore();
|
||||||
|
|
||||||
|
const loggedIn = computed(() => store.state.auth.isAuthenticated);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (loggedIn.value) {
|
||||||
|
onLogin();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const submit = handleSubmit((values, { resetForm }) => {
|
||||||
|
store.dispatch('auth/login', {
|
||||||
|
email: values.email,
|
||||||
|
password: values.password,
|
||||||
|
}).then(
|
||||||
|
() => {
|
||||||
|
resetForm();
|
||||||
|
onLogin();
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
loading.value = false;
|
||||||
|
console.log(error);
|
||||||
|
// message.value =
|
||||||
|
// (error.response && error.response.data && error.response.data.message) ||
|
||||||
|
// error.message ||
|
||||||
|
// error.toString();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}, ({errors}) => {
|
||||||
|
console.log(errors);
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
const onLogin = () => {
|
||||||
|
console.log('check if pitch types are available.');
|
||||||
|
|
||||||
|
store.dispatch('pitchTypes/initialize').then(() => {
|
||||||
|
router.push({ path: '/home'});
|
||||||
|
}, error => {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ion-page>
|
||||||
|
<ion-content>
|
||||||
|
<div class="login-container">
|
||||||
|
<div class="top-section">
|
||||||
|
<h1 class="login-title">Login</h1>
|
||||||
|
|
||||||
|
<div class="logo-container">
|
||||||
|
<img src="../assets/Bonn_Capitals_Insignia.png" alt="Logo" class="logo" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form @submit="submit" class="form-container">
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="label-row">
|
||||||
|
<label class="input-label" for="email">Email</label>
|
||||||
|
<small v-if="!meta.valid" class="error-message">{{ errors.email }}</small>
|
||||||
|
</div>
|
||||||
|
<Field id="email" name="email" type="email" >
|
||||||
|
<div class="input-with-icon">
|
||||||
|
<ion-icon :icon="personOutline" class="icon-login"></ion-icon>
|
||||||
|
<ion-input
|
||||||
|
name="email"
|
||||||
|
v-model="email"
|
||||||
|
v-bind="emailAttrs"
|
||||||
|
class="rounded-input"
|
||||||
|
:class="{ 'input-invalid': !meta.valid && errors.email }"
|
||||||
|
type="text" required placeholder="Email"></ion-input>
|
||||||
|
</div>
|
||||||
|
</Field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="label-row">
|
||||||
|
<label class="input-label" for="password">Password</label>
|
||||||
|
<small v-if="!meta.valid" class="error-message">{{ errors.password }}</small>
|
||||||
|
</div>
|
||||||
|
<Field id="password" name="password" type="password">
|
||||||
|
<div class="input-with-icon">
|
||||||
|
<ion-icon :icon="lockClosedOutline" class="icon-login"></ion-icon>
|
||||||
|
<ion-input
|
||||||
|
name="password"
|
||||||
|
v-model="password"
|
||||||
|
v-bind="passwordAttrs"
|
||||||
|
type="password"
|
||||||
|
class="rounded-input"
|
||||||
|
:class="{ 'input-invalid': !meta.valid && errors.password }"
|
||||||
|
required placeholder="Password"></ion-input>
|
||||||
|
</div>
|
||||||
|
</Field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ion-button expand="block" class="rounded-button" type="submit">
|
||||||
|
Login
|
||||||
|
</ion-button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</ion-content>
|
||||||
|
</ion-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.login-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-section {
|
||||||
|
background-color: #cbeec9;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-title {
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-container {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
height: 25vh;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
height: 100%;
|
||||||
|
width: auto;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-container {
|
||||||
|
padding-top: 1rem;
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-label {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-with-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 0;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rounded-input {
|
||||||
|
--border-radius: 50%;
|
||||||
|
padding: 12px;
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-invalid {
|
||||||
|
background-color: lightcoral;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
color: red;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
text-align: right;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rounded-button {
|
||||||
|
--border-radius: 12px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import {IonButton, IonContent, IonCol, IonHeader, IonInput, IonPage, IonTitle, IonToolbar} from '@ionic/vue';
|
||||||
|
|
||||||
|
const serverAddress = ref('');
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const saveServerAddress = () => {
|
||||||
|
localStorage.setItem('serverAddress', serverAddress.value);
|
||||||
|
router.push('/login');
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ion-page>
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Setup Backend Server</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content class="ion-padding">
|
||||||
|
<ion-row>
|
||||||
|
<ion-col class="ion-padding">
|
||||||
|
<ion-input name="ipaddress" v-model="serverAddress" type="text" required placeholder="Enter server address"></ion-input>
|
||||||
|
</ion-col>
|
||||||
|
<ion-col>
|
||||||
|
<ion-button type="button" fill="solid" expand="full" @click="saveServerAddress">Save</ion-button>
|
||||||
|
</ion-col>
|
||||||
|
</ion-row>
|
||||||
|
</ion-content>
|
||||||
|
</ion-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
Loading…
Reference in New Issue