added the cropping library

This commit is contained in:
ghassan committed 2026-01-25 21:25:23 +03:00
1 parent c1fc28087e
commit 7a18eb6588
13 files changed
+887 -192

No files matched your search

+96 -55
View File
@@ -285,15 +285,6 @@
}
</style>
<!-- Flag Icons CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/css/flag-icons.min.css">
<!-- Select2 CSS (for nationality dropdown) -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/select2.min.css" rel="stylesheet" />
<!-- Flatpickr CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
<div class="login-page">
<div class="login-box">
<div class="card">
@@ -311,11 +302,11 @@
<!-- Full Name -->
<div class="mb-3">
<label for="full_name" class="form-label">Full Name</label>
<input id="full_name" type="text"
class="form-control @error('full_name') is-invalid @enderror"
name="full_name"
value="{{ old('full_name') }}"
placeholder="Full Name"
required autocomplete="name">
@error('full_name')
<span class="invalid-feedback" role="alert">
@@ -326,11 +317,11 @@
<!-- Email Address -->
<div class="mb-3">
<label for="email" class="form-label">Email Address</label>
<input id="email" type="email"
class="form-control @error('email') is-invalid @enderror"
name="email"
value="{{ old('email') }}"
placeholder="Email Address"
required autocomplete="email">
@error('email')
<span class="invalid-feedback" role="alert">
@@ -341,10 +332,10 @@
<!-- Password -->
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input id="password" type="password"
class="form-control @error('password') is-invalid @enderror"
name="password"
placeholder="Password"
required autocomplete="new-password">
@error('password')
<span class="invalid-feedback" role="alert">
@@ -355,15 +346,16 @@
<!-- Confirm Password -->
<div class="mb-3">
<label for="password-confirm" class="form-label">Confirm Password</label>
<input id="password-confirm" type="password"
class="form-control"
name="password_confirmation"
placeholder="Confirm Password"
required autocomplete="new-password">
</div>
<!-- Mobile Number with Country Code -->
<div class="mb-3">
<label for="mobile_number" class="form-label">Mobile Number</label>
<x-country-code-dropdown
name="country_code"
id="country_code"
@@ -374,7 +366,6 @@
class="form-control @error('mobile_number') is-invalid @enderror"
name="mobile_number"
value="{{ old('mobile_number') }}"
placeholder="Mobile Number"
required autocomplete="tel">
</x-country-code-dropdown>
@error('mobile_number')
@@ -386,11 +377,12 @@
<!-- Gender -->
<div class="mb-3">
<label for="gender" class="form-label">Gender</label>
<select id="gender" class="form-select @error('gender') is-invalid @enderror"
name="gender" required>
<option value="">Select Gender</option>
<option value="m" {{ old('gender') == 'm' ? 'selected' : '' }}>Male</option>
<option value="f" {{ old('gender') == 'f' ? 'selected' : '' }}>Female</option>
<option value="m" {{ old('gender') == 'm' ? 'selected' : '' }}>♂️ Male</option>
<option value="f" {{ old('gender') == 'f' ? 'selected' : '' }}>♀️ Female</option>
</select>
@error('gender')
<span class="invalid-feedback" role="alert">
@@ -401,15 +393,54 @@
<!-- Birthdate -->
<div class="mb-3">
<input id="birthdate" type="text"
class="flatpickr-input @error('birthdate') is-invalid @enderror"
name="birthdate"
value="{{ old('birthdate') }}"
placeholder="Birthdate"
required
readonly>
<label for="birthdate" class="form-label">Birthdate</label>
<div class="row g-2">
<div class="col-4">
<select id="birth_day" class="form-select @error('birthdate') is-invalid @enderror" required>
<option value="">Day</option>
@for($day = 1; $day <= 31; $day++)
<option value="{{ str_pad($day, 2, '0', STR_PAD_LEFT) }}" {{ old('birth_day') == str_pad($day, 2, '0', STR_PAD_LEFT) ? 'selected' : '' }}>
{{ $day }}
</option>
@endfor
</select>
</div>
<div class="col-4">
<select id="birth_month" class="form-select @error('birthdate') is-invalid @enderror" required>
<option value="">Month</option>
<option value="01" {{ old('birth_month') == '01' ? 'selected' : '' }}>January</option>
<option value="02" {{ old('birth_month') == '02' ? 'selected' : '' }}>February</option>
<option value="03" {{ old('birth_month') == '03' ? 'selected' : '' }}>March</option>
<option value="04" {{ old('birth_month') == '04' ? 'selected' : '' }}>April</option>
<option value="05" {{ old('birth_month') == '05' ? 'selected' : '' }}>May</option>
<option value="06" {{ old('birth_month') == '06' ? 'selected' : '' }}>June</option>
<option value="07" {{ old('birth_month') == '07' ? 'selected' : '' }}>July</option>
<option value="08" {{ old('birth_month') == '08' ? 'selected' : '' }}>August</option>
<option value="09" {{ old('birth_month') == '09' ? 'selected' : '' }}>September</option>
<option value="10" {{ old('birth_month') == '10' ? 'selected' : '' }}>October</option>
<option value="11" {{ old('birth_month') == '11' ? 'selected' : '' }}>November</option>
<option value="12" {{ old('birth_month') == '12' ? 'selected' : '' }}>December</option>
</select>
</div>
<div class="col-4">
<select id="birth_year" class="form-select @error('birthdate') is-invalid @enderror" required>
<option value="">Year</option>
@php
$currentYear = date('Y');
$startYear = $currentYear - 10; // Start from 10 years ago
$endYear = 1900;
@endphp
@for($year = $startYear; $year >= $endYear; $year--)
<option value="{{ $year }}" {{ old('birth_year') == $year ? 'selected' : '' }}>
{{ $year }}
</option>
@endfor
</select>
</div>
</div>
<input type="hidden" id="birthdate" name="birthdate" value="{{ old('birthdate') }}">
@error('birthdate')
<span class="invalid-feedback" role="alert">
<span class="invalid-feedback d-block" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
@@ -417,7 +448,7 @@
<!-- Nationality -->
<div class="mb-3">
<x-country-dropdown
<x-nationality-dropdown
name="nationality"
id="nationality"
:value="old('nationality')"
@@ -434,38 +465,40 @@
<!-- /.login-box -->
</div>
<!-- jQuery (required for Select2) -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- Select2 JS -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/select2.min.js"></script>
<!-- Flatpickr JS -->
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Initialize Flatpickr for birthdate
flatpickr('#birthdate', {
dateFormat: 'Y-m-d',
maxDate: 'today',
yearRange: [1900, new Date().getFullYear()],
disableMobile: true,
showMonths: 1,
clickOpens: true,
onReady: function(selectedDates, dateStr, instance) {
const calendar = instance.calendarContainer;
calendar.style.fontSize = '14px';
}
});
// Combine birth date dropdowns into hidden field
const birthDay = document.getElementById('birth_day');
const birthMonth = document.getElementById('birth_month');
const birthYear = document.getElementById('birth_year');
const birthdateHidden = document.getElementById('birthdate');
// Form submission handler
$('#registrationForm').on('submit', function(e) {
console.log('Form submitting...');
console.log('Country code:', $('#country_code').val());
console.log('Nationality:', $('#nationality').val());
console.log('Mobile number:', $('#mobile_number').val());
});
function updateBirthdate() {
const day = birthDay.value;
const month = birthMonth.value;
const year = birthYear.value;
if (day && month && year) {
birthdateHidden.value = `${year}-${month}-${day}`;
} else {
birthdateHidden.value = '';
}
}
// Update hidden field when any dropdown changes
birthDay.addEventListener('change', updateBirthdate);
birthMonth.addEventListener('change', updateBirthdate);
birthYear.addEventListener('change', updateBirthdate);
// Initialize from old value if exists
if (birthdateHidden.value) {
const parts = birthdateHidden.value.split('-');
if (parts.length === 3) {
birthYear.value = parts[0];
birthMonth.value = parts[1];
birthDay.value = parts[2];
}
}
// Error handler
window.onerror = function(message, source, lineno, colno, error) {
@@ -475,8 +508,16 @@
console.error('Error:', error);
};
});
// Form submission handler
document.getElementById('registrationForm').addEventListener('submit', function(e) {
console.log('Form submitting...');
console.log('Country code:', document.getElementById('country_code').value);
console.log('Nationality:', document.getElementById('nationality').value);
console.log('Mobile number:', document.getElementById('mobile_number').value);
console.log('Birthdate:', document.getElementById('birthdate').value);
});
</script>
@stack('styles')
@stack('scripts')
@endsection
@@ -162,8 +162,8 @@
}
}
// Initialize all country code dropdowns on the page
document.querySelectorAll('[id$="List"]').forEach(function(listElement) {
// Initialize only country code dropdowns (not nationality dropdowns)
document.querySelectorAll('[id$="country_codeList"]').forEach(function(listElement) {
const componentId = listElement.id.replace('List', '');
initializeCountryDropdown(componentId, countries);
});
@@ -1,114 +1,166 @@
@props(['name' => 'nationality', 'id' => 'nationality', 'value' => '', 'required' => false, 'error' => null])
@props(['name' => 'nationality', 'id' => 'nationality', 'value' => '', 'required' => false, 'error' => null, 'label' => 'Nationality'])
<select id="{{ $id }}"
class="form-select nationality-select @error($name) is-invalid @enderror"
name="{{ $name }}"
{{ $required ? 'required' : '' }}>
<option value="">Select Nationality</option>
</select>
<label for="{{ $id }}" class="form-label">{{ $label }}</label>
<div class="dropdown w-100" onclick="event.stopPropagation()">
<button class="form-select dropdown-toggle d-flex align-items-center justify-content-between @error($name) is-invalid @enderror"
type="button"
id="{{ $id }}Dropdown"
data-bs-toggle="dropdown"
data-bs-auto-close="outside"
aria-expanded="false"
style="text-align: left; background-color: rgba(255,255,255,0.8);">
<span class="d-flex align-items-center">
<span id="{{ $id }}SelectedFlag"></span>
<span class="country-label" id="{{ $id }}SelectedCountry">Select Nationality</span>
</span>
</button>
<div class="dropdown-menu p-2 w-100" aria-labelledby="{{ $id }}Dropdown" onclick="event.stopPropagation()">
<input type="text"
class="form-control form-control-sm mb-2"
placeholder="Search country..."
id="{{ $id }}Search"
onmousedown="event.stopPropagation()"
onfocus="event.stopPropagation()"
oninput="event.stopPropagation()"
onkeydown="event.stopPropagation()"
onkeyup="event.stopPropagation()">
<div class="country-list" id="{{ $id }}List" style="max-height: 300px; overflow-y: auto;">
<!-- Countries will be populated by JavaScript -->
</div>
</div>
<input type="hidden" id="{{ $id }}" name="{{ $name }}" value="{{ $value }}" {{ $required ? 'required' : '' }}>
</div>
@if($error)
<span class="invalid-feedback" role="alert">
<span class="invalid-feedback d-block" role="alert">
<strong>{{ $error }}</strong>
</span>
@endif
@once
@push('styles')
<style>
.country-dropdown-btn {
min-width: 150px;
display: flex;
align-items: center;
justify-content: space-between;
}
.country-list {
max-height: 300px;
overflow-y: auto;
}
.dropdown-item {
cursor: pointer;
}
.dropdown-item:hover {
background-color: #f8f9fa;
}
</style>
@endpush
@push('scripts')
<script>
document.addEventListener('DOMContentLoaded', function() {
// Country data for nationality
const countries = [
{ name: 'United States', flagCode: 'us' },
{ name: 'Canada', flagCode: 'ca' },
{ name: 'United Kingdom', flagCode: 'gb' },
{ name: 'United Arab Emirates', flagCode: 'ae' },
{ name: 'Saudi Arabia', flagCode: 'sa' },
{ name: 'Qatar', flagCode: 'qa' },
{ name: 'Kuwait', flagCode: 'kw' },
{ name: 'Bahrain', flagCode: 'bh' },
{ name: 'Oman', flagCode: 'om' },
{ name: 'Egypt', flagCode: 'eg' },
{ name: 'India', flagCode: 'in' },
{ name: 'Pakistan', flagCode: 'pk' },
{ name: 'Bangladesh', flagCode: 'bd' },
{ name: 'Malaysia', flagCode: 'my' },
{ name: 'Singapore', flagCode: 'sg' },
{ name: 'Japan', flagCode: 'jp' },
{ name: 'China', flagCode: 'cn' },
{ name: 'South Korea', flagCode: 'kr' },
{ name: 'Australia', flagCode: 'au' },
{ name: 'Germany', flagCode: 'de' },
{ name: 'France', flagCode: 'fr' },
{ name: 'Italy', flagCode: 'it' },
{ name: 'Spain', flagCode: 'es' },
{ name: 'Netherlands', flagCode: 'nl' },
{ name: 'Sweden', flagCode: 'se' },
{ name: 'Norway', flagCode: 'no' },
{ name: 'Denmark', flagCode: 'dk' },
{ name: 'Finland', flagCode: 'fi' },
{ name: 'Switzerland', flagCode: 'ch' },
{ name: 'Austria', flagCode: 'at' },
{ name: 'Poland', flagCode: 'pl' },
{ name: 'Czech Republic', flagCode: 'cz' },
{ name: 'Hungary', flagCode: 'hu' },
{ name: 'Romania', flagCode: 'ro' },
{ name: 'Greece', flagCode: 'gr' },
{ name: 'Turkey', flagCode: 'tr' },
{ name: 'Russia', flagCode: 'ru' },
{ name: 'Brazil', flagCode: 'br' },
{ name: 'Mexico', flagCode: 'mx' },
{ name: 'Argentina', flagCode: 'ar' },
{ name: 'Chile', flagCode: 'cl' },
{ name: 'Colombia', flagCode: 'co' },
{ name: 'South Africa', flagCode: 'za' },
{ name: 'Nigeria', flagCode: 'ng' },
{ name: 'Kenya', flagCode: 'ke' },
{ name: 'Sri Lanka', flagCode: 'lk' },
{ name: 'Vietnam', flagCode: 'vn' },
{ name: 'Thailand', flagCode: 'th' },
{ name: 'Indonesia', flagCode: 'id' },
{ name: 'Philippines', flagCode: 'ph' },
{ name: 'New Zealand', flagCode: 'nz' },
{ name: 'Portugal', flagCode: 'pt' },
{ name: 'Ireland', flagCode: 'ie' },
{ name: 'Israel', flagCode: 'il' },
{ name: 'Jordan', flagCode: 'jo' },
{ name: 'Lebanon', flagCode: 'lb' },
{ name: 'Iraq', flagCode: 'iq' },
];
// Initialize all nationality dropdowns on the page
document.querySelectorAll('.nationality-select').forEach(function(selectElement) {
if (typeof $ !== 'undefined' && $.fn.select2) {
const $select = $(selectElement);
$select.select2({
data: countries.map(country => ({
id: country.name,
text: country.name,
flagCode: country.flagCode
})),
templateResult: function(data) {
if (!data.id) return data.text;
return $(`<span><span class="fi fi-${data.flagCode} me-2"></span> ${data.text}</span>`);
},
templateSelection: function(data) {
if (!data.id) return data.text;
return $(`<span><span class="fi fi-${data.flagCode} me-2"></span> ${data.text}</span>`);
},
placeholder: 'Select Nationality',
allowClear: true,
width: '100%'
// Load countries from JSON file
fetch('/data/countries.json')
.then(response => response.json())
.then(countries => {
// Initialize only nationality dropdowns (not country code dropdowns)
document.querySelectorAll('[id$="nationalityList"]').forEach(function(listElement) {
const componentId = listElement.id.replace('List', '');
initializeNationalityDropdown(componentId, countries);
});
})
.catch(error => console.error('Error loading countries:', error));
// Restore value if provided
const initialValue = selectElement.getAttribute('data-value') || '{{ $value }}';
if (initialValue) {
$select.val(initialValue).trigger('change');
function initializeNationalityDropdown(componentId, countries) {
const countryList = document.getElementById(componentId + 'List');
if (!countryList) return;
// Clear existing items
countryList.innerHTML = '';
// Populate country dropdown
countries.forEach(country => {
const button = document.createElement('button');
button.className = 'dropdown-item d-flex align-items-center';
button.type = 'button';
button.setAttribute('data-country-name', country.name);
button.setAttribute('data-flag', country.flag);
button.setAttribute('data-search', country.name.toLowerCase());
// Convert flag code to emoji
const flagEmoji = country.iso2
.toUpperCase()
.split('')
.map(char => String.fromCodePoint(127397 + char.charCodeAt(0)))
.join('');
button.innerHTML = `
<span class="me-2">${flagEmoji}</span>
<span>${country.name}</span>
`;
button.addEventListener('click', function() {
selectNationality(componentId, country.name, flagEmoji);
});
countryList.appendChild(button);
});
// Search functionality
const searchInput = document.getElementById(componentId + 'Search');
if (searchInput) {
searchInput.addEventListener('input', function(e) {
const searchTerm = e.target.value.toLowerCase();
const items = countryList.querySelectorAll('.dropdown-item');
items.forEach(item => {
const searchText = item.getAttribute('data-search') || '';
if (searchText.includes(searchTerm)) {
item.classList.remove('d-none');
} else {
item.classList.add('d-none');
}
});
});
}
// Set initial value if provided
const hiddenInput = document.getElementById(componentId);
if (hiddenInput && hiddenInput.value) {
const initialCountry = countries.find(c => c.name === hiddenInput.value);
if (initialCountry) {
const flagEmoji = initialCountry.iso2
.toUpperCase()
.split('')
.map(char => String.fromCodePoint(127397 + char.charCodeAt(0)))
.join('');
selectNationality(componentId, initialCountry.name, flagEmoji);
}
}
});
}
function selectNationality(componentId, name, flag) {
const flagElement = document.getElementById(componentId + 'SelectedFlag');
const countryElement = document.getElementById(componentId + 'SelectedCountry');
const hiddenInput = document.getElementById(componentId);
if (flagElement) flagElement.textContent = flag + ' ';
if (countryElement) countryElement.textContent = name;
if (hiddenInput) hiddenInput.value = name;
// Close the dropdown after selection
const dropdownButton = document.getElementById(componentId + 'Dropdown');
if (dropdownButton) {
const dropdown = bootstrap.Dropdown.getInstance(dropdownButton);
if (dropdown) dropdown.hide();
}
}
});
</script>
@endpush
+33 -15
View File
@@ -12,14 +12,40 @@
<!-- Profile Picture Section -->
<div class="mb-4 text-center">
<div class="mb-3">
<img src="{{ $user->profile_picture ? asset('storage/' . $user->profile_picture) : asset('images/default-avatar.png') }}"
alt="Profile Picture"
class="rounded-circle"
style="width: 120px; height: 120px; object-fit: cover; border: 3px solid #dee2e6;">
@if($user->profile_picture && file_exists(public_path('storage/' . $user->profile_picture)))
<img src="{{ asset('storage/' . $user->profile_picture) }}"
alt="Profile Picture"
style="width: 300px; height: 400px; object-fit: cover; border: 3px solid #dee2e6; border-radius: 8px;">
@elseif(file_exists(public_path('storage/images/profiles/profile_' . $user->id . '.png')))
<img src="{{ asset('storage/images/profiles/profile_' . $user->id . '.png') }}"
alt="Profile Picture"
style="width: 300px; height: 400px; object-fit: cover; border: 3px solid #dee2e6; border-radius: 8px;">
@elseif(file_exists(public_path('storage/images/profiles/profile_' . $user->id . '.jpg')))
<img src="{{ asset('storage/images/profiles/profile_' . $user->id . '.jpg') }}"
alt="Profile Picture"
style="width: 300px; height: 400px; object-fit: cover; border: 3px solid #dee2e6; border-radius: 8px;">
@elseif(file_exists(public_path('storage/images/profiles/profile_' . $user->id . '.jpeg')))
<img src="{{ asset('storage/images/profiles/profile_' . $user->id . '.jpeg') }}"
alt="Profile Picture"
style="width: 300px; height: 400px; object-fit: cover; border: 3px solid #dee2e6; border-radius: 8px;">
@else
<div style="width: 300px; height: 400px; background-color: #f0f0f0; border: 3px solid #dee2e6; border-radius: 8px; display: flex; align-items: center; justify-content: center; margin: 0 auto;">
<div class="text-center">
<i class="bi bi-person-circle" style="font-size: 100px; color: #dee2e6;"></i>
<p class="text-muted mt-2">No profile picture</p>
</div>
</div>
@endif
</div>
<button type="button" class="btn btn-outline-primary btn-sm" data-bs-toggle="modal" data-bs-target="#profilePictureModal">
<i class="fas fa-camera"></i> Change Profile Picture
</button>
<x-takeone-cropper
id="profile_picture"
width="300"
height="400"
shape="square"
folder="images/profiles"
filename="profile_{{ $user->id }}"
uploadUrl="{{ route('profile.upload-picture') }}"
/>
</div>
<form method="POST" action="{{ route('profile.update') }}">
@@ -196,14 +222,6 @@
</div>
</div>
<!-- Profile Picture Upload Modal -->
<x-image-upload-modal
id="profilePictureModal"
aspectRatio="1"
maxSizeMB="1"
title="Upload Profile Picture"
uploadUrl="{{ route('profile.upload-picture') }}"
/>
</div>
@push('scripts')
+2 -2
View File
@@ -42,8 +42,8 @@
<div class="d-flex">
<!-- Profile Picture -->
<div style="width: 180px; min-height: 250px; border-radius: 0.375rem 0 0 0.375rem;">
@if($relationship->dependent->media_gallery[0] ?? false)
<img src="{{ $relationship->dependent->media_gallery[0] }}" alt="{{ $relationship->dependent->full_name }}" class="w-100 h-100" style="object-fit: cover; border-radius: 0.375rem 0 0 0.375rem;">
@if($relationship->dependent->profile_picture)
<img src="{{ asset('storage/' . $relationship->dependent->profile_picture) }}" alt="{{ $relationship->dependent->full_name }}" class="w-100 h-100" style="object-fit: cover; border-radius: 0.375rem 0 0 0.375rem;">
@else
<div class="w-100 h-100 d-flex align-items-center justify-content-center text-white fw-bold" style="font-size: 3rem; background: linear-gradient(135deg, {{ $relationship->dependent->gender == 'm' ? '#0d6efd 0%, #0a58ca 100%' : '#d63384 0%, #a61e4d 100%' }}); border-radius: 0.375rem 0 0 0.375rem;">
{{ strtoupper(substr($relationship->dependent->full_name, 0, 1)) }}
+3
View File
@@ -447,5 +447,8 @@
@vite(['resources/js/app.js'])
@stack('scripts')
<!-- Modals Stack (for cropper and other modals) -->
@stack('modals')
</body>
</html>
@@ -0,0 +1,156 @@
@php
$id = $attributes->get('id');
$width = $attributes->get('width', 300);
$height = $attributes->get('height', 300);
$shape = $attributes->get('shape', 'circle');
$folder = $attributes->get('folder', 'uploads');
$filename = $attributes->get('filename', 'cropped_' . time());
$uploadUrl = $attributes->get('uploadUrl', route('profile.upload-picture'));
@endphp
@once
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/cropme.min.css">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/cropme.min.js"></script>
<style>
.modal-content-clean { border: none; border-radius: 15px; overflow: hidden; }
.cropme-wrapper { overflow: hidden !important; border-radius: 8px; }
.cropme-slider { display: none !important; }
.takeone-canvas {
height: 400px;
background: #111;
border-radius: 8px;
position: relative;
border: 1px solid #222;
}
.custom-slider-label {
font-size: 0.75rem;
font-weight: bold;
text-transform: uppercase;
color: #6c757d;
letter-spacing: 0.5px;
}
.form-range::-webkit-slider-thumb { background: #198754; }
.form-range::-moz-range-thumb { background: #198754; }
</style>
@endonce
<button type="button" class="btn btn-success px-4 fw-bold shadow-sm" data-bs-toggle="modal" data-bs-target="#cropperModal_{{ $id }}">
Change Photo
</button>
@push('modals')
<div class="modal fade" id="cropperModal_{{ $id }}" tabindex="-1" aria-hidden="true" data-bs-backdrop="static">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content modal-content-clean shadow-lg">
<div class="modal-body p-4 text-start">
<div class="mb-3 d-flex align-items-center">
<input type="file" id="input_{{ $id }}" class="form-control form-control-sm" accept="image/*">
<button type="button" class="btn-close ms-2" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div id="box_{{ $id }}" class="takeone-canvas"></div>
<div class="row mt-4">
<div class="col-md-6 mb-3">
<label class="custom-slider-label d-block mb-2">Zoom Level</label>
<input type="range" class="form-range" id="zoom_{{ $id }}" min="0" max="100" step="1" value="0">
</div>
<div class="col-md-6 mb-3">
<label class="custom-slider-label d-block mb-2">Rotation</label>
<input type="range" class="form-range" id="rot_{{ $id }}" min="-180" max="180" step="1" value="0">
</div>
</div>
<div class="d-grid gap-2 mt-2">
<button type="button" class="btn btn-success btn-lg fw-bold py-3" id="save_{{ $id }}">
Crop & Save Image
</button>
</div>
</div>
</div>
</div>
</div>
<script>
$(function() {
let cropper_{{ $id }} = null;
const el_{{ $id }} = document.getElementById("box_{{ $id }}");
const zoomMin_{{ $id }} = 0.01;
const zoomMax_{{ $id }} = 3;
function applyTransform_{{ $id }}(instance) {
if (!instance.properties.image) return;
const p = instance.properties;
const t = `translate3d(${p.x}px, ${p.y}px, 0) scale(${p.scale}) rotate(${p.deg}deg)`;
p.image.style.transform = t;
}
$('#input_{{ $id }}').on('change', function() {
if (this.files && this.files[0]) {
const reader = new FileReader();
reader.onload = function(event) {
if (cropper_{{ $id }}) cropper_{{ $id }}.destroy();
cropper_{{ $id }} = new Cropme(el_{{ $id }}, {
container: { width: '100%', height: 400 },
viewport: {
width: {{ $width }},
height: {{ $height }},
type: '{{ $shape }}',
border: { enable: true, width: 2, color: '#fff' }
},
transformOrigin: 'viewport',
zoom: { min: zoomMin_{{ $id }}, max: zoomMax_{{ $id }}, enable: true, mouseWheel: true, slider: false },
rotation: { enable: true, slider: false }
});
cropper_{{ $id }}.bind({ url: event.target.result }).then(() => {
$('#zoom_{{ $id }}').val(0);
$('#rot_{{ $id }}').val(0);
});
};
reader.readAsDataURL(this.files[0]);
}
});
$('#zoom_{{ $id }}').on('input', function() {
if (!cropper_{{ $id }} || !cropper_{{ $id }}.properties.image) return;
const p = parseFloat($(this).val());
const scale = zoomMin_{{ $id }} + (zoomMax_{{ $id }} - zoomMin_{{ $id }}) * (p / 100);
cropper_{{ $id }}.properties.scale = Math.min(Math.max(scale, zoomMin_{{ $id }}), zoomMax_{{ $id }});
applyTransform_{{ $id }}(cropper_{{ $id }});
});
$('#rot_{{ $id }}').on('input', function() {
if (cropper_{{ $id }}) {
cropper_{{ $id }}.rotate(parseInt($(this).val(), 10));
}
});
$('#save_{{ $id }}').on('click', function() {
if (!cropper_{{ $id }}) return;
const btn = $(this);
btn.prop('disabled', true).text('Uploading...');
cropper_{{ $id }}.crop({ type: 'base64' }).then(base64 => {
$.post("{{ $uploadUrl }}", {
_token: "{{ csrf_token() }}",
image: base64,
folder: '{{ $folder }}',
filename: '{{ $filename }}'
}).done((res) => {
alert('Saved successfully!');
$('#cropperModal_{{ $id }}').modal('hide');
// Reload page to show new image
location.reload();
}).fail((err) => {
alert('Upload failed: ' + (err.responseJSON?.message || 'Unknown error'));
}).always(() => {
btn.prop('disabled', false).text('Crop & Save Image');
});
});
});
});
</script>
@endpush