@romain130492/topchopsticks 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,12 @@
1
+ /** snake_case → "Business network · New friends". Never show a raw enum. */
2
+ export const humanizeValue = (value: string): string =>
3
+ String(value || '')
4
+ .split('_')
5
+ .filter(Boolean)
6
+ .map((word, index) => (index === 0 ? word.charAt(0).toUpperCase() + word.slice(1) : word))
7
+ .join(' ')
8
+
9
+ export const humanizeList = (values: unknown, separator = ' · '): string => {
10
+ const list = Array.isArray(values) ? values : [values]
11
+ return list.filter(Boolean).map((v) => humanizeValue(String(v))).join(separator)
12
+ }
@@ -0,0 +1,45 @@
1
+ import type { TemplateQuestion } from '../api/types'
2
+
3
+ /**
4
+ * Conditional questions.
5
+ *
6
+ * `event_price` only makes sense once someone picks "pay later", so it carries
7
+ * validation_rules: { visible_when: { key: 'booking_mode', equals: 'pay_later' } }
8
+ *
9
+ * A question whose rule does not hold is not rendered AND not required — those
10
+ * two must move together. Requiring something the flow never showed is what
11
+ * made every 1-to-1 submission fail with "Missing required organizer answer".
12
+ */
13
+
14
+ const asArray = (value: unknown): unknown[] => (Array.isArray(value) ? value : [value])
15
+
16
+ export function isQuestionVisible(
17
+ question: TemplateQuestion | null | undefined,
18
+ answers: Record<string, unknown> = {}
19
+ ): boolean {
20
+ const rule = question?.validation_rules?.visible_when
21
+ if (!rule || !rule.key) return true
22
+
23
+ const actual = answers[rule.key]
24
+
25
+ if (rule.equals !== undefined) {
26
+ return asArray(actual).some((entry) => String(entry) === String(rule.equals))
27
+ }
28
+ if (Array.isArray(rule.in)) {
29
+ return asArray(actual).some((entry) => rule.in!.map(String).includes(String(entry)))
30
+ }
31
+ // A rule naming a key with no comparison means "answered at all".
32
+ return actual !== undefined && actual !== null && actual !== ''
33
+ }
34
+
35
+ /** An answer counts only if it is really there. */
36
+ export function isAnswerFilled(value: unknown): boolean {
37
+ if (Array.isArray(value)) return value.length > 0
38
+ // A restaurant answer is an object; it counts only once it carries a real
39
+ // restaurants.id, so "typed something in the search box" is not an answer.
40
+ if (value && typeof value === 'object') {
41
+ const v = value as Record<string, unknown>
42
+ return Boolean(v.id || v.restaurant_id)
43
+ }
44
+ return value !== undefined && value !== null && value !== ''
45
+ }