Merge pull request #19 from Reckless-Satoshi/order-book-first-iteration

Add book order API endpoint and basic frontend cards page
This commit is contained in:
Reckless_Satoshi 2022-01-07 11:55:22 +00:00 committed by GitHub
commit 7e89f57779
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 785 additions and 231 deletions

View File

@ -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

View File

@ -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('<img src="%s" width="50" height="50" />' % self.get_avatar())

View File

@ -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:

View File

@ -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()),
]

View File

@ -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)

View File

@ -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=="
}
}
}

View File

@ -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"
}

View File

@ -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 <p>This is the order book page</p>;
}
// 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 (
<Grid className='orderBook' container spacing={1}>
<Grid item xs={12} align="center">
<Typography component="h4" variant="h4">
Order Book
</Typography>
</Grid>
<Grid item xs={6} align="right">
<FormControl >
<FormHelperText>
I want to
</FormHelperText>
<Select
label="Select Order Type"
required="true"
value={this.state.type}
inputProps={{
style: {textAlign:"center"}
}}
onChange={this.handleTypeChange}
>
<MenuItem value={1}>BUY</MenuItem>
<MenuItem value={0}>SELL</MenuItem>
</Select>
</FormControl>
</Grid>
<Grid item xs={6} align="left">
<FormControl >
<FormHelperText>
And pay with
</FormHelperText>
<Select
label="Select Payment Currency"
required="true"
value={this.state.currency}
inputProps={{
style: {textAlign:"center"}
}}
onChange={this.handleCurrencyChange}
>
<MenuItem value={1}>USD</MenuItem>
<MenuItem value={2}>EUR</MenuItem>
<MenuItem value={3}>ETH</MenuItem>
</Select>
</FormControl>
</Grid>
{this.state.orders.map((order) =>
<Grid container item sm={4}>
<Card elevation={6} sx={{ width: 945 }}>
{/* To fix! does not pass order.id to handleCardCLick. Instead passes the clicked </>*/}
<CardActionArea value={order.id} onClick={this.handleCardClick}>
<CardContent>
<List dense="true">
<ListItem >
<ListItemAvatar >
<Avatar
alt={order.maker_nick}
src={window.location.origin +'/static/assets/avatars/' + order.maker_nick + '.png'}
/>
</ListItemAvatar>
<ListItemText>
<Typography gutterBottom variant="h6">
{order.maker_nick}
</Typography>
</ListItemText>
</ListItem>
{/* CARD PARAGRAPH CONTENT */}
<ListItemText>
<Typography variant="subtitle1" color="text.secondary">
{order.type == 0 ? <b> Buys </b>: <b> Sells </b>}
<b>{parseFloat(parseFloat(order.amount).toFixed(4))}
{" " +this.getCurrencyCode(order.currency)}</b> <a> worth of bitcoin</a>
</Typography>
<Typography variant="subtitle1" color="text.secondary">
Payment via <b>{order.payment_method}</b>
</Typography>
<Typography variant="subtitle1" color="text.secondary">
Priced {order.is_explicit ?
" explicitly at " + this.pn(order.satoshis) + " Sats" : (
" at " +
parseFloat(parseFloat(order.premium).toFixed(4)) + "% over the market"
)}
</Typography>
<Typography variant="subtitle1" color="text.secondary">
<b>{" 42,354 "}{this.getCurrencyCode(order.currency)}/BTC</b> (Binance API)
</Typography>
</ListItemText>
</List>
</CardContent>
</CardActionArea>
</Card>
</Grid>
)}
<Grid item xs={12} align="center">
<Typography component="h5" variant="h5">
You are {this.state.type == 0 ? " selling " : " buying "} BTC for {this.state.currencyCode}
</Typography>
</Grid>
<Grid item xs={12} align="center">
<Button color="secondary" variant="contained" to="/" component={Link}>
Back
</Button>
</Grid>
</Grid>
);
};
}

View File

