Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | 4x 4x 24x 8x 159x 7x 7x | /**
* The handful of answers an admin types over and over in the WhatsApp inbox.
*
* WHY HARDCODED, AND WHY THAT IS THE WHOLE FEATURE
* The deleted RRM inbox's canned replies were a TypeScript object literal with
* no fetch, no hook and no query key, posting to the ordinary reply endpoint
* (`.context/briefs/whatsapp-inbox-triage.md` §4.1 verified this: no table
* holds them, and `rrm_templates` is a registry of Meta-approved templates,
* not operator text). The port's carve-out was "they come only if they are a
* hardcoded array hitting the existing reply endpoint" — which they are. So
* this module is content, not a feature: no new entity, no new write path.
*
* THE COPY IS NEW, AND DELIBERATELY SO
* All ten RRM bodies were partner-recruitment text — payouts in rupees, "where
* I got your number", "no exclusivity". Wrong audience: this inbox talks to
* homeowners who asked for a quote and to pros who are working a lead. The
* mechanism ported; every word below is written fresh for that audience and is
* **a first draft that whoever owns support copy should overwrite**. The two
* the approved mockup names, "Shared with pro" and "Need a day", are kept
* verbatim as labels.
*
* Rules the bodies follow, and that a replacement must keep:
* - No money. No figure, no rupee glyph: a quote is the pro's to give.
* - No promised date. "within a day" is a commitment the operator controls;
* "Tuesday" is one they do not.
* - "pro" or "designer" to the homeowner, never "vendor" or "partner".
* - Area in sq ft, the platform's one unit, when area comes up at all.
* - One short paragraph. These are WhatsApp messages, not emails.
*/
/**
* Closed union rather than `string`, so the record cannot quietly gain a
* seventh entry that no chip renders and no test covers.
*/
export type QuickReplyKey =
| "shared_with_pro"
| "need_a_day"
| "ask_for_details"
| "quote_on_the_way"
| "offer_a_call"
| "all_sorted";
export type QuickReply = {
key: QuickReplyKey;
/** Chip label. Two or three words, so several fit on one row. */
label: string;
/** What gets inserted into the draft. Never sent on its own. */
body: string;
};
export const QUICK_REPLIES: Record<QuickReplyKey, QuickReply> = {
shared_with_pro: {
key: "shared_with_pro",
label: "Shared with pro",
body: "Thanks for the message — I have passed this to the pro working on your project. They will reply here shortly.",
},
need_a_day: {
key: "need_a_day",
label: "Need a day",
body: "I am checking on this for you. Give me a day and I will come back with a proper answer rather than a guess.",
},
ask_for_details: {
key: "ask_for_details",
label: "Ask for details",
body: "To get you the right pro, could you share three things: the locality, the carpet area in sq ft, and roughly when you would like work to start?",
},
quote_on_the_way: {
key: "quote_on_the_way",
label: "Quote coming",
body: "Your estimate is being prepared. The pro will send it here as soon as it is ready, and you can ask them anything about it in this chat.",
},
offer_a_call: {
key: "offer_a_call",
label: "Offer a call",
body: "This is easier on a call. Is now a good time? If not, tell me a time that suits you and I will ring then.",
},
all_sorted: {
key: "all_sorted",
label: "All sorted",
body: "Glad that is sorted. Message here any time — this chat stays open.",
},
};
/**
* Chip render order. Explicit because object key order is not a design
* decision: the two the operator reaches for most often come first, and the
* closing line comes last.
*/
export const QUICK_REPLY_ORDER: readonly QuickReplyKey[] = [
"shared_with_pro",
"need_a_day",
"ask_for_details",
"quote_on_the_way",
"offer_a_call",
"all_sorted",
];
export function quickReplyBody(key: QuickReplyKey): string {
return QUICK_REPLIES[key].body;
}
export function quickReplyLabel(key: QuickReplyKey): string {
return QUICK_REPLIES[key].label;
}
/**
* First line of a body, for a tooltip or a title.
*
* Carried over from the RRM module with its rationale intact: an operator must
* be able to read what a control will put in front of a customer before
* pressing it. Here the chip only INSERTS, so the stake is lower — but the
* habit is the one worth keeping.
*/
export function firstLine(body: string): string {
// `split` always yields at least one element, including for "".
return (body.split("\n")[0] as string).trim();
}
/**
* Append a canned body to an existing draft.
*
* INSERTS, NEVER SENDS — this is the load-bearing decision ported from RRM. A
* chip that sends on one tap has no review step, and the one thing an operator
* cannot take back is a message that has already left. Appending below any
* existing text, separated by a blank line, also means tapping two chips
* composes rather than overwrites.
*/
export function appendQuickReply(draft: string, body: string): string {
const existing = draft.trimEnd();
return existing.length === 0 ? body : `${existing}\n\n${body}`;
}
|