create new session page for beta site
This commit is contained in:
parent
b589ad8553
commit
fa082230b8
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -6,5 +6,6 @@
|
|||
|
||||
@import './custom/nav';
|
||||
@import './custom/user';
|
||||
@import './custom/form';
|
||||
@import './custom/form';
|
||||
@import './custom/chips';
|
||||
@import './custom/common';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,88 @@
|
|||
.chip-container{
|
||||
border: 1px solid #edf2f9f5;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
background: floralwhite;
|
||||
//font-family: "Roboto";
|
||||
|
||||
.basic-chip a{
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.chip-title{
|
||||
padding: 20px 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.basic-chip{
|
||||
padding: 5px 10px;
|
||||
border-radius: 50px;
|
||||
display: inline-flex;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.click-chip, .click-chip-hover{
|
||||
padding: 5px 10px;
|
||||
border-radius: 50px;
|
||||
display: inline-flex;
|
||||
margin: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.click-chip-hover:hover{
|
||||
filter: brightness(85%);
|
||||
}
|
||||
|
||||
.outline{
|
||||
border: 1.5px solid #cccccc;
|
||||
color: #cccccc;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.outline-green{
|
||||
border: 1.5px solid #3a913f;
|
||||
color: #3a913f;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.outline-blue{
|
||||
border: 1.5px solid #0074c3;
|
||||
color: #0074c3;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.background-grey{
|
||||
background: #dddddd;
|
||||
color: #616161;
|
||||
}
|
||||
|
||||
.background-white{
|
||||
border: 1.5px solid #d8e2ef;
|
||||
background: #ffffff;
|
||||
color: #d0d0d0;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.background-green{
|
||||
background: #3a913f;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.background-blue{
|
||||
background: #0074c3;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.icon{
|
||||
/*background: #bdbdbd;*/
|
||||
color: #616161;
|
||||
margin: 0px 5px 0px -5px;
|
||||
background: #bbbbbb;
|
||||
padding: 2px;
|
||||
border-radius: 50px;
|
||||
}
|
||||
|
||||
.fa-times, .fa-times-circle{
|
||||
margin: 0px -5px 0px 5px;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,126 +1,149 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { Form, FormGroup, Input, Label, Text, Card, CardBody, Button } from 'reactstrap';
|
||||
import React, { useRef, useState, useEffect } from 'react';
|
||||
import { Form, FormGroup, Input, Label, Card, CardBody, Button, Row, Col } from 'reactstrap';
|
||||
import FalconCardHeader from '../common/FalconCardHeader';
|
||||
import Flex from '../common/Flex';
|
||||
import JKTooltip from '../common/JKTooltip';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import AsyncSelect from 'react-select/async';
|
||||
//import useAutoComplete from '../../hooks/useAutocomplete';
|
||||
import { useAuth } from '../../context/UserAuth';
|
||||
import JKFriendsAutoComplete from '../people/JKFriendsAutoComplete';
|
||||
import JKSessionInviteesChips from '../people/JKSessionInviteesChips';
|
||||
import { getFriends } from '../../helpers/rest';
|
||||
import Avatar from '../common/Avatar';
|
||||
|
||||
const CustomOption = props => {
|
||||
const { data, innerRef, innerProps } = props;
|
||||
return (
|
||||
<div ref={innerRef} {...innerProps}>
|
||||
<Flex direction="row" className="p-2 bg-200 mb-2">
|
||||
<div className="p-2 bg-300">
|
||||
<Avatar src={data.photoUrl} size="s" name={data.label} />
|
||||
</div>
|
||||
<div className="p-2 bg-300">{data.label}</div>
|
||||
</Flex>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const JKNewMusicSession = () => {
|
||||
const { t } = useTranslation();
|
||||
const { currentUser } = useAuth();
|
||||
const { t } = useTranslation();
|
||||
const [friends, setFriends] = useState([]);
|
||||
const [isFriendsFetched, setIsFriendsFetched] = useState(false)
|
||||
const [description, setDescription] = useState("")
|
||||
const [invitees, setInvitees] = useState([]);
|
||||
const [privacy, setPrivacy] = useState("1");
|
||||
|
||||
const friendOptions = async inputValue => {
|
||||
let matches = [];
|
||||
if (inputValue && inputValue.length >= 3) {
|
||||
await getFriends(currentUser.id)
|
||||
.then(resp => {
|
||||
return resp.json();
|
||||
})
|
||||
.then(data => {
|
||||
matches = data
|
||||
.filter(
|
||||
friend =>
|
||||
friend.first_name.toLowerCase().includes(inputValue.toLowerCase()) ||
|
||||
friend.last_name.toLowerCase().includes(inputValue.toLowerCase())
|
||||
)
|
||||
.map(opt => ({
|
||||
photoUrl: opt.photo_url,
|
||||
label: `${opt.first_name} ${opt.last_name}`,
|
||||
value: opt.id
|
||||
}));
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
fetchFriends();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if(isFriendsFetched){
|
||||
populateFormDataFromLocalStorage()
|
||||
}
|
||||
return matches;
|
||||
}, [isFriendsFetched])
|
||||
|
||||
const fetchFriends = () => {
|
||||
getFriends(currentUser.id)
|
||||
.then(resp => {
|
||||
if (resp.ok) {
|
||||
return resp.json();
|
||||
}
|
||||
})
|
||||
.then(data => {
|
||||
setFriends(data);
|
||||
setIsFriendsFetched(true);
|
||||
});
|
||||
}
|
||||
|
||||
const populateFormDataFromLocalStorage = () => {
|
||||
try {
|
||||
const formData = localStorage.getItem('formData');
|
||||
const formDataItems = JSON.parse(formData);
|
||||
setDescription(formDataItems['description']);
|
||||
setPrivacy(formDataItems['privacy']);
|
||||
const inviteeIds = formDataItems['inviteeIds'];
|
||||
const invitees = friends.filter(f => inviteeIds.includes(f.id));
|
||||
updateSessionInvitations(invitees);
|
||||
} catch (error) {
|
||||
console.error('localStorage is not available', error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = event => {
|
||||
event.preventDefault();
|
||||
const formData = new FormData(event.target);
|
||||
const payload = {
|
||||
privacy: formData.get('privacy'),
|
||||
description: formData.get('description'),
|
||||
inviteeIds: invitees.map(i => i.id)
|
||||
};
|
||||
console.log(payload); //TODO: handle payload
|
||||
|
||||
try {
|
||||
//store this payload in localstorage.
|
||||
localStorage.setItem('formData', JSON.stringify(payload))
|
||||
} catch (error) {
|
||||
console.error("localStorage is not available", error);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const handleSubmit = (event) => {
|
||||
event.preventDefault()
|
||||
const formData = new FormData(event.target)
|
||||
console.log(formData.get('privacy'));
|
||||
console.log(formData.get('friendIds'));
|
||||
console.log(formData.get('description'));
|
||||
const handleOnSelect = submittedItems => {
|
||||
updateSessionInvitations(submittedItems)
|
||||
};
|
||||
|
||||
const updateSessionInvitations = (submittedInvitees) => {
|
||||
const updatedInvitees = Array.from(new Set([...invitees, ...submittedInvitees]));
|
||||
setInvitees(updatedInvitees);
|
||||
|
||||
const friendIds = submittedInvitees.map(si => si.id)
|
||||
const updatedFriends = friends.filter(f => !friendIds.includes(f.id));
|
||||
setFriends(updatedFriends);
|
||||
}
|
||||
|
||||
const removeInvitee = invitee => {
|
||||
const updatedInvitees = invitees.filter(i => i.id !== invitee.id);
|
||||
setInvitees(updatedInvitees);
|
||||
const updatedFriends = [...friends, invitee];
|
||||
setFriends(updatedFriends);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Card>
|
||||
<FalconCardHeader title={t('page_title', { ns: 'people' })} titleClass="font-weight-bold" />
|
||||
<FalconCardHeader title={t('new.page_title', { ns: 'sessions' })} titleClass="font-weight-bold" />
|
||||
<CardBody className="pt-0">
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<FormGroup className="mb-3">
|
||||
<Label>
|
||||
Session Privacy{' '}
|
||||
<JKTooltip title="Public sessions can be seen in the Browse Sessions feature by other musicians in the JamKazam community, and others can join your session. If you choose the “Private - only invited musicians can join” option, your session will not be visible to the community as a whole, and only those musicians you invite will be able to see or join your session. If you choose the “Private – anyone can request to join, but requires approval” option, your session will be visible to the community in the Browse Sessions feature, and non-invited musicians may request to join your session, but you will have to grant permission per user to allow users into your session." />
|
||||
</Label>
|
||||
<Input type="select" aria-label="Session Privacy" name="privacy">
|
||||
<option value="1">Public – anyone can join</option>
|
||||
<option value="2">Private – only invited musicians can join</option>
|
||||
<option value="3">Private – anyone can request to join, but requires approval</option>
|
||||
</Input>
|
||||
</FormGroup>
|
||||
<Row>
|
||||
<Col>
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<FormGroup className="mb-3">
|
||||
<Label>
|
||||
{t('new.privacy', { ns: 'sessions' })}{' '}
|
||||
<JKTooltip title={t('new.privacy_help', { ns: 'sessions' })} />
|
||||
</Label>
|
||||
<Input type="select" aria-label="Session Privacy" name="privacy" value={privacy} onChange={(e) => setPrivacy(e.target.value)}>
|
||||
<option value="1">{t('new.privacy_opt_public', { ns: 'sessions' })}</option>
|
||||
<option value="2">{t('new.privacy_opt_private_invite', { ns: 'sessions' })}</option>
|
||||
<option value="3">{t('new.privacy_opt_private_approve', { ns: 'sessions' })}</option>
|
||||
</Input>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup className="mb-3">
|
||||
<Label>
|
||||
Session Invitations{' '}
|
||||
<JKTooltip title="If you invite other users to join your session, this will generate an in-app notification and in some cases also an email invitation to invitees. Invited users will also see your session in the “For Me” section of the Browse Sessions feature. Invited users can join your session without further permission or action on your part, regardless of whether your session is public or private." />
|
||||
</Label>
|
||||
<FormGroup className="mb-3">
|
||||
<Label>
|
||||
{t('new.invitations', { ns: 'sessions' })}{' '}
|
||||
<JKTooltip title={t('new.invitations_help', { ns: 'sessions' })} />
|
||||
</Label>
|
||||
<JKFriendsAutoComplete friends={friends} onSelect={handleOnSelect} />
|
||||
<JKSessionInviteesChips invitees={invitees} removeInvitee={removeInvitee} />
|
||||
</FormGroup>
|
||||
|
||||
<AsyncSelect
|
||||
name="friendIds"
|
||||
placeholder="Enter friend name"
|
||||
isMulti
|
||||
loadOptions={friendOptions}
|
||||
noOptionsMessage={e => (e.inputValue ? 'No options' : null)}
|
||||
components={{
|
||||
Option: CustomOption,
|
||||
DropdownIndicator: () => null,
|
||||
IndicatorSeparator: () => null
|
||||
}}
|
||||
styles={{
|
||||
control: (baseStyles, state) => ({
|
||||
...baseStyles,
|
||||
borderRadius: '1.25em'
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup className="mb-3">
|
||||
<Label>
|
||||
{t('new.description', { ns: 'sessions' })}{' '}
|
||||
<JKTooltip title={t('new.description_help', { ns: 'sessions' })} />
|
||||
</Label>
|
||||
<Input
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
name="description"
|
||||
type="textarea"
|
||||
placeholder={t('new.description_placeholder', { ns: 'sessions' })}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup className="mb-3">
|
||||
<Label>
|
||||
Session Description{' '}
|
||||
<JKTooltip title="If you’re creating a public session, we strongly recommend that you enter a description of your session – for example, what kinds of music you’re interested in playing. This description will be displayed next to your session in the Browse Sessions feature, which will help other musicians in the community understand if your session is a good fit for them." />
|
||||
</Label>
|
||||
<Input
|
||||
name="description"
|
||||
type="textarea"
|
||||
placeholder="Enter session description. Recommended for public sessions to attract other musicians and them know what
|
||||
to expect in your session."
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup className="mb-3">
|
||||
<Button color="primary">Create Session</Button>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
<FormGroup className="mb-3">
|
||||
<Button color="primary">{t('new.create_session', { ns: 'sessions' })}</Button>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
</Col>
|
||||
<Col />
|
||||
</Row>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,103 @@
|
|||
import React, { useState, useRef } from 'react';
|
||||
import { Input, InputGroup, InputGroupText, ListGroup, ListGroupItem, Row, Col, Button } from 'reactstrap';
|
||||
import JKSelectFriendsModal from './JKSelectFriendsModal';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
function JKFriendsAutoComplete({ friends, onSelect }) {
|
||||
const [filteredFriends, setFilteredFriends] = useState([]);
|
||||
const [showDropdown, setShowDropdown] = useState(false);
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const inputRef = useRef(null);
|
||||
const { t } = useTranslation();
|
||||
const MIN_FILTER_SIZE = 3;
|
||||
|
||||
const handleInputChange = e => {
|
||||
const val = e.target.value;
|
||||
setInputValue(val);
|
||||
if (val && val.length >= MIN_FILTER_SIZE) {
|
||||
const filtered = friends.filter(
|
||||
friend =>
|
||||
friend.first_name.toLowerCase().includes(val.toLowerCase()) ||
|
||||
friend.last_name.toLowerCase().includes(val.toLowerCase())
|
||||
);
|
||||
setFilteredFriends(filtered);
|
||||
setShowDropdown(true);
|
||||
} else {
|
||||
setShowDropdown(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleOnClick = friend => {
|
||||
onSelect([friend]);
|
||||
handleAfterSelect();
|
||||
};
|
||||
|
||||
const handleAfterSelect = () => {
|
||||
setShowDropdown(false);
|
||||
inputRef.current.focus();
|
||||
setInputValue('');
|
||||
};
|
||||
|
||||
const handleOnKeyDown = event => {
|
||||
if (event.key !== undefined) {
|
||||
if (event.key === 'Enter') {
|
||||
const first = filteredFriends[0];
|
||||
onSelect([first]);
|
||||
handleAfterSelect();
|
||||
} else if (event.key === 'ArrowDown') {
|
||||
console.log(event.target);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const highlight = event => {
|
||||
event.target.classList.add('bg-light');
|
||||
};
|
||||
|
||||
const unhighlight = event => {
|
||||
event.target.classList.remove('bg-light');
|
||||
};
|
||||
|
||||
const onSubmitFriendsModal = selectedFriends => {
|
||||
onSelect(selectedFriends);
|
||||
};
|
||||
|
||||
const toggleVisibility = (isVisible) => {
|
||||
setShowModal(isVisible)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Row className="mb-2">
|
||||
<Col md={9}>
|
||||
<InputGroup>
|
||||
<InputGroupText>
|
||||
<FontAwesomeIcon icon="search" transform="shrink-4 down-1" />
|
||||
</InputGroupText>
|
||||
|
||||
<Input onChange={handleInputChange} onKeyDown={handleOnKeyDown} value={inputValue} innerRef={inputRef} placeholder={t('new.invitations_filter_placeholder', { ns: 'sessions' })} />
|
||||
</InputGroup>
|
||||
</Col>
|
||||
<Col md={3}>
|
||||
<Button variant="outline-info" outline onClick={() => setShowModal(!showModal)}>
|
||||
{t('new.choose_friends', { ns: 'sessions' })}
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<ListGroup className={showDropdown ? 'd-block' : 'd-none'}>
|
||||
{filteredFriends.map(f => (
|
||||
<ListGroupItem key={f.id} onMouseOver={highlight} onMouseOut={unhighlight} onClick={() => handleOnClick(f)}>
|
||||
{f.first_name} {f.last_name}
|
||||
</ListGroupItem>
|
||||
))}
|
||||
</ListGroup>
|
||||
|
||||
<JKSelectFriendsModal friends={friends} show={showModal} toggleVisibility={toggleVisibility} onSubmit={onSubmitFriendsModal} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default JKFriendsAutoComplete;
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { Button, Modal, ModalHeader, ModalBody, ModalFooter, ListGroup, ListGroupItem, Input } from 'reactstrap';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
function JKSelectFriendsModal({ show, friends, onSubmit, toggleVisibility }) {
|
||||
const [modal, setModal] = useState(false);
|
||||
const [selected, setSelected] = useState([])
|
||||
const { t } = useTranslation();
|
||||
|
||||
const toggle = () => {
|
||||
toggleVisibility(!modal)
|
||||
setModal(!modal);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setModal(show);
|
||||
}, [show]);
|
||||
|
||||
const onCheckBoxClick = (event) => {
|
||||
const friend = friends.find(f => f.id === event.target.value)
|
||||
if(event.target.checked){
|
||||
const updated = Array.from(new Set([...selected, friend]))
|
||||
setSelected(updated)
|
||||
}else{
|
||||
const updated = selected.filter(s => s.id !== friend.id)
|
||||
setSelected(updated)
|
||||
}
|
||||
}
|
||||
|
||||
const onButtonClick = () => {
|
||||
onSubmit(selected)
|
||||
setSelected([])
|
||||
toggle()
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Modal isOpen={modal} toggle={toggle} scrollable={true}>
|
||||
<ModalHeader toggle={toggle}>Invite Friends to Session</ModalHeader>
|
||||
<ModalBody>
|
||||
<ListGroup flush>
|
||||
{friends.map(f => (
|
||||
<ListGroupItem key={f.id}>
|
||||
<Input type="checkbox" onClick={onCheckBoxClick} value={f.id} />
|
||||
{f.first_name} {f.last_name}
|
||||
</ListGroupItem>
|
||||
))}
|
||||
</ListGroup>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="secondary" outline onClick={toggle}>
|
||||
{t('new.cancel', { ns: 'sessions' })}
|
||||
</Button>{' '}
|
||||
<Button color="primary" onClick={onButtonClick}>
|
||||
{t('new.add_friends', { ns: 'sessions' })}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default JKSelectFriendsModal;
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import React from 'react';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faTimesCircle } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
function JKSessionInviteesChips({ invitees, removeInvitee }) {
|
||||
const remove = (event, invitee) => {
|
||||
event.preventDefault();
|
||||
removeInvitee(invitee);
|
||||
}
|
||||
return (
|
||||
<div className="chip-container">
|
||||
{invitees &&
|
||||
invitees.map(i => (
|
||||
<div key={i.id} className="basic-chip background-white">
|
||||
{ i.first_name} {i.last_name }
|
||||
<a onClick={(e) => remove(e, i)}>
|
||||
<FontAwesomeIcon icon={faTimesCircle} />
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default JKSessionInviteesChips;
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
import React, { useRef, useState } from 'react';
|
||||
|
||||
const KEY_CODES = {
|
||||
DOWN: 40,
|
||||
UP: 38,
|
||||
PAGE_DOWN: 34,
|
||||
ESCAPE: 27,
|
||||
PAGE_UP: 33,
|
||||
ENTER: 13
|
||||
};
|
||||
|
||||
export default function useAutoComplete({ delay = 500, source, onChange }) {
|
||||
const [myTimeout, setMyTimeOut] = useState(setTimeout(() => {}, 0));
|
||||
const listRef = useRef();
|
||||
const [suggestions, setSuggestions] = useState([]);
|
||||
const [isBusy, setBusy] = useState(false);
|
||||
const [selectedIndex, setSelectedIndex] = useState(-1);
|
||||
const [textValue, setTextValue] = useState('');
|
||||
|
||||
function delayInvoke(cb) {
|
||||
if (myTimeout) {
|
||||
clearTimeout(myTimeout);
|
||||
}
|
||||
setMyTimeOut(setTimeout(cb, delay));
|
||||
}
|
||||
|
||||
function selectOption(index) {
|
||||
if (index > -1) {
|
||||
onChange(suggestions[index]);
|
||||
setTextValue(suggestions[index].label);
|
||||
}
|
||||
clearSuggestions();
|
||||
}
|
||||
|
||||
async function getSuggestions(searchTerm) {
|
||||
if (searchTerm && source) {
|
||||
const options = await source(searchTerm);
|
||||
setSuggestions(options);
|
||||
}
|
||||
}
|
||||
|
||||
function clearSuggestions() {
|
||||
setSuggestions([]);
|
||||
setSelectedIndex(-1);
|
||||
}
|
||||
|
||||
function onTextChange(searchTerm) {
|
||||
setBusy(true);
|
||||
setTextValue(searchTerm);
|
||||
clearSuggestions();
|
||||
delayInvoke(() => {
|
||||
getSuggestions(searchTerm);
|
||||
setBusy(false);
|
||||
});
|
||||
}
|
||||
|
||||
const optionHeight = listRef?.current?.children[0]?.clientHeight;
|
||||
|
||||
function scrollUp() {
|
||||
if (selectedIndex > 0) {
|
||||
setSelectedIndex(selectedIndex - 1);
|
||||
}
|
||||
//listRef?.current?.scrollTop -= optionHeight
|
||||
}
|
||||
|
||||
function scrollDown() {
|
||||
if (selectedIndex < suggestions.length - 1) {
|
||||
setSelectedIndex(selectedIndex + 1);
|
||||
}
|
||||
//listRef?.current?.scrollTop = selectedIndex * optionHeight
|
||||
}
|
||||
|
||||
function pageDown() {
|
||||
setSelectedIndex(suggestions.length - 1);
|
||||
//listRef?.current?.scrollTop = suggestions.length * optionHeight
|
||||
}
|
||||
|
||||
function pageUp() {
|
||||
setSelectedIndex(0);
|
||||
//listRef?.current?.scrollTop = 0
|
||||
}
|
||||
|
||||
function onKeyDown(e) {
|
||||
const keyOperation = {
|
||||
[KEY_CODES.DOWN]: scrollDown,
|
||||
[KEY_CODES.UP]: scrollUp,
|
||||
[KEY_CODES.ENTER]: () => selectOption(selectedIndex),
|
||||
[KEY_CODES.ESCAPE]: clearSuggestions,
|
||||
[KEY_CODES.PAGE_DOWN]: pageDown,
|
||||
[KEY_CODES.PAGE_UP]: pageUp
|
||||
};
|
||||
if (keyOperation[e.keyCode]) {
|
||||
keyOperation[e.keyCode]();
|
||||
} else {
|
||||
setSelectedIndex(-1);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
bindOption: {
|
||||
onClick: e => {
|
||||
let nodes = Array.from(listRef.current.children);
|
||||
selectOption(nodes.indexOf(e.target.closest('li')));
|
||||
}
|
||||
},
|
||||
bindInput: {
|
||||
value: textValue,
|
||||
onChange: e => onTextChange(e.target.value),
|
||||
onKeyDown
|
||||
},
|
||||
bindOptions: {
|
||||
ref: listRef
|
||||
},
|
||||
isBusy,
|
||||
suggestions,
|
||||
selectedIndex
|
||||
};
|
||||
}
|
||||
|
|
@ -5,11 +5,13 @@ import commonTranslationsEN from './locales/en/common.json'
|
|||
import homeTranslationsEN from './locales/en/home.json'
|
||||
import peopleTranslationsEN from './locales/en/people.json'
|
||||
import authTranslationsEN from './locales/en/auth.json'
|
||||
import sessTranslationsEN from './locales/en/sessions.json'
|
||||
|
||||
import commonTranslationsES from './locales/es/common.json'
|
||||
import homeTranslationsES from './locales/es/home.json'
|
||||
import peopleTranslationsES from './locales/es/people.json'
|
||||
import authTranslationsES from './locales/es/auth.json'
|
||||
import sessTranslationsES from './locales/es/sessions.json'
|
||||
|
||||
i18n.use(initReactI18next).init({
|
||||
fallbackLng: 'en',
|
||||
|
|
@ -20,14 +22,16 @@ i18n.use(initReactI18next).init({
|
|||
common: commonTranslationsEN,
|
||||
home: homeTranslationsEN,
|
||||
people: peopleTranslationsEN,
|
||||
auth: authTranslationsEN
|
||||
auth: authTranslationsEN,
|
||||
sessions: sessTranslationsEN,
|
||||
},
|
||||
es: {
|
||||
//translations: require('./locales/es/translations.json')
|
||||
common: commonTranslationsES,
|
||||
home: homeTranslationsES,
|
||||
people: peopleTranslationsES,
|
||||
auth: authTranslationsES
|
||||
auth: authTranslationsES,
|
||||
sessions: sessTranslationsES,
|
||||
}
|
||||
},
|
||||
//ns: ['translations'],
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"new": {
|
||||
"page_title": "Create Session",
|
||||
"privacy": "Session Privacy",
|
||||
"privacy_help": "Public sessions can be seen in the Browse Sessions feature by other musicians in the JamKazam community, and others can join your session. If you choose the “Private - only invited musicians can join” option, your session will not be visible to the community as a whole, and only those musicians you invite will be able to see or join your session. If you choose the “Private – anyone can request to join, but requires approval” option, your session will be visible to the community in the Browse Sessions feature, and non-invited musicians may request to join your session, but you will have to grant permission per user to allow users into your session.",
|
||||
"privacy_opt_public": "Public – anyone can join",
|
||||
"privacy_opt_private_invite": "Private – only invited musicians can join",
|
||||
"privacy_opt_private_approve": "Private – anyone can request to join, but requires approval",
|
||||
"invitations": "Session Invitations",
|
||||
"invitations_help": "If you invite other users to join your session, this will generate an in-app notification and in some cases also an email invitation to invitees. Invited users will also see your session in the “For Me” section of the Browse Sessions feature. Invited users can join your session without further permission or action on your part, regardless of whether your session is public or private.",
|
||||
"invitations_filter_placeholder": "Enter friend name",
|
||||
"choose_friends": "Choose Friends",
|
||||
"add_friends": "Add Friends",
|
||||
"cancel": "Cancel",
|
||||
"description": "Session Description",
|
||||
"description_help": "If you’re creating a public session, we strongly recommend that you enter a description of your session – for example, what kinds of music you’re interested in playing. This description will be displayed next to your session in the Browse Sessions feature, which will help other musicians in the community understand if your session is a good fit for them.",
|
||||
"description_placeholder": "Enter session description. Recommended for public sessions to attract other musicians and them know what to expect in your session.",
|
||||
"create_session": "Create Session"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"page_title": "Página de inicio"
|
||||
}
|
||||
Loading…
Reference in New Issue