diff --git a/api/admin.py b/api/admin.py
index 8c38f3f3..dd761dd2 100644
--- a/api/admin.py
+++ b/api/admin.py
@@ -1,3 +1,36 @@
from django.contrib import admin
+from django.db import models
+from django.contrib.auth.models import Group, User
+from django.contrib.auth.admin import UserAdmin
+from .models import Order, Profile
-# Register your models here.
+admin.site.unregister(Group)
+admin.site.unregister(User)
+
+class ProfileInline(admin.StackedInline):
+ model = Profile
+ can_delete = False
+ fields = ('avatar_tag',)
+ readonly_fields = ['avatar_tag']
+
+# extended users with avatars
+@admin.register(User)
+class EUserAdmin(UserAdmin):
+ inlines = [ProfileInline]
+ list_display = ('avatar_tag',) + UserAdmin.list_display
+ list_display_links = ['username']
+ def avatar_tag(self, obj):
+ return obj.profile.avatar_tag()
+
+@admin.register(Order)
+class OrderAdmin(admin.ModelAdmin):
+ list_display = ('id','type','maker','taker','status','amount','currency','created_at','expires_at')
+ list_display_links = ('maker','taker')
+ pass
+
+@admin.register(Profile)
+class UserProfileAdmin(admin.ModelAdmin):
+ list_display = ('avatar_tag','user','id','total_ratings','avg_rating','num_disputes','lost_disputes')
+ list_display_links =['user']
+ readonly_fields = ['avatar_tag']
+ pass
\ No newline at end of file
diff --git a/api/models.py b/api/models.py
index 4ca3e704..150c4c35 100644
--- a/api/models.py
+++ b/api/models.py
@@ -1,11 +1,16 @@
from django.db import models
from django.contrib.auth.models import User
-from django.core.validators import MaxValueValidator, MinValueValidator
+from django.core.validators import MaxValueValidator, MinValueValidator, validate_comma_separated_integer_list
+from django.db.models.signals import post_save, pre_delete
+from django.dispatch import receiver
+
+from django.utils.html import mark_safe
+
+from pathlib import Path
#############################
# TODO
# Load hparams from .env file
-
min_satoshis_trade = 10*1000
max_satoshis_trade = 500*1000
@@ -45,6 +50,7 @@ class Order(models.Model):
# order info, id = models.CharField(max_length=64, unique=True, null=False)
status = models.PositiveSmallIntegerField(choices=Status.choices, default=Status.WFB)
created_at = models.DateTimeField(auto_now_add=True)
+ expires_at = models.DateTimeField()
# order details
type = models.PositiveSmallIntegerField(choices=Types.choices, null=False)
@@ -56,7 +62,7 @@ class Order(models.Model):
is_explicit = models.BooleanField(default=False, null=False) # pricing method. A explicit amount of sats, or a relative premium above/below market.
# order participants
- maker = models.ForeignKey(User, related_name='maker', on_delete=models.SET_NULL, null=True, default=None) # unique = True, a maker can only make one order
+ maker = models.ForeignKey(User, related_name='maker', on_delete=models.CASCADE, null=True, default=None) # unique = True, a maker can only make one order
taker = models.ForeignKey(User, related_name='taker', on_delete=models.SET_NULL, null=True, default=None) # unique = True, a taker can only take one order
# order collateral
@@ -71,3 +77,40 @@ class Order(models.Model):
# buyer payment LN invoice
has_invoice = models.BooleanField(default=False, null=False) # has invoice and is valid
invoice = models.CharField(max_length=300, unique=False, null=True, default=None)
+
+class Profile(models.Model):
+ user = models.OneToOneField(User,on_delete=models.CASCADE)
+
+ # Ratings stored as a comma separated integer list
+ total_ratings = models.PositiveIntegerField(null=False, default=0)
+ latest_ratings = models.CharField(max_length=999, null=True, default=None, validators=[validate_comma_separated_integer_list]) # Will only store latest ratings
+ avg_rating = models.DecimalField(max_digits=4, decimal_places=1, default=None, null=True, validators=[MinValueValidator(0), MaxValueValidator(100)])
+
+ # Disputes
+ num_disputes = models.PositiveIntegerField(null=False, default=0)
+ lost_disputes = models.PositiveIntegerField(null=False, default=0)
+
+ # RoboHash
+ avatar = models.ImageField(default="static/assets/avatars/unknown.png", verbose_name='Avatar')
+
+ @receiver(post_save, sender=User)
+ def create_user_profile(sender, instance, created, **kwargs):
+ if created:
+ Profile.objects.create(user=instance)
+
+ @receiver(post_save, sender=User)
+ def save_user_profile(sender, instance, **kwargs):
+ instance.profile.save()
+
+ def __str__(self):
+ return self.user.username
+
+ # to display avatars in admin panel
+ def get_avatar(self):
+ if not self.avatar:
+ return 'static/assets/avatars/unknown.png'
+ return self.avatar.url
+
+ # method to create a fake table field in read only mode
+ def avatar_tag(self):
+ return mark_safe('' % self.get_avatar())
diff --git a/api/serializers.py b/api/serializers.py
index 7fa68069..b73cb157 100644
--- a/api/serializers.py
+++ b/api/serializers.py
@@ -4,7 +4,7 @@ from .models import Order
class OrderSerializer(serializers.ModelSerializer):
class Meta:
model = Order
- fields = ('id','status','created_at','type','currency','amount','payment_method','is_explicit','premium','satoshis','maker','taker')
+ fields = ('id','status','created_at','expires_at','type','currency','amount','payment_method','is_explicit','premium','satoshis','maker','taker')
class MakeOrderSerializer(serializers.ModelSerializer):
class Meta:
diff --git a/api/urls.py b/api/urls.py
index 27f39862..1af71120 100644
--- a/api/urls.py
+++ b/api/urls.py
@@ -1,8 +1,9 @@
from django.urls import path
-from .views import MakeOrder, OrderView, UserGenerator
+from .views import MakeOrder, OrderView, UserGenerator, BookView
urlpatterns = [
path('make/', MakeOrder.as_view()),
path('order/', OrderView.as_view()),
path('usergen/', UserGenerator.as_view()),
+ path('book/', BookView.as_view()),
]
\ No newline at end of file
diff --git a/api/views.py b/api/views.py
index 784cd096..e7accd36 100644
--- a/api/views.py
+++ b/api/views.py
@@ -3,6 +3,7 @@ from rest_framework.views import APIView
from rest_framework.response import Response
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.models import User
+from django.conf.urls.static import static
from .serializers import OrderSerializer, MakeOrderSerializer
from .models import Order
@@ -17,6 +18,12 @@ from pathlib import Path
from datetime import timedelta
from django.utils import timezone
+# .env
+expiration_time = 8
+
+avatar_path = Path('frontend/static/assets/avatars')
+avatar_path.mkdir(parents=True, exist_ok=True)
+
# Create your views here.
class MakeOrder(APIView):
@@ -51,6 +58,7 @@ class MakeOrder(APIView):
premium=premium,
satoshis=satoshis,
is_explicit=is_explicit,
+ expires_at= timezone.now()+timedelta(hours=expiration_time),
maker=request.user)
order.save()
@@ -81,7 +89,7 @@ class OrderView(APIView):
#To do fix: data['status_message'] = Order.Status.get(order.status).label
data['status_message'] = Order.Status.WFB.label # Hardcoded WFB, should use order.status value.
-
+
data['maker_nick'] = str(order.maker)
data['taker_nick'] = str(order.taker)
@@ -96,7 +104,6 @@ class OrderView(APIView):
return Response({'Bad Request':'Order ID parameter not found in request'}, status=status.HTTP_400_BAD_REQUEST)
-
class UserGenerator(APIView):
lookup_url_kwarg = 'token'
NickGen = NickGenerator(
@@ -106,7 +113,7 @@ class UserGenerator(APIView):
use_noun=True,
max_num=999)
- def get(self,request):
+ def get(self,request, format=None):
'''
Get a new user derived from a high entropy token
@@ -139,18 +146,20 @@ class UserGenerator(APIView):
# generate avatar
rh = Robohash(hash)
- rh.assemble(roboset='set1') # bgset='any' for backgrounds ON
+ rh.assemble(roboset='set1', bgset='any')# for backgrounds ON
- avatars_path = Path('frontend/static/assets/avatars')
- avatars_path.mkdir(parents=True, exist_ok=True)
-
- with open(avatars_path.joinpath(nickname+".png"), "wb") as f:
- rh.img.save(f, format="png")
+ # Does not replace image if existing (avoid re-avatar in case of nick collusion)
- # Create new credentials if nickname is new
+ image_path = avatar_path.joinpath(nickname+".png")
+ if not image_path.exists():
+ with open(image_path, "wb") as f:
+ rh.img.save(f, format="png")
+
+ # Create new credentials and logsin if nickname is new
if len(User.objects.filter(username=nickname)) == 0:
User.objects.create_user(username=nickname, password=token, is_staff=False)
user = authenticate(request, username=nickname, password=token)
+ user.profile.avatar = str(image_path)[9:] # removes frontend/ from url (ugly, to be fixed)
login(request, user)
return Response(context, status=status.HTTP_201_CREATED)
@@ -159,17 +168,15 @@ class UserGenerator(APIView):
if user is not None:
login(request, user)
# Sends the welcome back message, only if created +30 mins ago
- if request.user.date_joined < (timezone.now()-timedelta(minutes=1)):
+ if request.user.date_joined < (timezone.now()-timedelta(minutes=30)):
context['found'] = 'We found your Robosat. Welcome back!'
return Response(context, status=status.HTTP_202_ACCEPTED)
else:
- # It is unlikely (1/20 Billions) but maybe the nickname is taken
+ # It is unlikely, but maybe the nickname is taken (1 in 20 Billion change)
context['found'] = 'Bad luck, this nickname is taken'
context['bad_request'] = 'Enter a different token'
return Response(context, status=status.HTTP_403_FORBIDDEN)
-
-
def delete(self,request):
user = User.objects.get(id = request.user.id)
@@ -177,10 +184,40 @@ class UserGenerator(APIView):
# However it might be a long time recovered user
# Only delete if user live is < 5 minutes
+ # TODO check if user exists AND it is not a maker or taker!
if user is not None:
+ avatar_file = avatar_path.joinpath(str(request.user)+".png")
+ avatar_file.unlink() # Unsafe if avatar does not exist.
logout(request)
user.delete()
- return Response(status=status.HTTP_301_MOVED_PERMANENTLY)
+
+ return Response({'user_deleted':'User deleted permanently'},status=status.HTTP_301_MOVED_PERMANENTLY)
return Response(status=status.HTTP_403_FORBIDDEN)
+class BookView(APIView):
+ serializer_class = OrderSerializer
+
+ def get(self,request, format=None):
+ currency = request.GET.get('currency')
+ type = request.GET.get('type')
+ queryset = Order.objects.filter(currency=currency, type=type, status=0) # TODO status = 1 for orders that are Public
+ if len(queryset)== 0:
+ return Response({'not_found':'No orders found, be the first to make one'}, status=status.HTTP_404_NOT_FOUND)
+
+ queryset = queryset.order_by('created_at')
+ book_data = []
+ for order in queryset:
+ data = OrderSerializer(order).data
+ user = User.objects.filter(id=data['maker'])
+ if len(user) == 1:
+ data['maker_nick'] = user[0].username
+ # TODO avoid sending status and takers for book views
+ #data.pop('status','taker')
+ book_data.append(data)
+
+ return Response(book_data, status=status.HTTP_200_OK)
+
+
+
+
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 4e559c3d..b5eb7116 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -191,7 +191,6 @@
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz",
"integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==",
- "dev": true,
"requires": {
"@babel/types": "^7.16.7"
}
@@ -558,7 +557,6 @@
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz",
"integrity": "sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q==",
- "dev": true,
"requires": {
"@babel/helper-plugin-utils": "^7.16.7"
}
@@ -1149,11 +1147,132 @@
"integrity": "sha512-ws57AidsDvREKrZKYffXddNkyaF14iHNHm8VQnZH6t99E8gczjNN0GpvcGny0imC80yQ0tHz1xVUKk/KFQSUyA==",
"dev": true
},
+ "@emotion/babel-plugin": {
+ "version": "11.7.2",
+ "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.7.2.tgz",
+ "integrity": "sha512-6mGSCWi9UzXut/ZAN6lGFu33wGR3SJisNl3c0tvlmb8XChH1b2SUvxvnOh7hvLpqyRdHHU9AiazV3Cwbk5SXKQ==",
+ "requires": {
+ "@babel/helper-module-imports": "^7.12.13",
+ "@babel/plugin-syntax-jsx": "^7.12.13",
+ "@babel/runtime": "^7.13.10",
+ "@emotion/hash": "^0.8.0",
+ "@emotion/memoize": "^0.7.5",
+ "@emotion/serialize": "^1.0.2",
+ "babel-plugin-macros": "^2.6.1",
+ "convert-source-map": "^1.5.0",
+ "escape-string-regexp": "^4.0.0",
+ "find-root": "^1.1.0",
+ "source-map": "^0.5.7",
+ "stylis": "4.0.13"
+ },
+ "dependencies": {
+ "escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="
+ },
+ "source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
+ }
+ }
+ },
+ "@emotion/cache": {
+ "version": "11.7.1",
+ "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.7.1.tgz",
+ "integrity": "sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A==",
+ "requires": {
+ "@emotion/memoize": "^0.7.4",
+ "@emotion/sheet": "^1.1.0",
+ "@emotion/utils": "^1.0.0",
+ "@emotion/weak-memoize": "^0.2.5",
+ "stylis": "4.0.13"
+ }
+ },
"@emotion/hash": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
"integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="
},
+ "@emotion/is-prop-valid": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.1.1.tgz",
+ "integrity": "sha512-bW1Tos67CZkOURLc0OalnfxtSXQJMrAMV0jZTVGJUPSOd4qgjF3+tTD5CwJM13PHA8cltGW1WGbbvV9NpvUZPw==",
+ "requires": {
+ "@emotion/memoize": "^0.7.4"
+ }
+ },
+ "@emotion/memoize": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.5.tgz",
+ "integrity": "sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ=="
+ },
+ "@emotion/react": {
+ "version": "11.7.1",
+ "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.7.1.tgz",
+ "integrity": "sha512-DV2Xe3yhkF1yT4uAUoJcYL1AmrnO5SVsdfvu+fBuS7IbByDeTVx9+wFmvx9Idzv7/78+9Mgx2Hcmr7Fex3tIyw==",
+ "requires": {
+ "@babel/runtime": "^7.13.10",
+ "@emotion/cache": "^11.7.1",
+ "@emotion/serialize": "^1.0.2",
+ "@emotion/sheet": "^1.1.0",
+ "@emotion/utils": "^1.0.0",
+ "@emotion/weak-memoize": "^0.2.5",
+ "hoist-non-react-statics": "^3.3.1"
+ }
+ },
+ "@emotion/serialize": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.2.tgz",
+ "integrity": "sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A==",
+ "requires": {
+ "@emotion/hash": "^0.8.0",
+ "@emotion/memoize": "^0.7.4",
+ "@emotion/unitless": "^0.7.5",
+ "@emotion/utils": "^1.0.0",
+ "csstype": "^3.0.2"
+ },
+ "dependencies": {
+ "csstype": {
+ "version": "3.0.10",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz",
+ "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA=="
+ }
+ }
+ },
+ "@emotion/sheet": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.1.0.tgz",
+ "integrity": "sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g=="
+ },
+ "@emotion/styled": {
+ "version": "11.6.0",
+ "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.6.0.tgz",
+ "integrity": "sha512-mxVtVyIOTmCAkFbwIp+nCjTXJNgcz4VWkOYQro87jE2QBTydnkiYusMrRGFtzuruiGK4dDaNORk4gH049iiQuw==",
+ "requires": {
+ "@babel/runtime": "^7.13.10",
+ "@emotion/babel-plugin": "^11.3.0",
+ "@emotion/is-prop-valid": "^1.1.1",
+ "@emotion/serialize": "^1.0.2",
+ "@emotion/utils": "^1.0.0"
+ }
+ },
+ "@emotion/unitless": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
+ "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
+ },
+ "@emotion/utils": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.0.0.tgz",
+ "integrity": "sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA=="
+ },
+ "@emotion/weak-memoize": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz",
+ "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA=="
+ },
"@material-ui/core": {
"version": "4.12.3",
"resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.12.3.tgz",
@@ -1230,6 +1349,65 @@
"react-is": "^16.8.0 || ^17.0.0"
}
},
+ "@mui/private-theming": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.2.3.tgz",
+ "integrity": "sha512-Lc1Cmu8lSsYZiXADi9PBb17Ho82ZbseHQujUFAcp6bCJ5x/d+87JYCIpCBMagPu/isRlFCwbziuXPmz7WOzJPQ==",
+ "requires": {
+ "@babel/runtime": "^7.16.3",
+ "@mui/utils": "^5.2.3",
+ "prop-types": "^15.7.2"
+ }
+ },
+ "@mui/styled-engine": {
+ "version": "5.2.6",
+ "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.2.6.tgz",
+ "integrity": "sha512-bqAhli8eGS6v2qxivy2/4K0Ag8o//jsu1G2G6QcieFiT6y7oIF/nd/6Tvw6OSm3roOTifVQWNKwkt1yFWhGS+w==",
+ "requires": {
+ "@babel/runtime": "^7.16.3",
+ "@emotion/cache": "^11.7.1",
+ "prop-types": "^15.7.2"
+ }
+ },
+ "@mui/system": {
+ "version": "5.2.6",
+ "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.2.6.tgz",
+ "integrity": "sha512-PZ7bmpWOLikWgqn2zWv9/Xa7lxnRBOmfjoMH7c/IVYJs78W3971brXJ3xV9MEWWQcoqiYQeXzUJaNf4rFbKCBA==",
+ "requires": {
+ "@babel/runtime": "^7.16.3",
+ "@mui/private-theming": "^5.2.3",
+ "@mui/styled-engine": "^5.2.6",
+ "@mui/types": "^7.1.0",
+ "@mui/utils": "^5.2.3",
+ "clsx": "^1.1.1",
+ "csstype": "^3.0.10",
+ "prop-types": "^15.7.2"
+ },
+ "dependencies": {
+ "csstype": {
+ "version": "3.0.10",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz",
+ "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA=="
+ }
+ }
+ },
+ "@mui/types": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.1.0.tgz",
+ "integrity": "sha512-Hh7ALdq/GjfIwLvqH3XftuY3bcKhupktTm+S6qRIDGOtPtRuq2L21VWzOK4p7kblirK0XgGVH5BLwa6u8z/6QQ=="
+ },
+ "@mui/utils": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.2.3.tgz",
+ "integrity": "sha512-sQujlajIS0zQKcGIS6tZR0L1R+ib26B6UtuEn+cZqwKHsPo3feuS+SkdscYBdcCdMbrZs4gj8WIJHl2z6tbSzQ==",
+ "requires": {
+ "@babel/runtime": "^7.16.3",
+ "@types/prop-types": "^15.7.4",
+ "@types/react-is": "^16.7.1 || ^17.0.0",
+ "prop-types": "^15.7.2",
+ "react-is": "^17.0.2"
+ }
+ },
"@types/eslint": {
"version": "8.2.1",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.2.1.tgz",
@@ -1268,6 +1446,11 @@
"integrity": "sha512-+XBAjfZmmivILUzO0HwBJoYkAyyySSLg5KCGBDFLomJo0sV6szvVLAf4ANZZ0pfWzgEds5KmGLG9D5hfEqOhaA==",
"dev": true
},
+ "@types/parse-json": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
+ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA=="
+ },
"@types/prop-types": {
"version": "15.7.4",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz",
@@ -1290,6 +1473,14 @@
}
}
},
+ "@types/react-is": {
+ "version": "17.0.3",
+ "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.3.tgz",
+ "integrity": "sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==",
+ "requires": {
+ "@types/react": "*"
+ }
+ },
"@types/react-transition-group": {
"version": "4.4.4",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.4.tgz",
@@ -1554,6 +1745,16 @@
"object.assign": "^4.1.0"
}
},
+ "babel-plugin-macros": {
+ "version": "2.8.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz",
+ "integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==",
+ "requires": {
+ "@babel/runtime": "^7.7.2",
+ "cosmiconfig": "^6.0.0",
+ "resolve": "^1.12.0"
+ }
+ },
"babel-plugin-polyfill-corejs2": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.0.tgz",
@@ -1619,6 +1820,11 @@
"get-intrinsic": "^1.0.2"
}
},
+ "callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="
+ },
"caniuse-lite": {
"version": "1.0.30001294",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001294.tgz",
@@ -1707,7 +1913,6 @@
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz",
"integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==",
- "dev": true,
"requires": {
"safe-buffer": "~5.1.1"
},
@@ -1715,8 +1920,7 @@
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
- "dev": true
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
}
}
},
@@ -1738,6 +1942,18 @@
}
}
},
+ "cosmiconfig": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz",
+ "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==",
+ "requires": {
+ "@types/parse-json": "^4.0.0",
+ "import-fresh": "^3.1.0",
+ "parse-json": "^5.0.0",
+ "path-type": "^4.0.0",
+ "yaml": "^1.7.2"
+ }
+ },
"cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -1824,6 +2040,14 @@
"integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==",
"dev": true
},
+ "error-ex": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "requires": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
"es-module-lexer": {
"version": "0.9.3",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz",
@@ -1932,6 +2156,11 @@
"pkg-dir": "^4.1.0"
}
},
+ "find-root": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
+ "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="
+ },
"find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
@@ -1945,8 +2174,7 @@
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
- "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
- "dev": true
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"gensync": {
"version": "1.0.0-beta.2",
@@ -1992,7 +2220,6 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
- "dev": true,
"requires": {
"function-bind": "^1.1.1"
}
@@ -2048,6 +2275,22 @@
"resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz",
"integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ=="
},
+ "import-fresh": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "requires": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "dependencies": {
+ "resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="
+ }
+ }
+ },
"import-local": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.3.tgz",
@@ -2064,11 +2307,15 @@
"integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==",
"dev": true
},
+ "is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0="
+ },
"is-core-module": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz",
"integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==",
- "dev": true,
"requires": {
"has": "^1.0.3"
}
@@ -2137,6 +2384,11 @@
"integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
"dev": true
},
+ "json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
+ },
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@@ -2243,6 +2495,11 @@
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"dev": true
},
+ "lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
+ },
"loader-runner": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz",
@@ -2435,6 +2692,25 @@
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"dev": true
},
+ "parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "requires": {
+ "callsites": "^3.0.0"
+ }
+ },
+ "parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "requires": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ }
+ },
"path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -2450,8 +2726,7 @@
"path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
- "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
- "dev": true
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
},
"path-to-regexp": {
"version": "1.8.0",
@@ -2461,6 +2736,11 @@
"isarray": "0.0.1"
}
},
+ "path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="
+ },
"picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@@ -2667,7 +2947,6 @@
"version": "1.20.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
"integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==",
- "dev": true,
"requires": {
"is-core-module": "^2.2.0",
"path-parse": "^1.0.6"
@@ -2787,6 +3066,11 @@
"integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
"dev": true
},
+ "stylis": {
+ "version": "4.0.13",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz",
+ "integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag=="
+ },
"supports-color": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
@@ -2991,6 +3275,11 @@
"resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz",
"integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==",
"dev": true
+ },
+ "yaml": {
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="
}
}
}
diff --git a/frontend/package.json b/frontend/package.json
index 280eda3e..f687e275 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -22,8 +22,11 @@
},
"dependencies": {
"@babel/plugin-proposal-class-properties": "^7.16.7",
+ "@emotion/react": "^11.7.1",
+ "@emotion/styled": "^11.6.0",
"@material-ui/core": "^4.12.3",
"@material-ui/icons": "^4.11.2",
+ "@mui/system": "^5.2.6",
"material-ui-image": "^3.3.2",
"react-router-dom": "^5.2.0"
}
diff --git a/frontend/src/components/BookPage.js b/frontend/src/components/BookPage.js
index 09a2970b..13f6ef3b 100644
--- a/frontend/src/components/BookPage.js
+++ b/frontend/src/components/BookPage.js
@@ -1,11 +1,173 @@
import React, { Component } from "react";
+import { Paper, Button , Divider, Card, CardActionArea, CardContent, Typography, Grid, Select, MenuItem, FormControl, FormHelperText, List, ListItem, ListItemText, Avatar, Link, RouterLink, ListItemAvatar} from "@material-ui/core"
export default class BookPage extends Component {
- constructor(props) {
- super(props);
- }
+ constructor(props) {
+ super(props);
+ this.state = {
+ orders: new Array(),
+ currency: 1,
+ type: 1,
+ };
+ this.getOrderDetails()
+ this.state.currencyCode = this.getCurrencyCode(this.state.currency)
+ }
- render() {
- return
This is the order book page
; - } + // Fix needed to handle HTTP 404 error when no order is found + // Show message to be the first one to make an order + getOrderDetails() { + fetch('/api/book' + '?currency=' + this.state.currency + "&type=" + this.state.type) + .then((response) => response.json()) + .then((data) => //console.log(data)); + this.setState({orders: data})); + } + + handleCardClick=(e)=>{ + console.log(e.target) + this.props.history.push('/order/' + e.target); + } + + // Make these two functions sequential. getOrderDetails needs setState to be finish beforehand. + handleTypeChange=(e)=>{ + this.setState({ + type: e.target.value, + }); + this.getOrderDetails(); + } + handleCurrencyChange=(e)=>{ + this.setState({ + currency: e.target.value, + currencyCode: this.getCurrencyCode(e.target.value), + }) + this.getOrderDetails(); + } + + // Gets currency code (3 letters) from numeric (e.g., 1 -> USD) + // Improve this function so currencies are read from json + getCurrencyCode(val){ + return (val == 1 ) ? "USD": ((val == 2 ) ? "EUR":"ETH") + } + + // pretty numbers + pn(x) { + return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); + } + + render() { + return ( +