Implement version 0.0.3 features

- Add file attachment support for product versions (Media Library)
- Add version auto-detection from uploaded filenames
- Implement secure customer downloads with hash verification
- Add license key copy-to-clipboard functionality
- Redesign customer licenses page with card-based UI
- Fix product versions meta box visibility for non-licensed types
- Add DownloadController for secure file delivery
- Update CLAUDE.md roadmap and session history

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-21 19:46:50 +01:00
parent 41e5f8d467
commit 78e43b9aea
15 changed files with 1036 additions and 133 deletions

View File

@@ -145,3 +145,23 @@
font-style: italic;
color: #666;
}
/* File Upload UI */
.selected-file-name {
display: inline-block;
margin-right: 10px;
padding: 5px 10px;
background: #e7f3ff;
border-radius: 3px;
font-weight: 500;
}
.selected-file-name:empty {
display: none;
}
#versions-table .dashicons-media-archive {
color: #2271b1;
vertical-align: middle;
margin-left: 5px;
}

View File

@@ -37,7 +37,206 @@
color: #383d41;
}
/* License Table */
/* License Cards */
.woocommerce-licenses {
display: flex;
flex-direction: column;
gap: 1.5em;
}
.license-card {
border: 1px solid #e5e5e5;
border-radius: 8px;
overflow: hidden;
background: #fff;
}
.license-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1em 1.5em;
background: #f8f9fa;
border-bottom: 1px solid #e5e5e5;
}
.license-header h3 {
margin: 0;
font-size: 1.1em;
}
.license-header h3 a {
color: inherit;
text-decoration: none;
}
.license-header h3 a:hover {
text-decoration: underline;
}
.license-details {
padding: 1.5em;
}
.license-key-row {
display: flex;
align-items: center;
gap: 0.75em;
margin-bottom: 1em;
flex-wrap: wrap;
}
.license-key-row label {
font-weight: 600;
}
.license-key-row code {
font-family: 'SF Mono', Monaco, Consolas, monospace;
background-color: #f5f5f5;
padding: 0.4em 0.8em;
border-radius: 4px;
font-size: 1em;
letter-spacing: 0.05em;
}
.copy-license-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
padding: 0;
background: #f0f0f0;
border: 1px solid #ddd;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s ease;
}
.copy-license-btn:hover {
background: #e5e5e5;
border-color: #ccc;
}
.copy-license-btn .dashicons {
font-size: 18px;
width: 18px;
height: 18px;
}
.copy-feedback {
display: inline-block;
margin-left: 0.5em;
padding: 0.25em 0.5em;
font-size: 0.85em;
border-radius: 3px;
}
.copy-feedback.success {
background: #d4edda;
color: #155724;
}
.copy-feedback.error {
background: #f8d7da;
color: #721c24;
}
.license-info-row {
display: flex;
gap: 2em;
color: #666;
font-size: 0.95em;
flex-wrap: wrap;
}
/* Download Section */
.license-downloads {
padding: 1em 1.5em;
background: #f8f9fa;
border-top: 1px solid #e5e5e5;
}
.license-downloads h4 {
margin: 0 0 0.75em 0;
font-size: 0.95em;
color: #333;
}
.download-list {
list-style: none;
margin: 0;
padding: 0;
}
.download-list li {
display: flex;
align-items: center;
gap: 1em;
padding: 0.5em 0;
border-bottom: 1px solid #eee;
}
.download-list li:last-child {
border-bottom: none;
}
.download-link {
display: inline-flex;
align-items: center;
gap: 0.5em;
color: #2271b1;
text-decoration: none;
font-weight: 500;
}
.download-link:hover {
text-decoration: underline;
}
.download-link .dashicons {
font-size: 16px;
width: 16px;
height: 16px;
}
.download-version {
background: #e7f3ff;
padding: 0.2em 0.5em;
border-radius: 3px;
font-size: 0.85em;
color: #2271b1;
}
.download-date {
color: #999;
font-size: 0.85em;
margin-left: auto;
}
/* Domain Field */
#licensed-product-domain-field {
margin-top: 2em;
padding: 1.5em;
background-color: #f8f9fa;
border: 1px solid #e5e5e5;
border-radius: 4px;
}
#licensed-product-domain-field h3 {
margin-top: 0;
margin-bottom: 1em;
font-size: 1.1em;
}
#licensed-product-domain-field .description {
display: block;
margin-top: 0.5em;
font-size: 0.9em;
color: #666;
}
/* Legacy License Table (kept for backwards compatibility) */
.woocommerce-licenses-table {
width: 100%;
border-collapse: collapse;
@@ -64,30 +263,33 @@
font-size: 0.9em;
}
/* Domain Field */
#licensed-product-domain-field {
margin-top: 2em;
padding: 1.5em;
background-color: #f8f9fa;
border: 1px solid #e5e5e5;
border-radius: 4px;
}
#licensed-product-domain-field h3 {
margin-top: 0;
margin-bottom: 1em;
font-size: 1.1em;
}
#licensed-product-domain-field .description {
display: block;
margin-top: 0.5em;
font-size: 0.9em;
color: #666;
}
/* Responsive */
@media screen and (max-width: 768px) {
.license-header {
flex-direction: column;
align-items: flex-start;
gap: 0.5em;
}
.license-key-row {
flex-direction: column;
align-items: flex-start;
}
.license-info-row {
flex-direction: column;
gap: 0.5em;
}
.download-list li {
flex-wrap: wrap;
}
.download-date {
margin-left: 0;
width: 100%;
}
.woocommerce-licenses-table,
.woocommerce-licenses-table thead,
.woocommerce-licenses-table tbody,

90
assets/js/frontend.js Normal file
View File

@@ -0,0 +1,90 @@
/**
* WC Licensed Product - Frontend Scripts
*
* @package Jeremias\WcLicensedProduct
*/
(function($) {
'use strict';
var WCLicensedProductFrontend = {
init: function() {
this.bindEvents();
},
bindEvents: function() {
$(document).on('click', '.copy-license-btn', this.copyLicenseKey);
},
/**
* Copy license key to clipboard
*/
copyLicenseKey: function(e) {
e.preventDefault();
var $btn = $(this);
var licenseKey = $btn.data('license-key');
if (!licenseKey) {
return;
}
// Use modern clipboard API if available
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(licenseKey)
.then(function() {
WCLicensedProductFrontend.showCopyFeedback($btn, true);
})
.catch(function() {
WCLicensedProductFrontend.fallbackCopy(licenseKey, $btn);
});
} else {
WCLicensedProductFrontend.fallbackCopy(licenseKey, $btn);
}
},
/**
* Fallback copy method for older browsers
*/
fallbackCopy: function(text, $btn) {
var $temp = $('<textarea>');
$('body').append($temp);
$temp.val(text).select();
try {
var success = document.execCommand('copy');
WCLicensedProductFrontend.showCopyFeedback($btn, success);
} catch (err) {
WCLicensedProductFrontend.showCopyFeedback($btn, false);
}
$temp.remove();
},
/**
* Show feedback after copy attempt
*/
showCopyFeedback: function($btn, success) {
var message = success
? wcLicensedProduct.strings.copied
: wcLicensedProduct.strings.copyFailed;
var $feedback = $('<span class="copy-feedback"></span>')
.text(message)
.addClass(success ? 'success' : 'error');
$btn.after($feedback);
setTimeout(function() {
$feedback.fadeOut(300, function() {
$(this).remove();
});
}, 1500);
}
};
$(document).ready(function() {
WCLicensedProductFrontend.init();
});
})(jQuery);

