Bulk and single SMS from Users, row selection with overflow summary, persistent large import toasts, and business SMS sender in Super Admin. Co-authored-by: Cursor <cursoragent@cursor.com>
140 lines
3.9 KiB
TypeScript
140 lines
3.9 KiB
TypeScript
import { getBusinessDomain } from '../lib/config'
|
|
|
|
export type WebsiteLinkKind = 'product' | 'storeItem' | 'blog' | 'portfolio' | 'video' | 'workshop'
|
|
|
|
export type WebsiteContentKind =
|
|
| 'product'
|
|
| 'blog'
|
|
| 'portfolio'
|
|
| 'video'
|
|
| 'instruction'
|
|
| 'workshop'
|
|
|
|
export type WebsiteLinkFilter = 'category' | 'tag'
|
|
|
|
export const WEBSITE_LINK_KINDS: WebsiteLinkKind[] = [
|
|
'product',
|
|
'storeItem',
|
|
'blog',
|
|
'portfolio',
|
|
'video',
|
|
'workshop',
|
|
]
|
|
|
|
export const WEBSITE_LINK_KIND_PATH: Record<WebsiteLinkKind, string> = {
|
|
product: '/products',
|
|
storeItem: '/store-items',
|
|
blog: '/blogs',
|
|
portfolio: '/portfolios',
|
|
video: '/videos',
|
|
workshop: '/workshops',
|
|
}
|
|
|
|
/** Matches backend `slugifyForUrl` used by website sitemaps / SEO paths. */
|
|
export function slugifyForWebsitePath(value: string, fallback = 'item'): string {
|
|
const slug = value
|
|
.trim()
|
|
.normalize('NFC')
|
|
.replace(/\s+/g, '-')
|
|
.replace(
|
|
/[^\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFFa-zA-Z0-9-]+/g,
|
|
'',
|
|
)
|
|
.replace(/-+/g, '-')
|
|
.replace(/^-|-$/g, '')
|
|
|
|
return slug || fallback
|
|
}
|
|
|
|
export function getWebsiteOrigin(domain = getBusinessDomain()): string {
|
|
const host = domain.trim().replace(/^https?:\/\//i, '').replace(/\/$/, '')
|
|
const protocol = window.location.protocol === 'http:' ? 'http:' : 'https:'
|
|
return `${protocol}//${host}`
|
|
}
|
|
|
|
/**
|
|
* Public storefront detail URL for a content item (canonical Meshkee website paths).
|
|
* Returns '' when a required slug/title is missing.
|
|
*/
|
|
export function buildWebsiteContentUrl(
|
|
kind: WebsiteContentKind,
|
|
item: {
|
|
id: string
|
|
slug?: string | null
|
|
title?: string | null
|
|
titleFa?: string | null
|
|
nameFa?: string | null
|
|
nameEn?: string | null
|
|
},
|
|
domain = getBusinessDomain(),
|
|
): string {
|
|
const origin = getWebsiteOrigin(domain)
|
|
const id = item.id?.trim()
|
|
if (!id) return ''
|
|
|
|
switch (kind) {
|
|
case 'product': {
|
|
const pathSlug = slugifyForWebsitePath(
|
|
item.nameFa?.trim() || item.nameEn?.trim() || item.title?.trim() || '',
|
|
'product',
|
|
)
|
|
return `${origin}/products/${encodeURIComponent(id)}/${encodeURIComponent(pathSlug)}`
|
|
}
|
|
case 'blog': {
|
|
const pathSlug = slugifyForWebsitePath(item.title?.trim() || '', 'blog')
|
|
return `${origin}/blog/${encodeURIComponent(id)}/${encodeURIComponent(pathSlug)}`
|
|
}
|
|
case 'portfolio': {
|
|
const pathSlug = slugifyForWebsitePath(
|
|
item.titleFa?.trim() || item.title?.trim() || '',
|
|
'portfolio',
|
|
)
|
|
return `${origin}/portfolios/${encodeURIComponent(id)}/${encodeURIComponent(pathSlug)}`
|
|
}
|
|
case 'video': {
|
|
const slug = item.slug?.trim()
|
|
return slug ? `${origin}/videos/${encodeURIComponent(slug)}` : ''
|
|
}
|
|
case 'instruction': {
|
|
const slug = item.slug?.trim()
|
|
return slug ? `${origin}/instructions/${encodeURIComponent(slug)}` : ''
|
|
}
|
|
case 'workshop': {
|
|
const slug = item.slug?.trim()
|
|
return slug ? `${origin}/workshops/${encodeURIComponent(slug)}` : ''
|
|
}
|
|
default:
|
|
return ''
|
|
}
|
|
}
|
|
|
|
export function buildWebsiteCategoryUrl(
|
|
kind: WebsiteLinkKind,
|
|
category: { id: string; slug?: string | null },
|
|
domain = getBusinessDomain(),
|
|
): string {
|
|
const path = WEBSITE_LINK_KIND_PATH[kind]
|
|
const query = category.slug
|
|
? `category=${encodeURIComponent(category.slug)}`
|
|
: `categoryId=${encodeURIComponent(category.id)}`
|
|
return `${getWebsiteOrigin(domain)}${path}?${query}`
|
|
}
|
|
|
|
export function buildWebsiteTagsUrl(
|
|
kind: WebsiteLinkKind,
|
|
tags: string[],
|
|
domain = getBusinessDomain(),
|
|
): string {
|
|
const cleaned = [
|
|
...new Set(tags.map((tag) => tag.trim()).filter(Boolean)),
|
|
]
|
|
if (cleaned.length === 0) return ''
|
|
|
|
const path = WEBSITE_LINK_KIND_PATH[kind]
|
|
const query =
|
|
cleaned.length === 1
|
|
? `tag=${encodeURIComponent(cleaned[0])}`
|
|
: `tags=${cleaned.map((tag) => encodeURIComponent(tag)).join(',')}`
|
|
return `${getWebsiteOrigin(domain)}${path}?${query}`
|
|
}
|