Skip to content

Commit

Permalink
Merge pull request #188 from snikket-im/invitation-ui
Browse files Browse the repository at this point in the history
Invitation admin UI improvements
  • Loading branch information
mwild1 authored Apr 30, 2024
2 parents 13c5d44 + 2ff47c4 commit 2d42099
Show file tree
Hide file tree
Showing 13 changed files with 197 additions and 85 deletions.
6 changes: 6 additions & 0 deletions snikket_web/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,10 @@ class InvitePost(BaseForm):
default="prosody:registered",
)

note = wtforms.StringField(
_l("Comment (optional)"),
)

action_create_invite = wtforms.SubmitField(
_l("New invitation link")
)
Expand Down Expand Up @@ -382,12 +386,14 @@ async def create_invite() -> typing.Union[str, werkzeug.Response]:
group_ids=form.circles.data,
role_names=[form.role.data],
ttl=form.lifetime.data,
note=form.note.data,
)
else:
invite = await client.create_account_invite(
group_ids=form.circles.data,
role_names=[form.role.data],
ttl=form.lifetime.data,
note=form.note.data,
)
await flash(
_("Invitation created"),
Expand Down
8 changes: 8 additions & 0 deletions snikket_web/prosodyclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ class AdminInviteInfo:
group_ids: typing.Collection[str]
role_names: typing.Collection[str]
is_reset: bool
note: typing.Optional[str]

@classmethod
def from_api_response(
Expand All @@ -181,6 +182,7 @@ def from_api_response(
role_names=data.get("roles", []),
reusable=data["reusable"],
is_reset=data.get("reset", False),
note=data.get("note"),
)


Expand Down Expand Up @@ -1091,6 +1093,7 @@ async def create_account_invite(
role_names: typing.Collection[str] = [],
restrict_username: typing.Optional[str] = None,
ttl: typing.Optional[int] = None,
note: typing.Optional[str] = None,
session: aiohttp.ClientSession,
) -> AdminInviteInfo:
payload: typing.Dict[str, typing.Any] = {}
Expand All @@ -1100,6 +1103,8 @@ async def create_account_invite(
payload["username"] = restrict_username
if ttl is not None:
payload["ttl"] = ttl
if note is not None:
payload["note"] = note

async with session.post(
self._admin_v1_endpoint("/invites/account"),
Expand All @@ -1114,6 +1119,7 @@ async def create_group_invite(
group_ids: typing.Collection[str] = [],
role_names: typing.Collection[str] = [],
ttl: typing.Optional[int] = None,
note: typing.Optional[str] = None,
session: aiohttp.ClientSession,
) -> AdminInviteInfo:
payload: typing.Dict[str, typing.Any] = {
Expand All @@ -1122,6 +1128,8 @@ async def create_group_invite(
}
if ttl is not None:
payload["ttl"] = ttl
if note is not None:
payload["note"] = note

async with session.post(
self._admin_v1_endpoint("/invites/group"),
Expand Down
7 changes: 3 additions & 4 deletions snikket_web/scss/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -992,19 +992,18 @@ div.profile-card {
}
}

/* clipboard button */
/* clipboard and share buttons */

.copy-to-clipboard {
.copy-to-clipboard, .share-button {
cursor: pointer;
font-style: normal;
text-decoration: none;
}

body.no-copy .copy-to-clipboard {
body.no-copy .copy-to-clipboard, body.no-share .share-button {
display: none !important;
}


/* magic */

pre.guru-meditation {
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.
6 changes: 6 additions & 0 deletions snikket_web/templates/admin_create_invite_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ <h2 class="form-title">{% trans %}Create new invitation{% endtrans %}</h2>
{%- call render_errors(invite_form.circles) -%}{%- endcall -%}
</div>

<!-- Comment -->
<div class="f-ebox">
{{ invite_form.note.label }}
{{ invite_form.note }}
</div>

<div class="f-bbox">
{%- call form_button("create_link", invite_form.action_create_invite, class="primary") %}{% endcall -%}
</div>
Expand Down
7 changes: 4 additions & 3 deletions snikket_web/templates/admin_edit_invite.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% extends "admin_app.html" %}
{% from "library.j2" import showuri, form_button, standard_button, extract_circle_name, invite_type_description %}
{% from "library.j2" import showuri, form_button, standard_button, extract_circle_name, invite_type_name, invite_type_description %}
{% block head_lead %}
{{ super() }}
{% include "copy-snippet.html" %}
Expand All @@ -13,9 +13,10 @@ <h1>{% trans %}View invitation{% endtrans %}</h1>
<dt>{% trans %}Valid until{% endtrans %}</dt>
<dd>{{ invite.expires | format_date }}</dd>
<dt><label for="link-field">{% trans %}Link{% endtrans %}</label></dt>
<dd>{% call showuri(invite.landing_page, id_="link-field") %}{% endcall %}</dd>
<dd>{% call showuri(invite.landing_page, id_="link-field") %}{% trans %}Invitation to Snikket{% endtrans %}{% endcall %}</dd>
<dt>{% trans %}Invitation type{% endtrans %}</dt>
<dd>{% call invite_type_description(invite) %}{% endcall %}</dd>
{% set invite_type = invite.reusable and "group" or "account" %}
<dd><span class="with-tooltip above" data-tooltip="{% call invite_type_description(invite_type) %}{% endcall %}">{% call invite_type_name(invite_type) %}{% endcall %}</span></dd>
{%- set ngroups = invite.group_ids | length -%}
{%- if ngroups > 1 -%}
{#- not supported via the web UI, but we should still display it properly -#}
Expand Down
14 changes: 10 additions & 4 deletions snikket_web/templates/admin_invites.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% extends "admin_app.html" %}
{% from "library.j2" import action_button, icon, clipboard_button, form_button, custom_form_button, extract_circle_name, invite_type_name, invite_type_description %}
{% from "library.j2" import action_button, icon, clipboard_button, share_button, form_button, custom_form_button, extract_circle_name, invite_type_name, invite_type_description %}
{% block head_lead %}
{{ super() }}
{% include "copy-snippet.html" %}
Expand All @@ -18,17 +18,18 @@ <h2>{% trans %}Pending invitations{% endtrans %}</h2>
<col/>
<thead>
<tr>
<th>{% trans %}Expires{% endtrans %}</th>
<th class="collapsible">{% trans %}Type{% endtrans %}</th>
<th class="collapsible">{% trans %}Circle{% endtrans %}</th>
<th>{% trans %}Expires{% endtrans %}</th>
<th>{% trans %}Comment{% endtrans %}</th>
<th>{% trans %}Actions{% endtrans %}</th>
</tr>
</thead>
<tbody>
{% for invite in invites %}
{% set invite_type = invite.reusable and "group" or "account" %}
<tr>
<td>{{ (invite.expires - now) | format_timedelta(add_direction=True) }}</td>
<td class="collapsible"><span class="with-tooltip above" data-tooltip="{% call invite_type_description(invite) %}{% endcall %}">{% call invite_type_name(invite) %}{% endcall %}</span></td>
<td class="collapsible"><span class="with-tooltip above" data-tooltip="{% call invite_type_description(invite_type) %}{% endcall %}">{% call invite_type_name(invite_type) %}{% endcall %}</span></td>
<td class="collapsible">
{#- -#}
<ul class="inline">
Expand All @@ -38,13 +39,18 @@ <h2>{% trans %}Pending invitations{% endtrans %}</h2>
</ul>
{#- -#}
</td>
<td>{{ (invite.expires - now) | format_timedelta(add_direction=True) }}</td>
<td>{% if invite.note is not none %}{{ invite.note }}{% endif %}</td>
<td class="nowrap">
{%- call action_button("more", url_for(".edit_invite", id_=invite.id_), class="secondary") -%}
{% trans %}Show invite details{% endtrans %}
{%- endcall -%}
{%- call clipboard_button(invite.landing_page, class="primary") -%}
{% trans %}Copy invite link to clipboard{% endtrans %}
{%- endcall -%}
{%- call share_button("Invitation to Snikket", invite.landing_page, class="primary") -%}
{% trans %}Share invitation link{% endtrans %}
{%- endcall -%}
{%- call custom_form_button("remove_link", form.action_revoke.name, invite.id_, class="secondary danger", slim=True) -%}
{% trans %}Delete invitation{% endtrans %}
{%- endcall -%}
Expand Down
2 changes: 1 addition & 1 deletion snikket_web/templates/admin_reset_user_password.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ <h2 class="form-title">{% trans user_name=localpart %}Password reset link for {{
<dt>{% trans %}Valid until{% endtrans %}</dt>
<dd>{{ reset_link.expires | format_date }}</dd>
<dt><label for="link-field">{% trans %}Link{% endtrans %}</label></dt>
<dd>{% call showuri(reset_link.landing_page, id_="link-field") %}{% endcall %}</dd>
<dd>{% call showuri(reset_link.landing_page, id_="link-field") %}Reset your Snikket password{% endcall %}</dd>
</dd>
<div class="f-bbox">
{%- call custom_form_button("remove_link", form.action_revoke.name, reset_link.id_, class="secondary danger") -%}
Expand Down
2 changes: 1 addition & 1 deletion snikket_web/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@
<meta name="msapplication-TileColor" content="#fbd308">
<meta name="theme-color" content="#fbd308">
</head>
<body{% if body_id | default(False) %} id="{{ body_id }}"{% endif %} class="{% if is_in_debug_mode %}debug{% endif %}{% if body_class | default(False) %} {{ body_class }}{% endif %}"{% if onload | default(False) %} onload="{{ onload }}"{% endif %}>{% block body %}{% endblock %}</body>
<body{% if body_id | default(False) %} id="{{ body_id }}"{% endif %} class="{% if is_in_debug_mode %}debug{% endif %}{% if body_class | default(False) %} {{ body_class }}{% endif %} no-copy no-share"{% if onload | default(False) %} onload="{{ onload }}"{% endif %}>{% block body %}{% endblock %}</body>
</html>
55 changes: 55 additions & 0 deletions snikket_web/templates/copy-snippet.html
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,63 @@
});
};

var copy_to_clipboard_btn = function(el) {
var text = el.dataset.cliptext;
if (!text) {
console.error('copy_to_clipboard used on element without text to copy');
}
copyTextToClipboard(text, el, function(success) {
var existing_result_el = document.getElementById("clipboard-result");
if (existing_result_el !== null) {
existing_result_el.parentNode.removeChild(existing_result_el);
}

var icon = "done";
if (!success) {
icon = "cancel";
}
var icon_bak = get_current_icon(el.firstChild);
change_icon(el.firstChild, icon);
setTimeout(function() {
change_icon(el.firstChild, icon_bak);
el.blur();
}, 1500);
});
};

var share_url_btn = function(el) {
let data = {
"title": el.dataset.shareTitle,
"url": el.dataset.shareUrl,
}

let icon_bak = get_current_icon(el.firstChild);

new Promise(function (resolve, reject) {
if(!navigator.canShare || !navigator.canShare(data)) {
return reject();
}
return resolve(navigator.share(data));
}).then(function () {
// Success
change_icon(el.firstChild, "done");
}, function () {
// Failure
change_icon(el.firstChild, "cancel");
}).finally(function () {
// Either way, clear status icon after 1.5s
setTimeout(function() {
change_icon(el.firstChild, icon_bak);
el.blur();
}, 1500);
});
}

window.addEventListener('load', function() {
document.body.classList.remove("no-copy");
if(navigator.share) {
document.body.classList.remove("no-share");
}
});
</script>

39 changes: 26 additions & 13 deletions snikket_web/templates/library.j2
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@
<em>—</em>
{%- else -%}
<div><input type="text" {% if id_ %}id="{{ id_ }}" {% endif %}readonly="readonly" value="{{ uri }}"></div>
<div>{% call clipboard_button(uri, show_label=True) %}{% trans %}Copy link{% endtrans %}{% endcall %}</div>
<div>
{% call clipboard_button(uri, show_label=True) %}{% trans %}Copy link{% endtrans %}{% endcall %}
{% call share_button(caller() if caller is not none else None, uri, show_label=True) %}{% trans %}Share{% endtrans %}{% endcall %}
</div>
{%- endif -%}
{% endmacro %}

Expand Down Expand Up @@ -82,7 +85,7 @@

{% macro clipboard_button(data, show_label=False, caller=None, class=None) -%}
{%- set label = caller() -%}
<a class="button{% if class %} {{ class }}{% endif %}"
<a class="button copy-to-clipboard{% if class %} {{ class }}{% endif %}"
href="#"
{% if not show_label %}
aria-label="{{ label }}"
Expand All @@ -97,6 +100,24 @@
</a>
{%- endmacro %}

{% macro share_button(title, url, show_label=False, caller=None, class=None) -%}
{%- set label = caller() -%}
<a class="button share-button{% if class %} {{ class }}{% endif %}"
href="#"
{% if not show_label %}
aria-label="{{ label }}"
title="{{ label }}"
{% endif %}
data-share-title="{{ title }}"
data-share-url="{{ url }}"
onclick="share_url_btn(this); return false;">
{%- call icon("share") %}{% endcall -%}
{%- if show_label %}
<span>{{ label }}</span>
{% endif -%}
</a>
{%- endmacro %}

{% macro render_errors(field, caller=None) -%}
{%- set error_list = field.errors if field.errors is not mapping else (field.errors.values() | flatten | list) -%}
{%- if error_list -%}
Expand Down Expand Up @@ -132,19 +153,11 @@
{%- endif -%}
{% endmacro %}

{%- macro invite_type_name(invite_info, caller=None) -%}
{%- if invite_info.reusable -%}
{% trans %}Group{% endtrans %}
{%- else -%}
{%- macro invite_type_name(invite_type, caller=None) -%}
{%- if invite_type == "account" -%}
{% trans %}Individual{% endtrans %}
{%- endif -%}
{%- endmacro -%}

{%- macro invite_type_description(invite_info, caller=None) -%}
{%- if invite_info.reusable -%}
{% trans %}Can be used multiple times to create accounts on this Snikket service.{% endtrans %}
{%- else -%}
{% trans %}Can be used once to create an account on this Snikket service.{% endtrans %}
{% trans %}Group{% endtrans %}
{%- endif -%}
{%- endmacro -%}

Expand Down
Loading

0 comments on commit 2d42099

Please sign in to comment.