@myrjfa/state 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.
Files changed (60) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +4 -0
  3. package/dist/index.d.ts +13 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +12 -0
  6. package/dist/lib/QueryProvider.d.ts +4 -0
  7. package/dist/lib/QueryProvider.d.ts.map +1 -0
  8. package/dist/lib/QueryProvider.js +10 -0
  9. package/dist/lib/actions.d.ts +141 -0
  10. package/dist/lib/actions.d.ts.map +1 -0
  11. package/dist/lib/actions.js +307 -0
  12. package/dist/lib/auth.d.ts +146 -0
  13. package/dist/lib/auth.d.ts.map +1 -0
  14. package/dist/lib/auth.js +125 -0
  15. package/dist/lib/authSessionManager.d.ts +2 -0
  16. package/dist/lib/authSessionManager.d.ts.map +1 -0
  17. package/dist/lib/authSessionManager.js +34 -0
  18. package/dist/lib/fetcher.d.ts +9 -0
  19. package/dist/lib/fetcher.d.ts.map +1 -0
  20. package/dist/lib/fetcher.js +78 -0
  21. package/dist/lib/hooks/use-mobile.d.ts +2 -0
  22. package/dist/lib/hooks/use-mobile.d.ts.map +1 -0
  23. package/dist/lib/hooks/use-mobile.js +15 -0
  24. package/dist/lib/models/application.d.ts +25 -0
  25. package/dist/lib/models/application.d.ts.map +1 -0
  26. package/dist/lib/models/application.js +130 -0
  27. package/dist/lib/models/blog.d.ts +63 -0
  28. package/dist/lib/models/blog.d.ts.map +1 -0
  29. package/dist/lib/models/blog.js +190 -0
  30. package/dist/lib/models/notfications.d.ts +26 -0
  31. package/dist/lib/models/notfications.d.ts.map +1 -0
  32. package/dist/lib/models/notfications.js +46 -0
  33. package/dist/lib/models/portfolio.d.ts +32 -0
  34. package/dist/lib/models/portfolio.d.ts.map +1 -0
  35. package/dist/lib/models/portfolio.js +66 -0
  36. package/dist/lib/models/props.d.ts +39 -0
  37. package/dist/lib/models/props.d.ts.map +1 -0
  38. package/dist/lib/models/props.js +260 -0
  39. package/dist/lib/models/review.d.ts +40 -0
  40. package/dist/lib/models/review.d.ts.map +1 -0
  41. package/dist/lib/models/review.js +72 -0
  42. package/dist/lib/models/tile.d.ts +26 -0
  43. package/dist/lib/models/tile.d.ts.map +1 -0
  44. package/dist/lib/models/tile.js +1 -0
  45. package/dist/lib/models/user.d.ts +148 -0
  46. package/dist/lib/models/user.d.ts.map +1 -0
  47. package/dist/lib/models/user.js +87 -0
  48. package/dist/lib/models/volunteerJob.d.ts +377 -0
  49. package/dist/lib/models/volunteerJob.d.ts.map +1 -0
  50. package/dist/lib/models/volunteerJob.js +149 -0
  51. package/dist/lib/severActions.d.ts +3 -0
  52. package/dist/lib/severActions.d.ts.map +1 -0
  53. package/dist/lib/severActions.js +19 -0
  54. package/dist/lib/userAtom.d.ts +191 -0
  55. package/dist/lib/userAtom.d.ts.map +1 -0
  56. package/dist/lib/userAtom.js +127 -0
  57. package/dist/lib/utils.d.ts +53 -0
  58. package/dist/lib/utils.d.ts.map +1 -0
  59. package/dist/lib/utils.js +182 -0
  60. package/package.json +51 -0
