production V2

This commit is contained in:
kenilkb 2026-07-17 10:18:36 +05:30
parent 61f3bd8057
commit 8687d72394
7 changed files with 168 additions and 54 deletions

View File

@ -18,7 +18,7 @@ export const PageHeader: React.FC<PageHeaderProps> = ({ title, subtitle, badge,
{badge && <div className="shrink-0">{badge}</div>} {badge && <div className="shrink-0">{badge}</div>}
</div> </div>
{subtitle && ( {subtitle && (
<p className="text-xs page-subtitle font-medium text-ink-500 truncate max-w-3xl"> <p className="text-xs page-subtitle font-medium text-ink-500 max-w-3xl leading-relaxed">
{subtitle} {subtitle}
</p> </p>
)} )}

View File

@ -274,12 +274,12 @@ export const AssetsPage = () => {
<PageHeader <PageHeader
title="Asset Library" title="Asset Library"
subtitle="Securely manage, distribute, and track marketing collateral and partner resources." subtitle="Securely manage, distribute, and track marketing collateral and partner resources."
badge={ // badge={
<div className="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full bg-ink-100 border border-ink-200 text-[10px] font-bold text-ink-900 tracking-wider uppercase shrink-0"> // <div className="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full bg-ink-100 border border-ink-200 text-[10px] font-bold text-ink-900 tracking-wider uppercase shrink-0">
<CheckCircle className="w-3.5 h-3.5 text-ink-950" /> // <CheckCircle className="w-3.5 h-3.5 text-ink-950" />
<span>Global CDN Active</span> // <span>Global CDN Active</span>
</div> // </div>
} // }
/> />
); );
@ -306,8 +306,7 @@ export const AssetsPage = () => {
<div className="flex items-center gap-1 bg-ink-50/50 p-1 rounded-xl border border-ink-200 shadow-sm shrink-0"> <div className="flex items-center gap-1 bg-ink-50/50 p-1 rounded-xl border border-ink-200 shadow-sm shrink-0">
<button <button
onClick={() => setSelectedCategory("ALL")} onClick={() => setSelectedCategory("ALL")}
className={`px-3.5 py-1.5 rounded-lg text-[10px] uppercase tracking-wider font-extrabold transition-all cursor-pointer whitespace-nowrap ${ className={`px-3.5 py-1.5 rounded-lg text-[10px] uppercase tracking-wider font-extrabold transition-all cursor-pointer whitespace-nowrap ${selectedCategory === "ALL"
selectedCategory === "ALL"
? "bg-ink-900 text-ink-0 shadow-sm" ? "bg-ink-900 text-ink-0 shadow-sm"
: "text-ink-600 hover:text-ink-900 hover:bg-ink-100/50" : "text-ink-600 hover:text-ink-900 hover:bg-ink-100/50"
}`} }`}
@ -316,8 +315,7 @@ export const AssetsPage = () => {
</button> </button>
<button <button
onClick={() => setSelectedCategory("RECOMMENDED")} onClick={() => setSelectedCategory("RECOMMENDED")}
className={`px-3.5 py-1.5 rounded-lg text-[10px] uppercase tracking-wider font-extrabold transition-all cursor-pointer whitespace-nowrap flex items-center gap-1.5 ${ className={`px-3.5 py-1.5 rounded-lg text-[10px] uppercase tracking-wider font-extrabold transition-all cursor-pointer whitespace-nowrap flex items-center gap-1.5 ${selectedCategory === "RECOMMENDED"
selectedCategory === "RECOMMENDED"
? "bg-gradient-to-r from-amber-500 to-amber-600 text-zinc-950 shadow-md shadow-amber-500/25" ? "bg-gradient-to-r from-amber-500 to-amber-600 text-zinc-950 shadow-md shadow-amber-500/25"
: "text-amber-700 dark:text-amber-400 hover:bg-amber-500/10" : "text-amber-700 dark:text-amber-400 hover:bg-amber-500/10"
}`} }`}

View File

@ -65,11 +65,16 @@ export const DashboardPage = () => {
} }
]; ];
const isAdmin = user?.role === 'ADMIN';
// Header component // Header component
const headerNode = ( const headerNode = (
<PageHeader <PageHeader
title={`Welcome back, ${user?.email?.split('@')[0]}`} title={`Welcome back, ${user?.email?.split('@')[0]}`}
subtitle={`You are authenticated as ${user?.role}. Manage your channel network, monitor compliance, and distribute assets globally.`} subtitle={isAdmin
? 'Manage your channel network, monitor compliance, and distribute assets globally.'
: `Access your shared resources, legal agreements, and explore our ecosystem of products and services.`
}
badge={ badge={
<div className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-ink-900 border border-ink-800 text-[10px] font-bold text-ink-0 tracking-wider uppercase shrink-0"> <div className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-ink-900 border border-ink-800 text-[10px] font-bold text-ink-0 tracking-wider uppercase shrink-0">
<div className="w-1 h-1 rounded-full bg-ink-0 animate-pulse" /> <div className="w-1 h-1 rounded-full bg-ink-0 animate-pulse" />

View File

@ -7,7 +7,6 @@ import {
Cloud, Cloud,
Globe, Globe,
ExternalLink, ExternalLink,
Sparkles,
Layers Layers
} from 'lucide-react'; } from 'lucide-react';
import { PageHeader } from '../components/ui/PageHeader'; import { PageHeader } from '../components/ui/PageHeader';
@ -52,12 +51,6 @@ export const EcosystemPage: React.FC = () => {
<PageHeader <PageHeader
title="Ecosystem Explorer" title="Ecosystem Explorer"
subtitle="Unlock access to Tech4Biz's complete network of industry-leading products, platforms, and specialized services." subtitle="Unlock access to Tech4Biz's complete network of industry-leading products, platforms, and specialized services."
badge={
<div className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-ink-950/5 dark:bg-ink-0/10 border border-ink-300 dark:border-ink-700 text-[10px] font-black text-ink-900 dark:text-ink-300 tracking-wider uppercase shrink-0">
<Sparkles className="w-3.5 h-3.5" />
<span>Ecosystem Active</span>
</div>
}
/> />
); );
@ -71,8 +64,7 @@ export const EcosystemPage: React.FC = () => {
<button <button
key={type} key={type}
onClick={() => setFilter(type)} onClick={() => setFilter(type)}
className={`px-4 py-2 rounded-lg text-xs font-black tracking-wider uppercase transition-all duration-300 cursor-pointer ${ className={`px-4 py-2 rounded-lg text-xs font-black tracking-wider uppercase transition-all duration-300 cursor-pointer ${filter === type
filter === type
? 'bg-ink-0 text-ink-950 shadow-sm border border-ink-200/50' ? 'bg-ink-0 text-ink-950 shadow-sm border border-ink-200/50'
: 'text-ink-500 hover:text-ink-900' : 'text-ink-500 hover:text-ink-900'
}`} }`}
@ -123,8 +115,7 @@ export const EcosystemPage: React.FC = () => {
)} )}
</div> </div>
<span className={`inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-[9px] font-black tracking-widest uppercase border ${ <span className={`inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-[9px] font-black tracking-widest uppercase border ${isProduct
isProduct
? 'bg-blue-500/10 text-blue-600 border-blue-500/20' ? 'bg-blue-500/10 text-blue-600 border-blue-500/20'
: 'bg-emerald-500/10 text-emerald-600 border-emerald-500/20' : 'bg-emerald-500/10 text-emerald-600 border-emerald-500/20'
}`}> }`}>
@ -153,8 +144,7 @@ export const EcosystemPage: React.FC = () => {
<ul className="grid grid-cols-1 gap-2.5 pt-1"> <ul className="grid grid-cols-1 gap-2.5 pt-1">
{offering.benefits.map((benefit, bIdx) => ( {offering.benefits.map((benefit, bIdx) => (
<li key={bIdx} className="flex items-start gap-2.5 text-xs font-medium text-ink-600"> <li key={bIdx} className="flex items-start gap-2.5 text-xs font-medium text-ink-600">
<span className={`w-1.5 h-1.5 rounded-full shrink-0 mt-1.5 ${ <span className={`w-1.5 h-1.5 rounded-full shrink-0 mt-1.5 ${isProduct ? 'bg-blue-500' : 'bg-emerald-500'
isProduct ? 'bg-blue-500' : 'bg-emerald-500'
}`} /> }`} />
<span>{benefit}</span> <span>{benefit}</span>
</li> </li>

View File

@ -139,12 +139,12 @@ export const LoginPage = () => {
<label className="block text-[10px] font-bold text-ink-500 uppercase tracking-wider"> <label className="block text-[10px] font-bold text-ink-500 uppercase tracking-wider">
Password Password
</label> </label>
<a {/* <a
href="#" href="#"
className="text-[10px] font-bold text-ink-500 hover:text-ink-900 transition-colors tracking-wider" className="text-[10px] font-bold text-ink-500 hover:text-ink-900 transition-colors tracking-wider"
> >
RECOVERY? RECOVERY?
</a> </a> */}
</div> </div>
<div className="relative group"> <div className="relative group">
<div className="absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none"> <div className="absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none">

View File

@ -950,6 +950,127 @@ export const DirectoryPage: React.FC = () => {
size="lg" size="lg"
> >
<div className="space-y-4 font-sans"> <div className="space-y-4 font-sans">
{/* ── Group Assignment Section ── */}
{!isAddingPartnerAssets && (
<div className="bg-ink-50/60 border border-ink-200 rounded-xl p-4 space-y-3">
<div className="flex items-center justify-between">
<h4 className="text-[10px] font-extrabold uppercase tracking-widest text-ink-500 flex items-center gap-1.5">
<Folder className="w-3.5 h-3.5 text-ink-600" />
<span>Recommendation Groups</span>
</h4>
</div>
{/* Current Groups as Tags */}
<div className="flex flex-wrap gap-2 items-center min-h-[28px]">
{(() => {
const currentGroups = selectedPartnerForAssets?.partnerGroup
? selectedPartnerForAssets.partnerGroup.split(',').map((s: string) => s.trim()).filter(Boolean)
: [];
if (currentGroups.length === 0) {
return (
<span className="text-[10px] text-ink-400 font-medium italic">No recommendation groups assigned</span>
);
}
return currentGroups.map((groupName: string) => (
<span
key={groupName}
className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-lg bg-ink-900 text-ink-0 text-[10px] font-bold tracking-wide"
>
<span>{groupName}</span>
<button
type="button"
onClick={async () => {
if (!selectedPartnerForAssets) return;
const updatedGroups = currentGroups
.filter((g: string) => g.toLowerCase() !== groupName.toLowerCase())
.join(', ');
try {
const currentAssetIds = selectedPartnerForAssets.sharedAssets?.map((sa: any) => sa.assetId) || [];
const updated = await updateMutation.mutateAsync({
partnerId: selectedPartnerForAssets.id,
params: {
partnerGroup: updatedGroups || null as any,
sharedAssetIds: currentAssetIds,
assignedNdaId: selectedPartnerForAssets.assignedNdaId === null ? "NONE" : selectedPartnerForAssets.assignedNdaId || undefined,
assignedMsaId: selectedPartnerForAssets.assignedMsaId === null ? "NONE" : selectedPartnerForAssets.assignedMsaId || undefined,
mfaEnabled: selectedPartnerForAssets.mfaEnabled,
showEcosystemTab: selectedPartnerForAssets.showEcosystemTab,
}
});
setSelectedPartnerForAssets(updated);
fetchPartners();
success("Group removed", `"${groupName}" removed from this partner's recommendations.`);
} catch (err: any) {
error("Failed to update", err.response?.data?.error || "Something went wrong.");
}
}}
className="hover:bg-ink-0/20 rounded-full p-0.5 transition-colors cursor-pointer"
title={`Remove ${groupName}`}
>
<svg className="w-2.5 h-2.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={3}>
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</span>
));
})()}
{/* Add Group Dropdown */}
{(() => {
const currentGroups = selectedPartnerForAssets?.partnerGroup
? selectedPartnerForAssets.partnerGroup.split(',').map((s: string) => s.trim().toLowerCase()).filter(Boolean)
: [];
const availableGroups = assetGroups.filter(
g => !currentGroups.includes(g.name.trim().toLowerCase())
);
if (availableGroups.length === 0) return null;
return (
<select
value=""
onChange={async (e) => {
const groupName = e.target.value;
if (!groupName || !selectedPartnerForAssets) return;
const existingGroups = selectedPartnerForAssets.partnerGroup
? selectedPartnerForAssets.partnerGroup.split(',').map((s: string) => s.trim()).filter(Boolean)
: [];
const updatedGroups = [...existingGroups, groupName].join(', ');
try {
const currentAssetIds = selectedPartnerForAssets.sharedAssets?.map((sa: any) => sa.assetId) || [];
const updated = await updateMutation.mutateAsync({
partnerId: selectedPartnerForAssets.id,
params: {
partnerGroup: updatedGroups,
sharedAssetIds: currentAssetIds,
assignedNdaId: selectedPartnerForAssets.assignedNdaId === null ? "NONE" : selectedPartnerForAssets.assignedNdaId || undefined,
assignedMsaId: selectedPartnerForAssets.assignedMsaId === null ? "NONE" : selectedPartnerForAssets.assignedMsaId || undefined,
mfaEnabled: selectedPartnerForAssets.mfaEnabled,
showEcosystemTab: selectedPartnerForAssets.showEcosystemTab,
}
});
setSelectedPartnerForAssets(updated);
fetchPartners();
success("Group added", `"${groupName}" added to this partner's recommendations.`);
} catch (err: any) {
error("Failed to update", err.response?.data?.error || "Something went wrong.");
}
}}
className="text-[10px] font-bold bg-ink-0 border border-dashed border-ink-300 hover:border-ink-400 rounded-lg px-2 py-1 text-ink-600 cursor-pointer outline-none transition-colors"
>
<option value="">+ Add Group</option>
{availableGroups.map(g => (
<option key={g.id} value={g.name}>{g.name} ({g.assets.length} assets)</option>
))}
</select>
);
})()}
</div>
</div>
)}
{/* Header Action */} {/* Header Action */}
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<h4 className="text-xs font-bold uppercase tracking-wider text-ink-500"> <h4 className="text-xs font-bold uppercase tracking-wider text-ink-500">

View File

@ -132,12 +132,12 @@ export const LegalTemplatesPage: React.FC = () => {
<PageHeader <PageHeader
title="Legal Agreements" title="Legal Agreements"
subtitle="Configure active documents required during partner onboarding." subtitle="Configure active documents required during partner onboarding."
badge={ // badge={
<div className="flex items-center gap-1.5 px-2.5 py-0.5 rounded-full bg-ink-100 border border-ink-200 text-[10px] font-bold text-ink-900 tracking-wider uppercase shrink-0"> // <div className="flex items-center gap-1.5 px-2.5 py-0.5 rounded-full bg-ink-100 border border-ink-200 text-[10px] font-bold text-ink-900 tracking-wider uppercase shrink-0">
<Shield className="w-3.5 h-3.5 text-ink-950" /> // <Shield className="w-3.5 h-3.5 text-ink-950" />
<span>Compliance Panel</span> // <span>Compliance Panel</span>
</div> // </div>
} // }
/> />
); );