feat: add feedback to api and front-end

This commit is contained in:
nftchance 2022-10-15 18:14:20 -05:00
parent 93cece08a4
commit cff93359c3
12 changed files with 186 additions and 32 deletions

View File

@ -4,6 +4,6 @@ from .models import Feedback
@admin.register(Feedback)
class FeedbackAdmin(admin.ModelAdmin):
list_display = ('url', 'liked', 'comment', 'created_at', 'updated_at')
list_display = ('feedback_url', 'liked', 'comment', 'created_at', 'updated_at')
list_filter = ('created_at', 'updated_at')
search_fields = ('url', 'comment')
search_fields = ('feedback_url', 'comment')

View File

@ -1,13 +1,17 @@
# Generated by Django 4.1.1 on 2022-10-15 21:06
# Generated by Django 4.1.1 on 2022-10-15 21:16
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = []
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
@ -22,11 +26,19 @@ class Migration(migrations.Migration):
verbose_name="ID",
),
),
("url", models.CharField(max_length=255)),
("feedback_url", models.CharField(max_length=255)),
("liked", models.BooleanField()),
("comment", models.TextField(blank=True, null=True)),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
(
"author",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="feedbacks",
to=settings.AUTH_USER_MODEL,
),
),
],
),
]

View File

@ -0,0 +1,26 @@
# Generated by Django 4.1.1 on 2022-10-15 22:31
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("feedback", "0001_initial"),
]
operations = [
migrations.AlterField(
model_name="feedback",
name="author",
field=models.ForeignKey(
blank=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="feedbacks",
to=settings.AUTH_USER_MODEL,
),
),
]

View File

@ -0,0 +1,26 @@
# Generated by Django 4.1.1 on 2022-10-15 22:41
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("feedback", "0002_alter_feedback_author"),
]
operations = [
migrations.AlterField(
model_name="feedback",
name="author",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="feedbacks",
to=settings.AUTH_USER_MODEL,
),
),
]

View File

@ -1,7 +1,17 @@
from django.contrib.auth import get_user_model
from django.db import models
User = get_user_model()
class Feedback(models.Model):
url = models.CharField(max_length=255)
author = models.ForeignKey(
'siwe_auth.Wallet',
on_delete=models.CASCADE,
related_name='feedbacks',
null=True
)
feedback_url = models.CharField(max_length=255)
liked = models.BooleanField()
comment = models.TextField(blank=True, null=True)

View File

@ -1,20 +1,40 @@
from django.contrib.auth import get_user_model
from rest_framework import serializers
from .models import Feedback
class FeedbackSerializer(serializers.ModelSerializer):
def validate_url(self, value):
if not all([value.startswith('http://'), 'badger' in value]):
raise serializers.ValidationError('Invalid URL')
User = get_user_model()
return value
class FeedbackUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = (
'url',
'ethereum_address',
'ens_name',
'ens_avatar',
)
class FeedbackSerializer(serializers.ModelSerializer):
author = FeedbackUserSerializer(read_only=True)
def create(self, validated_data):
validated_data['author'] = self.context['request'].user
return super().create(validated_data)
class Meta:
model = Feedback
fields = (
'url',
'liked',
'comment',
'created_at',
'url',
'id',
'author',
'feedback_url',
'liked',
'comment',
'created_at',
'updated_at'
)
)
depth = 1

View File

@ -5,4 +5,6 @@ from .views import (
)
router = routers.DefaultRouter()
router.register(r'badges', FeedbackViewSet)
router.register(r'feedback', FeedbackViewSet)
urlpatterns = router.urls

View File

@ -4,6 +4,8 @@ from rest_framework.permissions import (
IsAdminUser
)
from api.permissions import generator
from .models import Feedback
from .serializers import FeedbackSerializer
@ -13,8 +15,15 @@ class FeedbackViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated]
def get_permissions(self):
if self.action != 'create':
return [IsAdminUser]
def get_queryset(self):
if not self.request.user.is_admin:
return self.queryset.filter(author=self.request.user)
return self.queryset
return super().get_permissions()
def get_permissions(self):
permission_classes = []
if self.action in ['update', 'partial_update', 'destroy']:
permission_classes = [IsAdminUser]
return generator(self.permission_classes + permission_classes)

View File

@ -40,7 +40,7 @@ services:
env_file:
- ./.env
volumes:
- .:/api/code
- ./api:/code
depends_on:
- badger_db
links:

View File

@ -1,9 +1,10 @@
import { useState } from "react";
import { Button } from "@mui/material";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { postFeedbackRequest } from "@utils/api_requests";
import StatusIndicators from './StatusIndicators/StatusIndicators';
import "@style/Dashboard/Sidebar/Sidebar.css";
@ -14,8 +15,17 @@ const HelpSidebar = () => {
const collapseIcon = collapsed ? "chevron-left" : "chevron-right";
const toggleCollapsed = () => {
setCollapsed(!collapsed);
const onFeedbackSubmission = async ({ liked }) => {
const feedbackObj = {
feedback_url: window.location.href,
liked,
}
// TODO: Needs to be updated to support adding error when feedback is not added
const response = await postFeedbackRequest(feedbackObj);
if(response.status === 200) {
console.log("Feedback submitted successfully");
}
}
return (
@ -23,7 +33,7 @@ const HelpSidebar = () => {
<div className="sidebar__header">
<h5>Help</h5>
<div>
<Button onClick={toggleCollapsed}>
<Button onClick={() => { setCollapsed(!collapsed) }}>
<FontAwesomeIcon icon={['fal', collapseIcon]} />
</Button>
</div>
@ -44,11 +54,17 @@ const HelpSidebar = () => {
<StatusIndicators />
</div>
{/* <div className="sidebar__footer">
<div className="sidebar__footer">
<p>Do you like this page?</p>
<FontAwesomeIcon icon={['fal', 'thumbs-up']} />
<FontAwesomeIcon icon={['fal', 'thumbs-down']} />
</div> */}
<Button onClick={() => { onFeedbackSubmission({ liked: true }) }}>
<FontAwesomeIcon icon={['fal', 'thumbs-up']} />
</Button>
<Button onClick={() => { onFeedbackSubmission({ liked: false }) }}>
<FontAwesomeIcon icon={['fal', 'thumbs-down']} />
</Button>
</div>
</div>
)
}

View File

@ -34,13 +34,16 @@
.sidebar.right .sidebar__footer {
display: grid;
grid-template-columns: 200px auto auto;
grid-template-columns: auto auto auto;
align-items: center;
}
.sidebar.right .sidebar__footer p {
margin-right: 20px;
}
.sidebar.right .sidebar__footer svg {
color:rgba(0, 0, 0, .35);
margin-right: 10px;
}
.sidebar.right.collapsed {

View File

@ -3,6 +3,36 @@ import { cleanAddresses, getCSRFToken } from "./helpers";
const API_URL = process.env.REACT_APP_API_URL;
export async function postFeedbackRequest(feedback) {
let response;
try {
await fetch(`${API_URL}/feedback/`, {
method: 'POST',
mode: "cors",
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCSRFToken(),
},
credentials: 'include',
body: JSON.stringify(feedback)
})
.then(res => res.json())
.then(data => {
if(!data?.id) throw new Error("Feedback POST request failed");
response = data;
})
.catch(err => {
throw new Error(err);
})
}
catch(err) {
response = { error: err };
}
return response;
}
export async function postOrgRequest(org) {
let response;