feat(webDev): add inquiry modal for pricing plans

This commit introduces a 'Contact Sales' modal on the web development page, allowing users to inquire about specific service plans.

- The pricing plan buttons now trigger this modal, pre-filled with the selected plan's details.
- Users can add custom remarks to their inquiry.
- On submission, a pre-formatted message is generated and opened in WhatsApp using a new `useWhatsAppMsgSender` composable.
- Adds `NUXT_PUBLIC_WHATSAPP_NUMBER` to the runtime configuration.
- Refactors content validation by introducing Zod schemas for `PricingPlan` and `Button` props to improve type safety.
- Adds new i18n keys for the modal interface and message templates.
This commit is contained in:
xiaomai
2025-11-07 11:04:14 +08:00
parent 40b3ee147f
commit ccfd268682
14 changed files with 393 additions and 40 deletions

View File

@@ -0,0 +1,37 @@
// buttonSchema.ts
import { z } from "zod";
const ButtonSize = z.enum(["2xs", "xs", "sm", "md", "lg", "xl"]);
const ButtonColor = z.enum([
"error",
"primary",
"secondary",
"success",
"info",
"warning",
"neutral",
]);
const ButtonVariant = z.enum(["solid", "outline", "soft", "ghost", "link"]);
export const ButtonPropsSchema = z.object({
size: ButtonSize.optional().default("sm"),
type: z.enum(["button", "submit", "reset"]).optional().default("button"),
label: z.string().optional().default(""),
color: ButtonColor.optional().default("primary"),
variant: ButtonVariant.optional().default("solid"),
icon: z.string().optional().default(""),
leading: z.boolean().optional().default(false),
trailing: z.boolean().optional().default(false),
disabled: z.boolean().optional().default(false),
loading: z.boolean().optional().default(false),
loadingIcon: z.string().optional().default("i-heroicons-arrow-path-20-solid"),
block: z.boolean().optional().default(false),
to: z.string().optional().default(""),
target: z.string().optional().default(""),
padded: z.boolean().optional().default(true),
square: z.boolean().optional().default(false),
truncate: z.boolean().optional().default(false),
attrs: z.record(z.any()).optional().default({}),
});
export type ButtonProps = z.infer<typeof ButtonPropsSchema>;

View File

@@ -0,0 +1,57 @@
import { z } from "zod";
import { ButtonPropsSchema } from "./buttonSchema";
/**
* Zod schema for UPricingPlan component (basic props only)
* Reference: https://ui.nuxt.com/docs/components/pricing-plan#props
*/
export const PricingPlanPropsSchema = z.object({
/** The title of the pricing plan. */
title: z.string(),
/** The description text shown under the title. */
description: z.string().optional(),
/** The current price of the plan. */
price: z.string().optional(),
/**
* Discounted price.
* When set, the main price will appear with a strikethrough.
*/
discount: z.string().optional(),
/** The unit period (e.g. /month) displayed next to price. */
billingCycle: z.string().optional(),
/** Additional billing text above the billing cycle. */
billingPeriod: z.string().optional(),
/**
* List of plan features.
* Can be an array of strings or array of objects (feature items).
*/
features: z.array(z.string()).optional().default([]),
/** The button displayed at the bottom (ButtonProps). */
button: ButtonPropsSchema.optional(),
/**
* Visual variant of the pricing plan.
* @default "outline"
*/
variant: z.enum(["soft", "solid", "outline", "subtle"]).optional().default("outline"),
/**
* Layout orientation of the component.
* @default "vertical"
*/
orientation: z.enum(["vertical", "horizontal"]).optional().default("vertical"),
/** Optional tagline text displayed above price. */
tagline: z.string().optional(),
/** Terms or disclaimer text displayed below features. */
terms: z.string().optional(),
/**
* Highlights the pricing plan visually (adds a ring around it).
* @default false
*/
highlight: z.boolean().optional().default(false),
/**
* Enlarges the plan card slightly for emphasis.
* @default false
*/
scale: z.boolean().optional().default(false),
});
export type PricingPlanProps = z.infer<typeof PricingPlanPropsSchema>;