Shop P1a: Du-Form im Portal klein (du, dich, dein) wie im übrigen Kundenportal

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-05 09:17:09 +00:00
1 parent 17ad5cbcdc
commit 16a7f05aa2
10 files changed
+34 -34

No files matched your search

+1 -1
View File
@@ -252,7 +252,7 @@ function shopTile(projects, features) {
<article class="fn-tile" data-fn-tile="shop">
<div class="fn-tile-head">${svg('shop')}<span class="fn-pill fn-pill-${pill[1]}">${pill[0]}</span></div>
<h3>Webshop</h3>
<p>Dein eigener Online-Shop auf Deiner Website. Zahlungen gehen direkt auf Dein Stripe-Konto. ${esc(preis)} einmalig.</p>
<p>Dein eigener Online-Shop auf deiner Website. Zahlungen gehen direkt auf dein Stripe-Konto. ${esc(preis)} einmalig.</p>
${extra}
${count}
${action}
+10 -10
View File
@@ -18,7 +18,7 @@ export function anfrageZeilen(anfrage) {
const a = anfrage && typeof anfrage === 'object' ? anfrage : {}
const wege = [a.versand === true ? 'Versand' : '', a.abholung === true ? 'Abholung' : ''].filter(Boolean).join(' und ')
return [
['Was Du verkaufst', labelVon(WARENARTEN, a.warenart)],
['Was du verkaufst', labelVon(WARENARTEN, a.warenart)],
['Beschreibung', text(a.beschreibung)],
['Artikel', labelVon(ARTIKELZAHLEN, a.artikelzahl)],
['Lieferung', wege],
@@ -57,11 +57,11 @@ export function renderAnfrageFormular(root, { projectId, vorgabe = null, preis =
const absenden = h('button', { type: 'submit', class: 'kb-btn-primary', 'data-aktion': 'anfrage-senden' }, 'Anfrage senden')
const form = h('form', { class: 'sh-form', novalidate: true, 'aria-label': 'Webshop anfragen' },
feld('warenart', 'Was möchtest Du verkaufen?', warenart),
feld('beschreibung', 'Beschreibe kurz, was Du verkaufst', beschreibung, 'Zum Beispiel: handgemachte Kerzen, Bio-Honig oder Fanartikel eines Vereins.'),
feld('artikelzahl', 'Wie viele Artikel möchtest Du etwa anbieten?', artikelzahl),
feld('warenart', 'Was möchtest du verkaufen?', warenart),
feld('beschreibung', 'Beschreibe kurz, was du verkaufst', beschreibung, 'Zum Beispiel: handgemachte Kerzen, Bio-Honig oder Fanartikel eines Vereins.'),
feld('artikelzahl', 'Wie viele Artikel möchtest du etwa anbieten?', artikelzahl),
h('fieldset', { class: 'sh-gruppe' },
h('legend', null, 'Wie bekommen Deine Kunden die Ware?'),
h('legend', null, 'Wie bekommen deine Kunden die Ware?'),
hakenFeld('versand', versand, 'Versand'),
hakenFeld('abholung', abholung, 'Abholung')),
hakenFeld('unternehmer', unternehmer, 'Ich verkaufe als Unternehmer (nicht privat).'),
@@ -110,13 +110,13 @@ export function renderAnfrageFormular(root, { projectId, vorgabe = null, preis =
const erklaerung = h('div', { class: 'sh-erklaerung' },
h('h3', null, 'Webshop anfragen'),
h('p', null, 'Mit dem Webshop verkaufst Du direkt auf Deiner Website: Deine Kunden legen Artikel in den Warenkorb und bezahlen online.'),
h('p', null, 'Mit dem Webshop verkaufst du direkt auf deiner Website: deine Kunden legen Artikel in den Warenkorb und bezahlen online.'),
h('ul', null,
h('li', null, `${preis} einmalig für den Einbau in Deine Website, keine laufende Gebühr von WEBklar.`),
h('li', null, 'Die Zahlungen gehen direkt auf Dein eigenes Stripe-Konto. Die Gebühren von Stripe zahlst Du direkt an Stripe.'),
h('li', null, `${preis} einmalig für den Einbau in deine Website, keine laufende Gebühr von WEBklar.`),
h('li', null, 'Die Zahlungen gehen direkt auf dein eigenes Stripe-Konto. Die Gebühren von Stripe zahlst du direkt an Stripe.'),
h('li', null, 'Deine Kunden zahlen mit Karte, Apple Pay, Google Pay, Klarna oder SEPA-Lastschrift.'),
h('li', null, 'PayPal ist über Stripe leider nicht möglich.'),
h('li', null, 'Wir sehen uns Deine Anfrage kurz an. Erst nach unserer Zusage kannst Du bestellen.')),
h('li', null, 'Wir sehen uns deine Anfrage kurz an. Erst nach unserer Zusage kannst du bestellen.')),
ustHinweis ? h('p', { class: 'sh-hinweis' }, ustHinweis) : null)
const karte = h('section', { class: 'kb-panel sh-anfrage' }, erklaerung, form)
@@ -158,7 +158,7 @@ export function anfrageKarte(ansicht, { projectId, neu, neuLaden } = {}) {
h('span', { class: `sh-pille ${offen ? 'sh-pille-wait' : 'sh-pille-bad'}` }, offen ? 'In Prüfung' : 'Abgelehnt'),
h('h3', null, offen ? 'Deine Anfrage wird geprüft' : 'Deine Anfrage wurde leider abgelehnt'),
offen
? h('p', null, `${datum ? `Du hast Deine Anfrage am ${datum} Uhr gesendet. ` : ''}Wir melden uns, sobald wir sie angesehen haben. Bis dahin kannst Du sie zurückziehen und eine neue stellen.`)
? h('p', null, `${datum ? `Du hast deine Anfrage am ${datum} Uhr gesendet. ` : ''}Wir melden uns, sobald wir sie angesehen haben. Bis dahin kannst du sie zurückziehen und eine neue stellen.`)
: h('p', null, 'Unsere Begründung:'),
!offen ? h('blockquote', { class: 'sh-begruendung' }, text(ansicht?.begruendung) || 'Dazu liegt keine Begründung vor.') : null,
ansicht?.anfrage ? h('div', { class: 'sh-zusammenfassung' }, h('h4', null, 'Deine Angaben'), anfrageListe(ansicht.anfrage)) : null,
+5 -5
View File
@@ -5,7 +5,7 @@
zurueckziehen“; abgelehnt: Begruendung und „Neue Anfrage stellen“; angenommen: die Bestellseite (funktion-bestellen.js, key 'shop');
- state ordered: Statuskarte mit „Zahlung fortsetzen“ (funktion-bestellen.js);
- state setup | active | disabled: die Reiter Uebersicht, Artikel, Bestellungen, Einstellungen, Rechtliches (lazy geladen, siehe REITER);
setup mit Hinweis „Wir bauen Deinen Shop gerade ein …“, disabled mit Banner der Abschaltung und nur zum Lesen (ctx.nurLesen).
setup mit Hinweis „Wir bauen deinen Shop gerade ein …“, disabled mit Banner der Abschaltung und nur zum Lesen (ctx.nurLesen).
Rueckkehr: #webshop?bestellt=1&projekt=<id> (nach der Zahlung, „Danke!“) und #webshop?stripe=zurueck|neu&projekt=<id> (vom Onboarding bei
Stripe) werden einmal gelesen und aus der Adresse genommen.
Die Reiter sind Module mit render(panel, ctx) => void | Promise; ctx = { projectId, status, nurLesen, stripeRueckkehr, aktuell(), neuLaden(),
@@ -26,7 +26,7 @@ export const REITER = [
{ key: 'rechtliches', label: 'Rechtliches', laden: () => import('./shop-tab-rechtliches.js') },
]
const HINWEIS_SETUP = 'Wir bauen Deinen Shop gerade ein. Du kannst schon Artikel, Stripe und Rechtstexte vorbereiten.'
const HINWEIS_SETUP = 'Wir bauen deinen Shop gerade ein. Du kannst schon Artikel, Stripe und Rechtstexte vorbereiten.'
const HINWEIS_ABGESCHALTET = 'Dein Webshop ist abgeschaltet. Du kannst Bestellungen noch ansehen und exportieren.'
const DANKE_SETUP = 'Danke! Die Zahlung ist bei uns angekommen.'
const NICHT_VERFUEGBAR = 'Der Webshop ist noch nicht verfügbar.'
@@ -81,7 +81,7 @@ async function starte(root, projectId) {
}
if (S !== s) return
if (!s.funktionen?.shop?.verfuegbar) {
root.replaceChildren(infoKarte(NICHT_VERFUEGBAR, 'Sobald er für Dich bereit ist, findest Du ihn hier.'))
root.replaceChildren(infoKarte(NICHT_VERFUEGBAR, 'Sobald er für dich bereit ist, findest du ihn hier.'))
return
}
if (!s.projects.length) {
@@ -180,14 +180,14 @@ async function ladeNeu(s, optionen = {}) {
async function zeichneAnsicht(s, { status, aktuell, bestellformular = false, meldung = null }) {
s.status = status
if (!status.verfuegbar) {
s.buehne.replaceChildren(infoKarte(NICHT_VERFUEGBAR, 'Sobald er für Dich bereit ist, findest Du ihn hier.'))
s.buehne.replaceChildren(infoKarte(NICHT_VERFUEGBAR, 'Sobald er für dich bereit ist, findest du ihn hier.'))
return
}
const zustand = status.state || zustandVon(s.projekt)
if (zustand === 'off') zeigeAnfrage(s, { status, aktuell })
else if (zustand === 'ordered') zeigeBestellung(s, { status, aktuell, bestellformular })
else if (zustand === 'setup' || zustand === 'active' || zustand === 'disabled') zeigeReiter(s, { status, aktuell, zustand })
else s.buehne.replaceChildren(infoKarte('Webshop', 'Der Stand Deines Webshops ist gerade nicht lesbar. Bitte lade die Seite neu.'))
else s.buehne.replaceChildren(infoKarte('Webshop', 'Der Stand deines Webshops ist gerade nicht lesbar. Bitte lade die Seite neu.'))
if (meldung) zeigeMeldung(s, meldung.text, meldung.art)
}
+1 -1
View File
@@ -425,7 +425,7 @@ router.post('/rechtstexte/bestaetigen', requireSession, nurKunde, limitWrites, r
if (hash === '' || hash !== rechtstexteHash(entwurf, aktuell.einstellungen)) {
throw stop(409, 'Die Texte wurden inzwischen geändert. Bitte lies sie noch einmal durch und bestätige dann erneut.', 'texte_geaendert')
}
if (!vollstaendig(entwurf)) throw stop(400, 'Bitte fülle AGB, Widerrufsbelehrung, Widerrufsformular, Impressum und Datenschutz aus, bevor Du bestätigst.')
if (!vollstaendig(entwurf)) throw stop(400, 'Bitte fülle AGB, Widerrufsbelehrung, Widerrufsformular, Impressum und Datenschutz aus, bevor du bestätigst.')
const at = new Date().toISOString()
const bestaetigt = { texte: { ...entwurf }, hash, at, von }
const json = JSON.stringify({ entwurf, bestaetigt })
+2 -2
View File
@@ -33,8 +33,8 @@ const MAX_JSON = 60000
const GESCHLOSSEN_TEXT = 'Der Shop ist gerade geschlossen.'
const ZAHLUNG_TEXT = 'Die Zahlung kann gerade nicht gestartet werden. Bitte versuchen Sie es später noch einmal.'
const KLARNA_HINWEIS =
'Klarna/SEPA ist in Deinem Stripe-Konto nicht aktiviert. Bestellungen wurden deshalb nur mit Karte angeboten. ' +
'Bitte aktiviere die Zahlungsarten in Deinem Stripe-Dashboard unter Einstellungen > Zahlungsmethoden.'
'Klarna/SEPA ist in deinem Stripe-Konto nicht aktiviert. Bestellungen wurden deshalb nur mit Karte angeboten. ' +
'Bitte aktiviere die Zahlungsarten in deinem Stripe-Dashboard unter Einstellungen > Zahlungsmethoden.'
const col = () => config.collections.shopOrders
const istObjekt = (wert) => wert !== null && typeof wert === 'object' && !Array.isArray(wert)
+6 -6
View File
@@ -200,7 +200,7 @@ export async function bereitschaft(ctx, { produkte } = {}) {
{
id: 'einbau',
ok: doc?.state === 'active',
text: doc?.state === 'active' ? 'Der Shop ist eingebaut.' : 'Wir bauen den Shop gerade in Deine Website ein.',
text: doc?.state === 'active' ? 'Der Shop ist eingebaut.' : 'Wir bauen den Shop gerade in deine Website ein.',
},
{
id: 'stripe',
@@ -208,14 +208,14 @@ export async function bereitschaft(ctx, { produkte } = {}) {
text:
shop.stripeStatus?.charges_enabled === true
? 'Dein Zahlungskonto bei Stripe ist bereit.'
: 'Bitte richte Dein Zahlungskonto bei Stripe ein, damit Du Zahlungen empfangen kannst.',
: 'Bitte richte dein Zahlungskonto bei Stripe ein, damit du Zahlungen empfangen kannst.',
},
{
id: 'einstellungen',
ok: istVollstaendig(shop.einstellungen),
text: istVollstaendig(shop.einstellungen)
? 'Deine Shop-Angaben sind vollständig.'
: 'Bitte vervollständige Deine Shop-Angaben (Shopname, Anbieter mit Anschrift und E-Mail, Lieferzeit).',
: 'Bitte vervollständige deine Shop-Angaben (Shopname, Anbieter mit Anschrift und E-Mail, Lieferzeit).',
},
{
id: 'rechtstexte',
@@ -224,10 +224,10 @@ export async function bereitschaft(ctx, { produkte } = {}) {
stand === 'bestaetigt'
? 'Deine Rechtstexte sind bestätigt.'
: stand === 'geaendert'
? 'Bitte bestätige Deine geänderten Rechtstexte.'
? 'Bitte bestätige deine geänderten Rechtstexte.'
: vollstaendig(shop.rechtstexte.entwurf)
? 'Bitte bestätige Deine Rechtstexte.'
: 'Bitte ergänze Deine Rechtstexte (AGB, Widerrufsbelehrung, Widerrufsformular, Impressum und Datenschutz) und bestätige sie.',
? 'Bitte bestätige deine Rechtstexte.'
: 'Bitte ergänze deine Rechtstexte (AGB, Widerrufsbelehrung, Widerrufsformular, Impressum und Datenschutz) und bestätige sie.',
},
{
id: 'artikel',
+1 -1
View File
@@ -512,7 +512,7 @@ test('Webshop-Kachel: Text mit Preis, Knopf und Pille je Zustand, Ziel #webshop
const html = await renderMit([shopProject('p1', state)], { shop: { verfuegbar: true } })
const t = tile(html, 'shop')
assert.match(t, /<h3>Webshop<\/h3>/, state)
assert.match(t, /Dein eigener Online-Shop auf Deiner Website\. Zahlungen gehen direkt auf Dein Stripe-Konto\. 299 € einmalig\./, state)
assert.match(t, /Dein eigener Online-Shop auf deiner Website\. Zahlungen gehen direkt auf dein Stripe-Konto\. 299 € einmalig\./, state)
assert.match(t, new RegExp(`<button type="button" class="kb-btn-primary fn-tile-cta" data-view="webshop" data-project="p1">${knopf}</button>`), state)
assert.match(t, new RegExp(`fn-pill fn-pill-${klasse}">${pille}</span>`), state)
}
+1 -1
View File
@@ -393,7 +393,7 @@ test('Klarna nicht freigeschaltet: einmal mit Karte wiederholen, Hinweis fuer de
assert.match(order.stripeSessionId, /^cs_/)
const status = JSON.parse(aw.docs('shopConfig').get(FEATURE).stripeStatus)
assert.equal(status.charges_enabled, true)
assert.match(status.hinweis, /^Klarna\/SEPA ist in Deinem Stripe-Konto nicht aktiviert/)
assert.match(status.hinweis, /^Klarna\/SEPA ist in deinem Stripe-Konto nicht aktiviert/)
// Zwischenspeicher geleert: der naechste Kontext enthaelt den Hinweis
assert.match((await K.shopPerSiteKey(SITE_KEY, { now: T0 })).shop.stripeStatus.hinweis, /Klarna/)
})
+4 -4
View File
@@ -251,7 +251,7 @@ test('bereitschaft: einbau', async () => {
const { doc, shop } = await K.ladeShopFuerProjekt(KUNDE, PROJEKT)
const r = await K.bereitschaft({ doc, shop })
assert.equal(punkt(r, 'einbau').ok, false)
assert.equal(punkt(r, 'einbau').text, 'Wir bauen den Shop gerade in Deine Website ein.')
assert.equal(punkt(r, 'einbau').text, 'Wir bauen den Shop gerade in deine Website ein.')
assert.equal(r.bereit, false)
assert.equal(await K.istGeoeffnet({ doc, shop }), false)
})
@@ -284,7 +284,7 @@ test('bereitschaft und Oeffnung: Rechtstexte offen, geaendert, bestaetigt', asyn
let c = await K.ladeShopFuerProjekt(KUNDE, PROJEKT)
let r = await K.bereitschaft(c)
assert.equal(punkt(r, 'rechtstexte').ok, false)
assert.equal(punkt(r, 'rechtstexte').text, 'Bitte bestätige Deine Rechtstexte.')
assert.equal(punkt(r, 'rechtstexte').text, 'Bitte bestätige deine Rechtstexte.')
assert.equal(await K.istGeoeffnet(c), false, 'nie bestaetigt => zu')
assert.equal(K.oeffentlicheRechtstexte(c), null)
@@ -293,7 +293,7 @@ test('bereitschaft und Oeffnung: Rechtstexte offen, geaendert, bestaetigt', asyn
c = await K.ladeShopFuerProjekt(KUNDE, PROJEKT)
r = await K.bereitschaft(c)
assert.equal(punkt(r, 'rechtstexte').ok, false)
assert.match(punkt(r, 'rechtstexte').text, /^Bitte ergänze Deine Rechtstexte/)
assert.match(punkt(r, 'rechtstexte').text, /^Bitte ergänze deine Rechtstexte/)
// bestaetigt, Entwurf danach geaendert
const neu = { ...TEXTE, agb: 'Neue AGB' }
@@ -301,7 +301,7 @@ test('bereitschaft und Oeffnung: Rechtstexte offen, geaendert, bestaetigt', asyn
c = await K.ladeShopFuerProjekt(KUNDE, PROJEKT)
r = await K.bereitschaft(c)
assert.equal(punkt(r, 'rechtstexte').ok, false)
assert.equal(punkt(r, 'rechtstexte').text, 'Bitte bestätige Deine geänderten Rechtstexte.')
assert.equal(punkt(r, 'rechtstexte').text, 'Bitte bestätige deine geänderten Rechtstexte.')
assert.equal(r.bereit, false)
assert.equal(await K.istGeoeffnet(c), true, 'frueher bestaetigt => bleibt offen')
assert.deepEqual(K.oeffentlicheRechtstexte(c), TEXTE, 'Kaeufer sehen die bestaetigte Fassung')
+3 -3
View File
@@ -273,7 +273,7 @@ test('Anfrage abgelehnt: Begründung als Text; „Neue Anfrage stellen“ zeigt
await fx.klick(fx.finde(root, '[data-aktion="neue-anfrage"]'))
assert.ok(fx.finde(root, '[data-aktion="anfrage-senden"]'))
assert.equal(fx.finde(root, '[data-feld="beschreibung"]').value, 'Handgemachte Mützen und Schals.')
assert.equal(fx.finde(root, '[data-feld="unternehmer"]').checked, false, 'die Haken setzt Du neu')
assert.equal(fx.finde(root, '[data-feld="unternehmer"]').checked, false, 'die Haken setzt du neu')
},
))
@@ -411,13 +411,13 @@ test('Die echten Reiter-Module werden beim Wählen geladen', mitDom(welt1('activ
for (const r of REITER) assert.equal(typeof (await r.laden()).render, 'function', `${r.key} exportiert render`)
}))
test('Zustand setup: Hinweis „Wir bauen Deinen Shop gerade ein …“ über den Reitern, Reiter nicht schreibgeschützt', mitDom(welt1('setup', status('setup')), undefined, async (fx) => {
test('Zustand setup: Hinweis „Wir bauen deinen Shop gerade ein …“ über den Reitern, Reiter nicht schreibgeschützt', mitDom(welt1('setup', status('setup')), undefined, async (fx) => {
const gesehen = []
const alt = REITER[0].laden
REITER[0].laden = async () => ({ render: (panel, ctx) => { gesehen.push(ctx) } })
try {
const root = await oeffne(fx)
assert.equal(fx.text(fx.finde(root, '[data-hinweis="setup"]')), 'Wir bauen Deinen Shop gerade ein. Du kannst schon Artikel, Stripe und Rechtstexte vorbereiten.')
assert.equal(fx.text(fx.finde(root, '[data-hinweis="setup"]')), 'Wir bauen deinen Shop gerade ein. Du kannst schon Artikel, Stripe und Rechtstexte vorbereiten.')
assert.equal(fx.alle(root, '[role="tab"]').length, 5)
assert.equal(gesehen[0].nurLesen, false)
} finally {