View File

@@ -8,6 +8,8 @@
'use strict';
var WCLicensedProductVersions = {
mediaFrame: null,
init: function() {
this.bindEvents();
},
@@ -16,6 +18,113 @@
$('#add-version-btn').on('click', this.addVersion);
$(document).on('click', '.delete-version-btn', this.deleteVersion);
$(document).on('click', '.toggle-version-btn', this.toggleVersion);
// Media uploader events
$('#upload-version-file-btn').on('click', this.openMediaUploader.bind(this));
$('#remove-version-file-btn').on('click', this.removeSelectedFile);
// Listen for product type changes
$('#product-type').on('change', this.onProductTypeChange);
// Initial check for product type (handles page load for new products)
this.onProductTypeChange();
},
onProductTypeChange: function() {
var productType = $('#product-type').val();
var $metaBox = $('#wc_licensed_product_versions');
if (productType === 'licensed') {
$metaBox.show();
} else {
$metaBox.hide();
}
},
/**
* Open WordPress media uploader
*/
openMediaUploader: function(e) {
e.preventDefault();
var self = this;
// If frame already exists, open it
if (this.mediaFrame) {
this.mediaFrame.open();
return;
}
// Create media frame
this.mediaFrame = wp.media({
title: wcLicensedProductVersions.strings.selectFile,
button: {
text: wcLicensedProductVersions.strings.useThisFile
},
multiple: false,
library: {
type: ['application/zip', 'application/x-zip-compressed', 'application/octet-stream']
}
});
// When file is selected
this.mediaFrame.on('select', function() {
var attachment = self.mediaFrame.state().get('selection').first().toJSON();
// Set attachment ID
$('#new_attachment_id').val(attachment.id);
// Show filename
$('#selected_file_name').text(attachment.filename);
$('#remove-version-file-btn').show();
// Try to extract version from filename
var extractedVersion = self.extractVersionFromFilename(attachment.filename);
if (extractedVersion && !$('#new_version').val().trim()) {
$('#new_version').val(extractedVersion);
}
// Clear external URL when file is selected
$('#new_download_url').val('');
});
this.mediaFrame.open();
},
/**
* Remove selected file
*/
removeSelectedFile: function(e) {
e.preventDefault();
$('#new_attachment_id').val('');
$('#selected_file_name').text('');
$('#remove-version-file-btn').hide();
},
/**
* Extract version from filename
* Supports patterns like: plugin-v1.2.3.zip, plugin-1.2.3.zip, v1.2.3.zip
*/
extractVersionFromFilename: function(filename) {
// Remove extension
var basename = filename.replace(/\.[^/.]+$/, '');
// Try various patterns
var patterns = [
/[_-]v?(\d+\.\d+\.\d+)$/i, // ends with -v1.2.3 or -1.2.3
/[_-]v?(\d+\.\d+\.\d+)[_-]/i, // contains -v1.2.3- or -1.2.3-
/^v?(\d+\.\d+\.\d+)$/i // just version number
];
for (var i = 0; i < patterns.length; i++) {
var match = basename.match(patterns[i]);
if (match) {
return match[1];
}
}
return null;
},
addVersion: function(e) {
@@ -27,6 +136,7 @@
var version = $('#new_version').val().trim();
var downloadUrl = $('#new_download_url').val().trim();
var releaseNotes = $('#new_release_notes').val().trim();
var attachmentId = $('#new_attachment_id').val();
// Validate version
if (!version) {
@@ -51,7 +161,8 @@
product_id: productId,
version: version,
download_url: downloadUrl,
release_notes: releaseNotes
release_notes: releaseNotes,
attachment_id: attachmentId
},
success: function(response) {
if (response.success) {
@@ -65,6 +176,9 @@
$('#new_version').val('');
$('#new_download_url').val('');
$('#new_release_notes').val('');
$('#new_attachment_id').val('');
$('#selected_file_name').text('');
$('#remove-version-file-btn').hide();
} else {
alert(response.data.message || wcLicensedProductVersions.strings.error);
}