Initial commit

This commit is contained in:
Ed 2022-12-18 17:40:39 +01:00
commit d8ea9e6bd1
11 changed files with 895 additions and 0 deletions

113
app.py Normal file
View File

@ -0,0 +1,113 @@
from sanic import Sanic, response, exceptions
from sanic.response import text, html, redirect, raw
from jinja2 import Environment, FileSystemLoader
from time import time
import httpx
import re
import json
from ext import *
from config import *
app = Sanic(__name__)
app.static("/res", "res/")
app.ext.add_dependency(Order, get_order)
app.ext.add_dependency(Quotas, get_quotas)
from room import bp as room_bp
app.blueprint([room_bp,])
@app.exception(exceptions.SanicException)
async def clear_session(request, exception):
tpl = app.ctx.tpl.get_template('error.html')
response = html(tpl.render(exception=exception))
if exception.status_code == 403:
del response.cookies["foxo_code"]
del response.cookies["foxo_secret"]
return response
@app.before_server_start
async def main_start(*_):
print(">>>>>> main_start <<<<<<")
app.ctx.tpl = Environment(loader=FileSystemLoader("tpl"), autoescape=True)
app.ctx.tpl.globals.update(time=time)
app.ctx.tpl.globals.update(int=int)
app.ctx.tpl.globals.update(len=len)
@app.route("/furizon/beyond/order/<code>/<secret>/open/<secret2>")
async def redirect_explore(request, code, secret, order: Order, secret2=None):
response = redirect(app.url_for("welcome"))
if order and order.code != code: order = None
if not order:
async with httpx.AsyncClient() as client:
res = await client.get(f"https://reg.furizon.net/api/v1/organizers/furizon/events/beyond/orders/{code}/", headers=headers)
if res.status_code != 200:
raise exceptions.NotFound("This order code does not exist. Check that your order wasn't deleted, or the link is correct.")
res = res.json()
if secret != res['secret']:
raise exceptions.Forbidden("The secret part of the url is not correct. Check your E-Mail for the correct link, or contact support!")
response.cookies['foxo_code'] = code
response.cookies['foxo_secret'] = secret
return response
@app.route("/manage/welcome")
async def welcome(request, order: Order, quota: Quotas):
if not order:
raise exceptions.Forbidden("You have been logged out. Please access the link in your E-Mail to login again!")
pending_roommates = []
if order.pending_roommates:
print('Oleee')
print(order.ans('pending_roommates'))
for pr in order.pending_roommates:
if not pr: continue
print(pr)
pending_roommates.append(await get_order(code=pr, insecure=True))
print(pending_roommates)
room_members = []
if order.room_id:
if order.room_id != order.code:
room_owner = await get_order(code=order.room_id, insecure=True)
else:
room_owner = order
room_members.append(room_owner)
for member_id in room_owner.ans('room_members').split(','):
if member_id == room_owner.code: continue
if member_id == order.code:
room_members.append(order)
else:
room_members.append(await get_order(code=member_id, insecure=True))
tpl = app.ctx.tpl.get_template('welcome.html')
return html(tpl.render(order=order, quota=quota, room_members=room_members, pending_roommates=pending_roommates))
@app.route("/manage/download_ticket")
async def download_ticket(request, order: Order, quota: Quotas):
if not order:
raise exceptions.Forbidden("You have been logged out. Please access the link in your E-Mail to login again!")
if not order.status != 'confirmed':
raise exceptions.Forbidden("You are not allowed to download this ticket.")
async with httpx.AsyncClient() as client:
res = await client.get(f"https://reg.furizon.net/api/v1/organizers/furizon/events/beyond/orders/{order.code}/download/pdf/", headers=headers)
if res.status_code != 200:
raise exceptions.FileNotFound("Your ticket hasn't been generated yet. Please try later!")
return raw(res.content, content_type='application/pdf')
print(res.content)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8188, dev=True)

1
config.py Normal file
View File

@ -0,0 +1 @@
headers = {'Authorization': 'Token 2q5h7a2z9tqfz8ogu0qpriclwufelswc7obapw1mq2l1b4zaos7ebpz424zvz5gv'}

