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_desc": "وب‌سایت رسمی کنینا ایران جهت ارتقای زیرساخت‌ها و بهبود عملکرد به صورت موقت در دست به‌روزرسانی است. از شکیبایی شما سپاسگزاریم.",
"maintenance_eta": "زمان تقریبی بازگشایی: کمتر از ۲ ساعت آینده", "maintenance_eta": "زمان تقریبی بازگشایی: کمتر از ۲ ساعت آینده",
"maintenance_contact_phone": "۰۲۱-۸۸۸۸۸۸۸۸", "maintenance_contact_phone": "۰۲۱-۸۸۸۸۸۸۸۸",
"maintenance_contact_phone_link": "tel:02188888888",
"maintenance_badge": "سامانه در حال ارتقا", "maintenance_badge": "سامانه در حال ارتقا",
"catalog_mode": "false", "catalog_mode": "false",
"catalog_hide_prices": "false", "catalog_hide_prices": "false",
"catalog_disable_cart": "false", "catalog_disable_cart": "false",
"catalog_disable_checkout": "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 === // === Header / Navigation ===
"shipping_notice": "ارسال رایگان برای سفارش‌های بالای ۱۵۰ هزار تومان — گارانتی اصالت کنینا آلمان", "shipping_notice": "ارسال رایگان برای سفارش‌های بالای ۱۵۰ هزار تومان — گارانتی اصالت کنینا آلمان",
"brand_name_fa": "کنینا ایران", "brand_name_fa": "کنینا ایران",

View File

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

View File

