{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "badge", "title": "Badge", "description": "Badge card component with locked/unlocked states that opens in a modal or bottom drawer.", "dependencies": [ "@radix-ui/react-dialog", "lucide-react" ], "files": [ { "path": "registry/components/badge.tsx", "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as Dialog from \"@radix-ui/react-dialog\";\nimport { X } from \"lucide-react\";\n\nconst STATIC_BASE =\n \"https://s3.ap-south-1.amazonaws.com/static.masaischool.com\";\n\nconst LOCK_ICON_URL = `${STATIC_BASE}/lock.svg`;\nconst LINKEDIN_ICON_URL = `${STATIC_BASE}/linkedin.svg`;\n\nconst THEME_BACKGROUND_URL: Record<\"theme1\" | \"theme2\" | \"theme3\", string> = {\n theme1: `${STATIC_BASE}/theme1-yellow.svg`,\n theme2: `${STATIC_BASE}/theme2-blue.svg`,\n theme3: `${STATIC_BASE}/theme3-red.svg`,\n};\n\n/** Header tint behind the decorative SVG; paired with `THEME_BACKGROUND_URL`. */\nconst THEME_BACKGROUND_COLOR: Record<\"theme1\" | \"theme2\" | \"theme3\", string> = {\n theme1: \"#FFE8B526\",\n theme2: \"#EDFAFA80\",\n theme3: \"#F4AF7E1A\",\n};\n\ntype BadgeProps = {\n name: string;\n description: string;\n lockedBadgeText?: string;\n courseTitle?: string;\n sectionModuleName?: string;\n badgeUrl: string;\n countLabel?: string;\n isLocked: boolean;\n openIn: \"bottom-drawer\" | \"modal\";\n /** Decorative header background (SVG + tint); pick theme1, theme2, or theme3. */\n theme: \"theme1\" | \"theme2\" | \"theme3\";\n firstUnlockedDate: string | Date;\n};\n\nfunction formatUnlockedDate(value: string | Date) {\n const date = value instanceof Date ? value : new Date(value);\n\n if (Number.isNaN(date.getTime())) {\n return String(value);\n }\n\n const day = date.getDate();\n const mod100 = day % 100;\n const suffix =\n mod100 >= 11 && mod100 <= 13\n ? \"th\"\n : day % 10 === 1\n ? \"st\"\n : day % 10 === 2\n ? \"nd\"\n : day % 10 === 3\n ? \"rd\"\n : \"th\";\n const month = new Intl.DateTimeFormat(\"en-GB\", { month: \"short\" }).format(\n date,\n );\n const year = date.getFullYear();\n\n return `${day}${suffix} ${month} ${year}`;\n}\n\nfunction Badge({\n name,\n description,\n lockedBadgeText,\n courseTitle,\n sectionModuleName,\n badgeUrl,\n countLabel,\n isLocked,\n openIn,\n theme,\n firstUnlockedDate,\n}: BadgeProps) {\n const unlockedText = formatUnlockedDate(firstUnlockedDate);\n const drawer = openIn === \"bottom-drawer\";\n const detailText = isLocked ? lockedBadgeText || description : description;\n const themeBackgroundUrl = THEME_BACKGROUND_URL[theme];\n const themeBackgroundColor = THEME_BACKGROUND_COLOR[theme];\n const hasContextSection = Boolean(\n (courseTitle && courseTitle.trim()) ||\n (sectionModuleName && sectionModuleName.trim()),\n );\n\n const displayCountLabel = React.useMemo(() => {\n if (!countLabel) return undefined;\n const trimmed = countLabel.trim();\n if (!trimmed) return undefined;\n const numericPart = trimmed.toLowerCase().startsWith(\"x\")\n ? trimmed.slice(1)\n : trimmed;\n const count = Number.parseInt(numericPart, 10);\n if (!Number.isFinite(count) || count <= 1) return undefined;\n return `x${count}`;\n }, [countLabel]);\n const linkedinShareHref = React.useMemo(() => {\n const encoded = encodeURIComponent(badgeUrl);\n return `https://www.linkedin.com/sharing/share-offsite/?url=${encoded}`;\n }, [badgeUrl]);\n const isRepeatedUnlock = React.useMemo(() => {\n if (!displayCountLabel) return false;\n const count = Number.parseInt(displayCountLabel.replace(/^x/i, \"\"), 10);\n return Number.isFinite(count) && count > 2;\n }, [displayCountLabel]);\n\n return (\n \n \n \n \n {isLocked ? (\n \n \n Locked\n \n ) : displayCountLabel ? (\n \n {displayCountLabel}\n \n ) : null}\n \n \n\n \n \n \n \n \n
\n \n \n Close\n \n\n
\n
\n \n {isLocked ? (\n \n \n Locked\n \n ) : displayCountLabel ? (\n \n {displayCountLabel}\n \n ) : null}\n
\n
\n\n
\n \n {name}\n \n \n {detailText}\n \n
\n\n {!isLocked ? (\n
\n

\n {`${isRepeatedUnlock ? \"First unlocked on\" : \"Unlocked on\"} ${unlockedText}`}\n

\n
\n ) : null}\n
\n \n \n {hasContextSection ? (\n
\n {sectionModuleName ? (\n

\n {sectionModuleName}\n

\n ) : null}\n {courseTitle ? (\n

\n {courseTitle}\n

\n ) : null}\n
\n ) : null}\n {!isLocked ? (\n \n \n \n Share With Your Network\n \n \n ) : null}\n \n \n
\n
\n );\n}\n\nexport { Badge };\nexport type { BadgeProps };\n", "type": "registry:ui" } ], "type": "registry:ui" }