diff --git a/dashboard/src2/components/AppSidebar.vue b/dashboard/src2/components/AppSidebar.vue
index d40ac073e6..f6e819dc05 100644
--- a/dashboard/src2/components/AppSidebar.vue
+++ b/dashboard/src2/components/AppSidebar.vue
@@ -32,10 +32,10 @@
class="flex w-[204px] items-center rounded-md px-2 py-2 text-left"
:class="open ? 'bg-white shadow-sm' : 'hover:bg-gray-200'"
>
-
+
+ {{ onboarding.message }} +
+Frappe Cloud makes it easy to manage sites and apps like ERPNext in an easy to use dashboard with powerful features like automatic backups, custom domains, SSL certificates, custom apps, automatic updates and @@ -266,6 +269,7 @@ diff --git a/dashboard/src2/data/branding.js b/dashboard/src2/data/branding.js new file mode 100644 index 0000000000..4935908d7b --- /dev/null +++ b/dashboard/src2/data/branding.js @@ -0,0 +1,15 @@ +import { createResource } from 'frappe-ui'; + +export let BrandInfo = createResource({ + url: 'press.api.utils.get_brand_details', + cache: 'site.brand', + initialData: [] +}); + +export function fetchBrandInfo() { + BrandInfo.fetch(); +} + +export function getBrandInfo() { + return BrandInfo.data || []; +} diff --git a/dashboard/src2/main.js b/dashboard/src2/main.js index 405e798bda..397287e313 100644 --- a/dashboard/src2/main.js +++ b/dashboard/src2/main.js @@ -14,6 +14,7 @@ import * as Sentry from '@sentry/vue'; import { session } from './data/session.js'; import posthog from 'posthog-js'; import { toast } from 'vue-sonner'; +import { fetchBrandInfo } from './data/branding.js'; let request = options => { let _options = options || {}; @@ -50,6 +51,8 @@ getInitialData().then(() => { app.config.globalProperties.$socket = socket; window.$socket = socket; subscribeToJobUpdates(socket); + fetchBrandInfo(); + if (session.isLoggedIn) { fetchPlans(); session.roles.fetch(); diff --git a/dashboard/src2/pages/SetupAccount.vue b/dashboard/src2/pages/SetupAccount.vue index 8f6e50df40..da13c756d2 100644 --- a/dashboard/src2/pages/SetupAccount.vue +++ b/dashboard/src2/pages/SetupAccount.vue @@ -76,17 +76,14 @@ By clicking on {{ isInvitation ? 'Accept' : 'Create account' }}, you accept our - Terms of Service , - + Privacy Policy & - + Cookie Policy @@ -120,6 +117,7 @@ import LoginBox from '../components/auth/LoginBox.vue'; import Link from '@/components/Link.vue'; import Form from '@/components/Form.vue'; import ProductSignupPitch from '../components/ProductSignupPitch.vue'; +import { getBrandInfo } from '../data/branding'; export default { name: 'SetupAccount', @@ -221,6 +219,9 @@ export default { df.required = true; return df; }); + }, + policies() { + return getBrandInfo(); } } }; diff --git a/press/api/marketplace.py b/press/api/marketplace.py index ab5dd85fd7..8d4bff373d 100644 --- a/press/api/marketplace.py +++ b/press/api/marketplace.py @@ -25,7 +25,7 @@ get_plans_for_app, ) from press.utils import get_app_tag, get_current_team, get_last_doc, unique -from press.utils.billing import get_frappe_io_connection +from press.utils.billing import get_frappe_io_connection, disabled_frappeio_auth @frappe.whitelist() @@ -996,6 +996,9 @@ def get_discount_percent(plan, discount=0.0): "Bronze": 30.0, } + if disabled_frappeio_auth(): + return discount + if team.erpnext_partner and frappe.get_value( "Marketplace App Plan", plan, "partner_discount" ): diff --git a/press/api/partner.py b/press/api/partner.py index cdb8e1464c..733d2e2242 100644 --- a/press/api/partner.py +++ b/press/api/partner.py @@ -45,7 +45,20 @@ def update_partnership_date(team, partnership_date): @frappe.whitelist() def get_partner_details(partner_email): - from press.utils.billing import get_frappe_io_connection + from press.utils.billing import get_frappe_io_connection, disabled_frappeio_auth + + if disabled_frappeio_auth(): + return frappe._dict( + { + "email": "", + "partner_type": "", + "company_name": "", + "custom_ongoing_period_fc_invoice_contribution": "", + "custom_ongoing_period_enterprise_invoice_contribution": "", + "partner_name": "", + "custom_number_of_certified_members": "", + } + ) client = get_frappe_io_connection() data = client.get_doc( diff --git a/press/api/utils.py b/press/api/utils.py new file mode 100644 index 0000000000..f7502e12b9 --- /dev/null +++ b/press/api/utils.py @@ -0,0 +1,21 @@ +import frappe +from press.press.doctype.brand_settings.brand_settings import ( + get_brand_details as _get_brand_details, + get_brand_name as _get_brand_name, + get_onboarding_message, +) + + +@frappe.whitelist(allow_guest=True) +def get_brand_details(): + return _get_brand_details() + + +@frappe.whitelist(allow_guest=True) +def get_brand_name(): + return _get_brand_name() or "Frappe Cloud" + + +@frappe.whitelist(allow_guest=True) +def get_onboarding_details(): + return {"message": get_onboarding_message()} diff --git a/press/press/doctype/brand_settings/__init__.py b/press/press/doctype/brand_settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/press/press/doctype/brand_settings/brand_settings.js b/press/press/doctype/brand_settings/brand_settings.js new file mode 100644 index 0000000000..05f6f89a76 --- /dev/null +++ b/press/press/doctype/brand_settings/brand_settings.js @@ -0,0 +1,8 @@ +// Copyright (c) 2024, Frappe and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Brand Settings", { +// refresh(frm) { + +// }, +// }); diff --git a/press/press/doctype/brand_settings/brand_settings.json b/press/press/doctype/brand_settings/brand_settings.json new file mode 100644 index 0000000000..a7b6808bc2 --- /dev/null +++ b/press/press/doctype/brand_settings/brand_settings.json @@ -0,0 +1,107 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2024-07-22 14:06:23.709878", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "brand_section", + "brand_logo", + "footer_logo", + "column_break_oezy", + "brand_name", + "terms_section", + "terms_of_service", + "cookie_policy", + "column_break_gcuq", + "privacy_policy", + "onboarding_tab", + "onboarding_message" + ], + "fields": [ + { + "fieldname": "brand_section", + "fieldtype": "Section Break", + "label": "Brand" + }, + { + "fieldname": "column_break_oezy", + "fieldtype": "Column Break" + }, + { + "fieldname": "brand_name", + "fieldtype": "Data", + "label": "Brand Name" + }, + { + "fieldname": "terms_section", + "fieldtype": "Section Break", + "label": "Terms" + }, + { + "description": "Define full URL like https://frappecloud.com/terms", + "fieldname": "terms_of_service", + "fieldtype": "Data", + "label": "Terms of Service" + }, + { + "description": "Define full URL like https://frappecloud.com/cookie-policy", + "fieldname": "cookie_policy", + "fieldtype": "Data", + "label": "Cookie Policy" + }, + { + "fieldname": "column_break_gcuq", + "fieldtype": "Column Break" + }, + { + "description": "Define full URL like https://frappecloud.com/privacy", + "fieldname": "privacy_policy", + "fieldtype": "Data", + "label": "Privacy Policy" + }, + { + "fieldname": "brand_logo", + "fieldtype": "Attach Image", + "label": "Brand Logo" + }, + { + "fieldname": "footer_logo", + "fieldtype": "Attach Image", + "label": "Footer Logo" + }, + { + "fieldname": "onboarding_tab", + "fieldtype": "Tab Break", + "label": "Onboarding" + }, + { + "fieldname": "onboarding_message", + "fieldtype": "Text", + "label": "Onboarding Message" + } + ], + "index_web_pages_for_search": 1, + "issingle": 1, + "links": [], + "modified": "2024-07-22 16:22:33.080437", + "modified_by": "Administrator", + "module": "Press", + "name": "Brand Settings", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/press/press/doctype/brand_settings/brand_settings.py b/press/press/doctype/brand_settings/brand_settings.py new file mode 100644 index 0000000000..dbf9596120 --- /dev/null +++ b/press/press/doctype/brand_settings/brand_settings.py @@ -0,0 +1,51 @@ +# Copyright (c) 2024, Frappe and contributors +# For license information, please see license.txt + +import frappe +from frappe.model.document import Document + + +class BrandSettings(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + brand_logo: DF.AttachImage | None + brand_name: DF.Data | None + cookie_policy: DF.Data | None + footer_logo: DF.AttachImage | None + onboarding_message: DF.Text | None + privacy_policy: DF.Data | None + terms_of_service: DF.Data | None + # end: auto-generated types + + pass + + +def get_brand_details(): + brand_details = frappe.get_cached_doc("Brand Settings") + + return { + "brand_logo": brand_details.get("brand_logo"), + "brand_name": brand_details.get("brand_name") or "Frappe Cloud", + "footer_logo": brand_details.get("footer_logo"), + "terms_of_service": brand_details.get("terms_of_service") + or "https://frappecloud.com/terms", + "cookie_policy": brand_details.get("cookie_policy") + or "https://frappecloud.com/cookie-policy", + "privacy_policy": brand_details.get("privacy_policy") + or "https://frappecloud.com/privacy", + } + + +def get_brand_name(): + brand_details = frappe.get_cached_doc("Brand Settings") + return brand_details.get("brand_name") or "Frappe Cloud" + + +def get_onboarding_message(): + return frappe.db.get_single_value("Brand Settings", "onboarding_message") diff --git a/press/press/doctype/brand_settings/test_brand_settings.py b/press/press/doctype/brand_settings/test_brand_settings.py new file mode 100644 index 0000000000..4715cf3e93 --- /dev/null +++ b/press/press/doctype/brand_settings/test_brand_settings.py @@ -0,0 +1,9 @@ +# Copyright (c) 2024, Frappe and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestBrandSettings(FrappeTestCase): + pass diff --git a/press/press/doctype/invoice/invoice.py b/press/press/doctype/invoice/invoice.py index c1e077dfa7..65cd9d11d4 100644 --- a/press/press/doctype/invoice/invoice.py +++ b/press/press/doctype/invoice/invoice.py @@ -12,7 +12,11 @@ from press.api.client import dashboard_whitelist from press.overrides import get_permission_query_conditions_for_doctype from press.utils import log_error -from press.utils.billing import convert_stripe_money, get_frappe_io_connection +from press.utils.billing import ( + convert_stripe_money, + get_frappe_io_connection, + disabled_frappeio_auth +) class Invoice(Document): @@ -762,6 +766,9 @@ def create_invoice_on_frappeio(self): return try: + if disabled_frappeio_auth(): + return + team = frappe.get_doc("Team", self.team) address = ( frappe.get_doc("Address", team.billing_address) if team.billing_address else None @@ -815,6 +822,9 @@ def fetch_invoice_pdf(self): if self.frappe_invoice: from urllib.parse import urlencode + if disabled_frappeio_auth(): + return + client = self.get_frappeio_connection() print_format = frappe.db.get_single_value("Press Settings", "print_format") params = urlencode( diff --git a/press/press/doctype/press_settings/press_settings.json b/press/press/doctype/press_settings/press_settings.json index 7fd1812bc6..81433f3864 100644 --- a/press/press/doctype/press_settings/press_settings.json +++ b/press/press/doctype/press_settings/press_settings.json @@ -43,6 +43,7 @@ "erpnext_api_secret", "column_break_38", "frappeio_authentication_section", + "disable_frappe_auth", "frappe_url", "frappeio_api_key", "column_break_39", @@ -1204,6 +1205,12 @@ "fieldname": "branch", "fieldtype": "Data", "label": "Branch" + }, + { + "default": "0", + "fieldname": "disable_frappe_auth", + "fieldtype": "Check", + "label": "Disable Frappe Auth" } ], "issingle": 1, diff --git a/press/press/doctype/subscription/subscription.json b/press/press/doctype/subscription/subscription.json index d486d67a72..59dc34198b 100644 --- a/press/press/doctype/subscription/subscription.json +++ b/press/press/doctype/subscription/subscription.json @@ -15,7 +15,8 @@ "interval", "site", "marketplace_app_subscription", - "additional_storage" + "additional_storage", + "secret_key" ], "fields": [ { @@ -89,6 +90,11 @@ "fieldname": "additional_storage", "fieldtype": "Data", "label": "Additional Storage" + }, + { + "fieldname": "secret_key", + "fieldtype": "Data", + "label": "Secret Key" } ], "index_web_pages_for_search": 1, @@ -98,7 +104,7 @@ "link_fieldname": "subscription" } ], - "modified": "2024-06-26 18:32:18.087251", + "modified": "2024-08-30 12:55:23.639210", "modified_by": "Administrator", "module": "Press", "name": "Subscription", diff --git a/press/press/doctype/subscription/subscription.py b/press/press/doctype/subscription/subscription.py index bec0a56a3f..af41eb9071 100644 --- a/press/press/doctype/subscription/subscription.py +++ b/press/press/doctype/subscription/subscription.py @@ -31,6 +31,7 @@ class Subscription(Document): marketplace_app_subscription: DF.Link | None plan: DF.DynamicLink plan_type: DF.Link + secret_key: DF.Data | None site: DF.Link | None team: DF.Link # end: auto-generated types @@ -82,6 +83,14 @@ def get_list_query(query, **list_args): return query.run(as_dict=True) + def before_validate(self): + if not self.secret_key and self.document_type == "Marketplace App": + self.secret_key = frappe.utils.generate_hash(length=40) + if not frappe.db.exists("Site Config Key", {"key": f"sk_{self.document_name}"}): + frappe.get_doc( + doctype="Site Config Key", internal=True, key=f"sk_{self.document_name}" + ).insert(ignore_permissions=True) + def validate(self): self.validate_duplicate() diff --git a/press/press/doctype/team/team.py b/press/press/doctype/team/team.py index b23535348b..bc13aa4a3b 100644 --- a/press/press/doctype/team/team.py +++ b/press/press/doctype/team/team.py @@ -22,6 +22,7 @@ get_frappe_io_connection, get_stripe, process_micro_debit_test_charge, + disabled_frappeio_auth, ) from press.utils.telemetry import capture @@ -476,7 +477,7 @@ def create_partner_referral_code(self): self.save(ignore_permissions=True) def get_partnership_start_date(self): - if frappe.flags.in_test: + if frappe.flags.in_test or disabled_frappeio_auth(): return frappe.utils.getdate() client = get_frappe_io_connection() @@ -640,7 +641,7 @@ def update_billing_details_on_draft_invoices(self): frappe.get_doc("Invoice", draft_invoice).save() def update_billing_details_on_frappeio(self): - if frappe.flags.in_install: + if frappe.flags.in_install or disabled_frappeio_auth(): return try: @@ -947,6 +948,9 @@ def billing_details(self, timezone=None): def get_partner_level(self): # fetch partner level from frappe.io + if disabled_frappeio_auth(): + return "", "" + client = get_frappe_io_connection() response = client.session.get( f"{client.url}/api/method/get_partner_level", diff --git a/press/press/doctype/team_deletion_request/team_deletion_request.py b/press/press/doctype/team_deletion_request/team_deletion_request.py index a3bb826f50..39042ac295 100644 --- a/press/press/doctype/team_deletion_request/team_deletion_request.py +++ b/press/press/doctype/team_deletion_request/team_deletion_request.py @@ -169,7 +169,10 @@ def delete_stripe_customer(self): @handle_exc def delete_data_on_frappeio(self): """Anonymize data on frappe.io""" - from press.utils.billing import get_frappe_io_connection + from press.utils.billing import get_frappe_io_connection, disabled_frappeio_auth + + if disabled_frappeio_auth(): + return client = get_frappe_io_connection() response = client.session.delete( diff --git a/press/utils/billing.py b/press/utils/billing.py index e0d451dfc8..42288b6fb9 100644 --- a/press/utils/billing.py +++ b/press/utils/billing.py @@ -77,6 +77,10 @@ def get_erpnext_com_connection(): ) +def disabled_frappeio_auth(): + return frappe.db.get_single_value("Press Settings", "disable_frappe_auth", cache=True) + + def get_frappe_io_connection(): if hasattr(frappe.local, "press_frappeio_conn"): return frappe.local.press_frappeio_conn