cakkatrok-indonesia-utils 1.0.0
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.
- package/LICENSE +21 -0
- package/README.md +368 -0
- package/package.json +35 -0
- package/src/cli.js +60 -0
- package/src/index.d.ts +63 -0
- package/src/index.js +455 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rintis Pameling
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
# @rintisid/indonesia-utils
|
|
2
|
+
|
|
3
|
+
Utility functions for Indonesian JavaScript/Node.js projects.
|
|
4
|
+
|
|
5
|
+
This package is intentionally dependency-free and focused on common local use cases: Rupiah formatting, Indonesian phone numbers, Indonesian dates, slugs, unique payment amounts, and simple validation.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @rintisid/indonesia-utils
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
const {
|
|
17
|
+
formatRupiah,
|
|
18
|
+
parseRupiah,
|
|
19
|
+
normalizePhoneID,
|
|
20
|
+
isValidPhoneID,
|
|
21
|
+
formatPhoneID,
|
|
22
|
+
getProviderID,
|
|
23
|
+
slugifyID,
|
|
24
|
+
formatTanggalID,
|
|
25
|
+
relativeTimeID,
|
|
26
|
+
createUniqueAmount,
|
|
27
|
+
isValidEmail,
|
|
28
|
+
cleanText,
|
|
29
|
+
titleCaseID,
|
|
30
|
+
maskEmail,
|
|
31
|
+
maskPhoneID
|
|
32
|
+
} = require('@rintisid/indonesia-utils');
|
|
33
|
+
|
|
34
|
+
console.log(formatRupiah(15000));
|
|
35
|
+
// Rp15.000
|
|
36
|
+
|
|
37
|
+
console.log(parseRupiah('Rp15.000'));
|
|
38
|
+
// 15000
|
|
39
|
+
|
|
40
|
+
console.log(normalizePhoneID('0812-3456-7890'));
|
|
41
|
+
// 6281234567890
|
|
42
|
+
|
|
43
|
+
console.log(isValidPhoneID('0812-3456-7890'));
|
|
44
|
+
// true
|
|
45
|
+
|
|
46
|
+
console.log(formatPhoneID('081234567890'));
|
|
47
|
+
// +62 812 3456 7890
|
|
48
|
+
|
|
49
|
+
console.log(getProviderID('081234567890'));
|
|
50
|
+
// Telkomsel
|
|
51
|
+
|
|
52
|
+
console.log(slugifyID('Produk Lokal Indonesia & UMKM!'));
|
|
53
|
+
// produk-lokal-indonesia-dan-umkm
|
|
54
|
+
|
|
55
|
+
console.log(formatTanggalID('2026-07-08T10:30:00', { includeTime: true }));
|
|
56
|
+
// Rabu Legi, 8 Juli 2026 10.30
|
|
57
|
+
|
|
58
|
+
console.log(formatTanggalID('07 01 2007'));
|
|
59
|
+
// Minggu Wage, 7 Januari 2007
|
|
60
|
+
|
|
61
|
+
console.log(relativeTimeID('2026-07-07T10:30:00', '2026-07-08T10:30:00'));
|
|
62
|
+
// 1 hari yang lalu
|
|
63
|
+
|
|
64
|
+
console.log(relativeTimeID('07 01 2007', '08 01 2007'));
|
|
65
|
+
// 1 hari yang lalu
|
|
66
|
+
|
|
67
|
+
console.log(relativeTimeID('07 01 2007', '08 03 2026'));
|
|
68
|
+
// 19 tahun lebih 2 bulan 1 hari yang lalu
|
|
69
|
+
|
|
70
|
+
console.log(relativeTimeID('07 01 2007', '09 03 2026'));
|
|
71
|
+
// 19 tahun lebih 2 bulan 2 hari yang lalu
|
|
72
|
+
|
|
73
|
+
console.log(createUniqueAmount({ amount: 50000, activeAmounts: [50001, 50002] }));
|
|
74
|
+
// { baseAmount: 50000, uniqueCode: 3, finalAmount: 50003, formatted: 'Rp50.003' }
|
|
75
|
+
|
|
76
|
+
console.log(isValidEmail('user@example.com'));
|
|
77
|
+
// true
|
|
78
|
+
|
|
79
|
+
console.log(cleanText(' halo\n\t dunia '));
|
|
80
|
+
// halo dunia
|
|
81
|
+
|
|
82
|
+
console.log(titleCaseID('cara membuat nasi goreng yang enak'));
|
|
83
|
+
// Cara Membuat Nasi Goreng yang Enak
|
|
84
|
+
|
|
85
|
+
console.log(maskEmail('rintis@example.com'));
|
|
86
|
+
// r****s@example.com
|
|
87
|
+
|
|
88
|
+
console.log(maskPhoneID('081234567890'));
|
|
89
|
+
// +6281*****7890
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## CLI
|
|
93
|
+
|
|
94
|
+
After installing globally or running with `npx`, you can use:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
npx indonesia-utils rupiah 15000
|
|
98
|
+
npx indonesia-utils parse-rupiah "Rp15.000"
|
|
99
|
+
npx indonesia-utils phone 081234567890
|
|
100
|
+
npx indonesia-utils provider 081234567890
|
|
101
|
+
npx indonesia-utils slug "Produk Lokal Indonesia & UMKM!"
|
|
102
|
+
npx indonesia-utils date 2026-07-08
|
|
103
|
+
npx indonesia-utils date "07 01 2007"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## API
|
|
107
|
+
|
|
108
|
+
### `formatRupiah(value, options?)`
|
|
109
|
+
|
|
110
|
+
Formats a number into Indonesian Rupiah format.
|
|
111
|
+
|
|
112
|
+
```js
|
|
113
|
+
formatRupiah(15000); // Rp15.000
|
|
114
|
+
formatRupiah(15000, { space: true }); // Rp 15.000
|
|
115
|
+
formatRupiah(15000, { withSymbol: false }); // 15.000
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Options:
|
|
119
|
+
|
|
120
|
+
```js
|
|
121
|
+
{
|
|
122
|
+
symbol: 'Rp',
|
|
123
|
+
withSymbol: true,
|
|
124
|
+
decimal: 0,
|
|
125
|
+
space: false
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### `parseRupiah(value)`
|
|
130
|
+
|
|
131
|
+
Parses Rupiah text into a number.
|
|
132
|
+
|
|
133
|
+
```js
|
|
134
|
+
parseRupiah('Rp15.000'); // 15000
|
|
135
|
+
parseRupiah('Rp15.000,50'); // 15000.5
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### `normalizePhoneID(value, options?)`
|
|
139
|
+
|
|
140
|
+
Normalizes Indonesian phone numbers into `62` format.
|
|
141
|
+
|
|
142
|
+
```js
|
|
143
|
+
normalizePhoneID('0812-3456-7890'); // 6281234567890
|
|
144
|
+
normalizePhoneID('+62 812 3456 7890'); // 6281234567890
|
|
145
|
+
normalizePhoneID('81234567890'); // 6281234567890
|
|
146
|
+
normalizePhoneID('081234567890', { keepPlus: true }); // +6281234567890
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### `isValidPhoneID(value)`
|
|
150
|
+
|
|
151
|
+
Validates Indonesian mobile phone number format.
|
|
152
|
+
|
|
153
|
+
```js
|
|
154
|
+
isValidPhoneID('081234567890'); // true
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### `formatPhoneID(value, options?)`
|
|
158
|
+
|
|
159
|
+
Formats a normalized Indonesian phone number for display.
|
|
160
|
+
|
|
161
|
+
```js
|
|
162
|
+
formatPhoneID('081234567890'); // +62 812 3456 7890
|
|
163
|
+
formatPhoneID('081234567890', { international: false }); // 0 812 3456 7890
|
|
164
|
+
formatPhoneID('081234567890', { separator: '-' }); // +62-812-3456-7890
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### `getProviderID(value)`
|
|
168
|
+
|
|
169
|
+
Detects common Indonesian mobile provider from phone prefix.
|
|
170
|
+
|
|
171
|
+
```js
|
|
172
|
+
getProviderID('081234567890'); // Telkomsel
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Supported common providers:
|
|
176
|
+
|
|
177
|
+
- Telkomsel
|
|
178
|
+
- Indosat
|
|
179
|
+
- XL
|
|
180
|
+
- Axis
|
|
181
|
+
- Tri
|
|
182
|
+
- Smartfren
|
|
183
|
+
|
|
184
|
+
Unknown prefixes return `Unknown`.
|
|
185
|
+
|
|
186
|
+
### `slugifyID(value, options?)`
|
|
187
|
+
|
|
188
|
+
Creates clean slugs.
|
|
189
|
+
|
|
190
|
+
```js
|
|
191
|
+
slugifyID('Produk Lokal Indonesia & UMKM!');
|
|
192
|
+
// produk-lokal-indonesia-dan-umkm
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Options:
|
|
196
|
+
|
|
197
|
+
```js
|
|
198
|
+
{
|
|
199
|
+
separator: '-',
|
|
200
|
+
lower: true,
|
|
201
|
+
maxLength: 0,
|
|
202
|
+
replacement: {
|
|
203
|
+
'&': ' dan ',
|
|
204
|
+
'+': ' plus ',
|
|
205
|
+
'@': ' at '
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### `formatTanggalID(value?, options?)`
|
|
211
|
+
|
|
212
|
+
Formats a date in Indonesian.
|
|
213
|
+
|
|
214
|
+
Supported string input formats are ISO date strings and Indonesian date strings like `DD MM YYYY`.
|
|
215
|
+
|
|
216
|
+
```js
|
|
217
|
+
formatTanggalID('2026-07-08');
|
|
218
|
+
// Rabu Legi, 8 Juli 2026
|
|
219
|
+
|
|
220
|
+
formatTanggalID('07 01 2007');
|
|
221
|
+
// Minggu Wage, 7 Januari 2007
|
|
222
|
+
|
|
223
|
+
formatTanggalID('2026-07-08T10:30:00', { includeTime: true });
|
|
224
|
+
// Rabu Legi, 8 Juli 2026 10.30
|
|
225
|
+
|
|
226
|
+
formatTanggalID('2026-07-08', { dateStyle: 'numeric' });
|
|
227
|
+
// Rabu Legi, 08/07/2026
|
|
228
|
+
|
|
229
|
+
formatTanggalID('2026-07-08', { includeWeton: false });
|
|
230
|
+
// Rabu, 8 Juli 2026
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Options:
|
|
234
|
+
|
|
235
|
+
```js
|
|
236
|
+
{
|
|
237
|
+
includeDay: true,
|
|
238
|
+
shortMonth: false,
|
|
239
|
+
includeTime: false,
|
|
240
|
+
timeSeparator: '.',
|
|
241
|
+
dateStyle: 'long',
|
|
242
|
+
includeWeton: true
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### `relativeTimeID(value, baseValue?)`
|
|
247
|
+
|
|
248
|
+
Returns Indonesian relative time.
|
|
249
|
+
|
|
250
|
+
`value` and `baseValue` support the same two date input formats as `formatTanggalID`.
|
|
251
|
+
|
|
252
|
+
```js
|
|
253
|
+
relativeTimeID('2026-07-07T10:30:00', '2026-07-08T10:30:00');
|
|
254
|
+
// 1 hari yang lalu
|
|
255
|
+
|
|
256
|
+
relativeTimeID('07 01 2007', '08 01 2007');
|
|
257
|
+
// 1 hari yang lalu
|
|
258
|
+
|
|
259
|
+
relativeTimeID('07 01 2007', '08 03 2026');
|
|
260
|
+
// 19 tahun lebih 2 bulan 1 hari yang lalu
|
|
261
|
+
|
|
262
|
+
relativeTimeID('07 01 2007', '09 03 2026');
|
|
263
|
+
// 19 tahun lebih 2 bulan 2 hari yang lalu
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### `createUniqueAmount(options)`
|
|
267
|
+
|
|
268
|
+
Creates unique payment amount for manual transfer or QRIS mutation matching.
|
|
269
|
+
|
|
270
|
+
```js
|
|
271
|
+
createUniqueAmount({
|
|
272
|
+
amount: 50000,
|
|
273
|
+
activeAmounts: [50001, 50002]
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// {
|
|
277
|
+
// baseAmount: 50000,
|
|
278
|
+
// uniqueCode: 3,
|
|
279
|
+
// finalAmount: 50003,
|
|
280
|
+
// formatted: 'Rp50.003'
|
|
281
|
+
// }
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Options:
|
|
285
|
+
|
|
286
|
+
```js
|
|
287
|
+
{
|
|
288
|
+
amount: 50000,
|
|
289
|
+
min: 1,
|
|
290
|
+
max: 999,
|
|
291
|
+
activeAmounts: [],
|
|
292
|
+
mode: 'add' // or 'subtract'
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### `isValidEmail(value)`
|
|
297
|
+
|
|
298
|
+
Simple email validation.
|
|
299
|
+
|
|
300
|
+
```js
|
|
301
|
+
isValidEmail('user@example.com'); // true
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### `cleanText(value)`
|
|
305
|
+
|
|
306
|
+
Trims and normalizes whitespace.
|
|
307
|
+
|
|
308
|
+
```js
|
|
309
|
+
cleanText(' halo\n dunia '); // halo dunia
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### `titleCaseID(value)`
|
|
313
|
+
|
|
314
|
+
Converts text into Indonesian-style title case.
|
|
315
|
+
|
|
316
|
+
```js
|
|
317
|
+
titleCaseID('cara membuat nasi goreng yang enak');
|
|
318
|
+
// Cara Membuat Nasi Goreng yang Enak
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### `maskEmail(value)`
|
|
322
|
+
|
|
323
|
+
Masks email username.
|
|
324
|
+
|
|
325
|
+
```js
|
|
326
|
+
maskEmail('rintis@example.com'); // r****s@example.com
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### `maskPhoneID(value)`
|
|
330
|
+
|
|
331
|
+
Masks Indonesian phone number.
|
|
332
|
+
|
|
333
|
+
```js
|
|
334
|
+
maskPhoneID('081234567890'); // +6281*****7890
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
## Test
|
|
338
|
+
|
|
339
|
+
```bash
|
|
340
|
+
npm test
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
## Publish
|
|
344
|
+
|
|
345
|
+
Before publishing, make sure the package name is available or use your own npm scope.
|
|
346
|
+
|
|
347
|
+
```bash
|
|
348
|
+
npm login
|
|
349
|
+
npm publish --access public
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
If you want to publish without scope, change this in `package.json`:
|
|
353
|
+
|
|
354
|
+
```json
|
|
355
|
+
{
|
|
356
|
+
"name": "indonesia-utils"
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
Then publish:
|
|
361
|
+
|
|
362
|
+
```bash
|
|
363
|
+
npm publish
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
## License
|
|
367
|
+
|
|
368
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cakkatrok-indonesia-utils",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Utility functions for Indonesian projects: Rupiah, phone number, date, slug, unique payment amount, and validation.",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"types": "src/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"indonesia-utils": "src/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "node --test",
|
|
12
|
+
"example": "node examples/basic.js"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"indonesia",
|
|
16
|
+
"rupiah",
|
|
17
|
+
"idr",
|
|
18
|
+
"phone",
|
|
19
|
+
"validator",
|
|
20
|
+
"slugify",
|
|
21
|
+
"tanggal",
|
|
22
|
+
"utility",
|
|
23
|
+
"utils"
|
|
24
|
+
],
|
|
25
|
+
"author": "RintisW.P",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=16"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"src",
|
|
32
|
+
"README.md",
|
|
33
|
+
"LICENSE"
|
|
34
|
+
]
|
|
35
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const utils = require('./index');
|
|
5
|
+
|
|
6
|
+
const [, , command, ...args] = process.argv;
|
|
7
|
+
|
|
8
|
+
function help() {
|
|
9
|
+
console.log(`indonesia-utils
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
indonesia-utils rupiah <number>
|
|
13
|
+
indonesia-utils parse-rupiah <text>
|
|
14
|
+
indonesia-utils phone <number>
|
|
15
|
+
indonesia-utils provider <number>
|
|
16
|
+
indonesia-utils slug <text>
|
|
17
|
+
indonesia-utils date [date]
|
|
18
|
+
|
|
19
|
+
Examples:
|
|
20
|
+
indonesia-utils rupiah 15000
|
|
21
|
+
indonesia-utils phone 081234567890
|
|
22
|
+
indonesia-utils slug "Halo Dunia Indonesia"
|
|
23
|
+
`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
switch (command) {
|
|
28
|
+
case 'rupiah':
|
|
29
|
+
console.log(utils.formatRupiah(args[0]));
|
|
30
|
+
break;
|
|
31
|
+
case 'parse-rupiah':
|
|
32
|
+
console.log(utils.parseRupiah(args.join(' ')));
|
|
33
|
+
break;
|
|
34
|
+
case 'phone':
|
|
35
|
+
console.log(utils.normalizePhoneID(args[0]));
|
|
36
|
+
break;
|
|
37
|
+
case 'provider':
|
|
38
|
+
console.log(utils.getProviderID(args[0]));
|
|
39
|
+
break;
|
|
40
|
+
case 'slug':
|
|
41
|
+
console.log(utils.slugifyID(args.join(' ')));
|
|
42
|
+
break;
|
|
43
|
+
case 'date':
|
|
44
|
+
console.log(utils.formatTanggalID(args[0] ? args.join(' ') : new Date()));
|
|
45
|
+
break;
|
|
46
|
+
case 'help':
|
|
47
|
+
case '--help':
|
|
48
|
+
case '-h':
|
|
49
|
+
case undefined:
|
|
50
|
+
help();
|
|
51
|
+
break;
|
|
52
|
+
default:
|
|
53
|
+
console.error(`Unknown command: ${command}`);
|
|
54
|
+
help();
|
|
55
|
+
process.exitCode = 1;
|
|
56
|
+
}
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error(error.message);
|
|
59
|
+
process.exitCode = 1;
|
|
60
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export interface RupiahOptions {
|
|
2
|
+
symbol?: string;
|
|
3
|
+
withSymbol?: boolean;
|
|
4
|
+
decimal?: number;
|
|
5
|
+
space?: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface PhoneOptions {
|
|
9
|
+
countryCode?: string;
|
|
10
|
+
keepPlus?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface FormatPhoneOptions {
|
|
14
|
+
separator?: string;
|
|
15
|
+
international?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface SlugifyOptions {
|
|
19
|
+
separator?: string;
|
|
20
|
+
lower?: boolean;
|
|
21
|
+
maxLength?: number;
|
|
22
|
+
replacement?: Record<string, string>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface FormatTanggalOptions {
|
|
26
|
+
includeDay?: boolean;
|
|
27
|
+
shortMonth?: boolean;
|
|
28
|
+
includeTime?: boolean;
|
|
29
|
+
timeSeparator?: string;
|
|
30
|
+
dateStyle?: 'long' | 'numeric';
|
|
31
|
+
includeWeton?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface UniqueAmountOptions {
|
|
35
|
+
amount: number;
|
|
36
|
+
min?: number;
|
|
37
|
+
max?: number;
|
|
38
|
+
activeAmounts?: number[];
|
|
39
|
+
mode?: 'add' | 'subtract';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface UniqueAmountResult {
|
|
43
|
+
baseAmount: number;
|
|
44
|
+
uniqueCode: number;
|
|
45
|
+
finalAmount: number;
|
|
46
|
+
formatted: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function formatRupiah(value: number | string, options?: RupiahOptions): string;
|
|
50
|
+
export function parseRupiah(value: number | string): number;
|
|
51
|
+
export function normalizePhoneID(value: string | number, options?: PhoneOptions): string;
|
|
52
|
+
export function isValidPhoneID(value: string | number): boolean;
|
|
53
|
+
export function formatPhoneID(value: string | number, options?: FormatPhoneOptions): string;
|
|
54
|
+
export function getProviderID(value: string | number): string;
|
|
55
|
+
export function slugifyID(value: string, options?: SlugifyOptions): string;
|
|
56
|
+
export function formatTanggalID(value?: Date | string | number, options?: FormatTanggalOptions): string;
|
|
57
|
+
export function relativeTimeID(value: Date | string | number, baseValue?: Date | string | number): string;
|
|
58
|
+
export function createUniqueAmount(options: UniqueAmountOptions): UniqueAmountResult;
|
|
59
|
+
export function isValidEmail(value: string): boolean;
|
|
60
|
+
export function cleanText(value: string): string;
|
|
61
|
+
export function titleCaseID(value: string): string;
|
|
62
|
+
export function maskEmail(value: string): string;
|
|
63
|
+
export function maskPhoneID(value: string | number): string;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const MONTHS_LONG = [
|
|
4
|
+
'Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni',
|
|
5
|
+
'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember'
|
|
6
|
+
];
|
|
7
|
+
|
|
8
|
+
const MONTHS_SHORT = [
|
|
9
|
+
'Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun',
|
|
10
|
+
'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des'
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const DAYS_LONG = [
|
|
14
|
+
'Minggu', 'Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat', 'Sabtu'
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const PASARAN = ['Legi', 'Pahing', 'Pon', 'Wage', 'Kliwon'];
|
|
18
|
+
const PASARAN_ANCHOR = Date.UTC(1945, 7, 17);
|
|
19
|
+
|
|
20
|
+
const PROVIDER_PREFIXES = [
|
|
21
|
+
{ name: 'Telkomsel', prefixes: ['811', '812', '813', '821', '822', '823', '851', '852', '853'] },
|
|
22
|
+
{ name: 'Indosat', prefixes: ['814', '815', '816', '855', '856', '857', '858'] },
|
|
23
|
+
{ name: 'XL', prefixes: ['817', '818', '819', '859', '877', '878'] },
|
|
24
|
+
{ name: 'Axis', prefixes: ['831', '832', '833', '838'] },
|
|
25
|
+
{ name: 'Tri', prefixes: ['895', '896', '897', '898', '899'] },
|
|
26
|
+
{ name: 'Smartfren', prefixes: ['881', '882', '883', '884', '885', '886', '887', '888', '889'] }
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
function assertNumber(value, name) {
|
|
30
|
+
const number = Number(value);
|
|
31
|
+
if (!Number.isFinite(number)) {
|
|
32
|
+
throw new TypeError(`${name} must be a finite number`);
|
|
33
|
+
}
|
|
34
|
+
return number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function onlyDigits(value) {
|
|
38
|
+
return String(value ?? '').replace(/\D+/g, '');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function formatRupiah(value, options = {}) {
|
|
42
|
+
const {
|
|
43
|
+
symbol = 'Rp',
|
|
44
|
+
withSymbol = true,
|
|
45
|
+
decimal = 0,
|
|
46
|
+
space = false
|
|
47
|
+
} = options;
|
|
48
|
+
|
|
49
|
+
const number = assertNumber(value, 'value');
|
|
50
|
+
const formatted = new Intl.NumberFormat('id-ID', {
|
|
51
|
+
minimumFractionDigits: decimal,
|
|
52
|
+
maximumFractionDigits: decimal
|
|
53
|
+
}).format(number);
|
|
54
|
+
|
|
55
|
+
if (!withSymbol) return formatted;
|
|
56
|
+
return `${symbol}${space ? ' ' : ''}${formatted}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function parseRupiah(value) {
|
|
60
|
+
if (typeof value === 'number') return value;
|
|
61
|
+
if (value === null || value === undefined) return 0;
|
|
62
|
+
|
|
63
|
+
let text = String(value)
|
|
64
|
+
.trim()
|
|
65
|
+
.replace(/rp/gi, '')
|
|
66
|
+
.replace(/\s+/g, '');
|
|
67
|
+
|
|
68
|
+
const hasCommaDecimal = /,\d{1,2}$/.test(text);
|
|
69
|
+
|
|
70
|
+
if (hasCommaDecimal) {
|
|
71
|
+
text = text.replace(/\./g, '').replace(',', '.');
|
|
72
|
+
} else {
|
|
73
|
+
text = text.replace(/[.,]/g, '');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const number = Number(text.replace(/[^0-9.-]/g, ''));
|
|
77
|
+
return Number.isFinite(number) ? number : 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function normalizePhoneID(value, options = {}) {
|
|
81
|
+
const { countryCode = '62', keepPlus = false } = options;
|
|
82
|
+
let digits = onlyDigits(value);
|
|
83
|
+
|
|
84
|
+
if (!digits) return '';
|
|
85
|
+
|
|
86
|
+
if (digits.startsWith('00' + countryCode)) {
|
|
87
|
+
digits = digits.slice(2);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (digits.startsWith('0')) {
|
|
91
|
+
digits = countryCode + digits.slice(1);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!digits.startsWith(countryCode) && digits.startsWith('8')) {
|
|
95
|
+
digits = countryCode + digits;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return keepPlus ? `+${digits}` : digits;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function isValidPhoneID(value) {
|
|
102
|
+
const phone = normalizePhoneID(value);
|
|
103
|
+
return /^628[1-9]\d{7,11}$/.test(phone);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function formatPhoneID(value, options = {}) {
|
|
107
|
+
const { separator = ' ', international = true } = options;
|
|
108
|
+
const phone = normalizePhoneID(value);
|
|
109
|
+
|
|
110
|
+
if (!phone) return '';
|
|
111
|
+
if (!phone.startsWith('62')) return phone;
|
|
112
|
+
|
|
113
|
+
const national = phone.slice(2);
|
|
114
|
+
const chunks = [];
|
|
115
|
+
|
|
116
|
+
chunks.push(national.slice(0, 3));
|
|
117
|
+
if (national.length > 3) chunks.push(national.slice(3, 7));
|
|
118
|
+
if (national.length > 7) chunks.push(national.slice(7, 11));
|
|
119
|
+
if (national.length > 11) chunks.push(national.slice(11));
|
|
120
|
+
|
|
121
|
+
const prefix = international ? '+62' : '0';
|
|
122
|
+
return `${prefix}${separator}${chunks.filter(Boolean).join(separator)}`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function getProviderID(value) {
|
|
126
|
+
const phone = normalizePhoneID(value);
|
|
127
|
+
if (!phone.startsWith('62') || phone.length < 5) return 'Unknown';
|
|
128
|
+
|
|
129
|
+
const prefix = phone.slice(2, 5);
|
|
130
|
+
const found = PROVIDER_PREFIXES.find(provider => provider.prefixes.includes(prefix));
|
|
131
|
+
return found ? found.name : 'Unknown';
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function slugifyID(value, options = {}) {
|
|
135
|
+
const {
|
|
136
|
+
separator = '-',
|
|
137
|
+
lower = true,
|
|
138
|
+
maxLength = 0,
|
|
139
|
+
replacement = {
|
|
140
|
+
'&': ' dan ',
|
|
141
|
+
'+': ' plus ',
|
|
142
|
+
'@': ' at '
|
|
143
|
+
}
|
|
144
|
+
} = options;
|
|
145
|
+
|
|
146
|
+
let text = String(value ?? '');
|
|
147
|
+
|
|
148
|
+
for (const [from, to] of Object.entries(replacement)) {
|
|
149
|
+
text = text.split(from).join(to);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
text = text
|
|
153
|
+
.normalize('NFKD')
|
|
154
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
155
|
+
.replace(/[^\w\s-]/g, ' ')
|
|
156
|
+
.replace(/_/g, ' ')
|
|
157
|
+
.trim()
|
|
158
|
+
.replace(/\s+/g, separator)
|
|
159
|
+
.replace(new RegExp(`${escapeRegExp(separator)}+`, 'g'), separator);
|
|
160
|
+
|
|
161
|
+
if (lower) text = text.toLowerCase();
|
|
162
|
+
if (maxLength > 0) {
|
|
163
|
+
text = text.slice(0, maxLength).replace(new RegExp(`${escapeRegExp(separator)}$`), '');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return text;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function escapeRegExp(value) {
|
|
170
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function toDate(value) {
|
|
174
|
+
if (value instanceof Date) {
|
|
175
|
+
const date = new Date(value.getTime());
|
|
176
|
+
if (Number.isNaN(date.getTime())) {
|
|
177
|
+
throw new TypeError('Invalid date');
|
|
178
|
+
}
|
|
179
|
+
return date;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (typeof value === 'string') {
|
|
183
|
+
const text = value.trim();
|
|
184
|
+
const idDateMatch = text.match(/^(\d{1,2})[ /\-.](\d{1,2})[ /\-.](\d{4})(?:\s+(\d{1,2})[:.](\d{1,2}))?$/);
|
|
185
|
+
|
|
186
|
+
if (idDateMatch) {
|
|
187
|
+
const [, dayText, monthText, yearText, hourText = '0', minuteText = '0'] = idDateMatch;
|
|
188
|
+
const day = Number(dayText);
|
|
189
|
+
const month = Number(monthText);
|
|
190
|
+
const year = Number(yearText);
|
|
191
|
+
const hour = Number(hourText);
|
|
192
|
+
const minute = Number(minuteText);
|
|
193
|
+
const date = new Date(year, month - 1, day, hour, minute);
|
|
194
|
+
|
|
195
|
+
if (
|
|
196
|
+
date.getFullYear() !== year ||
|
|
197
|
+
date.getMonth() !== month - 1 ||
|
|
198
|
+
date.getDate() !== day ||
|
|
199
|
+
date.getHours() !== hour ||
|
|
200
|
+
date.getMinutes() !== minute
|
|
201
|
+
) {
|
|
202
|
+
throw new TypeError('Invalid date');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return date;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const date = new Date(value);
|
|
210
|
+
if (Number.isNaN(date.getTime())) {
|
|
211
|
+
throw new TypeError('Invalid date');
|
|
212
|
+
}
|
|
213
|
+
return date;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function pad2(value) {
|
|
217
|
+
return String(value).padStart(2, '0');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function getPasaranID(value) {
|
|
221
|
+
const date = toDate(value);
|
|
222
|
+
const utcDate = Date.UTC(date.getFullYear(), date.getMonth(), date.getDate());
|
|
223
|
+
const diffDays = Math.round((utcDate - PASARAN_ANCHOR) / (24 * 60 * 60 * 1000));
|
|
224
|
+
const index = ((diffDays % PASARAN.length) + PASARAN.length) % PASARAN.length;
|
|
225
|
+
return PASARAN[index];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function getWetonID(value) {
|
|
229
|
+
const date = toDate(value);
|
|
230
|
+
return `${DAYS_LONG[date.getDay()]} ${getPasaranID(date)}`;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function daysInMonth(year, month) {
|
|
234
|
+
return new Date(year, month + 1, 0).getDate();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function addCalendarMonths(value, months) {
|
|
238
|
+
const date = new Date(value.getTime());
|
|
239
|
+
const targetMonth = date.getMonth() + months;
|
|
240
|
+
const targetYear = date.getFullYear() + Math.floor(targetMonth / 12);
|
|
241
|
+
const normalizedMonth = ((targetMonth % 12) + 12) % 12;
|
|
242
|
+
const day = Math.min(date.getDate(), daysInMonth(targetYear, normalizedMonth));
|
|
243
|
+
|
|
244
|
+
return new Date(
|
|
245
|
+
targetYear,
|
|
246
|
+
normalizedMonth,
|
|
247
|
+
day,
|
|
248
|
+
date.getHours(),
|
|
249
|
+
date.getMinutes(),
|
|
250
|
+
date.getSeconds(),
|
|
251
|
+
date.getMilliseconds()
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function getCalendarMonthDiff(from, to) {
|
|
256
|
+
let months = (to.getFullYear() - from.getFullYear()) * 12;
|
|
257
|
+
months += to.getMonth() - from.getMonth();
|
|
258
|
+
|
|
259
|
+
if (addCalendarMonths(from, months) > to) {
|
|
260
|
+
months -= 1;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return months;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function getRelativeDateParts(from, to) {
|
|
267
|
+
const totalMonths = getCalendarMonthDiff(from, to);
|
|
268
|
+
const afterMonths = addCalendarMonths(from, totalMonths);
|
|
269
|
+
const days = Math.floor((to.getTime() - afterMonths.getTime()) / (24 * 60 * 60 * 1000));
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
years: Math.floor(totalMonths / 12),
|
|
273
|
+
months: totalMonths % 12,
|
|
274
|
+
totalMonths,
|
|
275
|
+
days
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function formatRelativeDateParts(parts) {
|
|
280
|
+
const segments = [];
|
|
281
|
+
|
|
282
|
+
if (parts.years > 0) segments.push(`${parts.years} tahun`);
|
|
283
|
+
if (parts.months > 0) segments.push(`${parts.months} bulan`);
|
|
284
|
+
if (parts.days > 0) segments.push(`${parts.days} hari`);
|
|
285
|
+
|
|
286
|
+
if (segments.length <= 1) return segments[0] ?? '';
|
|
287
|
+
return `${segments[0]} lebih ${segments.slice(1).join(' ')}`;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function formatTanggalID(value = new Date(), options = {}) {
|
|
291
|
+
const {
|
|
292
|
+
includeDay = true,
|
|
293
|
+
shortMonth = false,
|
|
294
|
+
includeTime = false,
|
|
295
|
+
timeSeparator = '.',
|
|
296
|
+
dateStyle = 'long',
|
|
297
|
+
includeWeton = true
|
|
298
|
+
} = options;
|
|
299
|
+
|
|
300
|
+
const date = toDate(value);
|
|
301
|
+
const day = includeWeton ? getWetonID(date) : DAYS_LONG[date.getDay()];
|
|
302
|
+
const monthName = (shortMonth ? MONTHS_SHORT : MONTHS_LONG)[date.getMonth()];
|
|
303
|
+
|
|
304
|
+
let result;
|
|
305
|
+
|
|
306
|
+
if (dateStyle === 'numeric') {
|
|
307
|
+
result = `${pad2(date.getDate())}/${pad2(date.getMonth() + 1)}/${date.getFullYear()}`;
|
|
308
|
+
} else {
|
|
309
|
+
result = `${date.getDate()} ${monthName} ${date.getFullYear()}`;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (includeDay) result = `${day}, ${result}`;
|
|
313
|
+
|
|
314
|
+
if (includeTime) {
|
|
315
|
+
result += ` ${pad2(date.getHours())}${timeSeparator}${pad2(date.getMinutes())}`;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return result;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function relativeTimeID(value, baseValue = new Date()) {
|
|
322
|
+
const date = toDate(value);
|
|
323
|
+
const base = toDate(baseValue);
|
|
324
|
+
const diffMs = date.getTime() - base.getTime();
|
|
325
|
+
const absMs = Math.abs(diffMs);
|
|
326
|
+
const earlier = diffMs < 0 ? date : base;
|
|
327
|
+
const later = diffMs < 0 ? base : date;
|
|
328
|
+
const dateParts = getRelativeDateParts(earlier, later);
|
|
329
|
+
|
|
330
|
+
const units = [
|
|
331
|
+
{ name: 'minggu', ms: 7 * 24 * 60 * 60 * 1000 },
|
|
332
|
+
{ name: 'hari', ms: 24 * 60 * 60 * 1000 },
|
|
333
|
+
{ name: 'jam', ms: 60 * 60 * 1000 },
|
|
334
|
+
{ name: 'menit', ms: 60 * 1000 },
|
|
335
|
+
{ name: 'detik', ms: 1000 }
|
|
336
|
+
];
|
|
337
|
+
|
|
338
|
+
if (absMs < 1000) return 'baru saja';
|
|
339
|
+
|
|
340
|
+
if (dateParts.totalMonths >= 12) {
|
|
341
|
+
const text = formatRelativeDateParts(dateParts);
|
|
342
|
+
if (diffMs < 0) return `${text} yang lalu`;
|
|
343
|
+
return `dalam ${text}`;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (dateParts.totalMonths >= 1) {
|
|
347
|
+
const text = formatRelativeDateParts(dateParts);
|
|
348
|
+
if (diffMs < 0) return `${text} yang lalu`;
|
|
349
|
+
return `dalam ${text}`;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const unit = units.find(item => absMs >= item.ms) || units[units.length - 1];
|
|
353
|
+
const count = Math.floor(absMs / unit.ms);
|
|
354
|
+
|
|
355
|
+
if (diffMs < 0) return `${count} ${unit.name} yang lalu`;
|
|
356
|
+
return `dalam ${count} ${unit.name}`;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function createUniqueAmount(options = {}) {
|
|
360
|
+
const {
|
|
361
|
+
amount,
|
|
362
|
+
min = 1,
|
|
363
|
+
max = 999,
|
|
364
|
+
activeAmounts = [],
|
|
365
|
+
mode = 'add'
|
|
366
|
+
} = options;
|
|
367
|
+
|
|
368
|
+
const baseAmount = assertNumber(amount, 'amount');
|
|
369
|
+
const minCode = Math.max(0, Math.floor(assertNumber(min, 'min')));
|
|
370
|
+
const maxCode = Math.max(minCode, Math.floor(assertNumber(max, 'max')));
|
|
371
|
+
const active = new Set(activeAmounts.map(Number));
|
|
372
|
+
|
|
373
|
+
for (let code = minCode; code <= maxCode; code += 1) {
|
|
374
|
+
const finalAmount = mode === 'subtract' ? baseAmount - code : baseAmount + code;
|
|
375
|
+
if (finalAmount <= 0) continue;
|
|
376
|
+
|
|
377
|
+
if (!active.has(finalAmount)) {
|
|
378
|
+
return {
|
|
379
|
+
baseAmount,
|
|
380
|
+
uniqueCode: code,
|
|
381
|
+
finalAmount,
|
|
382
|
+
formatted: formatRupiah(finalAmount)
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
throw new Error('No available unique amount in the selected range');
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function isValidEmail(value) {
|
|
391
|
+
return /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(String(value ?? '').trim());
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function cleanText(value) {
|
|
395
|
+
return String(value ?? '')
|
|
396
|
+
.replace(/[\t\n\r]+/g, ' ')
|
|
397
|
+
.replace(/\s+/g, ' ')
|
|
398
|
+
.trim();
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function titleCaseID(value) {
|
|
402
|
+
const smallWords = new Set(['dan', 'di', 'ke', 'dari', 'yang', 'atau', 'untuk', 'dengan']);
|
|
403
|
+
|
|
404
|
+
return cleanText(value)
|
|
405
|
+
.toLowerCase()
|
|
406
|
+
.split(' ')
|
|
407
|
+
.map((word, index) => {
|
|
408
|
+
if (index > 0 && smallWords.has(word)) return word;
|
|
409
|
+
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
410
|
+
})
|
|
411
|
+
.join(' ');
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function maskEmail(value) {
|
|
415
|
+
const email = String(value ?? '').trim();
|
|
416
|
+
if (!isValidEmail(email)) return email;
|
|
417
|
+
|
|
418
|
+
const [name, domain] = email.split('@');
|
|
419
|
+
const maskedName = name.length <= 2
|
|
420
|
+
? `${name[0] ?? ''}*`
|
|
421
|
+
: `${name[0]}${'*'.repeat(Math.min(name.length - 2, 5))}${name[name.length - 1]}`;
|
|
422
|
+
|
|
423
|
+
return `${maskedName}@${domain}`;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function maskPhoneID(value) {
|
|
427
|
+
const phone = normalizePhoneID(value, { keepPlus: true });
|
|
428
|
+
if (phone.length <= 8) return phone;
|
|
429
|
+
return `${phone.slice(0, 5)}${'*'.repeat(Math.max(phone.length - 9, 3))}${phone.slice(-4)}`;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
module.exports = {
|
|
433
|
+
formatRupiah,
|
|
434
|
+
parseRupiah,
|
|
435
|
+
normalizePhoneID,
|
|
436
|
+
isValidPhoneID,
|
|
437
|
+
formatPhoneID,
|
|
438
|
+
getProviderID,
|
|
439
|
+
slugifyID,
|
|
440
|
+
formatTanggalID,
|
|
441
|
+
relativeTimeID,
|
|
442
|
+
createUniqueAmount,
|
|
443
|
+
isValidEmail,
|
|
444
|
+
cleanText,
|
|
445
|
+
titleCaseID,
|
|
446
|
+
maskEmail,
|
|
447
|
+
maskPhoneID,
|
|
448
|
+
constants: {
|
|
449
|
+
MONTHS_LONG,
|
|
450
|
+
MONTHS_SHORT,
|
|
451
|
+
DAYS_LONG,
|
|
452
|
+
PASARAN,
|
|
453
|
+
PROVIDER_PREFIXES
|
|
454
|
+
}
|
|
455
|
+
};
|