Compare commits

..

No commits in common. "45c9db133c67dbe952b886bc55467930fd95ff76" and "2fca778930940281f79df25d3b01f1dc7f19254b" have entirely different histories.

47 changed files with 46760 additions and 173248 deletions

View File

@ -5,26 +5,12 @@ export const DEFAULT_UI_TEXTS: Record<string, string> = {
"maintenance_desc": "وب‌سایت رسمی کنینا ایران جهت ارتقای زیرساخت‌ها و بهبود عملکرد به صورت موقت در دست به‌روزرسانی است. از شکیبایی شما سپاسگزاریم.",
"maintenance_eta": "زمان تقریبی بازگشایی: کمتر از ۲ ساعت آینده",
"maintenance_contact_phone": "۰۲۱-۸۸۸۸۸۸۸۸",
"maintenance_contact_phone_link": "tel:02188888888",
"maintenance_badge": "سامانه در حال ارتقا",
"catalog_mode": "false",
"catalog_hide_prices": "false",
"catalog_disable_cart": "false",
"catalog_disable_checkout": "false",
// === Brand & Logo ===
"site_logo": "",
"site_logo_text_en": "Canina",
"site_logo_text_fa": "ایران",
"site_logo_subtitle": "نماینده رسمی CANINA PHARMA GMBH GERMANY",
"contact_phone": "۰۲۱-۸۸۸۸۴۴۴۴",
"contact_phone_link": "tel:02188884444",
"contact_email": "info@canina.ir",
"contact_address": "تهران، جردن، خیابان سعیدی، ساختمان کنینا، طبقه ۵، واحد ۱۹",
"contact_whatsapp": "09120000000",
"contact_telegram": "https://t.me/canina_iran",
"contact_instagram": "https://instagram.com/canina_iran",
// === Header / Navigation ===
"shipping_notice": "ارسال رایگان برای سفارش‌های بالای ۱۵۰ هزار تومان — گارانتی اصالت کنینا آلمان",
"brand_name_fa": "کنینا ایران",

View File

@ -38,43 +38,14 @@ export class SettingsController {
return this.settingsService.getUiTexts();
}
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('Admin')
@ApiBearerAuth()
@Put('ui-texts/:key')
@ApiOperation({ summary: 'ویرایش یا ثبت متن یک کلید در رابط کاربری با متد PUT' })
putUiText(@Param('key') key: string, @Body() body: any) {
const val =
typeof body === 'object' && body !== null && 'value' in body
? body.value
: typeof body === 'string'
? body
: JSON.stringify(body);
return this.settingsService.updateUiText(key, String(val ?? ''));
}
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('Admin')
@ApiBearerAuth()
@Patch('ui-texts/:key')
@ApiOperation({ summary: 'ویرایش یا ثبت متن یک کلید در رابط کاربری با متد PATCH' })
patchUiText(@Param('key') key: string, @Body() body: any) {
const val =
typeof body === 'object' && body !== null && 'value' in body
? body.value
: typeof body === 'string'
? body
: JSON.stringify(body);
return this.settingsService.updateUiText(key, String(val ?? ''));
}
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('Admin')
@ApiBearerAuth()
@Put('ui-texts')
@ApiOperation({ summary: 'ویرایش گروهی متون رابط کاربری' })
putBulkUiTexts(@Body() body: Record<string, string>) {
return this.settingsService.updateBulkUiTexts(body);
@Put('ui-texts/:key')
@ApiOperation({ summary: 'ویرایش یا ثبت متن یک کلید در رابط کاربری' })
updateUiText(@Param('key') key: string, @Body('value') value: string) {
return this.settingsService.updateUiText(key, value);
}
@Get('scientific-terms')

View File

@ -90,33 +90,13 @@ export class SettingsService implements OnModuleInit {
}
async updateUiText(key: string, value: string) {
const strVal = String(value ?? '');
try {
await this.prisma.setting.upsert({
where: { key },
update: { value: strVal },
create: { key, value: strVal, category: 'general' },
});
} catch (e) {
this.logger.warn(`Failed to sync setting key ${key}: ${e}`);
}
return this.prisma.uiText.upsert({
where: { key },
update: { value: strVal },
create: { key, value: strVal },
update: { value },
create: { key, value },
});
}
async updateBulkUiTexts(texts: Record<string, string>) {
if (!texts || typeof texts !== 'object') return { success: true };
const entries = Object.entries(texts);
for (const [key, value] of entries) {
await this.updateUiText(key, String(value ?? ''));
}
return { success: true, count: entries.length };
}
async getScientificTerms() {
return this.prisma.scientificTerm.findMany();
}

View File

@ -1,50 +1,63 @@
import { useState, useEffect, useRef } from 'react';
import { Link, useLocation } from 'react-router-dom';
import {
LayoutDashboard,
TrendingUp,
ShoppingCart,
Receipt,
Package,
FolderTree,
Tag,
Sparkles,
FileText,
Users,
Heart,
MessageSquare,
Building2,
PhoneCall,
Image,
FlaskConical,
MessageSquareQuote,
BookOpen,
Video,
Settings,
ShieldCheck,
Languages,
DollarSign,
Sliders,
Globe,
ChevronDown,
Star,
Layers,
} from 'lucide-react';
import { Users, ShoppingCart, Tag, Settings, TrendingUp, FolderTree, FileText, BookOpen, Heart, Package, LayoutDashboard, Languages, Image, Video, PhoneCall, Sparkles, MessageSquareQuote, FlaskConical, Building2, Globe, DollarSign, Sliders, MessageSquare, Receipt, CreditCard } from 'lucide-react';
import api from '../services/api';
interface SubMenuItem {
icon: React.ComponentType<{ className?: string }>;
label: string;
path: string;
badge?: string | number;
}
interface MenuGroup {
id: string;
title: string;
icon: React.ComponentType<{ className?: string }>;
items: SubMenuItem[];
}
const menuGroups = [
{
title: 'اصلی',
items: [
{ icon: LayoutDashboard, label: 'داشبورد', path: '/' },
{ icon: TrendingUp, label: 'گزارشات', path: '/reports' },
]
},
{
title: 'فروشگاه و تخصصی',
items: [
{ icon: ShoppingCart, label: 'سفارشات', path: '/orders' },
{ icon: Receipt, label: 'تراکنش‌ها و لاگ پرداخت', path: '/transactions' },
{ icon: Package, label: 'محصولات', path: '/products' },
{ icon: MessageSquare, label: 'نظرات محصولات', path: '/reviews' },
{ icon: FolderTree, label: 'دسته‌بندی‌ها', path: '/categories' },
{ icon: Tag, label: 'کدهای تخفیف', path: '/coupons' },
{ icon: Sparkles, label: 'مشاور هوشمند', path: '/smart-advisor' },
{ icon: FileText, label: 'نسخه‌های پزشکی', path: '/prescriptions' },
]
},
{
title: 'مدیریت و همکاران B2B',
items: [
{ icon: Users, label: 'کاربران', path: '/users' },
{ icon: Heart, label: 'حیوانات (Pets)', path: '/pets' },
{ icon: MessageSquare, label: 'تیکت‌ها & مشاوره دامپزشک', path: '/tickets' },
{ icon: Building2, label: 'مدیریت B2B & عمده', path: '/b2b' },
{ icon: PhoneCall, label: 'تماس با ما & اطلاعات', path: '/contact' },
]
},
{
title: 'محتوا و دانشنامه',
items: [
{ icon: Image, label: 'بنرها و اسلایدرها', path: '/banners' },
{ icon: FlaskConical, label: 'دانشنامه ترکیبات', path: '/ingredients' },
{ icon: MessageSquareQuote, label: 'نظرات و گواهی‌ها', path: '/testimonials' },
{ icon: FileText, label: 'وبلاگ', path: '/blogs' },
{ icon: BookOpen, label: 'دانشنامه عمومی', path: '/wiki' },
{ icon: Video, label: 'مدیریت ویدئوها', path: '/videos' },
]
},
{
title: 'سیستم و تنظیمات',
items: [
{ icon: Settings, label: 'تنظیمات کلی', path: '/settings' },
{ icon: MessageSquare, label: 'تنظیمات پیامک', path: '/settings/sms' },
{ icon: Globe, label: 'تنظیمات سئو', path: '/settings/seo' },
{ icon: DollarSign, label: 'تنظیمات مالی & ارسال', path: '/settings/financial' },
{ icon: Sliders, label: 'تنظیمات سیستمی', path: '/settings/system' },
{ icon: Languages, label: 'متون رابط کاربری', path: '/ui-texts' },
{ icon: Image, label: 'مدیریت رسانه', path: '/media' },
]
}
];
interface SidebarProps {
isOpen: boolean;
@ -56,17 +69,12 @@ export default function Sidebar({ isOpen, setIsOpen }: SidebarProps) {
const [newOrdersCount, setNewOrdersCount] = useState(0);
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
// Auto-close sidebar on mobile navigation
useEffect(() => {
setIsOpen(false);
}, [location.pathname, setIsOpen]);
useEffect(() => {
const fetchOrdersCount = async () => {
try {
const response = await api.get('/admin/dashboard/stats');
if (response.data?.success) {
setNewOrdersCount(response.data.data.newOrders || 0);
setNewOrdersCount(response.data.data.newOrders);
}
} catch (err) {
console.error('Failed to fetch stats in sidebar', err);
@ -79,106 +87,6 @@ export default function Sidebar({ isOpen, setIsOpen }: SidebarProps) {
};
}, []);
const menuGroups: MenuGroup[] = [
{
id: 'main',
title: 'داشبورد و تحلیل داده',
icon: LayoutDashboard,
items: [
{ icon: LayoutDashboard, label: 'نمای کلی داشبورد', path: '/' },
{ icon: TrendingUp, label: 'گزارشات و تحلیل فروش', path: '/reports' },
],
},
{
id: 'orders',
title: 'سفارشات و مالی',
icon: ShoppingCart,
items: [
{
icon: ShoppingCart,
label: 'مدیریت سفارشات',
path: '/orders',
badge: newOrdersCount > 0 ? newOrdersCount : undefined,
},
{ icon: Receipt, label: 'تراکنش‌ها و لاگ پرداخت', path: '/transactions' },
{ icon: DollarSign, label: 'تنظیمات مالی و ارسال', path: '/settings/financial' },
],
},
{
id: 'products',
title: 'محصولات و کاتالوگ',
icon: Package,
items: [
{ icon: Package, label: 'لیست محصولات', path: '/products' },
{ icon: Star, label: 'نظرات و امتیازات', path: '/reviews' },
{ icon: FolderTree, label: 'دسته‌بندی‌های تخصصی', path: '/categories' },
{ icon: Tag, label: 'کدهای تخفیف', path: '/coupons' },
{ icon: Sparkles, label: 'مشاور هوشمند بالینی', path: '/smart-advisor' },
{ icon: FileText, label: 'نسخه‌های دریافتی', path: '/prescriptions' },
],
},
{
id: 'crm',
title: 'کاربران و همکاران',
icon: Users,
items: [
{ icon: Users, label: 'مدیریت کاربران', path: '/users' },
{ icon: Heart, label: 'پرونده پت‌ها (همدم‌ها)', path: '/pets' },
{ icon: MessageSquare, label: 'تیکت‌ها و مشاوره تخصصی', path: '/tickets' },
{ icon: Building2, label: 'پرتال B2B و همکاران', path: '/b2b' },
{ icon: PhoneCall, label: 'اطلاعات تماس و پیام‌ها', path: '/contact' },
],
},
{
id: 'content',
title: 'محتوا و دانشنامه',
icon: Layers,
items: [
{ icon: Image, label: 'بنرها و اسلایدرها', path: '/banners' },
{ icon: FileText, label: 'وبلاگ و مقالات', path: '/blogs' },
{ icon: FlaskConical, label: 'دانشنامه ترکیبات', path: '/ingredients' },
{ icon: BookOpen, label: 'دانشنامه عمومی و علمی', path: '/wiki' },
{ icon: MessageSquareQuote, label: 'نظرات و گواهی متخصصان', path: '/testimonials' },
{ icon: Video, label: 'ویدئوها و آموزش‌ها', path: '/videos' },
{ icon: Image, label: 'کتابخانه رسانه', path: '/media' },
],
},
{
id: 'settings',
title: 'سیستم و تنظیمات',
icon: Settings,
items: [
{ icon: Settings, label: 'تنظیمات اصلی فروشگاه', path: '/settings' },
{ icon: Languages, label: 'متون رابط کاربری', path: '/ui-texts' },
{ icon: MessageSquare, label: 'درگاه پیامک (MeliPayamak)', path: '/settings/sms' },
{ icon: ShieldCheck, label: 'گواهی SSL و امنیت', path: '/settings/ssl' },
{ icon: Globe, label: 'تنظیمات سئو (SEO)', path: '/settings/seo' },
{ icon: Sliders, label: 'تنظیمات سیستمی پیشرفته', path: '/settings/system' },
],
},
];
// Auto expand active group
const [expandedGroups, setExpandedGroups] = useState<Record<string, boolean>>(() => {
const initial: Record<string, boolean> = {};
menuGroups.forEach((g) => {
const hasActive = g.items.some(
(item) =>
location.pathname === item.path ||
(item.path !== '/' && location.pathname.startsWith(item.path))
);
initial[g.id] = hasActive || g.id === 'main' || g.id === 'orders';
});
return initial;
});
const toggleGroup = (groupId: string) => {
setExpandedGroups((prev) => ({
...prev,
[groupId]: !prev[groupId],
}));
};
return (
<>
{/* Mobile Backdrop */}
@ -189,100 +97,43 @@ export default function Sidebar({ isOpen, setIsOpen }: SidebarProps) {
/>
)}
<aside
className={`w-64 bg-white border-l border-gray-200 h-screen fixed lg:fixed lg:right-0 lg:top-0 lg:bottom-0 flex flex-col font-vazir shadow-sm z-50 transform transition-transform duration-300 ease-in-out ${
isOpen ? 'translate-x-0' : 'translate-x-full lg:translate-x-0'
}`}
>
{/* Header */}
<div className="h-16 flex items-center justify-between px-5 border-b border-gray-200 shrink-0 bg-white">
<div className="flex items-center gap-2">
<div className="w-8 h-8 rounded-xl bg-purple-600 flex items-center justify-center text-white font-black text-sm italic shadow-md">
C
</div>
<span className="text-base font-black text-gray-900">کنینا | ادمین‌پنل</span>
</div>
<span className="text-[10px] bg-purple-50 text-purple-700 font-bold px-2 py-0.5 rounded-md border border-purple-200">
v2.4
</span>
<aside className={`w-64 bg-white border-l border-gray-200 h-screen fixed lg:fixed lg:right-0 lg:top-0 lg:bottom-0 flex flex-col font-vazir shadow-sm z-50 transform transition-transform duration-300 ease-in-out ${isOpen ? 'translate-x-0' : 'translate-x-full lg:translate-x-0'
}`}>
<div className="h-16 flex items-center justify-center border-b border-gray-200 shrink-0">
<h1 className="text-xl font-black text-purple-600">کنینا | ادمین‌پنل</h1>
</div>
{/* Menu Navigation */}
<nav className="flex-1 overflow-y-auto py-3 px-3 space-y-2">
{menuGroups.map((group) => {
const GroupIcon = group.icon;
const isExpanded = !!expandedGroups[group.id];
const hasActiveChild = group.items.some(
(item) =>
location.pathname === item.path ||
(item.path !== '/' && location.pathname.startsWith(item.path))
);
<nav className="flex-1 overflow-y-auto py-4 px-3 space-y-6">
{menuGroups.map((group, idx) => (
<div key={idx}>
<h3 className="px-3 mb-2 text-xs font-black text-gray-400 uppercase tracking-wider">{group.title}</h3>
<div className="space-y-1">
{group.items.map((item) => {
const isActive = location.pathname === item.path || (item.path !== '/' && location.pathname.startsWith(item.path));
const Icon = item.icon;
return (
<div key={group.id} className="rounded-2xl overflow-hidden bg-gray-50/50 border border-gray-100">
{/* Group Accordion Header */}
<button
type="button"
onClick={() => toggleGroup(group.id)}
className={`w-full flex items-center justify-between px-3 py-2.5 text-xs font-black transition-all ${
hasActiveChild ? 'text-purple-700 bg-purple-50/70' : 'text-gray-600 hover:bg-gray-100/60'
}`}
>
<div className="flex items-center gap-2">
<GroupIcon className={`w-4 h-4 ${hasActiveChild ? 'text-purple-600' : 'text-gray-400'}`} />
<span>{group.title}</span>
</div>
<ChevronDown
className={`w-4 h-4 transition-transform duration-200 text-gray-400 ${
isExpanded ? 'rotate-180' : ''
}`}
/>
</button>
{/* Submenu Items */}
{isExpanded && (
<div className="p-1 space-y-0.5 bg-white border-t border-gray-100">
{group.items.map((item) => {
const isActive =
location.pathname === item.path ||
(item.path !== '/' && location.pathname.startsWith(item.path));
const Icon = item.icon;
return (
<Link
key={item.path}
to={item.path}
onClick={() => setIsOpen(false)}
className={`flex items-center justify-between px-3 py-2 rounded-xl text-xs font-bold transition-all ${
isActive
? 'bg-purple-600 text-white shadow-sm shadow-purple-600/30'
: 'text-gray-600 hover:bg-purple-50/50 hover:text-purple-700'
}`}
>
<div className="flex items-center gap-2.5">
<Icon className={`w-4 h-4 ${isActive ? 'text-white' : 'text-gray-400'}`} />
<span>{item.label}</span>
</div>
{item.badge !== undefined && (
<span
className={`text-[10px] font-black px-1.5 py-0.5 rounded-full min-w-[18px] text-center ${
isActive
? 'bg-white text-purple-700'
: 'bg-purple-600 text-white'
}`}
>
{item.badge}
</span>
)}
</Link>
);
})}
</div>
)}
return (
<Link
key={item.path}
to={item.path}
className={`flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all font-bold ${isActive
? 'bg-purple-50 text-purple-600'
: 'text-gray-500 hover:bg-gray-50 hover:text-gray-900'
}`}
>
<Icon className="w-5 h-5" />
<span>{item.label}</span>
{item.label === 'سفارشات' && newOrdersCount > 0 && (
<span className="mr-auto bg-purple-600 text-white text-[11px] font-black px-2 py-0.5 rounded-full min-w-[20px] text-center">
{newOrdersCount}
</span>
)}
</Link>
);
})}
</div>
);
})}
</div>
))}
</nav>
</aside>
</>

View File

@ -239,17 +239,7 @@ export default function BannersManager() {
</td>
<td className="py-4 px-6">
<span className="inline-block px-3 py-1 bg-purple-50 text-purple-700 text-xs font-bold rounded-lg border border-purple-100">
{banner.position === 'home_hero'
? 'اسلایدر هیرو اصلی'
: banner.position === 'home_middle'
? 'بنر میانی صفحه نخست'
: banner.position === 'home_bottom'
? 'بنر عریض پایین خانه'
: banner.position === 'shop_top'
? 'بالای فروشگاه و کاتالوگ'
: banner.position === 'category_top'
? 'بالای دسته‌بندی'
: 'سایدبار'}
{banner.position === 'home_hero' ? 'اسلایدر هیرو اصلی' : banner.position === 'category_top' ? 'بالای دسته‌بندی' : 'سایدبار'}
</span>
</td>
<td className="py-4 px-6 text-gray-500 font-mono text-xs dir-ltr text-right">
@ -364,11 +354,8 @@ export default function BannersManager() {
className="w-full px-4 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 outline-none text-sm font-bold bg-white"
>
<option value="home_hero">اسلایدر هیرو اصلی (home_hero)</option>
<option value="home_middle">بنر میانی صفحه نخست (home_middle)</option>
<option value="home_bottom">بنر عریض پایین خانه (home_bottom)</option>
<option value="shop_top">بالای فروشگاه و کاتالوگ (shop_top)</option>
<option value="category_top">بالای دسته‌بندی (category_top)</option>
<option value="product_sidebar">سایدبار صفحات محصول و مقالات (product_sidebar)</option>
<option value="sidebar">سایدبار (sidebar)</option>
</select>
</div>

View File

@ -25,16 +25,12 @@ export default function Settings() {
ZIBAL_SANDBOX: 'true',
FRONTEND_URL: 'https://canina.ir',
CONTACT_PHONE: '۰۲۱-۸۸۸۸ ۴۴۴۴',
CONTACT_PHONE_LINK: 'tel:02188884444',
CONTACT_EMAIL: 'info@canina-iran.com',
SOCIAL_WHATSAPP: '09120000000',
SOCIAL_INSTAGRAM: 'https://instagram.com/canina_iran',
SOCIAL_TELEGRAM: 'https://t.me/canina_iran',
CONTACT_ADDRESS: 'تهران، جردن، خیابان سعیدی، ساختمان کنینا، طبقه ۵، واحد ۱۹',
BRAND_LOGO_URL: '',
BRAND_LOGO_TEXT_EN: 'Canina',
BRAND_LOGO_TEXT_FA: 'ایران',
BRAND_LOGO_SUBTITLE: 'نماینده رسمی CANINA PHARMA GMBH GERMANY',
BRAND_LOGO_URL: '/logo.png',
ENAMAD_CODE: '',
THEME_PRIMARY_COLOR: '#7c3aed',
BRAND_TYPOGRAPHY: 'vazirmatn',
@ -66,17 +62,13 @@ export default function Settings() {
ZIBAL_MERCHANT: response.data.data.ZIBAL_MERCHANT || 'zibal',
ZIBAL_SANDBOX: response.data.data.ZIBAL_SANDBOX || 'true',
FRONTEND_URL: response.data.data.FRONTEND_URL || 'https://canina.ir',
CONTACT_PHONE: response.data.data.CONTACT_PHONE || response.data.data.contact_phone || '۰۲۱-۸۸۸۸ ۴۴۴۴',
CONTACT_PHONE_LINK: response.data.data.CONTACT_PHONE_LINK || response.data.data.contact_phone_link || 'tel:02188884444',
CONTACT_EMAIL: response.data.data.CONTACT_EMAIL || response.data.data.contact_email || 'info@canina-iran.com',
SOCIAL_WHATSAPP: response.data.data.SOCIAL_WHATSAPP || response.data.data.contact_whatsapp || '09120000000',
SOCIAL_INSTAGRAM: response.data.data.SOCIAL_INSTAGRAM || response.data.data.contact_instagram || 'https://instagram.com/canina_iran',
SOCIAL_TELEGRAM: response.data.data.SOCIAL_TELEGRAM || response.data.data.contact_telegram || 'https://t.me/canina_iran',
CONTACT_ADDRESS: response.data.data.CONTACT_ADDRESS || response.data.data.contact_address || 'تهران، جردن، خیابان سعیدی، ساختمان کنینا، طبقه ۵، واحد ۱۹',
BRAND_LOGO_URL: response.data.data.BRAND_LOGO_URL || response.data.data.site_logo || '',
BRAND_LOGO_TEXT_EN: response.data.data.BRAND_LOGO_TEXT_EN || response.data.data.site_logo_text_en || 'Canina',
BRAND_LOGO_TEXT_FA: response.data.data.BRAND_LOGO_TEXT_FA || response.data.data.site_logo_text_fa || 'ایران',
BRAND_LOGO_SUBTITLE: response.data.data.BRAND_LOGO_SUBTITLE || response.data.data.site_logo_subtitle || 'نماینده رسمی CANINA PHARMA GMBH GERMANY',
CONTACT_PHONE: response.data.data.CONTACT_PHONE || '۰۲۱-۸۸۸۸ ۴۴۴۴',
CONTACT_EMAIL: response.data.data.CONTACT_EMAIL || 'info@canina-iran.com',
SOCIAL_WHATSAPP: response.data.data.SOCIAL_WHATSAPP || '09120000000',
SOCIAL_INSTAGRAM: response.data.data.SOCIAL_INSTAGRAM || 'https://instagram.com/canina_iran',
SOCIAL_TELEGRAM: response.data.data.SOCIAL_TELEGRAM || 'https://t.me/canina_iran',
CONTACT_ADDRESS: response.data.data.CONTACT_ADDRESS || 'تهران، جردن، خیابان سعیدی، ساختمان کنینا، طبقه ۵، واحد ۱۹',
BRAND_LOGO_URL: response.data.data.BRAND_LOGO_URL || '/logo.png',
ENAMAD_CODE: response.data.data.ENAMAD_CODE || '',
THEME_PRIMARY_COLOR: response.data.data.THEME_PRIMARY_COLOR || '#7c3aed',
BRAND_TYPOGRAPHY: response.data.data.BRAND_TYPOGRAPHY || 'vazirmatn',
@ -342,30 +334,16 @@ export default function Settings() {
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-xs font-bold text-gray-700 mb-1">متن نمایشی تلفن پشتیبانی و فروش</label>
<label className="block text-xs font-bold text-gray-700 mb-1">تلفن پشتیبانی و فروش</label>
<input
type="text"
value={settings.CONTACT_PHONE}
onChange={(e) => setSettings({ ...settings, CONTACT_PHONE: e.target.value })}
placeholder="مثال: ۰۲۱-۸۸۸۸۴۴۴۴ یا ۰۹۲۱۲۳۴۵۶۷۸"
className="w-full border border-gray-200 rounded-xl p-2.5 outline-none focus:ring-2 focus:ring-purple-500 text-xs font-bold"
dir="ltr"
/>
</div>
<div>
<label className="block text-xs font-bold text-gray-700 mb-1">لینک مستقیم تماس (Phone Link)</label>
<input
type="text"
value={settings.CONTACT_PHONE_LINK}
onChange={(e) => setSettings({ ...settings, CONTACT_PHONE_LINK: e.target.value })}
placeholder="مثال: tel:+982188884444 یا tel:09212345678"
className="w-full border border-gray-200 rounded-xl p-2.5 outline-none focus:ring-2 focus:ring-purple-500 text-xs font-mono"
dir="ltr"
/>
<p className="text-[10px] text-gray-400 mt-1">لینکی که با کلیک روی شماره تلفن در سایت باز می‌شود (مثلاً شماره بین‌المللی یا لینک تماس).</p>
</div>
<div>
<label className="block text-xs font-bold text-gray-700 mb-1">ایمیل رسمی پشتیبانی</label>
<input
@ -432,14 +410,14 @@ export default function Settings() {
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-xs font-bold text-gray-700 mb-1">لینک لوگوی تصویری (Brand Logo Image URL)</label>
<label className="block text-xs font-bold text-gray-700 mb-1">لینک لوگوی برند (Brand Logo URL)</label>
<div className="flex gap-2">
<input
type="text"
value={settings.BRAND_LOGO_URL}
onChange={(e) => setSettings({ ...settings, BRAND_LOGO_URL: e.target.value })}
className="flex-1 border border-gray-200 rounded-xl p-2.5 outline-none focus:ring-2 focus:ring-purple-500 text-xs font-mono"
placeholder="https://... یا /logo.png (اختیاری)"
placeholder="https://... یا /logo.png"
dir="ltr"
/>
{settings.BRAND_LOGO_URL && (
@ -448,41 +426,7 @@ export default function Settings() {
</div>
)}
</div>
<p className="text-[10px] text-gray-400 mt-1">در صورت خالی بودن، لوگوی متنی زیر در هدر، فوتر و صفحه تعمیرات استفاده خواهد شد.</p>
</div>
<div>
<label className="block text-xs font-bold text-gray-700 mb-1">متن انگلیسی لوگو (Text Logo EN)</label>
<input
type="text"
value={settings.BRAND_LOGO_TEXT_EN}
onChange={(e) => setSettings({ ...settings, BRAND_LOGO_TEXT_EN: e.target.value })}
placeholder="Canina"
className="w-full border border-gray-200 rounded-xl p-2.5 outline-none focus:ring-2 focus:ring-purple-500 text-xs font-bold"
dir="ltr"
/>
</div>
<div>
<label className="block text-xs font-bold text-gray-700 mb-1">متن فارسی لوگو (Text Logo FA)</label>
<input
type="text"
value={settings.BRAND_LOGO_TEXT_FA}
onChange={(e) => setSettings({ ...settings, BRAND_LOGO_TEXT_FA: e.target.value })}
placeholder="ایران"
className="w-full border border-gray-200 rounded-xl p-2.5 outline-none focus:ring-2 focus:ring-purple-500 text-xs font-bold"
/>
</div>
<div>
<label className="block text-xs font-bold text-gray-700 mb-1">زیرعنوان رسمی لوگو (Logo Subtitle)</label>
<input
type="text"
value={settings.BRAND_LOGO_SUBTITLE}
onChange={(e) => setSettings({ ...settings, BRAND_LOGO_SUBTITLE: e.target.value })}
placeholder="نماینده رسمی CANINA PHARMA GMBH GERMANY"
className="w-full border border-gray-200 rounded-xl p-2.5 outline-none focus:ring-2 focus:ring-purple-500 text-xs font-bold"
/>
<p className="text-[10px] text-gray-400 mt-1">این لوگو در صورت پر بودن، جایگزین تایپوگرافی هدر سایت در اپلیکیشن خواهد شد.</p>
</div>
<div className="md:col-span-2">

View File

@ -159,24 +159,14 @@ export default function UITexts() {
const value = edits[key] ?? texts[key] ?? '';
setSaving(key);
try {
await api.put(`/settings/ui-texts/${encodeURIComponent(key)}`, { value });
await api.put(`/settings/ui-texts/${key}`, { value });
setTexts(prev => ({ ...prev, [key]: value }));
setSavedKey(key);
toast.success('ذخیره‌سازی با موفقیت انجام شد');
setTimeout(() => setSavedKey(null), 2000);
} catch (e: any) {
} catch (e) {
console.error('Failed to save UI text:', e);
try {
// Fallback to PATCH if PUT fails
await api.patch(`/settings/ui-texts/${encodeURIComponent(key)}`, { value });
setTexts(prev => ({ ...prev, [key]: value }));
setSavedKey(key);
toast.success('ذخیره‌سازی با موفقیت انجام شد');
setTimeout(() => setSavedKey(null), 2000);
} catch (err: any) {
const errorMsg = err?.response?.data?.message || e?.response?.data?.message || 'خطا در ذخیره‌سازی';
toast.error(typeof errorMsg === 'string' ? errorMsg : 'خطا در ذخیره‌سازی');
}
toast.error('خطا در ذخیره‌سازی');
} finally {
setSaving(null);
}
@ -192,7 +182,7 @@ export default function UITexts() {
});
const fileUrl = res.data.url || res.data.fileUrl;
setEdits(prev => ({ ...prev, [key]: fileUrl }));
await api.put(`/settings/ui-texts/${encodeURIComponent(key)}`, { value: fileUrl });
await api.put(`/settings/ui-texts/${key}`, { value: fileUrl });
setTexts(prev => ({ ...prev, [key]: fileUrl }));
setSavedKey(key);
toast.success('تصویر با موفقیت آپلود شد');

View File

@ -5,7 +5,6 @@ import Hero from "../components/Hero";
import SmartAdvisor from "../components/SmartAdvisor";
import FeaturedProducts from "../components/FeaturedProducts";
import VetGallery from "../components/VetGallery";
import BannerPlacement from "../components/BannerPlacement";
import Link from 'next/link';
import { toPersian } from "../lib/utils";
import { useSettingsStore } from "../lib/store/settingsStore";
@ -59,7 +58,6 @@ export default function HomeClient({ initialData }: HomeClientProps) {
<>
<Hero banners={heroBanners} />
<SmartAdvisor rules={rules} />
<BannerPlacement banners={banners} position="home_middle" />
<FeaturedProducts />
<VetGallery testimonials={testimonials} />
@ -160,9 +158,6 @@ export default function HomeClient({ initialData }: HomeClientProps) {
</div>
</section>
{/* Bottom Banner Placement */}
<BannerPlacement banners={banners} position="home_bottom" />
{/* Call to Action */}
<section className="bg-canina-blue py-12 relative overflow-hidden font-sans">
<div className="absolute inset-0 opacity-10">

View File

@ -28,8 +28,6 @@ import {
import { useCartStore } from "../lib/store/cartStore";
import { useSettingsStore } from "../lib/store/settingsStore";
import SafeImage from "./SafeImage";
import BannerPlacement from "./BannerPlacement";
import { Banner } from "../lib/types";
const ICON_MAP: Record<string, React.ReactNode> = {
joints: <HeartPulse className="w-4 h-4" />,
@ -250,15 +248,11 @@ export default function ArchivePage({
const [symptomSearch, setSymptomSearch] = useState("");
const [categories, setCategories] = useState<{ id: string; label: string; icon: React.ReactNode }[]>([]);
const [symptoms, setSymptoms] = useState<string[]>([]);
const [banners, setBanners] = useState<Banner[]>([]);
useEffect(() => {
const loadFiltersAndBanners = async () => {
const loadFilters = async () => {
try {
const [filters, bannerData] = await Promise.all([
productService.getActiveFilters(),
productService.getBanners()
]);
const filters = await productService.getActiveFilters();
const mapped = [
{ id: "all", label: "همه محصولات", icon: <Activity className="w-4 h-4" /> },
...filters.categories.map(c => ({
@ -269,14 +263,11 @@ export default function ArchivePage({
];
setCategories(mapped);
setSymptoms(filters.symptoms);
if (Array.isArray(bannerData)) {
setBanners(bannerData);
}
} catch (e) {
console.error("Failed to load active filters or banners:", e);
console.error("Failed to load active filters:", e);
}
};
loadFiltersAndBanners();
loadFilters();
}, []);
const CATEGORY_LABELS = useMemo(() => {
@ -425,9 +416,6 @@ export default function ArchivePage({
</button>
</div>
{/* Shop Top Banner Placement */}
<BannerPlacement banners={banners} position="shop_top" className="!px-0 !py-2 mb-6" />
<div className="flex flex-col lg:flex-row gap-8">
{/* Mobile Overlay Backdrop */}

View File

@ -1,202 +0,0 @@
/* eslint-disable @next/next/no-img-element */
"use client";
import React from "react";
import Link from "next/link";
import { Banner } from "../lib/types";
interface BannerPlacementProps {
banners: Banner[];
position: "home_middle" | "home_bottom" | "shop_top" | "category_top" | "product_sidebar";
className?: string;
}
export default function BannerPlacement({
banners,
position,
className = "",
}: BannerPlacementProps) {
const filteredBanners = (banners || []).filter(
(b) => b.position === position && b.isActive !== false && b.imageUrl
);
if (!filteredBanners || filteredBanners.length === 0) {
return null;
}
// Multi-column layout for home_middle if 2 or more banners exist
if (position === "home_middle") {
return (
<section className={`py-8 max-w-7xl mx-auto px-4 sm:px-6 font-vazir ${className}`}>
<div
className={`grid gap-6 ${
filteredBanners.length > 1 ? "grid-cols-1 md:grid-cols-2" : "grid-cols-1"
}`}
>
{filteredBanners.map((banner) => {
const cardContent = (
<div className="group relative rounded-3xl overflow-hidden shadow-lg hover:shadow-2xl transition-all duration-300 border border-medical-gray-100 bg-white">
<div className="aspect-[16/7] sm:aspect-[16/6] md:aspect-[16/7] overflow-hidden bg-medical-gray-100 relative">
<img
src={banner.imageUrl}
alt={banner.title || "کنینا بنر"}
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
loading="lazy"
/>
{(banner.title || banner.subtitle) && (
<div className="absolute inset-0 bg-gradient-to-t from-black/80 via-black/30 to-transparent p-6 flex flex-col justify-end text-white text-right">
{banner.title && (
<h4 className="text-lg sm:text-xl font-black mb-1 leading-tight">
{banner.title}
</h4>
)}
{banner.subtitle && (
<p className="text-xs sm:text-sm text-medical-gray-200 font-bold opacity-90 line-clamp-2">
{banner.subtitle}
</p>
)}
</div>
)}
</div>
</div>
);
if (banner.linkUrl) {
const isExternal = banner.linkUrl.startsWith("http");
if (isExternal) {
return (
<a
key={banner.id}
href={banner.linkUrl}
target="_blank"
rel="noopener noreferrer"
className="block"
>
{cardContent}
</a>
);
}
return (
<Link key={banner.id} href={banner.linkUrl} className="block">
{cardContent}
</Link>
);
}
return <div key={banner.id}>{cardContent}</div>;
})}
</div>
</section>
);
}
// Wide banner layout for home_bottom and shop_top
if (position === "home_bottom" || position === "shop_top" || position === "category_top") {
return (
<section className={`py-6 max-w-7xl mx-auto px-4 sm:px-6 font-vazir ${className}`}>
<div className="space-y-6">
{filteredBanners.map((banner) => {
const cardContent = (
<div className="group relative rounded-3xl overflow-hidden shadow-xl hover:shadow-2xl transition-all duration-300 border border-medical-gray-200 bg-white">
<div className="aspect-[21/6] sm:aspect-[21/5] min-h-[140px] sm:min-h-[180px] overflow-hidden bg-medical-gray-100 relative">
<img
src={banner.imageUrl}
alt={banner.title || "کنینا"}
className="w-full h-full object-cover group-hover:scale-103 transition-transform duration-500"
loading="lazy"
/>
{(banner.title || banner.subtitle) && (
<div className="absolute inset-0 bg-gradient-to-l from-black/80 via-black/40 to-transparent p-6 sm:p-10 flex flex-col justify-center text-white text-right">
{banner.title && (
<h4 className="text-xl sm:text-3xl font-black mb-2 leading-tight">
{banner.title}
</h4>
)}
{banner.subtitle && (
<p className="text-xs sm:text-base text-medical-gray-200 font-bold max-w-xl">
{banner.subtitle}
</p>
)}
</div>
)}
</div>
</div>
);
if (banner.linkUrl) {
const isExternal = banner.linkUrl.startsWith("http");
if (isExternal) {
return (
<a
key={banner.id}
href={banner.linkUrl}
target="_blank"
rel="noopener noreferrer"
className="block"
>
{cardContent}
</a>
);
}
return (
<Link key={banner.id} href={banner.linkUrl} className="block">
{cardContent}
</Link>
);
}
return <div key={banner.id}>{cardContent}</div>;
})}
</div>
</section>
);
}
// Sidebar Banner Layout
return (
<div className={`space-y-4 font-vazir ${className}`}>
{filteredBanners.map((banner) => {
const cardContent = (
<div className="group relative rounded-2xl overflow-hidden shadow-md hover:shadow-xl transition-all duration-300 border border-medical-gray-200 bg-white">
<div className="aspect-[4/3] overflow-hidden bg-medical-gray-100 relative">
<img
src={banner.imageUrl}
alt={banner.title || "بنر کنینا"}
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
loading="lazy"
/>
{banner.title && (
<div className="absolute inset-0 bg-gradient-to-t from-black/80 to-transparent p-4 flex flex-col justify-end text-white text-right">
<span className="text-xs font-black">{banner.title}</span>
</div>
)}
</div>
</div>
);
if (banner.linkUrl) {
const isExternal = banner.linkUrl.startsWith("http");
if (isExternal) {
return (
<a
key={banner.id}
href={banner.linkUrl}
target="_blank"
rel="noopener noreferrer"
className="block"
>
{cardContent}
</a>
);
}
return (
<Link key={banner.id} href={banner.linkUrl} className="block">
{cardContent}
</Link>
);
}
return <div key={banner.id}>{cardContent}</div>;
})}
</div>
);
}

View File

@ -1,100 +0,0 @@
/* eslint-disable @next/next/no-img-element */
"use client";
import React from "react";
import Link from "next/link";
import { useSettingsStore } from "../lib/store/settingsStore";
interface BrandLogoProps {
className?: string;
isDarkBackground?: boolean;
href?: string;
size?: "sm" | "md" | "lg";
}
export default function BrandLogo({
className = "",
isDarkBackground = false,
href = "/",
size = "md",
}: BrandLogoProps) {
const getText = useSettingsStore((state) => state.getText);
const logoUrl = getText("site_logo", "");
const textEn = getText("site_logo_text_en", "Canina");
const textFa = getText("site_logo_text_fa", "ایران");
const subtitle = getText(
"site_logo_subtitle",
"نماینده رسمی CANINA PHARMA GMBH GERMANY"
);
const iconSizes = {
sm: "w-8 h-8 text-base rounded-xl",
md: "w-10 h-10 sm:w-12 sm:h-12 text-xl sm:text-2xl rounded-2xl",
lg: "w-14 h-14 sm:w-16 sm:h-16 text-2xl sm:text-3xl rounded-3xl",
};
const titleSizes = {
sm: "text-sm sm:text-base",
md: "text-base sm:text-2xl",
lg: "text-xl sm:text-3xl",
};
const content = (
<div className={`flex items-center gap-2.5 sm:gap-3 group shrink-0 ${className}`}>
{logoUrl ? (
<img
src={logoUrl}
alt={`${textEn} ${textFa}`}
className="h-10 sm:h-12 w-auto max-w-[160px] sm:max-w-[200px] object-contain shrink-0"
/>
) : (
<>
<div
className={`${iconSizes[size]} bg-canina-blue flex items-center justify-center text-white font-black group-hover:bg-medical-gray-900 transition-all shadow-md italic shrink-0`}
>
{textEn.charAt(0).toUpperCase() || "C"}
</div>
<div className="flex flex-col justify-center">
<span
className={`${titleSizes[size]} font-black italic font-sans flex items-center gap-1 leading-none pl-1 whitespace-nowrap ${
isDarkBackground ? "text-white" : "text-canina-blue"
}`}
>
{textEn}
{textFa && (
<span
className={`text-[11px] sm:text-sm not-italic font-bold border-r pr-1.5 mr-0.5 font-vazir ${
isDarkBackground
? "text-medical-gray-200 border-white/20"
: "text-medical-gray-700 border-medical-gray-300"
}`}
>
{textFa}
</span>
)}
</span>
{subtitle && (
<span
className={`hidden sm:block text-[8px] sm:text-[9px] font-bold uppercase tracking-wider leading-tight mt-1 whitespace-nowrap ${
isDarkBackground ? "text-medical-gray-400" : "text-medical-gray-400"
}`}
>
{subtitle}
</span>
)}
</div>
</>
)}
</div>
);
if (href) {
return (
<Link href={href} className="inline-block">
{content}
</Link>
);
}
return content;
}

View File

@ -217,11 +217,11 @@ export default function CheckoutPage() {
</p>
<div className="flex flex-col sm:flex-row gap-3 pt-2">
<a
href={getText('contact_phone_link', `tel:${getText('contact_phone', '02188884444').replace(/[^0-9+]/g, '')}`)}
href="tel:02188888888"
className="flex-1 px-4 py-3 bg-canina-blue hover:bg-canina-dark text-white rounded-xl text-xs font-black transition-all shadow-md flex items-center justify-center gap-2"
>
<Phone className="w-4 h-4" />
<span>تماس با پشتیبانی ({getText('contact_phone', '۰۲۱-۸۸۸۸۴۴۴۴')})</span>
<span>تماس با پشتیبانی</span>
</a>
<button
onClick={() => router.push('/shop')}

View File

@ -3,7 +3,6 @@
import React, { useState, useEffect } from "react";
import { Phone, Mail, MapPin, Instagram, ShieldCheck, Globe, ArrowUp, Download } from "lucide-react";
import { useSettingsStore } from "../lib/store/settingsStore";
import BrandLogo from "./BrandLogo";
import Link from "next/link";
import { NavigationTarget } from "../lib/types";
@ -54,21 +53,22 @@ export default function Footer({
{/* Brand Presence */}
<div className="space-y-8">
<BrandLogo isDarkBackground size="lg" />
<Link href="/" className="flex items-center gap-3 cursor-pointer group">
<div className="w-12 h-12 bg-canina-blue rounded-2xl flex items-center justify-center text-white font-bold text-2xl group-hover:bg-medical-gray-900 transition-all shadow-xl shadow-canina-blue/20 italic">C</div>
<div className="flex flex-col">
<span className="text-white font-extrabold text-2xl tracking-tighter leading-none italic font-sans">Canina <span className="text-sm not-italic font-medium border-l-2 border-white/20 pl-2 ml-2 font-shabnam">{getText('brand_name_fa', "ایران")}</span></span>
<span className="text-[10px] text-slate-400 font-bold uppercase tracking-widest leading-none mt-1.5 font-shabnam">{getText('brand_subtitle', "نماینده رسمی Canina Pharma GmbH آلمان")}</span>
</div>
</Link>
<p className="text-sm text-slate-300 leading-relaxed font-medium">
{getText('footer_brand_desc', "تدارک هوشمندانه سلامت برای همراهان وفادار شما. واردکننده انحصاری مکمل‌های درمانی با گرید دارویی اختصاصی از آلمان با سابقه ۴۰ سال نوآوری.")}
</p>
<div className="flex gap-4">
{getText('contact_instagram', '') && (
<a href={getText('contact_instagram', '#')} target="_blank" rel="noopener noreferrer" className="w-10 h-10 bg-white/10 rounded-xl flex items-center justify-center hover:bg-canina-blue hover:text-white transition-all text-slate-300">
<Instagram className="w-5 h-5" />
</a>
)}
{getText('contact_telegram', '') && (
<a href={getText('contact_telegram', '#')} target="_blank" rel="noopener noreferrer" className="w-10 h-10 bg-white/10 rounded-xl flex items-center justify-center hover:bg-canina-blue hover:text-white transition-all text-slate-300">
<Globe className="w-5 h-5" />
</a>
)}
{[Instagram, Globe].map((Icon, i) => (
<a key={i} href="#" className="w-10 h-10 bg-white/10 rounded-xl flex items-center justify-center hover:bg-canina-blue hover:text-white transition-all text-slate-300">
<Icon className="w-5 h-5" />
</a>
))}
</div>
</div>
@ -132,24 +132,24 @@ export default function Footer({
<h4 className="text-xl font-bold text-white border-b-2 border-canina-blue pb-2 max-w-max font-lalezar">{getText('footer_contact_title', "اطلاعات تماس نمایندگی")}</h4>
<div className="grid md:grid-cols-2 gap-8">
<div className="space-y-6">
<a href={getText('contact_phone_link', `tel:${getText('contact_phone', '02188884444').replace(/[^0-9+]/g, '')}`)} className="flex gap-4 group hover:opacity-90 transition-opacity">
<div className="flex gap-4 group">
<div className="w-12 h-12 bg-white/5 rounded-2xl flex items-center justify-center text-blue-400 group-hover:scale-110 transition-transform flex-shrink-0">
<Phone className="w-6 h-6" />
</div>
<div>
<p className="text-[11px] text-slate-400 font-extrabold uppercase mb-1">{getText('footer_phone_label', "خط ویژه فروش")}</p>
<p className="text-lg font-extrabold tracking-widest text-left text-slate-200" dir="ltr">{getText('contact_phone', "۰۲۱-۸۸۸۸۴۴۴۴")}</p>
<p className="text-lg font-extrabold tracking-widest text-left text-slate-200" dir="ltr">{getText('CONTACT_PHONE', getText('footer_phone', "۰۲۱-۸۸۸۸ ۴۴۴۴"))}</p>
</div>
</a>
<a href={`mailto:${getText('contact_email', 'info@canina.ir')}`} className="flex gap-4 group hover:opacity-90 transition-opacity">
</div>
<div className="flex gap-4 group">
<div className="w-12 h-12 bg-white/5 rounded-2xl flex items-center justify-center text-blue-400 group-hover:scale-110 transition-transform flex-shrink-0">
<Mail className="w-6 h-6" />
</div>
<div>
<p className="text-[11px] text-slate-400 font-extrabold uppercase mb-1">{getText('footer_email_label', "مکاتبات رسمی")}</p>
<p className="text-sm font-bold text-slate-300">{getText('contact_email', "info@canina.ir")}</p>
<p className="text-sm font-bold text-slate-300">{getText('CONTACT_EMAIL', getText('footer_email', "info@canina-iran.com"))}</p>
</div>
</a>
</div>
</div>
<div className="flex gap-4 group">
@ -158,7 +158,7 @@ export default function Footer({
</div>
<div>
<p className="text-[11px] text-slate-400 font-extrabold uppercase mb-1">{getText('footer_address_label', "دفتر مرکزی")}</p>
<p className="text-sm font-bold leading-relaxed text-slate-300">{getText('contact_address', "تهران، جردن، خیابان سعیدی، ساختمان کنینا، طبقه ۵، واحد ۱۹")}</p>
<p className="text-sm font-bold leading-relaxed text-slate-300">{getText('CONTACT_ADDRESS', getText('footer_address', "تهران، جردن، خیابان سعیدی، ساختمان کنینا، طبقه ۵، واحد ۱۹"))}</p>
</div>
</div>
</div>

View File

@ -12,7 +12,6 @@ import { toast } from "sonner";
import AuthModal from "./AuthModal";
import PrescriptionUploadModal from "./PrescriptionUploadModal";
import TickerBanner from "./TickerBanner";
import BrandLogo from "./BrandLogo";
import { cn } from "../lib/utils";
import { productService } from "../lib/services/productService";
@ -150,7 +149,30 @@ export default function Header({
{isMobileMenuOpen ? <X className="w-6 h-6" /> : <Menu className="w-6 h-6" />}
</button>
<BrandLogo />
<Link href="/" className="flex items-center gap-2 sm:gap-3 group shrink-0">
{(getText('BRAND_LOGO_URL', '') || getText('site_logo', '')) ? (
<img
src={getText('BRAND_LOGO_URL', '') || getText('site_logo', '')}
alt={getText('brand_name_fa', "کنینا ایران")}
className="h-10 sm:h-12 w-auto max-w-[140px] sm:max-w-[180px] object-contain shrink-0"
/>
) : (
<>
<div className="w-10 h-10 sm:w-12 sm:h-12 min-w-[40px] min-h-[40px] sm:min-w-[48px] sm:min-h-[48px] bg-canina-blue rounded-2xl flex items-center justify-center text-white font-bold text-xl sm:text-2xl group-hover:bg-medical-gray-900 transition-all shadow-md italic shrink-0">C</div>
<div className="flex flex-col justify-center">
<span className="text-canina-blue font-black text-base sm:text-2xl italic font-sans flex items-center gap-1 leading-none pl-1 whitespace-nowrap">
Canina
<span className="text-[11px] sm:text-sm not-italic font-medium border-r border-medical-gray-300 pr-1.5 mr-0.5 font-vazir text-medical-gray-700">
{getText('brand_name_fa', "ایران")}
</span>
</span>
<span className="hidden sm:block text-[9px] text-medical-gray-400 font-bold uppercase tracking-wider leading-tight mt-1 whitespace-nowrap">
نماینده رسمی CANINA PHARMA GMBH GERMANY
</span>
</div>
</>
)}
</Link>
</div>
{/* Center Search Input (Desktop) */}

View File

@ -1,12 +1,10 @@
/* eslint-disable @next/next/no-img-element */
"use client";
import { motion, AnimatePresence, animate } from "motion/react";
import { ChevronLeft, ChevronRight, Globe, ShieldCheck, Calendar, ArrowLeft } from "lucide-react";
import { useEffect, useState, useRef } from "react";
import { motion, animate } from "motion/react";
import { ChevronLeft, Globe, ShieldCheck, Calendar } from "lucide-react";
import { useEffect, useState } from "react";
import { toPersian } from "../lib/utils";
import { useSettingsStore } from "../lib/store/settingsStore";
import { useRouter } from 'next/navigation';
import { Banner } from "../lib/types";
function StatCounter({ target }: { target: number }) {
const [displayValue, setDisplayValue] = useState(0);
@ -14,7 +12,7 @@ function StatCounter({ target }: { target: number }) {
useEffect(() => {
const controls = animate(0, target, {
duration: 3,
ease: [0.16, 1, 0.3, 1],
ease: [0.16, 1, 0.3, 1], // Custom cubic-bezier for a more polished feel
onUpdate: (latest) => setDisplayValue(Math.round(latest))
});
return controls.stop;
@ -37,55 +35,28 @@ function StatCounter({ target }: { target: number }) {
);
}
import { useRouter } from 'next/navigation';
import { Banner } from "../lib/types";
export default function Hero({ banners = [], onShopNavigate }: { banners?: Banner[]; onShopNavigate?: () => void }) {
const router = useRouter();
const getText = useSettingsStore((state) => state?.getText || ((_k: string, fb: string) => fb));
const isInitialized = useSettingsStore((state) => state?.isInitialized ?? false);
const [mounted, setMounted] = useState(false);
const [currentSlide, setCurrentSlide] = useState(0);
const [isPaused, setIsPaused] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
const heroBanners = banners.filter(b => b.isActive !== false && b.imageUrl);
const totalSlides = heroBanners.length;
// Auto-advance slider
useEffect(() => {
if (totalSlides <= 1 || isPaused) return;
const interval = setInterval(() => {
setCurrentSlide(prev => (prev + 1) % totalSlides);
}, 5000);
return () => clearInterval(interval);
}, [totalSlides, isPaused]);
const activeBanner = totalSlides > 0 ? heroBanners[currentSlide] : null;
const defaultTitle = activeBanner?.title || (banners.length > 0 && banners[0].title ? banners[0].title : "تخصص آلمانی در خدمت\nسلامت پت‌های خانگی");
const defaultSubtitle = activeBanner?.subtitle || (banners.length > 0 && banners[0].subtitle ? banners[0].subtitle : 'از سال ۱۹۸۴، شرکت Canina pharma GmbH در برگیش گلادباخ آلمان، با بهره‌گیری از مواد اولیه طبیعی و فرآیندهای پیشرفته، استاندارد طلایی مکمل‌های دامپزشکی را تعریف می‌کند. کنینا نماینده رسمی این برند در ایران است.');
const defaultTitle = banners.length > 0 && banners[0].title ? banners[0].title : "تخصص آلمانی در خدمت\nسلامت پت‌های خانگی";
const defaultSubtitle = banners.length > 0 && banners[0].subtitle ? banners[0].subtitle : 'از سال ۱۹۸۴، شرکت Canina pharma GmbH در برگیش گلادباخ آلمان، با بهره‌گیری از مواد اولیه طبیعی و فرآیندهای پیشرفته، استاندارد طلایی مکمل‌های دامپزشکی را تعریف می‌کند. کنینا نماینده رسمی این برند در ایران است.';
const title = (mounted && isInitialized ? getText('hero_title', '') : '') || defaultTitle;
const subtitle = (mounted && isInitialized ? getText('hero_desc', '') : '') || defaultSubtitle;
const titleParts = title.split('\n');
const activeImageUrl = activeBanner?.imageUrl || (mounted && isInitialized ? getText('hero_image_url', '') : '') || (banners.length > 0 && banners[0].imageUrl ? banners[0].imageUrl : "/assets/images/hero-section-image.png");
const handleNextSlide = () => {
if (totalSlides > 1) {
setCurrentSlide(prev => (prev + 1) % totalSlides);
}
};
const handlePrevSlide = () => {
if (totalSlides > 1) {
setCurrentSlide(prev => (prev - 1 + totalSlides) % totalSlides);
}
};
return (
<section className="relative overflow-hidden bg-white py-16 lg:py-28 border-b border-medical-gray-100">
<section className="relative overflow-hidden bg-white py-20 lg:py-32 border-b border-medical-gray-100">
{/* Background patterns */}
<div className="absolute top-0 left-0 w-full h-full opacity-[0.03] pointer-events-none select-none overflow-hidden">
<div className="absolute top-0 right-0 w-[800px] h-[800px] bg-canina-blue rounded-full blur-[120px] -mr-96 -mt-96" />
@ -148,114 +119,56 @@ export default function Hero({ banners = [], onShopNavigate }: { banners?: Banne
</motion.div>
</div>
{/* Visual Element & Interactive Slider */}
<div
className="lg:w-1/2 relative w-full max-w-lg lg:max-w-none mx-auto"
onMouseEnter={() => setIsPaused(true)}
onMouseLeave={() => setIsPaused(false)}
>
{/* Visual Element */}
<div className="lg:w-1/2 relative">
<motion.div
initial={{ opacity: 0, scale: 0.9, rotate: -3 }}
initial={{ opacity: 0, scale: 0.9, rotate: -5 }}
animate={{ opacity: 1, scale: 1, rotate: 0 }}
transition={{ duration: 0.8, ease: "easeOut" }}
transition={{ duration: 1, ease: "easeOut" }}
className="relative z-10"
>
<div className="aspect-square bg-gradient-to-br from-slate-200 to-slate-300 rounded-[2.5rem] overflow-hidden border-4 border-white shadow-2xl relative group">
<AnimatePresence mode="wait">
<motion.img
key={activeImageUrl}
src={activeImageUrl}
alt={activeBanner?.title || "Canina Pharma Germany"}
initial={{ opacity: 0, scale: 1.05 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.98 }}
transition={{ duration: 0.5 }}
className="w-full h-full object-cover"
loading="eager"
referrerPolicy="no-referrer"
/>
</AnimatePresence>
{/* Gradient overlay and slide caption */}
<div className="absolute bottom-0 left-0 right-0 p-6 sm:p-8 bg-gradient-to-t from-black/85 via-black/40 to-transparent text-white text-right">
<div className="aspect-square bg-gradient-to-br from-slate-200 to-slate-300 rounded-[2.5rem] overflow-hidden border-4 border-white shadow-2xl relative">
<img
src={(mounted && isInitialized ? getText('hero_image_url', '') : '') || (banners.length > 0 && banners[0].imageUrl ? banners[0].imageUrl : "/assets/images/hero-section-image.png")}
alt={banners.length > 0 ? banners[0].title : "Canina Pharma Germany"}
className="w-full h-full object-cover"
loading="eager"
referrerPolicy="no-referrer"
/>
<div className="absolute bottom-0 left-0 right-0 p-8 bg-gradient-to-t from-black/80 to-transparent text-white text-right">
<div className="text-sm font-medium mb-1 opacity-80 uppercase tracking-widest text-[10px]">
{mounted && isInitialized ? getText('hero_image_badge', "سرآمد علمی در پزشکی پت‌ها") : "سرآمد علمی در پزشکی پت‌ها"}
</div>
<div className="text-lg sm:text-xl font-bold italic tracking-tighter">
{activeBanner?.title || (mounted && isInitialized ? getText('hero_image_title', "مکمل‌های تایید شده دامپزشکی با گواهی IFS") : "مکمل‌های تایید شده دامپزشکی با گواهی IFS")}
<div className="text-xl font-bold italic tracking-tighter">
{mounted && isInitialized ? getText('hero_image_title', "مکمل‌های تایید شده دامپزشکی با گواهی IFS") : "مکمل‌های تایید شده دامپزشکی با گواهی IFS"}
</div>
{activeBanner?.linkUrl && (
<a
href={activeBanner.linkUrl}
className="inline-flex items-center gap-1 text-xs text-canina-gold hover:underline font-bold mt-2"
>
<span>اطلاعات بیشتر و خرید</span>
<ArrowLeft className="w-3.5 h-3.5" />
</a>
)}
</div>
{/* Slider Navigation Arrows (shown if multiple slides exist) */}
{totalSlides > 1 && (
<>
<button
onClick={handlePrevSlide}
className="absolute right-4 top-1/2 -translate-y-1/2 w-10 h-10 rounded-full bg-black/40 hover:bg-black/70 text-white backdrop-blur-md flex items-center justify-center transition-all opacity-0 group-hover:opacity-100 z-30"
aria-label="اسلاید قبلی"
>
<ChevronRight className="w-5 h-5" />
</button>
<button
onClick={handleNextSlide}
className="absolute left-4 top-1/2 -translate-y-1/2 w-10 h-10 rounded-full bg-black/40 hover:bg-black/70 text-white backdrop-blur-md flex items-center justify-center transition-all opacity-0 group-hover:opacity-100 z-30"
aria-label="اسلاید بعدی"
>
<ChevronLeft className="w-5 h-5" />
</button>
{/* Dot Indicators */}
<div className="absolute bottom-4 left-1/2 -translate-x-1/2 flex items-center gap-2 z-30 bg-black/40 backdrop-blur-md px-3 py-1.5 rounded-full">
{heroBanners.map((_, idx) => (
<button
key={idx}
onClick={() => setCurrentSlide(idx)}
className={`transition-all rounded-full ${
currentSlide === idx
? "w-6 h-2 bg-canina-gold"
: "w-2 h-2 bg-white/60 hover:bg-white"
}`}
aria-label={`اسلاید ${idx + 1}`}
/>
))}
</div>
</>
)}
{/* Quality Badge inside the image container */}
{/* Quality Badge inside the image container to prevent text collision */}
<motion.div
animate={{ rotate: 360 }}
transition={{ duration: 20, repeat: Infinity, ease: "linear" }}
className="absolute top-6 right-6 lg:top-8 lg:right-8 z-20 w-20 h-20 lg:w-24 lg:h-24 bg-white shadow-2xl rounded-full p-2 border-2 border-dashed border-canina-blue flex items-center justify-center text-center"
className="absolute top-6 right-6 lg:top-8 lg:right-8 z-20 w-24 h-24 lg:w-28 lg:h-28 bg-white shadow-2xl rounded-full p-2 border-2 border-dashed border-canina-blue flex items-center justify-center text-center"
>
<div className="flex flex-col items-center">
<span className="text-lg lg:text-xl font-black text-canina-blue leading-none tracking-tighter">DE</span>
<span className="text-[6px] lg:text-[7px] font-bold text-medical-gray-500 uppercase tracking-widest mt-0.5 leading-tight">
{mounted && isInitialized ? getText('hero_quality_standard', "استاندارد کیفی آلمان") : "استاندارد کیفی آلمان"}
<span className="text-xl lg:text-2xl font-black text-canina-blue leading-none tracking-tighter">DE</span>
<span className="text-[7px] lg:text-[8px] font-bold text-medical-gray-500 uppercase tracking-widest mt-0.5 leading-tight">
{mounted && isInitialized ? getText('hero_quality_standard', "استاندارد کیفی آلمان (IFS & HACCP)") : "استاندارد کیفی آلمان (IFS & HACCP)"}
</span>
</div>
</motion.div>
{/* 3D Action Badge */}
<div className="absolute bottom-24 right-6 bg-canina-gold text-medical-gray-900 px-3.5 py-1.5 rounded-2xl text-xs font-black shadow-2xl z-20 flex items-center gap-1.5 font-vazir border border-white/20 select-none animate-bounce">
{/* 3D Action Badge clarifying the 3D mark */}
<div className="absolute bottom-28 right-6 bg-canina-gold text-medical-gray-900 px-4 py-2 rounded-2xl text-xs font-black shadow-2xl z-20 flex items-center gap-1.5 font-vazir border border-white/20 select-none animate-bounce">
<span className="text-sm">✨</span>
<span>فرمول ۳کاره</span>
<span>فرمول ۳کاره (تاثیر سه‌بعدی)</span>
</div>
</div>
</motion.div>
</div>
</div>
{/* Stats */}
{/* Stats - 3 side-by-side cards with centered text on all screens */}
<div className="max-w-7xl mx-auto px-2 sm:px-4 relative">
<motion.div
initial={{ opacity: 0, y: 20 }}

View File

@ -2,7 +2,6 @@
import React from "react";
import { Wrench, Phone, ShieldCheck, Clock } from "lucide-react";
import { useSettingsStore } from "../lib/store/settingsStore";
import BrandLogo from "./BrandLogo";
export default function MaintenancePage() {
const getText = useSettingsStore((state) => state.getText);
@ -14,8 +13,8 @@ export default function MaintenancePage() {
);
const badge = getText("maintenance_badge", "سامانه در حال به‌روزرسانی و ارتقا");
const eta = getText("maintenance_eta", "زمان تقریبی بازگشایی: کمتر از ۲ ساعت آینده");
const phone = getText("contact_phone", getText("maintenance_contact_phone", "۰۲۱-۸۸۸۸۴۴۴۴"));
const phoneLink = getText("contact_phone_link", getText("maintenance_contact_phone_link", `tel:${phone.replace(/[^0-9+]/g, "") || "02188884444"}`));
const phone = getText("maintenance_contact_phone", "۰۲۱-۸۸۸۸۸۸۸۸");
const rawPhone = phone.replace(/[^0-9]/g, "");
return (
<div className="min-h-screen bg-medical-gray-900 text-white flex flex-col items-center justify-center p-6 text-center font-vazir relative overflow-hidden" dir="rtl">
@ -24,13 +23,8 @@ export default function MaintenancePage() {
<div className="absolute bottom-10 right-10 w-80 h-80 bg-canina-gold/10 rounded-full blur-3xl pointer-events-none" />
<div className="relative z-10 max-w-xl mx-auto space-y-6">
{/* Brand Logo */}
<div className="flex justify-center mb-2">
<BrandLogo isDarkBackground size="lg" href="" />
</div>
<div className="w-20 h-20 bg-white/10 border border-white/20 rounded-3xl mx-auto flex items-center justify-center text-canina-gold shadow-2xl backdrop-blur-md">
<Wrench className="w-10 h-10 animate-bounce" />
<div className="w-24 h-24 bg-white/10 border border-white/20 rounded-3xl mx-auto flex items-center justify-center text-canina-gold shadow-2xl backdrop-blur-md">
<Wrench className="w-12 h-12 animate-bounce" />
</div>
<span className="inline-block px-4 py-1.5 bg-canina-gold/20 text-canina-gold border border-canina-gold/30 rounded-full text-xs font-black tracking-widest uppercase">
@ -54,7 +48,7 @@ export default function MaintenancePage() {
<div className="pt-2 flex flex-col sm:flex-row items-center justify-center gap-4">
<a
href={phoneLink}
href={`tel:${rawPhone || "02188888888"}`}
className="w-full sm:w-auto px-6 py-3.5 bg-canina-blue hover:bg-canina-dark text-white font-black text-xs rounded-xl flex items-center justify-center gap-2 shadow-lg transition-all"
>
<Phone className="w-4 h-4" />

View File

@ -693,11 +693,11 @@ export default function ProductPage({ productSlug }: { productSlug: string }) {
{isCartDisabled ? (
<div className="space-y-3">
<a
href={getText('contact_phone_link', `tel:${getText('contact_phone', '02188884444').replace(/[^0-9+]/g, '')}`)}
href="tel:02188888888"
className="w-full flex items-center justify-center gap-2 px-4 bg-canina-blue text-white rounded-2xl h-16 font-black text-sm hover:bg-canina-dark transition-all shadow-xl shadow-canina-blue/20 font-vazir"
>
<Phone className="w-5 h-5" />
<span>مشاوره و استعلام خرید ({getText('contact_phone', '۰۲۱-۸۸۸۸۴۴۴۴')})</span>
<span>مشاوره و استعلام خرید</span>
</a>
<p className="text-[11px] text-medical-gray-400 font-bold text-center">
خرید آنلاین در حالت کاتالوگ موقتاً غیرفعال است
@ -916,7 +916,7 @@ export default function ProductPage({ productSlug }: { productSlug: string }) {
<div className="flex items-center gap-2 flex-1 max-w-[240px]">
{isCartDisabled ? (
<a
href={getText('contact_phone_link', `tel:${getText('contact_phone', '02188884444').replace(/[^0-9+]/g, '')}`)}
href="tel:02188888888"
className="flex-1 bg-canina-blue text-white py-2.5 px-3 rounded-xl font-black text-xs hover:bg-canina-dark transition-all flex items-center justify-center gap-1.5 shadow-md shadow-canina-blue/20 whitespace-nowrap"
>
<Phone className="w-4 h-4" />

View File

@ -251,16 +251,6 @@ export class ProductService {
return [];
}
}
public async getBanners(): Promise<any[]> {
try {
const response = await api.get('/banners');
return response.data || [];
} catch (error) {
console.error("[ProductService] Failed to fetch banners:", error);
return [];
}
}
}
export const productService = ProductService.getInstance();

View File

@ -54,34 +54,17 @@ export const useSettingsStore = create<SettingsStore>()((set, get) => ({
}
},
getText: (key, fallback) => {
const texts = get().texts;
if (!texts) return fallback;
const value =
texts[key] ??
texts[key.toLowerCase()] ??
texts[key.toUpperCase()];
if (value === undefined || value === null || value === '') {
// Special aliases
if (key === 'contact_phone') return texts['CONTACT_PHONE'] || texts['footer_phone'] || texts['maintenance_contact_phone'] || fallback;
if (key === 'contact_phone_link') return texts['CONTACT_PHONE_LINK'] || texts['maintenance_contact_phone_link'] || (texts['contact_phone'] ? `tel:${texts['contact_phone'].replace(/[^0-9+]/g, '')}` : fallback);
if (key === 'site_logo') return texts['BRAND_LOGO_URL'] || texts['brand_logo_url'] || fallback;
if (key === 'site_logo_text_en') return texts['BRAND_LOGO_TEXT_EN'] || fallback;
if (key === 'site_logo_text_fa') return texts['BRAND_LOGO_TEXT_FA'] || texts['brand_name_fa'] || fallback;
if (key === 'site_logo_subtitle') return texts['BRAND_LOGO_SUBTITLE'] || texts['brand_subtitle'] || fallback;
const value = get().texts[key];
if (value === undefined || value === null) {
return fallback;
}
return value;
},
getBoolean: (key, fallback) => {
const texts = get().texts;
if (!texts) return fallback;
const value =
texts[key] ??
texts[key.toLowerCase()] ??
texts[key.toUpperCase()];
if (value === undefined || value === null || value === '') {
const value = get().texts[key];
if (value === undefined || value === null) {
return fallback;
}
return value === 'true' || value === '1' || (value as unknown) === true;
return value === 'true' || value === '1';
}
}));

File diff suppressed because it is too large Load Diff

View File

@ -7,41 +7,41 @@
"5": "SmsService",
"6": "AuthController",
"7": "app.module.ts",
"8": "CreateVideoDto",
"9": "ProductService",
"8": "VideosService",
"9": "products.ts",
"10": "ContactService",
"11": "compilerOptions",
"12": "UserDashboard.tsx",
"12": "toPersian",
"13": "auth.controller.ts",
"14": "ProductsService",
"15": "lib/services/api.ts",
"16": "tickets.controller.ts",
"17": "ArchivePage.tsx",
"15": "useCartStore",
"16": "eslint",
"17": "useSettingsStore",
"18": "MetricsController",
"19": "B2BService",
"19": "B2B Inquiry Controller",
"20": "CategoriesController",
"21": "RedisService",
"22": "MediaController",
"23": "Ingredient Management Controller",
"24": "adminRoutes.tsx",
"25": "admin.module.ts",
"26": "Coupons.tsx",
"26": "ConfirmModal.tsx",
"27": "Prescription Review Controller",
"28": "Smart Advisor Controller",
"29": "Testimonials Management Controller",
"30": "src/services/api.ts",
"30": "api",
"31": "Spinner.tsx",
"32": "PetProfile.tsx",
"33": "ZibalService",
"34": "main.ts",
"35": "admin.ts",
"36": "compilerOptions",
"37": "compilerOptions",
"38": "Transactions.tsx",
"35": "src/services/api.ts",
"36": "Backend TypeScript Config",
"37": "App TypeScript Config",
"38": "Pagination.tsx",
"39": "dependencies",
"40": "Node TypeScript Config",
"41": "Admin Blog Controller",
"42": "WholesaleApplyDto",
"42": "WholesaleService",
"43": "devDependencies",
"44": "Generic CRUD Controller",
"45": "seo.module.ts",
@ -52,16 +52,16 @@
"50": "Database Seeding Logic",
"51": "Prisma Database Migrations",
"52": "dependencies",
"53": "Orders.tsx",
"53": "UI Skeleton and Tables",
"54": "auth.service.ts",
"55": "BE-001",
"56": "VetGallery.tsx",
"56": "SafeImage.tsx",
"57": "NPM Lifecycle Scripts",
"58": "PetsController",
"58": "Pet Management API",
"59": "PrismaService",
"60": "Jest Testing Config",
"61": "CreateReviewDto",
"62": "BannersService",
"61": "WikiController",
"62": "videos.controller.ts",
"63": "FE-001",
"64": "VerifyOtpDto",
"65": "rules/graphify.md",
@ -73,10 +73,10 @@
"71": "Analytics and Report Charts",
"72": "Task Orchestration Scripts",
"73": "Backend Package Config",
"74": "useSettingsStore",
"74": "RegisterDto",
"75": "React Error Boundary",
"76": "Application Package Config",
"77": "WikiController",
"77": ".findAll",
"78": "Error and Not Found Pages",
"79": "VPN Utility Scripts",
"80": "NestJS CLI Config",
@ -89,7 +89,7 @@
"87": "Font Assets and Licenses",
"88": "Ledger Rebuild Scripts",
"89": "Evidence Validation Scripts",
"90": "SslController",
"90": "CreateVideoDto",
"91": "Blog Post Detail Page",
"92": "@types/node",
"93": "devDependencies",
@ -99,7 +99,7 @@
"97": "Home Management DTOs",
"98": "Wiki Management DTOs",
"99": "Wiki Page Routing",
"100": "toPersian",
"100": "ClientLayout.tsx",
"101": "Docker Deployment Scripts",
"102": "DB-001",
"103": "BlogsService",
@ -116,10 +116,10 @@
"114": "Manifest Data Generation",
"115": "Honest Manifest Synchronization",
"116": "Manifest Entry Synchronization",
"117": "PaymentController",
"117": "SendOtpDto",
"118": "TS-001",
"119": "TEST-001",
"120": "AdminTransactionFilterDto",
"120": "payment.service.ts",
"121": "UITexts.tsx",
"122": "Admin Panel TSConfig",
"123": "About Page Component",
@ -140,8 +140,8 @@
"138": "Operational Rules & Boundaries",
"139": "Operational Rules & Boundaries",
"140": "Bcrypt Type Definitions",
"141": "SettingsService",
"142": ".update",
"141": "bcryptjs",
"142": "helmet",
"143": "Blog Entity Model",
"144": "Home Entity Model",
"145": "Wiki Entity Model",
@ -206,7 +206,7 @@
"204": "backend/README.md",
"205": "Repository Map",
"206": "Sahel-Font",
"207": "zibal.service.ts",
"207": "AuthService",
"208": "Sahel-Font",
"209": "Role & Core Objective",
"210": "exclude",
@ -236,8 +236,8 @@
"234": "🔒 Security & Performance Review (09_devops_security)",
"235": "👁️ UX & Persona Interface Review (08_visual_qa)",
"236": "Compiler Diagnostic Dispositions",
"237": "useCartStore",
"238": "ProductPage.tsx",
"237": "OrderService",
"238": "js-yaml",
"239": "globals",
"240": "prettier",
"241": "prisma",
@ -265,25 +265,27 @@
"263": "application/public/fonts/sahel-font-v3.4.0/CHANGELOG.md",
"264": "tailwindcss",
"265": "typescript-eslint",
"266": ".handleZibalCallback",
"267": "PetsService",
"268": "PetsController",
"269": "generate-openapi.js",
"270": "InitiatePaymentDto",
"271": "SmsLogQueryDto",
"272": "CreateHealthLogDto",
"266": "@nestjs/core",
"267": "@nestjs/jwt",
"268": "@nestjs/swagger",
"269": "@nestjs/throttler",
"270": "passport-jwt",
"271": "@prisma/client",
"272": "swagger-ui-express",
"273": "AGENTS.md",
"274": "eslint-config-prettier",
"275": "CreateReminderDto",
"276": "Reviews.tsx",
"277": "Tickets.tsx",
"278": "Videos.tsx",
"279": "@types/passport-jwt",
"280": "@types/supertest",
"281": "typescript",
"282": "@types/react-dom",
"283": "generate-openapi.d.ts",
"275": "@eslint/js",
"276": "jest",
"277": "@nestjs/schematics",
"278": "@nestjs/testing",
"279": "source-map-support",
"280": "ts-jest",
"281": "tsconfig-paths",
"282": "@types/bcryptjs",
"283": "typescript-eslint",
"284": "contact/page.tsx",
"285": "eslint-plugin-prettier",
"286": "eslint-config-next",
"288": "SmsSettingsPage.tsx",
"290": "@tailwindcss/postcss"
}

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
C:\Users\p.aghaei\Desktop\Work\parsa\git.parsaaghayi.ir\canina
.

File diff suppressed because it is too large Load Diff

View File

@ -1,289 +0,0 @@
{
"0": "AdminService",
"1": "pets/pets.controller.ts",
"2": "Roles",
"3": "UsersService",
"4": "CmsController",
"5": "SmsService",
"6": "AuthController",
"7": "app.module.ts",
"8": "CreateVideoDto",
"9": "ProductService",
"10": "ContactService",
"11": "compilerOptions",
"12": "UserDashboard.tsx",
"13": "auth.controller.ts",
"14": "ProductsService",
"15": "lib/services/api.ts",
"16": "tickets.controller.ts",
"17": "ArchivePage.tsx",
"18": "MetricsController",
"19": "B2BService",
"20": "CategoriesController",
"21": "RedisService",
"22": "MediaController",
"23": "Ingredient Management Controller",
"24": "adminRoutes.tsx",
"25": "admin.module.ts",
"26": "Coupons.tsx",
"27": "Prescription Review Controller",
"28": "Smart Advisor Controller",
"29": "Testimonials Management Controller",
"30": "src/services/api.ts",
"31": "Spinner.tsx",
"32": "PetProfile.tsx",
"33": "ZibalService",
"34": "main.ts",
"35": "admin.ts",
"36": "compilerOptions",
"37": "compilerOptions",
"38": "Transactions.tsx",
"39": "dependencies",
"40": "Node TypeScript Config",
"41": "Admin Blog Controller",
"42": "WholesaleApplyDto",
"43": "devDependencies",
"44": "Generic CRUD Controller",
"45": "seo.module.ts",
"46": "PaginationDto",
"47": "Project Build Scripts",
"48": "Home Data Module",
"49": "devDependencies",
"50": "Database Seeding Logic",
"51": "Prisma Database Migrations",
"52": "dependencies",
"53": "Orders.tsx",
"54": "auth.service.ts",
"55": "BE-001",
"56": "VetGallery.tsx",
"57": "NPM Lifecycle Scripts",
"58": "PetsController",
"59": "PrismaService",
"60": "Jest Testing Config",
"61": "CreateReviewDto",
"62": "BannersService",
"63": "FE-001",
"64": "VerifyOtpDto",
"65": "rules/graphify.md",
"66": "App Health Controller",
"67": "dependencies",
"68": "OrdersService",
"69": "Integrity Validation Scripts",
"70": "Admin Panel Package Config",
"71": "Analytics and Report Charts",
"72": "Task Orchestration Scripts",
"73": "Backend Package Config",
"74": "useSettingsStore",
"75": "React Error Boundary",
"76": "Application Package Config",
"77": "WikiController",
"78": "Error and Not Found Pages",
"79": "VPN Utility Scripts",
"80": "NestJS CLI Config",
"81": "Seed TypeScript Config",
"82": "ADM-001",
"83": "Browser Utility Scripts",
"84": "Dev Server Startup",
"85": "Architectural Audit Findings",
"86": "Pagination API Schemas",
"87": "Font Assets and Licenses",
"88": "Ledger Rebuild Scripts",
"89": "Evidence Validation Scripts",
"90": "SslController",
"91": "Blog Post Detail Page",
"92": "@types/node",
"93": "devDependencies",
"94": "UI Text Seeding",
"95": "Wiki Terms Seeding",
"96": "Blog Management DTOs",
"97": "Home Management DTOs",
"98": "Wiki Management DTOs",
"99": "Wiki Page Routing",
"100": "toPersian",
"101": "Docker Deployment Scripts",
"102": "DB-001",
"103": "BlogsService",
"104": "Database Migration Scripts",
"105": "Scientific Terms Schema",
"106": "Blog Data Seeding",
"107": "Custom Data Seeding",
"108": "Products Table",
"109": "Auth Architecture and Planning",
"110": "Build Manifest Generation",
"111": "Classification Data Generation",
"112": "Evidence Data Generation",
"113": "Ledger Data Generation",
"114": "Manifest Data Generation",
"115": "Honest Manifest Synchronization",
"116": "Manifest Entry Synchronization",
"117": "PaymentController",
"118": "TS-001",
"119": "TEST-001",
"120": "AdminTransactionFilterDto",
"121": "UITexts.tsx",
"122": "Admin Panel TSConfig",
"123": "About Page Component",
"124": "Privacy Page Component",
"125": "Next.js Security Configuration",
"126": "Typography and Font Assets",
"127": "DEVOPS-001",
"128": "DOC-001",
"129": "What You Must Do When Invoked",
"130": "JwtAuthGuard",
"131": "راهنمای تست سیستم (Software Testing)",
"132": "Role & Core Objective",
"133": "Required Review Group Closures",
"134": "Operational Rules & Boundaries",
"135": "Operational Rules & Boundaries",
"136": "🏢 AI Software Agency — Master Orchestration Protocol v3",
"137": "@nestjs/cli",
"138": "Operational Rules & Boundaries",
"139": "Operational Rules & Boundaries",
"140": "Bcrypt Type Definitions",
"141": "SettingsService",
"142": ".update",
"143": "Blog Entity Model",
"144": "Home Entity Model",
"145": "Wiki Entity Model",
"146": "User Profile Management",
"147": "Pets Table",
"148": "User Database and Infrastructure",
"149": "API and Frontend Specifications",
"150": "Brand and Agent Guidelines",
"151": "Search Engine Robots Config",
"152": "Application ESLint Config",
"153": "PostCSS Configuration",
"154": "Vitest Test Setup",
"155": "Database Backup Script",
"156": "Application Startup Script",
"157": "What You Must Do When Invoked",
"158": "Backend ESLint Config",
"159": "User Logout Endpoint",
"160": "Company Profile",
"161": "Project Introduction",
"162": "Architecture Route Map",
"163": "Project Task Backlog",
"164": "Root ESLint Configuration",
"165": "PostCSS Build Config",
"166": "Tailwind CSS Configuration",
"167": "Vite Build Configuration",
"168": "Sahel Font Samples",
"169": "Shabnam Font History",
"170": "Vazirmatn Font History",
"171": "Vitest Test Configuration",
"172": "Variable Font Samples",
"173": "Shabnam Font Preview",
"174": "Production Docker Setup",
"175": "Staging Docker Setup",
"176": ".agents/workflows/graphify.md",
"177": "Role & Core Objective",
"178": "graphify reference: extra exports and benchmark",
"179": "graphify reference: query, path, explain",
"180": "graphify reference: add a URL and watch a folder",
"181": "graphify reference: commit hook and native CLAUDE.md integration",
"182": "graphify reference: incremental update and cluster-only",
"183": "graphify reference: GitHub clone and cross-repo merge",
"184": "graphify reference: transcribe video and audio",
"185": "Reconciled Audit Roles & Assignments",
"186": "instructions.md",
"187": "Phase 3.1 — Human Review Preparation and Master Backlog Critique",
"188": "CLAUDE.md",
"189": ".claude/CLAUDE.md",
"190": "extraction-spec.md",
"191": "User Login API",
"192": "Developer Standards and Architecture",
"193": "Deep Audit Summary Report",
"194": "Operational Rules & Boundaries",
"195": "Comprehensive Change Log",
"196": "Operational Rules & Boundaries",
"197": "1. Summary of Integrity Repairs Performed",
"198": "Operational Rules & Boundaries",
"199": "Operational Rules & Boundaries",
"200": "Operational Rules & Boundaries",
"201": "Vazirmatn Changelog",
"202": "Vazirmatn Font فونت وزیرمتن",
"203": "Operational Rules & Boundaries",
"204": "backend/README.md",
"205": "Repository Map",
"206": "Sahel-Font",
"207": "zibal.service.ts",
"208": "Sahel-Font",
"209": "Role & Core Objective",
"210": "exclude",
"211": "Phase 2 Final Quality Gate Summary Report",
"212": "Task Modifications Log",
"213": "Install",
"214": "BlogsController",
"215": "System Discovery",
"216": "Product Requirement Document (PRD)",
"217": "Baseline Command Plan & Reconciled Command History",
"218": "Architecture Specification",
"219": "Project Health Audit Report",
"220": "Open Questions",
"221": "Final Phase 2 Audit Closure Report",
"222": "📝 Active Agent Working Scratchpad",
"223": "🔍 Code Health Audit Review (01_auditor)",
"224": "Omitted File Inspection Report",
"225": "Phase 3.2 / 3.3 — Implementation Readiness & Architectural Finalization Report",
"226": "Phase 3 Audit Traceability Matrix",
"227": "API Contract Specification",
"228": "⚙️ Backend Technical Review (05_dev_backend)",
"229": "🎨 Frontend & Admin Panel Technical Review (06_dev_frontend)",
"230": "🚀 SEO & Content Strategy Review (12_seo_content)",
"231": "Raw Finding Verification & Disposition Report",
"232": "React + TypeScript + Vite",
"233": "application/README.md",
"234": "🔒 Security & Performance Review (09_devops_security)",
"235": "👁️ UX & Persona Interface Review (08_visual_qa)",
"236": "Compiler Diagnostic Dispositions",
"237": "useCartStore",
"238": "ProductPage.tsx",
"239": "globals",
"240": "prettier",
"241": "prisma",
"242": "supertest",
"243": "ts-loader",
"244": "ts-node",
"245": "@types/express",
"246": "@types/jest",
"247": "@types/js-yaml",
"248": "@types/multer",
"249": "eslint-plugin-react-hooks",
"250": "eslint-plugin-react-refresh",
"251": "tailwindcss",
"252": "typescript",
"253": "@testing-library/jest-dom",
"254": "@testing-library/react",
"255": "@types/react",
"256": "typescript",
"257": "vitest",
"258": "reviews/README.md",
"259": "axios",
"260": "admin-panel/public/fonts/sahel-font-v3.4.0/CHANGELOG.md",
"261": "shabnam-font-v5.0.1/CHANGELOG.md",
"262": "application/CLAUDE.md",
"263": "application/public/fonts/sahel-font-v3.4.0/CHANGELOG.md",
"264": "tailwindcss",
"265": "typescript-eslint",
"266": ".handleZibalCallback",
"267": "PetsService",
"268": "PetsController",
"269": "generate-openapi.js",
"270": "InitiatePaymentDto",
"271": "SmsLogQueryDto",
"272": "CreateHealthLogDto",
"273": "AGENTS.md",
"274": "eslint-config-prettier",
"275": "CreateReminderDto",
"276": "Reviews.tsx",
"277": "Tickets.tsx",
"278": "Videos.tsx",
"279": "@types/passport-jwt",
"280": "@types/supertest",
"281": "typescript",
"282": "@types/react-dom",
"283": "generate-openapi.d.ts",
"285": "eslint-plugin-prettier",
"288": "SmsSettingsPage.tsx",
"290": "@tailwindcss/postcss"
}

View File

@ -1 +0,0 @@
{"output_tokens": 7105}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +1,16 @@
# Graph Report - canina (2026-08-17)
# Graph Report - canina (2026-08-16)
## Corpus Check
- 493 files · ~709,356 words
- 467 files · ~685,071 words
- Verdict: corpus is large enough that graph structure adds value.
## Summary
- 3516 nodes · 5922 edges · 287 communities (192 shown, 95 thin omitted)
- Extraction: 96% EXTRACTED · 4% INFERRED · 0% AMBIGUOUS · INFERRED: 217 edges (avg confidence: 0.79)
- 3274 nodes · 5352 edges · 289 communities (175 shown, 114 thin omitted)
- Extraction: 96% EXTRACTED · 4% INFERRED · 0% AMBIGUOUS · INFERRED: 189 edges (avg confidence: 0.79)
- Token cost: 0 input · 0 output
## Graph Freshness
- Built from commit: `2fca7789`
- Built from commit: `5c13dd2f`
- Run `git rev-parse HEAD` and compare to check if the graph is stale.
- Run `graphify update .` after code changes (no API cost).
@ -23,41 +23,41 @@
- SmsService
- AuthController
- app.module.ts
- CreateVideoDto
- ProductService
- VideosService
- products.ts
- ContactService
- compilerOptions
- UserDashboard.tsx
- toPersian
- auth.controller.ts
- ProductsService
- lib/services/api.ts
- tickets.controller.ts
- ArchivePage.tsx
- useCartStore
- eslint
- useSettingsStore
- MetricsController
- B2BService
- B2B Inquiry Controller
- CategoriesController
- RedisService
- MediaController
- Ingredient Management Controller
- adminRoutes.tsx
- admin.module.ts
- Coupons.tsx
- ConfirmModal.tsx
- Prescription Review Controller
- Smart Advisor Controller
- Testimonials Management Controller
- src/services/api.ts
- api
- Spinner.tsx
- PetProfile.tsx
- ZibalService
- main.ts
- admin.ts
- compilerOptions
- compilerOptions
- Transactions.tsx
- src/services/api.ts
- Backend TypeScript Config
- App TypeScript Config
- Pagination.tsx
- dependencies
- Node TypeScript Config
- Admin Blog Controller
- WholesaleApplyDto
- WholesaleService
- devDependencies
- Generic CRUD Controller
- seo.module.ts
@ -68,16 +68,16 @@
- Database Seeding Logic
- Prisma Database Migrations
- dependencies
- Orders.tsx
- UI Skeleton and Tables
- auth.service.ts
- BE-001
- VetGallery.tsx
- SafeImage.tsx
- NPM Lifecycle Scripts
- PetsController
- Pet Management API
- PrismaService
- Jest Testing Config
- CreateReviewDto
- BannersService
- WikiController
- videos.controller.ts
- FE-001
- VerifyOtpDto
- rules/graphify.md
@ -89,10 +89,10 @@
- Analytics and Report Charts
- Task Orchestration Scripts
- Backend Package Config
- useSettingsStore
- RegisterDto
- React Error Boundary
- Application Package Config
- WikiController
- .findAll
- Error and Not Found Pages
- VPN Utility Scripts
- NestJS CLI Config
@ -105,7 +105,7 @@
- Font Assets and Licenses
- Ledger Rebuild Scripts
- Evidence Validation Scripts
- SslController
- CreateVideoDto
- Blog Post Detail Page
- @types/node
- devDependencies
@ -115,7 +115,7 @@
- Home Management DTOs
- Wiki Management DTOs
- Wiki Page Routing
- toPersian
- ClientLayout.tsx
- Docker Deployment Scripts
- DB-001
- BlogsService
@ -132,10 +132,10 @@
- Manifest Data Generation
- Honest Manifest Synchronization
- Manifest Entry Synchronization
- PaymentController
- SendOtpDto
- TS-001
- TEST-001
- AdminTransactionFilterDto
- payment.service.ts
- UITexts.tsx
- Admin Panel TSConfig
- About Page Component
@ -156,8 +156,8 @@
- Operational Rules & Boundaries
- Operational Rules & Boundaries
- Bcrypt Type Definitions
- SettingsService
- .update
- bcryptjs
- helmet
- Blog Entity Model
- Home Entity Model
- Wiki Entity Model
@ -215,7 +215,7 @@
- backend/README.md
- Repository Map
- Sahel-Font
- zibal.service.ts
- AuthService
- Sahel-Font
- Role & Core Objective
- exclude
@ -245,8 +245,8 @@
- 🔒 Security & Performance Review (09_devops_security)
- 👁️ UX & Persona Interface Review (08_visual_qa)
- Compiler Diagnostic Dispositions
- useCartStore
- ProductPage.tsx
- OrderService
- js-yaml
- globals
- prettier
- prisma
@ -267,38 +267,41 @@
- typescript
- vitest
- typescript-eslint
- .handleZibalCallback
- PetsService
- PetsController
- generate-openapi.js
- InitiatePaymentDto
- SmsLogQueryDto
- CreateHealthLogDto
- @nestjs/core
- @nestjs/jwt
- @nestjs/swagger
- @nestjs/throttler
- passport-jwt
- @prisma/client
- swagger-ui-express
- AGENTS.md
- eslint-config-prettier
- CreateReminderDto
- Reviews.tsx
- Tickets.tsx
- Videos.tsx
- @types/passport-jwt
- @types/supertest
- typescript
- @types/react-dom
- @eslint/js
- jest
- @nestjs/schematics
- @nestjs/testing
- source-map-support
- ts-jest
- tsconfig-paths
- @types/bcryptjs
- typescript-eslint
- contact/page.tsx
- eslint-plugin-prettier
- eslint-config-next
- SmsSettingsPage.tsx
- @tailwindcss/postcss
## God Nodes (most connected - your core abstractions)
1. `PrismaService` - 79 edges
2. `Roles()` - 73 edges
3. `useSettingsStore` - 43 edges
4. `PaginationDto` - 39 edges
5. `SmsService` - 38 edges
6. `api` - 37 edges
7. `AdminService` - 34 edges
8. `AdminController` - 33 edges
9. `toPersian()` - 32 edges
10. `JwtAuthGuard` - 31 edges
1. `PrismaService` - 74 edges
2. `Roles()` - 60 edges
3. `PaginationDto` - 39 edges
4. `SmsService` - 38 edges
5. `api` - 34 edges
6. `useSettingsStore` - 33 edges
7. `AdminService` - 31 edges
8. `toPersian()` - 31 edges
9. `AdminController` - 30 edges
10. `useCartStore` - 29 edges
## Surprising Connections (you probably didn't know these)
- `User Profile Photo` --conceptually_related_to--> `User Roles and Capabilities` [INFERRED]
@ -321,43 +324,43 @@
- **Rastikerdar Font Ecosystem** — frontend_application_public_fonts_shabnam_font_v5_0_1_readme, frontend_application_public_fonts_vazirmatn_v33_003_readme, vazir_font [EXTRACTED 0.95]
- **Canina Deployment Stack** — scripts_compose_prod, scripts_compose_stage [EXTRACTED 1.00]
## Communities (287 total, 95 thin omitted)
## Communities (289 total, 114 thin omitted)
### Community 0 - "AdminService"
Cohesion: 0.05
Nodes (34): AdminController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete (+26 more)
Cohesion: 0.06
Nodes (24): AdminController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete (+16 more)
### Community 1 - "pets/pets.controller.ts"
Cohesion: 0.16
Nodes (13): CreatePetDto, ApiProperty, ApiPropertyOptional, IsArray, IsNotEmpty, IsNumber, IsOptional, IsString (+5 more)
Cohesion: 0.05
Nodes (45): CreateHealthLogDto, ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString, CreatePetDto, ApiProperty (+37 more)
### Community 2 - "Roles"
Cohesion: 0.18
Nodes (16): Roles(), SettingsController, ApiBearerAuth, ApiOkResponse, ApiOperation, ApiTags, Body, Controller (+8 more)
Cohesion: 0.06
Nodes (33): BannersController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+25 more)
### Community 3 - "UsersService"
Cohesion: 0.06
Nodes (41): DEFAULT_JWT_REFRESH_SECRET, DEFAULT_JWT_SECRET, getJwtSecret(), AuthModule, Module, JwtPayload, JwtStrategy, Injectable (+33 more)
Nodes (36): JwtPayload, JwtStrategy, Injectable, AddressDto, ApiProperty, ApiPropertyOptional, IsBoolean, IsNotEmpty (+28 more)
### Community 4 - "CmsController"
Cohesion: 0.08
Nodes (26): CmsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+18 more)
Cohesion: 0.09
Nodes (24): CmsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+16 more)
### Community 6 - "AuthController"
Cohesion: 0.27
Nodes (12): AuthController, ApiBadRequestResponse, ApiBearerAuth, ApiOkResponse, ApiOperation, ApiTags, Body, Controller (+4 more)
Cohesion: 0.36
Nodes (9): AuthController, ApiBadRequestResponse, ApiOkResponse, ApiOperation, ApiTags, Body, Controller, Post (+1 more)
### Community 7 - "app.module.ts"
Cohesion: 0.09
Nodes (28): B2BModule, Module, BannersModule, Module, SmsModule, Global, Module, ContactModule (+20 more)
Nodes (28): B2BModule, Module, BannersModule, Module, CmsModule, Module, SmsModule, Global (+20 more)
### Community 8 - "CreateVideoDto"
Cohesion: 0.07
Nodes (32): CreateVideoDto, ApiProperty, ApiPropertyOptional, IsBoolean, IsNotEmpty, IsOptional, IsString, UpdateVideoDto (+24 more)
### Community 8 - "VideosService"
Cohesion: 0.12
Nodes (16): ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete, Get (+8 more)
### Community 9 - "ProductService"
### Community 9 - "products.ts"
Cohesion: 0.06
Nodes (34): Blog(), getBlogs(), metadata, CatalogClient(), metadata, metadata, metadata, BlogPage() (+26 more)
Nodes (30): Blog(), getBlogs(), metadata, CatalogClient(), metadata, metadata, metadata, BlogPage() (+22 more)
### Community 10 - "ContactService"
Cohesion: 0.13
@ -367,65 +370,61 @@ Nodes (11): ContactController, Body, Controller, Get, Param, Post, Put, Query (+
Cohesion: 0.06
Nodes (32): compilerOptions, allowJs, esModuleInterop, incremental, isolatedModules, jsx, lib, module (+24 more)
### Community 12 - "UserDashboard.tsx"
### Community 12 - "toPersian"
Cohesion: 0.12
Nodes (17): AddressModal(), AddressModalProps, CheckoutPage(), DeleteConfirmModal(), DeleteConfirmModalProps, SearchableSelect(), SearchableSelectProps, PRESET_AMOUNTS (+9 more)
Nodes (24): AddressModal(), AddressModalProps, BackButton(), BackButtonProps, CheckoutPage(), DeleteConfirmModal(), DeleteConfirmModalProps, HeaderButton() (+16 more)
### Community 13 - "auth.controller.ts"
Cohesion: 0.10
Nodes (19): AdminLoginDto, ApiProperty, IsEmail, IsNotEmpty, IsString, MinLength, LoginDto, ApiProperty (+11 more)
Cohesion: 0.16
Nodes (11): AdminLoginDto, ApiProperty, IsEmail, IsNotEmpty, IsString, MinLength, LoginDto, ApiProperty (+3 more)
### Community 14 - "ProductsService"
Cohesion: 0.09
Nodes (19): GetProductsDto, ApiPropertyOptional, IsEnum, IsOptional, IsString, ProductsController, ApiOperation, ApiTags (+11 more)
### Community 15 - "lib/services/api.ts"
### Community 15 - "useCartStore"
Cohesion: 0.08
Nodes (16): metadata, ContactFormClient(), ContactInfoItem, PrescriptionUploadModal(), PrescriptionUploadModalProps, api, ApiErrorPayload, baseURL (+8 more)
Nodes (31): VerifyContent(), AuthModal(), AuthModalProps, B2BPortal(), CartDrawer(), ContactInfoItem, Header(), MENU_ICONS (+23 more)
### Community 16 - "tickets.controller.ts"
### Community 17 - "useSettingsStore"
Cohesion: 0.09
Nodes (31): AdminUpdateTicketDto, CreateTicketDto, ReplyTicketDto, ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString (+23 more)
### Community 17 - "ArchivePage.tsx"
Cohesion: 0.09
Nodes (26): HomeClient(), HomeClientProps, getHomeData(), Home(), metadata, metadata, ArchivePage(), CATEGORY_MAP (+18 more)
Nodes (31): HomeClient(), HomeClientProps, getHomeData(), Home(), metadata, metadata, EnamadBadge(), Hero() (+23 more)
### Community 18 - "MetricsController"
Cohesion: 0.29
Nodes (5): ApiExcludeController, MetricsController, Controller, Get, Res
### Community 19 - "B2BService"
Cohesion: 0.14
Nodes (14): B2BController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Get, Param (+6 more)
### Community 19 - "B2B Inquiry Controller"
Cohesion: 0.13
Nodes (15): B2BController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Get, Param (+7 more)
### Community 20 - "CategoriesController"
Cohesion: 0.10
Nodes (16): CategoriesController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete (+8 more)
Cohesion: 0.09
Nodes (17): CategoriesController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete (+9 more)
### Community 21 - "RedisService"
Cohesion: 0.12
Nodes (4): AppModule, Module, RedisService, Injectable
Cohesion: 0.11
Nodes (7): AppModule, Module, RedisModule, Global, Module, RedisService, Injectable
### Community 22 - "MediaController"
Cohesion: 0.11
Nodes (14): MediaController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+6 more)
Nodes (16): MediaController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+8 more)
### Community 23 - "Ingredient Management Controller"
Cohesion: 0.13
Nodes (14): IngredientsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+6 more)
### Community 24 - "adminRoutes.tsx"
Cohesion: 0.06
Nodes (22): App(), HeroBanner, VetTestimonial, ContactInfoItem, ContactSubmission, CategoryDist, DashboardData, SslStatus (+14 more)
Cohesion: 0.12
Nodes (11): App(), ContactInfoItem, ContactSubmission, CategoryDist, DashboardData, AdminRouteConfig, ContactSubmissions, Dashboard (+3 more)
### Community 25 - "admin.module.ts"
Cohesion: 0.07
Nodes (20): AdminModule, Module, MediaService, Injectable, ReportsController, ApiBearerAuth, ApiOperation, ApiTags (+12 more)
Cohesion: 0.10
Nodes (14): AdminModule, Module, PetQuery, PetsService, Injectable, ReportsController, ApiBearerAuth, ApiOperation (+6 more)
### Community 26 - "Coupons.tsx"
Cohesion: 0.25
Nodes (5): Coupon, CouponFormData, CouponModalProps, CouponTarget, Coupons
### Community 26 - "ConfirmModal.tsx"
Cohesion: 0.08
Nodes (19): ConfirmModal(), ConfirmModalProps, HeroBanner, VetTestimonial, Coupon, CouponFormData, CouponModalProps, CouponTarget (+11 more)
### Community 27 - "Prescription Review Controller"
Cohesion: 0.14
@ -439,45 +438,45 @@ Nodes (14): SmartAdvisorController, ApiBearerAuth, ApiOperation, ApiTags, Body,
Cohesion: 0.13
Nodes (14): TestimonialsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+6 more)
### Community 30 - "src/services/api.ts"
Cohesion: 0.14
Nodes (18): Layout(), ProtectedRoute(), MenuGroup, Sidebar(), SidebarProps, SubMenuItem, SEARCHABLE_PAGES, Topbar() (+10 more)
### Community 30 - "api"
Cohesion: 0.24
Nodes (8): Layout(), menuGroups, Sidebar(), SidebarProps, SEARCHABLE_PAGES, Topbar(), TopbarProps, api
### Community 31 - "Spinner.tsx"
Cohesion: 0.12
Nodes (21): ConfirmModal(), ConfirmModalProps, Media, MediaSelector(), MediaSelectorProps, Pagination(), PaginationProps, Spinner() (+13 more)
Cohesion: 0.09
Nodes (20): Media, MediaSelector(), MediaSelectorProps, Spinner(), BlogPost, Category, BannersManager, Blogs (+12 more)
### Community 32 - "PetProfile.tsx"
Cohesion: 0.15
Nodes (15): BackButton(), BackButtonProps, PetProfile(), SafeImage(), SafeImageProps, CURRENT_SYMPTOMS, MEDICAL_HISTORIES, SmartAdvisor() (+7 more)
Cohesion: 0.08
Nodes (27): metadata, ArchivePage(), ArchiveProductCard(), CATEGORY_MAP, ICON_MAP, FeaturedProducts(), ProductCard(), OrderSuccess() (+19 more)
### Community 33 - "ZibalService"
Cohesion: 0.14
Nodes (4): PaymentService, Injectable, Injectable, ZibalService
Cohesion: 0.05
Nodes (41): InitiatePaymentDto, InitiateWalletTopupDto, ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString, ApiPropertyOptional (+33 more)
### Community 34 - "main.ts"
Cohesion: 0.14
Nodes (9): CustomHttpExceptionFilter, Catch, PrismaExceptionFilter, Catch, DecimalInterceptor, Injectable, ApiErrorResponse, ErrorDetailField (+1 more)
### Community 35 - "admin.ts"
Cohesion: 0.08
Nodes (20): B2BManager, FinancialSettingsPage, PrescriptionsManager, SeoSettingsPage, SmartAdvisorManager, SystemSettingsPage, AdminLoginPayload, B2BInquiry (+12 more)
### Community 35 - "src/services/api.ts"
Cohesion: 0.12
Nodes (20): ProtectedRoute(), Login(), B2BManager, FinancialSettingsPage, SmartAdvisorManager, ApiErrorPayload, failedQueue, useAdminAuthStore (+12 more)
### Community 36 - "compilerOptions"
Cohesion: 0.06
Nodes (31): compilerOptions, allowSyntheticDefaultImports, baseUrl, declaration, emitDecoratorMetadata, esModuleInterop, experimentalDecorators, forceConsistentCasingInFileNames (+23 more)
### Community 36 - "Backend TypeScript Config"
Cohesion: 0.09
Nodes (22): compilerOptions, allowSyntheticDefaultImports, baseUrl, declaration, emitDecoratorMetadata, esModuleInterop, experimentalDecorators, forceConsistentCasingInFileNames (+14 more)
### Community 37 - "compilerOptions"
### Community 37 - "App TypeScript Config"
Cohesion: 0.09
Nodes (22): compilerOptions, allowImportingTsExtensions, erasableSyntaxOnly, jsx, lib, module, moduleDetection, moduleResolution (+14 more)
### Community 38 - "Transactions.tsx"
Cohesion: 0.33
Nodes (4): GatewayHealth, Stats, Transaction, Transactions
### Community 38 - "Pagination.tsx"
Cohesion: 0.11
Nodes (13): Pagination(), PaginationProps, Media, Pet, GatewayHealth, Stats, Transaction, ProductItem (+5 more)
### Community 39 - "dependencies"
Cohesion: 0.05
Nodes (41): dependencies, bcrypt, bcryptjs, class-transformer, class-validator, helmet, ioredis, js-yaml (+33 more)
Cohesion: 0.10
Nodes (21): dependencies, bcrypt, class-transformer, class-validator, ioredis, @nestjs/common, @nestjs/passport, @nestjs/platform-express (+13 more)
### Community 40 - "Node TypeScript Config"
Cohesion: 0.10
@ -487,13 +486,13 @@ Nodes (20): compilerOptions, allowImportingTsExtensions, erasableSyntaxOnly, lib
Cohesion: 0.13
Nodes (15): BlogsController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete (+7 more)
### Community 42 - "WholesaleApplyDto"
Cohesion: 0.10
Nodes (20): ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString, WholesaleApplyDto, ApiBearerAuth, ApiOperation (+12 more)
### Community 42 - "WholesaleService"
Cohesion: 0.14
Nodes (14): ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Get, Param, Post (+6 more)
### Community 43 - "devDependencies"
Cohesion: 0.13
Nodes (15): eslint-config-next, devDependencies, eslint, eslint-config-next, jsdom, tailwindcss, @tailwindcss/postcss, @types/node (+7 more)
Nodes (15): devDependencies, eslint, jsdom, tailwindcss, @tailwindcss/postcss, @types/node, @types/react-dom, @vitejs/plugin-react (+7 more)
### Community 44 - "Generic CRUD Controller"
Cohesion: 0.13
@ -504,8 +503,8 @@ Cohesion: 0.16
Nodes (10): SeoController, ApiOperation, ApiTags, Controller, Get, Param, SeoModule, Module (+2 more)
### Community 46 - "PaginationDto"
Cohesion: 0.07
Nodes (24): CategoryQuery, BlogsModule, Module, BlogsService, Injectable, PaginationDto, SortOrder, ApiPropertyOptional (+16 more)
Cohesion: 0.14
Nodes (13): BlogsModule, Module, BlogsService, Injectable, PaginationDto, SortOrder, ApiPropertyOptional, IsEnum (+5 more)
### Community 47 - "Project Build Scripts"
Cohesion: 0.11
@ -531,52 +530,52 @@ Nodes (14): "coupons", "health_logs", "order_items", "orders", "pet_medical_cond
Cohesion: 0.10
Nodes (21): dependencies, axios, lucide-react, react, react-dom, react-hot-toast, react-router-dom, recharts (+13 more)
### Community 53 - "Orders.tsx"
Cohesion: 0.16
Nodes (13): Skeleton(), getPaymentMethodLabel(), Order, OrderItem, Orders(), PaymentTx, statusStyles, toPersianDigits() (+5 more)
### Community 53 - "UI Skeleton and Tables"
Cohesion: 0.18
Nodes (11): Skeleton(), Order, OrderItem, Orders(), statusStyles, toPersianDigits(), UserRecord, Orders (+3 more)
### Community 54 - "auth.service.ts"
Cohesion: 0.12
Nodes (11): AdminLoginInput, AuthService, LoginInput, RegisterInput, Injectable, SendOtpDto, ApiProperty, IsNotEmpty (+3 more)
Cohesion: 0.16
Nodes (7): AuthModule, Module, AdminLoginInput, AuthService, LoginInput, RegisterInput, Injectable
### Community 55 - "BE-001"
Cohesion: 0.06
Nodes (32): Acceptance Criteria, Affected Application, Affected Files, Alternative Direction, BE-001, Category, Completion Statement, Confidence (+24 more)
### Community 56 - "VetGallery.tsx"
Cohesion: 0.22
Nodes (8): DisplayVideoItem, FALLBACK_VIDEOS, TestimonialItem, VetGallery(), FALLBACK_VIDEOS, VideosPage(), Video, videoService
### Community 56 - "SafeImage.tsx"
Cohesion: 0.17
Nodes (10): ProductDetailModalProps, SafeImage(), SafeImageProps, DisplayVideoItem, FALLBACK_VIDEOS, TestimonialItem, FALLBACK_VIDEOS, VideosPage() (+2 more)
### Community 57 - "NPM Lifecycle Scripts"
Cohesion: 0.14
Nodes (14): scripts, build, docs:generate, format, lint, start, start:debug, start:dev (+6 more)
### Community 58 - "PetsController"
Cohesion: 0.10
Nodes (14): PetsController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Controller, Delete, Get (+6 more)
### Community 58 - "Pet Management API"
Cohesion: 0.15
Nodes (11): PetsController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Controller, Delete, Get (+3 more)
### Community 59 - "PrismaService"
Cohesion: 0.08
Nodes (14): B2BWholesaleOrderItem, MeliPayamakPattern, MeliPayamakResponse, SendPatternSmsOptions, SmsConfig, SmsLogQuery, CreateContactSubmissionDto, UpdateContactInfoItemDto (+6 more)
Cohesion: 0.06
Nodes (21): BlogQuery, Injectable, WikiQuery, WikiService, MeliPayamakPattern, MeliPayamakResponse, SendPatternSmsOptions, SmsConfig (+13 more)
### Community 60 - "Jest Testing Config"
Cohesion: 0.15
Nodes (13): jest, collectCoverageFrom, coverageDirectory, moduleFileExtensions, rootDir, testEnvironment, testRegex, transform (+5 more)
### Community 61 - "CreateReviewDto"
Cohesion: 0.07
Nodes (29): CreateReviewDto, ApiProperty, IsInt, IsNotEmpty, IsOptional, IsString, Min, ApiProperty (+21 more)
### Community 61 - "WikiController"
Cohesion: 0.20
Nodes (7): ApiTags, Controller, WikiController, Module, WikiModule, Injectable, WikiService
### Community 62 - "BannersService"
Cohesion: 0.13
Nodes (15): BannersController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+7 more)
### Community 62 - "videos.controller.ts"
Cohesion: 0.22
Nodes (7): GetVideosQueryDto, ApiPropertyOptional, IsBoolean, IsOptional, Module, VideosModule, Transform
### Community 63 - "FE-001"
Cohesion: 0.06
Nodes (31): Acceptance Criteria, Affected Application, Affected Files, Alternative Direction, Architecture Overview & Confirmed Strengths, Category, Completion Statement, Confidence (+23 more)
### Community 64 - "VerifyOtpDto"
Cohesion: 0.29
Cohesion: 0.22
Nodes (6): ApiProperty, IsNotEmpty, IsString, Matches, VerifyOtpDto, Length
### Community 66 - "App Health Controller"
@ -588,8 +587,8 @@ Cohesion: 0.12
Nodes (17): dependencies, axios, lucide-react, motion, next, react, react-dom, sonner (+9 more)
### Community 68 - "OrdersService"
Cohesion: 0.07
Nodes (29): CreateOrderDto, OrderItemDto, ApiProperty, ApiPropertyOptional, IsArray, IsNotEmpty, IsNumber, IsOptional (+21 more)
Cohesion: 0.06
Nodes (35): CreateOrderDto, OrderItemDto, ApiProperty, ApiPropertyOptional, IsArray, IsNotEmpty, IsNumber, IsOptional (+27 more)
### Community 69 - "Integrity Validation Scripts"
Cohesion: 0.20
@ -611,9 +610,9 @@ Nodes (8): cmd_next_task(), cmd_progress(), cmd_reset(), cmd_status(), cmd_unblo
Cohesion: 0.22
Nodes (8): author, description, license, name, prisma, seed, private, version
### Community 74 - "useSettingsStore"
Cohesion: 0.15
Nodes (14): metadata, BrandLogo(), BrandLogoProps, EnamadBadge(), Footer(), MENU_ICONS, MaintenancePage(), TickerBanner() (+6 more)
### Community 74 - "RegisterDto"
Cohesion: 0.22
Nodes (8): RegisterDto, ApiProperty, ApiPropertyOptional, IsEmail, IsNotEmpty, IsOptional, IsString, MinLength
### Community 75 - "React Error Boundary"
Cohesion: 0.22
@ -623,9 +622,9 @@ Nodes (3): ErrorBoundary, Props, State
Cohesion: 0.22
Nodes (8): name, private, scripts, build, dev, lint, start, version
### Community 77 - "WikiController"
Cohesion: 0.21
Nodes (9): ApiNotFoundResponse, ApiOkResponse, ApiOperation, ApiTags, Controller, Get, Param, Query (+1 more)
### Community 77 - ".findAll"
Cohesion: 0.32
Nodes (6): ApiNotFoundResponse, ApiOkResponse, ApiOperation, Get, Param, Query
### Community 79 - "VPN Utility Scripts"
Cohesion: 0.62
@ -667,25 +666,25 @@ Nodes (4): evidenceList, ledger, mandatoryMap, mandatoryScope
Cohesion: 0.40
Nodes (4): activeFiles, errors, validationOutput, warnings
### Community 90 - "SslController"
Cohesion: 0.13
Nodes (14): SslController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Get, Post (+6 more)
### Community 90 - "CreateVideoDto"
Cohesion: 0.25
Nodes (8): CreateVideoDto, ApiProperty, ApiPropertyOptional, IsBoolean, IsNotEmpty, IsOptional, IsString, UpdateVideoDto
### Community 91 - "Blog Post Detail Page"
Cohesion: 0.60
Nodes (4): BlogPostPage(), generateMetadata(), getBlog(), revalidate
### Community 93 - "devDependencies"
Cohesion: 0.09
Nodes (23): devDependencies, eslint, @eslint/eslintrc, @eslint/js, jest, @nestjs/schematics, @nestjs/testing, source-map-support (+15 more)
Cohesion: 0.22
Nodes (9): devDependencies, @eslint/eslintrc, @types/passport-jwt, @types/supertest, typescript, typescript, @eslint/eslintrc, @types/passport-jwt (+1 more)
### Community 99 - "Wiki Page Routing"
Cohesion: 0.83
Nodes (3): generateMetadata(), getWikiTerm(), WikiTermPage()
### Community 100 - "toPersian"
Cohesion: 0.11
Nodes (21): ClientLayout(), VerifyContent(), AuthModal(), AuthModalProps, extractOtpFromText(), B2BPortal(), CartDrawer(), HeaderButton() (+13 more)
### Community 100 - "ClientLayout.tsx"
Cohesion: 0.17
Nodes (8): ClientLayout(), Footer(), LoginModal(), LoginModalProps, MaintenancePage(), NetworkBanner(), useNetworkStatus(), NavigationTarget
### Community 101 - "Docker Deployment Scripts"
Cohesion: 0.50
@ -695,17 +694,13 @@ Nodes (3): COMPOSE_DOCKER_CLI_BUILD, DOCKER_BUILDKIT, deploy.sh script
Cohesion: 0.06
Nodes (31): Acceptance Criteria, Affected Application, Affected Files, Alternative Direction, Category, Completion Statement, Confidence, Database and Data Integrity Audit Report (+23 more)
### Community 103 - "BlogsService"
Cohesion: 0.22
Nodes (3): BlogQuery, BlogsService, Injectable
### Community 109 - "Auth Architecture and Planning"
Cohesion: 0.67
Nodes (3): ADR-AUTH-001: Admin Panel Authentication Architecture & Token Management, Master Task Backlog (Phase 3.3), Phase 3 Master Execution Plan
### Community 117 - "PaymentController"
### Community 117 - "SendOtpDto"
Cohesion: 0.25
Nodes (13): PaymentController, ApiBadRequestResponse, ApiBearerAuth, ApiOkResponse, ApiOperation, ApiTags, Body, Controller (+5 more)
Nodes (5): SendOtpDto, ApiProperty, IsNotEmpty, IsString, Matches
### Community 118 - "TS-001"
Cohesion: 0.06
@ -715,13 +710,13 @@ Nodes (31): Acceptance Criteria, Affected Application, Affected Files, Alternati
Cohesion: 0.06
Nodes (31): Acceptance Criteria, Affected Application, Affected Files, Alternative Direction, Category, Completion Statement, Confidence, Dependencies (+23 more)
### Community 120 - "AdminTransactionFilterDto"
Cohesion: 0.25
Nodes (7): AdminTransactionFilterDto, ApiPropertyOptional, IsIn, IsNumber, IsOptional, IsString, Type
### Community 120 - "payment.service.ts"
Cohesion: 0.22
Nodes (8): AdminTransactionFilterDto, ApiPropertyOptional, IsNumber, IsOptional, IsString, Type, ClientMetadata, IsIn
### Community 121 - "UITexts.tsx"
Cohesion: 0.29
Nodes (5): GROUP_PAGE_MAP, GROUPS, KEY_LABELS_FA, PAGE_TABS, UITexts
Cohesion: 0.33
Nodes (4): GROUP_PAGE_MAP, GROUPS, PAGE_TABS, UITexts
### Community 126 - "Typography and Font Assets"
Cohesion: 0.67
@ -740,7 +735,7 @@ Cohesion: 0.07
Nodes (26): For /graphify add and --watch, For /graphify query, For the commit hook and native CLAUDE.md integration, For --update and --cluster-only, /graphify, Honesty Rules, Interpreter guard for subcommands, Part A - Structural extraction for code files (+18 more)
### Community 130 - "JwtAuthGuard"
Cohesion: 0.18
Cohesion: 0.19
Nodes (7): JwtAuthGuard, Injectable, ROLES_KEY, RequestWithUser, RolesGuard, Injectable, UserReqPayload
### Community 131 - "راهنمای تست سیستم (Software Testing)"
@ -775,10 +770,6 @@ Nodes (17): 1. Hierarchical Decomposition Algorithm (3-Tier), 2. Sub-step Defini
Cohesion: 0.11
Nodes (17): 1. Always Read Tech Stack First, 2. Sub-step Execution (Token Resume Support), 3. Mandatory Test Authoring, 4. Code Quality Standards, 5. File Scope Boundary, 6. Forbidden Actions, Expected JSON Output Schema, IMPLEMENT MODE — Normal Operation (+9 more)
### Community 142 - ".update"
Cohesion: 0.24
Nodes (11): ApiNotFoundResponse, ApiOkResponse, ApiOperation, Body, Delete, Get, Param, Patch (+3 more)
### Community 157 - "What You Must Do When Invoked"
Cohesion: 0.07
Nodes (26): For /graphify add and --watch, For /graphify query, For the commit hook and native CLAUDE.md integration, For --update and --cluster-only, /graphify, Honesty Rules, Interpreter guard for subcommands, Part A - Structural extraction for code files (+18 more)
@ -871,10 +862,6 @@ Nodes (9): 1. Active Customer Storefront: React 19 + Vite Application, 2. Active
Cohesion: 0.20
Nodes (9): Arch Linux, Contributors, Install, Known problems for variable version, License, Sahel-Font, To Do (variable), طریقه استفاده از نسخه متغیر variable (+1 more)
### Community 207 - "zibal.service.ts"
Cohesion: 0.16
Nodes (9): GatewayHealthResult, IPaymentGateway, PaymentInquiryResult, PaymentRequestOptions, PaymentRequestResult, PaymentVerifyResult, ZibalInquiryResponse, ZibalRequestResponse (+1 more)
### Community 208 - "Sahel-Font"
Cohesion: 0.20
Nodes (9): Arch Linux, Contributors, Install, Known problems for variable version, License, Sahel-Font, To Do (variable), طریقه استفاده از نسخه متغیر variable (+1 more)
@ -885,7 +872,7 @@ Nodes (8): 1. Ask — Don't Assume, 2. Forbidden Actions, Brownfield Detection,
### Community 210 - "exclude"
Cohesion: 0.22
Nodes (8): exclude, extends, dist, node_modules, prisma, **/*spec.ts, test, ./tsconfig.json
Nodes (8): exclude, extends, node_modules, prisma, dist, **/*spec.ts, test, ./tsconfig.json
### Community 211 - "Phase 2 Final Quality Gate Summary Report"
Cohesion: 0.22
@ -979,77 +966,29 @@ Nodes (3): Expanding the ESLint configuration, React Compiler, React + TypeScrip
Cohesion: 0.50
Nodes (3): Deploy on Vercel, Getting Started, Learn More
### Community 237 - "useCartStore"
Cohesion: 0.10
Nodes (16): ArchiveProductCard(), FeaturedProducts(), ProductCard(), Header(), OrderSuccess(), OrderTracking(), mockProduct, mockProducts (+8 more)
### Community 238 - "ProductPage.tsx"
Cohesion: 0.16
Nodes (10): CalculatorState, ICON_MAP, ProductPage(), useCalculatorStore, ProductReviews(), ProductReviewsProps, ReviewItem, SCIENTIFIC_TERMS (+2 more)
### Community 266 - ".handleZibalCallback"
Cohesion: 0.24
Nodes (8): ApiPropertyOptional, IsOptional, IsString, ZibalCallbackQueryDto, Query, Res, Headers, Ip
### Community 268 - "PetsController"
Cohesion: 0.18
Nodes (9): PetsController, ApiBadRequestResponse, ApiBearerAuth, ApiCreatedResponse, ApiTags, Controller, UploadedFile, UseGuards (+1 more)
### Community 269 - "generate-openapi.js"
Cohesion: 0.25
Nodes (6): app_module_1, core_1, fs, path, swagger_1, yaml
### Community 270 - "InitiatePaymentDto"
Cohesion: 0.43
Nodes (7): InitiatePaymentDto, InitiateWalletTopupDto, ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString
### Community 271 - "SmsLogQueryDto"
Cohesion: 0.25
Nodes (7): SmsLogQueryDto, ApiPropertyOptional, IsIn, IsNumber, IsOptional, IsString, Type
### Community 272 - "CreateHealthLogDto"
Cohesion: 0.29
Nodes (6): CreateHealthLogDto, ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString
### Community 275 - "CreateReminderDto"
Cohesion: 0.29
Nodes (6): CreateReminderDto, ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString
### Community 276 - "Reviews.tsx"
Cohesion: 0.50
Nodes (4): ProductReview, Reviews(), toPersianDigits(), Reviews
### Community 277 - "Tickets.tsx"
Cohesion: 0.40
Nodes (3): Ticket, TicketMessage, Tickets
### Community 278 - "Videos.tsx"
Cohesion: 0.50
Nodes (4): fetchVideosList(), Video, Videos(), Videos
### Community 288 - "SmsSettingsPage.tsx"
Cohesion: 0.29
Nodes (5): PatternItem, SmsConfigState, SmsLogItem, SmsLogStats, SmsSettingsPage
## Knowledge Gaps
- **1208 isolated node(s):** `$schema`, `collection`, `sourceRoot`, `deleteOutDir`, `name` (+1203 more)
- **1182 isolated node(s):** `$schema`, `collection`, `sourceRoot`, `deleteOutDir`, `name` (+1177 more)
These have ≤1 connection - possible missing edges or undocumented components.
- **95 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
- **114 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
## Suggested Questions
_Questions this graph is uniquely positioned to answer:_
- **Why does `Roles()` connect `Roles` to `JwtAuthGuard`, `CmsController`, `ContactService`, `WholesaleApplyDto`, `ProductsService`, `tickets.controller.ts`, `Testimonials Management Controller`, `B2BService`, `PaymentController`, `Ingredient Management Controller`, `SslController`, `Prescription Review Controller`, `Smart Advisor Controller`, `CreateReviewDto`, `BannersService`?**
_High betweenness centrality (0.044) - this node is a cross-community bridge._
- **Why does `ApiResponse` connect `src/services/api.ts` to `UsersService`, `OrdersService`, `admin.ts`, `AuthController`, `PetsController`, `WikiController`, `ProductsService`, `Home Data Module`, `BlogsController`?**
_High betweenness centrality (0.039) - this node is a cross-community bridge._
- **Why does `PrismaService` connect `PrismaService` to `AdminService`, `pets/pets.controller.ts`, `JwtAuthGuard`, `UsersService`, `CmsController`, `SmsService`, `app.module.ts`, `CreateVideoDto`, `ContactService`, `PetsService`, `SettingsService`, `ProductsService`, `tickets.controller.ts`, `MetricsController`, `B2BService`, `CategoriesController`, `RedisService`, `Ingredient Management Controller`, `admin.module.ts`, `Prescription Review Controller`, `Smart Advisor Controller`, `Testimonials Management Controller`, `ZibalService`, `WholesaleApplyDto`, `seo.module.ts`, `PaginationDto`, `Home Data Module`, `auth.service.ts`, `PetsController`, `CreateReviewDto`, `BannersService`, `OrdersService`, `zibal.service.ts`, `BlogsService`?**
_High betweenness centrality (0.026) - this node is a cross-community bridge._
- **Why does `ApiResponse` connect `src/services/api.ts` to `pets/pets.controller.ts`, `UsersService`, `OrdersService`, `AuthController`, `ProductsService`, `Home Data Module`, `BlogsController`, `WikiController`?**
_High betweenness centrality (0.041) - this node is a cross-community bridge._
- **Why does `Roles()` connect `Roles` to `ZibalService`, `JwtAuthGuard`, `CmsController`, `ContactService`, `WholesaleService`, `ProductsService`, `B2B Inquiry Controller`, `Ingredient Management Controller`, `Prescription Review Controller`, `Smart Advisor Controller`, `Testimonials Management Controller`?**
_High betweenness centrality (0.034) - this node is a cross-community bridge._
- **Why does `PaginationDto` connect `PaginationDto` to `AdminService`, `pets/pets.controller.ts`, `JwtAuthGuard`, `OrdersService`, `Admin Blog Controller`, `Generic CRUD Controller`, `.findAll`, `ProductsService`, `CategoriesController`, `BlogsController`, `admin.module.ts`, `Pet Management API`, `WikiController`, `videos.controller.ts`?**
_High betweenness centrality (0.024) - this node is a cross-community bridge._
- **What connects `$schema`, `collection`, `sourceRoot` to the rest of the system?**
_1208 weakly-connected nodes found - possible documentation gaps or missing edges._
_1182 weakly-connected nodes found - possible documentation gaps or missing edges._
- **Should `AdminService` be split into smaller, more focused modules?**
_Cohesion score 0.05396825396825397 - nodes in this community are weakly interconnected._
- **Should `UsersService` be split into smaller, more focused modules?**
_Cohesion score 0.05547785547785548 - nodes in this community are weakly interconnected._
- **Should `CmsController` be split into smaller, more focused modules?**
_Cohesion score 0.08441558441558442 - nodes in this community are weakly interconnected._
_Cohesion score 0.06414414414414414 - nodes in this community are weakly interconnected._
- **Should `pets/pets.controller.ts` be split into smaller, more focused modules?**
_Cohesion score 0.0528169014084507 - nodes in this community are weakly interconnected._
- **Should `Roles` be split into smaller, more focused modules?**
_Cohesion score 0.06347340581839553 - nodes in this community are weakly interconnected._

View File

@ -1 +0,0 @@
{"nodes": [{"id": "$graphify-root$_docs_readme_md", "label": "README.md", "file_type": "document", "source_file": "docs/README.md", "source_location": "L1"}, {"id": "$graphify-root$_docs_readme_\u0641\u0647\u0631\u0633\u062a_\u0645\u0637\u0627\u0644\u0628_table_of_contents", "label": "\u0641\u0647\u0631\u0633\u062a \u0645\u0637\u0627\u0644\u0628 (Table of Contents)", "file_type": "document", "source_file": "docs/README.md", "source_location": "L7"}], "edges": [{"source": "$graphify-root$_docs_readme_md", "target": "$graphify-root$_docs_readme_\u0641\u0647\u0631\u0633\u062a_\u0645\u0637\u0627\u0644\u0628_table_of_contents", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/README.md", "source_location": "L7", "weight": 1.0}, {"source": "$graphify-root$_docs_readme_md", "target": "$graphify-root$_docs_01_introduction_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "docs/README.md", "source_location": "L11", "weight": 1.0, "target_file": "$graphify-root$/docs/01-introduction.md"}, {"source": "$graphify-root$_docs_readme_md", "target": "$graphify-root$_docs_02_user_guide_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "docs/README.md", "source_location": "L14", "weight": 1.0, "target_file": "$graphify-root$/docs/02-user-guide.md"}, {"source": "$graphify-root$_docs_readme_md", "target": "$graphify-root$_docs_03_developer_guide_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "docs/README.md", "source_location": "L17", "weight": 1.0, "target_file": "$graphify-root$/docs/03-developer-guide.md"}, {"source": "$graphify-root$_docs_readme_md", "target": "$graphify-root$_docs_04_setup_and_deployment_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "docs/README.md", "source_location": "L20", "weight": 1.0, "target_file": "$graphify-root$/docs/04-setup-and-deployment.md"}, {"source": "$graphify-root$_docs_readme_md", "target": "$graphify-root$_docs_05_devops_and_monitoring_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "docs/README.md", "source_location": "L23", "weight": 1.0, "target_file": "$graphify-root$/docs/05-devops-and-monitoring.md"}, {"source": "$graphify-root$_docs_readme_md", "target": "$graphify-root$_docs_06_testing_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "docs/README.md", "source_location": "L26", "weight": 1.0, "target_file": "$graphify-root$/docs/06-testing.md"}], "input_tokens": 0, "output_tokens": 0}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"nodes": [{"id": "docker_backend_prod", "label": "Backend Production Container", "file_type": "code", "source_file": "docker-compose.yml"}], "edges": [{"source": "docker_backend_prod", "target": "database_schema_users", "relation": "references", "confidence": "INFERRED", "confidence_score": 0.8, "source_file": "docker-compose.yml"}], "hyperedges": []}

View File

@ -0,0 +1 @@
{"nodes": [{"id": "docs_audit_frontend_integration_requirements", "label": "Comprehensive Frontend Integration Requirements & API Contract", "file_type": "document", "source_file": "docs/audit/FRONTEND_INTEGRATION_REQUIREMENTS.md", "rationale": "Specifies the API contract, authentication headers, and action items for both Storefront and Admin Panel."}], "edges": [{"source": "docs_audit_frontend_integration_requirements", "target": "docs_audit_cross_boundary_dependencies", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.9, "source_file": "docs/audit/FRONTEND_INTEGRATION_REQUIREMENTS.md"}], "hyperedges": []}

View File

@ -0,0 +1 @@
{"nodes": [{"id": "frontend_application_agents", "label": "Next.js Agent Rules & Brand Guidelines", "file_type": "document", "source_file": "frontend/application/AGENTS.md", "rationale": "Enforces brand naming 'Canina' and specific CSS/cookie naming conventions."}], "edges": [], "hyperedges": []}

View File

@ -0,0 +1 @@
{"nodes": [{"id": "canina_pharma_gmbh", "label": "Canina Pharma GmbH", "file_type": "concept", "source_file": "canina.md", "rationale": "German manufacturer of pharmaceutical-grade pet supplements."}], "edges": [], "hyperedges": []}

View File

@ -0,0 +1 @@
{"nodes": [{"id": "readme_smartadvisor", "label": "Smart Advisor Component", "file_type": "concept", "source_file": "README.md", "rationale": "Clinical screening engine for pet breed and symptoms."}], "edges": [{"source": "readme_smartadvisor", "target": "database_schema_products", "relation": "conceptually_related_to", "confidence": "EXTRACTED", "confidence_score": 0.95, "source_file": "README.md"}], "hyperedges": []}

View File

@ -0,0 +1 @@
{"nodes": [{"id": "scripts_compose_prod", "label": "Production Docker Compose", "file_type": "code", "source_file": "scripts/compose.prod.yml", "rationale": "Defines production infrastructure for the Canina application."}], "edges": [{"source": "scripts_compose_prod", "source_location": "34:5", "target": "scripts_compose_prod", "relation": "calls", "confidence": "INFERRED", "confidence_score": 0.9, "source_file": "scripts/compose.prod.yml", "rationale": "backend_prod depends on db_prod and redis_prod."}], "hyperedges": [{"id": "canina_deployment_stack", "label": "Canina Deployment Stack", "nodes": ["scripts_compose_prod", "scripts_compose_stage"], "relation": "form", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "scripts/compose.prod.yml"}]}

View File

@ -0,0 +1 @@
{"nodes": [{"id": "scripts_compose_stage", "label": "Staging Docker Compose", "file_type": "code", "source_file": "scripts/compose.stage.yml", "rationale": "Defines staging infrastructure for the Canina application."}], "edges": [{"source": "scripts_compose_stage", "source_location": "34:5", "target": "scripts_compose_stage", "relation": "calls", "confidence": "INFERRED", "confidence_score": 0.9, "source_file": "scripts/compose.stage.yml", "rationale": "backend_stage depends on db_stage and redis_stage."}], "hyperedges": []}

View File

@ -0,0 +1 @@
{"nodes": [{"id": "docs_02_user_guide_roles", "label": "User Roles and Capabilities", "file_type": "document", "source_file": "docs/02-user-guide.md", "rationale": "Defines B2C, B2B, and Admin roles and their respective functionalities within the system."}], "edges": [], "hyperedges": []}

View File

@ -0,0 +1 @@
{"nodes": [{"id": "docs_01_introduction_canina_iran", "label": "Canina Iran Project Introduction", "file_type": "document", "source_file": "docs/01-introduction.md", "rationale": "Overview of the Canina Iran platform, its goals, and the technology stack used (Next.js, NestJS, PostgreSQL, Prisma)."}], "edges": [], "hyperedges": []}

View File

@ -0,0 +1 @@
{"nodes": [{"id": "backend_integration_login", "label": "User Login API", "file_type": "code", "source_file": "BACKEND_INTEGRATION.md", "source_location": "2.1.1", "rationale": "Endpoint for user authentication supporting multiple roles."}, {"id": "backend_integration_logout", "label": "User Logout API", "file_type": "code", "source_file": "BACKEND_INTEGRATION.md", "source_location": "2.1.2"}], "edges": [], "hyperedges": []}

View File

@ -0,0 +1 @@
{"nodes": [{"id": "database_schema_users", "label": "Users Table", "file_type": "code", "source_file": "DATABASE_SCHEMA.md", "source_location": "2.1"}, {"id": "database_schema_pets", "label": "Pets Table", "file_type": "code", "source_file": "DATABASE_SCHEMA.md", "source_location": "2.6"}, {"id": "database_schema_products", "label": "Products Table", "file_type": "code", "source_file": "DATABASE_SCHEMA.md", "source_location": "2.4"}], "edges": [], "hyperedges": []}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff