Skip to content

Commit

Permalink
Merge pull request #182 from snikket-im/invitation-improvements
Browse files Browse the repository at this point in the history
Allow selecting a role when creating an invitation
  • Loading branch information
mwild1 authored Apr 17, 2024
2 parents b868432 + 7e26b5f commit 82db30f
Show file tree
Hide file tree
Showing 9 changed files with 218 additions and 142 deletions.
15 changes: 13 additions & 2 deletions snikket_web/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,12 +284,21 @@ class InvitePost(BaseForm):
type_ = wtforms.RadioField(
_l("Invitation type"),
choices=[
("account", _l("Individual (for one person)")),
("group", _l("Group (for multiple people)")),
("account", _l("Individual")),
("group", _l("Group")),
],
default="account",
)

role = wtforms.RadioField(
_l("Access Level"),
choices=[
("prosody:restricted", _l("Limited")),
("prosody:registered", _l("Normal user")),
("prosody:admin", _l("Administrator")),
],
)

action_create_invite = wtforms.SubmitField(
_l("New invitation link")
)
Expand Down Expand Up @@ -369,11 +378,13 @@ async def create_invite() -> typing.Union[str, werkzeug.Response]:
if form.type_.data == "group":
invite = await client.create_group_invite(
group_ids=form.circles.data,
role_names=[form.role.data],
ttl=form.lifetime.data,
)
else:
invite = await client.create_account_invite(
group_ids=form.circles.data,
role_names=[form.role.data],
ttl=form.lifetime.data,
)
await flash(
Expand Down
6 changes: 6 additions & 0 deletions snikket_web/prosodyclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ class AdminInviteInfo:
expires: datetime
reusable: bool
group_ids: typing.Collection[str]
role_names: typing.Collection[str]
is_reset: bool

@classmethod
Expand All @@ -177,6 +178,7 @@ def from_api_response(
xmpp_uri=data.get("xmpp_uri"),
landing_page=data.get("landing_page"),
group_ids=data.get("groups", []),
role_names=data.get("roles", []),
reusable=data["reusable"],
is_reset=data.get("reset", False),
)
Expand Down Expand Up @@ -1086,12 +1088,14 @@ async def create_account_invite(
self,
*,
group_ids: typing.Collection[str] = [],
role_names: typing.Collection[str] = [],
restrict_username: typing.Optional[str] = None,
ttl: typing.Optional[int] = None,
session: aiohttp.ClientSession,
) -> AdminInviteInfo:
payload: typing.Dict[str, typing.Any] = {}
payload["groups"] = list(group_ids)
payload["roles"] = list(role_names)
if restrict_username is not None:
payload["username"] = restrict_username
if ttl is not None:
Expand All @@ -1108,11 +1112,13 @@ async def create_group_invite(
self,
*,
group_ids: typing.Collection[str] = [],
role_names: typing.Collection[str] = [],
ttl: typing.Optional[int] = None,
session: aiohttp.ClientSession,
) -> AdminInviteInfo:
payload: typing.Dict[str, typing.Any] = {
"groups": list(group_ids),
"roles": list(role_names),
}
if ttl is not None:
payload["ttl"] = ttl
Expand Down
11 changes: 11 additions & 0 deletions snikket_web/scss/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,13 @@ div.form.layout-expanded {
margin: 0;
}

fieldset.descriptive-radio-selection {
p {
margin-top: 0;
margin-bottom: $w-s2;
}
}

input[type="radio"] + label, input[type="checkbox"] + label {
font-weight: inherit;
color: inherit;
Expand Down Expand Up @@ -363,6 +370,10 @@ div.form.layout-expanded {
margin-left: 0.25em;
}

.radio-button-ext {
margin-left: 0.5rem;
}

div.select-wrap {
display: block;
border-bottom: $w-s4 solid $primary-500;
Expand Down
5 changes: 5 additions & 0 deletions snikket_web/static/img/icons.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
46 changes: 42 additions & 4 deletions snikket_web/templates/admin_create_invite_form.html
Original file line number Diff line number Diff line change
@@ -1,20 +1,57 @@
{% from "library.j2" import form_button, render_errors %}
{% from "library.j2" import form_button,
render_errors,
access_level_description, access_level_icon,
invite_type_description, invite_type_icon
%}
<form method="POST" action="{{ url_for(".create_invite") }}">
{{- invite_form.csrf_token -}}
<div class="form layout-expanded">
<h2 class="form-title">{% trans %}Create new invitation{% endtrans %}</h2>
<p class="form-descr weak">{% trans %}Create a new invitation link to invite more users to your Snikket service by clicking the button below.{% endtrans %}</p>

<!-- Invitation type -->
<div class="f-ebox">
<fieldset>{#- -#}
<fieldset class="descriptive-radio-selection">{#- -#}
<legend>{{ invite_form.type_.label.text }}</legend>
<span>{% trans %}Choose whether this invitation link will allow more than one person to join.{% endtrans %}</span>
{{- invite_form.type_ -}}
<p>{% trans %}Choose whether this invitation link will allow more than one person to join.{% endtrans %}</p>

{%- for invite_type in invite_form.type_ -%}
<div class="radio-button-ext">
{{ invite_type }}<label for="{{ invite_type.id }}">
{%- trans title=invite_type.label.text, icon=invite_type_icon(invite_type.data), description=invite_type_description(invite_type.data) -%}
<span class="invite-type">{{ title }}{{ icon }}</span><p>{{ description }}</p>
{%- endtrans -%}
</label>
</div>
{%- endfor -%}
</fieldset>
</div>

<!-- Access level -->
<div class="f-ebox">
<fieldset class="descriptive-radio-selection">{#- -#}
<legend>{{ invite_form.role.label.text }}</legend>
<p>{% trans %}The access level of a user determines what interactions are allowed for them on your Snikket service.{% endtrans %}</p>
{%- for level in invite_form.role -%}
<div class="radio-button-ext">
{{ level }}<label for="{{ level.id }}">
{%- trans title=level.label.text, icon=access_level_icon(level.data), description=access_level_description(level.data) -%}
<span class="access-level">{{ title }}{{ icon }}</span><p>{{ description }}</p>
{%- endtrans -%}
</label>
</div>
{%- endfor -%}
</fieldset>
</div>


<!-- Valid for -->
<div class="f-ebox">
{{ invite_form.lifetime.label }}
<div class="select-wrap">{{ invite_form.lifetime }}</div>
</div>

<!-- Invite to circle -->
<div class="f-ebox">
{#
NOTE: This is for when/if we ever support multi-group invites.
Expand All @@ -28,6 +65,7 @@ <h2 class="form-title">{% trans %}Create new invitation{% endtrans %}</h2>
<div class="select-wrap">{{ invite_form.circles }}</div>
{%- call render_errors(invite_form.circles) -%}{%- endcall -%}
</div>

<div class="f-bbox">
{%- call form_button("create_link", invite_form.action_create_invite, class="primary") %}{% endcall -%}
</div>
Expand Down
18 changes: 1 addition & 17 deletions snikket_web/templates/admin_edit_user.html
Original file line number Diff line number Diff line change
@@ -1,21 +1,5 @@
{% extends "admin_app.html" %}
{% from "library.j2" import box, form_button, standard_button, icon %}
{% macro access_level_description(role, caller=None) %}
{%- if role == "prosody:restricted" -%}
{% trans %}Limited users can interact with users on the same Snikket service and be members of circles.{% endtrans %}
{%- elif role == "prosody:registered" -%}
{% trans %}Like limited users and can also interact with users on other Snikket services.{% endtrans %}
{%- elif role == "prosody:admin" -%}
{% trans %}Like normal users and can access the admin panel in the web portal.{% endtrans %}
{%- endif -%}
{% endmacro %}
{% macro access_level_icon(role, caller=None) %}
{%- if role == "prosody:restricted" -%}
{% call icon("lock") %}{% endcall %}
{%- elif role == "prosody:admin" -%}
{% call icon("admin") %}{% endcall %}
{%- endif -%}
{% endmacro %}
{% from "library.j2" import box, form_button, standard_button, icon, access_level_description, access_level_icon %}
{% block content %}
<h1>{% trans user_name=target_user.localpart %}Edit user {{ user_name }}{% endtrans %}</h1>
<form method="POST">{{ form.csrf_token }}<div class="form layout-expanded">
Expand Down
34 changes: 34 additions & 0 deletions snikket_web/templates/library.j2
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,37 @@
{% trans %}Can be used once to create an account on this Snikket service.{% endtrans %}
{%- endif -%}
{%- endmacro -%}

{% macro access_level_description(role, caller=None) %}
{%- if role == "prosody:restricted" -%}
{% trans %}Limited users can interact with users on the same Snikket service and be members of circles.{% endtrans %}
{%- elif role == "prosody:registered" -%}
{% trans %}Like limited users and can also interact with users on other Snikket services.{% endtrans %}
{%- elif role == "prosody:admin" -%}
{% trans %}Like normal users and can access the admin panel in the web portal.{% endtrans %}
{%- endif -%}
{% endmacro %}

{% macro access_level_icon(role, caller=None) %}
{%- if role == "prosody:restricted" -%}
{% call icon("lock") %}{% endcall %}
{%- elif role == "prosody:admin" -%}
{% call icon("admin") %}{% endcall %}
{%- endif -%}
{% endmacro %}

{% macro invite_type_description(invite_type, caller=None) %}
{%- if invite_type == "account" -%}
{% trans %}Invite a single person (invitation link can only be used once).{% endtrans %}
{%- elif invite_type == "group" -%}
{% trans %}Invite a group of people (invitation link can be used multiple times).{% endtrans %}
{%- endif -%}
{% endmacro %}

{% macro invite_type_icon(invite_type, caller=None) %}
{%- if invite_type == "account" -%}
{% call icon("person") %}{% endcall %}
{%- elif invite_type == "group" -%}
{% call icon("people") %}{% endcall %}
{%- endif -%}
{% endmacro %}
Loading

0 comments on commit 82db30f

Please sign in to comment.