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:
furqan
2026-01-20 12:08:59 +00:00
3 changed files with 135 additions and 13 deletions
+100 -11
View File
@@ -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>
);
+34
View File
@@ -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
View File
@@ -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;