Added to repo

This commit is contained in:
René Preikschat 2025-07-17 13:36:09 +02:00
commit 8f3ff7d089
39 changed files with 5813 additions and 0 deletions

21
Projektstruktur.txt Normal file
View File

@ -0,0 +1,21 @@
Projektstruktur
admin
add_or_edit_user
list_users
profile
show_own_profile (base)
edit_own_profile (sub)
cars
list_cars
car
add_or_edit_car
car_details
add_or_edit_maintenance_info
perform_maintenance
calendar
list_upcoming_maintenance
dashboard or /
show calendar, car_list and stats
ZXxvP4gKw0TZJHDFJpveRNY30xHjmmuj5bd65wTW498fCWhJ9I

View File

@ -0,0 +1,8 @@
{
"folders": [
{
"path": "car_management"
}
],
"settings": {}
}

23
car_management/.gitignore vendored Normal file
View File

@ -0,0 +1,23 @@
node_modules
# Output
.output
.vercel
.netlify
.wrangler
/.svelte-kit
/build
# OS
.DS_Store
Thumbs.db
# Env
.env
.env.*
!.env.example
!.env.test
# Vite
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

1
car_management/.npmrc Normal file
View File

@ -0,0 +1 @@
engine-strict=true

View File

@ -0,0 +1,9 @@
# Package Managers
package-lock.json
pnpm-lock.yaml
yarn.lock
bun.lock
bun.lockb
# Miscellaneous
/static/

View File

@ -0,0 +1,15 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
"overrides": [
{
"files": "*.svelte",
"options": {
"parser": "svelte"
}
}
]
}

3
car_management/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"php.version": "7.4"
}

38
car_management/README.md Normal file
View File

