Added to repo
This commit is contained in:
commit
8f3ff7d089
|
|
@ -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
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "car_management"
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
}
|
||||
|
|
@ -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-*
|
||||
|
|
@ -0,0 +1 @@
|
|||
engine-strict=true
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# Package Managers
|
||||
package-lock.json
|
||||
pnpm-lock.yaml
|
||||
yarn.lock
|
||||
bun.lock
|
||||
bun.lockb
|
||||
|
||||
# Miscellaneous
|
||||
/static/
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"php.version": "7.4"
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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 {};
|
||||
|
|
@ -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>
|
||||
|
|
@ -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'
|
||||
});
|
||||
|
|
@ -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'
|
||||
});
|
||||
|
|
@ -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} />
|
||||
|
|
@ -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>
|
||||
|
|
@ -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}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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()
|
||||
});
|
||||
|
|
@ -0,0 +1 @@
|
|||
// place files you want to import through the `$lib` alias in this folder.
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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}
|
||||
|
|
@ -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>
|
||||
|
|
@ -0,0 +1 @@
|
|||
export const prerender = false;
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
goto(`${base}/dashboard`);
|
||||
</script>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
localStorage.clear();
|
||||
goto(`${base}/login`);
|
||||
</script>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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]
|
||||
|
|
@ -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 |
|
|
@ -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;
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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 Self‑Signed‑Cert nutzt
|
||||
ws: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
Loading…
Reference in New Issue