Compare commits
2 Commits
a227c14e0a
...
8300e43e14
| Author | SHA1 | Date | |
|---|---|---|---|
| 8300e43e14 | |||
| 386297dc1e |
24
CONCEPT.md
Normal file
24
CONCEPT.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Concept Overview
|
||||||
|
|
||||||
|
The application is a **metadata‑driven admin UI** built on top of an OpenAPI description. By describing each resource in a small JSON config (type `ResourceConfig`), the UI automatically generates:
|
||||||
|
|
||||||
|
1. **Data tables** (with pagination, sorting, and actions) – `EnhancedTable`.
|
||||||
|
2. **Dynamic filters** – `FilterBar` creates appropriate filter widgets (autocomplete, number‑range, date‑range) based on field metadata.
|
||||||
|
3. **Forms for create/edit** – A generic form component can render inputs for every `ResourceField`, handling relations via the `displayFormat` template.
|
||||||
|
4. **Authentication layer** – `react‑auth` supplies a central `AuthProvider`, a `useAuth` hook, and route guarding, ensuring only authenticated users reach the admin pages.
|
||||||
|
|
||||||
|
### Core Principles
|
||||||
|
- **Declarative configuration**: Adding a new resource is just a JSON entry; no hand‑coded tables or forms.
|
||||||
|
- **Template‑based display**: `displayFormat` (e.g. `"{{firstName}} {{lastName}}"`) defines how related objects are shown across the UI, eliminating the need for separate `displayField` props.
|
||||||
|
- **Extensible UI**: Consumers can plug custom components (`components` prop) to override cell renderers, filter widgets, or action buttons without altering core logic.
|
||||||
|
- **Unified state**: TanStack Query caches server data, while `react‑auth` manages JWTs and user info. Both are provided via React context for easy access.
|
||||||
|
- **Responsive design**: The UI automatically switches to a card‑based layout on mobile, preserving functionality with a consistent look.
|
||||||
|
|
||||||
|
### Migration Goal for Lovable
|
||||||
|
The current repo implements these ideas with a solid foundation but could benefit from:
|
||||||
|
- **Improved UI/UX** (e.g., better loading states, richer snackbars, dark‑mode toggle).
|
||||||
|
- **More robust error handling** (centralized toast system, retry logic on auth failures).
|
||||||
|
- **Enhanced theming** (customizable palette, brand colors).
|
||||||
|
- **Accessibility** (ARIA roles, keyboard navigation).
|
||||||
|
|
||||||
|
By re‑using the existing `ResourceConfig` schema and `displayFormat` logic, the Lovable implementation can focus on UI polish and advanced handling while keeping the powerful code‑generation approach intact.
|
||||||
34
DESIGN.md
Normal file
34
DESIGN.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Design Overview
|
||||||
|
|
||||||
|
## React‑Auth
|
||||||
|
- **Purpose**: Centralize authentication flows (login, logout, token refresh) for the UI.
|
||||||
|
- **Key Concepts**
|
||||||
|
- **AuthProvider** – React context that stores `user`, `accessToken`, and `isAuthenticated`.
|
||||||
|
- **useAuth hook** – Exposes `login`, `logout`, `refreshToken`, and state values.
|
||||||
|
- **Route Guard** – HOC/Component (`ProtectedRoute`) that redirects unauthenticated users to the login page.
|
||||||
|
- **UI**: Simple MUI forms, error handling with snackbars, and a loading spinner while the auth request is pending.
|
||||||
|
- **Extensibility**: Plug‑in point for additional providers (OAuth, SSO) via a `providers` map.
|
||||||
|
|
||||||
|
## React‑OpenAPI
|
||||||
|
- **Purpose**: Generate UI components directly from an OpenAPI spec (tables, filters, forms).
|
||||||
|
- **Core Modules**
|
||||||
|
- `ResourceConfig` & `ResourceField` – Typed definitions that describe each endpoint and its fields, including `displayFormat` for rendering.
|
||||||
|
- `EnhancedTable` – Data‑grid component that renders rows according to the config, supports relation rendering, sorting, pagination, and custom cell renderers.
|
||||||
|
- `FilterBar` – Dynamically builds filter controls (autocomplete, number‑range, date‑range) based on the same config.
|
||||||
|
- **Data Flow**
|
||||||
|
1. Load OpenAPI spec → transform to `ResourceConfig` objects.
|
||||||
|
2. `useQuery` (TanStack) fetches data.
|
||||||
|
3. UI components consume the config to render tables and filter UI without hand‑written column definitions.
|
||||||
|
- **Design Goals**
|
||||||
|
- **Zero boilerplate** – Adding a new resource only requires a JSON config.
|
||||||
|
- **Consistency** – All tables share pagination, actions, and styling.
|
||||||
|
- **Extensibility** – Override components via `components` prop.
|
||||||
|
|
||||||
|
## src (Root Application)
|
||||||
|
- **Entry Point** – `main.tsx` mounts the React app with `BrowserRouter` and wraps it with `AuthProvider`.
|
||||||
|
- **Routing** – Routes are defined per‑resource (`/admin/:resource`, `/admin/:resource/edit/:id`). `ProtectedRoute` ensures auth.
|
||||||
|
- **State Management** – TanStack Query handles server state; React Context handles auth state.
|
||||||
|
- **Theming** – MUI theming with light/dark mode toggle (future enhancement).
|
||||||
|
|
||||||
|
---
|
||||||
|
These design notes serve as a concise reference for developers preparing a richer UI/UX implementation on the **lovable** platform.
|
||||||
49
IMPLEMENTATION.md
Normal file
49
IMPLEMENTATION.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# Implementation Details
|
||||||
|
|
||||||
|
## React‑Auth
|
||||||
|
- **File Structure**
|
||||||
|
- `src/auth/AuthContext.tsx` – Provides `AuthContext` and `AuthProvider`.
|
||||||
|
- `src/auth/useAuth.ts` – Custom hook returning context values and actions.
|
||||||
|
- `src/auth/ProtectedRoute.tsx` – Wrapper component that checks `isAuthenticated` and redirects.
|
||||||
|
- `src/auth/api.ts` – Thin wrapper around `axios` for login, logout, refresh.
|
||||||
|
- **Logic**
|
||||||
|
1. On `login`, POST credentials → store `accessToken` & user info in context and `localStorage`.
|
||||||
|
2. An `axios` interceptor attaches the token to every request.
|
||||||
|
3. `refreshToken` runs on 401 responses; it attempts a silent refresh and updates the context.
|
||||||
|
4. `logout` clears context and storage, navigating back to `/login`.
|
||||||
|
- **UI Components**
|
||||||
|
- `LoginForm` – MUI `TextField`s, validation, and submit handling.
|
||||||
|
- `AuthLoading` – Full‑screen spinner displayed while session restoration runs on app boot.
|
||||||
|
|
||||||
|
## React‑OpenAPI
|
||||||
|
- **Core Files**
|
||||||
|
- `src/react-openapi/types/config.ts` – Already defines `ResourceField` with `displayFormat`.
|
||||||
|
- `src/react-openapi/utils/options.ts` – Helper `resolveTemplate` parses `{{field}}` placeholders using the item data.
|
||||||
|
- `src/react-openapi/components/EnhancedTable.tsx` – Renders a MUI `DataGrid`. Uses `getFormattedDisplayValue` to compute readable labels for relation fields based on `displayFormat`.
|
||||||
|
- `src/react-openapi/components/FilterBar.tsx` – Generates filter inputs; extracts option labels using the same `displayFormat` logic.
|
||||||
|
- **Data Fetching**
|
||||||
|
- `useResource(resourceName)` – TanStack `useQuery` hook that builds the endpoint URL from `config.endpoint` and fetches data via the shared Axios instance.
|
||||||
|
- **Customization**
|
||||||
|
- `components` prop passed to `EnhancedTable`/`FilterBar` allows overriding cell renderers, filter widgets, and action buttons.
|
||||||
|
- **Error Handling**
|
||||||
|
- Centralized error toast (`useToast`) displays API errors.
|
||||||
|
- Table shows “No data” state when an empty array is returned.
|
||||||
|
|
||||||
|
## src (Application Core)
|
||||||
|
- **src/main.tsx** – Sets up MUI theme, React Router, `AuthProvider`, and `QueryClientProvider`.
|
||||||
|
- **src/App.tsx** – Defines routes:
|
||||||
|
```tsx
|
||||||
|
<Routes>
|
||||||
|
<Route path="/login" element={<LoginForm />} />
|
||||||
|
<Route element={<ProtectedRoute />}>
|
||||||
|
<Route path="/admin/:resource" element={<ResourceList />} />
|
||||||
|
<Route path="/admin/:resource/edit/:id" element={<ResourceForm />} />
|
||||||
|
</Route>
|
||||||
|
</Routes>
|
||||||
|
```
|
||||||
|
- **src/pages/ResourceList.tsx** – Reads `resource` from URL, loads its `ResourceConfig`, calls `useResource`, and renders `EnhancedTable` + `FilterBar`.
|
||||||
|
- **src/pages/ResourceForm.tsx** – Dynamically builds a form based on `ResourceField` definitions, using `displayFormat` for default values.
|
||||||
|
- **State Management** – TanStack Query caches paginated results; `AuthProvider` ensures all API calls include a valid JWT.
|
||||||
|
- **Theming** – `ThemeProvider` toggles light/dark mode via a context hook that persists the preference in `localStorage`.
|
||||||
|
|
||||||
|
These implementation notes detail the concrete file layout, data flow, and core logic that power the UI generated from OpenAPI specifications while maintaining authenticated access. They can be directly adapted for the **lovable** platform to provide a richer UI and better handling of auth and data rendering.
|
||||||
172
REFRACTOR_GUIDE.md
Normal file
172
REFRACTOR_GUIDE.md
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
# Refactor Guide – Deep Dive into the Khata‑UI Codebase
|
||||||
|
|
||||||
|
> This document walks through the entire repository, explains the current architecture, and provides a step‑by‑step refactor plan that will improve maintainability, type safety, and UI/UX while preserving the existing functional behavior.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Repository Layout (high‑level)
|
||||||
|
```
|
||||||
|
khata-ui/
|
||||||
|
├─ react-openapi/ # Core UI generated from OpenAPI configs
|
||||||
|
│ ├─ components/ # UI pieces: EnhancedTable, FilterBar, etc.
|
||||||
|
│ ├─ types/ # TypeScript interfaces (config, overrides)
|
||||||
|
│ └─ utils/ # Helper utilities (options, template resolution)
|
||||||
|
├─ src/ # Application entry point and pages
|
||||||
|
│ ├─ auth/ # Authentication context, hooks, and protected routes
|
||||||
|
│ ├─ pages/ # Dynamic resources (list, form)
|
||||||
|
│ └─ main.tsx # React root, providers, theming
|
||||||
|
├─ public/ # Static assets (favicon, index.html)
|
||||||
|
├─ index.html
|
||||||
|
├─ package.json
|
||||||
|
└─ tsconfig.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Concepts
|
||||||
|
| Area | Responsibility |
|
||||||
|
|------|-----------------|
|
||||||
|
| **Auth** | Central JWT handling, `AuthProvider`, `useAuth`, route guarding. |
|
||||||
|
| **OpenAPI‑driven UI** | Describes each resource via `ResourceConfig`/`ResourceField`. Generates tables, filters, and forms automatically. |
|
||||||
|
| **Data Layer** | TanStack Query (`useQuery`) fetches data; Axios instance carries auth token via interceptor. |
|
||||||
|
| **Theming** | MUI theme with light/dark mode toggle (future). |
|
||||||
|
| **Extensibility** | `components` prop on `EnhancedTable` / `FilterBar` lets callers inject custom cell renderers, filter widgets, or action buttons. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Detailed Module Walk‑through
|
||||||
|
### 2.1 `react-openapi/types/config.ts`
|
||||||
|
```ts
|
||||||
|
export interface ResourceField {
|
||||||
|
displayFormat: string; // <- single source of truth for rendering
|
||||||
|
type: FieldType;
|
||||||
|
label: string;
|
||||||
|
required?: boolean;
|
||||||
|
options?: string[];
|
||||||
|
readOnly?: boolean;
|
||||||
|
schema?: Record<string, ResourceField>;
|
||||||
|
formatter?: (value: any) => string;
|
||||||
|
relation?: string;
|
||||||
|
filterType?: "autocomplete" | "multiselect" | "number-range" | "date-range";
|
||||||
|
enumOption?: EnumOption;
|
||||||
|
enumLabels?: Record<string, string>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- `displayFormat` replaces the legacy `displayField`. It can be a **template string** (`"{{first}} {{last}}"`) or an **array of keys** for concatenation.
|
||||||
|
- All UI components now rely exclusively on this field.
|
||||||
|
|
||||||
|
### 2.2 `react-openapi/utils/options.ts`
|
||||||
|
- `resolveTemplate(format: string, item: any)` – interpolates `{{key}}` placeholders.
|
||||||
|
- `getFieldOptions`, `toGridValueOptions` convert enum definitions into MUI‑compatible arrays.
|
||||||
|
- **Refactor idea**: Move the `displayFormat` resolution logic from `EnhancedTable`/`FilterBar` into a dedicated helper (`formatDisplay(item, field)`), reducing duplication.
|
||||||
|
|
||||||
|
### 2.3 `react-openapi/components/EnhancedTable.tsx`
|
||||||
|
- **Core responsibilities**
|
||||||
|
1. Build column definitions from `config.fields`.
|
||||||
|
2. Render each cell via `FieldRenderer`.
|
||||||
|
3. Provide server‑side or client‑side pagination.
|
||||||
|
4. Add a static "Actions" column.
|
||||||
|
- **Key functions**
|
||||||
|
- `getFormattedDisplayValue(item, displayFormat?, enumValue?)` – now uses `resolveTemplate` and falls back to generic fields.
|
||||||
|
- `FieldRenderer` – decides how to render a cell based on `field.type`, `field.relation`, custom renderers, and `displayFormat`.
|
||||||
|
- **Duplication**: Both `EnhancedTable` and `FilterBar` perform very similar `displayFormat` extraction. Extracting this into a shared utility will shrink the component size and make testing easier.
|
||||||
|
|
||||||
|
### 2.4 `react-openapi/components/FilterBar.tsx`
|
||||||
|
- Generates filter controls for each **filterable** field.
|
||||||
|
- Uses `extractOptions` to populate autocomplete lists, falling back to `displayFormat` for label generation.
|
||||||
|
- **Opportunity**: Replace the inline `pull` helper with the shared formatter from `utils/options`.
|
||||||
|
|
||||||
|
### 2.5 Authentication (`src/auth`)
|
||||||
|
- `AuthContext.tsx` – provides `user`, `accessToken`, `isAuthenticated` plus actions.
|
||||||
|
- `useAuth.ts` – thin wrapper exposing the context values.
|
||||||
|
- `ProtectedRoute.tsx` – guards routes, redirects to `/login` when unauthenticated.
|
||||||
|
- `api.ts` – thin Axios wrapper (`login`, `logout`, `refresh`).
|
||||||
|
- **Refactor suggestions**
|
||||||
|
- Consolidate token storage (localStorage ↔ sessionStorage) behind a small `tokenStore` service.
|
||||||
|
- Add automatic token refresh using an interceptor that retries the original request.
|
||||||
|
- Provide a hook (`useAuthorizedQuery`) that injects the auth token into TanStack Query automatically.
|
||||||
|
|
||||||
|
### 2.6 Application Core (`src/pages`, `src/main.tsx`)
|
||||||
|
- `ResourceList.tsx` – reads `resource` param, loads the related `ResourceConfig` from a central map, fetches data, and renders `EnhancedTable` + `FilterBar`.
|
||||||
|
- `ResourceForm.tsx` – builds a dynamic form based on `ResourceField` definitions; uses `displayFormat` for default values on relation fields.
|
||||||
|
- `main.tsx` – wraps the app with `AuthProvider`, `QueryClientProvider`, and MUI `ThemeProvider`.
|
||||||
|
- **Future work**: Extract the “resource loader” into a hook (`useResourceConfig(resourceName)`) that also validates the config at runtime.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Refactor Roadmap – Step‑by‑Step
|
||||||
|
### Phase 1 – Consolidate Formatting Logic
|
||||||
|
1. **Create utility** `src/react-openapi/utils/formatDisplay.ts`
|
||||||
|
```ts
|
||||||
|
export const formatDisplay = (item: any, field: ResourceField, enumValue?: string) => {
|
||||||
|
if (enumValue) return resolveTemplate(enumValue, item);
|
||||||
|
const fmt = field.displayFormat;
|
||||||
|
if (!fmt) return item.name ?? item.title ?? item.label ?? item.id ?? JSON.stringify(item);
|
||||||
|
if (Array.isArray(fmt)) {
|
||||||
|
return fmt.map(k => item[k]).filter(Boolean).join(' ');
|
||||||
|
}
|
||||||
|
return resolveTemplate(fmt, item) || item.id || JSON.stringify(item);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
2. Replace *all* inline calls to `getFormattedDisplayValue` in `EnhancedTable` and `FilterBar` with `formatDisplay`.
|
||||||
|
3. Remove `getFormattedDisplayValue` from `EnhancedTable.tsx` (or keep it as a thin wrapper for backward compatibility).
|
||||||
|
4. Update imports accordingly.
|
||||||
|
5. Run TypeScript check – no errors.
|
||||||
|
|
||||||
|
### Phase 2 – Decouple UI from Config Loading
|
||||||
|
- Introduce **`configLoader.ts`** under `src/react-openapi/utils` that reads a JSON file (or fetches a remote spec) and produces a `Record<string, ResourceConfig>`.
|
||||||
|
- Replace hard‑coded imports in `src/pages/ResourceList.tsx` with a call to `useResourceConfig(resourceName)`.
|
||||||
|
- Add runtime validation (e.g., using `zod`) to ensure required fields (`displayFormat`, `type`, `label`) are present; surface errors via a toast.
|
||||||
|
|
||||||
|
### Phase 3 – Centralize Error & Loading UI
|
||||||
|
- Create `src/components/LoadingSpinner.tsx` and `src/components/ErrorToast.tsx`.
|
||||||
|
- Wrap all data‑fetching hooks (`useResource`, `useAuth` actions) with a HOC that automatically displays these components.
|
||||||
|
- Migrate the scattered `if (loading) …` checks into the new components.
|
||||||
|
|
||||||
|
### Phase 4 – Theming & Dark Mode
|
||||||
|
1. Add a `ThemeContext` that stores `mode: 'light' | 'dark'` and persists the preference.
|
||||||
|
2. Expose a toggle button (e.g., in the top‑right corner of `App.tsx`).
|
||||||
|
3. Update component styles to use theme‑aware colors (via `theme.palette`), ensuring the `Chip` variants already respect the palette.
|
||||||
|
|
||||||
|
### Phase 5 – Testing & CI
|
||||||
|
- **Unit tests** using `vitest` for:
|
||||||
|
- `formatDisplay` utility (various template & array cases).
|
||||||
|
- `AuthProvider` behavior (login, logout, token refresh).
|
||||||
|
- **Component tests** (`@testing-library/react`) for `EnhancedTable` and `FilterBar` verifying that `displayFormat` rendering matches expectations.
|
||||||
|
- Add a GitHub Actions workflow that runs `npm run lint && npx tsc --noEmit && vitest run` on each PR.
|
||||||
|
|
||||||
|
### Phase 6 – Documentation (the files you will publish)
|
||||||
|
- **DESIGN.md** – high‑level architecture (already present).
|
||||||
|
- **IMPLEMENTATION.md** – detailed file‑by‑file breakdown (already present).
|
||||||
|
- **CONCEPT.md** – why the metadata‑driven approach works (already present).
|
||||||
|
- **REFRACTOR_GUIDE.md** – the detailed guide you are reading now (this file).
|
||||||
|
- Keep these files in the repo root; they can be exported to the **lovable** platform directly.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Migration Checklist (what to verify after refactor)
|
||||||
|
- [ ] All UI components compile with TypeScript (`npx tsc --noEmit`).
|
||||||
|
- [ ] No runtime references to `displayField` remain (search `\.displayField`).
|
||||||
|
- [ ] `formatDisplay` correctly resolves:
|
||||||
|
- Template strings with multiple placeholders.
|
||||||
|
- Array of keys.
|
||||||
|
- Fallback to generic fields.
|
||||||
|
- [ ] Auth flow works (login ➜ token stored ➜ API requests succeed, protected routes guarded).
|
||||||
|
- [ ] Pagination works both client‑ and server‑side.
|
||||||
|
- [ ] Mobile layout (card view) still renders correctly.
|
||||||
|
- [ ] Dark‑mode toggle persists across reloads.
|
||||||
|
- [ ] Lint passes (`npm run lint` if configured) and tests pass.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Potential Future Enhancements
|
||||||
|
| Feature | Benefit | Rough Implementation |
|
||||||
|
|---------|---------|----------------------|
|
||||||
|
| **Bulk actions** (delete, export) | Improves admin productivity | Add a toolbar with selection model in `EnhancedTable`. |
|
||||||
|
| **Inline editing** | Faster data tweaks | Replace `onEdit` dialog with cell‑level edit mode using MUI `TextField`. |
|
||||||
|
| **GraphQL fallback** | Flexibility for back‑ends | Abstract data fetching behind an adapter interface (`useDataProvider`). |
|
||||||
|
| **Internationalisation** | Multi‑language UI | Wrap all static strings with `i18n.t()` and provide locale files. |
|
||||||
|
| **Performance profiling** | Identify render bottlenecks | Use React Profiler and memoize expensive formatters (`useMemo`). |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Closing Note
|
||||||
|
The current codebase already demonstrates a powerful pattern: **declare once, render everywhere**. By consolidating the display logic, adding a small utility layer, and strengthening the authentication and theming foundations, the project will become easier to extend, test, and hand‑off to the **lovable** UI platform while retaining its low‑code advantage.
|
||||||
@@ -379,7 +379,9 @@ function FieldRenderer({ params, field, fieldKey, config, onNavigate, navigate,
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field.type === 'datetime' || field.type === 'date') return value ? new Date(value).toLocaleString() : '';
|
if (field.type === 'datetime') return value ? new Date(value).toLocaleString() : '';
|
||||||
|
if (field.type === 'date') return value ? new Date(value).toLocaleDateString() : '';
|
||||||
|
|
||||||
|
|
||||||
if (field.type === 'enum') {
|
if (field.type === 'enum') {
|
||||||
const opt = getFieldOptions(field).find(o => o.key === value);
|
const opt = getFieldOptions(field).find(o => o.key === value);
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ export interface FieldOverride {
|
|||||||
// New optional properties to support custom config extensions
|
// New optional properties to support custom config extensions
|
||||||
path?: string;
|
path?: string;
|
||||||
refers?: string;
|
refers?: string;
|
||||||
|
// Added support for overriding the base field type and label
|
||||||
|
type?: FieldType;
|
||||||
|
label?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ResourceOverride {
|
export interface ResourceOverride {
|
||||||
|
|||||||
@@ -57,6 +57,14 @@ export const configuration: Record<string, ResourceOverride> = {
|
|||||||
format: {
|
format: {
|
||||||
path: 'source.format',
|
path: 'source.format',
|
||||||
},
|
},
|
||||||
|
start_date: {
|
||||||
|
type: 'date',
|
||||||
|
label: 'Start Date',
|
||||||
|
},
|
||||||
|
end_date: {
|
||||||
|
type: 'date',
|
||||||
|
label: 'End Date',
|
||||||
|
},
|
||||||
// account: {
|
// account: {
|
||||||
// refers: 'accounts',
|
// refers: 'accounts',
|
||||||
// },
|
// },
|
||||||
|
|||||||
Reference in New Issue
Block a user