Skip to content

Commit d0805ce

Browse files
committed
feat: add max char error states
1 parent 2e5a785 commit d0805ce

File tree

4 files changed

+57
-9
lines changed

4 files changed

+57
-9
lines changed

app/[locale]/enterprise/_components/ContactForm/index.tsx

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,18 @@ import Input from "@/components/ui/input"
88
import { Spinner } from "@/components/ui/spinner"
99
import { Textarea } from "@/components/ui/textarea"
1010

11+
import { cn } from "@/lib/utils/cn"
12+
13+
import { MAX_EMAIL_LENGTH, MAX_MESSAGE_LENGTH } from "../../constants"
14+
1115
type EnterpriseContactFormProps = {
1216
strings: {
1317
error: {
1418
domain: React.ReactNode // Link injected
1519
emailInvalid: string
20+
emailTooLong: string // Length injected via {length}
1621
general: string
22+
messageTooLong: string // Length injected via {length}
1723
required: string
1824
}
1925
placeholder: {
@@ -83,6 +89,13 @@ const sanitizeInput = (input: string): string =>
8389
.trim()
8490

8591
const EnterpriseContactForm = ({ strings }: EnterpriseContactFormProps) => {
92+
const getCharacterCountClasses = (currentLength: number, maxLength: number) =>
93+
cn(
94+
currentLength >= Math.floor(maxLength * 0.9) && "block", // Show char count when within 10% remaining to limit
95+
currentLength > maxLength - 64 && "text-warning-border", // Warning color within 64 chars (border version for proper contrast ratio),
96+
currentLength > maxLength && "text-error" // Error color over limit
97+
)
98+
8699
const [formData, setFormData] = useState<FormState>({
87100
email: "",
88101
message: "",
@@ -126,6 +139,8 @@ const EnterpriseContactForm = ({ strings }: EnterpriseContactFormProps) => {
126139

127140
if (!sanitized) return strings.error.required
128141

142+
if (sanitized.length > MAX_EMAIL_LENGTH) return strings.error.emailTooLong
143+
129144
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
130145
if (!emailRegex.test(sanitized)) return strings.error.emailInvalid
131146

@@ -142,6 +157,9 @@ const EnterpriseContactForm = ({ strings }: EnterpriseContactFormProps) => {
142157

143158
if (!sanitized) return strings.error.required
144159

160+
if (sanitized.length > MAX_MESSAGE_LENGTH)
161+
return strings.error.messageTooLong
162+
145163
return undefined
146164
}
147165

@@ -220,15 +238,28 @@ const EnterpriseContactForm = ({ strings }: EnterpriseContactFormProps) => {
220238
</div>
221239

222240
<div className="space-y-2">
223-
<Textarea
224-
placeholder={strings.placeholder.textarea}
225-
value={formData.message}
226-
onChange={handleInputChange("message")}
227-
onBlur={handleBlur("message")}
228-
hasError={!!errors.message}
229-
disabled={submissionState === "submitting"}
230-
className="min-h-[120px]"
231-
/>
241+
<div className="relative">
242+
<Textarea
243+
placeholder={strings.placeholder.textarea}
244+
value={formData.message}
245+
onChange={handleInputChange("message")}
246+
onBlur={handleBlur("message")}
247+
hasError={!!errors.message}
248+
disabled={submissionState === "submitting"}
249+
className="min-h-[120px]"
250+
/>
251+
<div
252+
className={cn(
253+
"absolute bottom-1 end-3 hidden rounded bg-background px-1 py-0.5 text-xs shadow",
254+
getCharacterCountClasses(
255+
formData.message.length,
256+
MAX_MESSAGE_LENGTH
257+
)
258+
)}
259+
>
260+
{formData.message.length}/{MAX_MESSAGE_LENGTH}
261+
</div>
262+
</div>
232263
{errors.message && (
233264
<p className="text-sm text-error" role="alert">
234265
{errors.message}

app/[locale]/enterprise/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const MAX_EMAIL_LENGTH = 2 ** 6 // 64
2+
export const MAX_MESSAGE_LENGTH = 2 ** 12 // 4,096

app/[locale]/enterprise/page.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import { BASE_TIME_UNIT } from "@/lib/constants"
5151
import CasesColumn from "./_components/CasesColumn"
5252
import EnterpriseContactForm from "./_components/ContactForm/lazy"
5353
import FeatureCard from "./_components/FeatureCard"
54+
import { MAX_EMAIL_LENGTH, MAX_MESSAGE_LENGTH } from "./constants"
5455
import type { Case, EcosystemPlayer, Feature } from "./types"
5556
import { parseActivity } from "./utils"
5657

@@ -504,7 +505,19 @@ const Page = async ({ params }: { params: { locale: Lang } }) => {
504505
emailInvalid: t(
505506
"page-enterprise-team-form-error-email-invalid"
506507
),
508+
emailTooLong: t(
509+
"page-enterprise-team-form-error-email-too-long",
510+
{
511+
length: MAX_EMAIL_LENGTH,
512+
}
513+
),
507514
general: t("page-enterprise-team-form-error-general"),
515+
messageTooLong: t(
516+
"page-enterprise-team-form-error-message-too-long",
517+
{
518+
length: MAX_MESSAGE_LENGTH,
519+
}
520+
),
508521
required: t("page-enterprise-team-form-error-required"),
509522
},
510523
placeholder: {

src/intl/en/page-enterprise.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@
4545
"page-enterprise-team-form-button-loading": "Beaming request",
4646
"page-enterprise-team-form-error-domain": "Please use a business, institutional, or organizational email address (e.g., no @gmail). For general question <a>join our Discord</a>.",
4747
"page-enterprise-team-form-error-email-invalid": "Please enter a valid email address",
48+
"page-enterprise-team-form-error-email-too-long": "Email address is too long (maximum {length} characters)",
4849
"page-enterprise-team-form-error-general": "Unable to send your message. Please try again or contact us directly at [email protected]",
50+
"page-enterprise-team-form-error-message-too-long": "Message is too long (maximum {length} characters)",
4951
"page-enterprise-team-form-error-required": "Required",
5052
"page-enterprise-team-form-error-short": "Please provide at least <span>#</span> characters describing your inquiry",
5153
"page-enterprise-team-form-placeholder-input": "Your e-mail",

0 commit comments

Comments
 (0)