aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/client/pages
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2025-12-07 03:24:42 +0900
committernsfisis <nsfisis@gmail.com>2025-12-07 03:24:46 +0900
commit26df54a09d7e195d0e33266e0b34f8e11d072277 (patch)
treeca7f47d8beb4fcea7419350852b845b8a3179ec1 /src/client/pages
parent39deb471d976d863d2ec803f908025a2366f1486 (diff)
downloadkioku-26df54a09d7e195d0e33266e0b34f8e11d072277.tar.gz
kioku-26df54a09d7e195d0e33266e0b34f8e11d072277.tar.zst
kioku-26df54a09d7e195d0e33266e0b34f8e11d072277.zip
feat(client): remove registration page
Diffstat (limited to 'src/client/pages')
-rw-r--r--src/client/pages/LoginPage.test.tsx2
-rw-r--r--src/client/pages/LoginPage.tsx5
-rw-r--r--src/client/pages/RegisterPage.test.tsx198
-rw-r--r--src/client/pages/RegisterPage.tsx105
-rw-r--r--src/client/pages/index.ts1
5 files changed, 1 insertions, 310 deletions
diff --git a/src/client/pages/LoginPage.test.tsx b/src/client/pages/LoginPage.test.tsx
index 03cd2f7..724f433 100644
--- a/src/client/pages/LoginPage.test.tsx
+++ b/src/client/pages/LoginPage.test.tsx
@@ -13,7 +13,6 @@ import { LoginPage } from "./LoginPage";
vi.mock("../api/client", () => ({
apiClient: {
login: vi.fn(),
- register: vi.fn(),
logout: vi.fn(),
isAuthenticated: vi.fn(),
getTokens: vi.fn(),
@@ -60,7 +59,6 @@ describe("LoginPage", () => {
expect(screen.getByLabelText("Username")).toBeDefined();
expect(screen.getByLabelText("Password")).toBeDefined();
expect(screen.getByRole("button", { name: "Login" })).toBeDefined();
- expect(screen.getByRole("link", { name: "Register" })).toBeDefined();
});
it("submits form and logs in successfully", async () => {
diff --git a/src/client/pages/LoginPage.tsx b/src/client/pages/LoginPage.tsx
index f72a6da..cc59105 100644
--- a/src/client/pages/LoginPage.tsx
+++ b/src/client/pages/LoginPage.tsx
@@ -1,5 +1,5 @@
import { type FormEvent, useEffect, useState } from "react";
-import { Link, useLocation } from "wouter";
+import { useLocation } from "wouter";
import { ApiClientError, useAuth } from "../stores";
export function LoginPage() {
@@ -74,9 +74,6 @@ export function LoginPage() {
{isSubmitting ? "Logging in..." : "Login"}
</button>
</form>
- <p>
- Don't have an account? <Link href="/register">Register</Link>
- </p>
</div>
);
}
diff --git a/src/client/pages/RegisterPage.test.tsx b/src/client/pages/RegisterPage.test.tsx
deleted file mode 100644
index adce8f0..0000000
--- a/src/client/pages/RegisterPage.test.tsx
+++ /dev/null
@@ -1,198 +0,0 @@
-/**
- * @vitest-environment jsdom
- */
-import { cleanup, render, screen, waitFor } from "@testing-library/react";
-import userEvent from "@testing-library/user-event";
-import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
-import { Router } from "wouter";
-import { memoryLocation } from "wouter/memory-location";
-import { apiClient } from "../api/client";
-import { AuthProvider } from "../stores";
-import { RegisterPage } from "./RegisterPage";
-
-vi.mock("../api/client", () => ({
- apiClient: {
- login: vi.fn(),
- register: vi.fn(),
- logout: vi.fn(),
- isAuthenticated: vi.fn(),
- getTokens: vi.fn(),
- },
- ApiClientError: class ApiClientError extends Error {
- constructor(
- message: string,
- public status: number,
- public code?: string,
- ) {
- super(message);
- this.name = "ApiClientError";
- }
- },
-}));
-
-function renderWithProviders(path = "/register") {
- const { hook } = memoryLocation({ path });
- return render(
- <Router hook={hook}>
- <AuthProvider>
- <RegisterPage />
- </AuthProvider>
- </Router>,
- );
-}
-
-describe("RegisterPage", () => {
- beforeEach(() => {
- vi.clearAllMocks();
- vi.mocked(apiClient.getTokens).mockReturnValue(null);
- vi.mocked(apiClient.isAuthenticated).mockReturnValue(false);
- });
-
- afterEach(() => {
- cleanup();
- vi.restoreAllMocks();
- });
-
- it("renders register form", async () => {
- renderWithProviders();
-
- expect(screen.getByRole("heading", { name: "Register" })).toBeDefined();
- expect(screen.getByLabelText("Username")).toBeDefined();
- expect(screen.getByLabelText("Password")).toBeDefined();
- expect(screen.getByLabelText("Confirm Password")).toBeDefined();
- expect(screen.getByRole("button", { name: "Register" })).toBeDefined();
- expect(screen.getByRole("link", { name: "Login" })).toBeDefined();
- });
-
- it("validates password match", async () => {
- const user = userEvent.setup();
- renderWithProviders();
-
- await user.type(screen.getByLabelText("Username"), "testuser");
- await user.type(screen.getByLabelText("Password"), "password123");
- await user.type(screen.getByLabelText("Confirm Password"), "differentpass");
- await user.click(screen.getByRole("button", { name: "Register" }));
-
- expect(screen.getByRole("alert").textContent).toBe(
- "Passwords do not match",
- );
- expect(apiClient.register).not.toHaveBeenCalled();
- });
-
- it("validates password length", async () => {
- const user = userEvent.setup();
- renderWithProviders();
-
- await user.type(screen.getByLabelText("Username"), "testuser");
- await user.type(screen.getByLabelText("Password"), "short");
- await user.type(screen.getByLabelText("Confirm Password"), "short");
- await user.click(screen.getByRole("button", { name: "Register" }));
-
- expect(screen.getByRole("alert").textContent).toBe(
- "Password must be at least 8 characters",
- );
- expect(apiClient.register).not.toHaveBeenCalled();
- });
-
- it("submits form and registers successfully", async () => {
- const user = userEvent.setup();
- const mockUser = { id: "user-1", username: "testuser" };
- vi.mocked(apiClient.register).mockResolvedValue({ user: mockUser });
- vi.mocked(apiClient.login).mockResolvedValue({
- accessToken: "access-token",
- refreshToken: "refresh-token",
- user: mockUser,
- });
-
- renderWithProviders();
-
- await user.type(screen.getByLabelText("Username"), "testuser");
- await user.type(screen.getByLabelText("Password"), "password123");
- await user.type(screen.getByLabelText("Confirm Password"), "password123");
- await user.click(screen.getByRole("button", { name: "Register" }));
-
- await waitFor(() => {
- expect(apiClient.register).toHaveBeenCalledWith(
- "testuser",
- "password123",
- );
- });
- expect(apiClient.login).toHaveBeenCalledWith("testuser", "password123");
- });
-
- it("displays error on registration failure", async () => {
- const user = userEvent.setup();
- const { ApiClientError } = await import("../api/client");
- vi.mocked(apiClient.register).mockRejectedValue(
- new ApiClientError("Username already taken", 409),
- );
-
- renderWithProviders();
-
- await user.type(screen.getByLabelText("Username"), "existinguser");
- await user.type(screen.getByLabelText("Password"), "password123");
- await user.type(screen.getByLabelText("Confirm Password"), "password123");
- await user.click(screen.getByRole("button", { name: "Register" }));
-
- await waitFor(() => {
- expect(screen.getByRole("alert").textContent).toBe(
- "Username already taken",
- );
- });
- });
-
- it("disables form while submitting", async () => {
- const user = userEvent.setup();
- vi.mocked(apiClient.register).mockImplementation(
- () => new Promise(() => {}), // Never resolves
- );
-
- renderWithProviders();
-
- await user.type(screen.getByLabelText("Username"), "testuser");
- await user.type(screen.getByLabelText("Password"), "password123");
- await user.type(screen.getByLabelText("Confirm Password"), "password123");
- await user.click(screen.getByRole("button", { name: "Register" }));
-
- await waitFor(() => {
- const button = screen.getByRole("button", { name: "Registering..." });
- expect(button.hasAttribute("disabled")).toBe(true);
- });
- expect(
- (screen.getByLabelText("Username") as HTMLInputElement).disabled,
- ).toBe(true);
- expect(
- (screen.getByLabelText("Password") as HTMLInputElement).disabled,
- ).toBe(true);
- expect(
- (screen.getByLabelText("Confirm Password") as HTMLInputElement).disabled,
- ).toBe(true);
- });
-
- it("calls navigate when already authenticated", async () => {
- vi.mocked(apiClient.isAuthenticated).mockReturnValue(true);
- vi.mocked(apiClient.getTokens).mockReturnValue({
- accessToken: "access-token",
- refreshToken: "refresh-token",
- });
-
- const { hook } = memoryLocation({ path: "/register" });
- const navigateSpy = vi.fn();
- const hookWithSpy: typeof hook = () => {
- const result = hook();
- return [result[0], navigateSpy];
- };
-
- render(
- <Router hook={hookWithSpy}>
- <AuthProvider>
- <RegisterPage />
- </AuthProvider>
- </Router>,
- );
-
- await waitFor(() => {
- expect(navigateSpy).toHaveBeenCalledWith("/", { replace: true });
- });
- });
-});
diff --git a/src/client/pages/RegisterPage.tsx b/src/client/pages/RegisterPage.tsx
deleted file mode 100644
index e6783bd..0000000
--- a/src/client/pages/RegisterPage.tsx
+++ /dev/null
@@ -1,105 +0,0 @@
-import { type FormEvent, useEffect, useState } from "react";
-import { Link, useLocation } from "wouter";
-import { ApiClientError, useAuth } from "../stores";
-
-export function RegisterPage() {
- const [, navigate] = useLocation();
- const { register, isAuthenticated } = useAuth();
- const [username, setUsername] = useState("");
- const [password, setPassword] = useState("");
- const [confirmPassword, setConfirmPassword] = useState("");
- const [error, setError] = useState<string | null>(null);
- const [isSubmitting, setIsSubmitting] = useState(false);
-
- // Redirect if already authenticated
- useEffect(() => {
- if (isAuthenticated) {
- navigate("/", { replace: true });
- }
- }, [isAuthenticated, navigate]);
-
- const handleSubmit = async (e: FormEvent) => {
- e.preventDefault();
- setError(null);
-
- if (password !== confirmPassword) {
- setError("Passwords do not match");
- return;
- }
-
- if (password.length < 8) {
- setError("Password must be at least 8 characters");
- return;
- }
-
- setIsSubmitting(true);
-
- try {
- await register(username, password);
- navigate("/", { replace: true });
- } catch (err) {
- if (err instanceof ApiClientError) {
- setError(err.message);
- } else {
- setError("Registration failed. Please try again.");
- }
- } finally {
- setIsSubmitting(false);
- }
- };
-
- return (
- <div>
- <h1>Register</h1>
- <form onSubmit={handleSubmit}>
- {error && (
- <div role="alert" style={{ color: "red" }}>
- {error}
- </div>
- )}
- <div>
- <label htmlFor="username">Username</label>
- <input
- id="username"
- type="text"
- value={username}
- onChange={(e) => setUsername(e.target.value)}
- required
- autoComplete="username"
- disabled={isSubmitting}
- />
- </div>
- <div>
- <label htmlFor="password">Password</label>
- <input
- id="password"
- type="password"
- value={password}
- onChange={(e) => setPassword(e.target.value)}
- required
- autoComplete="new-password"
- disabled={isSubmitting}
- />
- </div>
- <div>
- <label htmlFor="confirmPassword">Confirm Password</label>
- <input
- id="confirmPassword"
- type="password"
- value={confirmPassword}
- onChange={(e) => setConfirmPassword(e.target.value)}
- required
- autoComplete="new-password"
- disabled={isSubmitting}
- />
- </div>
- <button type="submit" disabled={isSubmitting}>
- {isSubmitting ? "Registering..." : "Register"}
- </button>
- </form>
- <p>
- Already have an account? <Link href="/login">Login</Link>
- </p>
- </div>
- );
-}
diff --git a/src/client/pages/index.ts b/src/client/pages/index.ts
index 99cf046..0844b31 100644
--- a/src/client/pages/index.ts
+++ b/src/client/pages/index.ts
@@ -1,4 +1,3 @@
export { HomePage } from "./HomePage";
export { LoginPage } from "./LoginPage";
export { NotFoundPage } from "./NotFoundPage";
-export { RegisterPage } from "./RegisterPage";