Skip to content

Commit

Permalink
(#238) Profile picture (#237)
Browse files Browse the repository at this point in the history
* profile picture

* requirements fix

* adding navbar image

* edits

* edit

* edit

* gitignore

* fix
  • Loading branch information
shub-garg authored Dec 4, 2024
1 parent 51947be commit 3db03e8
Show file tree
Hide file tree
Showing 12 changed files with 274 additions and 69 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,4 @@ GitHub.sublime-settings
.env
ps_env
.aider*
src/staticfiles
src/staticfiles
1 change: 1 addition & 0 deletions src/accounts/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class CustomUserAdmin(UserAdmin):
"last_name",
"user_type",
"is_staff",
"profile_image_url", # Display profile image URL
)

# Fields to filter by in the list view
Expand Down
46 changes: 44 additions & 2 deletions src/accounts/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,15 @@ class UserRegisterForm(UserCreationForm):
}
),
)
profile_image = forms.ImageField(
required=False,
widget=forms.ClearableFileInput(
attrs={
"class": "w-full p-2 rounded bg-gray-700 text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 h-12 border border-gray-600",
}
),
help_text="Optional. Upload a profile image.",
)

class Meta:
model = CustomUser
Expand All @@ -91,6 +100,7 @@ class Meta:
"password1",
"password2",
"user_type",
"profile_image", # Add this field
]


Expand Down Expand Up @@ -199,9 +209,25 @@ def get_invalid_login_error(self):


class ServiceSeekerForm(forms.ModelForm):
profile_image = forms.ImageField(
required=False,
widget=forms.ClearableFileInput(
attrs={
"class": "w-full p-2 rounded bg-gray-700 text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 h-12 border border-gray-600",
}
),
help_text="Upload a new profile image or leave blank to keep the current one.",
)
remove_profile_image = forms.BooleanField(
required=False,
initial=False,
label="Remove profile image",
help_text="Check this box to remove your current profile image.",
)

