production V2
This commit is contained in:
parent
61f3bd8057
commit
8687d72394
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -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"
|
||||||
}`}
|
}`}
|
||||||
|
|||||||
@ -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" />
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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">
|
||||||
|
|||||||
@ -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">
|
||||||
|
|||||||
@ -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>
|
||||||
}
|
// }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user