vault backup: 2025-06-02 19:55:06
This commit is contained in:
@@ -1,10 +1,13 @@
|
||||
|
||||
|
||||
# Branching
|
||||
- [ ] Create the feature/authentication branch based on develop
|
||||
|
||||
# Adding Models
|
||||
Create three files inside this folder:
|
||||
📂 Suggested Folder: shared/models/authentication
|
||||
- [ ] `AuthResponseDto.tsx`
|
||||
```tsx
|
||||
|
||||
- [ ] `AuthResponseDto.ts`
|
||||
```ts
|
||||
export interface AuthResponseDto {
|
||||
token: string;
|
||||
phoneNumber: string;
|
||||
@@ -12,16 +15,16 @@ export interface AuthResponseDto {
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] `LoginRequestDto`
|
||||
```tsx
|
||||
- [ ] `LoginRequestDto.ts`
|
||||
```ts
|
||||
export interface LoginRequestDto {
|
||||
phoneNumber: string;
|
||||
password: string;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] `RegisterRequestDto`
|
||||
```tsx
|
||||
- [ ] `RegisterRequestDto.ts`
|
||||
```ts
|
||||
export interface RegisterRequestDto {
|
||||
phoneNumber: string;
|
||||
password: string;
|
||||
@@ -29,9 +32,9 @@ export interface RegisterRequestDto {
|
||||
}
|
||||
```
|
||||
|
||||
# Adding Authentication API Calls in Agent
|
||||
- [ ] Add the following code
|
||||
```tsx
|
||||
# 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),
|
||||
@@ -39,7 +42,7 @@ login: (data: LoginRequestDto) =>
|
||||
request.post<{ token: string }>('/auth/login', data),
|
||||
};
|
||||
```
|
||||
- [ ] Update agent
|
||||
- [ ] Ensure `agent.ts` ends like this:
|
||||
```tsx
|
||||
const agent = {
|
||||
TransportationSearch,
|
||||
@@ -48,9 +51,10 @@ const agent = {
|
||||
}
|
||||
```
|
||||
|
||||
# Creating AuthStore
|
||||
# Creating `authStore.ts`
|
||||
Suggested Folder
|
||||
|
||||
📂 Suggested Folder: shared/store/
|
||||
- [ ] Create authStore.ts
|
||||
```tsx
|
||||
import { AuthResponseDto } from '@/shared/models/authentication/AuthResponseDto';
|
||||
import {create} from 'zustand';
|
||||
@@ -108,7 +112,137 @@ export const useAuthStore = create<AuthState>((set) => ({
|
||||
}));
|
||||
```
|
||||
|
||||
## 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";
|
||||
@@ -153,7 +287,7 @@ const LoginModal: React.FC<LoginModalProps> = ({ onClose }) => {
|
||||
|
||||
try {
|
||||
const response = await agent.Auth.login(form);
|
||||
login(response.token); // update store with full auth info
|
||||
login(response);
|
||||
setError(null);
|
||||
onClose();
|
||||
} catch (err: any) {
|
||||
@@ -208,75 +342,191 @@ const LoginModal: React.FC<LoginModalProps> = ({ onClose }) => {
|
||||
};
|
||||
|
||||
const styles: { [key: string]: React.CSSProperties } = {
|
||||
overlay: {
|
||||
position: "fixed",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: "rgba(0,0,0,0.5)",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
zIndex: 1000,
|
||||
},
|
||||
|
||||
modal: {
|
||||
backgroundColor: "white",
|
||||
padding: "2rem",
|
||||
borderRadius: "8px",
|
||||
boxShadow: "0 2px 10px rgba(0,0,0,0.3)",
|
||||
width: "320px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
},
|
||||
|
||||
input: {
|
||||
marginBottom: "1rem",
|
||||
padding: "0.5rem",
|
||||
fontSize: "1rem",
|
||||
borderRadius: "4px",
|
||||
border: "1px solid #ccc",
|
||||
},
|
||||
|
||||
button: {
|
||||
padding: "0.6rem 1.2rem",
|
||||
fontSize: "1rem",
|
||||
borderRadius: "4px",
|
||||
border: "none",
|
||||
backgroundColor: "#007bff",
|
||||
color: "white",
|
||||
cursor: "pointer",
|
||||
},
|
||||
//ADD STYLES
|
||||
};
|
||||
|
||||
export default LoginModal;
|
||||
```
|
||||
|
||||
## About LoginModal
|
||||
|
||||
# Add RegisterModal
|
||||
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 Zustand’s `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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Define RegisterRequestDto interface explicitly for form and request typing
|
||||
|
||||
|
||||
|
||||
const RegisterModal: React.FC<Props> = ({ onClose }) => {
|
||||
|
||||
const [form, setForm] = useState<RegisterRequestDto>({
|
||||
@@ -285,66 +535,44 @@ const RegisterModal: React.FC<Props> = ({ onClose }) => {
|
||||
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) {
|
||||
@@ -367,7 +595,7 @@ const RegisterModal: React.FC<Props> = ({ onClose }) => {
|
||||
|
||||
|
||||
const response = await agent.Auth.register(requestData);
|
||||
login(response.token);
|
||||
login(response);
|
||||
setForm({ phoneNumber: "", password: "", confirmPassword: "" });
|
||||
onClose();
|
||||
} catch (err: any) {
|
||||
@@ -375,8 +603,6 @@ const RegisterModal: React.FC<Props> = ({ onClose }) => {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div style={styles.overlay}>
|
||||
<div style={styles.modal}>
|
||||
@@ -449,48 +675,127 @@ const RegisterModal: React.FC<Props> = ({ onClose }) => {
|
||||
|
||||
|
||||
const styles: { [key: string]: React.CSSProperties } = {
|
||||
|
||||
overlay: {
|
||||
position: "fixed",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: "rgba(0,0,0,0.5)",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
zIndex: 1000,
|
||||
},
|
||||
|
||||
modal: {
|
||||
backgroundColor: "white",
|
||||
padding: "2rem",
|
||||
borderRadius: "8px",
|
||||
boxShadow: "0 2px 10px rgba(0,0,0,0.3)",
|
||||
width: "320px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
},
|
||||
|
||||
input: {
|
||||
marginBottom: "1rem",
|
||||
padding: "0.5rem",
|
||||
fontSize: "1rem",
|
||||
borderRadius: "4px",
|
||||
border: "1px solid #ccc",
|
||||
},
|
||||
|
||||
button: {
|
||||
padding: "0.6rem 1.2rem",
|
||||
fontSize: "1rem",
|
||||
borderRadius: "4px",
|
||||
border: "none",
|
||||
backgroundColor: "#007bff",
|
||||
color: "white",
|
||||
cursor: "pointer",
|
||||
},
|
||||
//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`
|
||||
|
||||
---
|
||||
|
||||
# Merge
|
||||
- [ ] Create a PR and merge the current branch with develop
|
||||
|
||||
Reference in New Issue
Block a user