refactor: change folder structure

This commit is contained in:
2025-07-18 13:02:13 +03:30
parent 835f833927
commit 3c05411a29
1329 changed files with 116 additions and 38 deletions
@@ -0,0 +1,803 @@
# Branching
- [ ] Create the feature/authentication branch based on develop
# Adding Models
Create three files inside this folder:
📂 Suggested Folder: shared/models/authentication
- [ ] `AuthResponseDto.ts`
```ts
export interface AuthResponseDto {
  token: string;
  phoneNumber: string;
  roles: string[];
}
```
- [ ] `LoginRequestDto.ts`
```ts
export interface LoginRequestDto {
  phoneNumber: string;
  password: string;
}
```
- [ ] `RegisterRequestDto.ts`
```ts
export interface RegisterRequestDto {
  phoneNumber: string;
  password: string;
  confirmPassword: string;
}
```
# Adding Authentication API Calls in `agent.ts`
- [ ] Add the `Auth` object to `agent.ts`:
```ts
const Auth = {
register: (data: RegisterRequestDto) =>
    request.post<{ token: string }>('/auth/register', data),
login: (data: LoginRequestDto) =>
    request.post<{ token: string }>('/auth/login', data),
};
```
- [ ] Ensure `agent.ts` ends like this:
```tsx
const agent = {
    TransportationSearch,
    Cities,
    Auth
}
```
# Creating `authStore.ts`
Suggested Folder
📂 Suggested Folder: shared/store/
- [ ] Create authStore.ts
```tsx
import { AuthResponseDto } from '@/shared/models/authentication/AuthResponseDto';
import {create} from 'zustand';
interface User {
  phoneNumber: string;
  roles: string[];
}
interface AuthState {
  isLoggedIn: boolean;
  user: User | null;
  token: string | null;
  login: (response: AuthResponseDto) => void;
  logout: () => void;
  setToken: (token: string) => void;
}
export const useAuthStore = create<AuthState>((set) => ({
  isLoggedIn: false,
  user: null,
  token: null,
  login: (response) =>
  set(() => ({
    token: response.token,
    user: {
      phoneNumber: response.phoneNumber,
      roles: response.roles
    },
    isLoggedIn: true,
  })),
  logout: () =>
    set(() => ({
      token: null,
      user: null,
      isLoggedIn: false,
    })),
  setToken: (token) =>
    set((state) => ({
      token,
      isLoggedIn: !!token,
      user: state.user,
    })),
}));
```
## About `authStore.ts`
This file defines a centralized **authentication state store** using [`Zustand`](https://github.com/pmndrs/zustand), a minimal and scalable state management library for React. It helps manage login state, user data, and authentication token across your application.
---
## 🔹 `User` Interface
```ts
interface User {
phoneNumber: string;
roles: string[];
}
```
This interface defines the shape of the `user` object stored in the auth state. It currently includes:
- `phoneNumber`: A string representing the user's phone number.
- `roles`: An array representing user roles
---
## 🔹 `AuthState` Interface
```ts
interface AuthState {
isLoggedIn: boolean;
user: User | null;
token: string | null;
login: (response: AuthResponseDto) => void;
logout: () => void;
setToken: (token: string) => void;
}
```
This defines the overall structure of the authentication store:
- `isLoggedIn`: Indicates whether a user is logged in.
- `user`: Stores user-specific data if authenticated; otherwise `null`.
- `token`: JWT or access token from the server.
- `login()`: Accepts an `AuthResponseDto` and updates the state.
- `logout()`: Clears all authentication-related data.
- `setToken()`: Sets the token and toggles login status accordingly.
---
## 🔹 Zustand Store Definition
```ts
export const useAuthStore = create<AuthState>((set) => ({ ... });
```
Creates a global auth store using Zustand. `create()` accepts a function that receives `set` (used to update state) and returns the initial store state and methods.
---
## 🔹 Initial State
```ts
isLoggedIn: false,
user: null,
token: null,
```
These lines define the initial, default state for an unauthenticated user.
---
## 🔹 `login()` Method
```ts
login: (response) =>
set(() => ({
token: response.token,
user: {
phoneNumber: response.phoneNumber,
roles: response.roles
},
isLoggedIn: true,
})),
```
- Accepts an `AuthResponseDto` object after a successful login.
- Extracts the `token`, `phoneNumber`, and `roles`, and sets them in state.
- Marks the user as `isLoggedIn: true`.
---
## 🔹 `logout()` Method
```ts
logout: () =>
set(() => ({
token: null,
user: null,
isLoggedIn: false,
})),
```
- Clears all auth-related data (token and user).
- Effectively logs the user out by setting `isLoggedIn` to `false`.
---
## 🔹 `setToken()` Method
```ts
setToken: (token) =>
set((state) => ({
token,
isLoggedIn: !!token,
user: state.user,
})),
```
- Updates the token in the store.
- Sets `isLoggedIn` based on whether a non-empty token exists.
- Retains the current `user` object.
---
# Add LoginModal
📂 Suggested Folder: shared/features/authentication/modals
## Example
```tsx
import React, { useState } from "react";
import { useAuthStore } from "@/store/authStore";
import agent from "@/shared/api/agent";
import { LoginRequestDto } from "@/shared/models/authentication/LoginRequestDto";
interface LoginModalProps {
  onClose: () => void;
}
const LoginModal: React.FC<LoginModalProps> = ({ onClose }) => {
 
  const login = useAuthStore((state) => state.login);
  const [form, setForm] = useState<LoginRequestDto>({
    phoneNumber: "",
    password: "",
  });
  const [error, setError] = useState<string | null>(null);
  const validate = () => {
    const phoneRegex = /^(?:\+98|0)?9\d{9}$/;
    if (!phoneRegex.test(form.phoneNumber)) {
      return "Invalid phone number format";
    }
    if (!form.password || form.password.length < 8) {
      return "Password must be at least 8 characters";
    }
    return null;
  };
  const handleSubmit = async () => {
    const validationError = validate();
    if (validationError) {
      setError(validationError);
      return;
    }
    try {
      const response = await agent.Auth.login(form);
      login(response);
      setError(null);
      onClose();
    } catch (err: any) {
      setError(err.response?.data?.message || "Login failed");
    }
  };
  return (
    <div style={styles.overlay}>
      <div style={styles.modal}>
        <h2 style={{ marginBottom: "1rem" }}>Login</h2>
        <input
          type="text"
          placeholder="Phone Number"
          value={form.phoneNumber}
          onChange={(e) => setForm({ ...form, phoneNumber: e.target.value })}
          style={styles.input}
        />
        <input
          type="password"
          placeholder="Password"
          value={form.password}
          onChange={(e) => setForm({ ...form, password: e.target.value })}
          style={styles.input}
        />
        <button onClick={handleSubmit} style={styles.button}>
          Login
        </button>
        {error && (
          <p style={{ color: "red", marginTop: "0.5rem", fontWeight: "bold" }}>
            {error}
          </p>
        )}
        <button
          onClick={onClose}
          style={{
            ...styles.button,
            marginTop: "0.5rem",
            backgroundColor: "#ccc",
            color: "#333",
          }}
        >
          Cancel
        </button>
      </div>
    </div>
  );
};
const styles: { [key: string]: React.CSSProperties } = {
//ADD STYLES
};
export default LoginModal;
```
## About LoginModal
This component provides a modal UI that allows users to log in using their **phone number and password**. It integrates with the authentication store and API to perform login logic and handle errors.
---
## 🔹 Props Interface
```tsx
interface LoginModalProps {
onClose: () => void;
}
```
- `onClose`: A callback to be called when the modal should be closed (e.g., user clicks "Cancel" or logs in successfully).
---
## 🔹 Component Setup
```tsx
const LoginModal: React.FC<LoginModalProps> = ({ onClose }) => { ... };
```
Defines a functional React component with the `onClose` prop destructured.
### 🔸 Accessing Auth Store
```tsx
const login = useAuthStore((state) => state.login);
```
Retrieves the `login` method from Zustands `authStore` so that the global auth state can be updated after successful login.
### 🔸 Local Form State
```tsx
const [form, setForm] = useState<LoginRequestDto>({
phoneNumber: "",
password: "",
});
```
Initializes `form` state with empty values for the phone number and password.
### 🔸 Error Handling State
```tsx
const [error, setError] = useState<string | null>(null);
```
Stores any error messages resulting from validation or login attempt.
---
## 🔹 Validation Logic
```tsx
const validate = () => {
const phoneRegex = /^(?:\+98|0)?9\d{9}$/;
if (!phoneRegex.test(form.phoneNumber)) {
return "Invalid phone number format";
}
if (!form.password || form.password.length < 8) {
return "Password must be at least 8 characters";
}
return null;
};
```
- Validates the phone number format (Iranian phone format in this case).
- Ensures password is at least 8 characters long.
- Returns a string error message or `null` if validation passes.
---
## 🔹 Submit Handler
```tsx
const handleSubmit = async () => {
const validationError = validate();
if (validationError) {
setError(validationError);
return;
}
try {
const response = await agent.Auth.login(form);
login(response);
setError(null);
onClose();
} catch (err: any) {
setError(err.response?.data?.message || "Login failed");
}
};
```
- Calls `validate()` and prevents submission if there's an error.
- Calls the backend API using `agent.Auth.login()`.
- On success: updates auth state, clears error, closes modal.
- On failure: shows error message.
---
## 🔹 UI Layout
```tsx
return (
<div style={styles.overlay}>
<div style={styles.modal}>
<h2>Login</h2>
<input ... />
<input ... />
<button onClick={handleSubmit}>Login</button>
{error && <p>{error}</p>}
<button onClick={onClose}>Cancel</button>
</div>
</div>
);
```
### Elements:
- **Phone Number Input**
- **Password Input**
- **Login Button**: Triggers `handleSubmit`.
- **Error Message**: Shown only if there's an error.
- **Cancel Button**: Triggers `onClose` callback.
---
## 🔹 Styles Placeholder
```tsx
const styles: { [key: string]: React.CSSProperties } = {
// Add modal styles here
};
```
This placeholder defines inline CSS styles for the modal. Each style (e.g., `overlay`, `modal`, `input`, `button`) should be defined here.
---
## ✅ Summary
This modal:
- Provides a simple, reusable login form.
- Validates input before calling the API.
- Updates global auth state via Zustand.
- Handles success/failure states.
- Uses modal-friendly inline styles (with room for improvement).
---
# Add RegisterModal
- [ ] Create RegisterModal
📂 Suggested Folder: shared/features/authentication/modals
```tsx
import agent from "@/shared/api/agent";
import { RegisterRequestDto } from "@/shared/models/authentication/RegisterRequestDto";
import { useAuthStore } from "@/store/authStore";
import React, { useState } from "react";
interface Props {
  onClose: () => void;
}
const RegisterModal: React.FC<Props> = ({ onClose }) => {
  const [form, setForm] = useState<RegisterRequestDto>({
    phoneNumber: "",
    password: "",
    confirmPassword: "",
  });
  const [error, setError] = useState<string | null>(null);
  const login = useAuthStore((state) => state.login);
 
  const validate = () => {
    const { phoneNumber, password, confirmPassword } = form;
    if (!phoneNumber || !password || !confirmPassword) {
      return "All fields are required.";
    }
    if (!/^\d{11}$/.test(phoneNumber)) {
      return "Phone number must be 11 digits.";
    }
    if (password.length < 6) {
      return "Password must be at least 6 characters.";
    }
    if (password !== confirmPassword) {
      return "Passwords do not match.";
    }
    return null;
  };
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setForm({ ...form, [e.target.name]: e.target.value });
  };
  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setError(null);
   
    const validationError = validate();
    if (validationError) {
      setError(validationError);
      return;
    }
    try {
      // Use the form as RegisterRequestDto explicitly
      const requestData: RegisterRequestDto = {
        phoneNumber: form.phoneNumber,
        password: form.password,
        confirmPassword: form.confirmPassword,
      };
      const response = await agent.Auth.register(requestData);
      login(response);
      setForm({ phoneNumber: "", password: "", confirmPassword: "" });
      onClose();
    } catch (err: any) {
      setError(err.response?.data?.message || "Registration failed.");
    }
  };
  return (
    <div style={styles.overlay}>
      <div style={styles.modal}>
        <h2 style={{ marginBottom: "1rem" }}>Register</h2>
        <form
          onSubmit={handleSubmit}
          style={{ display: "flex", flexDirection: "column" }}
        >
          <input
            type="text"
            name="phoneNumber"
            value={form.phoneNumber}
            onChange={handleChange}
            placeholder="Phone Number"
            style={styles.input}
          />
          <input
            type="password"
            name="password"
            value={form.password}
            onChange={handleChange}
            placeholder="Password"
            style={styles.input}
          />
          <input
            type="password"
            name="confirmPassword"
            value={form.confirmPassword}
            onChange={handleChange}
            placeholder="Confirm Password"
            style={styles.input}
          />
          {error && (
            <p
              style={{ color: "red", marginTop: "0.5rem", fontWeight: "bold" }}
            >
              {error}
            </p>
          )}
          <button type="submit" style={styles.button}>
            Register
          </button>
        </form>
        <button
          onClick={onClose}
          style={{
            ...styles.button,
            marginTop: "0.5rem",
            backgroundColor: "#ccc",
            color: "#333",
          }}
        >
          Cancel
        </button>
      </div>
    </div>
  );
};
const styles: { [key: string]: React.CSSProperties } = {
//ADD STYLES
};
export default RegisterModal;
```
## About RegisterModal
### **Component Structure**
### 1. **Props**
```tsx
interface Props {
  onClose: () => void;
}
```
- The modal only expects one prop: `onClose`, a function to close the modal (e.g., hide it from the screen).
---
### 2. **State Management**
```tsx
const [form, setForm] = useState<RegisterRequestDto>({
  phoneNumber: "",
  password: "",
  confirmPassword: "",
});
```
- Initializes the form state for inputs, based on the `RegisterRequestDto` shape.
```tsx
const [error, setError] = useState<string | null>(null);
```
- Stores any validation or server error message to display in the UI.
```tsx
const login = useAuthStore((state) => state.login);
```
- Accesses the `login` method from your global auth store, to automatically log in the user after successful registration.
---
### 3. **Validation Logic**
```tsx
const validate = () => {
  // Checks for empty fields
  // Validates phone number format (must be 11 digits)
  // Ensures password length is sufficient
  // Confirms password and confirmation match
};
```
- Ensures client-side validation before making a request to the server.
---
### 4. **Input Handling**
```tsx
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  setForm({ ...form, [e.target.name]: e.target.value });
};
```
- Updates the correct field in the `form` object dynamically based on the input `name`.
---
### 5. **Form Submission**
```tsx
const handleSubmit = async (e: React.FormEvent) => {
  e.preventDefault();
  setError(null);
  const validationError = validate();
  // If validation passes, submit the data to the backend
  // If backend response is successful, log in and close modal
  // If it fails, show error message
};
```
- Prevents default form submission
- Validates inputs
- Sends the data to `agent.Auth.register`
- On success: logs in user and clears form
- On failure: shows error from server
---
### 6. **JSX Render**
```tsx
<div style={styles.overlay}>...</div>
```
- **Modal Overlay**: darkened background behind the modal
- **Modal Box**: contains title, form, and buttons
### Inside `<form>`:
- Inputs for:
  - `phoneNumber`
  - `password`
  - `confirmPassword`
- Submit button for Register
- Error message display (if any)
- Cancel button that calls `onClose`
---
# Handle login/logout and register buttons in navbar
- [ ] implement a way for showing login and register buttons in navbar when user is not signed in
- [ ] when clicked, the button should show the related modal, for the user to sign in or register
# Merge
- [ ] Create a PR and merge the current branch with develop