diff --git a/booking_system/settings.py b/booking_system/settings.py
index c4a466d..d0c5741 100644
--- a/booking_system/settings.py
+++ b/booking_system/settings.py
@@ -139,9 +139,9 @@ EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = os.getenv('SMTP_HOST', 'smtp.hostinger.com')
EMAIL_PORT = int(os.getenv('SMTP_PORT', 465))
EMAIL_USE_SSL = True
-EMAIL_HOST_USER = os.getenv('SMTP_USERNAME', 'hello@attunehearttherapy.com')
+EMAIL_HOST_USER = os.getenv('SMTP_USERNAME', 'admin@attunehearttherapy.com')
EMAIL_HOST_PASSWORD = os.getenv('SMTP_PASSWORD')
-DEFAULT_FROM_EMAIL = os.getenv('SMTP_FROM', 'hello@attunehearttherapy.com')
+DEFAULT_FROM_EMAIL = os.getenv('SMTP_FROM', 'admin@attunehearttherapy.com')
REST_FRAMEWORK = {
diff --git a/booking_system/views.py b/booking_system/views.py
index 0da84f3..8ec9df0 100644
--- a/booking_system/views.py
+++ b/booking_system/views.py
@@ -8,6 +8,24 @@ def api_root(request, format=None):
base_url = request.build_absolute_uri('/api/')
endpoints = {
+ 'contact': {
+ 'description': 'Contact form submission endpoint',
+ 'base_path': '/api/auth/',
+ 'endpoints': {
+ 'contact': {
+ 'description': 'Submit a contact form',
+ 'url': request.build_absolute_uri('/api/auth/contact/'),
+ 'methods': ['POST'],
+ 'required_fields': ['name', 'email', 'phone', 'message'],
+ 'example_request': {
+ 'name': 'John Doe',
+ 'email': 'n8E5I@example.com',
+ 'phone': '+1234567890',
+ 'message': 'Hello, how can I help you?'
+ }
+ }
+ },
+ },
'authentication': {
'description': 'User authentication and management endpoints',
'base_path': '/api/auth/',
diff --git a/meetings/migrations/0002_alter_appointmentrequest_jitsi_room_id.py b/meetings/migrations/0002_alter_appointmentrequest_jitsi_room_id.py
new file mode 100644
index 0000000..3b5060a
--- /dev/null
+++ b/meetings/migrations/0002_alter_appointmentrequest_jitsi_room_id.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.2.8 on 2025-11-28 15:37
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('meetings', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='appointmentrequest',
+ name='jitsi_room_id',
+ field=models.CharField(blank=True, default=None, help_text='Jitsi room ID', max_length=100, null=True, unique=True),
+ ),
+ ]
diff --git a/templates/emails/admin_contact_notification.html b/templates/emails/admin_contact_notification.html
new file mode 100644
index 0000000..f1c8c8e
--- /dev/null
+++ b/templates/emails/admin_contact_notification.html
@@ -0,0 +1,105 @@
+
+
+
+
+
+ New Contact Form Submission
+
+
+
+
+
+
+
+
+
+
+ 🔔 New Contact Message
+
+
+ Someone just reached out to you
+
+ |
+
+
+
+
+
+
+
+
+ Contact Information
+
+ |
+
+
+ |
+ Name:
+ |
+
+ {{ contact_message.name }}
+ |
+
+
+ |
+ Email:
+ |
+
+
+ {{ contact_message.email }}
+
+ |
+
+ {% if contact_message.phone %}
+
+ |
+ Phone:
+ |
+
+
+ {{ contact_message.phone }}
+
+ |
+
+ {% endif %}
+
+ |
+ Received:
+ |
+
+ {{ contact_message.created_at|date:"F d, Y" }} at {{ contact_message.created_at|time:"h:i A" }}
+ |
+
+
+
+
+
+ Message
+
+
+ {{ contact_message.message }}
+
+
+
+
+ |
+
+
+
+ |
+
+ This is an automated notification from your contact form.
+ Please respond to the sender as soon as possible.
+
+ |
+
+
+ |
+
+
+
+
\ No newline at end of file
diff --git a/templates/emails/user_contact_confirmation.html b/templates/emails/user_contact_confirmation.html
new file mode 100644
index 0000000..244c077
--- /dev/null
+++ b/templates/emails/user_contact_confirmation.html
@@ -0,0 +1,80 @@
+
+
+
+
+
+ Thank You for Contacting Us
+
+
+
+
+
+
+
+
+
+ ✨ Thank You!
+
+
+ We've received your message
+
+ |
+
+
+
+
+
+
+ Hi {{ contact_message.name }},
+
+
+
+ Thank you for reaching out to us. We've received your message and our team will review it shortly. We typically respond within 24-48 hours.
+
+
+
+ Your Message:
+
+ {{ contact_message.message }}
+
+
+
+ 📧 We'll reply to:
+
+
+ {{ contact_message.email }}
+
+
+
+
+ If you have any urgent questions in the meantime, please don't hesitate to reach out to us directly.
+
+ |
+
+
+ |
+
+ Need immediate assistance?
+
+
+ Email us at {{support_email}}
+ or call us at +1 (754) 816-2311
+
+
+
+
+ © {{ current_year }} {{ company_name }}. All rights reserved.
+
+
+ |
+
+
+ |
+
+
+
+
\ No newline at end of file
diff --git a/users/admin.py b/users/admin.py
index 0c3609f..6abbec7 100644
--- a/users/admin.py
+++ b/users/admin.py
@@ -1,7 +1,5 @@
from django.contrib import admin
-from .models import CustomUser, UserProfile
-
-# Register your models here.
+from .models import CustomUser, UserProfile, ContactMessage
@admin.register(CustomUser)
class UserAdmin(admin.ModelAdmin):
@@ -17,3 +15,41 @@ class UserProfileAdmin(admin.ModelAdmin):
ordering = ('user__email',)
+
+@admin.register(ContactMessage)
+class ContactMessageAdmin(admin.ModelAdmin):
+ list_display = ['name', 'email', 'phone', 'created_at', 'is_read', 'is_responded']
+ list_filter = ['is_read', 'is_responded', 'created_at']
+ search_fields = ['name', 'email', 'phone', 'message']
+ readonly_fields = ['created_at']
+ date_hierarchy = 'created_at'
+
+ fieldsets = (
+ ('Contact Information', {
+ 'fields': ('name', 'email', 'phone')
+ }),
+ ('Message', {
+ 'fields': ('message',)
+ }),
+ ('Status', {
+ 'fields': ('is_read', 'is_responded', 'created_at')
+ }),
+ )
+
+ def get_queryset(self, request):
+ qs = super().get_queryset(request)
+ return qs.select_related()
+
+ actions = ['mark_as_read', 'mark_as_responded']
+
+ def mark_as_read(self, request, queryset):
+ updated = queryset.update(is_read=True)
+ self.message_user(request, f'{updated} message(s) marked as read.')
+ mark_as_read.short_description = "Mark selected as read"
+
+ def mark_as_responded(self, request, queryset):
+ updated = queryset.update(is_responded=True)
+ self.message_user(request, f'{updated} message(s) marked as responded.')
+ mark_as_responded.short_description = "Mark selected as responded"
+
+
diff --git a/users/migrations/0002_contactmessage.py b/users/migrations/0002_contactmessage.py
new file mode 100644
index 0000000..8525ae5
--- /dev/null
+++ b/users/migrations/0002_contactmessage.py
@@ -0,0 +1,31 @@
+# Generated by Django 5.2.8 on 2025-11-28 15:37
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('users', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='ContactMessage',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=255)),
+ ('email', models.EmailField(max_length=254)),
+ ('phone', models.CharField(blank=True, max_length=20)),
+ ('message', models.TextField()),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('is_read', models.BooleanField(default=False)),
+ ('is_responded', models.BooleanField(default=False)),
+ ],
+ options={
+ 'verbose_name': 'Contact Message',
+ 'verbose_name_plural': 'Contact Messages',
+ 'ordering': ['-created_at'],
+ },
+ ),
+ ]
diff --git a/users/models.py b/users/models.py
index ac1f77f..93fe49b 100644
--- a/users/models.py
+++ b/users/models.py
@@ -37,4 +37,22 @@ class UserProfile(models.Model):
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
- return f"{self.user.email} Profile"
\ No newline at end of file
+ return f"{self.user.email} Profile"
+
+
+class ContactMessage(models.Model):
+ name = models.CharField(max_length=255)
+ email = models.EmailField()
+ phone = models.CharField(max_length=20, blank=True)
+ message = models.TextField()
+ created_at = models.DateTimeField(auto_now_add=True)
+ is_read = models.BooleanField(default=False)
+ is_responded = models.BooleanField(default=False)
+
+ class Meta:
+ ordering = ['-created_at']
+ verbose_name = 'Contact Message'
+ verbose_name_plural = 'Contact Messages'
+
+ def __str__(self):
+ return f"{self.name} - {self.email} - {self.created_at.strftime('%Y-%m-%d')}"
diff --git a/users/serializers.py b/users/serializers.py
index 55a2108..ce01757 100644
--- a/users/serializers.py
+++ b/users/serializers.py
@@ -1,6 +1,6 @@
from rest_framework import serializers
from django.contrib.auth.password_validation import validate_password
-from .models import CustomUser, UserProfile
+from .models import CustomUser, UserProfile, ContactMessage
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
@@ -53,4 +53,23 @@ class ResetPasswordSerializer(serializers.Serializer):
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = CustomUser
- fields = ('id', 'email', 'first_name', 'last_name', 'phone_number', 'isVerified', 'date_joined', 'last_login', 'is_staff', 'is_superuser', 'is_active')
\ No newline at end of file
+ fields = ('id', 'email', 'first_name', 'last_name', 'phone_number', 'isVerified', 'date_joined', 'last_login', 'is_staff', 'is_superuser', 'is_active')
+
+from rest_framework import serializers
+from .models import ContactMessage
+
+class ContactMessageSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = ContactMessage
+ fields = ['id', 'name', 'email', 'phone', 'message', 'created_at']
+ read_only_fields = ['id', 'created_at']
+
+ def validate_name(self, value):
+ if len(value.strip()) < 2:
+ raise serializers.ValidationError("Name must be at least 2 characters long.")
+ return value.strip()
+
+ def validate_message(self, value):
+ if len(value.strip()) < 10:
+ raise serializers.ValidationError("Message must be at least 10 characters long.")
+ return value.strip()
diff --git a/users/urls.py b/users/urls.py
index 42984ec..34d68f6 100644
--- a/users/urls.py
+++ b/users/urls.py
@@ -3,6 +3,8 @@ from rest_framework_simplejwt.views import TokenRefreshView
from . import views
urlpatterns = [
+ path('contact/', views.ContactMessageView.as_view(), name='contact-message'),
+
path('register/', views.register_user, name='register'),
path('login/', views.login_user, name='login'),
diff --git a/users/utils.py b/users/utils.py
index 44d8137..e068ce0 100644
--- a/users/utils.py
+++ b/users/utils.py
@@ -6,6 +6,9 @@ from django.conf import settings
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
from django.utils.html import strip_tags
+import logging
+
+logger = logging.getLogger(__name__)
def generate_otp():
return str(random.randint(100000, 999999))
@@ -54,4 +57,49 @@ def send_otp_via_email(email, otp, user_name=None, context='registration'):
def is_otp_expired(otp_expiry):
if otp_expiry and timezone.now() < otp_expiry:
return False
- return True
\ No newline at end of file
+ return True
+
+def send_email_notifications(contact_message):
+ try:
+ send_admin_notification(contact_message)
+
+
+ send_user_confirmation(contact_message)
+
+ except Exception as e:
+ logger.error(f"Error sending email notifications: {str(e)}")
+
+def send_admin_notification(contact_message):
+ subject = f"New Contact Form Submission from {contact_message.name}"
+
+ html_content = render_to_string('emails/admin_contact_notification.html', {
+ 'contact_message': contact_message
+ })
+
+ email = EmailMultiAlternatives(
+ subject=subject,
+ body=html_content,
+ from_email=settings.DEFAULT_FROM_EMAIL,
+ to=[settings.DEFAULT_FROM_EMAIL]
+ )
+ email.content_subtype = 'html'
+ email.send()
+
+def send_user_confirmation(self, contact_message):
+ subject = "Thank you for contacting us"
+
+ html_content = render_to_string('emails/user_contact_confirmation.html', {
+ 'contact_message': contact_message,
+ 'company_name': 'Attune Heart Therapy',
+ 'support_email': 'admin@attunehearttherapy.com',
+ 'current_year': timezone.now().year,
+ })
+
+ email = EmailMultiAlternatives(
+ subject=subject,
+ body=html_content,
+ from_email=settings.DEFAULT_FROM_EMAIL,
+ to=[contact_message.email]
+ )
+ email.content_subtype = 'html'
+ email.send()
diff --git a/users/views.py b/users/views.py
index 5f8cea2..c088f75 100644
--- a/users/views.py
+++ b/users/views.py
@@ -1,16 +1,51 @@
from rest_framework import status, generics
from rest_framework.decorators import api_view, permission_classes
+from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import AllowAny, IsAuthenticated, IsAdminUser
from rest_framework_simplejwt.tokens import RefreshToken
from django.contrib.auth import authenticate
-from .models import CustomUser, UserProfile
-from .serializers import UserRegistrationSerializer, UserSerializer, ResetPasswordSerializer, ForgotPasswordSerializer, VerifyPasswordResetOTPSerializer
-from .utils import send_otp_via_email, is_otp_expired, generate_otp
+from .models import CustomUser, UserProfile, ContactMessage
+from .serializers import UserRegistrationSerializer, UserSerializer, ResetPasswordSerializer, ForgotPasswordSerializer, VerifyPasswordResetOTPSerializer, ContactMessageSerializer
+from .utils import send_otp_via_email, is_otp_expired, generate_otp,send_email_notifications
from django.utils import timezone
from datetime import timedelta
from rest_framework.reverse import reverse
from meetings.models import AppointmentRequest
+import logging
+
+logger = logging.getLogger(__name__)
+
+class ContactMessageView(APIView):
+ def post(self, request):
+ serializer = ContactMessageSerializer(data=request.data)
+
+ if serializer.is_valid():
+ try:
+ contact_message = serializer.save()
+
+ send_email_notifications(contact_message)
+
+ return Response({
+ 'success': True,
+ 'message': 'Thank you for your message. We will get back to you soon!',
+ 'data': serializer.data
+ }, status=status.HTTP_201_CREATED)
+
+ except Exception as e:
+ logger.error(f"Error processing contact form: {str(e)}")
+ return Response({
+ 'success': False,
+ 'message': 'There was an error processing your request. Please try again later.'
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+ return Response({
+ 'success': False,
+ 'message': 'Please check your input and try again.',
+ 'errors': serializer.errors
+ }, status=status.HTTP_400_BAD_REQUEST)
+
+
@api_view(['POST'])