feat: implmement feedback card and subcription section component

This commit is contained in:
Nam Doan
2026-01-06 11:06:01 +07:00
parent 1716ef2cf4
commit ad1e782713
5 changed files with 551 additions and 38 deletions
@@ -0,0 +1,74 @@
import { clx } from "@medusajs/ui"
import LocalizedClientLink from "@modules/common/components/localized-client-link"
import {
LayoutComponentDefinition,
LayoutContext,
} from "@vibentec/component-map"
export default async function VtFeedbackCard({
nodes,
context,
}: {
nodes: LayoutComponentDefinition
context: LayoutContext
}) {
const props = nodes.config ?? {}
const title: string = props.title ?? ""
const items = props.items ?? []
const classes = {
container: props.className ?? "",
title: props.titleClassName ?? "text-[#003F31] text-[28px] font-semibold mb-10",
grid: props.gridClassName ?? "grid grid-cols-1 small:grid-cols-2 xl:grid-cols-4 gap-6",
card: props.cardClassName ?? "rounded-2xl overflow-hidden bg-[#CFECD9]",
image: props.imageClassName ?? "w-full h-[260px] object-cover",
content: props.contentClassName ?? "p-6",
name: props.nameClassName ?? "text-[#003F31] text-[20px] font-bold",
subtitle: props.subtitleClassName ?? "mt-1 text-[#003f31b3] text-[14px]",
quote: props.quoteClassName ?? "mt-4 text-[#003F31] text-[16px]",
cta: props.ctaClassName ?? "mt-6 inline-flex items-center justify-center bg-[#FCEE56] text-[#0D382E] px-6 py-2 rounded-full font-bold",
}
if (!items || items.length === 0) return null
const renderCard = (entry: any, idx: number) => {
const imageEl = entry.imageSrc ? (
<img
src={entry.imageSrc}
alt={entry.imageAlt ?? entry.name ?? `feedback-card-${idx}`}
className={classes.image}
/>
) : null
const ctaEl = entry.cta?.href ? (
<LocalizedClientLink href={entry.cta.href} className={clx(classes.cta, entry.cta?.className)}>
{entry.cta.label ?? "Mehr erfahren"}
</LocalizedClientLink>
) : entry.cta?.label ? (
<button className={clx(classes.cta, entry.cta?.className)}>{entry.cta.label}</button>
) : null
return (
<div className={clx(classes.card, entry.className)} key={`vt-feedback-card-${idx}`}>
{imageEl}
<div className={clx(classes.content)}>
{entry.name && <div className={clx(classes.name, entry.nameClassName)}>{entry.name}</div>}
{entry.subtitle && (
<div className={clx(classes.subtitle, entry.subtitleClassName)}>{entry.subtitle}</div>
)}
{entry.quote && <div className={clx(classes.quote, entry.quoteClassName)}>{entry.quote}</div>}
{ctaEl}
</div>
</div>
)
}
return (
<section className={classes.container}>
{title && <h2 className={classes.title}>{title}</h2>}
<div className={classes.grid}>{items.map((it: any, idx: number) => renderCard(it, idx))}</div>
</section>
)
}
@@ -0,0 +1,193 @@
"use client"
import { Button } from "@medusajs/ui"
import { clx } from "@medusajs/ui"
import {
LayoutComponentDefinition,
LayoutContext,
} from "@vibentec/component-map"
import React, { useState } from "react"
interface BenefitItem {
icon?: string
imgSrc?: string
title?: string
description?: string
className?: string
iconClassName?: string
titleClassName?: string
descriptionClassName?: string
}
export default function VtSubcription({
nodes,
context,
}: {
nodes: LayoutComponentDefinition
context: LayoutContext
}) {
const props = nodes.config ?? {}
const [firstName, setFirstName] = useState("")
const [email, setEmail] = useState("")
const [accepted, setAccepted] = useState(false)
const [submitted, setSubmitted] = useState(false)
const classes = {
container: props.className ?? "content-container",
card: props.cardClassName ?? "rounded-2xl bg-[#CFECD9] p-8 small:p-12",
title:
props.titleClassName ??
"text-[#003F31] text-[28px] font-bold text-center",
description:
props.descriptionClassName ?? "mt-2 text-center text-[#003F31]",
highlight: props.highlightClassName ?? "font-bold",
form: props.formClassName ?? "mt-8 flex flex-col gap-6",
fields: props.fieldsClassName ?? "grid grid-cols-1 small:grid-cols-2 gap-4",
input:
props.inputClassName ??
"h-[52px] rounded-md border border-[#003F31]/40 px-4 bg-transparent text-[#003F31]",
checkboxRow: props.checkboxRowClassName ?? "flex items-center gap-3",
checkbox:
props.checkboxClassName ??
"w-5 h-5 rounded-md border border-[#003F31]/60",
checkboxLabel: props.checkboxLabelClassName ?? "text-[#003F31] text-[16px]",
subtextClass: props.subtextClassName ?? "text-[#003F31]",
submit:
props.submitClassName ?? "",
success: props.successClassName ?? "mt-4 text-center text-[#003F31]",
benefits:
props.benefitsClassName ??
"mt-8 grid grid-cols-1 small:grid-cols-3 gap-8",
benefitItem:
props.benefitItemClassName ??
"flex flex-col items-center text-center gap-3",
benefitIcon:
props.benefitIconClassName ??
"w-12 h-12 rounded-full bg-[#003F31] text-white flex items-center justify-center",
benefitTitle: props.benefitTitleClassName ?? "text-[#003F31] font-semibold",
benefitDesc: props.benefitDescClassName ?? "text-[#003F31] opacity-80",
}
const submitConfig = props.cta ?? {}
const policyLabel: string =
props.policyLabel ?? "Ich habe die DSGVO gelesen und akzeptiere sie."
const firstNameField = props.firstName ?? null
const emailField = props.email ?? null
const onSubmit = (e: React.FormEvent) => {
e.preventDefault()
if (!accepted) return
setSubmitted(true)
console.log("subscription_submit", { firstName, email, accepted })
}
return (
<section className={classes.container}>
<div className={classes.card}>
{props.title && <h2 className={classes.title}>{props.title}</h2>}
{props.description && (
<p className={classes.description}>
{props.descriptionPrefix}{" "}
<span className={classes.highlight}>
{props.descriptionHighlight}
</span>{" "}
{props.descriptionSuffix}
</p>
)}
{Array.isArray(props.benefits) && props.benefits.length > 0 && (
<div className={classes.benefits}>
{props.benefits.map((b: BenefitItem, i: number) => (
<div
key={`benefit-${i}`}
className={clx(classes.benefitItem, b.className)}
>
{b.imgSrc ? (
<img
src={b.imgSrc}
alt={b.title ?? `benefit-${i}`}
className={clx(classes.benefitIcon, b.iconClassName)}
/>
) : (
<div
className={clx(classes.benefitIcon, b.iconClassName)}
aria-hidden="true"
>
{b.icon ?? ""}
</div>
)}
{b.title && (
<div className={clx(classes.benefitTitle, b.titleClassName)}>
{b.title}
</div>
)}
{b.description && (
<div
className={clx(classes.benefitDesc, b.descriptionClassName)}
>
{b.description}
</div>
)}
</div>
))}
</div>
)}
{props.subtext && (
<p
className={clx(
"mt-2 text-center", classes?.subtextClass ?? "text-[#003F31]"
)}
>
{props.subtext}
</p>
)}
<form className={classes.form} onSubmit={onSubmit}>
<div className={classes.fields}>
{firstNameField && (
<input
type="text"
placeholder={firstNameField.placeholder ?? "Vorname"}
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
className={clx(classes.input, firstNameField.className)}
/>
)}
{emailField && (
<input
type="email"
placeholder={emailField.placeholder ?? "E-Mail-Adresse"}
value={email}
onChange={(e) => setEmail(e.target.value)}
className={clx(classes.input, emailField.className)}
required
/>
)}
</div>
{props.newCheckboxRowClassName && (
<label className={classes.checkboxRow}>
<input
type="checkbox"
checked={accepted}
onChange={(e) => setAccepted(e.target.checked)}
className={classes.checkbox}
/>
<span className={classes.checkboxLabel}>{policyLabel}</span>
</label>
)}
<button
type="submit"
className={clx(classes.submit, submitConfig.className)}
>
{submitConfig.label ?? "Anmelden"}
</button>
</form>
{submitted && (
<div className={classes.success}>
{props.successMessage ??
"Danke! Prüfe deine E-Mails für den Rabattcode."}
</div>
)}
</div>
</section>
)
}