diff --git a/apps/business/src/components/DailyActivityChart.module.css b/apps/business/src/components/DailyActivityChart.module.css index cc39e62..c2f1b09 100644 --- a/apps/business/src/components/DailyActivityChart.module.css +++ b/apps/business/src/components/DailyActivityChart.module.css @@ -91,7 +91,7 @@ justify-content: space-between; flex-shrink: 0; width: 40px; - padding-bottom: 28px; + padding-bottom: 22px; box-sizing: border-box; direction: ltr; } @@ -112,15 +112,10 @@ overflow: visible; } -.chartWrapScroll { - overflow-x: auto; - overflow-y: visible; -} - .chartGrid { position: absolute; inset-inline: 0; - bottom: 28px; + bottom: 22px; pointer-events: none; z-index: 0; } @@ -149,8 +144,8 @@ } .chartMonth { - gap: 6px; - overflow-x: auto; + gap: 4px; + overflow: visible; } .group { @@ -167,8 +162,8 @@ } .chartMonth .group { - flex: 1; - min-width: 36px; + flex: 1 1 0; + min-width: 0; } .bars { @@ -177,14 +172,13 @@ justify-content: center; width: 100%; height: 180px; - gap: 1px; + gap: 2px; } .barWrap { position: relative; display: flex; - flex: 1 1 0; - min-width: 0; + flex: 0 0 auto; align-items: flex-end; justify-content: center; height: 100%; @@ -257,6 +251,9 @@ } .label { + display: flex; + align-items: center; + justify-content: center; font-size: 9px; color: var(--text-muted); font-weight: 500; @@ -265,11 +262,22 @@ text-align: center; width: 100%; overflow: hidden; - text-overflow: ellipsis; } .chartMonth .label { + height: 68px; +} + +.chartMonth .labelText { + writing-mode: vertical-rl; + transform: rotate(180deg); + white-space: nowrap; + max-height: 68px; + overflow: hidden; + text-overflow: ellipsis; font-size: 10px; + line-height: 1.2; + letter-spacing: 0.04em; } .barPrimary { diff --git a/apps/business/src/components/DailyActivityChart.tsx b/apps/business/src/components/DailyActivityChart.tsx index 96906cd..dc87b15 100644 --- a/apps/business/src/components/DailyActivityChart.tsx +++ b/apps/business/src/components/DailyActivityChart.tsx @@ -12,6 +12,8 @@ import type { BusinessMessageKey } from '../i18n/messages' import styles from './DailyActivityChart.module.css' const CHART_HEIGHT = 180 +const LABEL_BAND_DAY = 22 +const LABEL_BAND_MONTH = 80 interface DailyActivityChartProps { titleKey: BusinessMessageKey @@ -143,6 +145,7 @@ export function DailyActivityChart({ const yAxisTicks = useMemo(() => buildYAxisTicks(maxValue), [maxValue]) const chartScaleMax = yAxisTicks[yAxisTicks.length - 1] ?? 1 + const labelBand = granularity === 'month' ? LABEL_BAND_MONTH : LABEL_BAND_DAY const subtitle = useMemo(() => { if (granularity === 'month') { @@ -191,7 +194,7 @@ export function DailyActivityChart({ -
+
) diff --git a/apps/business/src/components/GenerateWebsiteLinkModal.tsx b/apps/business/src/components/GenerateWebsiteLinkModal.tsx index 9a50f38..e37ac50 100644 --- a/apps/business/src/components/GenerateWebsiteLinkModal.tsx +++ b/apps/business/src/components/GenerateWebsiteLinkModal.tsx @@ -9,6 +9,7 @@ import { listBlogCategories } from '../services/blogCategoryService' import { listPortfolioCategories } from '../services/portfolioCategoryService' import { listProductCategories, mapProductCategoryToUi } from '../services/productCategoryService' import { listVideoCategories } from '../services/videoCategoryService' +import { listWorkshopCategories } from '../services/workshopCategoryService' import type { ProductCategory } from '../types/category' import { WEBSITE_LINK_KINDS, @@ -31,6 +32,7 @@ const KIND_LABEL: Record = { blog: 'website.linkGenerator.types.blog', portfolio: 'website.linkGenerator.types.portfolio', video: 'website.linkGenerator.types.video', + workshop: 'website.linkGenerator.types.workshop', } const ANIMATION_MS = 220 @@ -54,6 +56,8 @@ async function loadCategories(kind: WebsiteLinkKind, signal: AbortSignal) { return listPortfolioCategories(signal) case 'video': return listVideoCategories(signal) + case 'workshop': + return listWorkshopCategories(signal) case 'product': case 'storeItem': return listProductCategories(signal) diff --git a/apps/business/src/components/ImageCropper.module.css b/apps/business/src/components/ImageCropper.module.css index f14cc13..6293e22 100644 --- a/apps/business/src/components/ImageCropper.module.css +++ b/apps/business/src/components/ImageCropper.module.css @@ -13,7 +13,7 @@ gap: 8px; box-sizing: border-box; min-height: 0; - border: 2px dashed rgba(148, 163, 184, 0.4); + border: 2px dashed var(--primary); border-radius: var(--radius-sm); background: var(--surface); color: var(--text-secondary); diff --git a/apps/business/src/components/ImageUploader.module.css b/apps/business/src/components/ImageUploader.module.css index 11bf5ca..1f6b832 100644 --- a/apps/business/src/components/ImageUploader.module.css +++ b/apps/business/src/components/ImageUploader.module.css @@ -56,7 +56,7 @@ align-items: center; justify-content: center; gap: 6px; - border: 2px dashed rgba(148, 163, 184, 0.4); + border: 2px dashed var(--primary); border-radius: var(--radius-sm); background: var(--surface); color: var(--text-secondary); diff --git a/apps/business/src/components/InstructionCard.tsx b/apps/business/src/components/InstructionCard.tsx index 3b6ace8..43cde8b 100644 --- a/apps/business/src/components/InstructionCard.tsx +++ b/apps/business/src/components/InstructionCard.tsx @@ -41,6 +41,14 @@ export function InstructionCard({ const hasAbstract = Boolean(video.abstract?.trim()) const titleDir = isFa ? 'rtl' : titleLocale.dir const abstractDir = isFa ? 'rtl' : abstractLocale.dir + const hasVideo = Boolean(video.embedCode?.trim()) + const providerLabel = !hasVideo + ? null + : video.videoProvider === 'youtube' + ? 'YouTube' + : video.videoProvider === 'aparat' + ? 'Aparat' + : null return (
@@ -58,9 +66,11 @@ export function InstructionCard({
)} - - {video.videoProvider === 'youtube' ? 'YouTube' : 'Aparat'} - + {providerLabel ? ( + + {providerLabel} + + ) : null}
diff --git a/apps/business/src/components/ProductActivityChart.module.css b/apps/business/src/components/ProductActivityChart.module.css index b8ba766..6e3cc22 100644 --- a/apps/business/src/components/ProductActivityChart.module.css +++ b/apps/business/src/components/ProductActivityChart.module.css @@ -89,7 +89,7 @@ justify-content: space-between; flex-shrink: 0; width: 40px; - padding-bottom: 32px; + padding-bottom: 80px; box-sizing: border-box; direction: ltr; } @@ -107,14 +107,13 @@ position: relative; flex: 1; min-width: 0; - overflow-x: auto; - overflow-y: visible; + overflow: visible; } .chartGrid { position: absolute; inset-inline: 0; - bottom: 32px; + bottom: 80px; pointer-events: none; z-index: 0; } @@ -132,16 +131,16 @@ display: flex; flex-direction: row; align-items: flex-end; - gap: 14px; - min-width: 100%; + gap: 4px; + width: 100%; padding-bottom: 4px; direction: ltr; unicode-bidi: isolate; } .group { - flex: 1; - min-width: 36px; + flex: 1 1 0; + min-width: 0; display: flex; flex-direction: column; align-items: center; @@ -151,13 +150,16 @@ .bars { display: flex; align-items: flex-end; + justify-content: center; height: 200px; } .barWrap { position: relative; display: flex; + flex: 0 0 auto; align-items: flex-end; + justify-content: center; height: 100%; z-index: 1; } @@ -228,13 +230,30 @@ } .label { - font-size: 11px; + display: flex; + align-items: center; + justify-content: center; + height: 68px; + width: 100%; + overflow: hidden; + font-size: 10px; color: var(--text-muted); font-weight: 500; font-family: var(--font-ui); text-align: center; } +.labelText { + writing-mode: vertical-rl; + transform: rotate(180deg); + white-space: nowrap; + max-height: 68px; + overflow: hidden; + text-overflow: ellipsis; + line-height: 1.2; + letter-spacing: 0.04em; +} + @media (max-width: 768px) { .header { flex-direction: column; diff --git a/apps/business/src/components/ProductActivityChart.tsx b/apps/business/src/components/ProductActivityChart.tsx index c2ef2f9..4a03fd0 100644 --- a/apps/business/src/components/ProductActivityChart.tsx +++ b/apps/business/src/components/ProductActivityChart.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useRef, useState } from 'react' +import { useEffect, useMemo, useState } from 'react' import { useLocale } from '@meshkee/dashboard-ui' import { ApiError } from '../lib/api' import { listAllProducts } from '../services/productService' @@ -8,7 +8,8 @@ import { useT } from '../i18n/useT' import styles from './ProductActivityChart.module.css' const CHART_HEIGHT = 200 -const BAR_GAP = 6 +const LABEL_BAND = 80 +const BAR_GAP = 4 export function ProductActivityChart() { const t = useT() @@ -16,7 +17,6 @@ export function ProductActivityChart() { const [data, setData] = useState([]) const [isLoading, setIsLoading] = useState(true) const [error, setError] = useState('') - const chartWrapRef = useRef(null) useEffect(() => { const controller = new AbortController() @@ -51,13 +51,6 @@ export function ProductActivityChart() { const yAxisTicks = useMemo(() => buildYAxisTicks(maxValue), [maxValue]) const chartScaleMax = yAxisTicks[yAxisTicks.length - 1] ?? 1 - useEffect(() => { - if (isLoading || error) return - const wrap = chartWrapRef.current - if (!wrap) return - wrap.scrollLeft = wrap.scrollWidth - }, [isLoading, error, data]) - const totals = useMemo( () => ({ added: data.reduce((sum, item) => sum + item.added, 0), @@ -96,7 +89,7 @@ export function ProductActivityChart() { -
+
- - {item.label} + + {item.label}
) diff --git a/apps/business/src/i18n/messages.ts b/apps/business/src/i18n/messages.ts index 8c0b87d..ee2393a 100644 --- a/apps/business/src/i18n/messages.ts +++ b/apps/business/src/i18n/messages.ts @@ -1701,6 +1701,7 @@ const en = { 'website.linkGenerator.types.blog': 'Blog', 'website.linkGenerator.types.portfolio': 'Portfolio', 'website.linkGenerator.types.video': 'Video', + 'website.linkGenerator.types.workshop': 'Workshop', 'website.staticImages.subtitle': 'Websites load these by key from GET /tenants/{domain}/website/static-images', @@ -4031,6 +4032,7 @@ const fa: Record = { 'website.linkGenerator.types.blog': 'بلاگ', 'website.linkGenerator.types.portfolio': 'نمونه کار', 'website.linkGenerator.types.video': 'ویدیو', + 'website.linkGenerator.types.workshop': 'کارگاه', 'website.staticImages.subtitle': 'وب‌سایت‌ها این تصاویر را با کلید از GET /tenants/{domain}/website/static-images می‌خوانند', diff --git a/apps/business/src/pages/AddNewProductPage.module.css b/apps/business/src/pages/AddNewProductPage.module.css index 33d8e45..0204a95 100644 --- a/apps/business/src/pages/AddNewProductPage.module.css +++ b/apps/business/src/pages/AddNewProductPage.module.css @@ -290,7 +290,7 @@ padding: 3px; gap: 2px; background: transparent; - border: 1px solid #0a1628; + border: 1px solid var(--primary); border-radius: 50px; } diff --git a/apps/business/src/pages/InstructionDetailsPage.tsx b/apps/business/src/pages/InstructionDetailsPage.tsx index a1c869d..22c9c4f 100644 --- a/apps/business/src/pages/InstructionDetailsPage.tsx +++ b/apps/business/src/pages/InstructionDetailsPage.tsx @@ -83,8 +83,14 @@ export function InstructionDetailsPage() { const contentLocale = textLocaleAttrs( video.descriptionHtml?.replace(/<[^>]+>/g, ' ') ?? '', ) - const providerLabel = - video.videoProvider === 'youtube' ? 'YouTube' : 'Aparat' + const hasVideo = Boolean(video.embedCode?.trim()) + const providerLabel = !hasVideo + ? null + : video.videoProvider === 'youtube' + ? 'YouTube' + : video.videoProvider === 'aparat' + ? 'Aparat' + : null return (
diff --git a/apps/business/src/pages/InstructionListPage.tsx b/apps/business/src/pages/InstructionListPage.tsx index 1ddd143..f111c54 100644 --- a/apps/business/src/pages/InstructionListPage.tsx +++ b/apps/business/src/pages/InstructionListPage.tsx @@ -167,7 +167,7 @@ export function InstructionListPage() { /> ) : ( <> -
+
{videos.map((video) => ( = { @@ -18,6 +19,7 @@ export const WEBSITE_LINK_KIND_PATH: Record = { blog: '/blogs', portfolio: '/portfolios', video: '/videos', + workshop: '/workshops', } export function getWebsiteOrigin(domain = getBusinessDomain()): string { diff --git a/apps/customer/src/components/ImageCropper.module.css b/apps/customer/src/components/ImageCropper.module.css index 9115e98..10a5d0a 100644 --- a/apps/customer/src/components/ImageCropper.module.css +++ b/apps/customer/src/components/ImageCropper.module.css @@ -13,7 +13,7 @@ gap: 8px; width: 100%; min-height: 0; - border: 2px dashed rgba(148, 163, 184, 0.4); + border: 2px dashed var(--primary); border-radius: var(--radius-sm); background: color-mix(in srgb, var(--surface) 70%, transparent); color: var(--text-secondary); diff --git a/apps/customer/src/components/ImageUploader.module.css b/apps/customer/src/components/ImageUploader.module.css index ed15e5e..75fdd85 100644 --- a/apps/customer/src/components/ImageUploader.module.css +++ b/apps/customer/src/components/ImageUploader.module.css @@ -62,7 +62,7 @@ align-items: center; justify-content: center; gap: 6px; - border: 2px dashed rgba(148, 163, 184, 0.4); + border: 2px dashed var(--primary); border-radius: var(--radius-sm); background: color-mix(in srgb, var(--surface) 70%, transparent); color: var(--text-secondary); diff --git a/apps/super-admin/src/components/DomainLinksOverflowMenu.tsx b/apps/super-admin/src/components/DomainLinksOverflowMenu.tsx index 342f92e..e802d40 100644 --- a/apps/super-admin/src/components/DomainLinksOverflowMenu.tsx +++ b/apps/super-admin/src/components/DomainLinksOverflowMenu.tsx @@ -5,6 +5,7 @@ import { ExternalLink, FileText, Globe, + Mail, MoreHorizontal, Trash2, UserRound, @@ -20,6 +21,7 @@ type PopoverPosition = { interface DomainLinksOverflowMenuProps { host: string onAiPrompts: () => void + onEmailDns: () => void onRemove: () => void } @@ -42,6 +44,7 @@ function openHost(host: string) { export function DomainLinksOverflowMenu({ host, onAiPrompts, + onEmailDns, onRemove, }: DomainLinksOverflowMenuProps) { const [open, setOpen] = useState(false) @@ -61,7 +64,7 @@ export function DomainLinksOverflowMenu({ const size = popoverRef.current ? { width: popoverRef.current.offsetWidth, height: popoverRef.current.offsetHeight } - : { width: 220, height: 280 } + : { width: 220, height: 320 } setPopoverPos(fitFixedPopover(rect, size, 'end')) } @@ -190,6 +193,18 @@ export function DomainLinksOverflowMenu({ ))}
+