This commit is contained in:
copyrighttxt
2026-07-06 13:28:00 -04:00
commit 05211f73ef
441 changed files with 94128 additions and 0 deletions
+378
View File
@@ -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")
+168
View File
@@ -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 %}
+114
View File
@@ -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 %}
+78
View File
@@ -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 %}