class Meta:
model = CustomUser
fields = ["username", "email", "first_name", "last_name"]
fields = ["username", "email", "first_name", "last_name", "profile_image"]
widgets = {
"username": forms.TextInput(
attrs={
Expand Down Expand Up @@ -235,9 +261,25 @@ class Meta:
# Note: We're maintaining two different forms (even though they're the same) for seekers and providers to future-proof our code.
# We *might* add additional fields for service providers soon.
class ServiceProviderForm(forms.ModelForm):
profile_image = forms.ImageField(
required=False,
widget=forms.ClearableFileInput(
attrs={
"class": "w-full p-2 rounded bg-gray-700 text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 h-12 border border-gray-600",
}
),
help_text="Upload a new profile image or leave blank to keep the current one.",
)
remove_profile_image = forms.BooleanField(
required=False,
initial=False,
label="Remove profile image",
help_text="Check this box to remove your current profile image.",
)

class Meta:
model = CustomUser
fields = ["username", "email", "first_name", "last_name"]
fields = ["username", "email", "first_name", "last_name", "profile_image"]
widgets = {
"username": forms.TextInput(
attrs={
Expand Down
23 changes: 23 additions & 0 deletions src/accounts/migrations/0009_customuser_profile_image_url.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 5.1.1 on 2024-12-03 23:54

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("accounts", "0008_alter_customuser_first_name_and_more"),
]

operations = [
migrations.AddField(
model_name="customuser",
name="profile_image_url",
field=models.URLField(
blank=True,
help_text="URL to the user's profile image stored in S3.",
max_length=500,
null=True,
),
),
]
7 changes: 7 additions & 0 deletions src/accounts/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,12 @@ class CustomUser(AbstractUser):
max_length=20, choices=USER_TYPE_CHOICES, default="user"
)

profile_image_url = models.URLField(
max_length=500,
blank=True,
null=True,
help_text="URL to the user's profile image stored in S3.",
)

def __str__(self):
return f"{self.first_name} {self.last_name} ({self.username})"
95 changes: 59 additions & 36 deletions src/accounts/templates/profile_base.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,14 @@ <h1 class="text-4xl font-bold text-gray-100 mb-3">Profile</h1>
<div class="flex justify-between items-start mb-6">
<!-- Grouped Profile Picture and Information -->
<div class="flex items-center">
<div class="w-24 h-24 rounded-full bg-blue-600 text-white flex items-center justify-center text-4xl font-bold mr-6 uppercase">
{{ profile.username|first }}
</div>
{% if profile.profile_image_url %}
<img src="{{ profile.profile_image_url }}" alt="Profile Image"
class="w-24 h-24 rounded-full object-cover mr-6">
{% else %}
<div class="w-24 h-24 rounded-full bg-blue-600 text-white flex items-center justify-center text-4xl font-bold mr-6 uppercase">
{{ profile.username|first }}
</div>
{% endif %}
<div>
<h2 class="text-xl font-semibold text-blue-400 mb-2">Profile Information</h2>
<p class="text-gray-300"><strong>Username:</strong> {{ profile.username }}</p>
Expand All @@ -47,7 +52,7 @@ <h2 class="text-xl font-semibold text-blue-400 mb-2">Profile Information</h2>
border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700">
<i class="fas fa-comments mr-2 text-lg"></i>
<span class="tracking-wide">Forum Posts</span>
<span class="ml-2 bg-gray-100 text-gray-600 px-2.5 py-0.5 rounded-full text-sm">
<span class="ml-2 bg-gray-100 text-gray-600 px-2 py-0.5 rounded-full text-sm">
{{ user_posts|length }}
</span>
</button>
Expand All @@ -58,7 +63,7 @@ <h2 class="text-xl font-semibold text-blue-400 mb-2">Profile Information</h2>
<span class="tracking-wide">Bookmarks</span>
<span class="ml-2 bg-blue-100 text-blue-600 px-2.5 py-0.5 rounded-full text-sm">
{{ bookmarks|length }}
</span>
</span>
</button>

<button id="reviews-tab-btn"
Expand All @@ -68,7 +73,7 @@ <h2 class="text-xl font-semibold text-blue-400 mb-2">Profile Information</h2>
<span class="tracking-wide">Reviews</span>
<span class="ml-2 bg-gray-100 text-gray-600 px-2.5 py-0.5 rounded-full text-sm">
{{ reviews|length }}
</span>
</span>
</button>

</nav>
Expand Down Expand Up @@ -162,7 +167,7 @@ <h2 class="text-2xl font-semibold text-gray-400">My Reviews</h2>
<h2 class="text-2xl font-semibold text-gray-400">My Forum Posts</h2>
<span class="bg-blue-100 text-blue-800 text-sm font-medium px-3 py-1 rounded-full">
{{ user_posts|length }} posts
</span>
</span>
</div>
{% if user_posts %}
<div class="grid grid-cols-1 gap-4 w-full">
Expand All @@ -175,7 +180,8 @@ <h2 class="text-2xl font-semibold text-gray-400">My Forum Posts</h2>
{{ post.title }}
</a>
<p class="text-gray-400 text-sm mt-1">
{{ post.content|truncatewords:30 }}</p>
{{ post.content|truncatewords:30 }}
</p>
<div class="text-sm text-gray-500 mt-2 flex items-center gap-4">
<span><i class="fas fa-folder text-gray-400"></i> {{ post.category.name }}</span>
<span><i class="far fa-clock text-gray-400"></i> {{ post.created_at|date:"M d, Y" }}</span>
Expand All @@ -199,41 +205,48 @@ <h2 class="text-2xl font-semibold text-gray-400">My Forum Posts</h2>
</div>
</div>


<!-- Profile Edit Form Section -->
<div id="edit-profile" class="bg-gray-800 rounded-lg shadow-md p-6 hidden">
<h2 class="text-xl font-semibold text-blue-400 mb-4">Edit Profile</h2>
<form method="POST" class="space-y-4">
{% csrf_token %}
{% for field in form %}
<div>
<label class="block text-gray-300 font-medium mb-2" for="{{ field.id_for_label }}">
{{ field.label }}
</label>
{{ field }}
{% if field.errors %}
{% for error in field.errors %}
<p class="text-red-400 text-sm mt-1">{{ error }}</p>
{% endfor %}
{% endif %}
</div>
{% endfor %}
<form method="POST" enctype="multipart/form-data" class="space-y-4">
{% csrf_token %}
{% for field in form %}
<div>
<label class="block text-gray-300 font-medium mb-2" for="{{ field.id_for_label }}">
{{ field.label }}
</label>
{{ field }}
{% if field.help_text %}
<p class="text-gray-500 text-sm">{{ field.help_text }}</p>
{% endif %}
{% if field.errors %}
{% for error in field.errors %}
<p class="text-red-400 text-sm mt-1">{{ error }}</p>
{% endfor %}
{% endif %}
</div>
{% endfor %}

<div class="flex justify-between mt-6">
<button type="submit"
class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded transition-colors duration-300">
Save Changes
</button>
<button type="button" onclick="toggleEdit()"
class="bg-red-600 hover:bg-red-700 text-white font-bold py-2 px-4 rounded transition-colors duration-300">
Cancel
</button>
</div>
<!-- Optional: Show Remove Profile Image Checkbox Only If User Has an Image -->
{% if profile.profile_image_url %}
<!-- The 'remove_profile_image' field is already rendered in the loop above -->
<!-- No need to render it again here -->
{% endif %}

<div class="flex justify-between mt-6">
<button type="submit"
class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded transition-colors duration-300">
Save Changes
</button>
<button type="button" onclick="toggleEdit()"
class="bg-red-600 hover:bg-red-700 text-white font-bold py-2 px-4 rounded transition-colors duration-300">
Cancel
</button>
</div>
</form>
</div>
</div>

<!-- Modal HTML -->
<!-- Delete Confirmation Modal -->
<div id="deleteConfirmModal"
class="fixed inset-0 bg-gray-600 bg-opacity-50 hidden overflow-y-auto h-full w-full z-50 flex items-center justify-center">
<div class="bg-gray-800 rounded-lg p-8 max-w-md w-full mx-4 border border-gray-700"> <!-- Made width smaller and reduced padding -->
Expand Down Expand Up @@ -327,5 +340,15 @@ <h3 class="text-lg font-semibold mb-2 text-gray-100">Leave a Review</h3>
element.textContent = formatTimestamp(timestamp);
});
});