126
ext.py Normal file
View File

@ -0,0 +1,126 @@
from dataclasses import dataclass
from sanic import Request, exceptions
import httpx
import re
from config import *
@dataclass
class Order:
def __init__(self, data):
self.data = data
self.status = {'n': 'pending', 'p': 'paid', 'e': 'expired', 'c': 'canceled'}[self.data['status']]
self.code = data['code']
for p in self.data['positions']:
if p['item'] not in [16, 38]:
continue
self.position_id = p['id']
self.position_positionid = p['positionid']
self.answers = p['answers']
self.name = self.ans('fursona_name')
self.room_id = self.ans('room_id')
self.room_confirmed = self.ans('room_confirmed')
self.pending_room = self.ans('pending_room')
self.pending_roommates = self.ans('pending_roommates').split(',') if self.ans('pending_roommates') else []
self.room_members = self.ans('room_members').split(',') if self.ans('room_members') else []
self.room_owner = (self.code == self.room_id)
self.room_secret = self.ans('room_secret')
def __getitem__(self, var):
return self.data[var]
def ans(self, name):
for a in self.answers:
if a['question_identifier'] == name:
if a['answer'] in ['True', 'False']:
return bool(a['answer'] == 'True')
return a['answer']
return None
async def edit_answer(self, name, new_answer):
found = False
for key in range(len(self.answers)):
if self.answers[key]['question_identifier'] == name:
if new_answer != None:
print('EXISTING ANSWER UPDATE', name, '=>', new_answer)
self.answers[key]['answer'] = new_answer
found = True
else:
print('DEL ANSWER', name, '=>', new_answer)
del self.answers[key]
break
if (not found) and (new_answer is not None):
async with httpx.AsyncClient() as client:
res = await client.get('https://reg.furizon.net/api/v1/organizers/furizon/events/beyond/questions/', headers=headers)
res = res.json()
for r in res['results']:
if r['identifier'] != name: continue
print('ANSWER UPDATE', name, '=>', new_answer)
self.answers.append({
'question': r['id'],
'answer': new_answer,
'question_identifier': r['identifier'],
'options': r['options']
})
async def send_answers(self):
async with httpx.AsyncClient() as client:
res = await client.patch(f'https://reg.furizon.net/api/v1/organizers/furizon/events/beyond/orderpositions/{self.position_id}/', headers=headers, json={'answers': self.answers})
@dataclass
class Quotas:
def __init__(self, data):
self.data = data
self.capacity_mapping = {
1: 16,
2: 17,
3: 18,
4: 19,
5: 20
}
def get_left(self, capacity):
for quota in self.data['results']:
if quota['id'] == self.capacity_mapping[capacity]:
return quota['available_number']
async def get_quotas(request: Request=None):
async with httpx.AsyncClient() as client:
res = await client.get(f"https://reg.furizon.net/api/v1/organizers/furizon/events/beyond/quotas/?order=id&with_availability=true", headers=headers)
res = res.json()
return Quotas(res)
async def get_order(request: Request=None, code=None, secret=None, insecure=False):
if request:
await request.receive_body()
code = request.cookies.get("foxo_code")
secret = request.cookies.get("foxo_secret")
if re.match('^[A-Z0-9]{5}$', code or '') and (secret is None or re.match('^[a-z0-9]{16,}$', secret)):
print('Trying to get a session from stored', code, secret)
async with httpx.AsyncClient() as client:
res = await client.get(f"https://reg.furizon.net/api/v1/organizers/furizon/events/beyond/orders/{code}/", headers=headers)
if res.status_code != 200:
if request:
raise exceptions.Forbidden("Your session has expired due to order deletion or change! Please check your E-Mail for more info.")
res = res.json()
if res['status'] in ['c', 'e']:
if request:
raise exceptions.Forbidden(f"Your order has been deleted. Contact support with your order identifier ({res['code']}) for further info.")
if secret == res['secret'] or insecure:
return Order(res)
else:
if request:
raise exceptions.Forbidden("Your session has expired due to a token change. Please check your E-Mail for an updated link!")
return None

