rhombus 0.2.21

Next generation extendable CTF framework with batteries included
Documentation
{% extends "layout.html" %}
{% import "card.html" as card %}
{% import "icons.html" as icons %}

{% block content %}
  <div class="container my-4">
    <div class="mb-4 space-y-0.5">
      <h2 class="text-2xl font-bold tracking-tight">{{ t("account") }}</h2>
      <p class="text-muted-foreground">
        {% with link_start="<a hx-boost='true' hx-select='#screen' hx-target='#screen' hx-swap='outerHTML' href='/user/{}' class='underline'>" | replace("{}", user.id) %}
          {{ t("account-description", link_start=link_start, link_end="</a>") | safe }}
        {% endwith %}
      </p>
    </div>
    <div class="grid lg:grid-cols-2 gap-6">
      <div class="flex flex-col gap-6">
        {% if discord %}
          {% call card.root() %}
            {% call card.header() %}
              {% call card.title() %}
                {% with minijinja_lift=lang %}
                {% endwith %}

                {{ t("account-discord-integration") }}
              {% endcall %}
              {% call card.description() %}
                {% with minijinja_lift=lang %}
                {% endwith %}

                {{ t("account-discord-integration-description") }}
              {% endcall %}
            {% endcall %}
            {% call card.content() %}
              <div class="ml-[18px] mt-1 border-l pl-8">
                <h3
                  class="flex items-center text-xl font-semibold tracking-tight"
                >
                  <div
                    class="absolute ml-[-50px] inline-flex size-9 items-center justify-center rounded-full border-4 {% if user.discord_id %}
                      bg-green-300 dark:bg-green-500
                    {% else %}
                      bg-border
                    {% endif %}"
                  >
                    {% if user.discord_id %}
                      {{ icons.check(class="size-4") }}
                    {% else %}
                      {{ icons.ellipsis(class="size-4") }}
                    {% endif %}
                  </div>
                  {% if user.discord_id %}
                    {{ t("account-discord-integration-link-discord") }}
                  {% else %}
                    <a class="flex items-center gap-2" href="/signin/discord">
                      <span
                        >{{ t("account-discord-integration-link-discord") }}</span
                      >
                      {{ icons.external_link(class="size-6") }}
                    </a>
                  {% endif %}
                </h3>
                <p class="mt-2">
                  {{ t("account-discord-integration-link-discord-description") }}
                </p>

                <h3
                  class="mt-8 flex items-center text-xl font-semibold tracking-tight"
                >
                  <div
                    class="absolute ml-[-50px] inline-flex size-9 items-center justify-center rounded-full border-4 {% if discord.in_server %}
                      bg-green-300 dark:bg-green-500
                    {% else %}
                      bg-border
                    {% endif %}"
                  >
                    {% if discord.in_server %}
                      {{ icons.check(class="size-4") }}
                    {% else %}
                      {{ icons.ellipsis(class="size-4") }}
                    {% endif %}
                  </div>

                  {% if discord.in_server %}
                    {{ t("account-discord-integration-join-server") }}
                  {% else %}
                    <a
                      class="flex items-center gap-2"
                      href="{{ discord.invite_url }}"
                    >
                      <span
                        >{{ t("account-discord-integration-join-server") }}</span
                      >
                      {{ icons.external_link(class="size-6") }}
                    </a>
                  {% endif %}
                </h3>
                <p class="mt-2">
                  {{ t("account-discord-integration-join-server-description") }}
                </p>
              </div>
            {% endcall %}
          {% endcall %}
        {% endif %}

        {% call card.root() %}
          {% call card.header() %}
            {% call card.title() %}
              Divisions
            {% endcall %}
            {% call card.description() %}
              Join divisions to make your team eligible for different
              leaderboards
            {% endcall %}
          {% endcall %}
          {% call card.content() %}
            <div class="flex flex-col gap-2">
              <div class="flex items-center justify-between">
                <div class="text-muted-foreground">Division Name</div>
                <div class="text-muted-foreground">Joined</div>
              </div>
              {% for division in divisions %}
                <div class="flex items-center justify-between">
                  <div class="flex items-center">
                    {{ division.name }}
                    <div title="{{ division.description }}">
                      {{ icons.info(class="h-4 ml-2") }}
                    </div>
                  </div>
                  <div class="flex items-center">
                    <!-- prettier-ignore-start -->
                    <input
                      type="checkbox"
                      class="size-4 accent-primary"
                      disabled
                      {% if not division.eligible %}
                        title="Ineligible for division. {{ division.requirement }}"
                      {% else %}
                        checked
                        title="In the division"
                      {% endif %}
                    />
                    <!-- prettier-ignore-end -->
                  </div>
                </div>
              {% endfor %}
            </div>
          {% endcall %}
        {% endcall %}

        {% call card.root() %}
          {% call card.header() %}
            {% call card.title() %}
              Emails
            {% endcall %}
            {% call card.description() %}
              Manage connected emails to qualify for certain divisions
            {% endcall %}
          {% endcall %}
          {% call card.content() %}
            {% if emails | length > 0 %}
              <ul class="mb-4">
                {% for email in emails %}
                  <li
                    class="group flex justify-between even:bg-secondary p-2 {% if not email.verified %}italic{% endif %}"
                    {% if not email.verified %}
                      title="Pending verification"
                    {% endif %}
                  >
                    <span>{{ email.address }}</span>
                    {% if emails | length > 1 %}
                      <button
                        class="text-destructive hidden group-hover:block"
                        title="Unlink email from account"
                        hx-delete="/account/email"
                        hx-vals='{"email": "{{ email.address }}"}'
                      >
                        {{ icons.delete_x() }}
                      </button>
                    {% endif %}
                  </li>
                {% endfor %}
              </ul>
            {% endif %}
            <form
              class="grow flex justify-end items-center relative"
              hx-post="/account/email"
              hx-indicator="#loader"
              hx-swap="none"
            >
              <input
                type="email"
                required
                name="email"
                placeholder="New email to verify..."
                class="bg-background border p-2 rounded-md w-full focus-visible:outline-none"
              />
              <div id="loader" class="absolute mr-2 pointer-events-none">
                {{ icons.spinner(attrs='class="animate-spin"') }}
              </div>
            </form>
          {% endcall %}
        {% endcall %}

        {% call card.root() %}
          {% call card.header() %}
            {% call card.title() %}
              Settings
            {% endcall %}
            {% call card.description() %}
              Change settings about your account
            {% endcall %}
          {% endcall %}
          {% call card.content() %}
            <h4 class="text-sm">Change name</h4>
            <p class="text-sm text-muted-foreground mb-2">
              Must be between 3 and 30 characters. May only be changed every 30
              minutes.
            </p>
            <input
              type="text"
              name="name"
              value="{{ user.name }}"
              class="bg-background border p-2 rounded-md w-full focus-visible:outline-none"
              hx-post="/account/name"
              hx-target="next"
            />
            <div></div>
          {% endcall %}
        {% endcall %}
        {% include "account-cards.html" %}
      </div>
      <div>
        {% call card.root() %}
          {% call card.header() %}
            {% call card.title() %}
              Solves
            {% endcall %}
            {% call card.description() %}
              Challenges you have solved
            {% endcall %}
          {% endcall %}
          {% call card.content() %}
            {% with x=lang %}
            {% endwith %}
            {% if team.solves | length > 0 %}
              <ol class="flex flex-col gap-2">
                {% for challenge_id, solve in team.solves | dictsort(by="value", reverse=true) %}
                  {% if solve.user_id == user.id %}
                    {% with challenge=challenges[challenge_id], solver=team.users[solve.user_id], category=categories[challenge.category_id] %}
                      <li class="flex items-center h-8">
                        <a
                          hx-select="#screen"
                          hx-target="#screen"
                          hx-swap="outerHTML"
                          href="/challenges#{{ challenge.name }}"
                          class="font-bold"
                        >
                          <span style="color: {{ category.color }}"
                            >{{ category.name }} /
                          </span>
                          {{ challenge.name }}
                        </a>
                        <span class="text-muted-foreground">
                          &nbsp;/ {{ challenge.division_points[0].points }}
                          points /
                          <span title="{{ solve.solved_at }}">
                            {% with diff=timediff(solve.solved_at, now) %}
                              {{ t("time-difference", years=diff.years, days=diff.days, hours=diff.hours, minutes=diff.minutes, seconds=diff.seconds) }}
                            {%- endwith -%}
                          </span>
                        </span>
                      </li>
                    {% endwith %}
                  {% endif %}
                {% endfor %}
              </ol>
            {% else %}
              <p>You have no solves yet.</p>
            {% endif %}
          {% endcall %}
        {% endcall %}
      </div>
    </div>
  </div>
{% endblock %}