{% import "macros.html" as macros %} {% extends "root.html" %} {% block head %} <title>{{ profile.username }} - {{ config.name }}</title> {% endblock %} {% block body %} {{ macros::nav() }} <article> <div class="content_container flex flex-col gap-4"> {{ components::banner(username=profile.username) }} <div class="w-full flex gap-4 flex-collapse"> <div class="lhs flex flex-col gap-2 sm:w-full" style="min-width: 20rem" > <div class="card-nest w-full"> <div class="card flex gap-2" id="user_avatar_and_name"> {{ components::avatar(username=profile.username,size="72px") }} <div class="flex flex-col"> <!-- prettier-ignore --> <h3 id="username" class="username flex items-center gap-2"> {{ components::username(user=profile) }} {% if profile.is_verified %} <span title="Verified" style="color: var(--color-primary);" class="flex items-center"> {{ icon "badge-check" }} </span> {% endif %} </h3> <span class="fade">{{ profile.username }}</span> </div> </div> <div class="card flex flex-col gap-2" id="social"> <div class="w-full flex"> <a href="/@{{ profile.username }}/followers" class="w-full flex justify-center items-center gap-2" > <h4>{{ profile.follower_count }}</h4> <span>{{ text "auth:label.followers" }}</span> </a> <a href="/@{{ profile.username }}/following" class="w-full flex justify-center items-center gap-2" > <h4>{{ profile.following_count }}</h4> <span>{{ text "auth:label.following" }}</span> </a> </div> </div> </div> <div class="card-nest flex flex-col"> <div id="bio" class="card small"> {{ profile.settings.biography|markdown|safe }} </div> <div class="card flex flex-col gap-2"> <div class="w-full flex justify-between items-center"> <span class="notification chip">ID</span> <button title="Copy" onclick="trigger('atto::copy_text', ['{{ profile.id }}'])" class="camo small" > {{ icon "copy" }} </button> </div> <div class="w-full flex justify-between items-center"> <span class="notification chip">Joined</span> <span class="date">{{ profile.created }}</span> </div> {% if not profile.settings.private_last_seen or is_self or is_helper %} <div class="w-full flex justify-between items-center"> <span class="notification chip">Last seen</span> <div class="flex"> {{ components::online_indicator(user=profile) }} <span class="date"> {{ profile.last_seen }} </span> </div> </div> {% endif %} </div> </div> {% if not is_self and user %} <div class="card-nest"> <div class="card small"> <b>{{ text "auth:label.relationship" }}</b> </div> <div class="card flex gap-2 flex-wrap"> {% if not is_blocking %} {% if not is_following %} <button onclick="toggle_follow_user()"> {{ icon "user-plus" }} <span>{{ text "auto:action.follow" }}</span> </button> {% else %} <button onclick="toggle_follow_user()" class="quaternary red" > {{ icon "user-minus" }} <span>{{ text "auto:action.unfollow" }}</span> </button> {% endif %} <button onclick="toggle_block_user()" class="quaternary red" > {{ icon "shield" }} <span>{{ text "auto:action.block" }}</span> </button> {% else %} <button onclick="toggle_block_user()" class="quaternary red" > {{ icon "shield-off" }} <span>{{ text "auto:action.unblock" }}</span> </button> {% endif %} <script> globalThis.toggle_follow_user = () => { fetch( "/api/v1/auth/profile/{{ profile.id }}/follow", { method: "POST", }, ) .then((res) => res.json()) .then((res) => { trigger("atto::toast", [ res.ok ? "success" : "error", res.message, ]); }); }; globalThis.toggle_block_user = async () => { if ( !(await trigger("atto::confirm", [ "Are you sure you would like to do this?", ])) ) { return; } fetch( "/api/v1/auth/profile/{{ profile.id }}/block", { method: "POST", }, ) .then((res) => res.json()) .then((res) => { trigger("atto::toast", [ res.ok ? "success" : "error", res.message, ]); }); }; </script> </div> </div> {% endif %} {% if not profile.settings.private_communities or is_self or is_helper %} <div class="card-nest"> <div class="card small flex gap-2 items-center"> {{ icon "users-round" }} <span>{{ text "auth:label.joined_communities" }}</span> </div> <div class="card flex flex-wrap gap-2"> {% for community in communities %} <a href="/community/{{ community.title }}"> {{ components::community_avatar(id=community.id, community=community, size="48px") }} </a> {% endfor %} </div> </div> {% endif %} </div> <div class="rhs w-full flex flex-col gap-4"> {% if is_helper %} <div class="card-nest"> <div class="card small flex items-center gap-2"> {{ icon "shield" }} <span>{{ text "auth:label.moderation" }}</span> </div> <div class="card tertiary"> <div class="flex flex-col gap-2" id="mod_options"> <div class="card w-full flex flex-wrap gap-2" ui_ident="actions" > <a href="/settings?username={{ profile.username }}" class="button quaternary" > {{ icon "settings" }} <span>View settings</span> </a> <button class="red quaternary" onclick="delete_account(event)" > {{ icon "trash" }} <span >{{ text "settings:label.delete_account" }}</span > </button> {% if profile.permissions != 131073 %} <button class="red quaternary" onclick="update_user_role(131073)" > Ban </button> {% else %} <button class="quaternary" onclick="update_user_role(1)" > Unban </button> {% endif %} </div> </div> <script> setTimeout(() => { const ui = ns("ui"); const element = document.getElementById("mod_options"); async function profile_request( do_confirm, path, body, ) { if (do_confirm) { if ( !(await trigger("atto::confirm", [ "Are you sure you would like to do this?", ])) ) { return; } } fetch( `/api/v1/auth/profile/{{ profile.id }}/${path}`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(body), }, ) .then((res) => res.json()) .then((res) => { trigger("atto::toast", [ res.ok ? "success" : "error", res.message, ]); }); } globalThis.delete_account = async (e) => { e.preventDefault(); if ( !(await trigger("atto::confirm", [ "Are you sure you would like to do this?", ])) ) { return; } fetch( "/api/v1/auth/profile/{{ profile.id }}", { method: "DELETE", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ password: "", }), }, ) .then((res) => res.json()) .then((res) => { trigger("atto::toast", [ res.ok ? "success" : "error", res.message, ]); }); }; globalThis.update_user_role = async ( new_role, ) => { if ( !(await trigger("atto::confirm", [ "Are you sure you would like to do this?", ])) ) { return; } fetch( `/api/v1/auth/profile/{{ profile.id }}/role`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ role: Number.parseInt(new_role), }), }, ) .then((res) => res.json()) .then((res) => { trigger("atto::toast", [ res.ok ? "success" : "error", res.message, ]); }); }; ui.refresh_container(element, ["actions"]); setTimeout(() => { ui.refresh_container(element, ["actions"]); ui.generate_settings_ui( element, [ [ ["is_verified", "Is verified"], "{{ profile.is_verified }}", "checkbox", ], [ ["role", "Permission level"], "{{ profile.permissions }}", "input", ], ], null, { is_verified: (value) => { profile_request( false, "verified", { is_verified: value, }, ); }, role: (new_role) => { return update_user_role( new_role, ); }, }, ); }, 100); }, 150); </script> </div> </div> {% endif %} {% block content %}{% endblock %} </div> </div> </div> </article> {% endblock %}