Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion backend/accounts/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from rest_framework import serializers
from rest_framework.validators import UniqueValidator

from orders.serializers import DonationOrderSerializer


class UserSerializer(serializers.ModelSerializer):
email = serializers.EmailField(
Expand All @@ -23,6 +25,8 @@ class UserSerializer(serializers.ModelSerializer):
total_donations = serializers.FloatField(read_only=True)
total_dividends = serializers.FloatField(read_only=True)

donations = DonationOrderSerializer(read_only=True, many=True)

class Meta:
model = get_user_model()
fields = [
Expand All @@ -36,6 +40,7 @@ class Meta:
"password2",
"total_donations",
"total_dividends",
"donations",
]

def validate(self, attrs):
Expand All @@ -51,7 +56,7 @@ def validate(self, attrs):
# Update sanitized phone number in attrs
attrs["phone_number"] = phone_number

if(
if (
not attrs.get("phone_number").isdigit()
or len(attrs.get("phone_number")) < 10
):
Expand Down
7 changes: 7 additions & 0 deletions backend/orders/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from django.contrib import admin
from .models import DonationOrder


@admin.register(DonationOrder)
class DonationOrderAdmin(admin.ModelAdmin):
pass
5 changes: 4 additions & 1 deletion backend/orders/serializers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
from rest_framework import serializers
from .models import DonationOrder


class DonationOrderSerializer(serializers.HyperlinkedModelSerializer):
stripe_transaction_id = serializers.CharField(write_only=True)

class Meta:
model = DonationOrder
fields = ("donation_total", "account", "time", "status", "stripe_transaction_id")
fields = ("amount", "date", "status", "stripe_transaction_id")
9 changes: 2 additions & 7 deletions backend/orders/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,10 @@


router = DefaultRouter()
router.register(r'order', DonationViewSet, basename='donation')
router.register(r"order", DonationViewSet, basename="donation")

urlpatterns = [
path('', include(router.urls)),
path(
"user-donations/",
DonationViewSet.as_view({"get": "get_account_donations"}),
name="user-donations",
),
path("", include(router.urls)),
path(
"total-donations/",
DonationViewSet.as_view({"get": "get_total_donations"}),
Expand Down
33 changes: 9 additions & 24 deletions backend/orders/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,32 +38,17 @@ def get_queryset(self):
else:
return user.donations.all()

@action(detail=False, methods=["get"], url_path="total", permission_classes=[AllowAny])
@action(
detail=False, methods=["get"], url_path="total", permission_classes=[AllowAny]
)
def get_total_donations(self, request) -> float:
"""This method will return the total value of all donations

Example call: http://127.0.0.1:8000/order/total-donations/"""
total_donations = self.filter_queryset(DonationOrder.objects.all()).aggregate(
total=Sum("amount")
)["total"] or 0
total_donations = (
self.filter_queryset(DonationOrder.objects.all()).aggregate(
total=Sum("amount")
)["total"]
or 0
)
return Response({"donation_total": total_donations}, status=200)


def get_account_donations(self, request):
"""This method will return a 2-dimensional list of all the donations
made by a user. The first row will be the donation amount and the second
row will be the date of the donation"""

donation_amounts = []
donation_dates = []

donations = self.filter_queryset(self.get_queryset().filter(account=request.user))

for donation in donations:
donation_amounts.append(donation.amount)
donation_dates.append(donation.date)
donation_list = [donation_amounts, donation_dates]
# donation_list = [
# [donation.amount, donation.date] for donation in donations
# ]
return Response({"donations_list": donation_list}, status=200)
26 changes: 13 additions & 13 deletions webapp/src/components/navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import { isLoggedIn } from '@/util/request';

const NavBar: React.FC = () => {
const [menuOpen, setMenuOpen] = useState(false);
const [cookie,,removeCookie] = useCookies(["token"]);
const [cookie, , removeCookie] = useCookies(["token", "refresh"]);
const [loggedIn, setIsLoggedIn] = useState<boolean>(false);
useMemo(()=>{
isLoggedIn().then(()=>setIsLoggedIn(true)).catch(()=>setIsLoggedIn(false));
},[cookie]);
useMemo(() => {
isLoggedIn().then(() => setIsLoggedIn(true)).catch(() => setIsLoggedIn(false));
}, [cookie]);

return (
<header className="flex shadow-lg py-4 px-4 sm:px-10 bg-white font-[sans-serif] min-h-[70px] tracking-wide relative z-50">
Expand Down Expand Up @@ -63,15 +63,15 @@ const NavBar: React.FC = () => {
{/* Right Side Buttons */}
<div className="flex items-center ml-auto space-x-6">
{loggedIn ?
<button className="text-[#007bff] hover:underline" onClick={()=>removeCookie("token")}>Logout</button>:
<>
<button className='.btn-primary'>
<Link href="/login" className="text-[#007bff] hover:underline">Login</Link>
</button>
<button className="px-4 py-2 btn-login">
<Link href="/register">Sign up</Link>
</button>
</>
<button className="text-[#007bff] hover:underline" onClick={() => { removeCookie("token"); removeCookie("refresh"); }}>Logout</button> :
<>
<button className='.btn-primary'>
<Link href="/login" className="text-[#007bff] hover:underline">Login</Link>
</button>
<button className="px-4 py-2 btn-login">
<Link href="/register">Sign up</Link>
</button>
</>
}

{/* Mobile Menu Toggle Button */}
Expand Down
28 changes: 13 additions & 15 deletions webapp/src/pages/account.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
import { JSX, useEffect, useState } from "react";
import PieChart from "@/components/pie-chart";
import { AccountType, getAccountInfo, DonationsListType, getUserDonations } from "@/util/request";
import { AccountType, getAccountInfo, isLoggedIn } from "@/util/request";
import { useRouter } from "next/navigation";
import { useCookies } from "react-cookie";


const Account = (): JSX.Element => {
const router = useRouter();
const [accountInfo, setAccountInfo] = useState<AccountType>();
const [cookie] = useCookies(["token"])
useEffect(() => {
getAccountInfo().then((response) => setAccountInfo(response.data)).catch(() =>
router.push("login"))
}, []);



const [donations, setDonations] = useState<DonationsListType>();
useEffect(() => {
getUserDonations().then((response) => setDonations(response.data));
}, []);
isLoggedIn().then(() =>
getAccountInfo().then((response) => setAccountInfo(response.data)).catch(() => router.push("login"))
).catch(
() => router.push("login")
);
}, [cookie]);

// Variables are hard coded for now to demo until backend is implemented.
// const stocks_owned = ["Stock 1", "Stock 2", "Stock 3", "Stock 4", "Stock 5", "Stock 6", "Stock 7"];
Expand Down Expand Up @@ -54,15 +52,15 @@ const Account = (): JSX.Element => {
{/* Stocks owned */}
<div className="bg-gray-300 p-2 rounded-md">
<h3 className="text-lg font-bold">Donations:</h3>
<p>Total Donations: {accountInfo?.total_donations?.toLocaleString('en-US', { style: 'currency', currency: 'CAD' })}</p>
<p>Total Donations: {accountInfo?.total_donations?.toLocaleString('en-US', { style: 'currency', currency: 'CAD' })}</p>
<br></br>
{donations?.donations_list?.[0]?.map((donation, index) => (
{accountInfo?.donations.map((donation, index) => (
<p key={index}>
{donation.toLocaleString('en-US', { style: 'currency', currency: 'CAD' })} on {new Date(donations?.donations_list?.[1]?.[index]).toLocaleDateString('en-US')}
{donation.amount.toLocaleString('en-US', { style: 'currency', currency: 'CAD' })} on {new Date(donation.date).toLocaleDateString('en-US')}
</p>
))}



</div>

Expand Down
3 changes: 1 addition & 2 deletions webapp/src/pages/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ const LoginPage: () => JSX.Element = () => {
setMessage("");
sendLogin({ email, password })
.then((response) => {
console.log(response);
setCookie("token", response.data.access, { maxAge: 86400, path: "/" });
setCookie("refresh", response.data.refresh, { path: "/" });
router.push("account");
Expand Down Expand Up @@ -72,7 +71,7 @@ const LoginPage: () => JSX.Element = () => {
<Link href="/register" className="text-[#007bff] hover:underline">Register</Link>
<div className="field mt-5">
<div className="btn-primary h-10 flex items-center justify-center">
<input className="button" type="submit" value="Login" disabled={disabled}/>
<input className="button" type="submit" value="Login" disabled={disabled} />
</div>
</div>
</form>
Expand Down
28 changes: 12 additions & 16 deletions webapp/src/util/request.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,25 @@ backendConfig.interceptors.request.use((config) => {
const refreshToken: () => Promise<void> = () => {
const refresh_cookie = Cookie.get("refresh");
if (refresh_cookie) {
return backendConfig.post("login/refresh/", { "refresh": refresh_cookie }).then((refresh_response) => {
return backendConfig.post("login/refresh/", { "refresh": refresh_cookie }).then(refresh_response => {
Cookie.set("token", refresh_response.data.access);
return Promise.resolve();
}).catch((refresh_error) => {
console.log(refresh_error);
Cookie.remove("token")
return Promise.reject();
Cookie.remove("token");
Cookie.remove("refresh");
return Promise.reject(refresh_error);
});
}
return Promise.reject();
}

/* Auto refresh token */
backendConfig.interceptors.response.use(response => response, async (error) => {
if (error.response.status == 401 && !error.config?._refresh_retry) {
backendConfig.interceptors.response.use(response => response, error => {
if (error.response.status === 401 && !error.config?._refresh_retry && error.config.url !== "login/refresh/") {
error.config._refresh_retry = true;
console.log("Try refresh")
return refreshToken().then(() => backendConfig(error.config)).catch(() => error);
}
return error
return Promise.reject(error);
})

export const isLoggedIn: () => Promise<void> = async () => {
Expand Down Expand Up @@ -93,12 +92,11 @@ export type AccountType = {
is_active: boolean;
total_dividends: number;
total_donations: number;
};

export type DonationsListType = {
// amount: Array<number>;
// date: Array<Date>;
donations_list: Array<[number, Date]>;
donations: {
amount: number;
date: string;
status: string;
}[];
};

export type CharityType = {
Expand Down Expand Up @@ -140,8 +138,6 @@ export const sendCharity: (data: CharityFormData) =>

export const getAccountInfo: () => Promise<AxiosResponse<AccountType>> = () => backendConfig.get("account/");

export const getUserDonations: () => Promise<AxiosResponse<DonationsListType>> = () => backendConfig.get("user-donations/");

export const getCharities: () => Promise<AxiosResponse<CharityType[]>> = () => backendConfig.get("charity/");

export const setCharityApproved: (id: number, approved: boolean) => Promise<AxiosResponse<CharityType>> = (id: number, approved: boolean) => backendConfig.patch(`charity/${id}/`, { "is_approved": approved })
Expand Down