@ -90,33 +90,13 @@ export class SettingsService implements OnModuleInit {
} }
async updateUiText(key: string, value: string) { 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({ return this.prisma.uiText.upsert({
where: { key }, where: { key },
update: { value: strVal }, update: { value },
create: { key, value: strVal }, 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() { async getScientificTerms() {
return this.prisma.scientificTerm.findMany(); return this.prisma.scientificTerm.findMany();
} }

View File

@ -1,50 +1,63 @@
import { useState, useEffect, useRef } from 'react'; import { useState, useEffect, useRef } from 'react';
import { Link, useLocation } from 'react-router-dom'; import { Link, useLocation } from 'react-router-dom';
import { 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';
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 api from '../services/api'; import api from '../services/api';
interface SubMenuItem { const menuGroups = [
icon: React.ComponentType<{ className?: string }>; {
label: string; title: 'اصلی',
path: string; items: [
badge?: string | number; { icon: LayoutDashboard, label: 'داشبورد', path: '/' },
} { icon: TrendingUp, label: 'گزارشات', path: '/reports' },
]
interface MenuGroup { },
id: string; {
title: string; title: 'فروشگاه و تخصصی',
icon: React.ComponentType<{ className?: string }>; items: [
items: SubMenuItem[]; { 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 { interface SidebarProps {
isOpen: boolean; isOpen: boolean;
@ -56,17 +69,12 @@ export default function Sidebar({ isOpen, setIsOpen }: SidebarProps) {
const [newOrdersCount, setNewOrdersCount] = useState(0); const [newOrdersCount, setNewOrdersCount] = useState(0);
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null); const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
// Auto-close sidebar on mobile navigation
useEffect(() => {
setIsOpen(false);
}, [location.pathname, setIsOpen]);
useEffect(() => { useEffect(() => {
const fetchOrdersCount = async () => { const fetchOrdersCount = async () => {
try { try {
const response = await api.get('/admin/dashboard/stats'); const response = await api.get('/admin/dashboard/stats');
if (response.data?.success) { if (response.data?.success) {
setNewOrdersCount(response.data.data.newOrders || 0); setNewOrdersCount(response.data.data.newOrders);
} }
} catch (err) { } catch (err) {
console.error('Failed to fetch stats in sidebar', 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 ( return (
<> <>
{/* Mobile Backdrop */} {/* Mobile Backdrop */}
@ -189,100 +97,43 @@ export default function Sidebar({ isOpen, setIsOpen }: SidebarProps) {
/> />
)} )}
<aside <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'
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>
>
{/* 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>
</div> </div>
{/* Menu Navigation */} <nav className="flex-1 overflow-y-auto py-4 px-3 space-y-6">
<nav className="flex-1 overflow-y-auto py-3 px-3 space-y-2"> {menuGroups.map((group, idx) => (
{menuGroups.map((group) => { <div key={idx}>
const GroupIcon = group.icon; <h3 className="px-3 mb-2 text-xs font-black text-gray-400 uppercase tracking-wider">{group.title}</h3>
const isExpanded = !!expandedGroups[group.id]; <div className="space-y-1">
const hasActiveChild = group.items.some( {group.items.map((item) => {
(item) => const isActive = location.pathname === item.path || (item.path !== '/' && location.pathname.startsWith(item.path));
location.pathname === item.path || const Icon = item.icon;
(item.path !== '/' && location.pathname.startsWith(item.path))
);
return ( return (
<div key={group.id} className="rounded-2xl overflow-hidden bg-gray-50/50 border border-gray-100"> <Link
{/* Group Accordion Header */} key={item.path}
<button to={item.path}
type="button" className={`flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all font-bold ${isActive
onClick={() => toggleGroup(group.id)} ? 'bg-purple-50 text-purple-600'
className={`w-full flex items-center justify-between px-3 py-2.5 text-xs font-black transition-all ${ : 'text-gray-500 hover:bg-gray-50 hover:text-gray-900'
hasActiveChild ? 'text-purple-700 bg-purple-50/70' : 'text-gray-600 hover:bg-gray-100/60' }`}
}`} >
> <Icon className="w-5 h-5" />
<div className="flex items-center gap-2"> <span>{item.label}</span>
<GroupIcon className={`w-4 h-4 ${hasActiveChild ? 'text-purple-600' : 'text-gray-400'}`} /> {item.label === 'سفارشات' && newOrdersCount > 0 && (
<span>{group.title}</span> <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">
</div> {newOrdersCount}
<ChevronDown </span>
className={`w-4 h-4 transition-transform duration-200 text-gray-400 ${ )}
isExpanded ? 'rotate-180' : '' </Link>
}`} );
/> })}
</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>
)}
</div> </div>
); </div>
})} ))}
</nav> </nav>
</aside> </aside>
</> </>

View File

@ -239,17 +239,7 @@ export default function BannersManager() {
</td> </td>
<td className="py-4 px-6"> <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"> <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_hero' ? 'اسلایدر هیرو اصلی' : banner.position === 'category_top' ? 'بالای دسته‌بندی' : 'سایدبار'}
? 'اسلایدر هیرو اصلی'
: banner.position === 'home_middle'
? 'بنر میانی صفحه نخست'
: banner.position === 'home_bottom'
? 'بنر عریض پایین خانه'
: banner.position === 'shop_top'
? 'بالای فروشگاه و کاتالوگ'
: banner.position === 'category_top'
? 'بالای دسته‌بندی'
: 'سایدبار'}
</span> </span>
</td> </td>
<td className="py-4 px-6 text-gray-500 font-mono text-xs dir-ltr text-right"> <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" 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_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="category_top">بالای دسته‌بندی (category_top)</option>
<option value="product_sidebar">سایدبار صفحات محصول و مقالات (product_sidebar)</option> <option value="sidebar">سایدبار (sidebar)</option>
</select> </select>
</div> </div>

View File

@ -25,16 +25,12 @@ export default function Settings() {
ZIBAL_SANDBOX: 'true', ZIBAL_SANDBOX: 'true',
FRONTEND_URL: 'https://canina.ir', FRONTEND_URL: 'https://canina.ir',
CONTACT_PHONE: '۰۲۱-۸۸۸۸ ۴۴۴۴', CONTACT_PHONE: '۰۲۱-۸۸۸۸ ۴۴۴۴',
CONTACT_PHONE_LINK: 'tel:02188884444',
CONTACT_EMAIL: 'info@canina-iran.com', CONTACT_EMAIL: 'info@canina-iran.com',
SOCIAL_WHATSAPP: '09120000000', SOCIAL_WHATSAPP: '09120000000',
SOCIAL_INSTAGRAM: 'https://instagram.com/canina_iran', SOCIAL_INSTAGRAM: 'https://instagram.com/canina_iran',
SOCIAL_TELEGRAM: 'https://t.me/canina_iran', SOCIAL_TELEGRAM: 'https://t.me/canina_iran',
CONTACT_ADDRESS: 'تهران، جردن، خیابان سعیدی، ساختمان کنینا، طبقه ۵، واحد ۱۹', CONTACT_ADDRESS: 'تهران، جردن، خیابان سعیدی، ساختمان کنینا، طبقه ۵، واحد ۱۹',
BRAND_LOGO_URL: '', BRAND_LOGO_URL: '/logo.png',
BRAND_LOGO_TEXT_EN: 'Canina',
BRAND_LOGO_TEXT_FA: 'ایران',
BRAND_LOGO_SUBTITLE: 'نماینده رسمی CANINA PHARMA GMBH GERMANY',
ENAMAD_CODE: '', ENAMAD_CODE: '',
THEME_PRIMARY_COLOR: '#7c3aed', THEME_PRIMARY_COLOR: '#7c3aed',
BRAND_TYPOGRAPHY: 'vazirmatn', BRAND_TYPOGRAPHY: 'vazirmatn',
@ -66,17 +62,13 @@ export default function Settings() {
ZIBAL_MERCHANT: response.data.data.ZIBAL_MERCHANT || 'zibal', ZIBAL_MERCHANT: response.data.data.ZIBAL_MERCHANT || 'zibal',
ZIBAL_SANDBOX: response.data.data.ZIBAL_SANDBOX || 'true', ZIBAL_SANDBOX: response.data.data.ZIBAL_SANDBOX || 'true',
FRONTEND_URL: response.data.data.FRONTEND_URL || 'https://canina.ir', FRONTEND_URL: response.data.data.FRONTEND_URL || 'https://canina.ir',
CONTACT_PHONE: response.data.data.CONTACT_PHONE || response.data.data.contact_phone || '۰۲۱-۸۸۸۸ ۴۴۴۴', 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 || 'info@canina-iran.com',
CONTACT_EMAIL: response.data.data.CONTACT_EMAIL || response.data.data.contact_email || 'info@canina-iran.com', SOCIAL_WHATSAPP: response.data.data.SOCIAL_WHATSAPP || '09120000000',
SOCIAL_WHATSAPP: response.data.data.SOCIAL_WHATSAPP || response.data.data.contact_whatsapp || '09120000000', SOCIAL_INSTAGRAM: response.data.data.SOCIAL_INSTAGRAM || 'https://instagram.com/canina_iran',
SOCIAL_INSTAGRAM: response.data.data.SOCIAL_INSTAGRAM || response.data.data.contact_instagram || 'https://instagram.com/canina_iran', SOCIAL_TELEGRAM: response.data.data.SOCIAL_TELEGRAM || 'https://t.me/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 || 'تهران، جردن، خیابان سعیدی، ساختمان کنینا، طبقه ۵، واحد ۱۹',
CONTACT_ADDRESS: response.data.data.CONTACT_ADDRESS || response.data.data.contact_address || 'تهران، جردن، خیابان سعیدی، ساختمان کنینا، طبقه ۵، واحد ۱۹', BRAND_LOGO_URL: response.data.data.BRAND_LOGO_URL || '/logo.png',
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',
ENAMAD_CODE: response.data.data.ENAMAD_CODE || '', ENAMAD_CODE: response.data.data.ENAMAD_CODE || '',
THEME_PRIMARY_COLOR: response.data.data.THEME_PRIMARY_COLOR || '#7c3aed', THEME_PRIMARY_COLOR: response.data.data.THEME_PRIMARY_COLOR || '#7c3aed',
BRAND_TYPOGRAPHY: response.data.data.BRAND_TYPOGRAPHY || 'vazirmatn', 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 className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div> <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 <input
type="text" type="text"
value={settings.CONTACT_PHONE} value={settings.CONTACT_PHONE}
onChange={(e) => setSettings({ ...settings, CONTACT_PHONE: e.target.value })} 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" 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" dir="ltr"
/> />
</div> </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> <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 <input
@ -432,14 +410,14 @@ export default function Settings() {
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div> <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"> <div className="flex gap-2">
<input <input
type="text" type="text"
value={settings.BRAND_LOGO_URL} value={settings.BRAND_LOGO_URL}
onChange={(e) => setSettings({ ...settings, BRAND_LOGO_URL: e.target.value })} 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" 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" dir="ltr"
/> />
{settings.BRAND_LOGO_URL && ( {settings.BRAND_LOGO_URL && (
@ -448,41 +426,7 @@ export default function Settings() {
</div> </div>
)} )}
</div> </div>
<p className="text-[10px] text-gray-400 mt-1">در صورت خالی بودن، لوگوی متنی زیر در هدر، فوتر و صفحه تعمیرات استفاده خواهد شد.</p> <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"
/>
</div> </div>
<div className="md:col-span-2"> <div className="md:col-span-2">

View File

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

View File

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

View File

@ -28,8 +28,6 @@ import {
import { useCartStore } from "../lib/store/cartStore"; import { useCartStore } from "../lib/store/cartStore";
import { useSettingsStore } from "../lib/store/settingsStore"; import { useSettingsStore } from "../lib/store/settingsStore";
import SafeImage from "./SafeImage"; import SafeImage from "./SafeImage";
import BannerPlacement from "./BannerPlacement";
import { Banner } from "../lib/types";
const ICON_MAP: Record<string, React.ReactNode> = { const ICON_MAP: Record<string, React.ReactNode> = {
joints: <HeartPulse className="w-4 h-4" />, joints: <HeartPulse className="w-4 h-4" />,
@ -250,15 +248,11 @@ export default function ArchivePage({
const [symptomSearch, setSymptomSearch] = useState(""); const [symptomSearch, setSymptomSearch] = useState("");
const [categories, setCategories] = useState<{ id: string; label: string; icon: React.ReactNode }[]>([]); const [categories, setCategories] = useState<{ id: string; label: string; icon: React.ReactNode }[]>([]);
const [symptoms, setSymptoms] = useState<string[]>([]); const [symptoms, setSymptoms] = useState<string[]>([]);
const [banners, setBanners] = useState<Banner[]>([]);
useEffect(() => { useEffect(() => {
const loadFiltersAndBanners = async () => { const loadFilters = async () => {
try { try {
const [filters, bannerData] = await Promise.all([ const filters = await productService.getActiveFilters();
productService.getActiveFilters(),
productService.getBanners()
]);
const mapped = [ const mapped = [
{ id: "all", label: "همه محصولات", icon: <Activity className="w-4 h-4" /> }, { id: "all", label: "همه محصولات", icon: <Activity className="w-4 h-4" /> },
...filters.categories.map(c => ({ ...filters.categories.map(c => ({
@ -269,14 +263,11 @@ export default function ArchivePage({
]; ];
setCategories(mapped); setCategories(mapped);
setSymptoms(filters.symptoms); setSymptoms(filters.symptoms);
if (Array.isArray(bannerData)) {
setBanners(bannerData);
}
} catch (e) { } 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(() => { const CATEGORY_LABELS = useMemo(() => {
@ -425,9 +416,6 @@ export default function ArchivePage({
</button> </button>
</div> </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"> <div className="flex flex-col lg:flex-row gap-8">
{/* Mobile Overlay Backdrop */} {/* 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> </p>
<div className="flex flex-col sm:flex-row gap-3 pt-2"> <div className="flex flex-col sm:flex-row gap-3 pt-2">
<a <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" 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" /> <Phone className="w-4 h-4" />
<span>تماس با پشتیبانی ({getText('contact_phone', '۰۲۱-۸۸۸۸۴۴۴۴')})</span> <span>تماس با پشتیبانی</span>
</a> </a>
<button <button
onClick={() => router.push('/shop')} onClick={() => router.push('/shop')}

View File

@ -3,7 +3,6 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { Phone, Mail, MapPin, Instagram, ShieldCheck, Globe, ArrowUp, Download } from "lucide-react"; import { Phone, Mail, MapPin, Instagram, ShieldCheck, Globe, ArrowUp, Download } from "lucide-react";
import { useSettingsStore } from "../lib/store/settingsStore"; import { useSettingsStore } from "../lib/store/settingsStore";
import BrandLogo from "./BrandLogo";
import Link from "next/link"; import Link from "next/link";
import { NavigationTarget } from "../lib/types"; import { NavigationTarget } from "../lib/types";
@ -54,21 +53,22 @@ export default function Footer({
{/* Brand Presence */} {/* Brand Presence */}
<div className="space-y-8"> <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"> <p className="text-sm text-slate-300 leading-relaxed font-medium">
{getText('footer_brand_desc', "تدارک هوشمندانه سلامت برای همراهان وفادار شما. واردکننده انحصاری مکمل‌های درمانی با گرید دارویی اختصاصی از آلمان با سابقه ۴۰ سال نوآوری.")} {getText('footer_brand_desc', "تدارک هوشمندانه سلامت برای همراهان وفادار شما. واردکننده انحصاری مکمل‌های درمانی با گرید دارویی اختصاصی از آلمان با سابقه ۴۰ سال نوآوری.")}
</p> </p>
<div className="flex gap-4"> <div className="flex gap-4">
{getText('contact_instagram', '') && ( {[Instagram, Globe].map((Icon, i) => (
<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"> <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">
<Instagram className="w-5 h-5" /> <Icon className="w-5 h-5" />
</a> </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>
)}
</div> </div>
</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> <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="grid md:grid-cols-2 gap-8">
<div className="space-y-6"> <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"> <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" /> <Phone className="w-6 h-6" />
</div> </div>
<div> <div>
<p className="text-[11px] text-slate-400 font-extrabold uppercase mb-1">{getText('footer_phone_label', "خط ویژه فروش")}</p> <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> </div>
</a> </div>
<a href={`mailto:${getText('contact_email', 'info@canina.ir')}`} 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"> <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" /> <Mail className="w-6 h-6" />
</div> </div>
<div> <div>
<p className="text-[11px] text-slate-400 font-extrabold uppercase mb-1">{getText('footer_email_label', "مکاتبات رسمی")}</p> <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> </div>
</a> </div>
</div> </div>
<div className="flex gap-4 group"> <div className="flex gap-4 group">
@ -158,7 +158,7 @@ export default function Footer({
</div> </div>
<div> <div>
<p className="text-[11px] text-slate-400 font-extrabold uppercase mb-1">{getText('footer_address_label', "دفتر مرکزی")}</p> <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> </div>
</div> </div>

View File

@ -12,7 +12,6 @@ import { toast } from "sonner";
import AuthModal from "./AuthModal"; import AuthModal from "./AuthModal";
import PrescriptionUploadModal from "./PrescriptionUploadModal"; import PrescriptionUploadModal from "./PrescriptionUploadModal";
import TickerBanner from "./TickerBanner"; import TickerBanner from "./TickerBanner";
import BrandLogo from "./BrandLogo";
import { cn } from "../lib/utils"; import { cn } from "../lib/utils";
import { productService } from "../lib/services/productService"; 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" />} {isMobileMenuOpen ? <X className="w-6 h-6" /> : <Menu className="w-6 h-6" />}
</button> </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> </div>
{/* Center Search Input (Desktop) */} {/* Center Search Input (Desktop) */}

View File

@ -1,12 +1,10 @@
/* eslint-disable @next/next/no-img-element */ /* eslint-disable @next/next/no-img-element */
"use client"; "use client";
import { motion, AnimatePresence, animate } from "motion/react"; import { motion, animate } from "motion/react";
import { ChevronLeft, ChevronRight, Globe, ShieldCheck, Calendar, ArrowLeft } from "lucide-react"; import { ChevronLeft, Globe, ShieldCheck, Calendar } from "lucide-react";
import { useEffect, useState, useRef } from "react"; import { useEffect, useState } from "react";
import { toPersian } from "../lib/utils"; import { toPersian } from "../lib/utils";
import { useSettingsStore } from "../lib/store/settingsStore"; import { useSettingsStore } from "../lib/store/settingsStore";
import { useRouter } from 'next/navigation';
import { Banner } from "../lib/types";
function StatCounter({ target }: { target: number }) { function StatCounter({ target }: { target: number }) {
const [displayValue, setDisplayValue] = useState(0); const [displayValue, setDisplayValue] = useState(0);
@ -14,7 +12,7 @@ function StatCounter({ target }: { target: number }) {
useEffect(() => { useEffect(() => {
const controls = animate(0, target, { const controls = animate(0, target, {
duration: 3, 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)) onUpdate: (latest) => setDisplayValue(Math.round(latest))
}); });
return controls.stop; 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 }) { export default function Hero({ banners = [], onShopNavigate }: { banners?: Banner[]; onShopNavigate?: () => void }) {
const router = useRouter(); const router = useRouter();
const getText = useSettingsStore((state) => state?.getText || ((_k: string, fb: string) => fb)); const getText = useSettingsStore((state) => state?.getText || ((_k: string, fb: string) => fb));
const isInitialized = useSettingsStore((state) => state?.isInitialized ?? false); const isInitialized = useSettingsStore((state) => state?.isInitialized ?? false);
const [mounted, setMounted] = useState(false); const [mounted, setMounted] = useState(false);
const [currentSlide, setCurrentSlide] = useState(0);
const [isPaused, setIsPaused] = useState(false);
useEffect(() => { useEffect(() => {
setMounted(true); setMounted(true);
}, []); }, []);
const heroBanners = banners.filter(b => b.isActive !== false && b.imageUrl); const defaultTitle = banners.length > 0 && banners[0].title ? banners[0].title : "تخصص آلمانی در خدمت\nسلامت پت‌های خانگی";
const totalSlides = heroBanners.length; const defaultSubtitle = banners.length > 0 && banners[0].subtitle ? banners[0].subtitle : 'از سال ۱۹۸۴، شرکت Canina pharma GmbH در برگیش گلادباخ آلمان، با بهره‌گیری از مواد اولیه طبیعی و فرآیندهای پیشرفته، استاندارد طلایی مکمل‌های دامپزشکی را تعریف می‌کند. کنینا نماینده رسمی این برند در ایران است.';
// 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 title = (mounted && isInitialized ? getText('hero_title', '') : '') || defaultTitle; const title = (mounted && isInitialized ? getText('hero_title', '') : '') || defaultTitle;
const subtitle = (mounted && isInitialized ? getText('hero_desc', '') : '') || defaultSubtitle; const subtitle = (mounted && isInitialized ? getText('hero_desc', '') : '') || defaultSubtitle;
const titleParts = title.split('\n'); 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 ( 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 */} {/* 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 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" /> <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> </motion.div>
</div> </div>
{/* Visual Element & Interactive Slider */} {/* Visual Element */}
<div <div className="lg:w-1/2 relative">
className="lg:w-1/2 relative w-full max-w-lg lg:max-w-none mx-auto"
onMouseEnter={() => setIsPaused(true)}
onMouseLeave={() => setIsPaused(false)}
>
<motion.div <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 }} animate={{ opacity: 1, scale: 1, rotate: 0 }}
transition={{ duration: 0.8, ease: "easeOut" }} transition={{ duration: 1, ease: "easeOut" }}
className="relative z-10" 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"> <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">
<AnimatePresence mode="wait"> <img
<motion.img src={(mounted && isInitialized ? getText('hero_image_url', '') : '') || (banners.length > 0 && banners[0].imageUrl ? banners[0].imageUrl : "/assets/images/hero-section-image.png")}
key={activeImageUrl} alt={banners.length > 0 ? banners[0].title : "Canina Pharma Germany"}
src={activeImageUrl} className="w-full h-full object-cover"
alt={activeBanner?.title || "Canina Pharma Germany"} loading="eager"
initial={{ opacity: 0, scale: 1.05 }} referrerPolicy="no-referrer"
animate={{ opacity: 1, scale: 1 }} />
exit={{ opacity: 0, scale: 0.98 }} <div className="absolute bottom-0 left-0 right-0 p-8 bg-gradient-to-t from-black/80 to-transparent text-white text-right">
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="text-sm font-medium mb-1 opacity-80 uppercase tracking-widest text-[10px]"> <div className="text-sm font-medium mb-1 opacity-80 uppercase tracking-widest text-[10px]">
{mounted && isInitialized ? getText('hero_image_badge', "سرآمد علمی در پزشکی پت‌ها") : "سرآمد علمی در پزشکی پت‌ها"} {mounted && isInitialized ? getText('hero_image_badge', "سرآمد علمی در پزشکی پت‌ها") : "سرآمد علمی در پزشکی پت‌ها"}
</div> </div>
<div className="text-lg sm:text-xl font-bold italic tracking-tighter"> <div className="text-xl font-bold italic tracking-tighter">
{activeBanner?.title || (mounted && isInitialized ? getText('hero_image_title', "مکمل‌های تایید شده دامپزشکی با گواهی IFS") : "مکمل‌های تایید شده دامپزشکی با گواهی IFS")} {mounted && isInitialized ? getText('hero_image_title', "مکمل‌های تایید شده دامپزشکی با گواهی IFS") : "مکمل‌های تایید شده دامپزشکی با گواهی IFS"}
</div> </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> </div>
{/* Slider Navigation Arrows (shown if multiple slides exist) */} {/* Quality Badge inside the image container to prevent text collision */}
{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 */}
<motion.div <motion.div
animate={{ rotate: 360 }} animate={{ rotate: 360 }}
transition={{ duration: 20, repeat: Infinity, ease: "linear" }} 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"> <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-xl lg:text-2xl 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"> <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', "استاندارد کیفی آلمان") : "استاندارد کیفی آلمان"} {mounted && isInitialized ? getText('hero_quality_standard', "استاندارد کیفی آلمان (IFS & HACCP)") : "استاندارد کیفی آلمان (IFS & HACCP)"}
</span> </span>
</div> </div>
</motion.div> </motion.div>
{/* 3D Action Badge */} {/* 3D Action Badge clarifying the 3D mark */}
<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"> <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 className="text-sm">✨</span>
<span>فرمول ۳کاره</span> <span>فرمول ۳کاره (تاثیر سه‌بعدی)</span>
</div> </div>
</div> </div>
</motion.div> </motion.div>
</div> </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"> <div className="max-w-7xl mx-auto px-2 sm:px-4 relative">
<motion.div <motion.div
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}

View File

@ -2,7 +2,6 @@
import React from "react"; import React from "react";
import { Wrench, Phone, ShieldCheck, Clock } from "lucide-react"; import { Wrench, Phone, ShieldCheck, Clock } from "lucide-react";
import { useSettingsStore } from "../lib/store/settingsStore"; import { useSettingsStore } from "../lib/store/settingsStore";
import BrandLogo from "./BrandLogo";
export default function MaintenancePage() { export default function MaintenancePage() {
const getText = useSettingsStore((state) => state.getText); const getText = useSettingsStore((state) => state.getText);
@ -14,8 +13,8 @@ export default function MaintenancePage() {
); );
const badge = getText("maintenance_badge", "سامانه در حال به‌روزرسانی و ارتقا"); const badge = getText("maintenance_badge", "سامانه در حال به‌روزرسانی و ارتقا");
const eta = getText("maintenance_eta", "زمان تقریبی بازگشایی: کمتر از ۲ ساعت آینده"); const eta = getText("maintenance_eta", "زمان تقریبی بازگشایی: کمتر از ۲ ساعت آینده");
const phone = getText("contact_phone", getText("maintenance_contact_phone", "۰۲۱-۸۸۸۸۴۴۴۴")); const phone = getText("maintenance_contact_phone", "۰۲۱-۸۸۸۸۸۸۸۸");
const phoneLink = getText("contact_phone_link", getText("maintenance_contact_phone_link", `tel:${phone.replace(/[^0-9+]/g, "") || "02188884444"}`)); const rawPhone = phone.replace(/[^0-9]/g, "");
return ( 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"> <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="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"> <div className="relative z-10 max-w-xl mx-auto space-y-6">
{/* Brand Logo */} <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">
<div className="flex justify-center mb-2"> <Wrench className="w-12 h-12 animate-bounce" />
<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> </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"> <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"> <div className="pt-2 flex flex-col sm:flex-row items-center justify-center gap-4">
<a <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" 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" /> <Phone className="w-4 h-4" />

View File

@ -693,11 +693,11 @@ export default function ProductPage({ productSlug }: { productSlug: string }) {
{isCartDisabled ? ( {isCartDisabled ? (
<div className="space-y-3"> <div className="space-y-3">
<a <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" 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" /> <Phone className="w-5 h-5" />
<span>مشاوره و استعلام خرید ({getText('contact_phone', '۰۲۱-۸۸۸۸۴۴۴۴')})</span> <span>مشاوره و استعلام خرید</span>
</a> </a>
<p className="text-[11px] text-medical-gray-400 font-bold text-center"> <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]"> <div className="flex items-center gap-2 flex-1 max-w-[240px]">
{isCartDisabled ? ( {isCartDisabled ? (
<a <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" 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" /> <Phone className="w-4 h-4" />

View File

@ -251,16 +251,6 @@ export class ProductService {
return []; 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(); export const productService = ProductService.getInstance();

View File

@ -54,34 +54,17 @@ export const useSettingsStore = create<SettingsStore>()((set, get) => ({
} }
}, },
getText: (key, fallback) => { getText: (key, fallback) => {
const texts = get().texts; const value = get().texts[key];
if (!texts) return fallback; if (value === undefined || value === null) {
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;
return fallback; return fallback;
} }
return value; return value;
}, },
getBoolean: (key, fallback) => { getBoolean: (key, fallback) => {
const texts = get().texts; const value = get().texts[key];
if (!texts) return fallback; if (value === undefined || value === null) {
const value =
texts[key] ??
texts[key.toLowerCase()] ??
texts[key.toUpperCase()];
if (value === undefined || value === null || value === '') {
return fallback; 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", "5": "SmsService",
"6": "AuthController", "6": "AuthController",
"7": "app.module.ts", "7": "app.module.ts",
"8": "CreateVideoDto", "8": "VideosService",
"9": "ProductService", "9": "products.ts",
"10": "ContactService", "10": "ContactService",
"11": "compilerOptions", "11": "compilerOptions",
"12": "UserDashboard.tsx", "12": "toPersian",
"13": "auth.controller.ts", "13": "auth.controller.ts",
"14": "ProductsService", "14": "ProductsService",
"15": "lib/services/api.ts", "15": "useCartStore",
"16": "tickets.controller.ts", "16": "eslint",
"17": "ArchivePage.tsx", "17": "useSettingsStore",
"18": "MetricsController", "18": "MetricsController",
"19": "B2BService", "19": "B2B Inquiry Controller",
"20": "CategoriesController", "20": "CategoriesController",
"21": "RedisService", "21": "RedisService",
"22": "MediaController", "22": "MediaController",
"23": "Ingredient Management Controller", "23": "Ingredient Management Controller",
"24": "adminRoutes.tsx", "24": "adminRoutes.tsx",
"25": "admin.module.ts", "25": "admin.module.ts",
"26": "Coupons.tsx", "26": "ConfirmModal.tsx",
"27": "Prescription Review Controller", "27": "Prescription Review Controller",
"28": "Smart Advisor Controller", "28": "Smart Advisor Controller",
"29": "Testimonials Management Controller", "29": "Testimonials Management Controller",
"30": "src/services/api.ts", "30": "api",
"31": "Spinner.tsx", "31": "Spinner.tsx",
"32": "PetProfile.tsx", "32": "PetProfile.tsx",
"33": "ZibalService", "33": "ZibalService",
"34": "main.ts", "34": "main.ts",
"35": "admin.ts", "35": "src/services/api.ts",
"36": "compilerOptions", "36": "Backend TypeScript Config",
"37": "compilerOptions", "37": "App TypeScript Config",
"38": "Transactions.tsx", "38": "Pagination.tsx",
"39": "dependencies", "39": "dependencies",
"40": "Node TypeScript Config", "40": "Node TypeScript Config",
"41": "Admin Blog Controller", "41": "Admin Blog Controller",
"42": "WholesaleApplyDto", "42": "WholesaleService",
"43": "devDependencies", "43": "devDependencies",
"44": "Generic CRUD Controller", "44": "Generic CRUD Controller",
"45": "seo.module.ts", "45": "seo.module.ts",
@ -52,16 +52,16 @@
"50": "Database Seeding Logic", "50": "Database Seeding Logic",
"51": "Prisma Database Migrations", "51": "Prisma Database Migrations",
"52": "dependencies", "52": "dependencies",
"53": "Orders.tsx", "53": "UI Skeleton and Tables",
"54": "auth.service.ts", "54": "auth.service.ts",
"55": "BE-001", "55": "BE-001",
"56": "VetGallery.tsx", "56": "SafeImage.tsx",
"57": "NPM Lifecycle Scripts", "57": "NPM Lifecycle Scripts",
"58": "PetsController", "58": "Pet Management API",
"59": "PrismaService", "59": "PrismaService",
"60": "Jest Testing Config", "60": "Jest Testing Config",
"61": "CreateReviewDto", "61": "WikiController",
"62": "BannersService", "62": "videos.controller.ts",
"63": "FE-001", "63": "FE-001",
"64": "VerifyOtpDto", "64": "VerifyOtpDto",
"65": "rules/graphify.md", "65": "rules/graphify.md",
@ -73,10 +73,10 @@
"71": "Analytics and Report Charts", "71": "Analytics and Report Charts",
"72": "Task Orchestration Scripts", "72": "Task Orchestration Scripts",
"73": "Backend Package Config", "73": "Backend Package Config",
"74": "useSettingsStore", "74": "RegisterDto",
"75": "React Error Boundary", "75": "React Error Boundary",
"76": "Application Package Config", "76": "Application Package Config",
"77": "WikiController", "77": ".findAll",
"78": "Error and Not Found Pages", "78": "Error and Not Found Pages",
"79": "VPN Utility Scripts", "79": "VPN Utility Scripts",
"80": "NestJS CLI Config", "80": "NestJS CLI Config",
@ -89,7 +89,7 @@
"87": "Font Assets and Licenses", "87": "Font Assets and Licenses",
"88": "Ledger Rebuild Scripts", "88": "Ledger Rebuild Scripts",
"89": "Evidence Validation Scripts", "89": "Evidence Validation Scripts",
"90": "SslController", "90": "CreateVideoDto",
"91": "Blog Post Detail Page", "91": "Blog Post Detail Page",
"92": "@types/node", "92": "@types/node",
"93": "devDependencies", "93": "devDependencies",
@ -99,7 +99,7 @@
"97": "Home Management DTOs", "97": "Home Management DTOs",
"98": "Wiki Management DTOs", "98": "Wiki Management DTOs",
"99": "Wiki Page Routing", "99": "Wiki Page Routing",
"100": "toPersian", "100": "ClientLayout.tsx",
"101": "Docker Deployment Scripts", "101": "Docker Deployment Scripts",
"102": "DB-001", "102": "DB-001",
"103": "BlogsService", "103": "BlogsService",
@ -116,10 +116,10 @@
"114": "Manifest Data Generation", "114": "Manifest Data Generation",
"115": "Honest Manifest Synchronization", "115": "Honest Manifest Synchronization",
"116": "Manifest Entry Synchronization", "116": "Manifest Entry Synchronization",
"117": "PaymentController", "117": "SendOtpDto",
"118": "TS-001", "118": "TS-001",
"119": "TEST-001", "119": "TEST-001",
"120": "AdminTransactionFilterDto", "120": "payment.service.ts",
"121": "UITexts.tsx", "121": "UITexts.tsx",
"122": "Admin Panel TSConfig", "122": "Admin Panel TSConfig",
"123": "About Page Component", "123": "About Page Component",
@ -140,8 +140,8 @@
"138": "Operational Rules & Boundaries", "138": "Operational Rules & Boundaries",
"139": "Operational Rules & Boundaries", "139": "Operational Rules & Boundaries",
"140": "Bcrypt Type Definitions", "140": "Bcrypt Type Definitions",
"141": "SettingsService", "141": "bcryptjs",
"142": ".update", "142": "helmet",
"143": "Blog Entity Model", "143": "Blog Entity Model",
"144": "Home Entity Model", "144": "Home Entity Model",
"145": "Wiki Entity Model", "145": "Wiki Entity Model",
@ -206,7 +206,7 @@
"204": "backend/README.md", "204": "backend/README.md",
"205": "Repository Map", "205": "Repository Map",
"206": "Sahel-Font", "206": "Sahel-Font",
"207": "zibal.service.ts", "207": "AuthService",
"208": "Sahel-Font", "208": "Sahel-Font",
"209": "Role & Core Objective", "209": "Role & Core Objective",
"210": "exclude", "210": "exclude",
@ -236,8 +236,8 @@
"234": "🔒 Security & Performance Review (09_devops_security)", "234": "🔒 Security & Performance Review (09_devops_security)",
"235": "👁️ UX & Persona Interface Review (08_visual_qa)", "235": "👁️ UX & Persona Interface Review (08_visual_qa)",
"236": "Compiler Diagnostic Dispositions", "236": "Compiler Diagnostic Dispositions",
"237": "useCartStore", "237": "OrderService",
"238": "ProductPage.tsx", "238": "js-yaml",
"239": "globals", "239": "globals",
"240": "prettier", "240": "prettier",
"241": "prisma", "241": "prisma",
@ -265,25 +265,27 @@
"263": "application/public/fonts/sahel-font-v3.4.0/CHANGELOG.md", "263": "application/public/fonts/sahel-font-v3.4.0/CHANGELOG.md",
"264": "tailwindcss", "264": "tailwindcss",
"265": "typescript-eslint", "265": "typescript-eslint",
"266": ".handleZibalCallback", "266": "@nestjs/core",
"267": "PetsService", "267": "@nestjs/jwt",
"268": "PetsController", "268": "@nestjs/swagger",
"269": "generate-openapi.js", "269": "@nestjs/throttler",
"270": "InitiatePaymentDto", "270": "passport-jwt",
"271": "SmsLogQueryDto", "271": "@prisma/client",
"272": "CreateHealthLogDto", "272": "swagger-ui-express",
"273": "AGENTS.md", "273": "AGENTS.md",
"274": "eslint-config-prettier", "274": "eslint-config-prettier",
"275": "CreateReminderDto", "275": "@eslint/js",
"276": "Reviews.tsx", "276": "jest",
"277": "Tickets.tsx", "277": "@nestjs/schematics",
"278": "Videos.tsx", "278": "@nestjs/testing",
"279": "@types/passport-jwt", "279": "source-map-support",
"280": "@types/supertest", "280": "ts-jest",
"281": "typescript", "281": "tsconfig-paths",
"282": "@types/react-dom", "282": "@types/bcryptjs",
"283": "generate-openapi.d.ts", "283": "typescript-eslint",
"284": "contact/page.tsx",
"285": "eslint-plugin-prettier", "285": "eslint-plugin-prettier",
"286": "eslint-config-next",
"288": "SmsSettingsPage.tsx", "288": "SmsSettingsPage.tsx",
"290": "@tailwindcss/postcss" "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 ## Corpus Check
- 493 files · ~709,356 words - 467 files · ~685,071 words
- Verdict: corpus is large enough that graph structure adds value. - Verdict: corpus is large enough that graph structure adds value.
## Summary ## Summary
- 3516 nodes · 5922 edges · 287 communities (192 shown, 95 thin omitted) - 3274 nodes · 5352 edges · 289 communities (175 shown, 114 thin omitted)
- Extraction: 96% EXTRACTED · 4% INFERRED · 0% AMBIGUOUS · INFERRED: 217 edges (avg confidence: 0.79) - Extraction: 96% EXTRACTED · 4% INFERRED · 0% AMBIGUOUS · INFERRED: 189 edges (avg confidence: 0.79)
- Token cost: 0 input · 0 output - Token cost: 0 input · 0 output
## Graph Freshness ## 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 `git rev-parse HEAD` and compare to check if the graph is stale.
- Run `graphify update .` after code changes (no API cost). - Run `graphify update .` after code changes (no API cost).
@ -23,41 +23,41 @@
- SmsService - SmsService
- AuthController - AuthController
- app.module.ts - app.module.ts
- CreateVideoDto - VideosService
- ProductService - products.ts
- ContactService - ContactService
- compilerOptions - compilerOptions
- UserDashboard.tsx - toPersian
- auth.controller.ts - auth.controller.ts
- ProductsService - ProductsService
- lib/services/api.ts - useCartStore
- tickets.controller.ts - eslint
- ArchivePage.tsx - useSettingsStore
- MetricsController - MetricsController
- B2BService - B2B Inquiry Controller
- CategoriesController - CategoriesController
- RedisService - RedisService
- MediaController - MediaController
- Ingredient Management Controller - Ingredient Management Controller
- adminRoutes.tsx - adminRoutes.tsx
- admin.module.ts - admin.module.ts
- Coupons.tsx - ConfirmModal.tsx
- Prescription Review Controller - Prescription Review Controller
- Smart Advisor Controller - Smart Advisor Controller
- Testimonials Management Controller - Testimonials Management Controller
- src/services/api.ts - api
- Spinner.tsx - Spinner.tsx
- PetProfile.tsx - PetProfile.tsx
- ZibalService - ZibalService
- main.ts - main.ts
- admin.ts - src/services/api.ts
- compilerOptions - Backend TypeScript Config
- compilerOptions - App TypeScript Config
- Transactions.tsx - Pagination.tsx
- dependencies - dependencies
- Node TypeScript Config - Node TypeScript Config
- Admin Blog Controller - Admin Blog Controller
- WholesaleApplyDto - WholesaleService
- devDependencies - devDependencies
- Generic CRUD Controller - Generic CRUD Controller
- seo.module.ts - seo.module.ts
@ -68,16 +68,16 @@
- Database Seeding Logic - Database Seeding Logic
- Prisma Database Migrations - Prisma Database Migrations
- dependencies - dependencies
- Orders.tsx - UI Skeleton and Tables
- auth.service.ts - auth.service.ts
- BE-001 - BE-001
- VetGallery.tsx - SafeImage.tsx
- NPM Lifecycle Scripts - NPM Lifecycle Scripts
- PetsController - Pet Management API
- PrismaService - PrismaService
- Jest Testing Config - Jest Testing Config
- CreateReviewDto - WikiController
- BannersService - videos.controller.ts
- FE-001 - FE-001
- VerifyOtpDto - VerifyOtpDto
- rules/graphify.md - rules/graphify.md
@ -89,10 +89,10 @@
- Analytics and Report Charts - Analytics and Report Charts
- Task Orchestration Scripts - Task Orchestration Scripts
- Backend Package Config - Backend Package Config
- useSettingsStore - RegisterDto
- React Error Boundary - React Error Boundary
- Application Package Config - Application Package Config
- WikiController - .findAll
- Error and Not Found Pages - Error and Not Found Pages
- VPN Utility Scripts - VPN Utility Scripts
- NestJS CLI Config - NestJS CLI Config
@ -105,7 +105,7 @@
- Font Assets and Licenses - Font Assets and Licenses
- Ledger Rebuild Scripts - Ledger Rebuild Scripts
- Evidence Validation Scripts - Evidence Validation Scripts
- SslController - CreateVideoDto
- Blog Post Detail Page - Blog Post Detail Page
- @types/node - @types/node
- devDependencies - devDependencies
@ -115,7 +115,7 @@
- Home Management DTOs - Home Management DTOs
- Wiki Management DTOs - Wiki Management DTOs
- Wiki Page Routing - Wiki Page Routing
- toPersian - ClientLayout.tsx
- Docker Deployment Scripts - Docker Deployment Scripts
- DB-001 - DB-001
- BlogsService - BlogsService
@ -132,10 +132,10 @@
- Manifest Data Generation - Manifest Data Generation
- Honest Manifest Synchronization - Honest Manifest Synchronization
- Manifest Entry Synchronization - Manifest Entry Synchronization
- PaymentController - SendOtpDto
- TS-001 - TS-001
- TEST-001 - TEST-001
- AdminTransactionFilterDto - payment.service.ts
- UITexts.tsx - UITexts.tsx
- Admin Panel TSConfig - Admin Panel TSConfig
- About Page Component - About Page Component
@ -156,8 +156,8 @@
- Operational Rules & Boundaries - Operational Rules & Boundaries
- Operational Rules & Boundaries - Operational Rules & Boundaries
- Bcrypt Type Definitions - Bcrypt Type Definitions
- SettingsService - bcryptjs
- .update - helmet
- Blog Entity Model - Blog Entity Model
- Home Entity Model - Home Entity Model
- Wiki Entity Model - Wiki Entity Model
@ -215,7 +215,7 @@
- backend/README.md - backend/README.md
- Repository Map - Repository Map
- Sahel-Font - Sahel-Font
- zibal.service.ts - AuthService
- Sahel-Font - Sahel-Font
- Role & Core Objective - Role & Core Objective
- exclude - exclude
@ -245,8 +245,8 @@
- 🔒 Security & Performance Review (09_devops_security) - 🔒 Security & Performance Review (09_devops_security)
- 👁️ UX & Persona Interface Review (08_visual_qa) - 👁️ UX & Persona Interface Review (08_visual_qa)
- Compiler Diagnostic Dispositions - Compiler Diagnostic Dispositions
- useCartStore - OrderService
- ProductPage.tsx - js-yaml
- globals - globals
- prettier - prettier
- prisma - prisma
@ -267,38 +267,41 @@
- typescript - typescript
- vitest - vitest
- typescript-eslint - typescript-eslint
- .handleZibalCallback - @nestjs/core
- PetsService - @nestjs/jwt
- PetsController - @nestjs/swagger
- generate-openapi.js - @nestjs/throttler
- InitiatePaymentDto - passport-jwt
- SmsLogQueryDto - @prisma/client
- CreateHealthLogDto - swagger-ui-express
- AGENTS.md - AGENTS.md
- eslint-config-prettier - eslint-config-prettier
- CreateReminderDto - @eslint/js
- Reviews.tsx - jest
- Tickets.tsx - @nestjs/schematics
- Videos.tsx - @nestjs/testing
- @types/passport-jwt - source-map-support
- @types/supertest - ts-jest
- typescript - tsconfig-paths
- @types/react-dom - @types/bcryptjs
- typescript-eslint
- contact/page.tsx
- eslint-plugin-prettier - eslint-plugin-prettier
- eslint-config-next
- SmsSettingsPage.tsx - SmsSettingsPage.tsx
- @tailwindcss/postcss - @tailwindcss/postcss
## God Nodes (most connected - your core abstractions) ## God Nodes (most connected - your core abstractions)
1. `PrismaService` - 79 edges 1. `PrismaService` - 74 edges
2. `Roles()` - 73 edges 2. `Roles()` - 60 edges
3. `useSettingsStore` - 43 edges 3. `PaginationDto` - 39 edges
4. `PaginationDto` - 39 edges 4. `SmsService` - 38 edges
5. `SmsService` - 38 edges 5. `api` - 34 edges
6. `api` - 37 edges 6. `useSettingsStore` - 33 edges
7. `AdminService` - 34 edges 7. `AdminService` - 31 edges
8. `AdminController` - 33 edges 8. `toPersian()` - 31 edges
9. `toPersian()` - 32 edges 9. `AdminController` - 30 edges
10. `JwtAuthGuard` - 31 edges 10. `useCartStore` - 29 edges
## Surprising Connections (you probably didn't know these) ## Surprising Connections (you probably didn't know these)
- `User Profile Photo` --conceptually_related_to--> `User Roles and Capabilities` [INFERRED] - `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] - **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] - **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" ### Community 0 - "AdminService"
Cohesion: 0.05 Cohesion: 0.06
Nodes (34): AdminController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete (+26 more) Nodes (24): AdminController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete (+16 more)
### Community 1 - "pets/pets.controller.ts" ### Community 1 - "pets/pets.controller.ts"
Cohesion: 0.16 Cohesion: 0.05
Nodes (13): CreatePetDto, ApiProperty, ApiPropertyOptional, IsArray, IsNotEmpty, IsNumber, IsOptional, IsString (+5 more) Nodes (45): CreateHealthLogDto, ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString, CreatePetDto, ApiProperty (+37 more)
### Community 2 - "Roles" ### Community 2 - "Roles"
Cohesion: 0.18 Cohesion: 0.06
Nodes (16): Roles(), SettingsController, ApiBearerAuth, ApiOkResponse, ApiOperation, ApiTags, Body, Controller (+8 more) Nodes (33): BannersController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+25 more)
### Community 3 - "UsersService" ### Community 3 - "UsersService"
Cohesion: 0.06 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" ### Community 4 - "CmsController"
Cohesion: 0.08 Cohesion: 0.09
Nodes (26): CmsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+18 more) Nodes (24): CmsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+16 more)
### Community 6 - "AuthController" ### Community 6 - "AuthController"
Cohesion: 0.27 Cohesion: 0.36
Nodes (12): AuthController, ApiBadRequestResponse, ApiBearerAuth, ApiOkResponse, ApiOperation, ApiTags, Body, Controller (+4 more) Nodes (9): AuthController, ApiBadRequestResponse, ApiOkResponse, ApiOperation, ApiTags, Body, Controller, Post (+1 more)
### Community 7 - "app.module.ts" ### Community 7 - "app.module.ts"
Cohesion: 0.09 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" ### Community 8 - "VideosService"
Cohesion: 0.07 Cohesion: 0.12
Nodes (32): CreateVideoDto, ApiProperty, ApiPropertyOptional, IsBoolean, IsNotEmpty, IsOptional, IsString, UpdateVideoDto (+24 more) Nodes (16): ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete, Get (+8 more)
### Community 9 - "ProductService" ### Community 9 - "products.ts"
Cohesion: 0.06 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" ### Community 10 - "ContactService"
Cohesion: 0.13 Cohesion: 0.13
@ -367,65 +370,61 @@ Nodes (11): ContactController, Body, Controller, Get, Param, Post, Put, Query (+
Cohesion: 0.06 Cohesion: 0.06
Nodes (32): compilerOptions, allowJs, esModuleInterop, incremental, isolatedModules, jsx, lib, module (+24 more) Nodes (32): compilerOptions, allowJs, esModuleInterop, incremental, isolatedModules, jsx, lib, module (+24 more)
### Community 12 - "UserDashboard.tsx" ### Community 12 - "toPersian"
Cohesion: 0.12 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" ### Community 13 - "auth.controller.ts"
Cohesion: 0.10 Cohesion: 0.16
Nodes (19): AdminLoginDto, ApiProperty, IsEmail, IsNotEmpty, IsString, MinLength, LoginDto, ApiProperty (+11 more) Nodes (11): AdminLoginDto, ApiProperty, IsEmail, IsNotEmpty, IsString, MinLength, LoginDto, ApiProperty (+3 more)
### Community 14 - "ProductsService" ### Community 14 - "ProductsService"
Cohesion: 0.09 Cohesion: 0.09
Nodes (19): GetProductsDto, ApiPropertyOptional, IsEnum, IsOptional, IsString, ProductsController, ApiOperation, ApiTags (+11 more) Nodes (19): GetProductsDto, ApiPropertyOptional, IsEnum, IsOptional, IsString, ProductsController, ApiOperation, ApiTags (+11 more)
### Community 15 - "lib/services/api.ts" ### Community 15 - "useCartStore"
Cohesion: 0.08 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 Cohesion: 0.09
Nodes (31): AdminUpdateTicketDto, CreateTicketDto, ReplyTicketDto, ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString (+23 more) Nodes (31): HomeClient(), HomeClientProps, getHomeData(), Home(), metadata, metadata, EnamadBadge(), Hero() (+23 more)
### Community 17 - "ArchivePage.tsx"
Cohesion: 0.09
Nodes (26): HomeClient(), HomeClientProps, getHomeData(), Home(), metadata, metadata, ArchivePage(), CATEGORY_MAP (+18 more)
### Community 18 - "MetricsController" ### Community 18 - "MetricsController"
Cohesion: 0.29 Cohesion: 0.29
Nodes (5): ApiExcludeController, MetricsController, Controller, Get, Res Nodes (5): ApiExcludeController, MetricsController, Controller, Get, Res
### Community 19 - "B2BService" ### Community 19 - "B2B Inquiry Controller"
Cohesion: 0.14 Cohesion: 0.13
Nodes (14): B2BController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Get, Param (+6 more) Nodes (15): B2BController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Get, Param (+7 more)
### Community 20 - "CategoriesController" ### Community 20 - "CategoriesController"
Cohesion: 0.10 Cohesion: 0.09
Nodes (16): CategoriesController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete (+8 more) Nodes (17): CategoriesController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete (+9 more)
### Community 21 - "RedisService" ### Community 21 - "RedisService"
Cohesion: 0.12 Cohesion: 0.11
Nodes (4): AppModule, Module, RedisService, Injectable Nodes (7): AppModule, Module, RedisModule, Global, Module, RedisService, Injectable
### Community 22 - "MediaController" ### Community 22 - "MediaController"
Cohesion: 0.11 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" ### Community 23 - "Ingredient Management Controller"
Cohesion: 0.13 Cohesion: 0.13
Nodes (14): IngredientsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+6 more) Nodes (14): IngredientsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+6 more)
### Community 24 - "adminRoutes.tsx" ### Community 24 - "adminRoutes.tsx"
Cohesion: 0.06 Cohesion: 0.12
Nodes (22): App(), HeroBanner, VetTestimonial, ContactInfoItem, ContactSubmission, CategoryDist, DashboardData, SslStatus (+14 more) Nodes (11): App(), ContactInfoItem, ContactSubmission, CategoryDist, DashboardData, AdminRouteConfig, ContactSubmissions, Dashboard (+3 more)
### Community 25 - "admin.module.ts" ### Community 25 - "admin.module.ts"
Cohesion: 0.07 Cohesion: 0.10
Nodes (20): AdminModule, Module, MediaService, Injectable, ReportsController, ApiBearerAuth, ApiOperation, ApiTags (+12 more) Nodes (14): AdminModule, Module, PetQuery, PetsService, Injectable, ReportsController, ApiBearerAuth, ApiOperation (+6 more)
### Community 26 - "Coupons.tsx" ### Community 26 - "ConfirmModal.tsx"
Cohesion: 0.25 Cohesion: 0.08
Nodes (5): Coupon, CouponFormData, CouponModalProps, CouponTarget, Coupons Nodes (19): ConfirmModal(), ConfirmModalProps, HeroBanner, VetTestimonial, Coupon, CouponFormData, CouponModalProps, CouponTarget (+11 more)
### Community 27 - "Prescription Review Controller" ### Community 27 - "Prescription Review Controller"
Cohesion: 0.14 Cohesion: 0.14
@ -439,45 +438,45 @@ Nodes (14): SmartAdvisorController, ApiBearerAuth, ApiOperation, ApiTags, Body,
Cohesion: 0.13 Cohesion: 0.13
Nodes (14): TestimonialsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+6 more) Nodes (14): TestimonialsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+6 more)
### Community 30 - "src/services/api.ts" ### Community 30 - "api"
Cohesion: 0.14 Cohesion: 0.24
Nodes (18): Layout(), ProtectedRoute(), MenuGroup, Sidebar(), SidebarProps, SubMenuItem, SEARCHABLE_PAGES, Topbar() (+10 more) Nodes (8): Layout(), menuGroups, Sidebar(), SidebarProps, SEARCHABLE_PAGES, Topbar(), TopbarProps, api
### Community 31 - "Spinner.tsx" ### Community 31 - "Spinner.tsx"
Cohesion: 0.12 Cohesion: 0.09
Nodes (21): ConfirmModal(), ConfirmModalProps, Media, MediaSelector(), MediaSelectorProps, Pagination(), PaginationProps, Spinner() (+13 more) Nodes (20): Media, MediaSelector(), MediaSelectorProps, Spinner(), BlogPost, Category, BannersManager, Blogs (+12 more)
### Community 32 - "PetProfile.tsx" ### Community 32 - "PetProfile.tsx"
Cohesion: 0.15 Cohesion: 0.08
Nodes (15): BackButton(), BackButtonProps, PetProfile(), SafeImage(), SafeImageProps, CURRENT_SYMPTOMS, MEDICAL_HISTORIES, SmartAdvisor() (+7 more) Nodes (27): metadata, ArchivePage(), ArchiveProductCard(), CATEGORY_MAP, ICON_MAP, FeaturedProducts(), ProductCard(), OrderSuccess() (+19 more)
### Community 33 - "ZibalService" ### Community 33 - "ZibalService"
Cohesion: 0.14 Cohesion: 0.05
Nodes (4): PaymentService, Injectable, Injectable, ZibalService Nodes (41): InitiatePaymentDto, InitiateWalletTopupDto, ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString, ApiPropertyOptional (+33 more)
### Community 34 - "main.ts" ### Community 34 - "main.ts"
Cohesion: 0.14 Cohesion: 0.14
Nodes (9): CustomHttpExceptionFilter, Catch, PrismaExceptionFilter, Catch, DecimalInterceptor, Injectable, ApiErrorResponse, ErrorDetailField (+1 more) Nodes (9): CustomHttpExceptionFilter, Catch, PrismaExceptionFilter, Catch, DecimalInterceptor, Injectable, ApiErrorResponse, ErrorDetailField (+1 more)
### Community 35 - "admin.ts" ### Community 35 - "src/services/api.ts"
Cohesion: 0.08 Cohesion: 0.12
Nodes (20): B2BManager, FinancialSettingsPage, PrescriptionsManager, SeoSettingsPage, SmartAdvisorManager, SystemSettingsPage, AdminLoginPayload, B2BInquiry (+12 more) Nodes (20): ProtectedRoute(), Login(), B2BManager, FinancialSettingsPage, SmartAdvisorManager, ApiErrorPayload, failedQueue, useAdminAuthStore (+12 more)
### Community 36 - "compilerOptions" ### Community 36 - "Backend TypeScript Config"
Cohesion: 0.06 Cohesion: 0.09
Nodes (31): compilerOptions, allowSyntheticDefaultImports, baseUrl, declaration, emitDecoratorMetadata, esModuleInterop, experimentalDecorators, forceConsistentCasingInFileNames (+23 more) Nodes (22): compilerOptions, allowSyntheticDefaultImports, baseUrl, declaration, emitDecoratorMetadata, esModuleInterop, experimentalDecorators, forceConsistentCasingInFileNames (+14 more)
### Community 37 - "compilerOptions" ### Community 37 - "App TypeScript Config"
Cohesion: 0.09 Cohesion: 0.09
Nodes (22): compilerOptions, allowImportingTsExtensions, erasableSyntaxOnly, jsx, lib, module, moduleDetection, moduleResolution (+14 more) Nodes (22): compilerOptions, allowImportingTsExtensions, erasableSyntaxOnly, jsx, lib, module, moduleDetection, moduleResolution (+14 more)
### Community 38 - "Transactions.tsx" ### Community 38 - "Pagination.tsx"
Cohesion: 0.33 Cohesion: 0.11
Nodes (4): GatewayHealth, Stats, Transaction, Transactions Nodes (13): Pagination(), PaginationProps, Media, Pet, GatewayHealth, Stats, Transaction, ProductItem (+5 more)
### Community 39 - "dependencies" ### Community 39 - "dependencies"
Cohesion: 0.05 Cohesion: 0.10
Nodes (41): dependencies, bcrypt, bcryptjs, class-transformer, class-validator, helmet, ioredis, js-yaml (+33 more) Nodes (21): dependencies, bcrypt, class-transformer, class-validator, ioredis, @nestjs/common, @nestjs/passport, @nestjs/platform-express (+13 more)
### Community 40 - "Node TypeScript Config" ### Community 40 - "Node TypeScript Config"
Cohesion: 0.10 Cohesion: 0.10
@ -487,13 +486,13 @@ Nodes (20): compilerOptions, allowImportingTsExtensions, erasableSyntaxOnly, lib
Cohesion: 0.13 Cohesion: 0.13
Nodes (15): BlogsController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete (+7 more) Nodes (15): BlogsController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete (+7 more)
### Community 42 - "WholesaleApplyDto" ### Community 42 - "WholesaleService"
Cohesion: 0.10 Cohesion: 0.14
Nodes (20): ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString, WholesaleApplyDto, ApiBearerAuth, ApiOperation (+12 more) Nodes (14): ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Get, Param, Post (+6 more)
### Community 43 - "devDependencies" ### Community 43 - "devDependencies"
Cohesion: 0.13 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" ### Community 44 - "Generic CRUD Controller"
Cohesion: 0.13 Cohesion: 0.13
@ -504,8 +503,8 @@ Cohesion: 0.16
Nodes (10): SeoController, ApiOperation, ApiTags, Controller, Get, Param, SeoModule, Module (+2 more) Nodes (10): SeoController, ApiOperation, ApiTags, Controller, Get, Param, SeoModule, Module (+2 more)
### Community 46 - "PaginationDto" ### Community 46 - "PaginationDto"
Cohesion: 0.07 Cohesion: 0.14
Nodes (24): CategoryQuery, BlogsModule, Module, BlogsService, Injectable, PaginationDto, SortOrder, ApiPropertyOptional (+16 more) Nodes (13): BlogsModule, Module, BlogsService, Injectable, PaginationDto, SortOrder, ApiPropertyOptional, IsEnum (+5 more)
### Community 47 - "Project Build Scripts" ### Community 47 - "Project Build Scripts"
Cohesion: 0.11 Cohesion: 0.11
@ -531,52 +530,52 @@ Nodes (14): "coupons", "health_logs", "order_items", "orders", "pet_medical_cond
Cohesion: 0.10 Cohesion: 0.10
Nodes (21): dependencies, axios, lucide-react, react, react-dom, react-hot-toast, react-router-dom, recharts (+13 more) Nodes (21): dependencies, axios, lucide-react, react, react-dom, react-hot-toast, react-router-dom, recharts (+13 more)
### Community 53 - "Orders.tsx" ### Community 53 - "UI Skeleton and Tables"
Cohesion: 0.16 Cohesion: 0.18
Nodes (13): Skeleton(), getPaymentMethodLabel(), Order, OrderItem, Orders(), PaymentTx, statusStyles, toPersianDigits() (+5 more) Nodes (11): Skeleton(), Order, OrderItem, Orders(), statusStyles, toPersianDigits(), UserRecord, Orders (+3 more)
### Community 54 - "auth.service.ts" ### Community 54 - "auth.service.ts"
Cohesion: 0.12 Cohesion: 0.16
Nodes (11): AdminLoginInput, AuthService, LoginInput, RegisterInput, Injectable, SendOtpDto, ApiProperty, IsNotEmpty (+3 more) Nodes (7): AuthModule, Module, AdminLoginInput, AuthService, LoginInput, RegisterInput, Injectable
### Community 55 - "BE-001" ### Community 55 - "BE-001"
Cohesion: 0.06 Cohesion: 0.06
Nodes (32): Acceptance Criteria, Affected Application, Affected Files, Alternative Direction, BE-001, Category, Completion Statement, Confidence (+24 more) Nodes (32): Acceptance Criteria, Affected Application, Affected Files, Alternative Direction, BE-001, Category, Completion Statement, Confidence (+24 more)
### Community 56 - "VetGallery.tsx" ### Community 56 - "SafeImage.tsx"
Cohesion: 0.22 Cohesion: 0.17
Nodes (8): DisplayVideoItem, FALLBACK_VIDEOS, TestimonialItem, VetGallery(), FALLBACK_VIDEOS, VideosPage(), Video, videoService Nodes (10): ProductDetailModalProps, SafeImage(), SafeImageProps, DisplayVideoItem, FALLBACK_VIDEOS, TestimonialItem, FALLBACK_VIDEOS, VideosPage() (+2 more)
### Community 57 - "NPM Lifecycle Scripts" ### Community 57 - "NPM Lifecycle Scripts"
Cohesion: 0.14 Cohesion: 0.14
Nodes (14): scripts, build, docs:generate, format, lint, start, start:debug, start:dev (+6 more) Nodes (14): scripts, build, docs:generate, format, lint, start, start:debug, start:dev (+6 more)
### Community 58 - "PetsController" ### Community 58 - "Pet Management API"
Cohesion: 0.10 Cohesion: 0.15
Nodes (14): PetsController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Controller, Delete, Get (+6 more) Nodes (11): PetsController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Controller, Delete, Get (+3 more)
### Community 59 - "PrismaService" ### Community 59 - "PrismaService"
Cohesion: 0.08 Cohesion: 0.06
Nodes (14): B2BWholesaleOrderItem, MeliPayamakPattern, MeliPayamakResponse, SendPatternSmsOptions, SmsConfig, SmsLogQuery, CreateContactSubmissionDto, UpdateContactInfoItemDto (+6 more) Nodes (21): BlogQuery, Injectable, WikiQuery, WikiService, MeliPayamakPattern, MeliPayamakResponse, SendPatternSmsOptions, SmsConfig (+13 more)
### Community 60 - "Jest Testing Config" ### Community 60 - "Jest Testing Config"
Cohesion: 0.15 Cohesion: 0.15
Nodes (13): jest, collectCoverageFrom, coverageDirectory, moduleFileExtensions, rootDir, testEnvironment, testRegex, transform (+5 more) Nodes (13): jest, collectCoverageFrom, coverageDirectory, moduleFileExtensions, rootDir, testEnvironment, testRegex, transform (+5 more)
### Community 61 - "CreateReviewDto" ### Community 61 - "WikiController"
Cohesion: 0.07 Cohesion: 0.20
Nodes (29): CreateReviewDto, ApiProperty, IsInt, IsNotEmpty, IsOptional, IsString, Min, ApiProperty (+21 more) Nodes (7): ApiTags, Controller, WikiController, Module, WikiModule, Injectable, WikiService
### Community 62 - "BannersService" ### Community 62 - "videos.controller.ts"
Cohesion: 0.13 Cohesion: 0.22
Nodes (15): BannersController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+7 more) Nodes (7): GetVideosQueryDto, ApiPropertyOptional, IsBoolean, IsOptional, Module, VideosModule, Transform
### Community 63 - "FE-001" ### Community 63 - "FE-001"
Cohesion: 0.06 Cohesion: 0.06
Nodes (31): Acceptance Criteria, Affected Application, Affected Files, Alternative Direction, Architecture Overview & Confirmed Strengths, Category, Completion Statement, Confidence (+23 more) Nodes (31): Acceptance Criteria, Affected Application, Affected Files, Alternative Direction, Architecture Overview & Confirmed Strengths, Category, Completion Statement, Confidence (+23 more)
### Community 64 - "VerifyOtpDto" ### Community 64 - "VerifyOtpDto"
Cohesion: 0.29 Cohesion: 0.22
Nodes (6): ApiProperty, IsNotEmpty, IsString, Matches, VerifyOtpDto, Length Nodes (6): ApiProperty, IsNotEmpty, IsString, Matches, VerifyOtpDto, Length
### Community 66 - "App Health Controller" ### 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) Nodes (17): dependencies, axios, lucide-react, motion, next, react, react-dom, sonner (+9 more)
### Community 68 - "OrdersService" ### Community 68 - "OrdersService"
Cohesion: 0.07 Cohesion: 0.06
Nodes (29): CreateOrderDto, OrderItemDto, ApiProperty, ApiPropertyOptional, IsArray, IsNotEmpty, IsNumber, IsOptional (+21 more) Nodes (35): CreateOrderDto, OrderItemDto, ApiProperty, ApiPropertyOptional, IsArray, IsNotEmpty, IsNumber, IsOptional (+27 more)
### Community 69 - "Integrity Validation Scripts" ### Community 69 - "Integrity Validation Scripts"
Cohesion: 0.20 Cohesion: 0.20
@ -611,9 +610,9 @@ Nodes (8): cmd_next_task(), cmd_progress(), cmd_reset(), cmd_status(), cmd_unblo
Cohesion: 0.22 Cohesion: 0.22
Nodes (8): author, description, license, name, prisma, seed, private, version Nodes (8): author, description, license, name, prisma, seed, private, version
### Community 74 - "useSettingsStore" ### Community 74 - "RegisterDto"
Cohesion: 0.15 Cohesion: 0.22
Nodes (14): metadata, BrandLogo(), BrandLogoProps, EnamadBadge(), Footer(), MENU_ICONS, MaintenancePage(), TickerBanner() (+6 more) Nodes (8): RegisterDto, ApiProperty, ApiPropertyOptional, IsEmail, IsNotEmpty, IsOptional, IsString, MinLength
### Community 75 - "React Error Boundary" ### Community 75 - "React Error Boundary"
Cohesion: 0.22 Cohesion: 0.22
@ -623,9 +622,9 @@ Nodes (3): ErrorBoundary, Props, State
Cohesion: 0.22 Cohesion: 0.22
Nodes (8): name, private, scripts, build, dev, lint, start, version Nodes (8): name, private, scripts, build, dev, lint, start, version
### Community 77 - "WikiController" ### Community 77 - ".findAll"
Cohesion: 0.21 Cohesion: 0.32
Nodes (9): ApiNotFoundResponse, ApiOkResponse, ApiOperation, ApiTags, Controller, Get, Param, Query (+1 more) Nodes (6): ApiNotFoundResponse, ApiOkResponse, ApiOperation, Get, Param, Query
### Community 79 - "VPN Utility Scripts" ### Community 79 - "VPN Utility Scripts"
Cohesion: 0.62 Cohesion: 0.62
@ -667,25 +666,25 @@ Nodes (4): evidenceList, ledger, mandatoryMap, mandatoryScope
Cohesion: 0.40 Cohesion: 0.40
Nodes (4): activeFiles, errors, validationOutput, warnings Nodes (4): activeFiles, errors, validationOutput, warnings
### Community 90 - "SslController" ### Community 90 - "CreateVideoDto"
Cohesion: 0.13 Cohesion: 0.25
Nodes (14): SslController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Get, Post (+6 more) Nodes (8): CreateVideoDto, ApiProperty, ApiPropertyOptional, IsBoolean, IsNotEmpty, IsOptional, IsString, UpdateVideoDto
### Community 91 - "Blog Post Detail Page" ### Community 91 - "Blog Post Detail Page"
Cohesion: 0.60 Cohesion: 0.60
Nodes (4): BlogPostPage(), generateMetadata(), getBlog(), revalidate Nodes (4): BlogPostPage(), generateMetadata(), getBlog(), revalidate
### Community 93 - "devDependencies" ### Community 93 - "devDependencies"
Cohesion: 0.09 Cohesion: 0.22
Nodes (23): devDependencies, eslint, @eslint/eslintrc, @eslint/js, jest, @nestjs/schematics, @nestjs/testing, source-map-support (+15 more) Nodes (9): devDependencies, @eslint/eslintrc, @types/passport-jwt, @types/supertest, typescript, typescript, @eslint/eslintrc, @types/passport-jwt (+1 more)
### Community 99 - "Wiki Page Routing" ### Community 99 - "Wiki Page Routing"
Cohesion: 0.83 Cohesion: 0.83
Nodes (3): generateMetadata(), getWikiTerm(), WikiTermPage() Nodes (3): generateMetadata(), getWikiTerm(), WikiTermPage()
### Community 100 - "toPersian" ### Community 100 - "ClientLayout.tsx"
Cohesion: 0.11 Cohesion: 0.17
Nodes (21): ClientLayout(), VerifyContent(), AuthModal(), AuthModalProps, extractOtpFromText(), B2BPortal(), CartDrawer(), HeaderButton() (+13 more) Nodes (8): ClientLayout(), Footer(), LoginModal(), LoginModalProps, MaintenancePage(), NetworkBanner(), useNetworkStatus(), NavigationTarget
### Community 101 - "Docker Deployment Scripts" ### Community 101 - "Docker Deployment Scripts"
Cohesion: 0.50 Cohesion: 0.50
@ -695,17 +694,13 @@ Nodes (3): COMPOSE_DOCKER_CLI_BUILD, DOCKER_BUILDKIT, deploy.sh script
Cohesion: 0.06 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) 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" ### Community 109 - "Auth Architecture and Planning"
Cohesion: 0.67 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 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 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" ### Community 118 - "TS-001"
Cohesion: 0.06 Cohesion: 0.06
@ -715,13 +710,13 @@ Nodes (31): Acceptance Criteria, Affected Application, Affected Files, Alternati
Cohesion: 0.06 Cohesion: 0.06
Nodes (31): Acceptance Criteria, Affected Application, Affected Files, Alternative Direction, Category, Completion Statement, Confidence, Dependencies (+23 more) Nodes (31): Acceptance Criteria, Affected Application, Affected Files, Alternative Direction, Category, Completion Statement, Confidence, Dependencies (+23 more)
### Community 120 - "AdminTransactionFilterDto" ### Community 120 - "payment.service.ts"
Cohesion: 0.25 Cohesion: 0.22
Nodes (7): AdminTransactionFilterDto, ApiPropertyOptional, IsIn, IsNumber, IsOptional, IsString, Type Nodes (8): AdminTransactionFilterDto, ApiPropertyOptional, IsNumber, IsOptional, IsString, Type, ClientMetadata, IsIn
### Community 121 - "UITexts.tsx" ### Community 121 - "UITexts.tsx"
Cohesion: 0.29 Cohesion: 0.33
Nodes (5): GROUP_PAGE_MAP, GROUPS, KEY_LABELS_FA, PAGE_TABS, UITexts Nodes (4): GROUP_PAGE_MAP, GROUPS, PAGE_TABS, UITexts
### Community 126 - "Typography and Font Assets" ### Community 126 - "Typography and Font Assets"
Cohesion: 0.67 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) 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" ### Community 130 - "JwtAuthGuard"
Cohesion: 0.18 Cohesion: 0.19
Nodes (7): JwtAuthGuard, Injectable, ROLES_KEY, RequestWithUser, RolesGuard, Injectable, UserReqPayload Nodes (7): JwtAuthGuard, Injectable, ROLES_KEY, RequestWithUser, RolesGuard, Injectable, UserReqPayload
### Community 131 - "راهنمای تست سیستم (Software Testing)" ### Community 131 - "راهنمای تست سیستم (Software Testing)"
@ -775,10 +770,6 @@ Nodes (17): 1. Hierarchical Decomposition Algorithm (3-Tier), 2. Sub-step Defini
Cohesion: 0.11 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) 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" ### Community 157 - "What You Must Do When Invoked"
Cohesion: 0.07 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) 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 Cohesion: 0.20
Nodes (9): Arch Linux, Contributors, Install, Known problems for variable version, License, Sahel-Font, To Do (variable), طریقه استفاده از نسخه متغیر variable (+1 more) 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" ### Community 208 - "Sahel-Font"
Cohesion: 0.20 Cohesion: 0.20
Nodes (9): Arch Linux, Contributors, Install, Known problems for variable version, License, Sahel-Font, To Do (variable), طریقه استفاده از نسخه متغیر variable (+1 more) 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" ### Community 210 - "exclude"
Cohesion: 0.22 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" ### Community 211 - "Phase 2 Final Quality Gate Summary Report"
Cohesion: 0.22 Cohesion: 0.22
@ -979,77 +966,29 @@ Nodes (3): Expanding the ESLint configuration, React Compiler, React + TypeScrip
Cohesion: 0.50 Cohesion: 0.50
Nodes (3): Deploy on Vercel, Getting Started, Learn More 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" ### Community 288 - "SmsSettingsPage.tsx"
Cohesion: 0.29 Cohesion: 0.29
Nodes (5): PatternItem, SmsConfigState, SmsLogItem, SmsLogStats, SmsSettingsPage Nodes (5): PatternItem, SmsConfigState, SmsLogItem, SmsLogStats, SmsSettingsPage
## Knowledge Gaps ## 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. 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 ## Suggested Questions
_Questions this graph is uniquely positioned to answer:_ _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`?** - **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.044) - this node is a cross-community bridge._ _High betweenness centrality (0.041) - 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`?** - **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.039) - this node is a cross-community bridge._ _High betweenness centrality (0.034) - 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`?** - **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.026) - this node is a cross-community bridge._ _High betweenness centrality (0.024) - this node is a cross-community bridge._
- **What connects `$schema`, `collection`, `sourceRoot` to the rest of the system?** - **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?** - **Should `AdminService` be split into smaller, more focused modules?**
_Cohesion score 0.05396825396825397 - nodes in this community are weakly interconnected._ _Cohesion score 0.06414414414414414 - nodes in this community are weakly interconnected._
- **Should `UsersService` be split into smaller, more focused modules?** - **Should `pets/pets.controller.ts` be split into smaller, more focused modules?**
_Cohesion score 0.05547785547785548 - nodes in this community are weakly interconnected._ _Cohesion score 0.0528169014084507 - nodes in this community are weakly interconnected._
- **Should `CmsController` be split into smaller, more focused modules?** - **Should `Roles` be split into smaller, more focused modules?**
_Cohesion score 0.08441558441558442 - nodes in this community are weakly interconnected._ _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