Add Availability component
This commit is contained in:
parent
fe1dafae3c
commit
036bbcb396
@ -8,6 +8,8 @@
|
|||||||
"@fullcalendar/interaction": "^5.0.0-beta.4",
|
"@fullcalendar/interaction": "^5.0.0-beta.4",
|
||||||
"@fullcalendar/luxon": "^4.4.2",
|
"@fullcalendar/luxon": "^4.4.2",
|
||||||
"@fullcalendar/react": "^5.0.0-beta.4",
|
"@fullcalendar/react": "^5.0.0-beta.4",
|
||||||
|
"@fullcalendar/scrollgrid": "5.0.0-beta.4",
|
||||||
|
"@fullcalendar/timegrid": "5.0.0-beta.4",
|
||||||
"@testing-library/jest-dom": "^4.2.4",
|
"@testing-library/jest-dom": "^4.2.4",
|
||||||
"@testing-library/react": "^9.3.2",
|
"@testing-library/react": "^9.3.2",
|
||||||
"@testing-library/user-event": "^7.1.2",
|
"@testing-library/user-event": "^7.1.2",
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
|
|
||||||
export default function IntervalSelector() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
It's empty for now
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
153
src/components/Interval/IntervalSelector.js
Normal file
153
src/components/Interval/IntervalSelector.js
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
|
||||||
|
import { Icon, IconButton } from 'rsuite';
|
||||||
|
|
||||||
|
import FullCalendar from '@fullcalendar/react';
|
||||||
|
import dayGridPlugin from '@fullcalendar/daygrid';
|
||||||
|
import timeGridPlugin from '@fullcalendar/timegrid';
|
||||||
|
import interaction from '@fullcalendar/interaction';
|
||||||
|
import scrollGrid from '@fullcalendar/scrollgrid';
|
||||||
|
|
||||||
|
// FullCalendar props
|
||||||
|
const dayMinWidth = 110;
|
||||||
|
const slotMinTime = '08:00:00';
|
||||||
|
const slotMaxTime = '18:00:00';
|
||||||
|
|
||||||
|
export default function IntervalSelector({
|
||||||
|
selectedDates,
|
||||||
|
handleSelect,
|
||||||
|
availability,
|
||||||
|
setAvailability,
|
||||||
|
}) {
|
||||||
|
/// DATES DISPLAY
|
||||||
|
let startDate = selectedDates[0].start;
|
||||||
|
// create a Datetime object from the last event date,
|
||||||
|
// add one day, and change it to a date string
|
||||||
|
let lastEventDate = selectedDates[selectedDates.length - 1].start;
|
||||||
|
let endDate = DateTime.fromFormat(lastEventDate, 'yyyy-MM-dd')
|
||||||
|
.plus({ days: 1 })
|
||||||
|
.toFormat('yyyy-MM-dd');
|
||||||
|
|
||||||
|
// Create an event dates list used to create the days columns
|
||||||
|
const daysList = [];
|
||||||
|
selectedDates.forEach((event) => {
|
||||||
|
daysList.push(event.start);
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleDayDidMount = (info) => {
|
||||||
|
let currentDate = DateTime.fromJSDate(info.date).toFormat('yyyy-MM-dd');
|
||||||
|
if (!daysList.includes(currentDate)) {
|
||||||
|
info.el.remove();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleViewMount = ({ el }) => {
|
||||||
|
// Adapt column width to number of dates present
|
||||||
|
const timegridBody = el.querySelectorAll('div.fc-timegrid-body');
|
||||||
|
const timegridSlots = el.querySelectorAll(
|
||||||
|
'div.fc-timegrid-slots table',
|
||||||
|
);
|
||||||
|
// Update width based on rendered dates number
|
||||||
|
let newWidth = daysList.length * dayMinWidth;
|
||||||
|
timegridBody[0].style['min-width'] = `${newWidth}px`;
|
||||||
|
timegridSlots[0].style['min-width'] = `${newWidth}px`;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// AVAILABILITY INTERACTION
|
||||||
|
// Create an array of stringified availability
|
||||||
|
let avListJson = [];
|
||||||
|
availability.forEach((event) => {
|
||||||
|
avListJson.push(JSON.stringify(event));
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleClick = (eventStart, eventEnd) => {
|
||||||
|
let updatedAvailibility = [];
|
||||||
|
// Create an event object to compare
|
||||||
|
let currentAvailability = {
|
||||||
|
start: eventStart,
|
||||||
|
end: eventEnd,
|
||||||
|
};
|
||||||
|
// Make it to JSON
|
||||||
|
let currentAvJson = JSON.stringify(currentAvailability);
|
||||||
|
|
||||||
|
// Check if there's a similar event obj in the availability
|
||||||
|
let index = avListJson.findIndex(
|
||||||
|
(availability) => currentAvJson === availability,
|
||||||
|
);
|
||||||
|
if (index > -1) {
|
||||||
|
updatedAvailibility = [...availability];
|
||||||
|
updatedAvailibility.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
setAvailability([...updatedAvailibility]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleContent = ({ event }) => {
|
||||||
|
let eventStart = event.start;
|
||||||
|
let eventEnd = event.end;
|
||||||
|
|
||||||
|
let evStartDT = DateTime.fromJSDate(eventStart);
|
||||||
|
let evEndDT = DateTime.fromJSDate(eventEnd);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
flexDirection: 'row',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
{evStartDT.toFormat('HH:mm')} -{' '}
|
||||||
|
{evEndDT.toFormat('HH:mm')}
|
||||||
|
</p>
|
||||||
|
<IconButton
|
||||||
|
className={'event-close'}
|
||||||
|
icon={<Icon icon='close' />}
|
||||||
|
appearance="default"
|
||||||
|
size="xs"
|
||||||
|
circle
|
||||||
|
onClick={() => handleClick(eventStart, eventEnd)}
|
||||||
|
></IconButton>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FullCalendar
|
||||||
|
plugins={[dayGridPlugin, interaction, timeGridPlugin, scrollGrid]}
|
||||||
|
// timeZone={'UTC'}
|
||||||
|
events={availability}
|
||||||
|
// View props
|
||||||
|
headerToolbar={{ right: '' }}
|
||||||
|
initialView='timeGrid'
|
||||||
|
visibleRange={{
|
||||||
|
start: startDate,
|
||||||
|
end: endDate,
|
||||||
|
}}
|
||||||
|
allDaySlot={false}
|
||||||
|
dayMinWidth={dayMinWidth}
|
||||||
|
dayHeaderFormat={{
|
||||||
|
weekday: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
omitCommas: true,
|
||||||
|
}}
|
||||||
|
slotMinTime={slotMinTime}
|
||||||
|
slotMaxTime={slotMaxTime}
|
||||||
|
dayCellDidMount={(arg) => handleDayDidMount(arg)}
|
||||||
|
dayHeaderDidMount={(arg) => handleDayDidMount(arg)}
|
||||||
|
viewDidMount={(arg) => handleViewMount(arg)}
|
||||||
|
// Interaction props
|
||||||
|
selectable={true}
|
||||||
|
unselectAuto={true}
|
||||||
|
selectOverlap={false}
|
||||||
|
select={(info) => handleSelect(info)}
|
||||||
|
longPressDelay={150}
|
||||||
|
eventContent={(arg) => handleContent(arg)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -18,7 +18,7 @@ export default function MenuDropdown() {
|
|||||||
<Dropdown.Item eventKey={'login'}>Login</Dropdown.Item>
|
<Dropdown.Item eventKey={'login'}>Login</Dropdown.Item>
|
||||||
<Dropdown.Item eventKey={'register'}>Register</Dropdown.Item>
|
<Dropdown.Item eventKey={'register'}>Register</Dropdown.Item>
|
||||||
<Dropdown.Item eventKey={'/'}>Dashboard</Dropdown.Item>
|
<Dropdown.Item eventKey={'/'}>Dashboard</Dropdown.Item>
|
||||||
<Dropdown.Item eventKey={3}>Schedule a Meeting</Dropdown.Item>
|
<Dropdown.Item eventKey={'schedule'}>Schedule a Meeting</Dropdown.Item>
|
||||||
<Dropdown.Item eventKey={4}>Account Settings</Dropdown.Item>
|
<Dropdown.Item eventKey={4}>Account Settings</Dropdown.Item>
|
||||||
<Dropdown.Item eventKey={5}>Log Out</Dropdown.Item>
|
<Dropdown.Item eventKey={5}>Log Out</Dropdown.Item>
|
||||||
</Dropdown.Menu>
|
</Dropdown.Menu>
|
||||||
|
@ -9,18 +9,18 @@ export default function DaySelector({ eventsList, handleSelect, handleClear }) {
|
|||||||
return (
|
return (
|
||||||
<FullCalendar
|
<FullCalendar
|
||||||
plugins={[dayGridPlugin, interaction]}
|
plugins={[dayGridPlugin, interaction]}
|
||||||
initialView='dayGridMonth'
|
|
||||||
// showNonCurrentDates={false}
|
|
||||||
selectable={true}
|
|
||||||
// unselectAuto={false}
|
|
||||||
longPressDelay={150}
|
|
||||||
select={(info) => handleSelect(info)}
|
|
||||||
defaultAllDay={true}
|
|
||||||
events={eventsList}
|
events={eventsList}
|
||||||
|
// view props
|
||||||
|
initialView='dayGridMonth'
|
||||||
|
defaultAllDay={true}
|
||||||
customButtons={{
|
customButtons={{
|
||||||
resetSelection: { text: 'clear selection', click: handleClear },
|
resetSelection: { text: 'clear selection', click: handleClear },
|
||||||
}}
|
}}
|
||||||
headerToolbar={{ right: 'resetSelection today prev,next' }}
|
headerToolbar={{ right: 'resetSelection today prev,next' }}
|
||||||
|
// interaction props
|
||||||
|
selectable={true}
|
||||||
|
longPressDelay={150}
|
||||||
|
select={(info) => handleSelect(info)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Panel,
|
Panel,
|
||||||
Form,
|
Form,
|
||||||
@ -9,43 +10,32 @@ import {
|
|||||||
Message,
|
Message,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
} from 'rsuite';
|
} from 'rsuite';
|
||||||
import { DateTime } from 'luxon';
|
|
||||||
|
|
||||||
import NavBar from '../../components/Navbar/NavBar';
|
import NavBar from '../../components/Navbar/NavBar';
|
||||||
import TimezonePicker from '../../components/General/TimezonePicker';
|
import TimezonePicker from '../../components/General/TimezonePicker';
|
||||||
import IntervalSelector from '../../components/Availability/IntervalSelector';
|
import IntervalSelector from '../../components/Interval/IntervalSelector';
|
||||||
|
|
||||||
import './Availability.less';
|
import './Availability.less';
|
||||||
|
|
||||||
const eventsList = [
|
|
||||||
{
|
|
||||||
start: '2020-06-10',
|
|
||||||
display: 'background',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
start: '2020-06-11',
|
|
||||||
display: 'background',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
start: '2020-06-12',
|
|
||||||
display: 'background',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
start: '2020-06-13',
|
|
||||||
display: 'background',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function Availability({ title }) {
|
export default function Availability({ title }) {
|
||||||
// const [eventsList, setEventsList] = useState([]);
|
const [availability, setAvailability] = useState([...availabilityList]);
|
||||||
const [availability, setAvailability] = useState([]);
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log(availability);
|
||||||
|
}, [availability]);
|
||||||
|
|
||||||
const handleClear = () => {
|
const handleClear = () => {
|
||||||
setAvailability([]);
|
setAvailability([]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSelect = () => {
|
const handleSelect = ({ start, end }) => {
|
||||||
console.log('Congrats, you have selected!');
|
let updatedEvents = [];
|
||||||
|
let newAvailability = {
|
||||||
|
start: start,
|
||||||
|
end: end,
|
||||||
|
};
|
||||||
|
updatedEvents.push(newAvailability);
|
||||||
|
setAvailability([...availability, ...updatedEvents]);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -95,7 +85,9 @@ export default function Availability({ title }) {
|
|||||||
description='Select your availibility on the calendar.'
|
description='Select your availibility on the calendar.'
|
||||||
/>
|
/>
|
||||||
<IntervalSelector
|
<IntervalSelector
|
||||||
eventsList={eventsList}
|
selectedDates={eventsList}
|
||||||
|
availability={availability}
|
||||||
|
setAvailability={setAvailability}
|
||||||
handleSelect={handleSelect}
|
handleSelect={handleSelect}
|
||||||
handleClear={handleClear}
|
handleClear={handleClear}
|
||||||
/>
|
/>
|
||||||
@ -106,18 +98,76 @@ export default function Availability({ title }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert events to Luxon Datetime objects
|
|
||||||
const eventsToDates = (events) => {
|
|
||||||
let dates = [];
|
|
||||||
events.forEach((event) => {
|
|
||||||
dates.push(DateTime.fromFormat(event.start, 'yyyy-MM-dd'));
|
|
||||||
});
|
|
||||||
return dates;
|
|
||||||
};
|
|
||||||
|
|
||||||
const containerStyle = {
|
const containerStyle = {
|
||||||
// TODO Move to a .less file
|
// TODO Move to a .less file
|
||||||
maxWidth: '1200px',
|
maxWidth: '1200px',
|
||||||
margin: '30px auto',
|
margin: '30px auto',
|
||||||
backgroundColor: 'rgba(255,255,255,0.6)',
|
backgroundColor: 'rgba(255,255,255,0.6)',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO Remove fake data
|
||||||
|
const eventsList = [
|
||||||
|
{
|
||||||
|
start: '2020-06-05',
|
||||||
|
display: 'background',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start: '2020-06-06',
|
||||||
|
display: 'background',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start: '2020-06-08',
|
||||||
|
display: 'background',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start: '2020-06-10',
|
||||||
|
display: 'background',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start: '2020-06-11',
|
||||||
|
display: 'background',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start: '2020-06-21',
|
||||||
|
display: 'background',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start: '2020-06-29',
|
||||||
|
display: 'background',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const availabilityList = [
|
||||||
|
{
|
||||||
|
start: '2020-06-08T08:00:00.000Z',
|
||||||
|
end: '2020-06-08T12:00:00.000Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start: '2020-06-10T09:00:00.000Z',
|
||||||
|
end: '2020-06-10T13:00:00.000Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start: '2020-06-11T09:00:00.000Z',
|
||||||
|
end: '2020-06-11T13:00:00.000Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start: '2020-06-11T06:30:00.000Z',
|
||||||
|
end: '2020-06-11T08:30:00.000Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start: '2020-06-21T07:00:00.000Z',
|
||||||
|
end: '2020-06-21T11:30:00.000Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start: '2020-06-21T12:00:00.000Z',
|
||||||
|
end: '2020-06-21T15:30:00.000Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start: '2020-06-06T08:00:00.000Z',
|
||||||
|
end: '2020-06-06T14:00:00.000Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start: '2020-06-05T10:30:00.000Z',
|
||||||
|
end: '2020-06-05T14:30:00.000Z',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { Panel, Form, FormGroup, FormControl, ControlLabel, HelpBlock, Button } from 'rsuite';
|
import { Panel, Form, FormGroup, FormControl, ControlLabel, Button } from 'rsuite';
|
||||||
|
|
||||||
import NavBar from './../components/Navbar/NavBar';
|
import NavBar from './../components/Navbar/NavBar';
|
||||||
|
|
||||||
|
28
yarn.lock
28
yarn.lock
@ -1061,7 +1061,7 @@
|
|||||||
preact "^10.0.5"
|
preact "^10.0.5"
|
||||||
tslib "^1.9.3"
|
tslib "^1.9.3"
|
||||||
|
|
||||||
"@fullcalendar/daygrid@^5.0.0-beta.4":
|
"@fullcalendar/daygrid@5.0.0-beta.4", "@fullcalendar/daygrid@^5.0.0-beta.4":
|
||||||
version "5.0.0-beta.4"
|
version "5.0.0-beta.4"
|
||||||
resolved "https://registry.yarnpkg.com/@fullcalendar/daygrid/-/daygrid-5.0.0-beta.4.tgz#b5616fddfe9a5496e5e98f20d6fafdcb6393a42e"
|
resolved "https://registry.yarnpkg.com/@fullcalendar/daygrid/-/daygrid-5.0.0-beta.4.tgz#b5616fddfe9a5496e5e98f20d6fafdcb6393a42e"
|
||||||
integrity sha512-ZWCF4dUCDngPqOJ3iUGl/ZiSOah8mgo9fayPR3WLazGMoQTsSAqMBGSuhFaNNdMDWQ+dhcT2JBa8e9DNoe2S+g==
|
integrity sha512-ZWCF4dUCDngPqOJ3iUGl/ZiSOah8mgo9fayPR3WLazGMoQTsSAqMBGSuhFaNNdMDWQ+dhcT2JBa8e9DNoe2S+g==
|
||||||
@ -1082,6 +1082,14 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@fullcalendar/luxon/-/luxon-4.4.2.tgz#27b4913abd6fb323da8fda63e1bd03df96b21815"
|
resolved "https://registry.yarnpkg.com/@fullcalendar/luxon/-/luxon-4.4.2.tgz#27b4913abd6fb323da8fda63e1bd03df96b21815"
|
||||||
integrity sha512-NQFPsdDAyk152JzsyNMrfZWpnZUTxCrP3to/0es8Eoa97i/UxjxB5+N0SVwJqcNO0rQpaLHmeg5piHhraABShQ==
|
integrity sha512-NQFPsdDAyk152JzsyNMrfZWpnZUTxCrP3to/0es8Eoa97i/UxjxB5+N0SVwJqcNO0rQpaLHmeg5piHhraABShQ==
|
||||||
|
|
||||||
|
"@fullcalendar/premium-common@5.0.0-beta.4":
|
||||||
|
version "5.0.0-beta.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@fullcalendar/premium-common/-/premium-common-5.0.0-beta.4.tgz#fd6571f2cba1026a9a42da8385c6e8febfdc7065"
|
||||||
|
integrity sha512-ZxmlzzjKAVy/xPP/HKkgxit0utPmHyjLw8Zj83KhmWHM7DRo9Eko0rNrSm/gDvsk0YkWpoiJRKcRRu022ulzLQ==
|
||||||
|
dependencies:
|
||||||
|
"@fullcalendar/common" "5.0.0-beta.4"
|
||||||
|
tslib "^1.9.3"
|
||||||
|
|
||||||
"@fullcalendar/react@^5.0.0-beta.4":
|
"@fullcalendar/react@^5.0.0-beta.4":
|
||||||
version "5.0.0-beta.4"
|
version "5.0.0-beta.4"
|
||||||
resolved "https://registry.yarnpkg.com/@fullcalendar/react/-/react-5.0.0-beta.4.tgz#9c721093bfb49f938469f93960a10894623e152c"
|
resolved "https://registry.yarnpkg.com/@fullcalendar/react/-/react-5.0.0-beta.4.tgz#9c721093bfb49f938469f93960a10894623e152c"
|
||||||
@ -1090,6 +1098,24 @@
|
|||||||
"@fullcalendar/common" "5.0.0-beta.4"
|
"@fullcalendar/common" "5.0.0-beta.4"
|
||||||
tslib "^1.9.3"
|
tslib "^1.9.3"
|
||||||
|
|
||||||
|
"@fullcalendar/scrollgrid@5.0.0-beta.4":
|
||||||
|
version "5.0.0-beta.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@fullcalendar/scrollgrid/-/scrollgrid-5.0.0-beta.4.tgz#e4651af5f44b0156aa838b8a292358da9897d41c"
|
||||||
|
integrity sha512-E9pkjqW5+BDWXo07SBaHei7VMNR5OozzRXFGDRMU1W/u+BzMP4IypBp0M8wDtL78IoJK3fNsv49vOWsHi4x7Mg==
|
||||||
|
dependencies:
|
||||||
|
"@fullcalendar/common" "5.0.0-beta.4"
|
||||||
|
"@fullcalendar/premium-common" "5.0.0-beta.4"
|
||||||
|
tslib "^1.9.3"
|
||||||
|
|
||||||
|
"@fullcalendar/timegrid@5.0.0-beta.4":
|
||||||
|
version "5.0.0-beta.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@fullcalendar/timegrid/-/timegrid-5.0.0-beta.4.tgz#e31b92772705738191617c9f1af4b91d27fcf8c6"
|
||||||
|
integrity sha512-kdI3NQ0Yt7bPxn0v6d01AyQeW5gKVPgGA8J2hsTpITkN/eaQJUxrmmOGEFhSVwvvJU0m+ji71Z30eKESNs/leA==
|
||||||
|
dependencies:
|
||||||
|
"@fullcalendar/common" "5.0.0-beta.4"
|
||||||
|
"@fullcalendar/daygrid" "5.0.0-beta.4"
|
||||||
|
tslib "^1.9.3"
|
||||||
|
|
||||||
"@hapi/address@2.x.x":
|
"@hapi/address@2.x.x":
|
||||||
version "2.1.4"
|
version "2.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5"
|
resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5"
|
||||||
|
Loading…
Reference in New Issue
Block a user