@onairos/react-native 3.1.16 → 3.1.18
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 +404 -0
- package/lib/commonjs/assets/images/Checkbox.svg +3 -3
- package/lib/commonjs/assets/images/EnochE.svg +19 -19
- package/lib/commonjs/assets/images/Personalityprofile.svg +3 -3
- package/lib/commonjs/assets/images/Personalitytraits.svg +3 -3
- package/lib/commonjs/assets/images/Userpreferences.svg +3 -3
- package/lib/commonjs/assets/images/arrow.svg +20 -20
- package/lib/commonjs/assets/images/basicproficon.svg +43 -43
- package/lib/commonjs/assets/images/basicprofile.svg +3 -3
- package/lib/commonjs/assets/images/checkmark.svg +4 -4
- package/lib/commonjs/assets/images/contentanalysis.svg +3 -3
- package/lib/commonjs/assets/images/contenticon.svg +23 -23
- package/lib/commonjs/assets/images/personalityicon.svg +18 -18
- package/lib/commonjs/assets/images/x-close.svg +3 -3
- package/lib/commonjs/components/ModalSheet.js +8 -2
- package/lib/commonjs/components/ModalSheet.js.map +1 -1
- package/lib/commonjs/components/OnairosButton.js +290 -0
- package/lib/commonjs/components/OnairosButton.js.map +1 -0
- package/lib/commonjs/components/OnairosSignInButton.js +32 -8
- package/lib/commonjs/components/OnairosSignInButton.js.map +1 -1
- package/lib/commonjs/components/UniversalOnboarding.js +4 -4
- package/lib/commonjs/components/WelcomeScreen.js +29 -13
- package/lib/commonjs/components/WelcomeScreen.js.map +1 -1
- package/lib/commonjs/config/api.js +2 -2
- package/lib/commonjs/hooks/useConnections.js +6 -6
- package/lib/commonjs/hooks/useUserConnections.js +10 -10
- package/lib/commonjs/index.js +13 -6
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/services/apiClient.js +35 -35
- package/lib/commonjs/services/apiKeyService.js +99 -99
- package/lib/commonjs/services/authService.js +82 -82
- package/lib/commonjs/services/biometricPinService.js +10 -10
- package/lib/commonjs/services/connectedAccountsService.js +32 -32
- package/lib/commonjs/services/googleAuthService.js +15 -15
- package/lib/commonjs/services/imageCompressionService.js +15 -15
- package/lib/commonjs/services/jwtStorageService.js +59 -59
- package/lib/commonjs/services/mobileTrainingService.js +14 -14
- package/lib/commonjs/services/pinEncryptionService.js +10 -10
- package/lib/commonjs/services/pinStorageUtils.js +15 -15
- package/lib/commonjs/services/platformAuthService.js +47 -47
- package/lib/commonjs/services/storageService.js +31 -31
- package/lib/commonjs/services/trainingApiHelpers.js +33 -33
- package/lib/commonjs/services/userConnectionsService.js +24 -24
- package/lib/commonjs/utils/Portal.js +4 -4
- package/lib/commonjs/utils/api.js +24 -24
- package/lib/commonjs/utils/auth.js +18 -18
- package/lib/commonjs/utils/crypto.js +13 -13
- package/lib/commonjs/utils/encryption.js +12 -12
- package/lib/commonjs/utils/eventUtils.js +52 -52
- package/lib/commonjs/utils/programmaticFlow.js +16 -16
- package/lib/commonjs/utils/retryHelper.js +27 -27
- package/lib/module/assets/images/Checkbox.svg +3 -3
- package/lib/module/assets/images/EnochE.svg +19 -19
- package/lib/module/assets/images/Personalityprofile.svg +3 -3
- package/lib/module/assets/images/Personalitytraits.svg +3 -3
- package/lib/module/assets/images/Userpreferences.svg +3 -3
- package/lib/module/assets/images/arrow.svg +20 -20
- package/lib/module/assets/images/basicproficon.svg +43 -43
- package/lib/module/assets/images/basicprofile.svg +3 -3
- package/lib/module/assets/images/checkmark.svg +4 -4
- package/lib/module/assets/images/contentanalysis.svg +3 -3
- package/lib/module/assets/images/contenticon.svg +23 -23
- package/lib/module/assets/images/personalityicon.svg +18 -18
- package/lib/module/assets/images/x-close.svg +3 -3
- package/lib/module/components/ModalSheet.js +7 -2
- package/lib/module/components/ModalSheet.js.map +1 -1
- package/lib/module/components/OnairosButton.js +282 -0
- package/lib/module/components/OnairosButton.js.map +1 -0
- package/lib/module/components/OnairosSignInButton.js +32 -8
- package/lib/module/components/OnairosSignInButton.js.map +1 -1
- package/lib/module/components/UniversalOnboarding.js +4 -4
- package/lib/module/components/WelcomeScreen.js +25 -10
- package/lib/module/components/WelcomeScreen.js.map +1 -1
- package/lib/module/config/api.js +2 -2
- package/lib/module/hooks/useConnections.js +6 -6
- package/lib/module/hooks/useUserConnections.js +10 -10
- package/lib/module/index.js +11 -11
- package/lib/module/index.js.map +1 -1
- package/lib/module/services/apiClient.js +35 -35
- package/lib/module/services/apiKeyService.js +99 -99
- package/lib/module/services/authService.js +82 -82
- package/lib/module/services/biometricPinService.js +10 -10
- package/lib/module/services/connectedAccountsService.js +32 -32
- package/lib/module/services/googleAuthService.js +15 -15
- package/lib/module/services/imageCompressionService.js +15 -15
- package/lib/module/services/jwtStorageService.js +59 -59
- package/lib/module/services/mobileTrainingService.js +14 -14
- package/lib/module/services/pinEncryptionService.js +10 -10
- package/lib/module/services/pinStorageUtils.js +15 -15
- package/lib/module/services/platformAuthService.js +47 -47
- package/lib/module/services/storageService.js +31 -31
- package/lib/module/services/trainingApiHelpers.js +33 -33
- package/lib/module/services/userConnectionsService.js +24 -24
- package/lib/module/utils/Portal.js +4 -4
- package/lib/module/utils/api.js +24 -24
- package/lib/module/utils/auth.js +18 -18
- package/lib/module/utils/crypto.js +13 -13
- package/lib/module/utils/encryption.js +12 -12
- package/lib/module/utils/eventUtils.js +52 -52
- package/lib/module/utils/programmaticFlow.js +16 -16
- package/lib/module/utils/retryHelper.js +27 -27
- package/lib/typescript/components/ModalSheet.d.ts.map +1 -1
- package/lib/typescript/components/OnairosButton.d.ts +37 -0
- package/lib/typescript/components/OnairosButton.d.ts.map +1 -0
- package/lib/typescript/components/OnairosSignInButton.d.ts.map +1 -1
- package/lib/typescript/components/WelcomeScreen.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +3 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/package.json +145 -163
- package/src/api/index.ts +151 -151
- package/src/assets/images/Checkbox.svg +3 -3
- package/src/assets/images/EnochE.svg +19 -19
- package/src/assets/images/Personalityprofile.svg +3 -3
- package/src/assets/images/Personalitytraits.svg +3 -3
- package/src/assets/images/Userpreferences.svg +3 -3
- package/src/assets/images/arrow.svg +20 -20
- package/src/assets/images/basicproficon.svg +43 -43
- package/src/assets/images/basicprofile.svg +3 -3
- package/src/assets/images/checkmark.svg +4 -4
- package/src/assets/images/contentanalysis.svg +3 -3
- package/src/assets/images/contenticon.svg +23 -23
- package/src/assets/images/personalityicon.svg +18 -18
- package/src/assets/images/x-close.svg +3 -3
- package/src/components/BodyText.tsx +33 -33
- package/src/components/BrandMark.tsx +62 -62
- package/src/components/CodeInput.tsx +32 -32
- package/src/components/DataRequestScreen.tsx +355 -355
- package/src/components/EmailInput.tsx +31 -31
- package/src/components/EmailVerificationModal.tsx +363 -363
- package/src/components/ExistingUserDataConfirmation.tsx +506 -506
- package/src/components/GoogleButton.tsx +55 -55
- package/src/components/HeadingGroup.tsx +49 -49
- package/src/components/ModalHeader.tsx +125 -125
- package/src/components/ModalSheet.tsx +59 -57
- package/src/components/Onairos.tsx +422 -422
- package/src/components/OnairosButton.tsx +339 -0
- package/src/components/OnairosSignInButton.tsx +31 -9
- package/src/components/Overlay.tsx +506 -506
- package/src/components/PersonaImage.tsx +79 -79
- package/src/components/PersonaLoadingScreen.tsx +201 -201
- package/src/components/PersonalizationConsentScreen.tsx +410 -410
- package/src/components/PinCreationScreen.tsx +492 -492
- package/src/components/PinInput.tsx +555 -555
- package/src/components/PlatformConnectorsStep.tsx +891 -891
- package/src/components/PlatformList.tsx +144 -144
- package/src/components/PlatformToggle.tsx +226 -226
- package/src/components/PrimaryButton.tsx +213 -213
- package/src/components/SignInMatchAnimation.tsx +225 -225
- package/src/components/SignInStep.tsx +217 -217
- package/src/components/TrainingModal.tsx +1047 -1047
- package/src/components/UniversalOnboarding.tsx +2887 -2887
- package/src/components/VerificationStep.tsx +198 -198
- package/src/components/WelcomeScreen.tsx +490 -473
- package/src/components/icons/Basicproficon.tsx +30 -30
- package/src/components/icons/Basicprofile.tsx +17 -17
- package/src/components/icons/Checkbox.tsx +17 -17
- package/src/components/icons/Checkmark.tsx +24 -24
- package/src/components/icons/Contentanalysis.tsx +17 -17
- package/src/components/icons/Contenticon.tsx +30 -30
- package/src/components/icons/EnochE.tsx +39 -39
- package/src/components/icons/Personalityicon.tsx +22 -22
- package/src/components/icons/Personalityprofile.tsx +17 -17
- package/src/components/icons/Personalitytraits.tsx +17 -17
- package/src/components/icons/Userpreferences.tsx +17 -17
- package/src/components/icons/index.ts +12 -12
- package/src/components/onboarding/OAuthWebView.tsx +232 -232
- package/src/config/api.ts +25 -25
- package/src/context/AuthContext.tsx +393 -393
- package/src/hooks/useConnectedAccounts.ts +138 -138
- package/src/hooks/useConnections.ts +161 -161
- package/src/hooks/useCredentials.ts +174 -174
- package/src/hooks/useUserConnections.ts +165 -165
- package/src/index.js +14 -0
- package/src/index.ts +99 -96
- package/src/services/apiClient.ts +336 -336
- package/src/services/apiKeyService.ts +919 -919
- package/src/services/authService.ts +1008 -1008
- package/src/services/biometricPinService.ts +192 -192
- package/src/services/connectedAccountsService.ts +289 -289
- package/src/services/googleAuthService.ts +279 -279
- package/src/services/imageCompressionService.ts +302 -302
- package/src/services/jwtStorageService.ts +256 -256
- package/src/services/mobileTrainingService.ts +203 -203
- package/src/services/pinEncryptionService.ts +75 -75
- package/src/services/pinStorageUtils.ts +96 -96
- package/src/services/platformAuthService.ts +1346 -1346
- package/src/services/storageService.ts +451 -451
- package/src/services/trainingApiHelpers.ts +66 -66
- package/src/services/userConnectionsService.ts +556 -556
- package/src/services/youtubeMigrationService.ts +453 -453
- package/src/theme/index.ts +239 -239
- package/src/types/ambient.d.ts +28 -28
- package/src/types/index.ts +265 -265
- package/src/types/node-fix.d.ts +18 -18
- package/src/types/node-override.d.ts +23 -23
- package/src/types/opacity.d.ts +15 -15
- package/src/types/types.d.ts +17 -17
- package/src/utils/Portal.tsx +82 -82
- package/src/utils/api.js +111 -111
- package/src/utils/auth.js +103 -103
- package/src/utils/crypto.js +59 -59
- package/src/utils/encryption.ts +68 -68
- package/src/utils/eventUtils.ts +302 -302
- package/src/utils/haptics.ts +58 -58
- package/src/utils/imagePreloader.ts +2 -2
- package/src/utils/programmaticFlow.ts +112 -112
- package/src/utils/retryHelper.ts +274 -274
package/src/utils/eventUtils.ts
CHANGED
|
@@ -1,303 +1,303 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Event utility functions for event management
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Generate a random alphanumeric event code
|
|
7
|
-
* @param length - Length of the code (default: 6)
|
|
8
|
-
* @returns Random event code
|
|
9
|
-
*/
|
|
10
|
-
export const generateEventCode = (length: number = 6): string => {
|
|
11
|
-
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
12
|
-
let result = '';
|
|
13
|
-
|
|
14
|
-
for (let i = 0; i < length; i++) {
|
|
15
|
-
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return result;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Generate a smart event code based on the event title
|
|
23
|
-
* @param title - Event title
|
|
24
|
-
* @returns Smart event code
|
|
25
|
-
*/
|
|
26
|
-
export const generateSmartEventCode = (title: string): string => {
|
|
27
|
-
if (!title.trim()) {
|
|
28
|
-
return generateEventCode();
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Clean the title and split into words
|
|
32
|
-
const words = title
|
|
33
|
-
.trim()
|
|
34
|
-
.toUpperCase()
|
|
35
|
-
.replace(/[^A-Z0-9\s]/g, '') // Remove special characters
|
|
36
|
-
.split(/\s+/)
|
|
37
|
-
.filter(word => word.length > 0);
|
|
38
|
-
|
|
39
|
-
if (words.length === 0) {
|
|
40
|
-
return generateEventCode();
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
let code = '';
|
|
44
|
-
|
|
45
|
-
// Strategy 1: Take first 2 letters of each word (up to 3 words)
|
|
46
|
-
if (words.length >= 2) {
|
|
47
|
-
const significantWords = words.slice(0, 3);
|
|
48
|
-
code = significantWords
|
|
49
|
-
.map(word => word.substring(0, 2))
|
|
50
|
-
.join('');
|
|
51
|
-
} else {
|
|
52
|
-
// Strategy 2: Single word - take first 4 letters
|
|
53
|
-
code = words[0].substring(0, 4);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Add current year's last 2 digits
|
|
57
|
-
const currentYear = new Date().getFullYear().toString().slice(-2);
|
|
58
|
-
code += currentYear;
|
|
59
|
-
|
|
60
|
-
// Ensure minimum length of 4 and maximum of 8
|
|
61
|
-
if (code.length < 4) {
|
|
62
|
-
code += generateEventCode(4 - code.length);
|
|
63
|
-
} else if (code.length > 8) {
|
|
64
|
-
code = code.substring(0, 8);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return code;
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Validate event code format
|
|
72
|
-
* @param code - Event code to validate
|
|
73
|
-
* @returns Object with validation result and error message
|
|
74
|
-
*/
|
|
75
|
-
export const validateEventCode = (code: string): { isValid: boolean; error?: string } => {
|
|
76
|
-
if (!code || !code.trim()) {
|
|
77
|
-
return { isValid: false, error: 'Event code is required' };
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const trimmedCode = code.trim().toUpperCase();
|
|
81
|
-
|
|
82
|
-
if (trimmedCode.length < 3) {
|
|
83
|
-
return { isValid: false, error: 'Event code must be at least 3 characters' };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (trimmedCode.length > 10) {
|
|
87
|
-
return { isValid: false, error: 'Event code must be 10 characters or less' };
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (!/^[A-Z0-9]+$/.test(trimmedCode)) {
|
|
91
|
-
return { isValid: false, error: 'Event code must contain only letters and numbers' };
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return { isValid: true };
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Format event date for display
|
|
99
|
-
* @param dateString - Date string in YYYY-MM-DD format
|
|
100
|
-
* @returns Formatted date string
|
|
101
|
-
*/
|
|
102
|
-
export const formatEventDate = (dateString: string): string => {
|
|
103
|
-
try {
|
|
104
|
-
const date = new Date(dateString);
|
|
105
|
-
return date.toLocaleDateString('en-US', {
|
|
106
|
-
weekday: 'long',
|
|
107
|
-
year: 'numeric',
|
|
108
|
-
month: 'long',
|
|
109
|
-
day: 'numeric',
|
|
110
|
-
});
|
|
111
|
-
} catch (error) {
|
|
112
|
-
return dateString;
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Check if an event is happening today
|
|
118
|
-
* @param eventDate - Event date string
|
|
119
|
-
* @returns True if event is today
|
|
120
|
-
*/
|
|
121
|
-
export const isEventToday = (eventDate: string): boolean => {
|
|
122
|
-
try {
|
|
123
|
-
const today = new Date();
|
|
124
|
-
const event = new Date(eventDate);
|
|
125
|
-
|
|
126
|
-
return (
|
|
127
|
-
today.getFullYear() === event.getFullYear() &&
|
|
128
|
-
today.getMonth() === event.getMonth() &&
|
|
129
|
-
today.getDate() === event.getDate()
|
|
130
|
-
);
|
|
131
|
-
} catch (error) {
|
|
132
|
-
return false;
|
|
133
|
-
}
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Check if an event is in the past
|
|
138
|
-
* @param eventDate - Event date string
|
|
139
|
-
* @returns True if event is in the past
|
|
140
|
-
*/
|
|
141
|
-
export const isEventPast = (eventDate: string): boolean => {
|
|
142
|
-
try {
|
|
143
|
-
const today = new Date();
|
|
144
|
-
const event = new Date(eventDate);
|
|
145
|
-
|
|
146
|
-
// Set time to start of day for accurate comparison
|
|
147
|
-
today.setHours(0, 0, 0, 0);
|
|
148
|
-
event.setHours(0, 0, 0, 0);
|
|
149
|
-
|
|
150
|
-
return event < today;
|
|
151
|
-
} catch (error) {
|
|
152
|
-
return false;
|
|
153
|
-
}
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Get event status based on date and active status
|
|
158
|
-
* @param eventDate - Event date string
|
|
159
|
-
* @param isActive - Whether event is active
|
|
160
|
-
* @returns Event status string
|
|
161
|
-
*/
|
|
162
|
-
export const getEventStatus = (eventDate: string, isActive: boolean): string => {
|
|
163
|
-
if (!isActive) {
|
|
164
|
-
return 'Inactive';
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (isEventPast(eventDate)) {
|
|
168
|
-
return 'Past';
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (isEventToday(eventDate)) {
|
|
172
|
-
return 'Today';
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return 'Upcoming';
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Calculate days until event
|
|
180
|
-
* @param eventDate - Event date string
|
|
181
|
-
* @returns Number of days until event (negative if past)
|
|
182
|
-
*/
|
|
183
|
-
export const getDaysUntilEvent = (eventDate: string): number => {
|
|
184
|
-
try {
|
|
185
|
-
const today = new Date();
|
|
186
|
-
const event = new Date(eventDate);
|
|
187
|
-
|
|
188
|
-
// Set time to start of day for accurate comparison
|
|
189
|
-
today.setHours(0, 0, 0, 0);
|
|
190
|
-
event.setHours(0, 0, 0, 0);
|
|
191
|
-
|
|
192
|
-
const diffTime = event.getTime() - today.getTime();
|
|
193
|
-
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
194
|
-
|
|
195
|
-
return diffDays;
|
|
196
|
-
} catch (error) {
|
|
197
|
-
return 0;
|
|
198
|
-
}
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Sort events by date (upcoming first, then past events)
|
|
203
|
-
* @param events - Array of events
|
|
204
|
-
* @returns Sorted array of events
|
|
205
|
-
*/
|
|
206
|
-
export const sortEventsByDate = <T extends { eventDate: string; isActive: boolean }>(events: T[]): T[] => {
|
|
207
|
-
return events.sort((a, b) => {
|
|
208
|
-
// Active events first
|
|
209
|
-
if (a.isActive && !b.isActive) return -1;
|
|
210
|
-
if (!a.isActive && b.isActive) return 1;
|
|
211
|
-
|
|
212
|
-
// Then sort by date
|
|
213
|
-
const dateA = new Date(a.eventDate);
|
|
214
|
-
const dateB = new Date(b.eventDate);
|
|
215
|
-
|
|
216
|
-
// Upcoming events first (ascending), then past events (descending)
|
|
217
|
-
const today = new Date();
|
|
218
|
-
const aIsPast = dateA < today;
|
|
219
|
-
const bIsPast = dateB < today;
|
|
220
|
-
|
|
221
|
-
if (aIsPast && !bIsPast) return 1;
|
|
222
|
-
if (!aIsPast && bIsPast) return -1;
|
|
223
|
-
|
|
224
|
-
if (aIsPast && bIsPast) {
|
|
225
|
-
return dateB.getTime() - dateA.getTime(); // Past events: most recent first
|
|
226
|
-
} else {
|
|
227
|
-
return dateA.getTime() - dateB.getTime(); // Upcoming events: soonest first
|
|
228
|
-
}
|
|
229
|
-
});
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Filter events by search query
|
|
234
|
-
* @param events - Array of events
|
|
235
|
-
* @param query - Search query
|
|
236
|
-
* @returns Filtered array of events
|
|
237
|
-
*/
|
|
238
|
-
export const filterEventsBySearch = <T extends {
|
|
239
|
-
title: string;
|
|
240
|
-
description: string;
|
|
241
|
-
code: string;
|
|
242
|
-
location?: string;
|
|
243
|
-
organizer?: string;
|
|
244
|
-
tags?: string[];
|
|
245
|
-
}>(events: T[], query: string): T[] => {
|
|
246
|
-
if (!query.trim()) {
|
|
247
|
-
return events;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
const searchTerm = query.toLowerCase().trim();
|
|
251
|
-
|
|
252
|
-
return events.filter(event =>
|
|
253
|
-
event.title.toLowerCase().includes(searchTerm) ||
|
|
254
|
-
event.description.toLowerCase().includes(searchTerm) ||
|
|
255
|
-
event.code.toLowerCase().includes(searchTerm) ||
|
|
256
|
-
event.location?.toLowerCase().includes(searchTerm) ||
|
|
257
|
-
event.organizer?.toLowerCase().includes(searchTerm) ||
|
|
258
|
-
event.tags?.some(tag => tag.toLowerCase().includes(searchTerm))
|
|
259
|
-
);
|
|
260
|
-
};
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Get unique tags from events
|
|
264
|
-
* @param events - Array of events
|
|
265
|
-
* @returns Array of unique tags
|
|
266
|
-
*/
|
|
267
|
-
export const getUniqueEventTags = <T extends { tags?: string[] }>(events: T[]): string[] => {
|
|
268
|
-
const allTags = events.flatMap(event => event.tags || []);
|
|
269
|
-
return Array.from(new Set(allTags)).sort();
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Calculate event statistics
|
|
274
|
-
* @param events - Array of events
|
|
275
|
-
* @returns Event statistics object
|
|
276
|
-
*/
|
|
277
|
-
export const calculateEventStats = <T extends {
|
|
278
|
-
isActive: boolean;
|
|
279
|
-
attendeeCount: number;
|
|
280
|
-
maxAttendees: number;
|
|
281
|
-
eventDate: string;
|
|
282
|
-
}>(events: T[]) => {
|
|
283
|
-
const total = events.length;
|
|
284
|
-
const active = events.filter(e => e.isActive).length;
|
|
285
|
-
const inactive = total - active;
|
|
286
|
-
const totalAttendees = events.reduce((sum, e) => sum + e.attendeeCount, 0);
|
|
287
|
-
const totalCapacity = events.reduce((sum, e) => sum + e.maxAttendees, 0);
|
|
288
|
-
const upcoming = events.filter(e => e.isActive && !isEventPast(e.eventDate)).length;
|
|
289
|
-
const past = events.filter(e => isEventPast(e.eventDate)).length;
|
|
290
|
-
const today = events.filter(e => e.isActive && isEventToday(e.eventDate)).length;
|
|
291
|
-
|
|
292
|
-
return {
|
|
293
|
-
total,
|
|
294
|
-
active,
|
|
295
|
-
inactive,
|
|
296
|
-
upcoming,
|
|
297
|
-
past,
|
|
298
|
-
today,
|
|
299
|
-
totalAttendees,
|
|
300
|
-
totalCapacity,
|
|
301
|
-
averageAttendance: totalCapacity > 0 ? Math.round((totalAttendees / totalCapacity) * 100) : 0,
|
|
302
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Event utility functions for event management
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generate a random alphanumeric event code
|
|
7
|
+
* @param length - Length of the code (default: 6)
|
|
8
|
+
* @returns Random event code
|
|
9
|
+
*/
|
|
10
|
+
export const generateEventCode = (length: number = 6): string => {
|
|
11
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
12
|
+
let result = '';
|
|
13
|
+
|
|
14
|
+
for (let i = 0; i < length; i++) {
|
|
15
|
+
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return result;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Generate a smart event code based on the event title
|
|
23
|
+
* @param title - Event title
|
|
24
|
+
* @returns Smart event code
|
|
25
|
+
*/
|
|
26
|
+
export const generateSmartEventCode = (title: string): string => {
|
|
27
|
+
if (!title.trim()) {
|
|
28
|
+
return generateEventCode();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Clean the title and split into words
|
|
32
|
+
const words = title
|
|
33
|
+
.trim()
|
|
34
|
+
.toUpperCase()
|
|
35
|
+
.replace(/[^A-Z0-9\s]/g, '') // Remove special characters
|
|
36
|
+
.split(/\s+/)
|
|
37
|
+
.filter(word => word.length > 0);
|
|
38
|
+
|
|
39
|
+
if (words.length === 0) {
|
|
40
|
+
return generateEventCode();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let code = '';
|
|
44
|
+
|
|
45
|
+
// Strategy 1: Take first 2 letters of each word (up to 3 words)
|
|
46
|
+
if (words.length >= 2) {
|
|
47
|
+
const significantWords = words.slice(0, 3);
|
|
48
|
+
code = significantWords
|
|
49
|
+
.map(word => word.substring(0, 2))
|
|
50
|
+
.join('');
|
|
51
|
+
} else {
|
|
52
|
+
// Strategy 2: Single word - take first 4 letters
|
|
53
|
+
code = words[0].substring(0, 4);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Add current year's last 2 digits
|
|
57
|
+
const currentYear = new Date().getFullYear().toString().slice(-2);
|
|
58
|
+
code += currentYear;
|
|
59
|
+
|
|
60
|
+
// Ensure minimum length of 4 and maximum of 8
|
|
61
|
+
if (code.length < 4) {
|
|
62
|
+
code += generateEventCode(4 - code.length);
|
|
63
|
+
} else if (code.length > 8) {
|
|
64
|
+
code = code.substring(0, 8);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return code;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Validate event code format
|
|
72
|
+
* @param code - Event code to validate
|
|
73
|
+
* @returns Object with validation result and error message
|
|
74
|
+
*/
|
|
75
|
+
export const validateEventCode = (code: string): { isValid: boolean; error?: string } => {
|
|
76
|
+
if (!code || !code.trim()) {
|
|
77
|
+
return { isValid: false, error: 'Event code is required' };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const trimmedCode = code.trim().toUpperCase();
|
|
81
|
+
|
|
82
|
+
if (trimmedCode.length < 3) {
|
|
83
|
+
return { isValid: false, error: 'Event code must be at least 3 characters' };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (trimmedCode.length > 10) {
|
|
87
|
+
return { isValid: false, error: 'Event code must be 10 characters or less' };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (!/^[A-Z0-9]+$/.test(trimmedCode)) {
|
|
91
|
+
return { isValid: false, error: 'Event code must contain only letters and numbers' };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return { isValid: true };
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Format event date for display
|
|
99
|
+
* @param dateString - Date string in YYYY-MM-DD format
|
|
100
|
+
* @returns Formatted date string
|
|
101
|
+
*/
|
|
102
|
+
export const formatEventDate = (dateString: string): string => {
|
|
103
|
+
try {
|
|
104
|
+
const date = new Date(dateString);
|
|
105
|
+
return date.toLocaleDateString('en-US', {
|
|
106
|
+
weekday: 'long',
|
|
107
|
+
year: 'numeric',
|
|
108
|
+
month: 'long',
|
|
109
|
+
day: 'numeric',
|
|
110
|
+
});
|
|
111
|
+
} catch (error) {
|
|
112
|
+
return dateString;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Check if an event is happening today
|
|
118
|
+
* @param eventDate - Event date string
|
|
119
|
+
* @returns True if event is today
|
|
120
|
+
*/
|
|
121
|
+
export const isEventToday = (eventDate: string): boolean => {
|
|
122
|
+
try {
|
|
123
|
+
const today = new Date();
|
|
124
|
+
const event = new Date(eventDate);
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
today.getFullYear() === event.getFullYear() &&
|
|
128
|
+
today.getMonth() === event.getMonth() &&
|
|
129
|
+
today.getDate() === event.getDate()
|
|
130
|
+
);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Check if an event is in the past
|
|
138
|
+
* @param eventDate - Event date string
|
|
139
|
+
* @returns True if event is in the past
|
|
140
|
+
*/
|
|
141
|
+
export const isEventPast = (eventDate: string): boolean => {
|
|
142
|
+
try {
|
|
143
|
+
const today = new Date();
|
|
144
|
+
const event = new Date(eventDate);
|
|
145
|
+
|
|
146
|
+
// Set time to start of day for accurate comparison
|
|
147
|
+
today.setHours(0, 0, 0, 0);
|
|
148
|
+
event.setHours(0, 0, 0, 0);
|
|
149
|
+
|
|
150
|
+
return event < today;
|
|
151
|
+
} catch (error) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Get event status based on date and active status
|
|
158
|
+
* @param eventDate - Event date string
|
|
159
|
+
* @param isActive - Whether event is active
|
|
160
|
+
* @returns Event status string
|
|
161
|
+
*/
|
|
162
|
+
export const getEventStatus = (eventDate: string, isActive: boolean): string => {
|
|
163
|
+
if (!isActive) {
|
|
164
|
+
return 'Inactive';
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (isEventPast(eventDate)) {
|
|
168
|
+
return 'Past';
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (isEventToday(eventDate)) {
|
|
172
|
+
return 'Today';
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return 'Upcoming';
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Calculate days until event
|
|
180
|
+
* @param eventDate - Event date string
|
|
181
|
+
* @returns Number of days until event (negative if past)
|
|
182
|
+
*/
|
|
183
|
+
export const getDaysUntilEvent = (eventDate: string): number => {
|
|
184
|
+
try {
|
|
185
|
+
const today = new Date();
|
|
186
|
+
const event = new Date(eventDate);
|
|
187
|
+
|
|
188
|
+
// Set time to start of day for accurate comparison
|
|
189
|
+
today.setHours(0, 0, 0, 0);
|
|
190
|
+
event.setHours(0, 0, 0, 0);
|
|
191
|
+
|
|
192
|
+
const diffTime = event.getTime() - today.getTime();
|
|
193
|
+
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
194
|
+
|
|
195
|
+
return diffDays;
|
|
196
|
+
} catch (error) {
|
|
197
|
+
return 0;
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Sort events by date (upcoming first, then past events)
|
|
203
|
+
* @param events - Array of events
|
|
204
|
+
* @returns Sorted array of events
|
|
205
|
+
*/
|
|
206
|
+
export const sortEventsByDate = <T extends { eventDate: string; isActive: boolean }>(events: T[]): T[] => {
|
|
207
|
+
return events.sort((a, b) => {
|
|
208
|
+
// Active events first
|
|
209
|
+
if (a.isActive && !b.isActive) return -1;
|
|
210
|
+
if (!a.isActive && b.isActive) return 1;
|
|
211
|
+
|
|
212
|
+
// Then sort by date
|
|
213
|
+
const dateA = new Date(a.eventDate);
|
|
214
|
+
const dateB = new Date(b.eventDate);
|
|
215
|
+
|
|
216
|
+
// Upcoming events first (ascending), then past events (descending)
|
|
217
|
+
const today = new Date();
|
|
218
|
+
const aIsPast = dateA < today;
|
|
219
|
+
const bIsPast = dateB < today;
|
|
220
|
+
|
|
221
|
+
if (aIsPast && !bIsPast) return 1;
|
|
222
|
+
if (!aIsPast && bIsPast) return -1;
|
|
223
|
+
|
|
224
|
+
if (aIsPast && bIsPast) {
|
|
225
|
+
return dateB.getTime() - dateA.getTime(); // Past events: most recent first
|
|
226
|
+
} else {
|
|
227
|
+
return dateA.getTime() - dateB.getTime(); // Upcoming events: soonest first
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Filter events by search query
|
|
234
|
+
* @param events - Array of events
|
|
235
|
+
* @param query - Search query
|
|
236
|
+
* @returns Filtered array of events
|
|
237
|
+
*/
|
|
238
|
+
export const filterEventsBySearch = <T extends {
|
|
239
|
+
title: string;
|
|
240
|
+
description: string;
|
|
241
|
+
code: string;
|
|
242
|
+
location?: string;
|
|
243
|
+
organizer?: string;
|
|
244
|
+
tags?: string[];
|
|
245
|
+
}>(events: T[], query: string): T[] => {
|
|
246
|
+
if (!query.trim()) {
|
|
247
|
+
return events;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const searchTerm = query.toLowerCase().trim();
|
|
251
|
+
|
|
252
|
+
return events.filter(event =>
|
|
253
|
+
event.title.toLowerCase().includes(searchTerm) ||
|
|
254
|
+
event.description.toLowerCase().includes(searchTerm) ||
|
|
255
|
+
event.code.toLowerCase().includes(searchTerm) ||
|
|
256
|
+
event.location?.toLowerCase().includes(searchTerm) ||
|
|
257
|
+
event.organizer?.toLowerCase().includes(searchTerm) ||
|
|
258
|
+
event.tags?.some(tag => tag.toLowerCase().includes(searchTerm))
|
|
259
|
+
);
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Get unique tags from events
|
|
264
|
+
* @param events - Array of events
|
|
265
|
+
* @returns Array of unique tags
|
|
266
|
+
*/
|
|
267
|
+
export const getUniqueEventTags = <T extends { tags?: string[] }>(events: T[]): string[] => {
|
|
268
|
+
const allTags = events.flatMap(event => event.tags || []);
|
|
269
|
+
return Array.from(new Set(allTags)).sort();
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Calculate event statistics
|
|
274
|
+
* @param events - Array of events
|
|
275
|
+
* @returns Event statistics object
|
|
276
|
+
*/
|
|
277
|
+
export const calculateEventStats = <T extends {
|
|
278
|
+
isActive: boolean;
|
|
279
|
+
attendeeCount: number;
|
|
280
|
+
maxAttendees: number;
|
|
281
|
+
eventDate: string;
|
|
282
|
+
}>(events: T[]) => {
|
|
283
|
+
const total = events.length;
|
|
284
|
+
const active = events.filter(e => e.isActive).length;
|
|
285
|
+
const inactive = total - active;
|
|
286
|
+
const totalAttendees = events.reduce((sum, e) => sum + e.attendeeCount, 0);
|
|
287
|
+
const totalCapacity = events.reduce((sum, e) => sum + e.maxAttendees, 0);
|
|
288
|
+
const upcoming = events.filter(e => e.isActive && !isEventPast(e.eventDate)).length;
|
|
289
|
+
const past = events.filter(e => isEventPast(e.eventDate)).length;
|
|
290
|
+
const today = events.filter(e => e.isActive && isEventToday(e.eventDate)).length;
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
total,
|
|
294
|
+
active,
|
|
295
|
+
inactive,
|
|
296
|
+
upcoming,
|
|
297
|
+
past,
|
|
298
|
+
today,
|
|
299
|
+
totalAttendees,
|
|
300
|
+
totalCapacity,
|
|
301
|
+
averageAttendance: totalCapacity > 0 ? Math.round((totalAttendees / totalCapacity) * 100) : 0,
|
|
302
|
+
};
|
|
303
303
|
};
|