Merge pull request 'feat: added the base for module integrations' (#12) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/saas_frontend/pulls/12
This commit is contained in:
@@ -1,25 +1,114 @@
|
||||
import { Construction } from 'lucide-react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ExternalLink, Box } from 'lucide-react';
|
||||
import { moduleApi, type Module } from '../module/ModuleApi';
|
||||
|
||||
const Dashboard = () => {
|
||||
const { t } = useTranslation('dashboard');
|
||||
const [modules, setModules] = useState<Module[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchModules = async () => {
|
||||
try {
|
||||
const fetchedModules = await moduleApi.getAvailableModules();
|
||||
setModules(fetchedModules);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch modules', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchModules();
|
||||
}, []);
|
||||
|
||||
const handleModuleClick = async (moduleId: string) => {
|
||||
try {
|
||||
const response = await moduleApi.launchModule(moduleId);
|
||||
if (response.redirect_url) {
|
||||
window.location.href = response.redirect_url;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to launch module', error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-[80vh] flex flex-col items-center justify-center p-4 text-center animate-fade-in">
|
||||
<div className="bg-[var(--card-bg)] p-8 rounded-2xl border border-[var(--card-border)] shadow-sm max-w-lg w-full flex flex-col items-center">
|
||||
<div className="bg-orange-50 p-4 rounded-full mb-6">
|
||||
<Construction className="w-12 h-12 text-orange-500" />
|
||||
</div>
|
||||
|
||||
<h1 className="text-3xl font-bold text-[var(--text-primary)] mb-3">
|
||||
<div className="p-6 space-y-6">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-(--text-primary) mb-2">
|
||||
{t('title')}
|
||||
</h1>
|
||||
|
||||
<p className="text-[var(--text-secondary)] text-lg mb-8 leading-relaxed">
|
||||
<p className="text-(--text-secondary)">
|
||||
{t('message')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Modules Section */}
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold text-(--text-primary) mb-4">
|
||||
Available Modules
|
||||
</h2>
|
||||
|
||||
{loading ? (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{[1, 2, 3].map((i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="bg-(--card-bg) border border-(--card-border) rounded-lg p-6 animate-pulse"
|
||||
>
|
||||
<div className="h-12 w-12 bg-gray-300 rounded-lg mb-4"></div>
|
||||
<div className="h-6 bg-gray-300 rounded w-3/4 mb-2"></div>
|
||||
<div className="h-4 bg-gray-300 rounded w-full"></div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : modules.length > 0 ? (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{modules.map((module) => (
|
||||
<button
|
||||
key={module.module_id}
|
||||
onClick={() => handleModuleClick(module.module_id)}
|
||||
className="bg-(--card-bg) border border-(--card-border) rounded-lg p-6 hover:shadow-lg transition-all duration-200 text-left group hover:border-blue-500"
|
||||
>
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<div className="flex items-center gap-3">
|
||||
{module.icon_url ? (
|
||||
<img
|
||||
src={module.icon_url}
|
||||
alt={module.module_name}
|
||||
className="w-12 h-12 rounded-lg"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-12 h-12 rounded-lg bg-blue-100 flex items-center justify-center">
|
||||
<Box className="w-6 h-6 text-blue-600" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<ExternalLink className="w-5 h-5 text-(--text-secondary) group-hover:text-blue-500 transition-colors" />
|
||||
</div>
|
||||
|
||||
<h3 className="text-lg font-semibold text-(--text-primary) mb-2">
|
||||
{module.module_name}
|
||||
</h3>
|
||||
|
||||
{module.description && (
|
||||
<p className="text-sm text-(--text-secondary) line-clamp-2">
|
||||
{module.description}
|
||||
</p>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-(--card-bg) border border-(--card-border) rounded-lg p-8 text-center">
|
||||
<Box className="w-12 h-12 text-(--text-secondary) mx-auto mb-4" />
|
||||
<p className="text-(--text-secondary)">
|
||||
No modules available
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import axios from "axios";
|
||||
import { API_BASE_URL } from "../../constant";
|
||||
|
||||
export interface Module {
|
||||
module_id: string;
|
||||
module_name: string;
|
||||
description: string | null;
|
||||
icon_url: string | null;
|
||||
display_order: number;
|
||||
is_active: boolean;
|
||||
}
|
||||
|
||||
export interface LaunchResponse {
|
||||
redirect_url: string;
|
||||
grant_code: string;
|
||||
}
|
||||
|
||||
export const moduleApi = {
|
||||
getAvailableModules: async (): Promise<Module[]> => {
|
||||
const response = await axios.get(`${API_BASE_URL}/api/modules/available`, {
|
||||
withCredentials: true,
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
|
||||
launchModule: async (moduleId: string): Promise<LaunchResponse> => {
|
||||
const response = await axios.post(
|
||||
`${API_BASE_URL}/api/sso/initiate`,
|
||||
{ module_id: moduleId },
|
||||
{ withCredentials: true }
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
+1
-2
@@ -1,2 +1 @@
|
||||
// Use VITE_API_BASE_URL to match your backend host when working with cookies.
|
||||
export const API_BASE_URL = import.meta.env.VITE_API_BASE_URL;
|
||||
export const API_BASE_URL = import.meta.env.VITE_API_BASE_URL;
|
||||
Reference in New Issue
Block a user