Compare commits
7 Commits
main
...
email-veri
Author | SHA1 | Date | |
---|---|---|---|
1292184c14 | |||
88e6ee82d8 | |||
e2bfd52011 | |||
bfc607e8a8 | |||
2eb4fc0a0a | |||
1ead04c5a1 | |||
1171ac389c |
api
mobile/lib
web/src
@ -41,6 +41,7 @@ def register(username, email, password, how_find_us):
|
||||
{
|
||||
"username": username,
|
||||
"email": email,
|
||||
"emailVerified": False,
|
||||
"password": hashed_password,
|
||||
"createdAt": datetime.datetime.now(),
|
||||
"subscriptions": {
|
||||
@ -54,6 +55,7 @@ def register(username, email, password, how_find_us):
|
||||
},
|
||||
}
|
||||
)
|
||||
send_verification_email(db.users.find_one({"_id": result.inserted_id}))
|
||||
mail.send(
|
||||
{
|
||||
"to": os.environ.get("ADMIN_EMAIL"),
|
||||
@ -63,48 +65,7 @@ def register(username, email, password, how_find_us):
|
||||
),
|
||||
}
|
||||
)
|
||||
mail.send(
|
||||
{
|
||||
"to": email,
|
||||
"subject": "Welcome to {}!".format(os.environ.get("APP_NAME")),
|
||||
"text": """Dear {0},
|
||||
|
||||
Welcome to {3}! We won't send you many emails but we just want to introduce ourselves and to give you some tips to help you get started.
|
||||
|
||||
LOGGING-IN
|
||||
|
||||
To login to your account please visit {1} and click Login. Use your username ({0}) and password to get back into your account.
|
||||
|
||||
INTRODUCTION
|
||||
|
||||
{3} has been designed as a resource for weavers – not only for those working alone as individuals, but also for groups who wish to share ideas, design inspirations and weaving patterns. It is ideal for those looking for a depository to store their individual work, and also for groups such as guilds, teaching groups, or any other collaborative working partnerships.
|
||||
Projects can be created within {3} using the integral WIF-compatible draft editor, or alternatively files can be imported from other design software along with supporting images and other information you may wish to be saved within the project file. Once complete, projects may be stored privately, shared within a closed group, or made public for other {3} users to see. The choice is yours!
|
||||
|
||||
{3} is free to use. For more information please visit our website at {1}.
|
||||
|
||||
GETTING STARTED
|
||||
|
||||
Creating a profile: You can add a picture, links to a personal website, and other social media accounts to tell others more about yourself.
|
||||
|
||||
Creating a group: You have the option to do things alone, or create a group. By clicking on the ‘Create a group’ button, you can name your group, and then invite members via email or directly through {3} if they are existing {3} users.
|
||||
|
||||
Creating a new project: When you are ready to create/store a project on the system, you are invited to give the project a name, and a brief description. You will then be taken to a ‘Welcome to your project’ screen, where if you click on ‘add something’, you have the option of creating a new weaving pattern directly inside {3} or you can simply import a WIF file from your preferred weaving software. Once imported, you can perform further editing within {3}, or you can add supporting picture files and any other additional information you wish to keep (eg weaving notes, yarn details etc).
|
||||
|
||||
Once complete you then have the option of saving the file privately, shared within a group, or made public for other {3} users to see.
|
||||
|
||||
We hope you enjoy using {3} and if you have any comments or feedback please tell us by emailing {2}!
|
||||
|
||||
Best wishes,
|
||||
|
||||
The {3} Team
|
||||
""".format(
|
||||
username,
|
||||
os.environ.get("APP_URL"),
|
||||
os.environ.get("CONTACT_EMAIL"),
|
||||
os.environ.get("APP_NAME"),
|
||||
),
|
||||
}
|
||||
)
|
||||
return {"token": generate_access_token(result.inserted_id)}
|
||||
except Exception as e:
|
||||
print(e)
|
||||
@ -143,33 +104,17 @@ def update_email(user, data):
|
||||
if len(data["email"]) < 4:
|
||||
raise util.errors.BadRequest("New email is too short")
|
||||
db = database.get_db()
|
||||
db.users.update_one({"_id": user["_id"]}, {"$set": {"email": data["email"]}})
|
||||
mail.send(
|
||||
db.users.update_one(
|
||||
{"_id": user["_id"]},
|
||||
{
|
||||
"to": user["email"],
|
||||
"subject": "Your email address has changed on {}".format(
|
||||
os.environ.get("APP_NAME")
|
||||
),
|
||||
"text": "Dear {0},\n\nThis email is to let you know that we recently received a request to change your account email address on {2}. We have now made this change.\n\nThe new email address for your account is {1}.\n\nIf you think this is a mistake then please get in touch with us as soon as possible.".format(
|
||||
user["username"],
|
||||
data["email"],
|
||||
os.environ.get("APP_NAME"),
|
||||
),
|
||||
}
|
||||
)
|
||||
mail.send(
|
||||
{
|
||||
"to": data["email"],
|
||||
"subject": "Your email address has changed on {}".format(
|
||||
os.environ.get("APP_NAME")
|
||||
),
|
||||
"text": "Dear {0},\n\nThis email is to let you know that we recently received a request to change your account email address on {2}. We have now made this change.\n\nThe new email address for your account is {1}.\n\nIf you think this is a mistake then please get in touch with us as soon as possible.".format(
|
||||
user["username"],
|
||||
data["email"],
|
||||
os.environ.get("APP_NAME"),
|
||||
),
|
||||
}
|
||||
"$set": {
|
||||
"email": data["email"],
|
||||
"emailVerified": False,
|
||||
"emailVerifiedAt": None,
|
||||
}
|
||||
},
|
||||
)
|
||||
send_verification_email(db.users.find_one({"_id": user["_id"]}))
|
||||
return {"email": data["email"]}
|
||||
|
||||
|
||||
@ -233,6 +178,83 @@ def update_password(user, data):
|
||||
return {"passwordUpdated": True}
|
||||
|
||||
|
||||
def verify_email(token):
|
||||
if not token:
|
||||
raise util.errors.BadRequest("Invalid request")
|
||||
db = database.get_db()
|
||||
user = None
|
||||
try:
|
||||
id = jwt.decode(token, jwt_secret, algorithms="HS256")["sub"]
|
||||
user = db.users.find_one(
|
||||
{"_id": ObjectId(id), "tokens.emailVerification": token}
|
||||
)
|
||||
if not user:
|
||||
raise Exception
|
||||
except Exception:
|
||||
raise util.errors.BadRequest(
|
||||
"There was a problem verifying your email. Your token may be invalid or out of date"
|
||||
)
|
||||
db.users.update_one(
|
||||
{"_id": user["_id"]},
|
||||
{
|
||||
"$set": {
|
||||
"emailVerified": True,
|
||||
"emailVerifiedAt": datetime.datetime.utcnow(),
|
||||
},
|
||||
"$addToSet": {
|
||||
"emailVerifications": {
|
||||
"email": user["email"],
|
||||
"verifiedAt": datetime.datetime.utcnow(),
|
||||
}
|
||||
},
|
||||
"$unset": {"tokens.emailVerification": ""},
|
||||
},
|
||||
)
|
||||
mail.send(
|
||||
{
|
||||
"to": user["email"],
|
||||
"subject": "Welcome to {}!".format(os.environ.get("APP_NAME")),
|
||||
"text": """Dear {0},
|
||||
|
||||
Welcome to {3}! We won't send you many emails but we just want to introduce ourselves and to give you some tips to help you get started.
|
||||
|
||||
LOGGING-IN
|
||||
|
||||
To login to your account please visit {1} and click Login. Use your username ({0}) and password to get back into your account.
|
||||
|
||||
INTRODUCTION
|
||||
|
||||
{3} has been designed as a resource for weavers – not only for those working alone as individuals, but also for groups who wish to share ideas, design inspirations and weaving patterns. It is ideal for those looking for a depository to store their individual work, and also for groups such as guilds, teaching groups, or any other collaborative working partnerships.
|
||||
Projects can be created within {3} using the integral WIF-compatible draft editor, or alternatively files can be imported from other design software along with supporting images and other information you may wish to be saved within the project file. Once complete, projects may be stored privately, shared within a closed group, or made public for other {3} users to see. The choice is yours!
|
||||
|
||||
{3} is free to use. For more information please visit our website at {1}.
|
||||
|
||||
GETTING STARTED
|
||||
|
||||
Creating a profile: You can add a picture, links to a personal website, and other social media accounts to tell others more about yourself.
|
||||
|
||||
Creating a group: You have the option to do things alone, or create a group. By clicking on the ‘Create a group’ button, you can name your group, and then invite members via email or directly through {3} if they are existing {3} users.
|
||||
|
||||
Creating a new project: When you are ready to create/store a project on the system, you are invited to give the project a name, and a brief description. You will then be taken to a ‘Welcome to your project’ screen, where if you click on ‘add something’, you have the option of creating a new weaving pattern directly inside {3} or you can simply import a WIF file from your preferred weaving software. Once imported, you can perform further editing within {3}, or you can add supporting picture files and any other additional information you wish to keep (eg weaving notes, yarn details etc).
|
||||
|
||||
Once complete you then have the option of saving the file privately, shared within a group, or made public for other {3} users to see.
|
||||
|
||||
We hope you enjoy using {3} and if you have any comments or feedback please tell us by emailing {2}!
|
||||
|
||||
Best wishes,
|
||||
|
||||
The {3} Team
|
||||
""".format(
|
||||
user["username"],
|
||||
os.environ.get("APP_URL"),
|
||||
os.environ.get("CONTACT_EMAIL"),
|
||||
os.environ.get("APP_NAME"),
|
||||
),
|
||||
}
|
||||
)
|
||||
return {"emailVerified": True}
|
||||
|
||||
|
||||
def delete(user, password):
|
||||
if not password or not bcrypt.checkpw(password.encode("utf-8"), user["password"]):
|
||||
raise util.errors.BadRequest("Incorrect password")
|
||||
@ -279,6 +301,45 @@ def get_user_context(token):
|
||||
return None
|
||||
|
||||
|
||||
def send_verification_email(user):
|
||||
db = database.get_db()
|
||||
if user.get("emailVerified"):
|
||||
return {"emailVerified": True}
|
||||
existing_token = user.get("tokens", {}).get("emailVerification")
|
||||
if existing_token:
|
||||
ten_mins_ago = datetime.datetime.utcnow() - datetime.timedelta(minutes=10)
|
||||
decoded = jwt.decode(existing_token, jwt_secret, algorithms="HS256")
|
||||
if datetime.datetime.fromtimestamp(decoded["iat"]) > ten_mins_ago:
|
||||
raise util.errors.BadRequest(
|
||||
"Email verifications can only be sent every 10 minutes. Remember to check your spam folder."
|
||||
)
|
||||
|
||||
payload = {
|
||||
"exp": datetime.datetime.utcnow() + datetime.timedelta(days=30),
|
||||
"iat": datetime.datetime.utcnow(),
|
||||
"sub": str(user["_id"]),
|
||||
}
|
||||
token = jwt.encode(payload, jwt_secret, algorithm="HS256")
|
||||
mail.send(
|
||||
{
|
||||
"force": True,
|
||||
"to_user": user,
|
||||
"subject": "Verify your email address on {}".format(
|
||||
os.environ.get("APP_NAME")
|
||||
),
|
||||
"text": "Dear {0},\n\nThis email address has been used with an account on {2}. Please verify that you own this email address by following the link below:\n\n{1}\n\nIf you did not sign up for an account, please ignore this email or reach out to us.".format(
|
||||
user["username"],
|
||||
"{}/email/verify?token={}".format(os.environ.get("APP_URL"), token),
|
||||
os.environ.get("APP_NAME"),
|
||||
),
|
||||
}
|
||||
)
|
||||
db.users.update_one(
|
||||
{"_id": user["_id"]}, {"$set": {"tokens.emailVerification": token}}
|
||||
)
|
||||
return {"verificationEmailSent": True}
|
||||
|
||||
|
||||
def reset_password(data):
|
||||
if not data or "email" not in data:
|
||||
raise util.errors.BadRequest("Invalid request")
|
||||
|
@ -12,6 +12,7 @@ def me(user):
|
||||
"username": user["username"],
|
||||
"bio": user.get("bio"),
|
||||
"email": user.get("email"),
|
||||
"emailVerified": user.get("emailVerified"),
|
||||
"avatar": user.get("avatar"),
|
||||
"avatarUrl": user.get("avatar")
|
||||
and uploads.get_presigned_url(
|
||||
|
15
api/app.py
15
api/app.py
@ -156,6 +156,21 @@ def email_address(args):
|
||||
return util.jsonify(accounts.update_email(util.get_user(), args))
|
||||
|
||||
|
||||
@app.route("/accounts/email/verificationRequests", methods=["POST"])
|
||||
def email_verification_request():
|
||||
return util.jsonify(accounts.send_verification_email(util.get_user()))
|
||||
|
||||
|
||||
@app.route("/accounts/email/verified", methods=["PUT"])
|
||||
@use_args(
|
||||
{
|
||||
"token": fields.Str(required=True),
|
||||
}
|
||||
)
|
||||
def email_verify(args):
|
||||
return util.jsonify(accounts.verify_email(args.get("token")))
|
||||
|
||||
|
||||
@limiter.limit("5 per minute", key_func=util.limit_by_user, methods=["POST"])
|
||||
@app.route("/accounts/password", methods=["PUT"])
|
||||
@use_args(
|
||||
|
@ -36,5 +36,8 @@ def handle_send(data):
|
||||
|
||||
|
||||
def send(data):
|
||||
if data.get("to_user"):
|
||||
if not data["to_user"].get("emailVerified") and not data.get("force"):
|
||||
return
|
||||
thr = Thread(target=handle_send, args=[data])
|
||||
thr.start()
|
||||
|
@ -10,8 +10,8 @@ import 'model.dart';
|
||||
class Api {
|
||||
|
||||
String? _token;
|
||||
final String apiBase = 'https://api.treadl.com';
|
||||
//final String apiBase = 'http://192.168.5.134:2001';
|
||||
//final String apiBase = 'https://api.treadl.com';
|
||||
final String apiBase = 'http://localhost:2001';
|
||||
|
||||
Api({token: null}) {
|
||||
if (token != null) _token = token;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'api.dart';
|
||||
import 'util.dart';
|
||||
import 'lib.dart';
|
||||
|
@ -366,6 +366,7 @@ class LoginNeeded extends StatelessWidget {
|
||||
Text('You need to login to see this', style: TextStyle(fontSize: 20), textAlign: TextAlign.center),
|
||||
Image(image: AssetImage('assets/login.png'), width: 300),
|
||||
text != null ? Text(text!, textAlign: TextAlign.center) : SizedBox(height: 10),
|
||||
SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
context.push('/welcome');
|
||||
|
@ -15,6 +15,7 @@ import 'home.dart';
|
||||
import 'project.dart';
|
||||
import 'object.dart';
|
||||
import 'settings.dart';
|
||||
import 'verifyEmail.dart';
|
||||
import 'group.dart';
|
||||
import 'user.dart';
|
||||
|
||||
@ -41,6 +42,7 @@ final router = GoRouter(
|
||||
GoRoute(path: '/home', builder: (context, state) => HomeScreen()),
|
||||
GoRoute(path: '/settings', builder: (context, state) => SettingsScreen()),
|
||||
GoRoute(path: '/groups/:id', builder: (context, state) => GroupScreen(state.pathParameters['id']!)),
|
||||
GoRoute(path: '/email/verify', builder: (context, state) => VerifyEmailScreen(token: state.uri.queryParameters['token'])),
|
||||
GoRoute(path: '/:username', builder: (context, state) => UserScreen(state.pathParameters['username']!)),
|
||||
GoRoute(path: '/:username/:path', builder: (context, state) => ProjectScreen(state.pathParameters['username']!, state.pathParameters['path']!)),
|
||||
GoRoute(path: '/:username/:path/:id', builder: (context, state) => ObjectScreen(state.pathParameters['username']!, state.pathParameters['path']!, state.pathParameters['id']!)),
|
||||
|
@ -7,11 +7,14 @@ class User {
|
||||
final String username;
|
||||
String? avatar;
|
||||
String? avatarUrl;
|
||||
bool? emailVerified;
|
||||
|
||||
User(this.id, this.username, {this.avatar, this.avatarUrl}) {}
|
||||
|
||||
static User loadJSON(Map<String,dynamic> input) {
|
||||
return User(input['_id'], input['username'], avatar: input['avatar'], avatarUrl: input['avatarUrl']);
|
||||
User newUser = User(input['_id'], input['username'], avatar: input['avatar'], avatarUrl: input['avatarUrl']);
|
||||
newUser.emailVerified = input['emailVerified'];
|
||||
return newUser;
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,6 +25,13 @@ class AppModel extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void verifyEmail() {
|
||||
if (user != null) {
|
||||
user!.emailVerified = true;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
String? apiToken;
|
||||
Future<void> setToken(String? newToken) async {
|
||||
apiToken = newToken;
|
||||
@ -38,28 +48,4 @@ class AppModel extends ChangeNotifier {
|
||||
prefs.remove('apiToken');
|
||||
}
|
||||
}
|
||||
/*
|
||||
/// Internal, private state of the cart.
|
||||
final List<Item> _items = [];
|
||||
|
||||
/// An unmodifiable view of the items in the cart.
|
||||
UnmodifiableListView<Item> get items => UnmodifiableListView(_items);
|
||||
|
||||
/// The current total price of all items (assuming all items cost $42).
|
||||
int get totalPrice => _items.length * 42;
|
||||
|
||||
/// Adds [item] to cart. This and [removeAll] are the only ways to modify the
|
||||
/// cart from the outside.
|
||||
void add(Item item) {
|
||||
_items.add(item);
|
||||
// This call tells the widgets that are listening to this model to rebuild.
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Removes all items from the cart.
|
||||
void removeAll() {
|
||||
_items.clear();
|
||||
// This call tells the widgets that are listening to this model to rebuild.
|
||||
notifyListeners();
|
||||
}*/
|
||||
}
|
||||
|
@ -139,6 +139,13 @@ class _ProjectsTabState extends State<ProjectsTab> {
|
||||
appBar: AppBar(
|
||||
title: Text('My Projects'),
|
||||
actions: <Widget>[
|
||||
if (user != null && user.emailVerified != true) IconButton(
|
||||
onPressed: () {
|
||||
context.push('/email/verify');
|
||||
},
|
||||
icon: Icon(Icons.warning),
|
||||
color: Colors.red,
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.info_outline),
|
||||
onPressed: () {
|
||||
|
107
mobile/lib/verifyEmail.dart
Normal file
107
mobile/lib/verifyEmail.dart
Normal file
@ -0,0 +1,107 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'api.dart';
|
||||
import 'model.dart';
|
||||
|
||||
class _VerifyEmailScreenState extends State<VerifyEmailScreen> {
|
||||
final String? token;
|
||||
bool loading = false;
|
||||
String? error;
|
||||
String? success;
|
||||
Api api = Api();
|
||||
_VerifyEmailScreenState({required this.token});
|
||||
|
||||
@override
|
||||
initState() {
|
||||
super.initState();
|
||||
_verify(context);
|
||||
}
|
||||
|
||||
void _verify(BuildContext context) async {
|
||||
if (token == null) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
loading = true;
|
||||
error = null;
|
||||
success = null;
|
||||
});
|
||||
AppModel model = Provider.of<AppModel>(context, listen: false);
|
||||
var data = await api.request('PUT', '/accounts/email/verified', {'token': token});
|
||||
if (data['success'] == true) {
|
||||
model.verifyEmail();
|
||||
context.go('/');
|
||||
} else {
|
||||
setState(() {
|
||||
loading = false;
|
||||
error = data['message'];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _resend(BuildContext context) async {
|
||||
setState(() {
|
||||
loading = true;
|
||||
error = null;
|
||||
success = null;
|
||||
});
|
||||
var data = await api.request('POST', '/accounts/email/verificationRequests', null);
|
||||
if (data['success'] == true) {
|
||||
setState(() {
|
||||
success = 'Verification email sent. Remember to check your spam folder.';
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
error = data['message'];
|
||||
loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
AppModel model = Provider.of<AppModel>(context);
|
||||
User? user = model.user;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Email Verification'),
|
||||
),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.all(8),
|
||||
children: <Widget>[
|
||||
Text('Please verify your email address', style: Theme.of(context).textTheme.titleLarge),
|
||||
SizedBox(height: 20),
|
||||
if (loading) Container(
|
||||
alignment: Alignment.center,
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
Text('We have sent you an email with a verification link. Please tap the link in the email to verify your email address.', style: Theme.of(context).textTheme.bodyMedium),
|
||||
SizedBox(height: 20),
|
||||
if (success != null) Text(success!, style: TextStyle(color: Colors.green)),
|
||||
if (error != null) Text(error!, style: TextStyle(color: Colors.red)),
|
||||
SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
onPressed: () => _resend(context),
|
||||
child: Text('Resend Verification Email'),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
TextButton(
|
||||
onPressed: () => context.go('/'),
|
||||
child: Text('Cancel'),
|
||||
),
|
||||
]
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class VerifyEmailScreen extends StatefulWidget {
|
||||
final String? token;
|
||||
@override
|
||||
VerifyEmailScreen({required this.token});
|
||||
_VerifyEmailScreenState createState() => _VerifyEmailScreenState(token: token);
|
||||
}
|
@ -9,5 +9,11 @@ export const accounts = {
|
||||
},
|
||||
delete (password, success, fail) {
|
||||
api.authenticatedRequest('DELETE', '/accounts', { password }, success, fail)
|
||||
},
|
||||
requestEmailVerification (success, fail) {
|
||||
api.authenticatedRequest('POST', '/accounts/email/verificationRequests', {}, success, fail)
|
||||
},
|
||||
verifyEmail (token, success, fail) {
|
||||
api.authenticatedRequest('PUT', '/accounts/email/verified', { token }, success, fail)
|
||||
}
|
||||
}
|
||||
|
53
web/src/components/VerifyEmail.jsx
Normal file
53
web/src/components/VerifyEmail.jsx
Normal file
@ -0,0 +1,53 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import {
|
||||
Card, Loader, Message
|
||||
} from 'semantic-ui-react'
|
||||
import { toast } from 'react-toastify'
|
||||
import api from '../api'
|
||||
|
||||
export default function VerifyEmail () {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [success, setSuccess] = useState(false)
|
||||
const [error, setError] = useState()
|
||||
|
||||
useEffect(() => {
|
||||
verifyEmail()
|
||||
}, [])
|
||||
|
||||
const verifyEmail = () => {
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
const token = params.get('token')
|
||||
setLoading(true)
|
||||
api.accounts.verifyEmail(token, () => {
|
||||
setLoading(false)
|
||||
toast.info('Email verified successfully.')
|
||||
setSuccess(true)
|
||||
}, (err) => {
|
||||
setLoading(false)
|
||||
toast.error(err.message)
|
||||
setError(err.message)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Card.Group centered style={{ marginTop: 50 }}>
|
||||
<Card raised color='yellow'>
|
||||
<Card.Content>
|
||||
<Card.Header>Verifying your email...</Card.Header>
|
||||
{loading &&
|
||||
<div style={{ textAlign: 'center', marginTop: 20 }}>
|
||||
<Loader active inline='centered' />
|
||||
</div>}
|
||||
{success &&
|
||||
<Message positive>
|
||||
Email verified successfully. You can now close this page.
|
||||
</Message>}
|
||||
{error &&
|
||||
<Message negative>
|
||||
{error}
|
||||
</Message>}
|
||||
</Card.Content>
|
||||
</Card>
|
||||
</Card.Group>
|
||||
)
|
||||
}
|
@ -3,6 +3,7 @@ import { Link, useNavigate, useLocation } from 'react-router'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import styled from 'styled-components'
|
||||
import { Modal, Menu, Button, Container, Dropdown, Popup, Icon, List } from 'semantic-ui-react'
|
||||
import { toast } from 'react-toastify'
|
||||
import api from '../../api'
|
||||
import actions from '../../actions'
|
||||
import utils from '../../utils/utils.js'
|
||||
@ -15,7 +16,6 @@ import SearchBar from './SearchBar'
|
||||
const GROUPS_ENABLED = import.meta.env.VITE_GROUPS_ENABLED === 'true'
|
||||
|
||||
const StyledNavBar = styled.div`
|
||||
height:60px;
|
||||
background: linen;
|
||||
padding: 5px 0px;
|
||||
.logo{
|
||||
@ -51,6 +51,16 @@ export default function NavBar () {
|
||||
return { isAuthenticated, user, groups, helpModalOpen }
|
||||
})
|
||||
|
||||
const sendVerificationEmail = () => api.accounts.requestEmailVerification(() => {
|
||||
toast.success('Verification email sent')
|
||||
}, err => {
|
||||
toast.error(err.message)
|
||||
})
|
||||
|
||||
const checkVerification = () => {
|
||||
api.users.me(user => dispatch(actions.users.receive(user)))
|
||||
}
|
||||
|
||||
const logout = () => api.auth.logout(() => {
|
||||
dispatch(actions.auth.logout())
|
||||
navigate('/')
|
||||
@ -156,6 +166,16 @@ export default function NavBar () {
|
||||
</Menu>
|
||||
</div>
|
||||
</Container>
|
||||
{user && !user.emailVerified &&
|
||||
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', background: 'mistyrose', padding: '5px 5px', verticalAlign: 'middle', color: 'black' }}>
|
||||
<div>
|
||||
<Icon name='warning sign' />
|
||||
</div>
|
||||
<span style={{ marginLeft: 5 }}>Please verify your email address to access all features</span>
|
||||
<Button style={{ marginLeft: 5 }} basic size='mini' onClick={sendVerificationEmail}>Resend verification email</Button>
|
||||
<Button size='mini' basic as={Link} to='/settings/account'>Edit email</Button>
|
||||
<Button size='mini' basic onClick={checkVerification} icon><Icon name='refresh' /></Button>
|
||||
</div>}
|
||||
<AboutModal open={helpModalOpen} onClose={e => dispatch(actions.app.openHelpModal(false))} />
|
||||
</StyledNavBar>
|
||||
)
|
||||
|
@ -24,7 +24,7 @@ function AccountSettings () {
|
||||
const updateEmail = () => {
|
||||
api.accounts.updateEmail(newEmail, data => {
|
||||
setNewEmail('')
|
||||
dispatch(actions.users.receive(Object.assign({}, user, { email: data.email })))
|
||||
dispatch(actions.users.receive(Object.assign({}, user, { email: data.email, emailVerified: data.emailVerified })))
|
||||
}, err => toast.error(err.message))
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@ import Report from './components/main/Report'
|
||||
|
||||
import ForgottenPassword from './components/ForgottenPassword'
|
||||
import ResetPassword from './components/ResetPassword'
|
||||
import VerifyEmail from './components/VerifyEmail'
|
||||
|
||||
import Settings from './components/main/settings/Settings'
|
||||
import SettingsIdentity from './components/main/settings/Identity'
|
||||
@ -81,6 +82,7 @@ const router = createBrowserRouter([
|
||||
{ path: 'report', element: <Report /> },
|
||||
{ path: 'password/forgotten', element: <ForgottenPassword /> },
|
||||
{ path: 'password/reset', element: <ResetPassword /> },
|
||||
{ path: 'email/verify', element: <VerifyEmail /> },
|
||||
{
|
||||
path: 'settings',
|
||||
element: <Settings />,
|
||||
|
Loading…
Reference in New Issue
Block a user