refactor(chat): migrate unlock dialog styles to tailwind
This commit is contained in:
@@ -6,7 +6,9 @@ import { ChatHeader } from "../chat-header";
|
||||
import { ChatInsufficientCreditsBanner } from "../chat-insufficient-credits-banner";
|
||||
import { ChatInputTextField } from "../chat-input-text-field";
|
||||
import { ChatSendButton } from "../chat-send-button";
|
||||
import { HistoryUnlockDialog } from "../history-unlock-dialog";
|
||||
import { ImageBubble } from "../image-bubble";
|
||||
import { InsufficientCreditsDialog } from "../insufficient-credits-dialog";
|
||||
import { MessageAvatar } from "../message-avatar";
|
||||
import { PrivateMessageCard } from "../private-message-card";
|
||||
import { PwaInstallDialog } from "../pwa-install-dialog";
|
||||
@@ -223,4 +225,73 @@ describe("chat Tailwind components", () => {
|
||||
expect(html).toContain("Cancel");
|
||||
expect(html).toContain("OK");
|
||||
});
|
||||
|
||||
it("renders HistoryUnlockDialog open and loading states", () => {
|
||||
expect(
|
||||
renderToStaticMarkup(
|
||||
<HistoryUnlockDialog
|
||||
open={false}
|
||||
lockedCount={2}
|
||||
onClose={() => undefined}
|
||||
onConfirm={() => undefined}
|
||||
/>,
|
||||
),
|
||||
).toBe("");
|
||||
|
||||
const html = renderToStaticMarkup(
|
||||
<HistoryUnlockDialog
|
||||
open
|
||||
lockedCount={3}
|
||||
isLoading
|
||||
errorMessage="Could not unlock history."
|
||||
onClose={() => undefined}
|
||||
onConfirm={() => undefined}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(html).toContain('aria-labelledby="history-unlock-title"');
|
||||
expect(html).toContain("z-[200]");
|
||||
expect(html).toContain("Unlock previous messages?");
|
||||
expect(html).toContain("We found 3 locked messages");
|
||||
expect(html).toContain("Could not unlock history.");
|
||||
expect(html).toContain("Unlocking...");
|
||||
expect(html).toContain("text-[var(--color-pwa-button-text,#ffffff)]");
|
||||
expect(html).toContain("disabled");
|
||||
});
|
||||
|
||||
it("renders InsufficientCreditsDialog with credit details", () => {
|
||||
expect(
|
||||
renderToStaticMarkup(
|
||||
<InsufficientCreditsDialog
|
||||
open={false}
|
||||
creditBalance={0}
|
||||
requiredCredits={0}
|
||||
shortfallCredits={0}
|
||||
onClose={() => undefined}
|
||||
onConfirm={() => undefined}
|
||||
/>,
|
||||
),
|
||||
).toBe("");
|
||||
|
||||
const html = renderToStaticMarkup(
|
||||
<InsufficientCreditsDialog
|
||||
open
|
||||
creditBalance={2}
|
||||
requiredCredits={5}
|
||||
shortfallCredits={3}
|
||||
onClose={() => undefined}
|
||||
onConfirm={() => undefined}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(html).toContain('aria-labelledby="insufficient-credits-title"');
|
||||
expect(html).toContain("z-[220]");
|
||||
expect(html).toContain("Not enough credits");
|
||||
expect(html).toContain("Balance");
|
||||
expect(html).toContain("Required");
|
||||
expect(html).toContain("Still needed");
|
||||
expect(html).toContain("text-[var(--color-pwa-button-text,#ffffff)]");
|
||||
expect(html).toContain("bg-[linear-gradient(90deg,#ff67e0_0%,#ff52a2_100%)]");
|
||||
expect(html).toContain("Top up now");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
.overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 200;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
padding:
|
||||
calc(var(--dialog-safe-margin, 16px) + var(--app-safe-top, 0px))
|
||||
calc(var(--dialog-safe-margin, 16px) + var(--app-safe-right, 0px))
|
||||
calc(var(--dialog-safe-margin, 16px) + var(--app-safe-bottom, 0px))
|
||||
calc(var(--dialog-safe-margin, 16px) + var(--app-safe-left, 0px));
|
||||
}
|
||||
|
||||
.dialog {
|
||||
width: 100%;
|
||||
max-width: var(--pwa-install-dialog-max-width, 360px);
|
||||
padding:
|
||||
var(--responsive-card-padding-lg, 24px)
|
||||
var(--responsive-card-padding, 16px)
|
||||
var(--responsive-card-padding, 16px);
|
||||
text-align: center;
|
||||
background: var(--color-page-background, #ffffff);
|
||||
border-radius: var(--responsive-card-radius, 40px);
|
||||
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0 0 10px;
|
||||
font-size: var(--responsive-page-title, var(--font-size-22, 22px));
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
color: var(--color-text-foreground, #171717);
|
||||
}
|
||||
|
||||
.content {
|
||||
margin:
|
||||
0
|
||||
var(--responsive-card-padding-lg, 24px)
|
||||
var(--spacing-lg, 16px);
|
||||
font-size: var(--responsive-body, var(--font-size-lg, 16px));
|
||||
line-height: 1.5;
|
||||
text-align: left;
|
||||
color: #393939;
|
||||
}
|
||||
|
||||
.error {
|
||||
margin:
|
||||
0
|
||||
var(--responsive-card-padding-lg, 24px)
|
||||
var(--spacing-lg, 16px);
|
||||
font-size: var(--responsive-caption, var(--font-size-md, 14px));
|
||||
line-height: 1.4;
|
||||
text-align: left;
|
||||
color: #c0364c;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-md, 12px);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.button {
|
||||
flex: 1 1 auto;
|
||||
min-height: var(--pwa-button-height, 44px);
|
||||
border: 0;
|
||||
border-radius: var(--radius-bottom-sheet, 28px);
|
||||
font-size: var(--responsive-body, var(--font-size-lg, 16px));
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.button:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.72;
|
||||
}
|
||||
|
||||
.secondary {
|
||||
color: var(--color-page-background, #ffffff);
|
||||
background: var(--color-text-secondary, #9e9e9e);
|
||||
}
|
||||
|
||||
.primary {
|
||||
color: var(--color-page-background, #ffffff);
|
||||
background: linear-gradient(
|
||||
var(--color-button-gradient-start, #ff67e0),
|
||||
var(--color-button-gradient-end, #ff52a2)
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import styles from "./history-unlock-dialog.module.css";
|
||||
|
||||
export interface HistoryUnlockDialogProps {
|
||||
open: boolean;
|
||||
lockedCount: number;
|
||||
@@ -23,26 +21,31 @@ export function HistoryUnlockDialog({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.overlay}
|
||||
className="fixed inset-0 z-[200] flex items-center justify-center bg-[rgba(0,0,0,0.5)] pb-[calc(var(--dialog-safe-margin,16px)+var(--app-safe-bottom,0px))] pl-[calc(var(--dialog-safe-margin,16px)+var(--app-safe-left,0px))] pr-[calc(var(--dialog-safe-margin,16px)+var(--app-safe-right,0px))] pt-[calc(var(--dialog-safe-margin,16px)+var(--app-safe-top,0px))]"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="history-unlock-title"
|
||||
>
|
||||
<div className={styles.dialog}>
|
||||
<h2 id="history-unlock-title" className={styles.title}>
|
||||
<div className="w-full max-w-[var(--pwa-install-dialog-max-width,360px)] rounded-[var(--responsive-card-radius,40px)] bg-[var(--color-page-background,#ffffff)] px-[var(--responsive-card-padding,16px)] pb-[var(--responsive-card-padding,16px)] pt-[var(--responsive-card-padding-lg,24px)] text-center shadow-[0_10px_20px_rgba(0,0,0,0.1)]">
|
||||
<h2
|
||||
id="history-unlock-title"
|
||||
className="m-0 mb-2.5 text-[var(--responsive-page-title,var(--font-size-22,22px))] font-bold leading-[1.2] text-[var(--color-text-foreground,#171717)]"
|
||||
>
|
||||
Unlock previous messages?
|
||||
</h2>
|
||||
<p className={styles.content}>
|
||||
<p className="mx-[var(--responsive-card-padding-lg,24px)] mb-[var(--spacing-lg,16px)] mt-0 text-left text-[var(--responsive-body,var(--font-size-lg,16px))] leading-[1.5] text-[#393939]">
|
||||
We found {lockedCount} locked messages in your chat history. You can
|
||||
unlock them now to continue the conversation with full context.
|
||||
</p>
|
||||
{errorMessage ? (
|
||||
<p className={styles.error}>{errorMessage}</p>
|
||||
<p className="mx-[var(--responsive-card-padding-lg,24px)] mb-[var(--spacing-lg,16px)] mt-0 text-left text-[var(--responsive-caption,var(--font-size-md,14px))] leading-[1.4] text-[#c0364c]">
|
||||
{errorMessage}
|
||||
</p>
|
||||
) : null}
|
||||
<div className={styles.actions}>
|
||||
<div className="flex w-full gap-[var(--spacing-md,12px)]">
|
||||
<button
|
||||
type="button"
|
||||
className={`${styles.button} ${styles.secondary}`}
|
||||
className="flex min-h-[var(--pwa-button-height,44px)] flex-auto cursor-pointer items-center justify-center rounded-[var(--radius-bottom-sheet,28px)] border-0 bg-[var(--color-text-secondary,#9e9e9e)] text-[var(--responsive-body,var(--font-size-lg,16px))] font-semibold text-[var(--color-pwa-button-text,#ffffff)] disabled:cursor-not-allowed disabled:opacity-72"
|
||||
onClick={onClose}
|
||||
disabled={isLoading}
|
||||
>
|
||||
@@ -50,7 +53,7 @@ export function HistoryUnlockDialog({
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`${styles.button} ${styles.primary}`}
|
||||
className="flex min-h-[var(--pwa-button-height,44px)] flex-auto cursor-pointer items-center justify-center rounded-[var(--radius-bottom-sheet,28px)] border-0 bg-[linear-gradient(var(--color-button-gradient-start,#ff67e0),var(--color-button-gradient-end,#ff52a2))] text-[var(--responsive-body,var(--font-size-lg,16px))] font-semibold text-[var(--color-pwa-button-text,#ffffff)] disabled:cursor-not-allowed disabled:opacity-72"
|
||||
onClick={onConfirm}
|
||||
disabled={isLoading}
|
||||
>
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
.overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 220;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding:
|
||||
calc(var(--dialog-safe-margin, 16px) + var(--app-safe-top, 0px))
|
||||
calc(var(--dialog-safe-margin, 16px) + var(--app-safe-right, 0px))
|
||||
calc(var(--dialog-safe-margin, 16px) + var(--app-safe-bottom, 0px))
|
||||
calc(var(--dialog-safe-margin, 16px) + var(--app-safe-left, 0px));
|
||||
background: rgba(0, 0, 0, 0.52);
|
||||
}
|
||||
|
||||
.dialog {
|
||||
width: 100%;
|
||||
max-width: var(--pwa-install-dialog-max-width, 360px);
|
||||
padding:
|
||||
var(--responsive-card-padding-lg, 24px)
|
||||
var(--responsive-card-padding, 18px)
|
||||
var(--responsive-card-padding, 18px);
|
||||
text-align: center;
|
||||
background: var(--color-page-background, #ffffff);
|
||||
border: 1px solid rgba(246, 87, 160, 0.16);
|
||||
border-radius: var(--responsive-card-radius, 36px);
|
||||
box-shadow: 0 18px 46px rgba(38, 18, 31, 0.18);
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0 0 10px;
|
||||
color: var(--color-text-foreground, #171717);
|
||||
font-size: var(--responsive-page-title, var(--font-size-22, 22px));
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin: 0 var(--spacing-md, 12px) var(--page-section-gap, 18px);
|
||||
color: #393939;
|
||||
font-size: var(--responsive-body, var(--font-size-lg, 16px));
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.creditList {
|
||||
display: grid;
|
||||
gap: var(--spacing-sm, 8px);
|
||||
margin: 0 0 var(--page-section-gap, 18px);
|
||||
padding: var(--spacing-md, 12px);
|
||||
background: #fff4f9;
|
||||
border-radius: var(--responsive-card-radius-sm, 22px);
|
||||
}
|
||||
|
||||
.creditItem {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--spacing-md, 12px);
|
||||
color: #4a3b43;
|
||||
font-size: var(--responsive-caption, var(--font-size-md, 14px));
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.creditItem dt {
|
||||
margin: 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.creditItem dd {
|
||||
margin: 0;
|
||||
color: #f657a0;
|
||||
font-size: var(--responsive-body, var(--font-size-lg, 16px));
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-md, 12px);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.button {
|
||||
flex: 1 1 auto;
|
||||
min-height: var(--pwa-button-height, 44px);
|
||||
border: 0;
|
||||
border-radius: var(--radius-bottom-sheet, 28px);
|
||||
font-size: var(--responsive-body, var(--font-size-lg, 16px));
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.secondary {
|
||||
color: var(--color-page-background, #ffffff);
|
||||
background: var(--color-text-secondary, #9e9e9e);
|
||||
}
|
||||
|
||||
.primary {
|
||||
color: var(--color-page-background, #ffffff);
|
||||
background: linear-gradient(90deg, #ff67e0 0%, #ff52a2 100%);
|
||||
box-shadow: 0 6px 14px rgba(246, 87, 160, 0.28);
|
||||
}
|
||||
|
||||
.button:focus-visible {
|
||||
outline: 2px solid #f657a0;
|
||||
outline-offset: 3px;
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import styles from "./insufficient-credits-dialog.module.css";
|
||||
|
||||
export interface InsufficientCreditsDialogProps {
|
||||
open: boolean;
|
||||
creditBalance: number;
|
||||
@@ -25,47 +23,56 @@ export function InsufficientCreditsDialog({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.overlay}
|
||||
className="fixed inset-0 z-[220] flex items-center justify-center bg-[rgba(0,0,0,0.52)] pb-[calc(var(--dialog-safe-margin,16px)+var(--app-safe-bottom,0px))] pl-[calc(var(--dialog-safe-margin,16px)+var(--app-safe-left,0px))] pr-[calc(var(--dialog-safe-margin,16px)+var(--app-safe-right,0px))] pt-[calc(var(--dialog-safe-margin,16px)+var(--app-safe-top,0px))]"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="insufficient-credits-title"
|
||||
>
|
||||
<div className={styles.dialog}>
|
||||
<h2 id="insufficient-credits-title" className={styles.title}>
|
||||
<div className="w-full max-w-[var(--pwa-install-dialog-max-width,360px)] rounded-[var(--responsive-card-radius,36px)] border border-[rgba(246,87,160,0.16)] bg-[var(--color-page-background,#ffffff)] px-[var(--responsive-card-padding,18px)] pb-[var(--responsive-card-padding,18px)] pt-[var(--responsive-card-padding-lg,24px)] text-center shadow-[0_18px_46px_rgba(38,18,31,0.18)]">
|
||||
<h2
|
||||
id="insufficient-credits-title"
|
||||
className="m-0 mb-2.5 text-[var(--responsive-page-title,var(--font-size-22,22px))] font-bold leading-[1.2] text-[var(--color-text-foreground,#171717)]"
|
||||
>
|
||||
Not enough credits
|
||||
</h2>
|
||||
<p className={styles.content}>
|
||||
<p className="mx-[var(--spacing-md,12px)] mb-[var(--page-section-gap,18px)] mt-0 text-[var(--responsive-body,var(--font-size-lg,16px))] leading-[1.5] text-[#393939]">
|
||||
Top up your credits to unlock this message and keep the moment going.
|
||||
</p>
|
||||
|
||||
{showCreditDetail ? (
|
||||
<dl className={styles.creditList}>
|
||||
<div className={styles.creditItem}>
|
||||
<dt>Balance</dt>
|
||||
<dd>{creditBalance}</dd>
|
||||
<dl className="mb-[var(--page-section-gap,18px)] grid gap-[var(--spacing-sm,8px)] rounded-[var(--responsive-card-radius-sm,22px)] bg-[#fff4f9] p-[var(--spacing-md,12px)]">
|
||||
<div className="flex items-center justify-between gap-[var(--spacing-md,12px)] text-[var(--responsive-caption,var(--font-size-md,14px))] leading-[1.3] text-[#4a3b43]">
|
||||
<dt className="m-0 font-medium">Balance</dt>
|
||||
<dd className="m-0 text-[var(--responsive-body,var(--font-size-lg,16px))] font-extrabold text-[#f657a0]">
|
||||
{creditBalance}
|
||||
</dd>
|
||||
</div>
|
||||
<div className={styles.creditItem}>
|
||||
<dt>Required</dt>
|
||||
<dd>{requiredCredits}</dd>
|
||||
<div className="flex items-center justify-between gap-[var(--spacing-md,12px)] text-[var(--responsive-caption,var(--font-size-md,14px))] leading-[1.3] text-[#4a3b43]">
|
||||
<dt className="m-0 font-medium">Required</dt>
|
||||
<dd className="m-0 text-[var(--responsive-body,var(--font-size-lg,16px))] font-extrabold text-[#f657a0]">
|
||||
{requiredCredits}
|
||||
</dd>
|
||||
</div>
|
||||
<div className={styles.creditItem}>
|
||||
<dt>Still needed</dt>
|
||||
<dd>{shortfallCredits}</dd>
|
||||
<div className="flex items-center justify-between gap-[var(--spacing-md,12px)] text-[var(--responsive-caption,var(--font-size-md,14px))] leading-[1.3] text-[#4a3b43]">
|
||||
<dt className="m-0 font-medium">Still needed</dt>
|
||||
<dd className="m-0 text-[var(--responsive-body,var(--font-size-lg,16px))] font-extrabold text-[#f657a0]">
|
||||
{shortfallCredits}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
) : null}
|
||||
|
||||
<div className={styles.actions}>
|
||||
<div className="flex w-full gap-[var(--spacing-md,12px)]">
|
||||
<button
|
||||
type="button"
|
||||
className={`${styles.button} ${styles.secondary}`}
|
||||
className="flex min-h-[var(--pwa-button-height,44px)] flex-auto cursor-pointer items-center justify-center rounded-[var(--radius-bottom-sheet,28px)] border-0 bg-[var(--color-text-secondary,#9e9e9e)] text-[var(--responsive-body,var(--font-size-lg,16px))] font-bold text-[var(--color-pwa-button-text,#ffffff)] focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#f657a0]"
|
||||
onClick={onClose}
|
||||
>
|
||||
Later
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`${styles.button} ${styles.primary}`}
|
||||
className="flex min-h-[var(--pwa-button-height,44px)] flex-auto cursor-pointer items-center justify-center rounded-[var(--radius-bottom-sheet,28px)] border-0 bg-[linear-gradient(90deg,#ff67e0_0%,#ff52a2_100%)] text-[var(--responsive-body,var(--font-size-lg,16px))] font-bold text-[var(--color-pwa-button-text,#ffffff)] shadow-[0_6px_14px_rgba(246,87,160,0.28)] focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#f657a0]"
|
||||
onClick={onConfirm}
|
||||
>
|
||||
Top up now
|
||||
|
||||
Reference in New Issue
Block a user