chore: commit pending changes before custom services implementation
This commit is contained in:
@@ -64,6 +64,7 @@
|
|||||||
"@zxcvbn-ts/language-common": "^3.0.4",
|
"@zxcvbn-ts/language-common": "^3.0.4",
|
||||||
"@zxcvbn-ts/language-en": "^3.0.2",
|
"@zxcvbn-ts/language-en": "^3.0.2",
|
||||||
"cropperjs": "^1.6.2",
|
"cropperjs": "^1.6.2",
|
||||||
|
"heic2any": "^0.0.4",
|
||||||
"maplibre-gl": "^5.24.0"
|
"maplibre-gl": "^5.24.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
import FileDropZone from '$lib/components/ui/file-drop-zone.svelte';
|
import FileDropZone from '$lib/components/ui/file-drop-zone.svelte';
|
||||||
import { authStore } from '$lib/stores/auth.svelte';
|
import { authStore } from '$lib/stores/auth.svelte';
|
||||||
import * as AlertDialog from '$lib/components/ui/alert-dialog';
|
import * as AlertDialog from '$lib/components/ui/alert-dialog';
|
||||||
|
import heic2any from 'heic2any';
|
||||||
|
|
||||||
// =============== Image Upload ===============
|
// =============== Image Upload ===============
|
||||||
let uploading = $state(false);
|
let uploading = $state(false);
|
||||||
@@ -28,9 +29,60 @@
|
|||||||
|
|
||||||
let hasOversizedFiles = $derived(uploadFiles.some(isFileTooBig));
|
let hasOversizedFiles = $derived(uploadFiles.some(isFileTooBig));
|
||||||
|
|
||||||
function handleFilesDropped(files: File[]) {
|
function isHeicFile(file: File): boolean {
|
||||||
uploadFiles = files;
|
const name = file.name.toLowerCase();
|
||||||
generatePreviews(files);
|
return name.endsWith('.heic') || name.endsWith('.heif');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the browser can natively decode a HEIC file.
|
||||||
|
* Safari (macOS/iOS) and Chrome-on-Android have native HEIC support,
|
||||||
|
* which preserves HDR metadata and color profiles better than any conversion.
|
||||||
|
*/
|
||||||
|
function supportsNativeHeicDecode(file: File): Promise<boolean> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const img = new Image();
|
||||||
|
const url = URL.createObjectURL(file);
|
||||||
|
img.onload = () => { URL.revokeObjectURL(url); resolve(true); };
|
||||||
|
img.onerror = () => { URL.revokeObjectURL(url); resolve(false); };
|
||||||
|
img.src = url;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function convertHeicToPng(file: File): Promise<File> {
|
||||||
|
const result = await heic2any({ blob: file, toType: 'image/png' });
|
||||||
|
const blob = Array.isArray(result) ? result[0] : result;
|
||||||
|
const pngName = file.name.replace(/\.(heic|heif)$/i, '.png');
|
||||||
|
return new File([blob], pngName, { type: 'image/png' });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleFilesDropped(files: File[]) {
|
||||||
|
const converted = await Promise.all(
|
||||||
|
files.map(async (file) => {
|
||||||
|
if (!isHeicFile(file)) return file;
|
||||||
|
|
||||||
|
// Try native browser HEIC decode first (Safari, Chrome w/ HEVC)
|
||||||
|
// This preserves HDR metadata & color profiles at full quality
|
||||||
|
try {
|
||||||
|
const nativeOk = await supportsNativeHeicDecode(file);
|
||||||
|
if (nativeOk) return file;
|
||||||
|
} catch {
|
||||||
|
// Fall through to heic2any
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: convert to PNG via heic2any (libheif WASM)
|
||||||
|
// PNG is lossless, libheif handles ICC profiles correctly,
|
||||||
|
// and the downstream canvas pipeline is 8-bit anyway.
|
||||||
|
try {
|
||||||
|
return await convertHeicToPng(file);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('HEIC conversion failed for', file.name, e);
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
uploadFiles = converted;
|
||||||
|
generatePreviews(converted);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Generate scaled previews for each file */
|
/** Generate scaled previews for each file */
|
||||||
|
|||||||
Vendored
+4
-5
@@ -13,12 +13,12 @@
|
|||||||
"state": {
|
"state": {
|
||||||
"type": "markdown",
|
"type": "markdown",
|
||||||
"state": {
|
"state": {
|
||||||
"file": "Crussell/Technical Manual.md",
|
"file": "Crussell/Future Work - Gap Backlog.md",
|
||||||
"mode": "source",
|
"mode": "source",
|
||||||
"source": false
|
"source": false
|
||||||
},
|
},
|
||||||
"icon": "lucide-file",
|
"icon": "lucide-file",
|
||||||
"title": "Technical Manual"
|
"title": "Future Work - Gap Backlog"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -78,8 +78,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"direction": "horizontal",
|
"direction": "horizontal",
|
||||||
"width": 300,
|
"width": 300
|
||||||
"collapsed": true
|
|
||||||
},
|
},
|
||||||
"right": {
|
"right": {
|
||||||
"id": "2750d7726f904ef3",
|
"id": "2750d7726f904ef3",
|
||||||
@@ -172,10 +171,10 @@
|
|||||||
},
|
},
|
||||||
"active": "6776d739ee18449c",
|
"active": "6776d739ee18449c",
|
||||||
"lastOpenFiles": [
|
"lastOpenFiles": [
|
||||||
|
"Crussell/Technical Manual.md",
|
||||||
"Crussell/Future Work - Gap Backlog.md",
|
"Crussell/Future Work - Gap Backlog.md",
|
||||||
"Crussell/Loyalty & Discount System Reference.md",
|
"Crussell/Loyalty & Discount System Reference.md",
|
||||||
"Crussell/Overview.md",
|
"Crussell/Overview.md",
|
||||||
"Crussell/Technical Manual.md",
|
|
||||||
"Crussell/User Manual.md",
|
"Crussell/User Manual.md",
|
||||||
"Crussell/Admin Manual.md",
|
"Crussell/Admin Manual.md",
|
||||||
"Crussell/Test Implementation Plan.md",
|
"Crussell/Test Implementation Plan.md",
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ These are blockers: missing functionality that prevents daily operations, legal
|
|||||||
These improve the experience or add features, but the business can operate without them.
|
These improve the experience or add features, but the business can operate without them.
|
||||||
|
|
||||||
| # | Gap | Effort | Area | Notes |
|
| # | Gap | Effort | Area | Notes |
|
||||||
|---|---|---|---|---|
|
| --- | ---------------------------------------- | -------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| 12 | **One-off custom services** | M (1-2d) | Full-stack | Admin can't create single-use services. Every custom job (bridal party, special request) must be added to the permanent catalog. |
|
| 12 | **One-off custom services** | M (1-2d) | Full-stack | Admin can't create single-use services. Every custom job (bridal party, special request) must be added to the permanent catalog. |
|
||||||
| 13 | **One-off exceptional hours** | M (1d) | Full-stack | Single-day overrides (dentist appointment, afternoon off) require creating a full exceptional group. Time blockers handle unavailable periods; one-off *open* hours (e.g., "open Sunday 2pm-5pm") still need simplification. |
|
| 13 | **One-off exceptional hours** | M (1d) | Full-stack | Single-day overrides (dentist appointment, afternoon off) require creating a full exceptional group. Time blockers handle unavailable periods; one-off *open* hours (e.g., "open Sunday 2pm-5pm") still need simplification. |
|
||||||
| 14 | **Referral system UI** | M (1-2d) | Full-stack | Backend complete — registration validates codes, relationships recorded. Users can't see their referral code or track uses. Admin can't manage referral campaigns. |
|
| 14 | **Referral system UI** | M (1-2d) | Full-stack | Backend complete — registration validates codes, relationships recorded. Users can't see their referral code or track uses. Admin can't manage referral campaigns. |
|
||||||
@@ -78,31 +78,3 @@ E10 Sentry → error tracking, 5xx alerting
|
|||||||
```
|
```
|
||||||
|
|
||||||
All local (MVP + Stretch) items have zero external dependencies.
|
All local (MVP + Stretch) items have zero external dependencies.
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Notes on Removed Items
|
|
||||||
|
|
||||||
The following items were completed and removed from this document:
|
|
||||||
|
|
||||||
- Delete account endpoint, GDPR data export, delete_guest_user SQL function, GDPR anonymization
|
|
||||||
- Walk-in guest booking, walk-in slot blocking, reservation transitions
|
|
||||||
- ApprovalModal decline/cancel, admin notification panel, admin schedule page
|
|
||||||
- BookingFlow welcome step, shared format utilities, patch_test_duration_hours
|
|
||||||
- created_by_name on bookings, admin login redirect, customer relationship view
|
|
||||||
- Admin UI for edit requests, booking rescheduling (user + admin)
|
|
||||||
- Auto lunch protection, health check endpoint, graceful shutdown
|
|
||||||
- HSTS + Referrer-Policy headers, console.log cleanup, alert prototypes
|
|
||||||
- Idempotency keys for bookings, loyalty stamp redemption + discount system
|
|
||||||
- Test DB optimization, image optimization, image deletion fix
|
|
||||||
- User notification preferences UI, payment integration (Square), deposit payments
|
|
||||||
- Tips UI, gift card system (backend + admin UI), referral code registration
|
|
||||||
- CharCounter, loginInProgress rate limiting, profile picture limits, formatDateISO
|
|
||||||
- Portfolio upload limits, JWT revocation, MapLibre GL, NavBar enhancements
|
|
||||||
- TodayCalendar improvements, Svelte 5 $derived, SvelteDate, $app/paths resolve
|
|
||||||
- Vite WASM support, portfolio multi-format images, loyalty stamp redesign
|
|
||||||
- Admin role restrictions, account restrictions, BookingsByCreatedRange, enriched edit requests
|
|
||||||
- Email check endpoint, PhoneInput, business settings API, gift card audit log
|
|
||||||
- Gift card expiry, idle account cleanup, inventory cards, expired balance recovery
|
|
||||||
- Till sale idempotency, VAT on till sales, test infrastructure upgrades
|
|
||||||
- Financial data retention, gift card transaction audit log
|
|
||||||
|
|||||||
Reference in New Issue
Block a user