add: ability to join/leave/be banned from communities

This commit is contained in:
trisua 2025-03-31 15:39:49 -04:00
parent f3c2157dfc
commit 619184d02e
28 changed files with 618 additions and 197 deletions

View file

@ -29,16 +29,63 @@
{% if user %}
<div class="card flex" id="join_or_leave">
{% if not is_owner %} {% if not is_member %}
<button class="primary">
{% if not is_owner %} {% if not is_joined %}
<button class="primary" onclick="join_community()">
{{ icon "circle-plus" }}
<span>{{ text "communities:action.join" }}</span>
</button>
<script>
globalThis.join_community = () => {
fetch(
"/api/v1/communities/{{ community.id }}/join",
{
method: "POST",
},
)
.then((res) => res.json())
.then((res) => {
trigger("atto::toast", [
res.ok ? "success" : "error",
res.message,
]);
});
};
</script>
{% else %}
<button class="camo red">
<button
class="quaternary red"
onclick="leave_community()"
>
{{ icon "circle-minus" }}
<span>{{ text "communities:action.leave" }}</span>
</button>
<script>
globalThis.leave_community = async () => {
if (
!(await trigger("atto::confirm", [
"Are you sure you would like to do this?",
]))
) {
return;
}
fetch(
"/api/v1/communities/{{ community.id }}/memberships/{{ user.id }}",
{
method: "DELETE",
},
)
.then((res) => res.json())
.then((res) => {
trigger("atto::toast", [
res.ok ? "success" : "error",
res.message,
]);
});
};
</script>
{% endif %} {% else %}
<a
href="/community/{{ community.title }}/manage"
@ -64,7 +111,7 @@
<span class="notification chip">ID</span>
<button
title="Copy"
onclick="trigger('atto::copy_text', [{{ community.id }}])"
onclick="trigger('atto::copy_text', ['{{ community.id }}'])"
class="camo small"
>
{{ icon "copy" }}
@ -76,6 +123,11 @@
<span class="date">{{ community.created }}</span>
</div>
<div class="w-full flex justify-between items-center">
<span class="notification chip">Members</span>
<span>{{ community.member_count }}</span>
</div>
<div class="w-full flex justify-between items-center">
<span class="notification chip">Score</span>
<div class="flex gap-2">

View file

@ -1,7 +1,7 @@
{% import "macros.html" as macros %} {% import "components.html" as components
%} {% extends "communities/base.html" %} {% block content %}
<div class="flex flex-col gap-4 w-full">
{% if user %}
{% if user and can_post %}
<div class="card-nest">
<div class="card small">
<b>{{ text "communities:label.create_post" }}</b>

View file

