diff --git a/src/app/sidebar/components/user-header.module.css b/src/app/sidebar/components/user-header.module.css index 8810019e..764256c1 100644 --- a/src/app/sidebar/components/user-header.module.css +++ b/src/app/sidebar/components/user-header.module.css @@ -72,7 +72,9 @@ } .diamondIcon { - color: var(--color-accent, #f84d96); + width: 14px; + height: 13px; + display: block; flex-shrink: 0; } diff --git a/src/app/sidebar/components/user-header.tsx b/src/app/sidebar/components/user-header.tsx index fcc01a36..392ea95f 100644 --- a/src/app/sidebar/components/user-header.tsx +++ b/src/app/sidebar/components/user-header.tsx @@ -1,5 +1,7 @@ "use client"; +import Image from "next/image"; + import { UserMessageAvatar } from "@/app/chat/components/user-message-avatar"; import styles from "./user-header.module.css"; @@ -62,16 +64,13 @@ export function UserHeader({ )} {state === "vip" && ( - + /> VIP Member )} diff --git a/src/data/mock/chat/requests/send-image-message.json b/src/data/mock/chat/requests/send-image-message.json new file mode 100644 index 00000000..f55cf037 --- /dev/null +++ b/src/data/mock/chat/requests/send-image-message.json @@ -0,0 +1,11 @@ +{ + "message": "Look at this sunset.", + "image": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD...", + "imageId": "img_mock_001", + "imageThumbUrl": "https://cdn.cozsweet.com/mock/chat/img_mock_001_thumb.jpg", + "imageMediumUrl": "https://cdn.cozsweet.com/mock/chat/img_mock_001_medium.jpg", + "imageOriginalUrl": "https://cdn.cozsweet.com/mock/chat/img_mock_001_original.jpg", + "imageWidth": 1280, + "imageHeight": 960, + "useWebSocket": false +} diff --git a/src/data/mock/chat/requests/send-text-message.json b/src/data/mock/chat/requests/send-text-message.json new file mode 100644 index 00000000..68bb2ba8 --- /dev/null +++ b/src/data/mock/chat/requests/send-text-message.json @@ -0,0 +1,11 @@ +{ + "message": "I missed you today.", + "image": "", + "imageId": "", + "imageThumbUrl": "", + "imageMediumUrl": "", + "imageOriginalUrl": "", + "imageWidth": 0, + "imageHeight": 0, + "useWebSocket": false +} diff --git a/src/data/mock/chat/requests/sync-guest-messages.json b/src/data/mock/chat/requests/sync-guest-messages.json new file mode 100644 index 00000000..d1e30c7a --- /dev/null +++ b/src/data/mock/chat/requests/sync-guest-messages.json @@ -0,0 +1,14 @@ +{ + "messages": [ + { + "role": "user", + "content": "Hi Elio.", + "timestamp": "2026-06-23T02:10:00.000Z" + }, + { + "role": "assistant", + "content": "Hi, I am right here with you.", + "timestamp": "2026-06-23T02:10:04.000Z" + } + ] +} diff --git a/src/data/mock/chat/requests/unlock-private-message.json b/src/data/mock/chat/requests/unlock-private-message.json new file mode 100644 index 00000000..30427a37 --- /dev/null +++ b/src/data/mock/chat/requests/unlock-private-message.json @@ -0,0 +1,3 @@ +{ + "messageId": "msg_private_locked_001" +} diff --git a/src/data/mock/chat/responses/history-non-vip-with-locked-private-message.json b/src/data/mock/chat/responses/history-non-vip-with-locked-private-message.json new file mode 100644 index 00000000..ddf30d54 --- /dev/null +++ b/src/data/mock/chat/responses/history-non-vip-with-locked-private-message.json @@ -0,0 +1,46 @@ +{ + "code": 200, + "message": "success", + "success": true, + "data": { + "messages": [ + { + "role": "user", + "content": "I had a long day.", + "id": "msg_user_001", + "created_at": "2026-06-23T02:10:00.000Z", + "isPrivate": false, + "privateLocked": false, + "privateHint": null, + "image_url": null + }, + { + "role": "assistant", + "content": "Come here. Tell me everything, slowly.", + "id": "msg_ai_001", + "created_at": "2026-06-23T02:10:05.000Z", + "isPrivate": false, + "privateLocked": false, + "privateHint": null, + "image_url": null + }, + { + "role": "assistant", + "content": "", + "id": "msg_private_locked_001", + "created_at": "2026-06-23T02:11:12.000Z", + "isPrivate": true, + "privateLocked": true, + "privateHint": "Elio has a private message for you.", + "image_url": null + } + ], + "total": 3, + "limit": 50, + "offset": 0, + "isVip": false, + "privateFreeLimit": 1, + "privateUsedToday": 0, + "privateCanViewFree": true + } +} diff --git a/src/data/mock/chat/responses/history-vip-with-unlocked-private-message.json b/src/data/mock/chat/responses/history-vip-with-unlocked-private-message.json new file mode 100644 index 00000000..3088af64 --- /dev/null +++ b/src/data/mock/chat/responses/history-vip-with-unlocked-private-message.json @@ -0,0 +1,46 @@ +{ + "code": 200, + "message": "success", + "success": true, + "data": { + "messages": [ + { + "role": "user", + "content": "I had a long day.", + "id": "msg_user_001", + "created_at": "2026-06-23T02:10:00.000Z", + "isPrivate": false, + "privateLocked": false, + "privateHint": null, + "image_url": null + }, + { + "role": "assistant", + "content": "I saved this softer thought for you, just between us.", + "id": "msg_private_unlocked_001", + "created_at": "2026-06-23T02:11:12.000Z", + "isPrivate": true, + "privateLocked": false, + "privateHint": null, + "image_url": null + }, + { + "role": "assistant", + "content": "", + "id": "msg_ai_image_001", + "created_at": "2026-06-23T02:12:28.000Z", + "isPrivate": false, + "privateLocked": false, + "privateHint": null, + "image_url": "https://cdn.cozsweet.com/mock/chat/elio-schedule-001.jpg" + } + ], + "total": 3, + "limit": 50, + "offset": 0, + "isVip": true, + "privateFreeLimit": 1, + "privateUsedToday": 0, + "privateCanViewFree": true + } +} diff --git a/src/data/mock/chat/responses/send-message-daily-limit-blocked.json b/src/data/mock/chat/responses/send-message-daily-limit-blocked.json new file mode 100644 index 00000000..c2e151e6 --- /dev/null +++ b/src/data/mock/chat/responses/send-message-daily-limit-blocked.json @@ -0,0 +1,29 @@ +{ + "code": 200, + "message": "success", + "success": true, + "data": { + "mode": "http", + "reply": "", + "voiceUrl": "", + "audioUrl": "", + "intimacyChange": 0, + "newIntimacy": 42, + "relationshipStage": "close_friend", + "currentMood": "", + "messageId": "msg_blocked_daily_001", + "isGuest": false, + "timestamp": 1782180665000, + "blocked": true, + "blockReason": "daily_limit", + "blockDetail": { + "type": "daily_limit", + "usedToday": 20, + "limit": 20 + }, + "paywallTriggered": false, + "showUpgrade": false, + "imageType": null, + "imageUrl": null + } +} diff --git a/src/data/mock/chat/responses/send-message-photo-paywall.json b/src/data/mock/chat/responses/send-message-photo-paywall.json new file mode 100644 index 00000000..68952f44 --- /dev/null +++ b/src/data/mock/chat/responses/send-message-photo-paywall.json @@ -0,0 +1,25 @@ +{ + "code": 200, + "message": "success", + "success": true, + "data": { + "mode": "http", + "reply": "I can show you that photo after you activate VIP.", + "voiceUrl": "", + "audioUrl": "", + "intimacyChange": 0, + "newIntimacy": 42, + "relationshipStage": "close_friend", + "currentMood": "playful", + "messageId": "msg_photo_paywall_001", + "isGuest": false, + "timestamp": 1782180725000, + "blocked": null, + "blockReason": null, + "blockDetail": null, + "paywallTriggered": true, + "showUpgrade": true, + "imageType": null, + "imageUrl": null + } +} diff --git a/src/data/mock/chat/responses/send-message-success.json b/src/data/mock/chat/responses/send-message-success.json new file mode 100644 index 00000000..04a70917 --- /dev/null +++ b/src/data/mock/chat/responses/send-message-success.json @@ -0,0 +1,25 @@ +{ + "code": 200, + "message": "success", + "success": true, + "data": { + "mode": "http", + "reply": "I am here. Tell me what happened, one small piece at a time.", + "voiceUrl": "", + "audioUrl": "", + "intimacyChange": 0, + "newIntimacy": 42, + "relationshipStage": "close_friend", + "currentMood": "gentle", + "messageId": "msg_ai_reply_001", + "isGuest": false, + "timestamp": 1782180605000, + "blocked": null, + "blockReason": null, + "blockDetail": null, + "paywallTriggered": false, + "showUpgrade": false, + "imageType": null, + "imageUrl": null + } +} diff --git a/src/data/mock/chat/responses/send-message-with-image.json b/src/data/mock/chat/responses/send-message-with-image.json new file mode 100644 index 00000000..f382c30f --- /dev/null +++ b/src/data/mock/chat/responses/send-message-with-image.json @@ -0,0 +1,25 @@ +{ + "code": 200, + "message": "success", + "success": true, + "data": { + "mode": "http", + "reply": "This is the kind of view I would want to share with you.", + "voiceUrl": "", + "audioUrl": "", + "intimacyChange": 0, + "newIntimacy": 43, + "relationshipStage": "close_friend", + "currentMood": "warm", + "messageId": "msg_ai_image_reply_001", + "isGuest": false, + "timestamp": 1782180785000, + "blocked": null, + "blockReason": null, + "blockDetail": null, + "paywallTriggered": false, + "showUpgrade": false, + "imageType": "elio_schedule", + "imageUrl": "https://cdn.cozsweet.com/mock/chat/elio-schedule-001.jpg" + } +} diff --git a/src/data/mock/chat/responses/speech-to-text-success.json b/src/data/mock/chat/responses/speech-to-text-success.json new file mode 100644 index 00000000..1a932c36 --- /dev/null +++ b/src/data/mock/chat/responses/speech-to-text-success.json @@ -0,0 +1,8 @@ +{ + "code": 200, + "message": "success", + "success": true, + "data": { + "text": "I want to talk to you for a while." + } +} diff --git a/src/data/mock/chat/responses/sync-guest-messages-success.json b/src/data/mock/chat/responses/sync-guest-messages-success.json new file mode 100644 index 00000000..8e17268d --- /dev/null +++ b/src/data/mock/chat/responses/sync-guest-messages-success.json @@ -0,0 +1,9 @@ +{ + "code": 200, + "message": "success", + "success": true, + "data": { + "syncedCount": 2, + "totalHistory": 18 + } +} diff --git a/src/data/mock/chat/responses/unlock-private-not-found.json b/src/data/mock/chat/responses/unlock-private-not-found.json new file mode 100644 index 00000000..57fcd716 --- /dev/null +++ b/src/data/mock/chat/responses/unlock-private-not-found.json @@ -0,0 +1,14 @@ +{ + "code": 200, + "message": "success", + "success": true, + "data": { + "unlocked": false, + "content": null, + "showUpgrade": false, + "paywallTriggered": false, + "privateFreeLimit": 0, + "privateUsedToday": 0, + "reason": "not_found" + } +} diff --git a/src/data/mock/chat/responses/unlock-private-not-private.json b/src/data/mock/chat/responses/unlock-private-not-private.json new file mode 100644 index 00000000..d70c6b87 --- /dev/null +++ b/src/data/mock/chat/responses/unlock-private-not-private.json @@ -0,0 +1,14 @@ +{ + "code": 200, + "message": "success", + "success": true, + "data": { + "unlocked": true, + "content": "This message is already public.", + "showUpgrade": false, + "paywallTriggered": false, + "privateFreeLimit": 1, + "privateUsedToday": 0, + "reason": "not_private" + } +} diff --git a/src/data/mock/chat/responses/unlock-private-quota-exceeded.json b/src/data/mock/chat/responses/unlock-private-quota-exceeded.json new file mode 100644 index 00000000..4b4f02b8 --- /dev/null +++ b/src/data/mock/chat/responses/unlock-private-quota-exceeded.json @@ -0,0 +1,14 @@ +{ + "code": 200, + "message": "success", + "success": true, + "data": { + "unlocked": false, + "content": null, + "showUpgrade": true, + "paywallTriggered": true, + "privateFreeLimit": 1, + "privateUsedToday": 1, + "reason": "quota_exceeded" + } +} diff --git a/src/data/mock/chat/responses/unlock-private-success.json b/src/data/mock/chat/responses/unlock-private-success.json new file mode 100644 index 00000000..6f1596d5 --- /dev/null +++ b/src/data/mock/chat/responses/unlock-private-success.json @@ -0,0 +1,14 @@ +{ + "code": 200, + "message": "success", + "success": true, + "data": { + "unlocked": true, + "content": "I saved this softer thought for you, just between us.", + "showUpgrade": false, + "paywallTriggered": false, + "privateFreeLimit": 1, + "privateUsedToday": 1, + "reason": "ok" + } +} diff --git a/src/data/mock/chat/responses/upload-image-success.json b/src/data/mock/chat/responses/upload-image-success.json new file mode 100644 index 00000000..fcedf4db --- /dev/null +++ b/src/data/mock/chat/responses/upload-image-success.json @@ -0,0 +1,15 @@ +{ + "code": 200, + "message": "success", + "success": true, + "data": { + "success": true, + "imageId": "img_mock_001", + "thumbUrl": "https://cdn.cozsweet.com/mock/chat/img_mock_001_thumb.jpg", + "mediumUrl": "https://cdn.cozsweet.com/mock/chat/img_mock_001_medium.jpg", + "originalUrl": "https://cdn.cozsweet.com/mock/chat/img_mock_001_original.jpg", + "width": 1280, + "height": 960, + "bytes": 286432 + } +} diff --git a/src/data/mock/chat/websocket-events/ai-sentence.json b/src/data/mock/chat/websocket-events/ai-sentence.json new file mode 100644 index 00000000..3840b809 --- /dev/null +++ b/src/data/mock/chat/websocket-events/ai-sentence.json @@ -0,0 +1,9 @@ +{ + "type": "sentence", + "data": { + "index": 0, + "text": "I am listening.", + "total": 2, + "done": false + } +} diff --git a/src/data/mock/chat/websocket-events/connected.json b/src/data/mock/chat/websocket-events/connected.json new file mode 100644 index 00000000..6209fb81 --- /dev/null +++ b/src/data/mock/chat/websocket-events/connected.json @@ -0,0 +1,6 @@ +{ + "type": "connected", + "data": { + "userId": "user_mock_001" + } +} diff --git a/src/data/mock/chat/websocket-events/error.json b/src/data/mock/chat/websocket-events/error.json new file mode 100644 index 00000000..4dd12a3d --- /dev/null +++ b/src/data/mock/chat/websocket-events/error.json @@ -0,0 +1,6 @@ +{ + "type": "error", + "data": { + "message": "WebSocket connection interrupted." + } +} diff --git a/src/data/mock/chat/websocket-events/image.json b/src/data/mock/chat/websocket-events/image.json new file mode 100644 index 00000000..79e9fa6d --- /dev/null +++ b/src/data/mock/chat/websocket-events/image.json @@ -0,0 +1,6 @@ +{ + "type": "image", + "data": { + "imageUrl": "https://cdn.cozsweet.com/mock/chat/elio-schedule-001.jpg" + } +} diff --git a/src/data/mock/chat/websocket-events/paywall-status.json b/src/data/mock/chat/websocket-events/paywall-status.json new file mode 100644 index 00000000..d2681c09 --- /dev/null +++ b/src/data/mock/chat/websocket-events/paywall-status.json @@ -0,0 +1,9 @@ +{ + "type": "paywall_status", + "data": { + "paywallTriggered": true, + "showUpgrade": true, + "imageType": null, + "imageUrl": null + } +}