diff --git a/src/app/api/stores/[storeId]/route.ts b/src/app/api/stores/[storeId]/route.ts index ecd44cb..87fc1a7 100644 --- a/src/app/api/stores/[storeId]/route.ts +++ b/src/app/api/stores/[storeId]/route.ts @@ -146,6 +146,9 @@ function sanitizeLogoUrl(value: unknown) { if (typeof value !== "string") return null const v = value.trim() if (!v) return null + if (v.startsWith("/") && !v.includes("..")) { + return v.slice(0, 500) + } try { const parsed = new URL(v) if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return null @@ -155,6 +158,19 @@ function sanitizeLogoUrl(value: unknown) { } } +/** Storefront script runs on Shopify origin; relative /uploads/... must become absolute JUNO app URLs. */ +function resolveLogoUrlForInject(appBaseUrl: string, logoUrl: string | null) { + if (!logoUrl) return null + const v = logoUrl.trim() + if (!v) return null + if (/^https?:\/\//i.test(v)) return v + if (v.startsWith("/") && !v.includes("..")) { + const base = appBaseUrl.replace(/\/$/, "") + return `${base}${v}` + } + return null +} + function buildStorefrontScript(params: { appBaseUrl: string chatName: string @@ -332,18 +348,33 @@ function buildStorefrontScript(params: { btn.style.width = "56px"; btn.style.height = "56px"; btn.style.borderRadius = "16px"; - btn.style.border = "0"; btn.style.cursor = "pointer"; btn.style.boxShadow = "0 12px 28px rgba(0,0,0,0.20)"; - btn.style.background = brandColor || "#4f46e5"; - btn.style.color = "#fff"; btn.style.display = "flex"; btn.style.alignItems = "center"; btn.style.justifyContent = "center"; btn.style.fontFamily = fontFamily; btn.style.fontWeight = "800"; btn.style.letterSpacing = "-0.02em"; - btn.innerHTML = "Chat"; + btn.style.padding = "0"; + btn.style.overflow = "hidden"; + if (logoUrl) { + btn.style.background = "#ffffff"; + btn.style.border = "2px solid " + (brandColor || "#4f46e5"); + var fabImg = document.createElement("img"); + fabImg.src = logoUrl; + fabImg.alt = chatName || "Chat"; + fabImg.style.width = "36px"; + fabImg.style.height = "36px"; + fabImg.style.objectFit = "contain"; + fabImg.style.display = "block"; + btn.appendChild(fabImg); + } else { + btn.style.border = "0"; + btn.style.background = brandColor || "#4f46e5"; + btn.style.color = "#fff"; + btn.innerHTML = "Chat"; + } btn.addEventListener("click", function () { openPanel(); }); container.appendChild(btn); } @@ -447,7 +478,7 @@ async function syncStorefrontTheme(params: { tagline: params.tagline, brandColor: params.brandColor, fontFamily: params.fontFamily, - logoUrl: params.logoUrl, + logoUrl: resolveLogoUrlForInject(appBaseUrl, params.logoUrl), }) const putAsset = await putThemeAsset(cleanDomain, accessToken, apiVersion, themeId, assetKey, js) diff --git a/src/app/dashboard/stores/page.tsx b/src/app/dashboard/stores/page.tsx index 4326fb6..5c0d7a3 100644 --- a/src/app/dashboard/stores/page.tsx +++ b/src/app/dashboard/stores/page.tsx @@ -5,6 +5,7 @@ import { Store, Plus, Check, Loader2, AlertCircle, Building2, Trash2 } from "luc import AddStoreModal from "@/components/AddStoreModal" import DeleteStoreModal from "@/components/DeleteStoreModal" import StorefrontChatCustomizeModal, { type StorefrontChatCustomization } from "@/components/StorefrontChatCustomizeModal" +import { useTheme } from "@/components/ThemeProvider" interface StoreData { id: string @@ -38,6 +39,8 @@ export default function StoresPage() { const [customizeStoreId, setCustomizeStoreId] = useState(null) const [injectingStoreId, setInjectingStoreId] = useState(null) const [isAddModalOpen, setIsAddModalOpen] = useState(false) + const { theme } = useTheme() + const isLight = theme === "light" /* * Alignment Fix Details: * - Use React `createPortal` to render the modal backdrop and content at the end of `document.body`. @@ -261,17 +264,32 @@ export default function StoresPage() { {stores.map((store) => (
-
- +
+
{store.isActive && ( - + Active @@ -279,17 +297,23 @@ export default function StoresPage() {
-

{store.businessName}

-

{store.shopifyDomain}

-

+

+ {store.businessName} +

+

+ {store.shopifyDomain} +

+

Connected {new Date(store.createdAt).toLocaleDateString()}

-

Storefront Chat (Customers)

-

+

+ Storefront Chat (Customers) +

+

Show chat widget on the storefront

@@ -313,7 +337,11 @@ export default function StoresPage() {
diff --git a/src/app/storefront-chat/widget/page.tsx b/src/app/storefront-chat/widget/page.tsx index 7503c2b..fb507bb 100644 --- a/src/app/storefront-chat/widget/page.tsx +++ b/src/app/storefront-chat/widget/page.tsx @@ -179,16 +179,6 @@ export default function StorefrontChatWidgetPage() { return (
-
-
- -
-
-

JUNO Chat

-

Ask about products

-
-
-
{sendError ? (
diff --git a/src/components/StorefrontChatCustomizeModal.tsx b/src/components/StorefrontChatCustomizeModal.tsx index 463c9b8..8d8d7c9 100644 --- a/src/components/StorefrontChatCustomizeModal.tsx +++ b/src/components/StorefrontChatCustomizeModal.tsx @@ -3,6 +3,7 @@ import { useEffect, useMemo, useState } from "react" import { AnimatePresence, motion } from "framer-motion" import { Loader2, Palette, Save, Upload, WandSparkles, X } from "lucide-react" +import { useTheme } from "@/components/ThemeProvider" export type StorefrontChatCustomization = { storefrontChatName: string @@ -37,6 +38,9 @@ export default function StorefrontChatCustomizeModal({ onSave, onInject, }: Props) { + const { theme } = useTheme() + const isLight = theme === "light" + const [form, setForm] = useState(initialValues) const [saving, setSaving] = useState(false) const [injecting, setInjecting] = useState(false) @@ -122,68 +126,105 @@ export default function StorefrontChatCustomizeModal({ initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} - className="fixed inset-0 z-50 bg-black/70 backdrop-blur-sm" + className={`fixed inset-0 z-50 backdrop-blur-sm ${isLight ? "bg-slate-900/35" : "bg-black/70"}`} onClick={onClose} /> -
+
e.stopPropagation()} > -
+
-

Customize Storefront Chat

-

{storeName}

+

+ Customize Storefront Chat +

+

{storeName}

-
-

Supported: image files up to 3MB.

+

+ Supported: image files up to 3MB. +

-
-

Preview

+
+

+ Preview +

@@ -243,14 +304,22 @@ export default function StorefrontChatCustomizeModal({
{error ? ( -
{error}
+
{error}
) : null} -
+