frontend and backend progress
This commit is contained in:
parent
888cdae3cd
commit
787b9879fc
|
|
@ -0,0 +1,13 @@
|
||||||
|
# build stage
|
||||||
|
FROM node:lts-alpine AS build-stage
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm install
|
||||||
|
COPY . .
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# production stage
|
||||||
|
FROM nginx:stable-alpine AS production-stage
|
||||||
|
COPY --from=build-stage /app/dist /usr/share/nginx/html
|
||||||
|
EXPOSE 80
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
"@ionic/vue": "^8.0.0",
|
"@ionic/vue": "^8.0.0",
|
||||||
"@ionic/vue-router": "^8.0.0",
|
"@ionic/vue-router": "^8.0.0",
|
||||||
"axios": "^1.8.1",
|
"axios": "^1.8.1",
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
"ionicons": "^7.0.0",
|
"ionicons": "^7.0.0",
|
||||||
"vee-validate": "^4.15.0",
|
"vee-validate": "^4.15.0",
|
||||||
"vue": "^3.3.0",
|
"vue": "^3.3.0",
|
||||||
|
|
@ -4962,7 +4963,6 @@
|
||||||
"version": "1.11.13",
|
"version": "1.11.13",
|
||||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
|
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
|
||||||
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
|
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/de-indent": {
|
"node_modules/de-indent": {
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@
|
||||||
"@ionic/vue": "^8.0.0",
|
"@ionic/vue": "^8.0.0",
|
||||||
"@ionic/vue-router": "^8.0.0",
|
"@ionic/vue-router": "^8.0.0",
|
||||||
"axios": "^1.8.1",
|
"axios": "^1.8.1",
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
"ionicons": "^7.0.0",
|
"ionicons": "^7.0.0",
|
||||||
"vee-validate": "^4.15.0",
|
"vee-validate": "^4.15.0",
|
||||||
"vue": "^3.3.0",
|
"vue": "^3.3.0",
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { useStore } from 'vuex'
|
||||||
import { AppStore } from '@/store';
|
import { AppStore } from '@/store';
|
||||||
import { onMounted, onBeforeUnmount, ref } from "vue";
|
import { onMounted, onBeforeUnmount, ref } from "vue";
|
||||||
import EventBus from "./common/EventBus";
|
import EventBus from "./common/EventBus";
|
||||||
|
import backendService from '@/services/BackendService'
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const store = useStore<AppStore>();
|
const store = useStore<AppStore>();
|
||||||
|
|
@ -19,6 +20,8 @@ const isOnline = ref(navigator.onLine);
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
EventBus.on("logout", logout);
|
EventBus.on("logout", logout);
|
||||||
|
|
||||||
|
backendService.updateServer(backendService.getServer());
|
||||||
|
|
||||||
window.addEventListener("online", () => isOnline.value = true);
|
window.addEventListener("online", () => isOnline.value = true);
|
||||||
window.addEventListener("offline", () => isOnline.value = false);
|
window.addEventListener("offline", () => isOnline.value = false);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,14 @@ import BullpenSummaryView from "@/views/BullpenSummaryView.vue";
|
||||||
import SetupView from '@/views/SetupView.vue';
|
import SetupView from '@/views/SetupView.vue';
|
||||||
import backendService from '@/services/BackendService'
|
import backendService from '@/services/BackendService'
|
||||||
import BullpenListView from "@/views/BullpenListView.vue";
|
import BullpenListView from "@/views/BullpenListView.vue";
|
||||||
|
import ProfileView from "@/views/ProfileView.vue";
|
||||||
|
|
||||||
const routes: Array<RouteRecordRaw> = [
|
const routes: Array<RouteRecordRaw> = [
|
||||||
{ path: '/', redirect: '/login' },
|
{ path: '/', redirect: '/login' },
|
||||||
{ path: '/login', component: LoginView },
|
{ path: '/login', component: LoginView },
|
||||||
{ path: '/setup', component: SetupView },
|
{ path: '/setup', component: SetupView },
|
||||||
{ path: '/home', component: HomeView },
|
{ path: '/home', component: HomeView },
|
||||||
|
{ path: '/profile', component: ProfileView },
|
||||||
{ path: '/pitchers', component: PitcherList },
|
{ path: '/pitchers', component: PitcherList },
|
||||||
{ path: '/bullpen', component: BullpenView },
|
{ path: '/bullpen', component: BullpenView },
|
||||||
{ path: '/stats', component: BullpenListView },
|
{ path: '/stats', component: BullpenListView },
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ class BackendService {
|
||||||
return localStorage.getItem("server") !== null;
|
return localStorage.getItem("server") !== null;
|
||||||
}
|
}
|
||||||
getServer(): Server {
|
getServer(): Server {
|
||||||
return JSON.parse(localStorage.getItem("server") || '{"protocol":"http","host":"localhost","port":8080}');
|
// return JSON.parse(localStorage.getItem("server") || '{"protocol":"http","host":"localhost","port":8080}');
|
||||||
// return JSON.parse(localStorage.getItem("server") || '{"protocol":"https","host":"bullpen-api.palaeomatiker.home64.de","port":443}');
|
return JSON.parse(localStorage.getItem("server") || '{"protocol":"https","host":"bullpen-api.palaeomatiker.home64.de","port":443}');
|
||||||
}
|
}
|
||||||
updateServer(server: Server): void {
|
updateServer(server: Server): void {
|
||||||
localStorage.setItem("server", JSON.stringify(server));
|
localStorage.setItem("server", JSON.stringify(server));
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,24 @@
|
||||||
import {
|
import {
|
||||||
IonButton,
|
IonButton,
|
||||||
IonCard,
|
IonCard,
|
||||||
|
IonCardTitle,
|
||||||
|
IonCardSubtitle,
|
||||||
IonContent,
|
IonContent,
|
||||||
IonFooter,
|
IonFooter,
|
||||||
IonHeader,
|
IonHeader,
|
||||||
IonLabel,
|
|
||||||
IonPage,
|
IonPage,
|
||||||
IonTitle,
|
IonTitle,
|
||||||
IonToolbar
|
IonToolbar, IonLabel, IonList, IonItem, IonIcon, IonBadge
|
||||||
} from "@ionic/vue";
|
} from "@ionic/vue";
|
||||||
|
import {
|
||||||
|
baseballOutline
|
||||||
|
} from 'ionicons/icons';
|
||||||
|
|
||||||
|
import PitchType from "@/types/PitchType";
|
||||||
import {useStore} from 'vuex';
|
import {useStore} from 'vuex';
|
||||||
import {useRouter} from 'vue-router';
|
import {useRouter} from 'vue-router';
|
||||||
import {computed} from "vue";
|
import {computed} from "vue";
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
|
|
@ -20,6 +27,17 @@ const store = useStore();
|
||||||
const isAuthenticated = computed(() => store.state.auth.isAuthenticated);
|
const isAuthenticated = computed(() => store.state.auth.isAuthenticated);
|
||||||
const pitcher = computed(() => store.state.auth.user);
|
const pitcher = computed(() => store.state.auth.user);
|
||||||
const bullpens = computed(() => store.state.bullpen.bullpens);
|
const bullpens = computed(() => store.state.bullpen.bullpens);
|
||||||
|
const pitchTypes = computed(() => store.state.pitchTypes.pitchTypes);
|
||||||
|
|
||||||
|
const formatDate = (date: Date) => {
|
||||||
|
return dayjs(date).format('YYYY.MM.DD HH:mm');
|
||||||
|
}
|
||||||
|
|
||||||
|
const determinePitchTypeName = (id: number): string => {
|
||||||
|
const pitchType = pitchTypes.value.find((pitchType: PitchType) => pitchType.id === id);
|
||||||
|
|
||||||
|
return pitchType?.name ?? 'Unknown';
|
||||||
|
}
|
||||||
|
|
||||||
const gotoHome = () => {
|
const gotoHome = () => {
|
||||||
router.push({path: '/home'});
|
router.push({path: '/home'});
|
||||||
|
|
@ -37,7 +55,21 @@ const gotoHome = () => {
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<ion-card v-for="(bullpen, index) in bullpens.data" :key="index">
|
<ion-card v-for="(bullpen, index) in bullpens.data" :key="index">
|
||||||
<ion-label>Bullpen {{bullpen.id}} ({{bullpen.startedAt}})</ion-label>
|
<ion-card-header>
|
||||||
|
<ion-card-title>Bullpen from ({{formatDate(bullpen.startedAt)}})</ion-card-title>
|
||||||
|
<ion-card-subtitle></ion-card-subtitle>
|
||||||
|
</ion-card-header>
|
||||||
|
<ion-card-content>
|
||||||
|
<ion-list v-for="(pitch, index) in bullpen.pitches" :key="index">
|
||||||
|
<ion-item>
|
||||||
|
<ion-icon slot="start" :icon="baseballOutline" class="icon-login"></ion-icon>
|
||||||
|
<ion-label>{{determinePitchTypeName(pitch.pitchTypeId)}}</ion-label>
|
||||||
|
<ion-badge>{{pitch.aimedArea}}</ion-badge>
|
||||||
|
<ion-badge>{{pitch.hitArea}}</ion-badge>
|
||||||
|
</ion-item>
|
||||||
|
</ion-list>
|
||||||
|
</ion-card-content>
|
||||||
|
|
||||||
</ion-card>
|
</ion-card>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
<ion-footer>
|
<ion-footer>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { computed, ref, onMounted } from "vue";
|
||||||
import {
|
import {
|
||||||
IonPage,
|
IonPage,
|
||||||
IonIcon,
|
IonIcon,
|
||||||
IonLabel,
|
// IonLabel,
|
||||||
IonButton,
|
IonButton,
|
||||||
IonContent,
|
IonContent,
|
||||||
IonInput
|
IonInput
|
||||||
|
|
@ -20,11 +20,10 @@ import { useRouter } from 'vue-router';
|
||||||
import { useStore } from 'vuex'
|
import { useStore } from 'vuex'
|
||||||
import * as yup from 'yup';
|
import * as yup from 'yup';
|
||||||
import PitchTypeService from "@/services/PitchTypeService";
|
import PitchTypeService from "@/services/PitchTypeService";
|
||||||
import EventBus from "@/common/EventBus";
|
|
||||||
import PitchType from "@/types/PitchType";
|
import PitchType from "@/types/PitchType";
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const server = JSON.parse(localStorage.getItem("server") || '""');
|
// const server = JSON.parse(localStorage.getItem("server") || '""');
|
||||||
|
|
||||||
const schema = yup.object({
|
const schema = yup.object({
|
||||||
email: yup.string().email('Invalid email address').required('Email is required'),
|
email: yup.string().email('Invalid email address').required('Email is required'),
|
||||||
|
|
@ -100,7 +99,7 @@ const changeServer = () => {
|
||||||
<div class="logo-container">
|
<div class="logo-container">
|
||||||
<img src="../assets/Bonn_Capitals_Insignia.png" alt="Logo" class="logo" />
|
<img src="../assets/Bonn_Capitals_Insignia.png" alt="Logo" class="logo" />
|
||||||
</div>
|
</div>
|
||||||
<ion-label>{{server.protocol}}://{{server.host}}:{{server.port}}</ion-label>
|
<!-- <ion-label>{{server.protocol}}://{{server.host}}:{{server.port}}</ion-label>-->
|
||||||
</div>
|
</div>
|
||||||
<form @submit="submit" class="form-container">
|
<form @submit="submit" class="form-container">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,102 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {logOutOutline, personOutline, playOutline, statsChartOutline} from "ionicons/icons";
|
||||||
|
import {
|
||||||
|
IonAvatar,
|
||||||
|
IonButton,
|
||||||
|
IonContent,
|
||||||
|
IonFooter,
|
||||||
|
IonHeader,
|
||||||
|
IonIcon,
|
||||||
|
IonPage,
|
||||||
|
IonTitle,
|
||||||
|
IonToolbar
|
||||||
|
} from "@ionic/vue";
|
||||||
|
import {computed, ref} from "vue";
|
||||||
|
|
||||||
|
import {useStore} from "vuex";
|
||||||
|
import {useRouter} from "vue-router";
|
||||||
|
|
||||||
|
const userImage = ref(null);
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const store = useStore();
|
||||||
|
|
||||||
|
const pitcher = computed(() => store.state.auth.user);
|
||||||
|
const isAuthenticated = computed(() => store.state.auth.isAuthenticated);
|
||||||
|
|
||||||
|
const gotoHome = () => {
|
||||||
|
router.push({path: '/home'});
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ion-page>
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title v-if="isAuthenticated">Home of {{ pitcher.firstName }} {{ pitcher.lastName }}</ion-title>
|
||||||
|
<ion-title v-else>Home</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content class="ion-padding">
|
||||||
|
<div class="user-container">
|
||||||
|
<ion-avatar class="user-avatar">
|
||||||
|
<template v-if="userImage">
|
||||||
|
<img :src="userImage" alt="User Profile" class="avatar-frame" />
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<ion-icon :icon="personOutline" class="avatar-placeholder" />
|
||||||
|
</template>
|
||||||
|
</ion-avatar>
|
||||||
|
<div v-if="isAuthenticated" class="user-name">{{ pitcher.firstName }} {{ pitcher.lastName }}</div>
|
||||||
|
</div>
|
||||||
|
</ion-content>
|
||||||
|
|
||||||
|
<ion-footer>
|
||||||
|
<ion-button expand="full" color="success" @click="gotoHome">Home</ion-button>
|
||||||
|
</ion-footer>
|
||||||
|
</ion-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.user-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 30vh;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-avatar {
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 4px solid #ccc;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-frame {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-placeholder {
|
||||||
|
font-size: 60px;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-name {
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
secret: "bullpen-secret-key",
|
secret: "bullpen-secret-key",
|
||||||
|
|
||||||
// jwtExpiration: 3600, // 1 hour
|
jwtExpiration: 3600, // 1 hour
|
||||||
// jwtRefreshExpiration: 86400, // 24 hours
|
jwtRefreshExpiration: 86400 * 7, // 7 days
|
||||||
|
|
||||||
/* for test */
|
/* for test */
|
||||||
jwtExpiration: 30, // 30 seconds
|
// jwtExpiration: 30, // 30 seconds
|
||||||
jwtRefreshExpiration: 60 // 1 minute
|
// jwtRefreshExpiration: 60 // 1 minute
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue