15 changed files with 1430 additions and 29 deletions
@ -0,0 +1,105 @@ |
|||
:root { |
|||
--bg-color: hsl(0.0, 0.0%, 90.2%); |
|||
--primary: hsl(82.3, 54.4%, 44.7%); |
|||
--secondary: hsl(194.5, 100.0%, 26.9%); |
|||
--gray: hsl(37.5, 25.0%, 93.7%); |
|||
--container: hsl(37.5, 50.0%, 96.9%); |
|||
} |
|||
|
|||
body { |
|||
background: var(--bg-color) !important; |
|||
} |
|||
|
|||
.stepper { |
|||
font-family: sans-serif; |
|||
background: var(--bg-color); |
|||
& > div { |
|||
h2 { |
|||
font-size: 2.3rem; |
|||
color: var(--secondary); |
|||
text-align: center; |
|||
} |
|||
.radio-grid { |
|||
display: grid; |
|||
grid-gap: 2rem; |
|||
grid-template-columns: repeat(3, 1fr); |
|||
margin-top: 1rem; |
|||
label { |
|||
height: 20rem; |
|||
position: relative; |
|||
input { |
|||
visibility: hidden; |
|||
z-index: -99; |
|||
position: absolute; |
|||
} |
|||
input + span { |
|||
border: 1px var(--primary) solid; |
|||
border-radius: 2rem; |
|||
overflow: hidden; |
|||
cursor: pointer; |
|||
background: white; |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
width: 100%; |
|||
height: 100%; |
|||
span:first-child { |
|||
display: flex; |
|||
flex: 1 0 0; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
span:nth-child(2) { |
|||
flex: 0 0 4rem; |
|||
background: var(--gray); |
|||
display: flex; |
|||
width: 100%; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
} |
|||
input:checked + span { |
|||
border-color: var(--secondary); |
|||
} |
|||
svg { |
|||
color: var(--secondary); |
|||
fill: currentColor; |
|||
width: 8rem; |
|||
height: 8rem; |
|||
} |
|||
} |
|||
} |
|||
.form-container { |
|||
background: var(--container); |
|||
border-radius: 2rem; |
|||
padding: 3rem; |
|||
display: grid; |
|||
grid-gap: 1rem; |
|||
.separator { |
|||
background: white; |
|||
height: 5px; |
|||
} |
|||
.form-grid { |
|||
display: grid; |
|||
grid-gap: 0.5rem; |
|||
grid-template-columns: 2fr 3fr 30px; |
|||
align-items: center; |
|||
input[type="text"] { |
|||
font-weight: bold; |
|||
border: var(--primary) 1px solid; |
|||
text-align: right; |
|||
} |
|||
.unit { |
|||
text-align: right; |
|||
} |
|||
.comment { |
|||
grid-column: 1 / -1; |
|||
font-size: 1.5rem; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,86 @@ |
|||
import 'alpinejs'; |
|||
import wNumb from 'wnumb'; |
|||
|
|||
var units = { |
|||
currency: wNumb({ |
|||
mark: ',', |
|||
thousand: '.', |
|||
prefix: '', |
|||
suffix: '', |
|||
decimals: 2 |
|||
}), |
|||
percent: wNumb({ |
|||
mark: ',', |
|||
thousand: '', |
|||
prefix: '', |
|||
suffix: ' %', |
|||
decimals: 2 |
|||
}) |
|||
}; |
|||
|
|||
|
|||
window.stepper = { |
|||
parts: { |
|||
notar: 0.02, |
|||
grundsteuer: 0.065, |
|||
makler: 0.0357 |
|||
}, |
|||
units: units, |
|||
step: 2, |
|||
greetings: [ |
|||
{ value: 'frau', label: 'Frau' }, |
|||
{ value: 'herr', label: 'Herr' }, |
|||
{ value: 'divers', label: 'Divers' }, |
|||
], |
|||
jobs: [ |
|||
{ value: 'angestellt', label: 'Angestellte*r' }, |
|||
], |
|||
titles: [ |
|||
{ value: 'prof', label: 'Prof' }, |
|||
{ value: 'dr', label: 'Dr' }, |
|||
], |
|||
value: { |
|||
kind: 'kauf', |
|||
kauf: { |
|||
kaufpreis: 300000, |
|||
modernisierung: 0, |
|||
baukosten: 0, |
|||
eigenkapital: 0, |
|||
}, |
|||
bau: { |
|||
grundstueckspreis: 300000, |
|||
bezahlt: '0', |
|||
baukosten: 0, |
|||
eigenkapital: 0, |
|||
}, |
|||
anschluss: { |
|||
objektwert: 300000, |
|||
umschuldung: 50000, |
|||
zuskap: 50000 |
|||
}, |
|||
wert: 300000, |
|||
umschuldung: 0, |
|||
kapital_zus: 0, |
|||
greeting: null, |
|||
title: null, |
|||
firstname: '', |
|||
lastname: '', |
|||
zip: '', |
|||
location: '', |
|||
phone: '', |
|||
email: '', |
|||
job: '', |
|||
haushalt: '', |
|||
einnahme: '' |
|||
}, |
|||
kinds: [ |
|||
{label: 'Kauf einer Immobilie', value: 'kauf', icon: 'home'}, |
|||
{label: 'Eigenes Bauvorhaben', value: 'bau', icon: 'home'}, |
|||
{label: 'Anschlussfinanzierung', value: 'anschluss', icon: 'home'} |
|||
], |
|||
|
|||
/* Methods */ |
|||
svg(icon) { |
|||
return `<svg><use xlink:href="${this.sprite}#${icon}"></use></svg>`; |
|||
} |
|||
}; |
|||
|
After Width: | Height: | Size: 962 B |
@ -1,12 +1,19 @@ |
|||
{ |
|||
"name": "philipp/stepper", |
|||
"require": { |
|||
"twig/twig": "^3.1" |
|||
"twig/twig": "^3.1", |
|||
"symfony/var-dumper": "^5.1", |
|||
"illuminate/support": "^8.12" |
|||
}, |
|||
"authors": [ |
|||
{ |
|||
"name": "Philipp Lang", |
|||
"email": "philipp@zoomyboy.de" |
|||
} |
|||
] |
|||
], |
|||
"autoload": { |
|||
"psr-4": { |
|||
"Zoomyboy\\Stepper\\": "./src" |
|||
} |
|||
} |
|||
} |
|||
|
|||
File diff suppressed because it is too large
@ -0,0 +1,53 @@ |
|||
<?php |
|||
|
|||
namespace Zoomyboy\Stepper; |
|||
|
|||
use Twig\Loader\FilesystemLoader; |
|||
use Twig\Environment; |
|||
use Twig\TwigFilter as Filter; |
|||
use Twig\TwigFunction as Func; |
|||
|
|||
class Stepper { |
|||
|
|||
private $twig; |
|||
public $url; |
|||
|
|||
public function __construct() { |
|||
$loader = new FilesystemLoader(__DIR__.'/../views'); |
|||
|
|||
$this->twig = new Environment($loader); |
|||
|
|||
$this->twig->addFilter(new Filter('svg', [$this, 'svgTag'])); |
|||
$this->twig->addFilter(new Filter('prop', [$this, 'alpineProp'])); |
|||
$this->twig->addFunction(new Func('cprop', function ($aprop, $modifier) { |
|||
return $this->alpineProp($aprop, $modifier); |
|||
})); |
|||
} |
|||
|
|||
public function init() { |
|||
add_action('wp_enqueue_scripts', [ $this, 'enqueue' ]); |
|||
add_shortcode('stepper', [ $this, 'handle' ]); |
|||
} |
|||
|
|||
public function enqueue() { |
|||
wp_enqueue_script('stepper-js', $this->url.'assets/public/app.js'); |
|||
wp_enqueue_style('stepper-css', $this->url.'assets/public/app.css'); |
|||
} |
|||
|
|||
public function handle() { |
|||
echo $this->twig->render('stepper.twig.htm', [ |
|||
'sprite' => $this->url.'assets/public/sprite.svg' |
|||
]); |
|||
} |
|||
|
|||
public function svgTag($e, $class = '') { |
|||
return '<svg class="'.$class.'"><use xlink:href="'.$sprite.'#'.$e.'"></use></svg>'; |
|||
} |
|||
|
|||
public function alpineProp($prop, $modifier) { |
|||
return ':value="units.'.$modifier.'.to('.$prop.')" |
|||
@focus="$event.target.value = '.$prop.'" |
|||
@change="'.$prop.' = units.'.$modifier.'.from($event.target.value)"'; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,76 @@ |
|||
<div x-show="step == 2"> |
|||
<h2 class="gradient">Geben Sie Ihre persönlichen Daten ein</h2> |
|||
|
|||
<div class="form-container"> |
|||
<div class="field-grid"> |
|||
|
|||
<label for="greeting"> |
|||
<span>Anrede</span> |
|||
<select x-model="value.greeting" id="greeting"> |
|||
<template x-for="greeting in greetings"> |
|||
<option :value="greeting.value" x-text="greeting.label"></option> |
|||
</template> |
|||
</select> |
|||
</label> |
|||
|
|||
<label for="title"> |
|||
<span>Titel</span> |
|||
<select x-model="value.title" id="title"> |
|||
<template x-for="title in titles"> |
|||
<option :value="title.value" x-text="title.label"></option> |
|||
</template> |
|||
</select> |
|||
</label> |
|||
|
|||
<label for="firstname"> |
|||
<span>Vorname</span> |
|||
<input type="text" x-model="value.firstname" id="firstname"> |
|||
</label> |
|||
|
|||
<label for="lastname"> |
|||
<span>Nachname</span> |
|||
<input type="text" x-model="value.lastname" id="lastname"> |
|||
</label> |
|||
|
|||
<label for="zip"> |
|||
<span>PLZ</span> |
|||
<input type="text" x-model="value.zip" id="zip"> |
|||
</label> |
|||
|
|||
<label for="location"> |
|||
<span>Ort</span> |
|||
<input type="text" x-model="value.location" id="location"> |
|||
</label> |
|||
|
|||
<label for="phone"> |
|||
<span>Telefon</span> |
|||
<input type="tel" x-model="value.phone" id="phone"> |
|||
</label> |
|||
|
|||
<label for="email"> |
|||
<span>E-Mail-Adresse</span> |
|||
<input type="email" x-model="value.email" id="email"> |
|||
</label> |
|||
|
|||
<label for="job"> |
|||
<span>Beruf des Hauptverdieners</span> |
|||
<select x-model="value.job" id="job"> |
|||
<template x-for="job in jobs"> |
|||
<option :value="job.value" x-text="job.label"></option> |
|||
</template> |
|||
</select> |
|||
</label> |
|||
|
|||
<label for="haushalt"> |
|||
<span>Haushaltsnetto monatlich</span> |
|||
<input type="text" x-model="value.haushalt" id="haushalt"> |
|||
</label> |
|||
|
|||
<label for="einnahme"> |
|||
<span>Netto-Mieteinnahme monatlich</span> |
|||
<input type="text" x-model="value.einnahme" id="einnahme"> |
|||
</label> |
|||
|
|||
</div> |
|||
</div> |
|||
</div> |
|||
@ -0,0 +1,68 @@ |
|||
<form> |
|||
<div class="stepper" x-data="{ ...stepper, sprite: '{{sprite|raw}}' }"> |
|||
<div x-show="step == 0"> |
|||
<h2>Was möchten Sie finanzieren</h2> |
|||
<div class="radio-grid"> |
|||
<template x-for="ckind in kinds"> |
|||
<label> |
|||
<input @change="step++" type="radio" name="kind" :value="ckind.value" x-model="value.kind"> |
|||
<span> |
|||
<span x-html="svg(ckind.icon)"></span> |
|||
<span x-text="ckind.label"></span> |
|||
</span> |
|||
</label> |
|||
</template> |
|||
</div> |
|||
</div> |
|||
|
|||
{% include 'vorhaben.twig.htm' with { |
|||
'kind': "bau", |
|||
bezahlt: true, |
|||
second: {label: 'Baukosten', value: 'baukosten'}, |
|||
main: {label: 'Grundstückspreis', value: 'grundstueckspreis'} |
|||
} %} |
|||
|
|||
{% include 'vorhaben.twig.htm' with { |
|||
'kind': "kauf", |
|||
bezahlt: false, |
|||
second: {label: 'Modernisierung', value: 'modernisierung'}, |
|||
main: {label: 'Kaufpreis', value: 'kaufpreis'} |
|||
} %} |
|||
|
|||
{% include 'daten.twig.htm' %} |
|||
|
|||
<div x-show="step == 1 && kind == 'anschluss'"> |
|||
<h2 class="gradient">Das Vorhaben in Zahlen</h2> |
|||
|
|||
<div class="form-container"> |
|||
<div class="form-grid"> |
|||
<label>Objektwert</label> |
|||
<input type="text" {{cprop('value.anschluss.objektwert', 'currency') | raw}}> |
|||
<span class="unit">€</span> |
|||
</div> |
|||
<div class="separator"></div> |
|||
<div class="form-grid"> |
|||
<label>Umschuldung</label> |
|||
<input type="text" {{cprop('value.anschluss.umschuldung', 'currency') | raw}}> |
|||
<span class="unit">€</span> |
|||
<span class="comment">Bitte geben Sie hier die Summe der gleichzeitig umzuschuldenden Darlehen an.</span> |
|||
</div> |
|||
<div class="separator"></div> |
|||
<div class="form-grid"> |
|||
<label>Zusätzl. Kapital</label> |
|||
<input type="text" {{cprop('value.anschluss.zuskap', 'currency') | raw}}> |
|||
<span class="unit">€</span> |
|||
</div> |
|||
<div class="separator primary"></div> |
|||
<div> |
|||
<div>Darlehensbetrag</div> |
|||
<div x-text="units.currency.to(value.anschluss.objektwert + value.anschluss.umschuldung + value.anschluss.zuskap)"></div> |
|||
<span class="comment">Darlehensbeträge werden auf volle 1.000 Euro gerundet.</span> |
|||
</div> |
|||
<div class="separator gray"></div> |
|||
<button type="button" value="Werte übernehmen und weiter zum letzten Schritt"></button> |
|||
</div> |
|||
</div> |
|||
|
|||
</div> |
|||
</form> |
|||
@ -0,0 +1,65 @@ |
|||
<div x-show="step == 1 && value.kind == '{{kind}}'"> |
|||
<h2 class="gradient">Ihr Bauvorhaben</h2> |
|||
|
|||
<div class="form-container"> |
|||
<div class="form-grid"> |
|||
<label>{{main.label}}</label> |
|||
<input type="text" {{cprop('value.' ~ kind ~ '.' ~ main.value, 'currency') | raw}}> |
|||
<span class="unit">€</span> |
|||
</div> |
|||
{% if bezahlt %} |
|||
<div class="form-grid"> |
|||
<label>Bereits bezahlt?</label> |
|||
<select name="bezahlt" x-model="value.{{kind}}.bezahlt"> |
|||
<option value="1">Ja</option> |
|||
<option value="0">Nein</option> |
|||
</select> |
|||
</div> |
|||
{% endif %} |
|||
<div class="separator"></div> |
|||
<div class="form-grid"> |
|||
<label>{{second.label}}</label> |
|||
<input type="text" {{cprop('value.' ~ kind ~ '.' ~ second.value, 'currency') | raw}}> |
|||
<span class="unit">€</span> |
|||
</div> |
|||
<div class="separator"></div> |
|||
<div class="form-grid"> |
|||
<label>Notar- & Gerichtskosten</label> |
|||
<span> |
|||
<span class="result-label" x-text="units.percent.to(parts.notar * 100)"></span> |
|||
<span class="result" x-text="units.currency.to(value.{{kind}}.{{main.value}} * parts.notar)"></span> |
|||
</span> |
|||
<span class="unit">€</span> |
|||
</div> |
|||
<div class="separator primary"></div> |
|||
<div class="form-grid"> |
|||
<label>Grunderwerbsteuer</label> |
|||
<span> |
|||
<span class="result-label" x-text="units.percent.to(parts.grundsteuer * 100)"></span> |
|||
<span class="result" x-text="units.currency.to(value.{{kind}}.{{main.value}} * parts.grundsteuer)"></span> |
|||
</span> |
|||
<span class="unit">€</span> |
|||
</div> |
|||
<div class="separator"></div> |
|||
<div class="form-grid"> |
|||
<label>Makler <span class="result-label" x-text="units.percent.to(parts.makler * 100)"></span></label> |
|||
<input type="text" :value="units.currency.to(value.{{kind}}.{{main.value}} * parts.makler)"> |
|||
<span class="unit">€</span> |
|||
<span class="comment">Die vorgeschlagene Maklercourtage gentspricht der regional üblichen Höhe. Sie können diese jedoch bei Bedarf verändern oder auch komplett entfallen lassen.</span> |
|||
</div> |
|||
<div class="separator"></div> |
|||
<div class="form-grid"> |
|||
<label>Eigenkapital</label> |
|||
<input type="text" {{cprop('value.' ~ kind ~ '.eigenkapital', 'currency') | raw}}> |
|||
<span class="unit">€</span> |
|||
</div> |
|||
<div class="separator primary"></div> |
|||
<div> |
|||
<div><span>=</span>Darlehensbetrag</div> |
|||
<div x-text="units.currency.to(value.{{kind}}.{{main.value}} * (1+parts.notar+parts.grundsteuer+parts.makler) + value.{{kind}}.{{second.value}} - value.{{kind}}.eigenkapital)"></div> |
|||
<span class="comment">Darlehensbeträge werden auf volle 1.000 Euro gerundet.</span> |
|||
</div> |
|||
<div class="separator gray"></div> |
|||
<button @click="step++" type="button">Werte übernehmen und weiter zum letzten Schritt</button> |
|||
</div> |
|||
</div> |
|||
Loading…
Reference in new issue