Fixes
Fixed an issue where media manager only shows files uploaded in specific sections. Fixed an issue with icon picker names. Removed jumbo rounding and a few other style tweaks.
This commit is contained in:
74 files changed
+1622
-195
No files matched your search
@@ -1,8 +1,23 @@
|
||||
"use client";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Gift } from "lucide-react";
|
||||
import { LoyaltyCardFont, LoyaltyCardTextSize, RewardPositionMode } from "@/generated/prisma/enums";
|
||||
import { findCatalogIcon } from "@/lib/icons/icon-catalog";
|
||||
import { cn } from "@/lib/utils/cn";
|
||||
import { resolveRewardThreshold } from "@/services/loyalty/reward-threshold";
|
||||
|
||||
const FONT_CLASSES: Record<LoyaltyCardFont, string> = {
|
||||
[LoyaltyCardFont.SANS]: "font-sans",
|
||||
[LoyaltyCardFont.SERIF]: "font-serif",
|
||||
[LoyaltyCardFont.MONO]: "font-mono",
|
||||
};
|
||||
|
||||
const TOP_RIGHT_TEXT_SIZE_CLASSES: Record<LoyaltyCardTextSize, string> = {
|
||||
[LoyaltyCardTextSize.SM]: "text-[8px]",
|
||||
[LoyaltyCardTextSize.MD]: "text-[10px]",
|
||||
[LoyaltyCardTextSize.LG]: "text-[13px]",
|
||||
};
|
||||
|
||||
export interface LoyaltyCardProps {
|
||||
businessName: string;
|
||||
@@ -15,6 +30,14 @@ export interface LoyaltyCardProps {
|
||||
cardBackgroundColor?: string | null;
|
||||
cardBackgroundImageUrl?: string | null;
|
||||
stampIcon?: string | null;
|
||||
/** See resolveRewardThreshold's doc comment — determines whether the final stamp position renders as a normal stamp or as the reward slot itself. */
|
||||
rewardPosition?: RewardPositionMode;
|
||||
cardTopRightText?: string | null;
|
||||
cardFont?: LoyaltyCardFont;
|
||||
cardTopRightTextSize?: LoyaltyCardTextSize;
|
||||
cardTopRightTextColor?: string | null;
|
||||
cardBorderColor?: string | null;
|
||||
cardMemberNumberColor?: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,13 +62,34 @@ export function LoyaltyCard({
|
||||
cardBackgroundColor,
|
||||
cardBackgroundImageUrl,
|
||||
stampIcon,
|
||||
rewardPosition = RewardPositionMode.AFTER_ALL_STAMPS,
|
||||
cardTopRightText,
|
||||
cardFont = LoyaltyCardFont.SANS,
|
||||
cardTopRightTextSize = LoyaltyCardTextSize.MD,
|
||||
cardTopRightTextColor,
|
||||
cardBorderColor,
|
||||
cardMemberNumberColor,
|
||||
}: LoyaltyCardProps) {
|
||||
const icon = findCatalogIcon(stampIcon);
|
||||
const threshold = resolveRewardThreshold({ stampsRequired, rewardPosition });
|
||||
// Only FINAL_SLOT_IS_REWARD sets aside a dedicated slot — under
|
||||
// AFTER_ALL_STAMPS every position is a plain stamp and the reward is
|
||||
// shown separately (see the "Reward available" banner on the loyalty
|
||||
// page), not by any one slot.
|
||||
const rewardSlotIndex = rewardPosition === RewardPositionMode.FINAL_SLOT_IS_REWARD ? stampsRequired - 1 : null;
|
||||
const borderColor = cardBorderColor || "var(--color-accent-loyalty)";
|
||||
|
||||
return (
|
||||
<div
|
||||
className="ring-accent-loyalty relative w-full overflow-hidden rounded-2xl shadow-[0_30px_60px_rgba(0,0,0,0.45)] ring-[3px]"
|
||||
style={{ aspectRatio: "1.65 / 1", backgroundColor: cardBackgroundColor || "#141414" }}
|
||||
className={cn(
|
||||
"relative w-full overflow-hidden rounded-2xl",
|
||||
FONT_CLASSES[cardFont],
|
||||
)}
|
||||
style={{
|
||||
aspectRatio: "1.65 / 1",
|
||||
backgroundColor: cardBackgroundColor || "#141414",
|
||||
boxShadow: `0 30px 60px rgba(0,0,0,0.45), 0 0 0 3px ${borderColor}`,
|
||||
}}
|
||||
role="img"
|
||||
aria-label={`${businessName} loyalty card, ${stampsEarned} of ${stampsRequired} stamps`}
|
||||
>
|
||||
@@ -66,33 +110,49 @@ export function LoyaltyCard({
|
||||
/>
|
||||
|
||||
<div className="relative flex h-full flex-col justify-between p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<h2 className="text-lg font-black italic tracking-tight text-white">Loyalty Card</h2>
|
||||
<span className="border-accent-loyalty/30 text-accent-loyalty rounded border px-2 py-0.5 text-[8px] font-bold tracking-widest uppercase">
|
||||
Member #{memberNumber}
|
||||
</span>
|
||||
<div className="flex flex-col items-end gap-1">
|
||||
{cardTopRightText && (
|
||||
<span
|
||||
className={cn(TOP_RIGHT_TEXT_SIZE_CLASSES[cardTopRightTextSize], "text-right font-bold text-white")}
|
||||
style={{ color: cardTopRightTextColor || undefined }}
|
||||
>
|
||||
{cardTopRightText}
|
||||
</span>
|
||||
)}
|
||||
<span
|
||||
className="border-accent-loyalty/30 text-accent-loyalty rounded border px-2 py-0.5 text-[8px] font-bold tracking-widest uppercase"
|
||||
style={{ color: cardMemberNumberColor || undefined, borderColor: cardMemberNumberColor ? `${cardMemberNumberColor}4d` : undefined }}
|
||||
>
|
||||
Member #{memberNumber}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-5 gap-2">
|
||||
{Array.from({ length: stampsRequired }, (_, i) => {
|
||||
const earned = i < stampsEarned;
|
||||
// Only the final slot gets reward styling, and only once it's
|
||||
// actually been earned — otherwise an in-progress card (e.g.
|
||||
// 3/10 stamps) would show the reward label on slot 10 already.
|
||||
const isRewardSlot = earned && i === stampsRequired - 1;
|
||||
const isRewardSlot = i === rewardSlotIndex;
|
||||
const earned = isRewardSlot ? stampsEarned >= threshold : i < stampsEarned;
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
className={cn(
|
||||
"flex aspect-square items-center justify-center rounded-full",
|
||||
isRewardSlot
|
||||
? "bg-accent-loyalty scale-[1.08] shadow-lg"
|
||||
? earned
|
||||
? "bg-accent-loyalty scale-[1.08] shadow-lg"
|
||||
: "border-accent-loyalty/50 bg-accent-loyalty/10 border-2 border-dashed"
|
||||
: earned
|
||||
? "bg-white shadow-inner"
|
||||
: "border-2 border-dashed border-white/20 bg-white/10",
|
||||
)}
|
||||
>
|
||||
{isRewardSlot ? (
|
||||
<span className="text-ink text-center text-[7px] leading-tight font-black">{rewardLabel}</span>
|
||||
earned ? (
|
||||
<span className="text-ink text-center text-[7px] leading-tight font-black">{rewardLabel}</span>
|
||||
) : (
|
||||
<Gift className="text-accent-loyalty h-3.5 w-3.5" aria-hidden="true" />
|
||||
)
|
||||
) : earned ? (
|
||||
<FontAwesomeIcon icon={icon} className="text-ink h-3.5 w-3.5" />
|
||||
) : null}
|
||||
@@ -101,7 +161,7 @@ export function LoyaltyCard({
|
||||
})}
|
||||
</div>
|
||||
{termsLine && (
|
||||
<p className="text-center text-[7px] font-bold tracking-widest text-white/40 uppercase">{termsLine}</p>
|
||||
<p className="text-center text-[7px] mt-2 font-bold tracking-widest text-white/40 uppercase">{termsLine}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user