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:
1 parent
17ad5cbcdc
commit
16a7f05aa2
10 files changed
+34
-34
No files matched your search
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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/)
|
||||
})
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user