affiliations

This commit is contained in:
ghassan committed 2026-01-27 16:34:46 +03:00
1 parent 4f3f9c9aad
commit 0b5b3dd3ee
177 files changed
+9660 -8531

No files matched your search

+187 -39
View File
@@ -5,9 +5,19 @@
$shape = $attributes->get('shape', 'circle');
$folder = $attributes->get('folder', 'uploads');
$filename = $attributes->get('filename', 'cropped_' . time());
$uploadUrl = $attributes->get('uploadUrl', route('profile.upload-picture'));
$uploadUrl = $attributes->get('uploadUrl', '');
$currentImage = $attributes->get('currentImage', '');
$buttonText = $attributes->get('buttonText', 'Change Photo');
$buttonClass = $attributes->get('buttonClass', 'btn btn-success px-4 fw-bold shadow-sm');
$mode = $attributes->get('mode', 'ajax'); // 'ajax' or 'form'
$inputName = $attributes->get('inputName', 'image'); // For form mode - the hidden input name
$previewWidth = $attributes->get('previewWidth', $width);
$previewHeight = $attributes->get('previewHeight', $height);
$showPreview = $attributes->get('showPreview', $mode === 'form'); // Show preview by default in form mode
@endphp
<x-toast-notification />
@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>
@@ -32,12 +42,78 @@
}
.form-range::-webkit-slider-thumb { background: #198754; }
.form-range::-moz-range-thumb { background: #198754; }
.cropper-preview-container {
position: relative;
display: inline-block;
}
.cropper-preview-placeholder {
display: flex;
align-items: center;
justify-content: center;
background-color: #f0f0f0;
border: 2px dashed #dee2e6;
color: #6c757d;
}
.cropper-preview-image {
object-fit: cover;
border: 2px solid #dee2e6;
}
.cropper-remove-btn {
position: absolute;
top: -8px;
right: -8px;
width: 24px;
height: 24px;
border-radius: 50%;
background: #dc3545;
color: white;
border: none;
font-size: 14px;
line-height: 1;
cursor: pointer;
display: none;
}
.cropper-preview-container.has-image .cropper-remove-btn {
display: flex;
align-items: center;
justify-content: center;
}
</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
@if($mode === 'form')
{{-- Form mode: Show preview and hidden input --}}
<div class="cropper-form-wrapper text-center" id="wrapper_{{ $id }}">
<div class="cropper-preview-container mb-2" id="previewContainer_{{ $id }}">
@if($currentImage)
<img src="{{ $currentImage }}"
id="preview_{{ $id }}"
class="cropper-preview-image"
style="width: {{ $previewWidth }}px; height: {{ $previewHeight }}px; border-radius: {{ $shape === 'circle' ? '50%' : '8px' }};">
<button type="button" class="cropper-remove-btn" id="removeBtn_{{ $id }}" onclick="removeImage_{{ $id }}()">
<i class="bi bi-x"></i>
</button>
@else
<div id="preview_{{ $id }}"
class="cropper-preview-placeholder"
style="width: {{ $previewWidth }}px; height: {{ $previewHeight }}px; border-radius: {{ $shape === 'circle' ? '50%' : '8px' }};">
<i class="bi bi-image" style="font-size: 2rem;"></i>
</div>
@endif
</div>
<input type="hidden" name="{{ $inputName }}" id="hiddenInput_{{ $id }}" value="">
<input type="hidden" name="{{ $inputName }}_folder" value="{{ $folder }}">
<input type="hidden" name="{{ $inputName }}_filename" value="{{ $filename }}">
<button type="button" class="{{ $buttonClass }}" data-bs-toggle="modal" data-bs-target="#cropperModal_{{ $id }}">
<i class="bi bi-camera me-2"></i>{{ $buttonText }}
</button>
</div>
@else
{{-- AJAX mode: Just the button --}}
<button type="button" class="{{ $buttonClass }}" data-bs-toggle="modal" data-bs-target="#cropperModal_{{ $id }}">
<i class="bi bi-camera me-2"></i>{{ $buttonText }}
</button>
@endif
@push('modals')
<div class="modal fade" id="cropperModal_{{ $id }}" tabindex="-1" aria-hidden="true" data-bs-backdrop="static">
@@ -64,7 +140,11 @@
<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
@if($mode === 'form')
Crop & Apply
@else
Crop & Save Image
@endif
</button>
</div>
</div>
@@ -78,6 +158,8 @@ $(function() {
const el_{{ $id }} = document.getElementById("box_{{ $id }}");
const zoomMin_{{ $id }} = 0.01;
const zoomMax_{{ $id }} = 3;
const currentImage_{{ $id }} = '{{ $currentImage }}';
const mode_{{ $id }} = '{{ $mode }}';
function applyTransform_{{ $id }}(instance) {
if (!instance.properties.image) return;
@@ -86,29 +168,40 @@ $(function() {
p.image.style.transform = t;
}
function initCropper_{{ $id }}(imageUrl) {
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: imageUrl }).then(() => {
$('#zoom_{{ $id }}').val(0);
$('#rot_{{ $id }}').val(0);
});
}
// Load current image when modal opens
$('#cropperModal_{{ $id }}').on('shown.bs.modal', function() {
if (currentImage_{{ $id }} && !cropper_{{ $id }}) {
initCropper_{{ $id }}(currentImage_{{ $id }});
}
});
$('#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);
});
initCropper_{{ $id }}(event.target.result);
};
reader.readAsDataURL(this.files[0]);
}
@@ -131,26 +224,81 @@ $(function() {
$('#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!');
if (mode_{{ $id }} === 'form') {
// Form mode: Store base64 in hidden input and update preview
btn.prop('disabled', true).text('Processing...');
cropper_{{ $id }}.crop({ type: 'base64' }).then(base64 => {
// Store in hidden input
$('#hiddenInput_{{ $id }}').val(base64);
// Update preview
const previewContainer = $('#previewContainer_{{ $id }}');
const borderRadius = '{{ $shape }}' === 'circle' ? '50%' : '8px';
previewContainer.html(`
<img src="${base64}"
id="preview_{{ $id }}"
class="cropper-preview-image"
style="width: {{ $previewWidth }}px; height: {{ $previewHeight }}px; border-radius: ${borderRadius};">
<button type="button" class="cropper-remove-btn" id="removeBtn_{{ $id }}" onclick="removeImage_{{ $id }}()">
<i class="bi bi-x"></i>
</button>
`);
previewContainer.addClass('has-image');
// Close modal
$('#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');
btn.prop('disabled', false).text('Crop & Apply');
});
});
} else {
// AJAX mode: Upload to server
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) => {
$('#cropperModal_{{ $id }}').modal('hide');
Toast.success('Photo Updated!', 'Your image has been saved successfully.');
// Reload page after toast shows
setTimeout(() => location.reload(), 1500);
}).fail((err) => {
Toast.error('Upload Failed', err.responseJSON?.message || 'An error occurred while uploading.');
}).always(() => {
btn.prop('disabled', false).text('Crop & Save Image');
});
});
}
});
// Remove image function for form mode
window.removeImage_{{ $id }} = function() {
const previewContainer = $('#previewContainer_{{ $id }}');
const borderRadius = '{{ $shape }}' === 'circle' ? '50%' : '8px';
// Clear hidden input
$('#hiddenInput_{{ $id }}').val('');
// Reset preview to placeholder
previewContainer.html(`
<div id="preview_{{ $id }}"
class="cropper-preview-placeholder"
style="width: {{ $previewWidth }}px; height: {{ $previewHeight }}px; border-radius: ${borderRadius};">
<i class="bi bi-image" style="font-size: 2rem;"></i>
</div>
`);
previewContainer.removeClass('has-image');
};
// Initialize remove button visibility if current image exists
@if($currentImage && $mode === 'form')
$('#previewContainer_{{ $id }}').addClass('has-image');
@endif
});
</script>
@endpush