void-snippets-monorepo 0.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.
- package/README.md +2261 -0
- package/package.json +18 -0
- package/packages/client/package.json +47 -0
- package/packages/client/src/configure.ts +34 -0
- package/packages/client/src/index.ts +4 -0
- package/packages/client/src/services/base-api.service.ts +26 -0
- package/packages/client/src/services/resource-api.service.ts +117 -0
- package/packages/client/src/utils/handle-api-error.ts +20 -0
- package/packages/client/tsconfig.json +13 -0
- package/packages/client/tsup.config.ts +10 -0
- package/packages/core/package.json +41 -0
- package/packages/core/src/id.ts +19 -0
- package/packages/core/src/index.ts +4 -0
- package/packages/core/src/string-to-id.ts +22 -0
- package/packages/core/src/types/index.ts +86 -0
- package/packages/core/src/utils/catch-error.ts +20 -0
- package/packages/core/tsconfig.json +13 -0
- package/packages/core/tsup.config.ts +9 -0
- package/packages/react/package.json +80 -0
- package/packages/react/src/hooks/createResourceHooks.ts +872 -0
- package/packages/react/src/hooks/useAlertMessage.ts +45 -0
- package/packages/react/src/hooks/useAsyncState.ts +110 -0
- package/packages/react/src/hooks/useCallTimer.ts +37 -0
- package/packages/react/src/hooks/useModal.ts +71 -0
- package/packages/react/src/hooks/usePagination.ts +57 -0
- package/packages/react/src/index.ts +43 -0
- package/packages/react/src/routing/createRouteContract.ts +483 -0
- package/packages/react/src/socket/createSocketHooks.ts +351 -0
- package/packages/react/tsconfig.json +14 -0
- package/packages/react/tsup.config.ts +10 -0
- package/pnpm-workspace.yaml +2 -0
- package/tsconfig.base.json +12 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type ReactNode, useCallback, useState } from "react";
|
|
2
|
+
|
|
3
|
+
export type VSAlertVariant = "success" | "info" | "error";
|
|
4
|
+
|
|
5
|
+
export interface VSAlertState {
|
|
6
|
+
message: ReactNode | string;
|
|
7
|
+
type: VSAlertVariant;
|
|
8
|
+
isVisible: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Manages alert/toast message state with optional auto-hide.
|
|
13
|
+
*
|
|
14
|
+
* @param autoHideDuration - ms before alert hides automatically. Pass 0 to disable. Default: 3000
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* const { alert, showAlert, hideAlert } = useAlertMessage();
|
|
18
|
+
* showAlert('Saved successfully!', 'success');
|
|
19
|
+
* showAlert(<b>Something went wrong</b>, 'error');
|
|
20
|
+
*/
|
|
21
|
+
export function useAlertMessage(autoHideDuration = 3000) {
|
|
22
|
+
const [alert, setAlert] = useState<VSAlertState>({
|
|
23
|
+
message: null,
|
|
24
|
+
type: "info",
|
|
25
|
+
isVisible: false,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const showAlert = useCallback(
|
|
29
|
+
(message: ReactNode | string, type: VSAlertVariant = "info") => {
|
|
30
|
+
setAlert({ message, type, isVisible: true });
|
|
31
|
+
if (autoHideDuration) {
|
|
32
|
+
setTimeout(() => {
|
|
33
|
+
setAlert((prev) => ({ ...prev, isVisible: false }));
|
|
34
|
+
}, autoHideDuration);
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
[autoHideDuration]
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const hideAlert = useCallback(() => {
|
|
41
|
+
setAlert((prev) => ({ ...prev, isVisible: false }));
|
|
42
|
+
}, []);
|
|
43
|
+
|
|
44
|
+
return { alert, showAlert, hideAlert };
|
|
45
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState } from "react";
|
|
2
|
+
import { catchError } from "@void-snippets/core";
|
|
3
|
+
|
|
4
|
+
export type VSAsyncStatus = "idle" | "pending" | "success" | "error";
|
|
5
|
+
|
|
6
|
+
interface VSAsyncState<T> {
|
|
7
|
+
data: T | null;
|
|
8
|
+
status: VSAsyncStatus;
|
|
9
|
+
error: Error | null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface VSUseAsyncStateReturn<T> extends VSAsyncState<T> {
|
|
13
|
+
isLoading: boolean;
|
|
14
|
+
isSuccess: boolean;
|
|
15
|
+
isError: boolean;
|
|
16
|
+
|
|
17
|
+
setData: (data: T | null) => void;
|
|
18
|
+
setError: (error: Error | null) => void;
|
|
19
|
+
reset: () => void;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Executes an async function, updates state, and returns a [err, data] tuple.
|
|
23
|
+
* Allows immediate result handling without try/catch.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* const [err, data] = await execute(() => ContactsApis.create(payload));
|
|
27
|
+
* if (err) return showAlert(err.message, 'error');
|
|
28
|
+
* showAlert('Created!', 'success');
|
|
29
|
+
*/
|
|
30
|
+
execute: (
|
|
31
|
+
asyncFn: () => Promise<T>,
|
|
32
|
+
options?: {
|
|
33
|
+
onSuccess?: (data: T) => void;
|
|
34
|
+
onError?: (error: Error) => void;
|
|
35
|
+
}
|
|
36
|
+
) => Promise<[Error, null] | [null, T]>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Generic async state machine — tracks data, status, and error for any async operation.
|
|
41
|
+
* Pair with any async function: API calls, file reads, timers, etc.
|
|
42
|
+
*
|
|
43
|
+
* @param initialData - Optional initial data value. Default: null
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* const { data, isLoading, isError, execute } = useAsyncState<User>();
|
|
47
|
+
*
|
|
48
|
+
* const handleSubmit = async () => {
|
|
49
|
+
* const [err, user] = await execute(() => fetchUser(id));
|
|
50
|
+
* if (err) return;
|
|
51
|
+
* console.log(user.name);
|
|
52
|
+
* };
|
|
53
|
+
*/
|
|
54
|
+
export function useAsyncState<T>(
|
|
55
|
+
initialData: T | null = null
|
|
56
|
+
): VSUseAsyncStateReturn<T> {
|
|
57
|
+
const [state, setState] = useState<VSAsyncState<T>>({
|
|
58
|
+
data: initialData,
|
|
59
|
+
status: "idle",
|
|
60
|
+
error: null,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const setData = useCallback((data: T | null) => {
|
|
64
|
+
setState({ data, status: "success", error: null });
|
|
65
|
+
}, []);
|
|
66
|
+
|
|
67
|
+
const setError = useCallback((error: Error | null) => {
|
|
68
|
+
setState((prev) => ({ ...prev, error, status: "error" }));
|
|
69
|
+
}, []);
|
|
70
|
+
|
|
71
|
+
const reset = useCallback(() => {
|
|
72
|
+
setState({ data: initialData, status: "idle", error: null });
|
|
73
|
+
}, [initialData]);
|
|
74
|
+
|
|
75
|
+
const execute = useCallback(
|
|
76
|
+
async (
|
|
77
|
+
asyncFn: () => Promise<T>,
|
|
78
|
+
options?: {
|
|
79
|
+
onSuccess?: (data: T) => void;
|
|
80
|
+
onError?: (error: Error) => void;
|
|
81
|
+
}
|
|
82
|
+
): Promise<[Error, null] | [null, T]> => {
|
|
83
|
+
setState((prev) => ({ ...prev, status: "pending", error: null }));
|
|
84
|
+
|
|
85
|
+
const [err, res] = await catchError(asyncFn());
|
|
86
|
+
|
|
87
|
+
if (err) {
|
|
88
|
+
setState((prev) => ({ ...prev, status: "error", error: err }));
|
|
89
|
+
options?.onError?.(err);
|
|
90
|
+
return [err, null];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
setState({ data: res as T, status: "success", error: null });
|
|
94
|
+
options?.onSuccess?.(res as T);
|
|
95
|
+
return [null, res as T];
|
|
96
|
+
},
|
|
97
|
+
[]
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const flags = useMemo(
|
|
101
|
+
() => ({
|
|
102
|
+
isLoading: state.status === "pending",
|
|
103
|
+
isSuccess: state.status === "success",
|
|
104
|
+
isError: state.status === "error",
|
|
105
|
+
}),
|
|
106
|
+
[state.status]
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
return { ...state, ...flags, setData, setError, reset, execute };
|
|
110
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tracks elapsed time from a given start timestamp — useful for call durations,
|
|
5
|
+
* countdowns, or any elapsed-time display.
|
|
6
|
+
*
|
|
7
|
+
* @param startedAt - Unix timestamp in ms (e.g. Date.now()). Pass null/undefined to reset.
|
|
8
|
+
* @returns Formatted duration string "MM:SS"
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* const duration = useCallTimer(call.startedAt);
|
|
12
|
+
* // duration → "02:45"
|
|
13
|
+
*
|
|
14
|
+
* // Reset when no active call
|
|
15
|
+
* const duration = useCallTimer(activeCall ? activeCall.startedAt : null);
|
|
16
|
+
*/
|
|
17
|
+
export function useCallTimer(startedAt?: number | null): string {
|
|
18
|
+
const [duration, setDuration] = useState("00:00");
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (!startedAt) {
|
|
22
|
+
setDuration("00:00");
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const interval = setInterval(() => {
|
|
27
|
+
const diffInSeconds = Math.floor((Date.now() - startedAt) / 1000);
|
|
28
|
+
const minutes = Math.floor(diffInSeconds / 60).toString().padStart(2, "0");
|
|
29
|
+
const seconds = (diffInSeconds % 60).toString().padStart(2, "0");
|
|
30
|
+
setDuration(`${minutes}:${seconds}`);
|
|
31
|
+
}, 1000);
|
|
32
|
+
|
|
33
|
+
return () => clearInterval(interval);
|
|
34
|
+
}, [startedAt]);
|
|
35
|
+
|
|
36
|
+
return duration;
|
|
37
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { useCallback, useState } from "react";
|
|
2
|
+
|
|
3
|
+
export interface VSModalReturn<T> {
|
|
4
|
+
isOpen: boolean;
|
|
5
|
+
data: T | null;
|
|
6
|
+
isLoading: boolean;
|
|
7
|
+
openCreateModal: () => void;
|
|
8
|
+
openEditModal: (editData: T) => void;
|
|
9
|
+
setLoading: (loading: boolean) => void;
|
|
10
|
+
closeModal: () => void;
|
|
11
|
+
setModal: (open: boolean, editData?: T | null) => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Manages modal open/close state with optional data payload and loading state.
|
|
16
|
+
* Works for both create and edit modals — pass data to distinguish the mode.
|
|
17
|
+
*
|
|
18
|
+
* @typeParam T - The type of data the modal operates on (e.g. a Contact, User, etc.)
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* const modal = useModal<Contact.Base>();
|
|
22
|
+
*
|
|
23
|
+
* modal.openCreateModal(); // data → null (create mode)
|
|
24
|
+
* modal.openEditModal(contact); // data → contact (edit mode)
|
|
25
|
+
*
|
|
26
|
+
* if (modal.data) {
|
|
27
|
+
* // Edit mode — modal.data is Contact.Base
|
|
28
|
+
* } else {
|
|
29
|
+
* // Create mode
|
|
30
|
+
* }
|
|
31
|
+
*/
|
|
32
|
+
export function useModal<T = unknown>(): VSModalReturn<T> {
|
|
33
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
34
|
+
const [data, setData] = useState<T | null>(null);
|
|
35
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
36
|
+
|
|
37
|
+
const openCreateModal = useCallback(() => {
|
|
38
|
+
setIsOpen(true);
|
|
39
|
+
setData(null);
|
|
40
|
+
}, []);
|
|
41
|
+
|
|
42
|
+
const openEditModal = useCallback((editData: T) => {
|
|
43
|
+
setIsOpen(true);
|
|
44
|
+
setData(editData);
|
|
45
|
+
}, []);
|
|
46
|
+
|
|
47
|
+
const setLoading = useCallback((loading: boolean) => {
|
|
48
|
+
setIsLoading(loading);
|
|
49
|
+
}, []);
|
|
50
|
+
|
|
51
|
+
const closeModal = useCallback(() => {
|
|
52
|
+
setIsOpen(false);
|
|
53
|
+
setData(null);
|
|
54
|
+
}, []);
|
|
55
|
+
|
|
56
|
+
const setModal = useCallback((open: boolean, editData?: T | null) => {
|
|
57
|
+
setIsOpen(open);
|
|
58
|
+
setData(editData ?? null);
|
|
59
|
+
}, []);
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
isOpen,
|
|
63
|
+
data,
|
|
64
|
+
isLoading,
|
|
65
|
+
openCreateModal,
|
|
66
|
+
openEditModal,
|
|
67
|
+
setLoading,
|
|
68
|
+
closeModal,
|
|
69
|
+
setModal,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { useCallback, useState } from "react";
|
|
2
|
+
import type { VSQueryParams } from "@void-snippets/core";
|
|
3
|
+
|
|
4
|
+
export interface VSPaginationReturn {
|
|
5
|
+
page: number;
|
|
6
|
+
limit: number;
|
|
7
|
+
onPaginationChange: (newPage: number, newLimit: number) => void;
|
|
8
|
+
resetPagination: () => void;
|
|
9
|
+
setPage: (page: number) => void;
|
|
10
|
+
setLimit: (limit: number) => void;
|
|
11
|
+
/** Ready-to-use query params object — pass directly to useList() */
|
|
12
|
+
queryParams: VSQueryParams;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Manages pagination state and produces a ready-to-use queryParams object
|
|
17
|
+
* compatible with createResourceHooks' useList() and useInfinite().
|
|
18
|
+
*
|
|
19
|
+
* @param initialPage - Starting page. Default: 1
|
|
20
|
+
* @param initialLimit - Items per page. Default: 10
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* const { queryParams, onPaginationChange } = usePagination(1, 20);
|
|
24
|
+
*
|
|
25
|
+
* const { list, isLoading } = contactHooks.useList(queryParams);
|
|
26
|
+
*
|
|
27
|
+
* <Pagination onChange={onPaginationChange} total={pagination.totalDocuments} />
|
|
28
|
+
*/
|
|
29
|
+
export function usePagination(
|
|
30
|
+
initialPage = 1,
|
|
31
|
+
initialLimit = 10
|
|
32
|
+
): VSPaginationReturn {
|
|
33
|
+
const [page, setPage] = useState(initialPage);
|
|
34
|
+
const [limit, setLimit] = useState(initialLimit);
|
|
35
|
+
|
|
36
|
+
const onPaginationChange = useCallback(
|
|
37
|
+
(newPage: number, newLimit: number) => {
|
|
38
|
+
setPage(newPage);
|
|
39
|
+
setLimit(newLimit);
|
|
40
|
+
},
|
|
41
|
+
[]
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const resetPagination = useCallback(() => {
|
|
45
|
+
setPage(1);
|
|
46
|
+
}, []);
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
page,
|
|
50
|
+
limit,
|
|
51
|
+
onPaginationChange,
|
|
52
|
+
resetPagination,
|
|
53
|
+
setPage,
|
|
54
|
+
setLimit,
|
|
55
|
+
queryParams: { page, limit },
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Resource hooks factory
|
|
2
|
+
export { createResourceHooks } from "./hooks/createResourceHooks";
|
|
3
|
+
export type {
|
|
4
|
+
VSUseListReturn,
|
|
5
|
+
VSUseGetReturn,
|
|
6
|
+
VSResourceHooksOptions,
|
|
7
|
+
VSOptimisticHandlers,
|
|
8
|
+
VSOptimisticOperation,
|
|
9
|
+
} from "./hooks/createResourceHooks";
|
|
10
|
+
|
|
11
|
+
// Socket.IO hooks factory
|
|
12
|
+
export { createSocketHooks } from "./socket/createSocketHooks";
|
|
13
|
+
export type { VSSocketConnectionReturn } from "./socket/createSocketHooks";
|
|
14
|
+
|
|
15
|
+
// Route contract factory
|
|
16
|
+
export {
|
|
17
|
+
createRouteContract,
|
|
18
|
+
defineRoute,
|
|
19
|
+
useTypedSearchParams,
|
|
20
|
+
} from "./routing/createRouteContract";
|
|
21
|
+
export type {
|
|
22
|
+
RouteMetadata,
|
|
23
|
+
RouteDefinition,
|
|
24
|
+
ProcessedRoute,
|
|
25
|
+
} from "./routing/createRouteContract";
|
|
26
|
+
|
|
27
|
+
// General-purpose React hooks
|
|
28
|
+
export { useAlertMessage } from "./hooks/useAlertMessage";
|
|
29
|
+
export type { VSAlertVariant, VSAlertState } from "./hooks/useAlertMessage";
|
|
30
|
+
|
|
31
|
+
export { useAsyncState } from "./hooks/useAsyncState";
|
|
32
|
+
export type {
|
|
33
|
+
VSAsyncStatus,
|
|
34
|
+
VSUseAsyncStateReturn,
|
|
35
|
+
} from "./hooks/useAsyncState";
|
|
36
|
+
|
|
37
|
+
export { useCallTimer } from "./hooks/useCallTimer";
|
|
38
|
+
|
|
39
|
+
export { useModal } from "./hooks/useModal";
|
|
40
|
+
export type { VSModalReturn } from "./hooks/useModal";
|
|
41
|
+
|
|
42
|
+
export { usePagination } from "./hooks/usePagination";
|
|
43
|
+
export type { VSPaginationReturn } from "./hooks/usePagination";
|