refactor: billing components

This commit is contained in:
sokumon 2026-02-19 12:03:04 +05:30
parent 3d450041d0
commit c1585018d6
10 changed files with 136 additions and 160 deletions

View file

@ -19,7 +19,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.integrations.frappe_providers.frappecloud_billing import current_site_info, is_fc_site
from frappe.model.base_document import get_controller
from frappe.permissions import has_permission
from frappe.query_builder import DocType
@ -125,6 +125,8 @@ def get_bootinfo():
bootinfo.setup_wizard_completed_apps = get_setup_wizard_completed_apps() or []
bootinfo.desktop_icon_urls = get_desktop_icon_urls()
bootinfo.desktop_icon_style = get_icon_style() or "Subtle"
if bootinfo.is_fc_site:
bootinfo.site_info = current_site_info()
return bootinfo

View file

@ -80,6 +80,12 @@
margin-top: 60px;
padding: 20px;
}
.icons-container:has(.sidebar-card){
margin-top: 20px;
.sidebar-card{
gap: 6px;
}
}
.modal
.modal-body .icons-container,.folder-icon .icons-container {
padding:0px;

View file

@ -1,6 +1,6 @@
<!-- jinja -->
<div class="desktop-wrapper">
<header class="navbar navbar-expand navbar-container" role="navigation">
<header class="desktop-navbar navbar navbar-expand navbar-container" role="navigation">
<div class="navbar-home">
<img
id="brand-logo"

View file

@ -24,13 +24,13 @@ page_js = {"setup-wizard": "public/js/frappe/setup_wizard.js"}
# website
app_include_js = [
"libs.bundle.js",
"billing.bundle.js",
"desk.bundle.js",
"list.bundle.js",
"form.bundle.js",
"controls.bundle.js",
"report.bundle.js",
"telemetry.bundle.js",
"billing.bundle.js",
]
app_include_css = [

View file

@ -1,158 +1,91 @@
let frappeCloudBaseEndpoint = "https://frappecloud.com";
let isFCUser = false;
let isFCUser = true;
$(document).ready(function () {
if (
frappe.boot.is_fc_site &&
!!frappe.boot.setup_complete &&
!frappe.is_mobile() &&
frappe.user.has_role("System Manager")
) {
frappe.call({
method: "frappe.integrations.frappe_providers.frappecloud_billing.current_site_info",
callback: (r) => {
if (!r?.message) return;
const response = frappe.boot.site_info;
const trial_end_date = new Date(response.trial_end_date);
frappeCloudBaseEndpoint = response.base_url;
isFCUser = response.is_fc_user;
const response = r.message;
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()) {
addManageTrialBannerDesktop(response.trial_end_date);
}
addManageBillingDropdown();
$(".login-to-fc, .upgrade-plan-button").on("click", function () {
openFrappeCloudDashboard();
});
},
});
}
});
function setErrorMessage(message) {
$("#fc-login-error").text(message);
}
function addManageTrialBannerDesktop(trial_end_date) {
$(document).on("desktop_screen", function (event, data) {
const icons_container = data.desktop.wrapper.find(".icons-container").first();
$(".desktop-container").before(
generateTrialSubscriptionBanner(trial_end_date).css({
width: icons_container.width(),
margin: "auto",
padding: "20px 20px 0px",
})
);
icons_container.css("margin-top", "40px");
});
}
function addManageBillingDropdown() {
$(document).on("desktop_screen", function (event, data) {
data.desktop.add_menu_item({
label: __("Manage Billing"),
icon: "receipt-text",
condition: function () {
return frappe.boot.is_fc_site;
},
onClick: function () {
return openFrappeCloudDashboard();
},
});
});
}
function openFrappeCloudDashboard() {
window.open(`${frappeCloudBaseEndpoint}/dashboard/sites/${frappe.boot.sitename}`, "_blank");
}
function generateTrialSubscriptionBanner(trialEndDate) {
const trial_end_date = new Date(trialEndDate);
const today = new Date();
const diffTime = trial_end_date - today;
const trial_end_days = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
const trial_end_string =
trial_end_days > 1 ? `${trial_end_days} days` : `${trial_end_days} day`;
return $(`<div>
<style>
.trial-banner {
display: flex;
justify-content: space-between;
align-items: center;
background-color: var(--subtle-accent);
border-radius: var(--border-radius-md);
}
.trial-banner > div {
display: flex;
gap: 8px;
}
.trial-banner .info-icon {
margin: auto 0;
}
.trial-banner > div > div {
display: flex;
flex-direction: column;
justify-content: center;
}
.trial-banner .title {
font-size: var(--text-base);
font-weight: var(--weight-semibold);
}
.trial-banner .description {
font-size: var(--text-sm);
color: var(--text-muted);
}
.trial-banner .upgrade-plan-button {
height: fit-content;
background-color: var(--fg-color);
border: 1px solid var(--gray-300);
color: var(--gray-800);
border-radius: var(--border-radius);
}
.trial-banner .upgrade-plan-button:hover {
border-color: var(--gray-400);
}
</style>
<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)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 14.25C11.4518 14.25 14.25 11.4518 14.25 8C14.25 4.54822 11.4518 1.75 8 1.75C4.54822 1.75 1.75 4.54822 1.75 8C1.75 11.4518 4.54822 14.25 8 14.25ZM8 15.25C12.0041 15.25 15.25 12.0041 15.25 8C15.25 3.99594 12.0041 0.75 8 0.75C3.99594 0.75 0.75 3.99594 0.75 8C0.75 12.0041 3.99594 15.25 8 15.25ZM8 5.75C8.48325 5.75 8.875 5.35825 8.875 4.875C8.875 4.39175 8.48325 4 8 4C7.51675 4 7.125 4.39175 7.125 4.875C7.125 5.35825 7.51675 5.75 8 5.75ZM8.5 7.43555C8.5 7.1594 8.27614 6.93555 8 6.93555C7.72386 6.93555 7.5 7.1594 7.5 7.43555V11.143C7.5 11.4191 7.72386 11.643 8 11.643C8.27614 11.643 8.5 11.4191 8.5 11.143V7.43555Z" fill="#171717"/>
</g>
<defs>
<clipPath id="clip0_3360_13841">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>
<div>
<span class="title">
Your trial ends in ${trial_end_string}.
</span>
<span class="description">
${
isFCUser
? "Please upgrade for uninterrupted services"
: "Please contact your system administrator to upgrade your plan."
}
</span>
</div>
</div>
${
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>`
: ""
}
</div>
</div>`);
const banner_message = isFCUser
? "Please upgrade for uninterrupted services"
: "Please contact your system administrator to upgrade your plan.";
let card_args = {
title: `Your trial ends in ${trial_end_string}`,
message: banner_message,
outline: true,
close_button: true,
popper: true,
primary_button_alignment: "right",
};
isFCUser = true;
if (isFCUser) {
card_args.primary_action_label = "Upgrade";
card_args.primary_action_suffix_icon = "square-arrow-out-up-right";
card_args.styles = {
"sidebar-card-button-bg-color": "var(--surface-gray-2)",
"sidebar-card-button-color": "var(--ink-gray-7)",
"sidebar-card-button-outline": "var(--ink-gray-7)",
};
}
$(document).on("desktop_screen", function (event, data) {
if (
frappe.boot.is_fc_site &&
!!frappe.boot.setup_complete &&
!frappe.is_mobile() &&
frappe.user.has_role("System Manager")
) {
if (response.trial_end_date && trial_end_date > new Date()) {
card_args.parent = $(".icons-container").first();
let banner_card = new frappe.ui.SidebarCard(card_args);
}
addManageBillingDropdown(data.desktop);
$(".login-to-fc, .upgrade-plan-button").on("click", function () {
openFrappeCloudDashboard();
});
}
});
$(document).on("sidebar_setup", function (event, data) {
let sidebar = data.sidebar;
// card_args.close_button = null;
sidebar.add_card({
title: card_args.title,
icon: "info",
message: card_args.message,
// primary_action_icon: "zap",
// primary_action_label: "Upgrade",
// primary_button_width: "full",
// primary_action: () => {},
});
});
});
function setErrorMessage(message) {
$("#fc-login-error").text(message);
}
function addManageBillingDropdown(desktop) {
desktop.add_menu_item({
label: __("Manage Billing"),
icon: "receipt-text",
condition: function () {
return frappe.boot.is_fc_site;
},
onClick: function () {
return openFrappeCloudDashboard();
},
});
}
function openFrappeCloudDashboard() {
window.open(
`${frappeCloudBaseEndpoint}/dashboard/sites/${frappe.boot.site_info.name}`,
"_blank"
);
}

View file

@ -35,6 +35,7 @@ export class InfoCard {
trigger: $(this.label_span).find("svg").get(0),
close_button: true,
popper: true,
primary_button_width: "full",
};
if (this.df.documentation_url) {
card_args.primary_action_label = "Read More";

View file

@ -99,6 +99,7 @@ frappe.ui.Sidebar = class Sidebar {
this.workspace_sidebar_items = updated_items;
}
setup(workspace_title) {
$(document).trigger("sidebar_setup", { sidebar: this });
this.sidebar_title = workspace_title;
this.check_for_private_workspace(workspace_title);
this.workspace_title = this.sidebar_title.toLowerCase();
@ -110,11 +111,9 @@ frappe.ui.Sidebar = class Sidebar {
this.add_sidebar_cards();
}
add_card(card) {
if (
this.desktop_menu_items &&
this.desktop_menu_items.find((i) => i.to_title_case === card.title)
)
return;
if (this.cards && this.cards.find((i) => i.title === card.title)) return;
card.parent = this.wrapper.find(".body-sidebar-cards");
delete card.styles;
this.cards.push(card);
}
add_sidebar_cards() {

View file

@ -10,15 +10,16 @@
{% } else { %}
<div class="card-title-container card-close-button">
{%= frappe.utils.icon(card.icon, "sm", "", "", "card-icon") %}
<span class="flex flex-column">
<span class="flex flex-column w-100">
<div class="sidebar-card-title">{{ card.title }}</div>
<div class="sidebar-card-description">{{ card.message }}</div>
</span>
{%= frappe.utils.icon("x","sm", "", "", "card-icon cursor-pointer") %}
{%= frappe.utils.icon("x","sm", "", "", "card-icon cursor-pointer close-button") %}
</div>
{% } %}
{% if(card.primary_action_label) { %}
<button class="sidebar-card-button btn">
<div class="sidebar-card-actions flex">
{% if(card.primary_action_label) { %}
<button class="sidebar-card-button btn {%= card.primary_button_width ? 'w-100' : '' %}">
{% if (card.primary_action_icon) %}
{{ frappe.utils.icon(card.primary_action_icon, "sm", "", "", "", true) }}
{% } %}
@ -28,4 +29,5 @@
{% endif %}
</button>
{% } %}
</div>
</div>

View file

@ -5,6 +5,10 @@ frappe.provide("frappe.ui");
frappe.ui.SidebarCard = class SidebarCard {
constructor(opts) {
Object.assign(this, opts);
this.alignment_style_map = {
right: "flex-end",
left: "flex-start",
};
this.make(opts);
this.setup();
this.display = false;
@ -31,10 +35,16 @@ frappe.ui.SidebarCard = class SidebarCard {
],
});
}
if (this.outline) {
this.card.addClass("card-outline");
this.card.removeClass("px-2 py-2");
}
this.card.prependTo(this.parent);
this.set_button_alignment();
}
setup() {
this.setup_primary_action();
this.setup_close_button();
}
toggle() {
if (this.display) {
@ -59,6 +69,14 @@ frappe.ui.SidebarCard = class SidebarCard {
me.primary_action(event);
});
}
setup_close_button() {
const me = this;
if (this.close_button) {
this.card.find(".close-button").on("click", function () {
me.toggle();
});
}
}
set_styles() {
if (this.styles) {
const $root = $(":root");
@ -67,4 +85,11 @@ frappe.ui.SidebarCard = class SidebarCard {
}
}
}
set_button_alignment() {
if (this.primary_button_alignment) {
this.card
.find(".sidebar-card-actions")
.css("justifyContent", this.alignment_style_map[this.primary_button_alignment]);
}
}
};

View file

@ -1,6 +1,6 @@
:root {
--sidebar-card-button-outline: var(--surface-blue-3);
--sidebar-card-button-bg-color: var(var(--surface-blue-2));
--sidebar-card-button-bg-color: var(--surface-blue-2);
--sidebar-card-button-color: var(--ink-blue-3);
}
.card-title-container {
@ -54,3 +54,11 @@
.cursor-pointer {
cursor: pointer;
}
.card-outline {
border: 1px solid;
box-shadow: none;
border-color: var(--outline-gray-2, #e2e2e2);
border-radius: calc(var(--border-radius-lg) + 2px);
padding: calc(var(--padding-md) - 3px);
}