@ -0,0 +1,38 @@
# sv
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npx sv create
# create a new project in my-app
npx sv create my-app
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
To create a production version of your app:
```bash
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.

View File

@ -0,0 +1,40 @@
import prettier from 'eslint-config-prettier';
import { includeIgnoreFile } from '@eslint/compat';
import js from '@eslint/js';
import svelte from 'eslint-plugin-svelte';
import globals from 'globals';
import { fileURLToPath } from 'node:url';
import ts from 'typescript-eslint';
import svelteConfig from './svelte.config.js';
const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url));
export default ts.config(
includeIgnoreFile(gitignorePath),
js.configs.recommended,
...ts.configs.recommended,
...svelte.configs.recommended,
prettier,
...svelte.configs.prettier,
{
languageOptions: {
globals: { ...globals.browser, ...globals.node }
},
rules: {
// typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects.
// see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors
'no-undef': 'off'
}
},
{
files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'],
languageOptions: {
parserOptions: {
projectService: true,
extraFileExtensions: ['.svelte'],
parser: ts.parser,
svelteConfig
}
}
}
);

4913
car_management/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,44 @@
{
"name": "car-management",
"private": true,
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite dev --host",
"build": "vite build",
"preview": "vite preview",
"deploy": "npm run build && robocopy build I:\\cardb\\car_management /MIR /NFL /NDL /NJH /NJS /NC /NS",
"prepare": "svelte-kit sync || echo ''",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"format": "prettier --write .",
"lint": "prettier --check . && eslint ."
},
"devDependencies": {
"@eslint/compat": "^1.2.5",
"@eslint/js": "^9.18.0",
"@sveltejs/adapter-static": "^3.0.8",
"@sveltejs/kit": "^2.22.0",
"@sveltejs/vite-plugin-svelte": "^6.0.0",
"@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15",
"@tailwindcss/vite": "^4.0.0",
"eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-svelte": "^3.0.0",
"globals": "^16.0.0",
"prettier": "^3.4.2",
"prettier-plugin-svelte": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.11",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"tailwindcss": "^4.0.0",
"typescript": "^5.0.0",
"typescript-eslint": "^8.20.0",
"vite": "^7.0.4"
},
"dependencies": {
"lucide-svelte": "^0.525.0",
"svelte-i18n": "^4.0.1"
}
}

View File

@ -0,0 +1,71 @@
@import 'tailwindcss';
@plugin '@tailwindcss/forms';
@plugin '@tailwindcss/typography';
@theme {
--color-rich-black: #040F0F; /*Main Color*/
--color-rich-black-50: #BFEEEE;
--color-rich-black-100: #9EE5E5;
--color-rich-black-200: #7EDDDD;
--color-rich-black-300: #5ED4D4;
--color-rich-black-400: #3ECCCC;
--color-rich-black-500: #2FB1B1;
--color-rich-black-600: #279191;
--color-rich-black-700: #1E7171;
--color-rich-black-800: #155151;
--color-rich-black-900: #0D3030;
--color-rich-black-950: #040F0F; /*Main Color*/
--color-forest-green: #248232; /*Main Color*/
--color-forest-green-50: #DFF6E2;
--color-forest-green-100: #BFEDC6;
--color-forest-green-200: #9FE5A9;
--color-forest-green-300: #7FDC8D;
--color-forest-green-400: #5FD370;
--color-forest-green-500: #3FCA54;
--color-forest-green-600: #30B043;
--color-forest-green-700: #248232; /*Main Color*/
--color-forest-green-800: #1F702B;
--color-forest-green-900: #16501F;
--color-forest-green-950: #0D3012;
--color-pigment-green: #2BA84A; /*Main Color*/
--color-pigment-green-50: #DFF7E5;
--color-pigment-green-100: #BEEECA;
--color-pigment-green-200: #9EE6B0;
--color-pigment-green-300: #7DDE95;
--color-pigment-green-400: #5DD57B;
--color-pigment-green-500: #3CCD60;
--color-pigment-green-600: #2BA84A; /*Main Color*/
--color-pigment-green-700: #269241;
--color-pigment-green-800: #1D7232;
--color-pigment-green-900: #155124;
--color-pigment-green-950: #0D3116;
--color-gunmetal: #242D2D; /*Main Color*/
--color-gunmetal-50: #DDE4E4;
--color-gunmetal-100: #C6D2D2;
--color-gunmetal-200: #B0BFBF;
--color-gunmetal-300: #99ADAD;
--color-gunmetal-400: #829B9B;
--color-gunmetal-500: #6D8888;
--color-gunmetal-600: #5B7171;
--color-gunmetal-700: #495B5B;
--color-gunmetal-800: #364444;
--color-gunmetal-900: #242D2D; /*Main Color*/
--color-gunmetal-950: #121717;
--color-timberwolf: #D3D5D4; /*Main Color*/
--color-timberwolf-50: #EAEBEB;
--color-timberwolf-100: #D3D5D4; /*Main Color*/
--color-timberwolf-200: #C1C3C2;
--color-timberwolf-300: #ACAFAD;
--color-timberwolf-400: #979B99;
--color-timberwolf-500: #828785;
--color-timberwolf-600: #6E7270;
--color-timberwolf-700: #5A5E5C;
--color-timberwolf-800: #464947;
--color-timberwolf-900: #323423;
--color-timberwolf-950: #1E1F1F;
}

13
car_management/src/app.d.ts vendored Normal file
View File

@ -0,0 +1,13 @@
// See https://svelte.dev/docs/kit/types#app.d.ts
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {};

View File

@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View File

@ -0,0 +1,10 @@
// Initialisiere i18n vor allen Komponenten
import { register, init } from 'svelte-i18n';
register('de', () => import('./locales/de.json'));
register('en', () => import('./locales/en.json'));
init({
fallbackLocale: 'de',
initialLocale: 'de'
});

View File

@ -0,0 +1,10 @@
// src/hooks.ts
import { register, init } from 'svelte-i18n';
register('de', () => import('./locales/de.json'));
register('en', () => import('./locales/en.json'));
init({
fallbackLocale: 'de',
initialLocale: 'de'
});

View File

@ -0,0 +1,13 @@
<script lang="ts">
import Nav from '$lib/components/Nav.svelte';
import { _ } from 'svelte-i18n';
import { Home, User, LogOut } from 'lucide-svelte';
import { base } from '$app/paths';
const navItems = [
{ label: 'header.dashboard', icon: Home, href: `${base}/dashboard` },
{ label: 'header.profile', icon: User, href: `${base}/profile` },
{ label: 'header.logout', icon: LogOut, href: `${base}/logout` }
];
</script>
<Nav items={navItems} />

View File

@ -0,0 +1,77 @@
<script lang="ts">
import { MenuIcon, XIcon } from 'lucide-svelte';
import { _ } from 'svelte-i18n';
import { locale, locales } from 'svelte-i18n';
import HeaderButton from '$lib/components/uiElements/HeaderButton.svelte';
export let items: {
label: string;
href: string;
icon?: any;
}[] = [];
let menuOpen = false;
const toggleMenu = () => {
menuOpen = !menuOpen;
};
const setLanguage = (lang: string) => {
locale.set(lang);
};
</script>
<nav class="fixed top-0 left-0 z-10 w-full bg-pigment-green shadow shadow-green-800 dark:bg-forest-green dark:text-timberwolf text-gunmetal-950">
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div class="flex h-16 items-center justify-between">
<!-- Logo -->
<div class="flex flex-shrink-0 items-center">
<a href="/car_management/" class="text-xl font-bold">{$_('dashboard.title')}</a>
</div>
<!-- Desktop-Navigation: ab sm (min-width: 640px) sichtbar -->
<div class="hidden items-center space-x-4 sm:flex">
{#each items as item}
<HeaderButton button={{ type: 'link', label: item.label, href: item.href, icon: item.icon }} />
{/each}
{#each $locales as l}
<HeaderButton button={{ type: 'action', label: l, onClick: () => setLanguage(l) }} />
{/each}
</div>
<!-- Burger-Button: nur auf xs (sm:hidden) anzeigen -->
<button
class="flex items-center rounded-md p-2 text-gunmetal-950 hover:bg-gray-100 focus:outline-none sm:hidden dark:text-gray-100 dark:hover:bg-gray-700"
aria-label="Open main menu"
on:click={toggleMenu}
>
{#if menuOpen}
<XIcon class="h-6 w-6" />
{:else}
<MenuIcon class="h-6 w-6" />
{/if}
</button>
</div>
</div>
<!-- Mobile-Navigation: nur wenn geöffnet und auf xs -->
{#if menuOpen}
<div
class="border-t border-gray-200 bg-gunmetal text-timberwolf sm:hidden dark:border-gray-700 dark:bg-gunmetal"
>
<div class="space-y-1 px-2 pt-2 pb-3">
{#each items as item}
<a
href={item.href}
class="block rounded-md px-3 py-2 text-base font-medium text-timberwolf bg- hover:bg-gray-100 dark:hover:bg-gray-700"
on:click={() => (menuOpen = false)}
>
{#if item.icon}
<svelte:component this={item.icon} class="mr-2 inline-block h-5 w-5" />
{/if}
{$_(item.label)}
</a>
{/each}
</div>
</div>
{/if}
</nav>
<div class="h-16"></div>

View File

@ -0,0 +1,32 @@
<script lang="ts">
import { _ } from 'svelte-i18n';
export let button: {
type: string;
label: string;
href?: string;
icon?: any;
onClick?: any;
};
</script>
{#if button.type == 'link'}
<a
href={button.href}
class="flex items-center rounded-md px-3 py-2 text-sm font-medium text-gunmetal-950 hover:bg-timberwolf dark:text-timberwolf dark:hover:bg-gunmetal"
>
{#if button.icon}
<svelte:component this={button.icon} class="mr-2 h-5 w-5" />
{/if}
{$_(button.label)}
</a>
{:else if button.type == 'action'}
<button
class="flex items-center cursor-pointer rounded-md px-3 py-2 text-sm font-medium text-gunmetal-950 hover:bg-timberwolf dark:text-timberwolf dark:hover:bg-gunmetal"
on:click={button.onClick}
>
{#if button.icon}
<svelte:component this={button.icon} class="mr-2 h-5 w-5" />
{/if}
{$_(button.label)}
</button>
{/if}

View File

@ -0,0 +1,12 @@
<div class="grid min-h-[140px] w-full place-items-center overflow-x-scroll rounded-lg p-6 lg:overflow-visible">
<svg class="text-gray-300 animate-spin" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg"
width="24" height="24">
<path
d="M32 3C35.8083 3 39.5794 3.75011 43.0978 5.20749C46.6163 6.66488 49.8132 8.80101 52.5061 11.4939C55.199 14.1868 57.3351 17.3837 58.7925 20.9022C60.2499 24.4206 61 28.1917 61 32C61 35.8083 60.2499 39.5794 58.7925 43.0978C57.3351 46.6163 55.199 49.8132 52.5061 52.5061C49.8132 55.199 46.6163 57.3351 43.0978 58.7925C39.5794 60.2499 35.8083 61 32 61C28.1917 61 24.4206 60.2499 20.9022 58.7925C17.3837 57.3351 14.1868 55.199 11.4939 52.5061C8.801 49.8132 6.66487 46.6163 5.20749 43.0978C3.7501 39.5794 3 35.8083 3 32C3 28.1917 3.75011 24.4206 5.2075 20.9022C6.66489 17.3837 8.80101 14.1868 11.4939 11.4939C14.1868 8.80099 17.3838 6.66487 20.9022 5.20749C24.4206 3.7501 28.1917 3 32 3L32 3Z"
stroke="currentColor" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"></path>
<path
d="M32 3C36.5778 3 41.0906 4.08374 45.1692 6.16256C49.2477 8.24138 52.7762 11.2562 55.466 14.9605C58.1558 18.6647 59.9304 22.9531 60.6448 27.4748C61.3591 31.9965 60.9928 36.6232 59.5759 40.9762"
stroke="currentColor" stroke-width="5" stroke-linecap="round" stroke-linejoin="round" class="text-gray-900">
</path>
</svg>
</div>

View File

@ -0,0 +1,10 @@
// src/lib/i18n.ts
import { register, init, getLocaleFromNavigator } from 'svelte-i18n';
register('de', () => import('../locales/de.json'));
register('en', () => import('../locales/en.json'));
init({
fallbackLocale: 'de',
initialLocale: getLocaleFromNavigator()
});

View File

@ -0,0 +1 @@
// place files you want to import through the `$lib` alias in this folder.

View File

@ -0,0 +1,33 @@
{
"de":"DE",
"en":"EN",
"error": {
"not_found": "Seite nicht gefunden",
"home": "Zur Startseite"
},
"dashboard": {
"title": "RP-S Fahrzeugverwaltung",
"welcome": "Willkommen",
"sample_vehicle": "Dein erstes Fahrzeug",
"next_service": "Nächste Wartung: {date}"
},
"login": {
"title": "RP-S Fahrzeugverwaltung",
"login_failed": "Anmeldung fehlgeschlagen",
"network_error": "Netzwerkfehler",
"username": "Nutzername",
"password": "Passwort",
"loading": "Wird geladen",
"login": "Anmelden"
},
"header": {
"dashboard": "Übersicht",
"profile": "Profil",
"logout": "Abmelden"
},
"profile":{
"title": "Profilinformationen",
"user_display_name": "Anzeigename",
"user_name": "Anmeldename"
}
}

View File

@ -0,0 +1,33 @@
{
"de":"DE",
"en":"EN",
"error": {
"not_found": "Page not found",
"home": "Back to Home"
},
"dashboard": {
"title": "RP-S Car Management",
"welcome": "Welcome",
"sample_vehicle": "Your first vehicle",
"next_service": "Next service: {date}"
},
"login": {
"title": "RP-S Car Management",
"login_failed": "Login failed",
"network_error": "Network error",
"username": "Username",
"password": "Password",
"loading": "Loading",
"login": "Login"
},
"header": {
"dashboard": "Dashboard",
"profile": "Profile",
"logout": "Logout"
},
"profile":{
"title": "Profile Info",
"user_display_name": "Display name",
"user_name": "Login name"
}
}

View File

@ -0,0 +1,18 @@
<script lang="ts">
import { base } from '$app/paths';
import { _ } from 'svelte-i18n';
import { page } from '$app/state';
const currentPage = page.url.pathname.replace(base,"").replace("/","");
const blank_pages = ['logout'];
const blank_page = blank_pages.includes(currentPage);
</script>
{#if !blank_page}
<main class="flex min-h-screen flex-col items-center justify-center bg-gray-100 p-8">
<h1 class="mb-4 text-5xl font-extrabold text-red-600">{page.status}</h1>
<p class="mb-6 text-xl text-gray-700">{page.error?.message ?? $_('error.not_found')}</p>
<a href={base + '/'} class="rounded bg-blue-600 px-4 py-2 text-white hover:bg-blue-700"
>{$_('error.home')}</a
>
</main>
{/if}

View File

@ -0,0 +1,49 @@
<!-- src/routes/+layout.svelte -->
<script lang="ts">
import '../app.css';
import { onMount } from 'svelte';
import { page } from '$app/state';
import { goto } from '$app/navigation';
import { base } from '$app/paths';
async function validateToken(token: string) {
try {
const res = await fetch(`/api/validate.php`, {
headers: { Authorization: `Bearer ${token}` }
});
if (!res.ok) {
return false;
}
// JSON mit { valid: true, user: { username: '...' } }
const data = await res.json();
if (data.valid) {
// Username speichern, damit du ihn später abrufen kannst
localStorage.setItem('username', data.user.username);
return true;
} else {
return false;
}
} catch {
return false;
}
}
onMount(async () => {
const token = localStorage.getItem('token');
const currentPath = page.url.pathname;
// Wenn wir nicht bereits auf Login sind:
if (!currentPath.startsWith(`${base}/login`)) {
if (!token || !(await validateToken(token)) || currentPath.startsWith(`${base}/logout`)) {
// ungültig oder abgelaufen
localStorage.removeItem('token');
goto(`${base}/login`);
return;
}
}
});
</script>
<div class="bg-timberwolf min-h-dvh w-full dark:bg-gunmetal">
<slot />
</div>

View File

@ -0,0 +1 @@
export const prerender = false;

View File

@ -0,0 +1,5 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { base } from '$app/paths';
goto(`${base}/dashboard`);
</script>

View File

@ -0,0 +1,22 @@
<script lang="ts">
import { onMount } from 'svelte';
import { _ } from 'svelte-i18n';
import Header from '$lib/components/Header.svelte';
let username = '';
onMount(() => {
username = localStorage.getItem('user_display_name') || 'Gast';
});
</script>
<Header/>
<main class="p-8 text-gunmetal-950 dark:text-timberwolf">
<h1 class="mb-4 text-3xl font-bold">{$_('dashboard.title')}</h1>
<p class="mb-6">{$_('dashboard.welcome')}, {username}!</p>
<ul class="space-y-4">
<li class="rounded bg-white dark:bg-gunmetal border p-4 shadow">{$_('dashboard.sample_vehicle')}</li>
<li class="rounded bg-white dark:bg-gunmetal border p-4 shadow">
{$_('dashboard.next_service', { values: { date: '01.01.2026' } })}
</li>
</ul>
</main>

View File

@ -0,0 +1,78 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { base } from '$app/paths';
import { _ } from 'svelte-i18n';
let username = '';
let password = '';
let error = '';
let loading = false;
async function handleSubmit(event: Event) {
event.preventDefault();
error = '';
loading = true;
try {
const res = await fetch(`/api/login.php`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
const data = await res.json();
if (res.ok) {
localStorage.setItem('token', data.token);
localStorage.setItem('user_display_name', data.user.user_display_name);
goto(`${base}/dashboard`, { replaceState: true });
} else {
error = data.message || $_('login.login_failed');
}
} catch (e) {
error = $_('login.network_error');
} finally {
loading = false;
}
}
</script>
<main
class="flex min-h-screen sm:items-center justify-center bg-timberwolf dark:bg-gunmetal"
style="
background-image: url('{base}/images/login-background.jpg');
background-size: cover;
background-"
>
<div class="w-full sm:max-w-md sm:rounded-lg bg-timberwolf dark:bg-gunmetal dark:text-timberwolf p-8 shadow-lg">
<h1 class="mb-6 text-center text-2xl font-bold">{$_('login.title')}</h1>
{#if error}
<div class="mb-4 rounded bg-red-100 p-2 text-sm text-red-700">{error}</div>
{/if}
<form on:submit|preventDefault={handleSubmit} class="space-y-4">
<div>
<label for="username" class="mb-1 block text-sm font-medium">{$_('login.username')}</label>
<input
id="username"
type="text"
bind:value={username}
required
class="w-full rounded border px-3 py-2 dark:bg-gunmetal focus:border-forest-green focus:ring focus:outline-none"
/>
</div>
<div>
<label for="password" class="mb-1 block text-sm font-medium">{$_('login.password')}</label>
<input
id="password"
type="password"
bind:value={password}
required
class="w-full rounded border px-3 py-2 dark:bg-gunmetal focus:border-forest-green focus:ring focus:outline-none"
/>
</div>
<button
type="submit"
class="w-full rounded bg-forest-green-400 hover:bg-forest-green-500 dark:bg-forest-green-600 py-2 font-semibold text-gunmetal dark:hover:bg-forest-green-700 dark:hover:text-timberwolf cursor-pointer disabled:opacity-80"
disabled={loading}
>
{#if loading}{$_('login.loading')}...{:else}{$_('login.login')}{/if}
</button>
</form>
</div>
</main>

View File

@ -0,0 +1,6 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { base } from '$app/paths';
localStorage.clear();
goto(`${base}/login`);
</script>

View File

@ -0,0 +1,119 @@
<script lang="ts">
import { _ } from 'svelte-i18n';
import Header from '$lib/components/Header.svelte';
import LoadingSpinner from '$lib/components/uiElements/LoadingSpinner.svelte';
import { goto } from '$app/navigation';
import { base } from '$app/paths';
let changingPassword = false;
let passwordChange = {
currentPassword:"",
newPassword:"",
newPassword2:""
};
async function fetchUserData(): Promise<any> {
const token = localStorage.getItem('token');
const userInfoRequest = new Request(`/api/userInfo.php`, {
headers: { Authorization: `Bearer ${token}` }
});
const result = await fetch(userInfoRequest);
if (result.status == 401) {
goto(`${base}/login`);
}
if (!result.ok) {
throw new Error('Unable to fetch User Info');
}
const fetchedUserData = (await result.json()).user;
return fetchedUserData;
}
</script>
<Header />
<main class="flex items-center justify-center p-4 max-sm:p-0 bg-baby-powder dark:bg-gunmetal max-sm:h-full">
<figure
class="w-md rounded-xl sm:bg-gray-950/5 p-1 sm:inset-ring inset-ring-gray-950/5 sm:dark:bg-white/10 sm:dark:inset-ring-white/10 max-sm:w-full max-sm:p-0 max-sm:rounded-none max-sm:h-full"
>
<div
class="not-prose overflow-auto rounded-lg sm:bg-white p-2 sm:outline outline-white/5 sm:dark:bg-gunmetal-900/80 max-sm:rounded-none max-sm:h-full"
>
<h1 class="mb-4 text-center text-2xl font-bold dark:text-timberwolf">{$_('profile.title')}</h1>
{#await fetchUserData()}
<LoadingSpinner />
{:then userData}
<ul class="grid grid-cols-1 space-y-4">
<li class="mr-auto ml-auto w-xs rounded bg-white dark:bg-gunmetal dark:border dark:text-timberwolf p-4 shadow">
{$_('profile.user_display_name')}: {userData.userDisplayName}
</li>
<li class="mr-auto ml-auto w-xs rounded bg-white dark:bg-gunmetal dark:border dark:text-timberwolf p-4 shadow">
{$_('profile.user_name')}: {userData.userName}
</li>
{#if changingPassword}
<div class="mr-auto ml-auto w-xs rounded bg-gray-100 p-3">
<form>
<label class="block"
><span
class="block text-sm font-medium text-gray-700 after:ml-0.5 after:text-red-500 after:content-['*'] dark:text-white"
>Current Password</span
><input
id="currentPassword"
class="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 placeholder-gray-400 shadow-sm focus:border-sky-500 focus:outline focus:outline-sky-500 sm:text-sm"
placeholder="***"
type="password"
name="currentPassword"
bind:value={passwordChange.currentPassword}
/></label
>
<label class="block"
><span
class="block text-sm font-medium text-gray-700 after:ml-0.5 after:text-red-500 after:content-['*'] dark:text-white"
>New Password</span
><input
id="newPassword"
class="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 placeholder-gray-400 shadow-sm focus:border-sky-500 focus:outline focus:outline-sky-500 sm:text-sm"
placeholder="***"
type="password"
name="newPassword"
bind:value={passwordChange.newPassword}
/></label
>
<label class="block"
><span
class="block text-sm font-medium text-gray-700 after:ml-0.5 after:text-red-500 after:content-['*'] dark:text-white"
>Repeat new Password</span
><input
id="newPassword2"
class="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 placeholder-gray-400 shadow-sm focus:border-sky-500 focus:outline focus:outline-sky-500 sm:text-sm"
placeholder="***"
type="password"
name="newPassword2"
bind:value={passwordChange.newPassword}
/></label
>
<div class="w-full flex space space-x-1 pt-3">
<button type="button" class="w-1/2 cursor-pointer rounded bg-red-600 p-1 text-center text-white shadow hover:bg-red-400 active:bg-red-800"
on:click={()=>{changingPassword=false;}}>
Cancel
</button>
<button type="button" class="w-1/2 cursor-pointer rounded bg-blue-600 p-1 text-center text-white shadow hover:bg-blue-400 active:bg-blue-800">
Save
</button>
</div>
</form>
</div>
{:else}
<button
class="mr-auto ml-auto w-xs cursor-pointer rounded bg-forest-green-400 hover:bg-forest-green-500 dark:bg-forest-green-600 p-4 text-center text-gunmetal dark:hover:bg-forest-green-700 dark:hover:text-timberwolf shadow disabled:opacity-80"
on:click={() => {
changingPassword = true;
}}
>
Edit Password
</button>
{/if}
</ul>
{:catch exception}
{exception}
{/await}
</div>
</figure>
</main>

View File

@ -0,0 +1,10 @@
RewriteEngine On
RewriteBase /car_management/
# Wenn Datei oder Ordner existiert, liefere sie aus
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
# Sonst: bring alle Anfragen zur index.html
RewriteRule ^ index.html [L]

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 480 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

View File

@ -0,0 +1,25 @@
import adapter from '@sveltejs/adapter-static';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://svelte.dev/docs/kit/integrations
// for more information about preprocessors
preprocess: vitePreprocess(),
kit: {
paths: {
base: '/car_management'
},
adapter: adapter({
fallback: 'index.html',
strict: false
}),
prerender: {
entries: []
}
}
};
export default config;

View File

@ -0,0 +1,19 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler"
}
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}

View File

@ -0,0 +1,18 @@
import tailwindcss from '@tailwindcss/vite';
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [tailwindcss(), sveltekit()],
server: {
proxy: {
// Alle Anfragen, die mit /api/ beginnen, werden an dein API weitergeleitet
'/api': {
target: 'https://cardb.rp-s.de',
changeOrigin: true,
secure: false, // falls du ein SelfSignedCert nutzt
ws: true
}
}
}
});