@temboplus/frontend-core 1.0.2 → 1.0.3-beta.1

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,2 @@
1
+ export * from "./timezone/index.js";
2
+ export * from "./text/index.js";
@@ -0,0 +1 @@
1
+ export * from "./text.js";
@@ -32,62 +32,16 @@ declare function getInitialsFrom(fullName: string): string;
32
32
  /**
33
33
  * Validates a personal name (account holder name, wallet owner name, etc.)
34
34
  * Used across bank accounts and mobile-money wallets for consistent validation.
35
- *
36
- * @param {string} name - The name to validate
37
- * @returns {boolean} True if the name meets all validation criteria, false otherwise
38
- *
39
- * @example
40
- * // Returns true
41
- * validatePersonalName("John Smith");
42
- *
43
- * @example
44
- * // Returns false (too short)
45
- * validatePersonalName("Jo");
46
- *
47
- * @example
48
- * // Returns false (invalid characters)
49
- * validatePersonalName("User123");
50
35
  */
51
36
  declare function validatePersonalName(name: string): boolean;
52
37
  /**
53
38
  * Validates a business name (company, organization, etc.)
54
39
  * More lenient than personal name validation.
55
- *
56
- * @param {string} name - The business name to validate
57
- * @returns {boolean} True if the name meets business name criteria, false otherwise
58
- *
59
- * @example
60
- * // Returns true
61
- * validateBusinessName("ABC Corporation Ltd");
62
- *
63
- * @example
64
- * // Returns true
65
- * validateBusinessName("Smith & Sons Inc.");
66
- *
67
- * @example
68
- * // Returns true
69
- * validateBusinessName("M-Pesa");
70
40
  */
71
41
  declare function validateBusinessName(name: string): boolean;
72
42
  /**
73
43
  * Validates any name (personal or business) with automatic detection.
74
- * First tries to detect if it's a business name, then validates accordingly.
75
- * If detection fails, tries both validation methods.
76
- *
77
- * @param {string} name - The name to validate (personal or business)
78
- * @returns {boolean} True if the name is valid as either personal or business name
79
- *
80
- * @example
81
- * // Returns true (personal name)
82
- * validateName("John Smith");
83
- *
84
- * @example
85
- * // Returns true (business name)
86
- * validateName("CRDB Bank PLC");
87
- *
88
- * @example
89
- * // Returns true (could be either)
90
- * validateName("Smith Solutions");
44
+ * First tries personal name validation; if that fails, falls back to business name validation.
91
45
  */
92
46
  declare function validateName(name: string): boolean;
