f.name));
if (this.uploadedFiles.length + files.length > 5) {
alert('You can only upload up to 5 branding files.');
return;
}
files.forEach(file => {
const ext = file.name.split('.').pop().toLowerCase();
if (!['pdf', 'ai', 'eps', 'jpg', 'jpeg', 'png'].includes(ext)) {
alert('Only PDF, AI, EPS, JPG, and PNG files are accepted.');
return;
}
if (file.size > 10 * 1024 * 1024) {
alert('File size exceeds the 10MB limit.');
return;
}
this.uploadFile(file);
});
},
updateFileState(filename, data) {
const fileObj = this.uploadedFiles.find(f => f.name === filename);
if (fileObj) {
Object.assign(fileObj, data);
this.uploadedFiles = [...this.uploadedFiles]; // Force Alpine reactivity
}
},
uploadFile(file) {
this.uploadedFiles.push({
name: file.name,
progress: 0,
hashed_name: null,
error: null,
status: 'uploading'
});
this.isUploading = true;
this.uploadedFiles = [...this.uploadedFiles]; // Force immediate display of progress bar
console.log('uploadFile: Preparing payload for:', file.name);
const formData = new FormData();
formData.append('file', file);
const csrfToken = document.querySelector('meta[name=csrf-token]')?.getAttribute('content') || '';
console.log('uploadFile: CSRF token lookup result:', csrfToken ? 'Token Found' : 'Token Missing!');
const xhr = new XMLHttpRequest();
xhr.open('POST', '/upload-quote-file');
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
if (csrfToken) {
xhr.setRequestHeader('X-CSRF-TOKEN', csrfToken);
}
xhr.upload.addEventListener('progress', e => {
if (e.lengthComputable) {
const percentage = Math.round((e.loaded / e.total) * 100);
console.log('uploadFile progress (' + file.name + '): ' + percentage + '%');
this.updateFileState(file.name, { progress: percentage });
}
});
xhr.addEventListener('load', () => {
console.log('uploadFile load complete (' + file.name + '). Status code:', xhr.status);
if (xhr.status >= 200 && xhr.status < 300) {
try {
const response = JSON.parse(xhr.responseText);
console.log('uploadFile response (' + file.name + '):', response);
if (response.success) {
this.updateFileState(file.name, {
hashed_name: response.hashed_name,
status: 'success',
progress: 100
});
} else {
this.updateFileState(file.name, {
error: response.message || 'Upload failed.',
status: 'error'
});
}
} catch(err) {
console.error('JSON parse exception during load handler:', err);
this.updateFileState(file.name, {
error: 'Invalid server response.',
status: 'error'
});
}
} else {
let msg = 'Upload failed.';
try {
const err = JSON.parse(xhr.responseText);
msg = err.message || msg;
} catch(e) {}
this.updateFileState(file.name, {
error: msg,
status: 'error'
});
}
this.checkUploadStatus();
});
xhr.addEventListener('error', () => {
console.error('uploadFile net error (' + file.name + ')');
this.updateFileState(file.name, {
error: 'Network error occurred.',
status: 'error'
});
this.checkUploadStatus();
});
xhr.send(formData);
console.log('uploadFile: Sent payload for:', file.name);
},
checkUploadStatus() {
this.isUploading = this.uploadedFiles.some(f => f.status === 'uploading');
},
removeUploadedFile(index) {
this.uploadedFiles.splice(index, 1);
this.checkUploadStatus();
},
getBrandingFilesPayload() {
return this.uploadedFiles
.filter(f => f.status === 'success')
.map(f => ({
original_name: f.name,
hashed_name: f.hashed_name
}));
},
getAddIsDisabled(id) {
const cartElement = document.querySelector('[x-data=\'shoppingCart\']');
const cart = Alpine.$data(cartElement);
return cart.isInCart(id, this.getColourTitle());
},
init() {
// Clean up any leading '?&' from the search query string and parse parameters
let search = window.location.search;
search = search.replace(/^\?&/, '?');
const params = new URLSearchParams(search);
// Automatically set the Quantity
const quantityParam = params.get('quantity') || params.get('qty');
if (quantityParam) {
const qty = parseInt(quantityParam, 10);
if (!isNaN(qty) && qty > 0) {
this._quantity = qty;
}
}
// Automatically select the Color
const colourParam = params.get('colour') || params.get('color');
if (colourParam) {
this.$nextTick(() => {
const decodedColour = decodeURIComponent(colourParam).trim().toLowerCase();
const buttons = Array.from(document.querySelectorAll('.product-details button[title]'));
const match = buttons.find(b => b.getAttribute('title').trim().toLowerCase() === decodedColour);
if (match) match.click(); // Triggers the Alpine click handler to select color and update corresponding image
});
}
}
}">
Home
/
Catalogue
/
Bags
/
Cooler Bags
/
Office Pack
Branding Logo or Images
Select Files
Accepted file types: PDF, AI, EPS, JPG, PNG | Max file size: 10MB | Max files: 1
check_circle
error
cancel
cancel Remove from Quote Cart
Uploading branding...
add_shopping_cart Add to Quote Cart
Product Description
expand_more
Enhance your office experience with the Office Pack, a practical and stylish set designed to meet your daily needs. This pack includes a Sumo lunch cooler to keep your meals fresh, a Venture Notebook for all your notes and ideas, an Aluminium Napier Pen for smooth writing, and a Clear Flat Drink Bottle to stay hydrated throughout the day. Available in 11 attractive colours, this pack is perfect for professionals, students, and anyone who values convenience and functionality.
Collection
Office
Category
Gift Sets
Item Size
Cooler: 175 x 125 x 240mm (LxWxH)
Print Areas
Screenprint: Lunch Bag - 110 x 100mm (LxH), Drink Bottle - 90 x 150mm (LxH), Notebook - 100 x 150mm (LxH); Digital Transfer: Lunch Cooler - 110 x 100mm (LxH); Digital Print: Drink Bottle - 90 x 150mm (LxH), Notebook - 100 x 150mm (LxH); Laser Engraved: Pen - 50 x 6mm (LxH); Pad Print: Pen - 50 x 7mm (LxH)
Carton Notes
Gift items packed into Sumo cooler lunch bag.
Keywords
Summer,FP8299