@@ -0,0 +1,191 @@
1
+ export declare const storageInitializedAtom: import("jotai").PrimitiveAtom<boolean> & {
2
+ init: boolean;
3
+ };
4
+ export declare const userAtom: import("jotai").WritableAtom<{
5
+ username: string;
6
+ firstName: string;
7
+ lastName: string;
8
+ email: string;
9
+ password: string;
10
+ phoneNumber: string;
11
+ gender: "male" | "female" | "other";
12
+ rating: number;
13
+ ratingCount: number;
14
+ reviewCount: number;
15
+ joinedDate: Date;
16
+ isActive: boolean;
17
+ lastActive: Date;
18
+ verifiedEmail: boolean;
19
+ verifiedMobile: boolean;
20
+ role: "host" | "user" | "admin" | "hr" | "sales";
21
+ location?: string | undefined;
22
+ pinCode?: string | undefined;
23
+ dob?: Date | undefined;
24
+ profilePic?: string | undefined;
25
+ blogsCount?: number | undefined;
26
+ userBio?: string | undefined;
27
+ userIdCard?: string | undefined;
28
+ watchHistory?: string[] | undefined;
29
+ accessToken?: string | undefined;
30
+ refreshToken?: string | undefined;
31
+ verifiedIdCard?: boolean | undefined;
32
+ socialLinks?: Record<string, string> | undefined;
33
+ userResume?: string | undefined;
34
+ skills?: {
35
+ skill: string;
36
+ skillExpertise: number;
37
+ }[] | undefined;
38
+ wishlist?: string[] | undefined;
39
+ favBlogs?: string[] | undefined;
40
+ favPackages?: string[] | undefined;
41
+ id?: string | undefined;
42
+ selfie?: string | undefined;
43
+ selfieAction?: string | undefined;
44
+ jobCount?: number | undefined;
45
+ travelPackageCount?: number | undefined;
46
+ gstNumber?: string | undefined;
47
+ panCard?: string | undefined;
48
+ verifiedBusiness?: "true" | "false" | "inProgress" | undefined;
49
+ } | null, [{
50
+ username: string;
51
+ firstName: string;
52
+ lastName: string;
53
+ email: string;
54
+ password: string;
55
+ phoneNumber: string;
56
+ gender: "male" | "female" | "other";
57
+ rating: number;
58
+ ratingCount: number;
59
+ reviewCount: number;
60
+ joinedDate: Date;
61
+ isActive: boolean;
62
+ lastActive: Date;
63
+ verifiedEmail: boolean;
64
+ verifiedMobile: boolean;
65
+ role: "host" | "user" | "admin" | "hr" | "sales";
66
+ location?: string | undefined;
67
+ pinCode?: string | undefined;
68
+ dob?: Date | undefined;
69
+ profilePic?: string | undefined;
70
+ blogsCount?: number | undefined;
71
+ userBio?: string | undefined;
72
+ userIdCard?: string | undefined;
73
+ watchHistory?: string[] | undefined;
74
+ accessToken?: string | undefined;
75
+ refreshToken?: string | undefined;
76
+ verifiedIdCard?: boolean | undefined;
77
+ socialLinks?: Record<string, string> | undefined;
78
+ userResume?: string | undefined;
79
+ skills?: {
80
+ skill: string;
81
+ skillExpertise: number;
82
+ }[] | undefined;
83
+ wishlist?: string[] | undefined;
84
+ favBlogs?: string[] | undefined;
85
+ favPackages?: string[] | undefined;
86
+ id?: string | undefined;
87
+ selfie?: string | undefined;
88
+ selfieAction?: string | undefined;
89
+ jobCount?: number | undefined;
90
+ travelPackageCount?: number | undefined;
91
+ gstNumber?: string | undefined;
92
+ panCard?: string | undefined;
93
+ verifiedBusiness?: "true" | "false" | "inProgress" | undefined;
94
+ } | typeof import("jotai/utils").RESET | ((prev: {
95
+ username: string;
96
+ firstName: string;
97
+ lastName: string;
98
+ email: string;
99
+ password: string;
100
+ phoneNumber: string;
101
+ gender: "male" | "female" | "other";
102
+ rating: number;
103
+ ratingCount: number;
104
+ reviewCount: number;
105
+ joinedDate: Date;
106
+ isActive: boolean;
107
+ lastActive: Date;
108
+ verifiedEmail: boolean;
109
+ verifiedMobile: boolean;
110
+ role: "host" | "user" | "admin" | "hr" | "sales";
111
+ location?: string | undefined;
112
+ pinCode?: string | undefined;
113
+ dob?: Date | undefined;
114
+ profilePic?: string | undefined;
115
+ blogsCount?: number | undefined;
116
+ userBio?: string | undefined;
117
+ userIdCard?: string | undefined;
118
+ watchHistory?: string[] | undefined;
119
+ accessToken?: string | undefined;
120
+ refreshToken?: string | undefined;
121
+ verifiedIdCard?: boolean | undefined;
122
+ socialLinks?: Record<string, string> | undefined;
123
+ userResume?: string | undefined;
124
+ skills?: {
125
+ skill: string;
126
+ skillExpertise: number;
127
+ }[] | undefined;
128
+ wishlist?: string[] | undefined;
129
+ favBlogs?: string[] | undefined;
130
+ favPackages?: string[] | undefined;
131
+ id?: string | undefined;
132
+ selfie?: string | undefined;
133
+ selfieAction?: string | undefined;
134
+ jobCount?: number | undefined;
135
+ travelPackageCount?: number | undefined;
136
+ gstNumber?: string | undefined;
137
+ panCard?: string | undefined;
138
+ verifiedBusiness?: "true" | "false" | "inProgress" | undefined;
139
+ } | null) => {
140
+ username: string;
141
+ firstName: string;
142
+ lastName: string;
143
+ email: string;
144
+ password: string;
145
+ phoneNumber: string;
146
+ gender: "male" | "female" | "other";
147
+ rating: number;
148
+ ratingCount: number;
149
+ reviewCount: number;
150
+ joinedDate: Date;
151
+ isActive: boolean;
152
+ lastActive: Date;
153
+ verifiedEmail: boolean;
154
+ verifiedMobile: boolean;
155
+ role: "host" | "user" | "admin" | "hr" | "sales";
156
+ location?: string | undefined;
157
+ pinCode?: string | undefined;
158
+ dob?: Date | undefined;
159
+ profilePic?: string | undefined;
160
+ blogsCount?: number | undefined;
161
+ userBio?: string | undefined;
162
+ userIdCard?: string | undefined;
163
+ watchHistory?: string[] | undefined;
164
+ accessToken?: string | undefined;
165
+ refreshToken?: string | undefined;
166
+ verifiedIdCard?: boolean | undefined;
167
+ socialLinks?: Record<string, string> | undefined;
168
+ userResume?: string | undefined;
169
+ skills?: {
170
+ skill: string;
171
+ skillExpertise: number;
172
+ }[] | undefined;
173
+ wishlist?: string[] | undefined;
174
+ favBlogs?: string[] | undefined;
175
+ favPackages?: string[] | undefined;
176
+ id?: string | undefined;
177
+ selfie?: string | undefined;
178
+ selfieAction?: string | undefined;
179
+ jobCount?: number | undefined;
180
+ travelPackageCount?: number | undefined;
181
+ gstNumber?: string | undefined;
182
+ panCard?: string | undefined;
183
+ verifiedBusiness?: "true" | "false" | "inProgress" | undefined;
184
+ } | typeof import("jotai/utils").RESET | null) | null], void>;
185
+ export declare const isLoggedInAtom: import("jotai").WritableAtom<boolean, [boolean | typeof import("jotai/utils").RESET | ((prev: boolean) => boolean | typeof import("jotai/utils").RESET)], void>;
186
+ export declare const userLoadingAtom: import("jotai").Atom<boolean>;
187
+ export declare const initializeAuthSessionAtom: import("jotai").WritableAtom<null, [], Promise<void>> & {
188
+ init: null;
189
+ };
190
+ export declare function resetAuthState(): void;
191
+ //# sourceMappingURL=userAtom.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"userAtom.d.ts","sourceRoot":"","sources":["../../src/lib/userAtom.ts"],"names":[],"mappings":"AAgEA,eAAO,MAAM,sBAAsB;;CAAuB,CAAC;AAG3D,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6DAQpB,CAAC;AAEF,eAAO,MAAM,cAAc,iKAA6C,CAAC;AAGzE,eAAO,MAAM,eAAe,+BAM1B,CAAC;AAGH,eAAO,MAAM,yBAAyB;;CA2CrC,CAAC;AAGF,wBAAgB,cAAc,SAS7B"}
@@ -0,0 +1,127 @@
1
+ // atoms/userAtom.ts
2
+ import CryptoJS from 'crypto-js';
3
+ import { atom, getDefaultStore } from 'jotai';
4
+ import { atomWithStorage } from 'jotai/utils';
5
+ import { getCurrentUser } from './actions';
6
+ import { validateSession } from './auth';
7
+ const STORAGE_KEY = process.env.NEXT_PUBLIC_USER_KEY;
8
+ const LOGIN_KEY = process.env.NEXT_PUBLIC_LOGIN_KEY;
9
+ const SECRET_KEY = process.env.NEXT_PUBLIC_RACE;
10
+ const EXPIRY_DAYS = 90;
11
+ const EXPIRY_MS = EXPIRY_DAYS * 24 * 60 * 60 * 1000;
12
+ const store = getDefaultStore();
13
+ // 🔐 Encryption
14
+ const encrypt = (data) => CryptoJS.AES.encrypt(JSON.stringify(data), SECRET_KEY).toString();
15
+ const decrypt = (cipher) => {
16
+ try {
17
+ const bytes = CryptoJS.AES.decrypt(cipher, SECRET_KEY);
18
+ const decrypted = bytes.toString(CryptoJS.enc.Utf8);
19
+ return JSON.parse(decrypted);
20
+ }
21
+ catch (e) {
22
+ console.warn('Decryption failed:', e);
23
+ return null;
24
+ }
25
+ };
26
+ // 🧠 Wrap expiry in localStorage
27
+ const getWithExpiry = (key) => {
28
+ if (typeof window === 'undefined')
29
+ return null;
30
+ try {
31
+ const raw = localStorage.getItem(key);
32
+ if (!raw)
33
+ return null;
34
+ const data = decrypt(raw);
35
+ if (!data)
36
+ return null;
37
+ const { value, expiry } = data;
38
+ if (Date.now() > expiry) {
39
+ resetAuthState();
40
+ return null;
41
+ }
42
+ return value;
43
+ }
44
+ catch (e) {
45
+ console.warn('Invalid user data:', e);
46
+ resetAuthState();
47
+ return null;
48
+ }
49
+ };
50
+ const setWithExpiry = (key, value) => {
51
+ if (typeof window === 'undefined')
52
+ return null;
53
+ if (!value)
54
+ return localStorage.removeItem(key);
55
+ const expiry = Date.now() + EXPIRY_MS;
56
+ const encrypted = encrypt({ value, expiry });
57
+ localStorage.setItem(key, encrypted);
58
+ };
59
+ // Storage initialization atom - tracks if we've tried to load from storage
60
+ export const storageInitializedAtom = atom(false);
61
+ // 🍱 Base storage atom (persisted)
62
+ export const userAtom = atomWithStorage(STORAGE_KEY, null, {
63
+ getItem: (key) => getWithExpiry(key),
64
+ setItem: setWithExpiry,
65
+ removeItem: (key) => localStorage.removeItem(key),
66
+ });
67
+ export const isLoggedInAtom = atomWithStorage(LOGIN_KEY, false);
68
+ // 🕒 Tracks if user is loading
69
+ export const userLoadingAtom = atom((get) => {
70
+ // The issue was here - we need to properly track initialization state
71
+ const initialized = get(storageInitializedAtom);
72
+ // We're loading if we haven't initialized storage yet
73
+ return !initialized;
74
+ });
75
+ // Initialize storage atom effect
76
+ export const initializeAuthSessionAtom = atom(null, async (get, set) => {
77
+ // Only run in browser
78
+ if (typeof window === 'undefined')
79
+ return;
80
+ // 1. Validate session (lightweight, no DB)
81
+ const session = await validateSession();
82
+ if (session.success) {
83
+ console.log(session.message);
84
+ }
85
+ else {
86
+ resetAuthState();
87
+ set(storageInitializedAtom, true);
88
+ return;
89
+ }
90
+ // 2. Try cache (localStorage)
91
+ const userData = getWithExpiry(STORAGE_KEY);
92
+ if (userData) {
93
+ set(userAtom, userData);
94
+ set(isLoggedInAtom, true);
95
+ set(storageInitializedAtom, true);
96
+ return;
97
+ }
98
+ // 3. If cache miss: fetch user from backend using session cookie
99
+ try {
100
+ // This endpoint can hit DB or another cache as needed
101
+ const remoteUser = await getCurrentUser();
102
+ if (remoteUser) {
103
+ set(userAtom, remoteUser);
104
+ set(isLoggedInAtom, true);
105
+ setWithExpiry(STORAGE_KEY, remoteUser); // Write to local cache for next time
106
+ }
107
+ else {
108
+ resetAuthState();
109
+ }
110
+ }
111
+ catch {
112
+ resetAuthState();
113
+ }
114
+ finally {
115
+ set(storageInitializedAtom, true);
116
+ }
117
+ });
118
+ export function resetAuthState() {
119
+ if (typeof window === 'undefined') {
120
+ console.warn('resetAuthState called on server — skipped');
121
+ return;
122
+ }
123
+ localStorage.removeItem(STORAGE_KEY);
124
+ localStorage.removeItem(LOGIN_KEY);
125
+ store.set(userAtom, null);
126
+ store.set(isLoggedInAtom, false);
127
+ }
@@ -0,0 +1,53 @@
1
+ import { type ClassValue } from "clsx";
2
+ import { User } from "./models/user";
3
+ export declare const MAX_IMAGE_SIZE_MB = 5;
4
+ export declare const MAX_PDF_SIZE_MB = 10;
5
+ export declare const validRoles: string[];
6
+ export declare function cn(...inputs: ClassValue[]): string;
7
+ export declare function retrieveCity(location: string): string;
8
+ export declare function retrieveState(location: string): string;
9
+ export declare const safeNumber: (val: string | undefined) => number;
10
+ export declare function formatDate(date: string | Date): string;
11
+ export declare function formatRelativeTime(date: string | Date): string;
12
+ export declare function getStatusColor(status: string): string;
13
+ export declare function getProgressValue(status: string): number;
14
+ export declare const truncateText: (text: string, limit: number) => string;
15
+ export declare const numToStr: (input: number) => string;
16
+ export declare const handleEmail: (email: string, subject: string, body: string) => string;
17
+ export declare function formatDuration(start: number, end: number): string;
18
+ export declare function truncateDecimal(num: number, limit: number): number;
19
+ export declare function sanitize<T>(input: any, keys: (keyof T)[]): T;
20
+ export declare const formatTimestamp: (date: Date) => string;
21
+ export declare function extractHtmlContentDescription(html: string, limit?: number): string;
22
+ export declare function createMetadata({ title, description, path, }: {
23
+ title?: string;
24
+ description?: string;
25
+ path?: string;
26
+ }): {
27
+ title: string;
28
+ description: string;
29
+ alternates: {
30
+ canonical: string;
31
+ };
32
+ openGraph: {
33
+ title: string;
34
+ description: string;
35
+ url: string;
36
+ siteName: string;
37
+ images: {
38
+ url: string;
39
+ width: number;
40
+ height: number;
41
+ }[];
42
+ };
43
+ twitter: {
44
+ card: string;
45
+ title: string;
46
+ description: string;
47
+ images: string[];
48
+ };
49
+ };
50
+ export declare function convertEscapesToHTML(raw: string): string;
51
+ export declare const expertiseLevels: string[];
52
+ export declare function incompleteMVP(user?: User): string[];
53
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,UAAU,EAAQ,MAAM,MAAM,CAAC;AAG7C,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAGrC,eAAO,MAAM,iBAAiB,IAAI,CAAC;AAEnC,eAAO,MAAM,eAAe,KAAK,CAAC;AAElC,eAAO,MAAM,UAAU,UAA2C,CAAC;AAEnE,wBAAgB,EAAE,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,UAEzC;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGrD;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGtD;AAED,eAAO,MAAM,UAAU,GAAI,KAAK,MAAM,GAAG,SAAS,WAGjD,CAAC;AAEF,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAGtD;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAG9D;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAerD;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAWvD;AAGD,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,EAAE,OAAO,MAAM,WACO,CAAC;AAEhE,eAAO,MAAM,QAAQ,GAAI,OAAO,MAAM,WAKrC,CAAA;AAED,eAAO,MAAM,WAAW,GAAI,OAAO,MAAM,EAAE,SAAS,MAAM,EAAE,MAAM,MAAM,WACE,CAAC;AAE3E,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAcjE;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAElE;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,CAM5D;AAED,eAAO,MAAM,eAAe,GAAI,MAAM,IAAI,WAczC,CAAC;AAEF,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,SAAM,UAItE;AAGD,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,WAAW,EACX,IAAI,GACL,EAAE;IACD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;;;;;;;;;;;;;;;;;;;;;;;EA8BA;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAaxD;AAED,eAAO,MAAM,eAAe,UAA0D,CAAC;AAEvF,wBAAgB,aAAa,CAAC,IAAI,CAAC,EAAE,IAAI,GAAG,MAAM,EAAE,CAanD"}
@@ -0,0 +1,182 @@
1
+ import { load } from 'cheerio';
2
+ import { clsx } from "clsx";
3
+ import { format, formatDistanceToNow } from 'date-fns';
4
+ import he from "he";
5
+ import { twMerge } from "tailwind-merge";
6
+ export const MAX_IMAGE_SIZE_MB = 5;
7
+ export const MAX_PDF_SIZE_MB = 10;
8
+ export const validRoles = ["host", "user", "admin", "hr", "sales"];
9
+ export function cn(...inputs) {
10
+ return twMerge(clsx(inputs));
11
+ }
12
+ export function retrieveCity(location) {
13
+ const parts = location.split(", ");
14
+ return parts[0]?.toLowerCase() || '';
15
+ }
16
+ ;
17
+ export function retrieveState(location) {
18
+ const parts = location.split(", ");
19
+ return (parts.length > 1 ? parts[1]?.trim().toLowerCase() : parts[0]?.trim().toLowerCase()) || '';
20
+ }
21
+ ;
22
+ export const safeNumber = (val) => {
23
+ const num = Number(val);
24
+ return isNaN(num) ? 0 : num;
25
+ };
26
+ export function formatDate(date) {
27
+ const dateObj = typeof date === 'string' ? new Date(date) : date;
28
+ return format(dateObj, 'MMM d, yyyy');
29
+ }
30
+ export function formatRelativeTime(date) {
31
+ const dateObj = typeof date === 'string' ? new Date(date) : date;
32
+ return formatDistanceToNow(dateObj, { addSuffix: true });
33
+ }
34
+ export function getStatusColor(status) {
35
+ const statusColors = {
36
+ draft: 'text-gray-500 bg-gray-100',
37
+ posted: 'text-blue-500 bg-blue-100',
38
+ archived: 'text-gray-500 bg-gray-100',
39
+ pending: 'text-amber-500 bg-amber-100',
40
+ approved: 'text-green-500 bg-green-100',
41
+ denied: 'text-red-500 bg-red-100',
42
+ completed: 'text-purple-500 bg-purple-100',
43
+ withdrawn: 'text-red-500 bg-red-100',
44
+ open: 'text-green-500 bg-green-100',
45
+ closed: 'text-red-500 bg-red-100',
46
+ };
47
+ return statusColors[status] || 'text-gray-500 bg-gray-100';
48
+ }
49
+ export function getProgressValue(status) {
50
+ const progressValues = {
51
+ pending: 25,
52
+ adminApproved: 50,
53
+ approved: 75,
54
+ completed: 100,
55
+ withdrawn: 0,
56
+ denied: 0,
57
+ };
58
+ return progressValues[status] || 0;
59
+ }
60
+ // Helper functions
61
+ export const truncateText = (text, limit) => text.length > limit ? text.substring(0, limit) + "..." : text;
62
+ export const numToStr = (input) => {
63
+ if (input > 1000000000)
64
+ return `${input / 1000000}B+`;
65
+ if (input > 1000000)
66
+ return `${input / 1000000}M+`;
67
+ if (input > 1000)
68
+ return `${input / 1000}K+`;
69
+ return `${input}`;
70
+ };
71
+ export const handleEmail = (email, subject, body) => window.location.href = `mailto:${email}?subject=${subject}&body=${body}`;
72
+ export function formatDuration(start, end) {
73
+ // const diff = end - start;
74
+ // if (diff < 14) {
75
+ // return `${start} - ${end} days`;
76
+ // } else if (diff < 60) {
77
+ const weeksStart = Math.floor(start);
78
+ const weeksEnd = Math.floor(end);
79
+ return `${weeksStart} - ${weeksEnd} weeks`;
80
+ // } else {
81
+ // const monthsStart = Math.floor(start / 30);
82
+ // const monthsEnd = Math.floor(end / 30);
83
+ // return `${monthsStart} - ${monthsEnd} months`;
84
+ // }
85
+ }
86
+ export function truncateDecimal(num, limit) {
87
+ return Math.round(num * Math.pow(10, limit)) / Math.pow(10, limit);
88
+ }
89
+ export function sanitize(input, keys) {
90
+ const result = {};
91
+ keys.forEach((key) => {
92
+ if (key in input)
93
+ result[key] = input[key];
94
+ });
95
+ return result;
96
+ }
97
+ export const formatTimestamp = (date) => {
98
+ const now = new Date();
99
+ const diffMinutes = Math.floor((now.getTime() - date.getTime()) / (1000 * 60));
100
+ if (diffMinutes < 1)
101
+ return "Just now";
102
+ if (diffMinutes < 60)
103
+ return `${diffMinutes}m ago`;
104
+ const diffHours = Math.floor(diffMinutes / 60);
105
+ if (diffHours < 24)
106
+ return `${diffHours}h ago`;
107
+ const diffDays = Math.floor(diffHours / 24);
108
+ if (diffDays < 7)
109
+ return `${diffDays}d ago`;
110
+ return format(date, "MMM d");
111
+ };
112
+ export function extractHtmlContentDescription(html, limit = 150) {
113
+ const $ = load(html || '');
114
+ const firstParagraph = $('p').first().text().trim();
115
+ return truncateText(firstParagraph, limit);
116
+ }
117
+ // seo
118
+ export function createMetadata({ title, description, path, }) {
119
+ const baseUrl = "https://www.trippeaze.com";
120
+ const url = path ? `${baseUrl}${path}` : baseUrl;
121
+ return {
122
+ title: title ?? "Trippeaze",
123
+ description: description ?? "Discover and volunteer globally with Trippeaze",
124
+ alternates: {
125
+ canonical: url,
126
+ },
127
+ openGraph: {
128
+ title: title ?? "Trippeaze",
129
+ description: description ?? "Discover and volunteer globally with Trippeaze",
130
+ url,
131
+ siteName: "Trippeaze",
132
+ images: [
133
+ {
134
+ url: "/og-default.jpg",
135
+ width: 1200,
136
+ height: 630,
137
+ },
138
+ ],
139
+ },
140
+ twitter: {
141
+ card: "summary_large_image",
142
+ title: title ?? "Trippeaze",
143
+ description: description ?? "Discover and volunteer globally with Trippeaze",
144
+ images: ["/og-default.jpg"],
145
+ },
146
+ };
147
+ }
148
+ export function convertEscapesToHTML(raw) {
149
+ const decoded = he.decode(raw.trim());
150
+ return decoded
151
+ // Horizontal rules (like markdown's ---)
152
+ .replace(/(?:\r?\n)?-{3,}(?:\r?\n)?/g, "<hr />")
153
+ // Paragraph breaks (2+ newlines)
154
+ .replace(/(\r\n|\n|\r){2,}/g, "</p><p>")
155
+ // Line breaks
156
+ .replace(/(\r\n|\n|\r)/g, "<br />")
157
+ // Wrap the whole thing in <p> to begin with
158
+ .replace(/^/, "<p>")
159
+ .replace(/$/, "</p>");
160
+ }
161
+ export const expertiseLevels = ["none", "basic", "intermediate", "advanced", "expert"];
162
+ export function incompleteMVP(user) {
163
+ const incompletions = [];
164
+ if (user) {
165
+ if (!user.verifiedIdCard)
166
+ incompletions.push("Verify your id card");
167
+ if (!user.skills || user.skills.length == 0)
168
+ incompletions.push("Your skills");
169
+ if (!user.userResume)
170
+ incompletions.push("Profile Default Resume");
171
+ if (!user.location)
172
+ incompletions.push("your location");
173
+ if (!user.pinCode)
174
+ incompletions.push("PinCode");
175
+ if (!user.profilePic)
176
+ incompletions.push("Proile Pic");
177
+ }
178
+ else {
179
+ incompletions.push("Please login first");
180
+ }
181
+ return incompletions;
182
+ }
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@myrjfa/state",
3
+ "version": "1.0.0",
4
+ "description": "Shared state management for myrjfa apps",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "dependencies": {
11
+ "@tanstack/react-query": "^5.89.0",
12
+ "cheerio": "^1.0.0",
13
+ "clsx": "^2.1.1",
14
+ "crypto-js": "^4.2.0",
15
+ "he": "^1.2.0",
16
+ "tailwind-merge": "^3.0.2"
17
+ },
18
+ "peerDependencies": {
19
+ "date-fns": "*",
20
+ "jotai": "*",
21
+ "lucide-react": "*",
22
+ "next": "*",
23
+ "react": "*",
24
+ "react-dom": "*",
25
+ "zod": "*"
26
+ },
27
+ "devDependencies": {
28
+ "@types/crypto-js": "^4.2.2",
29
+ "@types/he": "^1.2.3",
30
+ "@types/node": "^22.13.10",
31
+ "@types/react": "^19.0.10",
32
+ "date-fns": "^3.0.0",
33
+ "jotai": "^2.12.2",
34
+ "lucide-react": "^0.482.0",
35
+ "typescript": "^5.8.2",
36
+ "zod": "^3.24.2"
37
+ },
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/Trippeaze/frontend-state.git"
41
+ },
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "author": "",
46
+ "license": "MIT",
47
+ "scripts": {
48
+ "build": "tsc",
49
+ "dev": "tsc --watch"
50
+ }
51
+ }