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 id="title" class="text-2xl font-bold tracking-tight">
        {{ team.name }}
      </h2>
      <p class="text-muted-foreground">
        Manage and view your team. View
        <a
          hx-boost="true"
          hx-select="#screen"
          hx-target="#screen"
          hx-swap="outerHTML"
          href="/team/{{ team.id }}"
          class="underline"
          >public profile</a
        >.
      </p>
    </div>
    <div class="grid lg:grid-cols-2 gap-6">
      <div class="flex flex-col gap-6">
        {% call card.root() %}
          {% call card.header() %}
            {% call card.title() %}
              Members
            {% endcall %}
            {% call card.description() %}
              Invite and view members on your team
            {% endcall %}
          {% endcall %}
          {% call card.content() %}
            {% with x=team_invite_url %}
            {% endwith %}
            <h4 class="text-sm">Invite Link</h4>
            <p class="mb-2 text-sm text-muted-foreground">
              Send this invite link to your team members
            </p>
            <div class="flex mb-4 gap-2">
              {% include "team-token.html" %}
              <button
                class="px-4 py-2 border border-input bg-background hover:bg-accent hover:text-accent-foreground inline-flex items-center justify-center rounded-md text-sm font-medium whitespace-nowrap ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"
                onclick="copyInviteTokenToClipboard()"
                title="Copy invite token to clipboard"
              >
                {{ icons.copy() }}
              </button>
              {% if user.is_team_owner %}
                <button
                  hx-post="/team/roll-token"
                  hx-target="#team-token"
                  hx-swap="outerHTML"
                  class="px-4 py-2 border border-input bg-background hover:bg-accent hover:text-accent-foreground inline-flex items-center justify-center rounded-md text-sm font-medium whitespace-nowrap ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"
                  title="Roll a new invite token"
                >
                  {{ icons.roll() }}
                </button>
              {% endif %}
              <script>
                function copyInviteTokenToClipboard() {
                  navigator.clipboard
                    .writeText(document.querySelector("#team-token").value)
                    .then(
                      () => {
                        rhombus.toast.success(
                          "Copied invite token to clipboard",
                        );
                      },
                      () => {
                        rhombus.toast.error(
                          "Failed to copy invite token to clipboard",
                        );
                      },
                    );
                }
              </script>
            </div>
            {% with x=team, y=max_players, z=lang %}
            {% endwith %}
            {% include "team-members.html" %}
          {% endcall %}
        {% endcall %}

        {% call card.root() %}
          {% call card.header() %}
            {% call card.title() %}
              Standing
            {% endcall %}
            {% call card.description() %}
              Your team's standing in the competition
            {% endcall %}
          {% endcall %}
          {% call card.content() %}
            {% with x=divisions, y=standings %}
            {% endwith %}
            {% include "standing-table.html" %}
          {% endcall %}
        {% endcall %}

        {% call card.root() %}
          {% call card.header() %}
            {% call card.title() %}
              Divisions
            {% endcall %}
            {% call card.description() %}
              Join your team to eligible divisions
            {% endcall %}
          {% endcall %}
          {% call card.content() %}
            {% with a=divisions, b=user, c=num_joined_divisions, d=team %}
            {% endwith %}
            {% include "team-divisions.html" %}
          {% endcall %}
        {% endcall %}

        {% if user.is_team_owner %}
          {% call card.root() %}
            {% call card.header() %}
              {% call card.title() %}
                Settings
              {% endcall %}
              {% call card.description() %}
                Change settings about your team (owner only)
              {% 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="{{ team.name }}"
                class="bg-background border p-2 rounded-md w-full focus-visible:outline-none"
                hx-post="/team/name"
                hx-target="next"
              />
              <div></div>
            {% endcall %}
          {% endcall %}
        {% endif %}
      </div>
      <div>
        {% call card.root() %}
          {% call card.header() %}
            {% call card.title() %}
              Solves
            {% endcall %}
            {% call card.description() %}
              Challenges your team has 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) %}
                  {% with challenge=challenges[challenge_id], solver=team.users[solve.user_id], category=categories[challenge.category_id] %}
                    <li class="flex items-center justify-between">
                      <div>
                        <a
                          hx-boost="true"
                          hx-select="#screen"
                          hx-target="#screen"
                          hx-swap="outerHTML"
                          href="/challenges#{{ challenge.name }}"
                          class="font-bold"
                          title="Go to challenge"
                        >
                          <span style="color: {{ category.color }}"
                            >{{ category.name }} /
                          </span>
                          {{ challenge.name }}
                        </a>
                        <span class="text-muted-foreground">
                          / {{ 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>
                      </div>
                      <a
                        hx-boost="true"
                        hx-select="#screen"
                        hx-target="#screen"
                        hx-swap="outerHTML"
                        href="/user/{{ solve.user_id }}"
                        class="flex items-center gap-2"
                        title="Go to public user profile"
                      >
                        <span>{{ solver.name }}</span>
                        <img
                          class="size-8 rounded-full"
                          src="{{ solver.avatar_url }}"
                        />
                      </a>
                    </li>
                  {% endwith %}
                {% endfor %}
              </ol>
            {% else %}
              <p>Your team has not solved any challenges yet!</p>
            {% endif %}
          {% endcall %}
        {% endcall %}
      </div>
    </div>
  </div>
  <script>
    document.addEventListener("DOMContentLoaded", () => {
      document
        .querySelectorAll('input[type="checkbox"][unchecked]')
        .forEach((el) => {
          el.checked = false;
        });
      document
        .querySelectorAll('input[type="checkbox"][checked]')
        .forEach((el) => {
          el.checked = true;
        });
    });
  </script>
{% endblock %}