@appcorp/fusion-storybook 0.2.4 → 0.2.6
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/base-modules/teacher/avatar-upload.d.ts +36 -0
- package/base-modules/teacher/avatar-upload.js +103 -0
- package/base-modules/teacher/constants.d.ts +2 -0
- package/base-modules/teacher/constants.js +14 -0
- package/base-modules/teacher/context.d.ts +7 -0
- package/base-modules/teacher/context.js +77 -0
- package/base-modules/teacher/form.js +8 -5
- package/base-modules/teacher/validate.d.ts +11 -11
- package/base-modules/teacher/validate.js +11 -9
- package/package.json +1 -1
- package/tsconfig.build.tsbuildinfo +1 -1
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Teacher avatar upload utility
|
|
3
|
+
* Handles client-side file validation, WebP conversion, and upload orchestration
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Validation error type
|
|
7
|
+
*/
|
|
8
|
+
export interface AvatarUploadError {
|
|
9
|
+
type: "size" | "format" | "conversion" | "unknown";
|
|
10
|
+
message: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Result of avatar upload process
|
|
14
|
+
*/
|
|
15
|
+
export interface AvatarUploadResult {
|
|
16
|
+
success: true;
|
|
17
|
+
fileBase64: string;
|
|
18
|
+
contentType: "image/webp";
|
|
19
|
+
originalFileName: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Validate file size and format
|
|
23
|
+
*/
|
|
24
|
+
export declare function validateAvatarFile(file: File): AvatarUploadError | null;
|
|
25
|
+
/**
|
|
26
|
+
* Convert image file to WebP format
|
|
27
|
+
*/
|
|
28
|
+
export declare function convertToWebPFormat(file: File): Promise<Blob | AvatarUploadError>;
|
|
29
|
+
/**
|
|
30
|
+
* Convert blob to base64 string
|
|
31
|
+
*/
|
|
32
|
+
export declare function blobToBase64(blob: Blob): Promise<string>;
|
|
33
|
+
/**
|
|
34
|
+
* Process avatar file: validate, convert to WebP, return base64
|
|
35
|
+
*/
|
|
36
|
+
export declare function processAvatarFile(file: File): Promise<AvatarUploadResult | AvatarUploadError>;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Teacher avatar upload utility
|
|
3
|
+
* Handles client-side file validation, WebP conversion, and upload orchestration
|
|
4
|
+
*/
|
|
5
|
+
import { blobToWebP } from "webp-converter-browser";
|
|
6
|
+
// Constants
|
|
7
|
+
const MAX_FILE_SIZE = 100 * 1024; // 100KB in bytes
|
|
8
|
+
const ALLOWED_MIME_TYPES = [
|
|
9
|
+
"image/webp",
|
|
10
|
+
"image/png",
|
|
11
|
+
"image/jpeg",
|
|
12
|
+
"image/jpg",
|
|
13
|
+
"image/gif",
|
|
14
|
+
"image/bmp",
|
|
15
|
+
];
|
|
16
|
+
/**
|
|
17
|
+
* Validate file size and format
|
|
18
|
+
*/
|
|
19
|
+
export function validateAvatarFile(file) {
|
|
20
|
+
// Check file size
|
|
21
|
+
if (file.size > MAX_FILE_SIZE) {
|
|
22
|
+
return {
|
|
23
|
+
type: "size",
|
|
24
|
+
message: `File size must be less than 100KB. Your file is ${Math.round(file.size / 1024)}KB.`,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
// Check MIME type
|
|
28
|
+
if (!ALLOWED_MIME_TYPES.includes(file.type)) {
|
|
29
|
+
return {
|
|
30
|
+
type: "format",
|
|
31
|
+
message: `Unsupported image format. Allowed formats: PNG, JPEG, GIF, BMP, WebP.`,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Convert image file to WebP format
|
|
38
|
+
*/
|
|
39
|
+
export async function convertToWebPFormat(file) {
|
|
40
|
+
try {
|
|
41
|
+
// blobToWebP expects a Blob, so we use the file directly (File is a Blob)
|
|
42
|
+
const webpBlob = await blobToWebP(file, { quality: 0.8 });
|
|
43
|
+
return webpBlob;
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
const errorMessage = error instanceof Error
|
|
47
|
+
? error.message
|
|
48
|
+
: "Unknown error during WebP conversion";
|
|
49
|
+
return {
|
|
50
|
+
type: "conversion",
|
|
51
|
+
message: `Failed to convert image: ${errorMessage}`,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Convert blob to base64 string
|
|
57
|
+
*/
|
|
58
|
+
export function blobToBase64(blob) {
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
const reader = new FileReader();
|
|
61
|
+
reader.onloadend = () => {
|
|
62
|
+
const base64String = reader.result;
|
|
63
|
+
// Remove "data:image/webp;base64," prefix
|
|
64
|
+
const base64 = base64String.split(",")[1];
|
|
65
|
+
resolve(base64);
|
|
66
|
+
};
|
|
67
|
+
reader.onerror = reject;
|
|
68
|
+
reader.readAsDataURL(blob);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Process avatar file: validate, convert to WebP, return base64
|
|
73
|
+
*/
|
|
74
|
+
export async function processAvatarFile(file) {
|
|
75
|
+
// Validate file
|
|
76
|
+
const validationError = validateAvatarFile(file);
|
|
77
|
+
if (validationError) {
|
|
78
|
+
return validationError;
|
|
79
|
+
}
|
|
80
|
+
// Convert to WebP
|
|
81
|
+
const conversionResult = await convertToWebPFormat(file);
|
|
82
|
+
if ("type" in conversionResult) {
|
|
83
|
+
// It's an error
|
|
84
|
+
return conversionResult;
|
|
85
|
+
}
|
|
86
|
+
// Convert blob to base64
|
|
87
|
+
try {
|
|
88
|
+
const fileBase64 = await blobToBase64(conversionResult);
|
|
89
|
+
return {
|
|
90
|
+
success: true,
|
|
91
|
+
fileBase64,
|
|
92
|
+
contentType: "image/webp",
|
|
93
|
+
originalFileName: file.name,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
98
|
+
return {
|
|
99
|
+
type: "unknown",
|
|
100
|
+
message: `Failed to encode image: ${errorMessage}`,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -21,3 +21,5 @@ export declare const GENDER_OPTIONS: readonly [{
|
|
|
21
21
|
readonly label: "Other";
|
|
22
22
|
readonly value: "OTHER";
|
|
23
23
|
}];
|
|
24
|
+
export declare const AVATAR_MAX_FILE_SIZE: number;
|
|
25
|
+
export declare const AVATAR_ALLOWED_MIME_TYPES: readonly ["image/webp", "image/png", "image/jpeg", "image/jpg", "image/gif", "image/bmp"];
|
|
@@ -14,7 +14,9 @@ export const pageLimit = Number(process.env.NEXT_PUBLIC_PAGE_LIMIT) || 10;
|
|
|
14
14
|
// API ROUTES
|
|
15
15
|
// ============================================================================
|
|
16
16
|
export const TEACHER_API_ROUTES = {
|
|
17
|
+
LIST: "/api/v1/teacher",
|
|
17
18
|
UNIT: "/api/v1/teacher",
|
|
19
|
+
AVATAR: "/api/v1/teacher-avatar",
|
|
18
20
|
};
|
|
19
21
|
// ============================================================================
|
|
20
22
|
// GENDER OPTIONS
|
|
@@ -25,3 +27,15 @@ export const GENDER_OPTIONS = [
|
|
|
25
27
|
{ label: "Female", value: "FEMALE" },
|
|
26
28
|
{ label: "Other", value: "OTHER" },
|
|
27
29
|
];
|
|
30
|
+
// ============================================================================
|
|
31
|
+
// AVATAR UPLOAD CONFIGURATION
|
|
32
|
+
// ============================================================================
|
|
33
|
+
export const AVATAR_MAX_FILE_SIZE = 100 * 1024; // 100KB in bytes
|
|
34
|
+
export const AVATAR_ALLOWED_MIME_TYPES = [
|
|
35
|
+
"image/webp",
|
|
36
|
+
"image/png",
|
|
37
|
+
"image/jpeg",
|
|
38
|
+
"image/jpg",
|
|
39
|
+
"image/gif",
|
|
40
|
+
"image/bmp",
|
|
41
|
+
];
|
|
@@ -27,6 +27,7 @@ export declare const TEACHER_ACTION_TYPES: {
|
|
|
27
27
|
disableSaveButton: boolean;
|
|
28
28
|
drawer: string | null;
|
|
29
29
|
errors: Record<string, string>;
|
|
30
|
+
avatarUploading: boolean;
|
|
30
31
|
address: string | null;
|
|
31
32
|
avatar: string | null;
|
|
32
33
|
bio: string | null;
|
|
@@ -59,6 +60,7 @@ export declare const TEACHER_ACTION_TYPES: {
|
|
|
59
60
|
disableSaveButton: boolean;
|
|
60
61
|
drawer: string | null;
|
|
61
62
|
errors: Record<string, string>;
|
|
63
|
+
avatarUploading: boolean;
|
|
62
64
|
address: string | null;
|
|
63
65
|
avatar: string | null;
|
|
64
66
|
bio: string | null;
|
|
@@ -93,6 +95,7 @@ export declare const TEACHER_ACTION_TYPES: {
|
|
|
93
95
|
disableSaveButton: boolean;
|
|
94
96
|
drawer: string | null;
|
|
95
97
|
errors: Record<string, string>;
|
|
98
|
+
avatarUploading: boolean;
|
|
96
99
|
address: string | null;
|
|
97
100
|
avatar: string | null;
|
|
98
101
|
bio: string | null;
|
|
@@ -125,6 +128,7 @@ export declare const TEACHER_ACTION_TYPES: {
|
|
|
125
128
|
disableSaveButton: boolean;
|
|
126
129
|
drawer: string | null;
|
|
127
130
|
errors: Record<string, string>;
|
|
131
|
+
avatarUploading: boolean;
|
|
128
132
|
address: string | null;
|
|
129
133
|
avatar: string | null;
|
|
130
134
|
bio: string | null;
|
|
@@ -157,6 +161,7 @@ export declare const TEACHER_ACTION_TYPES: {
|
|
|
157
161
|
disableSaveButton: boolean;
|
|
158
162
|
drawer: string | null;
|
|
159
163
|
errors: Record<string, string>;
|
|
164
|
+
avatarUploading: boolean;
|
|
160
165
|
address: string | null;
|
|
161
166
|
avatar: string | null;
|
|
162
167
|
bio: string | null;
|
|
@@ -189,6 +194,7 @@ export declare const useTeacherModule: () => {
|
|
|
189
194
|
closeDrawer: () => void;
|
|
190
195
|
deleteLoading: boolean;
|
|
191
196
|
handleAvatar: (files: File[]) => void;
|
|
197
|
+
handleAvatarUpload: (file: File) => Promise<void>;
|
|
192
198
|
handleChange: (field: string, value: string | number | boolean | Date | null | undefined) => void;
|
|
193
199
|
handleCreate: () => void;
|
|
194
200
|
handleDelete: (row?: TableRow) => void;
|
|
@@ -220,6 +226,7 @@ export declare const useTeacherModule: () => {
|
|
|
220
226
|
disableSaveButton: boolean;
|
|
221
227
|
drawer: string | null;
|
|
222
228
|
errors: Record<string, string>;
|
|
229
|
+
avatarUploading: boolean;
|
|
223
230
|
address: string | null;
|
|
224
231
|
avatar: string | null;
|
|
225
232
|
bio: string | null;
|
|
@@ -33,6 +33,7 @@ import { TEACHER_API_ROUTES, pageLimit } from "./constants";
|
|
|
33
33
|
import { teacherFormValidation } from "./validate";
|
|
34
34
|
import { getCachedTeachers, invalidateTeachersCache } from "./cache";
|
|
35
35
|
import { getCachedWorkspaceSync } from "../workspace/cache";
|
|
36
|
+
import { processAvatarFile } from "./avatar-upload";
|
|
36
37
|
// ============================================================================
|
|
37
38
|
// 1.1 DRAWER TYPES
|
|
38
39
|
// ============================================================================
|
|
@@ -58,6 +59,7 @@ const teacherConfig = {
|
|
|
58
59
|
disableSaveButton: false,
|
|
59
60
|
drawer: null,
|
|
60
61
|
errors: {},
|
|
62
|
+
avatarUploading: false,
|
|
61
63
|
// Form State
|
|
62
64
|
address: null,
|
|
63
65
|
avatar: null,
|
|
@@ -352,6 +354,80 @@ export const useTeacherModule = () => {
|
|
|
352
354
|
payload: { key: "avatar", value: url },
|
|
353
355
|
});
|
|
354
356
|
}, [dispatch]);
|
|
357
|
+
const handleAvatarUpload = useCallback(async (file) => {
|
|
358
|
+
try {
|
|
359
|
+
// Set uploading state
|
|
360
|
+
dispatch({
|
|
361
|
+
type: TEACHER_ACTION_TYPES.SET_INPUT_FIELD,
|
|
362
|
+
payload: { key: "avatarUploading", value: true },
|
|
363
|
+
});
|
|
364
|
+
// Process avatar file (validate, convert to WebP)
|
|
365
|
+
const result = await processAvatarFile(file);
|
|
366
|
+
if ("type" in result) {
|
|
367
|
+
// Error result
|
|
368
|
+
const error = result;
|
|
369
|
+
showToast(error.message, TOAST_VARIANT.ERROR);
|
|
370
|
+
dispatch({
|
|
371
|
+
type: TEACHER_ACTION_TYPES.SET_INPUT_FIELD,
|
|
372
|
+
payload: { key: "avatarUploading", value: false },
|
|
373
|
+
});
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
// Get teacherId from current state (if creating, id will be empty)
|
|
377
|
+
const teacherId = state.id;
|
|
378
|
+
if (!teacherId) {
|
|
379
|
+
showToast("Please save the teacher record first before uploading an avatar", TOAST_VARIANT.ERROR);
|
|
380
|
+
dispatch({
|
|
381
|
+
type: TEACHER_ACTION_TYPES.SET_INPUT_FIELD,
|
|
382
|
+
payload: { key: "avatarUploading", value: false },
|
|
383
|
+
});
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
// Upload to S3 via teacher-avatar endpoint
|
|
387
|
+
const response = await fetch(TEACHER_API_ROUTES.AVATAR, {
|
|
388
|
+
method: "POST",
|
|
389
|
+
headers: {
|
|
390
|
+
"Content-Type": "application/json",
|
|
391
|
+
"x-api-token": process.env.NEXT_PUBLIC_API_KEY,
|
|
392
|
+
},
|
|
393
|
+
body: JSON.stringify({
|
|
394
|
+
teacherId,
|
|
395
|
+
fileBase64: result.fileBase64,
|
|
396
|
+
contentType: result.contentType,
|
|
397
|
+
}),
|
|
398
|
+
});
|
|
399
|
+
if (!response.ok) {
|
|
400
|
+
const errorData = await response.json().catch(() => ({}));
|
|
401
|
+
const errorMessage = errorData.error || `Upload failed with status ${response.status}`;
|
|
402
|
+
showToast(errorMessage, TOAST_VARIANT.ERROR);
|
|
403
|
+
dispatch({
|
|
404
|
+
type: TEACHER_ACTION_TYPES.SET_INPUT_FIELD,
|
|
405
|
+
payload: { key: "avatarUploading", value: false },
|
|
406
|
+
});
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
const data = await response.json();
|
|
410
|
+
const avatarUrl = data.url;
|
|
411
|
+
// Update avatar field with presigned URL
|
|
412
|
+
dispatch({
|
|
413
|
+
type: TEACHER_ACTION_TYPES.SET_INPUT_FIELD,
|
|
414
|
+
payload: { key: "avatar", value: avatarUrl },
|
|
415
|
+
});
|
|
416
|
+
showToast("Avatar uploaded successfully", TOAST_VARIANT.SUCCESS);
|
|
417
|
+
}
|
|
418
|
+
catch (error) {
|
|
419
|
+
const errorMessage = error instanceof Error
|
|
420
|
+
? error.message
|
|
421
|
+
: "Unknown error uploading avatar";
|
|
422
|
+
showToast(errorMessage, TOAST_VARIANT.ERROR);
|
|
423
|
+
}
|
|
424
|
+
finally {
|
|
425
|
+
dispatch({
|
|
426
|
+
type: TEACHER_ACTION_TYPES.SET_INPUT_FIELD,
|
|
427
|
+
payload: { key: "avatarUploading", value: false },
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
}, [state.id, dispatch, showToast, theme]);
|
|
355
431
|
const applyFilters = useCallback(() => {
|
|
356
432
|
dispatch({
|
|
357
433
|
type: TEACHER_ACTION_TYPES.SET_CURRENT_PAGE,
|
|
@@ -492,6 +568,7 @@ export const useTeacherModule = () => {
|
|
|
492
568
|
closeDrawer,
|
|
493
569
|
deleteLoading,
|
|
494
570
|
handleAvatar,
|
|
571
|
+
handleAvatarUpload,
|
|
495
572
|
handleChange,
|
|
496
573
|
handleCreate,
|
|
497
574
|
handleDelete,
|
|
@@ -16,15 +16,18 @@ export const TeacherForm = () => {
|
|
|
16
16
|
const context = useTeacherModule();
|
|
17
17
|
const { address, avatar, bio, city, country, dateOfBirth, emergencyPhone, enabled, errors, experience, firstName, gender, joiningDate, lastName, phone, postalCode, qualification, specialization, teacherCode, userId, } = context.state;
|
|
18
18
|
const stateValue = context.state.state;
|
|
19
|
-
const { handleChange } = context;
|
|
19
|
+
const { handleChange, handleAvatarUpload } = context;
|
|
20
20
|
const users = getCachedUsersSync();
|
|
21
|
-
return (_jsxs("div", { className: "space-y-4", children: [_jsx(EnhancedInput, { error: errors.teacherCode, id: "teacherCode", info: t("formUniqueTeacherIdentifierLabel"), label: t("formTeacherCodeLabel"), onChange: (e) => handleChange("teacherCode", e.target.value), placeholder: t("formTeacherCodePlaceholder"), required: true, value: teacherCode }), _jsx(EnhancedInput, { error: errors.firstName, id: "firstName", info: t("formTeachersFirstNameLabel"), label: t("formFirstNameLabel"), onChange: (e) => handleChange("firstName", e.target.value), placeholder: t("formFirstNamePlaceholder"), required: true, value: firstName }), _jsx(EnhancedInput, { error: errors.lastName, id: "lastName", info: t("formTeachersLastNameLabel"), label: t("formLastNameLabel"), onChange: (e) => handleChange("lastName", e.target.value), placeholder: t("formLastNamePlaceholder"), required: true, value: lastName }), _jsx(EnhancedInput, { error: errors.joiningDate, id: "joiningDate", info: t("formDateTeacherJoinedTheSchoolLabel"), label: t("formJoiningDateLabel"), onChange: (e) => handleChange("joiningDate", e.target.value), onClick: (e) => { var _a, _b; return (_b = (_a = e.currentTarget).showPicker) === null || _b === void 0 ? void 0 : _b.call(_a); }, required: true, type: "date", value: joiningDate
|
|
21
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(EnhancedInput, { error: errors.teacherCode, id: "teacherCode", info: t("formUniqueTeacherIdentifierLabel"), label: t("formTeacherCodeLabel"), onChange: (e) => handleChange("teacherCode", e.target.value), placeholder: t("formTeacherCodePlaceholder"), required: true, value: teacherCode }), _jsx(EnhancedInput, { error: errors.firstName, id: "firstName", info: t("formTeachersFirstNameLabel"), label: t("formFirstNameLabel"), onChange: (e) => handleChange("firstName", e.target.value), placeholder: t("formFirstNamePlaceholder"), required: true, value: firstName }), _jsx(EnhancedInput, { error: errors.lastName, id: "lastName", info: t("formTeachersLastNameLabel"), label: t("formLastNameLabel"), onChange: (e) => handleChange("lastName", e.target.value), placeholder: t("formLastNamePlaceholder"), required: true, value: lastName }), _jsx(EnhancedInput, { error: errors.joiningDate, id: "joiningDate", info: t("formDateTeacherJoinedTheSchoolLabel"), label: t("formJoiningDateLabel"), onChange: (e) => handleChange("joiningDate", e.target.value), onClick: (e) => { var _a, _b; return (_b = (_a = e.currentTarget).showPicker) === null || _b === void 0 ? void 0 : _b.call(_a); }, required: true, type: "date", value: joiningDate
|
|
22
|
+
? new Date(joiningDate).toISOString().split("formTLabel")[0]
|
|
23
|
+
: "" }), _jsx(EnhancedCombobox, { emptyText: t("formNoTeacherUsersEmpty"), error: errors.userId, id: "userId", info: t("formLinkedSystemUserLabel"), label: t("formTeacherUserPlaceholder"), onValueChange: (value) => handleChange("userId", value || null), options: (_a = users.items) === null || _a === void 0 ? void 0 : _a.filter(({ workspaces }) => workspaces === null || workspaces === void 0 ? void 0 : workspaces.filter(({ role }) => (role === null || role === void 0 ? void 0 : role.userRole) === USER_ROLE.TEACHER).length).map((u) => ({
|
|
22
24
|
label: (u === null || u === void 0 ? void 0 : u.name) || (u === null || u === void 0 ? void 0 : u.email) || u.id,
|
|
23
25
|
value: u.id,
|
|
24
|
-
})), placeholder: t("formSelectLinkedUserPlaceholder"), searchPlaceholder: t("formSearchUsersPlaceholder"), value: userId || "" }), _jsx(EnhancedCombobox, { error: errors.gender, id: "gender", info: t("formTeachersGenderPlaceholder"), label: t("formGenderLabel"), onValueChange: (value) => handleChange("gender", value || null), options: GENDER_OPTIONS.slice(1), value: gender || "" }), _jsx(EnhancedInput, { error: errors.dateOfBirth, id: "dateOfBirth", info: t("formTeachersDateOfBirthLabel"), label: t("formDateOfBirthLabel"), onChange: (e) => handleChange("dateOfBirth", e.target.value), onClick: (e) => { var _a, _b; return (_b = (_a = e.currentTarget).showPicker) === null || _b === void 0 ? void 0 : _b.call(_a); }, type: "date", value: dateOfBirth
|
|
26
|
+
})), placeholder: t("formSelectLinkedUserPlaceholder"), searchPlaceholder: t("formSearchUsersPlaceholder"), value: userId || "" }), _jsx(EnhancedCombobox, { error: errors.gender, id: "gender", info: t("formTeachersGenderPlaceholder"), label: t("formGenderLabel"), onValueChange: (value) => handleChange("gender", value || null), options: GENDER_OPTIONS.slice(1), value: gender || "" }), _jsx(EnhancedInput, { error: errors.dateOfBirth, id: "dateOfBirth", info: t("formTeachersDateOfBirthLabel"), label: t("formDateOfBirthLabel"), onChange: (e) => handleChange("dateOfBirth", e.target.value), onClick: (e) => { var _a, _b; return (_b = (_a = e.currentTarget).showPicker) === null || _b === void 0 ? void 0 : _b.call(_a); }, type: "date", value: dateOfBirth
|
|
27
|
+
? new Date(dateOfBirth).toISOString().split("formTLabel")[0]
|
|
28
|
+
: "" }), _jsx(EnhancedInput, { error: errors.phone, id: "phone", info: t("formTeachersPhoneNumberLabel"), label: t("formPhoneLabel"), onChange: (e) => handleChange("phone", e.target.value), placeholder: t("formPhonePlaceholder"), type: "tel", value: phone || "" }), _jsx(EnhancedInput, { error: errors.emergencyPhone, id: "emergencyPhone", info: t("formEmergencyContactNumberLabel"), label: t("formEmergencyPhoneLabel"), onChange: (e) => handleChange("emergencyPhone", e.target.value), placeholder: t("formEmergencyPhonePlaceholder"), type: "tel", value: emergencyPhone || "" }), _jsx(EnhancedInput, { error: errors.qualification, id: "qualification", info: t("formTeachersEducationalQualificationLabel"), label: t("formQualificationLabel"), onChange: (e) => handleChange("qualification", e.target.value), placeholder: t("formQualificationPlaceholder"), value: qualification || "" }), _jsx(EnhancedInput, { error: errors.specialization, id: "specialization", info: t("formTeachersAreaOfSpecializationLabel"), label: t("formSpecializationLabel"), onChange: (e) => handleChange("specialization", e.target.value), placeholder: t("formSpecializationPlaceholder"), value: specialization || "" }), _jsx(EnhancedInput, { error: errors.experience, id: "experience", info: t("formYearsOfTeachingExperienceLabel"), label: t("formExperienceLabel"), onChange: (e) => handleChange("experience", parseInt(e.target.value) || null), placeholder: t("formExperiencePlaceholder"), type: "number", value: (experience === null || experience === void 0 ? void 0 : experience.toString()) || "" }), _jsx(EnhancedInput, { error: errors.address, id: "address", info: t("formStreetAddressLabel"), label: t("formAddressLabel"), onChange: (e) => handleChange("address", e.target.value), placeholder: t("formAddressPlaceholder"), value: address || "" }), _jsx(EnhancedInput, { error: errors.city, id: "city", info: t("formCityNameLabel"), label: t("formCityLabel"), onChange: (e) => handleChange("city", e.target.value), placeholder: t("formCityPlaceholder"), value: city || "" }), _jsx(EnhancedInput, { error: errors.state, id: "state", info: t("formStateOrProvinceLabel"), label: t("formStateProvinceLabel"), onChange: (e) => handleChange("state", e.target.value), placeholder: t("formStatePlaceholder"), value: stateValue || "" }), _jsx(EnhancedInput, { error: errors.country, id: "country", info: t("formCountryNameLabel"), label: t("formCountryLabel"), onChange: (e) => handleChange("country", e.target.value), placeholder: t("formCountryPlaceholder"), value: country || "" }), _jsx(EnhancedInput, { error: errors.postalCode, id: "postalCode", info: t("formPostalOrZipCodeLabel"), label: t("formPostalCodeLabel"), onChange: (e) => handleChange("postalCode", e.target.value), placeholder: t("formPostalCodePlaceholder"), value: postalCode || "" }), _jsx(EnhancedDropzone, { accept: ["image/*"], error: errors.avatar, id: "avatar", info: t("formUploadTeachersPhoto"), label: t("formAvatarLabel"), maxFiles: 1, maxSize: 100 * 1024, onChange: (files) => {
|
|
25
29
|
if (files.length > 0) {
|
|
26
|
-
|
|
27
|
-
handleChange("avatar", fileUrl);
|
|
30
|
+
handleAvatarUpload === null || handleAvatarUpload === void 0 ? void 0 : handleAvatarUpload(files[0]);
|
|
28
31
|
}
|
|
29
32
|
}, onRemoveRemote: () => handleChange("avatar", null), value: avatar ? [avatar] : [] }), _jsx(EnhancedTextarea, { error: errors.bio, id: "bio", info: t("formBriefBiographyOrDescriptionLabel"), label: t("formBioLabel"), onChange: (e) => handleChange("bio", e.target.value), placeholder: t("formBioPlaceholder"), rows: 4, value: bio || "" }), _jsx(EnhancedCheckbox, { checked: enabled, info: t("actionToggleActivateOrDeactivateTeacher"), label: t("formActiveTeacherLabel"), onCheckedChange: (checked) => handleChange("enabled", checked) })] }));
|
|
30
33
|
};
|
|
@@ -5,23 +5,23 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
export declare const teacherFormValidation: z.ZodObject<{
|
|
8
|
-
address: z.ZodOptional<z.ZodString
|
|
9
|
-
avatar: z.ZodOptional<z.ZodString
|
|
10
|
-
bio: z.ZodOptional<z.ZodString
|
|
11
|
-
city: z.ZodOptional<z.ZodString
|
|
12
|
-
country: z.ZodOptional<z.ZodString
|
|
8
|
+
address: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
9
|
+
avatar: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
10
|
+
bio: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
11
|
+
city: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
12
|
+
country: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
13
13
|
dateOfBirth: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
14
|
-
emergencyPhone: z.ZodOptional<z.ZodString
|
|
14
|
+
emergencyPhone: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
15
15
|
experience: z.ZodNullable<z.ZodOptional<z.ZodNumber>>;
|
|
16
16
|
firstName: z.ZodString;
|
|
17
17
|
gender: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
18
18
|
joiningDate: z.ZodString;
|
|
19
19
|
lastName: z.ZodString;
|
|
20
|
-
phone: z.ZodOptional<z.ZodString
|
|
21
|
-
postalCode: z.ZodOptional<z.ZodString
|
|
22
|
-
qualification: z.ZodOptional<z.ZodString
|
|
23
|
-
specialization: z.ZodOptional<z.ZodString
|
|
24
|
-
state: z.ZodOptional<z.ZodString
|
|
20
|
+
phone: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
21
|
+
postalCode: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
22
|
+
qualification: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
23
|
+
specialization: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
24
|
+
state: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
25
25
|
teacherCode: z.ZodString;
|
|
26
26
|
userId: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
27
27
|
}, z.core.$strip>;
|
|
@@ -8,15 +8,16 @@ import { z } from "zod";
|
|
|
8
8
|
// VALIDATION SCHEMA
|
|
9
9
|
// ============================================================================
|
|
10
10
|
export const teacherFormValidation = z.object({
|
|
11
|
-
address: z.string().optional(),
|
|
12
|
-
avatar: z.string().optional(),
|
|
13
|
-
bio: z.string().optional(),
|
|
14
|
-
city: z.string().optional(),
|
|
15
|
-
country: z.string().optional(),
|
|
11
|
+
address: z.string().optional().nullable(),
|
|
12
|
+
avatar: z.string().optional().nullable(),
|
|
13
|
+
bio: z.string().optional().nullable(),
|
|
14
|
+
city: z.string().optional().nullable(),
|
|
15
|
+
country: z.string().optional().nullable(),
|
|
16
16
|
dateOfBirth: z.string().optional().nullable(),
|
|
17
17
|
emergencyPhone: z
|
|
18
18
|
.string()
|
|
19
19
|
.optional()
|
|
20
|
+
.nullable()
|
|
20
21
|
.refine((val) => !val ||
|
|
21
22
|
/^([+]?[\s0-9]+)?(\d{3}|[(]?[0-9]+[)]?)?([-]?[\s]?[0-9])+$/.test(val), "Invalid phone number format"),
|
|
22
23
|
experience: z.number().optional().nullable(),
|
|
@@ -27,12 +28,13 @@ export const teacherFormValidation = z.object({
|
|
|
27
28
|
phone: z
|
|
28
29
|
.string()
|
|
29
30
|
.optional()
|
|
31
|
+
.nullable()
|
|
30
32
|
.refine((val) => !val ||
|
|
31
33
|
/^([+]?[\s0-9]+)?(\d{3}|[(]?[0-9]+[)]?)?([-]?[\s]?[0-9])+$/.test(val), "Invalid phone number format"),
|
|
32
|
-
postalCode: z.string().optional(),
|
|
33
|
-
qualification: z.string().optional(),
|
|
34
|
-
specialization: z.string().optional(),
|
|
35
|
-
state: z.string().optional(),
|
|
34
|
+
postalCode: z.string().optional().nullable(),
|
|
35
|
+
qualification: z.string().optional().nullable(),
|
|
36
|
+
specialization: z.string().optional().nullable(),
|
|
37
|
+
state: z.string().optional().nullable(),
|
|
36
38
|
teacherCode: z.string().min(1, "Teacher code is required"),
|
|
37
39
|
userId: z.string().optional().nullable(),
|
|
38
40
|
});
|