Skip to content

Commit 4945c8b

Browse files
authored
feat: support contact email list (#4391)
Closes #4390 Users requested to setup multiple emails in our default form action. **Testing** - try to enter multiple emails in project settings - submit form from published site
1 parent 971ae9f commit 4945c8b

File tree

5 files changed

+60
-31
lines changed

5 files changed

+60
-31
lines changed

apps/builder/app/builder/features/project-settings/section-general.tsx

Lines changed: 54 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
Tooltip,
1515
InputErrorsTooltip,
1616
ProBadge,
17+
TextArea,
1718
} from "@webstudio-is/design-system";
1819
import { InfoCircleIcon } from "@webstudio-is/icons";
1920
import { ImageControl } from "./image-control";
@@ -44,38 +45,61 @@ const defaultMetaSettings: ProjectMeta = {
4445

4546
const Email = z.string().email();
4647

48+
const validateContactEmail = (
49+
contactEmail: string,
50+
maxContactEmails: number
51+
) => {
52+
contactEmail = contactEmail.trim();
53+
if (contactEmail.length === 0) {
54+
return;
55+
}
56+
const emails = contactEmail.split(/\s*,\s*/);
57+
if (emails.length > maxContactEmails) {
58+
return `Only ${maxContactEmails} emails are allowed.`;
59+
}
60+
if (emails.every((email) => Email.safeParse(email).success) === false) {
61+
return "Contact email is invalid.";
62+
}
63+
};
64+
65+
const saveSetting = <Name extends keyof ProjectMeta>(
66+
name: keyof ProjectMeta,
67+
value: ProjectMeta[Name]
68+
) => {
69+
serverSyncStore.createTransaction([$pages], (pages) => {
70+
if (pages === undefined) {
71+
return;
72+
}
73+
if (pages.meta === undefined) {
74+
pages.meta = {};
75+
}
76+
pages.meta[name] = value;
77+
});
78+
};
79+
4780
export const SectionGeneral = () => {
48-
const { allowContactEmail } = useStore($userPlanFeatures);
81+
const { maxContactEmails } = useStore($userPlanFeatures);
82+
const allowContactEmail = maxContactEmails > 0;
4983
const [meta, setMeta] = useState(
5084
() => $pages.get()?.meta ?? defaultMetaSettings
5185
);
5286
const siteNameId = useId();
5387
const contactEmailId = useId();
54-
const contactEmailError =
55-
(meta.contactEmail ?? "").trim().length === 0 ||
56-
Email.safeParse(meta.contactEmail).success
57-
? undefined
58-
: "Contact email is invalid.";
88+
const contactEmailError = validateContactEmail(
89+
meta.contactEmail ?? "",
90+
maxContactEmails
91+
);
5992
const assets = useStore($assets);
6093
const asset = assets.get(meta.faviconAssetId ?? "");
6194
const favIconUrl = asset ? `${asset.name}` : undefined;
6295
const imageLoader = useStore($imageLoader);
6396

64-
const handleSave = <Setting extends keyof ProjectMeta>(setting: Setting) => {
65-
return (value: ProjectMeta[Setting]) => {
66-
setMeta({
67-
...meta,
68-
[setting]: value,
69-
});
70-
serverSyncStore.createTransaction([$pages], (pages) => {
71-
if (pages === undefined) {
72-
return;
73-
}
74-
if (pages.meta === undefined) {
75-
pages.meta = {};
76-
}
77-
pages.meta[setting] = value;
78-
});
97+
const handleSave = <Name extends keyof ProjectMeta>(
98+
name: keyof ProjectMeta
99+
) => {
100+
return (value: ProjectMeta[Name]) => {
101+
setMeta({ ...meta, [name]: value });
102+
saveSetting(name, value);
79103
};
80104
};
81105

@@ -120,14 +144,19 @@ export const SectionGeneral = () => {
120144
<InputErrorsTooltip
121145
errors={contactEmailError ? [contactEmailError] : undefined}
122146
>
123-
<InputField
147+
<TextArea
124148
id={contactEmailId}
125149
color={contactEmailError ? "error" : undefined}
126-
placeholder="email@address.com"
150+
placeholder="[email protected], jane@company.com"
127151
disabled={allowContactEmail === false}
152+
autoGrow={true}
153+
rows={1}
128154
value={meta.contactEmail ?? ""}
129-
onChange={(event) => {
130-
handleSave("contactEmail")(event.target.value);
155+
onChange={(value) => {
156+
setMeta({ ...meta, contactEmail: value });
157+
if (validateContactEmail(value, maxContactEmails) === undefined) {
158+
saveSetting("contactEmail", value);
159+
}
131160
}}
132161
/>
133162
</InputErrorsTooltip>

apps/builder/app/builder/shared/nano-states/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export const $activeInspectorPanel = atom<"style" | "settings">("style");
4141
export const $userPlanFeatures = atom<UserPlanFeatures>({
4242
allowShareAdminLinks: false,
4343
allowDynamicData: false,
44-
allowContactEmail: false,
44+
maxContactEmails: 0,
4545
maxDomainsAllowedPerUser: 1,
4646
hasSubscription: false,
4747
hasProPlan: false,

apps/builder/app/dashboard/dashboard.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const userPlanFeatures: UserPlanFeatures = {
3333
hasSubscription: false,
3434
allowShareAdminLinks: false,
3535
allowDynamicData: false,
36-
allowContactEmail: false,
36+
maxContactEmails: 0,
3737
maxDomainsAllowedPerUser: 1,
3838
};
3939

apps/builder/app/shared/db/user-plan-features.server.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export const getUserPlanFeatures = async (
4444
return {
4545
allowShareAdminLinks: true,
4646
allowDynamicData: true,
47-
allowContactEmail: true,
47+
maxContactEmails: 5,
4848
maxDomainsAllowedPerUser: Number.MAX_SAFE_INTEGER,
4949
hasSubscription,
5050
hasProPlan: true,
@@ -56,7 +56,7 @@ export const getUserPlanFeatures = async (
5656
return {
5757
allowShareAdminLinks: true,
5858
allowDynamicData: true,
59-
allowContactEmail: true,
59+
maxContactEmails: 5,
6060
maxDomainsAllowedPerUser: Number.MAX_SAFE_INTEGER,
6161
hasSubscription: true,
6262
hasProPlan: true,
@@ -67,7 +67,7 @@ export const getUserPlanFeatures = async (
6767
return {
6868
allowShareAdminLinks: false,
6969
allowDynamicData: false,
70-
allowContactEmail: false,
70+
maxContactEmails: 0,
7171
maxDomainsAllowedPerUser: 1,
7272
hasSubscription: false,
7373
hasProPlan: false,

packages/trpc-interface/src/context/context.server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ type DeploymentContext = {
6666
type UserPlanFeatures = {
6767
allowShareAdminLinks: boolean;
6868
allowDynamicData: boolean;
69-
allowContactEmail: boolean;
69+
maxContactEmails: number;
7070
maxDomainsAllowedPerUser: number;
7171
hasSubscription: boolean;
7272
} & (

0 commit comments

Comments
 (0)