@react-spa-scaffold/mcp 0.4.1 → 1.1.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.
Files changed (53) hide show
  1. package/README.md +41 -19
  2. package/dist/features/registry.d.ts.map +1 -1
  3. package/dist/features/registry.js +59 -25
  4. package/dist/features/registry.js.map +1 -1
  5. package/dist/features/types.d.ts +1 -0
  6. package/dist/features/types.d.ts.map +1 -1
  7. package/dist/features/versions.json +1 -1
  8. package/dist/server.d.ts.map +1 -1
  9. package/dist/server.js +2 -1
  10. package/dist/server.js.map +1 -1
  11. package/dist/tools/get-example.js +1 -1
  12. package/dist/tools/get-scaffold.d.ts +1 -0
  13. package/dist/tools/get-scaffold.d.ts.map +1 -1
  14. package/dist/tools/get-scaffold.js +33 -17
  15. package/dist/tools/get-scaffold.js.map +1 -1
  16. package/dist/utils/examples.d.ts.map +1 -1
  17. package/dist/utils/examples.js +19 -16
  18. package/dist/utils/examples.js.map +1 -1
  19. package/dist/utils/paths.d.ts +2 -1
  20. package/dist/utils/paths.d.ts.map +1 -1
  21. package/dist/utils/paths.js +15 -2
  22. package/dist/utils/paths.js.map +1 -1
  23. package/dist/utils/scaffold.d.ts +6 -1
  24. package/dist/utils/scaffold.d.ts.map +1 -1
  25. package/dist/utils/scaffold.js +86 -13
  26. package/dist/utils/scaffold.js.map +1 -1
  27. package/package.json +1 -1
  28. package/templates/CLAUDE.md +4 -2
  29. package/templates/docs/API_REFERENCE.md +0 -1
  30. package/templates/docs/INTERNATIONALIZATION.md +26 -0
  31. package/templates/gitignore +33 -0
  32. package/templates/package.json +1 -1
  33. package/templates/src/components/shared/RegisterForm/RegisterForm.tsx +91 -0
  34. package/templates/src/components/shared/RegisterForm/index.ts +1 -0
  35. package/templates/src/components/shared/index.ts +1 -0
  36. package/templates/src/components/ui/card.tsx +70 -0
  37. package/templates/src/components/ui/input.tsx +19 -0
  38. package/templates/src/components/ui/label.tsx +19 -0
  39. package/templates/src/hooks/index.ts +1 -1
  40. package/templates/src/hooks/useRegisterForm.ts +36 -0
  41. package/templates/src/lib/index.ts +1 -11
  42. package/templates/src/lib/validations.ts +6 -13
  43. package/templates/src/pages/Home.tsx +29 -10
  44. package/templates/tests/unit/components/RegisterForm.test.tsx +105 -0
  45. package/templates/tests/unit/hooks/useRegisterForm.test.tsx +153 -0
  46. package/templates/tests/unit/lib/validations.test.ts +22 -33
  47. package/templates/tests/unit/stores/preferencesStore.test.ts +81 -0
  48. package/templates/vitest.config.ts +1 -1
  49. package/templates/src/hooks/useContactForm.ts +0 -33
  50. package/templates/src/lib/constants.ts +0 -8
  51. package/templates/src/lib/format.ts +0 -119
  52. package/templates/tests/unit/hooks/useContactForm.test.ts +0 -60
  53. package/templates/tests/unit/lib/format.test.ts +0 -100
