add web
This commit is contained in:
@@ -0,0 +1,378 @@
|
||||
from app.services.economy import GetUserBalance, DecrementTargetBalance, IncrementTargetBalance
|
||||
from app.enums.TransactionType import TransactionType
|
||||
from app.models.exchange_offer import ExchangeOffer
|
||||
from app.models.user import User
|
||||
from app.util import auth, transactions, redislock, websiteFeatures
|
||||
from app.extensions import db, limiter, redis_controller
|
||||
from datetime import datetime, timedelta
|
||||
from app.pages.messages.messages import CreateSystemMessage
|
||||
from sqlalchemy import case
|
||||
from flask import Blueprint, render_template, request, redirect, url_for, jsonify, make_response, flash, abort
|
||||
|
||||
import redis_lock
|
||||
CurrencyExchangeRoute = Blueprint("currencyexchange", __name__, url_prefix="/currency-exchange")
|
||||
|
||||
@CurrencyExchangeRoute.errorhandler(429)
|
||||
def ratelimit_handler(e):
|
||||
flash("You are being ratelimited.", "danger")
|
||||
return redirect(request.referrer)
|
||||
|
||||
@CurrencyExchangeRoute.route("/", methods=["GET"])
|
||||
@auth.authenticated_required
|
||||
def index():
|
||||
CategoryType : str = request.args.get(
|
||||
key = "category",
|
||||
default = "all",
|
||||
type = str
|
||||
)
|
||||
SortType : str = request.args.get(
|
||||
key = "sort",
|
||||
default = "best",
|
||||
type = str
|
||||
)
|
||||
PageNumber : int = request.args.get(
|
||||
key = "page",
|
||||
default = 1,
|
||||
type = int
|
||||
)
|
||||
|
||||
if CategoryType not in ["tr", "rt", "all"]:
|
||||
CategoryType = "all"
|
||||
if SortType not in ["best", "worst", "created"]:
|
||||
SortType = "best"
|
||||
if PageNumber < 1:
|
||||
PageNumber = 1
|
||||
|
||||
QueryObj = ExchangeOffer.query
|
||||
QueryObj = QueryObj.filter_by(
|
||||
reciever_id = None
|
||||
)
|
||||
if CategoryType == "tr":
|
||||
QueryObj = QueryObj.filter_by(
|
||||
offer_currency_type = 1
|
||||
)
|
||||
elif CategoryType == "rt":
|
||||
QueryObj = QueryObj.filter_by(
|
||||
offer_currency_type = 0
|
||||
)
|
||||
|
||||
if SortType == "best":
|
||||
if CategoryType == "rt":
|
||||
QueryObj = QueryObj.order_by(
|
||||
ExchangeOffer.ratio.asc()
|
||||
)
|
||||
elif CategoryType == "tr":
|
||||
QueryObj = QueryObj.order_by(
|
||||
ExchangeOffer.ratio.desc()
|
||||
)
|
||||
else:
|
||||
QueryObj = QueryObj.order_by(
|
||||
ExchangeOffer.worth.desc()
|
||||
)
|
||||
elif SortType == "worst":
|
||||
if CategoryType == "rt":
|
||||
QueryObj = QueryObj.order_by(
|
||||
ExchangeOffer.ratio.desc()
|
||||
)
|
||||
elif CategoryType == "tr":
|
||||
QueryObj = QueryObj.order_by(
|
||||
ExchangeOffer.ratio.asc()
|
||||
)
|
||||
else:
|
||||
QueryObj = QueryObj.order_by(
|
||||
ExchangeOffer.worth.asc()
|
||||
)
|
||||
else:
|
||||
QueryObj = QueryObj.order_by(
|
||||
ExchangeOffer.created_at.desc()
|
||||
)
|
||||
|
||||
QueryObj = QueryObj.paginate(
|
||||
page = PageNumber,
|
||||
per_page = 15,
|
||||
error_out = False
|
||||
)
|
||||
|
||||
for Offer in QueryObj.items:
|
||||
Offer : ExchangeOffer
|
||||
if Offer.expires_at < datetime.utcnow():
|
||||
IncrementTargetBalance(Offer.creator, Offer.offer_value, Offer.offer_currency_type)
|
||||
transactions.CreateTransaction(
|
||||
Reciever = Offer.creator,
|
||||
Sender = User.query.filter_by(id = 1).first(),
|
||||
CurrencyAmount = Offer.offer_value,
|
||||
CurrencyType = Offer.offer_currency_type,
|
||||
TransactionType = TransactionType.Sale,
|
||||
AssetId = None,
|
||||
CustomText = "Offer expired."
|
||||
)
|
||||
|
||||
db.session.delete(Offer)
|
||||
db.session.commit()
|
||||
continue
|
||||
|
||||
return render_template(
|
||||
"currencyexchange/index.html",
|
||||
offers = QueryObj.items,
|
||||
hasNextPage = QueryObj.has_next,
|
||||
hasPrevPage = QueryObj.has_prev,
|
||||
PageCategory = CategoryType,
|
||||
SortType = SortType,
|
||||
PageNumber = PageNumber
|
||||
)
|
||||
|
||||
@CurrencyExchangeRoute.route("/view/<int:OfferId>", methods=["GET"])
|
||||
@auth.authenticated_required
|
||||
def viewOffer(OfferId : int):
|
||||
OfferObj : ExchangeOffer = ExchangeOffer.query.filter_by(
|
||||
id = OfferId
|
||||
).first()
|
||||
if OfferObj is None:
|
||||
return abort(404)
|
||||
AuthenticatedUser : User = auth.GetCurrentUser()
|
||||
|
||||
return render_template(
|
||||
"currencyexchange/view.html",
|
||||
offer = OfferObj,
|
||||
AuthenticatedUser = AuthenticatedUser
|
||||
)
|
||||
|
||||
@CurrencyExchangeRoute.route("/delete/<int:OfferId>", methods=["POST"])
|
||||
@auth.authenticated_required
|
||||
def deleteOffer(OfferId : int):
|
||||
OfferObj : ExchangeOffer = ExchangeOffer.query.filter_by(
|
||||
id = OfferId
|
||||
).first()
|
||||
if OfferObj is None:
|
||||
return abort(404)
|
||||
AuthenticatedUser : User = auth.GetCurrentUser()
|
||||
if OfferObj.creator_id != AuthenticatedUser.id:
|
||||
return abort(403)
|
||||
|
||||
if websiteFeatures.GetWebsiteFeature("CurrencyExchange") is False:
|
||||
flash("Currency exchange is currently disabled.", "error")
|
||||
return redirect(f"/currency-exchange/view/{OfferId}")
|
||||
|
||||
try:
|
||||
with redis_lock.Lock( redis_client = redis_controller, name = f"currency_exchange_order:{OfferId}", expire = 15, auto_renewal = True ):
|
||||
OfferObj : ExchangeOffer = ExchangeOffer.query.filter_by(
|
||||
id = OfferId
|
||||
).first()
|
||||
if OfferObj.reciever_id is not None:
|
||||
return abort(403)
|
||||
|
||||
IncrementTargetBalance(OfferObj.creator, OfferObj.offer_value, OfferObj.offer_currency_type)
|
||||
transactions.CreateTransaction(
|
||||
Reciever = OfferObj.creator,
|
||||
Sender = User.query.filter_by(id = 1).first(),
|
||||
CurrencyAmount = OfferObj.offer_value,
|
||||
CurrencyType = OfferObj.offer_currency_type,
|
||||
TransactionType = TransactionType.Sale,
|
||||
AssetId = None,
|
||||
CustomText = "Offer cancelled."
|
||||
)
|
||||
|
||||
db.session.delete(OfferObj)
|
||||
db.session.commit()
|
||||
|
||||
flash("Offer successfully deleted.", "success")
|
||||
return redirect("/currency-exchange/")
|
||||
except AssertionError:
|
||||
flash("Failed to delete offer, try again later.", "error")
|
||||
return redirect(f"/currency-exchange/view/{OfferId}")
|
||||
|
||||
@CurrencyExchangeRoute.route("/fill/<int:OfferId>", methods=["POST"])
|
||||
@auth.authenticated_required
|
||||
@limiter.limit("40/minute")
|
||||
def fillOffer(OfferId : int):
|
||||
OfferObj : ExchangeOffer = ExchangeOffer.query.filter_by(
|
||||
id = OfferId
|
||||
).first()
|
||||
if OfferObj is None:
|
||||
return abort(404)
|
||||
|
||||
if websiteFeatures.GetWebsiteFeature("CurrencyExchange") is False:
|
||||
flash("Currency exchange is currently disabled.", "error")
|
||||
return redirect(f"/currency-exchange/view/{OfferId}")
|
||||
|
||||
LockName = f"currency_exchange_order:{str(OfferId)}"
|
||||
try:
|
||||
with redis_lock.Lock( redis_client = redis_controller, name = LockName, expire = 15, auto_renewal = True ):
|
||||
OfferObj : ExchangeOffer = ExchangeOffer.query.filter_by(
|
||||
id = OfferId
|
||||
).first()
|
||||
|
||||
AuthenticatedUser : User = auth.GetCurrentUser()
|
||||
if OfferObj.creator_id == AuthenticatedUser.id:
|
||||
return abort(403)
|
||||
if OfferObj.reciever_id is not None:
|
||||
return abort(403)
|
||||
|
||||
if OfferObj.expires_at < datetime.utcnow():
|
||||
return abort(403)
|
||||
|
||||
SendingCurrencyType : int = 0 if OfferObj.offer_currency_type == 1 else 1
|
||||
UserRobuxBal, UserTicketsBal = GetUserBalance(AuthenticatedUser)
|
||||
if SendingCurrencyType == 0:
|
||||
if UserRobuxBal < OfferObj.receive_value:
|
||||
flash("You do not have enough Robux to fill this offer.", "error")
|
||||
return redirect(f"/currency-exchange/view/{OfferId}")
|
||||
else:
|
||||
if UserTicketsBal < OfferObj.receive_value:
|
||||
flash("You do not have enough Tickets to fill this offer.", "error")
|
||||
return redirect(f"/currency-exchange/view/{OfferId}")
|
||||
|
||||
transactions.CreateTransaction(
|
||||
Reciever = OfferObj.creator,
|
||||
Sender = User.query.filter_by(id = 1).first(), # Remain anonymous
|
||||
CurrencyAmount = OfferObj.receive_value,
|
||||
CurrencyType = SendingCurrencyType,
|
||||
TransactionType = TransactionType.Sale,
|
||||
AssetId = None,
|
||||
CustomText = f"Currency Exchange Order ({OfferId}) Filled"
|
||||
)
|
||||
transactions.CreateTransaction(
|
||||
Reciever = AuthenticatedUser,
|
||||
Sender = User.query.filter_by(id = 1).first(), # Remain anonymous
|
||||
CurrencyAmount = OfferObj.offer_value,
|
||||
CurrencyType = OfferObj.offer_currency_type,
|
||||
TransactionType = TransactionType.Sale,
|
||||
AssetId = None,
|
||||
CustomText = f"Currency Exchange Order ({OfferId}) Filled"
|
||||
)
|
||||
transactions.CreateTransaction(
|
||||
Reciever = User.query.filter_by(id = 1).first(), # Remain anonymous
|
||||
Sender = AuthenticatedUser,
|
||||
CurrencyAmount = OfferObj.receive_value,
|
||||
CurrencyType = SendingCurrencyType,
|
||||
TransactionType = TransactionType.Purchase,
|
||||
AssetId = None,
|
||||
CustomText = f"Currency Exchange Order ({OfferId}) Filled"
|
||||
)
|
||||
DecrementTargetBalance(AuthenticatedUser, OfferObj.receive_value, SendingCurrencyType)
|
||||
IncrementTargetBalance(AuthenticatedUser, OfferObj.offer_value, OfferObj.offer_currency_type)
|
||||
IncrementTargetBalance(OfferObj.creator, OfferObj.receive_value, SendingCurrencyType)
|
||||
|
||||
OfferObj.reciever_id = AuthenticatedUser.id
|
||||
db.session.commit()
|
||||
|
||||
CreateSystemMessage(
|
||||
subject = "Offer Filled",
|
||||
message = f"Your currency exchange offer ({OfferId}) has been filled, you have received {OfferObj.receive_value} {'Robux' if OfferObj.offer_currency_type == 1 else 'Tickets'} in exchange for {OfferObj.offer_value} {'Robux' if OfferObj.offer_currency_type == 0 else 'Tickets'}.",
|
||||
userid = OfferObj.creator_id
|
||||
)
|
||||
|
||||
flash("Offer successfully filled.", "success")
|
||||
return redirect(f"/currency-exchange/view/{OfferId}")
|
||||
except AssertionError:
|
||||
flash("Failed to fill offer, try again later.", "error")
|
||||
return redirect(f"/currency-exchange/view/{OfferId}")
|
||||
|
||||
@CurrencyExchangeRoute.route("/create", methods=["GET"])
|
||||
@auth.authenticated_required
|
||||
def createOfferPage():
|
||||
return render_template("currencyexchange/create.html")
|
||||
|
||||
@CurrencyExchangeRoute.route("/create", methods=["POST"])
|
||||
@auth.authenticated_required
|
||||
@limiter.limit("40/minute")
|
||||
def createOffer():
|
||||
AuthenticatedUser : User = auth.GetCurrentUser()
|
||||
|
||||
OfferCurrencyType : int = request.form.get(
|
||||
key = "offer-currency-type",
|
||||
default = None,
|
||||
type = int
|
||||
)
|
||||
OfferValue : int = request.form.get(
|
||||
key = "offer-currency-amount",
|
||||
default = None,
|
||||
type = int
|
||||
)
|
||||
ReceiveValue : int = request.form.get(
|
||||
key = "exchange-currency-amount",
|
||||
default = None,
|
||||
type = int
|
||||
)
|
||||
if OfferCurrencyType is None or OfferValue is None or ReceiveValue is None:
|
||||
flash("Invalid request", "error")
|
||||
return redirect("/currency-exchange/create")
|
||||
|
||||
if OfferValue <= 0 or ReceiveValue <= 0:
|
||||
flash("Invalid request", "error")
|
||||
return redirect("/currency-exchange/create")
|
||||
|
||||
if websiteFeatures.GetWebsiteFeature("CurrencyExchange") is False:
|
||||
flash("Currency exchange is currently disabled.", "error")
|
||||
return redirect(f"/currency-exchange/create")
|
||||
|
||||
try:
|
||||
with redis_lock.Lock( redis_client = redis_controller, name = f"currency_exchange_create_offer:{AuthenticatedUser.id}", expire = 15, auto_renewal = True ):
|
||||
ActiveOffers : int = ExchangeOffer.query.filter_by(
|
||||
creator_id = AuthenticatedUser.id,
|
||||
reciever_id = None
|
||||
).filter(
|
||||
ExchangeOffer.expires_at > datetime.utcnow()
|
||||
).count()
|
||||
|
||||
if ActiveOffers >= 25:
|
||||
flash("You have too many active offers, please cancel them to create a new offer.", "error")
|
||||
return redirect("/currency-exchange/create")
|
||||
|
||||
if OfferCurrencyType == 0:
|
||||
if OfferValue < 5:
|
||||
flash("You can only create offers of 5 Robux or more.", "error")
|
||||
return redirect("/currency-exchange/create")
|
||||
if OfferValue > 10000:
|
||||
flash("You can only create offers up to 10,000 Robux.", "error")
|
||||
return redirect("/currency-exchange/create")
|
||||
Ratio = ReceiveValue / OfferValue
|
||||
else:
|
||||
if OfferValue < 10:
|
||||
flash("You can only create offers of 10 Tickets or more.", "error")
|
||||
return redirect("/currency-exchange/create")
|
||||
if OfferValue > 100000:
|
||||
flash("You can only create offers up to 100,000 Tickets.", "error")
|
||||
return redirect("/currency-exchange/create")
|
||||
Ratio = OfferValue / ReceiveValue
|
||||
|
||||
if Ratio < 2 or Ratio > 20:
|
||||
flash("Bad exchange ratio", "error")
|
||||
return redirect("/currency-exchange/create")
|
||||
|
||||
UserRobuxBal, UserTicketsBal = GetUserBalance(AuthenticatedUser)
|
||||
if OfferCurrencyType == 0 and UserRobuxBal < OfferValue:
|
||||
flash("You do not have enough Robux to create this offer.", "error")
|
||||
return redirect("/currency-exchange/create")
|
||||
elif OfferCurrencyType == 1 and UserTicketsBal < OfferValue:
|
||||
flash("You do not have enough Tickets to create this offer.", "error")
|
||||
return redirect("/currency-exchange/create")
|
||||
|
||||
DecrementTargetBalance(AuthenticatedUser, OfferValue, OfferCurrencyType)
|
||||
transactions.CreateTransaction(
|
||||
Reciever = User.query.filter_by(id=1).first(),
|
||||
Sender = AuthenticatedUser,
|
||||
CurrencyAmount = OfferValue,
|
||||
CurrencyType = OfferCurrencyType,
|
||||
TransactionType = TransactionType.Purchase,
|
||||
CustomText = "Created Exchange Order"
|
||||
)
|
||||
|
||||
NewOffer : ExchangeOffer = ExchangeOffer(
|
||||
creator_id = AuthenticatedUser.id,
|
||||
offer_value = OfferValue,
|
||||
receive_value = ReceiveValue,
|
||||
offer_currency_type = OfferCurrencyType,
|
||||
created_at = datetime.utcnow(),
|
||||
expires_at = datetime.utcnow() + timedelta(days=31),
|
||||
ratio = Ratio,
|
||||
worth = abs( 20 - Ratio ) if OfferCurrencyType == 0 else ( 20 - abs( Ratio - 20 ) )
|
||||
)
|
||||
db.session.add(NewOffer)
|
||||
db.session.commit()
|
||||
|
||||
flash("Offer created successfully", "success")
|
||||
return redirect("/currency-exchange/")
|
||||
except AssertionError:
|
||||
flash("Failed to create offer, try again later.", "error")
|
||||
return redirect("/currency-exchange/create")
|
||||
@@ -0,0 +1,168 @@
|
||||
{% extends '__layout__.html' %}
|
||||
{% block title %}Create Offer{% endblock %}
|
||||
{% block head %}
|
||||
<style>
|
||||
.text-secondary {
|
||||
color: rgb(199, 199, 199) !important;
|
||||
}
|
||||
.text-robux {
|
||||
color: rgb(26, 212, 103) !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
.bg-dark {
|
||||
background-color: rgb(30,30,30) !important;
|
||||
}
|
||||
.form-control:disabled ~ label {
|
||||
color: --bs-secondary-bg;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="container position-relative" style="min-height: 100vh;margin-top: 200px;max-width: 900px;">
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
<div>
|
||||
{% for category, message in messages %}
|
||||
{% if category == 'error': %}
|
||||
<div class="alert border p-2 text-center alert-danger border-danger">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if category == 'success': %}
|
||||
<div class="alert border p-2 text-center alert-success border-success">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
<div class="row">
|
||||
<div class="col-8">
|
||||
<form method="post">
|
||||
<div class="d-flex align-items-center mb-1">
|
||||
<div class="border-top flex-fill me-2"></div>
|
||||
<h5 class="text-secondary">You are offering</h5>
|
||||
<div class="border-top flex-fill ms-2"></div>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<div class="form-floating">
|
||||
<select class="form-control" id="offer-currency-type" name="offer-currency-type" style="min-width: 100px;">
|
||||
<option value="0">Robux</option>
|
||||
<option value="1">Tickets</option>
|
||||
</select>
|
||||
<label for="offer-currency-type">Currency</label>
|
||||
</div>
|
||||
<div class="form-floating ms-2 flex-fill">
|
||||
<input type="number" class="form-control" id="offer-currency-amount" name="offer-currency-amount" placeholder="0" value="0">
|
||||
<label for="offer-currency-amount">Amount</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex align-items-center mt-3 mb-1">
|
||||
<div class="border-top flex-fill me-2"></div>
|
||||
<h5 class="text-secondary">In exchange for</h5>
|
||||
<div class="border-top flex-fill ms-2"></div>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<div class="form-floating">
|
||||
<select class="form-control" id="exchange-currency-type" name="exchange-currency-type" style="min-width: 100px;" disabled>
|
||||
<option value="0">Robux</option>
|
||||
<option value="1" selected>Tickets</option>
|
||||
</select>
|
||||
<label for="exchange-currency-type disabled">Currency</label>
|
||||
</div>
|
||||
<div class="form-floating ms-2 flex-fill">
|
||||
<input type="number" class="form-control" id="exchange-currency-amount" name="exchange-currency-amount" placeholder="0" value="0">
|
||||
<label for="exchange-currency-amount">Amount</label>
|
||||
</div>
|
||||
</div>
|
||||
<p class="m-0 mt-2 text-secondary">Robux to Tix Ratio: <span class="fw-bold text-white" id="robux-to-tix-ratio">0:0</span></p>
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<button type="submit" class="btn btn-primary btn-sm mt-3 w-100" id="submit-offer">Create Offer</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-4 border-start">
|
||||
<a class="text-decoration-none w-100 text-end" style="font-size: 12px;" href="/currency-exchange/">Return to Currency Exchange</a>
|
||||
<h3 class="w-100">New Exchange Offer</h3>
|
||||
<p class="text-secondary m-0 ms-2">You can create an offer to be placed onto the currency exchange system, this will allow other users to trade for example Robux to Tickets or vice versa.</p>
|
||||
|
||||
<p class="text-secondary m-0 mt-2 ms-2">Note: The minimum for Robux to Tix is 1:2 while the maximum is 1:20</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="position-absolute w-100 h-100" id="transparent-background-top" style="top: 0;left: 0;background-color: rgba(0,0,0,0.5);display: none;"></div>
|
||||
<script>
|
||||
const OfferCurrencyTypeSelect = document.getElementById('offer-currency-type');
|
||||
const ExchangeCurrencyTypeSelect = document.getElementById('exchange-currency-type');
|
||||
|
||||
OfferCurrencyTypeSelect.addEventListener('change', () => {
|
||||
if (OfferCurrencyTypeSelect.value == 0) {
|
||||
ExchangeCurrencyTypeSelect.value = 1;
|
||||
} else {
|
||||
ExchangeCurrencyTypeSelect.value = 0;
|
||||
}
|
||||
UpdateRatio();
|
||||
});
|
||||
|
||||
const OfferCurrencyAmountInput = document.getElementById('offer-currency-amount');
|
||||
const ExchangeCurrencyAmountInput = document.getElementById('exchange-currency-amount');
|
||||
const RobuxToTixRatio = document.getElementById('robux-to-tix-ratio');
|
||||
const SubmitButton = document.getElementById('submit-offer');
|
||||
const TransparentBackgroundTop = document.getElementById('transparent-background-top');
|
||||
|
||||
const DisableButton = () => {
|
||||
if ( SubmitButton.disabled ) return;
|
||||
|
||||
SubmitButton.disabled = true;
|
||||
SubmitButton.classList.add('disabled');
|
||||
}
|
||||
const EnableButton = () => {
|
||||
if ( !SubmitButton.disabled ) return;
|
||||
|
||||
SubmitButton.disabled = false;
|
||||
SubmitButton.classList.remove('disabled');
|
||||
}
|
||||
|
||||
const UpdateRatio = () => {
|
||||
if (OfferCurrencyTypeSelect.value == 0) {
|
||||
var ratio = ExchangeCurrencyAmountInput.value / OfferCurrencyAmountInput.value;
|
||||
// Round to 3 decimal places
|
||||
ratio = Math.round(ratio * 1000) / 1000;
|
||||
RobuxToTixRatio.innerText = `1:${ratio}`;
|
||||
|
||||
if (ratio < 1 || ratio > 50) {
|
||||
DisableButton();
|
||||
} else {
|
||||
EnableButton();
|
||||
}
|
||||
|
||||
} else {
|
||||
var ratio = ExchangeCurrencyAmountInput.value / OfferCurrencyAmountInput.value;
|
||||
// Round to 3 decimal places
|
||||
ratio = Math.round(ratio * 1000) / 1000;
|
||||
RobuxToTixRatio.innerText = `${ratio}:1`;
|
||||
|
||||
var ActualRatio = OfferCurrencyAmountInput.value / ExchangeCurrencyAmountInput.value;
|
||||
|
||||
if ( ActualRatio < 1 || ActualRatio > 50 ) {
|
||||
DisableButton();
|
||||
} else {
|
||||
EnableButton();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SubmitButton.addEventListener('click', () => {
|
||||
TransparentBackgroundTop.style.display = 'block';
|
||||
})
|
||||
|
||||
OfferCurrencyAmountInput.addEventListener('input', () => {
|
||||
UpdateRatio();
|
||||
});
|
||||
|
||||
ExchangeCurrencyAmountInput.addEventListener('input', () => {
|
||||
UpdateRatio();
|
||||
});
|
||||
DisableButton()
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,114 @@
|
||||
{% extends '__layout__.html' %}
|
||||
{% block title %}Currency Exchange{% endblock %}
|
||||
{% block head %}
|
||||
<style>
|
||||
.text-secondary {
|
||||
color: rgb(199, 199, 199) !important;
|
||||
}
|
||||
.text-robux {
|
||||
color: rgb(26, 212, 103) !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
.text-tickets {
|
||||
color: rgb(224, 224, 60) !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="container position-relative" style="min-height: 100vh;margin-top: 100px;max-width: 1200px;">
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
<div>
|
||||
{% for category, message in messages %}
|
||||
{% if category == 'error': %}
|
||||
<div class="alert border p-2 text-center alert-danger border-danger">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if category == 'success': %}
|
||||
<div class="alert border p-2 text-center alert-success border-success">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
<div class="d-flex align-items-center">
|
||||
<div>
|
||||
<h1 class="m-0">Currency Exchange</h1>
|
||||
<div class="d-flex">
|
||||
<a class="text-decoration-none mt-1 ms-1" href="/currency-exchange/create">Create Exchange Offer</a>
|
||||
<a class="text-decoration-none mt-1 ms-2" href="/transactions/">My Transactions</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ms-auto form-floating">
|
||||
<select class="form-control" id="change-category-select">
|
||||
<option value="rt" {%if PageCategory == "rt"%}selected{%endif%} selected>Tix to Robux</option>
|
||||
<option value="tr" {%if PageCategory == "tr"%}selected{%endif%}>Robux to Tix</option>
|
||||
<option value="all" {%if PageCategory == "all"%}selected{%endif%}>All</option>
|
||||
</select>
|
||||
<label for="change-category-select">Category</label>
|
||||
</div>
|
||||
<div class="ms-2 form-floating">
|
||||
<select class="form-control" id="change-sort-select">
|
||||
<option value="best" {%if SortType == "best"%}selected{%endif%}>Best Value</option>
|
||||
<option value="worst" {%if SortType == "worst"%}selected{%endif%}>Worst Value</option>
|
||||
<option value="created" {%if SortType == "created"%}selected{%endif%}>Created</option>
|
||||
</select>
|
||||
<label for="change-sort-select">Sort</label>
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-dark table-striped mt-2">
|
||||
<thead>
|
||||
<tr class="rounded-top">
|
||||
<th scope="col">Date</th>
|
||||
<th scope="col">Expires</th>
|
||||
<th scope="col">Offering</th>
|
||||
<th scope="col">Requesting</th>
|
||||
<th scope="col">R:T Ratio</th>
|
||||
<th scope="col">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="table-group-divider" style="border-color: rgb(20,20,20);">
|
||||
{% for OfferObj in offers: %}
|
||||
<tr>
|
||||
<td>{{ OfferObj.created_at.strftime("%d/%m/%Y") }}</td>
|
||||
<td>{{ OfferObj.expires_at.strftime("%d/%m/%Y") }}</td>
|
||||
<td>
|
||||
{% if OfferObj.offer_currency_type == 0 %}
|
||||
<span class="text-robux">R$ {{OfferObj.offer_value}}</span>
|
||||
{% else %}
|
||||
<span class="text-tickets">T$ {{OfferObj.offer_value}}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if OfferObj.offer_currency_type == 1 %}
|
||||
<span class="text-robux">R$ {{OfferObj.receive_value}}</span>
|
||||
{% else %}
|
||||
<span class="text-tickets">T$ {{OfferObj.receive_value}}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
1:{{round(OfferObj.ratio, 3)}}
|
||||
</td>
|
||||
<td><a class="text-decoration-none" href="/currency-exchange/view/{{OfferObj.id}}">View</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% if len(offers) == 0: %}
|
||||
<div class="text-center">
|
||||
<p class="text-secondary">No offers found</p>
|
||||
</div>
|
||||
{%endif%}
|
||||
<div class="align-items-center d-flex justify-content-center mt-2">
|
||||
<a class="btn btn-sm text-white btn-primary me-2 pagination-back-btn {% if not hasPrevPage: %}disabled {% endif %}" {% if hasPrevPage: %}href="/currency-exchange/?category={{PageCategory}}&sort={{SortType}}&page={{PageNumber-1}}"{%endif%}>Previous</a>
|
||||
<p class="m-0 pagination-page-number">Page {{PageNumber}}</p>
|
||||
<a class="btn btn-sm text-white btn-primary ms-2 pagination-next-btn {% if not hasNextPage: %}disabled {% endif %}" {% if hasNextPage: %}href="/currency-exchange/?category={{PageCategory}}&sort={{SortType}}&page={{PageNumber+1}}"{%endif%}>Next</a>
|
||||
</div>
|
||||
</div>
|
||||
<script>const s = document.getElementById("change-category-select");s.addEventListener("change", function() {var u = new URL(window.location.href);var sb = s.value;u.searchParams.set("category", sb);;window.location.href = u;});</script>
|
||||
<script>const aa = document.getElementById("change-sort-select");aa.addEventListener("change", function() {var u = new URL(window.location.href);var ab = aa.value;u.searchParams.set("sort", ab);;window.location.href = u;});</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,78 @@
|
||||
{% extends '__layout__.html' %}
|
||||
{% block title %}View Offer{% endblock %}
|
||||
{% block head %}
|
||||
<style>
|
||||
.text-secondary {
|
||||
color: rgb(199, 199, 199) !important;
|
||||
}
|
||||
.text-robux {
|
||||
color: rgb(26, 212, 103) !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
.text-tickets {
|
||||
color: rgb(224, 224, 60) !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="container position-relative" style="min-height: 100vh;margin-top: 200px;max-width: 900px;">
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
<div>
|
||||
{% for category, message in messages %}
|
||||
{% if category == 'error': %}
|
||||
<div class="alert border p-2 text-center alert-danger border-danger">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if category == 'success': %}
|
||||
<div class="alert border p-2 text-center alert-success border-success">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<a class="text-decoration-none mb-3" href="/currency-exchange/">Return to Currency Exchange</a>
|
||||
<h1 class="m-0">Viewing Offer</h1>
|
||||
{% if offer.creator_id == AuthenticatedUser.id: %}
|
||||
<p class="text-secondary m-0">This offer was created by you</p>
|
||||
{% endif %}
|
||||
<div class="row mt-2 p-2">
|
||||
<div class="col border p-2">
|
||||
<h5 class="text-center">Offering</h5>
|
||||
<h2 style="font-size: 3.5rem;" class="{% if offer.offer_currency_type == 0: %}text-robux{%else%}text-tickets{%endif%} w-100 text-center">{% if offer.offer_currency_type == 0: %}R${%else%}T${%endif%}{{offer.offer_value}}</h2>
|
||||
</div>
|
||||
<div class="col border p-2">
|
||||
<h5 class="text-center">R:T Ratio</h5>
|
||||
<h2 style="font-size: 3.5rem;" class="text-secondary w-100 text-center">1:{{round(offer.ratio,3)}}</h2>
|
||||
</div>
|
||||
<div class="col border p-2">
|
||||
<h5 class="text-center">Requesting</h5>
|
||||
<h2 style="font-size: 3.5rem;" class="{% if offer.offer_currency_type == 1: %}text-robux{%else%}text-tickets{%endif%} w-100 text-center">{% if offer.offer_currency_type == 1: %}R${%else%}T${%endif%}{{offer.receive_value}}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex mt-2">
|
||||
<p class="text-secondary m-0">Created: <span class="text-white">{{offer.created_at.strftime("%b %d, %Y %I:%M %p")}} UTC</span></p>
|
||||
<p class="text-secondary m-0 ms-3">Expires: <span class="text-white">{{offer.expires_at.strftime("%b %d, %Y %I:%M %p")}} UTC</span></p>
|
||||
{% if offer.reciever_id != None: %}
|
||||
<p class="text-danger m-0 ms-auto">This order has already been filled</p>
|
||||
{% else: %}
|
||||
{% if offer.creator_id == AuthenticatedUser.id: %}
|
||||
<form method="post" action="/currency-exchange/delete/{{offer.id}}" class="ms-auto">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button type="submit" class="btn btn-outline-danger">Delete Offer</button>
|
||||
</form>
|
||||
{%else%}
|
||||
<form method="post" action="/currency-exchange/fill/{{offer.id}}" class="ms-auto">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button type="submit" class="btn btn-outline-primary">Fufill Offer</button>
|
||||
</form>
|
||||
{%endif%}
|
||||
{%endif%}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user