document.querySelector('form').addEventListener('submit', function (e) {
const removeCheckbox = document.getElementById('id_remove_profile_image');
if (removeCheckbox && removeCheckbox.checked) {
const confirmRemoval = confirm("Are you sure you want to remove your profile picture?");
if (!confirmRemoval) {
e.preventDefault();
}
}
});
</script>
{% endblock %}
9 changes: 7 additions & 2 deletions src/accounts/templates/register.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
<!-- register.html -->

{% extends 'base.html' %}
{% block title %}Register{% endblock %}
{% load static %}
Expand All @@ -17,7 +19,7 @@ <h2 class="mt-6 text-center text-3xl font-extrabold text-gray-100">

<div class="mt-8 mx-auto w-full" style="min-width: 500px; !important;">
<div class="bg-gray-800 py-8 px-12 shadow-xl rounded-lg">
<form method="post" class="space-y-8">
<form method="post" enctype="multipart/form-data" class="space-y-8">
{% csrf_token %}
{% for field in form %}
<div>
Expand All @@ -27,6 +29,9 @@ <h2 class="mt-6 text-center text-3xl font-extrabold text-gray-100">
<div class="mt-1">
{{ field }}
</div>
{% if field.help_text %}
<p class="mt-2 text-sm text-gray-500">{{ field.help_text }}</p>
{% endif %}
{% if field.errors %}
<p class="mt-2 text-sm text-red-400">{{ field.errors|join:", " }}</p>
{% endif %}
Expand All @@ -52,4 +57,4 @@ <h2 class="mt-6 text-center text-3xl font-extrabold text-gray-100">
</div>
</div>
</div>
{% endblock %}
{% endblock %}
Loading

0 comments on commit 3db03e8

Please sign in to comment.