@kasuvia/sdk 0.0.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 (73) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +112 -0
  3. package/dist/index.d.mts +1891 -0
  4. package/dist/index.d.ts +1891 -0
  5. package/dist/index.js +3540 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/index.mjs +3314 -0
  8. package/dist/index.mjs.map +1 -0
  9. package/package.json +59 -0
  10. package/src/client/authClient.ts +135 -0
  11. package/src/client/graphqlClient.ts +53 -0
  12. package/src/client/httpClient.ts +298 -0
  13. package/src/config/index.ts +102 -0
  14. package/src/graphql/collections.graphql.ts +56 -0
  15. package/src/graphql/index.ts +7 -0
  16. package/src/graphql/products.graphql.ts +710 -0
  17. package/src/graphql/wishlist.graphql.ts +33 -0
  18. package/src/hooks/index.ts +26 -0
  19. package/src/hooks/use-auth.ts +182 -0
  20. package/src/hooks/use-bespoke-availability.ts +20 -0
  21. package/src/hooks/use-cart.ts +207 -0
  22. package/src/hooks/use-cms.ts +165 -0
  23. package/src/hooks/use-collections.ts +40 -0
  24. package/src/hooks/use-consultation-stream.ts +141 -0
  25. package/src/hooks/use-consultations.ts +76 -0
  26. package/src/hooks/use-countries.ts +24 -0
  27. package/src/hooks/use-currencies.ts +27 -0
  28. package/src/hooks/use-google-translate.ts +164 -0
  29. package/src/hooks/use-is-mounted.ts +9 -0
  30. package/src/hooks/use-languages.ts +33 -0
  31. package/src/hooks/use-order-stream.ts +195 -0
  32. package/src/hooks/use-orders.ts +64 -0
  33. package/src/hooks/use-payment.ts +68 -0
  34. package/src/hooks/use-products.ts +113 -0
  35. package/src/hooks/use-service-booking.ts +118 -0
  36. package/src/hooks/use-states.ts +28 -0
  37. package/src/hooks/use-wishlist.ts +121 -0
  38. package/src/index.ts +65 -0
  39. package/src/services/app.service.ts +17 -0
  40. package/src/services/auth.service.ts +332 -0
  41. package/src/services/bespoke.service.ts +17 -0
  42. package/src/services/cart.service.ts +245 -0
  43. package/src/services/cms.service.ts +112 -0
  44. package/src/services/collection.service.ts +77 -0
  45. package/src/services/config.service.ts +42 -0
  46. package/src/services/consultation.service.ts +51 -0
  47. package/src/services/index.ts +21 -0
  48. package/src/services/logistics.service.ts +95 -0
  49. package/src/services/notifications.service.ts +50 -0
  50. package/src/services/order.service.ts +169 -0
  51. package/src/services/payment.service.ts +127 -0
  52. package/src/services/product.service.ts +678 -0
  53. package/src/services/service-booking.service.ts +86 -0
  54. package/src/services/wishlist.service.ts +84 -0
  55. package/src/types/auth/index.ts +126 -0
  56. package/src/types/bespoke/index.ts +39 -0
  57. package/src/types/business/index.ts +6 -0
  58. package/src/types/cms/index.ts +77 -0
  59. package/src/types/consultations/index.ts +79 -0
  60. package/src/types/index.ts +27 -0
  61. package/src/types/media/index.ts +41 -0
  62. package/src/types/payments/index.ts +120 -0
  63. package/src/types/services/index.ts +80 -0
  64. package/src/types/store/index.ts +282 -0
  65. package/src/types/store/shipping.ts +38 -0
  66. package/src/types/ui/index.ts +32 -0
  67. package/src/utils/cache.ts +33 -0
  68. package/src/utils/checkout.ts +39 -0
  69. package/src/utils/customer.ts +20 -0
  70. package/src/utils/error-handler.ts +14 -0
  71. package/src/utils/index.ts +10 -0
  72. package/src/utils/logger.ts +79 -0
  73. package/src/utils/sanitize-url.ts +55 -0
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Orders Hook
3
+ *
4
+ * React Query hook for order operations.
5
+ */
6
+ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
7
+ import { getOrders, findOrder, createOrder, cancelOrder, trackOrder } from '../services/order.service'
8
+ import type { Order, FindOrderParams, CreateOrderParams } from '../types/store'
9
+
10
+ export function useOrders(params?: { email?: string; page?: number; page_size?: number }) {
11
+ return useQuery({
12
+ queryKey: ['orders', params],
13
+ queryFn: () => getOrders(params),
14
+ })
15
+ }
16
+
17
+ // Alias for useOrders for backward compatibility
18
+ export function useOrdersList(params?: { email?: string; page?: number; page_size?: number }) {
19
+ return useOrders(params)
20
+ }
21
+
22
+ export function useFindOrder(params: FindOrderParams) {
23
+ return useQuery({
24
+ queryKey: ['order', params],
25
+ queryFn: () => findOrder(params),
26
+ enabled: !!params.email && !!params.order_id,
27
+ })
28
+ }
29
+
30
+ export function useOrder(params: FindOrderParams) {
31
+ return useFindOrder(params)
32
+ }
33
+
34
+ export function useOrderTracking(orderId: string, token?: string) {
35
+ return useQuery({
36
+ queryKey: ['order-tracking', orderId, token],
37
+ queryFn: () => trackOrder(orderId, token),
38
+ enabled: !!orderId,
39
+ refetchInterval: 30000, // Refetch every 30 seconds for tracking updates
40
+ })
41
+ }
42
+
43
+ export function useCreateOrder() {
44
+ const queryClient = useQueryClient()
45
+
46
+ return useMutation({
47
+ mutationFn: createOrder,
48
+ onSuccess: () => {
49
+ queryClient.invalidateQueries({ queryKey: ['orders'] })
50
+ },
51
+ })
52
+ }
53
+
54
+ export function useCancelOrder() {
55
+ const queryClient = useQueryClient()
56
+
57
+ return useMutation({
58
+ mutationFn: ({ orderId, reason }: { orderId: string; reason: string }) =>
59
+ cancelOrder(orderId, reason),
60
+ onSuccess: () => {
61
+ queryClient.invalidateQueries({ queryKey: ['orders'] })
62
+ },
63
+ })
64
+ }
@@ -0,0 +1,68 @@
1
+ import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
2
+ import { paymentService } from "../services/payment.service";
3
+ import { getCacheTier } from "../utils/cache";
4
+ import { isInitialized } from "../config";
5
+ import type { DVACreateRequest, PaymentInitiateRequest } from "../types/payments";
6
+
7
+
8
+ /**
9
+ * Hook for creating a Dedicated Virtual Account (DVA)
10
+ */
11
+ export function useCreateDVA() {
12
+ const queryClient = useQueryClient();
13
+
14
+ return useMutation({
15
+ mutationFn: (request: DVACreateRequest) =>
16
+ paymentService.createDedicatedAccount(request),
17
+ onSuccess: () => {
18
+ queryClient.invalidateQueries({ queryKey: ['payment'] });
19
+ },
20
+ });
21
+ }
22
+
23
+ /**
24
+ * Hook for initiating card payment
25
+ */
26
+ export function useInitiatePayment() {
27
+ return useMutation({
28
+ mutationFn: (request: PaymentInitiateRequest) =>
29
+ paymentService.initiatePayment(request),
30
+ });
31
+ }
32
+
33
+ /**
34
+ * Hook for verifying a transaction
35
+ */
36
+ export function useVerifyTransaction() {
37
+ return useMutation({
38
+ mutationFn: (reference: string) =>
39
+ paymentService.verifyTransaction(reference),
40
+ });
41
+ }
42
+
43
+ /**
44
+ * Hook for getting transaction status
45
+ */
46
+ export function useTransactionStatus(transactionId: string, enabled = false) {
47
+ return useQuery({
48
+ queryKey: ['transaction', 'status', transactionId],
49
+ queryFn: () => paymentService.getTransactionStatus(transactionId),
50
+ enabled: isInitialized() && enabled,
51
+ ...getCacheTier('none'),
52
+ refetchInterval: 3000, // Poll every 3 seconds
53
+ });
54
+ }
55
+
56
+ /**
57
+ * Hook for checking transaction status with automatic polling
58
+ */
59
+ export function usePollTransactionStatus(reference: string, enabled = false) {
60
+ return useQuery({
61
+ queryKey: ['transaction', 'poll', reference],
62
+ queryFn: () => paymentService.verifyTransaction(reference),
63
+ enabled: isInitialized() && enabled,
64
+ ...getCacheTier('none'),
65
+ refetchInterval: 2000, // Poll every 2 seconds
66
+ retry: 10, // Retry up to 10 times
67
+ });
68
+ }
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Products Hooks
3
+ *
4
+ * React Query hooks for fetching products.
5
+ */
6
+ import { useQuery } from '@tanstack/react-query'
7
+ import {
8
+ getStorefrontProducts,
9
+ getLatestProducts,
10
+ getPopularProducts,
11
+ getFeaturedProducts,
12
+ getNewProducts,
13
+ getStorefrontProductBySlug,
14
+ getRecommendedProducts
15
+ } from '../services/product.service'
16
+ import type { ListParams } from '../types/store'
17
+ import { getCacheTier } from '../utils/cache'
18
+
19
+ export const productKeys = {
20
+ all: ["products"] as const,
21
+ list: (params?: ListParams) => [...productKeys.all, "list", params] as const,
22
+ detail: (slug: string) => [...productKeys.all, "detail", slug] as const,
23
+ recommended: (params?: ListParams) => [...productKeys.all, "recommended", params] as const,
24
+ latest: (params?: ListParams) => [...productKeys.all, "latest", params] as const,
25
+ popular: (params?: ListParams) => [...productKeys.all, "popular", params] as const,
26
+ featured: (params?: ListParams) => [...productKeys.all, "featured", params] as const,
27
+ new: (params?: ListParams) => [...productKeys.all, "new", params] as const,
28
+ }
29
+
30
+ /**
31
+ * Hook for fetching latest products.
32
+ */
33
+ export function useLatestProducts(params?: ListParams) {
34
+ return useQuery({
35
+ queryKey: productKeys.latest(params),
36
+ queryFn: () => getLatestProducts(params),
37
+ // medium — latest products change infrequently
38
+ ...getCacheTier('medium'),
39
+ })
40
+ }
41
+
42
+ /**
43
+ * Hook for fetching popular products.
44
+ */
45
+ export function usePopularProducts(params?: ListParams) {
46
+ return useQuery({
47
+ queryKey: productKeys.popular(params),
48
+ queryFn: () => getPopularProducts(params),
49
+ // medium — popular products change infrequently
50
+ ...getCacheTier('medium'),
51
+ })
52
+ }
53
+
54
+ /**
55
+ * Hook for fetching featured products.
56
+ */
57
+ export function useFeaturedProducts(params?: ListParams) {
58
+ return useQuery({
59
+ queryKey: productKeys.featured(params),
60
+ queryFn: () => getFeaturedProducts(params),
61
+ // medium — featured products change infrequently
62
+ ...getCacheTier('medium'),
63
+ })
64
+ }
65
+
66
+ /**
67
+ * Hook for fetching new products.
68
+ */
69
+ export function useNewProducts(params?: ListParams) {
70
+ return useQuery({
71
+ queryKey: productKeys.new(params),
72
+ queryFn: () => getNewProducts(params),
73
+ // medium — new products change infrequently
74
+ ...getCacheTier('medium'),
75
+ })
76
+ }
77
+
78
+ /**
79
+ * Hook for fetching all active storefront products.
80
+ */
81
+ export function useStorefrontProducts(params?: ListParams) {
82
+ return useQuery({
83
+ queryKey: productKeys.list(params),
84
+ queryFn: () => getStorefrontProducts(params),
85
+ // medium — product catalog changes infrequently
86
+ ...getCacheTier('medium'),
87
+ })
88
+ }
89
+
90
+ /**
91
+ * Hook for fetching a single product by slug.
92
+ */
93
+ export function useProduct(slug: string) {
94
+ return useQuery({
95
+ queryKey: productKeys.detail(slug),
96
+ queryFn: () => getStorefrontProductBySlug(slug),
97
+ // medium — product details change infrequently
98
+ ...getCacheTier('medium'),
99
+ enabled: !!slug,
100
+ })
101
+ }
102
+
103
+ /**
104
+ * Hook for fetching recommended products.
105
+ */
106
+ export function useRecommendedProducts(params?: ListParams) {
107
+ return useQuery({
108
+ queryKey: productKeys.recommended(params),
109
+ queryFn: () => getRecommendedProducts(params),
110
+ // medium — recommendations change infrequently
111
+ ...getCacheTier('medium'),
112
+ })
113
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Service Booking Hooks
3
+ *
4
+ * React Query hooks for service bookings.
5
+ */
6
+ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
7
+ import { serviceBookingService } from '../services/service-booking.service'
8
+ import type {
9
+ ServiceAppointment,
10
+ ServiceAppointmentCreatePayload,
11
+ ServiceDeliverable,
12
+ } from '../types/services'
13
+
14
+ /**
15
+ * Get available slots for a service product
16
+ */
17
+ export function useAvailableSlots(
18
+ product_id: string,
19
+ date_from: string,
20
+ date_to: string
21
+ ) {
22
+ return useQuery({
23
+ queryKey: ["available-slots", product_id, date_from, date_to],
24
+ queryFn: () =>
25
+ serviceBookingService.getAvailableSlots(product_id, date_from, date_to),
26
+ enabled: !!product_id && !!date_from && !!date_to,
27
+ })
28
+ }
29
+
30
+ /**
31
+ * Book a service appointment
32
+ */
33
+ export function useBookAppointment() {
34
+ const queryClient = useQueryClient()
35
+
36
+ return useMutation({
37
+ mutationFn: serviceBookingService.bookAppointment,
38
+ onSuccess: () => {
39
+ queryClient.invalidateQueries({ queryKey: ["appointments"] })
40
+ },
41
+ })
42
+ }
43
+
44
+ /**
45
+ * Get all appointments for the current user
46
+ */
47
+ export function useAppointments() {
48
+ return useQuery({
49
+ queryKey: ["appointments"],
50
+ queryFn: serviceBookingService.getAppointments,
51
+ })
52
+ }
53
+
54
+ /**
55
+ * Get a single appointment by ID
56
+ */
57
+ export function useAppointment(id: string) {
58
+ return useQuery({
59
+ queryKey: ["appointment", id],
60
+ queryFn: () => serviceBookingService.getAppointment(id),
61
+ enabled: !!id,
62
+ })
63
+ }
64
+
65
+ /**
66
+ * Cancel an appointment
67
+ */
68
+ export function useCancelAppointment() {
69
+ const queryClient = useQueryClient()
70
+
71
+ return useMutation({
72
+ mutationFn: (id: string) => serviceBookingService.cancelAppointment(id),
73
+ onSuccess: () => {
74
+ queryClient.invalidateQueries({ queryKey: ["appointments"] })
75
+ },
76
+ })
77
+ }
78
+
79
+ /**
80
+ * Get deliverables for a project-based service order
81
+ */
82
+ export function useDeliverables(order_id: string) {
83
+ return useQuery({
84
+ queryKey: ["deliverables", order_id],
85
+ queryFn: () => serviceBookingService.getDeliverables(order_id),
86
+ enabled: !!order_id,
87
+ })
88
+ }
89
+
90
+ /**
91
+ * Approve a deliverable
92
+ */
93
+ export function useApproveDeliverable(order_id: string, deliverable_id: string) {
94
+ const queryClient = useQueryClient()
95
+
96
+ return useMutation({
97
+ mutationFn: () =>
98
+ serviceBookingService.approveDeliverable(order_id, deliverable_id),
99
+ onSuccess: () => {
100
+ queryClient.invalidateQueries({ queryKey: ["deliverables", order_id] })
101
+ },
102
+ })
103
+ }
104
+
105
+ /**
106
+ * Request revision for a deliverable
107
+ */
108
+ export function useRequestRevision(order_id: string, deliverable_id: string) {
109
+ const queryClient = useQueryClient()
110
+
111
+ return useMutation({
112
+ mutationFn: (feedback: string) =>
113
+ serviceBookingService.requestRevision(order_id, deliverable_id, feedback),
114
+ onSuccess: () => {
115
+ queryClient.invalidateQueries({ queryKey: ["deliverables", order_id] })
116
+ },
117
+ })
118
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * States Hook
3
+ *
4
+ * React Query hook for fetching states for a country.
5
+ */
6
+ import { useQuery } from '@tanstack/react-query'
7
+ import { authAxios } from '../client/httpClient'
8
+ import type { State } from '../types/ui'
9
+
10
+ export async function getStates(countryIso: string): Promise<State[]> {
11
+ if (!countryIso) return []
12
+ const { data } = await authAxios.get<State[]>('auth/countries/', {
13
+ params: { iso: countryIso }
14
+ })
15
+ return data
16
+ }
17
+
18
+ export function useStates(countryIso: string) {
19
+ return useQuery({
20
+ queryKey: ['states', countryIso],
21
+ queryFn: () => getStates(countryIso),
22
+ enabled: !!countryIso,
23
+ retry: 1,
24
+ refetchOnWindowFocus: false,
25
+ refetchOnReconnect: false,
26
+ staleTime: 24 * 60 * 60 * 1000, // 24 hours
27
+ })
28
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Wishlist Hooks
3
+ *
4
+ * React Query hooks for wishlist operations.
5
+ */
6
+ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
7
+ import {
8
+ getWishlist,
9
+ addToWishlist,
10
+ removeFromWishlist,
11
+ removeProductFromWishlist,
12
+ checkInWishlist,
13
+ } from '../services/wishlist.service'
14
+ import { getCacheTier } from '../utils/cache'
15
+
16
+ export const wishlistKeys = {
17
+ all: ["wishlist"] as const,
18
+ list: () => [...wishlistKeys.all, "list"] as const,
19
+ check: (productId: string) => [...wishlistKeys.all, "check", productId] as const,
20
+ }
21
+
22
+ /**
23
+ * Hook for fetching the customer's wishlist.
24
+ * Authentication-gated only - returns empty for unauthenticated users.
25
+ */
26
+ export function useWishlist(params?: { page?: number; pageSize?: number }) {
27
+ return useQuery({
28
+ queryKey: [...wishlistKeys.list(), params],
29
+ queryFn: () => getWishlist(params),
30
+ // medium — wishlist changes infrequently
31
+ ...getCacheTier('medium'),
32
+ })
33
+ }
34
+
35
+ /**
36
+ * Hook for checking if a product is in the wishlist.
37
+ * Authentication-gated only - returns false for unauthenticated users.
38
+ */
39
+ export function useCheckInWishlist(productId: string) {
40
+ return useQuery({
41
+ queryKey: wishlistKeys.check(productId),
42
+ queryFn: () => checkInWishlist(productId),
43
+ // low — wishlist check changes frequently
44
+ ...getCacheTier('low'),
45
+ enabled: !!productId,
46
+ })
47
+ }
48
+
49
+ /**
50
+ * Hook for adding a product to the wishlist.
51
+ * Authentication-gated only - triggers login modal for unauthenticated users.
52
+ */
53
+ export function useAddToWishlist() {
54
+ const queryClient = useQueryClient()
55
+
56
+ return useMutation({
57
+ mutationFn: ({ productId, notes }: { productId: string; notes?: string }) => {
58
+ return addToWishlist(productId, notes)
59
+ },
60
+ onSuccess: () => {
61
+ // Invalidate all wishlist queries to ensure cache is updated
62
+ queryClient.invalidateQueries({ queryKey: wishlistKeys.all })
63
+ // Also invalidate specific check queries for all products
64
+ queryClient.invalidateQueries({ queryKey: ['wishlist', 'check'] })
65
+ },
66
+ })
67
+ }
68
+
69
+ /**
70
+ * Hook for removing an item from the wishlist by item ID.
71
+ */
72
+ export function useRemoveFromWishlist() {
73
+ const queryClient = useQueryClient()
74
+
75
+ return useMutation({
76
+ mutationFn: (itemId: string) => {
77
+ return removeFromWishlist(itemId)
78
+ },
79
+ onSuccess: () => {
80
+ // Invalidate all wishlist queries to ensure cache is updated
81
+ queryClient.invalidateQueries({ queryKey: wishlistKeys.all })
82
+ // Also invalidate specific check queries for all products
83
+ queryClient.invalidateQueries({ queryKey: ['wishlist', 'check'] })
84
+ },
85
+ })
86
+ }
87
+
88
+ /**
89
+ * Hook for removing a product from the wishlist by product ID.
90
+ */
91
+ export function useRemoveProductFromWishlist() {
92
+ const queryClient = useQueryClient()
93
+
94
+ return useMutation({
95
+ mutationFn: (productId: string) => {
96
+ return removeProductFromWishlist(productId)
97
+ },
98
+ onSuccess: () => {
99
+ // Invalidate all wishlist queries to ensure cache is updated
100
+ queryClient.invalidateQueries({ queryKey: wishlistKeys.all })
101
+ // Also invalidate specific check queries for all products
102
+ queryClient.invalidateQueries({ queryKey: ['wishlist', 'check'] })
103
+ },
104
+ })
105
+ }
106
+
107
+ /**
108
+ * Hook for getting wishlist count.
109
+ * Authentication-gated only - returns 0 for unauthenticated users.
110
+ */
111
+ export function useWishlistCount() {
112
+ const backendQuery = useQuery({
113
+ queryKey: wishlistKeys.list(),
114
+ queryFn: () => getWishlist(),
115
+ // low — wishlist count changes frequently
116
+ ...getCacheTier('low'),
117
+ select: (data) => data.total,
118
+ })
119
+
120
+ return backendQuery
121
+ }
package/src/index.ts ADDED
@@ -0,0 +1,65 @@
1
+ /**
2
+ * @kasuvia/sdk
3
+ *
4
+ * Kasuvia SDK - Core data fetching, authentication, and API client layer
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * import { initSdk, useCart, useOrders } from '@kasuvia/sdk'
9
+ *
10
+ * // Initialize SDK
11
+ * initSdk({
12
+ * businessId: 'B-123ABC',
13
+ * storefrontApiKey: 'your-api-key',
14
+ * environment: 'production'
15
+ * })
16
+ *
17
+ * // Configure HTTP clients after initialization
18
+ * configureHttpClients()
19
+ *
20
+ * // Use in components
21
+ * function Cart() {
22
+ * const { cart, addToCart } = useCart()
23
+ * // ...
24
+ * }
25
+ * ```
26
+ */
27
+
28
+ // Configuration
29
+ export { initSdk, getRuntimeConfig, getBackendUrl, isInitialized, resetConfig } from './config'
30
+ export type { SdkConfig } from './config'
31
+
32
+ // Client layer
33
+ export {
34
+ storeManagementAxios,
35
+ publicStoreAxios,
36
+ authAxios,
37
+ paymentAxios,
38
+ cmsAxios,
39
+ getOrCreateGuestSessionId,
40
+ getGuestSessionId,
41
+ clearGuestSessionId,
42
+ configureHttpClients,
43
+ } from './client/httpClient'
44
+ export { tokenStore, refreshToken } from './client/authClient'
45
+ export { getGraphQLClient, resetGraphQLClient } from './client/graphqlClient'
46
+
47
+ // Types
48
+ export * from './types'
49
+
50
+ // Services
51
+ export * from './services'
52
+
53
+ // React Hooks
54
+ export * from './hooks'
55
+
56
+ // Utils
57
+ export { getCacheTier, setEnvironmentMode } from './utils/cache'
58
+ export { logger, setLoggerEnvironmentMode } from './utils/logger'
59
+ export * from './utils/sanitize-url'
60
+ export * from './utils/customer'
61
+ export * from './utils/checkout'
62
+ export * from './utils/error-handler'
63
+
64
+ // App service functions
65
+ export { googleTranslate } from './services/app.service'
@@ -0,0 +1,17 @@
1
+ /**
2
+ * App Service
3
+ *
4
+ * Handles translation and other app-level utilities.
5
+ */
6
+
7
+ export async function googleTranslate(text: string, target: string, source = "en"): Promise<string> {
8
+ if (!text || target === source) return text;
9
+ try {
10
+ const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=${source}&tl=${target}&dt=t&q=${encodeURIComponent(text)}`;
11
+ const res = await fetch(url);
12
+ const data = (await res.json()) as [Array<[string, string]>, ...unknown[]];
13
+ return (data?.[0] || []).map((s) => s[0]).join("");
14
+ } catch {
15
+ return text;
16
+ }
17
+ }