diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 53650e98..2239fa63 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -142,7 +142,9 @@ "CREATE_ROLE": "Create Role", "USER_DELETE_FAILED": "User deletion failed", "ASSIGN_ROLE": "Assign Role", - "ENABLE_ROLE": "Enable Role" + "ENABLE_ROLE": "Enable Role", + "SEND_INVITATION_TO_NEW_ADMIN": "Send Invitation to new cohort admin", + "SEND_INVITATION": "Send Invitation" }, "LOGIN_PAGE": { "USERNAME": "Username", @@ -474,5 +476,27 @@ "DELETE_USER_DETAILS": "Delete User Details", "DELETE_USER_CONFIRMATION_MESSAGE": "Are you sure you want to Archived this user?", "NO_CHANGES_TO_UPDATE": "No Changes to Update" - } + }, + "COHORTINVITATION": { + "TAKE_ACTION": "Take Action", + "INVITED_AS_ADMIN": "You have been invited to be the Admin of the cohort", + "SENT":"Sent", + "RECEIVED":"Received", + "ACCEPT_INVITATION": "Accept Invitation", + "REJECT_INVITATION": "Reject Invitation", + "NO_PENDING_REQUESTS_SENT": "No pending requests sent", + "NO_PENDING_INVITATIONS": "No pending invitations", + "INVITED_TO_JOIN_COHORT": "Invited to join cohort", + "INVITED_BY": "Invited by", + "REVOKE_INVITATION": "Revoke Invitation", + "DELETE_CONFIRMATION": "Revoke request of ", + "CANCEL": "Cancel", + "REVOKE": "Revoke", + "ACCEPTED_SUCCESS": "Accepted request successfully", + "REJECTED_SUCCESS": "Rejected request", + "ERROR_UPDATING_REQUEST": "Error in updating request", + "INVITATION_DELETED_SUCCESS": "Invitation deleted successfully", + "INVITATION_DELETED_ERROR": "Failed to delete invitation", + "FETCH_INVITATIONS_ERROR": "Failed to fetch invitations" + } } diff --git a/public/locales/hi/common.json b/public/locales/hi/common.json index c45dc088..6e00dbb1 100644 --- a/public/locales/hi/common.json +++ b/public/locales/hi/common.json @@ -26,7 +26,9 @@ "DELETE": "हटाएं", "NO_DATA_FOUND": "कोई डेटा नहीं मिला", "SUBMIT": "जमा करें", - "BACK": "वापस" + "BACK": "वापस", + "SEND_INVITATION_TO_NEW_ADMIN": "नए समूह एडमिन को आमंत्रण भेजें", + "SEND_INVITATION": "आमंत्रण भेजें" }, "LOGIN_PAGE": { "USERNAME": "उपयोगकर्ता नाम", @@ -158,5 +160,25 @@ "EMAIL": "ईमेल", "YEAR_OF_ JOINING_SCP": "एससीपी में शामिल होने का वर्ष", "ASSIGN_CENTERS": "केंद्र आवंटित करें" + }, + "COHORTINVITATION": { + "TAKE_ACTION": "कार्रवाई करें", + "INVITED_AS_ADMIN": "आपण कोहोर्टचे प्रशासक होण्यासाठी आमंत्रित आहात", + "ACCEPT_INVITATION": "निमंत्रण स्वीकारें", + "REJECT_INVITATION": "निमंत्रण अस्वीकारें", + "NO_PENDING_REQUESTS_SENT": "कोई लंबित अनुरोध नहीं", + "NO_PENDING_INVITATIONS": "कोई लंबित निमंत्रण नहीं", + "INVITED_TO_JOIN_COHORT": "कोहोर्ट में शामिल होने के लिए आमंत्रित किया गया", + "INVITED_BY": "द्वारा आमंत्रित", + "DELETE_INVITATION": "निमंत्रण हटाएं", + "DELETE_CONFIRMATION": "को भेजा गया निमंत्रण हटाएं", + "CANCEL": "रद्द करें", + "REVOKE": " निमंत्रण रद्द करें", + "ACCEPTED_SUCCESS": "अनुरोध सफलतापूर्वक स्वीकारा गया", + "REJECTED_SUCCESS": "अनुरोध अस्वीकार किया गया", + "ERROR_UPDATING_REQUEST": "अनुरोध अपडेट करने में त्रुटि", + "INVITATION_DELETED_SUCCESS": "निमंत्रण सफलतापूर्वक हटाया गया", + "INVITATION_DELETED_ERROR": "निमंत्रण हटाने में विफल", + "FETCH_INVITATIONS_ERROR": "निमंत्रण लाने में विफल" } } \ No newline at end of file diff --git a/public/locales/mr/common.json b/public/locales/mr/common.json index 31789647..c603676a 100644 --- a/public/locales/mr/common.json +++ b/public/locales/mr/common.json @@ -26,7 +26,9 @@ "DELETE": "हटवा", "NO_DATA_FOUND": "डेटा सापडला नाही", "SUBMIT": "सबमिट करा", - "BACK": "परत जा" + "BACK": "परत जा", + "SEND_INVITATION_TO_NEW_ADMIN": "नवीन कोहोर्ट प्रशासकाला आमंत्रण पाठवा", + "SEND_INVITATION": "आमंत्रण पाठवा" }, "LOGIN_PAGE": { "USERNAME": "वापरकर्ता नाव", @@ -158,5 +160,25 @@ "EMAIL": "ईमेल", "YEAR_OF_ JOINING_SCP": "एससीपीमध्ये सामील होण्याचे वर्ष", "ASSIGN_CENTERS": "केंद्रे नियुक्त करा" + }, + "COHORTINVITATION": { + "TAKE_ACTION": "कारवाई करा", + "INVITED_AS_ADMIN": "आपण कोहोर्टचे प्रशासक होण्यासाठी आमंत्रित आहात", + "ACCEPT_INVITATION": "निमंत्रण स्वीकारा", + "REJECT_INVITATION": "निमंत्रण नाकार करा", + "NO_PENDING_REQUESTS_SENT": "कोणतीही प्रलंबित विनंती नाही", + "NO_PENDING_INVITATIONS": "कोणतीही प्रलंबित निमंत्रणे नाहीत", + "INVITED_TO_JOIN_COHORT": "कोहोर्टमध्ये सामील होण्यासाठी आमंत्रित केले", + "INVITED_BY": "आमंत्रित करणारे", + "DELETE_INVITATION": "निमंत्रण हटवा", + "DELETE_CONFIRMATION": "याला पाठवलेले निमंत्रण हटवा", + "CANCEL": "रद्द करा", + "REVOKE": "निमंत्रण रद्द करा", + "ACCEPTED_SUCCESS": "विनंती यशस्वीरित्या स्वीकारली", + "REJECTED_SUCCESS": "विनंती नाकारली", + "ERROR_UPDATING_REQUEST": "विनंती अद्यतनित करताना त्रुटी", + "INVITATION_DELETED_SUCCESS": "निमंत्रण यशस्वीरित्या हटवले", + "INVITATION_DELETED_ERROR": "निमंत्रण हटवता आले नाही", + "FETCH_INVITATIONS_ERROR": "निमंत्रणे प्राप्त करताना त्रुटी" } } \ No newline at end of file diff --git a/public/locales/or/common.json b/public/locales/or/common.json index fd989feb..adce3d68 100644 --- a/public/locales/or/common.json +++ b/public/locales/or/common.json @@ -26,7 +26,9 @@ "DELETE": "ହଟାନ୍ତୁ", "NO_DATA_FOUND": "ତଥ୍ୟ ମିଳିଲା ନାହିଁ", "SUBMIT": "ଦାଖଲ କରନ୍ତୁ", - "BACK": "ପଛକୁ ଯାଆନ୍ତୁ" + "BACK": "ପଛକୁ ଯାଆନ୍ତୁ", + "SEND_INVITATION_TO_NEW_ADMIN": "ନୂତନ କୋହୋର୍ଟ ଏଡମିନ୍ କୁ ନିମନ୍ତ୍ରଣ ପଠାନ୍ତୁ", + "SEND_INVITATION": "ନିମନ୍ତ୍ରଣ ପଠାନ୍ତୁ" }, "LOGIN_PAGE": { "USERNAME": "ଉପଭୋକ୍ତା ନାମ", @@ -158,5 +160,25 @@ "EMAIL": "ଇମେଲ୍", "YEAR_OF_ JOINING_SCP": "SCP ମଧ୍ୟରେ ସାମିଲ ହେବାର ବର୍ଷ", "ASSIGN_CENTERS": "କେନ୍ଦ୍ର ସୁପର୍ଦ୍ଦ କରନ୍ତୁ" + }, + "COHORTINVITATION": { + "TAKE_ACTION": "କାର୍ଯ୍ୟ କରନ୍ତୁ", + "INVITED_AS_ADMIN": "ଆପଣ କୋହୋର୍ଟର ଏଡମିନ୍ ହେବାକୁ ନିମନ୍ତ୍ରିତ ହୋଇଛନ୍ତି", + "ACCEPT_INVITATION": "ନିମନ୍ତ୍ରଣ ଗ୍ରହଣ କରନ୍ତୁ", + "REJECT_INVITATION": "ନିମନ୍ତ୍ରଣ ଅସ୍ଵୀକାର କରନ୍ତୁ", + "NO_PENDING_REQUESTS_SENT": "କୌଣସି ଅପେକ୍ଷିତ ଅନୁରୋଧ ନାହିଁ", + "NO_PENDING_INVITATIONS": "କୌଣସି ଅପେକ୍ଷିତ ନିମନ୍ତ୍ରଣ ନାହିଁ", + "INVITED_TO_JOIN_COHORT": "କୋହୋର୍ଟ ଯୋଗଦେବାକୁ ନିମନ୍ତ୍ରିତ କରାଯାଇଛି", + "INVITED_BY": "ନିମନ୍ତ୍ରିତ କରିଛନ୍ତି", + "DELETE_INVITATION": "ନିମନ୍ତ୍ରଣ ବିଲୋପ କରନ୍ତୁ", + "DELETE_CONFIRMATION": "କୁ ପଠାଯାଇଥିବା ନିମନ୍ତ୍ରଣ ବିଲୋପ କରନ୍ତୁ", + "CANCEL": "ବାତିଲ୍ କରନ୍ତୁ", + "REVOKE": "ନିମନ୍ତ୍ରଣ ବାତିଲ୍ କରନ୍ତୁ", + "ACCEPTED_SUCCESS": "ଅନୁରୋଧ ସଫଳତାର ସହିତ ଗ୍ରହଣ କରାଯାଇଛି", + "REJECTED_SUCCESS": "ଅନୁରୋଧ ଅସ୍ଵୀକାର କରାଯାଇଛି", + "ERROR_UPDATING_REQUEST": "ଅନୁରୋଧ ଅଦ୍ୟତନ କରିବାରେ ତ୍ରୁଟି", + "INVITATION_DELETED_SUCCESS": "ନିମନ୍ତ୍ରଣ ସଫଳତାର ସହିତ ବିଲୋପ କରାଯାଇଛି", + "INVITATION_DELETED_ERROR": "ନିମନ୍ତ୍ରଣ ବିଲୋପ କରିପାରିଲେ ନାହିଁ", + "FETCH_INVITATIONS_ERROR": "ନିମନ୍ତ୍ରଣ ପ୍ରାପ୍ତି ଉପରେ ତ୍ରୁଟି" } } \ No newline at end of file diff --git a/src/components/layouts/header/Header.tsx b/src/components/layouts/header/Header.tsx index 3c82a403..d325ea45 100644 --- a/src/components/layouts/header/Header.tsx +++ b/src/components/layouts/header/Header.tsx @@ -1,20 +1,21 @@ import React, { useEffect, useRef, useState } from "react"; import FeatherIcon from "feather-icons-react"; -import { AppBar, Box, IconButton, Toolbar } from "@mui/material"; +import { AppBar, Badge, Box, IconButton, Toolbar } from "@mui/material"; import MenuItem from "@mui/material/MenuItem"; import Select, { SelectChangeEvent } from "@mui/material/Select"; import config from "../../../../config.json"; import PropTypes from "prop-types"; import Image from "next/image"; -import TranslateIcon from '@mui/icons-material/Translate'; -import Menu from '@mui/material/Menu'; +import TranslateIcon from "@mui/icons-material/Translate"; +import Menu from "@mui/material/Menu"; import SearchBar from "./SearchBar"; -import { useRouter } from 'next/router'; - - +import { useRouter } from "next/router"; +import MailIcon from "@mui/icons-material/Mail"; import { useTranslation } from "next-i18next"; import { createTheme } from "@mui/material/styles"; import Profile from "./Profile"; +import { Mail } from "@mui/icons-material"; +import InvitationMenu from "./Invitation"; const Header = ({ sx, customClass, toggleMobileSidebar, position }: any) => { const { t } = useTranslation(); @@ -28,26 +29,24 @@ const Header = ({ sx, customClass, toggleMobileSidebar, position }: any) => { const [language, setLanguage] = useState(selectedLanguage); useEffect(() => { - if (typeof window !== 'undefined' && window.localStorage) { - const lang = localStorage.getItem('preferredLanguage') || 'en'; + if (typeof window !== "undefined" && window.localStorage) { + const lang = localStorage.getItem("preferredLanguage") || "en"; setLanguage(lang); - } }, [setLanguage]); const handleChange = (event: SelectChangeEvent) => { const newLocale = event.target.value; setLanguage(newLocale); - if (typeof window !== 'undefined' && window.localStorage) { - localStorage.setItem('preferredLanguage', newLocale); + if (typeof window !== "undefined" && window.localStorage) { + localStorage.setItem("preferredLanguage", newLocale); router.replace(router.pathname, router.asPath, { locale: newLocale }); } }; const handleClick = (event: React.MouseEvent) => { - console.log(event) + console.log(event); setAnchorEl(event.currentTarget); - console.log(anchorEl) - + console.log(anchorEl); }; const handleClose = () => { setAnchorEl(null); @@ -55,12 +54,13 @@ const Header = ({ sx, customClass, toggleMobileSidebar, position }: any) => { const handleMenuItemClick = (newLocale: any) => { console.log(newLocale); setLanguage(newLocale); - if (typeof window !== 'undefined' && window.localStorage) { - localStorage.setItem('preferredLanguage', newLocale); + if (typeof window !== "undefined" && window.localStorage) { + localStorage.setItem("preferredLanguage", newLocale); router.replace(router.pathname, router.asPath, { locale: newLocale }); } handleClose(); }; + return ( @@ -76,7 +76,6 @@ const Header = ({ sx, customClass, toggleMobileSidebar, position }: any) => { }, }} > - {/* ------------------------------------------- */} @@ -90,8 +89,7 @@ const Header = ({ sx, customClass, toggleMobileSidebar, position }: any) => { - - { > - - */} - anchorEl={anchorEl} - open={open} - onClose={handleClose} - PaperProps={{ - style: { - // maxHeight: ITEM_HEIGHT * 4.5, - width: '20ch', - }, - }} - > - {config.languages.map((lang) => ( - handleMenuItemClick(lang.code)} - - sx={{ - backgroundColor: lang.code === language ? 'rgba(0, 0, 0, 0.08)' : 'inherit', - '&:hover': { - backgroundColor: lang.code === language ? 'rgba(0, 0, 0, 0.12)' : 'rgba(0, 0, 0, 0.08)', - }, - }} - > - {lang.label} - - ))} - - + + + {config.languages.map((lang) => ( + handleMenuItemClick(lang.code)} + sx={{ + backgroundColor: + lang.code === language ? "rgba(0, 0, 0, 0.08)" : "inherit", + "&:hover": { + backgroundColor: + lang.code === language + ? "rgba(0, 0, 0, 0.12)" + : "rgba(0, 0, 0, 0.08)", + }, + }} + > + {lang.label} + + ))} + + {/* ------------------------------------------- */} {/* Profile Dropdown */} {/* ------------------------------------------- */} diff --git a/src/components/layouts/header/Invitation.tsx b/src/components/layouts/header/Invitation.tsx new file mode 100644 index 00000000..9ac45ebc --- /dev/null +++ b/src/components/layouts/header/Invitation.tsx @@ -0,0 +1,441 @@ +import React, { useState, useEffect } from "react"; +import { + Badge, + IconButton, + Menu, + Box, + Typography, + Tabs, + Tab, + Card, + CardContent, + Button, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Stack, + Alert, + Divider, + Tooltip, + Snackbar, +} from "@mui/material"; +import { Check, Close, Delete } from "@mui/icons-material"; +import ErrorOutlinedIcon from "@mui/icons-material/ErrorOutlined"; +import NotificationsNoneOutlinedIcon from "@mui/icons-material/NotificationsNoneOutlined"; +import { + fetchInvitationsRequest, + updateInvitation, +} from "@/services/InvitationService"; +import { showToastMessage } from "@/components/Toastify"; +import { useTranslation } from "react-i18next"; + +// Mock API response (can be replaced with actual API call) +interface Invitation { + invitationId?: string; + tenantId?: string; + cohortId?: string; + cohortName?: string; + invitedTo?: string; + invitedBy?: string; + invitationStatus?: string; + sentAt?: string; + updatedAt?: string; +} + +const InvitationMenu = () => { + // State variables + const { t } = useTranslation(); + const [anchorEl, setAnchorEl] = useState(null); + const [tabValue, setTabValue] = useState(0); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(""); + const [success, setSuccess] = useState(""); + const [confirmDelete, setConfirmDelete] = useState(null); + const [receivedInvitations, setReceivedInvitations] = useState( + [] + ); + const [sentInvitations, setSentInvitations] = useState([]); + // Notification states + const [notifications, setNotifications] = useState([]); + const [currentNotification, setCurrentNotification] = + useState(null); + const [notificationCount, setNotificationCount] = useState(0); + // Menu open state + const open = Boolean(anchorEl); + + const fetchInitialInvitations = async () => { + try { + const response = await fetchInvitationsRequest(); + + const received = response.receivedInvitations.filter( + (inv: Invitation) => inv.invitationStatus === "Pending" + ); + const sent = response.sentInvitations; + setSentInvitations(sent); + setReceivedInvitations(received); + setNotificationCount(received.length); + + // Transform into notifications + const newNotifications = received.map((invitation: Invitation) => ({ + id: invitation.invitationId, + cohortName: invitation.cohortName, + invitedBy: invitation.invitedBy, + })); + + setNotifications(newNotifications); + } catch (err) { + setError("Failed to fetch invitations"); + } + }; + // Fetch initial invitations on component mount + useEffect(() => { + fetchInitialInvitations(); + }, []); + + // Notification cycling logic + useEffect(() => { + if (notifications.length === 0) return; + + // If no current notification, show the first one + if (!currentNotification) { + setCurrentNotification(notifications[0]); + } + + // Set a timer to cycle through notifications + const timer = setTimeout(() => { + // Remove current notification + setNotifications((prev) => prev.slice(1)); + // Clear current notification + setCurrentNotification(null); + }, 5000); // 5 seconds + + return () => clearTimeout(timer); + }, [notifications, currentNotification]); + + // Handle menu open + const handleClick = (event: any) => { + setAnchorEl(event.currentTarget); + // Clear current notification when menu opens + setCurrentNotification(null); + setNotifications([]); + setTabValue(1); // Open Received tab + }; + + // Handle menu close + const handleClose = () => { + setAnchorEl(null); + setError(""); + setSuccess(""); + }; + + // Handle notification close + const handleCloseNotification = () => { + setCurrentNotification(null); + setNotifications((prev) => prev.slice(1)); + }; + + // Handle take action - open menu + const handleTakeAction = () => { + const notificationTrigger = document.getElementById("notification-trigger"); + if (notificationTrigger) { + handleClick({ currentTarget: notificationTrigger }); + } + }; + ///revoke invitation + const deleteInvitation = async (invitationId: string | undefined) => { + setLoading(true); + try { + setSentInvitations((prev) => + prev.filter((invite) => invite.invitationId !== invitationId) + ); + showToastMessage("Invitation deleted successfully", "success"); + } catch (err) { + showToastMessage("Failed to delete invitation", "error"); + } + setLoading(false); + setConfirmDelete(null); + }; + // Update invitation status + const updateInvitationStatus = async ( + tenantId: any, + invitationId: string | undefined, + invitationStatus: string | undefined + ) => { + try { + // Simulate API call + console.log("invitation", tenantId, invitationId); + + await updateInvitation({ + invitationId, + invitationStatus, + tenantId, + }); + + if (invitationStatus === "Accepted") { + showToastMessage(t("COHORTINVITATION.ACCEPTED_SUCCESS"), "success"); + } else { + showToastMessage(t("COHORTINVITATION.REJECTED_SUCCESS"), "success"); + } + fetchInitialInvitations(); + } catch (err) { + setError("Failed to update invitation status"); + showToastMessage(t("COHORTINVITATION.ERROR_UPDATING_REQUEST"), "error"); + } + }; + + return ( + <> + {/* Notification Display */} + {currentNotification && ( + + + + } + onClose={handleCloseNotification} + action={ + <> + + + + + + } + color="info" + sx={{ width: "200%", bgcolor: "#FFF3CB" }} + > + {t("COHORTINVITATION.INVITED_AS_ADMIN")}:{" "} + {currentNotification.cohortName} + + + + )} + + {/* Notification Icon */} + + + + + + + {/* Invitation Menu */} + + + {/* Error and Success Alerts */} + {error && ( + + {error} + + )} + {success && ( + + {success} + + )} + + + + {/* Tabs */} + setTabValue(newValue)} + variant="fullWidth" + sx={{ mb: 2 }} + > + + + + + {/* Sent Invitations */} + + {tabValue === 0 && ( + + {sentInvitations.length === 0 && ( + + {t("COHORTINVITATION.NO_PENDING_REQUESTS_SENT")} + + )} + {sentInvitations.map((invitation) => ( + + + + + Invited to {invitation.invitedTo} to join cohort{" "} + {invitation.cohortName} + + + + setConfirmDelete(invitation)} + > + + + + + + + ))} + + )} + {/* Received Invitations */} + {tabValue === 1 && ( + + {notificationCount === 0 && ( + + {t("COHORTINVITATION.NO_PENDING_INVITATIONS")} + + )} + {receivedInvitations.map((invitation) => ( + + + + + {t("COHORTINVITATION.INVITED_BY")}{" "} + {invitation.invitedBy} to cohort{" "} + {invitation.cohortName} + + + + + + updateInvitationStatus( + invitation.tenantId, + invitation.invitationId, + "Accepted" + ) + } + > + + + + + + updateInvitationStatus( + invitation.tenantId, + invitation.invitationId, + "Rejected" + ) + } + > + + + + + + + + ))} + + )} + + + {/* Delete Confirmation Dialog */} + setConfirmDelete(null)}> + Delete Invitation + + + {t("COHORTINVITATION.DELETE_CONFIRMATION")}{" "} + {confirmDelete?.invitedTo}? + + + + + + + + + ); +}; + +export default InvitationMenu; diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 10ed14ab..b6fe65c8 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -64,7 +64,7 @@ function App({ Component, pageProps }: AppProps) { await registerUser(); } if (keycloak.refreshToken) { - console.log("Get keycloak token"); + console.log("Get keycloak refresh token token", keycloak); localStorage.setItem("refreshToken", keycloak.refreshToken); } } catch (error) { diff --git a/src/pages/cohortAdminSchema.json b/src/pages/cohortAdminSchema.json index 533e9de6..5435ca96 100644 --- a/src/pages/cohortAdminSchema.json +++ b/src/pages/cohortAdminSchema.json @@ -1,67 +1,14 @@ { "type": "object", - "required": ["name", "username", "password"], + "required": ["email"], "properties": { - "name": { - "title": "Cohort Admin Name", - "type": "string", - "fieldId": null, - "validation": [], - "description": "Enter Full Name." - }, - "mobileNo": { - "title": "Mobile Number", - "fieldId": null, - "type": "string", - "pattern": "^[6-9]\\d{9}$", - "validation": ["mobile"] - }, "email": { "title": "Email Address", "type": "string", "format": "email", "pattern": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$", "validation": ["required"] - }, - "username": { - "title": "Username", - "fieldId": null, - "type": "string", - "minLength": 3, - "pattern": "^[a-zA-Z0-9!@#$%^&*()_+={}:;<>?,./~`|\\-]+$", - "validation": [], - "ui:options": { - "inputProps": { - "autoComplete": "off" - } - } - }, - "password": { - "title": "Password", - "fieldId": null, - "type": "string", - "pattern": "^(?=.*[A-Z])(?=.*[a-z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$", - "validation": [], - "ui:options": { - "inputProps": { - "autoComplete": "new-password" - } - } - }, - - "role": { - "title": "Role", - "type": "string", - "readOnly": true, - "oneOf": [ - { - "title": "Cohort Admin", - "const": "cohort_admin" - } - ], - "default": "cohort_admin", - "validation": [] } }, "dependencies": {} -} +} \ No newline at end of file diff --git a/src/pages/cohorts.tsx b/src/pages/cohorts.tsx index e080bcee..9a0dc458 100644 --- a/src/pages/cohorts.tsx +++ b/src/pages/cohorts.tsx @@ -38,6 +38,7 @@ import AddIcon from "@mui/icons-material/Add"; import userJsonSchema from "./userSchema.json"; import cohortASchema from "./cohortAdminSchema.json"; import updateCohortSchema from "./cohortUpdateSchema.json"; +import { sendRequest } from "@/services/InvitationService"; type cohortFilterDetails = { type?: string; @@ -186,47 +187,47 @@ const Center: React.FC = () => { // }, }; const cohortAdminUiSchema = { - name: { - "ui:widget": "text", - "ui:placeholder": "Enter your full name", - "ui:help": "Full name, numbers, letters and spaces are allowed.", - }, - username: { - "ui:widget": "text", - "ui:placeholder": "Enter your username", - "ui:help": "Username must be at least 3 characters long.", - "ui:options": { - autocomplete: false, - value: "", - }, - "ui:inputProps": { - autocomplete: false, - value: "", - }, - }, - password: { - "ui:widget": "password", - "ui:placeholder": "Enter a secure password", - "ui:help": - "Password must be at least 8 characters long and contain one uppercase letter, one lowercase letter, one number, and one special character.", - "ui:options": { - autocomplete: false, - }, - "ui:inputProps": { - autocomplete: false, - name: "password", - }, - }, - role: { - "ui:widget": "select", - "ui:placeholder": "Select a role", - // "ui:help": "Select a role.", - }, - mobileNo: { - "ui:widget": "text", - "ui:placeholder": "Mobile number", - "ui:help": "Enter a valid 10-digit mobile number.", - }, + // name: { + // "ui:widget": "text", + // "ui:placeholder": "Enter your full name", + // "ui:help": "Full name, numbers, letters and spaces are allowed.", + // }, + // username: { + // "ui:widget": "text", + // "ui:placeholder": "Enter your username", + // "ui:help": "Username must be at least 3 characters long.", + // "ui:options": { + // autocomplete: false, + // value: "", + // }, + // "ui:inputProps": { + // autocomplete: false, + // value: "", + // }, + // }, + // password: { + // "ui:widget": "password", + // "ui:placeholder": "Enter a secure password", + // "ui:help": + // "Password must be at least 8 characters long and contain one uppercase letter, one lowercase letter, one number, and one special character.", + // "ui:options": { + // autocomplete: false, + // }, + // "ui:inputProps": { + // autocomplete: false, + // name: "password", + // }, + // }, + // role: { + // "ui:widget": "select", + // "ui:placeholder": "Select a role", + // // "ui:help": "Select a role.", + // }, + // mobileNo: { + // "ui:widget": "text", + // "ui:placeholder": "Mobile number", + // "ui:help": "Enter a valid 10-digit mobile number.", + // }, email: { // "ui:widget": "text", "ui:placeholder": "Enter your email address", @@ -861,7 +862,6 @@ const Center: React.FC = () => { }; fetchData(); }, [isCreateCohortAdminModalOpen, Addmodalopen]); - const handleAddCohortAdminAction = async ( data: IChangeEvent, event: React.FormEvent @@ -877,21 +877,12 @@ const Center: React.FC = () => { ); let obj = { - name: formData?.name.replace(/\s/g, ""), - username: formData?.username.replace(/\s/g, ""), - password: formData?.password, - mobile: formData?.mobileNo, - email: formData?.email, - - tenantCohortRoleMapping: [ - { - roleId: cohortAdminRole?.roleId, - tenantId: selectedRowData?.tenantId, - cohortId: [selectedRowData?.cohortId], - }, - ], + invitedTo: formData?.email, + tenantId: selectedRowData?.tenantId, + cohortId: selectedRowData?.cohortId, }; - const resp = await userCreate(obj as any, selectedRowData?.tenantId); + ///sent invitation + const resp = await sendRequest(obj as any, selectedRowData?.tenantId); if (resp?.responseCode === 200 || resp?.responseCode === 201) { showToastMessage( @@ -914,7 +905,6 @@ const Center: React.FC = () => { setIsEditForm(false); } }; - const userProps = { tenants: listOfTenants, userType: t("COHORTS.COHORTS"), @@ -1202,7 +1192,7 @@ const Center: React.FC = () => { setSubmittedButtonStatus(true); }} > - {t("COMMON.ADD")} + {t("COMMON.SUBMIT")} diff --git a/src/services/InvitationService.ts b/src/services/InvitationService.ts new file mode 100644 index 00000000..8c46c8ae --- /dev/null +++ b/src/services/InvitationService.ts @@ -0,0 +1,76 @@ +import { updateInvitationStatusParams } from "@/utils/Interfaces"; +import { get } from "./RestClient"; +import config from "@/utils/urlConstants.json"; +import axios from "axios"; +import { cohortListData } from "./CohortService/cohortService"; +export const updateInvitation = async ({ + invitationId, + invitationStatus, + tenantId, +}: updateInvitationStatusParams): Promise => { + const apiUrl: string = `${process.env.NEXT_PUBLIC_BASE_URL}/${config.URLS.UPDATE_INVITATIONS_STATUS}${invitationId}`; + + try { + const token = localStorage.getItem("token"); + + const response = await axios.patch( + apiUrl, + { invitationStatus }, // Direct object instead of JSON.stringify + { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + Accept: "application/json, text/plain, */*", + tenantId, + }, + } + ); + + return response?.data; + } catch (error) { + console.error("Error in updating invitation status:", error); + return error; + } +}; + +export const fetchInvitationsRequest = async (): Promise => { + const token = localStorage.getItem("token"); + const headers = { + Authorization: `Bearer ${token}`, + }; + const apiUrl: string = `${process.env.NEXT_PUBLIC_BASE_URL}/${config.URLS.FETCH_INVITATIONS}`; + try { + const response = await get(apiUrl, headers); + return response?.data?.result; + } catch (error) { + return error; + } +}; + +export const sendRequest = async ( + data: cohortListData, + userTenantId: string +): Promise => { + const apiUrl: string = `${process.env.NEXT_PUBLIC_BASE_URL}/${config.URLS.SEND_INVITATION_REQUEST}`; + + try { + const token = localStorage.getItem("token"); + const headers = { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + tenantid: userTenantId, + }; + + const response = await axios.post(apiUrl, data, { headers }); + return response?.data; + } catch (error: unknown) { + let errorMessage = "An unexpected error occurred."; + + if (axios.isAxiosError(error) && error.response) { + errorMessage = error.response.data?.params?.err || "Error from API."; + } + + console.error("Error in creating user:", error); + throw new Error(errorMessage); + } +}; diff --git a/src/utils/Interfaces.ts b/src/utils/Interfaces.ts index 25a0d473..336cbb06 100644 --- a/src/utils/Interfaces.ts +++ b/src/utils/Interfaces.ts @@ -96,12 +96,11 @@ export interface CoursePlannerMetaData { medium: string; } - export interface GetTargetedSolutionsParams { - subject:string, + subject: string; state: string; - - medium: string + + medium: string; class: string; board: string; type: string; @@ -118,3 +117,8 @@ export interface GetUserProjectTemplateParams { role: string; } +export interface updateInvitationStatusParams { + invitationId: string | undefined; + invitationStatus: string | undefined; + tenantId: string | undefined; +} diff --git a/src/utils/urlConstants.json b/src/utils/urlConstants.json index 6e5b37f0..e680e156 100644 --- a/src/utils/urlConstants.json +++ b/src/utils/urlConstants.json @@ -34,6 +34,10 @@ "ROLES_LIST": "rbac/roles/list/roles", "USER_CREATE": "create", "USER_DELETE": "delete", - "USER_REGISTER": "auth/validateAndRegister" + "USER_REGISTER": "auth/validateAndRegister", + "FETCH_INVITATIONS":"invitation/getall", + "UPDATE_INVITATIONS_STATUS":"invitation/update?id=", + "SEND_INVITATION_REQUEST":"invitation/sendinvite", + "DELETE_INVITATION_REQUEST":"" } }