Implement invite flow in the web portal

This allows us to translate the pages using the same tooling and
to have consistent theming.
This commit is contained in:
Jonas Schäfer
2021-01-25 17:00:38 +01:00
parent f84adb28ac
commit c1132ae975
23 changed files with 963 additions and 23 deletions

View File

@@ -0,0 +1,7 @@
<footer>
<ul>
{#- -#}
<li>{% trans about_url=url_for('main.about') %}A <a href="{{ about_url }}">Snikket</a> service{% endtrans %}</li>
{#- -#}
</ul>
</footer>

View File

@@ -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 %}{% if body_class | default(False) %} class="{{ body_class }}"{% endif %}>{% block body %}{% endblock %}</body>
<body{% if body_id | default(False) %} id="{{ body_id }}"{% endif %}{% if body_class | default(False) %} class="{{ body_class }}"{% endif %}{% if onload | default(False) %} onload="{{ onload }}"{% endif %}>{% block body %}{% endblock %}</body>
</html>

View File

@@ -0,0 +1,10 @@
{% extends "base.html" %}
{% from "library.j2" import standard_button %}
{% block style %}
{{ super() }}
<link rel="stylesheet" type="text/css" href="{{ url_for("static", filename="css/invite.css") }}">
{% endblock %}
{% block body %}
<div id="mwrap"><main>{% block content %}{% endblock %}</main></div>
{%- include "_footer.html" -%}
{% endblock %}

View File

@@ -0,0 +1,12 @@
{% extends "invite.html" %}
{% block content %}
<div class="elevated box el-3">
<h1>{% trans site_name=config["SITE_NAME"] %}Invite to {{ site_name }}{% endtrans %}</h1>
<div class="powered-by">{% trans logo_url=url_for("static", filename="img/snikket-logo.svg") %}Powered by <img src="{{ logo_url }}" alt="Snikket">{% endtrans %}</div>
<div class="box alert">
<header>{% trans %}Invite expired{% endtrans %}</header>
<p>{% trans %}Sorry, it looks like this invite code has expired!{% endtrans %}</p>
</div>
<img alt="Sad person holding empty box" src="{{ url_for("static", filename="img/illus-empty.svg") }}" class="fullwidth">
</div>
{% endblock %}

View File

@@ -0,0 +1,43 @@
{% extends "invite.html" %}
{% set body_id = "invite" %}
{% from "library.j2" import form_button, render_errors %}
{% block head_lead %}
<title>{% trans site_name=config["SITE_NAME"] %}Register on {{ site_name }} | Snikket{% endtrans %}</title>
{% endblock %}
{% block content %}
<div class="elevated box el-3">
<h1>{% trans site_name=config["SITE_NAME"] %}Register on {{ site_name }}{% endtrans %}</h1>
<div class="powered-by">{% trans logo_url=url_for("static", filename="img/snikket-logo.svg") %}Powered by <img src="{{ logo_url }}" alt="Snikket">{% endtrans %}</div>
<p>{% trans site_name=config["SITE_NAME"] %}{{ site_name }} is using Snikket - a secure, privacy-friendly chat app.{% endtrans %}</p>
<h2>{% trans %}Create an account{% endtrans %}</h2>
<p>{% trans %}Creating an account will allow to communicate with other people using the Snikket app or compatible software. If you already have the app installed, we recommend that you continue the account creation process inside the app by clicking on the button below:{% endtrans %}</p>
<h3>{% trans %}App already installed?{% endtrans %}</h3>
{%- call standard_button("exit_to_app", invite.xmpp_uri, class="secondary") -%}
{% trans %}Open the app{% endtrans %}
{%- endcall -%}
<p class="weak">{% trans %}This button works only if you have the app installed already!{% endtrans %}</p>
<h3>{% trans %}Create an account online{% endtrans %}</h3>
<p>{% trans %}If you plan to use a legacy XMPP client, you can register an account online and enter your credentials into any XMPP-compatible software.{% endtrans %}</p>
<form method="POST"><div class="form layout-expanded">
{{- form.csrf_token -}}
{%- call render_errors(form) %}{% endcall -%}
<div class="f-ebox">
{{ form.localpart.label }}
<div class="lwrap">{{ form.localpart(class="localpart-magic") }}<span class="localpart-fixed">@{{ config["SNIKKET_DOMAIN"] }}</div>
<p class="field-desc weak">{% trans %}Choose a username, this will become the first part of your new chat address.{% endtrans %}</p>
</div>
<div class="f-ebox">
{{ form.password.label }}
{{ form.password }}
<p class="field-desc weak">{% trans %}Enter a secure password that you do not use anywhere else.{% endtrans %}</p>
</div>
<div class="f-ebox">
{{ form.password_confirm.label }}
{{ form.password_confirm }}
</div>
<div class="f-bbox">
{%- call form_button("done", form.action_register, class="primary") -%}{%- endcall -%}
</div>
</div></form>
</div>
{% endblock %}

View File

@@ -0,0 +1,20 @@
{% extends "invite.html" %}
{% set body_id = "invite" %}
{% from "library.j2" import form_button, clipboard_button %}
{% block head_lead %}
<title>{% trans site_name=config["SITE_NAME"] %}Successfully registered on {{ site_name }} | Snikket{% endtrans %}</title>
{%- include "copy-snippet.html" -%}
{% endblock %}
{% block content %}
<div class="elevated box el-3 form layout-expanded">
<h1>{% trans site_name=config["SITE_NAME"] %}Successfully registered on {{ site_name }}{% endtrans %}</h1>
<div class="powered-by">{% trans logo_url=url_for("static", filename="img/snikket-logo.svg") %}Powered by <img src="{{ logo_url }}" alt="Snikket">{% endtrans %}</div>
<p>{% trans site_name=config["SITE_NAME"], jid=jid %}Congratulations! You successfully registered on {{ site_name }} as {{ jid }}.{% endtrans %}</p>
<input type="text" readonly="readonly" value="{{ jid }}">
{%- call clipboard_button(jid, show_label=True) -%}
{% trans %}Copy address{% endtrans %}
{%- endcall -%}
<p>{% trans %}You can not set up your legacy XMPP client with the above address and the password you chose during registration.{% endtrans %}</p>
<p>{% trans %}You can now safely close this page.{% endtrans %}</p>
</div>
{% endblock %}

View File

@@ -0,0 +1,86 @@
{% extends "invite.html" %}
{% set onload = "onload();" %}
{% set body_id = "invite" %}
{% from "library.j2" import action_button %}
{% block head_lead %}
<title>{% trans site_name=config["SITE_NAME"] %}Invite to {{ site_name }} | Snikket{% endtrans %}</title>
<script async type="text/javascript" src="{{ url_for("static", filename="js/invite-magic.js") }}"></script>
<script async type="text/javascript" src="{{ url_for("static", filename="js/qrcode.min.js") }}"></script>
{% endblock %}
{% block content %}
<div class="elevated box el-3">
<h1>{% trans site_name=config["SITE_NAME"] %}Invite to {{ site_name }}{% endtrans %}</h1>
<div class="powered-by">{% trans logo_url=url_for("static", filename="img/snikket-logo.svg") %}Powered by <img src="{{ logo_url }}" alt="Snikket">{% endtrans %}</div>
{%- if invite.inviter -%}
<p>{% trans site_name=config["SITE_NAME"], inviter_name=invite.inviter %}You have been invited to chat with {{ inviter_name }} using Snikket, a secure, privacy-friendly chat app on {{ site_name }}.{% endtrans %}</p>
{%- else -%}
<p>{% trans site_name=config["SITE_NAME"] %}You have been invited to chat on {{ site_name }} using Snikket, a secure, privacy-friendly chat app.{% endtrans %}</p>
{%- endif -%}
<h2>{% trans %}Get started{% endtrans %}</h2>
<p>{% trans %}Install the Snikket App on your Android or iOS device.{% endtrans %}</p>
<div class="install-buttons">
<ul>
<li><a href="{{ play_store_url }}"><img alt='{% trans %}Get it on Google Play{% endtrans %}' src='https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png' class="play"/></a></li>
<li><a href="{{ apple_store_url }}"><img alt='{% trans %}Download on the App Store{% endtrans %}' src="{{ apple_store_badge() }}" class="apple"></a></li>
</ul>
{%- call standard_button("qrcode", "#qr-modal", class="primary", onclick="open_modal(this); return false;") -%}
{% trans %}Not on mobile?{% endtrans %}
{%- endcall -%}
</div>
<p>{% trans %}After installation the app should automatically open and prompt you to create an account. If not, simply click the button below.{% endtrans %}</p>
<h3>{% trans %}App already installed?{% endtrans %}</h3>
{%- call standard_button("exit_to_app", invite.xmpp_uri, class="secondary") -%}
{% trans %}Open the app{% endtrans %}
{%- endcall -%}
<p class="weak">{% trans %}This button works only if you have the app installed already!{% endtrans %}</p>
<h2>{% trans %}Alternatives{% endtrans %}</h2>
<p>{% trans register_url=url_for(".register", id_=invite_id) %}You can connect to Snikket using any XMPP-compatible software. If the button above does not work with your app, you may need to <a href="{{ register_url }}">register an account manually</a>.{% endtrans %}</p>
</div>
<div id="qr-modal" class="modal" tabindex="-1" role="dialog" aria-hidden="true" style="display: none;" onclick="close_modal(this); return false;">
<div role="document" class="elevated box el-2" onclick="event.stopPropagation();">
<header class="modal-title">
{#- -#}
<span>{% trans %}Scan invite code{% endtrans %}</span>
{#- -#}
{%- call action_button("close", "#", onclick="close_modal(this.parentNode.parentNode.parentNode); return false;", class="tertiary") -%}
{% trans %}Close{% endtrans %}
{%- endcall -%}
</header>
<p>{% trans %}You can transfer this invite to your mobile device by scanning a code with your camera. You can use either a QR scanner app or the Snikket app itself.{% endtrans %}</p>
<div class="tabbox">
{#- -#}
<nav class="tabs" role="tablist">
{#- -#}
<a href="#qr-info-url" class="active" role="tab" aria-controls="qr-info-url" aria-selected="true" onclick="select_tab(this); return false;">{% trans %}Using a QR code scanner{% endtrans %}</a>
{#- -#}
<a href="#qr-info-uri" role="tab" aria-controls="qr-info-uri" aria-selected="false" onclick="select_tab(this); return false;">{% trans %}Using the Snikket app{% endtrans %}</a>
{#- -#}
</nav>
{#- -#}
<div id="qr-info-url" class="tab-pane active">
<p>{% trans %}Use a <em>QR code</em> scanner on your mobile device to scan the code below:{% endtrans %}</p>
<div id="qr-invite-page" data-qrdata="{{ url_for(".view", id_=invite_id, _external=True) }}" class="qr"></div>
</div>
{#- -#}
<div id="qr-info-uri" class="tab-pane">
<img class="float-right" id="tutorial-scan" aria-hidden="true" alt="" src="{{ url_for("static", filename="img/tutorial-scan.png") }}">
<p>{% trans %}Install the Snikket app on your mobile device, open it, and tap the 'Scan' button at the top.{% endtrans %}</p>
<p>{% trans %}Your camera will turn on. Point it at the square code below until it is within the highlighted square on your screen, and wait until the app recognises it.{% endtrans %}</p>
<div id="qr-uri" data-qrdata="{{ invite.xmpp_uri }}" class="qr"></div>
</div>
{#- -#}
</div>
{#- -#}
{%- call standard_button("close", "#", onclick="close_modal(this.parentNode.parentNode); return false;", class="primary") -%}
{% trans %}Close{% endtrans %}
{%- endcall -%}
</div>
</div>
<script type="text/javascript">
var onload = function() {
apply_qr_code(document.getElementById("qr-invite-page"));
apply_qr_code(document.getElementById("qr-uri"));
};
</script>
{% endblock %}

View File

@@ -24,9 +24,9 @@
{%- if alt %}<span class="a11y-only">{{ alt }}</span>{% endif %}<svg class="icon icon-{{ name }}" aria-hidden="true"><use xlink:href="{{ url_for('static', filename='img/icons.svg' )}}#icon-{{ name }}"></use></svg>
{%- endmacro %}
{% macro standard_button(icon_name, href, caller=None, class=None) -%}
{% macro standard_button(icon_name, href, caller=None, class=None, onclick=None) -%}
{%- set label = caller() -%}
<a href="{{ href }}" class="button {% if class %}{{ class }}{% endif %}" aria-label="{{ a11y }}" title="{{ a11y }}">{% call icon(icon_name) %}{% endcall %}<span>{{ label }}</span></a>
<a href="{{ href }}" class="button {% if class %}{{ class }}{% endif %}" aria-label="{{ a11y }}" title="{{ a11y }}"{% if onclick %} onclick="{{ onclick }}"{% endif %}>{% call icon(icon_name) %}{% endcall %}<span>{{ label }}</span></a>
{%- endmacro %}
{% macro form_button(icon_name, button_obj, caller=None, class=None) -%}
@@ -52,9 +52,9 @@
</button>
{%- endmacro %}
{% macro action_button(icon_name, href, caller=None, class=None) -%}
{% macro action_button(icon_name, href, caller=None, class=None, onclick=None) -%}
{%- set a11y = caller() -%}
<a href="{{ href }}" class="button {% if class %}{{ class }}{% endif %}" aria-label="{{ a11y }}" title="{{ a11y }}">{% call icon(icon_name) %}{% endcall %}</a>
<a href="{{ href }}" class="button {% if class %}{{ class }}{% endif %}" aria-label="{{ a11y }}" title="{{ a11y }}"{% if onclick %} onclick="{{ onclick }}"{% endif %}>{% call icon(icon_name) %}{% endcall %}</a>
{%- endmacro %}
{% macro clipboard_button(data, show_label=False, caller=None, class=None) -%}

View File

@@ -8,11 +8,5 @@
{% block topbar_right %}{% endblock %}
</div>
<div id="mwrap"><main>{% block content %}{% endblock %}</main></div>
<footer>
<ul>
{#- -#}
<li>{% trans about_url=url_for('main.about') %}A <a href="{{ about_url }}">Snikket</a> service{% endtrans %}</li>
{#- -#}
</ul>
</footer>
{%- include "_footer.html" -%}
{% endblock %}