[NEW] Invite links (#1534)
This commit is contained in:
parent
ba27c580f4
commit
0673081465
|
@ -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
|
||||
]);
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
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
|
||||
});
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
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()
|
||||
]);
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
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);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,160 @@
|
|||
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';
|
||||
import { withTheme } from '../../theme';
|
||||
import { themedHeader } from '../../utils/navigation';
|
||||
import Separator from '../../containers/Separator';
|
||||
|
||||
const OPTIONS = {
|
||||
days: [{
|
||||
label: I18n.t('Never'), value: 0
|
||||
},
|
||||
{
|
||||
label: '1', value: 1
|
||||
},
|
||||
{
|
||||
label: '7', value: 7
|
||||
},
|
||||
{
|
||||
label: '15', value: 15
|
||||
},
|
||||
{
|
||||
label: '30', value: 30
|
||||
}],
|
||||
maxUses: [{
|
||||
label: I18n.t('No_limit'), value: 0
|
||||
},
|
||||
{
|
||||
label: '1', value: 1
|
||||
},
|
||||
{
|
||||
label: '5', value: 5
|
||||
},
|
||||
{
|
||||
label: '10', value: 10
|
||||
},
|
||||
{
|
||||
label: '25', value: 25
|
||||
},
|
||||
{
|
||||
label: '50', value: 50
|
||||
},
|
||||
{
|
||||
label: '100', value: 100
|
||||
}]
|
||||
};
|
||||
|
||||
class InviteUsersView extends React.Component {
|
||||
static navigationOptions = ({ screenProps }) => ({
|
||||
title: I18n.t('Invite_users'),
|
||||
...themedHeader(screenProps.theme)
|
||||
})
|
||||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
theme: PropTypes.string,
|
||||
timeDateFormat: PropTypes.string,
|
||||
createInviteLink: PropTypes.func,
|
||||
inviteLinksSetParams: PropTypes.func
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.rid = props.navigation.getParam('rid');
|
||||
}
|
||||
|
||||
onValueChangePicker = (key, value) => {
|
||||
const { inviteLinksSetParams } = this.props;
|
||||
const params = {
|
||||
[key]: value
|
||||
};
|
||||
inviteLinksSetParams(params);
|
||||
}
|
||||
|
||||
createInviteLink = () => {
|
||||
const { createInviteLink, navigation } = this.props;
|
||||
createInviteLink(this.rid);
|
||||
navigation.pop();
|
||||
}
|
||||
|
||||
renderPicker = (key) => {
|
||||
const { props } = this;
|
||||
const { theme } = props;
|
||||
return (
|
||||
<RNPickerSelect
|
||||
style={{ viewContainer: styles.viewContainer }}
|
||||
value={props[key]}
|
||||
textInputProps={{ style: { ...styles.pickerText, color: themes[theme].actionTintColor } }}
|
||||
useNativeAndroidPickerStyle={false}
|
||||
placeholder={{}}
|
||||
onValueChange={value => this.onValueChangePicker(key, value)}
|
||||
items={OPTIONS[key]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { theme } = this.props;
|
||||
return (
|
||||
<SafeAreaView style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]} forceInset={{ vertical: 'never' }}>
|
||||
<ScrollView
|
||||
{...scrollPersistTaps}
|
||||
style={{ backgroundColor: themes[theme].auxiliaryBackground }}
|
||||
contentContainerStyle={styles.contentContainer}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
<StatusBar theme={theme} />
|
||||
<Separator theme={theme} />
|
||||
<ListItem
|
||||
title={I18n.t('Expiration_Days')}
|
||||
right={() => this.renderPicker('days')}
|
||||
theme={theme}
|
||||
/>
|
||||
<Separator theme={theme} />
|
||||
<ListItem
|
||||
title={I18n.t('Max_number_of_uses')}
|
||||
right={() => this.renderPicker('maxUses')}
|
||||
theme={theme}
|
||||
/>
|
||||
<Separator theme={theme} />
|
||||
<View style={styles.innerContainer}>
|
||||
<View style={[styles.divider, { backgroundColor: themes[theme].separatorColor }]} />
|
||||
<Button
|
||||
title={I18n.t('Generate_New_Link')}
|
||||
type='primary'
|
||||
onPress={this.createInviteLink}
|
||||
theme={theme}
|
||||
/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
days: state.inviteLinks.days,
|
||||
maxUses: state.inviteLinks.maxUses
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
inviteLinksSetParams: params => dispatch(inviteLinksSetParamsAction(params)),
|
||||
createInviteLink: rid => dispatch(inviteLinksCreateAction(rid))
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(InviteUsersView));
|
|
@ -0,0 +1,45 @@
|
|||
import { StyleSheet } from 'react-native';
|
||||
|
||||
import sharedStyles from '../Styles';
|
||||
|
||||
export default StyleSheet.create({
|
||||
container: {
|
||||
flex: 1
|
||||
},
|
||||
innerContainer: {
|
||||
paddingHorizontal: 20
|
||||
},
|
||||
divider: {
|
||||
width: '100%',
|
||||
height: StyleSheet.hairlineWidth,
|
||||
marginVertical: 20
|
||||
},
|
||||
sectionSeparatorBorder: {
|
||||
height: 10
|
||||
},
|
||||
marginBottom: {
|
||||
height: 30
|
||||
},
|
||||
contentContainer: {
|
||||
marginVertical: 10
|
||||
},
|
||||
infoText: {
|
||||
...sharedStyles.textRegular,
|
||||
fontSize: 13,
|
||||
paddingHorizontal: 15,
|
||||
paddingVertical: 10
|
||||
},
|
||||
sectionTitle: {
|
||||
...sharedStyles.separatorBottom,
|
||||
paddingHorizontal: 15,
|
||||
paddingVertical: 10,
|
||||
fontSize: 14
|
||||
},
|
||||
viewContainer: {
|
||||
justifyContent: 'center'
|
||||
},
|
||||
pickerText: {
|
||||
...sharedStyles.textRegular,
|
||||
fontSize: 16
|
||||
}
|
||||
});
|
|
@ -0,0 +1,151 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { View, Share, ScrollView } from 'react-native';
|
||||
import { SafeAreaView } from 'react-navigation';
|
||||
import moment from 'moment';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import {
|
||||
inviteLinksCreate as inviteLinksCreateAction,
|
||||
inviteLinksClear as inviteLinksClearAction
|
||||
} from '../../actions/inviteLinks';
|
||||
import RCTextInput from '../../containers/TextInput';
|
||||
import styles from './styles';
|
||||
import Markdown from '../../containers/markdown';
|
||||
import Button from '../../containers/Button';
|
||||
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
||||
import I18n from '../../i18n';
|
||||
import StatusBar from '../../containers/StatusBar';
|
||||
import { themes } from '../../constants/colors';
|
||||
import { withTheme } from '../../theme';
|
||||
import { themedHeader } from '../../utils/navigation';
|
||||
|
||||
class InviteUsersView extends React.Component {
|
||||
static navigationOptions = ({ screenProps }) => ({
|
||||
title: I18n.t('Invite_users'),
|
||||
...themedHeader(screenProps.theme)
|
||||
})
|
||||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
theme: PropTypes.string,
|
||||
timeDateFormat: PropTypes.string,
|
||||
invite: PropTypes.object,
|
||||
createInviteLink: PropTypes.func,
|
||||
clearInviteLink: PropTypes.func
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.rid = props.navigation.getParam('rid');
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { createInviteLink } = this.props;
|
||||
createInviteLink(this.rid);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const { clearInviteLink } = this.props;
|
||||
clearInviteLink();
|
||||
}
|
||||
|
||||
share = () => {
|
||||
const { invite } = this.props;
|
||||
if (!invite) {
|
||||
return;
|
||||
}
|
||||
Share.share({ message: invite.url });
|
||||
}
|
||||
|
||||
edit = () => {
|
||||
const { navigation } = this.props;
|
||||
navigation.navigate('InviteUsersEditView', { rid: this.rid });
|
||||
}
|
||||
|
||||
linkExpirationText = () => {
|
||||
const { timeDateFormat, invite } = this.props;
|
||||
|
||||
if (!invite || !invite.url) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (invite.expires) {
|
||||
const expiration = new Date(invite.expires);
|
||||
|
||||
if (invite.maxUses) {
|
||||
const usesLeft = invite.maxUses - invite.uses;
|
||||
return I18n.t('Your_invite_link_will_expire_on__date__or_after__usesLeft__uses', { date: moment(expiration).format(timeDateFormat), usesLeft });
|
||||
}
|
||||
|
||||
return I18n.t('Your_invite_link_will_expire_on__date__', { date: moment(expiration).format(timeDateFormat) });
|
||||
}
|
||||
|
||||
if (invite.maxUses) {
|
||||
const usesLeft = invite.maxUses - invite.uses;
|
||||
return I18n.t('Your_invite_link_will_expire_after__usesLeft__uses', { usesLeft });
|
||||
}
|
||||
|
||||
return I18n.t('Your_invite_link_will_never_expire');
|
||||
}
|
||||
|
||||
renderExpiration = () => {
|
||||
const { theme } = this.props;
|
||||
const expirationMessage = this.linkExpirationText();
|
||||
return <Markdown msg={expirationMessage} username='' baseUrl='' theme={theme} />;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
theme, invite
|
||||
} = this.props;
|
||||
return (
|
||||
<SafeAreaView style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]} forceInset={{ vertical: 'never' }}>
|
||||
<ScrollView
|
||||
{...scrollPersistTaps}
|
||||
style={{ backgroundColor: themes[theme].auxiliaryBackground }}
|
||||
contentContainerStyle={styles.contentContainer}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
<StatusBar theme={theme} />
|
||||
<View style={styles.innerContainer}>
|
||||
<RCTextInput
|
||||
label={I18n.t('Invite_Link')}
|
||||
theme={theme}
|
||||
value={invite && invite.url}
|
||||
editable={false}
|
||||
/>
|
||||
{this.renderExpiration()}
|
||||
<View style={[styles.divider, { backgroundColor: themes[theme].separatorColor }]} />
|
||||
<Button
|
||||
title={I18n.t('Share_Link')}
|
||||
type='primary'
|
||||
onPress={this.share}
|
||||
theme={theme}
|
||||
/>
|
||||
<Button
|
||||
title={I18n.t('Edit_Invite')}
|
||||
type='secondary'
|
||||
onPress={this.edit}
|
||||
theme={theme}
|
||||
/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
timeDateFormat: state.settings.Message_TimeAndDateFormat,
|
||||
days: state.inviteLinks.days,
|
||||
maxUses: state.inviteLinks.maxUses,
|
||||
invite: state.inviteLinks.invite
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
createInviteLink: rid => dispatch(inviteLinksCreateAction(rid)),
|
||||
clearInviteLink: () => dispatch(inviteLinksClearAction())
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(InviteUsersView));
|
|
@ -0,0 +1,16 @@
|
|||
import { StyleSheet } from 'react-native';
|
||||
|
||||
export default StyleSheet.create({
|
||||
container: {
|
||||
flex: 1
|
||||
},
|
||||
innerContainer: {
|
||||
padding: 20,
|
||||
paddingBottom: 0
|
||||
},
|
||||
divider: {
|
||||
width: '100%',
|
||||
height: StyleSheet.hairlineWidth,
|
||||
marginVertical: 20
|
||||
}
|
||||
});
|
|
@ -62,7 +62,8 @@ class RoomActionsView extends React.Component {
|
|||
joined: !!room,
|
||||
canViewMembers: false,
|
||||
canAutoTranslate: false,
|
||||
canAddUser: false
|
||||
canAddUser: false,
|
||||
canInviteUser: false
|
||||
};
|
||||
if (room && room.observe && room.rid) {
|
||||
this.roomObservable = room.observe();
|
||||
|
@ -108,6 +109,7 @@ class RoomActionsView extends React.Component {
|
|||
this.setState({ canAutoTranslate });
|
||||
|
||||
this.canAddUser();
|
||||
this.canInviteUser();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -126,7 +128,6 @@ class RoomActionsView extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: move to componentDidMount
|
||||
// eslint-disable-next-line react/sort-comp
|
||||
canAddUser = async() => {
|
||||
const { room, joined } = this.state;
|
||||
|
@ -150,8 +151,15 @@ class RoomActionsView extends React.Component {
|
|||
this.setState({ canAddUser: canAdd });
|
||||
}
|
||||
|
||||
// TODO: move to componentDidMount
|
||||
// eslint-disable-next-line react/sort-comp
|
||||
canInviteUser = async() => {
|
||||
const { room } = this.state;
|
||||
const { rid } = room;
|
||||
const permissions = await RocketChat.hasPermission(['create-invite-links'], rid);
|
||||
|
||||
const canInviteUser = permissions && permissions['create-invite-links'];
|
||||
this.setState({ canInviteUser });
|
||||
}
|
||||
|
||||
canViewMembers = async() => {
|
||||
const { room } = this.state;
|
||||
const { rid, t, broadcast } = room;
|
||||
|
@ -172,7 +180,7 @@ class RoomActionsView extends React.Component {
|
|||
|
||||
get sections() {
|
||||
const {
|
||||
room, membersCount, canViewMembers, canAddUser, joined, canAutoTranslate
|
||||
room, membersCount, canViewMembers, canAddUser, canInviteUser, joined, canAutoTranslate
|
||||
} = this.state;
|
||||
const { jitsiEnabled } = this.props;
|
||||
const {
|
||||
|
@ -302,17 +310,28 @@ class RoomActionsView extends React.Component {
|
|||
|
||||
if (canAddUser) {
|
||||
actions.push({
|
||||
icon: 'user-plus',
|
||||
name: I18n.t('Add_user'),
|
||||
icon: 'plus',
|
||||
name: I18n.t('Add_users'),
|
||||
route: 'SelectedUsersView',
|
||||
params: {
|
||||
nextActionID: 'ADD_USER',
|
||||
rid,
|
||||
title: I18n.t('Add_user')
|
||||
title: I18n.t('Add_users')
|
||||
},
|
||||
testID: 'room-actions-add-user'
|
||||
});
|
||||
}
|
||||
if (canInviteUser) {
|
||||
actions.push({
|
||||
icon: 'user-plus',
|
||||
name: I18n.t('Invite_users'),
|
||||
route: 'InviteUsersView',
|
||||
params: {
|
||||
rid
|
||||
},
|
||||
testID: 'room-actions-invite-user'
|
||||
});
|
||||
}
|
||||
sections[2].data = [...actions, ...sections[2].data];
|
||||
|
||||
if (joined) {
|
||||
|
|
|
@ -161,7 +161,6 @@ class RoomsListView extends React.Component {
|
|||
groupByType: PropTypes.bool,
|
||||
showFavorites: PropTypes.bool,
|
||||
showUnread: PropTypes.bool,
|
||||
useRealName: PropTypes.bool,
|
||||
StoreLastMessage: PropTypes.bool,
|
||||
appState: PropTypes.string,
|
||||
theme: PropTypes.string,
|
||||
|
@ -486,10 +485,7 @@ class RoomsListView extends React.Component {
|
|||
});
|
||||
}, 300);
|
||||
|
||||
getRoomTitle = (item) => {
|
||||
const { useRealName } = this.props;
|
||||
return ((item.prid || useRealName) && item.fname) || item.name;
|
||||
};
|
||||
getRoomTitle = item => RocketChat.getRoomTitle(item)
|
||||
|
||||
goRoom = (item) => {
|
||||
this.cancelSearchingAndroid();
|
||||
|
|
Loading…
Reference in New Issue