diff --git a/README.md b/README.md index f6515c02bf..f522cb925a 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Core features include: - [Talawa Components](#talawa-components) -- [](#documentation--) +- [Documentation](#documentation) - [Installation](#installation) diff --git a/jest.config.js b/jest.config.js index 0227b05d77..34f219ba06 100644 --- a/jest.config.js +++ b/jest.config.js @@ -25,8 +25,9 @@ export default { moduleNameMapper: { '^react-native$': 'react-native-web', '^@mui/(.*)$': '/node_modules/@mui/$1', - '^@dicebear/core$': '/__mocks__/@dicebear/core.ts', - '^@dicebear/collection$': '/__mocks__/@dicebear/collection.ts', + '^@dicebear/core$': '/scripts/__mocks__/@dicebear/core.ts', + '^@dicebear/collection$': + '/scripts/__mocks__/@dicebear/collection.ts', }, moduleFileExtensions: [ 'web.js', diff --git a/public/locales/en.json b/public/locales/en.json index e50ffe89f5..f3d5aa1304 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -4,6 +4,7 @@ "fromPalisadoes": "An open source application by Palisadoes Foundation volunteers", "talawa_portal": "Talawa Admin Portal", "login": "Login", + "userLogin": "User Login", "register": "Register", "firstName": "First Name", "lastName": "Last Name", @@ -38,36 +39,6 @@ "numeric_value_check": "Atleaset one numeric value", "special_char_check": "Atleast one special character" }, - "userLoginPage": { - "title": "Talawa Admin", - "fromPalisadoes": "An open source application by Palisadoes Foundation volunteers", - "talawa_portal": "Talawa Admin Portal", - "login": "Login", - "register": "Register", - "firstName": "First Name", - "lastName": "Last Name", - "email": "Email", - "password": "Password", - "atleast_8_char_long": "Atleast 8 Character long", - "Password_and_Confirm_password_mismatches.": "Password and Confirm password mismatches.", - "confirmPassword": "Confirm Password", - "forgotPassword": "Forgot Password ?", - "enterEmail": "Enter Email", - "enterPassword": "Enter Password", - "doNotOwnAnAccount": "Do not own an account?", - "talawaApiUnavailable": "Talawa-API service is unavailable. Is it running? Check your network connectivity too.", - "captchaError": "Captcha Error!", - "Please_check_the_captcha": "Please, check the captcha.", - "Something_went_wrong": "Something went wrong, Please try after sometime.", - "passwordMismatches": "Password and Confirm password mismatches.", - "fillCorrectly": "Fill all the Details Correctly.", - "notAuthorised": "Sorry! you are not Authorised!", - "notFound": "User not found!", - "successfullyRegistered": "Successfully Registered. Please wait until you will be approved.", - "userLogin": "User Login", - "afterRegister": "Successfully registered. Please wait for admin to approve your request.", - "OR": "OR" - }, "latestEvents": { "eventCardTitle": "Upcoming Events", "eventCardSeeAll": "See All", @@ -199,7 +170,12 @@ "roleUpdated": "Role Updated.", "noResultsFoundFor": "No results found for ", "talawaApiUnavailable": "Talawa-API service is unavailable. Is it running? Check your network connectivity too.", - "cancel": "Cancel" + "cancel": "Cancel", + "admins": "Admins", + "members": "Members", + "joinNow": "Join Now", + "joined": "Joined", + "withdraw": "Widthdraw" }, "communityProfile": { "communityProfile": "Community Profile", @@ -380,7 +356,9 @@ "sortPost": "Sort Post", "tag": " Your browser does not support the video tag", "postCreatedSuccess": "Congratulations! You have Posted Something.", - "pinPost": "Pin post" + "pinPost": "Pin post", + "Next": "Next Page", + "Previous": "Previous Page" }, "postNotFound": { "post": "Post", @@ -666,7 +644,8 @@ "createdOrganizations": "Created Organizations", "search": "Search", "nothingToShow": "Nothing to show here.", - "selectOrganization": "Select Organization" + "organizations": "Organizations", + "searchByName": "Search By Name" }, "userSidebar": { "yourOrganizations": "Your Organizations", @@ -716,14 +695,7 @@ "nothingToShow": "Nothing to show here.", "search": "Search", "createEvent": "Create Event", - "eventTitle": "Event Title", - "eventDescription": "Event Description", - "eventLocation": "Event Location", - "startDate": "Select Start Date", - "endDate": "Select End Date", - "publicEvent": "Public Event", - "registerable": "Registerable", - "recurring": "Recurring", + "recurring": "Recurring Event", "startTime": "Start Time", "endTime": "End Time", "cancel": "Cancel", @@ -731,7 +703,18 @@ "listView": "List View", "calendarView": "Calendar View", "allDay": "All Day", - "eventCreated": "Event created and posted successfully." + "eventCreated": "Event created and posted successfully.", + "eventDetails": "Event Details", + "eventTitle": "Title", + "enterTitle": "Enter Title", + "eventDescription": "Description", + "enterDescription": "Enter Description", + "eventLocation": "Location", + "enterLocation": "Enter Location", + "startDate": "Start Date", + "endDate": "End Date", + "publicEvent": "Is Public", + "registerable": "Is Registerable" }, "userEventCard": { "location": "Location", diff --git a/public/locales/fr.json b/public/locales/fr.json index a01d8402d5..06f18512b6 100644 --- a/public/locales/fr.json +++ b/public/locales/fr.json @@ -4,6 +4,7 @@ "talawa_portal": "Portail D'Administrateur Talawa", "fromPalisadoes": "Une application open source par les volontaires de la Fondation Palissades", "login": "Connexion", + "userLogin": "Utilisateur en ligne", "register": "S'inscrire", "firstName": "Prénom", "lastName": "Nom de famille", @@ -38,36 +39,6 @@ "special_char_check": "Au moins un caractère spécial", "numeric_value_check": "Au moins une valeur numérique" }, - "userLoginPage": { - "title": "Administrateur Talawa", - "talawa_portal": "Portail D'Administrateur Talawa", - "fromPalisadoes": "Une application open source par les volontaires de la Fondation Palissades", - "login": "Connexion", - "register": "S'inscrire", - "firstName": "Prénom", - "lastName": "Nom de famille", - "email": "E-mail", - "password": "Mot de passe", - "atleast_8_char_long": "Au moins 8 caractères", - "Password_and_Confirm_password_mismatches.": "Le mot de passe et la confirmation du mot de passe ne correspondent pas.", - "confirmPassword": "Confirmez le mot de passe", - "forgotPassword": "Mot de passe oublié ?", - "enterEmail": "entrez l'e-mail", - "enterPassword": "Entrer le mot de passe", - "doNotOwnAnAccount": "Vous n'avez pas de compte ?", - "talawaApiUnavailable": "Le service Talawa-API n'est pas disponible. Est-il en cours d'exécution ? Vérifiez également votre connectivité réseau.", - "captchaError": "Erreur de captcha !", - "Please_check_the_captcha": "Veuillez vérifier le captcha.", - "Something_went_wrong": "Quelque chose s'est mal passé, veuillez réessayer plus tard.", - "passwordMismatches": "Le mot de passe et la confirmation du mot de passe ne correspondent pas.", - "fillCorrectly": "Remplissez tous les détails correctement.", - "notAuthorised": "Désolé ! vous n'êtes pas autorisé !", - "notFound": "Utilisateur introuvable !", - "successfullyRegistered": "Enregistré avec succès. Veuillez patienter jusqu'à ce que vous soyez approuvé.", - "userLogin": "Utilisateur en ligne", - "afterRegister": "Enregistré avec succès. Veuillez attendre que l'administrateur approuve votre demande.", - "OR": "OU" - }, "latestEvents": { "eventCardTitle": "Événements à venir", "eventCardSeeAll": "Voir Tout", @@ -192,7 +163,12 @@ "roleUpdated": "Rôle mis à jour.", "noResultsFoundFor": "Aucun résultat trouvé pour ", "talawaApiUnavailable": "Le service Talawa-API n'est pas disponible. Est-il en cours d'exécution ? Vérifiez également votre connectivité réseau.", - "cancel": "Annuler" + "cancel": "Annuler", + "admins": "Administrateurs", + "members": "Membres", + "joinNow": "Adhérer maintenant", + "joined": "Rejointes", + "withdraw": "Retirer" }, "dashboard": { "title": "Tableau de bord", @@ -364,7 +340,9 @@ "sortPost": "Trier les publications", "tag": "Votre navigateur ne prend pas en charge la balise vidéo", "postCreatedSuccess": "Félicitations ! Vous avez publié quelque chose.", - "pinPost": "Épingler le message" + "pinPost": "Épingler le message", + "Next": "Suivant ", + "Previous": "Précédent" }, "postNotFound": { "post": "Poste", @@ -650,7 +628,9 @@ "createdOrganizations": "Organisations créées", "search": "Recherche", "nothingToShow": "Rien à montrer ici.", - "selectOrganization": "Sélectionnez une organisation" + "selectOrganization": "Sélectionnez une organisation", + "organizations": "Organizations", + "searchByName": "Recherche par nom" }, "userSidebar": { "yourOrganizations": "Vos organisations", @@ -700,13 +680,6 @@ "nothingToShow": "Rien à montrer ici.", "search": "Recherche", "createEvent": "Créer un évènement", - "eventTitle": "Titre de l'événement", - "eventDescription": "Description de l'évenement", - "eventLocation": "Lieu de l'événement", - "startDate": "Sélectionnez la date de début", - "endDate": "Sélectionnez la date de fin", - "publicEvent": "Évennement publique", - "registerable": "Enregistrable", "recurring": "Récurrente", "startTime": "Heure de début", "endTime": "Heure de fin", @@ -715,7 +688,18 @@ "listView": "Vue en liste", "calendarView": "Vue du calendrier", "allDay": "Toute la journée", - "eventCreated": "Événement créé et publié avec succès." + "eventCreated": "Événement créé et publié avec succès.", + "eventDetails": "Détails de l'événement", + "eventTitle": "Titre", + "enterTitle": "Entrez le titre", + "eventDescription": "Description", + "enterDescription": "Entrez la description", + "eventLocation": "Emplacement", + "enterLocation": "Entrez l'emplacement", + "startDate": "Date de début", + "endDate": "Date de fin", + "publicEvent": "Est public", + "registerable": "Est enregistrable" }, "userEventCard": { "location": "Emplacement", diff --git a/public/locales/hi.json b/public/locales/hi.json index e44864f802..cffd2aa09a 100644 --- a/public/locales/hi.json +++ b/public/locales/hi.json @@ -4,6 +4,7 @@ "fromPalisadoes": "पलिसाडो के स्वयंसेवकों द्वारा एक खुला स्रोत अनुप्रयोग", "talawa_portal": "तलावा प्रशासन पोर्टल", "login": "लॉग इन करें", + "userLogin": "उपयोगकर्ता लॉगिन", "register": "पंजीकरण करवाना", "firstName": "पहला नाम", "lastName": "उपनाम", @@ -38,36 +39,6 @@ "numeric_value_check": "कम से कम एक संख्यात्मक मान", "special_char_check": "कम से कम एक विशेष पात्र" }, - "userLoginPage": { - "title": "तलवा व्यवस्थापक", - "fromPalisadoes": "पलिसाडो के स्वयंसेवकों द्वारा एक खुला स्रोत अनुप्रयोग", - "talawa_portal": "तलावा प्रशासन पोर्टल", - "login": "लॉग इन करें", - "register": "पंजीकरण करवाना", - "firstName": "पहला नाम", - "lastName": "उपनाम", - "email": "ईमेल", - "password": "पासवर्ड", - "atleast_8_char_long": "कम से कम 8 कैरेक्टर लंबा", - "Password_and_Confirm_password_mismatches.": "पासवर्ड और पुष्टि पासवर्ड बेमेल।", - "confirmPassword": "पासवर्ड की पुष्टि कीजिये", - "forgotPassword": "पासवर्ड भूल गए ?", - "enterEmail": "ईमेल दर्ज करें", - "enterPassword": "पास वर्ड दर्ज करें", - "doNotOwnAnAccount": "क्या आपके पास खाता नहीं है?", - "talawaApiUnavailable": "तलावा-एपीआई सेवा उपलब्ध नहीं है। क्या यह चल रही है? अपनी नेटवर्क कनेक्टिविटी की भी जाँच करें।", - "captchaError": "कैप्चा त्रुटि!", - "Please_check_the_captcha": "कृपया, कैप्चा जांचें।", - "Something_went_wrong": "कुछ गलत हुआ, कृपया कुछ समय बाद प्रयास करें।", - "passwordMismatches": "पासवर्ड और पुष्टि पासवर्ड मेल नहीं खाते।", - "fillCorrectly": "सभी विवरण सही ढंग से भरें।", - "notAuthorised": "क्षमा करें! आप अधिकृत नहीं हैं!", - "notFound": "उपयोगकर्ता नहीं मिला!", - "successfullyRegistered": "सफलतापूर्वक पंजीकृत। कृपया स्वीकृत होने तक प्रतीक्षा करें।", - "afterRegister": "पंजीकरण सफलतापूर्वक हो गया है। कृपया आपके अनुरोध को स्वीकार करने के लिए व्यवस्थापक की प्रतीक्षा करें।", - "userLogin": "उपयोगकर्ता लॉगिन", - "OR": "या" - }, "latestEvents": { "eventCardTitle": "आगामी घटनाएँ", "eventCardSeeAll": "सभी देखें", @@ -192,7 +163,12 @@ "roleUpdated": "भूमिका अपडेट की गई।", "noResultsFoundFor": "के लिए कोई परिणाम नहीं मिला ", "talawaApiUnavailable": "तलवा-एपीआई सेवा उपलब्ध नहीं है। क्या यह चल रहा है? अपनी नेटवर्क कनेक्टिविटी भी जांचें।", - "cancel": "रद्द करें" + "cancel": "रद्द करें", + "admins": "व्यवस्थापक", + "members": "सदस्य", + "joinNow": "अब शामिल हों", + "joined": "शामिल हुए", + "withdraw": "अनुरोध वापस लें" }, "dashboard": { "title": "डैशबोर्ड", @@ -365,7 +341,9 @@ "sortPost": "पोस्ट को क्रमित करें", "tag": "आपका ब्राउज़र वीडियो टैग का समर्थन नहीं करता", "postCreatedSuccess": "बधाई हो! आपने कुछ पोस्ट किया है।", - "pinPost": "पोस्ट को पिन करें" + "pinPost": "पोस्ट को पिन करें", + "Next": "अगला पृष्ठ", + "Previous": "पिछला पृष्ठ" }, "postNotFound": { "post": "पोस्ट", @@ -652,7 +630,9 @@ "createdOrganizations": "संगठन बनाये गये", "search": "खोज", "nothingToShow": "यहां दिखाने के लिए कुछ भी नहीं है.", - "selectOrganization": "संगठन का चयन करें" + "selectOrganization": "संगठन का चयन करें", + "organizations": "संगठन", + "searchByName": "नाम से खोजें" }, "userSidebar": { "yourOrganizations": "आपके संगठन", @@ -702,13 +682,6 @@ "nothingToShow": "यहां दिखाने के लिए कुछ भी नहीं है.", "search": "खोज", "createEvent": "कार्यक्रम बनाएँ", - "eventTitle": "कार्यक्रम का शीर्षक", - "eventDescription": "घटना विवरण", - "eventLocation": "घटना स्थान", - "startDate": "आरंभ तिथि चुनें", - "endDate": "अंतिम तिथि चुनें", - "publicEvent": "सार्वजनिक समारोह", - "registerable": "पंजीकरण योग्य", "recurring": "पुनरावर्ती", "startTime": "समय शुरू", "endTime": "अंत समय", @@ -717,7 +690,18 @@ "listView": "लिस्ट व्यू", "calendarView": "कैलेंडर दृश्य", "allDay": "पूरे दिन", - "eventCreated": "ईवेंट सफलतापूर्वक बनाया और पोस्ट किया गया." + "eventCreated": "इवेंट सफलतापूर्वक बनाया और पोस्ट किया गया।", + "eventDetails": "घटना विवरण", + "eventTitle": "शीर्षक", + "enterTitle": "शीर्षक दर्ज करें", + "eventDescription": "विवरण", + "enterDescription": "विवरण दर्ज करें", + "eventLocation": "स्थान", + "enterLocation": "स्थान दर्ज करें", + "startDate": "प्रारंभ तिथि", + "endDate": "अंतिम तिथि", + "publicEvent": "सार्वजनिक है", + "registerable": "पंजीकृत करने योग्य है" }, "userEventCard": { "location": "जगह", diff --git a/public/locales/sp.json b/public/locales/sp.json index c07f565358..bba2f5e0ed 100644 --- a/public/locales/sp.json +++ b/public/locales/sp.json @@ -4,6 +4,7 @@ "fromPalisadoes": "Una aplicación de código abierto de los voluntarios de la Fundación palisados", "talawa_portal": "Portal De Administración Talawa", "login": "Acceso", + "userLogin": "Inicio de sesión de usuario", "register": "Registro", "firstName": "Primer nombre", "lastName": "Apellido", @@ -38,36 +39,6 @@ "numeric_value_check": "Al menos un valor numérico", "special_char_check": "Al menos un carácter especial" }, - "userLoginPage": { - "title": "Administrador Talawa", - "fromPalisadoes": "Una aplicación de código abierto de los voluntarios de la Fundación palisados", - "talawa_portal": "Portal De Administración Talawa", - "login": "Acceso", - "register": "Registro", - "firstName": "Primer nombre", - "lastName": "Apellido", - "email": "Correo electrónico", - "password": "Clave", - "atleast_8_char_long": "Al menos 8 caracteres de largo", - "Password_and_Confirm_password_mismatches.": "Contraseña y Confirmar contraseña no coinciden.", - "confirmPassword": "Confirmar contraseña", - "forgotPassword": "Has olvidado tu contraseña ?", - "enterEmail": "ingrese correo electrónico", - "enterPassword": "introducir la contraseña", - "doNotOwnAnAccount": "¿No tienes una cuenta?", - "talawaApiUnavailable": "El servicio Talawa-API no está disponible. ¿Está funcionando? Verifica también la conectividad de tu red.", - "captchaError": "¡Error de captcha!", - "Please_check_the_captcha": "Por favor, revisa el captcha.", - "Something_went_wrong": "Algo salió mal. Inténtalo después de un tiempo", - "passwordMismatches": "Contraseña y Confirmar contraseña no coinciden.", - "fillCorrectly": "Complete todos los detalles correctamente.", - "notAuthorised": "¡Lo siento! ¡No estás autorizado!", - "notFound": "¡Usuario no encontrado!", - "successfullyRegistered": "Registrado con éxito. Espere hasta que sea aprobado", - "userLogin": "Inicio de sesión de usuario", - "afterRegister": "Registrado exitosamente. Espere a que el administrador apruebe su solicitud.", - "OR": "O" - }, "latestEvents": { "eventCardTitle": "Próximos Eventos", "eventCardSeeAll": "Ver Todos", @@ -192,7 +163,12 @@ "roleUpdated": "Rol actualizado.", "noResultsFoundFor": "No se encontraron resultados para ", "talawaApiUnavailable": "El servicio Talawa-API no está disponible. ¿Está funcionando? Compruebe también la conectividad de su red.", - "cancel": "Cancelar" + "cancel": "Cancelar", + "admins": "Administradores", + "members": "Miembros", + "joinNow": "Únete ahora", + "joined": "Unido", + "withdraw": "retirar" }, "dashboard": { "title": "Panel de", @@ -364,7 +340,9 @@ "sortPost": "Ordenar Publicaciones", "tag": "Su navegador no admite la etiqueta de video", "postCreatedSuccess": "¡Felicidades! Has publicado algo.", - "pinPost": "Fijar publicación" + "pinPost": "Fijar publicación", + "Next": "Siguiente página", + "Previous": "Página anterior" }, "postNotFound": { "post": "Publicaciones", @@ -650,7 +628,9 @@ "createdOrganizations": "Organizaciones creadas", "search": "Buscar", "nothingToShow": "Nada que mostrar aquí.", - "selectOrganization": "Seleccionar organización" + "selectOrganization": "Seleccionar organización", + "organizations": "Organizaciones", + "searchByName": "Buscar por nombre" }, "userSidebar": { "yourOrganizations": "Tus Organizaciones", @@ -700,13 +680,6 @@ "nothingToShow": "No hay nada que mostrar aquí.", "search": "Buscar", "createEvent": "Crear evento", - "eventTitle": "Título del evento", - "eventDescription": "Descripción del evento", - "eventLocation": "Lugar del evento", - "startDate": "Seleccione la fecha de inicio", - "endDate": "Seleccionar fecha de finalización", - "publicEvent": "Evento público", - "registerable": "Registrable", "recurring": "Periódica", "startTime": "Hora de inicio", "endTime": "Hora de finalización", @@ -715,7 +688,18 @@ "listView": "Vista de la lista", "calendarView": "Vista de calendario", "allDay": "Todo el dia", - "eventCreated": "Evento creado y publicado exitosamente." + "eventCreated": "Evento creado y publicado exitosamente.", + "eventDetails": "Detalles del evento", + "eventTitle": "Título", + "enterTitle": "Ingrese el título", + "eventDescription": "Descripción", + "enterDescription": "Ingresar descripción", + "eventLocation": "Ubicación", + "enterLocation": "Ingresar Ubicación", + "startDate": "Fecha de inicio", + "endDate": "Fecha de finalización", + "publicEvent": "Es público", + "registerable": "Es registrable" }, "userEventCard": { "location": "Ubicación", diff --git a/public/locales/zh.json b/public/locales/zh.json index be9c6f7087..1e7ffcecc4 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -4,6 +4,7 @@ "fromPalisadoes": "柵欄 基金会志愿者的开源应用程序", "talawa_portal": "塔拉瓦管理門戶", "login": "登錄", + "userLogin": "用户登录", "register": "登記", "firstName": "名", "lastName": "姓", @@ -38,36 +39,6 @@ "numeric_value_check": "至少一個數值", "special_char_check": "至少一個特殊字符" }, - "userLoginPage": { - "title": "塔拉瓦管理員", - "fromPalisadoes": "柵欄 基金会志愿者的开源应用程序", - "talawa_portal": "塔拉瓦管理門戶", - "login": "登錄", - "register": "登記", - "firstName": "名", - "lastName": "姓", - "email": "電子郵件", - "password": "密碼", - "atleast_8_char_long": "至少 8 個字符長", - "Password_and_Confirm_password_mismatches.": "密碼和確認密碼不匹配。", - "confirmPassword": "確認密碼", - "forgotPassword": "忘記密碼 ?", - "enterEmail": "输入电子邮件", - "enterPassword": "输入密码", - "doNotOwnAnAccount": "沒有帳戶嗎?", - "talawaApiUnavailable": "服務不可用。它正在運行嗎?還要檢查您的網絡連接。", - "captchaError": "驗證碼錯誤!", - "Please_check_the_captcha": "請檢查驗證碼。", - "Something_went_wrong": "出了點問題,請稍後再試。", - "passwordMismatches": "密碼和確認密碼不匹配。", - "fillCorrectly": "正確填寫所有細節。", - "notAuthorised": "抱歉!你沒有被授權!", - "notFound": "找不到用戶!", - "successfullyRegistered": "註冊成功,請等待審核通過。", - "userLogin": "用户登录", - "afterRegister": "註冊成功。 請等待管理員批准您的請求。", - "OR": "或者" - }, "latestEvents": { "eventCardTitle": "即将举行的活动", "eventCardSeeAll": "查看全部", @@ -192,7 +163,12 @@ "roleUpdated": "角色已更新。", "noResultsFoundFor": "未找到结果 ", "talawaApiUnavailable": "服務不可用。它在運行嗎?還要檢查您的網絡連接。", - "cancel": "取消" + "cancel": "取消", + "admins": "管理員", + "members": "成員", + "joinNow": "现在加入", + "joined": "加入", + "withdraw": "提取" }, "dashboard": { "title": "儀表板", @@ -365,7 +341,9 @@ "sortPost": "排序帖子", "tag": "您的浏览器不支持视频标签", "postCreatedSuccess": "恭喜!您已经发布了一些内容。", - "pinPost": "针柱" + "pinPost": "针柱", + "Next": "下一页", + "Previous": "上一页" }, "postNotFound": { "post": "郵政", @@ -652,7 +630,9 @@ "createdOrganizations": "創建的組織", "search": "搜索", "nothingToShow": "這裡沒有什麼可展示的。", - "selectOrganization": "選擇組織" + "selectOrganization": "選擇組織", + "organizations": "组织", + "searchByName": "按名稱搜索" }, "userSidebar": { "yourOrganizations": "您的組織", @@ -702,13 +682,6 @@ "nothingToShow": "這裡沒有什麼可顯示的。", "search": "搜索", "createEvent": "創建事件", - "eventTitle": "活動標題", - "eventDescription": "活動說明", - "eventLocation": "活動地點", - "startDate": "選擇開始日期", - "endDate": "選擇結束日期", - "publicEvent": "公共活動", - "registerable": "可註冊", "recurring": "再次發生的", "startTime": "開始時間", "endTime": "時間結束", @@ -717,7 +690,18 @@ "listView": "列表顯示", "calendarView": "日曆視圖", "allDay": "整天", - "eventCreated": "活動已成功創建並發布。" + "eventCreated": "活动创建并发布成功。", + "eventDetails": "活动详情", + "eventTitle": "标题", + "enterTitle": "输入标题", + "eventDescription": "描述", + "enterDescription": "输入描述", + "eventLocation": "地点", + "enterLocation": "输入位置", + "startDate": "开始日期", + "endDate": "结束日期", + "publicEvent": "公开", + "registerable": "可注册" }, "userEventCard": { "location": "地點", diff --git a/__mocks__/@dicebear/collection.ts b/scripts/__mocks__/@dicebear/collection.ts similarity index 100% rename from __mocks__/@dicebear/collection.ts rename to scripts/__mocks__/@dicebear/collection.ts diff --git a/__mocks__/@dicebear/core.ts b/scripts/__mocks__/@dicebear/core.ts similarity index 100% rename from __mocks__/@dicebear/core.ts rename to scripts/__mocks__/@dicebear/core.ts diff --git a/setup.ts b/setup.ts index 58587f2eb1..f930acc13a 100644 --- a/setup.ts +++ b/setup.ts @@ -1,72 +1,13 @@ import dotenv from 'dotenv'; import fs from 'fs'; import inquirer from 'inquirer'; -import fetch from 'node-fetch'; - -dotenv.config(); - -// Check Talawa API Connection -async function checkConnection(url: string): Promise { - console.log('\nChecking Talawa-API connection....'); - let isConnected = false; - await fetch(url) - .then(() => { - isConnected = true; - console.log('\nConnection to Talawa-API successful! 🎉'); - }) - .catch(() => { - console.log( - '\nTalawa-API service is unavailable. Is it running? Check your network connectivity too.', - ); - }); - return isConnected; -} - -async function askForTalawaApiUrl(): Promise { - const { endpoint } = await inquirer.prompt([ - { - type: 'input', - name: 'endpoint', - message: 'Enter your talawa-api endpoint:', - default: 'http://localhost:4000/graphql/', - }, - ]); - return endpoint; -} - -async function askForCustomPort(): Promise { - const { customPort } = await inquirer.prompt([ - { - type: 'input', - name: 'customPort', - message: - 'Enter custom port for development server (leave blank for default 4321):', - default: 4321, - }, - ]); - return customPort; -} - -// Check if all the fields in .env.example are present in .env -function checkEnvFile(): void { - const env = dotenv.parse(fs.readFileSync('.env')); - const envSample = dotenv.parse(fs.readFileSync('.env.example')); - const misplaced = Object.keys(envSample).filter((key) => !(key in env)); - if (misplaced.length > 0) { - const config = dotenv.parse(fs.readFileSync('.env.example')); - misplaced.map((key) => - fs.appendFileSync('.env', `${key}=${config[key]}\n`), - ); - } -} - -// Validate ReCaptcha -function validateRecaptcha(string: string): boolean { - const pattern = /^[a-zA-Z0-9_-]{40}$/; - return pattern.test(string); -} +import { checkConnection } from './src/setup/checkConnection/checkConnection'; +import { askForTalawaApiUrl } from './src/setup/askForTalawaApiUrl/askForTalawaApiUrl'; +import { checkEnvFile } from './src/setup/checkEnvFile/checkEnvFile'; +import { validateRecaptcha } from './src/setup/validateRecaptcha/validateRecaptcha'; +import { askForCustomPort } from './src/setup/askForCustomPort/askForCustomPort'; -async function main(): Promise { +export async function main(): Promise { console.log('Welcome to the Talawa Admin setup! 🚀'); if (!fs.existsSync('.env')) { diff --git a/src/App.tsx b/src/App.tsx index 9ae2b5020b..25bb15ac4d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -25,7 +25,6 @@ import MemberDetail from 'screens/MemberDetail/MemberDetail'; import Loader from 'components/Loader/Loader'; // User Portal Components -import UserLoginPage from 'screens/UserPortal/UserLoginPage/UserLoginPage'; import Organizations from 'screens/UserPortal/Organizations/Organizations'; import Home from 'screens/UserPortal/Home/Home'; import People from 'screens/UserPortal/People/People'; @@ -122,7 +121,6 @@ function app(): JSX.Element { {/* User Portal Routes */} - { }); describe('Testing Left Drawer component for SUPERADMIN', () => { + beforeEach(() => { + setItem('UserType', 'SUPERADMIN'); + }); test('Component should be rendered properly', () => { setItem('UserImage', ''); - setItem('UserType', 'SUPERADMIN'); render( @@ -109,7 +111,6 @@ describe('Testing Left Drawer component for SUPERADMIN', () => { }); test('Testing in roles screen', () => { - setItem('UserType', 'SUPERADMIN'); render( @@ -132,7 +133,6 @@ describe('Testing Left Drawer component for SUPERADMIN', () => { }); test('Testing Drawer when hideDrawer is null', () => { - setItem('UserType', 'SUPERADMIN'); render( @@ -145,7 +145,6 @@ describe('Testing Left Drawer component for SUPERADMIN', () => { }); test('Testing logout functionality', async () => { - setItem('UserType', 'SUPERADMIN'); render( @@ -162,8 +161,10 @@ describe('Testing Left Drawer component for SUPERADMIN', () => { }); describe('Testing Left Drawer component for ADMIN', () => { - test('Components should be rendered properly', () => { + beforeEach(() => { setItem('UserType', 'ADMIN'); + }); + test('Components should be rendered properly', () => { render( diff --git a/src/components/LeftDrawer/LeftDrawer.tsx b/src/components/LeftDrawer/LeftDrawer.tsx index 6679f0de5b..490c11a39d 100644 --- a/src/components/LeftDrawer/LeftDrawer.tsx +++ b/src/components/LeftDrawer/LeftDrawer.tsx @@ -1,7 +1,7 @@ import React from 'react'; import Button from 'react-bootstrap/Button'; import { useTranslation } from 'react-i18next'; -import { useHistory } from 'react-router-dom'; +import { useHistory, Link } from 'react-router-dom'; import { ReactComponent as AngleRightIcon } from 'assets/svgs/angleRight.svg'; import { ReactComponent as LogoutIcon } from 'assets/svgs/logout.svg'; import { ReactComponent as OrganizationsIcon } from 'assets/svgs/organizations.svg'; @@ -43,138 +43,131 @@ const leftDrawer = ({ }; return ( - <> -
- -

{t('talawaAdminPortal')}

-
{t('menu')}
-
- - {userType === 'SUPERADMIN' && ( - <> - - - - )} -
-
- + {userType === 'SUPERADMIN' && ( + <> + +
+ {t('users')} + + + + )} +
+
+ +
- + + ); }; diff --git a/src/components/LeftDrawerOrg/LeftDrawerOrg.module.css b/src/components/LeftDrawerOrg/LeftDrawerOrg.module.css index aa04d88a02..c169189422 100644 --- a/src/components/LeftDrawerOrg/LeftDrawerOrg.module.css +++ b/src/components/LeftDrawerOrg/LeftDrawerOrg.module.css @@ -87,9 +87,9 @@ border: none; width: 100%; margin-top: 5rem; + padding: 2.1rem 0.5rem; height: 52px; border-radius: 8px; - background-color: var(--bs-white); display: flex; align-items: center; } diff --git a/src/components/LeftDrawerOrg/LeftDrawerOrg.test.tsx b/src/components/LeftDrawerOrg/LeftDrawerOrg.test.tsx index 0ebf3cc42c..0d0efe11d3 100644 --- a/src/components/LeftDrawerOrg/LeftDrawerOrg.test.tsx +++ b/src/components/LeftDrawerOrg/LeftDrawerOrg.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import 'jest-localstorage-mock'; import { I18nextProvider } from 'react-i18next'; @@ -71,7 +71,11 @@ const MOCKS = [ request: { query: REVOKE_REFRESH_TOKEN, }, - result: {}, + result: { + data: { + revokeRefreshTokenForUser: true, + }, + }, }, { request: { @@ -196,6 +200,7 @@ const MOCKS_WITH_IMAGE = [ }, }, ]; + const MOCKS_EMPTY = [ { request: { @@ -254,10 +259,39 @@ const link = new StaticMockLink(MOCKS, true); const linkImage = new StaticMockLink(MOCKS_WITH_IMAGE, true); const linkEmpty = new StaticMockLink(MOCKS_EMPTY, true); -describe('Testing Left Drawer component for SUPERADMIN', () => { +describe('Testing LeftDrawerOrg component for SUPERADMIN', () => { + beforeEach(() => { + setItem('UserType', 'SUPERADMIN'); + }); + + test('Testing Profile button & its styling when screenName is Profile', async () => { + render( + + + + + + + + + , + ); + await wait(); + + const profileButton = screen.getByTestId('profileBtn'); + const userTypeSpan = screen.getByText(/Superadmin/i); + + expect(profileButton).toHaveClass('btn-success'); + expect(profileButton).not.toHaveClass('btn-light'); + expect(userTypeSpan).toHaveClass('text-white'); + }); + test('Component should be rendered properly', async () => { setItem('UserImage', ''); - setItem('UserType', 'SUPERADMIN'); render( @@ -279,7 +313,6 @@ describe('Testing Left Drawer component for SUPERADMIN', () => { }); test('Testing Profile Page & Organization Detail Modal', async () => { - setItem('UserType', 'SUPERADMIN'); render( @@ -297,7 +330,6 @@ describe('Testing Left Drawer component for SUPERADMIN', () => { }); test('Testing Menu Buttons', async () => { - setItem('UserType', 'SUPERADMIN'); render( @@ -315,7 +347,6 @@ describe('Testing Left Drawer component for SUPERADMIN', () => { }); test('Testing when image is present for Organization', async () => { - setItem('UserType', 'SUPERADMIN'); render( @@ -331,7 +362,6 @@ describe('Testing Left Drawer component for SUPERADMIN', () => { }); test('Testing when Organization does not exists', async () => { - setItem('UserType', 'SUPERADMIN'); render( @@ -350,7 +380,6 @@ describe('Testing Left Drawer component for SUPERADMIN', () => { }); test('Testing Drawer when hideDrawer is null', () => { - setItem('UserType', 'SUPERADMIN'); render( @@ -365,7 +394,6 @@ describe('Testing Left Drawer component for SUPERADMIN', () => { }); test('Testing Drawer when hideDrawer is true', () => { - setItem('UserType', 'SUPERADMIN'); render( @@ -380,7 +408,6 @@ describe('Testing Left Drawer component for SUPERADMIN', () => { }); test('Testing logout functionality', async () => { - setItem('UserType', 'SUPERADMIN'); render( @@ -393,7 +420,9 @@ describe('Testing Left Drawer component for SUPERADMIN', () => { , ); userEvent.click(screen.getByTestId('logoutBtn')); - expect(localStorage.clear).toHaveBeenCalled(); - expect(global.window.location.pathname).toBe('/'); + await waitFor(() => { + expect(localStorage.clear).toHaveBeenCalled(); + expect(global.window.location.pathname).toBe('/'); + }); }); }); diff --git a/src/components/LeftDrawerOrg/LeftDrawerOrg.tsx b/src/components/LeftDrawerOrg/LeftDrawerOrg.tsx index 1201865c55..b113dad9de 100644 --- a/src/components/LeftDrawerOrg/LeftDrawerOrg.tsx +++ b/src/components/LeftDrawerOrg/LeftDrawerOrg.tsx @@ -14,8 +14,6 @@ import { ReactComponent as LogoutIcon } from 'assets/svgs/logout.svg'; import { ReactComponent as TalawaLogo } from 'assets/svgs/talawa.svg'; import styles from './LeftDrawerOrg.module.css'; import { REVOKE_REFRESH_TOKEN } from 'GraphQl/Mutations/mutations'; -import useLocalStorage from 'utils/useLocalstorage'; -import Avatar from 'components/Avatar/Avatar'; export interface InterfaceLeftDrawerProps { orgId: string; @@ -30,6 +28,7 @@ const leftDrawerOrg = ({ targets, orgId, hideDrawer, + setHideDrawer, }: InterfaceLeftDrawerProps): JSX.Element => { const { t } = useTranslation('translation', { keyPrefix: 'leftDrawerOrg' }); const [organization, setOrganization] = @@ -48,13 +47,11 @@ const leftDrawerOrg = ({ const [revokeRefreshToken] = useMutation(REVOKE_REFRESH_TOKEN); - const { getItem } = useLocalStorage(); - - const userType = getItem('UserType'); - const firstName = getItem('FirstName'); - const lastName = getItem('LastName'); - const userImage = getItem('UserImage'); - const userId = getItem('id'); + const userType = localStorage.getItem('UserType'); + const firstName = localStorage.getItem('FirstName'); + const lastName = localStorage.getItem('LastName'); + const userImage = localStorage.getItem('UserImage'); + const userId = localStorage.getItem('id'); const history = useHistory(); // Set organization data @@ -77,15 +74,27 @@ const leftDrawerOrg = ({ return ( <>
+ {/* Close Drawer Btn for small devices */} + + {/* Branding Section */}
@@ -119,19 +128,16 @@ const leftDrawerOrg = ({ {organization.image ? ( {`profile ) : ( - )}
{organization.name} - {organization.address.city}, {organization.address.state} -
- {organization.address.postalCode},{' '} - {organization.address.countryCode} + {organization.location}
@@ -188,8 +194,8 @@ const leftDrawerOrg = ({ {userImage && userImage !== 'null' ? ( {`profile ) : ( - )} @@ -206,7 +212,7 @@ const leftDrawerOrg = ({ - ) : ( - - )} +
Testing ...
, }; +const resizeWindow = (width: number): void => { + window.innerWidth = width; + fireEvent(window, new Event('resize')); +}; + +const clickToggleMenuBtn = (toggleButton: HTMLElement): void => { + fireEvent.click(toggleButton); +}; + describe('Testing LeftDrawer in SuperAdminScreen', () => { test('Testing LeftDrawer in page functionality', async () => { setItem('UserType', 'SUPERADMIN'); @@ -35,31 +44,20 @@ describe('Testing LeftDrawer in SuperAdminScreen', () => { , ); - // Resize window to trigger handleResize - window.innerWidth = 800; // Set a width less than or equal to 820 - fireEvent(window, new Event('resize')); - - await waitFor(() => { - fireEvent.click(screen.getByTestId('openMenu') as HTMLElement); - }); + const toggleButton = screen.getByTestId('toggleMenuBtn') as HTMLElement; + const icon = toggleButton.querySelector('i'); - // sets hideDrawer to true - await waitFor(() => { - fireEvent.click(screen.getByTestId('menuBtn') as HTMLElement); - }); + // Resize window to a smaller width + resizeWindow(800); + clickToggleMenuBtn(toggleButton); + expect(icon).toHaveClass('fa fa-angle-double-right'); // Resize window back to a larger width - window.innerWidth = 1000; // Set a larger width - fireEvent(window, new Event('resize')); - - // sets hideDrawer to false - await waitFor(() => { - fireEvent.click(screen.getByTestId('openMenu') as HTMLElement); - }); + resizeWindow(1000); + clickToggleMenuBtn(toggleButton); + expect(icon).toHaveClass('fa fa-angle-double-left'); - // sets hideDrawer to true - await waitFor(() => { - fireEvent.click(screen.getByTestId('menuBtn') as HTMLElement); - }); + clickToggleMenuBtn(toggleButton); + expect(icon).toHaveClass('fa fa-angle-double-right'); }); }); diff --git a/src/components/SuperAdminScreen/SuperAdminScreen.tsx b/src/components/SuperAdminScreen/SuperAdminScreen.tsx index 989f1eecfa..9aeb10bec7 100644 --- a/src/components/SuperAdminScreen/SuperAdminScreen.tsx +++ b/src/components/SuperAdminScreen/SuperAdminScreen.tsx @@ -16,41 +16,39 @@ const superAdminScreen = ({ const [hideDrawer, setHideDrawer] = useState(null); const handleResize = (): void => { - if (window.innerWidth <= 820) { - setHideDrawer(!hideDrawer); + if (window.innerWidth <= 820 && !hideDrawer) { + setHideDrawer(true); } }; + + const toggleDrawer = (): void => { + setHideDrawer(!hideDrawer); + }; + useEffect(() => { handleResize(); window.addEventListener('resize', handleResize); return () => { window.removeEventListener('resize', handleResize); }; - }, []); + }, [hideDrawer]); return ( <> - {hideDrawer ? ( - - ) : ( - - )} + { await act(() => { @@ -25,6 +154,28 @@ let props = { name: 'organizationName', image: '', description: 'organizationDescription', + admins: [ + { + id: '123', + }, + ], + members: [], + address: { + city: '', + countryCode: '', + postalCode: '', + state: '', + }, + membershipRequestStatus: '', + userRegistrationRequired: true, + membershipRequests: [ + { + _id: '', + user: { + _id: '', + }, + }, + ], }; describe('Testing OrganizationCard Component [User Portal]', () => { @@ -64,4 +215,84 @@ describe('Testing OrganizationCard Component [User Portal]', () => { await wait(); }); + + test('Send membership request', async () => { + props = { + ...props, + image: 'organizationImage', + }; + + render( + + + + + + + + + , + ); + + await wait(); + + expect(screen.getByTestId('joinBtn')).toBeInTheDocument(); + + fireEvent.click(screen.getByTestId('joinBtn')); + }); + + test('send membership request to public org', async () => { + const cardProps = { + ...props, + id: '2', + image: 'organizationImage', + userRegistrationRequired: false, + }; + + render( + + + + + + + + + , + ); + + await wait(); + + expect(screen.getByTestId('joinBtn')).toBeInTheDocument(); + + fireEvent.click(screen.getByTestId('joinBtn')); + }); + + test('withdraw membership request', async () => { + const cardProps = { + ...props, + id: '3', + image: 'organizationImage', + userRegistrationRequired: true, + membershipRequestStatus: 'pending', + }; + + render( + + + + + + + + + , + ); + + await wait(); + + expect(screen.getByTestId('withdrawBtn')).toBeInTheDocument(); + + fireEvent.click(screen.getByTestId('withdrawBtn')); + }); }); diff --git a/src/components/UserPortal/OrganizationCard/OrganizationCard.tsx b/src/components/UserPortal/OrganizationCard/OrganizationCard.tsx index 427d29366d..551259a131 100644 --- a/src/components/UserPortal/OrganizationCard/OrganizationCard.tsx +++ b/src/components/UserPortal/OrganizationCard/OrganizationCard.tsx @@ -1,29 +1,184 @@ import React from 'react'; -import aboutImg from 'assets/images/defaultImg.png'; import styles from './OrganizationCard.module.css'; -import { Link } from 'react-router-dom'; +import { Button } from 'react-bootstrap'; +import { Tooltip } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import LocationOnIcon from '@mui/icons-material/LocationOn'; +import { + CANCEL_MEMBERSHIP_REQUEST, + JOIN_PUBLIC_ORGANIZATION, + SEND_MEMBERSHIP_REQUEST, +} from 'GraphQl/Mutations/OrganizationMutations'; +import { useMutation, useQuery } from '@apollo/client'; +import { + USER_JOINED_ORGANIZATIONS, + USER_ORGANIZATION_CONNECTION, +} from 'GraphQl/Queries/OrganizationQueries'; +import useLocalStorage from 'utils/useLocalstorage'; + +const { getItem } = useLocalStorage(); interface InterfaceOrganizationCardProps { id: string; name: string; image: string; description: string; + admins: { + id: string; + }[]; + members: { + id: string; + }[]; + address: { + city: string; + countryCode: string; + postalCode: string; + state: string; + }; + membershipRequestStatus: string; + userRegistrationRequired: boolean; + membershipRequests: { + _id: string; + user: { + _id: string; + }; + }[]; } +const userId: string | null = getItem('userId'); + function organizationCard(props: InterfaceOrganizationCardProps): JSX.Element { - const imageUrl = props.image ? props.image : aboutImg; - const redirectLink = `/user/organization/id=${props.id}`; + const { t } = useTranslation('translation', { + keyPrefix: 'users', + }); + const [sendMembershipRequest] = useMutation(SEND_MEMBERSHIP_REQUEST, { + refetchQueries: [ + { query: USER_ORGANIZATION_CONNECTION, variables: { id: userId } }, + ], + }); + const [joinPublicOrganization] = useMutation(JOIN_PUBLIC_ORGANIZATION, { + refetchQueries: [ + { query: USER_ORGANIZATION_CONNECTION, variables: { id: userId } }, + ], + }); + const [cancelMembershipRequest] = useMutation(CANCEL_MEMBERSHIP_REQUEST, { + refetchQueries: [ + { query: USER_ORGANIZATION_CONNECTION, variables: { id: userId } }, + ], + }); + const { refetch } = useQuery(USER_JOINED_ORGANIZATIONS, { + variables: { id: userId }, + }); + + async function joinOrganization(): Promise { + if (props.userRegistrationRequired) { + await sendMembershipRequest({ + variables: { + organizationId: props.id, + }, + }); + } else { + await joinPublicOrganization({ + variables: { + organizationId: props.id, + }, + }); + } + refetch(); + } + + async function withdrawMembershipRequest(): Promise { + const membershipRequest = props.membershipRequests.find( + (request) => request.user._id === userId, + ); + + await cancelMembershipRequest({ + variables: { + membershipRequestId: membershipRequest?._id, + }, + }); + } return ( - -
- -
- {props.name} - {props.description} + <> +
+
+
+ word.charAt(0)) + .slice(0, 2) + .join('')}` + } + alt={`${props.name} image`} + data-testid={props.image ? '' : 'emptyContainerForImage'} + /> +
+
+ +

{props.name}

+
+ {props.address && props.address.city && ( +
+
+ + {props.address.city}, + {props.address.state} +
+ + + {props.address.postalCode},{' '} + + + {props.address.countryCode} + +
+
+ )} +
+ {t('admins')}: {props.admins?.length} +
+
+ {t('members')}: {props.members?.length} +
+
+ {props.membershipRequestStatus === 'accepted' && ( + + )} + + {props.membershipRequestStatus === 'pending' && ( + + )} + {props.membershipRequestStatus === '' && ( + + )}
- + ); } diff --git a/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.test.tsx b/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.test.tsx index 2ff61609c1..35d9856351 100644 --- a/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.test.tsx +++ b/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.test.tsx @@ -37,10 +37,42 @@ const MOCK_ORGANIZATION_CONNECTION = { __typename: 'Organization', _id: '6401ff65ce8e8406b8f07af2', image: '', + address: { + city: 'abc', + countryCode: '123', + postalCode: '456', + state: 'def', + dependentLocality: 'ghi', + line1: 'asdfg', + line2: 'dfghj', + sortingCode: '4567', + }, name: 'anyOrganization1', description: 'desc', userRegistrationRequired: true, + createdAt: '12345678900', creator: { __typename: 'User', firstName: 'John', lastName: 'Doe' }, + members: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + admins: [ + { + _id: '45gj5678jk45678fvgbhnr4rtgh', + }, + ], + membershipRequests: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], }, ], }, diff --git a/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.tsx b/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.tsx index fbcc421ce9..8e6f790d80 100644 --- a/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.tsx +++ b/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.tsx @@ -54,7 +54,7 @@ function organizationNavbar(props: InterfaceNavbarProps): JSX.Element { /* istanbul ignore next */ const handleLogout = (): void => { localStorage.clear(); - window.location.replace('/user'); + window.location.replace('/'); }; const userName = getItem('name'); diff --git a/src/components/UserPortal/SecuredRouteForUser/SecuredRouteForUser.tsx b/src/components/UserPortal/SecuredRouteForUser/SecuredRouteForUser.tsx index ea63359dd1..b6b3eca310 100644 --- a/src/components/UserPortal/SecuredRouteForUser/SecuredRouteForUser.tsx +++ b/src/components/UserPortal/SecuredRouteForUser/SecuredRouteForUser.tsx @@ -10,7 +10,7 @@ const SecuredRouteForUser = (props: any): JSX.Element => { ) : ( - + ); }; diff --git a/src/components/UserPortal/UserNavbar/UserNavbar.tsx b/src/components/UserPortal/UserNavbar/UserNavbar.tsx index 93c5b08556..84314feb30 100644 --- a/src/components/UserPortal/UserNavbar/UserNavbar.tsx +++ b/src/components/UserPortal/UserNavbar/UserNavbar.tsx @@ -34,7 +34,7 @@ function userNavbar(): JSX.Element { const handleLogout = (): void => { revokeRefreshToken(); localStorage.clear(); - history.push('/user'); + history.push('/'); }; return ( diff --git a/src/components/UserPortal/UserSidebar/UserSidebar.module.css b/src/components/UserPortal/UserSidebar/UserSidebar.module.css index 00eb9b631f..d68fd6407b 100644 --- a/src/components/UserPortal/UserSidebar/UserSidebar.module.css +++ b/src/components/UserPortal/UserSidebar/UserSidebar.module.css @@ -6,7 +6,6 @@ padding: 0px 10px; padding-top: 50px; flex-grow: 1; - width: 250px; background-color: var(--bs-white); } diff --git a/src/components/UserPortal/UserSidebar/UserSidebar.tsx b/src/components/UserPortal/UserSidebar/UserSidebar.tsx index 43bc1c8457..4388ca293e 100644 --- a/src/components/UserPortal/UserSidebar/UserSidebar.tsx +++ b/src/components/UserPortal/UserSidebar/UserSidebar.tsx @@ -47,6 +47,7 @@ function userSidebar(): JSX.Element { React.useEffect(() => { if (data2) { setDetails(data2.user); + console.log(data2, 'user details'); } }, [data2]); diff --git a/src/screens/LoginPage/LoginPage.test.tsx b/src/screens/LoginPage/LoginPage.test.tsx index 28dcd974a9..31274afa94 100644 --- a/src/screens/LoginPage/LoginPage.test.tsx +++ b/src/screens/LoginPage/LoginPage.test.tsx @@ -166,7 +166,9 @@ describe('Testing Login Page Screen', () => { ); await wait(); - + const adminLink = screen.getByText(/Admin/i); + userEvent.click(adminLink); + await wait(); expect(screen.getByText(/Admin/i)).toBeInTheDocument(); expect(window.location).toBeAt('/orglist'); }); @@ -672,4 +674,27 @@ describe('Testing Login Page Screen', () => { expect(screen.queryByTestId('passwordCheck')).toBeNull(); }); + + test('Component Should be rendered properly for user login', async () => { + window.location.assign('/user/organizations'); + + render( + + + + + + + + + , + ); + + await wait(); + const userLink = screen.getByText(/User/i); + userEvent.click(userLink); + await wait(); + expect(screen.getByText(/User Login/i)).toBeInTheDocument(); + expect(window.location).toBeAt('/user/organizations'); + }); }); diff --git a/src/screens/LoginPage/LoginPage.tsx b/src/screens/LoginPage/LoginPage.tsx index 2189bf86f2..0f6649dc6d 100644 --- a/src/screens/LoginPage/LoginPage.tsx +++ b/src/screens/LoginPage/LoginPage.tsx @@ -41,7 +41,7 @@ import styles from './LoginPage.module.css'; import EmailOutlinedIcon from '@mui/icons-material/EmailOutlined'; import useLocalStorage from 'utils/useLocalstorage'; -function loginPage(): JSX.Element { +const loginPage = (): JSX.Element => { const { t } = useTranslation('translation', { keyPrefix: 'loginPage' }); const history = useHistory(); @@ -56,6 +56,7 @@ function loginPage(): JSX.Element { specialChar: boolean; }; const [showTab, setShowTab] = useState<'LOGIN' | 'REGISTER'>('LOGIN'); + const [role, setRole] = useState<'admin' | 'user'>('admin'); const [componentLoader, setComponentLoader] = useState(true); const [isInputFocused, setIsInputFocused] = useState(false); const [signformState, setSignFormState] = useState({ @@ -111,12 +112,16 @@ function loginPage(): JSX.Element { })); }; + const handleRoleToggle = (role: 'admin' | 'user'): void => { + setRole(role); + }; + const recaptchaRef = useRef(null); useEffect(() => { const isLoggedIn = getItem('IsLoggedIn'); if (isLoggedIn == 'TRUE') { - history.push('/orglist'); + history.push(role === 'admin' ? '/orglist' : '/user/organizations'); } setComponentLoader(false); }, []); @@ -216,9 +221,11 @@ function loginPage(): JSX.Element { /* istanbul ignore next */ if (signUpData) { toast.success( - 'Successfully Registered. Please wait until you will be approved.', + role === 'admin' + ? 'Successfully Registered. Please wait until you will be approved.' + : 'Successfully registered. Please wait for admin to approve your request.', ); - + setShowTab('LOGIN'); setSignFormState({ signfirstName: '', signlastName: '', @@ -273,21 +280,29 @@ function loginPage(): JSX.Element { /* istanbul ignore next */ if (loginData) { - if ( - loginData.login.user.userType === 'SUPERADMIN' || - (loginData.login.user.userType === 'ADMIN' && - loginData.login.user.adminApproved === true) - ) { + if (role === 'admin') { + if ( + loginData.login.user.userType === 'SUPERADMIN' || + (loginData.login.user.userType === 'ADMIN' && + loginData.login.user.adminApproved === true) + ) { + setItem('token', loginData.login.accessToken); + setItem('refreshToken', loginData.login.refreshToken); + setItem('id', loginData.login.user._id); + setItem('IsLoggedIn', 'TRUE'); + setItem('UserType', loginData.login.user.userType); + } else { + toast.warn(t('notAuthorised')); + } + } else { setItem('token', loginData.login.accessToken); setItem('refreshToken', loginData.login.refreshToken); - setItem('id', loginData.login.user._id); + setItem('userId', loginData.login.user._id); setItem('IsLoggedIn', 'TRUE'); setItem('UserType', loginData.login.user.userType); - if (getItem('IsLoggedIn') == 'TRUE') { - history.push('/orglist'); - } - } else { - toast.warn(t('notAuthorised')); + } + if (getItem('IsLoggedIn') == 'TRUE') { + history.push(role === 'admin' ? '/orglist' : '/user/organizations'); } } else { toast.warn(t('notFound')); @@ -382,7 +397,7 @@ function loginPage(): JSX.Element { }`} /> - + {/* LOGIN FORM */}
-
-

{t('login')}

- {t('email')} + +

+ {role === 'admin' ? t('login') : t('userLogin')} +

+ {t('email')}
- {t('password')} + {t('password')}
-
+
@@ -484,7 +500,7 @@ function loginPage(): JSX.Element { + const memberDetails = ( + + + {state == 1 ? ( +
+ +

+ {t('title')} +

+
+ - + +
+
+ + +
+ {userData?.user?.image ? ( + + ) : ( + + )} +
+ + + {/* User section */} +
+

+ + {userData?.user?.firstName} {userData?.user?.lastName} + +

+

+ {t('role')} :{' '} + {userData?.user?.userType} +

+

+ {t('email')} :{' '} + {userData?.user?.email} +

+

+ {t('createdOn')} :{' '} + {prettyDate(userData?.user?.createdAt)} +

+
+ +
+
+
+
+ {/* Main Section And Activity section */} +
+ + {/* Main Section */} + +
+
+
+ {t('main')} +
+
+
+ + {t('firstName')} + {userData?.user?.firstName} + + + {t('lastName')} + {userData?.user?.lastName} + + + {t('role')} + {userData?.user?.userType} + + + {t('language')} + + {getLanguageName(userData?.user?.appLanguageCode)} + + + + {t('adminApproved')} + + {userData?.user?.adminApproved ? 'Yes' : 'No'} + + + + {t('pluginCreationAllowed')} + + {userData?.user?.pluginCreationAllowed ? 'Yes' : 'No'} + + + + {t('createdOn')} + + {prettyDate(userData?.user?.createdAt)} + + +
-
- - -
- {userData?.user?.image ? ( - - ) : ( - - )} + + {/* Activity Section */} + + {/* Organizations */} +
+
+
+ {t('organizations')} +
- - - {/* User section */} -
-

- - {userData?.user?.firstName} {userData?.user?.lastName} - -

-

- {t('role')} :{' '} - {userData?.user?.userType} -

-

- {t('email')} :{' '} - {userData?.user?.email} -

-

- {t('createdOn')} :{' '} - {prettyDate(userData?.user?.createdAt)} -

+
+ + {t('created')} + + {userData?.user?.createdOrganizations?.length} + + + + {t('joined')} + + {userData?.user?.joinedOrganizations?.length} + + + + {t('adminForOrganizations')} + {userData?.user?.adminFor?.length} + + + {t('membershipRequests')} + + {userData?.user?.membershipRequests?.length} + +
- - -
-
-
- {/* Main Section And Activity section */} -
- - {/* Main Section */} - -
-
-
- {t('main')} -
-
-
- - {t('firstName')} - {userData?.user?.firstName} - - - {t('lastName')} - {userData?.user?.lastName} - - - {t('role')} - {userData?.user?.userType} - - - {t('language')} - - {getLanguageName(userData?.user?.appLanguageCode)} - - - - {t('adminApproved')} - - {userData?.user?.adminApproved ? 'Yes' : 'No'} - - - - {t('pluginCreationAllowed')} - - {userData?.user?.pluginCreationAllowed - ? 'Yes' - : 'No'} - - - - {t('createdOn')} - - {prettyDate(userData?.user?.createdAt)} - - -
-
- - {/* Activity Section */} - - {/* Organizations */} -
-
-
- {t('organizations')} -
-
-
- - {t('created')} - - {userData?.user?.createdOrganizations?.length} - - - - {t('joined')} - - {userData?.user?.joinedOrganizations?.length} - - - - {t('adminForOrganizations')} - {userData?.user?.adminFor?.length} - - - {t('membershipRequests')} - - {userData?.user?.membershipRequests?.length} - - -
-
- {/* Events */} -
-
-
- {t('events')} -
-
-
- - {t('created')} - - {userData?.user?.createdEvents?.length} - - - - {t('joined')} - - {userData?.user?.registeredEvents?.length} - - - - {t('adminForEvents')} - - {userData?.user?.eventAdmin?.length} - - -
-
- -
-
-
- ) : ( - - )} - - - +
+ {/* Events */} +
+
+
+ {t('events')} +
+
+
+ + {t('created')} + + {userData?.user?.createdEvents?.length} + + + + {t('joined')} + + {userData?.user?.registeredEvents?.length} + + + + {t('adminForEvents')} + {userData?.user?.eventAdmin?.length} + +
+
+ + +
+
+ ) : ( + + )} + +
+ ); + + return ( + <> + {calledFrom === 'orglist' ? ( + + {memberDetails} + + ) : ( + + {memberDetails} + + )} ); }; diff --git a/src/screens/OrgPost/OrgPost.test.tsx b/src/screens/OrgPost/OrgPost.test.tsx index 106d16bc38..465da98642 100644 --- a/src/screens/OrgPost/OrgPost.test.tsx +++ b/src/screens/OrgPost/OrgPost.test.tsx @@ -1,112 +1,135 @@ -import React from 'react'; import { MockedProvider } from '@apollo/react-testing'; -import { BrowserRouter } from 'react-router-dom'; -import { act, render, screen, fireEvent } from '@testing-library/react'; +import { act, fireEvent, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { Provider } from 'react-redux'; import 'jest-location-mock'; import { I18nextProvider } from 'react-i18next'; - -import OrgPost from './OrgPost'; -import { store } from 'state/store'; -import { ORGANIZATION_POST_CONNECTION_LIST } from 'GraphQl/Queries/Queries'; +import { Provider } from 'react-redux'; +import { BrowserRouter } from 'react-router-dom'; +import React from 'react'; import { CREATE_POST_MUTATION } from 'GraphQl/Mutations/mutations'; -import i18nForTest from 'utils/i18nForTest'; -import { StaticMockLink } from 'utils/StaticMockLink'; +import { ORGANIZATION_POST_LIST } from 'GraphQl/Queries/Queries'; import { ToastContainer } from 'react-toastify'; - +import { store } from 'state/store'; +import { StaticMockLink } from 'utils/StaticMockLink'; +import i18nForTest from 'utils/i18nForTest'; +import OrgPost from './OrgPost'; const MOCKS = [ { request: { - query: ORGANIZATION_POST_CONNECTION_LIST, + query: ORGANIZATION_POST_LIST, variables: { id: undefined, - title_contains: '', - text_contains: '', + after: null, + before: null, + first: 6, + last: null, }, }, result: { data: { - postsByOrganizationConnection: { - edges: [ - { - _id: '6411e53835d7ba2344a78e21', - title: 'postone', - text: 'This is the first post', - imageUrl: null, - videoUrl: null, - createdAt: '2023-08-24T09:26:56.524+00:00', - creator: { - _id: '640d98d9eb6a743d75341067', - firstName: 'Aditya', - lastName: 'Shelke', - email: 'adidacreator1@gmail.com', - }, - likeCount: 0, - commentCount: 0, - comments: [], - pinned: true, - likedBy: [], - }, - { - _id: '6411e54835d7ba2344a78e29', - title: 'posttwo', - text: 'Tis is the post two', - imageUrl: null, - videoUrl: null, - createdAt: '2023-08-24T09:26:56.524+00:00', - creator: { - _id: '640d98d9eb6a743d75341067', - firstName: 'Aditya', - lastName: 'Shelke', - email: 'adidacreator1@gmail.com', - }, - likeCount: 0, - commentCount: 0, - pinned: false, - likedBy: [], - comments: [], - }, - { - _id: '6411e54835d7ba2344a78e30', - title: 'posttwo', - text: 'Tis is the post two', - imageUrl: null, - videoUrl: null, - createdAt: '2023-08-24T09:26:56.524+00:00', - creator: { - _id: '640d98d9eb6a743d75341067', - firstName: 'Aditya', - lastName: 'Shelke', - email: 'adidacreator1@gmail.com', + organizations: [ + { + posts: { + edges: [ + { + node: { + _id: '6411e53835d7ba2344a78e21', + title: 'postone', + text: 'This is the first post', + imageUrl: null, + videoUrl: null, + createdAt: '2023-08-24T09:26:56.524+00:00', + creator: { + _id: '640d98d9eb6a743d75341067', + firstName: 'Aditya', + lastName: 'Shelke', + email: 'adidacreator1@gmail.com', + }, + likeCount: 0, + commentCount: 0, + comments: [], + pinned: true, + likedBy: [], + }, + cursor: '6411e53835d7ba2344a78e21', + }, + { + node: { + _id: '6411e54835d7ba2344a78e29', + title: 'posttwo', + text: 'Tis is the post two', + imageUrl: null, + videoUrl: null, + createdAt: '2023-08-24T09:26:56.524+00:00', + creator: { + _id: '640d98d9eb6a743d75341067', + firstName: 'Aditya', + lastName: 'Shelke', + email: 'adidacreator1@gmail.com', + }, + likeCount: 0, + commentCount: 0, + pinned: false, + likedBy: [], + comments: [], + }, + cursor: '6411e54835d7ba2344a78e29', + }, + { + node: { + _id: '6411e54835d7ba2344a78e30', + title: 'posttwo', + text: 'Tis is the post two', + imageUrl: null, + videoUrl: null, + createdAt: '2023-08-24T09:26:56.524+00:00', + creator: { + _id: '640d98d9eb6a743d75341067', + firstName: 'Aditya', + lastName: 'Shelke', + email: 'adidacreator1@gmail.com', + }, + likeCount: 0, + commentCount: 0, + pinned: true, + likedBy: [], + comments: [], + }, + cursor: '6411e54835d7ba2344a78e30', + }, + { + node: { + _id: '6411e54835d7ba2344a78e31', + title: 'posttwo', + text: 'Tis is the post two', + imageUrl: null, + videoUrl: null, + createdAt: '2023-08-24T09:26:56.524+00:00', + creator: { + _id: '640d98d9eb6a743d75341067', + firstName: 'Aditya', + lastName: 'Shelke', + email: 'adidacreator1@gmail.com', + }, + likeCount: 0, + commentCount: 0, + pinned: false, + likedBy: [], + comments: [], + }, + cursor: '6411e54835d7ba2344a78e31', + }, + ], + pageInfo: { + startCursor: '6411e53835d7ba2344a78e21', + endCursor: '6411e54835d7ba2344a78e31', + hasNextPage: false, + hasPreviousPage: false, }, - likeCount: 0, - commentCount: 0, - pinned: true, - likedBy: [], - comments: [], + totalCount: 4, }, - { - _id: '6411e54835d7ba2344a78e31', - title: 'posttwo', - text: 'Tis is the post two', - imageUrl: null, - videoUrl: null, - createdAt: '2023-08-24T09:26:56.524+00:00', - creator: { - _id: '640d98d9eb6a743d75341067', - firstName: 'Aditya', - lastName: 'Shelke', - email: 'adidacreator1@gmail.com', - }, - likeCount: 0, - commentCount: 0, - pinned: false, - likedBy: [], - comments: [], - }, - ], - }, + }, + ], }, }, }, @@ -164,27 +187,29 @@ describe('Organisation Post Page', () => { }; test('correct mock data should be queried', async () => { - const dataQuery1 = - MOCKS[0]?.result?.data?.postsByOrganizationConnection.edges[0]; + const dataQuery1 = MOCKS[0]?.result?.data?.organizations[0].posts.edges[0]; expect(dataQuery1).toEqual({ - _id: '6411e53835d7ba2344a78e21', - title: 'postone', - text: 'This is the first post', - imageUrl: null, - videoUrl: null, - createdAt: '2023-08-24T09:26:56.524+00:00', - creator: { - _id: '640d98d9eb6a743d75341067', - firstName: 'Aditya', - lastName: 'Shelke', - email: 'adidacreator1@gmail.com', + node: { + _id: '6411e53835d7ba2344a78e21', + title: 'postone', + text: 'This is the first post', + imageUrl: null, + videoUrl: null, + createdAt: '2023-08-24T09:26:56.524+00:00', + creator: { + _id: '640d98d9eb6a743d75341067', + firstName: 'Aditya', + lastName: 'Shelke', + email: 'adidacreator1@gmail.com', + }, + likeCount: 0, + commentCount: 0, + pinned: true, + likedBy: [], + comments: [], }, - likeCount: 0, - commentCount: 0, - pinned: true, - likedBy: [], - comments: [], + cursor: '6411e53835d7ba2344a78e21', }); }); diff --git a/src/screens/OrgPost/OrgPost.tsx b/src/screens/OrgPost/OrgPost.tsx index 7c2089b359..fc0adc0556 100644 --- a/src/screens/OrgPost/OrgPost.tsx +++ b/src/screens/OrgPost/OrgPost.tsx @@ -1,35 +1,37 @@ -import type { ChangeEvent } from 'react'; -import React, { useState, useEffect } from 'react'; -import SortIcon from '@mui/icons-material/Sort'; +import { useMutation, useQuery, type ApolloError } from '@apollo/client'; import { Search } from '@mui/icons-material'; -import Row from 'react-bootstrap/Row'; -import Modal from 'react-bootstrap/Modal'; +import SortIcon from '@mui/icons-material/Sort'; +import { CREATE_POST_MUTATION } from 'GraphQl/Mutations/mutations'; +import { ORGANIZATION_POST_LIST } from 'GraphQl/Queries/Queries'; +import Loader from 'components/Loader/Loader'; +import NotFound from 'components/NotFound/NotFound'; +import OrgPostCard from 'components/OrgPostCard/OrgPostCard'; +import OrganizationScreen from 'components/OrganizationScreen/OrganizationScreen'; +import type { ChangeEvent } from 'react'; +import React, { useEffect, useState } from 'react'; import { Form } from 'react-bootstrap'; -import { useMutation, useQuery } from '@apollo/client'; import Button from 'react-bootstrap/Button'; -import { toast } from 'react-toastify'; -import { useTranslation } from 'react-i18next'; import Dropdown from 'react-bootstrap/Dropdown'; -import styles from './OrgPost.module.css'; -import OrgPostCard from 'components/OrgPostCard/OrgPostCard'; -import { ORGANIZATION_POST_CONNECTION_LIST } from 'GraphQl/Queries/Queries'; -import { CREATE_POST_MUTATION } from 'GraphQl/Mutations/mutations'; +import Modal from 'react-bootstrap/Modal'; +import Row from 'react-bootstrap/Row'; +import { useTranslation } from 'react-i18next'; +import { toast } from 'react-toastify'; import convertToBase64 from 'utils/convertToBase64'; -import NotFound from 'components/NotFound/NotFound'; import { errorHandler } from 'utils/errorHandler'; -import Loader from 'components/Loader/Loader'; -import OrganizationScreen from 'components/OrganizationScreen/OrganizationScreen'; +import type { InterfaceQueryOrganizationPostListItem } from 'utils/interfaces'; +import styles from './OrgPost.module.css'; interface InterfaceOrgPost { _id: string; title: string; text: string; - imageUrl: string; - videoUrl: string; - organizationId: string; - creator: { firstName: string; lastName: string }; + imageUrl: string | null; + videoUrl: string | null; + creator: { _id: string; firstName: string; lastName: string; email: string }; pinned: boolean; createdAt: string; + likeCount: number; + commentCount: number; } function orgPost(): JSX.Element { @@ -51,6 +53,10 @@ function orgPost(): JSX.Element { const [file, setFile] = useState(null); const currentUrl = window.location.href.split('=')[1]; const [showTitle, setShowTitle] = useState(true); + const [after, setAfter] = useState(null); + const [before, setBefore] = useState(null); + const [first, setFirst] = useState(6); + const [last, setLast] = useState(null); const showInviteModal = (): void => { setPostModalIsOpen(true); @@ -73,19 +79,35 @@ function orgPost(): JSX.Element { loading: orgPostListLoading, error: orgPostListError, refetch, - } = useQuery(ORGANIZATION_POST_CONNECTION_LIST, { - variables: { id: currentUrl, title_contains: '', text_contains: '' }, + }: { + data?: { + organizations: InterfaceQueryOrganizationPostListItem[]; + }; + loading: boolean; + error?: ApolloError; + refetch: any; + } = useQuery(ORGANIZATION_POST_LIST, { + variables: { + id: currentUrl, + after: after, + before: before, + first: first, + last: last, + }, }); const [create, { loading: createPostLoading }] = useMutation(CREATE_POST_MUTATION); const [displayedPosts, setDisplayedPosts] = useState( - orgPostListData?.postsByOrganizationConnection.edges || [], + orgPostListData?.organizations[0].posts.edges.map((edge) => edge.node) || + [], ); + // ... + useEffect(() => { - if (orgPostListData && orgPostListData.postsByOrganizationConnection) { - const newDisplayedPosts = sortPosts( - orgPostListData.postsByOrganizationConnection.edges, + if (orgPostListData && orgPostListData.organizations) { + const newDisplayedPosts: InterfaceOrgPost[] = sortPosts( + orgPostListData.organizations[0].posts.edges.map((edge) => edge.node), sortingOption, ); setDisplayedPosts(newDisplayedPosts); @@ -181,7 +203,19 @@ function orgPost(): JSX.Element { const handleSorting = (option: string): void => { setSortingOption(option); }; - + const handleNextPage = (): void => { + setAfter(orgPostListData?.organizations[0].posts.pageInfo.endCursor); + setBefore(null); + setFirst(6); + setLast(null); + }; + const handlePreviousPage = (): void => { + setBefore(orgPostListData?.organizations[0].posts.pageInfo.startCursor); + setAfter(null); + setFirst(null); + setLast(6); + }; + // console.log(orgPostListData?.organizations[0].posts); const sortPosts = ( posts: InterfaceOrgPost[], sortingOption: string, @@ -322,9 +356,9 @@ function orgPost(): JSX.Element { _id: string; title: string; text: string; - imageUrl: string; - videoUrl: string; - organizationId: string; + imageUrl: string | null; + videoUrl: string | null; + creator: { firstName: string; lastName: string }; pinned: boolean; }) => ( @@ -334,8 +368,8 @@ function orgPost(): JSX.Element { postTitle={datas.title} postInfo={datas.text} postAuthor={`${datas.creator.firstName} ${datas.creator.lastName}`} - postPhoto={datas.imageUrl} - postVideo={datas.videoUrl} + postPhoto={datas?.imageUrl} + postVideo={datas?.videoUrl} pinned={datas.pinned} /> ), @@ -345,6 +379,31 @@ function orgPost(): JSX.Element { )}
+
+
+ +
+
+ +
+
{ @@ -79,7 +78,7 @@ describe('Organisation Dashboard Page', () => { expect(screen.getByText('Membership requests')).toBeInTheDocument(); // Checking if posts are rendered - expect(screen.getByText('Post 15')).toBeInTheDocument(); + expect(screen.getByText('postone')).toBeInTheDocument(); // Checking if membership requests are rendered expect(screen.getByText('Jane Doe')).toBeInTheDocument(); @@ -129,7 +128,6 @@ describe('Organisation Dashboard Page', () => { screen.getByText(/No membership requests present/i), ).toBeInTheDocument(); expect(screen.getByText(/No upcoming events/i)).toBeInTheDocument(); - expect(screen.getByText(/No posts present/i)).toBeInTheDocument(); }); test('Testing error scenario', async () => { diff --git a/src/screens/OrganizationDashboard/OrganizationDashboard.tsx b/src/screens/OrganizationDashboard/OrganizationDashboard.tsx index 1d79693e8d..8e27b0b3b8 100644 --- a/src/screens/OrganizationDashboard/OrganizationDashboard.tsx +++ b/src/screens/OrganizationDashboard/OrganizationDashboard.tsx @@ -1,33 +1,34 @@ -import React, { useEffect, useState } from 'react'; import { useQuery } from '@apollo/client'; +import React, { useEffect, useState } from 'react'; import { Button, Card } from 'react-bootstrap'; import Col from 'react-bootstrap/Col'; import Row from 'react-bootstrap/Row'; import { useTranslation } from 'react-i18next'; +import type { ApolloError } from '@apollo/client'; import { ORGANIZATIONS_LIST, - ORGANIZATION_POST_CONNECTION_LIST, ORGANIZATION_EVENT_CONNECTION_LIST, + ORGANIZATION_POST_LIST, } from 'GraphQl/Queries/Queries'; import { ReactComponent as AdminsIcon } from 'assets/svgs/admin.svg'; import { ReactComponent as BlockedUsersIcon } from 'assets/svgs/blockedUser.svg'; import { ReactComponent as EventsIcon } from 'assets/svgs/events.svg'; import { ReactComponent as PostsIcon } from 'assets/svgs/post.svg'; import { ReactComponent as UsersIcon } from 'assets/svgs/users.svg'; +import CardItem from 'components/OrganizationDashCards/CardItem'; +import CardItemLoading from 'components/OrganizationDashCards/CardItemLoading'; import DashBoardCard from 'components/OrganizationDashCards/DashboardCard'; +import DashboardCardLoading from 'components/OrganizationDashCards/DashboardCardLoading'; import OrganizationScreen from 'components/OrganizationScreen/OrganizationScreen'; -import styles from './OrganizationDashboard.module.css'; -import CardItem from 'components/OrganizationDashCards/CardItem'; -import type { ApolloError } from '@apollo/client'; +import { useHistory } from 'react-router-dom'; +import { toast } from 'react-toastify'; import type { InterfaceQueryOrganizationEventListItem, + InterfaceQueryOrganizationPostListItem, InterfaceQueryOrganizationsListObject, } from 'utils/interfaces'; -import { toast } from 'react-toastify'; -import { useHistory } from 'react-router-dom'; -import CardItemLoading from 'components/OrganizationDashCards/CardItemLoading'; -import DashboardCardLoading from 'components/OrganizationDashCards/DashboardCardLoading'; +import styles from './OrganizationDashboard.module.css'; function organizationDashboard(): JSX.Element { const { t } = useTranslation('translation', { keyPrefix: 'dashboard' }); @@ -62,8 +63,14 @@ function organizationDashboard(): JSX.Element { data: postData, loading: loadingPost, error: errorPost, - } = useQuery(ORGANIZATION_POST_CONNECTION_LIST, { - variables: { id: currentUrl }, + }: { + data?: { + organizations: InterfaceQueryOrganizationPostListItem[]; + }; + loading: boolean; + error?: ApolloError; + } = useQuery(ORGANIZATION_POST_LIST, { + variables: { id: currentUrl, first: 10 }, }); const { @@ -156,9 +163,7 @@ function organizationDashboard(): JSX.Element { }} > } /> @@ -272,15 +277,17 @@ function organizationDashboard(): JSX.Element { [...Array(4)].map((_, index) => { return ; }) - ) : postData?.postsByOrganizationConnection.edges.length == - 0 ? ( + ) : postData?.organizations[0].posts.totalCount == 0 ? ( + /* eslint-disable */
{t('noPostsPresent')}
) : ( - postData?.postsByOrganizationConnection.edges + /* eslint-enable */ + postData?.organizations[0].posts.edges .slice(0, 5) - .map((post: any) => { + .map((edge: any) => { + const post = edge.node; return ( input { text-decoration: none; diff --git a/src/screens/UserPortal/Donate/Donate.test.tsx b/src/screens/UserPortal/Donate/Donate.test.tsx index 6db29b8571..3d2419d024 100644 --- a/src/screens/UserPortal/Donate/Donate.test.tsx +++ b/src/screens/UserPortal/Donate/Donate.test.tsx @@ -55,8 +55,40 @@ const MOCKS = [ image: '', name: 'anyOrganization2', description: 'desc', + address: { + city: 'abc', + countryCode: '123', + postalCode: '456', + state: 'def', + dependentLocality: 'ghi', + line1: 'asdfg', + line2: 'dfghj', + sortingCode: '4567', + }, userRegistrationRequired: true, + createdAt: '12345678900', creator: { __typename: 'User', firstName: 'John', lastName: 'Doe' }, + members: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + admins: [ + { + _id: '45gj5678jk45678fvgbhnr4rtgh', + }, + ], + membershipRequests: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], }, ], }, diff --git a/src/screens/UserPortal/Events/Events.module.css b/src/screens/UserPortal/Events/Events.module.css index 52a305cc20..7df55f7414 100644 --- a/src/screens/UserPortal/Events/Events.module.css +++ b/src/screens/UserPortal/Events/Events.module.css @@ -81,3 +81,76 @@ flex-wrap: wrap; margin-top: 20px; } +.titlemodal { + color: #707070; + font-weight: 600; + font-size: 20px; + margin-bottom: 20px; + padding-bottom: 5px; + border-bottom: 3px solid #31bb6b; + width: 65%; +} + +.datediv { + display: flex; + flex-direction: row; + margin-bottom: 15px; +} + +.datebox { + width: 90%; + border-radius: 7px; + border-color: #e8e5e5; + outline: none; + box-shadow: none; + padding-top: 2px; + padding-bottom: 2px; + padding-right: 5px; + padding-left: 5px; + margin-right: 5px; + margin-left: 5px; +} + +.checkboxdiv > label { + margin-right: 50px; +} +.checkboxdiv > label > input { + margin-left: 10px; +} + +.checkboxdiv { + display: flex; +} +.checkboxdiv > div { + width: 50%; +} + +.dispflex { + display: flex; + align-items: center; +} +.dispflex > input { + border: none; + box-shadow: none; + margin-top: 5px; +} + +.greenregbtn { + margin: 1rem 0 0; + margin-top: 15px; + border: 1px solid #e8e5e5; + box-shadow: 0 2px 2px #e8e5e5; + padding: 10px 10px; + border-radius: 5px; + background-color: #31bb6b; + width: 100%; + font-size: 16px; + color: white; + outline: none; + font-weight: 600; + cursor: pointer; + transition: + transform 0.2s, + box-shadow 0.2s; + width: 100%; +} diff --git a/src/screens/UserPortal/Events/Events.test.tsx b/src/screens/UserPortal/Events/Events.test.tsx index 37335db38b..a812a751cd 100644 --- a/src/screens/UserPortal/Events/Events.test.tsx +++ b/src/screens/UserPortal/Events/Events.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { act, render, screen } from '@testing-library/react'; +import { act, fireEvent, render, screen } from '@testing-library/react'; import { MockedProvider } from '@apollo/react-testing'; import { I18nextProvider } from 'react-i18next'; @@ -15,6 +15,10 @@ import * as getOrganizationId from 'utils/getOrganizationId'; import { CREATE_EVENT_MUTATION } from 'GraphQl/Mutations/mutations'; import { toast } from 'react-toastify'; import dayjs from 'dayjs'; +import { LocalizationProvider } from '@mui/x-date-pickers'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { ThemeProvider } from 'react-bootstrap'; +import { createTheme } from '@mui/material'; jest.mock('react-toastify', () => ({ toast: { @@ -24,6 +28,28 @@ jest.mock('react-toastify', () => ({ }, })); +jest.mock('@mui/x-date-pickers/DatePicker', () => { + return { + DatePicker: jest.requireActual('@mui/x-date-pickers/DesktopDatePicker') + .DesktopDatePicker, + }; +}); + +jest.mock('@mui/x-date-pickers/TimePicker', () => { + return { + TimePicker: jest.requireActual('@mui/x-date-pickers/DesktopTimePicker') + .DesktopTimePicker, + }; +}); + +const theme = createTheme({ + palette: { + primary: { + main: '#31bb6b', + }, + }, +}); + const MOCKS = [ { request: { @@ -392,9 +418,13 @@ describe('Testing Events Screen [User Portal]', () => { - - - + + + + + + + , @@ -453,14 +483,17 @@ describe('Testing Events Screen [User Portal]', () => { - - - + + + + + + + , ); - await wait(); expect(getOrganizationIdSpy).toHaveBeenCalled(); @@ -500,9 +533,13 @@ describe('Testing Events Screen [User Portal]', () => { - - - + + + + + + + , @@ -520,4 +557,117 @@ describe('Testing Events Screen [User Portal]', () => { expect(screen.queryAllByText(calenderView)).not.toBeNull(); expect(screen.getByText('Sun')).toBeInTheDocument(); }); + + test('Testing DatePicker and TimePicker', async () => { + const getOrganizationIdSpy = jest + .spyOn(getOrganizationId, 'default') + .mockImplementation(() => { + return ''; + }); + + render( + + + + + + + + + + + + + , + ); + + await wait(); + expect(getOrganizationIdSpy).toHaveBeenCalled(); + + const startDate = '03/23/2024'; + const endDate = '04/23/2024'; + const startTime = '02:00 PM'; + const endTime = '06:00 PM'; + + userEvent.click(screen.getByTestId('createEventModalBtn')); + + expect(endDate).not.toBeNull(); + const endDateDatePicker = screen.getByLabelText('End Date'); + expect(startDate).not.toBeNull(); + const startDateDatePicker = screen.getByLabelText('Start Date'); + + fireEvent.change(startDateDatePicker, { + target: { value: startDate }, + }); + fireEvent.change(endDateDatePicker, { + target: { value: endDate }, + }); + + await wait(); + + expect(endDateDatePicker).toHaveValue(endDate); + expect(startDateDatePicker).toHaveValue(startDate); + + userEvent.click(screen.getByTestId('allDayEventCheck')); + + expect(endTime).not.toBeNull(); + const endTimePicker = screen.getByLabelText('End Time'); + expect(startTime).not.toBeNull(); + const startTimePicker = screen.getByLabelText('Start Time'); + + fireEvent.change(startTimePicker, { + target: { value: startTime }, + }); + fireEvent.change(endTimePicker, { + target: { value: endTime }, + }); + + await wait(); + expect(endTimePicker).toHaveValue(endTime); + expect(startTimePicker).toHaveValue(startTime); + }); + + test('EndDate null', async () => { + render( + + + + + + + + + + + + + , + ); + + await wait(); + + userEvent.click(screen.getByTestId('createEventModalBtn')); + + const endDateDatePicker = screen.getByLabelText('End Date'); + const startDateDatePicker = screen.getByLabelText('Start Date'); + + fireEvent.change(startDateDatePicker, { + target: { value: null }, + }); + fireEvent.change(endDateDatePicker, { + target: { value: null }, + }); + + userEvent.click(screen.getByTestId('allDayEventCheck')); + + const endTimePicker = screen.getByLabelText('End Time'); + const startTimePicker = screen.getByLabelText('Start Time'); + + fireEvent.change(startTimePicker, { + target: { value: null }, + }); + fireEvent.change(endTimePicker, { + target: { value: null }, + }); + }); }); diff --git a/src/screens/UserPortal/Events/Events.tsx b/src/screens/UserPortal/Events/Events.tsx index 1c35192a02..db09189deb 100644 --- a/src/screens/UserPortal/Events/Events.tsx +++ b/src/screens/UserPortal/Events/Events.tsx @@ -1,3 +1,4 @@ +import type { ChangeEvent } from 'react'; import React from 'react'; import OrganizationNavbar from 'components/UserPortal/OrganizationNavbar/OrganizationNavbar'; import OrganizationSidebar from 'components/UserPortal/OrganizationSidebar/OrganizationSidebar'; @@ -16,9 +17,10 @@ import { useTranslation } from 'react-i18next'; import HourglassBottomIcon from '@mui/icons-material/HourglassBottom'; import getOrganizationId from 'utils/getOrganizationId'; import Modal from 'react-bootstrap/Modal'; -import ReactDatePicker from 'react-datepicker'; -import { CREATE_EVENT_MUTATION } from 'GraphQl/Mutations/mutations'; +import { TimePicker, DatePicker } from '@mui/x-date-pickers'; +import type { Dayjs } from 'dayjs'; import dayjs from 'dayjs'; +import { CREATE_EVENT_MUTATION } from 'GraphQl/Mutations/mutations'; import { toast } from 'react-toastify'; import { errorHandler } from 'utils/errorHandler'; import EventCalendar from 'components/EventCalendar/EventCalendar'; @@ -47,6 +49,11 @@ interface InterfaceEventCardProps { }[]; } +const timeToDayJs = (time: string): Dayjs => { + const dateTimeString = dayjs().format('YYYY-MM-DD') + ' ' + time; + return dayjs(dateTimeString, { format: 'YYYY-MM-DD HH:mm:ss' }); +}; + export default function events(): JSX.Element { const { t } = useTranslation('translation', { keyPrefix: 'userEvents', @@ -62,8 +69,8 @@ export default function events(): JSX.Element { const [eventTitle, setEventTitle] = React.useState(''); const [eventDescription, setEventDescription] = React.useState(''); const [eventLocation, setEventLocation] = React.useState(''); - const [startDate, setStartDate] = React.useState(new Date()); - const [endDate, setEndDate] = React.useState(new Date()); + const [startDate, setStartDate] = React.useState(new Date()); + const [endDate, setEndDate] = React.useState(new Date()); const [isPublic, setIsPublic] = React.useState(true); const [isRegisterable, setIsRegisterable] = React.useState(true); const [isRecurring, setIsRecurring] = React.useState(false); @@ -91,7 +98,10 @@ export default function events(): JSX.Element { const userId = getItem('id') as string; const userRole = getItem('UserType') as string; - const createEvent = async (): Promise => { + const createEvent = async ( + e: ChangeEvent, + ): Promise => { + e.preventDefault(); try { const { data: createEventData } = await create({ variables: { @@ -187,16 +197,6 @@ export default function events(): JSX.Element { setEventDescription(event.target.value); }; - /* istanbul ignore next */ - const handleStartDateChange = (newDate: any): void => { - setStartDate(newDate); - }; - - /* istanbul ignore next */ - const handleEndDateChange = (newDate: any): void => { - setEndDate(newDate); - }; - /* istanbul ignore next */ React.useEffect(() => { if (data) { @@ -360,160 +360,170 @@ export default function events(): JSX.Element { -
{t('createEvent')}
-
- - - - {t('eventTitle')} - + + + - - - - - {t('eventDescription')} - + - - - - - {t('eventLocation')} - + - -
{t('startDate')}
- -
{t('endDate')}
- -
-
- - setIsPublic(!isPublic)} - /> -
- -
- - setIsRegisterable(!isRegisterable)} - /> -
- -
- - setIsRecurring(!isRecurring)} - /> -
- -
- - setIsAllDay(!isAllDay)} - /> +
+
+ { + if (date) { + setStartDate(date?.toDate()); + setEndDate(date?.toDate()); + } + }} + data-testid="eventStartDate" + /> +
+
+ { + if (date) { + setEndDate(date?.toDate()); + } + }} + minDate={dayjs(startDate)} + data-testid="eventEndDate" + /> +
- - {!isAllDay && ( -
-
- - setStartTime(e.target.value) +
+
+ { + if (time) { + setStartTime(time?.format('HH:mm:ss')); + setEndTime(time?.format('HH:mm:ss')); } - /> -
-
- - setEndTime(e.target.value) + }} + disabled={isAllDay} + /> +
+
+ { + if (time) { + setEndTime(time?.format('HH:mm:ss')); } - /> -
+ }} + minTime={timeToDayJs(startTime)} + disabled={isAllDay} + />
- )} -
+
+
+
+ + setIsAllDay(!isAllDay)} + /> +
+
+ + setIsRecurring(!isRecurring)} + /> +
+
+
+
+ + setIsPublic(!isPublic)} + /> +
+
+ + setIsRegisterable(!isRegisterable)} + /> +
+
+ + - - - -
diff --git a/src/screens/UserPortal/Home/Home.test.tsx b/src/screens/UserPortal/Home/Home.test.tsx index 8ee0edd8e3..b3c9bbc18e 100644 --- a/src/screens/UserPortal/Home/Home.test.tsx +++ b/src/screens/UserPortal/Home/Home.test.tsx @@ -1,24 +1,23 @@ -import React from 'react'; -import { act, fireEvent, render, screen, within } from '@testing-library/react'; import { MockedProvider } from '@apollo/react-testing'; +import { act, fireEvent, render, screen, within } from '@testing-library/react'; import { I18nextProvider } from 'react-i18next'; +import userEvent from '@testing-library/user-event'; +import { REACT_APP_CUSTOM_PORT } from 'Constant/constant'; +import { CREATE_POST_MUTATION } from 'GraphQl/Mutations/mutations'; import { - ORGANIZATION_POST_CONNECTION_LIST, ADVERTISEMENTS_GET, + ORGANIZATION_POST_LIST, } from 'GraphQl/Queries/Queries'; -import { BrowserRouter } from 'react-router-dom'; import { Provider } from 'react-redux'; +import { BrowserRouter } from 'react-router-dom'; +import { toast } from 'react-toastify'; import { store } from 'state/store'; -import i18nForTest from 'utils/i18nForTest'; import { StaticMockLink } from 'utils/StaticMockLink'; -import Home from './Home'; -import userEvent from '@testing-library/user-event'; import * as getOrganizationId from 'utils/getOrganizationId'; -import { CREATE_POST_MUTATION } from 'GraphQl/Mutations/mutations'; -import { toast } from 'react-toastify'; -import dayjs from 'dayjs'; -import { REACT_APP_CUSTOM_PORT } from 'Constant/constant'; +import i18nForTest from 'utils/i18nForTest'; +import Home from './Home'; +import React from 'react'; jest.mock('react-toastify', () => ({ toast: { @@ -44,96 +43,120 @@ const EMPTY_MOCKS = [ const MOCKS = [ { request: { - query: ORGANIZATION_POST_CONNECTION_LIST, + query: ORGANIZATION_POST_LIST, variables: { id: '', + first: 10, + after: null, + before: null, + last: null, }, }, result: { data: { - postsByOrganizationConnection: { - edges: [ - { - _id: '6411e53835d7ba2344a78e21', - title: 'postone', - text: 'THis is the frist post', - imageUrl: null, - videoUrl: null, - creator: { - _id: '640d98d9eb6a743d75341067', - firstName: 'Aditya', - lastName: 'Shelke', - email: 'adidacreator1@gmail.com', - }, - createdAt: dayjs(new Date()).add(1, 'day'), - likeCount: 0, - commentCount: 0, - comments: [], - likedBy: [], - pinned: false, - }, - { - _id: '6411e54835d7ba2344a78e29', - title: 'posttwo', - text: 'THis is the post two', - imageUrl: null, - videoUrl: null, - creator: { - _id: '640d98d9eb6a743d75341067', - firstName: 'Aditya', - lastName: 'Shelke', - email: 'adidacreator1@gmail.com', - }, - createdAt: dayjs(new Date()).add(1, 'day'), - likeCount: 0, - commentCount: 2, - comments: [ + organizations: [ + { + posts: { + edges: [ { - _id: '64eb13beca85de60ebe0ed0e', - creator: { - _id: '63d6064458fce20ee25c3bf7', - firstName: 'Noble', - lastName: 'Mittal', - email: 'test@gmail.com', - __typename: 'User', + node: { + _id: '6411e53835d7ba2344a78e21', + title: 'postone', + text: 'This is the first post', + imageUrl: null, + videoUrl: null, + createdAt: '2023-08-24T09:26:56.524+00:00', + creator: { + _id: '640d98d9eb6a743d75341067', + firstName: 'Aditya', + lastName: 'Shelke', + email: 'adidacreator1@gmail.com', + }, + likeCount: 0, + commentCount: 0, + comments: [], + pinned: true, + likedBy: [], }, - likeCount: 1, - likedBy: [ - { - _id: 1, + cursor: '6411e53835d7ba2344a78e21', + }, + { + node: { + _id: '6411e54835d7ba2344a78e29', + title: 'posttwo', + text: 'Tis is the post two', + imageUrl: null, + videoUrl: null, + createdAt: '2023-08-24T09:26:56.524+00:00', + creator: { + _id: '640d98d9eb6a743d75341067', + firstName: 'Aditya', + lastName: 'Shelke', + email: 'adidacreator1@gmail.com', }, - ], - text: 'First comment from Talawa user portal.', - __typename: 'Comment', + likeCount: 0, + commentCount: 0, + pinned: false, + likedBy: [], + comments: [], + }, + cursor: '6411e54835d7ba2344a78e29', }, { - _id: '64eb483aca85de60ebe0ef99', - creator: { - _id: '63d6064458fce20ee25c3bf7', - firstName: 'Noble', - lastName: 'Mittal', - email: 'test@gmail.com', - createdAt: '2023-02-18T09:22:27.969Z', - - __typename: 'User', + node: { + _id: '6411e54835d7ba2344a78e30', + title: 'posttwo', + text: 'Tis is the post two', + imageUrl: null, + videoUrl: null, + createdAt: '2023-08-24T09:26:56.524+00:00', + creator: { + _id: '640d98d9eb6a743d75341067', + firstName: 'Aditya', + lastName: 'Shelke', + email: 'adidacreator1@gmail.com', + }, + likeCount: 0, + commentCount: 0, + pinned: true, + likedBy: [], + comments: [], }, - likeCount: 0, - likedBy: [], - text: 'Great View', - __typename: 'Comment', + cursor: '6411e54835d7ba2344a78e30', }, - ], - likedBy: [ { - _id: '63d6064458fce20ee25c3bf7', - firstName: 'test', - lastName: 'abc', + node: { + _id: '6411e54835d7ba2344a78e31', + title: 'posttwo', + text: 'Tis is the post two', + imageUrl: null, + videoUrl: null, + createdAt: '2023-08-24T09:26:56.524+00:00', + creator: { + _id: '640d98d9eb6a743d75341067', + firstName: 'Aditya', + lastName: 'Shelke', + email: 'adidacreator1@gmail.com', + }, + likeCount: 0, + commentCount: 0, + pinned: false, + likedBy: [], + comments: [], + }, + cursor: '6411e54835d7ba2344a78e31', }, ], - pinned: false, + pageInfo: { + startCursor: '6411e53835d7ba2344a78e21', + endCursor: '6411e54835d7ba2344a78e31', + hasNextPage: false, + hasPreviousPage: false, + }, + totalCount: 4, }, - ], - }, + }, + ], }, }, }, diff --git a/src/screens/UserPortal/Home/Home.tsx b/src/screens/UserPortal/Home/Home.tsx index 9e2ee7c51d..bf6d6a6501 100644 --- a/src/screens/UserPortal/Home/Home.tsx +++ b/src/screens/UserPortal/Home/Home.tsx @@ -1,36 +1,36 @@ -import React, { useEffect, useRef, useState } from 'react'; -import type { ChangeEvent } from 'react'; +import { useMutation, useQuery } from '@apollo/client'; +import ChevronRightIcon from '@mui/icons-material/ChevronRight'; +import HourglassBottomIcon from '@mui/icons-material/HourglassBottom'; +import { CREATE_POST_MUTATION } from 'GraphQl/Mutations/mutations'; +import { + ADVERTISEMENTS_GET, + ORGANIZATION_POST_LIST, + USER_DETAILS, +} from 'GraphQl/Queries/Queries'; import OrganizationNavbar from 'components/UserPortal/OrganizationNavbar/OrganizationNavbar'; -import styles from './Home.module.css'; +import PostCard from 'components/UserPortal/PostCard/PostCard'; +import PromotedPost from 'components/UserPortal/PromotedPost/PromotedPost'; import UserSidebar from 'components/UserPortal/UserSidebar/UserSidebar'; -import ChevronRightIcon from '@mui/icons-material/ChevronRight'; +import type { ChangeEvent } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { Button, - Form, Col, Container, + Form, Image, - Row, Modal, + Row, } from 'react-bootstrap'; -import { Link } from 'react-router-dom'; -import getOrganizationId from 'utils/getOrganizationId'; -import PostCard from 'components/UserPortal/PostCard/PostCard'; -import { useMutation, useQuery } from '@apollo/client'; -import { - ADVERTISEMENTS_GET, - ORGANIZATION_POST_CONNECTION_LIST, - USER_DETAILS, -} from 'GraphQl/Queries/Queries'; -import { CREATE_POST_MUTATION } from 'GraphQl/Mutations/mutations'; -import { errorHandler } from 'utils/errorHandler'; import { useTranslation } from 'react-i18next'; -import convertToBase64 from 'utils/convertToBase64'; +import { Link } from 'react-router-dom'; import { toast } from 'react-toastify'; -import HourglassBottomIcon from '@mui/icons-material/HourglassBottom'; -import PromotedPost from 'components/UserPortal/PromotedPost/PromotedPost'; -import UserDefault from '../../../assets/images/defaultImg.png'; +import convertToBase64 from 'utils/convertToBase64'; +import { errorHandler } from 'utils/errorHandler'; +import getOrganizationId from 'utils/getOrganizationId'; import useLocalStorage from 'utils/useLocalstorage'; +import UserDefault from '../../../assets/images/defaultImg.png'; +import styles from './Home.module.css'; interface InterfacePostCardProps { id: string; @@ -101,8 +101,8 @@ export default function home(): JSX.Element { data, refetch, loading: loadingPosts, - } = useQuery(ORGANIZATION_POST_CONNECTION_LIST, { - variables: { id: organizationId }, + } = useQuery(ORGANIZATION_POST_LIST, { + variables: { id: organizationId, first: 10 }, }); const userId: string | null = getItem('userId'); @@ -151,7 +151,7 @@ export default function home(): JSX.Element { React.useEffect(() => { if (data) { - setPosts(data.postsByOrganizationConnection.edges); + setPosts(data.organizaitons[0].posts.edges); } }, [data]); diff --git a/src/screens/UserPortal/Organizations/Organizations.module.css b/src/screens/UserPortal/Organizations/Organizations.module.css index 1f49c0d158..e29fd53d43 100644 --- a/src/screens/UserPortal/Organizations/Organizations.module.css +++ b/src/screens/UserPortal/Organizations/Organizations.module.css @@ -6,18 +6,13 @@ color: white; } -.maxWidth { - max-width: 300px; -} - .colorLight { background-color: #f5f5f5; } .mainContainer { - width: 50%; - flex-grow: 3; - padding: 40px; + width: 75%; + padding: 20px; max-height: 100%; overflow: auto; } @@ -27,10 +22,6 @@ min-height: calc(100% - 40px); } -.gap { - gap: 20px; -} - .paddingY { padding: 30px 0px; } @@ -46,3 +37,19 @@ .backgroundWhite { background-color: white; } + +.input { + flex: 1; + position: relative; + margin-right: 10px; +} + +.btnsContainer input { + outline: 1px solid var(--bs-gray-400); +} + +@media screen and (max-width: 700px) { + .mainContainer { + width: 100%; + } +} diff --git a/src/screens/UserPortal/Organizations/Organizations.test.tsx b/src/screens/UserPortal/Organizations/Organizations.test.tsx index e92b17d2fe..a142d0c37e 100644 --- a/src/screens/UserPortal/Organizations/Organizations.test.tsx +++ b/src/screens/UserPortal/Organizations/Organizations.test.tsx @@ -61,17 +61,87 @@ const MOCKS = [ image: '', name: 'anyOrganization1', description: 'desc', + address: { + city: 'abc', + countryCode: '123', + postalCode: '456', + state: 'def', + dependentLocality: 'ghi', + line1: 'asdfg', + line2: 'dfghj', + sortingCode: '4567', + }, + createdAt: '1234567890', userRegistrationRequired: true, creator: { __typename: 'User', firstName: 'John', lastName: 'Doe' }, + members: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + admins: [ + { + _id: '45gj5678jk45678fvgbhnr4rtgh', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + membershipRequests: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], }, { __typename: 'Organization', _id: '6401ff65ce8e8406b8f07af3', image: '', name: 'anyOrganization2', + createdAt: '1234567890', + address: { + city: 'abc', + countryCode: '123', + postalCode: '456', + state: 'def', + dependentLocality: 'ghi', + line1: 'asdfg', + line2: 'dfghj', + sortingCode: '4567', + }, description: 'desc', userRegistrationRequired: true, creator: { __typename: 'User', firstName: 'John', lastName: 'Doe' }, + members: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + admins: [ + { + _id: '45gj5678jk45678fvgbhnr4rtgh', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + membershipRequests: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], }, ], }, @@ -93,6 +163,7 @@ const MOCKS = [ __typename: 'Organization', _id: '6401ff65ce8e8406b8f07af2', name: 'joinedOrganization', + createdAt: '1234567890', image: '', description: 'New Desc', }, @@ -118,8 +189,43 @@ const MOCKS = [ image: '', name: 'anyOrganization2', description: 'desc', + address: { + city: 'abc', + countryCode: '123', + postalCode: '456', + state: 'def', + dependentLocality: 'ghi', + line1: 'asdfg', + line2: 'dfghj', + sortingCode: '4567', + }, userRegistrationRequired: true, + createdAt: '1234567890', creator: { __typename: 'User', firstName: 'John', lastName: 'Doe' }, + members: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + admins: [ + { + _id: '45gj5678jk45678fvgbhnr4rtgh', + user: { + _id: '4567890fgvhbjn', + }, + }, + ], + membershipRequests: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], }, ], }, diff --git a/src/screens/UserPortal/Organizations/Organizations.tsx b/src/screens/UserPortal/Organizations/Organizations.tsx index b6dd7c994a..d089ac63a1 100644 --- a/src/screens/UserPortal/Organizations/Organizations.tsx +++ b/src/screens/UserPortal/Organizations/Organizations.tsx @@ -2,7 +2,7 @@ import React from 'react'; import UserNavbar from 'components/UserPortal/UserNavbar/UserNavbar'; import OrganizationCard from 'components/UserPortal/OrganizationCard/OrganizationCard'; import UserSidebar from 'components/UserPortal/UserSidebar/UserSidebar'; -import { Dropdown, Form, InputGroup } from 'react-bootstrap'; +import { Button, Dropdown, Form, InputGroup } from 'react-bootstrap'; import PaginationList from 'components/PaginationList/PaginationList'; import { CHECK_AUTH, @@ -11,7 +11,7 @@ import { USER_ORGANIZATION_CONNECTION, } from 'GraphQl/Queries/Queries'; import { useQuery } from '@apollo/client'; -import { SearchOutlined } from '@mui/icons-material'; +import { Search } from '@mui/icons-material'; import styles from './Organizations.module.css'; import { useTranslation } from 'react-i18next'; import HourglassBottomIcon from '@mui/icons-material/HourglassBottom'; @@ -24,6 +24,22 @@ interface InterfaceOrganizationCardProps { name: string; image: string; description: string; + admins: []; + members: []; + address: { + city: string; + countryCode: string; + postalCode: string; + state: string; + }; + membershipRequestStatus: string; + userRegistrationRequired: boolean; + membershipRequests: { + _id: string; + user: { + _id: string; + }; + }[]; } export default function organizations(): JSX.Element { const { t } = useTranslation('translation', { @@ -45,20 +61,26 @@ export default function organizations(): JSX.Element { const userId: string | null = getItem('userId'); const { - data, + data: organizationsData, refetch, loading: loadingOrganizations, } = useQuery(USER_ORGANIZATION_CONNECTION, { variables: { filter: filterName }, }); - const { data: data2 } = useQuery(USER_JOINED_ORGANIZATIONS, { - variables: { id: userId }, - }); + const { data: joinedOrganizationsData } = useQuery( + USER_JOINED_ORGANIZATIONS, + { + variables: { id: userId }, + }, + ); - const { data: data3 } = useQuery(USER_CREATED_ORGANIZATIONS, { - variables: { id: userId }, - }); + const { data: createdOrganizationsData } = useQuery( + USER_CREATED_ORGANIZATIONS, + { + variables: { id: userId }, + }, + ); const { data: userData, loading } = useQuery(CHECK_AUTH, { fetchPolicy: 'network-only', @@ -104,25 +126,76 @@ export default function organizations(): JSX.Element { /* istanbul ignore next */ React.useEffect(() => { - if (data) { - setOrganizations(data.organizationsConnection); + if (organizationsData) { + const organizations = organizationsData.organizationsConnection.map( + (organization: any) => { + let membershipRequestStatus = ''; + if ( + organization.members.find( + (member: { _id: string }) => member._id === userId, + ) + ) + membershipRequestStatus = 'accepted'; + else if ( + organization.membershipRequests.find( + (request: { user: { _id: string } }) => + request.user._id === userId, + ) + ) + membershipRequestStatus = 'pending'; + return { ...organization, membershipRequestStatus }; + }, + ); + setOrganizations(organizations); } - }, [data]); + }, [organizationsData]); /* istanbul ignore next */ React.useEffect(() => { if (mode == 0) { - if (data) { - setOrganizations(data.organizationsConnection); + if (organizationsData) { + const organizations = organizationsData.organizationsConnection.map( + (organization: any) => { + let membershipRequestStatus = ''; + if ( + organization.members.find( + (member: { _id: string }) => member._id === userId, + ) + ) + membershipRequestStatus = 'accepted'; + else if ( + organization.membershipRequests.find( + (request: { user: { _id: string } }) => + request.user._id === userId, + ) + ) + membershipRequestStatus = 'pending'; + return { ...organization, membershipRequestStatus }; + }, + ); + setOrganizations(organizations); } } else if (mode == 1) { - if (data2) { - setOrganizations(data2.users[0].joinedOrganizations); + console.log(joinedOrganizationsData, 'joined', userId); + if (joinedOrganizationsData) { + const membershipRequestStatus = 'accepted'; + const organizations = + joinedOrganizationsData?.users[0].joinedOrganizations.map( + (organization: any) => { + return { ...organization, membershipRequestStatus }; + }, + ); + setOrganizations(organizations); } } else if (mode == 2) { - if (data3) { - setOrganizations(data3.users[0].createdOrganizations); - } + const membershipRequestStatus = 'accepted'; + const organizations = + createdOrganizationsData?.users[0].createdOrganizations.map( + (organization: any) => { + return { ...organization, membershipRequestStatus }; + }, + ); + setOrganizations(organizations); } }, [mode]); @@ -150,32 +223,36 @@ export default function organizations(): JSX.Element {
-

{t('selectOrganization')}

+

{t('organizations')}

- - - - +
+ + +
@@ -196,60 +273,62 @@ export default function organizations(): JSX.Element {
- -
-
- {loadingOrganizations ? ( -
- Loading... -
- ) : ( - <> - {' '} - {organizations && organizations.length > 0 ? ( - (rowsPerPage > 0 - ? organizations.slice( - page * rowsPerPage, - page * rowsPerPage + rowsPerPage, - ) - : /* istanbul ignore next */ - organizations - ).map((organization: any, index) => { - const cardProps: InterfaceOrganizationCardProps = { - name: organization.name, - image: organization.image, - id: organization._id, - description: organization.description, - }; - return ; - }) - ) : ( - {t('nothingToShow')} - )} - - )} -
- - - - - - -
+
+ {loadingOrganizations ? ( +
+ Loading... +
+ ) : ( + <> + {' '} + {organizations && organizations?.length > 0 ? ( + (rowsPerPage > 0 + ? organizations.slice( + page * rowsPerPage, + page * rowsPerPage + rowsPerPage, + ) + : /* istanbul ignore next */ + organizations + ).map((organization: any, index) => { + const cardProps: InterfaceOrganizationCardProps = { + name: organization.name, + image: organization.image, + id: organization._id, + description: organization.description, + admins: organization.admins, + members: organization.members, + address: organization.address, + membershipRequestStatus: + organization.membershipRequestStatus, + userRegistrationRequired: + organization.userRegistrationRequired, + membershipRequests: organization.membershipRequests, + }; + return ; + }) + ) : ( + {t('nothingToShow')} + )} + + )}
+ + + + + + + +
diff --git a/src/screens/UserPortal/UserLoginPage/UserLoginPage.module.css b/src/screens/UserPortal/UserLoginPage/UserLoginPage.module.css deleted file mode 100644 index 413df8cecd..0000000000 --- a/src/screens/UserPortal/UserLoginPage/UserLoginPage.module.css +++ /dev/null @@ -1,208 +0,0 @@ -.login_background { - min-height: 100vh; -} - -.row .left_portion { - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; - height: 100vh; -} - -.row .left_portion .inner .palisadoes_logo { - width: 600px; - height: auto; -} - -.row .right_portion { - min-height: 100vh; - position: relative; - overflow-y: scroll; - display: flex; - flex-direction: column; - justify-content: center; - padding: 1rem 2.5rem; - background: var(--bs-white); -} - -.row .right_portion::-webkit-scrollbar { - display: none; -} - -.row .right_portion .langChangeBtn { - margin: 0; - position: absolute; - top: 1rem; - left: 1rem; -} - -.langChangeBtnStyle { - width: 7.5rem; - height: 2.2rem; - padding: 0; -} - -.row .right_portion .talawa_logo { - height: 5rem; - width: 5rem; - display: block; - margin: 1.5rem auto 1rem; - -webkit-animation: zoomIn 0.3s ease-in-out; - animation: zoomIn 0.3s ease-in-out; -} - -.row .orText { - display: block; - position: absolute; - top: calc(-0.7rem - 0.5rem); - left: calc(50% - 2.6rem); - margin: 0 auto; - padding: 0.5rem 2rem; - z-index: 100; - background: var(--bs-white); - color: var(--bs-secondary); -} - -@media (max-width: 992px) { - .row .left_portion { - padding: 0 2rem; - } - - .row .left_portion .inner .palisadoes_logo { - width: 100%; - } -} - -@media (max-width: 769px) { - .row { - flex-direction: column-reverse; - } - - .row .right_portion, - .row .left_portion { - height: unset; - } - - .row .right_portion { - min-height: 100vh; - overflow-y: unset; - } - - .row .left_portion .inner { - display: flex; - justify-content: center; - } - - .row .left_portion .inner .palisadoes_logo { - height: 70px; - width: unset; - position: absolute; - margin: 0.5rem; - top: 0; - right: 0; - z-index: 100; - } - - .row .left_portion .inner p { - margin-bottom: 0; - padding: 1rem; - } - - .socialIcons { - margin-bottom: 1rem; - } -} - -@media (max-width: 577px) { - .row .right_portion { - padding: 1rem 1rem 0 1rem; - } - - .row .right_portion .langChangeBtn { - position: absolute; - margin: 1rem; - left: 0; - top: 0; - } - - .marginTopForReg { - margin-top: 4rem !important; - } - - .row .right_portion .talawa_logo { - height: 120px; - margin: 0 auto 2rem auto; - } - - .socialIcons { - margin-bottom: 1rem; - } -} - -.active_tab { - -webkit-animation: fadeIn 0.3s ease-in-out; - animation: fadeIn 0.3s ease-in-out; -} - -@-webkit-keyframes zoomIn { - 0% { - opacity: 0; - -webkit-transform: scale(0.5); - transform: scale(0.5); - } - - 100% { - opacity: 1; - -webkit-transform: scale(1); - transform: scale(1); - } -} - -@keyframes zoomIn { - 0% { - opacity: 0; - -webkit-transform: scale(0.5); - transform: scale(0.5); - } - - 100% { - opacity: 1; - -webkit-transform: scale(1); - transform: scale(1); - } -} - -@-webkit-keyframes fadeIn { - 0% { - opacity: 0; - -webkit-transform: translateY(2rem); - transform: translateY(2rem); - } - - 100% { - opacity: 1; - -webkit-transform: translateY(0); - transform: translateY(0); - } -} - -@keyframes fadeIn { - 0% { - opacity: 0; - -webkit-transform: translateY(2rem); - transform: translateY(2rem); - } - - 100% { - opacity: 1; - -webkit-transform: translateY(0); - transform: translateY(0); - } -} - -.socialIcons { - display: flex; - gap: 16px; - justify-content: center; -} diff --git a/src/screens/UserPortal/UserLoginPage/UserLoginPage.test.tsx b/src/screens/UserPortal/UserLoginPage/UserLoginPage.test.tsx deleted file mode 100644 index 6fa50fc7f2..0000000000 --- a/src/screens/UserPortal/UserLoginPage/UserLoginPage.test.tsx +++ /dev/null @@ -1,631 +0,0 @@ -import React from 'react'; -import { MockedProvider } from '@apollo/react-testing'; -import { act, render, screen } from '@testing-library/react'; -import { Provider } from 'react-redux'; -import { BrowserRouter } from 'react-router-dom'; -import userEvent from '@testing-library/user-event'; -import { I18nextProvider } from 'react-i18next'; -import 'jest-localstorage-mock'; -import 'jest-location-mock'; -import { StaticMockLink } from 'utils/StaticMockLink'; -import LoginPage from './UserLoginPage'; -import { - LOGIN_MUTATION, - RECAPTCHA_MUTATION, - SIGNUP_MUTATION, -} from 'GraphQl/Mutations/mutations'; -import { store } from 'state/store'; -import i18nForTest from 'utils/i18nForTest'; -import { BACKEND_URL } from 'Constant/constant'; - -const MOCKS = [ - { - request: { - query: LOGIN_MUTATION, - variables: { - email: 'johndoe@gmail.com', - password: 'johndoe', - }, - }, - result: { - data: { - login: { - user: { - _id: '1', - userType: 'ADMIN', - adminApproved: true, - }, - accessToken: 'accessToken', - refreshToken: 'refreshToken', - }, - }, - }, - }, - { - request: { - query: SIGNUP_MUTATION, - variables: { - firstName: 'John', - lastName: 'Doe', - email: 'johndoe@gmail.com', - password: 'johnDoe', - }, - }, - result: { - data: { - register: { - user: { - _id: '1', - }, - accessToken: 'accessToken', - refreshToken: 'refreshToken', - }, - }, - }, - }, - { - request: { - query: RECAPTCHA_MUTATION, - variables: { - recaptchaToken: null, - }, - }, - result: { - data: { - recaptcha: true, - }, - }, - }, -]; - -const link = new StaticMockLink(MOCKS, true); - -async function wait(ms = 100): Promise { - await act(() => { - return new Promise((resolve) => { - setTimeout(resolve, ms); - }); - }); -} - -jest.mock('react-toastify', () => ({ - toast: { - success: jest.fn(), - warn: jest.fn(), - error: jest.fn(), - }, -})); - -jest.mock('Constant/constant.ts', () => ({ - ...jest.requireActual('Constant/constant.ts'), - REACT_APP_USE_RECAPTCHA: 'yes', - RECAPTCHA_SITE_KEY: 'xxx', -})); - -describe('Talawa-API server fetch check', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - test('Checks if Talawa-API resource is loaded successfully', async () => { - global.fetch = jest.fn(() => Promise.resolve({} as unknown as Response)); - - await act(async () => { - render( - - - - - - - - - , - ); - }); - - expect(fetch).toHaveBeenCalledWith(BACKEND_URL); - }); - - test('displays warning message when resource loading fails', async () => { - const mockError = new Error('Network error'); - global.fetch = jest.fn(() => Promise.reject(mockError)); - - await act(async () => { - render( - - - - - - - - - , - ); - }); - - expect(fetch).toHaveBeenCalledWith(BACKEND_URL); - }); -}); - -describe('Testing Login Page Screen', () => { - test('Component Should be rendered properly', async () => { - window.location.assign('/user/organizations'); - - render( - - - - - - - - - , - ); - - await wait(); - - expect(screen.getByText(/User Login/i)).toBeInTheDocument(); - expect(window.location).toBeAt('/user/organizations'); - }); - - test('Testing registration functionality', async () => { - const formData = { - firstName: 'John', - lastName: 'Doe', - email: 'johndoe@gmail.com', - password: 'johndoe', - confirmPassword: 'johndoe', - }; - - render( - - - - - - - - - , - ); - - await wait(); - - userEvent.click(screen.getByTestId(/goToRegisterPortion/i)); - - await wait(); - - userEvent.type( - screen.getByPlaceholderText(/First Name/i), - formData.firstName, - ); - userEvent.type( - screen.getByPlaceholderText(/Last name/i), - formData.lastName, - ); - userEvent.type(screen.getByTestId(/signInEmail/i), formData.email); - userEvent.type(screen.getByPlaceholderText('Password'), formData.password); - userEvent.type( - screen.getByPlaceholderText('Confirm Password'), - formData.confirmPassword, - ); - - userEvent.click(screen.getByTestId('registrationBtn')); - }); - - test('Testing registration functionality, when password and confirm password is not same', async () => { - const formData = { - firstName: 'John', - lastName: 'Doe', - email: 'johndoe@gmail.com', - password: 'johndoe', - confirmPassword: 'doeJohn', - }; - - render( - - - - - - - - - , - ); - - await wait(); - - userEvent.click(screen.getByTestId(/goToRegisterPortion/i)); - - userEvent.type( - screen.getByPlaceholderText(/First Name/i), - formData.firstName, - ); - userEvent.type( - screen.getByPlaceholderText(/Last Name/i), - formData.lastName, - ); - userEvent.type(screen.getByTestId(/signInEmail/i), formData.email); - userEvent.type(screen.getByPlaceholderText('Password'), formData.password); - userEvent.type( - screen.getByPlaceholderText('Confirm Password'), - formData.confirmPassword, - ); - - userEvent.click(screen.getByTestId('registrationBtn')); - }); - - test('Testing registration functionality, when input is not filled correctly', async () => { - const formData = { - firstName: 'J', - lastName: 'D', - email: 'johndoe@gmail.com', - password: 'joe', - confirmPassword: 'joe', - }; - - render( - - - - - - - - - , - ); - - await wait(); - - userEvent.click(screen.getByTestId(/goToRegisterPortion/i)); - - userEvent.type( - screen.getByPlaceholderText(/First Name/i), - formData.firstName, - ); - userEvent.type( - screen.getByPlaceholderText(/Last Name/i), - formData.lastName, - ); - userEvent.type(screen.getByTestId(/signInEmail/i), formData.email); - userEvent.type(screen.getByPlaceholderText('Password'), formData.password); - userEvent.type( - screen.getByPlaceholderText('Confirm Password'), - formData.confirmPassword, - ); - - userEvent.click(screen.getByTestId('registrationBtn')); - }); - - test('switches to login tab on successful registration', async () => { - const formData = { - firstName: 'John', - lastName: 'Doe', - email: 'johndoe@gmail.com', - password: 'johndoe', - confirmPassword: 'johndoe', - }; - - render( - - - - - - - - - , - ); - - await wait(); - - userEvent.click(screen.getByTestId(/goToRegisterPortion/i)); - userEvent.type( - screen.getByPlaceholderText(/First Name/i), - formData.firstName, - ); - userEvent.type( - screen.getByPlaceholderText(/Last name/i), - formData.lastName, - ); - userEvent.type(screen.getByTestId(/signInEmail/i), formData.email); - userEvent.type(screen.getByPlaceholderText('Password'), formData.password); - userEvent.type( - screen.getByPlaceholderText('Confirm Password'), - formData.confirmPassword, - ); - - userEvent.click(screen.getByTestId('registrationBtn')); - - await wait(); - - // Check if the login tab is now active by checking for elements that only appear in the login tab - expect(screen.getByTestId('loginBtn')).toBeInTheDocument(); - expect(screen.getByTestId('goToRegisterPortion')).toBeInTheDocument(); - }); - - test('Testing toggle login register portion', async () => { - render( - - - - - - - - - , - ); - - await wait(); - - userEvent.click(screen.getByTestId('goToRegisterPortion')); - - userEvent.click(screen.getByTestId('goToLoginPortion')); - - await wait(); - }); - - test('Testing login functionality', async () => { - const formData = { - email: 'johndoe@gmail.com', - password: 'johndoe', - }; - - render( - - - - - - - - - , - ); - - await wait(); - - userEvent.type(screen.getByTestId(/loginEmail/i), formData.email); - userEvent.type( - screen.getByPlaceholderText(/Enter Password/i), - formData.password, - ); - - userEvent.click(screen.getByTestId('loginBtn')); - - await wait(); - }); - - test('Testing password preview feature for login', async () => { - render( - - - - - - - - - , - ); - - await wait(); - - const input = screen.getByTestId('password') as HTMLInputElement; - const toggleText = screen.getByTestId('showLoginPassword'); - // password should be hidden - expect(input.type).toBe('password'); - // click the toggle button to show password - userEvent.click(toggleText); - expect(input.type).toBe('text'); - // click the toggle button to hide password - userEvent.click(toggleText); - expect(input.type).toBe('password'); - - await wait(); - }); - - test('Testing password preview feature for register', async () => { - render( - - - - - - - - - , - ); - - await wait(); - - userEvent.click(screen.getByTestId('goToRegisterPortion')); - - const input = screen.getByTestId('passwordField') as HTMLInputElement; - const toggleText = screen.getByTestId('showPassword'); - // password should be hidden - expect(input.type).toBe('password'); - // click the toggle button to show password - userEvent.click(toggleText); - expect(input.type).toBe('text'); - // click the toggle button to hide password - userEvent.click(toggleText); - expect(input.type).toBe('password'); - - await wait(); - }); - - test('Testing confirm password preview feature', async () => { - render( - - - - - - - - - , - ); - - await wait(); - - userEvent.click(screen.getByTestId('goToRegisterPortion')); - - const input = screen.getByTestId('cpassword') as HTMLInputElement; - const toggleText = screen.getByTestId('showPasswordCon'); - // password should be hidden - expect(input.type).toBe('password'); - // click the toggle button to show password - userEvent.click(toggleText); - expect(input.type).toBe('text'); - // click the toggle button to hide password - userEvent.click(toggleText); - expect(input.type).toBe('password'); - - await wait(); - }); - - test('Testing for the password error warning when user firsts lands on a page', async () => { - render( - - - - - - - - - , - ); - await wait(); - - expect(screen.queryByTestId('passwordCheck')).toBeNull(); - }); - - test('Testing for the password error warning when user clicks on password field and password is less than 8 character', async () => { - const password = { - password: '7', - }; - - render( - - - - - - - - - , - ); - await wait(); - - userEvent.click(screen.getByTestId('goToRegisterPortion')); - - userEvent.type(screen.getByPlaceholderText('Password'), password.password); - - expect(screen.getByTestId('passwordField')).toHaveFocus(); - - expect(password.password.length).toBeLessThan(8); - - expect(screen.queryByTestId('passwordCheck')).toBeInTheDocument(); - }); - - test('Testing for the password error warning when user clicks on password field and password is greater than or equal to 8 character', async () => { - const password = { - password: '12345678', - }; - - render( - - - - - - - - - , - ); - await wait(); - - userEvent.click(screen.getByTestId('goToRegisterPortion')); - - userEvent.type(screen.getByPlaceholderText('Password'), password.password); - - expect(screen.getByTestId('passwordField')).toHaveFocus(); - - expect(password.password.length).toBeGreaterThanOrEqual(8); - - expect(screen.queryByTestId('passwordCheck')).toBeNull(); - }); - - test('Testing for the password error warning when user clicks on fields except password field and password is less than 8 character', async () => { - const password = { - password: '7', - }; - - render( - - - - - - - - - , - ); - await wait(); - - userEvent.click(screen.getByTestId('goToRegisterPortion')); - - expect(screen.getByPlaceholderText('Password')).not.toHaveFocus(); - - userEvent.type(screen.getByPlaceholderText('Password'), password.password); - - expect(password.password.length).toBeLessThan(8); - - expect(screen.queryByTestId('passwordCheck')).toBeInTheDocument(); - }); - - test('Testing for the password error warning when user clicks on fields except password field and password is greater than or equal to 8 character', async () => { - const password = { - password: '12345678', - }; - - render( - - - - - - - - - , - ); - await wait(); - - userEvent.click(screen.getByTestId('goToRegisterPortion')); - - await wait(); - - expect(screen.getByPlaceholderText('Password')).not.toHaveFocus(); - - userEvent.type(screen.getByPlaceholderText('Password'), password.password); - - expect(password.password.length).toBeGreaterThanOrEqual(8); - - expect(screen.queryByTestId('passwordCheck')).toBeNull(); - }); -}); diff --git a/src/screens/UserPortal/UserLoginPage/UserLoginPage.tsx b/src/screens/UserPortal/UserLoginPage/UserLoginPage.tsx deleted file mode 100644 index c8053c8fef..0000000000 --- a/src/screens/UserPortal/UserLoginPage/UserLoginPage.tsx +++ /dev/null @@ -1,628 +0,0 @@ -import { useMutation } from '@apollo/client'; -import type { ChangeEvent } from 'react'; -import React, { useEffect, useRef, useState } from 'react'; -import { Form } from 'react-bootstrap'; -import Button from 'react-bootstrap/Button'; -import Col from 'react-bootstrap/Col'; -import Row from 'react-bootstrap/Row'; -import ReCAPTCHA from 'react-google-recaptcha'; -import { useTranslation } from 'react-i18next'; -import { Link, useHistory } from 'react-router-dom'; -import { toast } from 'react-toastify'; - -import { - FacebookLogo, - LinkedInLogo, - GithubLogo, - InstagramLogo, - SlackLogo, - TwitterLogo, - YoutubeLogo, -} from 'assets/svgs/social-icons'; - -import { - REACT_APP_USE_RECAPTCHA, - RECAPTCHA_SITE_KEY, - BACKEND_URL, -} from 'Constant/constant'; -import { - LOGIN_MUTATION, - RECAPTCHA_MUTATION, - SIGNUP_MUTATION, -} from 'GraphQl/Mutations/mutations'; -import { ReactComponent as TalawaLogo } from 'assets/svgs/talawa.svg'; -import { ReactComponent as PalisadoesLogo } from 'assets/svgs/palisadoes.svg'; -import ChangeLanguageDropDown from 'components/ChangeLanguageDropdown/ChangeLanguageDropDown'; -import LoginPortalToggle from 'components/LoginPortalToggle/LoginPortalToggle'; -import Loader from 'components/Loader/Loader'; -import { errorHandler } from 'utils/errorHandler'; -import styles from './UserLoginPage.module.css'; -import EmailOutlinedIcon from '@mui/icons-material/EmailOutlined'; -import useLocalStorage from 'utils/useLocalstorage'; - -function loginPage(): JSX.Element { - const { t } = useTranslation('translation', { keyPrefix: 'userLoginPage' }); - const history = useHistory(); - - document.title = t('title'); - - const { getItem, setItem } = useLocalStorage(); - - const [showTab, setShowTab] = useState<'LOGIN' | 'REGISTER'>('LOGIN'); - const [componentLoader, setComponentLoader] = useState(true); - const [isInputFocused, setIsInputFocused] = useState(false); - const [signformState, setSignFormState] = useState({ - signfirstName: '', - signlastName: '', - signEmail: '', - signPassword: '', - cPassword: '', - }); - const [formState, setFormState] = useState({ - email: '', - password: '', - }); - const [showPassword, setShowPassword] = useState(false); - const [showConfirmPassword, setShowConfirmPassword] = - useState(false); - const recaptchaRef = useRef(null); - - useEffect(() => { - const isLoggedIn = getItem('IsLoggedIn'); - if (isLoggedIn == 'TRUE') { - history.push('/user/organizations/'); - } - setComponentLoader(false); - }, []); - - const togglePassword = (): void => setShowPassword(!showPassword); - const toggleConfirmPassword = (): void => - setShowConfirmPassword(!showConfirmPassword); - - const [login, { loading: loginLoading }] = useMutation(LOGIN_MUTATION); - const [signup, { loading: signinLoading }] = useMutation(SIGNUP_MUTATION); - const [recaptcha, { loading: recaptchaLoading }] = - useMutation(RECAPTCHA_MUTATION); - - useEffect(() => { - async function loadResource(): Promise { - try { - await fetch(BACKEND_URL as string); - } catch (error: any) { - /* istanbul ignore next */ - errorHandler(t, error); - } - } - - loadResource(); - }, []); - - const verifyRecaptcha = async ( - recaptchaToken: any, - ): Promise => { - try { - /* istanbul ignore next */ - if (REACT_APP_USE_RECAPTCHA !== 'yes') { - return true; - } - const { data } = await recaptcha({ - variables: { - recaptchaToken, - }, - }); - - return data.recaptcha; - } catch (error: any) { - /* istanbul ignore next */ - toast.error(t('captchaError')); - } - }; - - const signupLink = async (e: ChangeEvent): Promise => { - e.preventDefault(); - - const { signfirstName, signlastName, signEmail, signPassword, cPassword } = - signformState; - - const recaptchaToken = recaptchaRef.current?.getValue(); - recaptchaRef.current?.reset(); - - const isVerified = await verifyRecaptcha(recaptchaToken); - /* istanbul ignore next */ - if (!isVerified) { - toast.error(t('Please_check_the_captcha')); - return; - } - - if ( - signfirstName.length > 1 && - signlastName.length > 1 && - signEmail.length >= 8 && - signPassword.length > 1 - ) { - if (cPassword == signPassword) { - try { - const { data: signUpData } = await signup({ - variables: { - firstName: signfirstName, - lastName: signlastName, - email: signEmail, - password: signPassword, - }, - }); - - /* istanbul ignore next */ - if (signUpData) { - toast.success(t('afterRegister')); - - setShowTab('LOGIN'); - - setSignFormState({ - signfirstName: '', - signlastName: '', - signEmail: '', - signPassword: '', - cPassword: '', - }); - } - } catch (error: any) { - /* istanbul ignore next */ - errorHandler(t, error); - } - } else { - toast.warn(t('passwordMismatches')); - } - } else { - toast.warn(t('fillCorrectly')); - } - }; - - const loginLink = async (e: ChangeEvent): Promise => { - e.preventDefault(); - - const recaptchaToken = recaptchaRef.current?.getValue(); - recaptchaRef.current?.reset(); - - const isVerified = await verifyRecaptcha(recaptchaToken); - /* istanbul ignore next */ - if (!isVerified) { - toast.error(t('Please_check_the_captcha')); - return; - } - - try { - const { data: loginData } = await login({ - variables: { - email: formState.email, - password: formState.password, - }, - }); - - /* istanbul ignore next */ - if (loginData) { - setItem('token', loginData.login.accessToken); - setItem('userId', loginData.login.user._id); - setItem('refreshToken', loginData.login.refreshToken); - setItem('IsLoggedIn', 'TRUE'); - navigator.clipboard.writeText(''); - if (getItem('IsLoggedIn') == 'TRUE') { - history.push('/user/organizations/'); - } - } else { - toast.warn(t('notAuthorised')); - } - } catch (error: any) { - /* istanbul ignore next */ - errorHandler(t, error); - } - }; - - if (componentLoader || loginLoading || signinLoading || recaptchaLoading) { - return ; - } - - return ( - <> -
- - -
- -

{t('fromPalisadoes')}

-
- - - - -
- - - - - - {/* LOGIN FORM */} -
-
-

- {t('userLogin')} -

- {t('email')} -
- { - setFormState({ - ...formState, - email: e.target.value, - }); - }} - autoComplete="username" - data-testid="loginEmail" - /> - -
- {t('password')} -
- { - setFormState({ - ...formState, - password: e.target.value, - }); - }} - autoComplete="current-password" - /> - -
-
- - {t('forgotPassword')} - -
- {REACT_APP_USE_RECAPTCHA === 'yes' ? ( -
- -
- ) : ( - /* istanbul ignore next */ - <> - )} - -
-
- {t('OR')} -
- -
-
- {/* REGISTER FORM */} -
-
-

- {t('register')} -

- - -
- {t('firstName')} - { - setSignFormState({ - ...signformState, - signfirstName: e.target.value, - }); - }} - /> -
- - -
- {t('lastName')} - { - setSignFormState({ - ...signformState, - signlastName: e.target.value, - }); - }} - /> -
- -
-
- {t('email')} -
- { - setSignFormState({ - ...signformState, - signEmail: e.target.value.toLowerCase(), - }); - }} - /> - -
-
- -
- {t('password')} -
- setIsInputFocused(true)} - onBlur={(): void => setIsInputFocused(false)} - required - value={signformState.signPassword} - onChange={(e): void => { - setSignFormState({ - ...signformState, - signPassword: e.target.value, - }); - }} - /> - -
- {isInputFocused && - signformState.signPassword.length < 8 && ( -
- {t('atleast_8_char_long')} -
- )} - {!isInputFocused && - signformState.signPassword.length > 0 && - signformState.signPassword.length < 8 && ( -
- {t('atleast_8_char_long')} -
- )} -
-
- {t('confirmPassword')} -
- { - setSignFormState({ - ...signformState, - cPassword: e.target.value, - }); - }} - data-testid="cpassword" - autoComplete="new-password" - /> - -
- {signformState.cPassword.length > 0 && - signformState.signPassword !== - signformState.cPassword && ( -
- {t('Password_and_Confirm_password_mismatches.')} -
- )} -
- {REACT_APP_USE_RECAPTCHA === 'yes' ? ( -
- -
- ) : ( - /* istanbul ignore next */ - <> - )} - -
-
- {t('OR')} -
- -
-
-
- -
-
- - ); -} - -export default loginPage; diff --git a/src/setup/askForCustomPort/askForCustomPort.test.ts b/src/setup/askForCustomPort/askForCustomPort.test.ts new file mode 100644 index 0000000000..0df6259ba1 --- /dev/null +++ b/src/setup/askForCustomPort/askForCustomPort.test.ts @@ -0,0 +1,24 @@ +import inquirer from 'inquirer'; +import { askForCustomPort } from './askForCustomPort'; + +jest.mock('inquirer'); + +describe('askForCustomPort', () => { + test('should return default port if user provides no input', async () => { + jest + .spyOn(inquirer, 'prompt') + .mockResolvedValueOnce({ customPort: '4321' }); + + const result = await askForCustomPort(); + expect(result).toBe('4321'); + }); + + test('should return user-provided port', async () => { + jest + .spyOn(inquirer, 'prompt') + .mockResolvedValueOnce({ customPort: '8080' }); + + const result = await askForCustomPort(); + expect(result).toBe('8080'); + }); +}); diff --git a/src/setup/askForCustomPort/askForCustomPort.ts b/src/setup/askForCustomPort/askForCustomPort.ts new file mode 100644 index 0000000000..8a923f678f --- /dev/null +++ b/src/setup/askForCustomPort/askForCustomPort.ts @@ -0,0 +1,14 @@ +import inquirer from 'inquirer'; + +export async function askForCustomPort(): Promise { + const { customPort } = await inquirer.prompt([ + { + type: 'input', + name: 'customPort', + message: + 'Enter custom port for development server (leave blank for default 4321):', + default: 4321, + }, + ]); + return customPort; +} diff --git a/src/setup/askForTalawaApiUrl/askForTalawaApiUrl.test.ts b/src/setup/askForTalawaApiUrl/askForTalawaApiUrl.test.ts new file mode 100644 index 0000000000..b1490222b4 --- /dev/null +++ b/src/setup/askForTalawaApiUrl/askForTalawaApiUrl.test.ts @@ -0,0 +1,58 @@ +import inquirer from 'inquirer'; +import { askForTalawaApiUrl } from './askForTalawaApiUrl'; + +jest.mock('inquirer', () => ({ + prompt: jest.fn(), +})); + +describe('askForTalawaApiUrl', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('should return the provided endpoint when user enters it', async () => { + const mockPrompt = jest.spyOn(inquirer, 'prompt').mockResolvedValueOnce({ + endpoint: 'http://example.com/graphql/', + }); + + const result = await askForTalawaApiUrl(); + + expect(mockPrompt).toHaveBeenCalledWith([ + { + type: 'input', + name: 'endpoint', + message: 'Enter your talawa-api endpoint:', + default: 'http://localhost:4000/graphql/', + }, + ]); + + expect(result).toBe('http://example.com/graphql/'); + }); + + test('should return the default endpoint when the user does not enter anything', async () => { + const mockPrompt = jest + .spyOn(inquirer, 'prompt') + .mockImplementation(async (questions: any) => { + const answers: Record = {}; + questions.forEach( + (question: { name: string | number; default: any }) => { + answers[question.name] = question.default; + }, + ); + return answers; + }); + + const result = await askForTalawaApiUrl(); + + expect(mockPrompt).toHaveBeenCalledWith([ + { + type: 'input', + name: 'endpoint', + message: 'Enter your talawa-api endpoint:', + default: 'http://localhost:4000/graphql/', + }, + ]); + + expect(result).toBe('http://localhost:4000/graphql/'); + }); +}); diff --git a/src/setup/askForTalawaApiUrl/askForTalawaApiUrl.ts b/src/setup/askForTalawaApiUrl/askForTalawaApiUrl.ts new file mode 100644 index 0000000000..97daa1ac89 --- /dev/null +++ b/src/setup/askForTalawaApiUrl/askForTalawaApiUrl.ts @@ -0,0 +1,13 @@ +import inquirer from 'inquirer'; + +export async function askForTalawaApiUrl(): Promise { + const { endpoint } = await inquirer.prompt([ + { + type: 'input', + name: 'endpoint', + message: 'Enter your talawa-api endpoint:', + default: 'http://localhost:4000/graphql/', + }, + ]); + return endpoint; +} diff --git a/src/setup/checkConnection/checkConnection.test.ts b/src/setup/checkConnection/checkConnection.test.ts new file mode 100644 index 0000000000..c6f5251bdf --- /dev/null +++ b/src/setup/checkConnection/checkConnection.test.ts @@ -0,0 +1,55 @@ +import { checkConnection } from './checkConnection'; + +jest.mock('node-fetch'); + +global.fetch = jest.fn((url) => { + if (url === 'http://example.com/graphql/') { + const responseInit: ResponseInit = { + status: 200, + statusText: 'OK', + headers: new Headers({ 'Content-Type': 'application/json' }), + }; + return Promise.resolve(new Response(JSON.stringify({}), responseInit)); + } else { + const errorResponseInit: ResponseInit = { + status: 500, + statusText: 'Internal Server Error', + headers: new Headers({ 'Content-Type': 'text/plain' }), + }; + return Promise.reject(new Response('Error', errorResponseInit)); + } +}); + +describe('checkConnection', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('should return true and log success message if the connection is successful', async () => { + jest.spyOn(console, 'log').mockImplementation((string) => string); + const result = await checkConnection('http://example.com/graphql/'); + + expect(result).toBe(true); + expect(console.log).toHaveBeenCalledWith( + '\nChecking Talawa-API connection....', + ); + expect(console.log).toHaveBeenCalledWith( + '\nConnection to Talawa-API successful! 🎉', + ); + }); + + it('should return false and log error message if the connection fails', async () => { + jest.spyOn(console, 'log').mockImplementation((string) => string); + const result = await checkConnection( + 'http://example_not_working.com/graphql/', + ); + + expect(result).toBe(false); + expect(console.log).toHaveBeenCalledWith( + '\nChecking Talawa-API connection....', + ); + expect(console.log).toHaveBeenCalledWith( + '\nTalawa-API service is unavailable. Is it running? Check your network connectivity too.', + ); + }); +}); diff --git a/src/setup/checkConnection/checkConnection.ts b/src/setup/checkConnection/checkConnection.ts new file mode 100644 index 0000000000..601bea98ca --- /dev/null +++ b/src/setup/checkConnection/checkConnection.ts @@ -0,0 +1,15 @@ +export async function checkConnection(url: string): Promise { + console.log('\nChecking Talawa-API connection....'); + let isConnected = false; + await fetch(url) + .then(() => { + isConnected = true; + console.log('\nConnection to Talawa-API successful! 🎉'); + }) + .catch(() => { + console.log( + '\nTalawa-API service is unavailable. Is it running? Check your network connectivity too.', + ); + }); + return isConnected; +} diff --git a/src/setup/checkEnvFile/checkEnvFile.test.ts b/src/setup/checkEnvFile/checkEnvFile.test.ts new file mode 100644 index 0000000000..a23976db4a --- /dev/null +++ b/src/setup/checkEnvFile/checkEnvFile.test.ts @@ -0,0 +1,47 @@ +import fs from 'fs'; +import { checkEnvFile } from './checkEnvFile'; + +jest.mock('fs'); + +describe('checkEnvFile', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('should append missing keys to the .env file', () => { + const envContent = 'EXISTING_KEY=existing_value\n'; + const envExampleContent = + 'EXISTING_KEY=existing_value\nNEW_KEY=default_value\n'; + + jest + .spyOn(fs, 'readFileSync') + .mockReturnValueOnce(envContent) + .mockReturnValueOnce(envExampleContent) + .mockReturnValueOnce(envExampleContent); + + jest.spyOn(fs, 'appendFileSync'); + + checkEnvFile(); + + expect(fs.appendFileSync).toHaveBeenCalledWith( + '.env', + 'NEW_KEY=default_value\n', + ); + }); + + it('should not append anything if all keys are present', () => { + const envContent = 'EXISTING_KEY=existing_value\n'; + const envExampleContent = 'EXISTING_KEY=existing_value\n'; + + jest + .spyOn(fs, 'readFileSync') + .mockReturnValueOnce(envContent) + .mockReturnValueOnce(envExampleContent); + + jest.spyOn(fs, 'appendFileSync'); + + checkEnvFile(); + + expect(fs.appendFileSync).not.toHaveBeenCalled(); + }); +}); diff --git a/src/setup/checkEnvFile/checkEnvFile.ts b/src/setup/checkEnvFile/checkEnvFile.ts new file mode 100644 index 0000000000..420a7c1321 --- /dev/null +++ b/src/setup/checkEnvFile/checkEnvFile.ts @@ -0,0 +1,16 @@ +import dotenv from 'dotenv'; +import fs from 'fs'; + +dotenv.config(); + +export function checkEnvFile(): void { + const env = dotenv.parse(fs.readFileSync('.env')); + const envSample = dotenv.parse(fs.readFileSync('.env.example')); + const misplaced = Object.keys(envSample).filter((key) => !(key in env)); + if (misplaced.length > 0) { + const config = dotenv.parse(fs.readFileSync('.env.example')); + misplaced.map((key) => + fs.appendFileSync('.env', `${key}=${config[key]}\n`), + ); + } +} diff --git a/src/setup/validateRecaptcha/validateRecaptcha.test.ts b/src/setup/validateRecaptcha/validateRecaptcha.test.ts new file mode 100644 index 0000000000..c77c9ed62b --- /dev/null +++ b/src/setup/validateRecaptcha/validateRecaptcha.test.ts @@ -0,0 +1,23 @@ +import { validateRecaptcha } from './validateRecaptcha'; + +describe('validateRecaptcha', () => { + it('should return true for a valid Recaptcha string', () => { + const validRecaptcha = 'ss7BEe32HPoDKTPXQevFkVvpvPzGebE2kIRv1ok4'; + expect(validateRecaptcha(validRecaptcha)).toBe(true); + }); + + it('should return false for an invalid Recaptcha string with special characters', () => { + const invalidRecaptcha = 'invalid@recaptcha!'; + expect(validateRecaptcha(invalidRecaptcha)).toBe(false); + }); + + it('should return false for an invalid Recaptcha string with incorrect length', () => { + const invalidRecaptcha = 'shortstring'; + expect(validateRecaptcha(invalidRecaptcha)).toBe(false); + }); + + it('should return false for an invalid Recaptcha string with spaces', () => { + const invalidRecaptcha = 'invalid recaptcha string'; + expect(validateRecaptcha(invalidRecaptcha)).toBe(false); + }); +}); diff --git a/src/setup/validateRecaptcha/validateRecaptcha.ts b/src/setup/validateRecaptcha/validateRecaptcha.ts new file mode 100644 index 0000000000..dcefb860fe --- /dev/null +++ b/src/setup/validateRecaptcha/validateRecaptcha.ts @@ -0,0 +1,4 @@ +export function validateRecaptcha(string: string): boolean { + const pattern = /^[a-zA-Z0-9_-]{40}$/; + return pattern.test(string); +} diff --git a/src/utils/StaticMockLink.ts b/src/utils/StaticMockLink.ts index 69554646e5..e2965cfcf0 100644 --- a/src/utils/StaticMockLink.ts +++ b/src/utils/StaticMockLink.ts @@ -96,35 +96,38 @@ export class StaticMockLink extends ApolloLink { } return new Observable((observer) => { - const timer = setTimeout(() => { - if (configError) { - try { - // The onError function can return false to indicate that - // configError need not be passed to observer.error. For - // example, the default implementation of onError calls - // observer.error(configError) and then returns false to - // prevent this extra (harmless) observer.error call. - if (this.onError(configError, observer) !== false) { - throw configError; + const timer = setTimeout( + () => { + if (configError) { + try { + // The onError function can return false to indicate that + // configError need not be passed to observer.error. For + // example, the default implementation of onError calls + // observer.error(configError) and then returns false to + // prevent this extra (harmless) observer.error call. + if (this.onError(configError, observer) !== false) { + throw configError; + } + } catch (error) { + observer.error(error); } - } catch (error) { - observer.error(error); - } - } else if (response) { - if (response.error) { - observer.error(response.error); - } else { - if (response.result) { - observer.next( - typeof response.result === 'function' - ? (response.result as ResultFunction)() - : response.result, - ); + } else if (response) { + if (response.error) { + observer.error(response.error); + } else { + if (response.result) { + observer.next( + typeof response.result === 'function' + ? (response.result as ResultFunction)() + : response.result, + ); + } + observer.complete(); } - observer.complete(); } - } - }, (response && response.delay) || 0); + }, + (response && response.delay) || 0, + ); return () => { clearTimeout(timer); @@ -170,4 +173,4 @@ export function mockSingleLink( } return new StaticMockLink(mocks, maybeTypename); -} +} \ No newline at end of file diff --git a/src/utils/interfaces.ts b/src/utils/interfaces.ts index 47b8c5a61d..d6700f118a 100644 --- a/src/utils/interfaces.ts +++ b/src/utils/interfaces.ts @@ -141,21 +141,45 @@ export interface InterfaceQueryOrganizationsListObject { email: string; }[]; } - +export interface InterfacePostForm { + posttitle: string; + postinfo: string; + postphoto: string | null; + postvideo: string | null; + pinned: boolean; +} export interface InterfaceQueryOrganizationPostListItem { - _id: string; - title: string; - text: string; - imageUrl: null; - videoUrl: null; - createdAt: string; - creator: { - _id: string; - firstName: string; - lastName: string; - email: string; - }[]; + posts: { + edges: { + node: { + _id: string; + title: string; + text: string; + imageUrl: string | null; + videoUrl: string | null; + creator: { + _id: string; + firstName: string; + lastName: string; + email: string; + }; + createdAt: string; + likeCount: number; + commentCount: number; + pinned: boolean; + }; + cursor: string; + }[]; + pageInfo: { + startCursor: string; + endCursor: string; + hasNextPage: boolean; + hasPreviousPage: boolean; + }; + totalCount: number; + }; } + export interface InterfaceQueryOrganizationEventListItem { _id: string; title: string; diff --git a/tsconfig.json b/tsconfig.json index 6aeebd66d6..57e5d011a4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,5 +17,5 @@ "noEmit": true, "jsx": "react-jsx" }, - "include": ["src", "src/App.tsx"] + "include": ["src", "src/App.tsx", "setup.ts"] }