93
47
  export declare const TextUtils: {
@@ -0,0 +1,49 @@
1
+ import { TZDate } from "@date-fns/tz";
2
+ /**
3
+ * Timezone-aware day-boundary helpers.
4
+ *
5
+ * `react-day-picker` and most form widgets hand us `Date` objects whose
6
+ * year/month/day come from the **browser's** local clock. When the user
7
+ * has chosen a different timezone in Settings, that's wrong: picking
8
+ * "May 25" from London should map to "midnight 25 May in Africa/Nairobi"
9
+ * (i.e. 21:00 UTC on the 24th), not "midnight 25 May BST" (23:00 UTC).
10
+ *
11
+ * Two distinct directions:
12
+ *
13
+ * - **Picker → API**: `startOfDayInTimezone` / `endOfDayInTimezone` take
14
+ * a picker `Date` and return a plain `Date` anchored at the right UTC
15
+ * instant for the wall-clock day boundary in `tz`. We deliberately
16
+ * return a plain `Date` (not `TZDate`) so `.toISOString()` produces
17
+ * UTC-`Z` format — every value reaching the server / SDK must be
18
+ * UTC-`Z`, never the `+HH:MM` offset form that `TZDate.toISOString()`
19
+ * emits by default.
20
+ *
21
+ * - **API → Picker**: `parseInTimezone` parses a server ISO string into
22
+ * a `TZDate` so `getDate()`/`getMonth()`/`getFullYear()` report the
23
+ * wall-clock day in `tz` — which is what react-day-picker uses to
24
+ * highlight a cell. Do NOT `.toISOString()` the result without going
25
+ * through `toUtcInstant` first.
26
+ */
27
+ /**
28
+ * Strip timezone identity from any `Date` subclass, returning the bare
29
+ * UTC instant. `.toISOString()` on the result is guaranteed `Z` form.
30
+ */
31
+ export declare function toUtcInstant(date: Date): Date;
32
+ /**
33
+ * UTC instant of midnight on `date`'s wall-clock day in `tz`.
34
+ */
35
+ export declare function startOfDayInTimezone(date: Date, tz: string): Date;
36
+ /**
37
+ * UTC instant of 23:59:59.999 on `date`'s wall-clock day in `tz`.
38
+ */
39
+ export declare function endOfDayInTimezone(date: Date, tz: string): Date;
40
+ /**
41
+ * Parse an ISO string as a `TZDate` so its Y/M/D/h getters report the
42
+ * wall-clock value in `tz`.
43
+ */
44
+ export declare function parseInTimezone(iso: string, tz: string): TZDate;
45
+ /**
46
+ * True when two dates fall on the same wall-clock day in `tz`. Accepts
47
+ * either a real `Date` or a `TZDate`.
48
+ */
49
+ export declare function sameDayInTimezone(a: Date, b: Date, tz: string): boolean;
@@ -0,0 +1,8 @@
1
+ /** `25 MAY 2026` */
2
+ export declare function formatDate(date: Date, tz?: string): string;
3
+ /** `25 MAY 2026, 02:30 PM` */
4
+ export declare function formatDateTime(date: Date, tz?: string): string;
5
+ /** `25 MAY 2026, 02:30:45 PM` — audit rows where seconds matter. */
6
+ export declare function formatDateTimeWithSeconds(date: Date, tz?: string): string;
7
+ /** `02:30 PM` */
8
+ export declare function formatTime(date: Date, tz?: string): string;
@@ -0,0 +1,6 @@
1
+ export * from "./markets.js";
2
+ export * from "./options.js";
3
+ export * from "./validate.js";
4
+ export * from "./format.js";
5
+ export * from "./boundaries.js";
6
+ export * from "./plain-date.js";
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Zones for the markets TemboPlus operates in. Always visible at the top
3
+ * of a timezone picker. Each entry pairs the canonical IANA id with the
4
+ * market-oriented label shown in that group (e.g. "Tanzania & Kenya"
5
+ * rather than "Africa / Nairobi") — operating rows read as markets, not
6
+ * zones. Editorial order, not alphabetical.
7
+ */
8
+ export declare const OPERATING_TIMEZONES: ReadonlyArray<{
9
+ value: string;
10
+ market: string;
11
+ }>;
12
+ /**
13
+ * Reference / anchor zones that are useful even when you don't operate
14
+ * there — UTC is universal, GMT/London/NY/Paris are how the rest of the
15
+ * world spells time. Kept deliberately short.
16
+ */
17
+ export declare const ANCHOR_TIMEZONES: ReadonlyArray<string>;
@@ -0,0 +1,36 @@
1
+ export type TimezoneGroup = "operating" | "anchors" | "others";
2
+ export interface TimezoneOption {
3
+ /**
4
+ * IANA id, e.g. `"Africa/Nairobi"`. This is what's persisted and what
5
+ * `Intl.DateTimeFormat` consumes.
6
+ */
7
+ value: string;
8
+ /**
9
+ * Friendly label for a dropdown, e.g. `"Africa / Nairobi (GMT+03:00)"`.
10
+ * The offset reflects the current moment — it shifts across DST
11
+ * boundaries for affected zones, which is acceptable for a picker.
12
+ */
13
+ label: string;
14
+ /**
15
+ * Market-oriented label rendered only for operating zones, e.g.
16
+ * `"Tanzania & Kenya (GMT+03:00)"`. Other groups render `label`.
17
+ */
18
+ marketLabel?: string;
19
+ /**
20
+ * Which dropdown section this option belongs to. Pickers typically hide
21
+ * the `others` section until the user starts typing.
22
+ */
23
+ group: TimezoneGroup;
24
+ }
25
+ /**
26
+ * Returns the current offset for `timeZone` as a `GMT±HH:MM` string, or
27
+ * an empty string if `Intl` can't resolve it.
28
+ */
29
+ export declare function formatOffset(timeZone: string): string;
30
+ /**
31
+ * Friendly dropdown label for an IANA id, e.g.
32
+ * `"Africa / Nairobi (GMT+03:00)"`.
33
+ */
34
+ export declare function formatLabel(timeZone: string): string;
35
+ /** Frozen list of every selectable timezone option, in display order. */
36
+ export declare const TIMEZONE_OPTIONS: ReadonlyArray<TimezoneOption>;
@@ -0,0 +1,57 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * A calendar day expressed as `YYYY-MM-DD`. No time, no timezone — just
4
+ * the digits a date picker hands you. Use this for any "what calendar
5
+ * day did the user pick?" boundary; convert to a `Date` only when you
6
+ * need to do timezone-aware arithmetic.
7
+ */
8
+ export type PlainDate = string;
9
+ /** Regex matching a valid `YYYY-MM-DD` string. */
10
+ export declare const PLAIN_DATE_PATTERN: RegExp;
11
+ /** Zod schema validating a {@link PlainDate}. */
12
+ export declare const plainDateSchema: z.ZodString;
13
+ /**
14
+ * Add or subtract whole calendar days from a `YYYY-MM-DD` string. Pure
15
+ * string-in/string-out, no dependency on the JS runtime's local
16
+ * timezone — we pin to UTC noon so DST cliffs and ±1s rounding can't
17
+ * tip the result onto an adjacent day.
18
+ *
19
+ * @example
20
+ * shiftPlainDate("2026-05-15", 1); // → "2026-05-16"
21
+ * shiftPlainDate("2026-05-15", -1); // → "2026-05-14"
22
+ * shiftPlainDate("2026-05-31", 1); // → "2026-06-01" (month wrap)
23
+ * shiftPlainDate("2026-01-01", -1); // → "2025-12-31" (year wrap)
24
+ * shiftPlainDate("2026-03-08", 1); // → "2026-03-09" (DST-safe; US "spring forward" day)
25
+ */
26
+ export declare function shiftPlainDate(date: PlainDate, days: number): PlainDate;
27
+ /**
28
+ * Pad an EAT date range by ±1 day on each side. Used to ensure that an
29
+ * upstream EAT-anchored query returns every transaction that falls on
30
+ * the caller's local-zone window, regardless of timezone offset (±1 is
31
+ * sufficient because the maximum delta from EAT (UTC+3) to any timezone
32
+ * on earth is 15 hours, comfortably under 24h).
33
+ *
34
+ * @example
35
+ * padEatRange({ startDate: "2026-05-15", endDate: "2026-05-16" });
36
+ * // → { startDate: "2026-05-14", endDate: "2026-05-17" }
37
+ */
38
+ export declare function padEatRange(range: {
39
+ startDate: PlainDate;
40
+ endDate: PlainDate;
41
+ }): {
42
+ startDate: PlainDate;
43
+ endDate: PlainDate;
44
+ };
45
+ /**
46
+ * Format an absolute instant as a `YYYY-MM-DD` string in the given IANA
47
+ * timezone. Uses `en-CA` locale because that's the one major locale
48
+ * whose default date format is already ISO-like — no stitching parts.
49
+ *
50
+ * @example
51
+ * const instant = new Date("2026-05-15T01:30:00+03:00");
52
+ * localDateInZone(instant, "Africa/Nairobi"); // → "2026-05-15"
53
+ * localDateInZone(instant, "UTC"); // → "2026-05-14"
54
+ * localDateInZone(instant, "America/New_York"); // → "2026-05-14"
55
+ * localDateInZone(instant, "Pacific/Auckland"); // → "2026-05-15"
56
+ */
57
+ export declare function localDateInZone(instant: Date, timezone: string): PlainDate;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * IANA id for East Africa Time. The TemboPlus database is anchored to
3
+ * this zone, so it's the natural default for any report or statement
4
+ * whose data originates there.
5
+ */
6
+ export declare const EAT_TIMEZONE = "Africa/Nairobi";
7
+ /**
8
+ * Default for callers that don't pin a specific timezone. Currently EAT;
9
+ * may change if/when TemboPlus operates in more markets.
10
+ */
11
+ export declare const DEFAULT_TIMEZONE = "Africa/Nairobi";
12
+ /**
13
+ * Validate that a value is a known IANA timezone. The known set comes
14
+ * from `Intl.supportedValuesOf("timeZone")` plus pinned operating and
15
+ * anchor zones.
16
+ */
17
+ export declare function isValidTimezone(value: unknown): value is string;
18
+ /**
19
+ * True when the caller's selected timezone is effectively EAT. Useful
20
+ * for short-circuiting EAT-anchored padding / post-filtering so callers
21
+ * who haven't migrated see byte-identical behaviour.
22
+ *
23
+ * @example
24
+ * isEatTimezone(undefined); // → true (no zone = legacy EAT behaviour)
25
+ * isEatTimezone("Africa/Nairobi"); // → true (canonical EAT id)
26
+ * isEatTimezone("UTC"); // → false
27
+ */
28
+ export declare function isEatTimezone(timezone: string | undefined): boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@temboplus/frontend-core",
3
- "version": "1.0.2",
3
+ "version": "1.0.3-beta.1",
4
4
  "description": "A JavaScript/TypeScript package providing common utilities and logic shared across front-end TemboPlus projects.",
5
5
  "author": "Okello Gerald",
6
6
  "license": "MIT",
@@ -37,10 +37,10 @@
37
37
  },
