From 6164c69c7a57f0b1c083b31a9aef17d087c4a9c1 Mon Sep 17 00:00:00 2001 From: nawalr <168575969+nawalragih@users.noreply.github.com> Date: Tue, 12 Nov 2024 13:32:01 -0500 Subject: [PATCH 01/10] adding email confirmation --- Backend/__init__.py | 0 Backend/accounts/accounts/settings.py | 13 ++++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 Backend/__init__.py diff --git a/Backend/__init__.py b/Backend/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Backend/accounts/accounts/settings.py b/Backend/accounts/accounts/settings.py index 3f1636b1..55dbb1e8 100644 --- a/Backend/accounts/accounts/settings.py +++ b/Backend/accounts/accounts/settings.py @@ -40,6 +40,7 @@ 'django.contrib.messages', 'django.contrib.staticfiles', 'profiles', + 'appointments' 'django.contrib.sites', 'allauth', 'allauth.account', @@ -165,4 +166,14 @@ ACCOUNT_USERNAME_REQUIRED = True MEDIA_URL = '/media/' -MEDIA_ROOT = os.path.join(BASE_DIR, 'media') \ No newline at end of file +MEDIA_ROOT = os.path.join(BASE_DIR, 'media') + +# Email backend configuration +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +EMAIL_HOST = 'smtp.gmail.com' +EMAIL_PORT = 587 +EMAIL_USE_TLS = True +EMAIL_HOST_USER = 'noreplystyle@gmail.com' # email address where emails sent from +EMAIL_HOST_PASSWORD = 'nsbrese!!24' # password +# Default 'From' email for outgoing emails +DEFAULT_FROM_EMAIL = 'noreply@gmail.com' \ No newline at end of file From 7de9042b426067cef9e8a931bb8fda60bd60a2fb Mon Sep 17 00:00:00 2001 From: nawalr <168575969+nawalragih@users.noreply.github.com> Date: Tue, 12 Nov 2024 13:33:25 -0500 Subject: [PATCH 02/10] fixed appointment database and email confirmation also moved the directory for better reading --- Backend/accounts/appointments/__init__.py | 0 Backend/{ => accounts}/appointments/models.py | 1 + Backend/{ => accounts}/appointments/urls.py | 0 Backend/accounts/appointments/views.py | 129 ++++++++++++++++++ 4 files changed, 130 insertions(+) create mode 100644 Backend/accounts/appointments/__init__.py rename Backend/{ => accounts}/appointments/models.py (90%) rename Backend/{ => accounts}/appointments/urls.py (100%) create mode 100644 Backend/accounts/appointments/views.py diff --git a/Backend/accounts/appointments/__init__.py b/Backend/accounts/appointments/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Backend/appointments/models.py b/Backend/accounts/appointments/models.py similarity index 90% rename from Backend/appointments/models.py rename to Backend/accounts/appointments/models.py index 5fb94f36..aa4285b8 100644 --- a/Backend/appointments/models.py +++ b/Backend/accounts/appointments/models.py @@ -7,6 +7,7 @@ class Appointment(models.Model): date = models.DateField() time = models.TimeField() address = models.CharField(max_length=255) + email = models.EmailField(max_length=255) def __str__(self): return f"{self.business_name} - {self.service}" diff --git a/Backend/appointments/urls.py b/Backend/accounts/appointments/urls.py similarity index 100% rename from Backend/appointments/urls.py rename to Backend/accounts/appointments/urls.py diff --git a/Backend/accounts/appointments/views.py b/Backend/accounts/appointments/views.py new file mode 100644 index 00000000..cc0e0212 --- /dev/null +++ b/Backend/accounts/appointments/views.py @@ -0,0 +1,129 @@ +import json +import os +import logging +import firebase_admin +from django.http import JsonResponse +from django.views.decorators.csrf import csrf_exempt +from django.views.decorators.http import require_http_methods +from django.core.exceptions import ObjectDoesNotExist +from django.core.mail import send_mail +from firebase_admin import auth, credentials, firestore +from django.conf import settings +from decouple import config +from pathlib import Path + +BASE_DIR = Path(__file__).resolve().parent.parent +cred_path = config('FIREBASE_SERVICE_ACCOUNT_KEY') +cred = credentials.Certificate(os.path.join(BASE_DIR, 'keys', cred_path)) +firebase_admin.initialize_app(cred) + +# Initialize Firestore +db = firestore.client() + +# Set up logging +logger = logging.getLogger(__name__) + +# Fetch all appointments +@require_http_methods(["GET"]) +def get_appointments(request): + appointments = Appointment.objects.all().values() + return JsonResponse(list(appointments), safe=False) + +# Create a new appointment +@csrf_exempt +@require_http_methods(["POST"]) +def create_appointment(request): + try: + # Verify the Firebase token + token = request.headers.get('Authorization').split('Bearer ')[-1] + decoded_token = auth.verify_id_token(token) + user_email = decoded_token.get('email') + + if not user_email: + return JsonResponse({"error": "Email not found in Firebase user data"}, status=400) + + data = json.loads(request.body) + + # Check for missing keys + required_keys = ['business_name', 'service', 'amount_due', 'date', 'time', 'address'] + for key in required_keys: + if key not in data: + return JsonResponse({"error": f"Missing key: {key}"}, status=400) + + appointment = Appointment.objects.create( + business_name=data['business_name'], + service=data['service'], + amount_due=data['amount_due'], + date=data['date'], + time=data['time'], + address=data['address'] + ) + + # email confirmation + send_mail( + subject='Appointment Confirmation', + message=f"Hello, your appointment for {appointment.service} on {appointment.date} at {appointment.time} has been booked.", + from_email='noreplystyle@gmail.com', + recipient_list=[user_email], e + fail_silently=False, + ) + + return JsonResponse({"id": appointment.id, "success": "Appointment created"}, status=201) + + except KeyError as e: + logger.error(f"Missing key: {str(e)}") + return JsonResponse({"error": f"Missing key: {str(e)}"}, status=400) + except Exception as e: + logger.error(f"Unexpected error: {str(e)}") + return JsonResponse({"error": "An unexpected error occurred"}, status=500) + +# Retrieve a specific appointment +@require_http_methods(["GET"]) +def get_appointment(request, id): + try: + appointment = Appointment.objects.get(id=id) + return JsonResponse(appointment.__dict__) + except ObjectDoesNotExist: + return JsonResponse({"error": "Appointment not found"}, status=404) + +# Update an appointment +@csrf_exempt +@require_http_methods(["PUT"]) +def update_appointment(request, id): + try: + data = json.loads(request.body) + + # Update appointment in the database + updated_count = Appointment.objects.filter(id=id).update( + business_name=data['business_name'], + service=data['service'], + amount_due=data['amount_due'], + date=data['date'], + time=data['time'], + address=data['address'] + ) + + if updated_count == 0: + return JsonResponse({"error": "Appointment not found"}, status=404) + + return JsonResponse({"success": "Appointment updated"}, status=200) + + except KeyError as e: + logger.error(f"Missing key: {str(e)}") + return JsonResponse({"error": f"Missing key: {str(e)}"}, status=400) + except Exception as e: + logger.error(f"Unexpected error: {str(e)}") + return JsonResponse({"error": "An unexpected error occurred"}, status=500) + +# Delete an appointment +@csrf_exempt +@require_http_methods(["DELETE"]) +def delete_appointment(request, id): + try: + deleted_count, _ = Appointment.objects.filter(id=id).delete() + if deleted_count == 0: + return JsonResponse({"error": "Appointment not found"}, status=404) + return JsonResponse({"success": "Appointment deleted"}, status=204) + except Exception as e: + logger.error(f"Unexpected error: {str(e)}") + return JsonResponse({"error": str(e)}, status=500) From dfc0a3f462b94fcc8c2734d16bcb088cb2075fcc Mon Sep 17 00:00:00 2001 From: nawalr <168575969+nawalragih@users.noreply.github.com> Date: Tue, 12 Nov 2024 13:33:38 -0500 Subject: [PATCH 03/10] Delete views.py --- Backend/appointments/views.py | 55 ----------------------------------- 1 file changed, 55 deletions(-) delete mode 100644 Backend/appointments/views.py diff --git a/Backend/appointments/views.py b/Backend/appointments/views.py deleted file mode 100644 index 77e77db2..00000000 --- a/Backend/appointments/views.py +++ /dev/null @@ -1,55 +0,0 @@ -from django.http import JsonResponse -from django.views.decorators.csrf import csrf_exempt -from .models import Appointment -import json - -# Fetch all appointments -def get_appointments(request): - appointments = Appointment.objects.all().values() - return JsonResponse(list(appointments), safe=False) - -# Create a new appointment -@csrf_exempt -def create_appointment(request): - if request.method == 'POST': - data = json.loads(request.body) - appointment = Appointment.objects.create( - business_name=data['business_name'], - service=data['service'], - amount_due=data['amount_due'], - date=data['date'], - time=data['time'], - address=data['address'] - ) - return JsonResponse({"id": appointment.id}, status=201) - -# Retrieve a specific appointment -def get_appointment(request, id): - appointment = Appointment.objects.filter(id=id).values().first() - if appointment: - return JsonResponse(appointment) - return JsonResponse({"error": "Appointment not found"}, status=404) - -# Update an appointment -@csrf_exempt -def update_appointment(request, id): - if request.method == 'PUT': - data = json.loads(request.body) - Appointment.objects.filter(id=id).update( - business_name=data['business_name'], - service=data['service'], - amount_due=data['amount_due'], - date=data['date'], - time=data['time'], - address=data['address'] - ) - return JsonResponse({"success": "Appointment updated"}, status=200) - return JsonResponse({"error": "Invalid method"}, status=405) - -# Delete an appointment -@csrf_exempt -def delete_appointment(request, id): - if request.method == 'DELETE': - Appointment.objects.filter(id=id).delete() - return JsonResponse({"success": "Appointment deleted"}, status=204) - return JsonResponse({"error": "Invalid method"}, status=405) From 543861d488bc76246ef8c9325d19204003601353 Mon Sep 17 00:00:00 2001 From: nawalr <168575969+nawalragih@users.noreply.github.com> Date: Tue, 12 Nov 2024 15:44:47 -0500 Subject: [PATCH 04/10] saving progress --- src/app/portfolio/page.tsx | 346 +++++++++++++++++++---------------- src/app/userprofile/page.tsx | 34 +++- 2 files changed, 220 insertions(+), 160 deletions(-) diff --git a/src/app/portfolio/page.tsx b/src/app/portfolio/page.tsx index 01383393..39200579 100644 --- a/src/app/portfolio/page.tsx +++ b/src/app/portfolio/page.tsx @@ -57,39 +57,37 @@ export default function Portfolio() { setServices(updatedServices); }; - const handleSubmit = async (e: React.FormEvent) => { + const handleSubmit = async (e:React.FormEvent) => { e.preventDefault(); - const formData = new FormData(); - formData.append('business_name', businessName); - formData.append('bio', bio); - if (profilePicture) formData.append('profile_picture', profilePicture); - photos.forEach(photo => { - formData.append('photos', photo); // Adjust this based on your API's expected structure - }); - - services.forEach((service, index) => { - formData.append(`services[${index}][name]`, service.name); - formData.append(`services[${index}][price]`, service.price); - formData.append(`services[${index}][time]`, service.time); - }); + const requestData = { + business_name: businessName, + service: services.map(service => `${service.name} (${service.price} - ${service.time})`).join(', '), + amount_due: services.reduce((total, service) => total + parseFloat(service.price || '0'), 0), + date: '', + time: '', + address: '', + }; try { const response = await fetch('/api/profile', { method: 'POST', - body: formData, + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(requestData), }); if (response.ok) { - alert('Portfolio saved successfully!'); - router.push('/appointments'); + const responseData = await response.json(); + console.log('Appointment created:', responseData); + router.push('/homepage'); // Redirect to homepage after success } else { - const error = await response.json(); - alert(`Error: ${error.message}`); + const errorData = await response.json(); + console.error('Error creating appointment:', errorData); } } catch (error) { - console.error('Error saving portfolio:', error); - alert('Failed to save portfolio. Please try again.'); + console.error('Unexpected error:', error); } }; @@ -98,150 +96,188 @@ export default function Portfolio() { }; return ( -
- -
- setBusinessName(e.target.value)} - required - /> -
+
+ +
+ setBusinessName(e.target.value)} + required + /> +
-
- {/* Carousel Section */} -
- {/* Profile Picture Upload */} -
- -
- {profilePicture ? ( - Profile - ) : ( -
- )} -
- Upload Icon +
+ {/* Carousel Section */} +
+ {/* Profile Picture Upload */} +
+ +
+ {profilePicture ? ( + Profile + ) : ( +
+ )} +
+ Upload Icon +
-
- - {/* Portfolio Photos Upload*/} - - - - {/* Description Section */} -
-

Description

-