feat: Add mock mode for UI-only development
- Created mock API implementation in src/api/mock.ts - Added isMockMode toggle to useStore - Implemented switching between real and mock API in client.ts - Added Mock Mode toggle to Login page - Added 'npm run dev:mock' command Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1 +1,2 @@
|
|||||||
VITE_API_DOMAIN=http://localhost:3000
|
VITE_API_DOMAIN=http://localhost:3000
|
||||||
|
VITE_MOCK_MODE=false
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
"dev:mock": "VITE_MOCK_MODE=true vite",
|
||||||
"build": "tsc -b && vite build",
|
"build": "tsc -b && vite build",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { useStore } from '../store/useStore';
|
import { useStore } from '../store/useStore';
|
||||||
import type { Profile, ApiResponse } from '../types/api';
|
import type { Profile, ApiResponse } from '../types/api';
|
||||||
|
import { mockApi } from './mock';
|
||||||
|
|
||||||
const getClient = () => {
|
const getClient = () => {
|
||||||
const { apiDomain, apiKey } = useStore.getState();
|
const { apiDomain, apiKey } = useStore.getState();
|
||||||
@@ -14,11 +15,17 @@ const getClient = () => {
|
|||||||
|
|
||||||
export const api = {
|
export const api = {
|
||||||
getProfile: async () => {
|
getProfile: async () => {
|
||||||
|
if (useStore.getState().isMockMode) {
|
||||||
|
return mockApi.getProfile();
|
||||||
|
}
|
||||||
const client = getClient();
|
const client = getClient();
|
||||||
const response = await client.get<ApiResponse<Profile>>('/v1/profile');
|
const response = await client.get<ApiResponse<Profile>>('/v1/profile');
|
||||||
return response.data.data;
|
return response.data.data;
|
||||||
},
|
},
|
||||||
checkHealth: async () => {
|
checkHealth: async () => {
|
||||||
|
if (useStore.getState().isMockMode) {
|
||||||
|
return mockApi.checkHealth();
|
||||||
|
}
|
||||||
const client = getClient();
|
const client = getClient();
|
||||||
const response = await client.get('/v1/health');
|
const response = await client.get('/v1/health');
|
||||||
return response.data;
|
return response.data;
|
||||||
|
|||||||
26
src/api/mock.ts
Normal file
26
src/api/mock.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import type { Profile } from '../types/api';
|
||||||
|
|
||||||
|
export const mockProfile: Profile = {
|
||||||
|
fullName: 'Иванов Иван Иванович',
|
||||||
|
group: 'ИКПИ-11',
|
||||||
|
faculty: 'Инфокоммуникационных сетей и систем',
|
||||||
|
studentId: '2210000',
|
||||||
|
email: 'ivanov.ii@example.com',
|
||||||
|
raw: {
|
||||||
|
'Дата рождения': '01.01.2004',
|
||||||
|
'Статус': 'Студент',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockApi = {
|
||||||
|
getProfile: async (): Promise<Profile> => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => resolve(mockProfile), 500);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
checkHealth: async (): Promise<any> => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => resolve({ status: 'ok', mock: true }), 300);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -4,7 +4,7 @@ import { useNavigate } from 'react-router-dom';
|
|||||||
import { api } from '../api/client';
|
import { api } from '../api/client';
|
||||||
|
|
||||||
export const Login: React.FC = () => {
|
export const Login: React.FC = () => {
|
||||||
const { apiDomain, apiKey, setApiDomain, setApiKey } = useStore();
|
const { apiDomain, apiKey, isMockMode, setApiDomain, setApiKey, setMockMode } = useStore();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -16,10 +16,14 @@ export const Login: React.FC = () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Basic validation
|
// Basic validation
|
||||||
if (!apiDomain || !apiKey) {
|
if (!isMockMode && (!apiDomain || !apiKey)) {
|
||||||
throw new Error('Please fill all fields');
|
throw new Error('Please fill all fields');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isMockMode && !apiKey) {
|
||||||
|
setApiKey('mock-session');
|
||||||
|
}
|
||||||
|
|
||||||
// Check health
|
// Check health
|
||||||
await api.checkHealth();
|
await api.checkHealth();
|
||||||
|
|
||||||
@@ -44,6 +48,7 @@ export const Login: React.FC = () => {
|
|||||||
placeholder="API Domain (e.g. https://api.example.com)"
|
placeholder="API Domain (e.g. https://api.example.com)"
|
||||||
value={apiDomain}
|
value={apiDomain}
|
||||||
onChange={(e) => setApiDomain(e.target.value)}
|
onChange={(e) => setApiDomain(e.target.value)}
|
||||||
|
disabled={isMockMode}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
@@ -51,7 +56,22 @@ export const Login: React.FC = () => {
|
|||||||
placeholder="API Key"
|
placeholder="API Key"
|
||||||
value={apiKey}
|
value={apiKey}
|
||||||
onChange={(e) => setApiKey(e.target.value)}
|
onChange={(e) => setApiKey(e.target.value)}
|
||||||
|
disabled={isMockMode}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: '16px', display: 'flex', alignItems: 'center' }}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="mockMode"
|
||||||
|
checked={isMockMode}
|
||||||
|
onChange={(e) => setMockMode(e.target.checked)}
|
||||||
|
style={{ marginRight: '8px' }}
|
||||||
|
/>
|
||||||
|
<label htmlFor="mockMode" style={{ fontSize: '14px', cursor: 'pointer' }}>
|
||||||
|
Enable Mock Mode (UI Only)
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<p style={{ color: 'var(--md-sys-color-error)', marginBottom: '16px' }}>
|
<p style={{ color: 'var(--md-sys-color-error)', marginBottom: '16px' }}>
|
||||||
{error}
|
{error}
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import { persist } from 'zustand/middleware';
|
|||||||
interface AppState {
|
interface AppState {
|
||||||
apiDomain: string;
|
apiDomain: string;
|
||||||
apiKey: string;
|
apiKey: string;
|
||||||
|
isMockMode: boolean;
|
||||||
setApiDomain: (domain: string) => void;
|
setApiDomain: (domain: string) => void;
|
||||||
setApiKey: (key: string) => void;
|
setApiKey: (key: string) => void;
|
||||||
|
setMockMode: (isMock: boolean) => void;
|
||||||
reset: () => void;
|
reset: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,8 +16,10 @@ export const useStore = create<AppState>()(
|
|||||||
(set) => ({
|
(set) => ({
|
||||||
apiDomain: import.meta.env.VITE_API_DOMAIN || '',
|
apiDomain: import.meta.env.VITE_API_DOMAIN || '',
|
||||||
apiKey: '',
|
apiKey: '',
|
||||||
|
isMockMode: import.meta.env.VITE_MOCK_MODE === 'true',
|
||||||
setApiDomain: (apiDomain) => set({ apiDomain }),
|
setApiDomain: (apiDomain) => set({ apiDomain }),
|
||||||
setApiKey: (apiKey) => set({ apiKey }),
|
setApiKey: (apiKey) => set({ apiKey }),
|
||||||
|
setMockMode: (isMockMode) => set({ isMockMode }),
|
||||||
reset: () => set({ apiKey: '' }),
|
reset: () => set({ apiKey: '' }),
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user