2025-03-25 23:58:27 -04:00
|
|
|
{% import "macros.html" as macros %} {% extends "root.html" %} {% block head %}
|
|
|
|
<title>{{ profile.username }} - {{ config.name }}</title>
|
|
|
|
{% endblock %} {% block body %} {{ macros::nav() }}
|
|
|
|
<article>
|
2025-03-31 19:31:36 -04:00
|
|
|
<div class="content_container flex flex-col gap-4">
|
|
|
|
{{ components::banner(username=profile.username) }}
|
|
|
|
|
2025-03-25 23:58:27 -04:00
|
|
|
<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">
|
2025-03-29 00:26:56 -04:00
|
|
|
{{
|
|
|
|
components::avatar(username=profile.username,size="72px")
|
2025-03-25 23:58:27 -04:00
|
|
|
}}
|
|
|
|
<div class="flex flex-col">
|
2025-03-26 21:46:21 -04:00
|
|
|
<!-- prettier-ignore -->
|
2025-04-01 16:12:13 -04:00
|
|
|
<h3 id="username" class="username flex items-center gap-2">
|
2025-03-31 11:45:34 -04:00
|
|
|
{{ components::username(user=profile) }}
|
2025-03-26 21:46:21 -04:00
|
|
|
|
|
|
|
{% if profile.is_verified %}
|
2025-04-01 16:12:13 -04:00
|
|
|
<span title="Verified" style="color: var(--color-primary);" class="flex items-center">
|
2025-03-26 21:46:21 -04:00
|
|
|
{{ icon "badge-check" }}
|
|
|
|
</span>
|
|
|
|
{% endif %}
|
2025-03-25 23:58:27 -04:00
|
|
|
</h3>
|
|
|
|
|
|
|
|
<span class="fade">{{ profile.username }}</span>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
2025-03-31 20:02:09 -04:00
|
|
|
<div class="card flex flex-col gap-2" id="social">
|
|
|
|
<div class="w-full flex">
|
|
|
|
<a
|
2025-03-31 22:35:11 -04:00
|
|
|
href="/@{{ profile.username }}/followers"
|
2025-03-31 20:02:09 -04:00
|
|
|
class="w-full flex justify-center items-center gap-2"
|
|
|
|
>
|
|
|
|
<h4>{{ profile.follower_count }}</h4>
|
|
|
|
<span>{{ text "auth:label.followers" }}</span>
|
|
|
|
</a>
|
|
|
|
<a
|
2025-03-31 22:35:11 -04:00
|
|
|
href="/@{{ profile.username }}/following"
|
2025-03-31 20:02:09 -04:00
|
|
|
class="w-full flex justify-center items-center gap-2"
|
|
|
|
>
|
|
|
|
<h4>{{ profile.following_count }}</h4>
|
|
|
|
<span>{{ text "auth:label.following" }}</span>
|
|
|
|
</a>
|
|
|
|
</div>
|
2025-03-25 23:58:27 -04:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="card-nest flex flex-col">
|
2025-03-26 21:46:21 -04:00
|
|
|
<div id="bio" class="card small">
|
2025-03-29 22:27:57 -04:00
|
|
|
{{ profile.settings.biography|markdown|safe }}
|
2025-03-25 23:58:27 -04:00
|
|
|
</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"
|
2025-03-31 15:39:49 -04:00
|
|
|
onclick="trigger('atto::copy_text', ['{{ profile.id }}'])"
|
2025-03-25 23:58:27 -04:00
|
|
|
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>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
2025-04-01 15:03:56 -04:00
|
|
|
{% if not is_self and user %}
|
2025-03-31 20:02:09 -04:00
|
|
|
<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>
|
2025-04-01 15:03:56 -04:00
|
|
|
{% endif %} {% if not profile.settings.private_communities or
|
2025-04-01 16:12:13 -04:00
|
|
|
is_self or is_helper %}
|
2025-03-25 23:58:27 -04:00
|
|
|
<div class="card-nest">
|
2025-03-26 21:46:21 -04:00
|
|
|
<div class="card small flex gap-2 items-center">
|
2025-03-25 23:58:27 -04:00
|
|
|
{{ icon "users-round" }}
|
2025-03-29 00:26:56 -04:00
|
|
|
<span>{{ text "auth:label.joined_communities" }}</span>
|
2025-03-25 23:58:27 -04:00
|
|
|
</div>
|
|
|
|
|
2025-03-29 00:26:56 -04:00
|
|
|
<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>
|
2025-03-25 23:58:27 -04:00
|
|
|
</div>
|
2025-04-01 15:03:56 -04:00
|
|
|
{% endif %}
|
2025-03-25 23:58:27 -04:00
|
|
|
</div>
|
|
|
|
|
2025-04-01 16:12:13 -04:00
|
|
|
<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>
|
2025-04-02 11:39:51 -04:00
|
|
|
|
|
|
|
{% 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 %}
|
2025-04-01 16:12:13 -04:00
|
|
|
</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,
|
|
|
|
]);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2025-04-02 11:39:51 -04:00
|
|
|
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,
|
|
|
|
]);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2025-04-01 16:12:13 -04:00
|
|
|
ui.refresh_container(element, ["actions"]);
|
2025-04-02 11:39:51 -04:00
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
ui.refresh_container(element, ["actions"]);
|
|
|
|
|
|
|
|
ui.generate_settings_ui(
|
|
|
|
element,
|
2025-04-01 16:12:13 -04:00
|
|
|
[
|
2025-04-02 11:39:51 -04:00
|
|
|
[
|
|
|
|
["is_verified", "Is verified"],
|
|
|
|
"{{ profile.is_verified }}",
|
|
|
|
"checkbox",
|
|
|
|
],
|
|
|
|
[
|
|
|
|
["role", "Permission level"],
|
|
|
|
"{{ profile.permissions }}",
|
|
|
|
"input",
|
|
|
|
],
|
2025-04-01 16:12:13 -04:00
|
|
|
],
|
2025-04-02 11:39:51 -04:00
|
|
|
null,
|
|
|
|
{
|
|
|
|
is_verified: (value) => {
|
|
|
|
profile_request(
|
|
|
|
false,
|
|
|
|
"verified",
|
|
|
|
{
|
|
|
|
is_verified: value,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
},
|
|
|
|
role: (new_role) => {
|
|
|
|
return update_user_role(
|
|
|
|
new_role,
|
|
|
|
);
|
|
|
|
},
|
2025-04-01 16:12:13 -04:00
|
|
|
},
|
2025-04-02 11:39:51 -04:00
|
|
|
);
|
|
|
|
}, 100);
|
2025-04-01 16:12:13 -04:00
|
|
|
}, 150);
|
|
|
|
</script>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
{% endif %} {% block content %}{% endblock %}
|
|
|
|
</div>
|
2025-03-25 23:58:27 -04:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</article>
|
|
|
|
{% endblock %}
|