frontend and backend progress

This commit is contained in:
Sascha Kühl 2025-04-28 22:49:22 +02:00
parent 888cdae3cd
commit 787b9879fc
10 changed files with 166 additions and 14 deletions

13
app/Dockerfile Normal file
View File

@ -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;"]

2
app/package-lock.json generated
View File

@ -17,6 +17,7 @@
"@ionic/vue": "^8.0.0",
"@ionic/vue-router": "^8.0.0",
"axios": "^1.8.1",
"dayjs": "^1.11.13",
"ionicons": "^7.0.0",
"vee-validate": "^4.15.0",
"vue": "^3.3.0",
@ -4962,7 +4963,6 @@
"version": "1.11.13",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
"dev": true,
"license": "MIT"
},
"node_modules/de-indent": {

View File

@ -21,6 +21,7 @@
"@ionic/vue": "^8.0.0",
"@ionic/vue-router": "^8.0.0",
"axios": "^1.8.1",
"dayjs": "^1.11.13",
"ionicons": "^7.0.0",
"vee-validate": "^4.15.0",
"vue": "^3.3.0",

View File

@ -5,6 +5,7 @@ import { useStore } from 'vuex'
import { AppStore } from '@/store';
import { onMounted, onBeforeUnmount, ref } from "vue";
import EventBus from "./common/EventBus";
import backendService from '@/services/BackendService'
const router = useRouter();
const store = useStore<AppStore>();
@ -19,6 +20,8 @@ const isOnline = ref(navigator.onLine);
onMounted(() => {
EventBus.on("logout", logout);
backendService.updateServer(backendService.getServer());
window.addEventListener("online", () => isOnline.value = true);
window.addEventListener("offline", () => isOnline.value = false);

View File

@ -9,12 +9,14 @@ import BullpenSummaryView from "@/views/BullpenSummaryView.vue";
import SetupView from '@/views/SetupView.vue';
import backendService from '@/services/BackendService'
import BullpenListView from "@/views/BullpenListView.vue";
import ProfileView from "@/views/ProfileView.vue";
const routes: Array<RouteRecordRaw> = [
{ path: '/', redirect: '/login' },
{ path: '/login', component: LoginView },
{ path: '/setup', component: SetupView },
{ path: '/home', component: HomeView },
{ path: '/profile', component: ProfileView },
{ path: '/pitchers', component: PitcherList },
{ path: '/bullpen', component: BullpenView },
{ path: '/stats', component: BullpenListView },

View File

@ -5,8 +5,8 @@ class BackendService {
return localStorage.getItem("server") !== null;
}
getServer(): Server {
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":"http","host":"localhost","port":8080}');
return JSON.parse(localStorage.getItem("server") || '{"protocol":"https","host":"bullpen-api.palaeomatiker.home64.de","port":443}');
}
updateServer(server: Server): void {
localStorage.setItem("server", JSON.stringify(server));

View File

@ -2,17 +2,24 @@
import {
IonButton,
IonCard,
IonCardTitle,
IonCardSubtitle,
IonContent,
IonFooter,
IonHeader,
IonLabel,
IonPage,
IonTitle,
IonToolbar
IonToolbar, IonLabel, IonList, IonItem, IonIcon, IonBadge
} from "@ionic/vue";
import {
baseballOutline
} from 'ionicons/icons';
import PitchType from "@/types/PitchType";
import {useStore} from 'vuex';
import {useRouter} from 'vue-router';
import {computed} from "vue";
import dayjs from 'dayjs';
const router = useRouter();
const store = useStore();
@ -20,6 +27,17 @@ const store = useStore();
const isAuthenticated = computed(() => store.state.auth.isAuthenticated);
const pitcher = computed(() => store.state.auth.user);
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 = () => {
router.push({path: '/home'});
@ -37,7 +55,21 @@ const gotoHome = () => {
</ion-header>
<ion-content>
<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-content>
<ion-footer>

View File

@ -3,7 +3,7 @@ import { computed, ref, onMounted } from "vue";
import {
IonPage,
IonIcon,
IonLabel,
// IonLabel,
IonButton,
IonContent,
IonInput
@ -20,11 +20,10 @@ import { useRouter } from 'vue-router';
import { useStore } from 'vuex'
import * as yup from 'yup';
import PitchTypeService from "@/services/PitchTypeService";
import EventBus from "@/common/EventBus";
import PitchType from "@/types/PitchType";
const loading = ref(false);
const server = JSON.parse(localStorage.getItem("server") || '""');
// const server = JSON.parse(localStorage.getItem("server") || '""');
const schema = yup.object({
email: yup.string().email('Invalid email address').required('Email is required'),
@ -100,7 +99,7 @@ const changeServer = () => {
<div class="logo-container">
<img src="../assets/Bonn_Capitals_Insignia.png" alt="Logo" class="logo" />
</div>
<ion-label>{{server.protocol}}://{{server.host}}:{{server.port}}</ion-label>
<!-- <ion-label>{{server.protocol}}://{{server.host}}:{{server.port}}</ion-label>-->
</div>
<form @submit="submit" class="form-container">
<div class="input-group">

View File

@ -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>

View File

@ -1,10 +1,10 @@
module.exports = {
secret: "bullpen-secret-key",
// jwtExpiration: 3600, // 1 hour
// jwtRefreshExpiration: 86400, // 24 hours
jwtExpiration: 3600, // 1 hour
jwtRefreshExpiration: 86400 * 7, // 7 days
/* for test */
jwtExpiration: 30, // 30 seconds
jwtRefreshExpiration: 60 // 1 minute
// jwtExpiration: 30, // 30 seconds
// jwtRefreshExpiration: 60 // 1 minute
};