38
38
  "homepage": "https://github.com/TemboPlus-Frontend/frontend-core-js#readme",
39
39
  "dependencies": {
40
+ "@date-fns/tz": "^1.4.1",
41
+ "date-fns": "^4.2.1",
40
42
  "libphonenumber-js": "^1.12.6",
41
- "pino": "^9.13.1",
42
43
  "tslib": "^2.8.1",
43
- "uuid": "^11.1.0",
44
44
  "zod": "^3.24.2"
45
45
  },
46
46
  "devDependencies": {
@@ -50,7 +50,6 @@
50
50
  "@rollup/plugin-node-resolve": "^16.0.1",
51
51
  "@rollup/plugin-terser": "^0.4.4",
52
52
  "@rollup/plugin-typescript": "^12.1.2",
53
- "pino-pretty": "^13.1.1",
54
53
  "rollup": "^4.39.0",
55
54
  "ts-node": "^10.9.2",
56
55
  "typescript": "^5.9.3",
@@ -1,16 +0,0 @@
1
- /**
2
- * Generates a unique UUID (version 4).
3
- * @returns {string} - A randomly generated UUID string.
4
- */
5
- declare function generateUniqueUUID(): string;
6
- /**
7
- * Generates a UUID (version 5) based on the input string and a predefined namespace.
8
- * @param {string} data - The input string to generate the UUID from.
9
- * @returns {string} - A UUID string generated from the input string.
10
- */
11
- declare function generateUuidBasedOn(data: string): string;
12
- export declare const IdUtils: {
13
- generateUniqueUUID: typeof generateUniqueUUID;
14
- generateUuidBasedOn: typeof generateUuidBasedOn;
15
- };
16
- export {};
@@ -1,4 +0,0 @@
1
- export * from "./id.js";
2
- export * from "./time.js";
3
- export * from "./text.js";
4
- export * from "./logger.js";
@@ -1,2 +0,0 @@
1
- import pino from "pino";
2
- export declare const logger: pino.Logger<never, boolean>;
@@ -1,26 +0,0 @@
1
- /**
2
- * Compares two dates (years, months, and days only) without considering the time.
3
- * @param {Date} a - The first Date object.
4
- * @param {Date} b - The second Date object.
5
- * @returns {boolean} - Returns true if the dates are equal in terms of year, month, and day; otherwise, false.
6
- */
7
- declare function compareDates(a: Date, b: Date): boolean;
8
- /**
9
- * Sorts two dates in descending order (latest date first).
10
- * @param {Date} a - The first Date object.
11
- * @param {Date} b - The second Date object.
12
- * @returns {number} - Returns a positive value if `b` is after `a`, a negative value if `a` is after `b`, and 0 if they are equal.
13
- */
14
- declare function sortDates(a: Date, b: Date): number;
15
- /**
16
- * Creates a delay for a given number of milliseconds.
17
- * @param {number} milliseconds - The delay duration in milliseconds.
18
- * @returns {Promise<void>} - A promise that resolves after the specified delay.
19
- */
20
- declare function delay(milliseconds: number): Promise<void>;
21
- export declare const TimeUtils: {
22
- compareDates: typeof compareDates;
23
- sortDates: typeof sortDates;
24
- delay: typeof delay;
25
- };
26
- export {};