[IMPROVE] Rewrite contextualbar Room Members - InviteUsers (#19694)
Co-authored-by: Guilherme Gazzo <guilherme@gazzo.xyz> Co-authored-by: Guilherme Gazzo <guilhermegazzo@gmail.com>pull/19721/head
parent
3ea8e7835c
commit
451dc84790
@ -1,10 +1,8 @@ |
||||
import './flexTabBar.html'; |
||||
import './tabs/inviteUsers.html'; |
||||
import './tabs/createInviteLink.html'; |
||||
import './tabs/membersList.html'; |
||||
import './tabs/uploadedFilesList.html'; |
||||
import './flexTabBar'; |
||||
import './tabs/inviteUsers'; |
||||
import './tabs/createInviteLink'; |
||||
import './tabs/membersList'; |
||||
import './tabs/uploadedFilesList'; |
||||
|
@ -1,68 +0,0 @@ |
||||
<template name="createInviteLink"> |
||||
|
||||
<form class="" role="form"> |
||||
{{#if isEditing}} |
||||
<div class="rc-user-info__row"> |
||||
<div class="rc-input"> |
||||
<div class="rc-input__title">{{_ "Expiration_(Days)"}}</div> |
||||
<label class="rc-select"> |
||||
<select class="rc-select__element js-type" name="expiration_days" id="expiration_days" aria-label="{{_ "Expiration_(Days)"}}"> |
||||
<option value="1" selected>1</option> |
||||
<option value="7">7</option> |
||||
<option value="15">15</option> |
||||
<option value="30">30</option> |
||||
<option value="0">{{_ 'Never'}}</option> |
||||
</select> |
||||
{{> icon block="rc-select__arrow" icon="arrow-down" }} |
||||
</label> |
||||
</div> |
||||
</div> |
||||
<div class="rc-user-info__row"> |
||||
<div class="rc-input"> |
||||
<div class="rc-input__title">{{_ "Max_number_of_uses"}}</div> |
||||
<label class="rc-select"> |
||||
<select class="rc-select__element js-type" name="max_uses" id="max_uses" aria-label="{{_ "Max_number_of_uses"}}"> |
||||
<option value="1">1</option> |
||||
<option value="5">5</option> |
||||
<option value="10">10</option> |
||||
<option value="25">25</option> |
||||
<option value="50">50</option> |
||||
<option value="100">100</option> |
||||
<option value="0" selected>{{_ 'No Limit'}}</option> |
||||
</select> |
||||
{{> icon block="rc-select__arrow" icon="arrow-down" }} |
||||
</label> |
||||
</div> |
||||
</div> |
||||
|
||||
<button class="rc-button rc-button--default js-confirm-invite" title="{{_ 'Generate_New_Link'}}">{{_ 'Generate_New_Link'}}</button> |
||||
{{else}} |
||||
<div class="rc-user-info__row"> |
||||
<div class="rc-input"> |
||||
<div class="rc-input__title">{{_ "Invite_Link"}}</div> |
||||
<div class="rc-input__wrapper"> |
||||
<div class="rc-input__icon rc-input__icon--right rc-input__icon--clickable js-copy"> |
||||
{{> icon block="rc-input__icon-svg" icon="copy" }} |
||||
</div> |
||||
<input type="text" class="rc-input__element rc-invite-link-input" name="link" id="link" readonly value="{{ url }}"/> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="rc-user-info__row"> |
||||
<div class="rc-input"> |
||||
<div class="rc-input__title">{{linkExpirationText}}</div> |
||||
</div> |
||||
</div> |
||||
<div class="rc-user-info__row"> |
||||
<button class="rc-button rc-button--outline js-edit-invite" title="{{_ 'Edit_Invite'}}">{{_ 'Edit_Invite'}}</button> |
||||
</div> |
||||
|
||||
|
||||
{{/if}} |
||||
</form> |
||||
|
||||
|
||||
<div class="rc-user-info__flex rc-user-info__row"> |
||||
<button class="rc-button rc-button--outline js-close" title="{{_ 'Close'}}">{{_ 'Close'}}</button> |
||||
</div> |
||||
</template> |
@ -1,109 +0,0 @@ |
||||
import { ReactiveVar } from 'meteor/reactive-var'; |
||||
import { Template } from 'meteor/templating'; |
||||
import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; |
||||
import toastr from 'toastr'; |
||||
import Clipboard from 'clipboard'; |
||||
import { Session } from 'meteor/session'; |
||||
|
||||
import { t, APIClient } from '../../../utils'; |
||||
import { formatDateAndTime } from '../../../lib/client/lib/formatDate'; |
||||
|
||||
function getInviteLink(instance, rid, days, maxUses) { |
||||
APIClient.v1.post('findOrCreateInvite', { |
||||
rid, |
||||
days, |
||||
maxUses, |
||||
}).then((result) => { |
||||
if (!result) { |
||||
toastr.error(t('Failed_to_generate_invite_link')); |
||||
return; |
||||
} |
||||
|
||||
instance.inviteData.set(result); |
||||
instance.url.set(result.url); |
||||
instance.isEditing.set(false); |
||||
}).catch(() => { |
||||
toastr.error(t('Failed_to_generate_invite_link')); |
||||
}); |
||||
} |
||||
|
||||
Template.createInviteLink.helpers({ |
||||
isEditing() { |
||||
return Template.instance().isEditing.get(); |
||||
}, |
||||
|
||||
isReady() { |
||||
return Template.instance().isReady.get(); |
||||
}, |
||||
|
||||
url() { |
||||
return Template.instance().url.get(); |
||||
}, |
||||
|
||||
linkExpirationText() { |
||||
const data = Template.instance().inviteData.get(); |
||||
|
||||
if (!data) { |
||||
return ''; |
||||
} |
||||
|
||||
if (data.expires) { |
||||
const expiration = new Date(data.expires); |
||||
|
||||
if (data.maxUses) { |
||||
const usesLeft = data.maxUses - data.uses; |
||||
return TAPi18n.__('Your_invite_link_will_expire_on__date__or_after__usesLeft__uses', { date: formatDateAndTime(expiration), usesLeft }); |
||||
} |
||||
|
||||
return TAPi18n.__('Your_invite_link_will_expire_on__date__', { date: formatDateAndTime(expiration) }); |
||||
} |
||||
|
||||
if (data.maxUses) { |
||||
const usesLeft = data.maxUses - data.uses; |
||||
return TAPi18n.__('Your_invite_link_will_expire_after__usesLeft__uses', { usesLeft }); |
||||
} |
||||
|
||||
return t('Your_invite_link_will_never_expire'); |
||||
}, |
||||
|
||||
}); |
||||
|
||||
Template.createInviteLink.events({ |
||||
'click .js-copy'(event, i) { |
||||
$(event.currentTarget).attr('data-clipboard-text', i.url.get()); |
||||
}, |
||||
'click .js-edit-invite'(e, instance) { |
||||
e.preventDefault(); |
||||
instance.isEditing.set(true); |
||||
}, |
||||
|
||||
'click .js-confirm-invite'(e, instance) { |
||||
e.preventDefault(); |
||||
|
||||
const { rid } = this; |
||||
const days = parseInt($('#expiration_days').val().trim()); |
||||
const maxUses = parseInt($('#max_uses').val().trim()); |
||||
|
||||
getInviteLink(instance, rid, days, maxUses); |
||||
}, |
||||
}); |
||||
|
||||
Template.createInviteLink.onCreated(function() { |
||||
this.isEditing = new ReactiveVar(false); |
||||
this.isReady = new ReactiveVar(false); |
||||
this.url = new ReactiveVar(''); |
||||
this.inviteData = new ReactiveVar(null); |
||||
|
||||
const { rid } = this.data; |
||||
|
||||
getInviteLink(this, rid); |
||||
|
||||
this.clipboard = new Clipboard('.js-copy'); |
||||
this.clipboard.on('success', () => { |
||||
Session.get('openedRoom') === rid && toastr.success(TAPi18n.__('Copied')); |
||||
}); |
||||
}); |
||||
|
||||
Template.createInviteLink.onDestroyed(function() { |
||||
this.clipboard.destroy(); |
||||
}); |
@ -0,0 +1,26 @@ |
||||
import { useCallback } from 'react'; |
||||
import PropTypes from 'prop-types'; |
||||
|
||||
import { useTranslation } from '../contexts/TranslationContext'; |
||||
import { useToastMessageDispatch } from '../contexts/ToastMessagesContext'; |
||||
|
||||
export default function useClipboard(text) { |
||||
const t = useTranslation(); |
||||
const dispatchToastMessage = useToastMessageDispatch(); |
||||
|
||||
const onClick = useCallback((e) => { |
||||
e.preventDefault(); |
||||
try { |
||||
navigator.clipboard.writeText(text); |
||||
dispatchToastMessage({ type: 'success', message: t('Copied') }); |
||||
} catch (e) { |
||||
dispatchToastMessage({ type: 'error', message: e }); |
||||
} |
||||
}, [dispatchToastMessage, t, text]); |
||||
|
||||
return onClick; |
||||
} |
||||
|
||||
useClipboard.propTypes = { |
||||
text: PropTypes.string.isRequired, |
||||
}; |
@ -0,0 +1,102 @@ |
||||
import React, { useMemo, useState } from 'react'; |
||||
import { Box, Field, Select, Button, InputBox } from '@rocket.chat/fuselage'; |
||||
import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; |
||||
|
||||
import { useTranslation } from '../../../../contexts/TranslationContext'; |
||||
import VerticalBar from '../../../../components/basic/VerticalBar'; |
||||
|
||||
export const EditInvite = ({ |
||||
onClickBack, |
||||
onClickClose, |
||||
onClickNewLink, |
||||
days, |
||||
setDays, |
||||
maxUses, |
||||
setMaxUses, |
||||
}) => { |
||||
const t = useTranslation(); |
||||
|
||||
const daysOptions = useMemo(() => [ |
||||
[1, 1], |
||||
[7, 7], |
||||
[15, 15], |
||||
[30, 30], |
||||
[0, t('Never')], |
||||
], [t]); |
||||
|
||||
const maxUsesOptions = useMemo(() => [ |
||||
[5, 5], |
||||
[10, 10], |
||||
[25, 25], |
||||
[50, 50], |
||||
[100, 100], |
||||
[0, t('No_Limit')], |
||||
], [t]); |
||||
|
||||
return ( |
||||
<> |
||||
<VerticalBar.Header> |
||||
{onClickBack && <VerticalBar.Back onClick={onClickBack} />} |
||||
<VerticalBar.Text>{t('Invite_Users')}</VerticalBar.Text> |
||||
{onClickClose && <VerticalBar.Close onClick={onClickClose} />} |
||||
</VerticalBar.Header> |
||||
|
||||
<VerticalBar.ScrollableContent> |
||||
<Field> |
||||
<Field.Label flexGrow={0}>{t('Expiration_(Days)')}</Field.Label> |
||||
<Field.Row> |
||||
{days === undefined ? <InputBox.Skeleton /> : <Select value={days} onChange={setDays} options={daysOptions} />} |
||||
</Field.Row> |
||||
</Field> |
||||
|
||||
<Field pb='x16'> |
||||
<Field.Label flexGrow={0}>{t('Max_number_of_uses')}</Field.Label> |
||||
<Field.Row> |
||||
{maxUses === undefined ? <InputBox.Skeleton /> : <Select value={maxUses} onChange={setMaxUses} options={maxUsesOptions} />} |
||||
</Field.Row> |
||||
</Field> |
||||
|
||||
<Box pb='x16'> |
||||
<Button primary onClick={onClickNewLink}>{t('Generate_New_Link')}</Button> |
||||
</Box> |
||||
</VerticalBar.ScrollableContent> |
||||
</> |
||||
); |
||||
}; |
||||
|
||||
export default ({ |
||||
tabBar, |
||||
onClickBack, |
||||
setParams, |
||||
linkText, |
||||
captionText, |
||||
days: _days, |
||||
maxUses: _maxUses, |
||||
|
||||
}) => { |
||||
const onClickClose = useMutableCallback(() => tabBar && tabBar.close()); |
||||
|
||||
const [days, setDays] = useState(_days); |
||||
const [maxUses, setMaxUses] = useState(_maxUses); |
||||
|
||||
const generateLink = useMutableCallback(() => { |
||||
setParams({ |
||||
days, |
||||
maxUses, |
||||
}); |
||||
}); |
||||
|
||||
return ( |
||||
<EditInvite |
||||
onClickBack={onClickBack} |
||||
onClickClose={onClickClose} |
||||
onClickNewLink={generateLink} |
||||
setDays={setDays} |
||||
days={days} |
||||
maxUses={maxUses} |
||||
setMaxUses={setMaxUses} |
||||
linkText={linkText} |
||||
captionText={captionText} |
||||
/> |
||||
); |
||||
}; |
@ -0,0 +1,21 @@ |
||||
import React from 'react'; |
||||
|
||||
import { EditInvite } from './EditInvite'; |
||||
import VerticalBar from '../../../../components/basic/VerticalBar'; |
||||
|
||||
export default { |
||||
title: 'components/RoomMembers/EditInvite', |
||||
component: EditInvite, |
||||
}; |
||||
|
||||
export const Default = () => <VerticalBar> |
||||
<EditInvite |
||||
onClickBack={alert} |
||||
onClickClose={alert} |
||||
onClickNewLink={alert} |
||||
expirationDate={1} |
||||
setExpirtaionDate={alert} |
||||
maxUses={5} |
||||
setMaxUses={alert} |
||||
/> |
||||
</VerticalBar>; |
@ -0,0 +1,3 @@ |
||||
import EditInvite from './EditInvite'; |
||||
|
||||
export default EditInvite; |
@ -0,0 +1,138 @@ |
||||
import React, { useState, useEffect } from 'react'; |
||||
import { Box, Field, UrlInput, Icon, Button, InputBox, Callout } from '@rocket.chat/fuselage'; |
||||
import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; |
||||
|
||||
import useClipboard from '../../../../hooks/useClipboard'; |
||||
import VerticalBar from '../../../../components/basic/VerticalBar'; |
||||
import { useTranslation } from '../../../../contexts/TranslationContext'; |
||||
import { useEndpoint } from '../../../../contexts/ServerContext'; |
||||
import { useFormatDateAndTime } from '../../../../hooks/useFormatDateAndTime'; |
||||
import EditInvite from '../EditInvite'; |
||||
|
||||
export const InviteUsers = ({ |
||||
onClickBack, |
||||
onClickClose, |
||||
onClickEdit, |
||||
captionText, |
||||
linkText, |
||||
error, |
||||
}) => { |
||||
const t = useTranslation(); |
||||
|
||||
const onClickCopy = useClipboard(linkText); |
||||
|
||||
return ( |
||||
<> |
||||
<VerticalBar.Header> |
||||
{onClickBack && <VerticalBar.Back onClick={onClickBack} />} |
||||
<VerticalBar.Text>{t('Invite_Users')}</VerticalBar.Text> |
||||
{onClickClose && <VerticalBar.Close onClick={onClickClose} />} |
||||
</VerticalBar.Header> |
||||
|
||||
<VerticalBar.ScrollableContent> |
||||
<Field> |
||||
<Field.Label flexGrow={0}>{t('Invite_Link')}</Field.Label> |
||||
<Field.Row> |
||||
{linkText === undefined ? <InputBox.Skeleton /> : <UrlInput value={linkText} addon={<Icon onClick={onClickCopy} name='copy' size='x16'/>}/>} |
||||
</Field.Row> |
||||
</Field> |
||||
|
||||
<Box pb='x8' color='neutral-600' fontScale='c2'>{captionText}</Box> |
||||
|
||||
{ error && <Callout mi='x24' type='danger'>{error.toString()}</Callout>} |
||||
|
||||
<Box pb='x16'> |
||||
{onClickEdit && <Button onClick={onClickEdit}>{t('Edit_Invite')}</Button>} |
||||
</Box> |
||||
</VerticalBar.ScrollableContent> |
||||
</> |
||||
); |
||||
}; |
||||
|
||||
export default ({ |
||||
rid, |
||||
tabBar, |
||||
onClickBack, |
||||
}) => { |
||||
const [editing, setEditing] = useState(false); |
||||
const format = useFormatDateAndTime(); |
||||
const t = useTranslation(); |
||||
|
||||
const onClickClose = useMutableCallback(() => tabBar && tabBar.close()); |
||||
|
||||
const handleEdit = useMutableCallback(() => setEditing(true)); |
||||
const onClickBackEditing = useMutableCallback(() => setEditing(false)); |
||||
|
||||
const findOrCreateInvite = useEndpoint('POST', 'findOrCreateInvite'); |
||||
|
||||
const [{ days = 1, maxUses = 0 }, setDayAndMaxUses] = useState({}); |
||||
|
||||
|
||||
const setParams = useMutableCallback((args) => { |
||||
setDayAndMaxUses(args); |
||||
setEditing(false); |
||||
}); |
||||
|
||||
const [state, setState] = useState(); |
||||
|
||||
const linkExpirationText = useMutableCallback((data) => { |
||||
if (!data) { |
||||
return ''; |
||||
} |
||||
|
||||
if (data.expires) { |
||||
const expiration = new Date(data.expires); |
||||
if (data.maxUses) { |
||||
const usesLeft = data.maxUses - data.uses; |
||||
return t('Your_invite_link_will_expire_on__date__or_after__usesLeft__uses', { date: format(expiration), usesLeft }); |
||||
} |
||||
|
||||
return t('Your_invite_link_will_expire_on__date__', { date: format(expiration) }); |
||||
} |
||||
|
||||
if (data.maxUses) { |
||||
const usesLeft = data.maxUses - data.uses; |
||||
return t('Your_invite_link_will_expire_after__usesLeft__uses', { usesLeft }); |
||||
} |
||||
|
||||
return t('Your_invite_link_will_never_expire'); |
||||
}); |
||||
|
||||
useEffect(() => { |
||||
if (editing) { |
||||
return; |
||||
} |
||||
(async () => { |
||||
try { |
||||
const data = await findOrCreateInvite({ rid, days, maxUses }); |
||||
setState({ |
||||
url: data.url, |
||||
caption: linkExpirationText(data), |
||||
}); |
||||
} catch (error) { |
||||
setState({ error }); |
||||
} |
||||
})(); |
||||
}, [findOrCreateInvite, editing, linkExpirationText, rid, days, maxUses]); |
||||
|
||||
|
||||
if (editing) { |
||||
return <EditInvite |
||||
onClickBack={onClickBackEditing} |
||||
linkText={state?.url} |
||||
captionText={state?.caption} |
||||
{...{ rid, tabBar, error: state?.error, setParams, days, maxUses }} |
||||
/>; |
||||
} |
||||
|
||||
return ( |
||||
<InviteUsers |
||||
error={state?.error} |
||||
onClickClose={onClickClose} |
||||
onClickBack={onClickBack} |
||||
onClickEdit={handleEdit} |
||||
linkText={state?.url} |
||||
captionText={state?.caption} |
||||
/> |
||||
); |
||||
}; |
@ -0,0 +1,19 @@ |
||||
import React from 'react'; |
||||
|
||||
import { InviteUsers } from './InviteUsers'; |
||||
import VerticalBar from '../../../../components/basic/VerticalBar'; |
||||
|
||||
export default { |
||||
title: 'components/RoomMembers/InviteUsers', |
||||
component: InviteUsers, |
||||
}; |
||||
|
||||
export const Default = () => <VerticalBar> |
||||
<InviteUsers |
||||
linkText='https://go.rocket.chat/invite?host=open.rocket.chat&path=invite%2F5sBs3a`' |
||||
captionText='Expire on February 4, 2020 4:45 PM.' |
||||
onClickBack={alert} |
||||
onClickClose={alert} |
||||
onClickEdit={alert} |
||||
/> |
||||
</VerticalBar>; |
@ -0,0 +1,3 @@ |
||||
import InviteUsers from './InviteUsers'; |
||||
|
||||
export default InviteUsers; |
Loading…
Reference in new issue