From f7003ecbbe28bbf9418dd9881fe2d78c1d7d6c1b Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Mon, 6 Oct 2025 10:15:53 +0530 Subject: [PATCH] feat: contact us --- frontend/src/components/AppSidebar.vue | 13 +++++++++ frontend/src/components/Settings/Settings.vue | 20 ++++++++++++++ frontend/src/components/SidebarLink.vue | 7 +++++ frontend/src/stores/settings.js | 16 +++++++++++ .../job_opportunity/job_opportunity.py | 4 +-- .../doctype/lms_settings/lms_settings.json | 27 +++++++++++++++++-- lms/lms/doctype/lms_settings/lms_settings.py | 9 ++++++- 7 files changed, 91 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/AppSidebar.vue b/frontend/src/components/AppSidebar.vue index c0f10da7..048bea5e 100644 --- a/frontend/src/components/AppSidebar.vue +++ b/frontend/src/components/AppSidebar.vue @@ -375,6 +375,18 @@ const addPrograms = async () => { }) } +const addContactUsDetails = () => { + if (settingsStore.contactUsEmail?.data || settingsStore.contactUsURL?.data) { + sidebarLinks.value.push({ + label: 'Contact Us', + icon: settingsStore.contactUsURL?.data ? 'Headset' : 'Mail', + to: settingsStore.contactUsURL?.data + ? settingsStore.contactUsURL.data + : `mailto:${settingsStore.contactUsEmail?.data}`, + }) + } +} + const checkIfCanAddProgram = async () => { if (isModerator.value || isInstructor.value) { return true @@ -645,6 +657,7 @@ const setUpOnboarding = () => { } watch(userResource, () => { + addContactUsDetails() if (userResource.data) { isModerator.value = userResource.data.is_moderator isInstructor.value = userResource.data.is_instructor diff --git a/frontend/src/components/Settings/Settings.vue b/frontend/src/components/Settings/Settings.vue index 815491bd..8c0feabe 100644 --- a/frontend/src/components/Settings/Settings.vue +++ b/frontend/src/components/Settings/Settings.vue @@ -161,6 +161,26 @@ const tabsStructure = computed(() => { }, ], }, + { + label: 'Contact Us', + icon: 'Phone', + fields: [ + { + label: 'Email', + name: 'contact_us_email', + type: 'text', + description: + 'Users can reach out to this email for support or inquiries.', + }, + { + label: 'URL', + name: 'contact_us_url', + type: 'text', + description: + 'Users can reach out to this URL for support or inquiries.', + }, + ], + }, ], }, { diff --git a/frontend/src/components/SidebarLink.vue b/frontend/src/components/SidebarLink.vue index f1d3f3a3..075809c7 100644 --- a/frontend/src/components/SidebarLink.vue +++ b/frontend/src/components/SidebarLink.vue @@ -88,6 +88,13 @@ function handleClick() { if (router.hasRoute(props.link.to)) { router.push({ name: props.link.to }) } else if (props.link.to) { + if ( + props.link.to.startsWith('http') || + props.link.to.startsWith('mailto:') + ) { + window.open(props.link.to, '_blank') + return + } window.location.href = `/${props.link.to}` } } diff --git a/frontend/src/stores/settings.js b/frontend/src/stores/settings.js index a29d9d52..9f3b68d4 100644 --- a/frontend/src/stores/settings.js +++ b/frontend/src/stores/settings.js @@ -21,6 +21,20 @@ export const useSettings = defineStore('settings', () => { cache: ['preventSkippingVideos'], }) + const contactUsEmail = createResource({ + url: 'lms.lms.api.get_lms_setting', + params: { field: 'contact_us_email' }, + auto: true, + cache: ['contactUsEmail'], + }) + + const contactUsURL = createResource({ + url: 'lms.lms.api.get_lms_setting', + params: { field: 'contact_us_url' }, + auto: true, + cache: ['contactUsURL'], + }) + const sidebarSettings = createResource({ url: 'lms.lms.api.get_sidebar_settings', cache: 'Sidebar Settings', @@ -32,6 +46,8 @@ export const useSettings = defineStore('settings', () => { activeTab, allowGuestAccess, preventSkippingVideos, + contactUsEmail, + contactUsURL, sidebarSettings, } }) diff --git a/lms/job/doctype/job_opportunity/job_opportunity.py b/lms/job/doctype/job_opportunity/job_opportunity.py index 8da32bde..bcd4858a 100644 --- a/lms/job/doctype/job_opportunity/job_opportunity.py +++ b/lms/job/doctype/job_opportunity/job_opportunity.py @@ -4,7 +4,7 @@ import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import add_months, get_link_to_form, getdate +from frappe.utils import add_months, get_link_to_form, getdate, validate_url from frappe.utils.user import get_system_managers from lms.lms.utils import generate_slug, validate_image @@ -16,7 +16,7 @@ class JobOpportunity(Document): self.company_logo = validate_image(self.company_logo) def validate_urls(self): - frappe.utils.validate_url(self.company_website, True) + validate_url(self.company_website, True) def autoname(self): if not self.name: diff --git a/lms/lms/doctype/lms_settings/lms_settings.json b/lms/lms/doctype/lms_settings/lms_settings.json index 8be2517c..36a94e5b 100644 --- a/lms/lms/doctype/lms_settings/lms_settings.json +++ b/lms/lms/doctype/lms_settings/lms_settings.json @@ -67,7 +67,11 @@ "meta_description", "meta_image", "column_break_xijv", - "meta_keywords" + "meta_keywords", + "contact_us_tab", + "contact_us_email", + "column_break_gcgv", + "contact_us_url" ], "fields": [ { @@ -416,13 +420,32 @@ "fieldname": "programming_exercises", "fieldtype": "Check", "label": "Programming Exercises" + }, + { + "fieldname": "contact_us_tab", + "fieldtype": "Tab Break", + "label": "Contact Us" + }, + { + "fieldname": "contact_us_email", + "fieldtype": "Data", + "label": "Contact Us Email" + }, + { + "fieldname": "column_break_gcgv", + "fieldtype": "Column Break" + }, + { + "fieldname": "contact_us_url", + "fieldtype": "Data", + "label": "Contact Us URL" } ], "grid_page_length": 50, "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2025-08-12 16:47:49.983018", + "modified": "2025-10-03 17:41:30.904723", "modified_by": "sayali@frappe.io", "module": "LMS", "name": "LMS Settings", diff --git a/lms/lms/doctype/lms_settings/lms_settings.py b/lms/lms/doctype/lms_settings/lms_settings.py index 7b79ef32..46a491cd 100644 --- a/lms/lms/doctype/lms_settings/lms_settings.py +++ b/lms/lms/doctype/lms_settings/lms_settings.py @@ -4,13 +4,14 @@ import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import get_url_to_list +from frappe.utils import get_url_to_list, validate_email_address, validate_url class LMSSettings(Document): def validate(self): self.validate_google_settings() self.validate_signup() + self.validate_contact_us_details() def validate_google_settings(self): if self.send_calendar_invite_for_evaluations: @@ -45,6 +46,12 @@ class LMSSettings(Document): if self.has_value_changed("disable_signup"): frappe.db.set_single_value("Website Settings", "disable_signup", self.disable_signup) + def validate_contact_us_details(self): + if self.contact_us_email and not validate_email_address(self.contact_us_email): + frappe.throw(_("Please enter a valid Contact Us Email.")) + if self.contact_us_url and not validate_url(self.contact_us_url, True): + frappe.throw(_("Please enter a valid Contact Us URL.")) + @frappe.whitelist() def check_payments_app():