Add UI for custom domains

This commit is contained in:
Nariman Jelveh
2025-08-21 16:12:15 -07:00
parent a770c5ade6
commit 992b10b566
2 changed files with 208 additions and 25 deletions
+207 -25
View File
@@ -34,12 +34,63 @@ async function UIWindowPublishWebsite(target_dir_uid, target_dir_name, target_di
h += `<form class="window-publishWebsite-form">`;
// error msg
h += `<div class="publish-website-error-msg"></div>`;
// subdomain
h += `<div style="overflow: hidden;">`;
h += `<label style="margin-bottom: 10px;">${i18n('pick_name_for_website')}</label>`;
h += `<div style="font-family: monospace;">${html_encode(window.extractProtocol(window.url))}://<input class="publish-website-subdomain" style="width:235px;" type="text" autocomplete="subdomain" spellcheck="false" autocorrect="off" autocapitalize="off" data-gramm_editor="false"/>.${html_encode(window.hosting_domain)}</div>`;
// Publishing options
h += `<div class="publishing-options" style="margin-bottom: 20px;">`;
h += `<label style="margin-bottom: 15px; display: block; font-weight: 600;">${i18n('choose_publishing_option')}</label>`;
// Check if user has active subscription for custom domains
const hasActiveSubscription = window.user && window.user.subscription && window.user.subscription.active;
// Puter subdomain option
h += `<div class="option-container" style="margin-bottom: 15px;">`;
h += `<label class="option-label" style="display: flex; align-items: center; cursor: pointer; padding: 10px; border: 2px solid #e1e8ed; border-radius: 8px;">`;
h += `<input type="radio" name="publishing-type" value="puter" checked style="margin-right: 10px;">`;
h += `<div>`;
h += `<div style="font-weight: 500; margin-bottom: 5px;">Free Puter Subdomain</div>`;
h += `<div style="font-size: 12px; color: #666; line-height: 1.4;">Get a free subdomain on puter.site - quick and easy setup</div>`;
h += `</div>`;
h += `</label>`;
h += `</div>`;
// Custom domain option
h += `<div class="option-container">`;
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 += `<label class="option-label custom-domain-label" style="${customDomainStyle}">`;
h += `<input type="radio" name="publishing-type" value="custom" ${customDomainDisabled ? 'disabled' : ''} style="margin-right: 10px;">`;
h += `<div>`;
h += `<div style="font-weight: 500; margin-bottom: 5px;">Custom Domain ${customDomainDisabled ? '(Premium)' : ''}</div>`;
if (customDomainDisabled) {
h += `<div style="font-size: 12px; color: #999; line-height: 1.4;">Upgrade to Premium to use your own domain name</div>`;
} else {
h += `<div style="font-size: 12px; color: #666; line-height: 1.4;">Use your own domain name with professional setup</div>`;
}
h += `</div>`;
h += `</label>`;
h += `</div>`;
h += `</div>`;
// Puter subdomain input (shown by default)
h += `<div class="puter-subdomain-section" style="overflow: hidden; margin-bottom: 20px;">`;
h += `<label style="margin-bottom: 10px; display: block;">${i18n('pick_name_for_website')}</label>`;
h += `<div style="font-family: monospace; display: flex; align-items: center; background: #f8f9fa; padding: 8px; border-radius: 6px; border: 1px solid #dee2e6;">`;
h += `<span style="color: #666;">${html_encode(window.extractProtocol(window.url))}://</span>`;
h += `<input class="publish-website-subdomain" style="border: none; background: #ffffff; outline: none; width: 150px; padding: 7px !important; " type="text" autocomplete="subdomain" spellcheck="false" autocorrect="off" autocapitalize="off" data-gramm_editor="false"/>`;
h += `<span style="color: #666;">.${html_encode(window.hosting_domain)}</span>`;
h += `</div>`;
h += `</div>`;
// Custom domain input (hidden by default)
h += `<div class="custom-domain-section" style="display: none; margin-bottom: 20px;">`;
h += `<label style="margin-bottom: 10px; display: block;">Enter your custom domain</label>`;
h += `<input class="publish-website-custom-domain" style="width: 100%; padding: 10px; border: 1px solid #dee2e6; border-radius: 6px; font-family: monospace;" type="text" placeholder="example.com" spellcheck="false" autocorrect="off" autocapitalize="off" data-gramm_editor="false"/>`;
h += `<div style="font-size: 12px; color: #666; margin-top: 5px;">Make sure your domain's DNS points to your hosting provider</div>`;
h += `</div>`;
// uid
h += `<input class="publishWebsiteTargetDirUID" type="hidden" value="${html_encode(target_dir_uid)}"/>`;
// 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. <a href="/settings/subscriptions" target="_blank">Upgrade now</a> 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' ?
' <span class="manage-your-websites-link">' + i18n('manage_your_subdomains') + '</span>' : ''
)
);
$(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' ?
' <span class="manage-your-websites-link">' + i18n('manage_your_subdomains') + '</span>' : ''
)
);
$(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(){
+1
View File
@@ -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',