From 992b10b566eb72ae9c2d922d2f6664e81bf69211 Mon Sep 17 00:00:00 2001 From: Nariman Jelveh Date: Thu, 21 Aug 2025 16:12:15 -0700 Subject: [PATCH] Add UI for custom domains --- src/gui/src/UI/UIWindowPublishWebsite.js | 232 ++++++++++++++++++++--- src/gui/src/i18n/translations/en.js | 1 + 2 files changed, 208 insertions(+), 25 deletions(-) diff --git a/src/gui/src/UI/UIWindowPublishWebsite.js b/src/gui/src/UI/UIWindowPublishWebsite.js index f3e2a21c5..8c282ad75 100644 --- a/src/gui/src/UI/UIWindowPublishWebsite.js +++ b/src/gui/src/UI/UIWindowPublishWebsite.js @@ -34,12 +34,63 @@ async function UIWindowPublishWebsite(target_dir_uid, target_dir_name, target_di h += `
`; // error msg h += `
`; - // subdomain - h += `
`; - h += ``; - - h += `
${html_encode(window.extractProtocol(window.url))}://.${html_encode(window.hosting_domain)}
`; + + // Publishing options + h += `
`; + h += ``; + + // Check if user has active subscription for custom domains + const hasActiveSubscription = window.user && window.user.subscription && window.user.subscription.active; + + // Puter subdomain option + h += `
`; + h += ``; + h += `
`; + + // Custom domain option + h += `
`; + const customDomainDisabled = !hasActiveSubscription; + const customDomainStyle = customDomainDisabled ? + 'display: flex; align-items: center; cursor: not-allowed; padding: 10px; border: 2px solid #e1e8ed; border-radius: 8px; opacity: 0.5; background-color: #f8f9fa;' : + 'display: flex; align-items: center; cursor: pointer; padding: 10px; border: 2px solid #e1e8ed; border-radius: 8px;'; + + h += ``; + h += `
`; h += `
`; + + // Puter subdomain input (shown by default) + h += `
`; + h += ``; + h += `
`; + h += `${html_encode(window.extractProtocol(window.url))}://`; + h += ``; + h += `.${html_encode(window.hosting_domain)}`; + h += `
`; + h += `
`; + + // Custom domain input (hidden by default) + h += ``; + // uid h += ``; // Publish @@ -67,6 +118,56 @@ async function UIWindowPublishWebsite(target_dir_uid, target_dir_name, target_di onAppend: function(this_window){ $(this_window).find(`.publish-website-subdomain`).val(window.generate_identifier()); $(this_window).find(`.publish-website-subdomain`).get(0).focus({preventScroll:true}); + + // Handle radio button changes + $(this_window).find('input[name="publishing-type"]:not(:disabled)').on('change', function(){ + const selectedValue = $(this).val(); + const puterSection = $(this_window).find('.puter-subdomain-section'); + const customSection = $(this_window).find('.custom-domain-section'); + const puterLabel = $(this_window).find('input[value="puter"]').closest('.option-label'); + const customLabel = $(this_window).find('input[value="custom"]').closest('.option-label'); + + // Update visual selection (only if not disabled) + puterLabel.css('border-color', selectedValue === 'puter' ? '#007bff' : '#e1e8ed'); + if (!$(this_window).find('input[value="custom"]').is(':disabled')) { + customLabel.css('border-color', selectedValue === 'custom' ? '#007bff' : '#e1e8ed'); + } + + if(selectedValue === 'puter'){ + puterSection.show(); + customSection.hide(); + $(this_window).find(`.publish-website-subdomain`).focus(); + } else if (selectedValue === 'custom') { + puterSection.hide(); + customSection.show(); + $(this_window).find(`.publish-website-custom-domain`).focus(); + } + }); + + // Add click handler for disabled custom domain option to show upgrade message + $(this_window).find('.custom-domain-label').on('click', function(e){ + const radioButton = $(this).find('input[type="radio"]'); + if (radioButton.is(':disabled')) { + e.preventDefault(); + // Could show upgrade modal here in the future + if(puter.defaultGUIOrigin === 'https://puter.com'){ + $(this_window).find('.publish-website-error-msg').html( + 'Custom domains require a Premium subscription. Upgrade now to use your own domain name.' + ); + }else{ + $(this_window).find('.publish-website-error-msg').html( + 'Custom domains are not available on this instance of Puter. Yet!' + ); + } + $(this_window).find('.publish-website-error-msg').fadeIn(); + setTimeout(() => { + $(this_window).find('.publish-website-error-msg').fadeOut(); + }, 5000); + } + }); + + // Style the selected option initially + $(this_window).find('input[value="puter"]').closest('.option-label').css('border-color', '#007bff'); }, window_class: 'window-publishWebsite', window_css:{ @@ -80,19 +181,46 @@ async function UIWindowPublishWebsite(target_dir_uid, target_dir_name, target_di } }) - $(el_window).find('.publish-btn').on('click', function(e){ - // todo do some basic validation client-side + // Function to load Entri SDK + async function loadEntriSDK() { + if (!window.entri) { + await new Promise((resolve, reject) => { + const script = document.createElement("script"); + script.type = "text/javascript"; + script.src = "https://cdn.goentri.com/entri.js"; + script.addEventListener("load", () => { + resolve(window.entri); + }); + script.addEventListener("error", () => { + reject(new Error("Failed to load the Entri SDK.")); + }); + document.body.appendChild(script); + }); + } + } - //Subdomain - let subdomain = $(el_window).find('.publish-website-subdomain').val(); - + $(el_window).find('.publish-btn').on('click', async function(e){ + e.preventDefault(); + + // Get the selected publishing type + const publishingType = $(el_window).find('input[name="publishing-type"]:checked').val(); + // disable 'Publish' button $(el_window).find('.publish-btn').prop('disabled', true); - puter.hosting.create( - subdomain, - target_dir_path).then((res)=>{ + try { + if (publishingType === 'puter') { + // Handle Puter subdomain publishing + let subdomain = $(el_window).find('.publish-website-subdomain').val(); + + if (!subdomain.trim()) { + throw new Error('Please enter a subdomain name'); + } + + const res = await puter.hosting.create(subdomain, target_dir_path); let url = 'https://' + subdomain + '.' + window.hosting_domain + '/'; + + // Show success $(el_window).find('.window-publishWebsite-form').hide(100, function(){ $(el_window).find('.publishWebsite-published-link').attr('href', url); $(el_window).find('.publishWebsite-published-link').text(url); @@ -109,18 +237,72 @@ async function UIWindowPublishWebsite(target_dir_uid, target_dir_name, target_di }) window.update_sites_cache(); - }).catch((err)=>{ - err = err.error; - $(el_window).find('.publish-website-error-msg').html( - err.message + ( - err.code === 'subdomain_limit_reached' ? - ' ' + i18n('manage_your_subdomains') + '' : '' - ) - ); - $(el_window).find('.publish-website-error-msg').fadeIn(); - // re-enable 'Publish' button - $(el_window).find('.publish-btn').prop('disabled', false); - }) + + } else if (publishingType === 'custom') { + // Handle custom domain publishing with Entri + let customDomain = $(el_window).find('.publish-website-custom-domain').val(); + + if (!customDomain.trim()) { + throw new Error('Please enter your custom domain'); + } + + // Step 1: First create a Puter subdomain to host the content + let subdomain = $(el_window).find('.publish-website-subdomain').val(); + if (!subdomain.trim()) { + // Generate a subdomain if not provided + subdomain = window.generate_identifier(); + } + + const hostingRes = await puter.hosting.create(subdomain, target_dir_path); + const puterSiteUrl = 'https://' + subdomain + '.' + window.hosting_domain; + + // Step 2: Load Entri SDK + await loadEntriSDK(); + + // Step 3: Get Entri config from the backend using the Puter subdomain as userHostedSite + const entriConfig = await puter.drivers.call("entri", "entri-service", "getConfig", { + domain: customDomain, + userHostedSite: subdomain + '.' + window.hosting_domain + }); + + // Step 4: Show Entri interface for custom domain setup + await entri.showEntri(entriConfig.result); + + // Step 5: Show success message with custom domain + let customUrl = 'https://' + customDomain + '/'; + + $(el_window).find('.window-publishWebsite-form').hide(100, function(){ + $(el_window).find('.publishWebsite-published-link').attr('href', customUrl); + $(el_window).find('.publishWebsite-published-link').text(customUrl); + $(el_window).find('.window-publishWebsite-success').show(100) + $(`.item[data-uid="${target_dir_uid}"] .item-has-website-badge`).show(); + }); + + // Update items to show both the Puter subdomain and custom domain + $(`.item[data-path^="${target_dir_path}/"]`).each(function(){ + // show the link badge + $(this).find('.item-has-website-url-badge').show(); + // update item's website_url attribute to use custom domain + $(this).attr('data-website_url', customUrl + $(this).attr('data-path').substring(target_dir_path.length)); + // Also store the puter subdomain URL as backup + $(this).attr('data-puter_website_url', puterSiteUrl + $(this).attr('data-path').substring(target_dir_path.length)); + }) + + window.update_sites_cache(); + } + + } catch (err) { + const errorMessage = err.message || (err.error && err.error.message) || 'An error occurred while publishing'; + $(el_window).find('.publish-website-error-msg').html( + errorMessage + ( + err.error && err.error.code === 'subdomain_limit_reached' ? + ' ' + i18n('manage_your_subdomains') + '' : '' + ) + ); + $(el_window).find('.publish-website-error-msg').fadeIn(); + // re-enable 'Publish' button + $(el_window).find('.publish-btn').prop('disabled', false); + } }) $(el_window).find('.publish-window-ok-btn').on('click', function(){ diff --git a/src/gui/src/i18n/translations/en.js b/src/gui/src/i18n/translations/en.js index 86c3125e6..7777fbed9 100644 --- a/src/gui/src/i18n/translations/en.js +++ b/src/gui/src/i18n/translations/en.js @@ -72,6 +72,7 @@ const en = { confirm_delete_user_title: "Delete Account?", confirm_session_revoke: "Are you sure you want to revoke this session?", confirm_your_email_address: "Confirm Your Email Address", + choose_publishing_option: "Choose how you want to publish your website:", contact_us: "Contact Us", contact_us_verification_required: "You must have a verified email address to use this.", contain: 'Contain',