refactor: sign up flow changes (#31205)

* fix: logout to site login page if the site is on Frappe Cloud

* fix: check if the site user is logged in before rendering trial banner

* fix: show dropdown even if the site is not on trial plan

* refactor: don't expose communication secret in boot

* feat: show install app button for fc sites

* fix: remove auth from desk

we can simplify it and let user do auth in fc

* fix: install app button condition

* refactor: use `is_fc_site` method

* fix: return boolean value for `is_fc_site` function

* fix: add install app button in /apps page

* fix: don't generate otp for login to fc

* fix: remove install app option from desk

* fix: design changes for trial banner

* fix: add more details to the `current_site_info` endpoint

also don't render trial banner if trial end date is passed

* fix: don't route user to welcome page

always put them on the site's dashboard

* fix: override base_url when needed

also remove misleading class

* fix: show banner to normal user to contact system admin for plan upgrade

* refactor: redirect from /login instead of every /logout code

* fix: rename login to fc to manage billing

also move it above the divider

* refactor: separate out site-login url from login.py
This commit is contained in:
Suhail 2025-03-03 22:06:15 +05:30 committed by GitHub
parent 2022ddf407
commit 5886234b53
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 71 additions and 141 deletions

View file

@ -15,6 +15,7 @@ from frappe.desk.doctype.form_tour.form_tour import get_onboarding_ui_tours
from frappe.desk.doctype.route_history.route_history import frequently_visited_links
from frappe.desk.form.load import get_meta_bundle
from frappe.email.inbox import get_email_accounts
from frappe.integrations.frappe_providers.frappecloud_billing import is_fc_site
from frappe.model.base_document import get_controller
from frappe.permissions import has_permission
from frappe.query_builder import DocType
@ -111,6 +112,7 @@ def get_bootinfo():
bootinfo.translated_doctypes = get_translated_doctypes()
bootinfo.subscription_conf = add_subscription_conf()
bootinfo.marketplace_apps = get_marketplace_apps()
bootinfo.is_fc_site = is_fc_site()
bootinfo.changelog_feed = get_changelog_feed_items()
bootinfo.enable_address_autocompletion = frappe.db.get_single_value(
"Geolocation Settings", "enable_address_autocompletion"
@ -139,7 +141,7 @@ def load_conf_settings(bootinfo):
from frappe.core.api.file import get_max_file_size
bootinfo.max_file_size = get_max_file_size()
for key in ("developer_mode", "socketio_port", "file_watcher_port", "fc_communication_secret"):
for key in ("developer_mode", "socketio_port", "file_watcher_port"):
if key in frappe.conf:
bootinfo[key] = frappe.conf.get(key)

View file

@ -6,11 +6,15 @@ from frappe import _
def get_base_url():
url = "https://frappecloud.com"
if frappe.conf.developer_mode and frappe.conf.get("saas_billing_base_url"):
url = frappe.conf.get("saas_billing_base_url")
if frappe.conf.developer_mode and frappe.conf.get("fc_base_url"):
url = frappe.conf.get("fc_base_url")
return url
def get_site_login_url():
return f"{get_base_url()}/dashboard/site-login"
def get_site_name():
site_name = frappe.local.site
if frappe.conf.developer_mode and frappe.conf.get("saas_billing_site_name"):
@ -29,15 +33,28 @@ def get_headers():
return {
"X-Site-Token": frappe.conf.get("fc_communication_secret"),
"X-Site-User": frappe.session.user,
"X-Site": get_site_name(),
}
@frappe.whitelist()
def current_site_info():
from frappe.utils import cint
request = requests.post(f"{get_base_url()}/api/method/press.saas.api.site.info", headers=get_headers())
if request.status_code == 200:
return request.json().get("message")
res = request.json().get("message")
if not res:
return None
return {
**res,
"site_name": get_site_name(),
"base_url": get_base_url(),
"setup_complete": cint(frappe.get_system_settings("setup_complete")),
}
else:
frappe.throw(_("Failed to get site info"))
@ -58,19 +75,19 @@ def api(method, data=None):
@frappe.whitelist()
def is_fc_site():
def is_fc_site() -> bool:
is_system_manager = frappe.get_roles(frappe.session.user).count("System Manager")
setup_completed = frappe.get_system_settings("setup_complete")
return is_system_manager and setup_completed and frappe.conf.get("fc_communication_secret")
return bool(is_system_manager and setup_completed and frappe.conf.get("fc_communication_secret"))
# login to frappe cloud dashboard
@frappe.whitelist()
def send_verification_code(route: str):
def send_verification_code():
request = requests.post(
f"{get_base_url()}/api/method/press.api.developer.saas.send_verification_code",
headers=get_headers(),
json={"domain": get_site_name(), "route": route},
json={"domain": get_site_name()},
)
if request.status_code == 200:
return request.json().get("message")

View file

@ -1,8 +1,9 @@
const frappeCloudBaseEndpoint = "https://frappecloud.com";
let frappeCloudBaseEndpoint = "https://frappecloud.com";
let isFCUser = false;
$(document).ready(function () {
if (
frappe.boot.fc_communication_secret &&
frappe.boot.is_fc_site &&
frappe.boot.setup_complete === 1 &&
!frappe.is_mobile() &&
frappe.user.has_role("System Manager")
@ -10,147 +11,40 @@ $(document).ready(function () {
frappe.call({
method: "frappe.integrations.frappe_providers.frappecloud_billing.current_site_info",
callback: (r) => {
if (!r?.message) return;
const response = r.message;
if (response.trial_end_date) {
const trial_end_date = new Date(response.trial_end_date);
frappeCloudBaseEndpoint = response.base_url;
isFCUser = response.is_fc_user;
if (response.trial_end_date && trial_end_date > new Date()) {
$(".layout-main-section").before(
generateTrialSubscriptionBanner(response.trial_end_date)
);
addLoginToFCDropdownItem();
$(".login-to-fc").on("click", function () {
window.route = "dashboard";
initiateRequestForLoginToFrappeCloud();
});
$(".upgrade-plan-button").on("click", function () {
window.route = "site-dashboard";
initiateRequestForLoginToFrappeCloud();
});
}
addManageBillingDropdown();
$(".login-to-fc, .upgrade-plan-button").on("click", function () {
openFrappeCloudDashboard();
});
},
});
}
});
function initiateRequestForLoginToFrappeCloud() {
frappe.confirm(__("Are you sure you want to login to Frappe Cloud dashboard?"), () => {
requestLoginToFC();
});
}
function requestLoginToFC(freezing_msg = "Initiating login to Frappe Cloud...") {
frappe.call({
method: "frappe.integrations.frappe_providers.frappecloud_billing.send_verification_code",
args: {
route: window.route,
},
freeze: true,
freeze_message: __(freezing_msg),
callback: function (r) {
if (r.message.is_user_logged_in) {
window.open(`${frappeCloudBaseEndpoint}${r.message.redirect_to}`, "_blank");
return;
} else {
showFCLoginDialog(r.message.email);
setErrorMessage("");
}
},
error: function (r) {
frappe.throw(__("Failed to login to Frappe Cloud. Please try again"));
},
});
}
function setErrorMessage(message) {
$("#fc-login-error").text(message);
}
function showFCLoginDialog(email) {
if (!window.fc_login_dialog) {
var d = new frappe.ui.Dialog({
title: __("Login to Frappe Cloud"),
primary_action_label: __("Verify", null, "Submit verification code"),
primary_action: verifyCode,
});
$(d.body).html(
repl(
`<div>
<p>We have sent the verification code to your email id <strong>${email}</strong></p>
<div class="form-group mt-2">
<div class="clearfix">
<label class="control-label" style="padding-right: 0px;">Verification Code</label>
</div>
<div class="control-input-wrapper">
<div class="control-input"><input type="text" class="input-with-feedback form-control" id="fc-login-verification-code"></div>
</div>
</div>
<p class="text-danger" id="fc-login-error"></p>
</div>`,
frappe.app
)
);
d.add_custom_action("Didn't receive code? Resend", () => {
d.hide();
requestLoginToFC("Resending Verification Code...");
});
window.fc_login_dialog = d;
}
function verifyCode() {
let otp = $("#fc-login-verification-code").val();
if (!otp) {
return;
}
frappe.call({
method: "frappe.integrations.frappe_providers.frappecloud_billing.verify_verification_code",
args: {
verification_code: otp,
route: window.route,
},
freeze: true,
freeze_message: __("Verifying verification code..."),
callback: function (r) {
const message = r.message;
if (message.login_token) {
window.fc_login_dialog.hide();
window.open(
`${frappeCloudBaseEndpoint}/api/method/press.api.developer.saas.login_to_fc?token=${message.login_token}`,
"_blank"
);
frappe.msgprint({
title: __("Frappe Cloud Login Successful"),
indicator: "green",
message: `<p>${__(
"You will be redirected to Frappe Cloud soon."
)}</p><p>${__(
"If you haven't been redirected,"
)} <a href="${frappeCloudBaseEndpoint}/api/method/press.api.developer.saas.login_to_fc?token=${
message.login_token
}" target="_blank">${__("Click here to login")}</a></p>`,
});
} else {
setErrorMessage("Login failed. Please try again");
}
},
error: function (r) {
if (r.exc) {
setErrorMessage(JSON.parse(JSON.parse(r._server_messages)[0])["message"]);
}
},
});
}
window.fc_login_dialog.show();
function addManageBillingDropdown() {
$(".dropdown-navbar-user .dropdown-menu .dropdown-divider").before(
`<div class="dropdown-item login-to-fc" target="_blank">Manage Billing</div>`
);
}
function addLoginToFCDropdownItem() {
$(".dropdown-navbar-user .dropdown-menu .dropdown-item:last()").before(
`<div class="dropdown-item login-to-fc" target="_blank">Login to Frappe Cloud</div>`
);
function openFrappeCloudDashboard() {
window.open(`${frappeCloudBaseEndpoint}/dashboard/sites/${frappe.boot.sitename}`, "_blank");
}
function generateTrialSubscriptionBanner(trialEndDate) {
@ -169,14 +63,13 @@ function generateTrialSubscriptionBanner(trialEndDate) {
align-items: center;
background-color: var(--subtle-accent);
border-radius: var(--border-radius-md);
box-shadow: var(--shadow-sm);
}
.trial-banner > div {
display: flex;
gap: 8px;
}
.trial-banner .info-icon {
margin: 4px 0;
margin: auto 0;
}
.trial-banner > div > div {
display: flex;
@ -202,7 +95,7 @@ function generateTrialSubscriptionBanner(trialEndDate) {
border-color: var(--gray-400);
}
</style>
<div class="trial-banner px-3 py-2 m-2">
<div class="trial-banner px-3 py-2 m-2 mt-4">
<div>
<svg class="info-icon" width="18" height="18" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_3360_13841)">
@ -219,18 +112,26 @@ function generateTrialSubscriptionBanner(trialEndDate) {
Your trial ends in ${trial_end_string}.
</span>
<span class="description">
Please upgrade for uninterrupted services
${
isFCUser
? "Please upgrade for uninterrupted services"
: "Please contact your system administrator to upgrade your plan."
}
</span>
</div>
</div>
<button type="button"
${
isFCUser
? `<button type="button"
class="upgrade-plan-button px-2 py-1"
>
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.2641 1C5.5758 1 4.97583 1.46845 4.80889 2.1362L3.57555 7.06953C3.33887 8.01625 4.05491 8.93333 5.03077 8.93333H7.50682L6.72168 14.4293C6.68838 14.6624 6.82229 14.8872 7.04319 14.9689C7.26408 15.0507 7.51204 14.9671 7.63849 14.7684L13.2161 6.00354C13.6398 5.33782 13.1616 4.46667 12.3725 4.46667H9.59038L10.3017 1.62127C10.3391 1.4719 10.3055 1.31365 10.2108 1.19229C10.116 1.07094 9.97063 1 9.81666 1H6.2641ZM5.77903 2.37873C5.83468 2.15615 6.03467 2 6.2641 2H9.17627L8.46492 4.8454C8.42758 4.99477 8.46114 5.15302 8.55589 5.27437C8.65064 5.39573 8.79602 5.46667 8.94999 5.46667H12.3725L8.0395 12.2757L8.5783 8.50404C8.5988 8.36056 8.55602 8.21523 8.46105 8.10573C8.36608 7.99623 8.22827 7.93333 8.08332 7.93333H5.03077C4.70548 7.93333 4.4668 7.62764 4.5457 7.31207L5.77903 2.37873Z" fill="currentColor"/>
</svg>
${__("Upgrade plan")}
</button>
</button>`
: ""
}
</div>
`);
}

View file

@ -163,6 +163,7 @@ def get_home_page_via_hooks():
def get_boot_data():
from frappe.integrations.frappe_providers.frappecloud_billing import is_fc_site
from frappe.locale import get_date_format, get_first_day_of_the_week, get_number_format, get_time_format
return {
@ -187,6 +188,7 @@ def get_boot_data():
},
"assets_json": get_assets_json(),
"sitename": frappe.local.site,
"is_fc_site": is_fc_site(),
}

View file

@ -23,6 +23,8 @@ no_cache = True
def get_context(context):
from frappe.integrations.frappe_providers.frappecloud_billing import is_fc_site
redirect_to = frappe.local.request.args.get("redirect-to")
redirect_to = sanitize_redirect(redirect_to)
@ -37,6 +39,12 @@ def get_context(context):
frappe.local.flags.redirect_location = redirect_to
raise frappe.Redirect
if is_fc_site():
from frappe.integrations.frappe_providers.frappecloud_billing import get_site_login_url
frappe.local.flags.redirect_location = get_site_login_url()
raise frappe.Redirect
context.no_header = True
context.for_test = "login.html"
context["title"] = "Login"