@ -1,5 +1,5 @@
import React, { Component } from 'react';
import { Button , Grid, Typography, TextField, Select, FormHelperText, MenuItem, FormControl, Radio, FormControlLabel, RadioGroup, Menu} from "@material-ui/core"
import { Paper, Button , Grid, Typography, TextField, Select, FormHelperText, MenuItem, FormControl, Radio, FormControlLabel, RadioGroup, Menu} from "@material-ui/core"
import { Link } from 'react-router-dom'
function getCookie(name) {
@ -79,7 +79,7 @@ export default class MakerPage extends Component {
premium: 0,
});
}
handleClickisExplicit=(e)=>{
handleClickExplicit=(e)=>{
this.setState({
isExplicit: true,
satoshis: 10000,
@ -104,130 +104,116 @@ export default class MakerPage extends Component {
};
fetch("/api/make/",requestOptions)
.then((response) => response.json())
.then((data) => this.props.history.push('/order/' + data.id));
.then((data) => (console.log(data) & this.props.history.push('/order/' + data.id)));
}
render() {
return (
<Grid container spacing={1}>
<Grid item xs={12} align="center">
<Typography component="h4" variant="h4">
Make an Order
</Typography>
</Grid>
<Grid item xs={12} align="center">
<FormControl component="fieldset">
<RadioGroup row defaultValue="0" onChange={this.handleTypeChange}>
<FormControlLabel
value="0"
<Grid item xs={12} align="center">
<Typography component="h4" variant="h4">
Make an Order
</Typography>
</Grid>
<Paper elevation={12} style={{ padding: 8,}}>
<Grid item xs={12} align="center">
<FormControl component="fieldset">
<FormHelperText>
<div align='center'>
Choose Buy or Sell Bitcoin
</div>
</FormHelperText>
<RadioGroup row defaultValue="0" onChange={this.handleTypeChange}>
<FormControlLabel
value="0"
control={<Radio color="primary"/>}
label="Buy"
labelPlacement="Top"
/>
<FormControlLabel
value="1"
control={<Radio color="secondary"/>}
label="Sell"
labelPlacement="Top"
/>
</RadioGroup>
</FormControl>
</Grid>
<Grid item xs={12} align="center">
<FormControl >
<TextField
label="Amount of Fiat to Trade"
type="number"
required="true"
defaultValue={this.defaultAmount}
inputProps={{
min:0 ,
style: {textAlign:"center"}
}}
onChange={this.handleAmountChange}
/>
<Select
label="Select Payment Currency"
required="true"
defaultValue={this.defaultCurrency}
inputProps={{
style: {textAlign:"center"}
}}
onChange={this.handleCurrencyChange}
>
<MenuItem value={1}>USD</MenuItem>
<MenuItem value={2}>EUR</MenuItem>
<MenuItem value={3}>ETH</MenuItem>
</Select>
</FormControl>
</Grid>
<Grid item xs={12} align="center">
<FormControl >
<TextField
label="Payment Method(s)"
type="text"
require={true}
inputProps={{
style: {textAlign:"center"}
}}
onChange={this.handlePaymentMethodChange}
/>
</FormControl>
</Grid>
<Grid item xs={12} align="center">
<FormControl component="fieldset">
<RadioGroup row defaultValue="relative">
<FormControlLabel
value="relative"
control={<Radio color="primary"/>}
label="Buy"
label="Relative"
labelPlacement="Top"
/>
<FormControlLabel
value="1"
onClick={this.handleClickRelative}
/>
<FormControlLabel
value="explicit"
control={<Radio color="secondary"/>}
label="Sell"
label="Explicit"
labelPlacement="Top"
/>
</RadioGroup>
<FormHelperText>
<div align='center'>
Choose Buy or Sell Bitcoin
</div>
</FormHelperText>
</FormControl>
</Grid>
<Grid item xs={12} align="center">
<FormControl >
<Select
require={true}
defaultValue={this.defaultCurrency}
inputProps={{
style: {textAlign:"center"}
}}
onChange={this.handleCurrencyChange}
>
<MenuItem value={1}>USD</MenuItem>
<MenuItem value={2}>EUR</MenuItem>
<MenuItem value={3}>ETH</MenuItem>
</Select>
<FormHelperText>
<div align='center'>
Select Payment Currency
</div>
</FormHelperText>
</FormControl>
</Grid>
<Grid item xs={12} align="center">
<FormControl >
<TextField
type="number"
require={true}
defaultValue={this.defaultAmount}
inputProps={{
min:0 ,
style: {textAlign:"center"}
}}
onChange={this.handleAmountChange}
/>
</FormControl>
<FormHelperText>
<div align='center'>
Amount of Fiat to Trade
</div>
</FormHelperText>
</Grid>
<Grid item xs={12} align="center">
<FormControl >
<TextField
type="text"
require={true}
inputProps={{
style: {textAlign:"center"}
}}
onChange={this.handlePaymentMethodChange}
/>
<FormHelperText>
<div align='center'>
Enter the Payment Method(s)
</div>
</FormHelperText>
</FormControl>
</Grid>
<Grid item xs={12} align="center">
<FormControl component="fieldset">
<RadioGroup row defaultValue="relative">
<FormControlLabel
value="relative"
control={<Radio color="primary"/>}
label="Relative"
labelPlacement="Top"
onClick={this.handleClickRelative}
/>
<FormControlLabel
value="explicit"
control={<Radio color="secondary"/>}
label="Explicit"
labelPlacement="Top"
onClick={this.handleClickisExplicit}
onShow="false"
/>
</RadioGroup>
<FormHelperText >
<div align='center'>
Choose a Pricing Method
</div>
</FormHelperText>
</FormControl>
</Grid>
{/* conditional shows either Premium % field or Satoshis field based on pricing method */}
{ this.state.isExplicit
? <Grid item xs={12} align="center">
<FormControl >
<TextField
onClick={this.handleClickExplicit}
/>
</RadioGroup>
<FormHelperText >
<div align='center'>
Choose a Pricing Method
</div>
</FormHelperText>
</FormControl>
</Grid>
{/* conditional shows either Premium % field or Satoshis field based on pricing method */}
{ this.state.isExplicit
? <Grid item xs={12} align="center">
<TextField
label="Explicit Amount in Satoshis"
type="number"
require={true}
required="true"
inputProps={{
// TODO read these from .env file
min:10000 ,
@ -235,36 +221,25 @@ export default class MakerPage extends Component {
style: {textAlign:"center"}
}}
onChange={this.handleSatoshisChange}
defaultValue={this.defaultSatoshis}
// defaultValue={this.defaultSatoshis}
/>
<FormHelperText>
<div align='center'>
Explicit Amount in Satoshis
</div>
</FormHelperText>
</FormControl>
</Grid>
: <Grid item xs={12} align="center">
<FormControl >
</Grid>
: <Grid item xs={12} align="center">
<TextField
label="Premium over Market (%)"
type="number"
require={true}
defaultValue={this.defaultPremium}
// defaultValue={this.defaultPremium}
inputProps={{
style: {textAlign:"center"}
}}
onChange={this.handlePremiumChange}
/>
<FormHelperText>
<div align='center'>
Premium Relative to Market Price (%)
</div>
</FormHelperText>
</FormControl>
</Grid>
}
</Grid>
}
</Paper>
</Grid>
<Grid item xs={12} align="center">
<Button color="primary" variant="contained" onClick={this.handleCreateOfferButtonPressed}>
<Button color="primary" variant="contained" onClick={this.handleCreateOfferButtonPressed} >
Create Order
</Button>
<Typography component="subtitle2" variant="subtitle2">
@ -277,14 +252,13 @@ export default class MakerPage extends Component {
}
</div>
</Typography>
</Grid>
<Grid item xs={12} align="center">
<Button color="secondary" variant="contained" to="/" component={Link}>
Back
</Button>
<Grid item xs={12} align="center">
<Button color="secondary" variant="contained" to="/" component={Link}>
Back
</Button>
</Grid>
</Grid>
</Grid>
);
}
}

View File

@ -1,7 +1,12 @@
import React, { Component } from "react";
import { Button , Grid, Typography, List, ListItem, ListItemText, ListItemAvatar, Avatar, Divider} from "@material-ui/core"
import { Paper, Button , Grid, Typography, List, ListItem, ListItemText, ListItemAvatar, Avatar, Divider} from "@material-ui/core"
import { Link } from 'react-router-dom'
// pretty numbers
function pn(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
export default class OrderPage extends Component {
constructor(props) {
super(props);
@ -36,27 +41,31 @@ export default class OrderPage extends Component {
});
}
// Fix to use proper react props
handleClickBackButton=()=>{
window.history.back();
}
render (){
return (
<Grid container spacing={1}>
<Grid item xs={12} align="center">
<Typography component="h5" variant="h5">
Robosat BTC {this.state.type ? " Sell " : " Buy "} Order
BTC {this.state.type ? " Sell " : " Buy "} Order
</Typography>
<Paper elevation={12} style={{ padding: 8,}}>
<List component="nav" aria-label="mailbox folders">
<ListItem>
<ListItemAvatar sx={{ width: 56, height: 56 }}>
<Avatar
alt={this.state.makerNick}
src={window.location.origin +'/static/assets/avatars/' + this.state.makerNick + '.png'}
sx={{ width: 56, height: 56 }}
/>
</ListItemAvatar>
<ListItemText primary={this.state.makerNick} secondary="Order maker" />
</ListItem>
<Divider />
<ListItem>
<ListItemText primary={this.state.amount+" "+this.state.currencyCode} secondary="Amount and currency requested"/>
<ListItemText primary={parseFloat(parseFloat(this.state.amount).toFixed(4))+" "+this.state.currencyCode} secondary="Amount and currency requested"/>
</ListItem>
<Divider />
<ListItem>
@ -65,9 +74,9 @@ export default class OrderPage extends Component {
<Divider />
<ListItem>
{this.state.isExplicit ?
<ListItemText primary={this.state.satoshis} secondary="Amount of Satoshis"/>
<ListItemText primary={pn(this.state.satoshis)} secondary="Amount of Satoshis"/>
:
<ListItemText primary={this.state.premium} secondary="Premium over market price"/>
<ListItemText primary={parseFloat(parseFloat(this.state.premium).toFixed(2))+"%"} secondary="Premium over market price"/>
}
</ListItem>
<Divider />
@ -93,6 +102,10 @@ export default class OrderPage extends Component {
<Grid item xs={12} align="center">
{this.state.isParticipant ? "" : <Button variant='contained' color='primary' to='/home' component={Link}>Take Order</Button>}
</Grid>
<Grid item xs={12} align="center">
{this.state.isParticipant ? "" : <Button variant='contained' color='secondary' onClick={this.handleClickBackButton}>Back</Button>}
</Grid>
</Paper>
</Grid>
</Grid>
);

View File

@ -1,5 +1,5 @@
import React, { Component } from "react";
import { Button , Grid, Typography, TextField, Select, FormHelperText, MenuItem, FormControl, Radio, FormControlLabel, RadioGroup, Menu} from "@material-ui/core"
import { Button , Grid, Typography, TextField, ButtonGroup} from "@material-ui/core"
import { Link } from 'react-router-dom'
import Image from 'material-ui-image'
@ -85,9 +85,39 @@ export default class UserGenPage extends Component {
this.getGeneratedUser();
}
// TO FIX CSRF TOKEN IS NOT UPDATED UNTIL WINDOW IS RELOADED
reload_for_csrf_to_work=()=>{
window.location.reload()
}
render() {
return (
<Grid container spacing={1}>
<Grid item xs={12} align="center">
<Typography component="h5" variant="h5">
<b>{this.state.nickname ? "⚡"+this.state.nickname+"⚡" : ""}</b>
</Typography>
</Grid>
<Grid item xs={12} align="center">
<div style={{ maxWidth: 200, maxHeight: 200 }}>
<Image className='newAvatar'
disableError='true'
cover='true'
color='null'
src={this.state.avatar_url}
/>
</div><br/>
</Grid>
{
this.state.found ?
<Grid item xs={12} align="center">
<Typography component="subtitle2" variant="subtitle2" color='primary'>
{this.state.found}<br/>
</Typography>
</Grid>
:
""
}
<Grid item xs={12} align="center">
<TextField
error={this.state.bad_request}
@ -102,36 +132,19 @@ export default class UserGenPage extends Component {
/>
</Grid>
<Grid item xs={12} align="center">
<div style={{ maxWidth: 200, maxHeight: 200 }}>
<Image className='newAvatar'
disableError='true'
cover='true'
color='null'
src={this.state.avatar_url}
/>
</div>
<Button onClick={this.handleAnotherButtonPressed}>Generate Another Robosat</Button>
</Grid>
<Grid item xs={12} align="center">
<ButtonGroup variant="contained" aria-label="outlined primary button group">
<Button color='primary' to='/make/' component={Link}>Make Order</Button>
<Button to='/home' component={Link}>INFO</Button>
<Button color='secondary' to='/book/' component={Link}>View Book</Button>
</ButtonGroup>
</Grid>
<Grid item xs={12} align="center">
<Typography component="h5" variant="h5">
<b>{this.state.nickname ? "⚡"+this.state.nickname+"⚡" : ""}</b>
</Typography>
</Grid>
{
this.state.found ?
<Grid item xs={12} align="center">
<Typography component="subtitle2" variant="subtitle2" color='primary'>
{this.state.found}<br/>
</Typography>
<Button variant='contained' color='primary' to='/home' component={Link}>Cool!</Button>
</Grid>
:
<Grid item xs={12} align="center">
<Button variant='contained' color='primary' to='/home' component={Link}>Take This Robosat!</Button>
</Grid>
}
<Grid item xs={12} align="center">
<Button variant='contained' to='/' component={Link} onClick={this.handleAnotherButtonPressed}>Give Me Another</Button>
Easy and Private Lightning peer-to-peer Exchange
</Typography>
</Grid>
</Grid>
);

View File

@ -1,15 +0,0 @@
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}

View File

@ -47,7 +47,7 @@ pip install robohash
### Install npm
`sudo apt install npm`
No need to repeat, this is the list of npm packages we use
npm packages we use
```
cd frontend
npm init -y
@ -59,6 +59,7 @@ npm install @babel/plugin-proposal-class-properties
npm install react-router-dom@5.2.0
npm install @material-ui/icons
npm install material-ui-image
npm install @mui/system @emotion/react @emotion/styled
```
### Launch the React render