feat: add DateUtils and StringUtils for date and string manipulation

- Implement DateUtils for handling date operations with timezone support.
- Add unit tests for DateUtils to ensure functionality and edge cases.
- Create StringUtils for string manipulation with a fluent API.
- Include unit tests for StringUtils covering various string operations.
- Introduce EncryptionUtils for secure data encryption and decryption.
- Add tests for EncryptionUtils to validate encryption and decryption processes.
- Update index.ts to export new utility modules.
- Introduce encryption key management with a dedicated key file.
This commit is contained in:
Firman Ramdhani
2026-01-28 18:18:29 +07:00
parent 97f48ce1b9
commit a1db9b93e1
15 changed files with 269 additions and 268 deletions
@@ -1,12 +1,12 @@
import { DateService } from '@repo/utils';
import { DateUtils } from '@repo/utils';
import React, { useState, useEffect, useMemo } from 'react';
export const DateServiceExample: React.FC = () => {
// --- 0. TIMEZONE CONFIGURATION (ROOT CONTROL) ---
const [currentZone, setCurrentZone] = useState<string>(DateService.getGlobalTimezone());
const [currentZone, setCurrentZone] = useState<string>(DateUtils.getGlobalTimezone());
// Ambil list timezone dari helper static yang kita buat sebelumnya
const timezoneList = useMemo(() => DateService.getSupportedTimezones(), []);
const timezoneList = useMemo(() => DateUtils.getSupportedTimezones(), []);
// --- 1. STATE & DATA ---
@@ -17,40 +17,40 @@ export const DateServiceExample: React.FC = () => {
{
id: 1,
title: 'Bayar Server (Lusa)',
dueDate: DateService.now().add(2, 'day').toISOString(),
dueDate: DateUtils.now().add(2, 'day').toISOString(),
},
{
id: 2,
title: 'Laporan Bulanan (Lewat 5 Hari)',
dueDate: DateService.now().subtract(5, 'day').toISOString(),
dueDate: DateUtils.now().subtract(5, 'day').toISOString(),
},
{
id: 3,
title: 'Meeting Tahunan (Bulan Depan)',
dueDate: DateService.now().add(1, 'month').toISOString(),
dueDate: DateUtils.now().add(1, 'month').toISOString(),
},
{
id: 4,
title: 'Deadline Besok Pagi',
dueDate: DateService.now().add(1, 'day').startOf('day').toISOString(),
dueDate: DateUtils.now().add(1, 'day').startOf('day').toISOString(),
},
];
}, [currentZone]); // Dependency: currentZone
const [currentTime, setCurrentTime] = useState<DateService>(DateService.now());
const [selectedDate, setSelectedDate] = useState<DateService>(DateService.now());
const [currentTime, setCurrentTime] = useState<DateUtils>(DateUtils.now());
const [selectedDate, setSelectedDate] = useState<DateUtils>(DateUtils.now());
// State demo logic calendar
const [calendarTarget, setCalendarTarget] = useState(() => DateService.now().add(1, 'day').startOf('day'));
const [calendarTarget, setCalendarTarget] = useState(() => DateUtils.now().add(1, 'day').startOf('day'));
// Static date (Memo kosong) -> Akan tetap di Timezone saat pertama kali load (kecuali di-refresh)
// Ini bagus untuk demo bahwa object lama tidak berubah (immutable) kecuali dibuat ulang.
const staticDate = useMemo(() => new DateService(), []);
const staticDate = useMemo(() => new DateUtils(), []);
// Effect update jam tiap detik
useEffect(() => {
const timer = setInterval(() => {
setCurrentTime(DateService.now());
setCurrentTime(DateUtils.now());
}, 1000);
return () => clearInterval(timer);
}, []);
@@ -61,17 +61,17 @@ export const DateServiceExample: React.FC = () => {
setCurrentZone(newZone);
// 1. Set Global Config (Inti Perubahan)
DateService.setGlobalConfig(newZone);
DateUtils.setGlobalConfig(newZone);
// 2. Force Refresh State agar UI langsung berubah
// Kita buat instance baru agar mengambil config timezone terbaru
setCurrentTime(DateService.now());
setCurrentTime(DateUtils.now());
// Kita recreate selectedDate dengan nilai raw yang sama, tapi context timezone baru
setSelectedDate((prev) => new DateService(prev.getRaw()));
setSelectedDate((prev) => new DateUtils(prev.getRaw()));
// Recreate target demo calendar
setCalendarTarget(DateService.now().add(1, 'day').startOf('day'));
setCalendarTarget(DateUtils.now().add(1, 'day').startOf('day'));
};
const handleAddDay = (days: number) => {
@@ -82,7 +82,7 @@ export const DateServiceExample: React.FC = () => {
<div className="p-8 max-w-5xl mx-auto bg-gray-50 min-h-screen font-sans text-gray-800">
{/* HEADER & TIMEZONE CONTROLLER */}
<div className="flex flex-col md:flex-row justify-between items-start md:items-center mb-8 gap-4 border-b border-gray-200 pb-6">
<h1 className="text-3xl font-bold text-blue-900">DateService Feature Showcase</h1>
<h1 className="text-3xl font-bold text-blue-900">DateUtils Feature Showcase</h1>
<div className="bg-white p-3 rounded-lg shadow-sm border border-blue-100 flex items-center gap-3">
<label htmlFor="tz-switcher" className="text-xs font-bold text-gray-500 uppercase tracking-wide">
@@ -162,11 +162,11 @@ export const DateServiceExample: React.FC = () => {
<div className="grid grid-cols-2 gap-4">
<div>
<div className="text-[10px] uppercase text-gray-500">Epoch Millis</div>
<div className="font-mono font-bold text-gray-700">{new DateService().epochMillis}</div>
<div className="font-mono font-bold text-gray-700">{new DateUtils().epochMillis}</div>
</div>
<div>
<div className="text-[10px] uppercase text-gray-500">Epoch Seconds</div>
<div className="font-mono font-bold text-gray-700">{new DateService().epochSeconds}</div>
<div className="font-mono font-bold text-gray-700">{new DateUtils().epochSeconds}</div>
</div>
</div>
</div>
@@ -278,8 +278,8 @@ export const DateServiceExample: React.FC = () => {
</thead>
<tbody>
{tasks.map((task) => {
const dueDate = new DateService(task.dueDate);
const now = DateService.now();
const dueDate = new DateUtils(task.dueDate);
const now = DateUtils.now();
const isOverdue = now.isAfter(dueDate);
const daysDiff = dueDate.diffCalendarDay(now);
@@ -1,10 +1,10 @@
import { EncryptionService } from '@repo/utils';
import { EncryptionUtils } from '@repo/utils';
import React, { useState } from 'react';
export const EncryptionExample: React.FC = () => {
// --- 1. SETUP INSTANCE ---
// Kita gunakan Singleton agar hemat memory
const cryptoService = EncryptionService.getInstance();
const cryptoService = EncryptionUtils.getInstance();
// --- 2. STATE ---
const [plainText, setPlainText] = useState<string>('');
@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import { useState } from 'react';
// ==========================================================
// 1. ATOMIC COMPONENTS (Berdasar Config 13px & Burgundy)
@@ -1,4 +1,4 @@
import { CurrencyService } from '@repo/utils';
import { CurrencyUtils } from '@repo/utils';
import { InputCurrencyProps } from '../types';
import { InputNumber } from './input-number.component';
@@ -6,13 +6,13 @@ import { InputNumber } from './input-number.component';
* InputCurrency
*
* A controlled InputNumber component with currency formatting.
* Delegates all formatting/parsing to CurrencyService.
* Delegates all formatting/parsing to CurrencyUtils.
*
* Features:
* - Display currency prefix (e.g., Rp, $)
* - Thousand separator formatting
* - Optional decimal rounding
* - Global prefix support via CurrencyService
* - Global prefix support via CurrencyUtils
*/
export const InputCurrency = (props: InputCurrencyProps) => {
const {
@@ -22,8 +22,8 @@ export const InputCurrency = (props: InputCurrencyProps) => {
...restProps
} = props;
// Create a CurrencyService instance for this input
const currencyService = new CurrencyService({
// Create a CurrencyUtils instance for this input
const currencyService = new CurrencyUtils({
prefix,
decimalSeparator,
decimalScale,