feat: seo preview #44

Merged
kailasdevdas merged 11 commits from feat/healthcheck-seo into dev 2026-05-26 07:33:40 +00:00
2 changed files with 144 additions and 190 deletions
Showing only changes of commit e6d77b72b4 - Show all commits
+137 -109
View File
@@ -1,131 +1,159 @@
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"; import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
interface SeoPreviewData { interface SeoPreviewData {
seo?: { seo?: {
ogImage?: string; ogImage?: string;
ogTitle?: string; ogTitle?: string;
seoTitle?: string; seoTitle?: string;
ogDescription?: string; ogDescription?: string;
metaDescription?: string; metaDescription?: string;
slug?: string; slug?: string;
}; };
doctorId?: string; doctorId?: string;
name?: string; name?: string;
} }
interface SeoPreviewProps { interface SeoPreviewProps {
open: boolean; open: boolean;
onOpenChange: (open: boolean) => void; onOpenChange: (open: boolean) => void;
previewData?: SeoPreviewData | null; previewData?: SeoPreviewData | null;
url?: string; url?: string;
title?: string; title?: string;
} }
export default function SeoPreview({ export default function SeoPreview({
open, open,
onOpenChange, onOpenChange,
previewData, previewData,
url, url,
title = "SEO Preview", title = "SEO Preview",
}: SeoPreviewProps) { }: SeoPreviewProps) {
const previewUrl = url || "#"; const previewUrl = url || "#";
const imageUrl =
previewData?.seo?.ogImage || "https://placehold.co/1200x630?text=GG+Hospital";
const ogTitle =
previewData?.seo?.ogTitle || previewData?.seo?.seoTitle || "GG Hospital";
const ogDescription =
previewData?.seo?.ogDescription || previewData?.seo?.metaDescription ||
"No description available";
const searchTitle =
previewData?.seo?.seoTitle || previewData?.seo?.ogTitle || "SEO title preview";
const searchDescription =
previewData?.seo?.metaDescription || previewData?.seo?.ogDescription ||
"No meta description available";
return ( const hasSeoData =
<Dialog open={open} onOpenChange={onOpenChange}> !!previewData?.seo &&
<DialogContent className="sm:!max-w-4xl overflow-hidden"> !!(
<DialogHeader> previewData.seo.ogImage ||
<DialogTitle className="text-xl">{title}</DialogTitle> previewData.seo.ogTitle ||
</DialogHeader> previewData.seo.seoTitle ||
previewData.seo.ogDescription ||
previewData.seo.metaDescription
);
{previewData ? ( const imageUrl =
<div className="space-y-10 py-2"> previewData?.seo?.ogImage ||
<div> "https://placehold.co/1200x630?text=GG+Hospital";
<p className="mb-4 text-sm font-semibold text-muted-foreground">
Social Media Preview (WhatsApp / Facebook)
</p>
<a const ogTitle =
href={previewUrl} previewData?.seo?.ogTitle || previewData?.seo?.seoTitle || "GG Hospital";
target="_blank"
rel="noopener noreferrer"
className="block max-w-[560px] overflow-hidden rounded-xl border bg-white shadow-sm transition hover:shadow-md"
>
<div className="aspect-[1.91/1] overflow-hidden bg-muted">
<img
src={imageUrl}
alt="OG Preview"
className="h-full w-full object-cover"
/>
</div>
<div className="border-t bg-[#f0f2f5] px-4 py-3"> const ogDescription =
<p className="truncate text-[11px] uppercase tracking-wide text-[#65676b]"> previewData?.seo?.ogDescription ||
gg-hospital.com previewData?.seo?.metaDescription ||
</p> "No description available";
<h3 className="mt-1 line-clamp-2 text-[18px] font-semibold leading-snug text-[#1c1e21]"> const searchTitle =
{ogTitle} previewData?.seo?.seoTitle ||
</h3> previewData?.seo?.ogTitle ||
"SEO title preview";
<p className="mt-1 line-clamp-2 text-[14px] text-[#65676b]"> const searchDescription =
{ogDescription} previewData?.seo?.metaDescription ||
</p> previewData?.seo?.ogDescription ||
</div> "No meta description available";
</a>
</div>
<div> return (
<p className="mb-4 text-sm font-semibold text-muted-foreground"> <Dialog open={open} onOpenChange={onOpenChange}>
Google Search Preview <DialogContent className="sm:!max-w-4xl overflow-hidden">
</p> <DialogHeader>
<DialogTitle className="text-xl">{title}</DialogTitle>
</DialogHeader>
<div className="rounded-xl border bg-white p-6"> {hasSeoData ? (
<a <div className="space-y-10 py-2">
href={previewUrl} {/* Social Preview */}
target="_blank" <div>
rel="noopener noreferrer" <p className="mb-4 text-sm font-semibold text-muted-foreground">
className="block" Social Media Preview (WhatsApp / Facebook)
> </p>
<p className="truncate text-[14px] text-[#202124] hover:underline">
{previewUrl}
</p>
<h3 className="mt-1 text-[22px] leading-tight text-[#1a0dab] hover:underline"> <a
{searchTitle} href={previewUrl}
</h3> target="_blank"
</a> rel="noopener noreferrer"
className="block max-w-[560px] overflow-hidden rounded-xl border bg-white shadow-sm transition hover:shadow-md"
>
<div className="aspect-[1.91/1] overflow-hidden bg-muted">
<img
src={imageUrl}
alt="OG Preview"
className="h-full w-full object-cover"
/>
</div>
<p className="mt-2 line-clamp-3 text-[14px] leading-6 text-[#4d5156]"> <div className="border-t bg-[#f0f2f5] px-4 py-3">
{searchDescription} <p className="truncate text-[11px] uppercase tracking-wide text-[#65676b]">
</p> gg-hospital.com
</div> </p>
</div>
</div>
) : (
<div className="p-6 text-sm text-muted-foreground">
No preview data available.
</div>
)}
<DialogFooter className="p-6 border-t bg-background z-10 mt-0"> <h3 className="mt-1 line-clamp-2 text-[18px] font-semibold leading-snug text-[#1c1e21]">
<Button variant="outline" onClick={() => onOpenChange(false)}> {ogTitle}
Close </h3>
</Button>
</DialogFooter> <p className="mt-1 line-clamp-2 text-[14px] text-[#65676b]">
</DialogContent> {ogDescription}
</Dialog> </p>
); </div>
</a>
</div>
{/* Google Preview */}
<div>
<p className="mb-4 text-sm font-semibold text-muted-foreground">
Google Search Preview
</p>
<div className="rounded-xl border bg-white p-6">
<a
href={previewUrl}
target="_blank"
rel="noopener noreferrer"
className="block"
>
<p className="truncate text-[14px] text-[#202124] hover:underline">
{previewUrl}
</p>
<h3 className="mt-1 text-[22px] leading-tight text-[#1a0dab] hover:underline">
{searchTitle}
</h3>
</a>
<p className="mt-2 line-clamp-3 text-[14px] leading-6 text-[#4d5156]">
{searchDescription}
</p>
</div>
</div>
</div>
) : (
<div className="flex items-center justify-center py-16 text-sm text-muted-foreground">
No preview data available.
</div>
)}
<DialogFooter className="mt-0 border-t bg-background p-6">
<Button variant="outline" onClick={() => onOpenChange(false)}>
Close
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
} }
+7 -81
View File
@@ -9,13 +9,13 @@ import {
createHealthPackageApi, createHealthPackageApi,
createCategoryApi, createCategoryApi,
updateCategoryApi, updateCategoryApi,
deleteCategoryApi,
HealthPackage, HealthPackage,
HealthCategory, HealthCategory,
} from "@/api/healthCheck"; } from "@/api/healthCheck";
import PackageInquiriesTab from "@/components/PackageInquiriesTab/PackageInquiriesTab"; import PackageInquiriesTab from "@/components/PackageInquiriesTab/PackageInquiriesTab";
import HealthPackageModal from "@/components/HealthPackageModal/HealthPackageModal"; import HealthPackageModal from "@/components/HealthPackageModal/HealthPackageModal";
import SeoPreview from "@/components/SeoPreview/SeoPreview";
import { import {
Table, Table,
@@ -771,86 +771,12 @@ export default function HealthPackagePage() {
</DialogContent> </DialogContent>
</Dialog> </Dialog>
{/* --- VIEW MODAL --- */} <SeoPreview
<Dialog open={viewModal} onOpenChange={setViewModal}> open={viewModal}
<DialogContent className="w-full !max-w-5xl h-[90vh] flex flex-col p-0 overflow-hidden"> onOpenChange={setViewModal}
<DialogHeader className="p-6 border-b bg-background z-10"> previewData={selectedPackage}
<DialogTitle className="text-2xl"> url="asdfasdfasD"
{selectedPackage?.name} />
</DialogTitle>
</DialogHeader>
<div className="flex-1 overflow-y-auto p-6 space-y-6">
<div className="flex justify-between items-center bg-muted/20 p-4 rounded-lg">
<div>
<p className="text-sm text-muted-foreground uppercase font-bold">
Category
</p>
<p className="text-base font-medium">
{selectedPackage?.category?.name}
</p>
</div>
<div className="text-right">
<p className="text-sm text-muted-foreground uppercase font-bold">
Pricing
</p>
<p className="text-xl font-bold">
{selectedPackage?.discountedPrice != null
? `${selectedPackage.discountedPrice}`
: selectedPackage?.price != null
? `${selectedPackage.price}`
: "Not Entered"}
</p>
</div>
</div>
<div>
<h3 className="font-bold text-base border-b pb-2 mb-4">
Inclusions
</h3>
<div className="space-y-6">
{selectedPackage?.inclusions &&
typeof selectedPackage.inclusions === "object" &&
!Array.isArray(selectedPackage.inclusions) ? (
Object.entries(selectedPackage.inclusions).map(
([category, tests], idx) => (
<div key={idx}>
<h4 className="font-semibold text-sm text-primary mb-3 uppercase tracking-wider">
{category}
</h4>
<div className="grid grid-cols-2 gap-3">
{Array.isArray(tests) &&
tests.map((item, i) => (
<div
key={i}
className="text-sm border p-3 rounded bg-background shadow-sm"
>
{item}
</div>
))}
</div>
</div>
),
)
) : (
<div className="grid grid-cols-2 gap-3">
{Array.isArray(selectedPackage?.inclusions) &&
selectedPackage.inclusions.map((item, i) => (
<div
key={i}
className="text-sm border p-3 rounded bg-background shadow-sm"
>
{item}
</div>
))}
</div>
)}
</div>
</div>
</div>
<DialogFooter className="p-6 border-t">
<Button onClick={() => setViewModal(false)}>Close</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div> </div>
); );
} }