Unverified Commit 06730814 authored by Diego Mello's avatar Diego Mello Committed by GitHub
Browse files

[NEW] Invite links (#1534)

parent ba27c580
......@@ -54,3 +54,11 @@ export const TOGGLE_CRASH_REPORT = 'TOGGLE_CRASH_REPORT';
export const SET_CUSTOM_EMOJIS = 'SET_CUSTOM_EMOJIS';
export const SET_ACTIVE_USERS = 'SET_ACTIVE_USERS';
export const USERS_TYPING = createRequestTypes('USERS_TYPING', ['ADD', 'REMOVE', 'CLEAR']);
export const INVITE_LINKS = createRequestTypes('INVITE_LINKS', [
'SET_TOKEN',
'SET_PARAMS',
'SET_INVITE',
'CREATE',
'CLEAR',
...defaultTypes
]);
import * as types from './actionsTypes';
export function inviteLinksSetToken(token) {
return {
type: types.INVITE_LINKS.SET_TOKEN,
token
};
}
export function inviteLinksRequest(token) {
return {
type: types.INVITE_LINKS.REQUEST,
token
};
}
export function inviteLinksSuccess() {
return {
type: types.INVITE_LINKS.SUCCESS
};
}
export function inviteLinksFailure() {
return {
type: types.INVITE_LINKS.FAILURE
};
}
export function inviteLinksClear() {
return {
type: types.INVITE_LINKS.CLEAR
};
}
export function inviteLinksCreate(rid) {
return {
type: types.INVITE_LINKS.CREATE,
rid
};
}
export function inviteLinksSetParams(params) {
return {
type: types.INVITE_LINKS.SET_PARAMS,
params
};
}
export function inviteLinksSetInvite(invite) {
return {
type: types.INVITE_LINKS.SET_INVITE,
invite
};
}
......@@ -59,6 +59,9 @@ export default {
Message_TimeFormat: {
type: 'valueAsString'
},
Message_TimeAndDateFormat: {
type: 'valueAsString'
},
Site_Name: {
type: 'valueAsString'
},
......
......@@ -80,7 +80,7 @@ export default {
Activity: 'Aktivität',
Add_Reaction: 'Reaktion hinzufügen',
Add_Server: 'Server hinzufügen',
Add_user: 'Nutzer hinzufügen',
Add_users: 'Nutzer hinzufügen',
Admin_Panel: 'Admin Panel',
Alert: 'Warnen',
alert: 'warnen',
......
......@@ -81,7 +81,7 @@ export default {
Activity: 'Activity',
Add_Reaction: 'Add Reaction',
Add_Server: 'Add Server',
Add_user: 'Add user',
Add_users: 'Add users',
Admin_Panel: 'Admin Panel',
Alert: 'Alert',
alert: 'alert',
......@@ -121,6 +121,7 @@ export default {
Cancel: 'Cancel',
changing_avatar: 'changing avatar',
creating_channel: 'creating channel',
creating_invite: 'creating invite',
Channel_Name: 'Channel Name',
Channels: 'Channels',
Chats: 'Chats',
......@@ -172,6 +173,7 @@ export default {
edit: 'edit',
edited: 'edited',
Edit: 'Edit',
Edit_Invite: 'Edit Invite',
Email_or_password_field_is_empty: 'Email or password field is empty',
Email: 'Email',
EMAIL: 'EMAIL',
......@@ -182,6 +184,7 @@ export default {
Everyone_can_access_this_channel: 'Everyone can access this channel',
erasing_room: 'erasing room',
Error_uploading: 'Error uploading',
Expiration_Days: 'Expiration (Days)',
Favorite: 'Favorite',
Favorites: 'Favorites',
Files: 'Files',
......@@ -195,6 +198,7 @@ export default {
Forgot_password: 'Forgot password',
Forgot_Password: 'Forgot Password',
Full_table: 'Click to see full table',
Generate_New_Link: 'Generate New Link',
Group_by_favorites: 'Group favorites',
Group_by_type: 'Group by type',
Hide: 'Hide',
......@@ -208,7 +212,10 @@ export default {
is_a_valid_RocketChat_instance: 'is a valid Rocket.Chat instance',
is_not_a_valid_RocketChat_instance: 'is not a valid Rocket.Chat instance',
is_typing: 'is typing',
Invalid_or_expired_invite_token: 'Invalid or expired invite token',
Invalid_server_version: 'The server you\'re trying to connect is using a version that\'s not supported by the app anymore: {{currentVersion}}.\n\nWe require version {{minVersion}}',
Invite_Link: 'Invite Link',
Invite_users: 'Invite users',
Join_the_community: 'Join the community',
Join: 'Join',
Just_invited_people_can_access_this_channel: 'Just invited people can access this channel',
......@@ -225,6 +232,7 @@ export default {
Login_error: 'Your credentials were rejected! Please try again.',
Login_with: 'Login with',
Logout: 'Logout',
Max_number_of_uses: 'Max number of uses',
members: 'members',
Members: 'Members',
Mentioned_Messages: 'Mentioned Messages',
......@@ -247,11 +255,13 @@ export default {
N_users: '{{n}} users',
name: 'name',
Name: 'Name',
Never: 'Never',
New_Message: 'New Message',
New_Password: 'New Password',
New_Server: 'New Server',
Next: 'Next',
No_files: 'No files',
No_limit: 'No limit',
No_mentioned_messages: 'No mentioned messages',
No_pinned_messages: 'No pinned messages',
No_results_found: 'No results found',
......@@ -362,6 +372,7 @@ export default {
Settings: 'Settings',
Settings_succesfully_changed: 'Settings succesfully changed!',
Share: 'Share',
Share_Link: 'Share Link',
Share_this_app: 'Share this app',
Show_Unread_Counter: 'Show Unread Counter',
Show_Unread_Counter_Info: 'Unread counter is displayed as a badge on the right of the channel, in the list',
......@@ -449,6 +460,10 @@ export default {
You: 'You',
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'You need to access at least one Rocket.Chat server to share something.',
Your_certificate: 'Your Certificate',
Your_invite_link_will_expire_after__usesLeft__uses: 'Your invite link will expire after {{usesLeft}} uses.',
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'Your invite link will expire on {{date}} or after {{usesLeft}} uses.',
Your_invite_link_will_expire_on__date__: 'Your invite link will expire on {{date}}.',
Your_invite_link_will_never_expire: 'Your invite link will never expire.',
Version_no: 'Version: {{version}}',
You_will_not_be_able_to_recover_this_message: 'You will not be able to recover this message!',
Change_Language: 'Change Language',
......
......@@ -80,7 +80,7 @@ export default {
Activity: 'Activité',
Add_Reaction: 'Ajouter une réaction',
Add_Server: 'Ajouter un serveur',
Add_user: 'Ajouter un utilisateur',
Add_users: 'Ajouter des utilisateurs',
Alert: 'Alerte',
alert: 'alerte',
alerts: 'alertes',
......
......@@ -88,7 +88,7 @@ export default {
Activity: 'Atividade',
Add_Reaction: 'Reagir',
Add_Server: 'Adicionar servidor',
Add_user: 'Adicionar usuário',
Add_users: 'Adicionar usuário',
Alert: 'Alerta',
alert: 'alerta',
alerts: 'alertas',
......@@ -123,6 +123,7 @@ export default {
Cancel: 'Cancelar',
changing_avatar: 'trocando avatar',
creating_channel: 'criando canal',
creating_invite: 'criando convite',
Channel_Name: 'Nome do Canal',
Channels: 'Canais',
Chats: 'Conversas',
......@@ -169,6 +170,7 @@ export default {
edited: 'editado',
erasing_room: 'apagando sala',
Edit: 'Editar',
Edit_Invite: 'Editar convite',
Email_or_password_field_is_empty: 'Email ou senha estão vazios',
Email: 'Email',
email: 'e-mail',
......@@ -176,6 +178,7 @@ export default {
Enable_notifications: 'Habilitar notificações',
Everyone_can_access_this_channel: 'Todos podem acessar este canal',
Error_uploading: 'Erro subindo',
Expiration_Days: 'Expira em (dias)',
Favorites: 'Favoritos',
Files: 'Arquivos',
File_description: 'Descrição do arquivo',
......@@ -188,6 +191,7 @@ export default {
Forgot_password: 'Esqueci minha senha',
Forgot_Password: 'Esqueci minha senha',
Full_table: 'Clique para ver a tabela completa',
Generate_New_Link: 'Gerar novo convite',
Group_by_favorites: 'Agrupar favoritos',
Group_by_type: 'Agrupar por tipo',
Has_joined_the_channel: 'Entrou no canal',
......@@ -196,7 +200,10 @@ export default {
Invisible: 'Invisível',
Invite: 'Convidar',
is_typing: 'está digitando',
Invalid_or_expired_invite_token: 'Token de convite inválido ou vencido',
Invalid_server_version: 'O servidor que você está conectando não é suportado mais por esta versão do aplicativo: {{currentVersion}}.\n\nEsta versão do aplicativo requer a versão {{minVersion}} do servidor para funcionar corretamente.',
Invite_Link: 'Link de Convite',
Invite_users: 'Convidar usuários',
Join_the_community: 'Junte-se à comunidade',
Join: 'Entrar',
Just_invited_people_can_access_this_channel: 'Apenas as pessoas convidadas podem acessar este canal',
......@@ -212,6 +219,7 @@ export default {
Login_error: 'Suas credenciais foram rejeitadas. Tente novamente por favor!',
Login_with: 'Login with',
Logout: 'Sair',
Max_number_of_uses: 'Número máximo de usos',
Members: 'Membros',
Mentioned_Messages: 'Mensagens mencionadas',
mentioned: 'mencionado',
......@@ -231,11 +239,13 @@ export default {
N_users: '{{n}} usuários',
name: 'nome',
Name: 'Nome',
Never: 'Nunca',
New_in_RocketChat_question_mark: 'Novo no Rocket.Chat?',
New_Message: 'Nova Mensagem',
New_Password: 'Nova Senha',
Next: 'Próximo',
No_files: 'Não há arquivos',
No_limit: 'Sem limite',
No_mentioned_messages: 'Não há menções',
No_pinned_messages: 'Não há mensagens fixadas',
No_results_found: 'Nenhum resultado encontrado',
......@@ -328,6 +338,7 @@ export default {
Settings: 'Configurações',
Settings_succesfully_changed: 'Configurações salvas com sucesso!',
Share: 'Compartilhar',
Share_Link: 'Share Link',
Sign_in_your_server: 'Entrar no seu servidor',
Sign_Up: 'Registrar',
Some_field_is_invalid_or_empty: 'Algum campo está inválido ou vazio',
......@@ -401,7 +412,10 @@ export default {
you_were_mentioned: 'você foi mencionado',
you: 'você',
You: 'Você',
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'Você precisa acessar ao menos um servidor Rocket.Chat para compartilhar.',
Your_invite_link_will_expire_after__usesLeft__uses: 'Seu link de convite irá vencer depois de {{usesLeft}} usos.',
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'Seu link de convite irá vencer em {{date}} ou depois de {{usesLeft}} usos.',
Your_invite_link_will_expire_on__date__: 'Seu link de convite irá vencer em {{date}}.',
Your_invite_link_will_never_expire: 'Seu link de convite nunca irá vencer.',
You_will_not_be_able_to_recover_this_message: 'Você não será capaz de recuperar essa mensagem!',
Write_External_Permission_Message: 'Rocket Chat precisa de acesso à sua galeria para salvar imagens',
Write_External_Permission: 'Acesso à Galeria',
......
......@@ -80,7 +80,7 @@ export default {
Activity: 'Actividade',
Add_Reaction: 'Adicionar Reacção',
Add_Server: 'Adicionar Servidor',
Add_user: 'Adicionar utilizador',
Add_users: 'Adicionar utilizadores',
Alert: 'Alerta',
alert: 'alerta',
alerts: 'alertas',
......
......@@ -80,7 +80,7 @@ export default {
Activity: 'Активность',
Add_Reaction: 'Добавить реакцию',
Add_Server: 'Добавить сервер',
Add_user: 'Добавить пользователя',
Add_users: 'Добавить пользователей',
Admin_Panel: 'Панель админа',
Alert: 'Оповещение',
alert: 'оповещение',
......
......@@ -80,7 +80,7 @@ export default {
Activity: '按活动排序',
Add_Reaction: '增加回复',
Add_Server: '添加服务器',
Add_user: '添加用户',
Add_users: '添加用户',
Alert: '警告',
alert: '警告',
alerts: '警告',
......
......@@ -49,7 +49,7 @@ if (isIOS) {
const parseDeepLinking = (url) => {
if (url) {
url = url.replace(/rocketchat:\/\/|https:\/\/go.rocket.chat\//, '');
const regex = /^(room|auth)\?/;
const regex = /^(room|auth|invite)\?/;
if (url.match(regex)) {
url = url.replace(regex, '').trim();
if (url) {
......@@ -146,6 +146,12 @@ const ChatsStack = createStackNavigator({
SelectedUsersView: {
getScreen: () => require('./views/SelectedUsersView').default
},
InviteUsersView: {
getScreen: () => require('./views/InviteUsersView').default
},
InviteUsersEditView: {
getScreen: () => require('./views/InviteUsersEditView').default
},
MessagesView: {
getScreen: () => require('./views/MessagesView').default
},
......
......@@ -1098,6 +1098,23 @@ const RocketChat = {
},
translateMessage(message, targetLanguage) {
return this.sdk.methodCall('autoTranslate.translateMessage', message, targetLanguage);
},
getRoomTitle(room) {
const { UI_Use_Real_Name: useRealName } = reduxStore.getState().settings;
return ((room.prid || useRealName) && room.fname) || room.name;
},
findOrCreateInvite({ rid, days, maxUses }) {
// RC 2.4.0
return this.sdk.post('findOrCreateInvite', { rid, days, maxUses });
},
validateInviteToken(token) {
// RC 2.4.0
return this.sdk.post('validateInviteToken', { token });
},
useInviteToken(token) {
// RC 2.4.0
return this.sdk.post('useInviteToken', { token });
}
};
......
......@@ -15,6 +15,7 @@ import crashReport from './crashReport';
import customEmojis from './customEmojis';
import activeUsers from './activeUsers';
import usersTyping from './usersTyping';
import inviteLinks from './inviteLinks';
export default combineReducers({
settings,
......@@ -32,5 +33,6 @@ export default combineReducers({
crashReport,
customEmojis,
activeUsers,
usersTyping
usersTyping,
inviteLinks
});
import { INVITE_LINKS } from '../actions/actionsTypes';
const initialState = {
token: '',
days: 1,
maxUses: 0,
invite: {}
};
export default (state = initialState, action) => {
switch (action.type) {
case INVITE_LINKS.SET_TOKEN:
return {
token: action.token
};
case INVITE_LINKS.SET_PARAMS:
return {
...state,
...action.params
};
case INVITE_LINKS.SET_INVITE:
return {
...state,
invite: action.invite
};
case INVITE_LINKS.REQUEST:
return state;
case INVITE_LINKS.SUCCESS:
return initialState;
case INVITE_LINKS.FAILURE:
return initialState;
case INVITE_LINKS.CLEAR:
return initialState;
default:
return state;
}
};
......@@ -6,6 +6,7 @@ import RNUserDefaults from 'rn-user-defaults';
import Navigation from '../lib/Navigation';
import * as types from '../actions/actionsTypes';
import { selectServerRequest } from '../actions/server';
import { inviteLinksSetToken, inviteLinksRequest } from '../actions/inviteLinks';
import database from '../lib/database';
import RocketChat from '../lib/rocketchat';
import EventEmitter from '../utils/events';
......@@ -15,6 +16,17 @@ const roomTypes = {
channel: 'c', direct: 'd', group: 'p'
};
const handleInviteLink = function* handleInviteLink({ params, requireLogin = false }) {
if (params.path && params.path.startsWith('invite/')) {
const token = params.path.replace('invite/', '');
if (requireLogin) {
yield put(inviteLinksSetToken(token));
} else {
yield put(inviteLinksRequest(token));
}
}
};
const navigate = function* navigate({ params }) {
yield put(appStart('inside'));
if (params.rid) {
......@@ -24,6 +36,8 @@ const navigate = function* navigate({ params }) {
yield Navigation.navigate('RoomsListView');
Navigation.navigate('RoomView', { rid: params.rid, name, t: roomTypes[type] });
}
} else {
yield handleInviteLink({ params });
}
};
......@@ -63,7 +77,7 @@ const handleOpen = function* handleOpen({ params }) {
const servers = yield serversCollection.find(host);
if (servers && user) {
yield put(selectServerRequest(host));
yield take(types.SERVER.SELECT_SUCCESS);
yield take(types.LOGIN.SUCCESS);
yield navigate({ params });
return;
}
......@@ -82,6 +96,8 @@ const handleOpen = function* handleOpen({ params }) {
if (params.token) {
yield take(types.SERVER.SELECT_SUCCESS);
yield RocketChat.connect({ server: host, user: { token: params.token } });
} else {
yield handleInviteLink({ params, requireLogin: true });
}
}
};
......
......@@ -8,6 +8,7 @@ import createChannel from './createChannel';
import init from './init';
import state from './state';
import deepLinking from './deepLinking';
import inviteLinks from './inviteLinks';
const root = function* root() {
yield all([
......@@ -19,7 +20,8 @@ const root = function* root() {
messages(),
selectServer(),
state(),
deepLinking()
deepLinking(),
inviteLinks()
]);
};
......
import {
put, takeLatest, delay, select
} from 'redux-saga/effects';
import { Alert } from 'react-native';
import { INVITE_LINKS } from '../actions/actionsTypes';
import { inviteLinksSuccess, inviteLinksFailure, inviteLinksSetInvite } from '../actions/inviteLinks';
import RocketChat from '../lib/rocketchat';
import log from '../utils/log';
import Navigation from '../lib/Navigation';
import I18n from '../i18n';
const handleRequest = function* handleRequest({ token }) {
try {
const validateResult = yield RocketChat.validateInviteToken(token);
if (!validateResult.valid) {
yield put(inviteLinksFailure());
return;
}
const result = yield RocketChat.useInviteToken(token);
if (!result.success) {
yield put(inviteLinksFailure());
return;
}
if (result.room && result.room.rid) {
yield delay(1000);
yield Navigation.navigate('RoomsListView');
const { room } = result;
Navigation.navigate('RoomView', {
rid: room.rid,
name: RocketChat.getRoomTitle(room),
t: room.t
});
}
yield put(inviteLinksSuccess());
} catch (e) {
yield put(inviteLinksFailure());
log(e);
}
};
const handleFailure = function handleFailure() {
Alert.alert(I18n.t('Oops'), I18n.t('Invalid_or_expired_invite_token'));
};
const handleCreateInviteLink = function* handleCreateInviteLink({ rid }) {
try {
const inviteLinks = yield select(state => state.inviteLinks);
const result = yield RocketChat.findOrCreateInvite({
rid, days: inviteLinks.days, maxUses: inviteLinks.maxUses
});
if (!result.success) {
Alert.alert(I18n.t('Oops'), I18n.t('There_was_an_error_while_action', { action: I18n.t('creating_invite') }));
return;
}
yield put(inviteLinksSetInvite(result));
} catch (e) {
log(e);
}
};
const root = function* root() {
yield takeLatest(INVITE_LINKS.REQUEST, handleRequest);
yield takeLatest(INVITE_LINKS.FAILURE, handleFailure);
yield takeLatest(INVITE_LINKS.CREATE, handleCreateInviteLink);
};
export default root;
......@@ -20,6 +20,7 @@ import I18n from '../i18n';
import database from '../lib/database';
import EventEmitter from '../utils/events';
import Navigation from '../lib/Navigation';
import { inviteLinksRequest } from '../actions/inviteLinks';
const getServer = state => state.server.server;
const loginWithPasswordCall = args => RocketChat.loginWithPassword(args);
......@@ -115,17 +116,27 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
yield put(setUser(user));
EventEmitter.emit('connected');
let currentRoot;
if (!user.username) {
yield put(appStart('setUsername'));
} else if (adding) {
yield put(serverFinishAdd());
yield put(appStart('inside'));
} else {
const currentRoot = yield select(state => state.app.root);
currentRoot = yield select(state => state.app.root);
if (currentRoot !== 'inside') {
yield put(appStart('inside'));
}
}
// after a successful login, check if it's been invited via invite link
currentRoot = yield select(state => state.app.root);
if (currentRoot === 'inside') {
const inviteLinkToken = yield select(state => state.inviteLinks.token);
if (inviteLinkToken) {
yield put(inviteLinksRequest(inviteLinkToken));
}
}
} catch (e) {
log(e);
}
......
import React from 'react';
import PropTypes from 'prop-types';
import { ScrollView, View } from 'react-native';
import { SafeAreaView } from 'react-navigation';
import { connect } from 'react-redux';
import RNPickerSelect from 'react-native-picker-select';
import {
inviteLinksSetParams as inviteLinksSetParamsAction,
inviteLinksCreate as inviteLinksCreateAction
} from '../../actions/inviteLinks';
import ListItem from '../../containers/ListItem';
import styles from './styles';
import Button from '../../containers/Button';
import scrollPersistTaps from '../../utils/scrollPersistTaps';
import I18n from '../../i18n';
import StatusBar from '../../containers/StatusBar';
import { themes } from '../../constants/colors';