mirror of
https://github.com/HeyPuter/puter.git
synced 2026-04-28 19:20:55 -05:00
Add UI for custom domains
This commit is contained in:
@@ -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(){
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user