+ );
+}
diff --git a/frontend/src/sign-up/SignUpButton.tsx b/frontend/src/sign-up/SignUpButton.tsx
new file mode 100644
index 0000000..2cc2f7e
--- /dev/null
+++ b/frontend/src/sign-up/SignUpButton.tsx
@@ -0,0 +1,24 @@
+type SignUpButtonProps = {
+ disabled?: boolean;
+};
+
+/**
+ * Primary Sign Up submit button.
+ * When requirements are met: primary-900 (#E16F39), clickable.
+ * When not met: primary-700 (current/inactive), disabled.
+ */
+export default function SignUpButton({ disabled }: SignUpButtonProps) {
+ return (
+
+ );
+}
diff --git a/frontend/src/sign-up/SignUpForm.tsx b/frontend/src/sign-up/SignUpForm.tsx
new file mode 100644
index 0000000..e86351c
--- /dev/null
+++ b/frontend/src/sign-up/SignUpForm.tsx
@@ -0,0 +1,122 @@
+import InputField from "./InputField";
+import PasswordField from "./PasswordField";
+import PasswordRequirements from "./PasswordRequirements";
+import SignUpButton from "./SignUpButton";
+import LoginPrompt from "./LoginPrompt";
+
+export type SignUpFormValues = {
+ firstName: string;
+ lastName: string;
+ email: string;
+ password: string;
+ passwordRe: string;
+};
+
+export type SignUpFormProps = {
+ values: SignUpFormValues;
+ onChange: (field: keyof SignUpFormValues, value: string) => void;
+ onSubmit: (e: React.FormEvent) => void;
+ error?: { state: boolean; message: string; item: string };
+ /** When true, Sign Up button uses primary-900 and is clickable; when false, primary-700 and disabled. */
+ passwordRequirementsMet?: boolean;
+ /** When true, all required fields (first name, last name, email, password, re-enter password) are filled. */
+ allFieldsFilled?: boolean;
+ /** When true, password and re-enter password are exactly the same. */
+ passwordsMatch?: boolean;
+};
+
+/**
+ * Sign Up form layout: title, fields, requirements, button, login prompt.
+ * Composes sign-up subcomponents; state and submit logic live in the parent.
+ */
+export default function SignUpForm({
+ values,
+ onChange,
+ onSubmit,
+ error,
+ passwordRequirementsMet = false,
+ allFieldsFilled = false,
+ passwordsMatch = false,
+}: SignUpFormProps) {
+ const hasError = error?.state ?? false;
+ const errorItem = error?.item ?? "";
+ const canSubmit =
+ passwordRequirementsMet && allFieldsFilled && passwordsMatch;
+
+ return (
+
+
Sign Up
+
+
+
+ );
+}
diff --git a/frontend/src/sign-up/index.ts b/frontend/src/sign-up/index.ts
new file mode 100644
index 0000000..5b3ec8b
--- /dev/null
+++ b/frontend/src/sign-up/index.ts
@@ -0,0 +1,8 @@
+export { default as BrandingPanel } from "./BrandingPanel";
+export { default as InputField } from "./InputField";
+export { default as LoginPrompt } from "./LoginPrompt";
+export { default as PasswordField } from "./PasswordField";
+export { default as PasswordRequirements } from "./PasswordRequirements";
+export { default as SignUpButton } from "./SignUpButton";
+export { default as SignUpForm } from "./SignUpForm";
+export type { SignUpFormProps, SignUpFormValues } from "./SignUpForm";