Compare commits
13 Commits
main
...
pot_develo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e70b4c091e | ||
|
|
7e38d5e405 | ||
|
|
f810e82b45 | ||
|
|
dff9f93a6b | ||
|
|
c4109ad6ac | ||
|
|
7a6efb900e | ||
|
|
82599f91d8 | ||
|
|
8fa156f625 | ||
|
|
55112cefa9 | ||
|
|
152c7c8a91 | ||
|
|
aa1c0da80e | ||
|
|
87174f207d | ||
|
|
400f879d29 |
@ -94,8 +94,13 @@ def accept_invitation(key: str | None = None):
|
||||
@frappe.whitelist()
|
||||
def invite_by_email(emails: str, role: str):
|
||||
frappe.only_for("Sales Manager")
|
||||
|
||||
if role not in ["Sales Manager", "Sales User"]:
|
||||
frappe.throw("Cannot invite for this role")
|
||||
|
||||
if not emails:
|
||||
return
|
||||
|
||||
email_string = validate_email_address(emails, throw=False)
|
||||
email_list = split_emails(email_string)
|
||||
if not email_list:
|
||||
|
||||
@ -418,16 +418,23 @@ def get_data(
|
||||
rows.append(field)
|
||||
|
||||
for kc in kanban_columns:
|
||||
column_filters = {column_field: kc.get("name")}
|
||||
# Start with base filters
|
||||
column_filters = []
|
||||
|
||||
# Convert and add the main filters first
|
||||
if filters:
|
||||
base_filters = convert_filter_to_tuple(doctype, filters)
|
||||
column_filters.extend(base_filters)
|
||||
|
||||
# Add the column-specific filter
|
||||
if column_field and kc.get("name"):
|
||||
column_filters.append([doctype, column_field, "=", kc.get("name")])
|
||||
|
||||
order = kc.get("order")
|
||||
if (column_field in filters and filters.get(column_field) != kc.get("name")) or kc.get("delete"):
|
||||
if kc.get("delete"):
|
||||
column_data = []
|
||||
else:
|
||||
column_filters.update(filters.copy())
|
||||
page_length = 20
|
||||
|
||||
if kc.get("page_length"):
|
||||
page_length = kc.get("page_length")
|
||||
page_length = kc.get("page_length", 20)
|
||||
|
||||
if order:
|
||||
column_data = get_records_based_on_order(
|
||||
@ -437,26 +444,20 @@ def get_data(
|
||||
column_data = frappe.get_list(
|
||||
doctype,
|
||||
fields=rows,
|
||||
filters=convert_filter_to_tuple(doctype, column_filters),
|
||||
filters=column_filters,
|
||||
order_by=order_by,
|
||||
page_length=page_length,
|
||||
)
|
||||
|
||||
new_filters = filters.copy()
|
||||
new_filters.update({column_field: kc.get("name")})
|
||||
|
||||
all_count = frappe.get_list(
|
||||
doctype,
|
||||
filters=convert_filter_to_tuple(doctype, new_filters),
|
||||
filters=column_filters,
|
||||
fields="count(*) as total_count",
|
||||
)[0].total_count
|
||||
|
||||
kc["all_count"] = all_count
|
||||
kc["count"] = len(column_data)
|
||||
|
||||
for d in column_data:
|
||||
getCounts(d, doctype)
|
||||
|
||||
if order:
|
||||
column_data = sorted(
|
||||
column_data,
|
||||
|
||||
99
crm/api/settings.py
Normal file
@ -0,0 +1,99 @@
|
||||
import frappe
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_email_account(data):
|
||||
service = data.get("service")
|
||||
service_config = email_service_config.get(service)
|
||||
if not service_config:
|
||||
return "Service not supported"
|
||||
|
||||
try:
|
||||
email_doc = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Email Account",
|
||||
"email_id": data.get("email_id"),
|
||||
"email_account_name": data.get("email_account_name"),
|
||||
"service": service,
|
||||
"enable_incoming": data.get("enable_incoming"),
|
||||
"enable_outgoing": data.get("enable_outgoing"),
|
||||
"default_incoming": data.get("default_incoming"),
|
||||
"default_outgoing": data.get("default_outgoing"),
|
||||
"email_sync_option": "ALL",
|
||||
"initial_sync_count": 100,
|
||||
"create_contact": 1,
|
||||
"track_email_status": 1,
|
||||
"use_tls": 1,
|
||||
"use_imap": 1,
|
||||
"smtp_port": 587,
|
||||
**service_config,
|
||||
}
|
||||
)
|
||||
if service == "Frappe Mail":
|
||||
email_doc.api_key = data.get("api_key")
|
||||
email_doc.api_secret = data.get("api_secret")
|
||||
email_doc.frappe_mail_site = data.get("frappe_mail_site")
|
||||
email_doc.append_to = "CRM Lead"
|
||||
else:
|
||||
email_doc.append("imap_folder", {"append_to": "CRM Lead", "folder_name": "INBOX"})
|
||||
email_doc.password = data.get("password")
|
||||
# validate whether the credentials are correct
|
||||
email_doc.get_incoming_server()
|
||||
|
||||
# if correct credentials, save the email account
|
||||
email_doc.save()
|
||||
except Exception as e:
|
||||
frappe.throw(str(e))
|
||||
|
||||
|
||||
email_service_config = {
|
||||
"Frappe Mail": {
|
||||
"domain": None,
|
||||
"password": None,
|
||||
"awaiting_password": 0,
|
||||
"ascii_encode_password": 0,
|
||||
"login_id_is_different": 0,
|
||||
"login_id": None,
|
||||
"use_imap": 0,
|
||||
"use_ssl": 0,
|
||||
"validate_ssl_certificate": 0,
|
||||
"use_starttls": 0,
|
||||
"email_server": None,
|
||||
"incoming_port": 0,
|
||||
"always_use_account_email_id_as_sender": 1,
|
||||
"use_tls": 0,
|
||||
"use_ssl_for_outgoing": 0,
|
||||
"smtp_server": None,
|
||||
"smtp_port": None,
|
||||
"no_smtp_authentication": 0,
|
||||
},
|
||||
"GMail": {
|
||||
"email_server": "imap.gmail.com",
|
||||
"use_ssl": 1,
|
||||
"smtp_server": "smtp.gmail.com",
|
||||
},
|
||||
"Outlook": {
|
||||
"email_server": "imap-mail.outlook.com",
|
||||
"use_ssl": 1,
|
||||
"smtp_server": "smtp-mail.outlook.com",
|
||||
},
|
||||
"Sendgrid": {
|
||||
"smtp_server": "smtp.sendgrid.net",
|
||||
"smtp_port": 587,
|
||||
},
|
||||
"SparkPost": {
|
||||
"smtp_server": "smtp.sparkpostmail.com",
|
||||
},
|
||||
"Yahoo": {
|
||||
"email_server": "imap.mail.yahoo.com",
|
||||
"use_ssl": 1,
|
||||
"smtp_server": "smtp.mail.yahoo.com",
|
||||
"smtp_port": 587,
|
||||
},
|
||||
"Yandex": {
|
||||
"email_server": "imap.yandex.com",
|
||||
"use_ssl": 1,
|
||||
"smtp_server": "smtp.yandex.com",
|
||||
"smtp_port": 587,
|
||||
},
|
||||
}
|
||||
@ -41,13 +41,15 @@
|
||||
"fieldname": "from",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "From"
|
||||
"label": "From",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"label": "Status",
|
||||
"options": "Initiated\nRinging\nIn Progress\nCompleted\nFailed\nBusy\nNo Answer\nQueued\nCanceled"
|
||||
"options": "Initiated\nRinging\nIn Progress\nCompleted\nFailed\nBusy\nNo Answer\nQueued\nCanceled",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "start_time",
|
||||
@ -69,13 +71,15 @@
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Type",
|
||||
"options": "Incoming\nOutgoing"
|
||||
"options": "Incoming\nOutgoing",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "to",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "To"
|
||||
"label": "To",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"description": "Call duration in seconds",
|
||||
@ -153,7 +157,7 @@
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2025-01-22 17:57:59.289548",
|
||||
"modified": "2025-04-01 16:01:54.479309",
|
||||
"modified_by": "Administrator",
|
||||
"module": "FCRM",
|
||||
"name": "CRM Call Log",
|
||||
|
||||
@ -19,7 +19,8 @@
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Title"
|
||||
"label": "Title",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "content",
|
||||
@ -49,7 +50,7 @@
|
||||
"link_fieldname": "note"
|
||||
}
|
||||
],
|
||||
"modified": "2024-01-19 21:56:30.123334",
|
||||
"modified": "2025-04-01 15:30:14.742001",
|
||||
"modified_by": "Administrator",
|
||||
"module": "FCRM",
|
||||
"name": "FCRM Note",
|
||||
|
||||
@ -7,8 +7,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Frappe CRM VERSION\n"
|
||||
"Report-Msgid-Bugs-To: shariq@frappe.io\n"
|
||||
"POT-Creation-Date: 2025-03-23 09:35+0000\n"
|
||||
"PO-Revision-Date: 2025-03-23 09:35+0000\n"
|
||||
"POT-Creation-Date: 2025-04-06 09:35+0000\n"
|
||||
"PO-Revision-Date: 2025-04-06 09:35+0000\n"
|
||||
"Last-Translator: shariq@frappe.io\n"
|
||||
"Language-Team: shariq@frappe.io\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@ -20,7 +20,7 @@ msgstr ""
|
||||
msgid " (New)"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Modals/TaskModal.vue:95
|
||||
#: frontend/src/components/Modals/TaskModal.vue:66
|
||||
#: frontend/src/components/Telephony/TaskPanel.vue:67
|
||||
msgid "01/04/2024 11:30 PM"
|
||||
msgstr ""
|
||||
@ -242,7 +242,7 @@ msgstr ""
|
||||
msgid "Add to Holidays"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:381
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:410
|
||||
msgid "Add your first comment"
|
||||
msgstr ""
|
||||
|
||||
@ -380,7 +380,7 @@ msgstr ""
|
||||
msgid "Assignment cleared successfully"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:494
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:541
|
||||
msgid "Assignment rule"
|
||||
msgstr ""
|
||||
|
||||
@ -623,7 +623,7 @@ msgstr ""
|
||||
msgid "Call duration in seconds"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:473
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:513
|
||||
msgid "Call log"
|
||||
msgstr ""
|
||||
|
||||
@ -631,8 +631,8 @@ msgstr ""
|
||||
msgid "Call using {0}"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Modals/NoteModal.vue:43
|
||||
#: frontend/src/components/Modals/TaskModal.vue:43
|
||||
#: frontend/src/components/Modals/NoteModal.vue:30
|
||||
#: frontend/src/components/Modals/TaskModal.vue:30
|
||||
msgid "Call with John Doe"
|
||||
msgstr ""
|
||||
|
||||
@ -682,11 +682,20 @@ msgstr ""
|
||||
msgid "Capture"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:518
|
||||
msgid "Capturing leads"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:458
|
||||
msgid "Change"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Activities/TaskArea.vue:44
|
||||
msgid "Change Status"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:421
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:450
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:457
|
||||
msgid "Change deal status"
|
||||
msgstr ""
|
||||
|
||||
@ -742,7 +751,7 @@ msgstr ""
|
||||
msgid "Close Date"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:97
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:107
|
||||
msgid "Collapse"
|
||||
msgstr ""
|
||||
|
||||
@ -777,7 +786,7 @@ msgstr ""
|
||||
#: crm/fcrm/doctype/crm_notification/crm_notification.json
|
||||
#: frontend/src/components/CommentBox.vue:80
|
||||
#: frontend/src/components/CommunicationArea.vue:19
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:491
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:538
|
||||
msgid "Comment"
|
||||
msgstr ""
|
||||
|
||||
@ -830,7 +839,7 @@ msgstr ""
|
||||
#. Label of the contact (Link) field in DocType 'CRM Deal'
|
||||
#: crm/fcrm/doctype/crm_contacts/crm_contacts.json
|
||||
#: crm/fcrm/doctype/crm_deal/crm_deal.json
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:469
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:509
|
||||
#: frontend/src/pages/Lead.vue:262 frontend/src/pages/MobileLead.vue:133
|
||||
msgid "Contact"
|
||||
msgstr ""
|
||||
@ -877,7 +886,7 @@ msgstr ""
|
||||
#: crm/fcrm/doctype/fcrm_note/fcrm_note.json
|
||||
#: frontend/src/components/Modals/EmailTemplateModal.vue:61
|
||||
#: frontend/src/components/Modals/EmailTemplateModal.vue:74
|
||||
#: frontend/src/components/Modals/NoteModal.vue:47
|
||||
#: frontend/src/components/Modals/NoteModal.vue:34
|
||||
msgid "Content"
|
||||
msgstr ""
|
||||
|
||||
@ -885,13 +894,15 @@ msgstr ""
|
||||
msgid "Content Type"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:351
|
||||
#: frontend/src/components/ListBulkActions.vue:70
|
||||
#: frontend/src/pages/Lead.vue:200 frontend/src/pages/MobileLead.vue:49
|
||||
#: frontend/src/pages/MobileLead.vue:96
|
||||
msgid "Convert"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:324
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:343
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:350
|
||||
msgid "Convert lead to deal"
|
||||
msgstr ""
|
||||
|
||||
@ -912,14 +923,14 @@ msgid "Converted successfully"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Modals/AddressModal.vue:100
|
||||
#: frontend/src/components/Modals/CallLogModal.vue:104
|
||||
#: frontend/src/components/Modals/CallLogModal.vue:85
|
||||
#: frontend/src/components/Modals/ContactModal.vue:37
|
||||
#: frontend/src/components/Modals/DealModal.vue:63
|
||||
#: frontend/src/components/Modals/EmailTemplateModal.vue:9
|
||||
#: frontend/src/components/Modals/LeadModal.vue:34
|
||||
#: frontend/src/components/Modals/NoteModal.vue:8
|
||||
#: frontend/src/components/Modals/OrganizationModal.vue:37
|
||||
#: frontend/src/components/Modals/TaskModal.vue:8
|
||||
#: frontend/src/components/Modals/NoteModal.vue:6
|
||||
#: frontend/src/components/Modals/OrganizationModal.vue:25
|
||||
#: frontend/src/components/Modals/TaskModal.vue:6
|
||||
#: frontend/src/components/Modals/ViewModal.vue:16
|
||||
#: frontend/src/pages/CallLogs.vue:11 frontend/src/pages/Contacts.vue:13
|
||||
#: frontend/src/pages/Contacts.vue:57 frontend/src/pages/Deals.vue:13
|
||||
@ -952,12 +963,12 @@ msgid "Create New"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Activities/Activities.vue:383
|
||||
#: frontend/src/components/Modals/NoteModal.vue:18
|
||||
#: frontend/src/components/Modals/NoteModal.vue:15
|
||||
msgid "Create Note"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Activities/Activities.vue:398
|
||||
#: frontend/src/components/Modals/TaskModal.vue:18
|
||||
#: frontend/src/components/Modals/TaskModal.vue:15
|
||||
msgid "Create Task"
|
||||
msgstr ""
|
||||
|
||||
@ -976,15 +987,15 @@ msgstr ""
|
||||
msgid "Create lead"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:303
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:322
|
||||
msgid "Create your first lead"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:361
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:390
|
||||
msgid "Create your first note"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:341
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:370
|
||||
msgid "Create your first task"
|
||||
msgstr ""
|
||||
|
||||
@ -995,23 +1006,23 @@ msgstr ""
|
||||
msgid "Currency"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:503
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:550
|
||||
msgid "Custom actions"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:458
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:498
|
||||
msgid "Custom branding"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:502
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:549
|
||||
msgid "Custom fields"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:505
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:552
|
||||
msgid "Custom list actions"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:504
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:551
|
||||
msgid "Custom statuses"
|
||||
msgstr ""
|
||||
|
||||
@ -1019,7 +1030,7 @@ msgstr ""
|
||||
msgid "Customer created successfully"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:499
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:546
|
||||
msgid "Customization"
|
||||
msgstr ""
|
||||
|
||||
@ -1028,7 +1039,7 @@ msgid "Customize quick filters"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Activities/DataFields.vue:6
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:492
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:539
|
||||
#: frontend/src/pages/Deal.vue:541 frontend/src/pages/Lead.vue:528
|
||||
#: frontend/src/pages/MobileDeal.vue:456 frontend/src/pages/MobileLead.vue:359
|
||||
msgid "Data"
|
||||
@ -1044,7 +1055,7 @@ msgstr ""
|
||||
msgid "Date"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:468
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:508
|
||||
#: frontend/src/components/Telephony/ExotelCallUI.vue:205
|
||||
#: frontend/src/pages/Tasks.vue:129
|
||||
msgid "Deal"
|
||||
@ -1186,7 +1197,7 @@ msgstr ""
|
||||
#. Label of the description (Text Editor) field in DocType 'CRM Task'
|
||||
#: crm/fcrm/doctype/crm_holiday/crm_holiday.json
|
||||
#: crm/fcrm/doctype/crm_task/crm_task.json
|
||||
#: frontend/src/components/Modals/TaskModal.vue:48
|
||||
#: frontend/src/components/Modals/TaskModal.vue:35
|
||||
msgid "Description"
|
||||
msgstr ""
|
||||
|
||||
@ -1286,8 +1297,8 @@ msgstr ""
|
||||
msgid "Duration"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:516
|
||||
#: frontend/src/components/Settings/Settings.vue:122
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:563
|
||||
#: frontend/src/components/Settings/Settings.vue:130
|
||||
msgid "ERPNext"
|
||||
msgstr ""
|
||||
|
||||
@ -1332,7 +1343,7 @@ msgstr ""
|
||||
msgid "Edit"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Modals/CallLogModal.vue:100
|
||||
#: frontend/src/components/Modals/CallLogModal.vue:81
|
||||
msgid "Edit Call Log"
|
||||
msgstr ""
|
||||
|
||||
@ -1352,7 +1363,7 @@ msgstr ""
|
||||
msgid "Edit Grid Row Fields Layout"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Modals/NoteModal.vue:18
|
||||
#: frontend/src/components/Modals/NoteModal.vue:15
|
||||
msgid "Edit Note"
|
||||
msgstr ""
|
||||
|
||||
@ -1360,7 +1371,7 @@ msgstr ""
|
||||
msgid "Edit Quick Entry Layout"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Modals/TaskModal.vue:18
|
||||
#: frontend/src/components/Modals/TaskModal.vue:15
|
||||
msgid "Edit Task"
|
||||
msgstr ""
|
||||
|
||||
@ -1401,6 +1412,10 @@ msgstr ""
|
||||
msgid "Email"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Settings/Settings.vue:107
|
||||
msgid "Email Accounts"
|
||||
msgstr ""
|
||||
|
||||
#. Label of the email_sent_at (Datetime) field in DocType 'CRM Invitation'
|
||||
#: crm/fcrm/doctype/crm_invitation/crm_invitation.json
|
||||
msgid "Email Sent At"
|
||||
@ -1410,7 +1425,7 @@ msgstr ""
|
||||
msgid "Email Templates"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:490
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:537
|
||||
msgid "Email communication"
|
||||
msgstr ""
|
||||
|
||||
@ -1418,7 +1433,7 @@ msgstr ""
|
||||
msgid "Email from Lead"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:474
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:514
|
||||
msgid "Email template"
|
||||
msgstr ""
|
||||
|
||||
@ -1535,7 +1550,7 @@ msgstr ""
|
||||
#. Agent'
|
||||
#: crm/fcrm/doctype/crm_call_log/crm_call_log.json
|
||||
#: crm/fcrm/doctype/crm_telephony_agent/crm_telephony_agent.json
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:514
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:561
|
||||
#: frontend/src/components/Settings/TelephonySettings.vue:26
|
||||
#: frontend/src/components/Settings/TelephonySettings.vue:48
|
||||
msgid "Exotel"
|
||||
@ -1566,7 +1581,7 @@ msgstr ""
|
||||
msgid "Exotel settings updated successfully"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:97
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:107
|
||||
msgid "Expand"
|
||||
msgstr ""
|
||||
|
||||
@ -1708,7 +1723,7 @@ msgstr ""
|
||||
msgid "Frappe CRM"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:520
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:567
|
||||
msgid "Frappe CRM mobile"
|
||||
msgstr ""
|
||||
|
||||
@ -1762,7 +1777,7 @@ msgid "Gender"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Settings/GeneralSettings.vue:4
|
||||
#: frontend/src/components/Settings/Settings.vue:93
|
||||
#: frontend/src/components/Settings/Settings.vue:95
|
||||
msgid "General"
|
||||
msgstr ""
|
||||
|
||||
@ -1791,7 +1806,7 @@ msgstr ""
|
||||
msgid "Group By: "
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:83
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:93
|
||||
msgid "Help"
|
||||
msgstr ""
|
||||
|
||||
@ -1850,7 +1865,7 @@ msgstr ""
|
||||
msgid "Holidays"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:459
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:499
|
||||
#: frontend/src/components/Settings/GeneralSettings.vue:97
|
||||
msgid "Home actions"
|
||||
msgstr ""
|
||||
@ -1929,7 +1944,7 @@ msgstr ""
|
||||
msgid "Initiating call..."
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:510
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:557
|
||||
msgid "Integration"
|
||||
msgstr ""
|
||||
|
||||
@ -1937,12 +1952,12 @@ msgstr ""
|
||||
msgid "Integration Not Enabled"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Settings/Settings.vue:107
|
||||
#: frontend/src/components/Settings/Settings.vue:115
|
||||
msgid "Integrations"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:446
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:449
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:486
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:489
|
||||
msgid "Introduction"
|
||||
msgstr ""
|
||||
|
||||
@ -1963,7 +1978,7 @@ msgstr ""
|
||||
msgid "Invalid credentials"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Settings/Settings.vue:99
|
||||
#: frontend/src/components/Settings/Settings.vue:101
|
||||
msgid "Invite Members"
|
||||
msgstr ""
|
||||
|
||||
@ -1975,11 +1990,11 @@ msgstr ""
|
||||
msgid "Invite by email"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:460
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:500
|
||||
msgid "Invite members"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:313
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:332
|
||||
msgid "Invite your team"
|
||||
msgstr ""
|
||||
|
||||
@ -2044,7 +2059,7 @@ msgstr ""
|
||||
|
||||
#: frontend/src/components/Filter.vue:75 frontend/src/components/Filter.vue:108
|
||||
#: frontend/src/components/Modals/AssignmentModal.vue:35
|
||||
#: frontend/src/components/Modals/TaskModal.vue:75
|
||||
#: frontend/src/components/Modals/TaskModal.vue:51
|
||||
#: frontend/src/components/Telephony/TaskPanel.vue:47
|
||||
msgid "John Doe"
|
||||
msgstr ""
|
||||
@ -2132,7 +2147,7 @@ msgstr ""
|
||||
|
||||
#. Label of the lead (Link) field in DocType 'CRM Deal'
|
||||
#: crm/fcrm/doctype/crm_deal/crm_deal.json
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:467
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:507
|
||||
#: frontend/src/components/Telephony/ExotelCallUI.vue:205
|
||||
#: frontend/src/pages/Tasks.vue:130
|
||||
msgid "Lead"
|
||||
@ -2249,10 +2264,6 @@ msgstr ""
|
||||
msgid "Logo"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/SignupBanner.vue:9
|
||||
msgid "Loved the demo?"
|
||||
msgstr ""
|
||||
|
||||
#. Option for the 'Priority' (Select) field in DocType 'CRM Task'
|
||||
#: crm/fcrm/doctype/crm_task/crm_task.json
|
||||
msgid "Low"
|
||||
@ -2321,7 +2332,7 @@ msgstr ""
|
||||
msgid "Mark all as read"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:464
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:504
|
||||
msgid "Masters"
|
||||
msgstr ""
|
||||
|
||||
@ -2374,7 +2385,7 @@ msgstr ""
|
||||
msgid "Mobile Number Missing"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:523
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:570
|
||||
msgid "Mobile app installation"
|
||||
msgstr ""
|
||||
|
||||
@ -2435,7 +2446,7 @@ msgstr ""
|
||||
msgid "New Address"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Modals/CallLogModal.vue:100
|
||||
#: frontend/src/components/Modals/CallLogModal.vue:81
|
||||
msgid "New Call Log"
|
||||
msgstr ""
|
||||
|
||||
@ -2660,7 +2671,7 @@ msgstr ""
|
||||
|
||||
#. Label of the note (Link) field in DocType 'CRM Call Log'
|
||||
#: crm/fcrm/doctype/crm_call_log/crm_call_log.json
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:471
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:511
|
||||
msgid "Note"
|
||||
msgstr ""
|
||||
|
||||
@ -2674,7 +2685,7 @@ msgid "Notes View"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Activities/EmailArea.vue:13
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:495
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:542
|
||||
msgid "Notification"
|
||||
msgstr ""
|
||||
|
||||
@ -2724,13 +2735,13 @@ msgstr ""
|
||||
msgid "Only one {0} can be set as primary."
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Modals/NoteModal.vue:25
|
||||
#: frontend/src/components/Modals/TaskModal.vue:25
|
||||
#: frontend/src/components/Modals/NoteModal.vue:18
|
||||
#: frontend/src/components/Modals/TaskModal.vue:18
|
||||
msgid "Open Deal"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Modals/NoteModal.vue:26
|
||||
#: frontend/src/components/Modals/TaskModal.vue:26
|
||||
#: frontend/src/components/Modals/NoteModal.vue:19
|
||||
#: frontend/src/components/Modals/TaskModal.vue:19
|
||||
msgid "Open Lead"
|
||||
msgstr ""
|
||||
|
||||
@ -2769,7 +2780,7 @@ msgstr ""
|
||||
#. Label of the organization (Data) field in DocType 'CRM Lead'
|
||||
#: crm/fcrm/doctype/crm_deal/crm_deal.json
|
||||
#: crm/fcrm/doctype/crm_lead/crm_lead.json
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:470
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:510
|
||||
#: frontend/src/pages/Contact.vue:606 frontend/src/pages/Lead.vue:236
|
||||
#: frontend/src/pages/MobileContact.vue:602
|
||||
#: frontend/src/pages/MobileLead.vue:106
|
||||
@ -2815,7 +2826,7 @@ msgstr ""
|
||||
msgid "Organizations"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:487
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:534
|
||||
msgid "Other features"
|
||||
msgstr ""
|
||||
|
||||
@ -2903,7 +2914,7 @@ msgstr ""
|
||||
msgid "Pinned Views"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:483
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:530
|
||||
msgid "Pinned view"
|
||||
msgstr ""
|
||||
|
||||
@ -2968,8 +2979,8 @@ msgstr ""
|
||||
msgid "Probability"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:457
|
||||
#: frontend/src/components/Settings/Settings.vue:83
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:497
|
||||
#: frontend/src/components/Settings/Settings.vue:85
|
||||
msgid "Profile"
|
||||
msgstr ""
|
||||
|
||||
@ -2986,7 +2997,7 @@ msgstr ""
|
||||
msgid "Public Views"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:482
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:529
|
||||
msgid "Public view"
|
||||
msgstr ""
|
||||
|
||||
@ -3009,7 +3020,7 @@ msgstr ""
|
||||
msgid "Quick Filters updated successfully"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:506
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:553
|
||||
msgid "Quick entry layout"
|
||||
msgstr ""
|
||||
|
||||
@ -3320,7 +3331,7 @@ msgstr ""
|
||||
#: frontend/src/components/Controls/GridRowFieldsModal.vue:26
|
||||
#: frontend/src/components/DropdownItem.vue:21
|
||||
#: frontend/src/components/Modals/AddressModal.vue:100
|
||||
#: frontend/src/components/Modals/CallLogModal.vue:104
|
||||
#: frontend/src/components/Modals/CallLogModal.vue:85
|
||||
#: frontend/src/components/Modals/DataFieldsModal.vue:26
|
||||
#: frontend/src/components/Modals/QuickEntryModal.vue:26
|
||||
#: frontend/src/components/Modals/SidePanelModal.vue:26
|
||||
@ -3340,7 +3351,7 @@ msgstr ""
|
||||
msgid "Saved Views"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:481
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:528
|
||||
msgid "Saved view"
|
||||
msgstr ""
|
||||
|
||||
@ -3381,7 +3392,7 @@ msgstr ""
|
||||
msgid "Send an email"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:401
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:430
|
||||
msgid "Send email"
|
||||
msgstr ""
|
||||
|
||||
@ -3395,7 +3406,7 @@ msgstr ""
|
||||
msgid "Series"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:493
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:540
|
||||
msgid "Service level agreement"
|
||||
msgstr ""
|
||||
|
||||
@ -3423,13 +3434,13 @@ msgstr ""
|
||||
msgid "Set first name"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:450
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:490
|
||||
msgid "Setting up"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:454
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:494
|
||||
#: frontend/src/components/Settings/Settings.vue:11
|
||||
#: frontend/src/components/Settings/Settings.vue:79
|
||||
#: frontend/src/components/Settings/Settings.vue:81
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
|
||||
@ -3462,10 +3473,6 @@ msgstr ""
|
||||
msgid "Sidebar Items"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/SignupBanner.vue:15
|
||||
msgid "Sign up now"
|
||||
msgstr ""
|
||||
|
||||
#. Description of the 'Condition' (Code) field in DocType 'CRM Service Level
|
||||
#. Agreement'
|
||||
#: crm/fcrm/doctype/crm_service_level_agreement/crm_service_level_agreement.json
|
||||
@ -3609,7 +3616,7 @@ msgstr ""
|
||||
|
||||
#. Option for the 'Type' (Select) field in DocType 'CRM Notification'
|
||||
#: crm/fcrm/doctype/crm_notification/crm_notification.json
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:472
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:512
|
||||
msgid "Task"
|
||||
msgstr ""
|
||||
|
||||
@ -3618,7 +3625,7 @@ msgstr ""
|
||||
msgid "Tasks"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Settings/Settings.vue:110
|
||||
#: frontend/src/components/Settings/Settings.vue:118
|
||||
msgid "Telephony"
|
||||
msgstr ""
|
||||
|
||||
@ -3702,8 +3709,8 @@ msgstr ""
|
||||
#. Label of the title (Data) field in DocType 'FCRM Note'
|
||||
#: crm/fcrm/doctype/crm_task/crm_task.json
|
||||
#: crm/fcrm/doctype/fcrm_note/fcrm_note.json
|
||||
#: frontend/src/components/Modals/NoteModal.vue:41
|
||||
#: frontend/src/components/Modals/TaskModal.vue:41
|
||||
#: frontend/src/components/Modals/NoteModal.vue:30
|
||||
#: frontend/src/components/Modals/TaskModal.vue:30
|
||||
msgid "Title"
|
||||
msgstr ""
|
||||
|
||||
@ -3750,8 +3757,8 @@ msgstr ""
|
||||
msgid "Tomorrow"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Modals/NoteModal.vue:56
|
||||
#: frontend/src/components/Modals/TaskModal.vue:58
|
||||
#: frontend/src/components/Modals/NoteModal.vue:37
|
||||
#: frontend/src/components/Modals/TaskModal.vue:39
|
||||
msgid "Took a call with John Doe and discussed the new project."
|
||||
msgstr ""
|
||||
|
||||
@ -3760,10 +3767,6 @@ msgstr ""
|
||||
msgid "Total Holidays"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/SignupBanner.vue:12
|
||||
msgid "Try Frappe CRM for free with a 14-day trial."
|
||||
msgstr ""
|
||||
|
||||
#. Option for the 'Weekly Off' (Select) field in DocType 'CRM Holiday List'
|
||||
#. Option for the 'Workday' (Select) field in DocType 'CRM Service Day'
|
||||
#: crm/fcrm/doctype/crm_holiday_list/crm_holiday_list.json
|
||||
@ -3782,7 +3785,7 @@ msgstr ""
|
||||
#. Agent'
|
||||
#: crm/fcrm/doctype/crm_call_log/crm_call_log.json
|
||||
#: crm/fcrm/doctype/crm_telephony_agent/crm_telephony_agent.json
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:513
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:560
|
||||
#: frontend/src/components/Settings/TelephonySettings.vue:25
|
||||
#: frontend/src/components/Settings/TelephonySettings.vue:35
|
||||
msgid "Twilio"
|
||||
@ -3857,8 +3860,8 @@ msgstr ""
|
||||
#: frontend/src/components/ColumnSettings.vue:134
|
||||
#: frontend/src/components/Modals/AssignmentModal.vue:17
|
||||
#: frontend/src/components/Modals/EmailTemplateModal.vue:9
|
||||
#: frontend/src/components/Modals/NoteModal.vue:8
|
||||
#: frontend/src/components/Modals/TaskModal.vue:8
|
||||
#: frontend/src/components/Modals/NoteModal.vue:6
|
||||
#: frontend/src/components/Modals/TaskModal.vue:6
|
||||
#: frontend/src/components/Settings/GeneralSettings.vue:112
|
||||
#: frontend/src/components/Settings/ProfileSettings.vue:71
|
||||
#: frontend/src/components/Settings/SettingsPage.vue:31
|
||||
@ -3927,10 +3930,14 @@ msgstr ""
|
||||
msgid "View Name"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:478
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:525
|
||||
msgid "Views"
|
||||
msgstr ""
|
||||
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:521
|
||||
msgid "Web form"
|
||||
msgstr ""
|
||||
|
||||
#. Label of the webhook_verify_token (Data) field in DocType 'CRM Exotel
|
||||
#. Settings'
|
||||
#: crm/fcrm/doctype/crm_exotel_settings/crm_exotel_settings.json
|
||||
@ -3975,8 +3982,8 @@ msgstr ""
|
||||
|
||||
#. Option for the 'Type' (Select) field in DocType 'CRM Notification'
|
||||
#: crm/fcrm/doctype/crm_notification/crm_notification.json
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:515
|
||||
#: frontend/src/components/Settings/Settings.vue:116
|
||||
#: frontend/src/components/Layouts/AppSidebar.vue:562
|
||||
#: frontend/src/components/Settings/Settings.vue:124
|
||||
#: frontend/src/pages/Deal.vue:567 frontend/src/pages/Lead.vue:554
|
||||
#: frontend/src/pages/MobileDeal.vue:482 frontend/src/pages/MobileLead.vue:385
|
||||
msgid "WhatsApp"
|
||||
@ -4137,7 +4144,7 @@ msgstr ""
|
||||
msgid "kanban"
|
||||
msgstr ""
|
||||
|
||||
#: crm/api/doc.py:38 crm/api/doc.py:156 crm/api/doc.py:500
|
||||
#: crm/api/doc.py:38 crm/api/doc.py:156 crm/api/doc.py:501
|
||||
msgid "label"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@ -1 +1 @@
|
||||
Subproject commit 3423aa5b5c38d3a1b143ae8ab08cbde7360f9a7c
|
||||
Subproject commit 29307e4fffaacdbb3d9c5d95c5270b2f245a5607
|
||||
7
frontend/components.d.ts
vendored
@ -78,11 +78,17 @@ declare module 'vue' {
|
||||
EditIcon: typeof import('./src/components/Icons/EditIcon.vue')['default']
|
||||
EditValueModal: typeof import('./src/components/Modals/EditValueModal.vue')['default']
|
||||
Email2Icon: typeof import('./src/components/Icons/Email2Icon.vue')['default']
|
||||
EmailAccountCard: typeof import('./src/components/Settings/EmailAccountCard.vue')['default']
|
||||
EmailAccountList: typeof import('./src/components/Settings/EmailAccountList.vue')['default']
|
||||
EmailAdd: typeof import('./src/components/Settings/EmailAdd.vue')['default']
|
||||
EmailArea: typeof import('./src/components/Activities/EmailArea.vue')['default']
|
||||
EmailAtIcon: typeof import('./src/components/Icons/EmailAtIcon.vue')['default']
|
||||
EmailConfig: typeof import('./src/components/Settings/EmailConfig.vue')['default']
|
||||
EmailContent: typeof import('./src/components/Activities/EmailContent.vue')['default']
|
||||
EmailEdit: typeof import('./src/components/Settings/EmailEdit.vue')['default']
|
||||
EmailEditor: typeof import('./src/components/EmailEditor.vue')['default']
|
||||
EmailIcon: typeof import('./src/components/Icons/EmailIcon.vue')['default']
|
||||
EmailProviderIcon: typeof import('./src/components/Settings/EmailProviderIcon.vue')['default']
|
||||
EmailTemplateModal: typeof import('./src/components/Modals/EmailTemplateModal.vue')['default']
|
||||
EmailTemplateSelectorModal: typeof import('./src/components/Modals/EmailTemplateSelectorModal.vue')['default']
|
||||
EmailTemplatesListView: typeof import('./src/components/ListViews/EmailTemplatesListView.vue')['default']
|
||||
@ -140,6 +146,7 @@ declare module 'vue' {
|
||||
ListIcon: typeof import('./src/components/Icons/ListIcon.vue')['default']
|
||||
ListRows: typeof import('./src/components/ListViews/ListRows.vue')['default']
|
||||
LoadingIndicator: typeof import('./src/components/Icons/LoadingIndicator.vue')['default']
|
||||
LucidePlus: typeof import('~icons/lucide/plus')['default']
|
||||
MarkAsDoneIcon: typeof import('./src/components/Icons/MarkAsDoneIcon.vue')['default']
|
||||
MaximizeIcon: typeof import('./src/components/Icons/MaximizeIcon.vue')['default']
|
||||
MenuIcon: typeof import('./src/components/Icons/MenuIcon.vue')['default']
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
"dependencies": {
|
||||
"@twilio/voice-sdk": "^2.10.2",
|
||||
"@vueuse/integrations": "^10.3.0",
|
||||
"frappe-ui": "^0.1.121",
|
||||
"frappe-ui": "^0.1.123",
|
||||
"gemoji": "^8.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
"mime": "^4.0.1",
|
||||
|
||||
@ -1,55 +1,36 @@
|
||||
<template>
|
||||
<Dialog v-model="show" :options="dialogOptions">
|
||||
<template #body>
|
||||
<div class="bg-surface-modal px-4 pb-6 pt-5 sm:px-6">
|
||||
<div class="mb-5 flex items-center justify-between">
|
||||
<div class="px-4 pt-5 pb-6 bg-surface-modal sm:px-6">
|
||||
<div class="flex items-center justify-between mb-5">
|
||||
<div>
|
||||
<h3 class="text-2xl font-semibold leading-6 text-ink-gray-9">
|
||||
{{ __(dialogOptions.title) || __('Untitled') }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<Button
|
||||
v-if="isManager() && !isMobileView"
|
||||
variant="ghost"
|
||||
class="w-7"
|
||||
@click="openQuickEntryModal"
|
||||
>
|
||||
<EditIcon class="h-4 w-4" />
|
||||
<Button v-if="isManager() && !isMobileView" variant="ghost" class="w-7" @click="openQuickEntryModal">
|
||||
<EditIcon class="w-4 h-4" />
|
||||
</Button>
|
||||
<Button variant="ghost" class="w-7" @click="show = false">
|
||||
<FeatherIcon name="x" class="h-4 w-4" />
|
||||
<FeatherIcon name="x" class="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="tabs.data">
|
||||
<FieldLayout
|
||||
:tabs="tabs.data"
|
||||
:data="_callLog"
|
||||
doctype="CRM Call Log"
|
||||
/>
|
||||
<ErrorMessage class="mt-2" :message="error" />
|
||||
<FieldLayout :tabs="tabs.data" :data="_callLog" doctype="CRM Call Log" />
|
||||
<ErrorMessage class="mt-8" :message="error" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-4 pb-7 pt-4 sm:px-6">
|
||||
<div class="px-4 pt-4 pb-7 sm:px-6">
|
||||
<div class="space-y-2">
|
||||
<Button
|
||||
class="w-full"
|
||||
v-for="action in dialogOptions.actions"
|
||||
:key="action.label"
|
||||
v-bind="action"
|
||||
:label="__(action.label)"
|
||||
:loading="loading"
|
||||
/>
|
||||
<Button class="w-full" v-for="action in dialogOptions.actions" :key="action.label" v-bind="action"
|
||||
:label="__(action.label)" :loading="loading" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Dialog>
|
||||
<QuickEntryModal
|
||||
v-if="showQuickEntryModal"
|
||||
v-model="showQuickEntryModal"
|
||||
doctype="CRM Call Log"
|
||||
/>
|
||||
<QuickEntryModal v-if="showQuickEntryModal" v-model="showQuickEntryModal" doctype="CRM Call Log" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@ -67,7 +48,7 @@ const props = defineProps({
|
||||
options: {
|
||||
type: Object,
|
||||
default: {
|
||||
afterInsert: () => {},
|
||||
afterInsert: () => { },
|
||||
},
|
||||
},
|
||||
})
|
||||
@ -175,6 +156,13 @@ const createCallLog = createResource({
|
||||
},
|
||||
onError(err) {
|
||||
loading.value = false
|
||||
if (err.exc_type == 'MandatoryError') {
|
||||
const errorMessage = err.messages
|
||||
.map(msg => msg.split('Log:')[1].trim())
|
||||
.join(', ')
|
||||
error.value = `These fields are required: ${errorMessage}`
|
||||
return
|
||||
}
|
||||
error.value = err
|
||||
},
|
||||
})
|
||||
|
||||
@ -1,34 +1,25 @@
|
||||
<template>
|
||||
<Dialog
|
||||
v-model="show"
|
||||
:options="{
|
||||
size: 'xl',
|
||||
actions: [
|
||||
{
|
||||
label: editMode ? __('Update') : __('Create'),
|
||||
variant: 'solid',
|
||||
onClick: () => updateNote(),
|
||||
},
|
||||
],
|
||||
}"
|
||||
>
|
||||
<Dialog v-model="show" :options="{
|
||||
size: 'xl',
|
||||
actions: [
|
||||
{
|
||||
label: editMode ? __('Update') : __('Create'),
|
||||
variant: 'solid',
|
||||
onClick: () => updateNote(),
|
||||
},
|
||||
],
|
||||
}">
|
||||
<template #body-title>
|
||||
<div class="flex items-center gap-3">
|
||||
<h3 class="text-2xl font-semibold leading-6 text-ink-gray-9">
|
||||
{{ editMode ? __('Edit Note') : __('Create Note') }}
|
||||
</h3>
|
||||
<Button
|
||||
v-if="_note?.reference_docname"
|
||||
size="sm"
|
||||
:label="
|
||||
_note.reference_doctype == 'CRM Deal'
|
||||
? __('Open Deal')
|
||||
: __('Open Lead')
|
||||
"
|
||||
@click="redirect()"
|
||||
>
|
||||
<Button v-if="_note?.reference_docname" size="sm" :label="_note.reference_doctype == 'CRM Deal'
|
||||
? __('Open Deal')
|
||||
: __('Open Lead')
|
||||
" @click="redirect()">
|
||||
<template #suffix>
|
||||
<ArrowUpRightIcon class="h-4 w-4" />
|
||||
<ArrowUpRightIcon class="w-4 h-4" />
|
||||
</template>
|
||||
</Button>
|
||||
</div>
|
||||
@ -36,27 +27,17 @@
|
||||
<template #body-content>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div>
|
||||
<FormControl
|
||||
ref="title"
|
||||
:label="__('Title')"
|
||||
v-model="_note.title"
|
||||
:placeholder="__('Call with John Doe')"
|
||||
/>
|
||||
<FormControl ref="title" :label="__('Title')" v-model="_note.title" :placeholder="__('Call with John Doe')"
|
||||
required />
|
||||
</div>
|
||||
<div>
|
||||
<div class="mb-1.5 text-xs text-ink-gray-5">{{ __('Content') }}</div>
|
||||
<TextEditor
|
||||
variant="outline"
|
||||
ref="content"
|
||||
<TextEditor variant="outline" ref="content"
|
||||
editor-class="!prose-sm overflow-auto min-h-[180px] max-h-80 py-1.5 px-2 rounded border border-[--surface-gray-2] bg-surface-gray-2 placeholder-ink-gray-4 hover:border-outline-gray-modals hover:bg-surface-gray-3 hover:shadow-sm focus:bg-surface-white focus:border-outline-gray-4 focus:shadow-sm focus:ring-0 focus-visible:ring-2 focus-visible:ring-outline-gray-3 text-ink-gray-8 transition-colors"
|
||||
:bubbleMenu="true"
|
||||
:content="_note.content"
|
||||
@change="(val) => (_note.content = val)"
|
||||
:placeholder="
|
||||
__('Took a call with John Doe and discussed the new project.')
|
||||
"
|
||||
/>
|
||||
:bubbleMenu="true" :content="_note.content" @change="(val) => (_note.content = val)" :placeholder="__('Took a call with John Doe and discussed the new project.')
|
||||
" />
|
||||
</div>
|
||||
<ErrorMessage class="mt-4" v-if="error" :message="__(error)" />
|
||||
</div>
|
||||
</template>
|
||||
</Dialog>
|
||||
@ -94,17 +75,12 @@ const router = useRouter()
|
||||
|
||||
const { updateOnboardingStep } = useOnboarding('frappecrm')
|
||||
|
||||
const error = ref(null)
|
||||
const title = ref(null)
|
||||
const editMode = ref(false)
|
||||
let _note = ref({})
|
||||
|
||||
async function updateNote() {
|
||||
if (
|
||||
props.note.title === _note.value.title &&
|
||||
props.note.content === _note.value.content
|
||||
)
|
||||
return
|
||||
|
||||
if (_note.value.name) {
|
||||
let d = await call('frappe.client.set_value', {
|
||||
doctype: 'FCRM Note',
|
||||
@ -124,6 +100,12 @@ async function updateNote() {
|
||||
reference_doctype: props.doctype,
|
||||
reference_docname: props.doc || '',
|
||||
},
|
||||
}, {
|
||||
onError: (err) => {
|
||||
if (err.error.exc_type == 'MandatoryError') {
|
||||
error.value = "Title is mandatory"
|
||||
}
|
||||
}
|
||||
})
|
||||
if (d.name) {
|
||||
updateOnboardingStep('create_first_note')
|
||||
|
||||
@ -1,43 +1,28 @@
|
||||
<template>
|
||||
<Dialog v-model="show" :options="{ size: 'xl' }">
|
||||
<template #body>
|
||||
<div class="bg-surface-modal px-4 pb-6 pt-5 sm:px-6">
|
||||
<div class="mb-5 flex items-center justify-between">
|
||||
<div class="px-4 pt-5 pb-6 bg-surface-modal sm:px-6">
|
||||
<div class="flex items-center justify-between mb-5">
|
||||
<div>
|
||||
<h3 class="text-2xl font-semibold leading-6 text-ink-gray-9">
|
||||
{{ __('New Organization') }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<Button
|
||||
v-if="isManager() && !isMobileView"
|
||||
variant="ghost"
|
||||
class="w-7"
|
||||
@click="openQuickEntryModal"
|
||||
>
|
||||
<EditIcon class="h-4 w-4" />
|
||||
<Button v-if="isManager() && !isMobileView" variant="ghost" class="w-7" @click="openQuickEntryModal">
|
||||
<EditIcon class="w-4 h-4" />
|
||||
</Button>
|
||||
<Button variant="ghost" class="w-7" @click="show = false">
|
||||
<FeatherIcon name="x" class="h-4 w-4" />
|
||||
<FeatherIcon name="x" class="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<FieldLayout
|
||||
v-if="tabs.data?.length"
|
||||
:tabs="tabs.data"
|
||||
:data="_organization"
|
||||
doctype="CRM Organization"
|
||||
/>
|
||||
<FieldLayout v-if="tabs.data?.length" :tabs="tabs.data" :data="_organization" doctype="CRM Organization" />
|
||||
<ErrorMessage class="mt-8" v-if="error" :message="__(error)" />
|
||||
</div>
|
||||
<div class="px-4 pb-7 pt-4 sm:px-6">
|
||||
<div class="px-4 pt-4 pb-7 sm:px-6">
|
||||
<div class="space-y-2">
|
||||
<Button
|
||||
class="w-full"
|
||||
variant="solid"
|
||||
:label="__('Create')"
|
||||
:loading="loading"
|
||||
@click="createOrganization"
|
||||
/>
|
||||
<Button class="w-full" variant="solid" :label="__('Create')" :loading="loading" @click="createOrganization" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -59,7 +44,7 @@ const props = defineProps({
|
||||
type: Object,
|
||||
default: {
|
||||
redirect: true,
|
||||
afterInsert: () => {},
|
||||
afterInsert: () => { },
|
||||
},
|
||||
},
|
||||
})
|
||||
@ -84,6 +69,7 @@ let _organization = ref({
|
||||
})
|
||||
|
||||
let doc = ref({})
|
||||
const error = ref(null)
|
||||
|
||||
async function createOrganization() {
|
||||
const doc = await call('frappe.client.insert', {
|
||||
@ -91,6 +77,12 @@ async function createOrganization() {
|
||||
doctype: 'CRM Organization',
|
||||
..._organization.value,
|
||||
},
|
||||
}, {
|
||||
onError: (err) => {
|
||||
if (err.error.exc_type == 'ValidationError') {
|
||||
error.value = err.error?.messages?.[0]
|
||||
}
|
||||
}
|
||||
})
|
||||
loading.value = false
|
||||
if (doc.name) {
|
||||
|
||||
@ -1,34 +1,25 @@
|
||||
<template>
|
||||
<Dialog
|
||||
v-model="show"
|
||||
:options="{
|
||||
size: 'xl',
|
||||
actions: [
|
||||
{
|
||||
label: editMode ? __('Update') : __('Create'),
|
||||
variant: 'solid',
|
||||
onClick: () => updateTask(),
|
||||
},
|
||||
],
|
||||
}"
|
||||
>
|
||||
<Dialog v-model="show" :options="{
|
||||
size: 'xl',
|
||||
actions: [
|
||||
{
|
||||
label: editMode ? __('Update') : __('Create'),
|
||||
variant: 'solid',
|
||||
onClick: () => updateTask(),
|
||||
},
|
||||
],
|
||||
}">
|
||||
<template #body-title>
|
||||
<div class="flex items-center gap-3">
|
||||
<h3 class="text-2xl font-semibold leading-6 text-ink-gray-9">
|
||||
{{ editMode ? __('Edit Task') : __('Create Task') }}
|
||||
</h3>
|
||||
<Button
|
||||
v-if="task?.reference_docname"
|
||||
size="sm"
|
||||
:label="
|
||||
task.reference_doctype == 'CRM Deal'
|
||||
? __('Open Deal')
|
||||
: __('Open Lead')
|
||||
"
|
||||
@click="redirect()"
|
||||
>
|
||||
<Button v-if="task?.reference_docname" size="sm" :label="task.reference_doctype == 'CRM Deal'
|
||||
? __('Open Deal')
|
||||
: __('Open Lead')
|
||||
" @click="redirect()">
|
||||
<template #suffix>
|
||||
<ArrowUpRightIcon class="h-4 w-4" />
|
||||
<ArrowUpRightIcon class="w-4 h-4" />
|
||||
</template>
|
||||
</Button>
|
||||
</div>
|
||||
@ -36,74 +27,53 @@
|
||||
<template #body-content>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div>
|
||||
<FormControl
|
||||
ref="title"
|
||||
:label="__('Title')"
|
||||
v-model="_task.title"
|
||||
:placeholder="__('Call with John Doe')"
|
||||
/>
|
||||
<FormControl ref="title" :label="__('Title')" v-model="_task.title" :placeholder="__('Call with John Doe')"
|
||||
required />
|
||||
</div>
|
||||
<div>
|
||||
<div class="mb-1.5 text-xs text-ink-gray-5">
|
||||
{{ __('Description') }}
|
||||
</div>
|
||||
<TextEditor
|
||||
variant="outline"
|
||||
ref="description"
|
||||
<TextEditor variant="outline" ref="description"
|
||||
editor-class="!prose-sm overflow-auto min-h-[180px] max-h-80 py-1.5 px-2 rounded border border-[--surface-gray-2] bg-surface-gray-2 placeholder-ink-gray-4 hover:border-outline-gray-modals hover:bg-surface-gray-3 hover:shadow-sm focus:bg-surface-white focus:border-outline-gray-4 focus:shadow-sm focus:ring-0 focus-visible:ring-2 focus-visible:ring-outline-gray-3 text-ink-gray-8 transition-colors"
|
||||
:bubbleMenu="true"
|
||||
:content="_task.description"
|
||||
@change="(val) => (_task.description = val)"
|
||||
:placeholder="
|
||||
__('Took a call with John Doe and discussed the new project.')
|
||||
"
|
||||
/>
|
||||
:bubbleMenu="true" :content="_task.description" @change="(val) => (_task.description = val)" :placeholder="__('Took a call with John Doe and discussed the new project.')
|
||||
" />
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<Dropdown :options="taskStatusOptions(updateTaskStatus)">
|
||||
<Button :label="_task.status" class="w-full justify-between">
|
||||
<Button :label="_task.status" class="justify-between w-full">
|
||||
<template #prefix>
|
||||
<TaskStatusIcon :status="_task.status" />
|
||||
</template>
|
||||
</Button>
|
||||
</Dropdown>
|
||||
<Link
|
||||
class="form-control"
|
||||
:value="getUser(_task.assigned_to).full_name"
|
||||
doctype="User"
|
||||
@change="(option) => (_task.assigned_to = option)"
|
||||
:placeholder="__('John Doe')"
|
||||
:hideMe="true"
|
||||
>
|
||||
<template #prefix>
|
||||
<UserAvatar class="mr-2 !h-4 !w-4" :user="_task.assigned_to" />
|
||||
</template>
|
||||
<template #item-prefix="{ option }">
|
||||
<UserAvatar class="mr-2" :user="option.value" size="sm" />
|
||||
</template>
|
||||
<template #item-label="{ option }">
|
||||
<Tooltip :text="option.value">
|
||||
<div class="cursor-pointer text-ink-gray-9">
|
||||
{{ getUser(option.value).full_name }}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</template>
|
||||
<Link class="form-control" :value="getUser(_task.assigned_to).full_name" doctype="User"
|
||||
@change="(option) => (_task.assigned_to = option)" :placeholder="__('John Doe')" :hideMe="true">
|
||||
<template #prefix>
|
||||
<UserAvatar class="mr-2 !h-4 !w-4" :user="_task.assigned_to" />
|
||||
</template>
|
||||
<template #item-prefix="{ option }">
|
||||
<UserAvatar class="mr-2" :user="option.value" size="sm" />
|
||||
</template>
|
||||
<template #item-label="{ option }">
|
||||
<Tooltip :text="option.value">
|
||||
<div class="cursor-pointer text-ink-gray-9">
|
||||
{{ getUser(option.value).full_name }}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</template>
|
||||
</Link>
|
||||
<DateTimePicker
|
||||
class="datepicker w-36"
|
||||
v-model="_task.due_date"
|
||||
:placeholder="__('01/04/2024 11:30 PM')"
|
||||
:formatter="(date) => getFormat(date, '', true, true)"
|
||||
input-class="border-none"
|
||||
/>
|
||||
<DateTimePicker class="datepicker w-36" v-model="_task.due_date" :placeholder="__('01/04/2024 11:30 PM')"
|
||||
:formatter="(date) => getFormat(date, '', true, true)" input-class="border-none" />
|
||||
<Dropdown :options="taskPriorityOptions(updateTaskPriority)">
|
||||
<Button :label="_task.priority" class="w-full justify-between">
|
||||
<Button :label="_task.priority" class="justify-between w-full">
|
||||
<template #prefix>
|
||||
<TaskPriorityIcon :priority="_task.priority" />
|
||||
</template>
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
<ErrorMessage class="mt-4" v-if="error" :message="__(error)" />
|
||||
</div>
|
||||
</template>
|
||||
</Dialog>
|
||||
@ -147,6 +117,7 @@ const router = useRouter()
|
||||
const { getUser } = usersStore()
|
||||
const { updateOnboardingStep } = useOnboarding('frappecrm')
|
||||
|
||||
const error = ref(null)
|
||||
const title = ref(null)
|
||||
const editMode = ref(false)
|
||||
const _task = ref({
|
||||
@ -200,6 +171,12 @@ async function updateTask() {
|
||||
reference_docname: props.doc || null,
|
||||
..._task.value,
|
||||
},
|
||||
}, {
|
||||
onError: (err) => {
|
||||
if (err.error.exc_type == 'MandatoryError') {
|
||||
error.value = "Title is mandatory"
|
||||
}
|
||||
}
|
||||
})
|
||||
if (d.name) {
|
||||
updateOnboardingStep('create_first_task')
|
||||
|
||||
55
frontend/src/components/Settings/EmailAccountCard.vue
Normal file
@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<div class="flex items-center justify-between p-1 border-b border-gray-200 cursor-pointer">
|
||||
<!-- avatar and name -->
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<EmailProviderIcon :logo="emailIcon[emailAccount.service]" />
|
||||
<div>
|
||||
<p class="text-sm font-semibold text-gray-700">
|
||||
{{ emailAccount.email_account_name }}
|
||||
</p>
|
||||
<div class="text-sm text-gray-500">{{ emailAccount.email_id }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Badge variant="subtle" :label="badgeTitleColor[0]" :theme="badgeTitleColor[1]" />
|
||||
</div>
|
||||
<!-- email id -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { emailIcon } from "./emailConfig";
|
||||
import EmailProviderIcon from "./EmailProviderIcon.vue";
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
emailAccount: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const badgeTitleColor = computed(() => {
|
||||
if (
|
||||
props.emailAccount.default_incoming &&
|
||||
props.emailAccount.default_outgoing
|
||||
) {
|
||||
const color =
|
||||
props.emailAccount.enable_incoming && props.emailAccount.enable_outgoing
|
||||
? "blue"
|
||||
: "gray";
|
||||
return ["Default Sending and Inbox", color];
|
||||
} else if (props.emailAccount.default_incoming) {
|
||||
const color = props.emailAccount.enable_incoming ? "blue" : "gray";
|
||||
return ["Default Inbox", color];
|
||||
} else if (props.emailAccount.default_outgoing) {
|
||||
const color = props.emailAccount.enable_outgoing ? "blue" : "gray";
|
||||
return ["Default Sending", color];
|
||||
} else {
|
||||
const color = props.emailAccount.enable_incoming ? "blue" : "gray";
|
||||
return ["Inbox", color];
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
50
frontend/src/components/Settings/EmailAccountList.vue
Normal file
@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- header -->
|
||||
<div class="flex items-center justify-between">
|
||||
<h1 class="text-xl font-semibold">Email Accounts</h1>
|
||||
<Button label="Add Account" theme="gray" variant="solid" @click="emit('update:step', 'email-add')" class="mr-8">
|
||||
<template #prefix>
|
||||
<LucidePlus class="w-4 h-4" />
|
||||
</template>
|
||||
</Button>
|
||||
</div>
|
||||
<!-- list accounts -->
|
||||
<div v-if="!emailAccounts.loading && Boolean(emailAccounts.data?.length)" class="mt-4">
|
||||
<div v-for="emailAccount in emailAccounts.data" :key="emailAccount.name">
|
||||
<EmailAccountCard :emailAccount="emailAccount" @click="emit('update:step', 'email-edit', emailAccount)" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- fallback if no email accounts -->
|
||||
<div v-else class="flex items-center justify-center h-64 text-gray-500">
|
||||
Please add an email account to continue.
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { createListResource } from "frappe-ui";
|
||||
import EmailAccountCard from "./EmailAccountCard.vue";
|
||||
|
||||
const emit = defineEmits(["update:step"]);
|
||||
|
||||
const emailAccounts = createListResource({
|
||||
doctype: "Email Account",
|
||||
cache: true,
|
||||
fields: ["*"],
|
||||
filters: {
|
||||
email_id: ["Not Like", "%example%"],
|
||||
},
|
||||
pageLength: 10,
|
||||
auto: true,
|
||||
onSuccess: (accounts) => {
|
||||
// convert 0 to false to handle boolean fields
|
||||
accounts.forEach((account) => {
|
||||
account.enable_incoming = Boolean(account.enable_incoming);
|
||||
account.enable_outgoing = Boolean(account.enable_outgoing);
|
||||
account.default_incoming = Boolean(account.default_incoming);
|
||||
account.default_outgoing = Boolean(account.default_outgoing);
|
||||
});
|
||||
},
|
||||
});
|
||||
</script>
|
||||
121
frontend/src/components/Settings/EmailAdd.vue
Normal file
@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<div class="flex flex-col h-full gap-4">
|
||||
<!-- title and desc -->
|
||||
<div role="heading" aria-level="1" class="flex flex-col gap-1">
|
||||
<h5 class="text-xl font-semibold">Setup Email</h5>
|
||||
<p class="text-sm text-gray-600">
|
||||
Choose the email service provider you want to configure.
|
||||
</p>
|
||||
</div>
|
||||
<!-- email service provider selection -->
|
||||
<div class="flex flex-wrap items-center">
|
||||
<div v-for="s in services" :key="s.name" class="flex flex-col items-center gap-1 mt-4 w-[70px]"
|
||||
@click="handleSelect(s)">
|
||||
<EmailProviderIcon :service-name="s.name" :logo="s.icon" :selected="selectedService?.name === s?.name" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="selectedService" class="flex flex-col gap-4">
|
||||
<!-- email service provider info -->
|
||||
<div class="flex items-center gap-2 p-2 rounded-md ring-1 ring-gray-200">
|
||||
<CircleAlert class="w-5 h-6 text-blue-500 w-min-5 w-max-5 min-h-5 max-w-5" />
|
||||
<div class="text-xs text-gray-700 text-wrap">
|
||||
{{ selectedService.info }}
|
||||
<a :href="selectedService.link" target="_blank" class="text-blue-500 underline">here</a>.
|
||||
</div>
|
||||
</div>
|
||||
<!-- service provider fields -->
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
<div v-for="field in fields" :key="field.name" class="flex flex-col gap-1">
|
||||
<FormControl v-model="state[field.name]" :label="field.label" :name="field.name" :type="field.type"
|
||||
:placeholder="field.placeholder" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div v-for="field in incomingOutgoingFields" :key="field.name" class="flex flex-col gap-1">
|
||||
<FormControl v-model="state[field.name]" :label="field.label" :name="field.name" :type="field.type" />
|
||||
<p class="text-gray-500 text-p-sm">{{ field.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<ErrorMessage v-if="error" class="ml-1" :message="error" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- action button -->
|
||||
<div v-if="selectedService" class="flex justify-between mt-auto">
|
||||
<Button label="Back" theme="gray" variant="outline" :disabled="addEmailRes.loading"
|
||||
@click="emit('update:step', 'email-list')" />
|
||||
<Button label="Create" variant="solid" :loading="addEmailRes.loading" @click="createEmailAccount" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, reactive, ref } from "vue";
|
||||
import { createResource } from "frappe-ui";
|
||||
import CircleAlert from "~icons/lucide/circle-alert";
|
||||
import { createToast } from "@/utils";
|
||||
import {
|
||||
customProviderFields,
|
||||
popularProviderFields,
|
||||
services,
|
||||
validateInputs,
|
||||
incomingOutgoingFields,
|
||||
} from "./emailConfig";
|
||||
import EmailProviderIcon from "./EmailProviderIcon.vue";
|
||||
|
||||
const emit = defineEmits();
|
||||
|
||||
const state = reactive({
|
||||
service: "",
|
||||
email_account_name: "",
|
||||
email_id: "",
|
||||
password: "",
|
||||
api_key: "",
|
||||
api_secret: "",
|
||||
frappe_mail_site: "",
|
||||
enable_incoming: false,
|
||||
enable_outgoing: false,
|
||||
default_incoming: false,
|
||||
default_outgoing: false,
|
||||
});
|
||||
|
||||
const selectedService = ref(null);
|
||||
const fields = computed(() =>
|
||||
selectedService.value.custom ? customProviderFields : popularProviderFields
|
||||
);
|
||||
|
||||
function handleSelect(service) {
|
||||
selectedService.value = service;
|
||||
state.service = service.name;
|
||||
}
|
||||
|
||||
const addEmailRes = createResource({
|
||||
url: "crm.api.settings.create_email_account",
|
||||
makeParams: (val) => {
|
||||
return {
|
||||
...val,
|
||||
};
|
||||
},
|
||||
onSuccess: () => {
|
||||
createToast({
|
||||
title: "Email account created successfully",
|
||||
icon: "check",
|
||||
iconClasses: "text-green-600",
|
||||
});
|
||||
emit("update:step", "email-list");
|
||||
},
|
||||
onError: () => {
|
||||
error.value = "Failed to create email account, Invalid credentials";
|
||||
},
|
||||
});
|
||||
|
||||
const error = ref();
|
||||
function createEmailAccount() {
|
||||
error.value = validateInputs(state, selectedService.value.custom);
|
||||
if (error.value) return;
|
||||
|
||||
addEmailRes.submit({ data: state });
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
27
frontend/src/components/Settings/EmailConfig.vue
Normal file
@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<div class="flex-1 p-8">
|
||||
<div v-if="step === 'email-add'" class="h-full">
|
||||
<EmailAdd @update:step="updateStep" />
|
||||
</div>
|
||||
<div v-else-if="step === 'email-list'" class="h-full">
|
||||
<EmailAccountList @update:step="updateStep" />
|
||||
</div>
|
||||
<div v-else-if="step === 'email-edit'" class="h-full">
|
||||
<EmailEdit :account-data="accountData" @update:step="updateStep" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import EmailAdd from "./EmailAdd.vue";
|
||||
import EmailAccountList from "./EmailAccountList.vue";
|
||||
import EmailEdit from "./EmailEdit.vue";
|
||||
|
||||
const step = ref("email-list");
|
||||
const accountData = ref(null);
|
||||
function updateStep(newStep, data) {
|
||||
step.value = newStep;
|
||||
accountData.value = data;
|
||||
}
|
||||
</script>
|
||||
186
frontend/src/components/Settings/EmailEdit.vue
Normal file
@ -0,0 +1,186 @@
|
||||
<template>
|
||||
<div class="flex flex-col h-full gap-4">
|
||||
<!-- title and desc -->
|
||||
<div role="heading" aria-level="1" class="flex justify-between gap-1">
|
||||
<h5 class="text-lg font-semibold">Edit Email</h5>
|
||||
</div>
|
||||
<div class="w-fit">
|
||||
<EmailProviderIcon :logo="emailIcon[accountData.service]" :service-name="accountData.service" />
|
||||
</div>
|
||||
<!-- banner for setting up email account -->
|
||||
<div class="flex items-center gap-2 p-2 rounded-md ring-1 ring-gray-200">
|
||||
<CircleAlert class="w-5 h-6 text-blue-500 w-min-5 w-max-5 min-h-5 max-w-5" />
|
||||
<div class="text-xs text-gray-700 text-wrap">
|
||||
{{ info.description }}
|
||||
<a :href="info.link" target="_blank" class="text-blue-500 underline">here</a>
|
||||
.
|
||||
</div>
|
||||
</div>
|
||||
<!-- fields -->
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
<div v-for="field in fields" :key="field.name" class="flex flex-col gap-1">
|
||||
<FormControl v-model="state[field.name]" :label="field.label" :name="field.name" :type="field.type"
|
||||
:placeholder="field.placeholder" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div v-for="field in incomingOutgoingFields" :key="field.name" class="flex flex-col gap-1">
|
||||
<FormControl v-model="state[field.name]" :label="field.label" :name="field.name" :type="field.type" />
|
||||
<p class="text-gray-500 text-p-sm">{{ field.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<ErrorMessage v-if="error" class="ml-1" :message="error" />
|
||||
</div>
|
||||
<!-- action buttons -->
|
||||
<div class="flex justify-between mt-auto">
|
||||
<Button label="Back" theme="gray" variant="outline" :disabled="loading"
|
||||
@click="emit('update:step', 'email-list')" />
|
||||
<Button label="Update Account" variant="solid" @click="updateAccount" :loading="loading" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, reactive, ref } from "vue";
|
||||
import { call } from "frappe-ui";
|
||||
import EmailProviderIcon from "./EmailProviderIcon.vue";
|
||||
import {
|
||||
emailIcon,
|
||||
services,
|
||||
popularProviderFields,
|
||||
customProviderFields,
|
||||
validateInputs,
|
||||
incomingOutgoingFields,
|
||||
} from "./emailConfig";
|
||||
import { createToast } from "@/utils";
|
||||
import CircleAlert from "~icons/lucide/circle-alert";
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
accountData: null,
|
||||
})
|
||||
|
||||
const emit = defineEmits();
|
||||
|
||||
const state = reactive({
|
||||
email_account_name: props.accountData.email_account_name || "",
|
||||
service: props.accountData.service || "",
|
||||
email_id: props.accountData.email_id || "",
|
||||
api_key: props.accountData?.api_key || null,
|
||||
api_secret: props.accountData?.api_secret || null,
|
||||
password: props.accountData?.password || null,
|
||||
frappe_mail_site: props.accountData?.frappe_mail_site || "",
|
||||
enable_incoming: props.accountData.enable_incoming || false,
|
||||
enable_outgoing: props.accountData.enable_outgoing || false,
|
||||
default_outgoing: props.accountData.default_outgoing || false,
|
||||
default_incoming: props.accountData.default_incoming || false,
|
||||
});
|
||||
|
||||
const info = {
|
||||
description: "To know more about setting up email accounts, click",
|
||||
link: "https://docs.erpnext.com/docs/user/manual/en/email-account",
|
||||
};
|
||||
|
||||
const isCustomService = computed(() => {
|
||||
return services.find((s) => s.name === props.accountData.service).custom;
|
||||
});
|
||||
|
||||
const fields = computed(() => {
|
||||
if (isCustomService.value) {
|
||||
return customProviderFields;
|
||||
}
|
||||
return popularProviderFields;
|
||||
});
|
||||
|
||||
const error = ref();
|
||||
const loading = ref(false);
|
||||
async function updateAccount() {
|
||||
error.value = validateInputs(state, isCustomService.value);
|
||||
if (error.value) return;
|
||||
const old = { ...props.accountData };
|
||||
const updatedEmailAccount = { ...state };
|
||||
|
||||
const nameChanged =
|
||||
old.email_account_name !== updatedEmailAccount.email_account_name;
|
||||
delete old.email_account_name;
|
||||
delete updatedEmailAccount.email_account_name;
|
||||
|
||||
const otherFieldsChanged = isDirty.value;
|
||||
const values = updatedEmailAccount;
|
||||
|
||||
if (!nameChanged && !otherFieldsChanged) {
|
||||
createToast({
|
||||
title: "No changes made",
|
||||
icon: "info",
|
||||
iconClasses: "text-blue-600",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (nameChanged) {
|
||||
try {
|
||||
loading.value = true;
|
||||
await callRenameDoc();
|
||||
succesHandler();
|
||||
} catch (err) {
|
||||
errorHandler();
|
||||
}
|
||||
}
|
||||
if (otherFieldsChanged) {
|
||||
try {
|
||||
loading.value = true;
|
||||
await callSetValue(values);
|
||||
succesHandler();
|
||||
} catch (err) {
|
||||
errorHandler();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const isDirty = computed(() => {
|
||||
return (
|
||||
state.email_id !== props.accountData.email_id ||
|
||||
state.api_key !== props.accountData.api_key ||
|
||||
state.api_secret !== props.accountData.api_secret ||
|
||||
state.password !== props.accountData.password ||
|
||||
state.enable_incoming !== props.accountData.enable_incoming ||
|
||||
state.enable_outgoing !== props.accountData.enable_outgoing ||
|
||||
state.default_outgoing !== props.accountData.default_outgoing ||
|
||||
state.default_incoming !== props.accountData.default_incoming ||
|
||||
state.frappe_mail_site !== props.accountData.frappe_mail_site
|
||||
);
|
||||
});
|
||||
|
||||
async function callRenameDoc() {
|
||||
const d = await call("frappe.client.rename_doc", {
|
||||
doctype: "Email Account",
|
||||
old_name: props.accountData.email_account_name,
|
||||
new_name: state.email_account_name,
|
||||
});
|
||||
return d;
|
||||
}
|
||||
|
||||
async function callSetValue(values) {
|
||||
const d = await call("frappe.client.set_value", {
|
||||
doctype: "Email Account",
|
||||
name: state.email_account_name,
|
||||
fieldname: values,
|
||||
});
|
||||
return d.name;
|
||||
}
|
||||
|
||||
function succesHandler() {
|
||||
emit("update:step", "email-list");
|
||||
createToast({
|
||||
title: "Email account updated successfully",
|
||||
icon: "check",
|
||||
iconClasses: "text-green-600",
|
||||
});
|
||||
}
|
||||
|
||||
function errorHandler() {
|
||||
loading.value = false;
|
||||
error.value = "Failed to update email account, Invalid credentials";
|
||||
}
|
||||
</script>
|
||||
29
frontend/src/components/Settings/EmailProviderIcon.vue
Normal file
@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<div class="flex items-center justify-center w-8 h-8 bg-gray-100 cursor-pointer rounded-xl hover:bg-gray-200"
|
||||
:class="{ 'ring-2 ring-blue-500': selected }">
|
||||
<img :src="logo" class="w-4 h-4" />
|
||||
</div>
|
||||
<p v-if="serviceName" class="text-xs text-center text-gray-700">
|
||||
{{ serviceName }}
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
defineProps({
|
||||
logo: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
serviceName: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
selected: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
@ -6,8 +6,8 @@
|
||||
>
|
||||
<template #body>
|
||||
<div class="flex h-[calc(100vh_-_8rem)]">
|
||||
<div class="flex w-52 shrink-0 flex-col bg-surface-gray-2 p-2">
|
||||
<h1 class="mb-3 px-2 pt-2 text-lg font-semibold text-ink-gray-9">
|
||||
<div class="flex flex-col p-2 w-52 shrink-0 bg-surface-gray-2">
|
||||
<h1 class="px-2 pt-2 mb-3 text-lg font-semibold text-ink-gray-9">
|
||||
{{ __('Settings') }}
|
||||
</h1>
|
||||
<div v-for="tab in tabs">
|
||||
@ -34,7 +34,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex relative flex-1 flex-col overflow-y-auto bg-surface-modal"
|
||||
class="relative flex flex-col flex-1 overflow-y-auto bg-surface-modal"
|
||||
>
|
||||
<Button
|
||||
class="absolute right-5 top-5"
|
||||
@ -53,12 +53,14 @@ import WhatsAppIcon from '@/components/Icons/WhatsAppIcon.vue'
|
||||
import ERPNextIcon from '@/components/Icons/ERPNextIcon.vue'
|
||||
import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
|
||||
import InviteIcon from '@/components/Icons/InviteIcon.vue'
|
||||
import Email2Icon from '@/components/Icons/Email2Icon.vue'
|
||||
import GeneralSettings from '@/components/Settings/GeneralSettings.vue'
|
||||
import InviteMemberPage from '@/components/Settings/InviteMemberPage.vue'
|
||||
import ProfileSettings from '@/components/Settings/ProfileSettings.vue'
|
||||
import WhatsAppSettings from '@/components/Settings/WhatsAppSettings.vue'
|
||||
import ERPNextSettings from '@/components/Settings/ERPNextSettings.vue'
|
||||
import TelephonySettings from '@/components/Settings/TelephonySettings.vue'
|
||||
import EmailConfig from '@/components/Settings/EmailConfig.vue'
|
||||
import SidebarLink from '@/components/SidebarLink.vue'
|
||||
import { usersStore } from '@/stores/users'
|
||||
import {
|
||||
@ -101,6 +103,12 @@ const tabs = computed(() => {
|
||||
component: markRaw(InviteMemberPage),
|
||||
condition: () => isManager(),
|
||||
},
|
||||
{
|
||||
label: __('Email Accounts'),
|
||||
icon: Email2Icon,
|
||||
component: markRaw(EmailConfig),
|
||||
condition: () => isManager(),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
179
frontend/src/components/Settings/emailConfig.js
Normal file
@ -0,0 +1,179 @@
|
||||
import { validateEmail } from '../../utils'
|
||||
|
||||
import LogoGmail from '@/images/gmail.png'
|
||||
import LogoOutlook from '@/images/outlook.png'
|
||||
import LogoSendgrid from '@/images/sendgrid.png'
|
||||
import LogoSparkpost from '@/images/sparkpost.webp'
|
||||
import LogoYahoo from '@/images/yahoo.png'
|
||||
import LogoYandex from '@/images/yandex.png'
|
||||
import LogoFrappeMail from '@/images/frappe-mail.svg'
|
||||
|
||||
const fixedFields = [
|
||||
{
|
||||
label: 'Account Name',
|
||||
name: 'email_account_name',
|
||||
type: 'text',
|
||||
placeholder: 'Support / Sales',
|
||||
},
|
||||
{
|
||||
label: 'Email ID',
|
||||
name: 'email_id',
|
||||
type: 'email',
|
||||
placeholder: 'johndoe@example.com',
|
||||
},
|
||||
]
|
||||
|
||||
export const incomingOutgoingFields = [
|
||||
{
|
||||
label: 'Enable Incoming',
|
||||
name: 'enable_incoming',
|
||||
type: 'checkbox',
|
||||
description:
|
||||
'If enabled, records can be created from the incoming emails on this account.',
|
||||
},
|
||||
{
|
||||
label: 'Enable Outgoing',
|
||||
name: 'enable_outgoing',
|
||||
type: 'checkbox',
|
||||
description: 'If enabled, outgoing emails can be sent from this account.',
|
||||
},
|
||||
{
|
||||
label: 'Default Incoming',
|
||||
name: 'default_incoming',
|
||||
type: 'checkbox',
|
||||
description:
|
||||
'If enabled, all replies to your company (eg: replies@yourcomany.com) will come to this account. Note: Only one account can be default incoming.',
|
||||
},
|
||||
{
|
||||
label: 'Default Outgoing',
|
||||
name: 'default_outgoing',
|
||||
type: 'checkbox',
|
||||
description:
|
||||
'If enabled, all outgoing emails will be sent from this account. Note: Only one account can be default outgoing.',
|
||||
},
|
||||
]
|
||||
|
||||
export const popularProviderFields = [
|
||||
...fixedFields,
|
||||
{
|
||||
label: 'Password',
|
||||
name: 'password',
|
||||
type: 'password',
|
||||
placeholder: '********',
|
||||
},
|
||||
]
|
||||
|
||||
export const customProviderFields = [
|
||||
...fixedFields,
|
||||
{
|
||||
label: 'Frappe Mail Site',
|
||||
name: 'frappe_mail_site',
|
||||
type: 'text',
|
||||
placeholder: 'https://frappemail.com',
|
||||
},
|
||||
{
|
||||
label: 'API Key',
|
||||
name: 'api_key',
|
||||
type: 'text',
|
||||
placeholder: '********',
|
||||
},
|
||||
{
|
||||
label: 'API Secret',
|
||||
name: 'api_secret',
|
||||
type: 'password',
|
||||
placeholder: '********',
|
||||
},
|
||||
]
|
||||
|
||||
export const services = [
|
||||
{
|
||||
name: 'GMail',
|
||||
icon: LogoGmail,
|
||||
info: `Setting up GMail requires you to enable two factor authentication
|
||||
and app specific passwords. Read more`,
|
||||
link: 'https://support.google.com/accounts/answer/185833',
|
||||
custom: false,
|
||||
},
|
||||
{
|
||||
name: 'Outlook',
|
||||
icon: LogoOutlook,
|
||||
info: `Setting up Outlook requires you to enable two factor authentication
|
||||
and app specific passwords. Read more`,
|
||||
link: 'https://support.microsoft.com/en-us/account-billing/how-to-get-and-use-app-passwords-5896ed9b-4263-e681-128a-a6f2979a7944',
|
||||
custom: false,
|
||||
},
|
||||
{
|
||||
name: 'Sendgrid',
|
||||
icon: LogoSendgrid,
|
||||
info: `Setting up Sendgrid requires you to enable two factor authentication
|
||||
and app specific passwords. Read more `,
|
||||
link: 'https://sendgrid.com/docs/ui/account-and-settings/two-factor-authentication/',
|
||||
custom: false,
|
||||
},
|
||||
{
|
||||
name: 'SparkPost',
|
||||
icon: LogoSparkpost,
|
||||
info: `Setting up SparkPost requires you to enable two factor authentication
|
||||
and app specific passwords. Read more `,
|
||||
link: 'https://support.sparkpost.com/docs/my-account-and-profile/enabling-two-factor-authentication',
|
||||
custom: false,
|
||||
},
|
||||
{
|
||||
name: 'Yahoo',
|
||||
icon: LogoYahoo,
|
||||
info: `Setting up Yahoo requires you to enable two factor authentication
|
||||
and app specific passwords. Read more `,
|
||||
link: 'https://help.yahoo.com/kb/SLN15241.html',
|
||||
custom: false,
|
||||
},
|
||||
{
|
||||
name: 'Yandex',
|
||||
icon: LogoYandex,
|
||||
info: `Setting up Yandex requires you to enable two factor authentication
|
||||
and app specific passwords. Read more `,
|
||||
link: 'https://yandex.com/support/id/authorization/app-passwords.html',
|
||||
custom: false,
|
||||
},
|
||||
{
|
||||
name: 'Frappe Mail',
|
||||
icon: LogoFrappeMail,
|
||||
info: `Setting up Frappe Mail requires you to have an API key and API Secret of your email account. Read more `,
|
||||
link: 'https://github.com/frappe/mail',
|
||||
custom: true,
|
||||
},
|
||||
]
|
||||
|
||||
export const emailIcon = {
|
||||
GMail: LogoGmail,
|
||||
Outlook: LogoOutlook,
|
||||
Sendgrid: LogoSendgrid,
|
||||
SparkPost: LogoSparkpost,
|
||||
Yahoo: LogoYahoo,
|
||||
Yandex: LogoYandex,
|
||||
'Frappe Mail': LogoFrappeMail,
|
||||
}
|
||||
|
||||
export function validateInputs(state, isCustom) {
|
||||
if (!state.email_account_name) {
|
||||
return 'Account name is required'
|
||||
}
|
||||
if (!state.email_id) {
|
||||
return 'Email ID is required'
|
||||
}
|
||||
const validEmail = validateEmail(state.email_id)
|
||||
if (!validEmail) {
|
||||
return 'Invalid email ID'
|
||||
}
|
||||
if (!isCustom && !state.password) {
|
||||
return 'Password is required'
|
||||
}
|
||||
if (isCustom) {
|
||||
if (!state.api_key) {
|
||||
return 'API Key is required'
|
||||
}
|
||||
if (!state.api_secret) {
|
||||
return
|
||||
}
|
||||
}
|
||||
return ''
|
||||
}
|
||||
4
frontend/src/images/frappe-mail.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="44" height="44" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.5714 44L31.4286 44C38.3716 44 44 38.3716 44 31.4286L44 12.5714C44 5.62842 38.3716 0 31.4286 0L12.5714 0C5.62842 0 0 5.62842 0 12.5714L0 31.4286C0 38.3716 5.62842 44 12.5714 44Z" fill="#0466DC"/>
|
||||
<path d="M9.42859 12.5715V14.8972L12.5714 17.4587L18.5743 22.3458C19.5329 23.1315 20.7586 23.5715 22 23.5715C23.2414 23.5715 24.4672 23.1315 25.4257 22.3458L31.4286 17.443V28.2701H12.5714V21.5287L9.42859 18.9672V27.4844C9.42859 29.653 11.1886 31.413 13.3572 31.413H30.6429C32.8115 31.413 34.5715 29.653 34.5715 27.4844V12.5715H9.42859ZM23.4457 19.9101C22.6286 20.5701 21.3714 20.5701 20.57 19.9101L15.4157 15.7144H28.6L23.4457 19.9101Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 763 B |
BIN
frontend/src/images/gmail.png
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
BIN
frontend/src/images/outlook.png
Normal file
|
After Width: | Height: | Size: 215 KiB |
BIN
frontend/src/images/sendgrid.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
frontend/src/images/sparkpost.webp
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
frontend/src/images/yahoo.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
frontend/src/images/yandex.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
13
frontend/src/types.ts
Normal file
@ -0,0 +1,13 @@
|
||||
export interface EmailAccount {
|
||||
email_account_name: string
|
||||
email_id: string
|
||||
service: string
|
||||
api_key?: string
|
||||
api_secret?: string
|
||||
password?: string
|
||||
frappe_mail_site?: string
|
||||
enable_outgoing?: boolean
|
||||
enable_incoming?: boolean
|
||||
default_outgoing?: boolean
|
||||
default_incoming?: boolean
|
||||
}
|
||||
@ -2542,10 +2542,10 @@ fraction.js@^4.3.7:
|
||||
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7"
|
||||
integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==
|
||||
|
||||
frappe-ui@^0.1.121:
|
||||
version "0.1.121"
|
||||
resolved "https://registry.yarnpkg.com/frappe-ui/-/frappe-ui-0.1.121.tgz#a8d37f300228edfcbb6b4fffb343f0773dcfd933"
|
||||
integrity sha512-gvtKKZECPD2MU5X4MwPUKr2hSOs1+s1DA9laP3aPnmH0ukJRSFEhDOyjCMfH9k6ZdAe/vZCIbT4XucxLq/fOEA==
|
||||
frappe-ui@^0.1.123:
|
||||
version "0.1.123"
|
||||
resolved "https://registry.yarnpkg.com/frappe-ui/-/frappe-ui-0.1.123.tgz#501139a103a03e52648d9ee9ea85aa54bc8102e0"
|
||||
integrity sha512-WkTnKZ+n82d9xZ9g9ZQXVkFyKU2wlcfT6/9g8/2biJuXMwmo/80I29EKGb9nrM1Liuj0Wtyg9nsqvfvgktdHbw==
|
||||
dependencies:
|
||||
"@headlessui/vue" "^1.7.14"
|
||||
"@popperjs/core" "^2.11.2"
|
||||
|
||||