BIN
res/avatar.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

BIN
res/new.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

279
room.py Normal file
View File

@ -0,0 +1,279 @@
from sanic.response import html, redirect, text
from sanic import Blueprint, exceptions
from random import choice
from ext import *
from config import headers
bp = Blueprint("room", url_prefix="/manage/room")
@bp.post("/create")
async def room_create_post(request, order: Order):
if not order: raise exceptions.Forbidden("You have been logged out. Please access the link in your E-Mail to login again!")
error = None
name = request.form.get('name')
if len(name) > 64 or len(name) < 4:
error = "Your room name is invalid. Please try another one."
if order.room_id:
error = "You are already in another room. You need to delete (if it's yours) or leave it before creating another."
if not error:
await order.edit_answer('room_name', name)
await order.edit_answer('room_id', order.code)
await order.edit_answer('room_members', order.code)
await order.edit_answer('room_secret', ''.join(choice('0123456789') for _ in range(6)))
await order.send_answers()
return redirect('/manage/welcome')
tpl = request.app.ctx.tpl.get_template('create_room.html')
return html(tpl.render(order=order, error=error))
@bp.route("/create")
async def room_create(request, order: Order):
if not order: raise exceptions.Forbidden("You have been logged out. Please access the link in your E-Mail to login again!")
tpl = request.app.ctx.tpl.get_template('create_room.html')
return html(tpl.render(order=order))
@bp.route("/delete")
async def delete_room(request, order: Order):
if not order:
raise exceptions.Forbidden("You have been logged out. Please access the link in your E-Mail to login again!")
if order.room_id != order.code:
raise exceptions.BadRequest("You are not allowed to delete room of others.")
if order.ans('room_confirmed'):
raise exceptions.BadRequest("You are not allowed to change your room after it has been confirmed.")
if len(order.room_members) > 1:
raise exceptions.BadRequest("You can only delete a room once there is nobody else inside.")
await order.edit_answer('room_name', None)
await order.edit_answer('room_id', None)
await order.edit_answer('room_members', None)
await order.edit_answer('room_secret', None)
await order.send_answers()
return redirect('/manage/welcome')
@bp.post("/join")
async def join_room(request, order: Order):
if not order:
raise exceptions.Forbidden("You have been logged out. Please access the link in your E-Mail to login again!")
if order.pending_room:
raise exceptions.BadRequest("There is already a pending join request. Wait for the room owner to accept or refuse it.")
if order.room_id:
raise exceptions.BadRequest("You are in another room already. Why would you join another?")
code = request.form.get('code').strip()
room_secret = request.form.get('room_secret').strip()
if (not code) or (not room_secret):
raise exceptions.BadRequest("The code or pin you provided are not valid.")
room_owner = await get_order(code=code, insecure=True)
if not room_owner:
raise exceptions.BadRequest("The code you provided is not valid.")
if room_owner.room_secret != room_secret:
raise exceptions.BadRequest("The code or pin you provided is not valid.")
if room_owner.room_confirmed:
raise exceptions.BadRequest("The room you're trying to join has been confirmed already")
#if room_owner.pending_roommates and (order.code in room_owner.pending_roommates):
#raise exceptions.BadRequest("What? You should never reach this check, but whatever...")
await order.edit_answer('pending_room', code)
await order.send_answers()
pending_roommates = room_owner.pending_roommates
if not order.code in pending_roommates:
pending_roommates.append(order.code)
await room_owner.edit_answer('pending_roommates', ','.join(pending_roommates))
await room_owner.send_answers()
return redirect('/manage/welcome')
@bp.route("/kick/<code>")
async def kick_member(request, code, order: Order):
if not order:
raise exceptions.Forbidden("You have been logged out. Please access the link in your E-Mail to login again!")
if order.room_confirmed:
raise exceptions.BadRequest("You cannot kick people out of confirmed rooms")
if not order.room_owner:
raise exceptions.BadRequest("You cannot kick people if you're not the room owner")
to_kick = await get_order(code=code, insecure=True)
if to_kick.room_id != order.code:
raise exceptions.BadRequest("You cannot kick people of other rooms")
await to_kick.edit_answer('room_id', None)
await order.edit_answer('room_members', ','.join([x for x in order.room_members if x != to_kick.code]) or None)
await order.send_answers()
await to_kick.send_answers()
return redirect('/manage/welcome')
@bp.route("/renew_secret")
async def renew_secret(request, order: Order):
if not order:
raise exceptions.Forbidden("You have been logged out. Please access the link in your E-Mail to login again!")
if not order.room_id:
raise exceptions.BadRequest("What room were you even trying to renew?")
if not order.room_owner:
raise exceptions.BadRequest("You are not allowed to renew rooms of others.")
await order.edit_answer('room_secret', ''.join(choice('0123456789') for _ in range(6)))
await order.send_answers()
return redirect('/manage/welcome')
@bp.route("/cancel_request")
async def renew_secret(request, order: Order):
if not order:
raise exceptions.Forbidden("You have been logged out. Please access the link in your E-Mail to login again!")
if not order.pending_room:
raise exceptions.BadRequest("There is no pending room.")
room_owner = await get_order(code=order.pending_room, insecure=True)
pending_roommates = room_owner.pending_roommates
if order.code in pending_roommates:
pending_roommates.remove(order.code)
await room_owner.edit_answer('pending_roommates', ','.join(pending_roommates) if pending_roommates else None)
await room_owner.send_answers()
await order.edit_answer('pending_room', None)
await order.send_answers()
return redirect('/manage/welcome')
@bp.route("/approve/<code>")
async def reject_roomreq(request, code, order: Order):
if not order:
raise exceptions.Forbidden("You have been logged out. Please access the link in your E-Mail to login again!")
if not code in order.pending_roommates:
raise exceptions.BadRequest("You cannot accept people that didn't request to join your room")
if order.room_confirmed:
raise exceptions.BadRequest("You cannot accept people to a confirmed room.")
pending_member = await get_order(code=code, insecure=True)
if pending_member.room_id:
raise exceptions.BadRequest("You cannot accept people who are in a room.")
if pending_member.pending_room != order.code:
raise exceptions.BadRequest("You cannot accept people who are in another room or waiting to accept another request.")
await pending_member.edit_answer('room_id', order.code)
await pending_member.edit_answer('pending_room', None)
await order.edit_answer('room_members', ','.join([*order.room_members, pending_member.code]))
await order.edit_answer('pending_roommates', (','.join([x for x in order.pending_roommates if x != pending_member.code]) or None))
await pending_member.send_answers()
await order.send_answers()
return redirect('/manage/welcome')
@bp.route("/reject/<code>")
async def reject_roomreq(request, code, order: Order):
if not order:
raise exceptions.Forbidden("You have been logged out. Please access the link in your E-Mail to login again!")
if not code in order.pending_roommates:
raise exceptions.BadRequest("You cannot reject people that didn't request to join your room")
if order.room_confirmed:
raise exceptions.BadRequest("You cannot reject people to a confirmed room.")
pending_member = await get_order(code=code, insecure=True)
if pending_member.room_id:
raise exceptions.BadRequest("You cannot reject people who are in a room.")
if pending_member.pending_room != order.code:
raise exceptions.BadRequest("You cannot reject people who are in another room or waiting to accept another request.")
await pending_member.edit_answer('pending_room', None)
await order.edit_answer('pending_roommates', (','.join([x for x in order.pending_roommates if x != pending_member.code]) or None))
await pending_member.send_answers()
await order.send_answers()
return redirect('/manage/welcome')
@bp.route("/confirm")
async def confirm_room(request, order: Order, quotas: Quotas):
if not order:
raise exceptions.Forbidden("You have been logged out. Please access the link in your E-Mail to login again!")
if not order.room_id:
raise exceptions.BadRequest("Try joining a room before confirming it.")
if order.room_id != order.code:
raise exceptions.BadRequest("You are not allowed to confirm rooms of others.")
if quotas.get_left(len(order.room_members)) == 0:
raise exceptions.BadRequest("There are no more rooms of this size to reserve.")
room_members = []
for m in order.room_members:
if m == order.code:
res = order
else:
res = await get_order(code=m, insecure=True)
if res.room_id != order.code:
raise exceptions.BadRequest("Please contact support: some of the members in your room are actually somewhere else")
if res.status != 'paid':
raise exceptions.BadRequest("Somebody hasn't paid.")
room_members.append(res)
for rm in room_members:
await rm.edit_answer('room_id', order.code)
await rm.edit_answer('room_confirmed', "True")
await rm.edit_answer('pending_roommates', None)
await rm.edit_answer('pending_room', None)
print(room_members)
print(len(room_members))
thing = {
'order': order.code,
'addon_to': order.position_positionid,
'item': 39,
'variation': ([None, 16, 17, 18, 19, 20])[len(room_members)]
}
async with httpx.AsyncClient() as client:
res = await client.post("https://reg.furizon.net/api/v1/organizers/furizon/events/beyond/orderpositions/", headers=headers, json=thing)
print(thing)
print(res.json())
print(res.status_code)
if res.status_code != 201:
raise exceptions.BadRequest("Something has gone wrong! Please contact support immediately")
for rm in room_members:
await rm.send_answers()
return redirect('/manage/welcome')