@ -10,10 +10,14 @@
<a href="#/profile" data-tab-button="profile"
>{{ text "settings:tab.profile" }}</a
>
<a href="#/members" data-tab-button="members"
>{{ text "communities:tab.members" }}</a
>
</div>
<div class="card tertiary w-full" data-tab="general">
<div id="manage_fields" class="flex flex-col gap-2">
<div class="w-full flex flex-col gap-2" data-tab="general">
<div id="manage_fields" class="card tertiary flex flex-col gap-2">
<div class="card-nest" ui_ident="read_access">
<div class="card small">
<b>Read access</b>
@ -30,7 +34,7 @@
<div class="card-nest" ui_ident="write_access">
<div class="card small">
<b>Write access</b>
<b>Post permission</b>
</div>
<div class="card">
@ -42,6 +46,18 @@
</div>
</div>
</div>
<div class="flex gap-2 flex-wrap">
<button onclick="save_context()">
{{ icon "check" }}
<span>{{ text "general:action.save" }}</span>
</button>
<a href="/community/{{ community.title }}" class="button secondary">
{{ icon "arrow-left" }}
<span>{{ text "general:action.back" }}</span>
</a>
</div>
</div>
<div
@ -95,19 +111,179 @@
</div>
</div>
<div class="flex gap-2 flex-wrap">
<button onclick="save_context()">
{{ icon "check" }}
<span>{{ text "general:action.save" }}</span>
</button>
<div
class="card tertiary w-full hidden flex flex-col gap-2"
data-tab="members"
>
<div class="card-nest">
<div class="card small">
<b>{{ text "communities:label.select_member" }}</b>
</div>
<a href="/community/{{ community.title }}" class="button secondary">
{{ icon "arrow-left" }}
<span>{{ text "general:action.back" }}</span>
</a>
<form
class="card flex-col gap-2"
onsubmit="select_user_from_form(event)"
>
<div class="flex flex-col gap-1">
<div class="flex flex-col gap-1">
<label for="uid"
>{{ text "communities:label.user_id" }}</label
>
<input
type="number"
name="uid"
id="uid"
placeholder="user id"
required
minlength="18"
/>
</div>
<button class="primary">
{{ text "communities:action.select" }}
</button>
</div>
</form>
</div>
<div class="card flex flex-col gap-2 w-full" id="membership_info"></div>
</div>
</main>
<script>
setTimeout(() => {
const element = document.getElementById("membership_info");
const ui = ns("ui");
globalThis.ban_user = async (uid) => {
if (
!(await trigger("atto::confirm", [
"Are you sure you would like to do this?",
]))
) {
return;
}
fetch(
`/api/v1/communities/{{ community.id }}/memberships/${uid}/role`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
role: 33,
}),
},
)
.then((res) => res.json())
.then((res) => {
trigger("atto::toast", [
res.ok ? "success" : "error",
res.message,
]);
});
};
globalThis.unban_user = async (uid) => {
if (
!(await trigger("atto::confirm", [
"Are you sure you would like to do this?",
]))
) {
return;
}
fetch(
`/api/v1/communities/{{ community.id }}/memberships/${uid}/role`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
role: 5,
}),
},
)
.then((res) => res.json())
.then((res) => {
trigger("atto::toast", [
res.ok ? "success" : "error",
res.message,
]);
});
};
globalThis.select_user_from_form = (e) => {
e.preventDefault();
fetch(
`/api/v1/communities/{{ community.id }}/memberships/${e.target.uid.value}`,
)
.then((res) => res.json())
.then((res) => {
trigger("atto::toast", [
res.ok ? "success" : "error",
res.message,
]);
if (!res.ok) {
return;
}
element.innerHTML = `<div class="flex gap-2" ui_ident="actions">
<a target="_blank" class="button" href="/api/v1/auth/profile/find/${e.target.uid.value}">Open user profile</a>
${res.payload.role !== 33 ? `<button class="red quaternary" onclick="ban_user('${e.target.uid.value}')">Ban</button>` : `<button class="quaternary" onclick="unban_user('${e.target.uid.value}')">Unban</button>`}
</div>`;
ui.refresh_container(element, ["actions"]);
ui.generate_settings_ui(
element,
[
[
["role", "Permission level"],
res.payload.role,
"input",
],
],
null,
{
role: async (new_role) => {
if (
!(await trigger("atto::confirm", [
"Are you sure you would like to do this?",
]))
) {
return;
}
fetch(
`/api/v1/communities/{{ community.id }}/memberships/${e.target.uid.value}/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,
]);
});
},
},
);
});
};
}, 250);
</script>
<script>
setTimeout(() => {
const ui = ns("ui");

View file

@ -57,7 +57,7 @@
<span class="notification chip">ID</span>
<button
title="Copy"
onclick="trigger('atto::copy_text', [{{ profile.id }}])"
onclick="trigger('atto::copy_text', ['{{ profile.id }}'])"
class="camo small"
>
{{ icon "copy" }}

View file

@ -3,34 +3,21 @@
{% endblock %} {% block body %} {{ macros::nav() }}
<main class="flex flex-col gap-2">
<div class="pillmenu">
<a
data-tab-button="account"
class="active"
href="#/account"
onclick="show_save_button()"
>
<a data-tab-button="account" class="active" href="#/account">
{{ text "settings:tab.account" }}
</a>
<a
data-tab-button="profile"
href="#/profile"
onclick="show_save_button()"
>
<a data-tab-button="profile" href="#/profile">
{{ text "settings:tab.profile" }}
</a>
<a
data-tab-button="sessions"
href="#/sessions"
onclick="hide_save_button()"
>
<a data-tab-button="sessions" href="#/sessions">
{{ text "settings:tab.sessions" }}
</a>
</div>
<div class="card w-full tertiary" data-tab="account">
<div class="flex flex-col gap-2" id="account_settings">
<div class="w-full flex flex-col gap-2" data-tab="account">
<div class="card tertiary flex flex-col gap-2" id="account_settings">
<div class="card-nest" ui_ident="change_password">
<div class="card small">
<b>{{ text "settings:label.change_password" }}</b>
@ -107,10 +94,15 @@
</form>
</div>
</div>
<button onclick="save_settings()" id="save_button">
{{ icon "check" }}
<span>{{ text "general:action.save" }}</span>
</button>
</div>
<div class="card w-full tertiary hidden" data-tab="profile">
<div class="flex flex-col gap-2" id="profile_settings">
<div class="w-full hidden flex flex-col gap-2" data-tab="profile">
<div class="card tertiary flex flex-col gap-2" id="profile_settings">
<div class="card-nest" ui_ident="change_avatar">
<div class="card small">
<b>{{ text "settings:label.change_avatar" }}</b>
@ -188,20 +180,7 @@
{% endfor %}
</div>
<button onclick="save_settings()" id="save_button" data-turbo-permanent>
{{ icon "check" }}
<span>{{ text "general:action.save" }}</span>
</button>
<script>
function show_save_button() {
document.getElementById("save_button").removeAttribute("style");
}
function hide_save_button() {
document.getElementById("save_button").style.display = "none";
}
setTimeout(() => {
const ui = ns("ui");
const settings = JSON.parse("{{ user_settings_serde|safe }}");