fix: loading state not visible when submitting contact form (#352)#417
Conversation
📝 WalkthroughWalkthroughClient ContactPage becomes a controlled form with loading/error/success state and submits POST /api/contact; server adds Zod validation and an Express POST route that persists submissions to a new Prisma ChangesContact Form Submission Feature
Sequence Diagram(s)sequenceDiagram
participant Browser as Browser (ContactPage)
participant FrontendHandler as ContactPage.handleSubmit
participant API as POST /api/contact
participant Validator as contactSchema.safeParse
participant Database as prisma.contactSubmission
Browser->>FrontendHandler: User clicks submit (formData)
FrontendHandler->>API: POST formData (name,email,subject,message)
API->>Validator: Validate request body
alt Validation success
Validator-->>API: Parsed ContactInput
API->>Database: create(parsed data)
Database-->>API: Record created
API-->>FrontendHandler: 201 success
FrontendHandler->>Browser: show success banner, clear form
else Validation failure
Validator-->>API: issues
API-->>FrontendHandler: 400 with issues
FrontendHandler->>Browser: show validation error banner
else Server error
API-->>FrontendHandler: 500 error
FrontendHandler->>Browser: show generic error banner
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds an end-to-end “Contact Us” flow (client form → public API endpoint → DB persistence) with validation.
Changes:
- Added Zod validation schema for contact submissions.
- Added public
/api/contactExpress route that stores submissions via Prisma. - Updated the client Contact page to submit a contact form and show success/error UI.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| server/src/module/contact/contact.validation.ts | Introduces Zod schema/type for validating contact payloads. |
| server/src/module/contact/contact.routes.ts | Adds POST endpoint writing validated submissions to the database. |
| server/src/index.ts | Wires the new contact router at /api/contact. |
| server/src/database/prisma/schema/base.prisma | Adds Prisma model to persist contact submissions. |
| client/src/module/legal/ContactPage.tsx | Implements contact form UI + submission logic. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const [success, setSuccess] = useState(false); | ||
| const [error, setError] = useState(""); | ||
|
|
||
| const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => { |
| setFormData((prev) => ({ ...prev, [e.target.name]: e.target.value })); | ||
| }; | ||
|
|
||
| const handleSubmit = async (e: React.FormEvent) => { |
| contactRouter.post("/", async (req: Request, res: Response) => { | ||
| try { | ||
| const parsed = contactSchema.safeParse(req.body); | ||
| if (!parsed.success) { | ||
| return res.status(400).json({ message: "Validation failed", errors: parsed.error.issues }); | ||
| } | ||
| await prisma.contactSubmission.create({ data: parsed.data }); | ||
| return res.status(201).json({ message: "Message sent successfully" }); | ||
| } catch { | ||
| return res.status(500).json({ message: "Failed to send message" }); | ||
| } | ||
| }); |
| name: z.string().min(1, "Name is required").max(100), | ||
| email: z.string().email("Invalid email address"), | ||
| subject: z.string().min(1, "Subject is required").max(200), | ||
| message: z.string().min(1, "Message is required").max(5000), |
| model contactSubmission { | ||
| id Int @id @default(autoincrement()) | ||
| name String | ||
| email String | ||
| subject String | ||
| message String | ||
| createdAt DateTime @default(now()) | ||
|
|
||
| @@index([createdAt]) | ||
| } |
| {error && ( | ||
| <div className="flex items-center gap-2 rounded-lg bg-red-50 dark:bg-red-900/20 px-3 py-2 text-xs text-red-600 dark:text-red-400"> | ||
| <AlertCircle className="h-3.5 w-3.5 flex-shrink-0" /> | ||
| {error} | ||
| </div> | ||
| )} | ||
|
|
||
| {success && ( | ||
| <div className="flex items-center gap-2 rounded-lg bg-emerald-50 dark:bg-emerald-900/20 px-3 py-2 text-xs text-emerald-600 dark:text-emerald-400"> | ||
| <CheckCircle className="h-3.5 w-3.5 flex-shrink-0" /> | ||
| Message sent! We'll get back to you within 24-48 hours. | ||
| </div> | ||
| )} |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@client/src/module/legal/ContactPage.tsx`:
- Around line 57-76: The inputs for name (id/name="name"), email
(id/name="email"), subject (id/name="subject") and the textarea message
(id/name="message") lack client-side maxLength limits; add maxLength attributes
on each element to mirror the backend schema caps (use the exact character
limits from the server schema) so the HTML inputs enforce the same constraints
before submit; update the JSX for <input id="name">, <input id="email">, <input
id="subject"> and <textarea id="message"> to include the appropriate maxLength
values.
- Line 81: In ContactPage JSX, replace the Tailwind v3 utility "flex-shrink-0"
with the Tailwind v4 canonical "shrink-0" on icon elements (e.g., the
AlertCircle icon and the other icon used in the same component) so the className
uses "shrink-0" instead of "flex-shrink-0"; update each icon element's className
accordingly to ensure consistent v4 utility naming.
In `@server/src/module/contact/contact.routes.ts`:
- Around line 8-19: The POST route declared with contactRouter.post("/")
currently bypasses the required route-layer middleware chain; update the route
to include the standard middleware sequence (auth, role, usageLimit, validation)
before the async handler so it uses the existing auth and role middlewares,
invokes usageLimit with the appropriate action (e.g.,
usageLimit("contactSubmission")), and runs validation via contactSchema (or the
app's validation middleware) instead of only calling contactSchema.safeParse
inside the handler; keep the handler logic (creating prisma.contactSubmission)
but remove inline validation and ensure the route uses the shared middleware
names (auth, role, usageLimit, validation) to conform with the
server/src/**/*.routes.ts guidelines.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 0b3ceabd-9aed-43c3-b1ee-7828a854a8ad
📒 Files selected for processing (5)
client/src/module/legal/ContactPage.tsxserver/src/database/prisma/schema/base.prismaserver/src/index.tsserver/src/module/contact/contact.routes.tsserver/src/module/contact/contact.validation.ts
| <input id="name" name="name" value={formData.name} onChange={handleChange} required | ||
| className="w-full rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 px-3 py-2 text-sm text-gray-900 dark:text-white placeholder-gray-400 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500" /> | ||
| </div> | ||
| <div> | ||
| <label htmlFor="email" className="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">Email</label> | ||
| <input id="email" name="email" type="email" value={formData.email} onChange={handleChange} required | ||
| className="w-full rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 px-3 py-2 text-sm text-gray-900 dark:text-white placeholder-gray-400 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500" /> | ||
| </div> | ||
| </div> | ||
|
|
||
| <div> | ||
| <label htmlFor="subject" className="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">Subject</label> | ||
| <input id="subject" name="subject" value={formData.subject} onChange={handleChange} required | ||
| className="w-full rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 px-3 py-2 text-sm text-gray-900 dark:text-white placeholder-gray-400 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500" /> | ||
| </div> | ||
|
|
||
| <div> | ||
| <label htmlFor="message" className="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">Message</label> | ||
| <textarea id="message" name="message" rows={5} value={formData.message} onChange={handleChange} required | ||
| className="w-full rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 px-3 py-2 text-sm text-gray-900 dark:text-white placeholder-gray-400 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 resize-y" /> |
There was a problem hiding this comment.
Add client-side maxLength to match backend schema constraints.
These fields can exceed server limits and fail only after submit. Mirror backend caps in the inputs/textarea for immediate feedback.
💡 Suggested patch
- <input id="name" name="name" value={formData.name} onChange={handleChange} required
+ <input id="name" name="name" value={formData.name} onChange={handleChange} required maxLength={100}
className="w-full rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 px-3 py-2 text-sm text-gray-900 dark:text-white placeholder-gray-400 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500" />
@@
- <input id="subject" name="subject" value={formData.subject} onChange={handleChange} required
+ <input id="subject" name="subject" value={formData.subject} onChange={handleChange} required maxLength={200}
className="w-full rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 px-3 py-2 text-sm text-gray-900 dark:text-white placeholder-gray-400 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500" />
@@
- <textarea id="message" name="message" rows={5} value={formData.message} onChange={handleChange} required
+ <textarea id="message" name="message" rows={5} value={formData.message} onChange={handleChange} required maxLength={5000}
className="w-full rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 px-3 py-2 text-sm text-gray-900 dark:text-white placeholder-gray-400 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 resize-y" />📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <input id="name" name="name" value={formData.name} onChange={handleChange} required | |
| className="w-full rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 px-3 py-2 text-sm text-gray-900 dark:text-white placeholder-gray-400 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500" /> | |
| </div> | |
| <div> | |
| <label htmlFor="email" className="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">Email</label> | |
| <input id="email" name="email" type="email" value={formData.email} onChange={handleChange} required | |
| className="w-full rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 px-3 py-2 text-sm text-gray-900 dark:text-white placeholder-gray-400 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500" /> | |
| </div> | |
| </div> | |
| <div> | |
| <label htmlFor="subject" className="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">Subject</label> | |
| <input id="subject" name="subject" value={formData.subject} onChange={handleChange} required | |
| className="w-full rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 px-3 py-2 text-sm text-gray-900 dark:text-white placeholder-gray-400 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500" /> | |
| </div> | |
| <div> | |
| <label htmlFor="message" className="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">Message</label> | |
| <textarea id="message" name="message" rows={5} value={formData.message} onChange={handleChange} required | |
| className="w-full rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 px-3 py-2 text-sm text-gray-900 dark:text-white placeholder-gray-400 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 resize-y" /> | |
| <input id="name" name="name" value={formData.name} onChange={handleChange} required maxLength={100} | |
| className="w-full rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 px-3 py-2 text-sm text-gray-900 dark:text-white placeholder-gray-400 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500" /> | |
| </div> | |
| <div> | |
| <label htmlFor="email" className="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">Email</label> | |
| <input id="email" name="email" type="email" value={formData.email} onChange={handleChange} required | |
| className="w-full rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 px-3 py-2 text-sm text-gray-900 dark:text-white placeholder-gray-400 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500" /> | |
| </div> | |
| </div> | |
| <div> | |
| <label htmlFor="subject" className="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">Subject</label> | |
| <input id="subject" name="subject" value={formData.subject} onChange={handleChange} required maxLength={200} | |
| className="w-full rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 px-3 py-2 text-sm text-gray-900 dark:text-white placeholder-gray-400 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500" /> | |
| </div> | |
| <div> | |
| <label htmlFor="message" className="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">Message</label> | |
| <textarea id="message" name="message" rows={5} value={formData.message} onChange={handleChange} required maxLength={5000} | |
| className="w-full rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 px-3 py-2 text-sm text-gray-900 dark:text-white placeholder-gray-400 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 resize-y" /> | |
| </div> |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/module/legal/ContactPage.tsx` around lines 57 - 76, The inputs for
name (id/name="name"), email (id/name="email"), subject (id/name="subject") and
the textarea message (id/name="message") lack client-side maxLength limits; add
maxLength attributes on each element to mirror the backend schema caps (use the
exact character limits from the server schema) so the HTML inputs enforce the
same constraints before submit; update the JSX for <input id="name">, <input
id="email">, <input id="subject"> and <textarea id="message"> to include the
appropriate maxLength values.
|
|
||
| {error && ( | ||
| <div className="flex items-center gap-2 rounded-lg bg-red-50 dark:bg-red-900/20 px-3 py-2 text-xs text-red-600 dark:text-red-400"> | ||
| <AlertCircle className="h-3.5 w-3.5 flex-shrink-0" /> |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In Tailwind CSS v4.x, is flex-shrink-0deprecated/removed in favor ofshrink-0?
💡 Result:
In Tailwind CSS v4.x, flex-shrink-0 is replaced by (and thus effectively deprecated/removed in favor of) shrink-0. Evidence: The Tailwind v4 docs list shrink-0 as the utility for flex-shrink: 0 under the “flex-shrink” utilities page [1]. A TailwindLabs pull request for v4 migration explicitly maps legacy flex-shrink-0 to the new shrink-0 class [2]. The Tailwind v3 discussion also notes the direction of renaming flex utilities from flex-grow-*/flex-shrink-* to grow-*/shrink-* [3]. So if you’re targeting v4.x, use shrink-0 instead of flex-shrink-0.
Citations:
- 1: https://tailwindcss.com/docs/flex-shrink
- 2: Migrate legacy classes to the v4 alternative tailwindlabs/tailwindcss#14643
- 3: v3.0.0-alpha.2 tailwindlabs/tailwindcss#6012
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="client/src/module/legal/ContactPage.tsx"
# Print surrounding lines for both mentioned line numbers
sed -n '60,110p' "$FILE" | cat -nRepository: Sachinchaurasiya360/InternHack
Length of output: 3686
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="client/src/module/legal/ContactPage.tsx"
sed -n '60,110p' "$FILE" | cat -nRepository: Sachinchaurasiya360/InternHack
Length of output: 3686
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="client/src/module/legal/ContactPage.tsx"
sed -n '60,110p' "$FILE" | cat -nRepository: Sachinchaurasiya360/InternHack
Length of output: 3686
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="client/src/module/legal/ContactPage.tsx"
sed -n '60,110p' "$FILE" | cat -nRepository: Sachinchaurasiya360/InternHack
Length of output: 3686
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="client/src/module/legal/ContactPage.tsx"
sed -n '60,110p' "$FILE" | cat -nRepository: Sachinchaurasiya360/InternHack
Length of output: 3686
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="client/src/module/legal/ContactPage.tsx"
sed -n '70,100p' "$FILE" | cat -nRepository: Sachinchaurasiya360/InternHack
Length of output: 2094
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="client/src/module/legal/ContactPage.tsx"
sed -n '70,95p' "$FILE" | cat -nRepository: Sachinchaurasiya360/InternHack
Length of output: 1929
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="client/src/module/legal/ContactPage.tsx"
sed -n '70,95p' "$FILE" | cat -nRepository: Sachinchaurasiya360/InternHack
Length of output: 1929
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="client/src/module/legal/ContactPage.tsx"
sed -n '70,95p' "$FILE" | cat -nRepository: Sachinchaurasiya360/InternHack
Length of output: 1929
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="client/src/module/legal/ContactPage.tsx"
sed -n '75,90p' "$FILE" | cat -nRepository: Sachinchaurasiya360/InternHack
Length of output: 1222
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="client/src/module/legal/ContactPage.tsx"
sed -n '75,90p' "$FILE" | cat -nRepository: Sachinchaurasiya360/InternHack
Length of output: 1222
Use Tailwind v4 canonical class shrink-0 instead of flex-shrink-0
client/src/module/legal/ContactPage.tsx uses flex-shrink-0 on both icons; switch to shrink-0 to match Tailwind v4 utility naming.
💡 Suggested patch
- <AlertCircle className="h-3.5 w-3.5 flex-shrink-0" />
+ <AlertCircle className="h-3.5 w-3.5 shrink-0" />
@@
- <CheckCircle className="h-3.5 w-3.5 flex-shrink-0" />
+ <CheckCircle className="h-3.5 w-3.5 shrink-0" />🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/module/legal/ContactPage.tsx` at line 81, In ContactPage JSX,
replace the Tailwind v3 utility "flex-shrink-0" with the Tailwind v4 canonical
"shrink-0" on icon elements (e.g., the AlertCircle icon and the other icon used
in the same component) so the className uses "shrink-0" instead of
"flex-shrink-0"; update each icon element's className accordingly to ensure
consistent v4 utility naming.
| contactRouter.post("/", async (req: Request, res: Response) => { | ||
| try { | ||
| const parsed = contactSchema.safeParse(req.body); | ||
| if (!parsed.success) { | ||
| return res.status(400).json({ message: "Validation failed", errors: parsed.error.issues }); | ||
| } | ||
| await prisma.contactSubmission.create({ data: parsed.data }); | ||
| return res.status(201).json({ message: "Message sent successfully" }); | ||
| } catch { | ||
| return res.status(500).json({ message: "Failed to send message" }); | ||
| } | ||
| }); |
There was a problem hiding this comment.
Route declaration skips the required middleware chain.
POST / is defined without the route-layer chain (auth/role/usage-limit/validation) mandated for *.routes.ts. Please align this route with the standard middleware structure (or apply an explicit, standardized public-route exemption pattern).
As per coding guidelines, server/src/**/*.routes.ts routes must include a middleware chain: “Routes layer must contain Express router with middleware chain (auth, role, usage-limit, validation)” and enforce daily limits via usageLimit(action).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/module/contact/contact.routes.ts` around lines 8 - 19, The POST
route declared with contactRouter.post("/") currently bypasses the required
route-layer middleware chain; update the route to include the standard
middleware sequence (auth, role, usageLimit, validation) before the async
handler so it uses the existing auth and role middlewares, invokes usageLimit
with the appropriate action (e.g., usageLimit("contactSubmission")), and runs
validation via contactSchema (or the app's validation middleware) instead of
only calling contactSchema.safeParse inside the handler; keep the handler logic
(creating prisma.contactSubmission) but remove inline validation and ensure the
route uses the shared middleware names (auth, role, usageLimit, validation) to
conform with the server/src/**/*.routes.ts guidelines.
…aurasiya360#352) Add full contact form with loading indicator: - Contact form with name, email, subject, message fields - Loading state with Loader2 spinner on submit button - Error and success feedback messages - Backend contact endpoint with schema validation - ContactSubmission Prisma model
7e1c577 to
d271e09
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
client/src/module/legal/ContactPage.tsx (2)
185-196: ⚡ Quick winUse the shared
Buttoncomponent for these new social action buttons.These are new button-like CTAs but implemented as styled anchors. Use
ButtonwithasChildfor consistency with the design system.💡 Suggested patch
- <div className="mt-10 flex flex-wrap justify-center gap-4"> - <a href="https://x.com/internhack_xyz" target="_blank" rel="noopener noreferrer" - className="px-5 py-3 rounded-2xl border border-gray-200 dark:border-gray-900 bg-white/70 dark:bg-[`#070707`] backdrop-blur-sm text-sm font-medium text-lime-500 dark:text-lime-400 hover:border-lime-400/40 dark:hover:border-lime-500/40 hover:shadow-[0_0_16px_rgba(163,230,53,0.05)] hover:bg-lime-500/5 transition-all"> - Twitter / X - </a> - <a href="https://www.linkedin.com/company/internhack" target="_blank" rel="noopener noreferrer" - className="px-5 py-3 rounded-2xl border border-gray-200 dark:border-gray-900 bg-white/70 dark:bg-[`#070707`] backdrop-blur-sm text-sm font-medium text-lime-500 dark:text-lime-400 hover:border-lime-400/40 dark:hover:border-lime-500/40 hover:shadow-[0_0_16px_rgba(163,230,53,0.05)] hover:bg-lime-500/5 transition-all"> - LinkedIn - </a> - <a href="mailto:mrsachinchaurasiya@gmail.com" - className="px-5 py-3 rounded-2xl border border-gray-200 dark:border-gray-900 bg-white/70 dark:bg-[`#070707`] backdrop-blur-sm text-sm font-medium text-lime-500 dark:text-lime-400 hover:border-lime-400/40 dark:hover:border-lime-500/40 hover:shadow-[0_0_16px_rgba(163,230,53,0.05)] hover:bg-lime-500/5 transition-all"> - Email Support - </a> + <div className="mt-10 flex flex-wrap justify-center gap-4"> + <Button asChild variant="mono" size="lg"> + <a href="https://x.com/internhack_xyz" target="_blank" rel="noopener noreferrer"> + Twitter / X + </a> + </Button> + <Button asChild variant="mono" size="lg"> + <a href="https://www.linkedin.com/company/internhack" target="_blank" rel="noopener noreferrer"> + LinkedIn + </a> + </Button> + <Button asChild variant="mono" size="lg"> + <a href="mailto:mrsachinchaurasiya@gmail.com"> + Email Support + </a> + </Button> </div>As per coding guidelines,
client/src/**/*.tsxshould use the reusableButtoncomponent for all new buttons and useasChildwhen composing with other elements.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@client/src/module/legal/ContactPage.tsx` around lines 185 - 196, Replace the plain anchor CTAs in ContactPage.tsx with the shared Button component using the asChild prop so the anchors become children of Button (preserve href, target, rel and the existing className/styling or map to Button props like variant/size if available); import Button at the top if missing and wrap each <a ...>Twitter / X</a>, <a ...>LinkedIn</a>, and <a ...>Email Support</a> inside <Button asChild>...</Button> to keep semantics and design-system consistency.
40-41: 🏗️ Heavy liftReplace arbitrary Tailwind utility values with canonical v4 classes.
This file repeatedly uses arbitrary utilities (for example
dark:bg-[#070707]andhover:shadow-[...]), which breaks the canonical Tailwind v4 class rule.As per coding guidelines,
client/src/**/*.{tsx,ts}must use canonical TailwindCSS v4 classes only.Also applies to: 60-60, 114-115, 149-150, 165-166, 186-194
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@client/src/module/legal/ContactPage.tsx` around lines 40 - 41, In ContactPage.tsx replace all arbitrary Tailwind utility values in JSX className strings (e.g., dark:bg-[`#070707`], hover:shadow-[...], and any bracketed color/box-shadow sizes) with canonical Tailwind v4 classes; find these on the root <div className=...>, the CTA/button elements, form containers and card elements inside the ContactPage component and swap to standard classes like dark:bg-black, bg-slate-900, shadow-md/shadow-lg, ring-1/ring-2, and the nearest pre-defined color/spacing utilities so no bracketed/arbitrary values remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@server/src/module/contact/contact.validation.ts`:
- Around line 4-7: The string fields in the contact schema (name, subject,
message, and email) allow whitespace-only input because .min(1) runs before
trimming; update each z.string() for name, subject, message (and email if
desired) to trim input before validations (e.g., use
z.string().trim().min(...).max(...) or z.string().transform(s =>
s.trim()).min(...).max(...)) so leading/trailing whitespace is removed prior to
the .min check and empty/whitespace-only values are rejected; ensure the same
change is applied to all relevant schema properties in contact.validation.ts.
---
Nitpick comments:
In `@client/src/module/legal/ContactPage.tsx`:
- Around line 185-196: Replace the plain anchor CTAs in ContactPage.tsx with the
shared Button component using the asChild prop so the anchors become children of
Button (preserve href, target, rel and the existing className/styling or map to
Button props like variant/size if available); import Button at the top if
missing and wrap each <a ...>Twitter / X</a>, <a ...>LinkedIn</a>, and <a
...>Email Support</a> inside <Button asChild>...</Button> to keep semantics and
design-system consistency.
- Around line 40-41: In ContactPage.tsx replace all arbitrary Tailwind utility
values in JSX className strings (e.g., dark:bg-[`#070707`], hover:shadow-[...],
and any bracketed color/box-shadow sizes) with canonical Tailwind v4 classes;
find these on the root <div className=...>, the CTA/button elements, form
containers and card elements inside the ContactPage component and swap to
standard classes like dark:bg-black, bg-slate-900, shadow-md/shadow-lg,
ring-1/ring-2, and the nearest pre-defined color/spacing utilities so no
bracketed/arbitrary values remain.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 7c69c6a4-7bb6-4a2c-9dc6-f4c5acec17d1
📒 Files selected for processing (5)
client/src/module/legal/ContactPage.tsxserver/src/database/prisma/schema/base.prismaserver/src/index.tsserver/src/module/contact/contact.routes.tsserver/src/module/contact/contact.validation.ts
| name: z.string().min(1, "Name is required").max(100), | ||
| email: z.string().email("Invalid email address"), | ||
| subject: z.string().min(1, "Subject is required").max(200), | ||
| message: z.string().min(1, "Message is required").max(5000), |
There was a problem hiding this comment.
Trim user-input strings before minimum-length checks.
Whitespace-only values currently satisfy .min(1) and get accepted as valid content.
💡 Suggested patch
export const contactSchema = z.object({
- name: z.string().min(1, "Name is required").max(100),
- email: z.string().email("Invalid email address"),
- subject: z.string().min(1, "Subject is required").max(200),
- message: z.string().min(1, "Message is required").max(5000),
+ name: z.string().trim().min(1, "Name is required").max(100),
+ email: z.string().trim().email("Invalid email address"),
+ subject: z.string().trim().min(1, "Subject is required").max(200),
+ message: z.string().trim().min(1, "Message is required").max(5000),
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| name: z.string().min(1, "Name is required").max(100), | |
| email: z.string().email("Invalid email address"), | |
| subject: z.string().min(1, "Subject is required").max(200), | |
| message: z.string().min(1, "Message is required").max(5000), | |
| export const contactSchema = z.object({ | |
| name: z.string().trim().min(1, "Name is required").max(100), | |
| email: z.string().trim().email("Invalid email address"), | |
| subject: z.string().trim().min(1, "Subject is required").max(200), | |
| message: z.string().trim().min(1, "Message is required").max(5000), | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/module/contact/contact.validation.ts` around lines 4 - 7, The
string fields in the contact schema (name, subject, message, and email) allow
whitespace-only input because .min(1) runs before trimming; update each
z.string() for name, subject, message (and email if desired) to trim input
before validations (e.g., use z.string().trim().min(...).max(...) or
z.string().transform(s => s.trim()).min(...).max(...)) so leading/trailing
whitespace is removed prior to the .min check and empty/whitespace-only values
are rejected; ensure the same change is applied to all relevant schema
properties in contact.validation.ts.
|
Merged with two fixes: (1) replaced |
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…mission (#417) - Replace bare fetch('/api/contact') with api.post('/contact') to use the shared axios instance (correct base URL, auth headers, interceptors) - Add missing Prisma migration for contactSubmission model Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Description
Fixes #352 - Submit button shows no loading indicator when form is being submitted.
Changes
Frontend (
ContactPage.tsx)Loader2spinner from lucide-react shown on submit button during submissionButtoncomponent following project patternsBackend
/api/contactPOST endpoint with Zod validationcontactSubmissionPrisma modelDatabase
contactSubmission— runnpx prisma migrate devafter merging to applySummary by CodeRabbit