You've already forked snikket-web-portal
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:
@@ -216,9 +216,11 @@ def create_app() -> quart.Quart:
|
||||
from .main import bp as main_bp
|
||||
from .user import bp as user_bp
|
||||
from .admin import bp as admin_bp
|
||||
from .invite import bp as invite_bp
|
||||
|
||||
app.register_blueprint(main_bp)
|
||||
app.register_blueprint(user_bp, url_prefix="/user")
|
||||
app.register_blueprint(admin_bp, url_prefix="/admin")
|
||||
app.register_blueprint(invite_bp, url_prefix="/invite")
|
||||
|
||||
return app
|
||||
|
||||
158
snikket_web/invite.py
Normal file
158
snikket_web/invite.py
Normal file
@@ -0,0 +1,158 @@
|
||||
import pathlib
|
||||
import typing
|
||||
import urllib.parse
|
||||
|
||||
import aiohttp
|
||||
|
||||
import quart.flask_patch
|
||||
from quart import (
|
||||
Blueprint,
|
||||
render_template,
|
||||
redirect,
|
||||
url_for,
|
||||
session as http_session,
|
||||
)
|
||||
|
||||
import wtforms
|
||||
|
||||
import flask_wtf
|
||||
from flask_babel import lazy_gettext as _l
|
||||
|
||||
from .infra import client, selected_locale
|
||||
|
||||
|
||||
bp = Blueprint("invite", __name__)
|
||||
|
||||
|
||||
INVITE_SESSION_JID = "invite-session-jid"
|
||||
|
||||
|
||||
# https://play.google.com/store/apps/details?id=org.snikket.android&referrer={uri|urlescape}&pcampaignid=pcampaignidMKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1
|
||||
|
||||
|
||||
def apple_store_badge() -> str:
|
||||
locale = selected_locale()
|
||||
filename = "{}.svg".format(locale)
|
||||
static_path = pathlib.Path(__file__).parent / "static" / "img" / "apple"
|
||||
if (static_path / filename).exists():
|
||||
return url_for("static", filename="img/apple/{}".format(filename))
|
||||
return url_for("static", filename="img/apple/en.svg")
|
||||
|
||||
|
||||
@bp.context_processor
|
||||
def context() -> typing.Mapping[str, typing.Any]:
|
||||
return {
|
||||
"apple_store_badge": apple_store_badge,
|
||||
}
|
||||
|
||||
|
||||
@bp.route("/<id_>")
|
||||
async def view(id_: str) -> str:
|
||||
try:
|
||||
invite = await client.get_public_invite_by_id(id_)
|
||||
except aiohttp.ClientResponseError as exc:
|
||||
if exc.status == 404:
|
||||
# invite expired
|
||||
return await render_template("invite_invalid.html")
|
||||
raise
|
||||
|
||||
play_store_url = (
|
||||
"https://play.google.com/store/apps/details?" +
|
||||
urllib.parse.urlencode(
|
||||
(
|
||||
("id", "org.snikket.android"),
|
||||
("referrer", invite.xmpp_uri),
|
||||
("pcampaignid",
|
||||
"pcampaignidMKT-Other-global-all-co-prtnr-py-"
|
||||
"PartBadge-Mar2515-1"),
|
||||
),
|
||||
)
|
||||
)
|
||||
apple_store_url = (
|
||||
"https://apps.apple.com/us/app/tigase-messenger/id1153516838"
|
||||
)
|
||||
|
||||
return await render_template(
|
||||
"invite_view.html",
|
||||
invite=invite,
|
||||
play_store_url=play_store_url,
|
||||
apple_store_url=apple_store_url,
|
||||
invite_id=id_,
|
||||
)
|
||||
|
||||
|
||||
class RegisterForm(flask_wtf.FlaskForm): # type:ignore
|
||||
localpart = wtforms.StringField(
|
||||
_l("Username"),
|
||||
)
|
||||
|
||||
password = wtforms.PasswordField(
|
||||
_l("Password"),
|
||||
)
|
||||
|
||||
password_confirm = wtforms.PasswordField(
|
||||
_l("Confirm password"),
|
||||
validators=[wtforms.validators.InputRequired(),
|
||||
wtforms.validators.EqualTo(
|
||||
"password",
|
||||
_l("The passwords must match")
|
||||
)]
|
||||
)
|
||||
|
||||
action_register = wtforms.SubmitField(
|
||||
_l("Create account")
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/<id_>/register", methods=["GET", "POST"])
|
||||
async def register(id_: str) -> typing.Union[str, quart.Response]:
|
||||
invite = await client.get_public_invite_by_id(id_)
|
||||
form = RegisterForm()
|
||||
|
||||
if form.validate_on_submit():
|
||||
# log the user in? show a guide? no idea.
|
||||
try:
|
||||
jid = await client.register_with_token(
|
||||
username=form.localpart.data,
|
||||
password=form.password.data,
|
||||
token=id_,
|
||||
)
|
||||
except aiohttp.ClientResponseError as exc:
|
||||
if exc.status == 409:
|
||||
form.localpart.errors.append(
|
||||
_l("That user name is already taken")
|
||||
)
|
||||
elif exc.status == 403:
|
||||
form.localpart.errors.append(
|
||||
_l("Registration was declined for unknown reasons")
|
||||
)
|
||||
elif exc.status == 400:
|
||||
form.localpart.errors.append(
|
||||
_l("The user name was not valid")
|
||||
)
|
||||
elif exc.status == 404:
|
||||
return redirect(url_for(".view", id_=id_))
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
http_session[INVITE_SESSION_JID] = jid
|
||||
return redirect(url_for(".success"))
|
||||
|
||||
return await render_template(
|
||||
"invite_register.html",
|
||||
invite=invite,
|
||||
form=form,
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/success", methods=["GET", "POST"])
|
||||
async def success() -> str:
|
||||
return await render_template(
|
||||
"invite_success.html",
|
||||
jid=http_session.get(INVITE_SESSION_JID, ""),
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/-")
|
||||
async def index() -> quart.Response:
|
||||
return redirect(url_for("index"))
|
||||
@@ -114,6 +114,22 @@ class AdminGroupInfo:
|
||||
)
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class PublicInviteInfo:
|
||||
inviter: typing.Optional[str]
|
||||
xmpp_uri: str
|
||||
|
||||
@classmethod
|
||||
def from_api_response(
|
||||
cls,
|
||||
data: typing.Mapping[str, typing.Any],
|
||||
) -> "PublicInviteInfo":
|
||||
return cls(
|
||||
inviter=data.get("inviter") or None,
|
||||
xmpp_uri=data["uri"],
|
||||
)
|
||||
|
||||
|
||||
class HTTPSessionManager:
|
||||
def __init__(self, app_context_attribute: str):
|
||||
self._app_context_attribute = app_context_attribute
|
||||
@@ -258,6 +274,9 @@ class ProsodyClient:
|
||||
def _admin_v1_endpoint(self, subpath: str) -> str:
|
||||
return "{}/admin_api{}".format(self._endpoint_base, subpath)
|
||||
|
||||
def _public_v1_endpoint(self, subpath: str) -> str:
|
||||
return "{}/register_api{}".format(self._endpoint_base, subpath)
|
||||
|
||||
async def _oauth2_bearer_token(self,
|
||||
session: aiohttp.ClientSession,
|
||||
jid: str,
|
||||
@@ -1053,3 +1072,29 @@ class ProsodyClient:
|
||||
return False
|
||||
scopes = http_session[self.SESSION_CACHED_SCOPE].split()
|
||||
return SCOPE_ADMIN in scopes
|
||||
|
||||
async def get_public_invite_by_id(self, id_: str) -> PublicInviteInfo:
|
||||
async with self._plain_session as session:
|
||||
async with session.get(self._public_v1_endpoint(
|
||||
"/invite/{}".format(id_)
|
||||
)) as resp:
|
||||
resp.raise_for_status()
|
||||
return PublicInviteInfo.from_api_response(await resp.json())
|
||||
|
||||
async def register_with_token(
|
||||
self,
|
||||
token: str,
|
||||
username: str,
|
||||
password: str,
|
||||
) -> str:
|
||||
payload = {
|
||||
"username": username,
|
||||
"password": password,
|
||||
"token": token,
|
||||
}
|
||||
async with self._plain_session as session:
|
||||
async with session.post(
|
||||
self._public_v1_endpoint("/register"),
|
||||
json=payload) as resp:
|
||||
resp.raise_for_status()
|
||||
return (await resp.json())["jid"]
|
||||
|
||||
@@ -184,7 +184,7 @@ label.required:after {
|
||||
padding: $w-s4;
|
||||
}
|
||||
|
||||
p.form-desc.weak {
|
||||
p.form-desc.weak, p.field-desc.weak {
|
||||
color: $gray-300;
|
||||
}
|
||||
|
||||
@@ -1116,7 +1116,7 @@ pre.guru-meditation {
|
||||
}
|
||||
}
|
||||
|
||||
p.form-desc.weak {
|
||||
p.form-desc.weak, p.field-desc.weak {
|
||||
color: $gray-700;
|
||||
}
|
||||
}
|
||||
|
||||
185
snikket_web/scss/invite.scss
Normal file
185
snikket_web/scss/invite.scss
Normal file
@@ -0,0 +1,185 @@
|
||||
@import "_theme.scss";
|
||||
|
||||
div.powered-by {
|
||||
text-align: right;
|
||||
line-height: 1.5;
|
||||
|
||||
> img {
|
||||
height: 1.5em;
|
||||
vertical-align: -0.2em;
|
||||
margin-left: 0.2em;
|
||||
}
|
||||
}
|
||||
|
||||
div.modal {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1024;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
> div {
|
||||
padding: $w-l1;
|
||||
margin-left: auto;
|
||||
max-width: 40rem;
|
||||
margin-right: auto;
|
||||
|
||||
> header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
> span {
|
||||
display: inline-block;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
> a.button {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div.install-buttons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
list-style-type: none;
|
||||
margin: $w-l1 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
img.apple {
|
||||
height: $w-l2;
|
||||
margin: $w-s2;
|
||||
}
|
||||
|
||||
img.play {
|
||||
height: $w-l3;
|
||||
}
|
||||
|
||||
.tabbox {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: $w-l1 0;
|
||||
|
||||
> nav.tabs {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
> a {
|
||||
display: inline-block;
|
||||
padding: $w-s2;
|
||||
border-top-left-radius: $w-s4;
|
||||
border-top-right-radius: $w-s4;
|
||||
|
||||
&, &:visited {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
text-decoration-color: $accent-500;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $accent-900;
|
||||
border-color: $accent-800;
|
||||
color: black;
|
||||
}
|
||||
|
||||
&.active {
|
||||
text-decoration: none;
|
||||
background: linear-gradient(0deg, $accent-600, $accent-700);
|
||||
color: $accent-200;
|
||||
|
||||
&:hover, &:focus {
|
||||
background: linear-gradient(0deg, $accent-700, $accent-800);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: $accent-600;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .tab-pane {
|
||||
display: none;
|
||||
padding: 0 $w-0;
|
||||
background: $accent-900;
|
||||
|
||||
&.active {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.qr {
|
||||
margin: $w-l1 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
|
||||
> img {
|
||||
padding: $w-0;
|
||||
background: white;
|
||||
}
|
||||
}
|
||||
|
||||
.float-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#tutorial-scan {
|
||||
width: $w-l5;
|
||||
margin: $w-l1;
|
||||
|
||||
box-shadow:
|
||||
0 1px 3px rgba(0, 0, 0, 0.12),
|
||||
0 1px 2px rgba(0, 0, 0, 0.24);
|
||||
}
|
||||
|
||||
div.form.layout-expanded .lwrap {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
input.localpart-magic {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
flex: 0 0 auto;
|
||||
background: $gray-900;
|
||||
border: none;
|
||||
border-bottom: $w-s4 solid $primary-500;
|
||||
margin-bottom: -$w-s4;
|
||||
padding: 0 $w-s3;
|
||||
}
|
||||
}
|
||||
|
||||
.fullwidth {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#invite {
|
||||
background: url('../img/invite-bg.jpg');
|
||||
background-attachment: fixed;
|
||||
background-size: cover;
|
||||
}
|
||||
46
snikket_web/static/img/apple/en.svg
Normal file
46
snikket_web/static/img/apple/en.svg
Normal file
@@ -0,0 +1,46 @@
|
||||
<svg id="livetype" xmlns="http://www.w3.org/2000/svg" width="119.66407" height="40" viewBox="0 0 119.66407 40">
|
||||
<title>Download_on_the_App_Store_Badge_US-UK_RGB_blk_4SVG_092917</title>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M110.13477,0H9.53468c-.3667,0-.729,0-1.09473.002-.30615.002-.60986.00781-.91895.0127A13.21476,13.21476,0,0,0,5.5171.19141a6.66509,6.66509,0,0,0-1.90088.627A6.43779,6.43779,0,0,0,1.99757,1.99707,6.25844,6.25844,0,0,0,.81935,3.61816a6.60119,6.60119,0,0,0-.625,1.90332,12.993,12.993,0,0,0-.1792,2.002C.00587,7.83008.00489,8.1377,0,8.44434V31.5586c.00489.3105.00587.6113.01515.9219a12.99232,12.99232,0,0,0,.1792,2.0019,6.58756,6.58756,0,0,0,.625,1.9043A6.20778,6.20778,0,0,0,1.99757,38.001a6.27445,6.27445,0,0,0,1.61865,1.1787,6.70082,6.70082,0,0,0,1.90088.6308,13.45514,13.45514,0,0,0,2.0039.1768c.30909.0068.6128.0107.91895.0107C8.80567,40,9.168,40,9.53468,40H110.13477c.3594,0,.7246,0,1.084-.002.3047,0,.6172-.0039.9219-.0107a13.279,13.279,0,0,0,2-.1768,6.80432,6.80432,0,0,0,1.9082-.6308,6.27742,6.27742,0,0,0,1.6172-1.1787,6.39482,6.39482,0,0,0,1.1816-1.6143,6.60413,6.60413,0,0,0,.6191-1.9043,13.50643,13.50643,0,0,0,.1856-2.0019c.0039-.3106.0039-.6114.0039-.9219.0078-.3633.0078-.7246.0078-1.0938V9.53613c0-.36621,0-.72949-.0078-1.09179,0-.30664,0-.61426-.0039-.9209a13.5071,13.5071,0,0,0-.1856-2.002,6.6177,6.6177,0,0,0-.6191-1.90332,6.46619,6.46619,0,0,0-2.7988-2.7998,6.76754,6.76754,0,0,0-1.9082-.627,13.04394,13.04394,0,0,0-2-.17676c-.3047-.00488-.6172-.01074-.9219-.01269-.3594-.002-.7246-.002-1.084-.002Z" style="fill: #a6a6a6"/>
|
||||
<path d="M8.44483,39.125c-.30468,0-.602-.0039-.90429-.0107a12.68714,12.68714,0,0,1-1.86914-.1631,5.88381,5.88381,0,0,1-1.65674-.5479,5.40573,5.40573,0,0,1-1.397-1.0166,5.32082,5.32082,0,0,1-1.02051-1.3965,5.72186,5.72186,0,0,1-.543-1.6572,12.41351,12.41351,0,0,1-.1665-1.875c-.00634-.2109-.01464-.9131-.01464-.9131V8.44434S.88185,7.75293.8877,7.5498a12.37039,12.37039,0,0,1,.16553-1.87207,5.7555,5.7555,0,0,1,.54346-1.6621A5.37349,5.37349,0,0,1,2.61183,2.61768,5.56543,5.56543,0,0,1,4.01417,1.59521a5.82309,5.82309,0,0,1,1.65332-.54394A12.58589,12.58589,0,0,1,7.543.88721L8.44532.875H111.21387l.9131.0127a12.38493,12.38493,0,0,1,1.8584.16259,5.93833,5.93833,0,0,1,1.6709.54785,5.59374,5.59374,0,0,1,2.415,2.41993,5.76267,5.76267,0,0,1,.5352,1.64892,12.995,12.995,0,0,1,.1738,1.88721c.0029.2832.0029.5874.0029.89014.0079.375.0079.73193.0079,1.09179V30.4648c0,.3633,0,.7178-.0079,1.0752,0,.3252,0,.6231-.0039.9297a12.73126,12.73126,0,0,1-.1709,1.8535,5.739,5.739,0,0,1-.54,1.67,5.48029,5.48029,0,0,1-1.0156,1.3857,5.4129,5.4129,0,0,1-1.3994,1.0225,5.86168,5.86168,0,0,1-1.668.5498,12.54218,12.54218,0,0,1-1.8692.1631c-.2929.0068-.5996.0107-.8974.0107l-1.084.002Z"/>
|
||||
</g>
|
||||
<g id="_Group_" data-name="<Group>">
|
||||
<g id="_Group_2" data-name="<Group>">
|
||||
<g id="_Group_3" data-name="<Group>">
|
||||
<path id="_Path_" data-name="<Path>" d="M24.76888,20.30068a4.94881,4.94881,0,0,1,2.35656-4.15206,5.06566,5.06566,0,0,0-3.99116-2.15768c-1.67924-.17626-3.30719,1.00483-4.1629,1.00483-.87227,0-2.18977-.98733-3.6085-.95814a5.31529,5.31529,0,0,0-4.47292,2.72787c-1.934,3.34842-.49141,8.26947,1.3612,10.97608.9269,1.32535,2.01018,2.8058,3.42763,2.7533,1.38706-.05753,1.9051-.88448,3.5794-.88448,1.65876,0,2.14479.88448,3.591.8511,1.48838-.02416,2.42613-1.33124,3.32051-2.66914a10.962,10.962,0,0,0,1.51842-3.09251A4.78205,4.78205,0,0,1,24.76888,20.30068Z" style="fill: #fff"/>
|
||||
<path id="_Path_2" data-name="<Path>" d="M22.03725,12.21089a4.87248,4.87248,0,0,0,1.11452-3.49062,4.95746,4.95746,0,0,0-3.20758,1.65961,4.63634,4.63634,0,0,0-1.14371,3.36139A4.09905,4.09905,0,0,0,22.03725,12.21089Z" style="fill: #fff"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path d="M42.30227,27.13965h-4.7334l-1.13672,3.35645H34.42727l4.4834-12.418h2.083l4.4834,12.418H43.438ZM38.0591,25.59082h3.752l-1.84961-5.44727h-.05176Z" style="fill: #fff"/>
|
||||
<path d="M55.15969,25.96973c0,2.81348-1.50586,4.62109-3.77832,4.62109a3.0693,3.0693,0,0,1-2.84863-1.584h-.043v4.48438h-1.8584V21.44238H48.4302v1.50586h.03418a3.21162,3.21162,0,0,1,2.88281-1.60059C53.645,21.34766,55.15969,23.16406,55.15969,25.96973Zm-1.91016,0c0-1.833-.94727-3.03809-2.39258-3.03809-1.41992,0-2.375,1.23047-2.375,3.03809,0,1.82422.95508,3.0459,2.375,3.0459C52.30227,29.01563,53.24953,27.81934,53.24953,25.96973Z" style="fill: #fff"/>
|
||||
<path d="M65.12453,25.96973c0,2.81348-1.50586,4.62109-3.77832,4.62109a3.0693,3.0693,0,0,1-2.84863-1.584h-.043v4.48438h-1.8584V21.44238H58.395v1.50586h.03418A3.21162,3.21162,0,0,1,61.312,21.34766C63.60988,21.34766,65.12453,23.16406,65.12453,25.96973Zm-1.91016,0c0-1.833-.94727-3.03809-2.39258-3.03809-1.41992,0-2.375,1.23047-2.375,3.03809,0,1.82422.95508,3.0459,2.375,3.0459C62.26711,29.01563,63.21438,27.81934,63.21438,25.96973Z" style="fill: #fff"/>
|
||||
<path d="M71.71047,27.03613c.1377,1.23145,1.334,2.04,2.96875,2.04,1.56641,0,2.69336-.80859,2.69336-1.91895,0-.96387-.67969-1.541-2.28906-1.93652l-1.60937-.3877c-2.28027-.55078-3.33887-1.61719-3.33887-3.34766,0-2.14258,1.86719-3.61426,4.51855-3.61426,2.624,0,4.42285,1.47168,4.4834,3.61426h-1.876c-.1123-1.23926-1.13672-1.9873-2.63379-1.9873s-2.52148.75684-2.52148,1.8584c0,.87793.6543,1.39453,2.25488,1.79l1.36816.33594c2.54785.60254,3.60645,1.626,3.60645,3.44238,0,2.32324-1.85059,3.77832-4.79395,3.77832-2.75391,0-4.61328-1.4209-4.7334-3.667Z" style="fill: #fff"/>
|
||||
<path d="M83.34621,19.2998v2.14258h1.72168v1.47168H83.34621v4.99121c0,.77539.34473,1.13672,1.10156,1.13672a5.80752,5.80752,0,0,0,.61133-.043v1.46289a5.10351,5.10351,0,0,1-1.03223.08594c-1.833,0-2.54785-.68848-2.54785-2.44434V22.91406H80.16262V21.44238H81.479V19.2998Z" style="fill: #fff"/>
|
||||
<path d="M86.065,25.96973c0-2.84863,1.67773-4.63867,4.29395-4.63867,2.625,0,4.29492,1.79,4.29492,4.63867,0,2.85645-1.66113,4.63867-4.29492,4.63867C87.72609,30.6084,86.065,28.82617,86.065,25.96973Zm6.69531,0c0-1.9541-.89551-3.10742-2.40137-3.10742s-2.40039,1.16211-2.40039,3.10742c0,1.96191.89453,3.10645,2.40039,3.10645S92.76027,27.93164,92.76027,25.96973Z" style="fill: #fff"/>
|
||||
<path d="M96.18606,21.44238h1.77246v1.541h.043a2.1594,2.1594,0,0,1,2.17773-1.63574,2.86616,2.86616,0,0,1,.63672.06934v1.73828a2.59794,2.59794,0,0,0-.835-.1123,1.87264,1.87264,0,0,0-1.93652,2.083v5.37012h-1.8584Z" style="fill: #fff"/>
|
||||
<path d="M109.3843,27.83691c-.25,1.64355-1.85059,2.77148-3.89844,2.77148-2.63379,0-4.26855-1.76465-4.26855-4.5957,0-2.83984,1.64355-4.68164,4.19043-4.68164,2.50488,0,4.08008,1.7207,4.08008,4.46582v.63672h-6.39453v.1123a2.358,2.358,0,0,0,2.43555,2.56445,2.04834,2.04834,0,0,0,2.09082-1.27344Zm-6.28223-2.70215h4.52637a2.1773,2.1773,0,0,0-2.2207-2.29785A2.292,2.292,0,0,0,103.10207,25.13477Z" style="fill: #fff"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="_Group_4" data-name="<Group>">
|
||||
<g>
|
||||
<path d="M37.82619,8.731a2.63964,2.63964,0,0,1,2.80762,2.96484c0,1.90625-1.03027,3.002-2.80762,3.002H35.67092V8.731Zm-1.22852,5.123h1.125a1.87588,1.87588,0,0,0,1.96777-2.146,1.881,1.881,0,0,0-1.96777-2.13379h-1.125Z" style="fill: #fff"/>
|
||||
<path d="M41.68068,12.44434a2.13323,2.13323,0,1,1,4.24707,0,2.13358,2.13358,0,1,1-4.24707,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C44.57522,13.99463,45.01369,13.42432,45.01369,12.44434Z" style="fill: #fff"/>
|
||||
<path d="M51.57326,14.69775h-.92187l-.93066-3.31641h-.07031l-.92676,3.31641h-.91309l-1.24121-4.50293h.90137l.80664,3.436h.06641l.92578-3.436h.85254l.92578,3.436h.07031l.80273-3.436h.88867Z" style="fill: #fff"/>
|
||||
<path d="M53.85354,10.19482H54.709v.71533h.06641a1.348,1.348,0,0,1,1.34375-.80225,1.46456,1.46456,0,0,1,1.55859,1.6748v2.915h-.88867V12.00586c0-.72363-.31445-1.0835-.97168-1.0835a1.03294,1.03294,0,0,0-1.0752,1.14111v2.63428h-.88867Z" style="fill: #fff"/>
|
||||
<path d="M59.09377,8.437h.88867v6.26074h-.88867Z" style="fill: #fff"/>
|
||||
<path d="M61.21779,12.44434a2.13346,2.13346,0,1,1,4.24756,0,2.1338,2.1338,0,1,1-4.24756,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C64.11232,13.99463,64.5508,13.42432,64.5508,12.44434Z" style="fill: #fff"/>
|
||||
<path d="M66.4009,13.42432c0-.81055.60352-1.27783,1.6748-1.34424l1.21973-.07031v-.38867c0-.47559-.31445-.74414-.92187-.74414-.49609,0-.83984.18213-.93848.50049h-.86035c.09082-.77344.81836-1.26953,1.83984-1.26953,1.12891,0,1.76563.562,1.76563,1.51318v3.07666h-.85547v-.63281h-.07031a1.515,1.515,0,0,1-1.35254.707A1.36026,1.36026,0,0,1,66.4009,13.42432Zm2.89453-.38477v-.37646l-1.09961.07031c-.62012.0415-.90137.25244-.90137.64941,0,.40527.35156.64111.835.64111A1.0615,1.0615,0,0,0,69.29543,13.03955Z" style="fill: #fff"/>
|
||||
<path d="M71.34816,12.44434c0-1.42285.73145-2.32422,1.86914-2.32422a1.484,1.484,0,0,1,1.38086.79h.06641V8.437h.88867v6.26074h-.85156v-.71143h-.07031a1.56284,1.56284,0,0,1-1.41406.78564C72.0718,14.772,71.34816,13.87061,71.34816,12.44434Zm.918,0c0,.95508.4502,1.52979,1.20313,1.52979.749,0,1.21191-.583,1.21191-1.52588,0-.93848-.46777-1.52979-1.21191-1.52979C72.72121,10.91846,72.26613,11.49707,72.26613,12.44434Z" style="fill: #fff"/>
|
||||
<path d="M79.23,12.44434a2.13323,2.13323,0,1,1,4.24707,0,2.13358,2.13358,0,1,1-4.24707,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C82.12453,13.99463,82.563,13.42432,82.563,12.44434Z" style="fill: #fff"/>
|
||||
<path d="M84.66945,10.19482h.85547v.71533h.06641a1.348,1.348,0,0,1,1.34375-.80225,1.46456,1.46456,0,0,1,1.55859,1.6748v2.915H87.605V12.00586c0-.72363-.31445-1.0835-.97168-1.0835a1.03294,1.03294,0,0,0-1.0752,1.14111v2.63428h-.88867Z" style="fill: #fff"/>
|
||||
<path d="M93.51516,9.07373v1.1416h.97559v.74854h-.97559V13.2793c0,.47168.19434.67822.63672.67822a2.96657,2.96657,0,0,0,.33887-.02051v.74023a2.9155,2.9155,0,0,1-.4834.04541c-.98828,0-1.38184-.34766-1.38184-1.21582v-2.543h-.71484v-.74854h.71484V9.07373Z" style="fill: #fff"/>
|
||||
<path d="M95.70461,8.437h.88086v2.48145h.07031a1.3856,1.3856,0,0,1,1.373-.80664,1.48339,1.48339,0,0,1,1.55078,1.67871v2.90723H98.69v-2.688c0-.71924-.335-1.0835-.96289-1.0835a1.05194,1.05194,0,0,0-1.13379,1.1416v2.62988h-.88867Z" style="fill: #fff"/>
|
||||
<path d="M104.76125,13.48193a1.828,1.828,0,0,1-1.95117,1.30273A2.04531,2.04531,0,0,1,100.73,12.46045a2.07685,2.07685,0,0,1,2.07617-2.35254c1.25293,0,2.00879.856,2.00879,2.27V12.688h-3.17969v.0498a1.1902,1.1902,0,0,0,1.19922,1.29,1.07934,1.07934,0,0,0,1.07129-.5459Zm-3.126-1.45117h2.27441a1.08647,1.08647,0,0,0-1.1084-1.1665A1.15162,1.15162,0,0,0,101.63527,12.03076Z" style="fill: #fff"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 11 KiB |
@@ -27,6 +27,16 @@ licensed under the terms of the Apache 2.0 License -->
|
||||
<g><rect fill="none" height="24" width="24" /><rect fill="none" height="24" width="24" /></g>
|
||||
<g><path d="M10.3,7.7L10.3,7.7c-0.39,0.39-0.39,1.01,0,1.4l1.9,1.9H3c-0.55,0-1,0.45-1,1v0c0,0.55,0.45,1,1,1h9.2l-1.9,1.9 c-0.39,0.39-0.39,1.01,0,1.4l0,0c0.39,0.39,1.01,0.39,1.4,0l3.59-3.59c0.39-0.39,0.39-1.02,0-1.41L11.7,7.7 C11.31,7.31,10.69,7.31,10.3,7.7z M20,19h-7c-0.55,0-1,0.45-1,1v0c0,0.55,0.45,1,1,1h7c1.1,0,2-0.9,2-2V5c0-1.1-0.9-2-2-2h-7 c-0.55,0-1,0.45-1,1v0c0,0.55,0.45,1,1,1h7V19z" /></g>
|
||||
</symbol>
|
||||
<!-- from: action/exit_to_app/materialiconsround/24px.svg -->
|
||||
<symbol id="icon-exit_to_app" viewBox="0 0 24 24">
|
||||
<path d="M0 0h24v24H0V0z" fill="none" />
|
||||
<path d="M10.79 16.29c.39.39 1.02.39 1.41 0l3.59-3.59c.39-.39.39-1.02 0-1.41L12.2 7.7c-.39-.39-1.02-.39-1.41 0-.39.39-.39 1.02 0 1.41L12.67 11H4c-.55 0-1 .45-1 1s.45 1 1 1h8.67l-1.88 1.88c-.39.39-.38 1.03 0 1.41zM19 3H5c-1.11 0-2 .9-2 2v3c0 .55.45 1 1 1s1-.45 1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1H6c-.55 0-1-.45-1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1v3c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z" />
|
||||
</symbol>
|
||||
<!-- from: communication/qr_code/materialiconsround/24px.svg -->
|
||||
<symbol id="icon-qrcode" viewBox="0 0 24 24">
|
||||
<g><rect fill="none" height="24" width="24" /><rect fill="none" height="24" width="24" /></g>
|
||||
<g><g><path d="M5,11h4c1.1,0,2-0.9,2-2V5c0-1.1-0.9-2-2-2H5C3.9,3,3,3.9,3,5v4C3,10.1,3.9,11,5,11z M5,5h4v4H5V5z" /><path d="M5,21h4c1.1,0,2-0.9,2-2v-4c0-1.1-0.9-2-2-2H5c-1.1,0-2,0.9-2,2v4C3,20.1,3.9,21,5,21z M5,15h4v4H5V15z" /><path d="M13,5v4c0,1.1,0.9,2,2,2h4c1.1,0,2-0.9,2-2V5c0-1.1-0.9-2-2-2h-4C13.9,3,13,3.9,13,5z M19,9h-4V5h4V9z" /><path d="M21,20.5v-1c0-0.28-0.22-0.5-0.5-0.5h-1c-0.28,0-0.5,0.22-0.5,0.5v1c0,0.28,0.22,0.5,0.5,0.5h1C20.78,21,21,20.78,21,20.5 z" /><path d="M13,13.5v1c0,0.28,0.22,0.5,0.5,0.5h1c0.28,0,0.5-0.22,0.5-0.5v-1c0-0.28-0.22-0.5-0.5-0.5h-1C13.22,13,13,13.22,13,13.5z" /><path d="M16.5,15h-1c-0.28,0-0.5,0.22-0.5,0.5v1c0,0.28,0.22,0.5,0.5,0.5h1c0.28,0,0.5-0.22,0.5-0.5v-1C17,15.22,16.78,15,16.5,15 z" /><path d="M13,17.5v1c0,0.28,0.22,0.5,0.5,0.5h1c0.28,0,0.5-0.22,0.5-0.5v-1c0-0.28-0.22-0.5-0.5-0.5h-1C13.22,17,13,17.22,13,17.5z" /><path d="M15.5,21h1c0.28,0,0.5-0.22,0.5-0.5v-1c0-0.28-0.22-0.5-0.5-0.5h-1c-0.28,0-0.5,0.22-0.5,0.5v1C15,20.78,15.22,21,15.5,21 z" /><path d="M17.5,19h1c0.28,0,0.5-0.22,0.5-0.5v-1c0-0.28-0.22-0.5-0.5-0.5h-1c-0.28,0-0.5,0.22-0.5,0.5v1C17,18.78,17.22,19,17.5,19 z" /><path d="M18.5,13h-1c-0.28,0-0.5,0.22-0.5,0.5v1c0,0.28,0.22,0.5,0.5,0.5h1c0.28,0,0.5-0.22,0.5-0.5v-1C19,13.22,18.78,13,18.5,13 z" /><path d="M19.5,17h1c0.28,0,0.5-0.22,0.5-0.5v-1c0-0.28-0.22-0.5-0.5-0.5h-1c-0.28,0-0.5,0.22-0.5,0.5v1C19,16.78,19.22,17,19.5,17 z" /></g></g>
|
||||
</symbol>
|
||||
<!-- from: communication/vpn_key/materialiconsround/24px.svg -->
|
||||
<symbol id="icon-passwd" viewBox="0 0 24 24">
|
||||
<path d="M0 0h24v24H0V0z" fill="none" />
|
||||
@@ -92,4 +102,9 @@ licensed under the terms of the Apache 2.0 License -->
|
||||
<path d="M0 0h24v24H0V0z" fill="none" />
|
||||
<path d="M7 10H5V8c0-.55-.45-1-1-1s-1 .45-1 1v2H1c-.55 0-1 .45-1 1s.45 1 1 1h2v2c0 .55.45 1 1 1s1-.45 1-1v-2h2c.55 0 1-.45 1-1s-.45-1-1-1zm11 1c1.66 0 2.99-1.34 2.99-3S19.66 5 18 5c-.32 0-.63.05-.91.14.57.81.9 1.79.9 2.86s-.34 2.04-.9 2.86c.28.09.59.14.91.14zm-5 0c1.66 0 2.99-1.34 2.99-3S14.66 5 13 5s-3 1.34-3 3 1.34 3 3 3zm0 2c-2 0-6 1-6 3v1c0 .55.45 1 1 1h10c.55 0 1-.45 1-1v-1c0-2-4-3-6-3zm6.62.16c.83.73 1.38 1.66 1.38 2.84v1.5c0 .17-.02.34-.05.5h2.55c.28 0 .5-.22.5-.5V16c0-1.54-2.37-2.49-4.38-2.84z" />
|
||||
</symbol>
|
||||
<!-- from: navigation/close/materialiconsround/24px.svg -->
|
||||
<symbol id="icon-close" viewBox="0 0 24 24">
|
||||
<path d="M0 0h24v24H0V0z" fill="none" />
|
||||
<path d="M18.3 5.71c-.39-.39-1.02-.39-1.41 0L12 10.59 7.11 5.7c-.39-.39-1.02-.39-1.41 0-.39.39-.39 1.02 0 1.41L10.59 12 5.7 16.89c-.39.39-.39 1.02 0 1.41.39.39 1.02.39 1.41 0L12 13.41l4.89 4.89c.39.39 1.02.39 1.41 0 .39-.39.39-1.02 0-1.41L13.41 12l4.89-4.89c.38-.38.38-1.02 0-1.4z" />
|
||||
</symbol>
|
||||
</defs></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 12 KiB |
1
snikket_web/static/img/illus-empty.svg
Normal file
1
snikket_web/static/img/illus-empty.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 34 KiB |
BIN
snikket_web/static/img/invite-bg.jpg
Normal file
BIN
snikket_web/static/img/invite-bg.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
BIN
snikket_web/static/img/tutorial-scan.png
Normal file
BIN
snikket_web/static/img/tutorial-scan.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.5 KiB |
55
snikket_web/static/js/invite-magic.js
Normal file
55
snikket_web/static/js/invite-magic.js
Normal file
@@ -0,0 +1,55 @@
|
||||
var open_modal = function(a_el) {
|
||||
var modal_id = "" + a_el.getAttribute("href").split("#")[1];
|
||||
var modal_el = document.getElementById(modal_id);
|
||||
modal_el.setAttribute("aria-modal", "true");
|
||||
modal_el.removeAttribute("aria-hidden");
|
||||
modal_el.style.setProperty("display", "block");
|
||||
};
|
||||
|
||||
var close_modal = function(modal_el) {
|
||||
modal_el.removeAttribute("aria-modal");
|
||||
modal_el.setAttribute("aria-hidden", "true");
|
||||
modal_el.style.setProperty("display", "none");
|
||||
};
|
||||
|
||||
var find_tabbox_el = function(tab_content_el) {
|
||||
var current = tab_content_el;
|
||||
while (current) {
|
||||
if (current.classList.contains("tabbox")) {
|
||||
return current;
|
||||
}
|
||||
current = current.parentNode;
|
||||
};
|
||||
return null;
|
||||
};
|
||||
|
||||
var clear_active_tab = function(tabbox_el) {
|
||||
var nav_el = tabbox_el.firstChild;
|
||||
var child = nav_el.firstChild;
|
||||
while (child) {
|
||||
child.setAttribute("aria-selected", "false");
|
||||
child.classList.remove("active");
|
||||
child = child.nextSibling;
|
||||
}
|
||||
|
||||
var child = nav_el.nextSibling;
|
||||
while (child) {
|
||||
if (child.classList.contains("tab-pane")) {
|
||||
child.classList.remove("active");
|
||||
}
|
||||
child = child.nextSibling;
|
||||
}
|
||||
};
|
||||
|
||||
var select_tab = function(tab_header_el) {
|
||||
var tab_id = "" + tab_header_el.getAttribute("href").split("#")[1];
|
||||
var tab_el = document.getElementById(tab_id);
|
||||
clear_active_tab(find_tabbox_el(tab_el));
|
||||
tab_el.classList.add("active");
|
||||
tab_header_el.classList.add("active");
|
||||
tab_header_el.setAttribute("aria-selected", "true");
|
||||
};
|
||||
|
||||
var apply_qr_code = function(target_el) {
|
||||
new QRCode(target_el, target_el.dataset.qrdata);
|
||||
};
|
||||
17
snikket_web/static/js/qrcode.min.js
vendored
Normal file
17
snikket_web/static/js/qrcode.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
7
snikket_web/templates/_footer.html
Normal file
7
snikket_web/templates/_footer.html
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
10
snikket_web/templates/invite.html
Normal file
10
snikket_web/templates/invite.html
Normal 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 %}
|
||||
12
snikket_web/templates/invite_invalid.html
Normal file
12
snikket_web/templates/invite_invalid.html
Normal 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 %}
|
||||
43
snikket_web/templates/invite_register.html
Normal file
43
snikket_web/templates/invite_register.html
Normal 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 %}
|
||||
20
snikket_web/templates/invite_success.html
Normal file
20
snikket_web/templates/invite_success.html
Normal 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 %}
|
||||
86
snikket_web/templates/invite_view.html
Normal file
86
snikket_web/templates/invite_view.html
Normal 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 %}
|
||||
@@ -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) -%}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2021-01-24 11:05+0100\n"
|
||||
"POT-Creation-Date: 2021-01-25 17:00+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -93,14 +93,42 @@ msgstr ""
|
||||
msgid "Main"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/main.py:36
|
||||
msgid "Address"
|
||||
#: snikket_web/invite.py:86
|
||||
msgid "Username"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/main.py:41
|
||||
#: snikket_web/invite.py:90 snikket_web/main.py:41
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/invite.py:94
|
||||
msgid "Confirm password"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/invite.py:98
|
||||
msgid "The passwords must match"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/invite.py:103
|
||||
msgid "Create account"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/invite.py:123
|
||||
msgid "That user name is already taken"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/invite.py:127
|
||||
msgid "Registration was declined for unknown reasons"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/invite.py:131
|
||||
msgid "The user name was not valid"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/main.py:36
|
||||
msgid "Address"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/main.py:46
|
||||
msgid "Sign in"
|
||||
msgstr ""
|
||||
@@ -159,6 +187,11 @@ msgstr ""
|
||||
msgid "Incorrect password"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/_footer.html:4 snikket_web/templates/login.html:36
|
||||
#, python-format
|
||||
msgid "A <a href=\"%(about_url)s\">Snikket</a> service"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/about.html:9
|
||||
msgid "About Snikket"
|
||||
msgstr ""
|
||||
@@ -589,6 +622,219 @@ msgstr ""
|
||||
msgid "The web portal encountered an internal error."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_invalid.html:4
|
||||
#: snikket_web/templates/invite_view.html:11
|
||||
#, python-format
|
||||
msgid "Invite to %(site_name)s"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_invalid.html:5
|
||||
#: snikket_web/templates/invite_register.html:9
|
||||
#: snikket_web/templates/invite_success.html:10
|
||||
#: snikket_web/templates/invite_view.html:12
|
||||
#, python-format
|
||||
msgid "Powered by <img src=\"%(logo_url)s\" alt=\"Snikket\">"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_invalid.html:7
|
||||
msgid "Invite expired"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_invalid.html:8
|
||||
msgid "Sorry, it looks like this invite code has expired!"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_register.html:4
|
||||
#, python-format
|
||||
msgid "Register on %(site_name)s | Snikket"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_register.html:8
|
||||
#, python-format
|
||||
msgid "Register on %(site_name)s"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_register.html:10
|
||||
#, python-format
|
||||
msgid "%(site_name)s is using Snikket - a secure, privacy-friendly chat app."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_register.html:11
|
||||
msgid "Create an account"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_register.html:12
|
||||
msgid ""
|
||||
"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:"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_register.html:13
|
||||
#: snikket_web/templates/invite_view.html:30
|
||||
msgid "App already installed?"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_register.html:15
|
||||
#: snikket_web/templates/invite_view.html:32
|
||||
msgid "Open the app"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_register.html:17
|
||||
#: snikket_web/templates/invite_view.html:34
|
||||
msgid "This button works only if you have the app installed already!"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_register.html:18
|
||||
msgid "Create an account online"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_register.html:19
|
||||
msgid ""
|
||||
"If you plan to use a legacy XMPP client, you can register an account "
|
||||
"online and enter your credentials into any XMPP-compatible software."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_register.html:26
|
||||
msgid ""
|
||||
"Choose a username, this will become the first part of your new chat "
|
||||
"address."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_register.html:31
|
||||
msgid "Enter a secure password that you do not use anywhere else."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_success.html:4
|
||||
#, python-format
|
||||
msgid "Successfully registered on %(site_name)s | Snikket"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_success.html:9
|
||||
#, python-format
|
||||
msgid "Successfully registered on %(site_name)s"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_success.html:11
|
||||
#, python-format
|
||||
msgid "Congratulations! You successfully registered on %(site_name)s as %(jid)s."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_success.html:14
|
||||
msgid "Copy address"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_success.html:16
|
||||
msgid ""
|
||||
"You can not set up your legacy XMPP client with the above address and the"
|
||||
" password you chose during registration."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_success.html:17
|
||||
msgid "You can now safely close this page."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_view.html:5
|
||||
#, python-format
|
||||
msgid "Invite to %(site_name)s | Snikket"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_view.html:14
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You have been invited to chat with %(inviter_name)s using Snikket, a "
|
||||
"secure, privacy-friendly chat app on %(site_name)s."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_view.html:16
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You have been invited to chat on %(site_name)s using Snikket, a secure, "
|
||||
"privacy-friendly chat app."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_view.html:18
|
||||
msgid "Get started"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_view.html:19
|
||||
msgid "Install the Snikket App on your Android or iOS device."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_view.html:22
|
||||
msgid "Get it on Google Play"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_view.html:23
|
||||
msgid "Download on the App Store"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_view.html:26
|
||||
msgid "Not on mobile?"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_view.html:29
|
||||
msgid ""
|
||||
"After installation the app should automatically open and prompt you to "
|
||||
"create an account. If not, simply click the button below."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_view.html:36
|
||||
msgid "Alternatives"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_view.html:37
|
||||
#, python-format
|
||||
msgid ""
|
||||
"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)s\">register an account manually</a>."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_view.html:43
|
||||
msgid "Scan invite code"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_view.html:46
|
||||
#: snikket_web/templates/invite_view.html:75
|
||||
msgid "Close"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_view.html:49
|
||||
msgid ""
|
||||
"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."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_view.html:54
|
||||
msgid "Using a QR code scanner"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_view.html:56
|
||||
msgid "Using the Snikket app"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_view.html:61
|
||||
msgid ""
|
||||
"Use a <em>QR code</em> scanner on your mobile device to scan the code "
|
||||
"below:"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_view.html:67
|
||||
msgid ""
|
||||
"Install the Snikket app on your mobile device, open it, and tap the "
|
||||
"'Scan' button at the top."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/invite_view.html:68
|
||||
msgid ""
|
||||
"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."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/library.j2:18
|
||||
msgid "Copy link"
|
||||
msgstr ""
|
||||
@@ -609,11 +855,6 @@ msgstr ""
|
||||
msgid "Login failed"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/login.html:36 snikket_web/templates/unauth.html:14
|
||||
#, python-format
|
||||
msgid "A <a href=\"%(about_url)s\">Snikket</a> service"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/user_home.html:3
|
||||
msgid "Welcome!"
|
||||
msgstr ""
|
||||
|
||||
@@ -3,6 +3,8 @@ action/bug_report:bug_report
|
||||
action/done:done
|
||||
action/logout:logout
|
||||
action/login:login
|
||||
action/exit_to_app:exit_to_app
|
||||
communication/qr_code:qrcode
|
||||
communication/vpn_key:passwd
|
||||
content/add_circle_outline:add
|
||||
content/add_link:create_link
|
||||
@@ -16,3 +18,4 @@ navigation/cancel:cancel
|
||||
navigation/more_vert:more
|
||||
social/groups:groups
|
||||
social/group_add:create_group
|
||||
navigation/close:close
|
||||
|
||||
Reference in New Issue
Block a user