Compare commits
No commits in common. "2089ee4161a7e70e4938f422db954885d9d5a8d6" and "70fd59a4be0426d809adc1b74e969a7c296aa158" have entirely different histories.
2089ee4161
...
70fd59a4be
30 changed files with 442 additions and 1058 deletions
|
|
@ -6,9 +6,3 @@ TIMELINE_ORDER_PUNISHED = "ORDER_PUNISHED"
|
|||
TIMELINE_ORDERS_POOL_CREATED = "ORDERS_POOL_CREATED"
|
||||
TIMELINE_ORDERS_POOL_UPDATED = "ORDERS_POOL_UPDATED"
|
||||
TIMELINE_ORDERS_POOL_DELETED = "ORDERS_POOL_DELETED"
|
||||
|
||||
TIMELINE_ORDERS_POOL_EVENTS = [
|
||||
TIMELINE_ORDERS_POOL_CREATED,
|
||||
TIMELINE_ORDERS_POOL_UPDATED,
|
||||
TIMELINE_ORDERS_POOL_DELETED
|
||||
]
|
||||
|
|
|
|||
|
|
@ -40,10 +40,6 @@ class User(BaseModel):
|
|||
verify_mastodon_favorite = BooleanField(null=False, default=False)
|
||||
verify_delay = IntegerField(null=True)
|
||||
|
||||
permission_orders_pools_view = BooleanField(null=False, default=True)
|
||||
permission_orders_pools_details = BooleanField(null=False, default=True)
|
||||
permission_orders_pools_edit = BooleanField(null=False, default=True)
|
||||
|
||||
def mastodon_account(self):
|
||||
if self.mastodon_server is None or self.mastodon_username is None:
|
||||
return
|
||||
|
|
@ -187,9 +183,6 @@ class OrderStatus(BaseModel):
|
|||
null=True
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.pool} {self.id}"
|
||||
|
||||
class Meta:
|
||||
table_name = 'order_status'
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import datetime
|
|||
import json
|
||||
from peewee import JOIN, fn
|
||||
|
||||
from db.constants import TIMELINE_ORDERS_POOL_EVENTS
|
||||
from util import sqlite_time
|
||||
|
||||
from .models import database, User, OrdersPool, DomSubUsers, Repeat, SkipDay, OrderStatus, MastodonServer, TimelineEvent
|
||||
|
|
@ -55,45 +54,6 @@ def user_preferences_set(id, mastodon_post_public, mastodon_attn_list):
|
|||
def user_has_doms(id):
|
||||
return DomSubUsers.select().where(DomSubUsers.sub_id == id).count() > 0
|
||||
|
||||
def user_doms(id):
|
||||
return [d.dom for d in DomSubUsers.select(DomSubUsers.dom).where(DomSubUsers.sub_id == id)]
|
||||
|
||||
def user_subs(id):
|
||||
return [d.sub for d in DomSubUsers.select(DomSubUsers.sub).where(DomSubUsers.dom_id == id)]
|
||||
|
||||
def user_can_orders_pools_view(user, sub):
|
||||
doms = user_doms(sub.id)
|
||||
|
||||
if len(doms) > 0:
|
||||
if user == sub:
|
||||
return user.permission_orders_pools_view
|
||||
else:
|
||||
return user in doms
|
||||
else:
|
||||
return user.id == sub.id
|
||||
|
||||
def user_can_orders_pools_details(user, sub):
|
||||
doms = user_doms(sub.id)
|
||||
|
||||
if len(doms) > 0:
|
||||
if user == sub:
|
||||
return user.permission_orders_pools_details
|
||||
else:
|
||||
return user in doms
|
||||
else:
|
||||
return user.id == sub.id
|
||||
|
||||
def user_can_orders_pools_edit(user, sub):
|
||||
doms = user_doms(sub.id)
|
||||
|
||||
if len(doms) > 0:
|
||||
if user == sub:
|
||||
return user.permission_orders_pools_edit
|
||||
else:
|
||||
return user in doms
|
||||
else:
|
||||
return user.id == sub.id
|
||||
|
||||
def mastodon_server_get(name):
|
||||
return MastodonServer.get(name=name)
|
||||
|
||||
|
|
@ -226,22 +186,13 @@ def timeline_event_put(type, text, user, orders_pool=None, order_status=None, ac
|
|||
extra=json.dumps(extra) if extra is not None else None
|
||||
)
|
||||
|
||||
def timeline_event_recent(user_id, limit=5):
|
||||
user = User.get_by_id(user_id)
|
||||
|
||||
can_view_orders_pools = user_can_orders_pools_view(user, user)
|
||||
|
||||
result = TimelineEvent.select()
|
||||
|
||||
if(can_view_orders_pools):
|
||||
result = result.where(
|
||||
(TimelineEvent.user_id == user_id) |
|
||||
(TimelineEvent.actor_user_id == user_id)
|
||||
def timeline_event_recent(user_ids, actor_ids=None):
|
||||
return (TimelineEvent
|
||||
.select()
|
||||
.where((
|
||||
TimelineEvent.user_id.in_(user_ids) |
|
||||
(TimelineEvent.actor_user_id.in_(actor_ids) if actor_ids is not None else True)
|
||||
))
|
||||
.order_by(TimelineEvent.updated_at.desc())
|
||||
.limit(10)
|
||||
)
|
||||
else:
|
||||
result = result.where(
|
||||
((TimelineEvent.user_id == user_id) & TimelineEvent.type.not_in(TIMELINE_ORDERS_POOL_EVENTS)) |
|
||||
(TimelineEvent.actor_user_id == user_id)
|
||||
)
|
||||
|
||||
return result.order_by(TimelineEvent.updated_at.desc()).limit(limit)
|
||||
|
|
|
|||
40
main.py
40
main.py
|
|
@ -7,24 +7,38 @@ import json
|
|||
from db.constants import TIMELINE_ORDER_ISSUED, TIMELINE_ORDER_NOT_ISSUED
|
||||
from scheduling import OrderScheduler
|
||||
from generate import generate_order
|
||||
from orders import order_issue, order_check, punishment_issue
|
||||
from db.queries import orders_pool_by_id, order_status_by_id
|
||||
from orders import order_issue, order_check
|
||||
from db.queries import orders_pool_by_id, timeline_event_put
|
||||
from telegram.telegram import handle_commands
|
||||
from telegram.commands import commands
|
||||
from util import make_session
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
async def do_order_issue(orders_pool_id):
|
||||
orders_pool = orders_pool_by_id(orders_pool_id)
|
||||
|
||||
await order_issue(orders_pool)
|
||||
issue_result = await order_issue(orders_pool)
|
||||
|
||||
async def do_punishment_issue(order_status_id):
|
||||
order_status = order_status_by_id(order_status_id)
|
||||
|
||||
async with make_session() as session:
|
||||
await punishment_issue(session, order_status)
|
||||
if 'order_status' in issue_result:
|
||||
order_status = issue_result['order_status']
|
||||
logger.info(f'Issued order id {order_status.id}')
|
||||
timeline_event_put(
|
||||
TIMELINE_ORDER_ISSUED,
|
||||
order_status.text.split("\n")[0],
|
||||
user=orders_pool.user,
|
||||
orders_pool=orders_pool,
|
||||
order_status=order_status,
|
||||
extra={
|
||||
"mastodon_status_url": issue_result['mastodon_status']['url']
|
||||
}
|
||||
)
|
||||
elif 'reason' in issue_result:
|
||||
timeline_event_put(
|
||||
TIMELINE_ORDER_NOT_ISSUED,
|
||||
issue_result['reason'],
|
||||
user=orders_pool.user,
|
||||
orders_pool=orders_pool
|
||||
)
|
||||
|
||||
if __name__=='__main__':
|
||||
logging.basicConfig(
|
||||
|
|
@ -47,9 +61,6 @@ if __name__=='__main__':
|
|||
parser_issue = subparsers.add_parser('check', help='Check on the status of an order')
|
||||
parser_issue.add_argument('order_status_id')
|
||||
|
||||
parser_punish = subparsers.add_parser('punish', help="Punish an order")
|
||||
parser_punish.add_argument('order_status_id')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.command == 'generate':
|
||||
|
|
@ -66,11 +77,6 @@ if __name__=='__main__':
|
|||
loop.run_until_complete(
|
||||
order_check(args.order_status_id)
|
||||
)
|
||||
elif args.command == 'punish':
|
||||
loop = asyncio.new_event_loop()
|
||||
loop.run_until_complete(
|
||||
do_punishment_issue(args.order_status_id)
|
||||
)
|
||||
else:
|
||||
loop = asyncio.new_event_loop()
|
||||
s = OrderScheduler(loop)
|
||||
|
|
|
|||
|
|
@ -1,51 +0,0 @@
|
|||
"""Peewee migrations -- 029_add_sub_permissions.py.
|
||||
|
||||
Some examples (model - class or model name)::
|
||||
|
||||
> Model = migrator.orm['table_name'] # Return model in current state by name
|
||||
> Model = migrator.ModelClass # Return model in current state by name
|
||||
|
||||
> migrator.sql(sql) # Run custom SQL
|
||||
> migrator.run(func, *args, **kwargs) # Run python function with the given args
|
||||
> migrator.create_model(Model) # Create a model (could be used as decorator)
|
||||
> migrator.remove_model(model, cascade=True) # Remove a model
|
||||
> migrator.add_fields(model, **fields) # Add fields to a model
|
||||
> migrator.change_fields(model, **fields) # Change fields
|
||||
> migrator.remove_fields(model, *field_names, cascade=True)
|
||||
> migrator.rename_field(model, old_field_name, new_field_name)
|
||||
> migrator.rename_table(model, new_table_name)
|
||||
> migrator.add_index(model, *col_names, unique=False)
|
||||
> migrator.add_not_null(model, *field_names)
|
||||
> migrator.add_default(model, field_name, default)
|
||||
> migrator.add_constraint(model, name, sql)
|
||||
> migrator.drop_index(model, *col_names)
|
||||
> migrator.drop_not_null(model, *field_names)
|
||||
> migrator.drop_constraints(model, *constraints)
|
||||
|
||||
"""
|
||||
|
||||
from contextlib import suppress
|
||||
|
||||
import peewee as pw
|
||||
from peewee_migrate import Migrator
|
||||
|
||||
|
||||
with suppress(ImportError):
|
||||
import playhouse.postgres_ext as pw_pext
|
||||
|
||||
|
||||
def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
|
||||
"""Write your migrations here."""
|
||||
|
||||
migrator.add_fields(
|
||||
'user',
|
||||
|
||||
permission_orders_pools_view=pw.BooleanField(default=True),
|
||||
permission_orders_pools_details=pw.BooleanField(default=True),
|
||||
permission_orders_pools_edit=pw.BooleanField(default=True))
|
||||
|
||||
|
||||
def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
|
||||
"""Write your rollback migrations here."""
|
||||
|
||||
migrator.remove_fields('user', 'permission_orders_pools_view', 'permission_orders_pools_details', 'permission_orders_pools_edit')
|
||||
148
orders.py
148
orders.py
|
|
@ -2,7 +2,7 @@ import logging
|
|||
import datetime
|
||||
import asyncio
|
||||
|
||||
from db.constants import TIMELINE_ORDER_CONFIRMED, TIMELINE_ORDER_ISSUED, TIMELINE_ORDER_NOT_ISSUED, TIMELINE_ORDER_NOT_PUNISHED, TIMELINE_ORDER_PUNISHED
|
||||
from db.constants import TIMELINE_ORDER_CONFIRMED, TIMELINE_ORDER_NOT_PUNISHED, TIMELINE_ORDER_PUNISHED
|
||||
from util import make_session
|
||||
from generate import generate_order, generate_punishment
|
||||
from db.queries import domsubusers_doms, order_status_by_id, order_status_put, order_status_confirm, timeline_event_put
|
||||
|
|
@ -10,55 +10,57 @@ from mastodon import Mastodon
|
|||
from telegram.telegram import Telegram
|
||||
from settings import ENV
|
||||
from util import timezone
|
||||
from jinja2 import Environment, PackageLoader, select_autoescape
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
template_env = Environment(
|
||||
loader=PackageLoader("orders"),
|
||||
autoescape=select_autoescape()
|
||||
)
|
||||
template_env.globals['ENV'] = ENV
|
||||
|
||||
def filter_short_time(value):
|
||||
return value.strftime("%I:%M %p")
|
||||
template_env.filters["short_time"] = filter_short_time
|
||||
|
||||
async def order_mastodon_post(session, orders_pool, orders_str, repeats, due_at, verify_at=None):
|
||||
user = orders_pool.user
|
||||
|
||||
template = template_env.get_template('mastodon_order')
|
||||
post = template.render(
|
||||
user=user,
|
||||
orders_str=orders_str,
|
||||
repeats=repeats,
|
||||
due_at=due_at,
|
||||
verify_at=verify_at
|
||||
)
|
||||
post = "Here are today's orders for @%s -\n\n" % user.mastodon_account()
|
||||
post += orders_str + "\n\n"
|
||||
if repeats > 1:
|
||||
post += f"These are the same orders from the last {repeats} days\n\n"
|
||||
post += "Proof of compliance is due by " + due_at.strftime("%I:%M %p") + "\n"
|
||||
if verify_at is not None:
|
||||
post += "Verification due by " + verify_at.strftime("%I:%M %p") + "\n"
|
||||
post += "\n"
|
||||
|
||||
if ENV == 'dev':
|
||||
post += "⚠️ DEV"
|
||||
elif user.mastodon_attn_list:
|
||||
post += f"ATTN - {user.mastodon_attn_list}\n"
|
||||
|
||||
m = Mastodon(session)
|
||||
return await m.statusPost(post, user)
|
||||
|
||||
async def order_telegram_post(session, orders_pool, orders_str, repeats, due_at, m_url, verify_at=None):
|
||||
template = template_env.get_template("telegram_order")
|
||||
|
||||
post = template.render(
|
||||
orders_str = orders_str,
|
||||
repeats=repeats,
|
||||
due_at=due_at,
|
||||
verify_at=verify_at,
|
||||
m_url=m_url
|
||||
)
|
||||
post = "Here are your orders -\n\n"
|
||||
post += orders_str + "\n\n"
|
||||
if repeats > 1:
|
||||
post += f"These are the same orders from the last {repeats} days\n\n"
|
||||
post += "Proof of compliance is due by " + due_at.strftime("%I:%M %p") + "\n"
|
||||
if verify_at is not None:
|
||||
post += "Verification due by " + verify_at.strftime("%I:%M %p") + "\n"
|
||||
post += "\n"
|
||||
post += m_url
|
||||
if ENV == 'dev':
|
||||
post += "\n⚠️ DEV"
|
||||
|
||||
t = Telegram(session)
|
||||
await t.message_send(orders_pool.user.telegram_chat_id, post)
|
||||
|
||||
async def order_telegram_message(session, orders_pool, message):
|
||||
template = template_env.get_template("telegram_message")
|
||||
async def order_telegram_post_need_mastodon(session, orders_pool):
|
||||
post = "Cannot issue an order without a mastodon username"
|
||||
if ENV == 'dev':
|
||||
post += "\n⚠️ DEV"
|
||||
|
||||
post = template.render(
|
||||
message=message
|
||||
)
|
||||
t = Telegram(session)
|
||||
await t.message_send(orders_pool.user.telegram_chat_id, post)
|
||||
|
||||
async def order_telegram_post_none(session, orders_pool):
|
||||
post = "No orders for today"
|
||||
if ENV == 'dev':
|
||||
post += "\n⚠️ DEV"
|
||||
|
||||
t = Telegram(session)
|
||||
await t.message_send(orders_pool.user.telegram_chat_id, post)
|
||||
|
|
@ -68,33 +70,15 @@ async def order_issue(orders_pool):
|
|||
user = orders_pool.user
|
||||
|
||||
if user.mastodon_username is None:
|
||||
logger.info(f"{orders_pool} - Cannot issue order without mastodon username")
|
||||
await order_telegram_message(
|
||||
session, orders_pool,
|
||||
"Cannot issue order without mastodon username"
|
||||
)
|
||||
timeline_event_put(
|
||||
TIMELINE_ORDER_NOT_ISSUED,
|
||||
"Cannot issue order without mastodon username",
|
||||
user=orders_pool.user,
|
||||
orders_pool=orders_pool
|
||||
)
|
||||
logger.info('Cannot issue order without mastodon username')
|
||||
await order_telegram_post_need_mastodon(session, orders_pool)
|
||||
return { "reason": "Cannot issue order without mastodon username" }
|
||||
|
||||
orders_info = generate_order(orders_pool)
|
||||
|
||||
if 'orders' not in orders_info:
|
||||
logger.info(f"{orders_pool} - {orders_info['reason']}")
|
||||
await order_telegram_message(
|
||||
session, orders_pool,
|
||||
"No orders this time"
|
||||
)
|
||||
timeline_event_put(
|
||||
TIMELINE_ORDER_NOT_ISSUED,
|
||||
orders_info['reason'],
|
||||
user=orders_pool.user,
|
||||
orders_pool=orders_pool
|
||||
)
|
||||
await order_telegram_post_none(session, orders_pool)
|
||||
return { "reason": orders_info['reason'] }
|
||||
|
||||
orders_str = "\n".join(orders_info['orders'])
|
||||
|
|
@ -129,7 +113,8 @@ async def order_issue(orders_pool):
|
|||
verify_at=verify_at
|
||||
)
|
||||
|
||||
order_status = order_status_put(
|
||||
return {
|
||||
"order_status" : order_status_put(
|
||||
orders_pool,
|
||||
orders_pool.user,
|
||||
m_status['id'],
|
||||
|
|
@ -137,34 +122,20 @@ async def order_issue(orders_pool):
|
|||
due_at,
|
||||
orders_str,
|
||||
verify_at=verify_at
|
||||
)
|
||||
|
||||
timeline_event_put(
|
||||
TIMELINE_ORDER_ISSUED,
|
||||
order_status.text.split("\n")[0],
|
||||
user=orders_pool.user,
|
||||
orders_pool=orders_pool,
|
||||
order_status=order_status,
|
||||
extra={
|
||||
"mastodon_status_url": m_status['url']
|
||||
}
|
||||
)
|
||||
|
||||
logger.info(f"Issued {order_status}")
|
||||
|
||||
return {
|
||||
"order_status" : order_status,
|
||||
),
|
||||
"mastodon_status": m_status
|
||||
}
|
||||
|
||||
async def punishment_mastodon_post(session, orders_pool, punishment_str, reply_id=None):
|
||||
user = orders_pool.user
|
||||
|
||||
template = template_env.get_template('mastodon_punishment')
|
||||
post = template.render(
|
||||
user=user,
|
||||
punishment_str=punishment_str
|
||||
)
|
||||
post = "@%s has failed to post proof of compliance. Here is the punishment -\n\n" % user.mastodon_account()
|
||||
post += punishment_str + "\n\n"
|
||||
|
||||
if ENV == 'dev':
|
||||
post += "⚠️ DEV"
|
||||
elif user.mastodon_attn_list:
|
||||
post += f"ATTN - {user.mastodon_attn_list}\n"
|
||||
|
||||
m = Mastodon(session)
|
||||
return await m.statusPost(
|
||||
|
|
@ -174,19 +145,18 @@ async def punishment_mastodon_post(session, orders_pool, punishment_str, reply_i
|
|||
)
|
||||
|
||||
async def punishment_telegram_post(session, orders_pool, punishment_str, m_url):
|
||||
template = template_env.get_template('telegram_punishment')
|
||||
|
||||
post = template.render(
|
||||
punishment_str=punishment_str,
|
||||
m_url=m_url
|
||||
)
|
||||
post = "You failed to show proof of compliance. Here is your punishment -\n\n"
|
||||
post += punishment_str + "\n\n"
|
||||
post += m_url
|
||||
if ENV == 'dev':
|
||||
post += "\n\n⚠️ DEV"
|
||||
|
||||
t = Telegram(session)
|
||||
await t.message_send(orders_pool.user.telegram_chat_id, post)
|
||||
|
||||
async def punishment_issue(session, order_status):
|
||||
if order_status.pool is None or order_status.pool.punishment_pool is None:
|
||||
logger.info(f'Unable to issue a punishment for {order_status}, no punishment pool for order pool {order_status.pool.name}')
|
||||
logger.info(f'Unable to issue a punishment for {order_status.id}, no punishment pool for order pool {order_status.pool.name}')
|
||||
return { "reason": "No punishment pool"}
|
||||
|
||||
punishment_pool = order_status.pool.punishment_pool
|
||||
|
|
@ -247,7 +217,7 @@ async def order_check(order_status_id):
|
|||
user = order_status.user
|
||||
|
||||
if order_status.punishment.count() > 0:
|
||||
logger.info(f'Punishment already issued for {order_status}')
|
||||
logger.info(f'Punishment already issued for {order_status.id}')
|
||||
return
|
||||
|
||||
m = Mastodon(session)
|
||||
|
|
@ -297,7 +267,7 @@ async def order_check(order_status_id):
|
|||
|
||||
confirmed_at = d['created_at']
|
||||
order_status_confirm(order_status.id, confirmed_at)
|
||||
logger.info(f"Confirmed order {order_status}")
|
||||
logger.info('Confirmed order %s' % (order_status.id))
|
||||
timeline_event_put(
|
||||
TIMELINE_ORDER_CONFIRMED,
|
||||
order_status.text.split("\n")[0],
|
||||
|
|
@ -311,7 +281,7 @@ async def order_check(order_status_id):
|
|||
break
|
||||
|
||||
if confirmed_at is None:
|
||||
logger.info(f"Order {order_status} remains unconfirmed")
|
||||
logger.info('Order %s remains unconfirmed' % (order_status.id))
|
||||
|
||||
due_at = datetime.datetime.fromisoformat(order_status.due_at)
|
||||
if(due_at < datetime.datetime.now(datetime.UTC)):
|
||||
|
|
@ -327,7 +297,7 @@ async def order_check(order_status_id):
|
|||
elif user.verify_mastodon_favorite and had_favorites is False:
|
||||
reason = "No replies had a favorite from a dom or tagged account"
|
||||
|
||||
logger.info(f"Time to issue a punishment for {order_status}")
|
||||
logger.info('Time to issue a punishment for %s' % order_status.id)
|
||||
|
||||
issue_result = await punishment_issue(session, order_status)
|
||||
|
||||
|
|
|
|||
|
|
@ -92,11 +92,12 @@ class OrderScheduler():
|
|||
)
|
||||
return
|
||||
|
||||
logger.info(f'Issuing order for {orders_pool.name}[{orders_pool.user.telegram_username}]')
|
||||
|
||||
issue_result = await order_issue(orders_pool)
|
||||
|
||||
if 'order_status' in issue_result:
|
||||
order_status = issue_result['order_status']
|
||||
|
||||
if order_status.due_at is not None or order_status.verify_at is not None:
|
||||
# Schedule check
|
||||
check_time = order_status.verify_at if order_status.verify_at is not None else order_status.due_at
|
||||
|
|
@ -105,6 +106,23 @@ class OrderScheduler():
|
|||
self.scheduled_check,
|
||||
args=(order_status.id,)
|
||||
)
|
||||
timeline_event_put(
|
||||
TIMELINE_ORDER_ISSUED,
|
||||
order_status.text.split("\n")[0],
|
||||
user=orders_pool.user,
|
||||
orders_pool=orders_pool,
|
||||
order_status=order_status,
|
||||
extra={
|
||||
"mastodon_status_url": issue_result['mastodon_status']['url']
|
||||
}
|
||||
)
|
||||
elif 'reason' in issue_result:
|
||||
timeline_event_put(
|
||||
TIMELINE_ORDER_NOT_ISSUED,
|
||||
issue_result['reason'],
|
||||
user=orders_pool.user,
|
||||
orders_pool=orders_pool
|
||||
)
|
||||
|
||||
async def scheduled_check(self, outstanding_order_id):
|
||||
await order_check(outstanding_order_id)
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
{% block main %}{% endblock %}
|
||||
{% block attn %}
|
||||
{% if user.mastodon_attn_list %}
|
||||
ATTN - {{user.mastodon_attn_list}}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block env %}
|
||||
{% if ENV == 'dev' %}⚠️ DEV{% endif %}
|
||||
{% endblock %}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
{% extends "mastodon_base" %}
|
||||
|
||||
{% block main %}
|
||||
Here are today's orders for @{{user.mastodon_account()}}
|
||||
|
||||
{{orders_str}}
|
||||
|
||||
{% if repeats > 1 %}
|
||||
These are the same orders from the last {{repeats}} days.
|
||||
{% endif %}
|
||||
|
||||
Proof of compliance is due by {{due_at|short_time}}
|
||||
{% if verify_at %}
|
||||
Verification due by {{verify_at|short_time}}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
{% extends "mastodon_base" %}
|
||||
|
||||
{% block main %}
|
||||
@{{user.mastodon_account()}} failed to show proof of compliance. Here is the punishment -
|
||||
|
||||
{{punishment_str}}
|
||||
{% endblock %}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
{% block main %}{% endblock %}
|
||||
{% if ENV == 'dev' %}⚠️ DEV{% endif %}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
{% extends "telegram_base" %}
|
||||
{% block main %}
|
||||
{{message}}
|
||||
{% endblock %}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
{% extends "telegram_base" %}
|
||||
|
||||
{% block main %}
|
||||
Here are your orders -
|
||||
|
||||
{{orders_str}}
|
||||
{% if repeats > 1 %}
|
||||
These are the same orders from the last {{repeats}} days.
|
||||
{% endif %}
|
||||
Proof of compliance is due by {{due_at|short_time}}
|
||||
{% if verify_at %}
|
||||
Proof of verification is due by {{verify_at|short_time}}
|
||||
{% endif %}
|
||||
{{m_url}}
|
||||
{% endblock %}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
{% extends "telegram_base" %}
|
||||
|
||||
{% block main %}
|
||||
You failed to show proof of compliance. Here is your punishment -
|
||||
|
||||
{{punishment_str}}
|
||||
|
||||
{{m_url}}
|
||||
{% endblock %}
|
||||
110
web/api.py
110
web/api.py
|
|
@ -6,7 +6,7 @@ from flask import Blueprint, jsonify, abort, request
|
|||
from flask_login import login_required, current_user
|
||||
from db.constants import TIMELINE_ORDERS_POOL_CREATED, TIMELINE_ORDERS_POOL_DELETED, TIMELINE_ORDERS_POOL_UPDATED
|
||||
from db.models import database, OrdersPool, Order, OrderAddOn, MastodonServer
|
||||
from db.queries import timeline_event_put, timeline_event_recent, user_can_orders_pools_details, user_can_orders_pools_edit, user_can_orders_pools_view, user_doms, user_get, domsubusers_list, orders_pool_list, orders_pool, mastodon_server_get, mastodon_server_put, user_has_doms, user_mastodon_server_set, user_preferences_set
|
||||
from db.queries import timeline_event_put, timeline_event_recent, user_get, domsubusers_list, orders_pool_list, orders_pool, mastodon_server_get, mastodon_server_put, user_has_doms, user_mastodon_server_set, user_preferences_set
|
||||
from settings import MASTODON_OAUTH_CLIENT_NAME, MASTODON_OAUTH_REDIRECT_URI, MASTODON_OAUTH_SCOPES, MASTODON_OAUTH_CLIENT_WEBSITE
|
||||
from util import time_sqlite
|
||||
|
||||
|
|
@ -79,14 +79,13 @@ def timeline():
|
|||
"username": t.user.telegram_username,
|
||||
"actor_username": t.actor.telegram_username if t.actor is not None else None,
|
||||
"orders_pool": {
|
||||
"id": t.orders_pool.id if user_can_orders_pools_edit(current_user.db_user, t.user) else None,
|
||||
"id": t.orders_pool.id,
|
||||
"name": t.orders_pool.name,
|
||||
} if (
|
||||
t.orders_pool is not None and
|
||||
user_can_orders_pools_view(current_user.db_user, t.user)
|
||||
) else None,
|
||||
} if t.orders_pool is not None else None,
|
||||
"order_status": t.order_status.text if t.order_status is not None else None,
|
||||
} for t in timeline_event_recent(current_user.db_user.id)
|
||||
} for t in timeline_event_recent(
|
||||
subs,
|
||||
[current_user.db_user.id,])
|
||||
])
|
||||
|
||||
@api.route('/mastodon_oauth')
|
||||
|
|
@ -164,28 +163,16 @@ def authorized_sub(func):
|
|||
@login_required
|
||||
@authorized_sub
|
||||
def sub(username, sub):
|
||||
if request.method == "POST":
|
||||
if (user_has_doms(sub) and sub.id == current_user.db_user.id and
|
||||
('permission_orders_pools_view' in request.json or
|
||||
'permission_orders_pools_details' in request.json or
|
||||
'permission_orders_pools_edit' in request.json)):
|
||||
if user_has_doms(sub) and sub.id == current_user.db_user.id:
|
||||
abort(403)
|
||||
return
|
||||
|
||||
if 'verify_mastodon_favorite' in request.json:
|
||||
if request.method == "POST":
|
||||
sub.verify_mastodon_favorite = bool(request.json['verify_mastodon_favorite'])
|
||||
if 'verify_mastodon_alt_text' in request.json:
|
||||
sub.verify_mastodon_alt_text = bool(request.json['verify_mastodon_alt_text'])
|
||||
if 'verify_delay' in request.json and request.json['verify_delay'] is not None:
|
||||
if request.json['verify_delay'] is not None:
|
||||
sub.verify_delay = int(request.json["verify_delay"])
|
||||
|
||||
if 'permission_orders_pools_view' in request.json:
|
||||
sub.permission_orders_pools_view = bool(request.json['permission_orders_pools_view'])
|
||||
if 'permission_orders_pools_details' in request.json:
|
||||
sub.permission_orders_pools_details = bool(request.json['permission_orders_pools_details'])
|
||||
if 'permission_orders_pools_edit' in request.json:
|
||||
sub.permission_orders_pools_edit = bool(request.json['permission_orders_pools_edit'])
|
||||
|
||||
sub.save()
|
||||
|
||||
return ('', 204)
|
||||
|
|
@ -196,27 +183,13 @@ def sub(username, sub):
|
|||
"mastodon_username": sub.mastodon_username,
|
||||
"verify_mastodon_alt_text": sub.verify_mastodon_alt_text,
|
||||
"verify_mastodon_favorite": sub.verify_mastodon_favorite,
|
||||
"verify_delay": sub.verify_delay,
|
||||
"can_edit_permissions": current_user.db_user in user_doms(sub.id),
|
||||
"permission_orders_pools_view": sub.permission_orders_pools_view,
|
||||
"permission_orders_pools_details": sub.permission_orders_pools_details,
|
||||
"permission_orders_pools_edit": sub.permission_orders_pools_edit
|
||||
"verify_delay": sub.verify_delay
|
||||
})
|
||||
|
||||
@api.route('/orders/')
|
||||
@login_required
|
||||
def my_order_sets():
|
||||
user = current_user.db_user
|
||||
result = {
|
||||
'permissions': {
|
||||
'can_view': user_can_orders_pools_view(user, user),
|
||||
'can_details': user_can_orders_pools_details(user, user),
|
||||
'can_edit': user_can_orders_pools_edit(user, user)
|
||||
}
|
||||
}
|
||||
|
||||
if result['permissions']['can_details']:
|
||||
result['pools'] = [
|
||||
return jsonify([
|
||||
{
|
||||
'id': op.id,
|
||||
'name': op.name,
|
||||
|
|
@ -234,39 +207,13 @@ def my_order_sets():
|
|||
}
|
||||
for op
|
||||
in orders_pool_list(current_user.db_user)
|
||||
]
|
||||
elif result['permissions']['can_view']:
|
||||
result['pools'] = [
|
||||
{
|
||||
'id': op.id,
|
||||
'name': op.name,
|
||||
'scheduled': op.scheduled,
|
||||
'time': op.time,
|
||||
'weekends': op.weekends,
|
||||
'weekdays': op.weekdays,
|
||||
'probability': op.probability,
|
||||
'punishment_pool_name': op.punishment_pool.name if op.punishment_pool is not None else None,
|
||||
}
|
||||
for op
|
||||
in orders_pool_list(current_user.db_user)
|
||||
]
|
||||
|
||||
return jsonify(result)
|
||||
])
|
||||
|
||||
@api.route('/orders/<username>/sets')
|
||||
@login_required
|
||||
@authorized_sub
|
||||
def sub_order_sets(username, sub):
|
||||
result = {
|
||||
'permissions': {
|
||||
'can_view': user_can_orders_pools_view(current_user.db_user, sub),
|
||||
'can_details': user_can_orders_pools_details(current_user.db_user, sub),
|
||||
'can_edit': user_can_orders_pools_edit(current_user.db_user, sub)
|
||||
}
|
||||
}
|
||||
|
||||
if result['permissions']['can_details']:
|
||||
result['pools'] = [
|
||||
return jsonify([
|
||||
{
|
||||
'id': op.id,
|
||||
'name': op.name,
|
||||
|
|
@ -284,33 +231,12 @@ def sub_order_sets(username, sub):
|
|||
}
|
||||
for op
|
||||
in orders_pool_list(sub.id)
|
||||
]
|
||||
elif result['permissions']['can_view']:
|
||||
result['pools'] = [
|
||||
{
|
||||
'id': op.id,
|
||||
'name': op.name,
|
||||
'scheduled': op.scheduled,
|
||||
'time': op.time,
|
||||
'weekends': op.weekends,
|
||||
'weekdays': op.weekdays,
|
||||
'probability': op.probability,
|
||||
'punishment_pool_name': op.punishment_pool.name if op.punishment_pool is not None else None,
|
||||
}
|
||||
for op
|
||||
in orders_pool_list(sub.id)
|
||||
]
|
||||
|
||||
return jsonify(result)
|
||||
])
|
||||
|
||||
@api.route('/orders/<username>/sets/', methods=['POST'])
|
||||
@login_required
|
||||
@authorized_sub
|
||||
def sub_order_set_create(username, sub):
|
||||
if not user_can_orders_pools_edit(current_user.db_user, sub):
|
||||
abort(403)
|
||||
return
|
||||
|
||||
# Create new
|
||||
with database.atomic() as transaction:
|
||||
try:
|
||||
|
|
@ -361,10 +287,6 @@ def sub_order_set(username, set_id, sub):
|
|||
op = orders_pool(sub.id, set_id)
|
||||
|
||||
if request.method == 'POST':
|
||||
if not user_can_orders_pools_edit(current_user.db_user, sub):
|
||||
abort(403)
|
||||
return
|
||||
|
||||
def update_add_ons(order, add_ons):
|
||||
for updated_add_on in add_ons:
|
||||
if isinstance(updated_add_on['id'], int):
|
||||
|
|
@ -449,10 +371,6 @@ def sub_order_set(username, set_id, sub):
|
|||
abort(500)
|
||||
elif request.method == 'DELETE':
|
||||
try:
|
||||
if not user_can_orders_pools_edit(current_user.db_user, sub):
|
||||
abort(403)
|
||||
return
|
||||
|
||||
op.delete_instance(recursive=True)
|
||||
|
||||
timeline_event_put(
|
||||
|
|
|
|||
|
|
@ -81,11 +81,12 @@ def index():
|
|||
print('Redirect to dashboard')
|
||||
return redirect('/dashboard/')
|
||||
|
||||
@app.route('/dashboard/')
|
||||
@app.route('/profile/')
|
||||
@app.route('/profile/', defaults={'path': ''})
|
||||
@app.route('/dashboard/', defaults={'path': ''})
|
||||
@app.route('/orders/<path:path>')
|
||||
@app.route('/dashboard/<path:path>')
|
||||
@login_required
|
||||
def dashboard(path=''):
|
||||
def dashboard(path):
|
||||
return render_template('dashboard.html')
|
||||
|
||||
@app.route('/logout')
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React from "react";
|
||||
import { useDisclosure } from "@mantine/hooks";
|
||||
|
||||
import { Button, Modal, Text, Flex, ButtonVariant } from "@mantine/core";
|
||||
import { Button, Modal, Text, Flex } from "@mantine/core";
|
||||
|
||||
export const ConfirmDialogButton: React.FC<{
|
||||
text: string;
|
||||
|
|
@ -9,15 +9,7 @@ export const ConfirmDialogButton: React.FC<{
|
|||
buttonColor: string;
|
||||
onConfirm: () => void;
|
||||
children: React.ReactNode;
|
||||
variant?: ButtonVariant;
|
||||
}> = ({
|
||||
text,
|
||||
buttonText,
|
||||
buttonColor,
|
||||
onConfirm,
|
||||
children,
|
||||
variant = "filled",
|
||||
}) => {
|
||||
}> = ({ text, buttonText, buttonColor, onConfirm, children }) => {
|
||||
const [opened, { open, close }] = useDisclosure();
|
||||
|
||||
const handleConfirm = React.useCallback(() => {
|
||||
|
|
@ -30,15 +22,13 @@ export const ConfirmDialogButton: React.FC<{
|
|||
<Modal opened={opened} onClose={close} p="sm" withCloseButton={false}>
|
||||
<Text mb="xl">{text}</Text>
|
||||
<Flex gap="md" justify="flex-end">
|
||||
<Button variant="outline" onClick={close}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={close}>Cancel</Button>
|
||||
<Button color={buttonColor} onClick={handleConfirm}>
|
||||
{buttonText}
|
||||
</Button>
|
||||
</Flex>
|
||||
</Modal>
|
||||
<Button variant={variant} color={buttonColor} onClick={open}>
|
||||
<Button color={buttonColor} onClick={open}>
|
||||
{children}
|
||||
</Button>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { Text, Title, Flex, Card, Image } from "@mantine/core";
|
|||
import { useLoaderData } from "react-router";
|
||||
import { IconPencil } from "@tabler/icons-react";
|
||||
import { NavigateButton } from "./NavigateButton";
|
||||
import { OrderSets, OrderSetsResponse } from "./OrderSets";
|
||||
import { OrderSetProps, OrderSets } from "./OrderSets";
|
||||
import { useUserContext } from "./UserContext";
|
||||
import { TimelineList } from "./TimelineList";
|
||||
|
||||
|
|
@ -25,7 +25,15 @@ const SubsList: React.FC<SubsListProps> = ({ subs }) => (
|
|||
</Title>
|
||||
<Flex gap="md" wrap="wrap">
|
||||
{subs.map(({ sub_username, telegram_photo_url }) => (
|
||||
<Card key={sub_username} padding="lg" withBorder w="320px">
|
||||
<Card
|
||||
key={sub_username}
|
||||
shadow="sm"
|
||||
padding="lg"
|
||||
radius="md"
|
||||
withBorder
|
||||
bg="gray.2"
|
||||
w="320px"
|
||||
>
|
||||
{telegram_photo_url ? (
|
||||
<Card.Section>
|
||||
<Image
|
||||
|
|
@ -54,20 +62,14 @@ const SubsList: React.FC<SubsListProps> = ({ subs }) => (
|
|||
export const Dashboard: React.FC = () => {
|
||||
const [orderSets, subs, timeline] =
|
||||
useLoaderData<
|
||||
[OrderSetsResponse, SubsListProps["subs"], TimelineEvent[]]
|
||||
[OrderSetProps["orderSets"], SubsListProps["subs"], TimelineEvent[]]
|
||||
>();
|
||||
const { username } = useUserContext();
|
||||
|
||||
return (
|
||||
<>
|
||||
{timeline.length > 0 ? <TimelineList timeline={timeline} /> : null}
|
||||
{orderSets.permissions?.can_view && orderSets.pools.length > 0 ? (
|
||||
<OrderSets
|
||||
orderSets={orderSets.pools}
|
||||
permissions={orderSets.permissions}
|
||||
username={username}
|
||||
/>
|
||||
) : null}
|
||||
<OrderSets orderSets={orderSets} username={username} />
|
||||
{subs.length > 0 ? <SubsList subs={subs} /> : null}
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,32 +1,25 @@
|
|||
import {
|
||||
Avatar,
|
||||
Text,
|
||||
Container,
|
||||
Flex,
|
||||
UnstyledButton,
|
||||
Anchor,
|
||||
} from "@mantine/core";
|
||||
import { Avatar, Text, Container, Flex, UnstyledButton } from "@mantine/core";
|
||||
import React from "react";
|
||||
import { useUserContext } from "./UserContext";
|
||||
import { Link, Outlet, useNavigate } from "react-router";
|
||||
import { Outlet, useNavigate } from "react-router";
|
||||
|
||||
export const Header: React.FC = () => {
|
||||
const { username, telegram_photo_url } = useUserContext();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleClick = React.useCallback(() => {
|
||||
navigate("/profile/");
|
||||
navigate("/profile");
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Container p="sm">
|
||||
<Flex justify="flex-end">
|
||||
<Anchor component={Link} to={"/profile/"}>
|
||||
<UnstyledButton onClick={handleClick}>
|
||||
<Flex align="center" gap="sm">
|
||||
<Text>{username}</Text>
|
||||
<Text c="blue">{username}</Text>
|
||||
<Avatar src={telegram_photo_url} />
|
||||
</Flex>
|
||||
</Anchor>
|
||||
</UnstyledButton>
|
||||
</Flex>
|
||||
<Outlet />
|
||||
</Container>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import {
|
|||
NumberInput,
|
||||
Text,
|
||||
Select,
|
||||
Anchor,
|
||||
} from "@mantine/core";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { TimeInput } from "@mantine/dates";
|
||||
|
|
@ -83,22 +82,9 @@ export const OrderSet: React.FC = () => {
|
|||
|
||||
const [orderSets, setOrderSets] = React.useState([]);
|
||||
React.useEffect(() => {
|
||||
fetch(`/api/orders/${username}/sets`).then(async (response) => {
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.permissions?.can_edit) {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
message: "Not authorized",
|
||||
color: "red",
|
||||
});
|
||||
navigate(`/dashboard/`);
|
||||
}
|
||||
|
||||
setOrderSets(data.pools);
|
||||
}
|
||||
});
|
||||
fetch(`/api/orders/${username}/sets`)
|
||||
.then((response) => response.json())
|
||||
.then(setOrderSets);
|
||||
}, [username]);
|
||||
|
||||
const [showScheduling, setShowScheduling] = React.useState(
|
||||
|
|
@ -262,9 +248,7 @@ export const OrderSet: React.FC = () => {
|
|||
<>
|
||||
<Box mb="lg">
|
||||
<Title order={1}>{orderSet?.name || "New Order Set"}</Title>
|
||||
<Anchor component={Link} to={`/orders/${username}`}>
|
||||
Return to {username}
|
||||
</Anchor>
|
||||
<Link to={`/orders/${username}`}>Return to {username}</Link>
|
||||
</Box>
|
||||
<form id="order-set" onSubmit={handleSubmit}>
|
||||
<TextInput {...form.getInputProps("name")} label="Name" />
|
||||
|
|
@ -275,7 +259,7 @@ export const OrderSet: React.FC = () => {
|
|||
mt="lg"
|
||||
/>
|
||||
<Collapse in={showScheduling}>
|
||||
<Paper>
|
||||
<Paper bg="gray.2">
|
||||
<Input.Wrapper
|
||||
error={form.getInputProps("probability").error}
|
||||
label="Probability"
|
||||
|
|
@ -332,7 +316,7 @@ export const OrderSet: React.FC = () => {
|
|||
</Title>
|
||||
{form.getValues().orders.map(({ id: order_id, _delete }, idx) =>
|
||||
_delete ? null : (
|
||||
<Paper key={order_id}>
|
||||
<Paper key={order_id} bg="gray.2">
|
||||
<Flex gap="xl" justify="space-between">
|
||||
<TextInput
|
||||
{...form.getInputProps(`orders.${idx}.name`)}
|
||||
|
|
@ -422,7 +406,7 @@ export const OrderSet: React.FC = () => {
|
|||
</Button>
|
||||
<Box my="40px" />
|
||||
<Affix position={{ bottom: 0 }} w="100%">
|
||||
<Paper mb={0}>
|
||||
<Paper mb={0} bg="gray.1">
|
||||
<Container>
|
||||
<Flex justify="flex-end">
|
||||
<Button type="submit" form="order-set">
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import {
|
|||
RingProgress,
|
||||
Alert,
|
||||
Grid,
|
||||
Anchor,
|
||||
} from "@mantine/core";
|
||||
import { IconAlertTriangle } from "@tabler/icons-react";
|
||||
import { TimeValue } from "@mantine/dates";
|
||||
|
|
@ -22,38 +21,20 @@ import { useUserContext } from "./UserContext";
|
|||
import { DonutChart } from "@mantine/charts";
|
||||
|
||||
const COLORS_ROTATION = [
|
||||
"orange.8",
|
||||
"gray.7",
|
||||
"orange.7",
|
||||
"gray.6",
|
||||
"orange.6",
|
||||
"gray.5",
|
||||
"orange.5",
|
||||
"gray.4",
|
||||
"teal",
|
||||
"pink",
|
||||
"lime",
|
||||
"violet",
|
||||
"orange",
|
||||
"blue",
|
||||
"yellow",
|
||||
"grape",
|
||||
"green",
|
||||
"red",
|
||||
"cyan",
|
||||
"gray",
|
||||
];
|
||||
|
||||
export interface OrderSetsResponse {
|
||||
pools: (Pick<
|
||||
OrderSet,
|
||||
| "id"
|
||||
| "name"
|
||||
| "scheduled"
|
||||
| "time"
|
||||
| "weekends"
|
||||
| "weekdays"
|
||||
| "orders"
|
||||
| "probability"
|
||||
> & {
|
||||
orders: Pick<OrderSetOrder, "id" | "name" | "weight">;
|
||||
punishment_pool_name: string;
|
||||
})[];
|
||||
permissions: {
|
||||
can_view: boolean;
|
||||
can_details: boolean;
|
||||
can_edit: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface OrderSetProps {
|
||||
orderSets: (Pick<
|
||||
OrderSet,
|
||||
|
|
@ -71,14 +52,12 @@ export interface OrderSetProps {
|
|||
})[];
|
||||
username: string;
|
||||
linkBack?: React.ReactNode;
|
||||
permissions?: OrderSetsResponse["permissions"];
|
||||
}
|
||||
|
||||
export const OrderSets: React.FC<OrderSetProps> = ({
|
||||
orderSets,
|
||||
username,
|
||||
linkBack,
|
||||
permissions,
|
||||
}) => {
|
||||
const { username: current_user } = useUserContext();
|
||||
const fetcher = useFetcher();
|
||||
|
|
@ -95,13 +74,12 @@ export const OrderSets: React.FC<OrderSetProps> = ({
|
|||
const [isMastodonSet, setIsMastodonSet] = React.useState(true);
|
||||
React.useEffect(() => {
|
||||
if (username) {
|
||||
fetch(`/api/subs/${username}`).then(async (response) => {
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
fetch(`/api/subs/${username}`)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
if (!data.mastodon_server || !data.mastodon_username) {
|
||||
setIsMastodonSet(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [username]);
|
||||
|
|
@ -109,12 +87,12 @@ export const OrderSets: React.FC<OrderSetProps> = ({
|
|||
const [portalRef, setPortalRef] = React.useState<HTMLElement | null>();
|
||||
|
||||
return (
|
||||
<Box my="lg">
|
||||
<>
|
||||
<Box mb="lg">
|
||||
<Title order={1}>Order Sets for {username}</Title>
|
||||
{linkBack ? linkBack : null}
|
||||
</Box>
|
||||
{isMastodonSet ? null : (
|
||||
{orderSets.length > 0 && isMastodonSet ? null : (
|
||||
<Flex justify="center">
|
||||
<Alert
|
||||
variant="light"
|
||||
|
|
@ -129,9 +107,7 @@ export const OrderSets: React.FC<OrderSetProps> = ({
|
|||
{username === current_user ? (
|
||||
<>
|
||||
<br />
|
||||
<Anchor component={Link} to="/profile/">
|
||||
Edit Profile
|
||||
</Anchor>
|
||||
<Link to="/profile/">Edit Profile</Link>
|
||||
</>
|
||||
) : null}
|
||||
</Alert>
|
||||
|
|
@ -144,7 +120,6 @@ export const OrderSets: React.FC<OrderSetProps> = ({
|
|||
}
|
||||
}}
|
||||
>
|
||||
{permissions?.can_view ? (
|
||||
<Grid gutter="md">
|
||||
{orderSets
|
||||
? orderSets.map(
|
||||
|
|
@ -165,6 +140,7 @@ export const OrderSets: React.FC<OrderSetProps> = ({
|
|||
padding="lg"
|
||||
radius="md"
|
||||
withBorder
|
||||
bg="gray.2"
|
||||
mb="0"
|
||||
>
|
||||
<Flex direction="column" gap="md" h="100%">
|
||||
|
|
@ -187,10 +163,10 @@ export const OrderSets: React.FC<OrderSetProps> = ({
|
|||
</Flex>
|
||||
<Flex gap="md" align="center">
|
||||
{weekdays ? (
|
||||
<Badge color="orange.7">Weekdays</Badge>
|
||||
<Badge color="blue">Weekdays</Badge>
|
||||
) : null}
|
||||
{weekends ? (
|
||||
<Badge color="orange.7">Weekends</Badge>
|
||||
<Badge color="blue">Weekends</Badge>
|
||||
) : null}
|
||||
<RingProgress
|
||||
size={30}
|
||||
|
|
@ -201,10 +177,7 @@ export const OrderSets: React.FC<OrderSetProps> = ({
|
|||
</Text>
|
||||
}
|
||||
sections={[
|
||||
{
|
||||
color: "orange",
|
||||
value: probability * 100,
|
||||
},
|
||||
{ color: "cyan", value: probability * 100 },
|
||||
]}
|
||||
/>
|
||||
</Flex>
|
||||
|
|
@ -215,8 +188,8 @@ export const OrderSets: React.FC<OrderSetProps> = ({
|
|||
<b>Punishments:</b> {punishment_pool_name}
|
||||
</Text>
|
||||
) : null}
|
||||
<Flex justify="end" align="flex-end" gap="sm">
|
||||
{permissions?.can_details && orders.length > 0 ? (
|
||||
<Flex justify="end" align="flex-end" gap="md">
|
||||
{orders.length > 0 ? (
|
||||
<DonutChart
|
||||
flex={1}
|
||||
size={130}
|
||||
|
|
@ -225,34 +198,23 @@ export const OrderSets: React.FC<OrderSetProps> = ({
|
|||
name,
|
||||
value: weight,
|
||||
color:
|
||||
COLORS_ROTATION[
|
||||
idx % COLORS_ROTATION.length
|
||||
],
|
||||
COLORS_ROTATION[idx % COLORS_ROTATION.length],
|
||||
}))}
|
||||
tooltipDataSource="segment"
|
||||
/>
|
||||
) : null}
|
||||
{permissions?.can_edit ? (
|
||||
<>
|
||||
<ConfirmDialogButton
|
||||
buttonColor="red.8"
|
||||
buttonText="Delete"
|
||||
variant="transparent"
|
||||
text={`Are you sure you want to delete ${name}?`}
|
||||
onConfirm={() => handleDelete(id)}
|
||||
>
|
||||
<IconTrash />
|
||||
</ConfirmDialogButton>
|
||||
<NavigateButton
|
||||
to={`/orders/${username}/${id}`}
|
||||
>
|
||||
<IconPencil
|
||||
style={{ marginRight: "0.5rem" }}
|
||||
/>
|
||||
<NavigateButton to={`/orders/${username}/${id}`}>
|
||||
<IconPencil style={{ marginRight: "0.5rem" }} />
|
||||
Edit
|
||||
</NavigateButton>
|
||||
</>
|
||||
) : null}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Card>
|
||||
|
|
@ -261,29 +223,13 @@ export const OrderSets: React.FC<OrderSetProps> = ({
|
|||
)
|
||||
: null}
|
||||
</Grid>
|
||||
) : (
|
||||
<Flex justify="center">
|
||||
<Alert
|
||||
variant="light"
|
||||
color="orange"
|
||||
title="Warning"
|
||||
icon={<IconAlertTriangle />}
|
||||
my="md"
|
||||
w="40rem"
|
||||
>
|
||||
You are not authorized to view order pools for <b>{username}</b>.
|
||||
</Alert>
|
||||
</Flex>
|
||||
)}
|
||||
</div>
|
||||
{permissions?.can_edit ? (
|
||||
<Box my="lg">
|
||||
<NavigateButton to={`/orders/${username}/new`}>
|
||||
<IconPlus style={{ marginRight: "0.5rem" }} />
|
||||
New
|
||||
</NavigateButton>
|
||||
</Box>
|
||||
) : null}
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import React from "react";
|
||||
import { useUserContext } from "./UserContext";
|
||||
import {
|
||||
Anchor,
|
||||
Avatar,
|
||||
Box,
|
||||
Button,
|
||||
|
|
@ -135,11 +134,9 @@ export const Profile: React.FC = () => {
|
|||
<Title>{username}</Title>
|
||||
</Flex>
|
||||
<Box mb="md">
|
||||
<Anchor component={Link} to={`/dashboard/`}>
|
||||
Return to dashboard
|
||||
</Anchor>
|
||||
<Link to={`/dashboard`}>Return to dashboard</Link>
|
||||
</Box>
|
||||
<Paper withBorder>
|
||||
<Paper bg="gray.1">
|
||||
<Title order={4}>Mastodon</Title>
|
||||
<TextInput label="Account" w="50%" value={mastodon_account} />
|
||||
<Button onClick={open}>Authorize with Mastodon</Button>
|
||||
|
|
|
|||
|
|
@ -1,129 +0,0 @@
|
|||
import React from "react";
|
||||
import { Button, Checkbox, Paper, Title } from "@mantine/core";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { fetchHeaders } from "./fetch";
|
||||
|
||||
type ProfilePermissionsProps = Pick<
|
||||
UserProfile,
|
||||
| "permission_orders_pools_view"
|
||||
| "permission_orders_pools_details"
|
||||
| "permission_orders_pools_edit"
|
||||
> & { username: string };
|
||||
|
||||
type ProfilePermissionsForm = Pick<
|
||||
UserProfile,
|
||||
| "permission_orders_pools_view"
|
||||
| "permission_orders_pools_details"
|
||||
| "permission_orders_pools_edit"
|
||||
>;
|
||||
|
||||
export const ProfilePermissions: React.FC<ProfilePermissionsProps> = ({
|
||||
username,
|
||||
permission_orders_pools_view,
|
||||
permission_orders_pools_details,
|
||||
permission_orders_pools_edit,
|
||||
}) => {
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [detailsDisabled, setDetailsDisabled] = React.useState(
|
||||
!permission_orders_pools_view,
|
||||
);
|
||||
const [editDisabled, setEditDisabled] = React.useState(
|
||||
!permission_orders_pools_details,
|
||||
);
|
||||
const form = useForm<ProfilePermissionsForm>({
|
||||
mode: "uncontrolled",
|
||||
initialValues: {
|
||||
permission_orders_pools_view,
|
||||
permission_orders_pools_details,
|
||||
permission_orders_pools_edit,
|
||||
},
|
||||
});
|
||||
|
||||
form.watch("permission_orders_pools_view", ({ value }) => {
|
||||
if (!value) {
|
||||
form.setFieldValue("permission_orders_pools_details", false);
|
||||
setDetailsDisabled(true);
|
||||
} else {
|
||||
setDetailsDisabled(false);
|
||||
}
|
||||
});
|
||||
|
||||
form.watch("permission_orders_pools_details", ({ value }) => {
|
||||
if (!value) {
|
||||
form.setFieldValue("permission_orders_pools_edit", false);
|
||||
setEditDisabled(true);
|
||||
} else {
|
||||
setEditDisabled(false);
|
||||
}
|
||||
});
|
||||
|
||||
const handleSubmit = React.useCallback(
|
||||
form.onSubmit((values) => {
|
||||
setLoading(true);
|
||||
fetch(`/api/subs/${username}`, {
|
||||
method: "POST",
|
||||
headers: fetchHeaders(),
|
||||
body: JSON.stringify(values),
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
notifications.show({
|
||||
title: "Success",
|
||||
message: "Permissions have been saved",
|
||||
color: "green",
|
||||
});
|
||||
} else {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
message: "There was a problem saving permissions",
|
||||
color: "red",
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<Paper>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Title order={4} mb="md">
|
||||
Permissions
|
||||
</Title>
|
||||
<Checkbox
|
||||
{...form.getInputProps("permission_orders_pools_view", {
|
||||
type: "checkbox",
|
||||
})}
|
||||
key={form.key("permission_orders_pools_view")}
|
||||
label="Sub can view orders sets"
|
||||
mb="md"
|
||||
/>
|
||||
<Checkbox
|
||||
{...form.getInputProps("permission_orders_pools_details", {
|
||||
type: "checkbox",
|
||||
})}
|
||||
key={form.key("permission_orders_pools_details")}
|
||||
disabled={detailsDisabled}
|
||||
label="Sub can view orders sets details"
|
||||
mb="md"
|
||||
/>
|
||||
<Checkbox
|
||||
{...form.getInputProps("permission_orders_pools_edit", {
|
||||
type: "checkbox",
|
||||
})}
|
||||
key={form.key("permission_orders_pools_edit")}
|
||||
disabled={editDisabled}
|
||||
label="Sub can edit orders sets"
|
||||
mb="md"
|
||||
/>
|
||||
<Button type="submit" loading={loading} mt="md">
|
||||
Save
|
||||
</Button>
|
||||
</form>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
|
@ -75,7 +75,7 @@ export const ProfileVerification: React.FC<ProfileVerificationProps> = ({
|
|||
);
|
||||
|
||||
return (
|
||||
<Paper>
|
||||
<Paper bg="gray.1">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Title order={4} mb="md">
|
||||
Order Verification
|
||||
|
|
|
|||
46
web/vite/src/SubOrderSets.tsx
Normal file
46
web/vite/src/SubOrderSets.tsx
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import React from "react";
|
||||
import { Params, useLoaderData, useParams, Link } from "react-router";
|
||||
import { OrderSetProps, OrderSets } from "./OrderSets";
|
||||
import { ProfileVerification } from "./ProfileVerification";
|
||||
import { Title } from "@mantine/core";
|
||||
|
||||
export const subOrderSetsLoader = async ({
|
||||
params: { username },
|
||||
}: {
|
||||
params: Params<string>;
|
||||
}) => fetch(`/api/orders/${username}/sets`).then((response) => response.json());
|
||||
|
||||
export const SubOrderSets: React.FC = () => {
|
||||
const { username: sub_username } = useParams();
|
||||
const orderSets = useLoaderData<OrderSetProps["orderSets"]>();
|
||||
|
||||
const [profile, setProfile] = React.useState<UserProfile | null>(null);
|
||||
React.useEffect(() => {
|
||||
fetch(`/api/subs/${sub_username}`)
|
||||
.then((response) => response.json())
|
||||
.then(setProfile);
|
||||
}, [sub_username]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<OrderSets
|
||||
username={sub_username}
|
||||
orderSets={orderSets}
|
||||
linkBack={<Link to={`/dashboard/`}>Return to dashboard</Link>}
|
||||
/>
|
||||
{profile ? (
|
||||
<>
|
||||
<Title order={1} mb="md">
|
||||
Sub Profile
|
||||
</Title>
|
||||
<ProfileVerification
|
||||
username={sub_username}
|
||||
verify_mastodon_alt_text={profile.verify_mastodon_alt_text}
|
||||
verify_mastodon_favorite={profile.verify_mastodon_favorite}
|
||||
verify_delay={profile.verify_delay}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { Timeline, Text, Title, Box, Flex, Card, Anchor } from "@mantine/core";
|
||||
import { Timeline, Text, Title, Box, Flex } from "@mantine/core";
|
||||
import React from "react";
|
||||
import moment from "moment";
|
||||
import { Link } from "react-router";
|
||||
|
|
@ -14,7 +14,6 @@ import {
|
|||
IconLock,
|
||||
IconX,
|
||||
} from "@tabler/icons-react";
|
||||
import { useUserContext } from "./UserContext";
|
||||
|
||||
const TIMELINE_TYPE = {
|
||||
ORDER_NOT_ISSUED: {
|
||||
|
|
@ -29,27 +28,27 @@ const TIMELINE_TYPE = {
|
|||
},
|
||||
ORDER_CONFIRMED: {
|
||||
title: "Order confirmed",
|
||||
color: "green.9",
|
||||
color: "green.6",
|
||||
bullet: <IconCheck />,
|
||||
},
|
||||
ORDER_NOT_PUNISHED: {
|
||||
title: "Order not punished",
|
||||
color: "gray.8",
|
||||
color: "gray.6",
|
||||
bullet: null,
|
||||
},
|
||||
ORDER_PUNISHED: {
|
||||
title: "Order punished",
|
||||
color: "red.8",
|
||||
color: "red.6",
|
||||
bullet: <IconAlertCircle />,
|
||||
},
|
||||
ORDERS_POOL_CREATED: {
|
||||
title: "Orders pool created",
|
||||
color: "green.6",
|
||||
color: "green.3",
|
||||
bullet: <IconFilePlus />,
|
||||
},
|
||||
ORDERS_POOL_UPDATED: {
|
||||
title: "Orders pool updated",
|
||||
color: "green.6",
|
||||
color: "green.3",
|
||||
bullet: <IconFileDiff />,
|
||||
},
|
||||
ORDERS_POOL_DELETED: {
|
||||
|
|
@ -61,15 +60,11 @@ const TIMELINE_TYPE = {
|
|||
|
||||
export const TimelineList: React.FC<{
|
||||
timeline: TimelineEvent[];
|
||||
}> = ({ timeline }) => {
|
||||
const { username: my_username } = useUserContext();
|
||||
|
||||
return (
|
||||
}> = ({ timeline }) => (
|
||||
<Box>
|
||||
<Title order={1} mb="lg">
|
||||
Timeline
|
||||
</Title>
|
||||
<Card py="xs">
|
||||
<Timeline bulletSize={24} lineWidth={2} my="lg">
|
||||
{timeline.map(
|
||||
({
|
||||
|
|
@ -94,23 +89,16 @@ export const TimelineList: React.FC<{
|
|||
<Flex mt={4} gap="xs">
|
||||
{extra?.mastodon_status_url ? (
|
||||
<Text size="xs">
|
||||
<Anchor href={extra.mastodon_status_url} target="_blank">
|
||||
<a href={extra.mastodon_status_url} target="_blank">
|
||||
Mastodon Post <IconExternalLink size="0.75rem" />
|
||||
</Anchor>
|
||||
</a>
|
||||
</Text>
|
||||
) : null}
|
||||
{orders_pool ? (
|
||||
<Text size="xs">
|
||||
{orders_pool.id ? (
|
||||
<Anchor
|
||||
component={Link}
|
||||
to={`/orders/${username}/${orders_pool.id}`}
|
||||
>
|
||||
<Link to={`/orders/${username}/${orders_pool.id}`}>
|
||||
{orders_pool.name}
|
||||
</Anchor>
|
||||
) : (
|
||||
orders_pool.name
|
||||
)}
|
||||
</Link>
|
||||
</Text>
|
||||
) : null}
|
||||
{actor_username ? (
|
||||
|
|
@ -119,15 +107,7 @@ export const TimelineList: React.FC<{
|
|||
</Text>
|
||||
) : null}
|
||||
<Text size="xs">
|
||||
{username != my_username ? (
|
||||
<Anchor component={Link} to={`/orders/${username}`}>
|
||||
<IconLock size="0.75rem" /> {username}
|
||||
</Anchor>
|
||||
) : (
|
||||
<>
|
||||
<IconLock size="0.75rem" /> {username}
|
||||
</>
|
||||
)}
|
||||
</Text>
|
||||
<Text size="xs">{moment(updated_at).fromNow()}</Text>
|
||||
</Flex>
|
||||
|
|
@ -135,7 +115,5 @@ export const TimelineList: React.FC<{
|
|||
),
|
||||
)}
|
||||
</Timeline>
|
||||
</Card>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,68 +0,0 @@
|
|||
import React from "react";
|
||||
import { Params, useLoaderData, useParams, Link } from "react-router";
|
||||
import { OrderSetProps, OrderSets, OrderSetsResponse } from "./OrderSets";
|
||||
import { ProfileVerification } from "./ProfileVerification";
|
||||
import { Anchor, Title } from "@mantine/core";
|
||||
import { ProfilePermissions } from "./ProfilePermissions";
|
||||
|
||||
export const userOrderSetsLoader = async ({
|
||||
params: { username },
|
||||
}: {
|
||||
params: Params<string>;
|
||||
}) => fetch(`/api/orders/${username}/sets`).then((response) => response.json());
|
||||
|
||||
export const UserOrderSets: React.FC = () => {
|
||||
const { username: sub_username } = useParams();
|
||||
const orderSets = useLoaderData<OrderSetsResponse>();
|
||||
|
||||
const [profile, setProfile] = React.useState<UserProfile | null>(null);
|
||||
React.useEffect(() => {
|
||||
fetch(`/api/subs/${sub_username}`).then(async (response) => {
|
||||
if (response.ok) {
|
||||
setProfile(await response.json());
|
||||
}
|
||||
});
|
||||
}, [sub_username]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<OrderSets
|
||||
username={sub_username}
|
||||
orderSets={orderSets.pools}
|
||||
permissions={orderSets.permissions}
|
||||
linkBack={
|
||||
<Anchor component={Link} to={`/dashboard/`}>
|
||||
Return to dashboard
|
||||
</Anchor>
|
||||
}
|
||||
/>
|
||||
{profile ? (
|
||||
<>
|
||||
<Title order={1} mb="md">
|
||||
Sub Profile
|
||||
</Title>
|
||||
<ProfileVerification
|
||||
username={sub_username}
|
||||
verify_mastodon_alt_text={profile.verify_mastodon_alt_text}
|
||||
verify_mastodon_favorite={profile.verify_mastodon_favorite}
|
||||
verify_delay={profile.verify_delay}
|
||||
/>
|
||||
{profile.can_edit_permissions ? (
|
||||
<ProfilePermissions
|
||||
username={sub_username}
|
||||
permission_orders_pools_view={
|
||||
profile.permission_orders_pools_view
|
||||
}
|
||||
permission_orders_pools_details={
|
||||
profile.permission_orders_pools_details
|
||||
}
|
||||
permission_orders_pools_edit={
|
||||
profile.permission_orders_pools_edit
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
4
web/vite/src/index.d.ts
vendored
4
web/vite/src/index.d.ts
vendored
|
|
@ -9,10 +9,6 @@ type UserProfile = {
|
|||
verify_mastodon_alt_text?: boolean;
|
||||
verify_mastodon_favorite?: boolean;
|
||||
verify_delay?: number;
|
||||
can_edit_permissions?: boolean;
|
||||
permission_orders_pools_view?: boolean;
|
||||
permission_orders_pools_details?: boolean;
|
||||
permission_orders_pools_edit?: boolean;
|
||||
}
|
||||
|
||||
type OrderSetOrderAddOn = {
|
||||
|
|
|
|||
|
|
@ -8,20 +8,16 @@ import {
|
|||
Input,
|
||||
Paper,
|
||||
Slider,
|
||||
Image,
|
||||
mergeThemeOverrides,
|
||||
} from "@mantine/core";
|
||||
import { Notifications } from "@mantine/notifications";
|
||||
|
||||
import { mantineTheme } from "./theme";
|
||||
|
||||
import "@mantine/core/styles.css";
|
||||
import "@mantine/dates/styles.css";
|
||||
import "@mantine/notifications/styles.css";
|
||||
import "@mantine/charts/styles.css";
|
||||
|
||||
import { Dashboard, subsListLoader } from "./Dashboard";
|
||||
import { UserOrderSets, userOrderSetsLoader } from "./UserOrderSets";
|
||||
import { SubOrderSets, subOrderSetsLoader } from "./SubOrderSets";
|
||||
import { OrderSet, orderSetLoader, orderSetAction } from "./OrderSet";
|
||||
import { UserContextProvider } from "./UserContext";
|
||||
import { Header } from "./Header";
|
||||
|
|
@ -50,11 +46,6 @@ const theme = createTheme({
|
|||
},
|
||||
}) as any,
|
||||
}),
|
||||
Image: Image.extend({
|
||||
defaultProps: {
|
||||
radius: "xs",
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -75,8 +66,8 @@ const router = createBrowserRouter([
|
|||
},
|
||||
{
|
||||
path: "orders/:username",
|
||||
Component: UserOrderSets,
|
||||
loader: userOrderSetsLoader,
|
||||
Component: SubOrderSets,
|
||||
loader: subOrderSetsLoader,
|
||||
},
|
||||
{
|
||||
path: "orders/:username/new",
|
||||
|
|
@ -92,11 +83,9 @@ const router = createBrowserRouter([
|
|||
},
|
||||
]);
|
||||
|
||||
const mergedTheme = mergeThemeOverrides(mantineTheme, theme);
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<MantineProvider defaultColorScheme="dark" theme={mergedTheme}>
|
||||
<MantineProvider theme={theme}>
|
||||
<UserContextProvider>
|
||||
<Notifications />
|
||||
<RouterProvider router={router} />
|
||||
|
|
|
|||
|
|
@ -1,78 +0,0 @@
|
|||
import { Card, Container, createTheme, Paper, rem, Select } from "@mantine/core";
|
||||
import type { MantineThemeOverride } from "@mantine/core";
|
||||
|
||||
const CONTAINER_SIZES: Record<string, string> = {
|
||||
xxs: rem("200px"),
|
||||
xs: rem("300px"),
|
||||
sm: rem("400px"),
|
||||
md: rem("500px"),
|
||||
lg: rem("600px"),
|
||||
xl: rem("1400px"),
|
||||
xxl: rem("1600px"),
|
||||
};
|
||||
|
||||
export const mantineTheme: MantineThemeOverride = createTheme({
|
||||
/** Put your mantine theme override here */
|
||||
fontSizes: {
|
||||
xs: rem("12px"),
|
||||
sm: rem("14px"),
|
||||
md: rem("16px"),
|
||||
lg: rem("18px"),
|
||||
xl: rem("20px"),
|
||||
"2xl": rem("24px"),
|
||||
"3xl": rem("30px"),
|
||||
"4xl": rem("36px"),
|
||||
"5xl": rem("48px"),
|
||||
},
|
||||
spacing: {
|
||||
"3xs": rem("4px"),
|
||||
"2xs": rem("8px"),
|
||||
xs: rem("10px"),
|
||||
sm: rem("12px"),
|
||||
md: rem("16px"),
|
||||
lg: rem("20px"),
|
||||
xl: rem("24px"),
|
||||
"2xl": rem("28px"),
|
||||
"3xl": rem("32px"),
|
||||
},
|
||||
primaryColor: "orange",
|
||||
components: {
|
||||
/** Put your mantine component override here */
|
||||
Container: Container.extend({
|
||||
vars: (_, { size, fluid }) => ({
|
||||
root: {
|
||||
"--container-size": fluid
|
||||
? "100%"
|
||||
: size !== undefined && size in CONTAINER_SIZES
|
||||
? CONTAINER_SIZES[size]
|
||||
: rem(size),
|
||||
},
|
||||
}),
|
||||
}),
|
||||
Paper: Paper.extend({
|
||||
defaultProps: {
|
||||
p: "md",
|
||||
shadow: "xl",
|
||||
radius: "md",
|
||||
withBorder: true,
|
||||
},
|
||||
}),
|
||||
|
||||
Card: Card.extend({
|
||||
defaultProps: {
|
||||
p: "xl",
|
||||
shadow: "xl",
|
||||
radius: "var(--mantine-radius-default)",
|
||||
withBorder: true,
|
||||
},
|
||||
}),
|
||||
Select: Select.extend({
|
||||
defaultProps: {
|
||||
checkIconPosition: "right",
|
||||
},
|
||||
}),
|
||||
},
|
||||
other: {
|
||||
style: "mantine",
|
||||
},
|
||||
});
|
||||
Loading…
Reference in a new issue