Full-stack Next.js 15 application with: - Landing page with marketing components (Hero, Testimonials, Pricing, FAQ) - Multi-step candidature form with API route - Stripe Checkout integration (subscription + webhooks) - Supabase Auth (login/register) with middleware protection - Dashboard with progress tracking and module system - Formations pages with completion tracking - Profile management with password change - Database schema with RLS policies - Resend email integration for transactional emails Stack: Next.js 15, TypeScript, Tailwind CSS v4, Supabase, Stripe, Resend https://claude.ai/code/session_01H2aRGDaKgarPvhay2HxN6Y
78 lines
2.2 KiB
TypeScript
78 lines
2.2 KiB
TypeScript
"use client";
|
|
|
|
import { cn } from "@/lib/utils";
|
|
import { InputHTMLAttributes, TextareaHTMLAttributes, forwardRef } from "react";
|
|
|
|
interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
|
|
label?: string;
|
|
error?: string;
|
|
}
|
|
|
|
const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
({ className, label, error, id, ...props }, ref) => {
|
|
return (
|
|
<div className="space-y-1.5">
|
|
{label && (
|
|
<label
|
|
htmlFor={id}
|
|
className="block text-sm font-medium text-white/80"
|
|
>
|
|
{label}
|
|
</label>
|
|
)}
|
|
<input
|
|
ref={ref}
|
|
id={id}
|
|
className={cn(
|
|
"w-full px-4 py-3 bg-dark-lighter border border-dark-border rounded-[12px] text-white placeholder:text-white/30 focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-colors",
|
|
error && "border-error focus:border-error focus:ring-error",
|
|
className
|
|
)}
|
|
{...props}
|
|
/>
|
|
{error && <p className="text-sm text-error">{error}</p>}
|
|
</div>
|
|
);
|
|
}
|
|
);
|
|
|
|
Input.displayName = "Input";
|
|
|
|
// Textarea séparé
|
|
interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {
|
|
label?: string;
|
|
error?: string;
|
|
}
|
|
|
|
const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
({ className, label, error, id, ...props }, ref) => {
|
|
return (
|
|
<div className="space-y-1.5">
|
|
{label && (
|
|
<label
|
|
htmlFor={id}
|
|
className="block text-sm font-medium text-white/80"
|
|
>
|
|
{label}
|
|
</label>
|
|
)}
|
|
<textarea
|
|
ref={ref}
|
|
id={id}
|
|
className={cn(
|
|
"w-full px-4 py-3 bg-dark-lighter border border-dark-border rounded-[12px] text-white placeholder:text-white/30 focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-colors resize-none",
|
|
error && "border-error focus:border-error focus:ring-error",
|
|
className
|
|
)}
|
|
{...props}
|
|
/>
|
|
{error && <p className="text-sm text-error">{error}</p>}
|
|
</div>
|
|
);
|
|
}
|
|
);
|
|
|
|
Textarea.displayName = "Textarea";
|
|
|
|
export { Input as default, Textarea };
|