21
tpl/base.html Normal file
View File

@ -0,0 +1,21 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>{% block title %}{% endblock %}</title>
<link rel="stylesheet" href="https://unpkg.com/@picocss/pico@latest/css/pico.min.css">
<style>
.propic {border-radius:100%;}
.people img {border-radius:100%;max-width:6em;}
.people div {text-align:center;}
.people h3, .people p {margin:0;}
mark {background:#0f0;}
h1 img {max-height:1.4em;}
.notice {padding:0.8em;border-radius:3px;background:#e53935;color:#eee;}
.notice a {color:#eee;font-decoration:underline;}
</style>
</head>
<body>
{% block main %}{% endblock %}
</body>
</html>

22
tpl/create_room.html Normal file
View File

@ -0,0 +1,22 @@
{% extends "base.html" %}
{% block title %}Welcome!{% endblock %}
{% block main %}
<style>
.container {max-width:40em}
</style>
<main class="container">
<header>
<h1>Create a new room</h1>
</header>
{% if error %}
<p class="notice">{{error}}</p>
{% endif %}
<p>Give a name to your room. This name will be public and shown in the room list, so nothing offensive! :)</p>
<form method="POST">
<label for="name">Room name</label>
<input type="text" name="name" placeholder="{{order.name}}'s room" required minlength="4" maxlength="64" />
<input type="submit" value="Create room" />
</form>
</main>
{% endblock %}

8
tpl/error.html Normal file
View File

@ -0,0 +1,8 @@
{% extends "base.html" %}
{% block title %}Error {{exception.status_code}}{% endblock %}
{% block main %}
<main class="container">
<h1>Oh no! Error {{exception.status_code}}</h1>
<p>{{exception}}</p>
</main>
{% endblock %}

97
tpl/manage.html Normal file
View File

@ -0,0 +1,97 @@
{% extends "base.html" %}
{% block title %}Your Booking{% endblock %}
{% block main %}
<main class="container">
<header>
<h1><img src="{{order.ans('propic') or '/res/avatar.jpg'}}" class="propic"> {{order.ans('fursona_name')}}'s booking</h1>
</header>
{% if order.status() == 'pending' %}
<p class="notice">⚠️ Your order is still pending. You will not be able to reserve a room. <a href="#">Check your payment status</a></p>
{% endif %}
{% if not order.ans('propic') %}
<p class="notice">⚠️ You still haven't uploaded a profile pic. <a href="#">Upload one now</a></p>
{% endif %}
<section id="room_prompt">
<h3>Room Status:
{% if order.ans('room_confirmed') %}
{{order.ans('room_confirmed')}}
<span style="color: green;">room confirmed</span></h3>
<p>Your room is confirmed! Enjoy the convention :)</p>
{% elif order.ans('room_members') %}
<span style="color: yellow;">pending room</span></h3>
<p>You have a room, but it is not confirmed! Make sure all room owners have paid, then use the button below to reserve the room for two people.</p>
<h5>Your room</h5>
<div class="grid people">
{% set room = namespace(forbidden=false) %}
{% for person in roommate_orders %}
<div>
<img class="propic" src="https://picsum.photos/200" />
<h3>{{person.ans('fursona_name')}}</h3>
<p>{{person.ans('staff_title') if person.ans('staff_title') else ''}}{{' · Fursuiter' if person.ans('is_fursuiter') != 'No'}}</p>
<p>{{('<strong style="color:red;">UNPAID</strong>' if person.status() == 'pending')|safe}}</p>
</div>
{% if person.status() != 'paid' %}
{% set room.forbidden = True %}
{% endif %}
{% endfor %}
</div>
<h5>Available rooms</h5>
<table>
<tr>
<td>Single room</td>
<td>{{room_qty[1]}} available</td>
</tr>
<tr>
<td>Double room</td>
<td>{{room_qty[2]}} available</td>
</tr>
<tr>
<td>Triple room</td>
<td>{{room_qty[3]}} available</td>
</tr>
<tr>
<td>Quadruple room</td>
<td>{{room_qty[4]}} available</td>
</tr>
<tr>
<td>Quintuple room</td>
<td>{{room_qty[5]}} available</td>
</tr>
</table>
{% if room.forbidden %}
<p>Since at least one of your roommates still have a pending reservation, you will not be able to reserve a room for now.</p>
<button disabled>Reserve a room for 3 people</button>
{% else %}
<button>Reserve a room for 3 people</button>
{% endif %}
{% else %}
<span style="color: red;">no room</span></h3>
<p>You currently don't have any room. If you don't create or join one within 42 days, 10 hours, 32 minutes, 13 seconds, we will assign you to a random one.</p>
<form>
<div class="grid">
<button>Create a room</button>
<button>Join another room</button>
</div>
</form>
{% endif %}
</section>
</main>
<!--<section id="room_prompt">
<h3>Room status: </h3>
<p>Good news</p>
<div class="grid">
<button>Create a room</button>
<button>Join another room</button>
</div>
</section>
</main>-->
{% endblock %}

228
tpl/welcome.html Normal file
View File

@ -0,0 +1,228 @@
{% extends "base.html" %}
{% block title %}{{order.name}}'s Booking{% endblock %}
{% block main %}
<style>
.container {max-width:40em}
</style>
<main class="container">
<header>
<h1>{{order.name}}'s Booking</h1>
</header>
<!-- Payment section -->
<section>
<h2>Payment</h2>
{% if order.status == 'pending' %}
<p class="notice">⚠️ Your order is still pending due to incomplete payment. You will not be able to reserve a room for now. However, you will be able to create one with your friends and confirm it once all attendants have completed the order!</p>
<p>If you wish to <strong>change payment method, check payment instructions or complete a failed payment</strong> please access the payment area.</p>
{% elif order.status == 'paid' %}
<p class="notice" style="background:#050;">✅ Your order has been completed and approved! See you at furizon!</p>
{% endif %}
<table>
<tr>
<td>Reference ID</td>
<td>{{order.code}}</td>
</tr>
<tr>
<td>Order total</td>
<td>{{order.data['total']}}€ by {{'Credit card' if order.data['payment_provider'] == 'stripe' else 'Bank Transfer'}}</td>
</tr>
</table>
{% if order.status == 'paid' %}
<p style="text-align:right;"><a href="/manage/download_ticket?name=BEYOND-{{order.code}}.pdf" role="button">Download ticket</a></p>
{% else %}
<a href="{{order.url}}"><button>Payment area</button></a>
{% endif %}
</section>
<!-- Room section -->
<section>
<h2>Your room {% if room_members %}- {{room_members[0].ans('room_name')}}{% endif %}</h2>
{# Show alert if room owner has wrong people inside #}
{% if order.room_owner and quota.get_left(len(room_members)) == 0 and (not order.room_confirmed) %}
<p class="notice">⚠️ Your room contains {{len(room_members)}} people inside, but sadly there are 0 rooms of this size available. Add or remove people until you reach the size of an available room.</p>
{% endif %}
{# Show alert if room was not confirmed #}
{% if order.room_id and not order.room_confirmed %}
<p class="notice">⚠️ Your room hasn't been confirmed yet. Unconfirmed rooms are subject to changes by the staff as we optimize for hotel capacity.</p>
{% endif %}
{# Show notice if the room is confirmed #}
{% if order.room_confirmed %}
<p class="notice" style="background:#060">✅ Your room has been confirmed</p>
{% endif %}
{# Show roommates if room is set #}
{% if order.room_id %}
<div class="grid people" style="padding-bottom:1em;">
{% set room = namespace(forbidden=false) %}
{% for person in room_members %}
<div>
<img class="propic" src="{{person.ans('propic') or '/res/avatar.jpg'}}" />
<h3>{{person.ans('fursona_name')}}</h3>
{% if person.code == order.room_id %}<p><strong>ROOM OWNER</strong></p>{% endif %}
<p>{{person.ans('staff_title') if person.ans('staff_title') else ''}}{{' · Fursuiter' if person.ans('is_fursuiter') != 'No'}}</p>
{% if person.status == 'pending' %}
<p><strong style="color:red;">UNPAID</strong></p>
{% else %}
<p><strong style="color:#060;">PAID</strong></p>
{% endif %}
{% if order.room_owner and person.code != order.code %}<a href="/manage/room/kick/{{person.code}}">KICK</a>{% endif %}
</div>
{% if person.status != 'paid' %}
{% set room.forbidden = True %}
{% endif %}
{% endfor %}
{% if order.room_id == order.code and not order.room_confirmed and len(room_members) < 5 %}
<div>
<a href="javascript:document.getElementById('modal-roominvite').setAttribute('open', 'true');">
<img class="propic" src="/res/new.png" />
<h3>Invite</h3>
<p>Get room code</p>
</a>
</div>
{% endif %}
</div>
{% elif order.pending_room %}
<p>You have have asked to join the room of another member. Wait for them to confirm or reject your request.</p>
<a role="button" href="/manage/room/cancel_request">Cancel pending join request</a>
{% else %}
<p class="notice">🎲 If you don't join a room or create your one within the room deadline, we will randomly put you into a room with free spots.</p>
<p>To join a room, ask somebody to send you their room code.</p>
<p class="grid">
<a role="button" href="/manage/room/create">Create a room</a>
<a role="button" href="javascript:document.getElementById('modal-joinroom').setAttribute('open', 'true');">Join a room</a>
</p>
{% endif %}
{% if order.room_owner %}
{% if quota.get_left(len(room_members)) == 0 %}
<p class="notice">⚠️ There are no more {{[None,'single','double','triple','quadruple','quintuple'][len(room_members)]}}, therefore you will not be able to confirm this room. Please add or remove people until you reach an available room.</p>
{% elif room.forbidden %}
<p class="notice">⚠️ There are roommates who still didn't pay for the order, therefore you will not be able to confirm this room. Please ask them to pay or kick them out from your room.</p>
{% endif %}
{% endif %}
{% if order.room_owner %}
<p class="grid">
{% if len(room_members) == 1 %}
<a href="/manage/room/delete" role="button">Delete room</a>
{% endif %}
{% if not order.room_confirmed %}
<a role="button" {% if not room.forbidden and quota.get_left(len(room_members)) > 0 %}href="javascript:document.getElementById('modal-roomconfirm').setAttribute('open', 'true');"{% endif %}>Confirm <strong>{{[None,'single','double','triple','quadruple','quintuple'][len(room_members)]}}</strong> room</a>
{% endif %}
</p>
{% endif %}
{# Pending roommates #}
{% if pending_roommates %}
<h4>Pending roommates</h4>
<p>These people have asked to join your room.</p>
<table>
{% for person in pending_roommates %}
<tr>
<td><img style="max-height:2em" class="propic" src="{{person.ans('propic') or '/res/avatar.jpg'}}" /></td>
<td>{{person.name}}</td>
{% if person.status == 'pending' %}
<td><strong style="color:red;">UNPAID</strong></td>
{% else %}
<td><strong style="color:#060;">PAID</strong></td>
{% endif %}
<td><a role="button" href="/manage/room/approve/{{person.code}}">Approve</a></td>
<td><a role="button" href="/manage/room/reject/{{person.code}}">Reject</a></td>
</tr>
</div>
{% if person.status != 'paid' %}
{% set room.forbidden = True %}
{% endif %}
{% endfor %}
</table>
{% endif %}
{# Room availability is always shown #}
<h4>Room availability</h4>
<table>
{% for q in quota.data['results'] if 'Room' in q['name'] %}
<tr {% if q['available_number'] == 0 %}style="text-decoration:line-through;"{% endif %}>
<td>{{q['name']}}</td>
<td>{{q['available_number']}} left</td>
</tr>
{% endfor %}
</table>
</main>
{% if order.room_owner and not order.room_confirmed %}
<!-- Room Invite dialog -->
<dialog id="modal-roominvite">
<article>
<a href="#close" aria-label="Close" class="close" onClick="javascript:this.parentElement.parentElement.removeAttribute('open')"></a>
<h3>Invite your friends!</h3>
<label for="code">Reference Code</label>
<input name="code" type="text" onclick="select()" value="{{order.code}}" readonly />
<label for="room_secret">Room PIN</label>
<input name="room_secret" type="password" onclick="select()" onmouseover="this.type = 'text';" value="{{order.ans('room_secret')}}" readonly />
<p>Send your Ticket ID and room PIN to other attendants you want in your room.</p>
<p>If you want to change the room PIN, use the "Reset PIN" button to change the secret code.</p>
<footer>
<a href="javascript:document.getElementById('modal-roominvite').removeAttribute('open')" role="button" class="secondary">Close</a>
<a href="/manage/room/renew_secret" role="button">Reset PIN</a>
</footer>
</article>
</dialog>
<dialog id="modal-roomconfirm">
<article>
<a href="#close" aria-label="Close" class="close" onClick="javascript:this.parentElement.parentElement.removeAttribute('open')"></a>
<h3>Confirm this room</h3>
<p>Confirming the room is the only way to guarantee that you will stay with your friends.</p>
<p>Confirmed room cannot be changed. You will not be able to add or remove roommates, or change to another size.</p>
<p>In case somebody from your room decides to not participate, they will be replaced with a random person, or your room size will be changed.</p>
<h4>Your room</h4>
<table>
<tr>
<td>Room type</td>
<td><strong>{{[None,'Single','Double','Triple','Quadruple','Quintuple'][len(room_members)]}} Room</strong></td>
</tr>
<tr>
<td>Rooms left of this type</td>
<td><strong>{{quota.get_left(len(room_members))}}</strong></td>
</tr>
</table>
<footer>
<a href="javascript:document.getElementById('modal-roomconfirm').removeAttribute('open')" role="button" class="secondary">Close</a>
<a href="/manage/room/confirm" role="button">Confirm <strong>{{[None,'single','double','triple','quadruple','quintuple'][len(room_members)]}}</strong> room</a>
</footer>
</article>
</dialog>
{% endif %}
{% if not order.room_id %}
<form method="post" action="/manage/room/join">
<dialog id="modal-joinroom">
<article>
<a href="#close" aria-label="Close" class="close" onClick="javascript:this.parentElement.parentElement.removeAttribute('open')"></a>
<h3>Join a room!</h3>
<label for="code">Reference Code</label>
<input name="code" placeholder="XXXXXX" type="text" value="" />
<label for="room_secret">Room pin</label>
<input name="room_secret" placeholder="00000" type="text" value="" />
<footer>
<input type="submit" value="Send request" />
</footer>
</article>
</dialog>
</form>
{% endif %}
{% endblock %}