Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | 2x 2x 2x 2x 2x 2x 2x 33x 13x 13x 13x 13x 2x 1x 2x 11x 4x 4x 7x 7x 13x 13x 2x 4x 3x 3x 23x 9x 4x 5x 5x 2x 2x 2x | // /find-us-badge — the "Find us on Interioring" link page for listed designers.
//
// Linked only from our WhatsApp and email messages: noindex, out of the
// sitemap and every nav. It takes ?pro={slug}; a published designer's slug personalises every
// snippet, anything else shows placeholders. Everything the page prints about a
// designer comes from the API lookup below — the raw query value is never echoed.
import { ApiError } from "./api/client";
import type { ApiResponse, Pro } from "./api/types";
// Section 6 "Need help?" contact details, as confirmed by the owner. Change
// them HERE ONLY — the page and its WhatsApp/email buttons read nothing
// else. `whatsapp` is shown as written; the wa.me link uses its digits.
export const FIND_US_BADGE_CONTACT = {
whatsapp: "+91 88972 26999",
email: "srini@interioring.com",
} as const;
// Snippets always point at production, whichever environment renders the page:
// designers paste them into their live websites.
export const SITE_ORIGIN = "https://www.interioring.com";
export const PAGE_URL = `${SITE_ORIGIN}/find-us-badge`;
// Stable, self-hosted badge files (apps/marketplace/public/badges/). We host
// them so the image can be changed later; never rename them. The older
// /badges/verified-on-interioring-* files stay in public/badges/ because
// designers already hotlink them.
export const BADGE_PATHS = {
light: "/badges/find-us-on-interioring-light.svg",
dark: "/badges/find-us-on-interioring-dark.svg",
} as const;
export type BadgeTheme = keyof typeof BADGE_PATHS;
export const PROFILE_LINK_PLACEHOLDER = "YOUR-PROFILE-LINK";
export const BUSINESS_NAME_PLACEHOLDER = "YOUR-BUSINESS-NAME";
export interface BadgePro {
slug: string;
businessName: string;
}
export interface SnippetValues {
profileUrl: string;
businessName: string;
}
// Real slugs are lowercase-hyphenated; seed slugs also carry underscores.
// Anything else (quotes, angle brackets, slashes) never reaches the API.
const SLUG_RE = /^[A-Za-z0-9_-]{1,100}$/;
export function parseProParam(raw: string | null | undefined): string | null {
return raw && SLUG_RE.test(raw) ? raw : null;
}
type ProLookup = Pro | { redirectTo?: string };
interface ProBySlugClient {
getProBySlug(slug: string): Promise<ApiResponse<ProLookup>>;
}
/**
* Look the ?pro= value up against published designers. Returns the slug and
* business name exactly as the API holds them, or null for a missing, invalid,
* unknown, draft or archived (delisted) designer. A renamed slug answers
* `{ redirectTo: "/pros/<new-slug>" }`; that one hop is followed.
*/
export async function resolveBadgePro(
api: ProBySlugClient,
raw: string | null | undefined,
): Promise<BadgePro | null> {
let slug = parseProParam(raw);
for (let hop = 0; slug && hop < 2; hop++) {
let data: ProLookup | undefined;
try {
data = (await api.getProBySlug(slug)).data;
} catch (err) {
if (!(err instanceof ApiError && err.status === 404)) {
console.error("find-us-badge: pro lookup failed", err);
}
return null;
}
if (data && "redirectTo" in data && data.redirectTo) {
slug = parseProParam(data.redirectTo.replace(/^\/pros\//, ""));
continue;
}
const pro = data as Pro | undefined;
const businessName = pro?.businessName?.trim();
const proSlug = parseProParam(pro?.slug);
if (pro?.status !== "published" || !proSlug || !businessName) return null;
return { slug: proSlug, businessName };
}
return null;
}
export function profileUrl(slug: string): string {
return `${SITE_ORIGIN}/pros/${slug}`;
}
export function snippetValues(pro: BadgePro | null): SnippetValues {
return pro
? { profileUrl: profileUrl(pro.slug), businessName: pro.businessName }
: {
profileUrl: PROFILE_LINK_PLACEHOLDER,
businessName: BUSINESS_NAME_PLACEHOLDER,
};
}
// Attribute-escape for the snippet strings below. Their attributes are always
// double-quoted, so & " < > is enough; an apostrophe (common in business
// names) stays readable in the code the designer copies.
function escapeAttr(value: string): string {
return value
.replace(/&/g, "&")
.replace(/"/g, """)
.replace(/</g, "<")
.replace(/>/g, ">");
}
// NO UTM PARAMETERS on the profile link. This is a deliberate, documented
// exception to OUT-09 / DEC-016 ("every link gets a UTM"): a UTM would create a
// parameterised URL variant and weaken the backlink. Keep these snippets a
// plain followed <a href> to /pros/{slug}: no rel="nofollow"/"sponsored"/"ugc",
// no redirect, no shortener, no /go/ link.
function linkOpen(v: SnippetValues): string {
return `<a href="${escapeAttr(v.profileUrl)}" title="${escapeAttr(`${v.businessName} on Interioring`)}">`;
}
export function buildHtmlLinkSnippet(v: SnippetValues): string {
return `${linkOpen(v)}Find us on Interioring</a>`;
}
export function buildBadgeSnippet(v: SnippetValues, theme: BadgeTheme): string {
const src = escapeAttr(`${SITE_ORIGIN}${BADGE_PATHS[theme]}`);
return `${linkOpen(v)}<img src="${src}" alt="Find us on Interioring" width="180" height="48" loading="lazy"></a>`;
}
/** wa.me share link carrying this page's URL (with ?pro= when validated). */
export function buildShareUrl(slug: string | null): string {
const pageUrl = slug
? `${PAGE_URL}?pro=${encodeURIComponent(slug)}`
: PAGE_URL;
return `https://wa.me/?text=${encodeURIComponent(pageUrl)}`;
}
export const CONTACT_LINKS = {
whatsapp: `https://wa.me/${FIND_US_BADGE_CONTACT.whatsapp.replace(/\D/g, "")}`,
email: `mailto:${FIND_US_BADGE_CONTACT.email}`,
} as const;
|