@@ -1,119 +0,0 @@
1
- /**
2
- * Formatting utilities for dates, numbers, and currencies.
3
- * All formatters are locale-aware.
4
- */
5
-
6
- /**
7
- * Format a date with locale support
8
- */
9
- export function formatDate(
10
- date: Date | string | number,
11
- options: Intl.DateTimeFormatOptions = {},
12
- locale?: string,
13
- ): string {
14
- const dateObj = date instanceof Date ? date : new Date(date);
15
-
16
- if (isNaN(dateObj.getTime())) {
17
- return 'Invalid date';
18
- }
19
-
20
- const defaultOptions: Intl.DateTimeFormatOptions = {
21
- year: 'numeric',
22
- month: 'short',
23
- day: 'numeric',
24
- ...options,
25
- };
26
-
27
- return new Intl.DateTimeFormat(locale, defaultOptions).format(dateObj);
28
- }
29
-
30
- /**
31
- * Format a date with time
32
- */
33
- export function formatDateTime(
34
- date: Date | string | number,
35
- options: Intl.DateTimeFormatOptions = {},
36
- locale?: string,
37
- ): string {
38
- return formatDate(
39
- date,
40
- {
41
- hour: '2-digit',
42
- minute: '2-digit',
43
- ...options,
44
- },
45
- locale,
46
- );
47
- }
48
-
49
- /**
50
- * Format relative time (e.g., "2 hours ago", "in 3 days")
51
- */
52
- export function formatRelativeTime(date: Date | string | number, locale?: string): string {
53
- const dateObj = date instanceof Date ? date : new Date(date);
54
-
55
- if (isNaN(dateObj.getTime())) {
56
- return 'Invalid date';
57
- }
58
-
59
- const now = new Date();
60
- const diffInSeconds = Math.floor((dateObj.getTime() - now.getTime()) / 1000);
61
- const absoluteDiff = Math.abs(diffInSeconds);
62
-
63
- const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
64
-
65
- if (absoluteDiff < 60) {
66
- return rtf.format(diffInSeconds, 'second');
67
- } else if (absoluteDiff < 3600) {
68
- return rtf.format(Math.floor(diffInSeconds / 60), 'minute');
69
- } else if (absoluteDiff < 86400) {
70
- return rtf.format(Math.floor(diffInSeconds / 3600), 'hour');
71
- } else if (absoluteDiff < 2592000) {
72
- return rtf.format(Math.floor(diffInSeconds / 86400), 'day');
73
- } else if (absoluteDiff < 31536000) {
74
- return rtf.format(Math.floor(diffInSeconds / 2592000), 'month');
75
- } else {
76
- return rtf.format(Math.floor(diffInSeconds / 31536000), 'year');
77
- }
78
- }
79
-
80
- /**
81
- * Format a number with locale support
82
- */
83
- export function formatNumber(value: number, options: Intl.NumberFormatOptions = {}, locale?: string): string {
84
- return new Intl.NumberFormat(locale, options).format(value);
85
- }
86
-
87
- /**
88
- * Format a number as currency
89
- */
90
- export function formatCurrency(value: number, currency = 'USD', locale?: string): string {
91
- return new Intl.NumberFormat(locale, {
92
- style: 'currency',
93
- currency,
94
- }).format(value);
95
- }
96
-
97
- /**
98
- * Format a number as percentage
99
- */
100
- export function formatPercent(value: number, decimals = 0, locale?: string): string {
101
- return new Intl.NumberFormat(locale, {
102
- style: 'percent',
103
- minimumFractionDigits: decimals,
104
- maximumFractionDigits: decimals,
105
- }).format(value);
106
- }
107
-
108
- /**
109
- * Format bytes to human readable string
110
- */
111
- export function formatBytes(bytes: number, decimals = 2): string {
112
- if (bytes === 0) return '0 Bytes';
113
-
114
- const k = 1024;
115
- const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
116
- const i = Math.floor(Math.log(bytes) / Math.log(k));
117
-
118
- return `${parseFloat((bytes / Math.pow(k, i)).toFixed(decimals))} ${sizes[i]}`;
119
- }
@@ -1,60 +0,0 @@
1
- import { act, renderHook, waitFor } from '@testing-library/react';
2
- import { describe, expect, it } from 'vitest';
3
-
4
- import { useContactForm } from '@/hooks/useContactForm';
5
-
6
- describe('useContactForm', () => {
7
- describe('initial state', () => {
8
- it('initializes with empty values and no errors', () => {
9
- const { result } = renderHook(() => useContactForm());
10
-
11
- expect(result.current.form.getValues()).toEqual({ name: '', email: '', message: '' });
12
- expect(result.current.errors).toEqual({});
13
- expect(result.current.isSubmitting).toBe(false);
14
- expect(typeof result.current.onSubmit).toBe('function');
15
- });
16
- });
17
-
18
- describe('validation', () => {
19
- it.each([
20
- { field: 'name', value: 'J', valid: { email: 'test@example.com', message: 'Valid message here' } },
21
- { field: 'email', value: 'invalid', valid: { name: 'John', message: 'Valid message here' } },
22
- { field: 'message', value: 'Short', valid: { name: 'John', email: 'test@example.com' } },
23
- ])('rejects invalid $field', async ({ field, value, valid }) => {
24
- const { result } = renderHook(() => useContactForm());
25
-
26
- act(() => {
27
- result.current.form.setValue(field as 'name' | 'email' | 'message', value);
28
- Object.entries(valid).forEach(([k, v]) => {
29
- result.current.form.setValue(k as 'name' | 'email' | 'message', v);
30
- });
31
- });
32
-
33
- await act(async () => {
34
- await result.current.form.trigger();
35
- });
36
-
37
- await waitFor(() => {
38
- expect(result.current.errors[field as keyof typeof result.current.errors]).toBeDefined();
39
- });
40
- });
41
-
42
- it('passes with valid data', async () => {
43
- const { result } = renderHook(() => useContactForm());
44
-
45
- act(() => {
46
- result.current.form.setValue('name', 'John Doe');
47
- result.current.form.setValue('email', 'test@example.com');
48
- result.current.form.setValue('message', 'This is a valid message that is long enough.');
49
- });
50
-
51
- await act(async () => {
52
- await result.current.form.trigger();
53
- });
54
-
55
- await waitFor(() => {
56
- expect(result.current.errors).toEqual({});
57
- });
58
- });
59
- });
60
- });
@@ -1,100 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
-
3
- import {
4
- formatBytes,
5
- formatCurrency,
6
- formatDate,
7
- formatDateTime,
8
- formatNumber,
9
- formatPercent,
10
- formatRelativeTime,
11
- } from '@/lib/format';
12
-
13
- describe('formatDate', () => {
14
- it('formats date objects and strings', () => {
15
- const date = new Date('2024-01-15T12:00:00Z');
16
- expect(formatDate(date, {}, 'en-US')).toContain('2024');
17
- expect(formatDate('2024-06-20', {}, 'en-US')).toContain('2024');
18
- });
19
-
20
- it('returns "Invalid date" for invalid input', () => {
21
- expect(formatDate('invalid')).toBe('Invalid date');
22
- });
23
-
24
- it('respects custom options', () => {
25
- const date = new Date('2024-01-15');
26
- expect(formatDate(date, { weekday: 'long' }, 'en-US')).toContain('Monday');
27
- });
28
- });
29
-
30
- describe('formatDateTime', () => {
31
- it('includes time in output', () => {
32
- const date = new Date('2024-01-15T14:30:00Z');
33
- expect(formatDateTime(date, {}, 'en-US')).toMatch(/\d{1,2}:\d{2}/);
34
- });
35
- });
36
-
37
- describe('formatRelativeTime', () => {
38
- it.each([
39
- { offset: -30 * 1000, unit: 'seconds', pattern: /second|now/i },
40
- { offset: -5 * 60 * 1000, unit: 'minutes', pattern: /minute/i },
41
- { offset: -60 * 60 * 1000, unit: 'hours', pattern: /hour|ago/i },
42
- { offset: 24 * 60 * 60 * 1000, unit: 'days (future)', pattern: /day|tomorrow/i },
43
- { offset: -45 * 24 * 60 * 60 * 1000, unit: 'months', pattern: /month/i },
44
- { offset: -400 * 24 * 60 * 60 * 1000, unit: 'years', pattern: /year/i },
45
- ])('formats $unit correctly', ({ offset, pattern }) => {
46
- const date = new Date(Date.now() + offset);
47
- expect(formatRelativeTime(date, 'en-US')).toMatch(pattern);
48
- });
49
-
50
- it('returns "Invalid date" for invalid input', () => {
51
- expect(formatRelativeTime('invalid')).toBe('Invalid date');
52
- });
53
-
54
- it('accepts timestamp numbers', () => {
55
- const timestamp = Date.now() - 60 * 60 * 1000;
56
- expect(formatRelativeTime(timestamp, 'en-US')).toMatch(/hour|ago/i);
57
- });
58
- });
59
-
60
- describe('formatNumber', () => {
61
- it.each([
62
- { value: 1234567.89, options: {}, expected: '1,234,567.89' },
63
- { value: 1234.5, options: { minimumFractionDigits: 2 }, expected: '1,234.50' },
64
- ])('formats $value with options', ({ value, options, expected }) => {
65
- expect(formatNumber(value, options, 'en-US')).toBe(expected);
66
- });
67
- });
68
-
69
- describe('formatCurrency', () => {
70
- it.each([
71
- { value: 99.99, currency: 'USD', locale: 'en-US', expected: '$99.99' },
72
- { value: 99.99, currency: 'EUR', locale: 'de-DE', contains: '€' },
73
- ])('formats $currency correctly', ({ value, currency, locale, expected, contains }) => {
74
- const result = formatCurrency(value, currency, locale);
75
- if (expected) expect(result).toBe(expected);
76
- if (contains) expect(result).toContain(contains);
77
- });
78
- });
79
-
80
- describe('formatPercent', () => {
81
- it.each([
82
- { value: 0.25, decimals: 0, expected: '25%' },
83
- { value: 0.2567, decimals: 2, expected: '25.67%' },
84
- ])('formats $value with $decimals decimals', ({ value, decimals, expected }) => {
85
- expect(formatPercent(value, decimals, 'en-US')).toBe(expected);
86
- });
87
- });
88
-
89
- describe('formatBytes', () => {
90
- it.each([
91
- { bytes: 0, expected: '0 Bytes' },
92
- { bytes: 1024, expected: '1 KB' },
93
- { bytes: 1024 * 1024, expected: '1 MB' },
94
- { bytes: 1024 * 1024 * 1024, expected: '1 GB' },
95
- { bytes: 1536, decimals: 1, expected: '1.5 KB' },
96
- { bytes: 1536, decimals: 0, expected: '2 KB' },
97
- ])('formats $bytes bytes', ({ bytes, decimals, expected }) => {
98
- expect(formatBytes(bytes, decimals)).toBe(expected);
99
- });
100
- });