Compare commits
2 Commits
2fca778930
...
45c9db133c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45c9db133c | ||
|
|
2d594c5d69 |
@ -5,12 +5,26 @@ export const DEFAULT_UI_TEXTS: Record<string, string> = {
|
||||
"maintenance_desc": "وبسایت رسمی کنینا ایران جهت ارتقای زیرساختها و بهبود عملکرد به صورت موقت در دست بهروزرسانی است. از شکیبایی شما سپاسگزاریم.",
|
||||
"maintenance_eta": "زمان تقریبی بازگشایی: کمتر از ۲ ساعت آینده",
|
||||
"maintenance_contact_phone": "۰۲۱-۸۸۸۸۸۸۸۸",
|
||||
"maintenance_contact_phone_link": "tel:02188888888",
|
||||
"maintenance_badge": "سامانه در حال ارتقا",
|
||||
"catalog_mode": "false",
|
||||
"catalog_hide_prices": "false",
|
||||
"catalog_disable_cart": "false",
|
||||
"catalog_disable_checkout": "false",
|
||||
|
||||
// === Brand & Logo ===
|
||||
"site_logo": "",
|
||||
"site_logo_text_en": "Canina",
|
||||
"site_logo_text_fa": "ایران",
|
||||
"site_logo_subtitle": "نماینده رسمی CANINA PHARMA GMBH GERMANY",
|
||||
"contact_phone": "۰۲۱-۸۸۸۸۴۴۴۴",
|
||||
"contact_phone_link": "tel:02188884444",
|
||||
"contact_email": "info@canina.ir",
|
||||
"contact_address": "تهران، جردن، خیابان سعیدی، ساختمان کنینا، طبقه ۵، واحد ۱۹",
|
||||
"contact_whatsapp": "09120000000",
|
||||
"contact_telegram": "https://t.me/canina_iran",
|
||||
"contact_instagram": "https://instagram.com/canina_iran",
|
||||
|
||||
// === Header / Navigation ===
|
||||
"shipping_notice": "ارسال رایگان برای سفارشهای بالای ۱۵۰ هزار تومان — گارانتی اصالت کنینا آلمان",
|
||||
"brand_name_fa": "کنینا ایران",
|
||||
|
||||
@ -41,11 +41,40 @@ export class SettingsController {
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles('Admin')
|
||||
@ApiBearerAuth()
|
||||
@Patch('ui-texts/:key')
|
||||
@Put('ui-texts/:key')
|
||||
@ApiOperation({ summary: 'ویرایش یا ثبت متن یک کلید در رابط کاربری' })
|
||||
updateUiText(@Param('key') key: string, @Body('value') value: string) {
|
||||
return this.settingsService.updateUiText(key, value);
|
||||
@ApiOperation({ summary: 'ویرایش یا ثبت متن یک کلید در رابط کاربری با متد PUT' })
|
||||
putUiText(@Param('key') key: string, @Body() body: any) {
|
||||
const val =
|
||||
typeof body === 'object' && body !== null && 'value' in body
|
||||
? body.value
|
||||
: typeof body === 'string'
|
||||
? body
|
||||
: JSON.stringify(body);
|
||||
return this.settingsService.updateUiText(key, String(val ?? ''));
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles('Admin')
|
||||
@ApiBearerAuth()
|
||||
@Patch('ui-texts/:key')
|
||||
@ApiOperation({ summary: 'ویرایش یا ثبت متن یک کلید در رابط کاربری با متد PATCH' })
|
||||
patchUiText(@Param('key') key: string, @Body() body: any) {
|
||||
const val =
|
||||
typeof body === 'object' && body !== null && 'value' in body
|
||||
? body.value
|
||||
: typeof body === 'string'
|
||||
? body
|
||||
: JSON.stringify(body);
|
||||
return this.settingsService.updateUiText(key, String(val ?? ''));
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles('Admin')
|
||||
@ApiBearerAuth()
|
||||
@Put('ui-texts')
|
||||
@ApiOperation({ summary: 'ویرایش گروهی متون رابط کاربری' })
|
||||
putBulkUiTexts(@Body() body: Record<string, string>) {
|
||||
return this.settingsService.updateBulkUiTexts(body);
|
||||
}
|
||||
|
||||
@Get('scientific-terms')
|
||||
|
||||
@ -90,13 +90,33 @@ export class SettingsService implements OnModuleInit {
|
||||
}
|
||||
|
||||
async updateUiText(key: string, value: string) {
|
||||
const strVal = String(value ?? '');
|
||||
try {
|
||||
await this.prisma.setting.upsert({
|
||||
where: { key },
|
||||
update: { value: strVal },
|
||||
create: { key, value: strVal, category: 'general' },
|
||||
});
|
||||
} catch (e) {
|
||||
this.logger.warn(`Failed to sync setting key ${key}: ${e}`);
|
||||
}
|
||||
|
||||
return this.prisma.uiText.upsert({
|
||||
where: { key },
|
||||
update: { value },
|
||||
create: { key, value },
|
||||
update: { value: strVal },
|
||||
create: { key, value: strVal },
|
||||
});
|
||||
}
|
||||
|
||||
async updateBulkUiTexts(texts: Record<string, string>) {
|
||||
if (!texts || typeof texts !== 'object') return { success: true };
|
||||
const entries = Object.entries(texts);
|
||||
for (const [key, value] of entries) {
|
||||
await this.updateUiText(key, String(value ?? ''));
|
||||
}
|
||||
return { success: true, count: entries.length };
|
||||
}
|
||||
|
||||
async getScientificTerms() {
|
||||
return this.prisma.scientificTerm.findMany();
|
||||
}
|
||||
|
||||
@ -1,63 +1,50 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { Users, ShoppingCart, Tag, Settings, TrendingUp, FolderTree, FileText, BookOpen, Heart, Package, LayoutDashboard, Languages, Image, Video, PhoneCall, Sparkles, MessageSquareQuote, FlaskConical, Building2, Globe, DollarSign, Sliders, MessageSquare, Receipt, CreditCard } from 'lucide-react';
|
||||
import {
|
||||
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';
|
||||
|
||||
const menuGroups = [
|
||||
{
|
||||
title: 'اصلی',
|
||||
items: [
|
||||
{ icon: LayoutDashboard, label: 'داشبورد', path: '/' },
|
||||
{ icon: TrendingUp, label: 'گزارشات', path: '/reports' },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'فروشگاه و تخصصی',
|
||||
items: [
|
||||
{ icon: ShoppingCart, label: 'سفارشات', path: '/orders' },
|
||||
{ icon: Receipt, label: 'تراکنشها و لاگ پرداخت', path: '/transactions' },
|
||||
{ icon: Package, label: 'محصولات', path: '/products' },
|
||||
{ icon: MessageSquare, label: 'نظرات محصولات', path: '/reviews' },
|
||||
{ icon: FolderTree, label: 'دستهبندیها', path: '/categories' },
|
||||
{ icon: Tag, label: 'کدهای تخفیف', path: '/coupons' },
|
||||
{ icon: Sparkles, label: 'مشاور هوشمند', path: '/smart-advisor' },
|
||||
{ icon: FileText, label: 'نسخههای پزشکی', path: '/prescriptions' },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'مدیریت و همکاران B2B',
|
||||
items: [
|
||||
{ icon: Users, label: 'کاربران', path: '/users' },
|
||||
{ icon: Heart, label: 'حیوانات (Pets)', path: '/pets' },
|
||||
{ icon: MessageSquare, label: 'تیکتها & مشاوره دامپزشک', path: '/tickets' },
|
||||
{ icon: Building2, label: 'مدیریت B2B & عمده', path: '/b2b' },
|
||||
{ icon: PhoneCall, label: 'تماس با ما & اطلاعات', path: '/contact' },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'محتوا و دانشنامه',
|
||||
items: [
|
||||
{ icon: Image, label: 'بنرها و اسلایدرها', path: '/banners' },
|
||||
{ icon: FlaskConical, label: 'دانشنامه ترکیبات', path: '/ingredients' },
|
||||
{ icon: MessageSquareQuote, label: 'نظرات و گواهیها', path: '/testimonials' },
|
||||
{ icon: FileText, label: 'وبلاگ', path: '/blogs' },
|
||||
{ icon: BookOpen, label: 'دانشنامه عمومی', path: '/wiki' },
|
||||
{ icon: Video, label: 'مدیریت ویدئوها', path: '/videos' },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'سیستم و تنظیمات',
|
||||
items: [
|
||||
{ icon: Settings, label: 'تنظیمات کلی', path: '/settings' },
|
||||
{ icon: MessageSquare, label: 'تنظیمات پیامک', path: '/settings/sms' },
|
||||
{ icon: Globe, label: 'تنظیمات سئو', path: '/settings/seo' },
|
||||
{ icon: DollarSign, label: 'تنظیمات مالی & ارسال', path: '/settings/financial' },
|
||||
{ icon: Sliders, label: 'تنظیمات سیستمی', path: '/settings/system' },
|
||||
{ icon: Languages, label: 'متون رابط کاربری', path: '/ui-texts' },
|
||||
{ icon: Image, label: 'مدیریت رسانه', path: '/media' },
|
||||
]
|
||||
}
|
||||
];
|
||||
interface SubMenuItem {
|
||||
icon: React.ComponentType<{ className?: string }>;
|
||||
label: string;
|
||||
path: string;
|
||||
badge?: string | number;
|
||||
}
|
||||
|
||||
interface MenuGroup {
|
||||
id: string;
|
||||
title: string;
|
||||
icon: React.ComponentType<{ className?: string }>;
|
||||
items: SubMenuItem[];
|
||||
}
|
||||
|
||||
interface SidebarProps {
|
||||
isOpen: boolean;
|
||||
@ -69,12 +56,17 @@ export default function Sidebar({ isOpen, setIsOpen }: SidebarProps) {
|
||||
const [newOrdersCount, setNewOrdersCount] = useState(0);
|
||||
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
||||
|
||||
// Auto-close sidebar on mobile navigation
|
||||
useEffect(() => {
|
||||
setIsOpen(false);
|
||||
}, [location.pathname, setIsOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchOrdersCount = async () => {
|
||||
try {
|
||||
const response = await api.get('/admin/dashboard/stats');
|
||||
if (response.data?.success) {
|
||||
setNewOrdersCount(response.data.data.newOrders);
|
||||
setNewOrdersCount(response.data.data.newOrders || 0);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch stats in sidebar', err);
|
||||
@ -87,6 +79,106 @@ export default function Sidebar({ isOpen, setIsOpen }: SidebarProps) {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const menuGroups: MenuGroup[] = [
|
||||
{
|
||||
id: 'main',
|
||||
title: 'داشبورد و تحلیل داده',
|
||||
icon: LayoutDashboard,
|
||||
items: [
|
||||
{ icon: LayoutDashboard, label: 'نمای کلی داشبورد', path: '/' },
|
||||
{ icon: TrendingUp, label: 'گزارشات و تحلیل فروش', path: '/reports' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'orders',
|
||||
title: 'سفارشات و مالی',
|
||||
icon: ShoppingCart,
|
||||
items: [
|
||||
{
|
||||
icon: ShoppingCart,
|
||||
label: 'مدیریت سفارشات',
|
||||
path: '/orders',
|
||||
badge: newOrdersCount > 0 ? newOrdersCount : undefined,
|
||||
},
|
||||
{ icon: Receipt, label: 'تراکنشها و لاگ پرداخت', path: '/transactions' },
|
||||
{ icon: DollarSign, label: 'تنظیمات مالی و ارسال', path: '/settings/financial' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'products',
|
||||
title: 'محصولات و کاتالوگ',
|
||||
icon: Package,
|
||||
items: [
|
||||
{ icon: Package, label: 'لیست محصولات', path: '/products' },
|
||||
{ icon: Star, label: 'نظرات و امتیازات', path: '/reviews' },
|
||||
{ icon: FolderTree, label: 'دستهبندیهای تخصصی', path: '/categories' },
|
||||
{ icon: Tag, label: 'کدهای تخفیف', path: '/coupons' },
|
||||
{ icon: Sparkles, label: 'مشاور هوشمند بالینی', path: '/smart-advisor' },
|
||||
{ icon: FileText, label: 'نسخههای دریافتی', path: '/prescriptions' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'crm',
|
||||
title: 'کاربران و همکاران',
|
||||
icon: Users,
|
||||
items: [
|
||||
{ icon: Users, label: 'مدیریت کاربران', path: '/users' },
|
||||
{ icon: Heart, label: 'پرونده پتها (همدمها)', path: '/pets' },
|
||||
{ icon: MessageSquare, label: 'تیکتها و مشاوره تخصصی', path: '/tickets' },
|
||||
{ icon: Building2, label: 'پرتال B2B و همکاران', path: '/b2b' },
|
||||
{ icon: PhoneCall, label: 'اطلاعات تماس و پیامها', path: '/contact' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'content',
|
||||
title: 'محتوا و دانشنامه',
|
||||
icon: Layers,
|
||||
items: [
|
||||
{ icon: Image, label: 'بنرها و اسلایدرها', path: '/banners' },
|
||||
{ icon: FileText, label: 'وبلاگ و مقالات', path: '/blogs' },
|
||||
{ icon: FlaskConical, label: 'دانشنامه ترکیبات', path: '/ingredients' },
|
||||
{ icon: BookOpen, label: 'دانشنامه عمومی و علمی', path: '/wiki' },
|
||||
{ icon: MessageSquareQuote, label: 'نظرات و گواهی متخصصان', path: '/testimonials' },
|
||||
{ icon: Video, label: 'ویدئوها و آموزشها', path: '/videos' },
|
||||
{ icon: Image, label: 'کتابخانه رسانه', path: '/media' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'settings',
|
||||
title: 'سیستم و تنظیمات',
|
||||
icon: Settings,
|
||||
items: [
|
||||
{ icon: Settings, label: 'تنظیمات اصلی فروشگاه', path: '/settings' },
|
||||
{ icon: Languages, label: 'متون رابط کاربری', path: '/ui-texts' },
|
||||
{ icon: MessageSquare, label: 'درگاه پیامک (MeliPayamak)', path: '/settings/sms' },
|
||||
{ icon: ShieldCheck, label: 'گواهی SSL و امنیت', path: '/settings/ssl' },
|
||||
{ icon: Globe, label: 'تنظیمات سئو (SEO)', path: '/settings/seo' },
|
||||
{ icon: Sliders, label: 'تنظیمات سیستمی پیشرفته', path: '/settings/system' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Auto expand active group
|
||||
const [expandedGroups, setExpandedGroups] = useState<Record<string, boolean>>(() => {
|
||||
const initial: Record<string, boolean> = {};
|
||||
menuGroups.forEach((g) => {
|
||||
const hasActive = g.items.some(
|
||||
(item) =>
|
||||
location.pathname === item.path ||
|
||||
(item.path !== '/' && location.pathname.startsWith(item.path))
|
||||
);
|
||||
initial[g.id] = hasActive || g.id === 'main' || g.id === 'orders';
|
||||
});
|
||||
return initial;
|
||||
});
|
||||
|
||||
const toggleGroup = (groupId: string) => {
|
||||
setExpandedGroups((prev) => ({
|
||||
...prev,
|
||||
[groupId]: !prev[groupId],
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Mobile Backdrop */}
|
||||
@ -97,43 +189,100 @@ export default function Sidebar({ isOpen, setIsOpen }: SidebarProps) {
|
||||
/>
|
||||
)}
|
||||
|
||||
<aside className={`w-64 bg-white border-l border-gray-200 h-screen fixed lg:fixed lg:right-0 lg:top-0 lg:bottom-0 flex flex-col font-vazir shadow-sm z-50 transform transition-transform duration-300 ease-in-out ${isOpen ? 'translate-x-0' : 'translate-x-full lg:translate-x-0'
|
||||
}`}>
|
||||
<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>
|
||||
<aside
|
||||
className={`w-64 bg-white border-l border-gray-200 h-screen fixed lg:fixed lg:right-0 lg:top-0 lg:bottom-0 flex flex-col font-vazir shadow-sm z-50 transform transition-transform duration-300 ease-in-out ${
|
||||
isOpen ? 'translate-x-0' : 'translate-x-full lg:translate-x-0'
|
||||
}`}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="h-16 flex items-center justify-between px-5 border-b border-gray-200 shrink-0 bg-white">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-8 h-8 rounded-xl bg-purple-600 flex items-center justify-center text-white font-black text-sm italic shadow-md">
|
||||
C
|
||||
</div>
|
||||
<span className="text-base font-black text-gray-900">کنینا | ادمینپنل</span>
|
||||
</div>
|
||||
<span className="text-[10px] bg-purple-50 text-purple-700 font-bold px-2 py-0.5 rounded-md border border-purple-200">
|
||||
v2.4
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<nav className="flex-1 overflow-y-auto py-4 px-3 space-y-6">
|
||||
{menuGroups.map((group, idx) => (
|
||||
<div key={idx}>
|
||||
<h3 className="px-3 mb-2 text-xs font-black text-gray-400 uppercase tracking-wider">{group.title}</h3>
|
||||
<div className="space-y-1">
|
||||
{/* Menu Navigation */}
|
||||
<nav className="flex-1 overflow-y-auto py-3 px-3 space-y-2">
|
||||
{menuGroups.map((group) => {
|
||||
const GroupIcon = group.icon;
|
||||
const isExpanded = !!expandedGroups[group.id];
|
||||
const hasActiveChild = group.items.some(
|
||||
(item) =>
|
||||
location.pathname === item.path ||
|
||||
(item.path !== '/' && location.pathname.startsWith(item.path))
|
||||
);
|
||||
|
||||
return (
|
||||
<div key={group.id} className="rounded-2xl overflow-hidden bg-gray-50/50 border border-gray-100">
|
||||
{/* Group Accordion Header */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => toggleGroup(group.id)}
|
||||
className={`w-full flex items-center justify-between px-3 py-2.5 text-xs font-black transition-all ${
|
||||
hasActiveChild ? 'text-purple-700 bg-purple-50/70' : 'text-gray-600 hover:bg-gray-100/60'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<GroupIcon className={`w-4 h-4 ${hasActiveChild ? 'text-purple-600' : 'text-gray-400'}`} />
|
||||
<span>{group.title}</span>
|
||||
</div>
|
||||
<ChevronDown
|
||||
className={`w-4 h-4 transition-transform duration-200 text-gray-400 ${
|
||||
isExpanded ? 'rotate-180' : ''
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
|
||||
{/* Submenu Items */}
|
||||
{isExpanded && (
|
||||
<div className="p-1 space-y-0.5 bg-white border-t border-gray-100">
|
||||
{group.items.map((item) => {
|
||||
const isActive = location.pathname === item.path || (item.path !== '/' && location.pathname.startsWith(item.path));
|
||||
const isActive =
|
||||
location.pathname === item.path ||
|
||||
(item.path !== '/' && location.pathname.startsWith(item.path));
|
||||
const Icon = item.icon;
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={item.path}
|
||||
to={item.path}
|
||||
className={`flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all font-bold ${isActive
|
||||
? 'bg-purple-50 text-purple-600'
|
||||
: 'text-gray-500 hover:bg-gray-50 hover:text-gray-900'
|
||||
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'
|
||||
}`}
|
||||
>
|
||||
<Icon className="w-5 h-5" />
|
||||
<div className="flex items-center gap-2.5">
|
||||
<Icon className={`w-4 h-4 ${isActive ? 'text-white' : 'text-gray-400'}`} />
|
||||
<span>{item.label}</span>
|
||||
{item.label === 'سفارشات' && newOrdersCount > 0 && (
|
||||
<span className="mr-auto bg-purple-600 text-white text-[11px] font-black px-2 py-0.5 rounded-full min-w-[20px] text-center">
|
||||
{newOrdersCount}
|
||||
</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>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
</aside>
|
||||
</>
|
||||
|
||||
@ -239,7 +239,17 @@ export default function BannersManager() {
|
||||
</td>
|
||||
<td className="py-4 px-6">
|
||||
<span className="inline-block px-3 py-1 bg-purple-50 text-purple-700 text-xs font-bold rounded-lg border border-purple-100">
|
||||
{banner.position === 'home_hero' ? 'اسلایدر هیرو اصلی' : banner.position === 'category_top' ? 'بالای دستهبندی' : 'سایدبار'}
|
||||
{banner.position === 'home_hero'
|
||||
? 'اسلایدر هیرو اصلی'
|
||||
: banner.position === 'home_middle'
|
||||
? 'بنر میانی صفحه نخست'
|
||||
: banner.position === 'home_bottom'
|
||||
? 'بنر عریض پایین خانه'
|
||||
: banner.position === 'shop_top'
|
||||
? 'بالای فروشگاه و کاتالوگ'
|
||||
: banner.position === 'category_top'
|
||||
? 'بالای دستهبندی'
|
||||
: 'سایدبار'}
|
||||
</span>
|
||||
</td>
|
||||
<td className="py-4 px-6 text-gray-500 font-mono text-xs dir-ltr text-right">
|
||||
@ -354,8 +364,11 @@ export default function BannersManager() {
|
||||
className="w-full px-4 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 outline-none text-sm font-bold bg-white"
|
||||
>
|
||||
<option value="home_hero">اسلایدر هیرو اصلی (home_hero)</option>
|
||||
<option value="home_middle">بنر میانی صفحه نخست (home_middle)</option>
|
||||
<option value="home_bottom">بنر عریض پایین خانه (home_bottom)</option>
|
||||
<option value="shop_top">بالای فروشگاه و کاتالوگ (shop_top)</option>
|
||||
<option value="category_top">بالای دستهبندی (category_top)</option>
|
||||
<option value="sidebar">سایدبار (sidebar)</option>
|
||||
<option value="product_sidebar">سایدبار صفحات محصول و مقالات (product_sidebar)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
||||
@ -25,12 +25,16 @@ export default function Settings() {
|
||||
ZIBAL_SANDBOX: 'true',
|
||||
FRONTEND_URL: 'https://canina.ir',
|
||||
CONTACT_PHONE: '۰۲۱-۸۸۸۸ ۴۴۴۴',
|
||||
CONTACT_PHONE_LINK: 'tel:02188884444',
|
||||
CONTACT_EMAIL: 'info@canina-iran.com',
|
||||
SOCIAL_WHATSAPP: '09120000000',
|
||||
SOCIAL_INSTAGRAM: 'https://instagram.com/canina_iran',
|
||||
SOCIAL_TELEGRAM: 'https://t.me/canina_iran',
|
||||
CONTACT_ADDRESS: 'تهران، جردن، خیابان سعیدی، ساختمان کنینا، طبقه ۵، واحد ۱۹',
|
||||
BRAND_LOGO_URL: '/logo.png',
|
||||
BRAND_LOGO_URL: '',
|
||||
BRAND_LOGO_TEXT_EN: 'Canina',
|
||||
BRAND_LOGO_TEXT_FA: 'ایران',
|
||||
BRAND_LOGO_SUBTITLE: 'نماینده رسمی CANINA PHARMA GMBH GERMANY',
|
||||
ENAMAD_CODE: '',
|
||||
THEME_PRIMARY_COLOR: '#7c3aed',
|
||||
BRAND_TYPOGRAPHY: 'vazirmatn',
|
||||
@ -62,13 +66,17 @@ export default function Settings() {
|
||||
ZIBAL_MERCHANT: response.data.data.ZIBAL_MERCHANT || 'zibal',
|
||||
ZIBAL_SANDBOX: response.data.data.ZIBAL_SANDBOX || 'true',
|
||||
FRONTEND_URL: response.data.data.FRONTEND_URL || 'https://canina.ir',
|
||||
CONTACT_PHONE: response.data.data.CONTACT_PHONE || '۰۲۱-۸۸۸۸ ۴۴۴۴',
|
||||
CONTACT_EMAIL: response.data.data.CONTACT_EMAIL || 'info@canina-iran.com',
|
||||
SOCIAL_WHATSAPP: response.data.data.SOCIAL_WHATSAPP || '09120000000',
|
||||
SOCIAL_INSTAGRAM: response.data.data.SOCIAL_INSTAGRAM || 'https://instagram.com/canina_iran',
|
||||
SOCIAL_TELEGRAM: response.data.data.SOCIAL_TELEGRAM || 'https://t.me/canina_iran',
|
||||
CONTACT_ADDRESS: response.data.data.CONTACT_ADDRESS || 'تهران، جردن، خیابان سعیدی، ساختمان کنینا، طبقه ۵، واحد ۱۹',
|
||||
BRAND_LOGO_URL: response.data.data.BRAND_LOGO_URL || '/logo.png',
|
||||
CONTACT_PHONE: response.data.data.CONTACT_PHONE || response.data.data.contact_phone || '۰۲۱-۸۸۸۸ ۴۴۴۴',
|
||||
CONTACT_PHONE_LINK: response.data.data.CONTACT_PHONE_LINK || response.data.data.contact_phone_link || 'tel:02188884444',
|
||||
CONTACT_EMAIL: response.data.data.CONTACT_EMAIL || response.data.data.contact_email || 'info@canina-iran.com',
|
||||
SOCIAL_WHATSAPP: response.data.data.SOCIAL_WHATSAPP || response.data.data.contact_whatsapp || '09120000000',
|
||||
SOCIAL_INSTAGRAM: response.data.data.SOCIAL_INSTAGRAM || response.data.data.contact_instagram || 'https://instagram.com/canina_iran',
|
||||
SOCIAL_TELEGRAM: response.data.data.SOCIAL_TELEGRAM || response.data.data.contact_telegram || 'https://t.me/canina_iran',
|
||||
CONTACT_ADDRESS: response.data.data.CONTACT_ADDRESS || response.data.data.contact_address || 'تهران، جردن، خیابان سعیدی، ساختمان کنینا، طبقه ۵، واحد ۱۹',
|
||||
BRAND_LOGO_URL: response.data.data.BRAND_LOGO_URL || response.data.data.site_logo || '',
|
||||
BRAND_LOGO_TEXT_EN: response.data.data.BRAND_LOGO_TEXT_EN || response.data.data.site_logo_text_en || 'Canina',
|
||||
BRAND_LOGO_TEXT_FA: response.data.data.BRAND_LOGO_TEXT_FA || response.data.data.site_logo_text_fa || 'ایران',
|
||||
BRAND_LOGO_SUBTITLE: response.data.data.BRAND_LOGO_SUBTITLE || response.data.data.site_logo_subtitle || 'نماینده رسمی CANINA PHARMA GMBH GERMANY',
|
||||
ENAMAD_CODE: response.data.data.ENAMAD_CODE || '',
|
||||
THEME_PRIMARY_COLOR: response.data.data.THEME_PRIMARY_COLOR || '#7c3aed',
|
||||
BRAND_TYPOGRAPHY: response.data.data.BRAND_TYPOGRAPHY || 'vazirmatn',
|
||||
@ -334,16 +342,30 @@ export default function Settings() {
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-xs font-bold text-gray-700 mb-1">تلفن پشتیبانی و فروش</label>
|
||||
<label className="block text-xs font-bold text-gray-700 mb-1">متن نمایشی تلفن پشتیبانی و فروش</label>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.CONTACT_PHONE}
|
||||
onChange={(e) => setSettings({ ...settings, CONTACT_PHONE: e.target.value })}
|
||||
placeholder="مثال: ۰۲۱-۸۸۸۸۴۴۴۴ یا ۰۹۲۱۲۳۴۵۶۷۸"
|
||||
className="w-full border border-gray-200 rounded-xl p-2.5 outline-none focus:ring-2 focus:ring-purple-500 text-xs font-bold"
|
||||
dir="ltr"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-xs font-bold text-gray-700 mb-1">لینک مستقیم تماس (Phone Link)</label>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.CONTACT_PHONE_LINK}
|
||||
onChange={(e) => setSettings({ ...settings, CONTACT_PHONE_LINK: e.target.value })}
|
||||
placeholder="مثال: tel:+982188884444 یا tel:09212345678"
|
||||
className="w-full border border-gray-200 rounded-xl p-2.5 outline-none focus:ring-2 focus:ring-purple-500 text-xs font-mono"
|
||||
dir="ltr"
|
||||
/>
|
||||
<p className="text-[10px] text-gray-400 mt-1">لینکی که با کلیک روی شماره تلفن در سایت باز میشود (مثلاً شماره بینالمللی یا لینک تماس).</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-xs font-bold text-gray-700 mb-1">ایمیل رسمی پشتیبانی</label>
|
||||
<input
|
||||
@ -410,14 +432,14 @@ export default function Settings() {
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-xs font-bold text-gray-700 mb-1">لینک لوگوی برند (Brand Logo URL)</label>
|
||||
<label className="block text-xs font-bold text-gray-700 mb-1">لینک لوگوی تصویری (Brand Logo Image URL)</label>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={settings.BRAND_LOGO_URL}
|
||||
onChange={(e) => setSettings({ ...settings, BRAND_LOGO_URL: e.target.value })}
|
||||
className="flex-1 border border-gray-200 rounded-xl p-2.5 outline-none focus:ring-2 focus:ring-purple-500 text-xs font-mono"
|
||||
placeholder="https://... یا /logo.png"
|
||||
placeholder="https://... یا /logo.png (اختیاری)"
|
||||
dir="ltr"
|
||||
/>
|
||||
{settings.BRAND_LOGO_URL && (
|
||||
@ -426,7 +448,41 @@ export default function Settings() {
|
||||
</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 className="md:col-span-2">
|
||||
|
||||
@ -159,14 +159,24 @@ export default function UITexts() {
|
||||
const value = edits[key] ?? texts[key] ?? '';
|
||||
setSaving(key);
|
||||
try {
|
||||
await api.put(`/settings/ui-texts/${key}`, { value });
|
||||
await api.put(`/settings/ui-texts/${encodeURIComponent(key)}`, { value });
|
||||
setTexts(prev => ({ ...prev, [key]: value }));
|
||||
setSavedKey(key);
|
||||
toast.success('ذخیرهسازی با موفقیت انجام شد');
|
||||
setTimeout(() => setSavedKey(null), 2000);
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
console.error('Failed to save UI text:', e);
|
||||
toast.error('خطا در ذخیرهسازی');
|
||||
try {
|
||||
// Fallback to PATCH if PUT fails
|
||||
await api.patch(`/settings/ui-texts/${encodeURIComponent(key)}`, { value });
|
||||
setTexts(prev => ({ ...prev, [key]: value }));
|
||||
setSavedKey(key);
|
||||
toast.success('ذخیرهسازی با موفقیت انجام شد');
|
||||
setTimeout(() => setSavedKey(null), 2000);
|
||||
} catch (err: any) {
|
||||
const errorMsg = err?.response?.data?.message || e?.response?.data?.message || 'خطا در ذخیرهسازی';
|
||||
toast.error(typeof errorMsg === 'string' ? errorMsg : 'خطا در ذخیرهسازی');
|
||||
}
|
||||
} finally {
|
||||
setSaving(null);
|
||||
}
|
||||
@ -182,7 +192,7 @@ export default function UITexts() {
|
||||
});
|
||||
const fileUrl = res.data.url || res.data.fileUrl;
|
||||
setEdits(prev => ({ ...prev, [key]: fileUrl }));
|
||||
await api.put(`/settings/ui-texts/${key}`, { value: fileUrl });
|
||||
await api.put(`/settings/ui-texts/${encodeURIComponent(key)}`, { value: fileUrl });
|
||||
setTexts(prev => ({ ...prev, [key]: fileUrl }));
|
||||
setSavedKey(key);
|
||||
toast.success('تصویر با موفقیت آپلود شد');
|
||||
|
||||
@ -5,6 +5,7 @@ import Hero from "../components/Hero";
|
||||
import SmartAdvisor from "../components/SmartAdvisor";
|
||||
import FeaturedProducts from "../components/FeaturedProducts";
|
||||
import VetGallery from "../components/VetGallery";
|
||||
import BannerPlacement from "../components/BannerPlacement";
|
||||
import Link from 'next/link';
|
||||
import { toPersian } from "../lib/utils";
|
||||
import { useSettingsStore } from "../lib/store/settingsStore";
|
||||
@ -58,6 +59,7 @@ export default function HomeClient({ initialData }: HomeClientProps) {
|
||||
<>
|
||||
<Hero banners={heroBanners} />
|
||||
<SmartAdvisor rules={rules} />
|
||||
<BannerPlacement banners={banners} position="home_middle" />
|
||||
<FeaturedProducts />
|
||||
<VetGallery testimonials={testimonials} />
|
||||
|
||||
@ -158,6 +160,9 @@ export default function HomeClient({ initialData }: HomeClientProps) {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Bottom Banner Placement */}
|
||||
<BannerPlacement banners={banners} position="home_bottom" />
|
||||
|
||||
{/* Call to Action */}
|
||||
<section className="bg-canina-blue py-12 relative overflow-hidden font-sans">
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
|
||||
@ -28,6 +28,8 @@ import {
|
||||
import { useCartStore } from "../lib/store/cartStore";
|
||||
import { useSettingsStore } from "../lib/store/settingsStore";
|
||||
import SafeImage from "./SafeImage";
|
||||
import BannerPlacement from "./BannerPlacement";
|
||||
import { Banner } from "../lib/types";
|
||||
|
||||
const ICON_MAP: Record<string, React.ReactNode> = {
|
||||
joints: <HeartPulse className="w-4 h-4" />,
|
||||
@ -248,11 +250,15 @@ export default function ArchivePage({
|
||||
const [symptomSearch, setSymptomSearch] = useState("");
|
||||
const [categories, setCategories] = useState<{ id: string; label: string; icon: React.ReactNode }[]>([]);
|
||||
const [symptoms, setSymptoms] = useState<string[]>([]);
|
||||
const [banners, setBanners] = useState<Banner[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const loadFilters = async () => {
|
||||
const loadFiltersAndBanners = async () => {
|
||||
try {
|
||||
const filters = await productService.getActiveFilters();
|
||||
const [filters, bannerData] = await Promise.all([
|
||||
productService.getActiveFilters(),
|
||||
productService.getBanners()
|
||||
]);
|
||||
const mapped = [
|
||||
{ id: "all", label: "همه محصولات", icon: <Activity className="w-4 h-4" /> },
|
||||
...filters.categories.map(c => ({
|
||||
@ -263,11 +269,14 @@ export default function ArchivePage({
|
||||
];
|
||||
setCategories(mapped);
|
||||
setSymptoms(filters.symptoms);
|
||||
if (Array.isArray(bannerData)) {
|
||||
setBanners(bannerData);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to load active filters:", e);
|
||||
console.error("Failed to load active filters or banners:", e);
|
||||
}
|
||||
};
|
||||
loadFilters();
|
||||
loadFiltersAndBanners();
|
||||
}, []);
|
||||
|
||||
const CATEGORY_LABELS = useMemo(() => {
|
||||
@ -416,6 +425,9 @@ export default function ArchivePage({
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Shop Top Banner Placement */}
|
||||
<BannerPlacement banners={banners} position="shop_top" className="!px-0 !py-2 mb-6" />
|
||||
|
||||
<div className="flex flex-col lg:flex-row gap-8">
|
||||
|
||||
{/* Mobile Overlay Backdrop */}
|
||||
|
||||
202
frontend/application/components/BannerPlacement.tsx
Normal file
202
frontend/application/components/BannerPlacement.tsx
Normal file
@ -0,0 +1,202 @@
|
||||
/* 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>
|
||||
);
|
||||
}
|
||||
100
frontend/application/components/BrandLogo.tsx
Normal file
100
frontend/application/components/BrandLogo.tsx
Normal file
@ -0,0 +1,100 @@
|
||||
/* 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;
|
||||
}
|
||||
@ -217,11 +217,11 @@ export default function CheckoutPage() {
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-3 pt-2">
|
||||
<a
|
||||
href="tel:02188888888"
|
||||
href={getText('contact_phone_link', `tel:${getText('contact_phone', '02188884444').replace(/[^0-9+]/g, '')}`)}
|
||||
className="flex-1 px-4 py-3 bg-canina-blue hover:bg-canina-dark text-white rounded-xl text-xs font-black transition-all shadow-md flex items-center justify-center gap-2"
|
||||
>
|
||||
<Phone className="w-4 h-4" />
|
||||
<span>تماس با پشتیبانی</span>
|
||||
<span>تماس با پشتیبانی ({getText('contact_phone', '۰۲۱-۸۸۸۸۴۴۴۴')})</span>
|
||||
</a>
|
||||
<button
|
||||
onClick={() => router.push('/shop')}
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Phone, Mail, MapPin, Instagram, ShieldCheck, Globe, ArrowUp, Download } from "lucide-react";
|
||||
import { useSettingsStore } from "../lib/store/settingsStore";
|
||||
import BrandLogo from "./BrandLogo";
|
||||
import Link from "next/link";
|
||||
|
||||
import { NavigationTarget } from "../lib/types";
|
||||
@ -53,22 +54,21 @@ export default function Footer({
|
||||
|
||||
{/* Brand Presence */}
|
||||
<div className="space-y-8">
|
||||
<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>
|
||||
<BrandLogo isDarkBackground size="lg" />
|
||||
<p className="text-sm text-slate-300 leading-relaxed font-medium">
|
||||
{getText('footer_brand_desc', "تدارک هوشمندانه سلامت برای همراهان وفادار شما. واردکننده انحصاری مکملهای درمانی با گرید دارویی اختصاصی از آلمان با سابقه ۴۰ سال نوآوری.")}
|
||||
</p>
|
||||
<div className="flex gap-4">
|
||||
{[Instagram, Globe].map((Icon, i) => (
|
||||
<a key={i} href="#" className="w-10 h-10 bg-white/10 rounded-xl flex items-center justify-center hover:bg-canina-blue hover:text-white transition-all text-slate-300">
|
||||
<Icon className="w-5 h-5" />
|
||||
{getText('contact_instagram', '') && (
|
||||
<a href={getText('contact_instagram', '#')} target="_blank" rel="noopener noreferrer" className="w-10 h-10 bg-white/10 rounded-xl flex items-center justify-center hover:bg-canina-blue hover:text-white transition-all text-slate-300">
|
||||
<Instagram className="w-5 h-5" />
|
||||
</a>
|
||||
))}
|
||||
)}
|
||||
{getText('contact_telegram', '') && (
|
||||
<a href={getText('contact_telegram', '#')} target="_blank" rel="noopener noreferrer" className="w-10 h-10 bg-white/10 rounded-xl flex items-center justify-center hover:bg-canina-blue hover:text-white transition-all text-slate-300">
|
||||
<Globe className="w-5 h-5" />
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -132,24 +132,24 @@ export default function Footer({
|
||||
<h4 className="text-xl font-bold text-white border-b-2 border-canina-blue pb-2 max-w-max font-lalezar">{getText('footer_contact_title', "اطلاعات تماس نمایندگی")}</h4>
|
||||
<div className="grid md:grid-cols-2 gap-8">
|
||||
<div className="space-y-6">
|
||||
<div className="flex gap-4 group">
|
||||
<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="w-12 h-12 bg-white/5 rounded-2xl flex items-center justify-center text-blue-400 group-hover:scale-110 transition-transform flex-shrink-0">
|
||||
<Phone className="w-6 h-6" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-[11px] text-slate-400 font-extrabold uppercase mb-1">{getText('footer_phone_label', "خط ویژه فروش")}</p>
|
||||
<p className="text-lg font-extrabold tracking-widest text-left text-slate-200" dir="ltr">{getText('CONTACT_PHONE', getText('footer_phone', "۰۲۱-۸۸۸۸ ۴۴۴۴"))}</p>
|
||||
<p className="text-lg font-extrabold tracking-widest text-left text-slate-200" dir="ltr">{getText('contact_phone', "۰۲۱-۸۸۸۸۴۴۴۴")}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-4 group">
|
||||
</a>
|
||||
<a href={`mailto:${getText('contact_email', 'info@canina.ir')}`} className="flex gap-4 group hover:opacity-90 transition-opacity">
|
||||
<div className="w-12 h-12 bg-white/5 rounded-2xl flex items-center justify-center text-blue-400 group-hover:scale-110 transition-transform flex-shrink-0">
|
||||
<Mail className="w-6 h-6" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-[11px] text-slate-400 font-extrabold uppercase mb-1">{getText('footer_email_label', "مکاتبات رسمی")}</p>
|
||||
<p className="text-sm font-bold text-slate-300">{getText('CONTACT_EMAIL', getText('footer_email', "info@canina-iran.com"))}</p>
|
||||
</div>
|
||||
<p className="text-sm font-bold text-slate-300">{getText('contact_email', "info@canina.ir")}</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4 group">
|
||||
@ -158,7 +158,7 @@ export default function Footer({
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-[11px] text-slate-400 font-extrabold uppercase mb-1">{getText('footer_address_label', "دفتر مرکزی")}</p>
|
||||
<p className="text-sm font-bold leading-relaxed text-slate-300">{getText('CONTACT_ADDRESS', getText('footer_address', "تهران، جردن، خیابان سعیدی، ساختمان کنینا، طبقه ۵، واحد ۱۹"))}</p>
|
||||
<p className="text-sm font-bold leading-relaxed text-slate-300">{getText('contact_address', "تهران، جردن، خیابان سعیدی، ساختمان کنینا، طبقه ۵، واحد ۱۹")}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -12,6 +12,7 @@ import { toast } from "sonner";
|
||||
import AuthModal from "./AuthModal";
|
||||
import PrescriptionUploadModal from "./PrescriptionUploadModal";
|
||||
import TickerBanner from "./TickerBanner";
|
||||
import BrandLogo from "./BrandLogo";
|
||||
import { cn } from "../lib/utils";
|
||||
import { productService } from "../lib/services/productService";
|
||||
|
||||
@ -149,30 +150,7 @@ export default function Header({
|
||||
{isMobileMenuOpen ? <X className="w-6 h-6" /> : <Menu className="w-6 h-6" />}
|
||||
</button>
|
||||
|
||||
<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>
|
||||
<BrandLogo />
|
||||
</div>
|
||||
|
||||
{/* Center Search Input (Desktop) */}
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
"use client";
|
||||
import { motion, animate } from "motion/react";
|
||||
import { ChevronLeft, Globe, ShieldCheck, Calendar } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { motion, AnimatePresence, animate } from "motion/react";
|
||||
import { ChevronLeft, ChevronRight, Globe, ShieldCheck, Calendar, ArrowLeft } from "lucide-react";
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
import { toPersian } from "../lib/utils";
|
||||
import { useSettingsStore } from "../lib/store/settingsStore";
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { Banner } from "../lib/types";
|
||||
|
||||
function StatCounter({ target }: { target: number }) {
|
||||
const [displayValue, setDisplayValue] = useState(0);
|
||||
@ -12,7 +14,7 @@ function StatCounter({ target }: { target: number }) {
|
||||
useEffect(() => {
|
||||
const controls = animate(0, target, {
|
||||
duration: 3,
|
||||
ease: [0.16, 1, 0.3, 1], // Custom cubic-bezier for a more polished feel
|
||||
ease: [0.16, 1, 0.3, 1],
|
||||
onUpdate: (latest) => setDisplayValue(Math.round(latest))
|
||||
});
|
||||
return controls.stop;
|
||||
@ -35,28 +37,55 @@ function StatCounter({ target }: { target: number }) {
|
||||
);
|
||||
}
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { Banner } from "../lib/types";
|
||||
|
||||
export default function Hero({ banners = [], onShopNavigate }: { banners?: Banner[]; onShopNavigate?: () => void }) {
|
||||
const router = useRouter();
|
||||
const getText = useSettingsStore((state) => state?.getText || ((_k: string, fb: string) => fb));
|
||||
const isInitialized = useSettingsStore((state) => state?.isInitialized ?? false);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [currentSlide, setCurrentSlide] = useState(0);
|
||||
const [isPaused, setIsPaused] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
const defaultTitle = banners.length > 0 && banners[0].title ? banners[0].title : "تخصص آلمانی در خدمت\nسلامت پتهای خانگی";
|
||||
const defaultSubtitle = banners.length > 0 && banners[0].subtitle ? banners[0].subtitle : 'از سال ۱۹۸۴، شرکت Canina pharma GmbH در برگیش گلادباخ آلمان، با بهرهگیری از مواد اولیه طبیعی و فرآیندهای پیشرفته، استاندارد طلایی مکملهای دامپزشکی را تعریف میکند. کنینا نماینده رسمی این برند در ایران است.';
|
||||
const heroBanners = banners.filter(b => b.isActive !== false && b.imageUrl);
|
||||
const totalSlides = heroBanners.length;
|
||||
|
||||
// Auto-advance slider
|
||||
useEffect(() => {
|
||||
if (totalSlides <= 1 || isPaused) return;
|
||||
const interval = setInterval(() => {
|
||||
setCurrentSlide(prev => (prev + 1) % totalSlides);
|
||||
}, 5000);
|
||||
return () => clearInterval(interval);
|
||||
}, [totalSlides, isPaused]);
|
||||
|
||||
const activeBanner = totalSlides > 0 ? heroBanners[currentSlide] : null;
|
||||
|
||||
const defaultTitle = activeBanner?.title || (banners.length > 0 && banners[0].title ? banners[0].title : "تخصص آلمانی در خدمت\nسلامت پتهای خانگی");
|
||||
const defaultSubtitle = activeBanner?.subtitle || (banners.length > 0 && banners[0].subtitle ? banners[0].subtitle : 'از سال ۱۹۸۴، شرکت Canina pharma GmbH در برگیش گلادباخ آلمان، با بهرهگیری از مواد اولیه طبیعی و فرآیندهای پیشرفته، استاندارد طلایی مکملهای دامپزشکی را تعریف میکند. کنینا نماینده رسمی این برند در ایران است.');
|
||||
|
||||
const title = (mounted && isInitialized ? getText('hero_title', '') : '') || defaultTitle;
|
||||
const subtitle = (mounted && isInitialized ? getText('hero_desc', '') : '') || defaultSubtitle;
|
||||
const titleParts = title.split('\n');
|
||||
|
||||
const activeImageUrl = activeBanner?.imageUrl || (mounted && isInitialized ? getText('hero_image_url', '') : '') || (banners.length > 0 && banners[0].imageUrl ? banners[0].imageUrl : "/assets/images/hero-section-image.png");
|
||||
|
||||
const handleNextSlide = () => {
|
||||
if (totalSlides > 1) {
|
||||
setCurrentSlide(prev => (prev + 1) % totalSlides);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePrevSlide = () => {
|
||||
if (totalSlides > 1) {
|
||||
setCurrentSlide(prev => (prev - 1 + totalSlides) % totalSlides);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="relative overflow-hidden bg-white py-20 lg:py-32 border-b border-medical-gray-100">
|
||||
<section className="relative overflow-hidden bg-white py-16 lg:py-28 border-b border-medical-gray-100">
|
||||
{/* Background patterns */}
|
||||
<div className="absolute top-0 left-0 w-full h-full opacity-[0.03] pointer-events-none select-none overflow-hidden">
|
||||
<div className="absolute top-0 right-0 w-[800px] h-[800px] bg-canina-blue rounded-full blur-[120px] -mr-96 -mt-96" />
|
||||
@ -119,56 +148,114 @@ export default function Hero({ banners = [], onShopNavigate }: { banners?: Banne
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Visual Element */}
|
||||
<div className="lg:w-1/2 relative">
|
||||
{/* Visual Element & Interactive Slider */}
|
||||
<div
|
||||
className="lg:w-1/2 relative w-full max-w-lg lg:max-w-none mx-auto"
|
||||
onMouseEnter={() => setIsPaused(true)}
|
||||
onMouseLeave={() => setIsPaused(false)}
|
||||
>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.9, rotate: -5 }}
|
||||
initial={{ opacity: 0, scale: 0.9, rotate: -3 }}
|
||||
animate={{ opacity: 1, scale: 1, rotate: 0 }}
|
||||
transition={{ duration: 1, ease: "easeOut" }}
|
||||
transition={{ duration: 0.8, ease: "easeOut" }}
|
||||
className="relative z-10"
|
||||
>
|
||||
<div className="aspect-square bg-gradient-to-br from-slate-200 to-slate-300 rounded-[2.5rem] overflow-hidden border-4 border-white shadow-2xl relative">
|
||||
<img
|
||||
src={(mounted && isInitialized ? getText('hero_image_url', '') : '') || (banners.length > 0 && banners[0].imageUrl ? banners[0].imageUrl : "/assets/images/hero-section-image.png")}
|
||||
alt={banners.length > 0 ? banners[0].title : "Canina Pharma Germany"}
|
||||
<div className="aspect-square bg-gradient-to-br from-slate-200 to-slate-300 rounded-[2.5rem] overflow-hidden border-4 border-white shadow-2xl relative group">
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.img
|
||||
key={activeImageUrl}
|
||||
src={activeImageUrl}
|
||||
alt={activeBanner?.title || "Canina Pharma Germany"}
|
||||
initial={{ opacity: 0, scale: 1.05 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.98 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="w-full h-full object-cover"
|
||||
loading="eager"
|
||||
referrerPolicy="no-referrer"
|
||||
/>
|
||||
<div className="absolute bottom-0 left-0 right-0 p-8 bg-gradient-to-t from-black/80 to-transparent text-white text-right">
|
||||
</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]">
|
||||
{mounted && isInitialized ? getText('hero_image_badge', "سرآمد علمی در پزشکی پتها") : "سرآمد علمی در پزشکی پتها"}
|
||||
</div>
|
||||
<div className="text-xl font-bold italic tracking-tighter">
|
||||
{mounted && isInitialized ? getText('hero_image_title', "مکملهای تایید شده دامپزشکی با گواهی IFS") : "مکملهای تایید شده دامپزشکی با گواهی IFS"}
|
||||
<div className="text-lg sm:text-xl font-bold italic tracking-tighter">
|
||||
{activeBanner?.title || (mounted && isInitialized ? getText('hero_image_title', "مکملهای تایید شده دامپزشکی با گواهی IFS") : "مکملهای تایید شده دامپزشکی با گواهی IFS")}
|
||||
</div>
|
||||
{activeBanner?.linkUrl && (
|
||||
<a
|
||||
href={activeBanner.linkUrl}
|
||||
className="inline-flex items-center gap-1 text-xs text-canina-gold hover:underline font-bold mt-2"
|
||||
>
|
||||
<span>اطلاعات بیشتر و خرید</span>
|
||||
<ArrowLeft className="w-3.5 h-3.5" />
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Quality Badge inside the image container to prevent text collision */}
|
||||
{/* Slider Navigation Arrows (shown if multiple slides exist) */}
|
||||
{totalSlides > 1 && (
|
||||
<>
|
||||
<button
|
||||
onClick={handlePrevSlide}
|
||||
className="absolute right-4 top-1/2 -translate-y-1/2 w-10 h-10 rounded-full bg-black/40 hover:bg-black/70 text-white backdrop-blur-md flex items-center justify-center transition-all opacity-0 group-hover:opacity-100 z-30"
|
||||
aria-label="اسلاید قبلی"
|
||||
>
|
||||
<ChevronRight className="w-5 h-5" />
|
||||
</button>
|
||||
<button
|
||||
onClick={handleNextSlide}
|
||||
className="absolute left-4 top-1/2 -translate-y-1/2 w-10 h-10 rounded-full bg-black/40 hover:bg-black/70 text-white backdrop-blur-md flex items-center justify-center transition-all opacity-0 group-hover:opacity-100 z-30"
|
||||
aria-label="اسلاید بعدی"
|
||||
>
|
||||
<ChevronLeft className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
{/* Dot Indicators */}
|
||||
<div className="absolute bottom-4 left-1/2 -translate-x-1/2 flex items-center gap-2 z-30 bg-black/40 backdrop-blur-md px-3 py-1.5 rounded-full">
|
||||
{heroBanners.map((_, idx) => (
|
||||
<button
|
||||
key={idx}
|
||||
onClick={() => setCurrentSlide(idx)}
|
||||
className={`transition-all rounded-full ${
|
||||
currentSlide === idx
|
||||
? "w-6 h-2 bg-canina-gold"
|
||||
: "w-2 h-2 bg-white/60 hover:bg-white"
|
||||
}`}
|
||||
aria-label={`اسلاید ${idx + 1}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Quality Badge inside the image container */}
|
||||
<motion.div
|
||||
animate={{ rotate: 360 }}
|
||||
transition={{ duration: 20, repeat: Infinity, ease: "linear" }}
|
||||
className="absolute top-6 right-6 lg:top-8 lg:right-8 z-20 w-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"
|
||||
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"
|
||||
>
|
||||
<div className="flex flex-col items-center">
|
||||
<span className="text-xl lg:text-2xl font-black text-canina-blue leading-none tracking-tighter">DE</span>
|
||||
<span className="text-[7px] lg:text-[8px] font-bold text-medical-gray-500 uppercase tracking-widest mt-0.5 leading-tight">
|
||||
{mounted && isInitialized ? getText('hero_quality_standard', "استاندارد کیفی آلمان (IFS & HACCP)") : "استاندارد کیفی آلمان (IFS & HACCP)"}
|
||||
<span className="text-lg lg:text-xl font-black text-canina-blue leading-none tracking-tighter">DE</span>
|
||||
<span className="text-[6px] lg:text-[7px] font-bold text-medical-gray-500 uppercase tracking-widest mt-0.5 leading-tight">
|
||||
{mounted && isInitialized ? getText('hero_quality_standard', "استاندارد کیفی آلمان") : "استاندارد کیفی آلمان"}
|
||||
</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* 3D Action Badge clarifying the 3D mark */}
|
||||
<div className="absolute bottom-28 right-6 bg-canina-gold text-medical-gray-900 px-4 py-2 rounded-2xl text-xs font-black shadow-2xl z-20 flex items-center gap-1.5 font-vazir border border-white/20 select-none animate-bounce">
|
||||
{/* 3D Action Badge */}
|
||||
<div className="absolute bottom-24 right-6 bg-canina-gold text-medical-gray-900 px-3.5 py-1.5 rounded-2xl text-xs font-black shadow-2xl z-20 flex items-center gap-1.5 font-vazir border border-white/20 select-none animate-bounce">
|
||||
<span className="text-sm">✨</span>
|
||||
<span>فرمول ۳کاره (تاثیر سهبعدی)</span>
|
||||
<span>فرمول ۳کاره</span>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats - 3 side-by-side cards with centered text on all screens */}
|
||||
{/* Stats */}
|
||||
<div className="max-w-7xl mx-auto px-2 sm:px-4 relative">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
import React from "react";
|
||||
import { Wrench, Phone, ShieldCheck, Clock } from "lucide-react";
|
||||
import { useSettingsStore } from "../lib/store/settingsStore";
|
||||
import BrandLogo from "./BrandLogo";
|
||||
|
||||
export default function MaintenancePage() {
|
||||
const getText = useSettingsStore((state) => state.getText);
|
||||
@ -13,8 +14,8 @@ export default function MaintenancePage() {
|
||||
);
|
||||
const badge = getText("maintenance_badge", "سامانه در حال بهروزرسانی و ارتقا");
|
||||
const eta = getText("maintenance_eta", "زمان تقریبی بازگشایی: کمتر از ۲ ساعت آینده");
|
||||
const phone = getText("maintenance_contact_phone", "۰۲۱-۸۸۸۸۸۸۸۸");
|
||||
const rawPhone = phone.replace(/[^0-9]/g, "");
|
||||
const phone = getText("contact_phone", getText("maintenance_contact_phone", "۰۲۱-۸۸۸۸۴۴۴۴"));
|
||||
const phoneLink = getText("contact_phone_link", getText("maintenance_contact_phone_link", `tel:${phone.replace(/[^0-9+]/g, "") || "02188884444"}`));
|
||||
|
||||
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">
|
||||
@ -23,8 +24,13 @@ export default function MaintenancePage() {
|
||||
<div className="absolute bottom-10 right-10 w-80 h-80 bg-canina-gold/10 rounded-full blur-3xl pointer-events-none" />
|
||||
|
||||
<div className="relative z-10 max-w-xl mx-auto space-y-6">
|
||||
<div className="w-24 h-24 bg-white/10 border border-white/20 rounded-3xl mx-auto flex items-center justify-center text-canina-gold shadow-2xl backdrop-blur-md">
|
||||
<Wrench className="w-12 h-12 animate-bounce" />
|
||||
{/* Brand Logo */}
|
||||
<div className="flex justify-center mb-2">
|
||||
<BrandLogo isDarkBackground size="lg" href="" />
|
||||
</div>
|
||||
|
||||
<div className="w-20 h-20 bg-white/10 border border-white/20 rounded-3xl mx-auto flex items-center justify-center text-canina-gold shadow-2xl backdrop-blur-md">
|
||||
<Wrench className="w-10 h-10 animate-bounce" />
|
||||
</div>
|
||||
|
||||
<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">
|
||||
@ -48,7 +54,7 @@ export default function MaintenancePage() {
|
||||
|
||||
<div className="pt-2 flex flex-col sm:flex-row items-center justify-center gap-4">
|
||||
<a
|
||||
href={`tel:${rawPhone || "02188888888"}`}
|
||||
href={phoneLink}
|
||||
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" />
|
||||
|
||||
@ -693,11 +693,11 @@ export default function ProductPage({ productSlug }: { productSlug: string }) {
|
||||
{isCartDisabled ? (
|
||||
<div className="space-y-3">
|
||||
<a
|
||||
href="tel:02188888888"
|
||||
href={getText('contact_phone_link', `tel:${getText('contact_phone', '02188884444').replace(/[^0-9+]/g, '')}`)}
|
||||
className="w-full flex items-center justify-center gap-2 px-4 bg-canina-blue text-white rounded-2xl h-16 font-black text-sm hover:bg-canina-dark transition-all shadow-xl shadow-canina-blue/20 font-vazir"
|
||||
>
|
||||
<Phone className="w-5 h-5" />
|
||||
<span>مشاوره و استعلام خرید</span>
|
||||
<span>مشاوره و استعلام خرید ({getText('contact_phone', '۰۲۱-۸۸۸۸۴۴۴۴')})</span>
|
||||
</a>
|
||||
<p className="text-[11px] text-medical-gray-400 font-bold text-center">
|
||||
خرید آنلاین در حالت کاتالوگ موقتاً غیرفعال است
|
||||
@ -916,7 +916,7 @@ export default function ProductPage({ productSlug }: { productSlug: string }) {
|
||||
<div className="flex items-center gap-2 flex-1 max-w-[240px]">
|
||||
{isCartDisabled ? (
|
||||
<a
|
||||
href="tel:02188888888"
|
||||
href={getText('contact_phone_link', `tel:${getText('contact_phone', '02188884444').replace(/[^0-9+]/g, '')}`)}
|
||||
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" />
|
||||
|
||||
@ -251,6 +251,16 @@ export class ProductService {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async getBanners(): Promise<any[]> {
|
||||
try {
|
||||
const response = await api.get('/banners');
|
||||
return response.data || [];
|
||||
} catch (error) {
|
||||
console.error("[ProductService] Failed to fetch banners:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const productService = ProductService.getInstance();
|
||||
|
||||
@ -54,17 +54,34 @@ export const useSettingsStore = create<SettingsStore>()((set, get) => ({
|
||||
}
|
||||
},
|
||||
getText: (key, fallback) => {
|
||||
const value = get().texts[key];
|
||||
if (value === undefined || value === null) {
|
||||
const texts = get().texts;
|
||||
if (!texts) return fallback;
|
||||
const value =
|
||||
texts[key] ??
|
||||
texts[key.toLowerCase()] ??
|
||||
texts[key.toUpperCase()];
|
||||
if (value === undefined || value === null || value === '') {
|
||||
// Special aliases
|
||||
if (key === 'contact_phone') return texts['CONTACT_PHONE'] || texts['footer_phone'] || texts['maintenance_contact_phone'] || fallback;
|
||||
if (key === 'contact_phone_link') return texts['CONTACT_PHONE_LINK'] || texts['maintenance_contact_phone_link'] || (texts['contact_phone'] ? `tel:${texts['contact_phone'].replace(/[^0-9+]/g, '')}` : fallback);
|
||||
if (key === 'site_logo') return texts['BRAND_LOGO_URL'] || texts['brand_logo_url'] || fallback;
|
||||
if (key === 'site_logo_text_en') return texts['BRAND_LOGO_TEXT_EN'] || fallback;
|
||||
if (key === 'site_logo_text_fa') return texts['BRAND_LOGO_TEXT_FA'] || texts['brand_name_fa'] || fallback;
|
||||
if (key === 'site_logo_subtitle') return texts['BRAND_LOGO_SUBTITLE'] || texts['brand_subtitle'] || fallback;
|
||||
return fallback;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
getBoolean: (key, fallback) => {
|
||||
const value = get().texts[key];
|
||||
if (value === undefined || value === null) {
|
||||
const texts = get().texts;
|
||||
if (!texts) return fallback;
|
||||
const value =
|
||||
texts[key] ??
|
||||
texts[key.toLowerCase()] ??
|
||||
texts[key.toUpperCase()];
|
||||
if (value === undefined || value === null || value === '') {
|
||||
return fallback;
|
||||
}
|
||||
return value === 'true' || value === '1';
|
||||
return value === 'true' || value === '1' || (value as unknown) === true;
|
||||
}
|
||||
}));
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -7,41 +7,41 @@
|
||||
"5": "SmsService",
|
||||
"6": "AuthController",
|
||||
"7": "app.module.ts",
|
||||
"8": "VideosService",
|
||||
"9": "products.ts",
|
||||
"8": "CreateVideoDto",
|
||||
"9": "ProductService",
|
||||
"10": "ContactService",
|
||||
"11": "compilerOptions",
|
||||
"12": "toPersian",
|
||||
"12": "UserDashboard.tsx",
|
||||
"13": "auth.controller.ts",
|
||||
"14": "ProductsService",
|
||||
"15": "useCartStore",
|
||||
"16": "eslint",
|
||||
"17": "useSettingsStore",
|
||||
"15": "lib/services/api.ts",
|
||||
"16": "tickets.controller.ts",
|
||||
"17": "ArchivePage.tsx",
|
||||
"18": "MetricsController",
|
||||
"19": "B2B Inquiry Controller",
|
||||
"19": "B2BService",
|
||||
"20": "CategoriesController",
|
||||
"21": "RedisService",
|
||||
"22": "MediaController",
|
||||
"23": "Ingredient Management Controller",
|
||||
"24": "adminRoutes.tsx",
|
||||
"25": "admin.module.ts",
|
||||
"26": "ConfirmModal.tsx",
|
||||
"26": "Coupons.tsx",
|
||||
"27": "Prescription Review Controller",
|
||||
"28": "Smart Advisor Controller",
|
||||
"29": "Testimonials Management Controller",
|
||||
"30": "api",
|
||||
"30": "src/services/api.ts",
|
||||
"31": "Spinner.tsx",
|
||||
"32": "PetProfile.tsx",
|
||||
"33": "ZibalService",
|
||||
"34": "main.ts",
|
||||
"35": "src/services/api.ts",
|
||||
"36": "Backend TypeScript Config",
|
||||
"37": "App TypeScript Config",
|
||||
"38": "Pagination.tsx",
|
||||
"35": "admin.ts",
|
||||
"36": "compilerOptions",
|
||||
"37": "compilerOptions",
|
||||
"38": "Transactions.tsx",
|
||||
"39": "dependencies",
|
||||
"40": "Node TypeScript Config",
|
||||
"41": "Admin Blog Controller",
|
||||
"42": "WholesaleService",
|
||||
"42": "WholesaleApplyDto",
|
||||
"43": "devDependencies",
|
||||
"44": "Generic CRUD Controller",
|
||||
"45": "seo.module.ts",
|
||||
@ -52,16 +52,16 @@
|
||||
"50": "Database Seeding Logic",
|
||||
"51": "Prisma Database Migrations",
|
||||
"52": "dependencies",
|
||||
"53": "UI Skeleton and Tables",
|
||||
"53": "Orders.tsx",
|
||||
"54": "auth.service.ts",
|
||||
"55": "BE-001",
|
||||
"56": "SafeImage.tsx",
|
||||
"56": "VetGallery.tsx",
|
||||
"57": "NPM Lifecycle Scripts",
|
||||
"58": "Pet Management API",
|
||||
"58": "PetsController",
|
||||
"59": "PrismaService",
|
||||
"60": "Jest Testing Config",
|
||||
"61": "WikiController",
|
||||
"62": "videos.controller.ts",
|
||||
"61": "CreateReviewDto",
|
||||
"62": "BannersService",
|
||||
"63": "FE-001",
|
||||
"64": "VerifyOtpDto",
|
||||
"65": "rules/graphify.md",
|
||||
@ -73,10 +73,10 @@
|
||||
"71": "Analytics and Report Charts",
|
||||
"72": "Task Orchestration Scripts",
|
||||
"73": "Backend Package Config",
|
||||
"74": "RegisterDto",
|
||||
"74": "useSettingsStore",
|
||||
"75": "React Error Boundary",
|
||||
"76": "Application Package Config",
|
||||
"77": ".findAll",
|
||||
"77": "WikiController",
|
||||
"78": "Error and Not Found Pages",
|
||||
"79": "VPN Utility Scripts",
|
||||
"80": "NestJS CLI Config",
|
||||
@ -89,7 +89,7 @@
|
||||
"87": "Font Assets and Licenses",
|
||||
"88": "Ledger Rebuild Scripts",
|
||||
"89": "Evidence Validation Scripts",
|
||||
"90": "CreateVideoDto",
|
||||
"90": "SslController",
|
||||
"91": "Blog Post Detail Page",
|
||||
"92": "@types/node",
|
||||
"93": "devDependencies",
|
||||
@ -99,7 +99,7 @@
|
||||
"97": "Home Management DTOs",
|
||||
"98": "Wiki Management DTOs",
|
||||
"99": "Wiki Page Routing",
|
||||
"100": "ClientLayout.tsx",
|
||||
"100": "toPersian",
|
||||
"101": "Docker Deployment Scripts",
|
||||
"102": "DB-001",
|
||||
"103": "BlogsService",
|
||||
@ -116,10 +116,10 @@
|
||||
"114": "Manifest Data Generation",
|
||||
"115": "Honest Manifest Synchronization",
|
||||
"116": "Manifest Entry Synchronization",
|
||||
"117": "SendOtpDto",
|
||||
"117": "PaymentController",
|
||||
"118": "TS-001",
|
||||
"119": "TEST-001",
|
||||
"120": "payment.service.ts",
|
||||
"120": "AdminTransactionFilterDto",
|
||||
"121": "UITexts.tsx",
|
||||
"122": "Admin Panel TSConfig",
|
||||
"123": "About Page Component",
|
||||
@ -140,8 +140,8 @@
|
||||
"138": "Operational Rules & Boundaries",
|
||||
"139": "Operational Rules & Boundaries",
|
||||
"140": "Bcrypt Type Definitions",
|
||||
"141": "bcryptjs",
|
||||
"142": "helmet",
|
||||
"141": "SettingsService",
|
||||
"142": ".update",
|
||||
"143": "Blog Entity Model",
|
||||
"144": "Home Entity Model",
|
||||
"145": "Wiki Entity Model",
|
||||
@ -206,7 +206,7 @@
|
||||
"204": "backend/README.md",
|
||||
"205": "Repository Map",
|
||||
"206": "Sahel-Font",
|
||||
"207": "AuthService",
|
||||
"207": "zibal.service.ts",
|
||||
"208": "Sahel-Font",
|
||||
"209": "Role & Core Objective",
|
||||
"210": "exclude",
|
||||
@ -236,8 +236,8 @@
|
||||
"234": "🔒 Security & Performance Review (09_devops_security)",
|
||||
"235": "👁️ UX & Persona Interface Review (08_visual_qa)",
|
||||
"236": "Compiler Diagnostic Dispositions",
|
||||
"237": "OrderService",
|
||||
"238": "js-yaml",
|
||||
"237": "useCartStore",
|
||||
"238": "ProductPage.tsx",
|
||||
"239": "globals",
|
||||
"240": "prettier",
|
||||
"241": "prisma",
|
||||
@ -265,27 +265,25 @@
|
||||
"263": "application/public/fonts/sahel-font-v3.4.0/CHANGELOG.md",
|
||||
"264": "tailwindcss",
|
||||
"265": "typescript-eslint",
|
||||
"266": "@nestjs/core",
|
||||
"267": "@nestjs/jwt",
|
||||
"268": "@nestjs/swagger",
|
||||
"269": "@nestjs/throttler",
|
||||
"270": "passport-jwt",
|
||||
"271": "@prisma/client",
|
||||
"272": "swagger-ui-express",
|
||||
"266": ".handleZibalCallback",
|
||||
"267": "PetsService",
|
||||
"268": "PetsController",
|
||||
"269": "generate-openapi.js",
|
||||
"270": "InitiatePaymentDto",
|
||||
"271": "SmsLogQueryDto",
|
||||
"272": "CreateHealthLogDto",
|
||||
"273": "AGENTS.md",
|
||||
"274": "eslint-config-prettier",
|
||||
"275": "@eslint/js",
|
||||
"276": "jest",
|
||||
"277": "@nestjs/schematics",
|
||||
"278": "@nestjs/testing",
|
||||
"279": "source-map-support",
|
||||
"280": "ts-jest",
|
||||
"281": "tsconfig-paths",
|
||||
"282": "@types/bcryptjs",
|
||||
"283": "typescript-eslint",
|
||||
"284": "contact/page.tsx",
|
||||
"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",
|
||||
"286": "eslint-config-next",
|
||||
"288": "SmsSettingsPage.tsx",
|
||||
"290": "@tailwindcss/postcss"
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
.
|
||||
C:\Users\p.aghaei\Desktop\Work\parsa\git.parsaaghayi.ir\canina
|
||||
2883
graphify-out/2026-08-17/.graphify_analysis.json
Normal file
2883
graphify-out/2026-08-17/.graphify_analysis.json
Normal file
File diff suppressed because it is too large
Load Diff
289
graphify-out/2026-08-17/.graphify_labels.json
Normal file
289
graphify-out/2026-08-17/.graphify_labels.json
Normal file
@ -0,0 +1,289 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
1
graphify-out/2026-08-17/.graphify_semantic_marker
Normal file
1
graphify-out/2026-08-17/.graphify_semantic_marker
Normal file
@ -0,0 +1 @@
|
||||
{"output_tokens": 7105}
|
||||
1055
graphify-out/2026-08-17/GRAPH_REPORT.md
Normal file
1055
graphify-out/2026-08-17/GRAPH_REPORT.md
Normal file
File diff suppressed because it is too large
Load Diff
108850
graphify-out/2026-08-17/graph.json
Normal file
108850
graphify-out/2026-08-17/graph.json
Normal file
File diff suppressed because it is too large
Load Diff
3194
graphify-out/2026-08-17/manifest.json
Normal file
3194
graphify-out/2026-08-17/manifest.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,16 +1,16 @@
|
||||
# Graph Report - canina (2026-08-16)
|
||||
# Graph Report - canina (2026-08-17)
|
||||
|
||||
## Corpus Check
|
||||
- 467 files · ~685,071 words
|
||||
- 493 files · ~709,356 words
|
||||
- Verdict: corpus is large enough that graph structure adds value.
|
||||
|
||||
## Summary
|
||||
- 3274 nodes · 5352 edges · 289 communities (175 shown, 114 thin omitted)
|
||||
- Extraction: 96% EXTRACTED · 4% INFERRED · 0% AMBIGUOUS · INFERRED: 189 edges (avg confidence: 0.79)
|
||||
- 3516 nodes · 5922 edges · 287 communities (192 shown, 95 thin omitted)
|
||||
- Extraction: 96% EXTRACTED · 4% INFERRED · 0% AMBIGUOUS · INFERRED: 217 edges (avg confidence: 0.79)
|
||||
- Token cost: 0 input · 0 output
|
||||
|
||||
## Graph Freshness
|
||||
- Built from commit: `5c13dd2f`
|
||||
- Built from commit: `2fca7789`
|
||||
- Run `git rev-parse HEAD` and compare to check if the graph is stale.
|
||||
- Run `graphify update .` after code changes (no API cost).
|
||||
|
||||
@ -23,41 +23,41 @@
|
||||
- SmsService
|
||||
- AuthController
|
||||
- app.module.ts
|
||||
- VideosService
|
||||
- products.ts
|
||||
- CreateVideoDto
|
||||
- ProductService
|
||||
- ContactService
|
||||
- compilerOptions
|
||||
- toPersian
|
||||
- UserDashboard.tsx
|
||||
- auth.controller.ts
|
||||
- ProductsService
|
||||
- useCartStore
|
||||
- eslint
|
||||
- useSettingsStore
|
||||
- lib/services/api.ts
|
||||
- tickets.controller.ts
|
||||
- ArchivePage.tsx
|
||||
- MetricsController
|
||||
- B2B Inquiry Controller
|
||||
- B2BService
|
||||
- CategoriesController
|
||||
- RedisService
|
||||
- MediaController
|
||||
- Ingredient Management Controller
|
||||
- adminRoutes.tsx
|
||||
- admin.module.ts
|
||||
- ConfirmModal.tsx
|
||||
- Coupons.tsx
|
||||
- Prescription Review Controller
|
||||
- Smart Advisor Controller
|
||||
- Testimonials Management Controller
|
||||
- api
|
||||
- src/services/api.ts
|
||||
- Spinner.tsx
|
||||
- PetProfile.tsx
|
||||
- ZibalService
|
||||
- main.ts
|
||||
- src/services/api.ts
|
||||
- Backend TypeScript Config
|
||||
- App TypeScript Config
|
||||
- Pagination.tsx
|
||||
- admin.ts
|
||||
- compilerOptions
|
||||
- compilerOptions
|
||||
- Transactions.tsx
|
||||
- dependencies
|
||||
- Node TypeScript Config
|
||||
- Admin Blog Controller
|
||||
- WholesaleService
|
||||
- WholesaleApplyDto
|
||||
- devDependencies
|
||||
- Generic CRUD Controller
|
||||
- seo.module.ts
|
||||
@ -68,16 +68,16 @@
|
||||
- Database Seeding Logic
|
||||
- Prisma Database Migrations
|
||||
- dependencies
|
||||
- UI Skeleton and Tables
|
||||
- Orders.tsx
|
||||
- auth.service.ts
|
||||
- BE-001
|
||||
- SafeImage.tsx
|
||||
- VetGallery.tsx
|
||||
- NPM Lifecycle Scripts
|
||||
- Pet Management API
|
||||
- PetsController
|
||||
- PrismaService
|
||||
- Jest Testing Config
|
||||
- WikiController
|
||||
- videos.controller.ts
|
||||
- CreateReviewDto
|
||||
- BannersService
|
||||
- FE-001
|
||||
- VerifyOtpDto
|
||||
- rules/graphify.md
|
||||
@ -89,10 +89,10 @@
|
||||
- Analytics and Report Charts
|
||||
- Task Orchestration Scripts
|
||||
- Backend Package Config
|
||||
- RegisterDto
|
||||
- useSettingsStore
|
||||
- React Error Boundary
|
||||
- Application Package Config
|
||||
- .findAll
|
||||
- WikiController
|
||||
- Error and Not Found Pages
|
||||
- VPN Utility Scripts
|
||||
- NestJS CLI Config
|
||||
@ -105,7 +105,7 @@
|
||||
- Font Assets and Licenses
|
||||
- Ledger Rebuild Scripts
|
||||
- Evidence Validation Scripts
|
||||
- CreateVideoDto
|
||||
- SslController
|
||||
- Blog Post Detail Page
|
||||
- @types/node
|
||||
- devDependencies
|
||||
@ -115,7 +115,7 @@
|
||||
- Home Management DTOs
|
||||
- Wiki Management DTOs
|
||||
- Wiki Page Routing
|
||||
- ClientLayout.tsx
|
||||
- toPersian
|
||||
- Docker Deployment Scripts
|
||||
- DB-001
|
||||
- BlogsService
|
||||
@ -132,10 +132,10 @@
|
||||
- Manifest Data Generation
|
||||
- Honest Manifest Synchronization
|
||||
- Manifest Entry Synchronization
|
||||
- SendOtpDto
|
||||
- PaymentController
|
||||
- TS-001
|
||||
- TEST-001
|
||||
- payment.service.ts
|
||||
- AdminTransactionFilterDto
|
||||
- UITexts.tsx
|
||||
- Admin Panel TSConfig
|
||||
- About Page Component
|
||||
@ -156,8 +156,8 @@
|
||||
- Operational Rules & Boundaries
|
||||
- Operational Rules & Boundaries
|
||||
- Bcrypt Type Definitions
|
||||
- bcryptjs
|
||||
- helmet
|
||||
- SettingsService
|
||||
- .update
|
||||
- Blog Entity Model
|
||||
- Home Entity Model
|
||||
- Wiki Entity Model
|
||||
@ -215,7 +215,7 @@
|
||||
- backend/README.md
|
||||
- Repository Map
|
||||
- Sahel-Font
|
||||
- AuthService
|
||||
- zibal.service.ts
|
||||
- Sahel-Font
|
||||
- Role & Core Objective
|
||||
- exclude
|
||||
@ -245,8 +245,8 @@
|
||||
- 🔒 Security & Performance Review (09_devops_security)
|
||||
- 👁️ UX & Persona Interface Review (08_visual_qa)
|
||||
- Compiler Diagnostic Dispositions
|
||||
- OrderService
|
||||
- js-yaml
|
||||
- useCartStore
|
||||
- ProductPage.tsx
|
||||
- globals
|
||||
- prettier
|
||||
- prisma
|
||||
@ -267,41 +267,38 @@
|
||||
- typescript
|
||||
- vitest
|
||||
- typescript-eslint
|
||||
- @nestjs/core
|
||||
- @nestjs/jwt
|
||||
- @nestjs/swagger
|
||||
- @nestjs/throttler
|
||||
- passport-jwt
|
||||
- @prisma/client
|
||||
- swagger-ui-express
|
||||
- .handleZibalCallback
|
||||
- PetsService
|
||||
- PetsController
|
||||
- generate-openapi.js
|
||||
- InitiatePaymentDto
|
||||
- SmsLogQueryDto
|
||||
- CreateHealthLogDto
|
||||
- AGENTS.md
|
||||
- eslint-config-prettier
|
||||
- @eslint/js
|
||||
- jest
|
||||
- @nestjs/schematics
|
||||
- @nestjs/testing
|
||||
- source-map-support
|
||||
- ts-jest
|
||||
- tsconfig-paths
|
||||
- @types/bcryptjs
|
||||
- typescript-eslint
|
||||
- contact/page.tsx
|
||||
- CreateReminderDto
|
||||
- Reviews.tsx
|
||||
- Tickets.tsx
|
||||
- Videos.tsx
|
||||
- @types/passport-jwt
|
||||
- @types/supertest
|
||||
- typescript
|
||||
- @types/react-dom
|
||||
- eslint-plugin-prettier
|
||||
- eslint-config-next
|
||||
- SmsSettingsPage.tsx
|
||||
- @tailwindcss/postcss
|
||||
|
||||
## God Nodes (most connected - your core abstractions)
|
||||
1. `PrismaService` - 74 edges
|
||||
2. `Roles()` - 60 edges
|
||||
3. `PaginationDto` - 39 edges
|
||||
4. `SmsService` - 38 edges
|
||||
5. `api` - 34 edges
|
||||
6. `useSettingsStore` - 33 edges
|
||||
7. `AdminService` - 31 edges
|
||||
8. `toPersian()` - 31 edges
|
||||
9. `AdminController` - 30 edges
|
||||
10. `useCartStore` - 29 edges
|
||||
1. `PrismaService` - 79 edges
|
||||
2. `Roles()` - 73 edges
|
||||
3. `useSettingsStore` - 43 edges
|
||||
4. `PaginationDto` - 39 edges
|
||||
5. `SmsService` - 38 edges
|
||||
6. `api` - 37 edges
|
||||
7. `AdminService` - 34 edges
|
||||
8. `AdminController` - 33 edges
|
||||
9. `toPersian()` - 32 edges
|
||||
10. `JwtAuthGuard` - 31 edges
|
||||
|
||||
## Surprising Connections (you probably didn't know these)
|
||||
- `User Profile Photo` --conceptually_related_to--> `User Roles and Capabilities` [INFERRED]
|
||||
@ -324,43 +321,43 @@
|
||||
- **Rastikerdar Font Ecosystem** — frontend_application_public_fonts_shabnam_font_v5_0_1_readme, frontend_application_public_fonts_vazirmatn_v33_003_readme, vazir_font [EXTRACTED 0.95]
|
||||
- **Canina Deployment Stack** — scripts_compose_prod, scripts_compose_stage [EXTRACTED 1.00]
|
||||
|
||||
## Communities (289 total, 114 thin omitted)
|
||||
## Communities (287 total, 95 thin omitted)
|
||||
|
||||
### Community 0 - "AdminService"
|
||||
Cohesion: 0.06
|
||||
Nodes (24): AdminController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete (+16 more)
|
||||
Cohesion: 0.05
|
||||
Nodes (34): AdminController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete (+26 more)
|
||||
|
||||
### Community 1 - "pets/pets.controller.ts"
|
||||
Cohesion: 0.05
|
||||
Nodes (45): CreateHealthLogDto, ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString, CreatePetDto, ApiProperty (+37 more)
|
||||
Cohesion: 0.16
|
||||
Nodes (13): CreatePetDto, ApiProperty, ApiPropertyOptional, IsArray, IsNotEmpty, IsNumber, IsOptional, IsString (+5 more)
|
||||
|
||||
### Community 2 - "Roles"
|
||||
Cohesion: 0.06
|
||||
Nodes (33): BannersController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+25 more)
|
||||
Cohesion: 0.18
|
||||
Nodes (16): Roles(), SettingsController, ApiBearerAuth, ApiOkResponse, ApiOperation, ApiTags, Body, Controller (+8 more)
|
||||
|
||||
### Community 3 - "UsersService"
|
||||
Cohesion: 0.06
|
||||
Nodes (36): JwtPayload, JwtStrategy, Injectable, AddressDto, ApiProperty, ApiPropertyOptional, IsBoolean, IsNotEmpty (+28 more)
|
||||
Nodes (41): DEFAULT_JWT_REFRESH_SECRET, DEFAULT_JWT_SECRET, getJwtSecret(), AuthModule, Module, JwtPayload, JwtStrategy, Injectable (+33 more)
|
||||
|
||||
### Community 4 - "CmsController"
|
||||
Cohesion: 0.09
|
||||
Nodes (24): CmsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+16 more)
|
||||
Cohesion: 0.08
|
||||
Nodes (26): CmsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+18 more)
|
||||
|
||||
### Community 6 - "AuthController"
|
||||
Cohesion: 0.36
|
||||
Nodes (9): AuthController, ApiBadRequestResponse, ApiOkResponse, ApiOperation, ApiTags, Body, Controller, Post (+1 more)
|
||||
Cohesion: 0.27
|
||||
Nodes (12): AuthController, ApiBadRequestResponse, ApiBearerAuth, ApiOkResponse, ApiOperation, ApiTags, Body, Controller (+4 more)
|
||||
|
||||
### Community 7 - "app.module.ts"
|
||||
Cohesion: 0.09
|
||||
Nodes (28): B2BModule, Module, BannersModule, Module, CmsModule, Module, SmsModule, Global (+20 more)
|
||||
Nodes (28): B2BModule, Module, BannersModule, Module, SmsModule, Global, Module, ContactModule (+20 more)
|
||||
|
||||
### Community 8 - "VideosService"
|
||||
Cohesion: 0.12
|
||||
Nodes (16): ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete, Get (+8 more)
|
||||
### Community 8 - "CreateVideoDto"
|
||||
Cohesion: 0.07
|
||||
Nodes (32): CreateVideoDto, ApiProperty, ApiPropertyOptional, IsBoolean, IsNotEmpty, IsOptional, IsString, UpdateVideoDto (+24 more)
|
||||
|
||||
### Community 9 - "products.ts"
|
||||
### Community 9 - "ProductService"
|
||||
Cohesion: 0.06
|
||||
Nodes (30): Blog(), getBlogs(), metadata, CatalogClient(), metadata, metadata, metadata, BlogPage() (+22 more)
|
||||
Nodes (34): Blog(), getBlogs(), metadata, CatalogClient(), metadata, metadata, metadata, BlogPage() (+26 more)
|
||||
|
||||
### Community 10 - "ContactService"
|
||||
Cohesion: 0.13
|
||||
@ -370,61 +367,65 @@ Nodes (11): ContactController, Body, Controller, Get, Param, Post, Put, Query (+
|
||||
Cohesion: 0.06
|
||||
Nodes (32): compilerOptions, allowJs, esModuleInterop, incremental, isolatedModules, jsx, lib, module (+24 more)
|
||||
|
||||
### Community 12 - "toPersian"
|
||||
### Community 12 - "UserDashboard.tsx"
|
||||
Cohesion: 0.12
|
||||
Nodes (24): AddressModal(), AddressModalProps, BackButton(), BackButtonProps, CheckoutPage(), DeleteConfirmModal(), DeleteConfirmModalProps, HeaderButton() (+16 more)
|
||||
Nodes (17): AddressModal(), AddressModalProps, CheckoutPage(), DeleteConfirmModal(), DeleteConfirmModalProps, SearchableSelect(), SearchableSelectProps, PRESET_AMOUNTS (+9 more)
|
||||
|
||||
### Community 13 - "auth.controller.ts"
|
||||
Cohesion: 0.16
|
||||
Nodes (11): AdminLoginDto, ApiProperty, IsEmail, IsNotEmpty, IsString, MinLength, LoginDto, ApiProperty (+3 more)
|
||||
Cohesion: 0.10
|
||||
Nodes (19): AdminLoginDto, ApiProperty, IsEmail, IsNotEmpty, IsString, MinLength, LoginDto, ApiProperty (+11 more)
|
||||
|
||||
### Community 14 - "ProductsService"
|
||||
Cohesion: 0.09
|
||||
Nodes (19): GetProductsDto, ApiPropertyOptional, IsEnum, IsOptional, IsString, ProductsController, ApiOperation, ApiTags (+11 more)
|
||||
|
||||
### Community 15 - "useCartStore"
|
||||
### Community 15 - "lib/services/api.ts"
|
||||
Cohesion: 0.08
|
||||
Nodes (31): VerifyContent(), AuthModal(), AuthModalProps, B2BPortal(), CartDrawer(), ContactInfoItem, Header(), MENU_ICONS (+23 more)
|
||||
Nodes (16): metadata, ContactFormClient(), ContactInfoItem, PrescriptionUploadModal(), PrescriptionUploadModalProps, api, ApiErrorPayload, baseURL (+8 more)
|
||||
|
||||
### Community 17 - "useSettingsStore"
|
||||
### Community 16 - "tickets.controller.ts"
|
||||
Cohesion: 0.09
|
||||
Nodes (31): HomeClient(), HomeClientProps, getHomeData(), Home(), metadata, metadata, EnamadBadge(), Hero() (+23 more)
|
||||
Nodes (31): AdminUpdateTicketDto, CreateTicketDto, ReplyTicketDto, ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString (+23 more)
|
||||
|
||||
### Community 17 - "ArchivePage.tsx"
|
||||
Cohesion: 0.09
|
||||
Nodes (26): HomeClient(), HomeClientProps, getHomeData(), Home(), metadata, metadata, ArchivePage(), CATEGORY_MAP (+18 more)
|
||||
|
||||
### Community 18 - "MetricsController"
|
||||
Cohesion: 0.29
|
||||
Nodes (5): ApiExcludeController, MetricsController, Controller, Get, Res
|
||||
|
||||
### Community 19 - "B2B Inquiry Controller"
|
||||
Cohesion: 0.13
|
||||
Nodes (15): B2BController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Get, Param (+7 more)
|
||||
### Community 19 - "B2BService"
|
||||
Cohesion: 0.14
|
||||
Nodes (14): B2BController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Get, Param (+6 more)
|
||||
|
||||
### Community 20 - "CategoriesController"
|
||||
Cohesion: 0.09
|
||||
Nodes (17): CategoriesController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete (+9 more)
|
||||
Cohesion: 0.10
|
||||
Nodes (16): CategoriesController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete (+8 more)
|
||||
|
||||
### Community 21 - "RedisService"
|
||||
Cohesion: 0.11
|
||||
Nodes (7): AppModule, Module, RedisModule, Global, Module, RedisService, Injectable
|
||||
Cohesion: 0.12
|
||||
Nodes (4): AppModule, Module, RedisService, Injectable
|
||||
|
||||
### Community 22 - "MediaController"
|
||||
Cohesion: 0.11
|
||||
Nodes (16): MediaController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+8 more)
|
||||
Nodes (14): MediaController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+6 more)
|
||||
|
||||
### Community 23 - "Ingredient Management Controller"
|
||||
Cohesion: 0.13
|
||||
Nodes (14): IngredientsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+6 more)
|
||||
|
||||
### Community 24 - "adminRoutes.tsx"
|
||||
Cohesion: 0.12
|
||||
Nodes (11): App(), ContactInfoItem, ContactSubmission, CategoryDist, DashboardData, AdminRouteConfig, ContactSubmissions, Dashboard (+3 more)
|
||||
Cohesion: 0.06
|
||||
Nodes (22): App(), HeroBanner, VetTestimonial, ContactInfoItem, ContactSubmission, CategoryDist, DashboardData, SslStatus (+14 more)
|
||||
|
||||
### Community 25 - "admin.module.ts"
|
||||
Cohesion: 0.10
|
||||
Nodes (14): AdminModule, Module, PetQuery, PetsService, Injectable, ReportsController, ApiBearerAuth, ApiOperation (+6 more)
|
||||
Cohesion: 0.07
|
||||
Nodes (20): AdminModule, Module, MediaService, Injectable, ReportsController, ApiBearerAuth, ApiOperation, ApiTags (+12 more)
|
||||
|
||||
### Community 26 - "ConfirmModal.tsx"
|
||||
Cohesion: 0.08
|
||||
Nodes (19): ConfirmModal(), ConfirmModalProps, HeroBanner, VetTestimonial, Coupon, CouponFormData, CouponModalProps, CouponTarget (+11 more)
|
||||
### Community 26 - "Coupons.tsx"
|
||||
Cohesion: 0.25
|
||||
Nodes (5): Coupon, CouponFormData, CouponModalProps, CouponTarget, Coupons
|
||||
|
||||
### Community 27 - "Prescription Review Controller"
|
||||
Cohesion: 0.14
|
||||
@ -438,45 +439,45 @@ Nodes (14): SmartAdvisorController, ApiBearerAuth, ApiOperation, ApiTags, Body,
|
||||
Cohesion: 0.13
|
||||
Nodes (14): TestimonialsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+6 more)
|
||||
|
||||
### Community 30 - "api"
|
||||
Cohesion: 0.24
|
||||
Nodes (8): Layout(), menuGroups, Sidebar(), SidebarProps, SEARCHABLE_PAGES, Topbar(), TopbarProps, api
|
||||
### Community 30 - "src/services/api.ts"
|
||||
Cohesion: 0.14
|
||||
Nodes (18): Layout(), ProtectedRoute(), MenuGroup, Sidebar(), SidebarProps, SubMenuItem, SEARCHABLE_PAGES, Topbar() (+10 more)
|
||||
|
||||
### Community 31 - "Spinner.tsx"
|
||||
Cohesion: 0.09
|
||||
Nodes (20): Media, MediaSelector(), MediaSelectorProps, Spinner(), BlogPost, Category, BannersManager, Blogs (+12 more)
|
||||
Cohesion: 0.12
|
||||
Nodes (21): ConfirmModal(), ConfirmModalProps, Media, MediaSelector(), MediaSelectorProps, Pagination(), PaginationProps, Spinner() (+13 more)
|
||||
|
||||
### Community 32 - "PetProfile.tsx"
|
||||
Cohesion: 0.08
|
||||
Nodes (27): metadata, ArchivePage(), ArchiveProductCard(), CATEGORY_MAP, ICON_MAP, FeaturedProducts(), ProductCard(), OrderSuccess() (+19 more)
|
||||
Cohesion: 0.15
|
||||
Nodes (15): BackButton(), BackButtonProps, PetProfile(), SafeImage(), SafeImageProps, CURRENT_SYMPTOMS, MEDICAL_HISTORIES, SmartAdvisor() (+7 more)
|
||||
|
||||
### Community 33 - "ZibalService"
|
||||
Cohesion: 0.05
|
||||
Nodes (41): InitiatePaymentDto, InitiateWalletTopupDto, ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString, ApiPropertyOptional (+33 more)
|
||||
Cohesion: 0.14
|
||||
Nodes (4): PaymentService, Injectable, Injectable, ZibalService
|
||||
|
||||
### Community 34 - "main.ts"
|
||||
Cohesion: 0.14
|
||||
Nodes (9): CustomHttpExceptionFilter, Catch, PrismaExceptionFilter, Catch, DecimalInterceptor, Injectable, ApiErrorResponse, ErrorDetailField (+1 more)
|
||||
|
||||
### Community 35 - "src/services/api.ts"
|
||||
Cohesion: 0.12
|
||||
Nodes (20): ProtectedRoute(), Login(), B2BManager, FinancialSettingsPage, SmartAdvisorManager, ApiErrorPayload, failedQueue, useAdminAuthStore (+12 more)
|
||||
### Community 35 - "admin.ts"
|
||||
Cohesion: 0.08
|
||||
Nodes (20): B2BManager, FinancialSettingsPage, PrescriptionsManager, SeoSettingsPage, SmartAdvisorManager, SystemSettingsPage, AdminLoginPayload, B2BInquiry (+12 more)
|
||||
|
||||
### Community 36 - "Backend TypeScript Config"
|
||||
Cohesion: 0.09
|
||||
Nodes (22): compilerOptions, allowSyntheticDefaultImports, baseUrl, declaration, emitDecoratorMetadata, esModuleInterop, experimentalDecorators, forceConsistentCasingInFileNames (+14 more)
|
||||
### Community 36 - "compilerOptions"
|
||||
Cohesion: 0.06
|
||||
Nodes (31): compilerOptions, allowSyntheticDefaultImports, baseUrl, declaration, emitDecoratorMetadata, esModuleInterop, experimentalDecorators, forceConsistentCasingInFileNames (+23 more)
|
||||
|
||||
### Community 37 - "App TypeScript Config"
|
||||
### Community 37 - "compilerOptions"
|
||||
Cohesion: 0.09
|
||||
Nodes (22): compilerOptions, allowImportingTsExtensions, erasableSyntaxOnly, jsx, lib, module, moduleDetection, moduleResolution (+14 more)
|
||||
|
||||
### Community 38 - "Pagination.tsx"
|
||||
Cohesion: 0.11
|
||||
Nodes (13): Pagination(), PaginationProps, Media, Pet, GatewayHealth, Stats, Transaction, ProductItem (+5 more)
|
||||
### Community 38 - "Transactions.tsx"
|
||||
Cohesion: 0.33
|
||||
Nodes (4): GatewayHealth, Stats, Transaction, Transactions
|
||||
|
||||
### Community 39 - "dependencies"
|
||||
Cohesion: 0.10
|
||||
Nodes (21): dependencies, bcrypt, class-transformer, class-validator, ioredis, @nestjs/common, @nestjs/passport, @nestjs/platform-express (+13 more)
|
||||
Cohesion: 0.05
|
||||
Nodes (41): dependencies, bcrypt, bcryptjs, class-transformer, class-validator, helmet, ioredis, js-yaml (+33 more)
|
||||
|
||||
### Community 40 - "Node TypeScript Config"
|
||||
Cohesion: 0.10
|
||||
@ -486,13 +487,13 @@ Nodes (20): compilerOptions, allowImportingTsExtensions, erasableSyntaxOnly, lib
|
||||
Cohesion: 0.13
|
||||
Nodes (15): BlogsController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete (+7 more)
|
||||
|
||||
### Community 42 - "WholesaleService"
|
||||
Cohesion: 0.14
|
||||
Nodes (14): ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Get, Param, Post (+6 more)
|
||||
### Community 42 - "WholesaleApplyDto"
|
||||
Cohesion: 0.10
|
||||
Nodes (20): ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString, WholesaleApplyDto, ApiBearerAuth, ApiOperation (+12 more)
|
||||
|
||||
### Community 43 - "devDependencies"
|
||||
Cohesion: 0.13
|
||||
Nodes (15): devDependencies, eslint, jsdom, tailwindcss, @tailwindcss/postcss, @types/node, @types/react-dom, @vitejs/plugin-react (+7 more)
|
||||
Nodes (15): eslint-config-next, devDependencies, eslint, eslint-config-next, jsdom, tailwindcss, @tailwindcss/postcss, @types/node (+7 more)
|
||||
|
||||
### Community 44 - "Generic CRUD Controller"
|
||||
Cohesion: 0.13
|
||||
@ -503,8 +504,8 @@ Cohesion: 0.16
|
||||
Nodes (10): SeoController, ApiOperation, ApiTags, Controller, Get, Param, SeoModule, Module (+2 more)
|
||||
|
||||
### Community 46 - "PaginationDto"
|
||||
Cohesion: 0.14
|
||||
Nodes (13): BlogsModule, Module, BlogsService, Injectable, PaginationDto, SortOrder, ApiPropertyOptional, IsEnum (+5 more)
|
||||
Cohesion: 0.07
|
||||
Nodes (24): CategoryQuery, BlogsModule, Module, BlogsService, Injectable, PaginationDto, SortOrder, ApiPropertyOptional (+16 more)
|
||||
|
||||
### Community 47 - "Project Build Scripts"
|
||||
Cohesion: 0.11
|
||||
@ -530,52 +531,52 @@ Nodes (14): "coupons", "health_logs", "order_items", "orders", "pet_medical_cond
|
||||
Cohesion: 0.10
|
||||
Nodes (21): dependencies, axios, lucide-react, react, react-dom, react-hot-toast, react-router-dom, recharts (+13 more)
|
||||
|
||||
### Community 53 - "UI Skeleton and Tables"
|
||||
Cohesion: 0.18
|
||||
Nodes (11): Skeleton(), Order, OrderItem, Orders(), statusStyles, toPersianDigits(), UserRecord, Orders (+3 more)
|
||||
### Community 53 - "Orders.tsx"
|
||||
Cohesion: 0.16
|
||||
Nodes (13): Skeleton(), getPaymentMethodLabel(), Order, OrderItem, Orders(), PaymentTx, statusStyles, toPersianDigits() (+5 more)
|
||||
|
||||
### Community 54 - "auth.service.ts"
|
||||
Cohesion: 0.16
|
||||
Nodes (7): AuthModule, Module, AdminLoginInput, AuthService, LoginInput, RegisterInput, Injectable
|
||||
Cohesion: 0.12
|
||||
Nodes (11): AdminLoginInput, AuthService, LoginInput, RegisterInput, Injectable, SendOtpDto, ApiProperty, IsNotEmpty (+3 more)
|
||||
|
||||
### Community 55 - "BE-001"
|
||||
Cohesion: 0.06
|
||||
Nodes (32): Acceptance Criteria, Affected Application, Affected Files, Alternative Direction, BE-001, Category, Completion Statement, Confidence (+24 more)
|
||||
|
||||
### Community 56 - "SafeImage.tsx"
|
||||
Cohesion: 0.17
|
||||
Nodes (10): ProductDetailModalProps, SafeImage(), SafeImageProps, DisplayVideoItem, FALLBACK_VIDEOS, TestimonialItem, FALLBACK_VIDEOS, VideosPage() (+2 more)
|
||||
### Community 56 - "VetGallery.tsx"
|
||||
Cohesion: 0.22
|
||||
Nodes (8): DisplayVideoItem, FALLBACK_VIDEOS, TestimonialItem, VetGallery(), FALLBACK_VIDEOS, VideosPage(), Video, videoService
|
||||
|
||||
### Community 57 - "NPM Lifecycle Scripts"
|
||||
Cohesion: 0.14
|
||||
Nodes (14): scripts, build, docs:generate, format, lint, start, start:debug, start:dev (+6 more)
|
||||
|
||||
### Community 58 - "Pet Management API"
|
||||
Cohesion: 0.15
|
||||
Nodes (11): PetsController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Controller, Delete, Get (+3 more)
|
||||
### Community 58 - "PetsController"
|
||||
Cohesion: 0.10
|
||||
Nodes (14): PetsController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Controller, Delete, Get (+6 more)
|
||||
|
||||
### Community 59 - "PrismaService"
|
||||
Cohesion: 0.06
|
||||
Nodes (21): BlogQuery, Injectable, WikiQuery, WikiService, MeliPayamakPattern, MeliPayamakResponse, SendPatternSmsOptions, SmsConfig (+13 more)
|
||||
Cohesion: 0.08
|
||||
Nodes (14): B2BWholesaleOrderItem, MeliPayamakPattern, MeliPayamakResponse, SendPatternSmsOptions, SmsConfig, SmsLogQuery, CreateContactSubmissionDto, UpdateContactInfoItemDto (+6 more)
|
||||
|
||||
### Community 60 - "Jest Testing Config"
|
||||
Cohesion: 0.15
|
||||
Nodes (13): jest, collectCoverageFrom, coverageDirectory, moduleFileExtensions, rootDir, testEnvironment, testRegex, transform (+5 more)
|
||||
|
||||
### Community 61 - "WikiController"
|
||||
Cohesion: 0.20
|
||||
Nodes (7): ApiTags, Controller, WikiController, Module, WikiModule, Injectable, WikiService
|
||||
### Community 61 - "CreateReviewDto"
|
||||
Cohesion: 0.07
|
||||
Nodes (29): CreateReviewDto, ApiProperty, IsInt, IsNotEmpty, IsOptional, IsString, Min, ApiProperty (+21 more)
|
||||
|
||||
### Community 62 - "videos.controller.ts"
|
||||
Cohesion: 0.22
|
||||
Nodes (7): GetVideosQueryDto, ApiPropertyOptional, IsBoolean, IsOptional, Module, VideosModule, Transform
|
||||
### Community 62 - "BannersService"
|
||||
Cohesion: 0.13
|
||||
Nodes (15): BannersController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+7 more)
|
||||
|
||||
### Community 63 - "FE-001"
|
||||
Cohesion: 0.06
|
||||
Nodes (31): Acceptance Criteria, Affected Application, Affected Files, Alternative Direction, Architecture Overview & Confirmed Strengths, Category, Completion Statement, Confidence (+23 more)
|
||||
|
||||
### Community 64 - "VerifyOtpDto"
|
||||
Cohesion: 0.22
|
||||
Cohesion: 0.29
|
||||
Nodes (6): ApiProperty, IsNotEmpty, IsString, Matches, VerifyOtpDto, Length
|
||||
|
||||
### Community 66 - "App Health Controller"
|
||||
@ -587,8 +588,8 @@ Cohesion: 0.12
|
||||
Nodes (17): dependencies, axios, lucide-react, motion, next, react, react-dom, sonner (+9 more)
|
||||
|
||||
### Community 68 - "OrdersService"
|
||||
Cohesion: 0.06
|
||||
Nodes (35): CreateOrderDto, OrderItemDto, ApiProperty, ApiPropertyOptional, IsArray, IsNotEmpty, IsNumber, IsOptional (+27 more)
|
||||
Cohesion: 0.07
|
||||
Nodes (29): CreateOrderDto, OrderItemDto, ApiProperty, ApiPropertyOptional, IsArray, IsNotEmpty, IsNumber, IsOptional (+21 more)
|
||||
|
||||
### Community 69 - "Integrity Validation Scripts"
|
||||
Cohesion: 0.20
|
||||
@ -610,9 +611,9 @@ Nodes (8): cmd_next_task(), cmd_progress(), cmd_reset(), cmd_status(), cmd_unblo
|
||||
Cohesion: 0.22
|
||||
Nodes (8): author, description, license, name, prisma, seed, private, version
|
||||
|
||||
### Community 74 - "RegisterDto"
|
||||
Cohesion: 0.22
|
||||
Nodes (8): RegisterDto, ApiProperty, ApiPropertyOptional, IsEmail, IsNotEmpty, IsOptional, IsString, MinLength
|
||||
### Community 74 - "useSettingsStore"
|
||||
Cohesion: 0.15
|
||||
Nodes (14): metadata, BrandLogo(), BrandLogoProps, EnamadBadge(), Footer(), MENU_ICONS, MaintenancePage(), TickerBanner() (+6 more)
|
||||
|
||||
### Community 75 - "React Error Boundary"
|
||||
Cohesion: 0.22
|
||||
@ -622,9 +623,9 @@ Nodes (3): ErrorBoundary, Props, State
|
||||
Cohesion: 0.22
|
||||
Nodes (8): name, private, scripts, build, dev, lint, start, version
|
||||
|
||||
### Community 77 - ".findAll"
|
||||
Cohesion: 0.32
|
||||
Nodes (6): ApiNotFoundResponse, ApiOkResponse, ApiOperation, Get, Param, Query
|
||||
### Community 77 - "WikiController"
|
||||
Cohesion: 0.21
|
||||
Nodes (9): ApiNotFoundResponse, ApiOkResponse, ApiOperation, ApiTags, Controller, Get, Param, Query (+1 more)
|
||||
|
||||
### Community 79 - "VPN Utility Scripts"
|
||||
Cohesion: 0.62
|
||||
@ -666,25 +667,25 @@ Nodes (4): evidenceList, ledger, mandatoryMap, mandatoryScope
|
||||
Cohesion: 0.40
|
||||
Nodes (4): activeFiles, errors, validationOutput, warnings
|
||||
|
||||
### Community 90 - "CreateVideoDto"
|
||||
Cohesion: 0.25
|
||||
Nodes (8): CreateVideoDto, ApiProperty, ApiPropertyOptional, IsBoolean, IsNotEmpty, IsOptional, IsString, UpdateVideoDto
|
||||
### Community 90 - "SslController"
|
||||
Cohesion: 0.13
|
||||
Nodes (14): SslController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Get, Post (+6 more)
|
||||
|
||||
### Community 91 - "Blog Post Detail Page"
|
||||
Cohesion: 0.60
|
||||
Nodes (4): BlogPostPage(), generateMetadata(), getBlog(), revalidate
|
||||
|
||||
### Community 93 - "devDependencies"
|
||||
Cohesion: 0.22
|
||||
Nodes (9): devDependencies, @eslint/eslintrc, @types/passport-jwt, @types/supertest, typescript, typescript, @eslint/eslintrc, @types/passport-jwt (+1 more)
|
||||
Cohesion: 0.09
|
||||
Nodes (23): devDependencies, eslint, @eslint/eslintrc, @eslint/js, jest, @nestjs/schematics, @nestjs/testing, source-map-support (+15 more)
|
||||
|
||||
### Community 99 - "Wiki Page Routing"
|
||||
Cohesion: 0.83
|
||||
Nodes (3): generateMetadata(), getWikiTerm(), WikiTermPage()
|
||||
|
||||
### Community 100 - "ClientLayout.tsx"
|
||||
Cohesion: 0.17
|
||||
Nodes (8): ClientLayout(), Footer(), LoginModal(), LoginModalProps, MaintenancePage(), NetworkBanner(), useNetworkStatus(), NavigationTarget
|
||||
### Community 100 - "toPersian"
|
||||
Cohesion: 0.11
|
||||
Nodes (21): ClientLayout(), VerifyContent(), AuthModal(), AuthModalProps, extractOtpFromText(), B2BPortal(), CartDrawer(), HeaderButton() (+13 more)
|
||||
|
||||
### Community 101 - "Docker Deployment Scripts"
|
||||
Cohesion: 0.50
|
||||
@ -694,13 +695,17 @@ Nodes (3): COMPOSE_DOCKER_CLI_BUILD, DOCKER_BUILDKIT, deploy.sh script
|
||||
Cohesion: 0.06
|
||||
Nodes (31): Acceptance Criteria, Affected Application, Affected Files, Alternative Direction, Category, Completion Statement, Confidence, Database and Data Integrity Audit Report (+23 more)
|
||||
|
||||
### Community 103 - "BlogsService"
|
||||
Cohesion: 0.22
|
||||
Nodes (3): BlogQuery, BlogsService, Injectable
|
||||
|
||||
### Community 109 - "Auth Architecture and Planning"
|
||||
Cohesion: 0.67
|
||||
Nodes (3): ADR-AUTH-001: Admin Panel Authentication Architecture & Token Management, Master Task Backlog (Phase 3.3), Phase 3 Master Execution Plan
|
||||
|
||||
### Community 117 - "SendOtpDto"
|
||||
### Community 117 - "PaymentController"
|
||||
Cohesion: 0.25
|
||||
Nodes (5): SendOtpDto, ApiProperty, IsNotEmpty, IsString, Matches
|
||||
Nodes (13): PaymentController, ApiBadRequestResponse, ApiBearerAuth, ApiOkResponse, ApiOperation, ApiTags, Body, Controller (+5 more)
|
||||
|
||||
### Community 118 - "TS-001"
|
||||
Cohesion: 0.06
|
||||
@ -710,13 +715,13 @@ Nodes (31): Acceptance Criteria, Affected Application, Affected Files, Alternati
|
||||
Cohesion: 0.06
|
||||
Nodes (31): Acceptance Criteria, Affected Application, Affected Files, Alternative Direction, Category, Completion Statement, Confidence, Dependencies (+23 more)
|
||||
|
||||
### Community 120 - "payment.service.ts"
|
||||
Cohesion: 0.22
|
||||
Nodes (8): AdminTransactionFilterDto, ApiPropertyOptional, IsNumber, IsOptional, IsString, Type, ClientMetadata, IsIn
|
||||
### Community 120 - "AdminTransactionFilterDto"
|
||||
Cohesion: 0.25
|
||||
Nodes (7): AdminTransactionFilterDto, ApiPropertyOptional, IsIn, IsNumber, IsOptional, IsString, Type
|
||||
|
||||
### Community 121 - "UITexts.tsx"
|
||||
Cohesion: 0.33
|
||||
Nodes (4): GROUP_PAGE_MAP, GROUPS, PAGE_TABS, UITexts
|
||||
Cohesion: 0.29
|
||||
Nodes (5): GROUP_PAGE_MAP, GROUPS, KEY_LABELS_FA, PAGE_TABS, UITexts
|
||||
|
||||
### Community 126 - "Typography and Font Assets"
|
||||
Cohesion: 0.67
|
||||
@ -735,7 +740,7 @@ Cohesion: 0.07
|
||||
Nodes (26): For /graphify add and --watch, For /graphify query, For the commit hook and native CLAUDE.md integration, For --update and --cluster-only, /graphify, Honesty Rules, Interpreter guard for subcommands, Part A - Structural extraction for code files (+18 more)
|
||||
|
||||
### Community 130 - "JwtAuthGuard"
|
||||
Cohesion: 0.19
|
||||
Cohesion: 0.18
|
||||
Nodes (7): JwtAuthGuard, Injectable, ROLES_KEY, RequestWithUser, RolesGuard, Injectable, UserReqPayload
|
||||
|
||||
### Community 131 - "راهنمای تست سیستم (Software Testing)"
|
||||
@ -770,6 +775,10 @@ Nodes (17): 1. Hierarchical Decomposition Algorithm (3-Tier), 2. Sub-step Defini
|
||||
Cohesion: 0.11
|
||||
Nodes (17): 1. Always Read Tech Stack First, 2. Sub-step Execution (Token Resume Support), 3. Mandatory Test Authoring, 4. Code Quality Standards, 5. File Scope Boundary, 6. Forbidden Actions, Expected JSON Output Schema, IMPLEMENT MODE — Normal Operation (+9 more)
|
||||
|
||||
### Community 142 - ".update"
|
||||
Cohesion: 0.24
|
||||
Nodes (11): ApiNotFoundResponse, ApiOkResponse, ApiOperation, Body, Delete, Get, Param, Patch (+3 more)
|
||||
|
||||
### Community 157 - "What You Must Do When Invoked"
|
||||
Cohesion: 0.07
|
||||
Nodes (26): For /graphify add and --watch, For /graphify query, For the commit hook and native CLAUDE.md integration, For --update and --cluster-only, /graphify, Honesty Rules, Interpreter guard for subcommands, Part A - Structural extraction for code files (+18 more)
|
||||
@ -862,6 +871,10 @@ Nodes (9): 1. Active Customer Storefront: React 19 + Vite Application, 2. Active
|
||||
Cohesion: 0.20
|
||||
Nodes (9): Arch Linux, Contributors, Install, Known problems for variable version, License, Sahel-Font, To Do (variable), طریقه استفاده از نسخه متغیر variable (+1 more)
|
||||
|
||||
### Community 207 - "zibal.service.ts"
|
||||
Cohesion: 0.16
|
||||
Nodes (9): GatewayHealthResult, IPaymentGateway, PaymentInquiryResult, PaymentRequestOptions, PaymentRequestResult, PaymentVerifyResult, ZibalInquiryResponse, ZibalRequestResponse (+1 more)
|
||||
|
||||
### Community 208 - "Sahel-Font"
|
||||
Cohesion: 0.20
|
||||
Nodes (9): Arch Linux, Contributors, Install, Known problems for variable version, License, Sahel-Font, To Do (variable), طریقه استفاده از نسخه متغیر variable (+1 more)
|
||||
@ -872,7 +885,7 @@ Nodes (8): 1. Ask — Don't Assume, 2. Forbidden Actions, Brownfield Detection,
|
||||
|
||||
### Community 210 - "exclude"
|
||||
Cohesion: 0.22
|
||||
Nodes (8): exclude, extends, node_modules, prisma, dist, **/*spec.ts, test, ./tsconfig.json
|
||||
Nodes (8): exclude, extends, dist, node_modules, prisma, **/*spec.ts, test, ./tsconfig.json
|
||||
|
||||
### Community 211 - "Phase 2 Final Quality Gate Summary Report"
|
||||
Cohesion: 0.22
|
||||
@ -966,29 +979,77 @@ Nodes (3): Expanding the ESLint configuration, React Compiler, React + TypeScrip
|
||||
Cohesion: 0.50
|
||||
Nodes (3): Deploy on Vercel, Getting Started, Learn More
|
||||
|
||||
### Community 237 - "useCartStore"
|
||||
Cohesion: 0.10
|
||||
Nodes (16): ArchiveProductCard(), FeaturedProducts(), ProductCard(), Header(), OrderSuccess(), OrderTracking(), mockProduct, mockProducts (+8 more)
|
||||
|
||||
### Community 238 - "ProductPage.tsx"
|
||||
Cohesion: 0.16
|
||||
Nodes (10): CalculatorState, ICON_MAP, ProductPage(), useCalculatorStore, ProductReviews(), ProductReviewsProps, ReviewItem, SCIENTIFIC_TERMS (+2 more)
|
||||
|
||||
### Community 266 - ".handleZibalCallback"
|
||||
Cohesion: 0.24
|
||||
Nodes (8): ApiPropertyOptional, IsOptional, IsString, ZibalCallbackQueryDto, Query, Res, Headers, Ip
|
||||
|
||||
### Community 268 - "PetsController"
|
||||
Cohesion: 0.18
|
||||
Nodes (9): PetsController, ApiBadRequestResponse, ApiBearerAuth, ApiCreatedResponse, ApiTags, Controller, UploadedFile, UseGuards (+1 more)
|
||||
|
||||
### Community 269 - "generate-openapi.js"
|
||||
Cohesion: 0.25
|
||||
Nodes (6): app_module_1, core_1, fs, path, swagger_1, yaml
|
||||
|
||||
### Community 270 - "InitiatePaymentDto"
|
||||
Cohesion: 0.43
|
||||
Nodes (7): InitiatePaymentDto, InitiateWalletTopupDto, ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString
|
||||
|
||||
### Community 271 - "SmsLogQueryDto"
|
||||
Cohesion: 0.25
|
||||
Nodes (7): SmsLogQueryDto, ApiPropertyOptional, IsIn, IsNumber, IsOptional, IsString, Type
|
||||
|
||||
### Community 272 - "CreateHealthLogDto"
|
||||
Cohesion: 0.29
|
||||
Nodes (6): CreateHealthLogDto, ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString
|
||||
|
||||
### Community 275 - "CreateReminderDto"
|
||||
Cohesion: 0.29
|
||||
Nodes (6): CreateReminderDto, ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString
|
||||
|
||||
### Community 276 - "Reviews.tsx"
|
||||
Cohesion: 0.50
|
||||
Nodes (4): ProductReview, Reviews(), toPersianDigits(), Reviews
|
||||
|
||||
### Community 277 - "Tickets.tsx"
|
||||
Cohesion: 0.40
|
||||
Nodes (3): Ticket, TicketMessage, Tickets
|
||||
|
||||
### Community 278 - "Videos.tsx"
|
||||
Cohesion: 0.50
|
||||
Nodes (4): fetchVideosList(), Video, Videos(), Videos
|
||||
|
||||
### Community 288 - "SmsSettingsPage.tsx"
|
||||
Cohesion: 0.29
|
||||
Nodes (5): PatternItem, SmsConfigState, SmsLogItem, SmsLogStats, SmsSettingsPage
|
||||
|
||||
## Knowledge Gaps
|
||||
- **1182 isolated node(s):** `$schema`, `collection`, `sourceRoot`, `deleteOutDir`, `name` (+1177 more)
|
||||
- **1208 isolated node(s):** `$schema`, `collection`, `sourceRoot`, `deleteOutDir`, `name` (+1203 more)
|
||||
These have ≤1 connection - possible missing edges or undocumented components.
|
||||
- **114 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
|
||||
- **95 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
|
||||
|
||||
## Suggested Questions
|
||||
_Questions this graph is uniquely positioned to answer:_
|
||||
|
||||
- **Why does `ApiResponse` connect `src/services/api.ts` to `pets/pets.controller.ts`, `UsersService`, `OrdersService`, `AuthController`, `ProductsService`, `Home Data Module`, `BlogsController`, `WikiController`?**
|
||||
_High betweenness centrality (0.041) - this node is a cross-community bridge._
|
||||
- **Why does `Roles()` connect `Roles` to `ZibalService`, `JwtAuthGuard`, `CmsController`, `ContactService`, `WholesaleService`, `ProductsService`, `B2B Inquiry Controller`, `Ingredient Management Controller`, `Prescription Review Controller`, `Smart Advisor Controller`, `Testimonials Management Controller`?**
|
||||
_High betweenness centrality (0.034) - this node is a cross-community bridge._
|
||||
- **Why does `PaginationDto` connect `PaginationDto` to `AdminService`, `pets/pets.controller.ts`, `JwtAuthGuard`, `OrdersService`, `Admin Blog Controller`, `Generic CRUD Controller`, `.findAll`, `ProductsService`, `CategoriesController`, `BlogsController`, `admin.module.ts`, `Pet Management API`, `WikiController`, `videos.controller.ts`?**
|
||||
_High betweenness centrality (0.024) - this node is a cross-community bridge._
|
||||
- **Why does `Roles()` connect `Roles` to `JwtAuthGuard`, `CmsController`, `ContactService`, `WholesaleApplyDto`, `ProductsService`, `tickets.controller.ts`, `Testimonials Management Controller`, `B2BService`, `PaymentController`, `Ingredient Management Controller`, `SslController`, `Prescription Review Controller`, `Smart Advisor Controller`, `CreateReviewDto`, `BannersService`?**
|
||||
_High betweenness centrality (0.044) - this node is a cross-community bridge._
|
||||
- **Why does `ApiResponse` connect `src/services/api.ts` to `UsersService`, `OrdersService`, `admin.ts`, `AuthController`, `PetsController`, `WikiController`, `ProductsService`, `Home Data Module`, `BlogsController`?**
|
||||
_High betweenness centrality (0.039) - this node is a cross-community bridge._
|
||||
- **Why does `PrismaService` connect `PrismaService` to `AdminService`, `pets/pets.controller.ts`, `JwtAuthGuard`, `UsersService`, `CmsController`, `SmsService`, `app.module.ts`, `CreateVideoDto`, `ContactService`, `PetsService`, `SettingsService`, `ProductsService`, `tickets.controller.ts`, `MetricsController`, `B2BService`, `CategoriesController`, `RedisService`, `Ingredient Management Controller`, `admin.module.ts`, `Prescription Review Controller`, `Smart Advisor Controller`, `Testimonials Management Controller`, `ZibalService`, `WholesaleApplyDto`, `seo.module.ts`, `PaginationDto`, `Home Data Module`, `auth.service.ts`, `PetsController`, `CreateReviewDto`, `BannersService`, `OrdersService`, `zibal.service.ts`, `BlogsService`?**
|
||||
_High betweenness centrality (0.026) - this node is a cross-community bridge._
|
||||
- **What connects `$schema`, `collection`, `sourceRoot` to the rest of the system?**
|
||||
_1182 weakly-connected nodes found - possible documentation gaps or missing edges._
|
||||
_1208 weakly-connected nodes found - possible documentation gaps or missing edges._
|
||||
- **Should `AdminService` be split into smaller, more focused modules?**
|
||||
_Cohesion score 0.06414414414414414 - nodes in this community are weakly interconnected._
|
||||
- **Should `pets/pets.controller.ts` be split into smaller, more focused modules?**
|
||||
_Cohesion score 0.0528169014084507 - nodes in this community are weakly interconnected._
|
||||
- **Should `Roles` be split into smaller, more focused modules?**
|
||||
_Cohesion score 0.06347340581839553 - nodes in this community are weakly interconnected._
|
||||
_Cohesion score 0.05396825396825397 - nodes in this community are weakly interconnected._
|
||||
- **Should `UsersService` be split into smaller, more focused modules?**
|
||||
_Cohesion score 0.05547785547785548 - nodes in this community are weakly interconnected._
|
||||
- **Should `CmsController` be split into smaller, more focused modules?**
|
||||
_Cohesion score 0.08441558441558442 - nodes in this community are weakly interconnected._
|
||||
@ -0,0 +1 @@
|
||||
{"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
@ -1 +0,0 @@
|
||||
{"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": []}
|
||||
@ -1 +0,0 @@
|
||||
{"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": []}
|
||||
@ -1 +0,0 @@
|
||||
{"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": []}
|
||||
@ -1 +0,0 @@
|
||||
{"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": []}
|
||||
@ -1 +0,0 @@
|
||||
{"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": []}
|
||||
@ -1 +0,0 @@
|
||||
{"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"}]}
|
||||
@ -1 +0,0 @@
|
||||
{"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": []}
|
||||
@ -1 +0,0 @@
|
||||
{"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": []}
|
||||
@ -1 +0,0 @@
|
||||
{"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": []}
|
||||
@ -1 +0,0 @@
|
||||
{"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": []}
|
||||
@ -1 +0,0 @@
|
||||
{"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": []}
|
||||
2
graphify-out/cache/stat-index.json
vendored
2
graphify-out/cache/stat-index.json
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
96918
graphify-out/graph.json
96918
graphify-out/graph.json
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user