diff --git a/src/features/auth/ui/RegisterForm/RegisterForm.spec.tsx b/src/features/auth/ui/RegisterForm/RegisterForm.spec.tsx
new file mode 100644
index 0000000..367301f
--- /dev/null
+++ b/src/features/auth/ui/RegisterForm/RegisterForm.spec.tsx
@@ -0,0 +1,115 @@
+import { render, screen } from "@testing-library/react";
+import { RegisterForm } from "./RegisterForm";
+import { selectAuthData, useAuthStore } from "../../model";
+import { MOCK_NEW_EMAIL, MOCK_PASSWORD } from "../../api";
+import userEvent from "@testing-library/user-event";
+
+describe("RegisterForm", () => {
+ afterEach(() => {
+ useAuthStore.getState().reset();
+ vi.restoreAllMocks();
+ });
+
+ it("should render form", () => {
+ render();
+
+ const form = screen.getByRole("form");
+ expect(form).toBeInTheDocument();
+ });
+
+ it("should disable button when form is invalid", async () => {
+ render();
+
+ const registerButton = screen.getByRole("button", { name: /register/i });
+
+ expect(registerButton).toBeDisabled();
+ });
+
+ it("should disable button when auth store status equals loading", async () => {
+ useAuthStore.setState({ status: "loading" });
+
+ render();
+
+ const emailInput = screen.getByRole("textbox", { name: /email/i });
+ const passwordInput = screen.getByLabelText(/password/i);
+ const confirmPasswordInput = screen.getByLabelText(/confirm/i);
+ const registerButton = screen.getByRole("button", { name: /register/i });
+
+ await userEvent.type(emailInput, MOCK_NEW_EMAIL);
+ await userEvent.type(passwordInput, MOCK_PASSWORD);
+ await userEvent.type(confirmPasswordInput, MOCK_PASSWORD);
+
+ expect(registerButton).toBeDisabled();
+ });
+
+ it("should disable button when password and confirm password do not match", async () => {
+ render();
+
+ const emailInput = screen.getByRole("textbox", { name: /email/i });
+ const passwordInput = screen.getByLabelText(/password/i);
+ const confirmPasswordInput = screen.getByLabelText(/confirm/i);
+ const registerButton = screen.getByRole("button", { name: /register/i });
+
+ await userEvent.type(emailInput, MOCK_NEW_EMAIL);
+ await userEvent.type(passwordInput, MOCK_PASSWORD);
+ await userEvent.type(confirmPasswordInput, MOCK_PASSWORD + "1");
+
+ expect(registerButton).toBeDisabled();
+ });
+
+ it("should enable button when password and confirm password match and auth store status isn't equal to loading", async () => {
+ useAuthStore.setState({ status: "idle" });
+
+ render();
+
+ const emailInput = screen.getByRole("textbox", { name: /email/i });
+ const passwordInput = screen.getByLabelText(/password/i);
+ const confirmPasswordInput = screen.getByLabelText(/confirm/i);
+ const registerButton = screen.getByRole("button", { name: /register/i });
+
+ await userEvent.type(emailInput, MOCK_NEW_EMAIL);
+ await userEvent.type(passwordInput, MOCK_PASSWORD);
+ await userEvent.type(confirmPasswordInput, MOCK_PASSWORD);
+
+ expect(registerButton).not.toBeDisabled();
+ });
+
+ it("should change email value in auth store when user types", async () => {
+ render();
+ const emailInput = screen.getByRole("textbox", { name: /email/i });
+ await userEvent.type(emailInput, MOCK_NEW_EMAIL);
+
+ const storeEmailValue = selectAuthData(useAuthStore.getState()).email;
+
+ expect(storeEmailValue).toBe(MOCK_NEW_EMAIL);
+ });
+
+ it("should change password value in auth store when user types", async () => {
+ render();
+ const passwordInput = screen.getByLabelText(/password/i);
+ await userEvent.type(passwordInput, MOCK_PASSWORD);
+
+ const storePasswordValue = selectAuthData(useAuthStore.getState()).password;
+
+ expect(storePasswordValue).toBe(MOCK_PASSWORD);
+ });
+
+ it("should perform register api call when user clicks register button", async () => {
+ const registerSpy = vi.spyOn(useAuthStore.getState(), "register");
+ useAuthStore.setState({ status: "idle" });
+
+ render();
+
+ const emailInput = screen.getByRole("textbox", { name: /email/i });
+ const passwordInput = screen.getByLabelText(/password/i);
+ const confirmPasswordInput = screen.getByLabelText(/confirm/i);
+ const registerButton = screen.getByRole("button", { name: /register/i });
+
+ await userEvent.type(emailInput, MOCK_NEW_EMAIL);
+ await userEvent.type(passwordInput, MOCK_PASSWORD);
+ await userEvent.type(confirmPasswordInput, MOCK_PASSWORD);
+ await userEvent.click(registerButton);
+
+ expect(registerSpy).toHaveBeenCalled();
+ });
+});
diff --git a/src/features/auth/ui/RegisterForm/RegisterForm.stories.ts b/src/features/auth/ui/RegisterForm/RegisterForm.stories.ts
new file mode 100644
index 0000000..d9e36b6
--- /dev/null
+++ b/src/features/auth/ui/RegisterForm/RegisterForm.stories.ts
@@ -0,0 +1,12 @@
+import type { Meta, StoryObj } from "@storybook/react-vite";
+import { RegisterForm } from "./RegisterForm";
+
+const meta: Meta = {
+ component: RegisterForm,
+ title: "features/auth/RegisterForm",
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {};
diff --git a/src/features/auth/ui/RegisterForm/RegisterForm.tsx b/src/features/auth/ui/RegisterForm/RegisterForm.tsx
index 65b0a22..f12da9f 100644
--- a/src/features/auth/ui/RegisterForm/RegisterForm.tsx
+++ b/src/features/auth/ui/RegisterForm/RegisterForm.tsx
@@ -1,5 +1,64 @@
+import { useState, type ChangeEvent, type SubmitEvent } from "react";
+import {
+ selectFormValid,
+ selectStatusIsLoading,
+ useAuthStore,
+} from "../../model";
+
+/**
+ * Register form component
+ */
export function RegisterForm() {
+ const { formData, setEmail, setPassword, register } = useAuthStore();
+
+ const [confirmedPassword, setConfirmedPassword] = useState("");
+
+ const formValid = useAuthStore(selectFormValid);
+ const isLoading = useAuthStore(selectStatusIsLoading);
+
+ const passwordMatch = formData?.password?.value === confirmedPassword;
+ const disabled = isLoading || !formValid || !passwordMatch;
+
+ const handleEmailChange = (e: ChangeEvent) => {
+ setEmail(e.target.value);
+ };
+
+ const handlePasswordChange = (e: ChangeEvent) => {
+ setPassword(e.target.value);
+ };
+
+ const handleConfirmChange = (e: ChangeEvent) => {
+ setConfirmedPassword(e.target.value);
+ };
+
+ const handleSubmit = (e: SubmitEvent) => {
+ e.preventDefault();
+ register();
+ };
+
return (
-
+
);
}