diff --git a/website/client/assets/scss/modal.scss b/website/client/assets/scss/modal.scss
index 64d9820818..60c0274d13 100644
--- a/website/client/assets/scss/modal.scss
+++ b/website/client/assets/scss/modal.scss
@@ -16,6 +16,7 @@
}
.modal-dialog {
+ margin: 5.5rem auto 3rem;
width: auto;
.title {
diff --git a/website/client/components/groups/inviteModal.vue b/website/client/components/groups/inviteModal.vue
index 8d8869f00d..166b75dfaa 100644
--- a/website/client/components/groups/inviteModal.vue
+++ b/website/client/components/groups/inviteModal.vue
@@ -1,152 +1,178 @@
-b-modal#invite-modal(:title="$t('inviteFriends')", size='lg')
- .modal-body
- p.alert.alert-info(v-html="$t('inviteAlertInfo')")
- .form-horizontal
- table.table.table-striped
- thead
- tr
- th {{ $t('userId') }}
- tbody
- tr(v-for='user in invitees')
- td
- input.form-control(type='text', v-model='user.uuid')
- tr
- td
- button.btn.btn-primary.pull-right(@click='addUuid()')
- i.glyphicon.glyphicon-plus
- | +
- tr
- td
- .col-6.col-offset-6
- button.btn.btn-primary.btn-block(@click='inviteNewUsers("uuid")') {{sendInviteText}}
- hr
- p.alert.alert-info {{ $t('inviteByEmail') }}
- .form-horizontal
- table.table.table-striped
- thead
- tr
- th {{ $t('name') }}
- th {{ $t('email') }}
- tbody
- tr(v-for='email in emails')
- td
- input.form-control(type='text', v-model='email.name')
- td
- input.form-control(type='email', v-model='email.email')
- tr
- td(colspan=2)
- button.btn.btn-primary.pull-right(@click='addEmail()')
- i.glyphicon.glyphicon-plus
- | +
- tr
- td.form-group(colspan=2)
- label.col-sm-1.control-label {{ $t('byColon') }}
- .col-sm-5
- input.form-control(type='text', v-model='inviter')
- .col-sm-6
- button.btn.btn-primary.btn-block(@click='inviteNewUsers("email")') {{sendInviteText}}
+ b-modal#invite-modal(:title='$t(`inviteTo${groupType}`)', :hide-footer='true')
+ div
+ strong {{ $t('inviteEmailUsername') }}
+ .small {{ $t('inviteEmailUsernameInfo') }}
+ .input-group(v-for='(invite, index) in invites')
+ .d-flex.align-items-center.justify-content-center(v-if='index === invites.length - 1 && invite.text.length === 0')
+ .svg-icon.positive-icon(v-html='icons.positiveIcon')
+ input.form-control(
+ type='text',
+ :placeholder='$t("emailOrUsernameInvite")',
+ v-model='invite.text',
+ v-on:change='checkInviteList',
+ :class='{"input-valid": invite.valid, "is-invalid input-invalid": invite.valid === false}',
+ )
+ .modal-footer.d-flex.justify-content-center
+ a.mr-3(@click='close()') {{ $t('cancel') }}
+ button.btn.btn-primary(@click='sendInvites()', :class='{disabled: cannotSubmit}', :disabled='cannotSubmit') {{ $t('sendInvitations') }}
+
+
+
+
diff --git a/website/client/components/groups/inviteModalOld.vue b/website/client/components/groups/inviteModalOld.vue
new file mode 100644
index 0000000000..8d8869f00d
--- /dev/null
+++ b/website/client/components/groups/inviteModalOld.vue
@@ -0,0 +1,152 @@
+
+b-modal#invite-modal(:title="$t('inviteFriends')", size='lg')
+ .modal-body
+ p.alert.alert-info(v-html="$t('inviteAlertInfo')")
+ .form-horizontal
+ table.table.table-striped
+ thead
+ tr
+ th {{ $t('userId') }}
+ tbody
+ tr(v-for='user in invitees')
+ td
+ input.form-control(type='text', v-model='user.uuid')
+ tr
+ td
+ button.btn.btn-primary.pull-right(@click='addUuid()')
+ i.glyphicon.glyphicon-plus
+ | +
+ tr
+ td
+ .col-6.col-offset-6
+ button.btn.btn-primary.btn-block(@click='inviteNewUsers("uuid")') {{sendInviteText}}
+ hr
+ p.alert.alert-info {{ $t('inviteByEmail') }}
+ .form-horizontal
+ table.table.table-striped
+ thead
+ tr
+ th {{ $t('name') }}
+ th {{ $t('email') }}
+ tbody
+ tr(v-for='email in emails')
+ td
+ input.form-control(type='text', v-model='email.name')
+ td
+ input.form-control(type='email', v-model='email.email')
+ tr
+ td(colspan=2)
+ button.btn.btn-primary.pull-right(@click='addEmail()')
+ i.glyphicon.glyphicon-plus
+ | +
+ tr
+ td.form-group(colspan=2)
+ label.col-sm-1.control-label {{ $t('byColon') }}
+ .col-sm-5
+ input.form-control(type='text', v-model='inviter')
+ .col-sm-6
+ button.btn.btn-primary.btn-block(@click='inviteNewUsers("email")') {{sendInviteText}}
+
+
+
diff --git a/website/client/components/header/index.vue b/website/client/components/header/index.vue
index dffdef7046..af6f3ad020 100644
--- a/website/client/components/header/index.vue
+++ b/website/client/components/header/index.vue
@@ -1,6 +1,6 @@
div
- invite-modal(:group='inviteModalGroup')
+ invite-modal(:group='inviteModalGroup', :groupType='inviteModalGroupType')
create-party-modal
#app-header.row(:class="{'hide-header': $route.name === 'groupPlan'}")
members-modal(:hide-badge="true")
@@ -115,6 +115,7 @@ export default {
expandedMember: null,
currentWidth: 0,
inviteModalGroup: undefined,
+ inviteModalGroupType: undefined,
};
},
computed: {
@@ -178,6 +179,7 @@ export default {
mounted () {
this.$root.$on('inviteModal::inviteToGroup', (group) => {
this.inviteModalGroup = group;
+ this.inviteModalGroupType = group.type === 'guild' ? 'Guild' : 'Party';
this.$root.$emit('bv::show::modal', 'invite-modal');
});
},
diff --git a/website/client/components/settings/verifyUsername.vue b/website/client/components/settings/verifyUsername.vue
index f48d4836ae..b096c5f033 100644
--- a/website/client/components/settings/verifyUsername.vue
+++ b/website/client/components/settings/verifyUsername.vue
@@ -24,9 +24,6 @@
padding-left: 2rem;
padding-right: 2rem;
}
- .modal-dialog {
- transform: translate(0, 50vh) translate(-5%, -48%);
- }
}
diff --git a/website/client/store/actions/user.js b/website/client/store/actions/user.js
index 9ae2ef3f3c..d15bd3e805 100644
--- a/website/client/store/actions/user.js
+++ b/website/client/store/actions/user.js
@@ -151,3 +151,14 @@ export async function togglePrivateMessagesOpt (store) {
store.state.user.data.inbox.optOut = !store.state.user.data.inbox.optOut;
return response;
}
+
+export async function userLookup (store, params) {
+ let response;
+ if (params.uuid) {
+ response = await axios.get(`/api/v4/members/${params.uuid}`);
+ }
+ if (params.username) {
+ response = await axios.get(`/api/v4/members/username/${params.username}`);
+ }
+ return response;
+}
diff --git a/website/common/locales/en/groups.json b/website/common/locales/en/groups.json
index b8ed626485..e38e5543f9 100644
--- a/website/common/locales/en/groups.json
+++ b/website/common/locales/en/groups.json
@@ -185,7 +185,7 @@
"inviteExistUser": "Invite Existing Users",
"byColon": "By:",
"inviteNewUsers": "Invite New Users",
- "sendInvitations": "Send Invitations",
+ "sendInvitations": "Send Invites",
"invitationsSent": "Invitations sent!",
"invitationSent": "Invitation sent!",
"invitedFriend": "Invited a Friend",
@@ -367,6 +367,10 @@
"liked": "Liked",
"joinGuild": "Join Guild",
"inviteToGuild": "Invite to Guild",
+ "inviteToParty": "Invite to Party",
+ "inviteEmailUsername": "Invite via Email or Username",
+ "inviteEmailUsernameInfo": "Invite users via a valid email or username. If an email isn't registered yet, we'll invite them to join.",
+ "emailOrUsernameInvite": "Email address or username",
"messageGuildLeader": "Message Guild Leader",
"donateGems": "Donate Gems",
"updateGuild": "Update Guild",
diff --git a/website/server/controllers/api-v3/groups.js b/website/server/controllers/api-v3/groups.js
index fcf437ce0d..e17362c0f6 100644
--- a/website/server/controllers/api-v3/groups.js
+++ b/website/server/controllers/api-v3/groups.js
@@ -17,9 +17,9 @@ import {
import { removeFromArray } from '../../libs/collectionManipulators';
import { sendTxn as sendTxnEmail } from '../../libs/email';
import {
- _inviteByUUID,
- _inviteByEmail,
- _inviteByUserName,
+ inviteByUUID,
+ inviteByEmail,
+ inviteByUserName,
} from '../../libs/invites';
import common from '../../../common';
import payments from '../../libs/payments/payments';
@@ -1038,13 +1038,13 @@ api.inviteToGroup = {
const results = [];
if (uuids) {
- const uuidInvites = uuids.map((uuid) => _inviteByUUID(uuid, group, user, req, res));
+ const uuidInvites = uuids.map((uuid) => inviteByUUID(uuid, group, user, req, res));
const uuidResults = await Promise.all(uuidInvites);
results.push(...uuidResults);
}
if (emails) {
- const emailInvites = emails.map((invite) => _inviteByEmail(invite, group, user, req, res));
+ const emailInvites = emails.map((invite) => inviteByEmail(invite, group, user, req, res));
user.invitesSent += emails.length;
await user.save();
const emailResults = await Promise.all(emailInvites);
@@ -1052,7 +1052,7 @@ api.inviteToGroup = {
}
if (usernames) {
- const usernameInvites = usernames.map((username) => _inviteByUserName(username, group, user, req, res));
+ const usernameInvites = usernames.map((username) => inviteByUserName(username, group, user, req, res));
const usernameResults = await Promise.all(usernameInvites);
results.push(...usernameResults);
}
diff --git a/website/server/controllers/api-v3/members.js b/website/server/controllers/api-v3/members.js
index 5a9759d48c..edb26b5571 100644
--- a/website/server/controllers/api-v3/members.js
+++ b/website/server/controllers/api-v3/members.js
@@ -119,6 +119,33 @@ api.getMember = {
},
};
+api.getMemberByUsername = {
+ method: 'GET',
+ url: '/members/username/:username',
+ middlewares: [],
+ async handler (req, res) {
+ req.checkParams('username', res.t('invalidReqParams')).notEmpty();
+
+ let validationErrors = req.validationErrors();
+ if (validationErrors) throw validationErrors;
+
+ let username = req.params.username.toLowerCase();
+
+ let member = await User
+ .findOne({'auth.local.lowerCaseUsername': username})
+ .select(memberFields)
+ .exec();
+
+ if (!member || !member.flags.verifiedUsername) throw new NotFound(res.t('userNotFound'));
+
+ // manually call toJSON with minimize: true so empty paths aren't returned
+ let memberToJSON = member.toJSON({minimize: true});
+ User.addComputedStatsToJSONObj(memberToJSON.stats, member);
+
+ res.respond(200, memberToJSON);
+ },
+};
+
/**
* @api {get} /api/v3/members/:memberId/achievements Get member achievements object
* @apiName GetMemberAchievements
diff --git a/website/server/libs/invites/index.js b/website/server/libs/invites/index.js
index ef5a51b1f3..d5225338ef 100644
--- a/website/server/libs/invites/index.js
+++ b/website/server/libs/invites/index.js
@@ -100,7 +100,7 @@ async function inviteUserToParty (userToInvite, group, inviter, res) {
userToInvite.invitations.party = partyInvite;
}
-async function addInvitiationToUser (userToInvite, group, inviter, res) {
+async function addInvitationToUser (userToInvite, group, inviter, res) {
const publicGuild = group.type === 'guild' && group.privacy === 'public';
if (group.type === 'guild') {
@@ -123,7 +123,7 @@ async function addInvitiationToUser (userToInvite, group, inviter, res) {
}
}
-async function _inviteByUUID (uuid, group, inviter, req, res) {
+async function inviteByUUID (uuid, group, inviter, req, res) {
const userToInvite = await User.findById(uuid).exec();
if (!userToInvite) {
@@ -137,10 +137,10 @@ async function _inviteByUUID (uuid, group, inviter, req, res) {
throw new NotAuthorized(res.t(objections[0], { userId: uuid, username: userToInvite.profile.name}));
}
- return await addInvitiationToUser(userToInvite, group, inviter, res);
+ return await addInvitationToUser(userToInvite, group, inviter, res);
}
-async function _inviteByEmail (invite, group, inviter, req, res) {
+async function inviteByEmail (invite, group, inviter, req, res) {
let userReturnInfo;
if (!invite.email) throw new BadRequest(res.t('inviteMissingEmail'));
@@ -154,7 +154,7 @@ async function _inviteByEmail (invite, group, inviter, req, res) {
.exec();
if (userToContact) {
- userReturnInfo = await _inviteByUUID(userToContact._id, group, inviter, req, res);
+ userReturnInfo = await inviteByUUID(userToContact._id, group, inviter, req, res);
} else {
userReturnInfo = invite.email;
@@ -188,7 +188,9 @@ async function _inviteByEmail (invite, group, inviter, req, res) {
return userReturnInfo;
}
-async function _inviteByUserName (username, group, inviter, req, res) {
+async function inviteByUserName (username, group, inviter, req, res) {
+ if (username.indexOf('@') === 0) username = username.slice(1, username.length);
+ username = username.toLowerCase();
const userToInvite = await User.findOne({'auth.local.lowerCaseUsername': username}).exec();
if (!userToInvite) {
@@ -199,11 +201,11 @@ async function _inviteByUserName (username, group, inviter, req, res) {
throw new BadRequest(res.t('cannotInviteSelfToGroup'));
}
- return await addInvitiationToUser(userToInvite, group, inviter, res);
+ return await addInvitationToUser(userToInvite, group, inviter, res);
}
module.exports = {
- _inviteByUUID,
- _inviteByEmail,
- _inviteByUserName,
+ inviteByUUID,
+ inviteByEmail,
+ inviteByUserName,
};