changing profile
This commit is contained in:
parent
7bc32398ed
commit
b4a62485fb
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -222,11 +222,13 @@ class ProfileForm(forms.ModelForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Profile
|
model = Profile
|
||||||
fields = ['profile_picture', 'phone_number', 'country_code']
|
fields = ['profile_picture', 'phone_number', 'country_code', 'email_verified', 'phone_verified']
|
||||||
widgets = {
|
widgets = {
|
||||||
'profile_picture': forms.FileInput(attrs={'class': 'form-control'}),
|
'profile_picture': forms.FileInput(attrs={'class': 'form-control'}),
|
||||||
'phone_number': forms.TextInput(attrs={'class': 'form-control'}),
|
'phone_number': forms.TextInput(attrs={'class': 'form-control'}),
|
||||||
'country_code': forms.Select(attrs={'class': 'form-select'}),
|
'country_code': forms.Select(attrs={'class': 'form-select'}),
|
||||||
|
'email_verified': forms.HiddenInput(),
|
||||||
|
'phone_verified': forms.HiddenInput(),
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@ -246,6 +248,15 @@ class ProfileForm(forms.ModelForm):
|
|||||||
self.fields['last_name'].initial = user.last_name
|
self.fields['last_name'].initial = user.last_name
|
||||||
self.fields['email'].initial = user.email
|
self.fields['email'].initial = user.email
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
cleaned_data = super().clean()
|
||||||
|
email_verified = cleaned_data.get('email_verified')
|
||||||
|
phone_verified = cleaned_data.get('phone_verified')
|
||||||
|
|
||||||
|
if not email_verified and not phone_verified:
|
||||||
|
raise forms.ValidationError(_("At least one contact method (Email or Phone) must be verified."))
|
||||||
|
return cleaned_data
|
||||||
|
|
||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
profile = super().save(commit=False)
|
profile = super().save(commit=False)
|
||||||
user = profile.user
|
user = profile.user
|
||||||
|
|||||||
@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-01-25 03:08
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0028_profile_profile_picture'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='profile',
|
||||||
|
name='email_verified',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='profile',
|
||||||
|
name='phone_verified',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
||||||
Binary file not shown.
@ -68,6 +68,8 @@ class Profile(models.Model):
|
|||||||
|
|
||||||
# New Profile Picture field
|
# New Profile Picture field
|
||||||
profile_picture = models.ImageField(_('Profile Picture'), upload_to='profiles/', blank=True, null=True)
|
profile_picture = models.ImageField(_('Profile Picture'), upload_to='profiles/', blank=True, null=True)
|
||||||
|
email_verified = models.BooleanField(default=False)
|
||||||
|
phone_verified = models.BooleanField(default=False)
|
||||||
|
|
||||||
def is_expired(self):
|
def is_expired(self):
|
||||||
if self.subscription_plan == "NONE":
|
if self.subscription_plan == "NONE":
|
||||||
|
|||||||
@ -25,8 +25,10 @@
|
|||||||
<h4 class="mb-0"><i class="fa-solid fa-user-edit me-2"></i>{% trans "Edit Profile" %}</h4>
|
<h4 class="mb-0"><i class="fa-solid fa-user-edit me-2"></i>{% trans "Edit Profile" %}</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
<form method="post" enctype="multipart/form-data">
|
<form method="post" enctype="multipart/form-data" id="profileForm">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
{{ form.email_verified }}
|
||||||
|
{{ form.phone_verified }}
|
||||||
|
|
||||||
<div class="text-center mb-4">
|
<div class="text-center mb-4">
|
||||||
{% if profile.profile_picture %}
|
{% if profile.profile_picture %}
|
||||||
@ -58,7 +60,18 @@
|
|||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label fw-bold">{% trans "Email" %}</label>
|
<label class="form-label fw-bold">{% trans "Email" %}</label>
|
||||||
{{ form.email }}
|
<div class="input-group">
|
||||||
|
{{ form.email }}
|
||||||
|
<button class="btn btn-outline-secondary" type="button" id="verifyEmailBtn">
|
||||||
|
<span id="emailStatus">
|
||||||
|
{% if profile.email_verified %}
|
||||||
|
<i class="fa-solid fa-check-circle text-success"></i> {% trans "Verified" %}
|
||||||
|
{% else %}
|
||||||
|
{% trans "Verify" %}
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
{% if form.email.errors %}
|
{% if form.email.errors %}
|
||||||
<div class="text-danger small">{{ form.email.errors }}</div>
|
<div class="text-danger small">{{ form.email.errors }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -74,7 +87,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-md-8 mb-3">
|
<div class="col-md-8 mb-3">
|
||||||
<label class="form-label fw-bold">{% trans "Phone Number" %}</label>
|
<label class="form-label fw-bold">{% trans "Phone Number" %}</label>
|
||||||
{{ form.phone_number }}
|
<div class="input-group">
|
||||||
|
{{ form.phone_number }}
|
||||||
|
<button class="btn btn-outline-secondary" type="button" id="verifyPhoneBtn">
|
||||||
|
<span id="phoneStatus">
|
||||||
|
{% if profile.phone_verified %}
|
||||||
|
<i class="fa-solid fa-check-circle text-success"></i> {% trans "Verified" %}
|
||||||
|
{% else %}
|
||||||
|
{% trans "Verify" %}
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
{% if form.phone_number.errors %}
|
{% if form.phone_number.errors %}
|
||||||
<div class="text-danger small">{{ form.phone_number.errors }}</div>
|
<div class="text-danger small">{{ form.phone_number.errors }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -95,7 +119,7 @@
|
|||||||
<a href="{% url 'dashboard' %}" class="btn btn-outline-secondary">
|
<a href="{% url 'dashboard' %}" class="btn btn-outline-secondary">
|
||||||
<i class="fa-solid fa-arrow-left me-1"></i> {% trans "Back to Dashboard" %}
|
<i class="fa-solid fa-arrow-left me-1"></i> {% trans "Back to Dashboard" %}
|
||||||
</a>
|
</a>
|
||||||
<button type="submit" class="btn btn-primary px-4">
|
<button type="submit" class="btn btn-primary px-4" id="saveProfileBtn">
|
||||||
<i class="fa-solid fa-save me-1"></i> {% trans "Save Changes" %}
|
<i class="fa-solid fa-save me-1"></i> {% trans "Save Changes" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -134,4 +158,228 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- OTP Modal -->
|
||||||
|
<div class="modal fade" id="otpModal" tabindex="-1" aria-labelledby="otpModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-sm">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="otpModalLabel">{% trans "Enter OTP" %}</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p class="small text-muted" id="otpSentMsg"></p>
|
||||||
|
<input type="text" id="otpInput" class="form-control text-center" maxlength="6" placeholder="123456" style="letter-spacing: 5px; font-size: 1.5rem; font-weight: bold;">
|
||||||
|
<div id="otpError" class="text-danger small mt-2 d-none"></div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
|
||||||
|
<button type="button" class="btn btn-primary" id="confirmOtpBtn">{% trans "Verify" %}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_js %}
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const emailInput = document.getElementById('id_email');
|
||||||
|
const phoneInput = document.getElementById('id_phone_number');
|
||||||
|
const countryCodeInput = document.getElementById('id_country_code');
|
||||||
|
const emailVerifiedInput = document.getElementById('id_email_verified');
|
||||||
|
const phoneVerifiedInput = document.getElementById('id_phone_verified');
|
||||||
|
const verifyEmailBtn = document.getElementById('verifyEmailBtn');
|
||||||
|
const verifyPhoneBtn = document.getElementById('verifyPhoneBtn');
|
||||||
|
const emailStatus = document.getElementById('emailStatus');
|
||||||
|
const phoneStatus = document.getElementById('phoneStatus');
|
||||||
|
const saveBtn = document.getElementById('saveProfileBtn');
|
||||||
|
|
||||||
|
const otpModal = new bootstrap.Modal(document.getElementById('otpModal'));
|
||||||
|
const otpInput = document.getElementById('otpInput');
|
||||||
|
const confirmOtpBtn = document.getElementById('confirmOtpBtn');
|
||||||
|
const otpError = document.getElementById('otpError');
|
||||||
|
const otpSentMsg = document.getElementById('otpSentMsg');
|
||||||
|
|
||||||
|
let currentVerificationMethod = '';
|
||||||
|
let currentVerificationValue = '';
|
||||||
|
|
||||||
|
const initialEmail = emailInput.value;
|
||||||
|
const initialPhone = phoneInput.value;
|
||||||
|
const initialCountryCode = countryCodeInput.value;
|
||||||
|
|
||||||
|
function updateSaveButtonState() {
|
||||||
|
const isEmailVerified = emailVerifiedInput.value === 'True';
|
||||||
|
const isPhoneVerified = phoneVerifiedInput.value === 'True';
|
||||||
|
|
||||||
|
// Enable if at least one is verified
|
||||||
|
if (isEmailVerified || isPhoneVerified) {
|
||||||
|
saveBtn.disabled = false;
|
||||||
|
} else {
|
||||||
|
saveBtn.disabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emailInput.addEventListener('input', function() {
|
||||||
|
if (this.value !== initialEmail) {
|
||||||
|
emailVerifiedInput.value = 'False';
|
||||||
|
emailStatus.innerHTML = '{% trans "Verify" %}';
|
||||||
|
verifyEmailBtn.classList.remove('btn-success');
|
||||||
|
verifyEmailBtn.classList.add('btn-outline-secondary');
|
||||||
|
} else {
|
||||||
|
if ("{{ profile.email_verified|yesno:'true,false' }}" === "true") {
|
||||||
|
emailVerifiedInput.value = 'True';
|
||||||
|
emailStatus.innerHTML = '<i class="fa-solid fa-check-circle text-success"></i> {% trans "Verified" %}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateSaveButtonState();
|
||||||
|
});
|
||||||
|
|
||||||
|
phoneInput.addEventListener('input', function() {
|
||||||
|
if (this.value !== initialPhone) {
|
||||||
|
phoneVerifiedInput.value = 'False';
|
||||||
|
phoneStatus.innerHTML = '{% trans "Verify" %}';
|
||||||
|
verifyPhoneBtn.classList.remove('btn-success');
|
||||||
|
verifyPhoneBtn.classList.add('btn-outline-secondary');
|
||||||
|
} else {
|
||||||
|
if ("{{ profile.phone_verified|yesno:'true,false' }}" === "true" && countryCodeInput.value === initialCountryCode) {
|
||||||
|
phoneVerifiedInput.value = 'True';
|
||||||
|
phoneStatus.innerHTML = '<i class="fa-solid fa-check-circle text-success"></i> {% trans "Verified" %}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateSaveButtonState();
|
||||||
|
});
|
||||||
|
|
||||||
|
countryCodeInput.addEventListener('change', function() {
|
||||||
|
if (this.value !== initialCountryCode) {
|
||||||
|
phoneVerifiedInput.value = 'False';
|
||||||
|
phoneStatus.innerHTML = '{% trans "Verify" %}';
|
||||||
|
} else {
|
||||||
|
if ("{{ profile.phone_verified|yesno:'true,false' }}" === "true" && phoneInput.value === initialPhone) {
|
||||||
|
phoneVerifiedInput.value = 'True';
|
||||||
|
phoneStatus.innerHTML = '<i class="fa-solid fa-check-circle text-success"></i> {% trans "Verified" %}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateSaveButtonState();
|
||||||
|
});
|
||||||
|
|
||||||
|
verifyEmailBtn.addEventListener('click', function() {
|
||||||
|
if (emailVerifiedInput.value === 'True') return;
|
||||||
|
|
||||||
|
const email = emailInput.value;
|
||||||
|
if (!email) return;
|
||||||
|
|
||||||
|
verifyEmailBtn.disabled = true;
|
||||||
|
verifyEmailBtn.innerHTML = '<span class="spinner-border spinner-border-sm"></span>';
|
||||||
|
|
||||||
|
fetch("{% url 'send_otp_profile' %}", {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
method: 'email',
|
||||||
|
value: email
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
verifyEmailBtn.disabled = false;
|
||||||
|
verifyEmailBtn.innerHTML = '{% trans "Verify" %}';
|
||||||
|
if (data.success) {
|
||||||
|
currentVerificationMethod = 'email';
|
||||||
|
currentVerificationValue = email;
|
||||||
|
otpSentMsg.innerText = '{% trans "A code was sent to" %} ' + email;
|
||||||
|
otpInput.value = '';
|
||||||
|
otpError.classList.add('d-none');
|
||||||
|
otpModal.show();
|
||||||
|
} else {
|
||||||
|
alert(data.error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
verifyPhoneBtn.addEventListener('click', function() {
|
||||||
|
if (phoneVerifiedInput.value === 'True') return;
|
||||||
|
|
||||||
|
const phone = phoneInput.value;
|
||||||
|
const countryCode = countryCodeInput.value;
|
||||||
|
if (!phone) return;
|
||||||
|
|
||||||
|
verifyPhoneBtn.disabled = true;
|
||||||
|
verifyPhoneBtn.innerHTML = '<span class="spinner-border spinner-border-sm"></span>';
|
||||||
|
|
||||||
|
fetch("{% url 'send_otp_profile' %}", {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
method: 'phone',
|
||||||
|
value: phone,
|
||||||
|
country_code: countryCode
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
verifyPhoneBtn.disabled = false;
|
||||||
|
verifyPhoneBtn.innerHTML = '{% trans "Verify" %}';
|
||||||
|
if (data.success) {
|
||||||
|
currentVerificationMethod = 'phone';
|
||||||
|
currentVerificationValue = phone;
|
||||||
|
otpSentMsg.innerText = '{% trans "A code was sent to WhatsApp number" %} ' + countryCode + phone;
|
||||||
|
otpInput.value = '';
|
||||||
|
otpError.classList.add('d-none');
|
||||||
|
otpModal.show();
|
||||||
|
} else {
|
||||||
|
alert(data.error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
confirmOtpBtn.addEventListener('click', function() {
|
||||||
|
const otp = otpInput.value;
|
||||||
|
if (otp.length !== 6) return;
|
||||||
|
|
||||||
|
confirmOtpBtn.disabled = true;
|
||||||
|
confirmOtpBtn.innerHTML = '<span class="spinner-border spinner-border-sm"></span>';
|
||||||
|
|
||||||
|
fetch("{% url 'verify_otp_profile' %}", {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
method: currentVerificationMethod,
|
||||||
|
value: currentVerificationValue,
|
||||||
|
country_code: countryCodeInput.value,
|
||||||
|
code: otp
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
confirmOtpBtn.disabled = false;
|
||||||
|
confirmOtpBtn.innerHTML = '{% trans "Verify" %}';
|
||||||
|
if (data.success) {
|
||||||
|
if (currentVerificationMethod === 'email') {
|
||||||
|
emailVerifiedInput.value = 'True';
|
||||||
|
emailStatus.innerHTML = '<i class="fa-solid fa-check-circle text-success"></i> {% trans "Verified" %}';
|
||||||
|
} else {
|
||||||
|
phoneVerifiedInput.value = 'True';
|
||||||
|
phoneStatus.innerHTML = '<i class="fa-solid fa-check-circle text-success"></i> {% trans "Verified" %}';
|
||||||
|
}
|
||||||
|
otpModal.hide();
|
||||||
|
updateSaveButtonState();
|
||||||
|
} else {
|
||||||
|
otpError.innerText = data.error;
|
||||||
|
otpError.classList.remove('d-none');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initial check
|
||||||
|
updateSaveButtonState();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@ -35,4 +35,6 @@ urlpatterns = [
|
|||||||
path("admin/refund/<str:receipt_number>/", views.issue_refund, name="issue_refund"),
|
path("admin/refund/<str:receipt_number>/", views.issue_refund, name="issue_refund"),
|
||||||
path("admin/settings/", views.admin_app_settings, name="admin_app_settings"),
|
path("admin/settings/", views.admin_app_settings, name="admin_app_settings"),
|
||||||
path("api/chat/", views.chat_api, name="chat_api"),
|
path("api/chat/", views.chat_api, name="chat_api"),
|
||||||
|
path("profile/send-otp/", views.send_otp_profile, name="send_otp_profile"),
|
||||||
|
path("profile/verify-otp/", views.verify_otp_profile, name="verify_otp_profile"),
|
||||||
]
|
]
|
||||||
@ -877,4 +877,65 @@ def chat_api(request):
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Chat API Error: {str(e)}")
|
logger.error(f"Chat API Error: {str(e)}")
|
||||||
return JsonResponse({'success': False, 'error': str(e)})
|
return JsonResponse({'success': False, 'error': str(e)})
|
||||||
|
@login_required
|
||||||
|
@csrf_exempt
|
||||||
|
def send_otp_profile(request):
|
||||||
|
if request.method != 'POST':
|
||||||
|
return JsonResponse({'success': False, 'error': 'Method not allowed'}, status=405)
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = json.loads(request.body)
|
||||||
|
method = data.get('method') # 'email' or 'phone'
|
||||||
|
value = data.get('value')
|
||||||
|
|
||||||
|
if method == 'email':
|
||||||
|
otp = OTPCode.generate_code(email=value)
|
||||||
|
if send_otp_email(value, otp.code):
|
||||||
|
return JsonResponse({'success': True})
|
||||||
|
else:
|
||||||
|
return JsonResponse({'success': False, 'error': _('Failed to send email')})
|
||||||
|
elif method == 'phone':
|
||||||
|
country_code = data.get('country_code', '966')
|
||||||
|
full_phone = f"{country_code}{value}"
|
||||||
|
otp = OTPCode.generate_code(phone_number=full_phone)
|
||||||
|
msg = _("Your verification code is: %(code)s") % {"code": otp.code}
|
||||||
|
if send_whatsapp_message(full_phone, msg):
|
||||||
|
return JsonResponse({'success': True})
|
||||||
|
else:
|
||||||
|
return JsonResponse({'success': False, 'error': _('Failed to send WhatsApp message')})
|
||||||
|
|
||||||
|
return JsonResponse({'success': False, 'error': 'Invalid method'})
|
||||||
|
except Exception as e:
|
||||||
|
return JsonResponse({'success': False, 'error': str(e)})
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@csrf_exempt
|
||||||
|
def verify_otp_profile(request):
|
||||||
|
if request.method != 'POST':
|
||||||
|
return JsonResponse({'success': False, 'error': 'Method not allowed'}, status=405)
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = json.loads(request.body)
|
||||||
|
method = data.get('method')
|
||||||
|
value = data.get('value')
|
||||||
|
code = data.get('code')
|
||||||
|
|
||||||
|
if method == 'email':
|
||||||
|
otp_record = OTPCode.objects.filter(email=value, code=code, is_used=False).last()
|
||||||
|
elif method == 'phone':
|
||||||
|
country_code = data.get('country_code', '966')
|
||||||
|
full_phone = f"{country_code}{value}"
|
||||||
|
otp_record = OTPCode.objects.filter(phone_number=full_phone, code=code, is_used=False).last()
|
||||||
|
else:
|
||||||
|
return JsonResponse({'success': False, 'error': 'Invalid method'})
|
||||||
|
|
||||||
|
if otp_record and otp_record.is_valid():
|
||||||
|
otp_record.is_used = True
|
||||||
|
otp_record.save()
|
||||||
|
return JsonResponse({'success': True})
|
||||||
|
else:
|
||||||
|
return JsonResponse({'success': False, 'error': _('Invalid or expired code')})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return JsonResponse({'success': False, 'error': str(e)})
|
||||||
|
|||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user