Compare commits
11 Commits
87511398fb
...
feature/fr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f71ed9285b | ||
|
|
6516172a6b | ||
|
|
f98ccf468c | ||
|
|
4d95159c4f | ||
|
|
e7ac79049d | ||
|
|
85763e568f | ||
|
|
98e3133d88 | ||
|
|
8d283064a0 | ||
|
|
a6f4b993dd | ||
|
|
3d7eb850ec | ||
|
|
69d026afce |
@@ -2,17 +2,7 @@ import { api as baseApi, useTokenStore } from "shared/api";
|
|||||||
|
|
||||||
export const authHttpClient = baseApi.extend({
|
export const authHttpClient = baseApi.extend({
|
||||||
hooks: {
|
hooks: {
|
||||||
beforeRequest: [
|
beforeRequest: [],
|
||||||
(request) => {
|
|
||||||
const token = useTokenStore.getState().accessToken;
|
|
||||||
|
|
||||||
if (token) {
|
|
||||||
request.headers.set("Authorization", `Bearer ${token}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return request;
|
|
||||||
},
|
|
||||||
],
|
|
||||||
afterResponse: [
|
afterResponse: [
|
||||||
async (request, options, response) => {
|
async (request, options, response) => {
|
||||||
if (response.status !== 401) {
|
if (response.status !== 401) {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
export * from "./selectFormValid/selectFormValid";
|
export * from "./selectFormValid/selectFormValid";
|
||||||
export * from "./selectAuthData/selectAuthData";
|
export * from "./selectAuthData/selectAuthData";
|
||||||
export * from "./selectStatusIsLoading/selectStatusIsLoading";
|
export * from "./selectStatusIsLoading/selectStatusIsLoading";
|
||||||
|
export * from "./selectEmail/selectEmail";
|
||||||
|
export * from "./selectPassword/selectPassword";
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import type { AuthStore } from "../../types/store";
|
||||||
|
|
||||||
|
export const selectEmail = (state: AuthStore) =>
|
||||||
|
state.formData?.email?.value ?? "";
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import type { AuthStore } from "../../types/store";
|
||||||
|
|
||||||
|
export const selectPassword = (state: AuthStore) =>
|
||||||
|
state.formData?.password?.value ?? "";
|
||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
MOCK_NEW_EMAIL,
|
MOCK_NEW_EMAIL,
|
||||||
MOCK_PASSWORD,
|
MOCK_PASSWORD,
|
||||||
} from "../../../api/calls/mocks";
|
} from "../../../api/calls/mocks";
|
||||||
|
import { useTokenStore } from "shared/api";
|
||||||
|
|
||||||
const server = setupServer(
|
const server = setupServer(
|
||||||
apiCalls.loginMock,
|
apiCalls.loginMock,
|
||||||
@@ -21,6 +22,7 @@ describe("authStore", () => {
|
|||||||
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
|
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
useAuthStore.getState().reset();
|
useAuthStore.getState().reset();
|
||||||
|
useTokenStore.getState().reset();
|
||||||
server.resetHandlers();
|
server.resetHandlers();
|
||||||
});
|
});
|
||||||
afterAll(() => server.close());
|
afterAll(() => server.close());
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { create } from "zustand";
|
|||||||
import type { AuthStore, AuthStoreState } from "../../types/store";
|
import type { AuthStore, AuthStoreState } from "../../types/store";
|
||||||
import { login, logout, register } from "../../../api";
|
import { login, logout, register } from "../../../api";
|
||||||
import { callApi } from "shared/utils";
|
import { callApi } from "shared/utils";
|
||||||
import { UNEXPECTED_ERROR_MESSAGE } from "shared/api";
|
import { UNEXPECTED_ERROR_MESSAGE, useTokenStore } from "shared/api";
|
||||||
import { selectAuthData, selectFormValid } from "../../selectors";
|
import { selectAuthData, selectFormValid } from "../../selectors";
|
||||||
import { validateEmail, validatePassword } from "../../../lib";
|
import { validateEmail, validatePassword } from "../../../lib";
|
||||||
|
|
||||||
@@ -68,6 +68,7 @@ export const useAuthStore = create<AuthStore>()((set, get) => ({
|
|||||||
user: responseData?.user,
|
user: responseData?.user,
|
||||||
error: null,
|
error: null,
|
||||||
});
|
});
|
||||||
|
useTokenStore.setState({ accessToken: responseData?.accessToken });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
set({
|
set({
|
||||||
@@ -108,6 +109,7 @@ export const useAuthStore = create<AuthStore>()((set, get) => ({
|
|||||||
user: responseData?.user,
|
user: responseData?.user,
|
||||||
error: null,
|
error: null,
|
||||||
});
|
});
|
||||||
|
useTokenStore.setState({ accessToken: responseData?.accessToken });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
set({
|
set({
|
||||||
@@ -138,7 +140,7 @@ export const useAuthStore = create<AuthStore>()((set, get) => ({
|
|||||||
error: null,
|
error: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
// useTokenStore.setState({ accessToken: null });
|
useTokenStore.setState({ accessToken: null });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
set({ error: new Error(UNEXPECTED_ERROR_MESSAGE) });
|
set({ error: new Error(UNEXPECTED_ERROR_MESSAGE) });
|
||||||
|
|||||||
14
src/features/auth/ui/DefaultButton/DefaultButton.tsx
Normal file
14
src/features/auth/ui/DefaultButton/DefaultButton.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import type { ButtonHTMLAttributes } from "react";
|
||||||
|
|
||||||
|
export type ButtonAttributes = ButtonHTMLAttributes<HTMLButtonElement>;
|
||||||
|
|
||||||
|
export function DefaultButtonComponent({
|
||||||
|
disabled,
|
||||||
|
children,
|
||||||
|
}: ButtonAttributes) {
|
||||||
|
return (
|
||||||
|
<button type="submit" disabled={disabled}>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
19
src/features/auth/ui/DefaultInput/DefaultInput.tsx
Normal file
19
src/features/auth/ui/DefaultInput/DefaultInput.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import type { InputHTMLAttributes } from "react";
|
||||||
|
|
||||||
|
export type InputAttributes = InputHTMLAttributes<HTMLInputElement>;
|
||||||
|
|
||||||
|
export function DefaultInputComponent({
|
||||||
|
type,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
["aria-label"]: ariaLabel,
|
||||||
|
}: InputAttributes) {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
type={type}
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
aria-label={ariaLabel}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,24 +1,26 @@
|
|||||||
import { selectAuthData, useAuthStore } from "../../model";
|
import { selectAuthData, useAuthStore } from "../../model";
|
||||||
import { render, screen } from "@testing-library/react";
|
import { act, render, screen } from "@testing-library/react";
|
||||||
import userEvent from "@testing-library/user-event";
|
import userEvent from "@testing-library/user-event";
|
||||||
import { LoginForm } from "./LoginForm";
|
import { LoginForm } from "./LoginForm";
|
||||||
import { MOCK_EMAIL, MOCK_PASSWORD } from "../../api";
|
import { MOCK_EMAIL, MOCK_PASSWORD } from "../../api";
|
||||||
|
import { useTokenStore } from "shared/api";
|
||||||
|
|
||||||
describe("LoginForm", () => {
|
describe("LoginForm", () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
useAuthStore.getState().reset();
|
useAuthStore.getState().reset();
|
||||||
|
useTokenStore.getState().reset();
|
||||||
vi.restoreAllMocks();
|
vi.restoreAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render form", () => {
|
it("should render form", () => {
|
||||||
render(<LoginForm />);
|
act(() => render(<LoginForm />));
|
||||||
const form = screen.getByRole("form");
|
const form = screen.getByRole("form");
|
||||||
|
|
||||||
expect(form).toBeInTheDocument();
|
expect(form).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("disables submit button when form is invalid", () => {
|
it("disables submit button when form is invalid", () => {
|
||||||
render(<LoginForm />);
|
act(() => render(<LoginForm />));
|
||||||
const loginButton = screen.getByRole("button", { name: /login/i });
|
const loginButton = screen.getByRole("button", { name: /login/i });
|
||||||
|
|
||||||
expect(loginButton).toBeDisabled();
|
expect(loginButton).toBeDisabled();
|
||||||
@@ -27,7 +29,7 @@ describe("LoginForm", () => {
|
|||||||
it("disables submit button when auth store status equals loading", async () => {
|
it("disables submit button when auth store status equals loading", async () => {
|
||||||
useAuthStore.setState({ status: "loading" });
|
useAuthStore.setState({ status: "loading" });
|
||||||
|
|
||||||
render(<LoginForm />);
|
act(() => render(<LoginForm />));
|
||||||
|
|
||||||
const emailInput = screen.getByRole("textbox", { name: /email/i });
|
const emailInput = screen.getByRole("textbox", { name: /email/i });
|
||||||
const passwordInput = screen.getByLabelText(/password/i);
|
const passwordInput = screen.getByLabelText(/password/i);
|
||||||
@@ -41,7 +43,7 @@ describe("LoginForm", () => {
|
|||||||
it("enables submit button when form is valid and auth store status isn't equal to loading", async () => {
|
it("enables submit button when form is valid and auth store status isn't equal to loading", async () => {
|
||||||
useAuthStore.setState({ status: "idle" });
|
useAuthStore.setState({ status: "idle" });
|
||||||
|
|
||||||
render(<LoginForm />);
|
act(() => render(<LoginForm />));
|
||||||
|
|
||||||
const emailInput = screen.getByRole("textbox", { name: /email/i });
|
const emailInput = screen.getByRole("textbox", { name: /email/i });
|
||||||
const passwordInput = screen.getByLabelText(/password/i);
|
const passwordInput = screen.getByLabelText(/password/i);
|
||||||
@@ -53,7 +55,7 @@ describe("LoginForm", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("updates email value in auth store when user types", async () => {
|
it("updates email value in auth store when user types", async () => {
|
||||||
render(<LoginForm />);
|
act(() => render(<LoginForm />));
|
||||||
const emailInput = screen.getByRole("textbox", { name: /email/i });
|
const emailInput = screen.getByRole("textbox", { name: /email/i });
|
||||||
await userEvent.type(emailInput, MOCK_EMAIL);
|
await userEvent.type(emailInput, MOCK_EMAIL);
|
||||||
|
|
||||||
@@ -62,7 +64,7 @@ describe("LoginForm", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("updates password value in auth store when user types", async () => {
|
it("updates password value in auth store when user types", async () => {
|
||||||
render(<LoginForm />);
|
act(() => render(<LoginForm />));
|
||||||
const passwordInput = screen.getByLabelText(/password/i);
|
const passwordInput = screen.getByLabelText(/password/i);
|
||||||
await userEvent.type(passwordInput, MOCK_PASSWORD);
|
await userEvent.type(passwordInput, MOCK_PASSWORD);
|
||||||
|
|
||||||
@@ -74,7 +76,7 @@ describe("LoginForm", () => {
|
|||||||
const loginSpy = vi.spyOn(useAuthStore.getState(), "login");
|
const loginSpy = vi.spyOn(useAuthStore.getState(), "login");
|
||||||
useAuthStore.setState({ status: "idle" });
|
useAuthStore.setState({ status: "idle" });
|
||||||
|
|
||||||
render(<LoginForm />);
|
act(() => render(<LoginForm />));
|
||||||
|
|
||||||
const emailInput = screen.getByRole("textbox", { name: /email/i });
|
const emailInput = screen.getByRole("textbox", { name: /email/i });
|
||||||
const passwordInput = screen.getByLabelText(/password/i);
|
const passwordInput = screen.getByLabelText(/password/i);
|
||||||
|
|||||||
@@ -1,16 +1,37 @@
|
|||||||
import {
|
import {
|
||||||
|
selectEmail,
|
||||||
selectFormValid,
|
selectFormValid,
|
||||||
|
selectPassword,
|
||||||
selectStatusIsLoading,
|
selectStatusIsLoading,
|
||||||
useAuthStore,
|
useAuthStore,
|
||||||
} from "../../model";
|
} from "../../model";
|
||||||
import type { SubmitEvent, ChangeEvent } from "react";
|
import {
|
||||||
|
type SubmitEvent,
|
||||||
|
type ChangeEvent,
|
||||||
|
cloneElement,
|
||||||
|
type ReactElement,
|
||||||
|
} from "react";
|
||||||
|
import {
|
||||||
|
DefaultButtonComponent,
|
||||||
|
type ButtonAttributes,
|
||||||
|
} from "../DefaultButton/DefaultButton";
|
||||||
|
import {
|
||||||
|
DefaultInputComponent,
|
||||||
|
type InputAttributes,
|
||||||
|
} from "../DefaultInput/DefaultInput";
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
InputComponent?: ReactElement<InputAttributes>;
|
||||||
|
ButtonComponent?: ReactElement<ButtonAttributes>;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Login form component
|
* Login form component
|
||||||
*/
|
*/
|
||||||
export function LoginForm() {
|
export function LoginForm({ InputComponent, ButtonComponent }: Props) {
|
||||||
const { formData, setEmail, setPassword, login } = useAuthStore();
|
const { setEmail, setPassword, login } = useAuthStore();
|
||||||
|
const email = useAuthStore(selectEmail);
|
||||||
|
const password = useAuthStore(selectPassword);
|
||||||
const formValid = useAuthStore(selectFormValid);
|
const formValid = useAuthStore(selectFormValid);
|
||||||
const isLoading = useAuthStore(selectStatusIsLoading);
|
const isLoading = useAuthStore(selectStatusIsLoading);
|
||||||
|
|
||||||
@@ -31,21 +52,47 @@ export function LoginForm() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<form aria-label="Login form" onSubmit={handleSubmit}>
|
<form aria-label="Login form" onSubmit={handleSubmit}>
|
||||||
<input
|
{/* EMAIL */}
|
||||||
type="email"
|
{InputComponent ? (
|
||||||
aria-label="Email"
|
cloneElement(InputComponent, {
|
||||||
value={formData?.email?.value ?? ""}
|
value: email,
|
||||||
onChange={handleEmailChange}
|
onChange: handleEmailChange,
|
||||||
/>
|
"aria-label": "Email",
|
||||||
<input
|
})
|
||||||
type="password"
|
) : (
|
||||||
aria-label="Password"
|
<DefaultInputComponent
|
||||||
value={formData?.password?.value ?? ""}
|
type="email"
|
||||||
onChange={handlePasswordChange}
|
value={email}
|
||||||
/>
|
onChange={handleEmailChange}
|
||||||
<button type="submit" disabled={disabled}>
|
aria-label="Email"
|
||||||
Login
|
/>
|
||||||
</button>
|
)}
|
||||||
|
{/* PASSWORD */}
|
||||||
|
{InputComponent ? (
|
||||||
|
cloneElement(InputComponent, {
|
||||||
|
value: password,
|
||||||
|
onChange: handlePasswordChange,
|
||||||
|
"aria-label": "Password",
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<DefaultInputComponent
|
||||||
|
type="password"
|
||||||
|
value={password}
|
||||||
|
onChange={handlePasswordChange}
|
||||||
|
aria-label="Password"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{/* BUTTON */}
|
||||||
|
{ButtonComponent ? (
|
||||||
|
cloneElement(ButtonComponent, {
|
||||||
|
type: "submit",
|
||||||
|
disabled: disabled,
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<DefaultButtonComponent disabled={disabled}>
|
||||||
|
Login
|
||||||
|
</DefaultButtonComponent>
|
||||||
|
)}
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,26 @@
|
|||||||
import { render, screen } from "@testing-library/react";
|
import { act, render, screen } from "@testing-library/react";
|
||||||
import { RegisterForm } from "./RegisterForm";
|
import { RegisterForm } from "./RegisterForm";
|
||||||
import { selectAuthData, useAuthStore } from "../../model";
|
import { selectAuthData, useAuthStore } from "../../model";
|
||||||
import { MOCK_NEW_EMAIL, MOCK_PASSWORD } from "../../api";
|
import { MOCK_NEW_EMAIL, MOCK_PASSWORD } from "../../api";
|
||||||
import userEvent from "@testing-library/user-event";
|
import userEvent from "@testing-library/user-event";
|
||||||
|
import { useTokenStore } from "shared/api";
|
||||||
|
|
||||||
describe("RegisterForm", () => {
|
describe("RegisterForm", () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
useAuthStore.getState().reset();
|
useAuthStore.getState().reset();
|
||||||
|
useTokenStore.getState().reset();
|
||||||
vi.restoreAllMocks();
|
vi.restoreAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render form", () => {
|
it("should render form", () => {
|
||||||
render(<RegisterForm />);
|
act(() => render(<RegisterForm />));
|
||||||
|
|
||||||
const form = screen.getByRole("form");
|
const form = screen.getByRole("form");
|
||||||
expect(form).toBeInTheDocument();
|
expect(form).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should disable button when form is invalid", async () => {
|
it("should disable button when form is invalid", async () => {
|
||||||
render(<RegisterForm />);
|
act(() => render(<RegisterForm />));
|
||||||
|
|
||||||
const registerButton = screen.getByRole("button", { name: /register/i });
|
const registerButton = screen.getByRole("button", { name: /register/i });
|
||||||
|
|
||||||
@@ -28,7 +30,7 @@ describe("RegisterForm", () => {
|
|||||||
it("should disable button when auth store status equals loading", async () => {
|
it("should disable button when auth store status equals loading", async () => {
|
||||||
useAuthStore.setState({ status: "loading" });
|
useAuthStore.setState({ status: "loading" });
|
||||||
|
|
||||||
render(<RegisterForm />);
|
act(() => render(<RegisterForm />));
|
||||||
|
|
||||||
const emailInput = screen.getByRole("textbox", { name: /email/i });
|
const emailInput = screen.getByRole("textbox", { name: /email/i });
|
||||||
const passwordInput = screen.getByLabelText(/password/i);
|
const passwordInput = screen.getByLabelText(/password/i);
|
||||||
@@ -43,7 +45,7 @@ describe("RegisterForm", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should disable button when password and confirm password do not match", async () => {
|
it("should disable button when password and confirm password do not match", async () => {
|
||||||
render(<RegisterForm />);
|
act(() => render(<RegisterForm />));
|
||||||
|
|
||||||
const emailInput = screen.getByRole("textbox", { name: /email/i });
|
const emailInput = screen.getByRole("textbox", { name: /email/i });
|
||||||
const passwordInput = screen.getByLabelText(/password/i);
|
const passwordInput = screen.getByLabelText(/password/i);
|
||||||
@@ -60,7 +62,7 @@ describe("RegisterForm", () => {
|
|||||||
it("should enable button when password and confirm password match and auth store status isn't equal to loading", async () => {
|
it("should enable button when password and confirm password match and auth store status isn't equal to loading", async () => {
|
||||||
useAuthStore.setState({ status: "idle" });
|
useAuthStore.setState({ status: "idle" });
|
||||||
|
|
||||||
render(<RegisterForm />);
|
act(() => render(<RegisterForm />));
|
||||||
|
|
||||||
const emailInput = screen.getByRole("textbox", { name: /email/i });
|
const emailInput = screen.getByRole("textbox", { name: /email/i });
|
||||||
const passwordInput = screen.getByLabelText(/password/i);
|
const passwordInput = screen.getByLabelText(/password/i);
|
||||||
@@ -75,7 +77,7 @@ describe("RegisterForm", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should change email value in auth store when user types", async () => {
|
it("should change email value in auth store when user types", async () => {
|
||||||
render(<RegisterForm />);
|
act(() => render(<RegisterForm />));
|
||||||
const emailInput = screen.getByRole("textbox", { name: /email/i });
|
const emailInput = screen.getByRole("textbox", { name: /email/i });
|
||||||
await userEvent.type(emailInput, MOCK_NEW_EMAIL);
|
await userEvent.type(emailInput, MOCK_NEW_EMAIL);
|
||||||
|
|
||||||
@@ -85,7 +87,7 @@ describe("RegisterForm", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should change password value in auth store when user types", async () => {
|
it("should change password value in auth store when user types", async () => {
|
||||||
render(<RegisterForm />);
|
act(() => render(<RegisterForm />));
|
||||||
const passwordInput = screen.getByLabelText(/password/i);
|
const passwordInput = screen.getByLabelText(/password/i);
|
||||||
await userEvent.type(passwordInput, MOCK_PASSWORD);
|
await userEvent.type(passwordInput, MOCK_PASSWORD);
|
||||||
|
|
||||||
@@ -98,7 +100,7 @@ describe("RegisterForm", () => {
|
|||||||
const registerSpy = vi.spyOn(useAuthStore.getState(), "register");
|
const registerSpy = vi.spyOn(useAuthStore.getState(), "register");
|
||||||
useAuthStore.setState({ status: "idle" });
|
useAuthStore.setState({ status: "idle" });
|
||||||
|
|
||||||
render(<RegisterForm />);
|
act(() => render(<RegisterForm />));
|
||||||
|
|
||||||
const emailInput = screen.getByRole("textbox", { name: /email/i });
|
const emailInput = screen.getByRole("textbox", { name: /email/i });
|
||||||
const passwordInput = screen.getByLabelText(/password/i);
|
const passwordInput = screen.getByLabelText(/password/i);
|
||||||
|
|||||||
@@ -1,22 +1,45 @@
|
|||||||
import { useState, type ChangeEvent, type SubmitEvent } from "react";
|
|
||||||
import {
|
import {
|
||||||
|
cloneElement,
|
||||||
|
useState,
|
||||||
|
type ChangeEvent,
|
||||||
|
type ReactElement,
|
||||||
|
type SubmitEvent,
|
||||||
|
} from "react";
|
||||||
|
import {
|
||||||
|
selectEmail,
|
||||||
selectFormValid,
|
selectFormValid,
|
||||||
|
selectPassword,
|
||||||
selectStatusIsLoading,
|
selectStatusIsLoading,
|
||||||
useAuthStore,
|
useAuthStore,
|
||||||
} from "../../model";
|
} from "../../model";
|
||||||
|
import {
|
||||||
|
DefaultInputComponent,
|
||||||
|
type InputAttributes,
|
||||||
|
} from "../DefaultInput/DefaultInput";
|
||||||
|
import {
|
||||||
|
DefaultButtonComponent,
|
||||||
|
type ButtonAttributes,
|
||||||
|
} from "../DefaultButton/DefaultButton";
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
InputComponent?: ReactElement<InputAttributes>;
|
||||||
|
ButtonComponent?: ReactElement<ButtonAttributes>;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register form component
|
* Register form component
|
||||||
*/
|
*/
|
||||||
export function RegisterForm() {
|
export function RegisterForm({ InputComponent, ButtonComponent }: Props) {
|
||||||
const { formData, setEmail, setPassword, register } = useAuthStore();
|
const { setEmail, setPassword, register } = useAuthStore();
|
||||||
|
const email = useAuthStore(selectEmail);
|
||||||
|
const password = useAuthStore(selectPassword);
|
||||||
|
|
||||||
const [confirmedPassword, setConfirmedPassword] = useState("");
|
const [confirmedPassword, setConfirmedPassword] = useState("");
|
||||||
|
|
||||||
const formValid = useAuthStore(selectFormValid);
|
const formValid = useAuthStore(selectFormValid);
|
||||||
const isLoading = useAuthStore(selectStatusIsLoading);
|
const isLoading = useAuthStore(selectStatusIsLoading);
|
||||||
|
|
||||||
const passwordMatch = formData?.password?.value === confirmedPassword;
|
const passwordMatch = password === confirmedPassword;
|
||||||
const disabled = isLoading || !formValid || !passwordMatch;
|
const disabled = isLoading || !formValid || !passwordMatch;
|
||||||
|
|
||||||
const handleEmailChange = (e: ChangeEvent<HTMLInputElement>) => {
|
const handleEmailChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
@@ -38,27 +61,63 @@ export function RegisterForm() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<form aria-label="Register Form" onSubmit={handleSubmit}>
|
<form aria-label="Register Form" onSubmit={handleSubmit}>
|
||||||
<input
|
{/* EMAIL */}
|
||||||
type="email"
|
{InputComponent ? (
|
||||||
aria-label="Email"
|
cloneElement(InputComponent, {
|
||||||
value={formData?.email?.value ?? ""}
|
value: email,
|
||||||
onChange={handleEmailChange}
|
onChange: handleEmailChange,
|
||||||
/>
|
"aria-label": "Email",
|
||||||
<input
|
})
|
||||||
type="password"
|
) : (
|
||||||
aria-label="Password"
|
<DefaultInputComponent
|
||||||
value={formData?.password?.value ?? ""}
|
type="email"
|
||||||
onChange={handlePasswordChange}
|
value={email}
|
||||||
/>
|
onChange={handleEmailChange}
|
||||||
<input
|
aria-label="Email"
|
||||||
type="password"
|
/>
|
||||||
aria-label="Confirm"
|
)}
|
||||||
value={confirmedPassword}
|
|
||||||
onChange={handleConfirmChange}
|
{/* PASSWORD */}
|
||||||
/>
|
{InputComponent ? (
|
||||||
<button type="submit" aria-label="Register" disabled={disabled}>
|
cloneElement(InputComponent, {
|
||||||
Register
|
value: password,
|
||||||
</button>
|
onChange: handlePasswordChange,
|
||||||
|
"aria-label": "Password",
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<DefaultInputComponent
|
||||||
|
type="password"
|
||||||
|
value={password}
|
||||||
|
onChange={handlePasswordChange}
|
||||||
|
aria-label="Password"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{/* PASSWORD */}
|
||||||
|
{InputComponent ? (
|
||||||
|
cloneElement(InputComponent, {
|
||||||
|
value: confirmedPassword,
|
||||||
|
onChange: handleConfirmChange,
|
||||||
|
"aria-label": "Confirm",
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<DefaultInputComponent
|
||||||
|
type="password"
|
||||||
|
value={confirmedPassword}
|
||||||
|
onChange={handleConfirmChange}
|
||||||
|
aria-label="Confirm"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{/* BUTTON */}
|
||||||
|
{ButtonComponent ? (
|
||||||
|
cloneElement(ButtonComponent, {
|
||||||
|
type: "submit",
|
||||||
|
disabled: disabled,
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<DefaultButtonComponent disabled={disabled}>
|
||||||
|
Register
|
||||||
|
</DefaultButtonComponent>
|
||||||
|
)}
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user