Skip to content

Commit 8a5e3a7

Browse files
committed
Merge branch 'develop' of https://github.com/fahad-aot/forms-flow-ai-micro-front-ends into feature/fwf-5993-integrate-login-detials-api
2 parents 38441fd + 9fdb2c6 commit 8a5e3a7

File tree

18 files changed

+667
-150
lines changed

18 files changed

+667
-150
lines changed

forms-flow-admin/src/components/dashboard/dashboard.tsx

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
fetchdashboards,
1414
} from "../../services/dashboard";
1515
import { Translation, useTranslation } from "react-i18next";
16-
import { TableFooter, V8CustomButton, BreadCrumbs } from "@formsflow/components";
16+
import { TableFooter, V8CustomButton } from "@formsflow/components";
1717
import { useHistory } from "react-router-dom";
1818
import { navigateToAdminDashboard, getRedirectUrl } from "@formsflow/service";
1919

@@ -238,28 +238,8 @@ const InsightDashboard = React.memo((props: any) => {
238238
return list;
239239
};
240240

241-
// Breadcrumb configuration
242-
const breadcrumbItems = [
243-
{ label: t("Manage"), id: "manage" },
244-
{ label: t("Dashboards"), id: "dashboards" }
245-
];
246-
247-
const handleBreadcrumbClick = (item: { label: string; id?: string }) => {
248-
if (item.id === "manage" || item.id === "dashboards") {
249-
navigateToAdminDashboard(history, tenantId);
250-
}
251-
};
252-
253241
return (
254242
<>
255-
<div style={{ marginBottom: "15px" }}>
256-
<BreadCrumbs
257-
items={breadcrumbItems}
258-
variant="default"
259-
onBreadcrumbClick={handleBreadcrumbClick}
260-
dataTestId="admin-dashboard-breadcrumbs"
261-
/>
262-
</div>
263243
<div className="" role="definition">
264244
<br />
265245
<div>
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import React, { useState, useEffect, useMemo } from "react";
2+
import { Tabs, Tab, Collapse } from "react-bootstrap";
3+
import { useTranslation } from "react-i18next";
4+
import { useHistory, useParams, useLocation } from "react-router-dom";
5+
import AdminDashboard from "../dashboard";
6+
import RoleManagement from "../roles";
7+
import UserManagement from "../users";
8+
import Organization from "../organization";
9+
import { StorageService } from "@formsflow/service";
10+
import { BreadCrumbs, UpArrowIcon, DownArrowIcon } from "@formsflow/components";
11+
import { MULTITENANCY_ENABLED } from "../../constants";
12+
13+
interface ManageProps {
14+
props: any;
15+
setTab: (tab: string) => void;
16+
setDashboardCount?: React.Dispatch<React.SetStateAction<number | undefined>>;
17+
setRoleCount?: React.Dispatch<React.SetStateAction<number | undefined>>;
18+
setUserCount?: React.Dispatch<React.SetStateAction<number | undefined>>;
19+
}
20+
21+
const Manage: React.FC<ManageProps> = ({ props, setTab, setDashboardCount, setRoleCount, setUserCount }) => {
22+
const { t } = useTranslation();
23+
const history = useHistory();
24+
const { tenantId, tab: urlTab } = useParams<{ tenantId?: string; tab?: string }>();
25+
const location = useLocation();
26+
const [tabContentExpanded, setTabContentExpanded] = useState<boolean>(true);
27+
28+
const userRoles = JSON.parse(
29+
StorageService.get(StorageService.User.USER_ROLE) || "[]"
30+
);
31+
32+
const isDashboardManager = userRoles?.includes("manage_dashboard_authorizations");
33+
const isRoleManager = userRoles?.includes("manage_roles");
34+
const isUserManager = userRoles?.includes("manage_users");
35+
36+
const baseUrl = MULTITENANCY_ENABLED ? `/tenant/${tenantId}/` : "/";
37+
38+
// Get active tab from URL or default to organization
39+
const activeTab = useMemo((): string => {
40+
if (urlTab) {
41+
// Validate that the tab from URL is valid
42+
const validTabs = ["organization", "dashboard", "users", "roles"];
43+
if (validTabs.includes(urlTab)) {
44+
// Check permissions for restricted tabs
45+
if (urlTab === "dashboard" && !isDashboardManager) return "organization";
46+
if (urlTab === "users" && !isUserManager) return "organization";
47+
if (urlTab === "roles" && !isRoleManager) return "organization";
48+
return urlTab;
49+
}
50+
}
51+
// If no tab in URL or invalid tab, check if we're at /admin (without tab)
52+
if (location.pathname === `${baseUrl}admin` || location.pathname === `${baseUrl}admin/`) {
53+
return "organization";
54+
}
55+
return "organization";
56+
}, [urlTab, location.pathname, baseUrl, isDashboardManager, isUserManager, isRoleManager]);
57+
58+
// Redirect to default tab if on /admin without a tab
59+
useEffect(() => {
60+
if (location.pathname === `${baseUrl}admin` || location.pathname === `${baseUrl}admin/`) {
61+
history.replace(`${baseUrl}admin/organization`);
62+
}
63+
}, [location.pathname, baseUrl, history]);
64+
65+
const handleTabChange = (key: string | null) => {
66+
if (key) {
67+
const tabNameMap: { [key: string]: string } = {
68+
"organization": "Organization",
69+
"dashboard": "Dashboard",
70+
"users": "Users",
71+
"roles": "Roles"
72+
};
73+
setTab(tabNameMap[key] || "Organization");
74+
// Navigate to the tab route - this will update the URL and activeTab will update via useMemo
75+
history.push(`${baseUrl}admin/${key}`);
76+
}
77+
};
78+
79+
const handleTabContentToggle = () => {
80+
setTabContentExpanded(!tabContentExpanded);
81+
};
82+
83+
const breadcrumbItems = [
84+
{ label: t("Manage"), id: "manage" }
85+
];
86+
87+
return (
88+
<div className="manage-container">
89+
<div className="header-section-1">
90+
<div className="section-seperation-left">
91+
<BreadCrumbs
92+
items={breadcrumbItems}
93+
variant="default"
94+
dataTestId="manage-breadcrumbs"
95+
/>
96+
</div>
97+
</div>
98+
99+
<div className="manage-tabs-wrapper">
100+
<div className="manage-tabs-header">
101+
<Tabs
102+
activeKey={activeTab}
103+
onSelect={handleTabChange}
104+
id="manage-tabs"
105+
className="pill-tabs"
106+
>
107+
<Tab eventKey="organization" title={t("Organization")} />
108+
{isDashboardManager && (
109+
<Tab eventKey="dashboard" title={t("Dashboards")} />
110+
)}
111+
{isUserManager && (
112+
<Tab eventKey="users" title={t("Users")} />
113+
)}
114+
{isRoleManager && (
115+
<Tab eventKey="roles" title={t("Roles")} />
116+
)}
117+
</Tabs>
118+
<div
119+
className="manage-tabs-chevron"
120+
onClick={handleTabContentToggle}
121+
role="button"
122+
tabIndex={0}
123+
onKeyDown={(e) => {
124+
if (e.key === "Enter" || e.key === " ") {
125+
e.preventDefault();
126+
handleTabContentToggle();
127+
}
128+
}}
129+
>
130+
{tabContentExpanded ? (
131+
<UpArrowIcon className="svgIcon-medium-dark" />
132+
) : (
133+
<DownArrowIcon className="svgIcon-medium-dark" />
134+
)}
135+
</div>
136+
</div>
137+
<Collapse in={tabContentExpanded}>
138+
<div>
139+
<div className="tab-content">
140+
{activeTab === "organization" && (
141+
<div className="manage-content">
142+
<Organization {...props} />
143+
</div>
144+
)}
145+
{activeTab === "dashboard" && isDashboardManager && (
146+
<div className="manage-content">
147+
<AdminDashboard
148+
{...props}
149+
setTab={setTab}
150+
setCount={setDashboardCount}
151+
/>
152+
</div>
153+
)}
154+
{activeTab === "users" && isUserManager && (
155+
<div className="manage-content">
156+
<UserManagement
157+
{...props}
158+
setTab={setTab}
159+
setCount={setUserCount}
160+
/>
161+
</div>
162+
)}
163+
{activeTab === "roles" && isRoleManager && (
164+
<div className="manage-content">
165+
<RoleManagement
166+
{...props}
167+
setTab={setTab}
168+
setCount={setRoleCount}
169+
/>
170+
</div>
171+
)}
172+
</div>
173+
</div>
174+
</Collapse>
175+
</div>
176+
</div>
177+
);
178+
};
179+
180+
export default Manage;
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import React, { useEffect, useState, useCallback } from "react";
2+
import { useTranslation } from "react-i18next";
3+
import { Collapse } from "react-bootstrap";
4+
import { V8CustomButton, UpArrowIcon, DownArrowIcon } from "@formsflow/components";
5+
import "./organization.scss";
6+
import { StorageService } from "@formsflow/service";
7+
8+
interface AccordionSectionProps {
9+
title: string;
10+
isOpen: boolean;
11+
onToggle: () => void;
12+
children: React.ReactNode;
13+
}
14+
15+
const AccordionSection: React.FC<AccordionSectionProps> = ({ title, isOpen, onToggle, children }) => {
16+
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
17+
if (e.key === "Enter" || e.key === " ") {
18+
e.preventDefault();
19+
onToggle();
20+
}
21+
}, [onToggle]);
22+
23+
return (
24+
<div className="organization-section">
25+
<div
26+
className="organization-section-header"
27+
onClick={onToggle}
28+
role="button"
29+
tabIndex={0}
30+
onKeyDown={handleKeyDown}
31+
>
32+
<h3 className="organization-section-title">{title}</h3>
33+
{isOpen ? <UpArrowIcon className="svgIcon-medium-dark"/> : <DownArrowIcon className="svgIcon-medium-dark"/>}
34+
</div>
35+
<Collapse in={isOpen}>
36+
<div>{children}</div>
37+
</Collapse>
38+
</div>
39+
);
40+
};
41+
42+
const Organization: React.FC<any> = (props) => {
43+
const { t } = useTranslation();
44+
const [subscriptionOpen, setSubscriptionOpen] = useState(true);
45+
const [termsOpen, setTermsOpen] = useState(true);
46+
const [daysDifference, setDaysDifference] = useState<number | null>(null);
47+
48+
useEffect(() => {
49+
// Calculate remaining days from expiry_dt
50+
try {
51+
const tenantDataStr = StorageService.get("TENANT_DATA");
52+
const expiry_dt = tenantDataStr
53+
? JSON.parse(tenantDataStr)?.expiry_dt
54+
: null;
55+
56+
if (expiry_dt && !Number.isNaN(Date.parse(expiry_dt))) {
57+
const expiry = new Date(expiry_dt);
58+
const currentDate = new Date();
59+
currentDate.setHours(0, 0, 0, 0);
60+
expiry.setHours(0, 0, 0, 0);
61+
const timeDifference = expiry.getTime() - currentDate.getTime();
62+
const days = Math.floor(timeDifference / (1000 * 60 * 60 * 24));
63+
setDaysDifference(days);
64+
} else {
65+
setDaysDifference(null);
66+
}
67+
} catch (error) {
68+
console.error("Error calculating days difference:", error);
69+
setDaysDifference(null);
70+
}
71+
}, []);
72+
73+
74+
const isTrial = daysDifference !== null && daysDifference > 0;
75+
const subscriptionBtnLabel = isTrial ? t("Upgrade") : t("Contact Sales");
76+
77+
const renderExternalButtons = (label: string) => {
78+
const key = label.toLowerCase()
79+
.replace(/view our /g, '')
80+
.split(' ')[0]; // Extract first word after "view our"
81+
const dataTestId = `view-${key}-button`;
82+
83+
return (
84+
<V8CustomButton
85+
label={t(label)}
86+
variant="secondary"
87+
dataTestId={dataTestId}
88+
icon={<i className="fa fa-external-link me-2" aria-hidden="true"></i>}
89+
/>
90+
);
91+
};
92+
93+
return (
94+
<div className="organization-container">
95+
<div className="organization-content">
96+
<AccordionSection
97+
title={t("Subscription")}
98+
isOpen={subscriptionOpen}
99+
onToggle={() => setSubscriptionOpen(!subscriptionOpen)}
100+
>
101+
<div className="subscription-card">
102+
<div className="subscription-status">
103+
<span className="status-text">{isTrial ? t("Trial") : t("Active")}</span>
104+
</div>
105+
<p className="subscription-description">
106+
{isTrial
107+
? t(`You have ${daysDifference} days left of your free trial.`)
108+
: t("You are currently using a paid version of FormsFlow.")}
109+
</p>
110+
{renderExternalButtons(subscriptionBtnLabel)}
111+
</div>
112+
</AccordionSection>
113+
114+
<AccordionSection
115+
title={t("Terms & Conditions")}
116+
isOpen={termsOpen}
117+
onToggle={() => setTermsOpen(!termsOpen)}
118+
>
119+
<div className="terms-actions">
120+
{renderExternalButtons('View our Terms and Conditions')}
121+
{renderExternalButtons('View our Privacy Policy')}
122+
</div>
123+
</AccordionSection>
124+
</div>
125+
</div>
126+
);
127+
};
128+
129+
export default Organization;

0 commit comments

Comments
 (0)