profile view

This commit is contained in:
2026-04-03 13:40:06 +05:30
parent aa04b105d0
commit 399b2656b8
7 changed files with 95 additions and 5 deletions

View File

@@ -64,6 +64,8 @@ function Dashboard() {
); );
} }
import ProfileView from "./components/ProfileView";
function AdminApp() { function AdminApp() {
const { currentUser, login, logout, loading, error } = useAuth(); const { currentUser, login, logout, loading, error } = useAuth();
const config = React.useContext(ConfigContext); const config = React.useContext(ConfigContext);
@@ -93,6 +95,7 @@ function AdminApp() {
> >
<Routes> <Routes>
<Route path="/" element={<Dashboard />} /> <Route path="/" element={<Dashboard />} />
<Route path="/profile" element={<ProfileView />} />
<Route path="/:resourceName" element={<ResourceRouteWrapper />} /> <Route path="/:resourceName" element={<ResourceRouteWrapper />} />
<Route path="/:resourceName/:id" element={<ResourceRouteWrapper />} /> <Route path="/:resourceName/:id" element={<ResourceRouteWrapper />} />
<Route path="/:resourceName/create" element={<ResourceRouteWrapper />} /> <Route path="/:resourceName/create" element={<ResourceRouteWrapper />} />

View File

@@ -12,6 +12,7 @@ import {
ListItemIcon, ListItemIcon,
ListItemText, ListItemText,
CssBaseline, CssBaseline,
Button,
IconButton, IconButton,
Tooltip, Tooltip,
useMediaQuery, useMediaQuery,
@@ -176,9 +177,15 @@ export default function AdminLayout({
<Typography variant="h6" noWrap component="div" sx={{ flexGrow: 1, fontWeight: 'bold' }}> <Typography variant="h6" noWrap component="div" sx={{ flexGrow: 1, fontWeight: 'bold' }}>
Admin Panel Admin Panel
</Typography> </Typography>
<Typography variant="body1" sx={{ mr: 2, fontWeight: 500, display: { xs: 'none', sm: 'block' } }}> <Box sx={{ display: { xs: 'none', sm: 'flex' }, alignItems: 'center', mr: 2 }}>
{username} <Button
</Typography> color="inherit"
onClick={() => navigate('/profile')}
sx={{ textTransform: 'none', fontWeight: 500 }}
>
{username}
</Button>
</Box>
<Tooltip title="Logout"> <Tooltip title="Logout">
<IconButton color="inherit" onClick={onLogout}> <IconButton color="inherit" onClick={onLogout}>
<LogoutIcon /> <LogoutIcon />

View File

@@ -0,0 +1,58 @@
import * as React from 'react';
import { Box, Typography, Paper, CircularProgress, Alert } from '@mui/material';
import { useResource } from '../hooks/useResource';
import GenericForm from './GenericForm';
import { ConfigContext } from '../App';
export default function ProfileView() {
const appConfig = React.useContext(ConfigContext);
const profileConfig = appConfig?.profile;
const resourceConfig = appConfig?.resources.find(r => r.name === profileConfig?.resource);
if (!profileConfig || !resourceConfig) {
debugger;
return <Alert severity="error">Profile configuration not found.</Alert>;
}
const { useMe, useUpdate } = useResource(resourceConfig);
const { data: profile, isLoading, error } = useMe();
const updateMutation = useUpdate();
const handleSave = async (formData: any) => {
try {
const id = profile[resourceConfig.primaryKey];
await updateMutation.mutateAsync({ id, data: formData });
} catch (err) {
console.error('Profile update failed:', err);
}
};
if (isLoading) {
return (
<Box sx={{ display: 'flex', justifyContent: 'center', p: 4 }}>
<CircularProgress />
</Box>
);
}
if (error) {
return <Alert severity="error">Failed to load profile data.</Alert>;
}
return (
<Box sx={{ maxWidth: 800, mx: 'auto', mt: 4 }}>
<Typography variant="h4" gutterBottom>
My Profile
</Typography>
<Paper sx={{ p: 4, mt: 2 }}>
<GenericForm
config={resourceConfig}
initialData={profile}
onSave={handleSave}
onCancel={() => window.history.back()}
loading={updateMutation.isPending}
/>
</Paper>
</Box>
);
}

View File

@@ -40,3 +40,8 @@ export const configuration: Record<string, ResourceOverride> = {
}, },
}, },
}; };
export const profileConfiguration = {
"extraFields": ['name'],
"resource": "payors"
};

View File

@@ -81,9 +81,21 @@ export function useResource<T = any>(config: ResourceConfig) {
}, },
}); });
// --- READ ME ---
const useMe = () =>
useQuery({
queryKey: [name, "me"],
queryFn: async () => {
// @ts-ignore
const res = await api.get<T>(`${endpoint}/me`);
return res.data;
},
});
return { return {
useList, useList,
useRead, useRead,
useMe,
useCreate, useCreate,
useUpdate, useUpdate,
useDelete, useDelete,

View File

@@ -35,4 +35,8 @@ export interface AppConfig {
baseUrl: string; baseUrl: string;
authBaseUrl: string; authBaseUrl: string;
resources: ResourceConfig[]; resources: ResourceConfig[];
profile?: {
resource: string;
extraFields?: Record<string, any>;
};
} }

View File

@@ -1,6 +1,6 @@
import SwaggerParser from "@apidevtools/swagger-parser"; import SwaggerParser from "@apidevtools/swagger-parser";
import { AppConfig, ResourceConfig, ResourceField, FieldType } from "../types/config"; import { AppConfig, ResourceConfig, ResourceField, FieldType } from "../types/config";
import { configuration } from "../configuration"; import { configuration, profileConfiguration } from "../configuration";
/** /**
* Maps OpenAPI property types to our internal FieldType * Maps OpenAPI property types to our internal FieldType
@@ -115,7 +115,7 @@ export async function loadConfigFromOpenApi(baseUrl: string): Promise<AppConfig>
resourcePaths[base].methods.push(...methods); resourcePaths[base].methods.push(...methods);
// Identify the list endpoint for this resource // Identify the list endpoint for this resource
if (!path.includes("{") && paths[path]?.get?.responses?.["200"]) { if (!resourcePaths[base].listPath && !path.includes("{") && paths[path]?.get?.responses?.["200"]) {
resourcePaths[base].listPath = path; resourcePaths[base].listPath = path;
} }
} }
@@ -170,5 +170,6 @@ export async function loadConfigFromOpenApi(baseUrl: string): Promise<AppConfig>
baseUrl: serverBaseUrl, baseUrl: serverBaseUrl,
authBaseUrl: authBaseUrl, authBaseUrl: authBaseUrl,
resources, resources,
profile: profileConfiguration,
}; };
} }