Implement environment variable setup for frontend; create .env.production and .env.development files if they don't exist, and update API URLs in frontend components to use dynamic API_BASE_URL for improved configuration management.

This commit is contained in:
Torsten Schulz (local)
2025-10-18 23:36:31 +02:00
parent b066ffdeb4
commit efbb699b4b
25 changed files with 577 additions and 41 deletions

View File

@@ -0,0 +1,2 @@
# TimeClock v3 - Frontend Development Environment
VITE_API_URL=http://localhost:3010/api

3
frontend/.env.production Normal file
View File

@@ -0,0 +1,3 @@
# TimeClock v3 - Frontend Production Environment
# API Base URL (relativ, da Apache als Proxy dient)
VITE_API_URL=/api

76
frontend/FIX_API_URLS.sh Executable file
View File

@@ -0,0 +1,76 @@
#!/bin/bash
# =============================================================================
# TimeClock v3 - Fix hardcodierte API URLs
# =============================================================================
set -e
echo "🔧 Ersetze hardcodierte API-URLs durch zentrale Konfiguration..."
cd "$(dirname "$0")"
# Backup erstellen
echo "📦 Erstelle Backup..."
tar -czf ~/timeclock-frontend-backup-$(date +%Y%m%d_%H%M%S).tar.gz src/
# .env.production erstellen
echo "📝 Erstelle .env.production..."
cat > .env.production << 'EOF'
# TimeClock v3 - Frontend Production Environment
# API Base URL (relativ, da Apache als Proxy dient)
VITE_API_URL=/api
EOF
# .env.development erstellen
echo "📝 Erstelle .env.development..."
cat > .env.development << 'EOF'
# TimeClock v3 - Frontend Development Environment
VITE_API_URL=http://localhost:3010/api
EOF
# Ersetze alle hardcodierten URLs in Views
echo "🔄 Ersetze URLs in Views..."
# Pattern: http://localhost:3010/api → ${API_URL}
# Muss in mehreren Schritten erfolgen, da die Syntax unterschiedlich ist
find src/views -name "*.vue" -type f -exec sed -i "s|'http://localhost:3010/api|'\${API_URL}|g" {} \;
find src/views -name "*.vue" -type f -exec sed -i 's|"http://localhost:3010/api|"${API_URL}|g' {} \;
find src/views -name "*.vue" -type f -exec sed -i 's|`http://localhost:3010/api|`${API_URL}|g' {} \;
# Füge API_URL Import hinzu wo benötigt
echo "📦 Füge Imports hinzu..."
# Einfacher Ansatz: Erstelle eine Liste der Dateien mit API-Aufrufen
grep -l "localhost:3010" src/views/*.vue 2>/dev/null | while read -r file; do
# Prüfe ob Import bereits vorhanden
if ! grep -q "import.*API_BASE_URL.*from.*@/config/api" "$file"; then
# Füge Import nach dem ersten <script setup> hinzu
sed -i '/<script setup>/a import { API_BASE_URL as API_URL } from '\''@/config/api'\''' "$file"
fi
done
find src/components -name "*.vue" -type f -exec sed -i "s|'http://localhost:3010/api|'\${API_URL}|g" {} \;
find src/components -name "*.vue" -type f -exec sed -i 's|"http://localhost:3010/api|"${API_URL}|g' {} \;
find src/components -name "*.vue" -type f -exec sed -i 's|`http://localhost:3010/api|`${API_URL}|g' {} \;
grep -l "localhost:3010" src/components/*.vue 2>/dev/null | while read -r file; do
if ! grep -q "import.*API_BASE_URL.*from.*@/config/api" "$file"; then
sed -i '/<script setup>/a import { API_BASE_URL as API_URL } from '\''@/config/api'\''' "$file"
fi
done
echo ""
echo "✅ Fertig!"
echo ""
echo "📋 Nächste Schritte:"
echo " 1. npm run build"
echo " 2. Kopiere dist/ auf den Server"
echo ""
echo "🔍 Prüfe ob alle URLs ersetzt wurden:"
echo " grep -r 'localhost:3010' src/"
echo ""
exit 0

View File

@@ -92,7 +92,7 @@ const fetchStats = async () => {
const fetchCurrentState = async () => {
try {
const response = await fetch('http://localhost:3010/api/time-entries/current-state', {
const response = await fetch('${API_URL}/time-entries/current-state', {
headers: authStore.getAuthHeaders()
})
@@ -108,7 +108,7 @@ const fetchCurrentState = async () => {
// Lade die aktuellen Worklog-Daten (nur einmal pro Minute)
const fetchWorklogData = async () => {
try {
const response = await fetch('http://localhost:3010/api/time-entries/running', {
const response = await fetch('${API_URL}/time-entries/running', {
headers: authStore.getAuthHeaders()
})
@@ -277,7 +277,7 @@ const handleAction = async (action) => {
try {
loading.value = true
const response = await fetch('http://localhost:3010/api/time-entries/clock', {
const response = await fetch('${API_URL}/time-entries/clock', {
method: 'POST',
headers: {
...authStore.getAuthHeaders(),

View File

@@ -0,0 +1,66 @@
// TimeClock v3 - API Configuration
// Zentrale Konfiguration für alle API-Anfragen
// API Base URL aus Umgebungsvariablen
// In Production: /api (relativ, durch Apache Proxy)
// In Development: http://localhost:3010/api
export const API_BASE_URL = import.meta.env.VITE_API_URL || '/api'
// Vollständige API URL (für absolute URLs falls benötigt)
export const getApiUrl = (endpoint) => {
// Wenn endpoint mit / beginnt, direkt verwenden
if (endpoint.startsWith('/')) {
return `${API_BASE_URL}${endpoint}`
}
// Sonst / hinzufügen
return `${API_BASE_URL}/${endpoint}`
}
// Hilfsfunktion für Auth-Header
export const getAuthHeaders = () => {
const token = localStorage.getItem('timeclock_token')
const headers = {
'Content-Type': 'application/json'
}
if (token) {
headers['Authorization'] = `Bearer ${token}`
}
return headers
}
// Hilfsfunktion für API-Requests mit Fehlerbehandlung
export const apiRequest = async (endpoint, options = {}) => {
const url = getApiUrl(endpoint)
const defaultOptions = {
headers: getAuthHeaders()
}
const response = await fetch(url, {
...defaultOptions,
...options,
headers: {
...defaultOptions.headers,
...options.headers
}
})
const data = await response.json()
if (!response.ok) {
throw new Error(data.error || data.message || 'API-Anfrage fehlgeschlagen')
}
return data
}
export default {
API_BASE_URL,
getApiUrl,
getAuthHeaders,
apiRequest
}

View File

@@ -1,7 +1,8 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { API_BASE_URL } from '@/config/api'
const API_URL = 'http://localhost:3010/api'
const API_URL = API_BASE_URL
export const useAuthStore = defineStore('auth', () => {
const user = ref(null)

View File

@@ -1,8 +1,9 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { useAuthStore } from './authStore'
import { API_BASE_URL } from '@/config/api'
const API_URL = 'http://localhost:3010/api'
const API_URL = API_BASE_URL
export const useTimeStore = defineStore('time', () => {
const entries = ref([])

View File

@@ -101,7 +101,7 @@ async function loadCalendar() {
error.value = null
const response = await fetch(
`http://localhost:3010/api/calendar?year=${selectedYear.value}&month=${selectedMonth.value}`,
`${API_URL}/calendar?year=${selectedYear.value}&month=${selectedMonth.value}`,
{
headers: {
'Authorization': `Bearer ${authStore.token}`

View File

@@ -137,7 +137,7 @@ async function exportData() {
// Download CSV/Excel-Datei
async function downloadFile(format) {
const url = `http://localhost:3010/api/export/${format}?startDate=${form.value.startDate}&endDate=${form.value.endDate}`
const url = `${API_URL}/export/${format}?startDate=${form.value.startDate}&endDate=${form.value.endDate}`
const response = await fetch(url, {
headers: {
@@ -166,7 +166,7 @@ async function downloadFile(format) {
// PDF-Export (Browser-Print)
async function exportPDF() {
const url = `http://localhost:3010/api/export/pdf?startDate=${form.value.startDate}&endDate=${form.value.endDate}`
const url = `${API_URL}/export/pdf?startDate=${form.value.startDate}&endDate=${form.value.endDate}`
const response = await fetch(url, {
headers: {

View File

@@ -212,7 +212,7 @@ const form = ref({
// Lade alle Bundesländer
async function loadStates() {
try {
const response = await fetch('http://localhost:3010/api/holidays/states', {
const response = await fetch('${API_URL}/holidays/states', {
headers: {
'Authorization': `Bearer ${authStore.token}`
}
@@ -233,7 +233,7 @@ async function loadStates() {
async function loadHolidays() {
try {
loading.value = true
const response = await fetch('http://localhost:3010/api/holidays', {
const response = await fetch('${API_URL}/holidays', {
headers: {
'Authorization': `Bearer ${authStore.token}`
}
@@ -276,7 +276,7 @@ async function createHoliday() {
try {
loading.value = true
const response = await fetch('http://localhost:3010/api/holidays', {
const response = await fetch('${API_URL}/holidays', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -318,7 +318,7 @@ async function deleteHoliday(id) {
try {
loading.value = true
const response = await fetch(`http://localhost:3010/api/holidays/${id}`, {
const response = await fetch(`${API_URL}/holidays/${id}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${authStore.token}`

View File

@@ -91,7 +91,7 @@ const form = ref({
// Lade alle Einladungen
async function loadInvitations() {
try {
const response = await fetch('http://localhost:3010/api/invite', {
const response = await fetch('${API_URL}/invite', {
headers: {
'Authorization': `Bearer ${authStore.token}`
}
@@ -116,7 +116,7 @@ async function sendInvite() {
try {
loading.value = true
const response = await fetch('http://localhost:3010/api/invite', {
const response = await fetch('${API_URL}/invite', {
method: 'POST',
headers: {
'Content-Type': 'application/json',

View File

@@ -145,7 +145,7 @@ const handleLogin = async () => {
const handleGoogleLogin = () => {
// Redirect zu Google OAuth
window.location.href = 'http://localhost:3010/api/auth/google'
window.location.href = '${API_URL}/auth/google'
}
</script>

View File

@@ -92,7 +92,7 @@ async function changePassword() {
try {
loading.value = true
const response = await fetch('http://localhost:3010/api/password', {
const response = await fetch('${API_URL}/password', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',

View File

@@ -102,7 +102,7 @@ const form = ref({
// Lade alle Watcher
async function loadWatchers() {
try {
const response = await fetch('http://localhost:3010/api/watcher', {
const response = await fetch('${API_URL}/watcher', {
headers: {
'Authorization': `Bearer ${authStore.token}`
}
@@ -128,7 +128,7 @@ async function addWatcher() {
try {
loading.value = true
const response = await fetch('http://localhost:3010/api/watcher', {
const response = await fetch('${API_URL}/watcher', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -172,7 +172,7 @@ async function removeWatcher(id, email) {
try {
loading.value = true
const response = await fetch(`http://localhost:3010/api/watcher/${id}`, {
const response = await fetch(`${API_URL}/watcher/${id}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${authStore.token}`

View File

@@ -135,7 +135,7 @@ const form = ref({
async function loadProfile() {
try {
loading.value = true
const response = await fetch('http://localhost:3010/api/profile', {
const response = await fetch('${API_URL}/profile', {
headers: {
'Authorization': `Bearer ${authStore.token}`
}
@@ -165,7 +165,7 @@ async function loadProfile() {
// Lade Bundesländer
async function loadStates() {
try {
const response = await fetch('http://localhost:3010/api/profile/states', {
const response = await fetch('${API_URL}/profile/states', {
headers: {
'Authorization': `Bearer ${authStore.token}`
}
@@ -185,7 +185,7 @@ async function loadStates() {
async function saveProfile() {
try {
loading.value = true
const response = await fetch('http://localhost:3010/api/profile', {
const response = await fetch('${API_URL}/profile', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',

View File

@@ -82,7 +82,7 @@ const { showModal, modalConfig, alert, confirm, onConfirm, onCancel } = useModal
async function loadUsers() {
try {
loading.value = true
const response = await fetch('http://localhost:3010/api/roles/users', {
const response = await fetch('${API_URL}/roles/users', {
headers: {
'Authorization': `Bearer ${authStore.token}`
}
@@ -133,7 +133,7 @@ async function demoteToUser(userId, userName) {
async function updateRole(userId, newRole) {
try {
loading.value = true
const response = await fetch(`http://localhost:3010/api/roles/users/${userId}`, {
const response = await fetch(`${API_URL}/roles/users/${userId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',

View File

@@ -123,7 +123,7 @@ const form = ref({
async function loadSickEntries() {
try {
loading.value = true
const response = await fetch('http://localhost:3010/api/sick', {
const response = await fetch('${API_URL}/sick', {
headers: {
'Authorization': `Bearer ${authStore.token}`
}
@@ -145,7 +145,7 @@ async function loadSickEntries() {
// Lade alle Krankheitstypen
async function loadSickTypes() {
try {
const response = await fetch('http://localhost:3010/api/sick/types', {
const response = await fetch('${API_URL}/sick/types', {
headers: {
'Authorization': `Bearer ${authStore.token}`
}
@@ -171,7 +171,7 @@ async function createSick() {
try {
loading.value = true
const response = await fetch('http://localhost:3010/api/sick', {
const response = await fetch('${API_URL}/sick', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -212,7 +212,7 @@ async function deleteSick(id) {
try {
loading.value = true
const response = await fetch(`http://localhost:3010/api/sick/${id}`, {
const response = await fetch(`${API_URL}/sick/${id}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${authStore.token}`

View File

@@ -179,7 +179,7 @@ async function loadWorklogEntries() {
if (!date) return
// Hole alle Worklog-Einträge für das Datum vom Backend
const response = await fetch(`http://localhost:3010/api/timefix/worklog-entries?date=${date}`, {
const response = await fetch(`${API_URL}/timefix/worklog-entries?date=${date}`, {
headers: authStore.getAuthHeaders()
})
@@ -224,7 +224,7 @@ function onEntrySelected() {
async function loadTimefixes() {
try {
loading.value = true
const response = await fetch('http://localhost:3010/api/timefix', {
const response = await fetch('${API_URL}/timefix', {
headers: authStore.getAuthHeaders()
})
@@ -248,7 +248,7 @@ async function createTimefix() {
try {
loading.value = true
const response = await fetch('http://localhost:3010/api/timefix', {
const response = await fetch('${API_URL}/timefix', {
method: 'POST',
headers: {
...authStore.getAuthHeaders(),
@@ -290,7 +290,7 @@ async function deleteTimefix(id) {
try {
loading.value = true
const response = await fetch(`http://localhost:3010/api/timefix/${id}`, {
const response = await fetch(`${API_URL}/timefix/${id}`, {
method: 'DELETE',
headers: authStore.getAuthHeaders()
})

View File

@@ -177,7 +177,7 @@ function onWishtypeChange() {
async function loadTimewishes() {
try {
loading.value = true
const response = await fetch('http://localhost:3010/api/timewish', {
const response = await fetch('${API_URL}/timewish', {
headers: {
'Authorization': `Bearer ${authStore.token}`
}
@@ -205,7 +205,7 @@ async function createTimewish() {
try {
loading.value = true
const response = await fetch('http://localhost:3010/api/timewish', {
const response = await fetch('${API_URL}/timewish', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -248,7 +248,7 @@ async function deleteTimewish(id) {
try {
loading.value = true
const response = await fetch(`http://localhost:3010/api/timewish/${id}`, {
const response = await fetch(`${API_URL}/timewish/${id}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${authStore.token}`

View File

@@ -135,7 +135,7 @@ function onStartDateChange() {
async function loadVacations() {
try {
loading.value = true
const response = await fetch('http://localhost:3010/api/vacation', {
const response = await fetch('${API_URL}/vacation', {
headers: authStore.getAuthHeaders()
})
@@ -159,7 +159,7 @@ async function createVacation() {
try {
loading.value = true
const response = await fetch('http://localhost:3010/api/vacation', {
const response = await fetch('${API_URL}/vacation', {
method: 'POST',
headers: {
...authStore.getAuthHeaders(),
@@ -197,7 +197,7 @@ async function deleteVacation(id) {
try {
loading.value = true
const response = await fetch(`http://localhost:3010/api/vacation/${id}`, {
const response = await fetch(`${API_URL}/vacation/${id}`, {
method: 'DELETE',
headers: authStore.getAuthHeaders()
})

View File

@@ -233,7 +233,7 @@ const loadWeekData = async () => {
try {
loading.value = true
const response = await fetch(`http://localhost:3010/api/week-overview?weekOffset=${weekOffset.value}`, {
const response = await fetch(`${API_URL}/week-overview?weekOffset=${weekOffset.value}`, {
headers: {
'Authorization': `Bearer ${authStore.token}`,
'Content-Type': 'application/json'

View File

@@ -61,7 +61,7 @@ async function loadStatistics() {
loading.value = true
error.value = null
const response = await fetch(`http://localhost:3010/api/workdays?year=${selectedYear.value}`, {
const response = await fetch(`${API_URL}/workdays?year=${selectedYear.value}`, {
headers: {
'Authorization': `Bearer ${authStore.token}`
}