Login Popup zu Kundendaten bewegt

This commit is contained in:
Moritz Utcke
2025-02-20 21:25:38 +11:00
parent eac6f0c035
commit 927f0157be
35 changed files with 719 additions and 692 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

View File

@@ -1,17 +1,18 @@
import { createCallerFactory } from "astro-typesafe-api/server";
export const createCaller = createCallerFactory({
"bild": await import("../src/pages/api/bild.ts"),
"klimafaktoren": await import("../src/pages/api/klimafaktoren.ts"),
"postleitzahlen": await import("../src/pages/api/postleitzahlen.ts"),
"auth/access-token": await import("../src/pages/api/auth/access-token.ts"),
"auth/forgot-password": await import("../src/pages/api/auth/forgot-password.ts"),
"auth/refresh-token": await import("../src/pages/api/auth/refresh-token.ts"),
"admin/ausstellen": await import("../src/pages/api/admin/ausstellen.ts"),
"admin/bestellbestaetigung": await import("../src/pages/api/admin/bestellbestaetigung.ts"),
"admin/erinnern": await import("../src/pages/api/admin/erinnern.ts"),
"admin/nicht-ausstellen": await import("../src/pages/api/admin/nicht-ausstellen.ts"),
"admin/post-ausstellen": await import("../src/pages/api/admin/post-ausstellen.ts"),
"admin/registriernummer": await import("../src/pages/api/admin/registriernummer.ts"),
"auth/access-token": await import("../src/pages/api/auth/access-token.ts"),
"auth/forgot-password": await import("../src/pages/api/auth/forgot-password.ts"),
"auth/refresh-token": await import("../src/pages/api/auth/refresh-token.ts"),
"aufnahme": await import("../src/pages/api/aufnahme/index.ts"),
"bedarfsausweis-wohnen/[uid]": await import("../src/pages/api/bedarfsausweis-wohnen/[uid].ts"),
"bedarfsausweis-wohnen": await import("../src/pages/api/bedarfsausweis-wohnen/index.ts"),
@@ -26,9 +27,9 @@ export const createCaller = createCallerFactory({
"user/self": await import("../src/pages/api/user/self.ts"),
"verbrauchsausweis-gewerbe/[uid]": await import("../src/pages/api/verbrauchsausweis-gewerbe/[uid].ts"),
"verbrauchsausweis-gewerbe": await import("../src/pages/api/verbrauchsausweis-gewerbe/index.ts"),
"webhooks/mollie": await import("../src/pages/api/webhooks/mollie.ts"),
"verbrauchsausweis-wohnen/[uid]": await import("../src/pages/api/verbrauchsausweis-wohnen/[uid].ts"),
"verbrauchsausweis-wohnen": await import("../src/pages/api/verbrauchsausweis-wohnen/index.ts"),
"webhooks/mollie": await import("../src/pages/api/webhooks/mollie.ts"),
"aufnahme/[uid]/bilder": await import("../src/pages/api/aufnahme/[uid]/bilder.ts"),
"aufnahme/[uid]": await import("../src/pages/api/aufnahme/[uid]/index.ts"),
"objekt/[uid]": await import("../src/pages/api/objekt/[uid]/index.ts"),

View File

@@ -3,14 +3,14 @@ import { api } from "astro-typesafe-api/client"
import { exclude } from "#lib/exclude.js";
import Cookies from "js-cookie";
import { API_ACCESS_TOKEN_COOKIE_NAME } from "#lib/constants.js";
import { AufnahmeClient, BedarfsausweisWohnenClient, ObjektClient, UploadedGebaeudeBild, VerbrauchsausweisGewerbeClient, VerbrauchsausweisWohnenClient, } from "#components/Ausweis/types.js";
import { AufnahmeClient, BedarfsausweisWohnenClient, BildClient, ObjektClient, UploadedGebaeudeBild, VerbrauchsausweisGewerbeClient, VerbrauchsausweisWohnenClient, } from "#components/Ausweis/types.js";
import { Enums } from "@ibcornelsen/database/client";
export async function ausweisSpeichern(
ausweis: VerbrauchsausweisWohnenClient | VerbrauchsausweisGewerbeClient | BedarfsausweisWohnenClient,
objekt: ObjektClient,
aufnahme: AufnahmeClient,
bilder: (UploadedGebaeudeBild & { base64?: string })[],
bilder: BildClient[],
ausweisart: Enums.Ausweisart
) {
if (objekt.uid) {
@@ -99,26 +99,15 @@ export async function ausweisSpeichern(
ausweis.uid = uid;
}
for (const bild of bilder) {
if (bild.uid) {
continue;
await api.aufnahme._uid.bilder.PUT.fetch(bilder.map(bild => bild.uid), {
params: {
uid: aufnahme.uid
},
headers: {
"Authorization": `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
}
const response = await api.aufnahme._uid.bilder.PUT.fetch({
data: bild.data,
kategorie: bild.kategorie
}, {
params: {
uid: aufnahme.uid
},
headers: {
"Authorization": `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
}
})
bild.uid = response.uid
}
})
return {
uid_ausweis: ausweis.uid,
uid_aufnahme: aufnahme.uid,

View File

@@ -38,6 +38,7 @@ export async function validateAccessTokenClient() {
const { accessToken: newAccessToken, accessTokenExpiry, refreshToken: newRefreshToken, refreshTokenExpiry } = await api.auth["access-token"].GET.fetch({
refreshToken
})
Cookies.set(API_ACCESS_TOKEN_COOKIE_NAME, newAccessToken, {
domain: `.${window.location.hostname}`,

View File

@@ -1,6 +1,4 @@
<script lang="ts">
import Hilfe from "#components/Ausweis/Hilfe.svelte";
export let spaeterWeitermachen;
export let automatischAusfüllen;
</script>
@@ -18,13 +16,6 @@ sm:grid-cols-[1fr_min-content_min-content_min-content] sm:justify-self-end sm:mt
>Automatisch Ausfüllen
</button>
<Hilfe />
<button class="button" type="button" on:click={spaeterWeitermachen}
>Später Weitermachen
</button>
</div>

View File

@@ -1,12 +1,13 @@
<script lang="ts">
import Hilfe from "#components/Ausweis/Hilfe.svelte";
import { ausweisSpeichern } from "#client/lib/ausweisSpeichern.js";
import { validateAccessTokenClient } from "#client/lib/validateAccessToken.js";
import { AufnahmeClient, BedarfsausweisWohnenClient, BenutzerClient, ObjektClient, UploadedGebaeudeBild, VerbrauchsausweisGewerbeClient, VerbrauchsausweisWohnenClient } from "#components/Ausweis/types.js";
import Overlay from "#components/Overlay.svelte";
import EmbeddedAuthFlowModule from "#modules/EmbeddedAuthFlowModule.svelte";
import { Enums } from "@ibcornelsen/database/client";
import { AusweisTyp, Enums } from "@ibcornelsen/database/client";
import { openWindowWithPost } from "#lib/helpers/window.js";
import { PRICES } from "#lib/constants.js";
export let ausweis: VerbrauchsausweisWohnenClient | VerbrauchsausweisGewerbeClient | BedarfsausweisWohnenClient;
export let bilder: UploadedGebaeudeBild[];
@@ -15,25 +16,17 @@
export let aufnahme: AufnahmeClient;
export let ausweisart: Enums.Ausweisart
let ausweistyp: AusweisTyp = Enums.AusweisTyp.Standard;
async function ausweisAbschicken() {
loginAction = ausweisAbschicken
if (!await validateAccessTokenClient()) {
loginOverlayHidden = false;
return
}
loginOverlayHidden = true
const result = await ausweisSpeichern(ausweis, objekt, aufnahme, bilder, ausweisart);
if (result !== null) {
window.history.pushState(
{},
"",
`${location.pathname}?uid=${ausweis.uid}`
);
window.location.href = `/kundendaten?uid=${ausweis.uid}`;
}
openWindowWithPost("/kundendaten", {
ausweis,
objekt,
aufnahme,
bilder,
ausweisart,
ausweistyp
}, "")
}
let loginAction: () => any = ausweisAbschicken;
@@ -61,6 +54,18 @@
}
}
async function hilfeBestellen() {
openWindowWithPost("/kundendaten", {
ausweis,
objekt,
aufnahme,
bilder,
ausweisart,
ausweistyp
}, "")
}
let showHelp: boolean = false;
let loginOverlayHidden = true;
</script>
@@ -69,7 +74,86 @@ sm:grid-cols-[1fr_min-content_min-content_min-content] sm:justify-self-end sm:mt
<div></div>
<Hilfe />
<div>
<button class="button" type="button" on:click={() => (showHelp = !showHelp)}
>Hilfe anfordern</button
>
</div>
{#if showHelp}
<div class="col-start-1 row-start-2 col-span-4 mt-4">
<div
class="bereich-box grid relative
grid-cols-1 gap-x-4 gap-y-4
"
>
<div class="pr-12">
Gerne helfen wir Ihnen wenn Sie nicht weiterkommen oder Fragen
haben. Kurze Fragen zum Formular oder der Ausweisart werden
kostenfrei telefonisch unter <a href="tel:+4940209339850"
>040/209339850</a
> beantwortet (bis 5min). Sollten Sie Unterstützung bei der Erstellung
benötgen oder lieber die Arbeit von unserem Ingenieurbüro erledigen
lassen, bieten wir Ihnen folgende Hilfen an. Bitte treffen Sie Ihre
Auswahl und klicken auf weiter:
</div>
<hr class="m-0" />
<div class="grid grid-cols-[30px_490px_200px_1fr] items-center">
<input
type="radio"
class=" accent-secondary w-[20px] h-[20px]"
id="Produkttb1"
value={Enums.AusweisTyp.Beratung}
name="Produkt"
bind:group={ausweistyp}
/>
<div class="justify-self-stretch">
Verbrauchsausweis online inkl. ausführlicher telefonischer
Beratung
</div>
<div class="text-right">
<b>{PRICES[ausweisart][Enums.AusweisTyp.Beratung]}</b> inkl. MwSt.
</div>
</div>
<div class="grid grid-cols-[30px_490px_200px_1fr] items-center">
<input
type="radio"
class=" accent-secondary w-[20px] h-[20px]"
id="Produktof1"
value={Enums.AusweisTyp.Offline}
name="Produkt"
bind:group={ausweistyp}
/>
<div>
Verbrauchsausweis offline (Sie schicken uns 3
Verbrauchsabrechnungen zu)
</div>
<div class="text-right">
<b>{PRICES[ausweisart][Enums.AusweisTyp.Offline]}</b> inkl. MwSt.
</div>
</div>
<hr class="m-0" />
<button class="button" on:click={hilfeBestellen}>jetzt Hilfe bestellen</button>
<button
class="button absolute top-2 right-2 w-[30px] h-[30px] text-sm p-0"
type="button"
on:click={() => (showHelp = !showHelp)}>X</button
>
</div>
</div>
{/if}
<button class="button" type="button" on:click={spaeterWeitermachen}
>Später Weitermachen
@@ -78,7 +162,7 @@ sm:grid-cols-[1fr_min-content_min-content_min-content] sm:justify-self-end sm:mt
<div>
<Overlay bind:hidden={loginOverlayHidden}>
<div class="bg-white w-full max-w-screen-sm py-8">
<EmbeddedAuthFlowModule onLogin={loginAction}></EmbeddedAuthFlowModule>
<EmbeddedAuthFlowModule onLogin={loginAction} email={""}></EmbeddedAuthFlowModule>
</div>
</Overlay>

View File

@@ -1,83 +0,0 @@
<script lang="ts">
import { PRICES } from "#lib/constants.js";
let showHelp: boolean = false;
</script>
<div>
<button class="button" type="button" on:click={() => (showHelp = !showHelp)}
>Hilfe anfordern</button
>
</div>
{#if showHelp}
<div class="col-start-1 row-start-2 col-span-4 mt-4">
<div
class="bereich-box grid relative
grid-cols-1 gap-x-4 gap-y-4
"
>
<div class="pr-12">
Gerne helfen wir Ihnen wenn Sie nicht weiterkommen oder Fragen
haben. Kurze Fragen zum Formular oder der Ausweisart werden
kostenfrei telefonisch unter <a href="tel:+4940209339850"
>040/209339850</a
> beantwortet (bis 5min). Sollten Sie Unterstützung bei der Erstellung
benötgen oder lieber die Arbeit von unserem Ingenieurbüro erledigen
lassen, bieten wir Ihnen folgende Hilfen an. Bitte treffen Sie Ihre
Auswahl und klicken auf weiter:
</div>
<hr class="m-0" />
<div class="grid grid-cols-[30px_490px_200px_1fr] items-center">
<input
type="radio"
class=" accent-secondary w-[20px] h-[20px]"
id="Produkttb1"
value="ptb"
name="Produkt"
/>
<div class="justify-self-stretch">
Verbrauchsausweis online inkl. ausführlicher telefonischer
Beratung
</div>
<div class="text-right">
<b>{PRICES.VerbrauchsausweisWohnen[1]}</b> inkl. MwSt.
</div>
</div>
<div class="grid grid-cols-[30px_490px_200px_1fr] items-center">
<input
type="radio"
class=" accent-secondary w-[20px] h-[20px]"
id="Produktof1"
value="pof"
name="Produkt"
/>
<div>
Verbrauchsausweis offline (Sie schicken uns 3
Verbrauchsabrechnungen zu)
</div>
<div class="text-right">
<b>{PRICES.VerbrauchsausweisWohnen[2]}</b> inkl. MwSt.
</div>
</div>
<hr class="m-0" />
<button class="button">jetzt Hilfe bestellen</button>
<button
class="button absolute top-2 right-2 w-[30px] h-[30px] text-sm p-0"
type="button"
on:click={() => (showHelp = !showHelp)}>X</button
>
</div>
</div>
{/if}

View File

@@ -1,140 +1,142 @@
<script lang="ts">
import HelpLabel from "#components/labels/HelpLabel.svelte";
import Inputlabel from "#components/labels/InputLabel.svelte";
import ZipSearch from "#components/PlzSuche.svelte";
import ZipSearch from "#components/PlzSuche.svelte";
import { BenutzerClient, RechnungClient } from "./types.js";
export let user: BenutzerClient;
export let rechnung: Partial<RechnungClient>;
$: {
if (!rechnung.abweichende_versand_adresse) {
rechnung.versand_empfaenger = rechnung.empfaenger
rechnung.versand_ort = rechnung.ort
rechnung.versand_plz = rechnung.plz
rechnung.versand_strasse = rechnung.strasse
rechnung.versand_zusatzzeile = rechnung.zusatzzeile
}
}
</script>
<div id="rechnungsadresse" class="bereich-box grid
<div
id="rechnungsadresse"
class="bereich-box grid
grid-cols-1 gap-x-4 gap-y-8
sm:grid-cols-2 sm:gap-x-6 sm:gap-y-8
xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
">
"
>
<!-- Empfänger * -->
<!-- Empfänger * -->
<div class="input-standard order-1 md:order-1 xl:order-1">
<Inputlabel title="Rechnungs-Empfänger *"></Inputlabel>
<div class="input-standard order-1 md:order-1 xl:order-1">
<Inputlabel title="Rechnungs-Empfänger *"></Inputlabel>
<input
name="rechnung_empfaenger"
type="text"
bind:value={rechnung.empfaenger}
required
data-rule-maxlength="100"
data-msg-maxlength="max. 100 Zeichen"
/>
<input
name="rechnung_empfaenger"
type="text"
bind:value={rechnung.empfaenger}
required
data-rule-maxlength="100"
data-msg-maxlength="max. 100 Zeichen"
/>
<div class="help-label">
<HelpLabel>
Bitte geben Sie den Empfänger ein, auf den die Rechnung ausgestellt wird.
</HelpLabel>
</div>
</div>
<!-- Straße, Hausnummer * -->
<div class="input-standard order21 md:order-2 xl:order-2">
<Inputlabel title="Straße, Hausnummer *"></Inputlabel>
<input
name="rechnung_strasse"
bind:value={rechnung.strasse}
type="text"
required
data-rule-maxlength="40"
data-msg-maxlength="max. 40 Zeichen"
/>
<div class="help-label">
<HelpLabel>
Bitte geben Sie die Strasse und Hausnummer, so wie Sie auf der Rechnung erscheinen soll, ein.
</HelpLabel>
</div>
</div>
<!-- PLZ / ORT -->
<div class="grid grid-cols-[2fr_4fr] gap-x-4 order-3 md:order-3 xl:order-3">
<div class="input-noHelp">
<Inputlabel title="PLZ *"></Inputlabel>
<ZipSearch
name="rechnung_plz"
bind:zip={rechnung.plz}
bind:city={rechnung.ort}
/>
</div>
<div class="input-standard">
<Inputlabel title="Ort *"></Inputlabel>
<input
name="rechnung_ort"
readonly
type="text"
required
value={rechnung.ort}
/>
<div class="help-label">
<HelpLabel>
Bitte geben Sie die PLZ des Ortes, so wie Sie auf der Rechnung erscheinen soll, ein.
</HelpLabel>
<HelpLabel>
Bitte geben Sie den Empfänger ein, auf den die Rechnung
ausgestellt wird.
</HelpLabel>
</div>
</div>
</div>
</div>
<!-- Straße, Hausnummer * -->
<!-- Zusatzzeile -->
<div class="input-standard order21 md:order-2 xl:order-2">
<Inputlabel title="Straße, Hausnummer *"></Inputlabel>
<div class="input-standard order-4 md:order-4 xl:order-4">
<Inputlabel title="Zusatzzeile"></Inputlabel>
<input
name="rechnung_zusatzzeile"
bind:value={rechnung.zusatzzeile}
type="text"
data-rule-maxlength="80"
data-msg-maxlength="max. 80 Zeichen"
/>
<input
name="rechnung_strasse"
bind:value={rechnung.strasse}
type="text"
required
data-rule-maxlength="40"
data-msg-maxlength="max. 40 Zeichen"
/>
<div class="help-label">
<HelpLabel>
Bitte geben Sie die Strasse und Hausnummer, so wie Sie auf der
Rechnung erscheinen soll, ein.
</HelpLabel>
</div>
</div>
<!-- PLZ / ORT -->
<div class="grid grid-cols-[2fr_4fr] gap-x-4 order-3 md:order-3 xl:order-3">
<div class="input-noHelp">
<Inputlabel title="PLZ *"></Inputlabel>
<ZipSearch
name="rechnung_plz"
bind:zip={rechnung.plz}
bind:city={rechnung.ort}
/>
</div>
<div class="input-standard">
<Inputlabel title="Ort *"></Inputlabel>
<input
name="rechnung_ort"
readonly
type="text"
required
value={rechnung.ort}
/>
<div class="help-label">
<HelpLabel>
Bitte geben Sie, falls erforderlich, zusätzliche nformationen ein.
Bitte geben Sie die PLZ des Ortes, so wie Sie auf der
Rechnung erscheinen soll, ein.
</HelpLabel>
</div>
</div>
</div>
</div>
<!-- E-mail -->
<!-- Zusatzzeile -->
<div class="input-standard order-5 md:order-5 xl:order-5">
<Inputlabel title="E-mail *"></Inputlabel>
<div class="input-standard order-4 md:order-4 xl:order-4">
<Inputlabel title="Zusatzzeile"></Inputlabel>
<input
name="rechnung_email"
bind:value={rechnung.email}
type="email"
/>
<input
name="rechnung_zusatzzeile"
bind:value={rechnung.zusatzzeile}
type="text"
data-rule-maxlength="80"
data-msg-maxlength="max. 80 Zeichen"
/>
<div class="help-label">
<HelpLabel>
Bitte geben Sie die E-Mail Adresse des Rechnungsempfängers ein.
</HelpLabel>
</div>
</div>
<HelpLabel>
Bitte geben Sie, falls erforderlich, zusätzliche nformationen
ein.
</HelpLabel>
</div>
</div>
<!-- Telefon
<!-- E-mail -->
<div class="input-standard order-5 md:order-5 xl:order-5">
<Inputlabel title="E-mail *"></Inputlabel>
<input name="rechnung_email" bind:value={rechnung.email} type="email" />
<div class="help-label">
<HelpLabel>
Bitte geben Sie die E-Mail Adresse des Rechnungsempfängers ein.
</HelpLabel>
</div>
</div>
<!-- Telefon
<div class="input-standard order-6 md:order-6 xl:order-6">
<Inputlabel title="Telefon *"></Inputlabel>
@@ -153,147 +155,147 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
</div>
-->
<div class="col-span-3 order-7 md:order-7 xl:order-7">
<div class="col-span-3 order-7 md:order-7 xl:order-7">
<div
class="grid grid-cols-[25px_max-content] items-center justify-items-start"
>
<input
id="abweichende_versand_adresse"
class="w-[15px] h-[15px]"
type="checkbox"
name="abweichende_versand_adresse"
bind:checked={rechnung.abweichende_versand_adresse}
/>
<div class="grid grid-cols-[25px_max-content] items-center justify-items-start">
<label for="abweichende_versand_adresse" class="cursor-pointer"
>abweichende Versandadresse</label
>
</div>
</div>
<input
id="abweichende_versand_adresse"
class="w-[15px] h-[15px]"
type="checkbox"
name="abweichende_versand_adresse"
bind:checked={rechnung.abweichende_versand_adresse}
/>
{#if rechnung.abweichende_versand_adresse}
<!-- Versand Empfänger * -->
<label for="abweichende_versand_adresse" class="cursor-pointer"
>abweichende Versandadresse</label>
<div class="input-standard order-8 md:order-8 xl:order-8">
<Inputlabel title="Versand-Empfänger *"></Inputlabel>
</div>
<input
name="versand_empfaenger"
type="text"
readonly={!rechnung.abweichende_versand_adresse}
bind:value={rechnung.versand_empfaenger}
required
data-rule-maxlength="100"
data-msg-maxlength="max. 100 Zeichen"
/>
</div>
{#if rechnung.abweichende_versand_adresse}
<!-- Versand Empfänger * -->
<div class="input-standard order-8 md:order-8 xl:order-8">
<Inputlabel title="Versand-Empfänger *"></Inputlabel>
<input
name="versand_empfaenger"
type="text"
readonly={!rechnung.abweichende_versand_adresse}
bind:value={rechnung.versand_empfaenger}
required
data-rule-maxlength="100"
data-msg-maxlength="max. 100 Zeichen"
/>
<div class="help-label">
<div class="help-label">
<HelpLabel>
Bitte geben Sie den Namen des Versand-Empfängers ein.
</HelpLabel>
</div>
</div>
<!-- Versand Straße, Hausnummer * -->
<div class="input-standard order-9 md:order-9 xl:order-9">
<Inputlabel title="Straße, Hausnummer *"></Inputlabel>
<input
name="versand_strasse"
type="text"
readonly={!rechnung.abweichende_versand_adresse}
bind:value={rechnung.versand_strasse}
required
data-rule-maxlength="40"
data-msg-maxlength="max. 40 Zeichen"
/>
<div class="help-label">
<HelpLabel>
Bitte geben Sie die Versand-Empfänger Strasse und Hausnummer ein, an die die Rechnung versandt wird.
</HelpLabel>
</div>
</div>
<!-- PLZ / ORT -->
<div class="grid grid-cols-[2fr_4fr] gap-x-4 order-10 md:order-10 xl:order-10">
<div class="input-noHelp">
<Inputlabel title="PLZ *"></Inputlabel>
<ZipSearch
name="versand_plz"
readonly={!rechnung.abweichende_versand_adresse}
bind:zip={rechnung.versand_plz}
bind:city={rechnung.versand_ort}
/>
</div>
<div class="input-standard">
<Inputlabel title="Ort *"></Inputlabel>
<input
name="versand_ort"
type="text"
readonly
required
bind:value={rechnung.versand_ort}
/>
<div class="help-label">
<HelpLabel>
Bitte geben Sie die Versand-Empfänger PLZ des Ortes ein, an den die Rechnung versandt wird.
</HelpLabel>
</div>
</div>
</div>
<!-- Versand Straße, Hausnummer * -->
<!-- Zusatzzeile -->
<div class="input-standard order-9 md:order-9 xl:order-9">
<Inputlabel title="Straße, Hausnummer *"></Inputlabel>
<div class="input-standard order-11 md:order-11 xl:order-11">
<Inputlabel title="Zusatzzeile"></Inputlabel>
<input
name="versand_strasse"
type="text"
readonly={!rechnung.abweichende_versand_adresse}
bind:value={rechnung.versand_strasse}
required
data-rule-maxlength="40"
data-msg-maxlength="max. 40 Zeichen"
/>
<input
name="versand_zusatzzeile"
type="text"
readonly={!rechnung.abweichende_versand_adresse}
bind:value={rechnung.versand_zusatzzeile}
data-rule-maxlength="80"
data-msg-maxlength="max. 80 Zeichen"
/>
<div class="help-label">
<div class="help-label">
<HelpLabel>
Bitte geben Sie, falls erforderlich, zusätzliche nformationen ein.
Bitte geben Sie die Versand-Empfänger Strasse und Hausnummer
ein, an die die Rechnung versandt wird.
</HelpLabel>
</div>
</div>
</div>
<!-- E-mail -->
<!-- PLZ / ORT -->
<div class="input-standard order-12 md:order-12 xl:order-12">
<Inputlabel title="E-mail *"></Inputlabel>
<div
class="grid grid-cols-[2fr_4fr] gap-x-4 order-10 md:order-10 xl:order-10"
>
<div class="input-noHelp">
<Inputlabel title="PLZ *"></Inputlabel>
<ZipSearch
name="versand_plz"
readonly={!rechnung.abweichende_versand_adresse}
bind:zip={rechnung.versand_plz}
bind:city={rechnung.versand_ort}
/>
</div>
<input
name="rechnung_email"
bind:value={rechnung.email}
type="email"
/>
<div class="input-standard">
<Inputlabel title="Ort *"></Inputlabel>
<div class="help-label">
<input
name="versand_ort"
type="text"
readonly
required
bind:value={rechnung.versand_ort}
/>
<div class="help-label">
<HelpLabel>
Bitte geben Sie die Versand-Empfänger PLZ des Ortes ein,
an den die Rechnung versandt wird.
</HelpLabel>
</div>
</div>
</div>
<!-- Zusatzzeile -->
<div class="input-standard order-11 md:order-11 xl:order-11">
<Inputlabel title="Zusatzzeile"></Inputlabel>
<input
name="versand_zusatzzeile"
type="text"
readonly={!rechnung.abweichende_versand_adresse}
bind:value={rechnung.versand_zusatzzeile}
data-rule-maxlength="80"
data-msg-maxlength="max. 80 Zeichen"
/>
<div class="help-label">
<HelpLabel>
Bitte geben Sie die E-Mail Adresse des Versand-Empfängers ein.
Bitte geben Sie, falls erforderlich, zusätzliche
nformationen ein.
</HelpLabel>
</div>
</div>
</div>
<!-- Telefon
<!-- E-mail -->
<div class="input-standard order-12 md:order-12 xl:order-12">
<Inputlabel title="E-mail *"></Inputlabel>
<input
name="rechnung_email"
bind:value={rechnung.email}
type="email"
/>
<div class="help-label">
<HelpLabel>
Bitte geben Sie die E-Mail Adresse des Versand-Empfängers
ein.
</HelpLabel>
</div>
</div>
<!-- Telefon
<div class="input-standard order-[13] md:order-[13] xl:order-[13]">
<Inputlabel title="Telefon *"></Inputlabel>
@@ -311,7 +313,5 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
</div>
</div>
-->
{/if }
</div>
{/if}
</div>

View File

@@ -153,8 +153,8 @@ export function getAusweisartFromUUID(uid: string): Enums.Ausweisart | null {
return null;
}
export type UnterlageClient = Omit<Unterlage, "id" | "objekt_id">
export type BildClient = Omit<Bild, "id" | "objekt_id">
export type UnterlageClient = Omit<Unterlage, "id" | "aufnahme_id">
export type BildClient = Omit<Bild, "id" | "aufnahme_id">
export type ObjektKomplettClient = ObjektClient & {
aufnahmen: AufnahmeKomplettClient[]

View File

@@ -1,10 +1,10 @@
<script lang="ts">
import UploadImages from "./UploadImages.svelte";
import type { Enums } from "@ibcornelsen/database/client";
import { BedarfsausweisWohnenClient, ObjektClient, UploadedGebaeudeBild, VerbrauchsausweisGewerbeClient, VerbrauchsausweisWohnenClient } from "./Ausweis/types.js";
import { BedarfsausweisWohnenClient, BildClient, ObjektClient, UploadedGebaeudeBild, VerbrauchsausweisGewerbeClient, VerbrauchsausweisWohnenClient } from "./Ausweis/types.js";
import { RotateCounterClockwise, Trash, Upload } from "radix-svelte-icons";
export let images: UploadedGebaeudeBild[] = [];
export let images: BildClient[] = [];
export let max: number = Infinity;
export let min: number = 1;
export let name: string = "";
@@ -42,7 +42,7 @@
{#if image.kategorie == kategorie}
<div class="relative group">
<img
src={image.data ? image.data : `/bilder/${image.uid}.webp`}
src="/bilder/{image.uid}.webp"
alt={kategorie}
class="h-full max-h-96 w-full rounded-lg border-2 group-hover:contrast-50 object-cover transition-all"
/>

View File

@@ -9,13 +9,15 @@
// Array of base64 encoded images read into the input.
import {
BedarfsausweisWohnenClient,
BildClient,
ObjektClient,
UploadedGebaeudeBild,
VerbrauchsausweisGewerbeClient,
VerbrauchsausweisWohnenClient,
} from "./Ausweis/types.js";
import { api } from "astro-typesafe-api/client";
export let images: UploadedGebaeudeBild[] = [];
export let images: BildClient[] = [];
export let ausweis:
| VerbrauchsausweisWohnenClient
| VerbrauchsausweisGewerbeClient
@@ -78,7 +80,12 @@
// Get the scaled-down data from the canvas in the desired output format and quality
const dataURL = canvas.toDataURL("image/jpeg", 0.8);
images.push({ data: dataURL as string, kategorie });
const { uid } = await api.bild.PUT.fetch({
data: dataURL,
kategorie
})
images.push({ uid, kategorie });
images = images;
if (i == Math.min(files.length, max) - 1) {

View File

@@ -148,7 +148,7 @@ export async function endEnergieVerbrauchVerbrauchsausweis_2016(
energieVerbrauchHeizung_2 * durchschnittsKlimafaktor;
let kuehlungsZuschlag = 0;
if (ausweis.wird_gekuehlt) {
if (aufnahme.kuehlung) {
kuehlungsZuschlag = 6 * 3 * energetischeNutzflaeche;
}

View File

@@ -21,41 +21,35 @@ export enum VALID_UUID_PREFIXES {
"gnw" = "GEG Nachweis Wohnen"
}
export enum AusweisTyp {
Standard,
Beratung,
Offline
}
/**
* Ein Objekt welches alle definierten Preise für unsere Basisprodukte enthält.
*/
export const PRICES: Record<Enums.Ausweisart, Record<AusweisTyp, number>> = {
export const PRICES: Record<Enums.Ausweisart, Record<Enums.AusweisTyp, number>> = {
// per E-Mail , inkl.Beratung, offline
BedarfsausweisWohnen: {
[AusweisTyp.Standard]: 95,
[AusweisTyp.Beratung]: 125,
[AusweisTyp.Offline]: 295
[Enums.AusweisTyp.Standard]: 95,
[Enums.AusweisTyp.Beratung]: 125,
[Enums.AusweisTyp.Offline]: 295
},
VerbrauchsausweisWohnen: {
[AusweisTyp.Standard]: 65,
[AusweisTyp.Beratung]: 95,
[AusweisTyp.Offline]: 180
[Enums.AusweisTyp.Standard]: 65,
[Enums.AusweisTyp.Beratung]: 95,
[Enums.AusweisTyp.Offline]: 180
},
VerbrauchsausweisGewerbe: {
[AusweisTyp.Standard]: 95,
[AusweisTyp.Beratung]: 125,
[AusweisTyp.Offline]: 360
[Enums.AusweisTyp.Standard]: 95,
[Enums.AusweisTyp.Beratung]: 125,
[Enums.AusweisTyp.Offline]: 360
},
BedarfsausweisGewerbe: {
[AusweisTyp.Standard]: 500,
[AusweisTyp.Beratung]: 700,
[AusweisTyp.Offline]: 1000
[Enums.AusweisTyp.Standard]: 500,
[Enums.AusweisTyp.Beratung]: 700,
[Enums.AusweisTyp.Offline]: 1000
},
GEGNachweisWohnen: {
[AusweisTyp.Standard]: 500,
[AusweisTyp.Beratung]: 700,
[AusweisTyp.Offline]: 1000
[Enums.AusweisTyp.Standard]: 500,
[Enums.AusweisTyp.Beratung]: 700,
[Enums.AusweisTyp.Offline]: 1000
}
};

View File

@@ -1,18 +1,23 @@
export function openWindowWithPost(url: string, data: Record<string, any>) {
var form = document.createElement("form");
form.target = "_blank";
form.method = "POST";
form.action = url;
form.style.display = "none";
export function openWindowWithPost(url: string, data: Record<string, any>, target = "_blank") {
const form = document.createElement("form");
form.target = target;
form.method = "POST";
form.action = url;
form.style.display = "none";
for (var key in data) {
var input = document.createElement("input");
input.type = "hidden";
input.name = key;
input.value = data[key];
form.appendChild(input);
}
document.body.appendChild(form);
form.submit();
document.body.removeChild(form);
}
const createInput = (name: string, value: any) => {
const input = document.createElement("input");
input.type = "hidden";
input.name = name;
input.value = JSON.stringify(value);
return input;
};
Object.entries(data).forEach(([key, value]) => {
form.appendChild(createInput(key, value));
});
document.body.appendChild(form);
form.submit();
document.body.removeChild(form);
}

View File

@@ -5,7 +5,7 @@ import { PDFDocument, rgb, StandardFonts, TextAlignment } from "pdf-lib";
import { checkbox, flex, text } from "./elements/index.js";
import { xml2pdf } from "./elements/xml2pdf.js";
import moment from "moment";
import { Heizungsstatus } from "@ibcornelsen/database/server";
import { BilderKategorie, Heizungsstatus } from "@ibcornelsen/database/server";
import { fileURLToPath } from "url";
import { copyPage } from "./utils/copyPage.js";
@@ -297,33 +297,40 @@ export async function pdfDatenblattVerbrauchsausweisWohnen(ausweis: Verbrauchsau
const images: string[][] = []
for (const bild of bilder) {
let badge: string[];
let image: string = "";
let individualHeight = (pages[2].getHeight() - 370) / 4;
if (bild.uid) {
image = `<img src="${fileURLToPath(new URL(`../../../../persistent/images/${bilder[0].uid}.webp`, import.meta.url))}" width="${(pages[2].getHeight() - 120) / 3.1}" />`
} else if (bild.data) {
image = `<img data="${bild.data}" width="${(pages[2].getWidth() - 120) / 3.1}" height="180" />`
}
const sortedImages = bilder.reduce((acc, c) => {
let img: string;
if (images.length > 0) {
let badge = images[images.length - 1]
if (badge.length == 3) {
badge = [image]
images.push(badge)
} else {
badge.push(image)
}
if (c.uid) {
img = `<img src="${fileURLToPath(new URL(`../../../../persistent/images/${bilder[0].uid}.webp`, import.meta.url))}" width="${(pages[2].getHeight() - 120) / 4.1}" height="${individualHeight}" />`
} else {
badge = [image]
images.push(badge)
img = `<img data="${c.data}" width="${(pages[2].getWidth() - 120) / 4.1}" height="${individualHeight}" />`
}
if (c.kategorie in acc) {
acc[c.kategorie].push(img)
} else {
acc[c.kategorie] = [img];
}
return acc;
}, {} as Record<BilderKategorie, string[]>)
let text = ""
for (const [kategorie, images] of Object.entries(sortedImages)) {
text += `<flex direction="column" gap="4" width="${pages[2].getWidth() - 120}" height="${individualHeight}" marginTop="25">
<text font="bold" size="14">${kategorie}</text>
<flex direction="row" justify="space-between" width="${pages[2].getWidth() - 120}" height="${individualHeight}">
${images.join("")}
</flex>
</flex>`
}
const layoutPage3 = xml2pdf(`<layout height="${pages[2].getHeight()}" width="${pages[2].getWidth()}" marginTop="150" marginLeft="60" marginRight="60">
${images.map(badge => `<flex direction="row" justify="space-between" width="${pages[2].getWidth() - 120}" height="180" marginTop="15">${badge.join("")}</flex>`).join("")}
</layout>`, { "default": font })
${text}
</layout>`, { "default": font, bold })
layout.draw(pages[0], 0, pages[0].getHeight())
layoutPage2.draw(pages[1], 0, pages[1].getHeight())

View File

@@ -132,7 +132,7 @@ export async function pdfVerbrauchsausweisGewerbe(ausweis: VerbrauchsausweisGewe
}
// Kühlung
if (ausweis.wird_gekuehlt) {
if (aufnahme.kuehlung) {
addCheckMark(pages[0], 213, height - 362.5)
} else {
addCheckMark(pages[0], 355, height - 373.5)

View File

@@ -147,6 +147,35 @@ export async function pdfVerbrauchsausweisWohnen(ausweis: VerbrauchsausweisWohne
height: 50
})
const erneuerbareEnergienVerwendung = []
if (ausweis.alternative_heizung) {
erneuerbareEnergienVerwendung.push("Heizung")
}
if (ausweis.alternative_kuehlung) {
erneuerbareEnergienVerwendung.push("Kühlung")
}
if (ausweis.alternative_lueftung) {
erneuerbareEnergienVerwendung.push("Lüftung")
}
if (ausweis.alternative_warmwasser) {
erneuerbareEnergienVerwendung.push("Warmwasser")
}
pages[0].drawText(erneuerbareEnergienVerwendung.join(", "), {
x: 358,
y: height - 337,
size: 8
})
// Kühlung
if (aufnahme.kuehlung) {
addCheckMark(pages[0], 354, height - 376.5)
}
/* -------------------------------- Seite 2 -------------------------------- */
@@ -444,7 +473,7 @@ export async function pdfVerbrauchsausweisWohnen(ausweis: VerbrauchsausweisWohne
Math.round(berechnungen?.kuehlungsZuschlag || 0).toString(),
"0",
"0",
"1.8"
""
);
}

View File

@@ -4,6 +4,8 @@
import EmbeddedRegisterModule from "./EmbeddedRegisterModule.svelte"
export let onLogin: (response: Awaited<ReturnType<typeof loginClient>>) => any;
export let email: string = "";
export let password: string = "";
let route: "login" | "signup" = "login"
@@ -12,16 +14,16 @@
}
const loginData = {
email: "",
email,
passwort: "",
}
</script>
{#if route == "login"}
<EmbeddedLoginModule onLogin={onLogin} data={loginData} {navigate} />
<EmbeddedLoginModule onLogin={onLogin} bind:email bind:password {navigate} />
{:else}
<EmbeddedRegisterModule onRegister={(response) => {
loginData.email = response.email
<EmbeddedRegisterModule bind:email bind:password onRegister={(response) => {
email = response.email
navigate("login")
}} {navigate} />
{/if}

View File

@@ -3,13 +3,14 @@
import { loginClient } from "#lib/login.js";
export let navigate: (target: string) => void;
export let data: { email: string; passwort: string };
export let email: string;
export let password: string;
export let onLogin: (response: Awaited<ReturnType<typeof loginClient>>) => any;
async function login(e: SubmitEvent) {
e.preventDefault()
const response = await loginClient(data.email, data.passwort)
const response = await loginClient(email, password)
if (response === null) {
addNotification({
@@ -35,7 +36,7 @@
type="email"
placeholder="Email"
name="email"
bind:value={data.email}
bind:value={email}
required
/>
</div>
@@ -46,7 +47,7 @@
type="password"
placeholder="********"
name="passwort"
bind:value={data.passwort}
bind:value={password}
required
/>
</div>

View File

@@ -4,8 +4,8 @@
export let navigate: (target: string) => void;
export let onRegister: (response: { email: string, name: string, vorname: string }) => void;
let passwort: string;
let email: string;
export let password: string;
export let email: string;
let vorname: string;
let name: string;
@@ -14,7 +14,7 @@
try {
const response = await api.user.PUT.fetch({
email,
passwort,
passwort: password,
vorname,
name,
});
@@ -82,7 +82,7 @@
placeholder="********"
name="passwort"
class="px-2.5 py-1.5 rounded-lg border bg-gray-50"
bind:value={passwort}
bind:value={password}
required
/>
</div>

View File

@@ -10,13 +10,13 @@
import LoginDialog from "#components/LoginDialog.svelte";
import {
API_ACCESS_TOKEN_COOKIE_NAME,
AusweisTyp,
PRICES,
} from "#lib/constants.js";
import Cookies from "js-cookie";
import {
AufnahmeClient,
BenutzerClient,
BildClient,
getAusweisartFromUUID,
ObjektClient,
RechnungClient,
@@ -25,14 +25,20 @@
import { validateAccessTokenClient } from "src/client/lib/validateAccessToken.js";
import { api } from "astro-typesafe-api/client";
import PaymentOption from "#components/PaymentOption.svelte";
import Overlay from "#components/Overlay.svelte";
import EmbeddedAuthFlowModule from "./EmbeddedAuthFlowModule.svelte";
import { ausweisSpeichern } from "#client/lib/ausweisSpeichern.js";
import { addNotification } from "#components/Notifications/shared.js";
import NotificationWrapper from "#components/Notifications/NotificationWrapper.svelte";
export let user: BenutzerClient;
export let user: Partial<BenutzerClient>;
export let ausweis: VerbrauchsausweisWohnenClient;
export let aufnahme: AufnahmeClient;
export let objekt: ObjektClient;
export let bilder: BildClient[];
export let ausweisart: Enums.Ausweisart;
export let aktiveBezahlmethode: Bezahlmethoden = Enums.Bezahlmethoden.paypal;
export let ausweis_typ: AusweisTyp = AusweisTyp.Standard;
export let ausweistyp: Enums.AusweisTyp = Enums.AusweisTyp.Standard;
let rechnung: Partial<RechnungClient> = {
email: user.email,
@@ -81,7 +87,7 @@
let prices = PRICES[ausweisart];
let basePrice: number = prices[0];
let basePrice: number = prices[ausweistyp];
$: price =
basePrice +
@@ -135,9 +141,35 @@
}
async function bestellen() {
const ausweisart = getAusweisartFromUUID(
ausweis.uid
) as Enums.Ausweisart;
if (!form.checkValidity()) {
addNotification({
dismissable: true,
message: "Fehlende Daten.",
subtext: "Nicht alle notwendigen Felder sind ausgefüllt, bitte füllen sie diese aus bevor sie es erneut versuchen.."
})
form.reportValidity();
return;
}
if (!await validateAccessTokenClient()) {
loginAction = bestellen
rechnung = rechnung
loginOverlayHidden = false;
return
}
loginOverlayHidden = true
const result = await ausweisSpeichern(ausweis, objekt, aufnahme, bilder, ausweisart)
if (result === null) {
addNotification({
dismissable: true,
message: "Ups... Das hat nicht geklappt.",
subtext: "Der Ausweis konnte nicht gespeichert werden, bitte versuchen sie es erneut oder kontaktieren sie unseren Support."
})
}
try {
const { uid, checkout_url } = await api.rechnung.PUT.fetch(
@@ -158,7 +190,7 @@
versand_ort: rechnung.versand_ort,
telefon: rechnung.telefon,
ausweis_uid: ausweis.uid,
ausweis_typ,
ausweistyp,
},
{
headers: {
@@ -209,6 +241,10 @@
ausweisart === Enums.Ausweisart.GEGNachweisWohnen ||
ausweisart === Enums.Ausweisart.GEGNachweisBedarfsausweis ||
ausweisart === Enums.Ausweisart.GEGNachweisGewerbe;
let loginOverlayHidden = true;
let loginAction = () => {};
let form: HTMLFormElement;
</script>
<div
@@ -239,7 +275,7 @@
<h1 class="text-secondary text-3xl m-0">Energiesausweis erstellen</h1>
<h2 class="text-primary text-xl">
{ausweisart}
{prices[0]}
{prices[ausweistyp]}
</h2>
{#if gegAnfrage}
<Progressbar
@@ -252,7 +288,7 @@
</div>
</div>
<div id="formInput-2">
<form id="formInput-2" bind:this={form}>
<div id="formular-box" class="formular-boxen ring-0">
<Bereich
bereich="1"
@@ -262,7 +298,7 @@
>
<Bereich bereich="2" title="Rechnungsadresse">
<Rechnungsadresse bind:user bind:rechnung /></Bereich
<Rechnungsadresse bind:rechnung /></Bereich
>
{#if !gegAnfrage}
@@ -376,11 +412,12 @@ grid-cols-5 justify-around justify-items-center items-center"
<div></div>
<button class="button">Speichern</button>
<button class="button" type="button">Speichern</button>
{#if gegAnfrage}
<button
class="button cursor-pointer"
type="button"
data-cy="bestellen"
on:click={anfordern}>Angebot anfordern</button
>
@@ -388,12 +425,22 @@ grid-cols-5 justify-around justify-items-center items-center"
<button
class="button cursor-pointer"
data-cy="bestellen"
type="button"
on:click={bestellen}>Kostenpflichtig bestellen</button
>
{/if}
</div>
</div>
</div>
<Overlay bind:hidden={loginOverlayHidden}>
<div class="bg-white w-full max-w-screen-sm py-8">
<EmbeddedAuthFlowModule onLogin={loginAction} email={rechnung.email}></EmbeddedAuthFlowModule>
</div>
</Overlay>
</form>
<NotificationWrapper></NotificationWrapper>
<!--
<div class="bereich-box pr-12 mt-6">

View File

@@ -97,7 +97,7 @@
<form id="formInput-1" data-cy="ausweis" name="ausweis">
<div id="formular-box" class="formular-boxen ring-0">
<ButtonSpaeterHilfe {automatischAusfüllen} {spaeterWeitermachen} />
<ButtonSpaeterHilfe {automatischAusfüllen} />
<!-- A Prüfung der Ausweisart -->
@@ -201,7 +201,7 @@
</div>
<ButtonWeiterHilfe {spaeterWeitermachen}
<ButtonWeiterHilfe
bind:ausweis
bind:bilder
bind:user

View File

@@ -51,7 +51,6 @@
AufnahmeClient,
} from "#components/Ausweis/types.js";
import { Enums } from "@ibcornelsen/database/client";
import { onMount } from "svelte";
// TODO: Vom Server sollte ein volles Objekt kommen, dass alle Subobjekte enthält, weil es sonst zu Problemen führen kann
// wenn aufnahme oder objekt nicht existiert...
@@ -82,30 +81,6 @@
}
}
async function spaeterWeitermachen() {
// TODO FIX
// const result = await ausweisSpeichern(
// ausweis,
// objekt,
// aufnahme,
// bilder
// );
// if (result !== null) {
// // Falls der Nutzer zurück navigiert, sollte er wieder auf seinen Vorgang kommen.
// // Sonst müsste er alles neu eingeben...
// ausweis.uid = result.uid_ausweis;
// objekt.uid = result.uid_objekt;
// aufnahme.uid = result.uid_aufnahme;
// window.history.pushState(
// {},
// "",
// `${location.pathname}?uid=${result.uid_ausweis}`
// );
// speichernOverlayHidden = false;
// }
}
function automatischAusfüllen() {
aufnahme.baujahr_gebaeude = [1962];
aufnahme.baujahr_heizung = [1952];
@@ -139,30 +114,6 @@
ausweis = ausweis;
}
async function ausweisAbschicken(e: SubmitEvent) {
// if (e && e.preventDefault) e.preventDefault();
// const result = await ausweisSpeichern(
// ausweis,
// objekt,
// aufnahme,
// bilder
// );
// if (result !== null) {
// // Falls der Nutzer zurück navigiert, sollte er wieder auf seinen Vorgang kommen.
// // Sonst müsste er alles neu eingeben...
// ausweis.uid = result.uid_ausweis;
// objekt.uid = result.uid_objekt;
// aufnahme.uid = result.uid_aufnahme;
// window.history.pushState(
// {},
// "",
// `${location.pathname}?uid=${result.uid_ausweis}`
// );
// // window.location.href = `/kundendaten?uid=${result.uid_ausweis}`;
// }
}
let waitOverlayHidden = true;
let speichernOverlayHidden = true;
@@ -225,7 +176,7 @@ const ausweisart: Enums.Ausweisart = "VerbrauchsausweisWohnen"
<div id="progress-box" class="w-full box relative px-4 py-3 text-center order-2 self-stretch">
<h1 class="text-secondary text-3xl m-0">Energieausweis erstellen</h1>
<h2 class="text-primary text-xl">{ausweisart} {PRICES.VerbrauchsausweisWohnen[0]}</h2>
<h2 class="text-primary text-xl">{ausweisart} {PRICES[ausweisart][Enums.AusweisTyp.Standard]}</h2>
<Progressbar active={0}/>
</div>
@@ -233,10 +184,10 @@ const ausweisart: Enums.Ausweisart = "VerbrauchsausweisWohnen"
<form id="formInput-1" on:submit={ausweisAbschicken} name="ausweis" data-test="ausweis">
<div id="formInput-1" data-test="ausweis">
<div id="formular-box" class="formular-boxen ring-0">
<ButtonSpaeterHilfe {automatischAusfüllen} {spaeterWeitermachen} />
<ButtonSpaeterHilfe {automatischAusfüllen} />
<!-- A Prüfung der Ausweisart -->
@@ -346,7 +297,7 @@ const ausweisart: Enums.Ausweisart = "VerbrauchsausweisWohnen"
</form>
</div>
<RawNotificationWrapper class="fixed left-8 bottom-8 max-w-[400px] flex flex-col gap-4 z-50">
{#each Object.entries($notifications) as [uid, notification] (uid)}

View File

@@ -5,86 +5,49 @@ import { z } from "zod";
import isBase64 from "is-base64";
import { fileURLToPath } from "url";
import { writeFileSync } from "fs";
import { UUidWithPrefix } from "#components/Ausweis/types.js";
export const PUT = defineApiRoute({
input: BildSchema.pick({
kategorie: true,
}).merge(z.object({
data: z.string()
})),
output: z.object({
uid: z.string({ description: "Die UID des Bildes." })
}),
input: z.array(UUidWithPrefix),
output: z.void(),
middleware: authorizationMiddleware,
async fetch(input, ctx, user) {
const data = input.data;
if (!isBase64(data, { mimeRequired: true })) {
throw new APIError({
code: "BAD_REQUEST",
message: "Das Bild ist nicht base64.",
});
}
let aufnahme = await prisma.aufnahme.findUnique({
const aufnahme = await prisma.aufnahme.findUnique({
where: {
uid: ctx.params.uid,
benutzer_id: user.id
},
});
uid: ctx.params.uid
}
})
if (!aufnahme) {
throw new APIError({
code: "NOT_FOUND",
message: "Objekt nicht gefunden oder gehört einem anderen Benutzer.",
});
}
const dataWithoutPrefix = data.replace(
/^data:image\/\w+;base64,/,
""
);
const buffer = Buffer.from(dataWithoutPrefix, "base64");
const bild = await prisma.bild.create({
data: {
kategorie: input.kategorie,
aufnahme: {
connect: {
id: aufnahme.id,
},
},
},
select: {
uid: true,
},
});
const filePath = fileURLToPath(new URL(`../../../../../persistent/images/${bild.uid}.webp`, import.meta.url));
try {
// Wir optimieren das Bild und konvertieren es in WebP
// TODO: Sharp scheint nicht zu funktionieren, wir müssen das nochmal testen
// const optimizedBuffer = await sharp(buffer).webp({ quality: 80 }).toArray();
writeFileSync(filePath, buffer)
} catch(e) {
// Bild wurde nicht gespeichert, wir löschen den Eintrag wieder
await prisma.bild.delete({
where: {
uid: bild.uid
}
message: "Aufnahme existiert nicht oder gehört einem anderen Benutzer."
})
// Und geben einen Fehler zurück
throw new APIError({
code: "INTERNAL_SERVER_ERROR",
message: "Bild konnte nicht gespeichert werden.",
});
}
return {
uid: bild.uid
};
prisma.$transaction(async tx => {
for (const uid of input) {
const img = await tx.bild.update({
where: {
uid,
aufnahme_id: null
},
data: {
aufnahme_id: aufnahme.id
},
select: {
uid: true
}
})
if (!img) {
throw new APIError({
code: "NOT_FOUND",
message: "Bild existiert nicht oder gehört bereits zu einer anderen Aufnahme."
})
}
}
})
},
})
@@ -115,8 +78,8 @@ export const GET = defineApiRoute({
if (!aufnahme) {
throw new APIError({
code: "FORBIDDEN",
message: "Objekt existiert nicht oder gehört einem anderen Benutzer."
code: "NOT_FOUND",
message: "Aufnahme existiert nicht oder gehört einem anderen Benutzer."
})
}

View File

@@ -94,7 +94,7 @@ export const GET = defineApiRoute({
const refreshToken = encodeToken({
uid: user.uid,
typ: TokenType.Refresh,
exp: refreshTokenExpiry.unix(),
exp: refreshTokenExpiry.valueOf(),
});
// Und erstellen einen neuen
@@ -107,7 +107,7 @@ export const GET = defineApiRoute({
},
});
const accessTokenExpiry = moment().add(2, "days").unix();
const accessTokenExpiry = moment().add(2, "days").valueOf();
const accessToken = encodeToken({
uid: user.uid,
typ: TokenType.Access,
@@ -118,7 +118,7 @@ export const GET = defineApiRoute({
accessToken,
accessTokenExpiry: accessTokenExpiry,
refreshToken,
refreshTokenExpiry: refreshTokenExpiry.unix(),
refreshTokenExpiry: refreshTokenExpiry.valueOf(),
};
},
});

106
src/pages/api/bild.ts Normal file
View File

@@ -0,0 +1,106 @@
import { authorizationMiddleware, maybeAuthorizationMiddleware } from "#lib/middleware/authorization.js";
import { BildSchema } from "@ibcornelsen/database/client";
import { prisma } from "@ibcornelsen/database/server";
import { defineApiRoute, APIError } from "astro-typesafe-api/server";
import { z } from "astro:content";
import { fileURLToPath } from "url";
import isBase64 from "is-base64";
import { writeFileSync } from "fs"
import { UUidWithPrefix } from "#components/Ausweis/types.js";
export const PUT = defineApiRoute({
input: BildSchema.pick({
kategorie: true,
}).merge(z.object({
data: z.string()
})),
output: z.object({
uid: z.string({ description: "Die UID des Bildes." })
}),
async fetch(input) {
const data = input.data;
if (!isBase64(data, { mimeRequired: true })) {
throw new APIError({
code: "BAD_REQUEST",
message: "Das Bild ist nicht base64.",
});
}
const dataWithoutPrefix = data.replace(
/^data:image\/\w+;base64,/,
""
);
const buffer = Buffer.from(dataWithoutPrefix, "base64");
const bild = await prisma.bild.create({
data: {
kategorie: input.kategorie
},
select: {
uid: true,
},
});
const filePath = fileURLToPath(new URL(`../../../persistent/images/${bild.uid}.webp`, import.meta.url));
try {
// Wir optimieren das Bild und konvertieren es in WebP
// TODO: Sharp scheint nicht zu funktionieren, wir müssen das nochmal testen
// const optimizedBuffer = await sharp(buffer).webp({ quality: 80 }).toArray();
writeFileSync(filePath, buffer)
} catch(e) {
// Bild wurde nicht gespeichert, wir löschen den Eintrag wieder
await prisma.bild.delete({
where: {
uid: bild.uid
}
})
// Und geben einen Fehler zurück
throw new APIError({
code: "INTERNAL_SERVER_ERROR",
message: "Bild konnte nicht gespeichert werden.",
});
}
return {
uid: bild.uid
};
},
})
export const DELETE = defineApiRoute({
input: z.object({
uid: UUidWithPrefix
}),
middleware: maybeAuthorizationMiddleware,
async fetch(input, context, user) {
try {
if (user) {
await prisma.bild.delete({
where: {
uid: input.uid,
aufnahme: {
benutzer: {
id: user.id
}
}
}
})
} else {
await prisma.bild.delete({
where: {
uid: input.uid,
aufnahme_id: null
}
})
}
} catch(e) {
throw new APIError({
code: "INTERNAL_SERVER_ERROR",
message: "Bild konnte nicht gelöscht werden."
})
}
},
})

View File

@@ -9,7 +9,7 @@ import {
} from "#lib/middleware/authorization.js";
import { UUidWithPrefix } from "#components/Ausweis/types.js";
import { getPrismaAusweisAdapter } from "#lib/server/ausweis.js";
import { AusweisTyp, PRICES, SERVICES } from "#lib/constants.js";
import { PRICES, SERVICES } from "#lib/constants.js";
export const PUT = defineApiRoute({
meta: {
@@ -23,7 +23,7 @@ export const PUT = defineApiRoute({
.object({
ausweisart: z.nativeEnum(Enums.Ausweisart),
ausweis_uid: UUidWithPrefix,
ausweis_typ: z.nativeEnum(AusweisTyp)
ausweistyp: z.nativeEnum(Enums.AusweisTyp)
})
.merge(
RechnungSchema.omit({
@@ -49,8 +49,8 @@ export const PUT = defineApiRoute({
// Wir erstellen eine Mollie Payment Referenz und eine neue Rechnung in unserer Datenbank, daraufhin geben
// wir eine Checkout URL zurück auf die der Nutzer weitergeleitet werden kann.
const { ausweis_uid, ausweisart, bezahlmethode, services, ausweis_typ } = input;
let betrag = PRICES[ausweisart][ausweis_typ]
const { ausweis_uid, ausweisart, bezahlmethode, services, ausweistyp } = input;
let betrag = PRICES[ausweisart][ausweistyp]
const servicePriceList = SERVICES[ausweisart]
for (const service of input.services) {
@@ -94,6 +94,8 @@ export const PUT = defineApiRoute({
bezahlmethode: bezahlmethode,
status: Enums.Rechnungsstatus.open,
aufnahme_id: ausweis.aufnahme_id,
services,
ausweistyp
},
select: {
uid: true,

View File

@@ -16,7 +16,9 @@ export const PUT = defineApiRoute({
tags: ["Verbrauchsausweis Wohnen"],
},
input: z.object({
ausweis: VerbrauchsausweisWohnenSchema.omit({
ausweis: VerbrauchsausweisWohnenSchema.merge(z.object({
startdatum: z.coerce.date()
})).omit({
id: true,
benutzer_id: true,
uid: true,

View File

@@ -1,29 +1,11 @@
import { API_ACCESS_TOKEN_COOKIE_NAME } from "#lib/constants.js";
import { validateAccessTokenServer } from "#server/lib/validateAccessToken.js";
import { prisma } from "@ibcornelsen/database/server";
import { APIRoute } from "astro";
import { APIError, defineApiRoute } from "astro-typesafe-api/server";
import * as fs from "fs";
import { fileURLToPath } from "url";
export const GET: APIRoute = async (Astro) => {
const { uid } = Astro.params
const token = Astro.cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)?.value;
if (!token) {
return new Response(null, {
status: 400
})
}
const valid = validateAccessTokenServer(Astro);
if (!valid) {
return new Response(null, {
status: 401
})
}
const image = await prisma.bild.findUnique({
where: {
@@ -38,6 +20,13 @@ export const GET: APIRoute = async (Astro) => {
}
const path = fileURLToPath(new URL(`../../../persistent/images/${image.uid}.webp`, import.meta.url))
if (!fs.existsSync(path)) {
return new Response(null, {
status: 404
})
}
const buffer = fs.readFileSync(path)
return new Response(buffer, {

View File

@@ -3,98 +3,37 @@
import KundendatenModule from "#modules/KundendatenModule.svelte";
import AusweisLayout from "#layouts/AusweisLayoutPruefung.astro";
import { Enums } from "@ibcornelsen/database/client";
import { createCaller } from "../astro-typesafe-api-caller";
import { API_ACCESS_TOKEN_COOKIE_NAME, API_REFRESH_TOKEN_COOKIE_NAME } from "#lib/constants";
import { validateAccessTokenServer } from "#server/lib/validateAccessToken";
import { BedarfsausweisWohnenClient, getAusweisartFromUUID, VerbrauchsausweisGewerbeClient, VerbrauchsausweisWohnenClient } from "#components/Ausweis/types";
import { getCurrentUser } from "#lib/server/user";
// Man sollte nur auf diese Seite kommen, wenn ein Ausweis bereits vorliegt und in der Datenbank abgespeichert wurde.
const uid = Astro.url.searchParams.get("uid");
const valid = await validateAccessTokenServer(Astro)
if (!uid || !valid) {
return Astro.redirect("/404");
}
const user = await getCurrentUser(Astro) || {}
const params = new URLSearchParams(await Astro.request.text());
const caller = createCaller(Astro)
const ausweisart = getAusweisartFromUUID(uid);
let ausweis: VerbrauchsausweisWohnenClient | VerbrauchsausweisGewerbeClient | BedarfsausweisWohnenClient;
if (ausweisart === Enums.Ausweisart.VerbrauchsausweisWohnen) {
ausweis = await caller["verbrauchsausweis-wohnen"]._uid.GET.fetch(undefined, {
headers: {
Authorization: `Bearer ${Astro.cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)?.value}`
},
params: {
uid
}
})
} else if (ausweisart === Enums.Ausweisart.VerbrauchsausweisGewerbe) {
ausweis = await caller["verbrauchsausweis-gewerbe"]._uid.GET.fetch(undefined, {
headers: {
Authorization: `Bearer ${Astro.cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)?.value}`
},
params: {
uid
}
})
} else if (ausweisart === Enums.Ausweisart.BedarfsausweisWohnen) {
ausweis = await caller["bedarfsausweis-wohnen"]._uid.GET.fetch(undefined, {
headers: {
Authorization: `Bearer ${Astro.cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)?.value}`
},
params: {
uid
}
})
} else if (ausweisart === Enums.Ausweisart.GEGNachweisWohnen) {
ausweis = await caller["geg-nachweis-wohnen"]._uid.GET.fetch(undefined, {
headers: {
Authorization: `Bearer ${Astro.cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)?.value}`
},
params: {
uid
}
})
} else {
if (!params.has("ausweis") || !params.has("aufnahme") || !params.has("objekt") || !params.has("bilder") || !params.has("ausweisart")) {
return Astro.redirect("/404")
}
const aufnahme = await caller.aufnahme._uid.GET.fetch(undefined, {
headers: {
Authorization: `Bearer ${Astro.cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)?.value}`
},
params: {
uid: ausweis.uid_aufnahme
let ausweis, aufnahme, objekt, ausweisart, bilder, ausweistyp;
try {
ausweis = JSON.parse(params.get("ausweis") || "")
aufnahme = JSON.parse(params.get("aufnahme") || "")
objekt = JSON.parse(params.get("objekt") || "")
ausweisart = JSON.parse(params.get("ausweisart") || "") as Enums.Ausweisart;
bilder = JSON.parse(params.get("bilder") || "");
ausweistyp = JSON.parse(params.get("ausweistyp") || "") as Enums.AusweisTyp;
if (!ausweisart || !Object.keys(Enums.Ausweisart).includes(ausweisart) || !ausweistyp || !Object.keys(Enums.AusweisTyp).includes(ausweistyp)) {
throw new Error()
}
})
const objekt = await caller.objekt._uid.GET.fetch(undefined, {
headers: {
Authorization: `Bearer ${Astro.cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)?.value}`
},
params: {
uid: aufnahme.uid_objekt
}
})
const user = await caller.user.self.GET.fetch(undefined, {
headers: {
Authorization: `Bearer ${Astro.cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)?.value}`
}
});
aufnahme.ausweisart = "VerbrauchsausweisWohnen"
if (!ausweis || !user) {
return Astro.redirect("/404");
} catch(e){
return Astro.redirect("/404")
}
---
<AusweisLayout title="Kundendaten Aufnehmen - IBCornelsen">
<KundendatenModule {user} {ausweis} {objekt} {aufnahme} {ausweisart} bezahlmethode={Enums.Bezahlmethoden.paypal} client:load></KundendatenModule>
<KundendatenModule {user} {ausweis} {objekt} {aufnahme} {bilder} {ausweisart} {ausweistyp} aktiveBezahlmethode={Enums.Bezahlmethoden.paypal} client:load></KundendatenModule>
</AusweisLayout>