-
{currentVariant.icon}
+ {/* Top accent strip */}
+
+
+ {/* Soft decorative glow blob */}
+
+
+
+
+ {/* Icon with glow burst */}
+
-
+
{title}
-
+
+ {message}
+
-
+ {/* Divider */}
+
+
+ {/* Actions */}
+
-
+
diff --git a/src/components/ui/Status.tsx b/src/components/ui/Status.tsx
index 5b7d134..dacf0ef 100644
--- a/src/components/ui/Status.tsx
+++ b/src/components/ui/Status.tsx
@@ -40,6 +40,8 @@ type Tone =
| "urgent"
| "featured";
+type PulseKind = "ping" | "pulse" | "none";
+
function resolveTone(status: string): Tone {
if (greens.includes(status)) return "green";
if (reds.includes(status)) return "red";
@@ -56,80 +58,140 @@ function resolveTone(status: string): Tone {
const toneStyles: Record<
Tone,
- { root: string; dot: string; dotRing?: string }
+ {
+ root: string;
+ dot: string;
+ dotRing?: string;
+ pingColor?: string;
+ pulse: PulseKind;
+ shimmer: boolean;
+ }
> = {
green: {
root: "bg-gradient-to-br from-green via-green to-green-dark text-white shadow-[0_2px_10px_-2px_rgba(34,173,92,0.55)] ring-1 ring-inset ring-white/25",
dot: "bg-white shadow-[0_0_10px_rgba(255,255,255,0.85)]",
dotRing: "ring-1 ring-white/40",
+ pingColor: "bg-white/70",
+ pulse: "ping",
+ shimmer: true,
},
red: {
root: "bg-gradient-to-br from-error via-error to-error-dark text-white shadow-[0_2px_10px_-2px_rgba(242,48,48,0.5)] ring-1 ring-inset ring-white/20",
dot: "bg-white shadow-[0_0_10px_rgba(255,255,255,0.75)]",
dotRing: "ring-1 ring-white/35",
+ pingColor: "bg-white/70",
+ pulse: "pulse",
+ shimmer: true,
},
blue: {
root: "bg-gradient-to-br from-blue via-blue to-blue-dark text-white shadow-[0_2px_10px_-2px_rgba(60,80,224,0.45)] ring-1 ring-inset ring-white/25",
dot: "bg-white shadow-[0_0_10px_rgba(255,255,255,0.8)]",
dotRing: "ring-1 ring-white/35",
+ pingColor: "bg-white/70",
+ pulse: "ping",
+ shimmer: true,
},
yellow: {
root: "border border-yellow-dark-2/15 bg-gradient-to-br from-yellow-light to-yellow-light/70 text-yellow-dark-2 shadow-sm shadow-yellow-dark/10 ring-1 ring-inset ring-white/60",
dot: "bg-yellow-dark-2 shadow-[0_0_8px_rgba(217,119,6,0.45)]",
dotRing: "ring-2 ring-yellow-dark-2/25",
+ pingColor: "bg-yellow-dark-2/50",
+ pulse: "pulse",
+ shimmer: false,
},
orange: {
root: "bg-gradient-to-br from-orange-light to-orange-light/85 text-white shadow-[0_2px_10px_-2px_rgba(245,148,96,0.45)] ring-1 ring-inset ring-white/25",
dot: "bg-white shadow-[0_0_8px_rgba(255,255,255,0.7)]",
dotRing: "ring-1 ring-white/30",
+ pingColor: "bg-white/70",
+ pulse: "pulse",
+ shimmer: true,
},
gray: {
root: "border border-gray-4/60 bg-gradient-to-b from-gray-1 to-gray-2 text-gray-7 shadow-sm ring-1 ring-inset ring-white/80",
dot: "bg-gray-5 shadow-inner",
dotRing: "ring-1 ring-gray-4/50",
+ pulse: "none",
+ shimmer: false,
},
new: {
root: "border border-green-light-3/80 bg-gradient-to-br from-green-light-6 via-green-light-6 to-green-light-5 text-green-dark shadow-sm shadow-green/10 ring-1 ring-inset ring-white/70",
dot: "bg-green shadow-[0_0_8px_rgba(34,173,92,0.5)]",
dotRing: "ring-2 ring-green/20",
+ pingColor: "bg-green/60",
+ pulse: "ping",
+ shimmer: false,
},
info: {
root: "border border-blue-light-3/80 bg-gradient-to-br from-blue-light-5 via-blue-light-5 to-blue-light-4 text-blue-dark shadow-sm shadow-blue/10 ring-1 ring-inset ring-white/70",
dot: "bg-blue shadow-[0_0_8px_rgba(60,80,224,0.45)]",
dotRing: "ring-2 ring-blue/15",
+ pingColor: "bg-blue/60",
+ pulse: "ping",
+ shimmer: false,
},
urgent: {
root: "border border-error-light-3/90 bg-gradient-to-br from-error-light-6 via-error-light-6 to-error-light-5 text-error-dark shadow-sm shadow-error/10 ring-1 ring-inset ring-white/70",
dot: "bg-error shadow-[0_0_8px_rgba(242,48,48,0.45)]",
dotRing: "ring-2 ring-error/20",
+ pingColor: "bg-error/60",
+ pulse: "pulse",
+ shimmer: false,
},
featured: {
root: "bg-gradient-to-br from-primary via-[#6B63F3] to-primary text-white shadow-[0_2px_12px_-2px_rgba(87,80,241,0.55)] ring-1 ring-inset ring-white/30",
dot: "bg-white shadow-[0_0_12px_rgba(255,255,255,0.9)]",
dotRing: "ring-1 ring-white/40",
+ pingColor: "bg-white/70",
+ pulse: "ping",
+ shimmer: true,
},
};
const Status = ({ status = "draft", children }: IProps) => {
const tone = resolveTone(status);
- const { root, dot, dotRing } = toneStyles[tone];
+ const { root, dot, dotRing, pingColor, pulse, shimmer } = toneStyles[tone];
return (
+ {/* Shimmer sweep */}
+ {shimmer && (
+
+ )}
+
+ {/* Dot + radar ping */}
+ {pulse === "ping" && pingColor && (
+
)}
- />
- {children}
+
+
+
+ {children}
);
};
diff --git a/src/constants/tooltip.ts b/src/constants/tooltip.ts
index 24cb35c..d9e406e 100644
--- a/src/constants/tooltip.ts
+++ b/src/constants/tooltip.ts
@@ -1,6 +1,58 @@
import { CSSProperties } from "react";
import { VariantType } from "react-tooltip";
+type Palette = {
+ from: string;
+ to: string;
+ shadow: string;
+ border: string;
+};
+
+const variantPalettes: Record
= {
+ error: {
+ from: "#F23030",
+ to: "#B00B0B",
+ shadow:
+ "0 10px 30px -10px rgba(242,48,48,0.55), inset 0 1px 0 rgba(255,255,255,0.18)",
+ border: "rgba(255,255,255,0.14)",
+ },
+ info: {
+ from: "#5750F1",
+ to: "#3B36B3",
+ shadow:
+ "0 10px 30px -10px rgba(87,80,241,0.55), inset 0 1px 0 rgba(255,255,255,0.2)",
+ border: "rgba(255,255,255,0.16)",
+ },
+ success: {
+ from: "#22AD5C",
+ to: "#147F3D",
+ shadow:
+ "0 10px 30px -10px rgba(34,173,92,0.55), inset 0 1px 0 rgba(255,255,255,0.2)",
+ border: "rgba(255,255,255,0.16)",
+ },
+ warning: {
+ from: "#FBB040",
+ to: "#D97706",
+ shadow:
+ "0 10px 30px -10px rgba(217,119,6,0.55), inset 0 1px 0 rgba(255,255,255,0.2)",
+ border: "rgba(255,255,255,0.16)",
+ },
+ dark: {
+ from: "#1F2937",
+ to: "#0B1220",
+ shadow:
+ "0 10px 30px -10px rgba(0,0,0,0.65), inset 0 1px 0 rgba(255,255,255,0.08)",
+ border: "rgba(255,255,255,0.08)",
+ },
+ light: {
+ from: "#FFFFFF",
+ to: "#F1F5F9",
+ shadow:
+ "0 10px 30px -10px rgba(0,0,0,0.18), inset 0 1px 0 rgba(255,255,255,0.6)",
+ border: "rgba(15,23,42,0.06)",
+ },
+};
+
export const _TooltipDefaultParams = ({
id,
variant = id === "delete" ? "error" : "info",
@@ -9,10 +61,20 @@ export const _TooltipDefaultParams = ({
id: string;
variant?: VariantType;
styles?: CSSProperties;
-}) => ({
- id,
- delayShow: 500,
- delayHide: 500,
- variant,
- style: { backgroundColor: variant === "error" ? "#F23030" : "", ...styles },
-});
+}) => {
+ const palette = variantPalettes[variant] ?? variantPalettes.info;
+
+ return {
+ id,
+ delayShow: 220,
+ delayHide: 120,
+ variant,
+ className: "fancy-tooltip",
+ style: {
+ background: `linear-gradient(135deg, ${palette.from} 0%, ${palette.to} 100%)`,
+ boxShadow: palette.shadow,
+ border: `1px solid ${palette.border}`,
+ ...styles,
+ } satisfies CSSProperties,
+ };
+};
diff --git a/src/css/style.css b/src/css/style.css
index 30645ad..6cf139a 100644
--- a/src/css/style.css
+++ b/src/css/style.css
@@ -164,6 +164,39 @@
@apply !rounded-full !transition-opacity !duration-500;
}
+/* ---- Fancy tooltip ---- */
+.react-tooltip.fancy-tooltip {
+ @apply !rounded-md !px-2 !py-0.5 !font-medium !tracking-normal !text-white;
+ font-size: 10.5px !important;
+ line-height: 1.3 !important;
+ z-index: 99999;
+ transform: scale(0.85) translateY(4px);
+ transform-origin: center;
+ transition:
+ opacity var(--rt-transition-show-delay, 0.15s) ease-out,
+ transform 260ms cubic-bezier(0.34, 1.56, 0.64, 1) !important;
+ backdrop-filter: blur(6px);
+ -webkit-backdrop-filter: blur(6px);
+}
+
+.react-tooltip.fancy-tooltip.react-tooltip__show {
+ transform: scale(1) translateY(0);
+}
+
+.react-tooltip.fancy-tooltip.react-tooltip__closing {
+ transform: scale(0.92) translateY(2px);
+ transition:
+ opacity var(--rt-transition-closing-delay, 0.15s) ease-in,
+ transform 200ms cubic-bezier(0.4, 0, 1, 1) !important;
+}
+
+.react-tooltip.fancy-tooltip .react-tooltip-arrow {
+ background: inherit;
+ border: inherit;
+ width: 6px !important;
+ height: 6px !important;
+}
+
.tableCheckbox:checked ~ div {
@apply border-primary bg-primary;
}
diff --git a/src/lib/api/services/customers-service.ts b/src/lib/api/services/customers-service.ts
index 3a9446b..6f58b5e 100644
--- a/src/lib/api/services/customers-service.ts
+++ b/src/lib/api/services/customers-service.ts
@@ -66,7 +66,7 @@ export const customersService = {
}): Promise => {
try {
return (
- await api.post(
+ await api.patch(
API_ENDPOINTS.CUSTOMERS.ASSIGN_COMPANY_TO_CUSTOMER(id),
credentials,
)
diff --git a/src/lib/api/types/Customers.ts b/src/lib/api/types/Customers.ts
index 8213774..feb2a93 100644
--- a/src/lib/api/types/Customers.ts
+++ b/src/lib/api/types/Customers.ts
@@ -100,7 +100,7 @@ export const customersListResponse = z.object({
customers: z.array(customersSchema),
});
const assignCustomerToCompanySchema = z.object({
- company_ids: z.array(z.string()),
+ companies: z.array(z.string()).min(1),
});
export const CustomerListResponseSchema = createApiResponseSchema(
customersListResponse,