Rename main folders and write sql backend adaptor
This commit is contained in:
parent
1d34f8e06d
commit
fdc58b428b
212 changed files with 3577 additions and 4775 deletions
167
frontend/src/components/GoogleCalendar/GoogleCalendar.jsx
Normal file
167
frontend/src/components/GoogleCalendar/GoogleCalendar.jsx
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
import { useState, useEffect } from 'react'
|
||||
import { loadGapiInsideDOM } from 'gapi-script'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { Button, Center } from '/src/components'
|
||||
import { Loader } from '../Loading/Loading.styles'
|
||||
import {
|
||||
CalendarList,
|
||||
CheckboxInput,
|
||||
CheckboxLabel,
|
||||
CalendarLabel,
|
||||
Info,
|
||||
Options,
|
||||
Title,
|
||||
Icon,
|
||||
LinkButton,
|
||||
} from './GoogleCalendar.styles'
|
||||
|
||||
import googleLogo from '/src/res/google.svg'
|
||||
|
||||
const signIn = () => window.gapi.auth2.getAuthInstance().signIn()
|
||||
|
||||
const signOut = () => window.gapi.auth2.getAuthInstance().signOut()
|
||||
|
||||
const GoogleCalendar = ({ timeZone, timeMin, timeMax, onImport }) => {
|
||||
const [signedIn, setSignedIn] = useState(undefined)
|
||||
const [calendars, setCalendars] = useState(undefined)
|
||||
const [freeBusyLoading, setFreeBusyLoading] = useState(false)
|
||||
const { t } = useTranslation('event')
|
||||
|
||||
const calendarLogin = async () => {
|
||||
const gapi = await loadGapiInsideDOM()
|
||||
gapi.load('client:auth2', () => {
|
||||
window.gapi.client.init({
|
||||
clientId: '276505195333-9kjl7e48m272dljbspkobctqrpet0n8m.apps.googleusercontent.com',
|
||||
discoveryDocs: ['https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest'],
|
||||
scope: 'https://www.googleapis.com/auth/calendar.readonly',
|
||||
})
|
||||
.then(() => {
|
||||
// Listen for state changes
|
||||
window.gapi.auth2.getAuthInstance().isSignedIn.listen(isSignedIn => setSignedIn(isSignedIn))
|
||||
|
||||
// Handle initial sign-in state
|
||||
setSignedIn(window.gapi.auth2.getAuthInstance().isSignedIn.get())
|
||||
})
|
||||
.catch(e => {
|
||||
console.error(e)
|
||||
setSignedIn(false)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const importAvailability = () => {
|
||||
setFreeBusyLoading(true)
|
||||
gtag('event', 'google_cal_sync', {
|
||||
'event_category': 'event',
|
||||
})
|
||||
window.gapi.client.calendar.freebusy.query({
|
||||
timeMin,
|
||||
timeMax,
|
||||
timeZone,
|
||||
items: calendars.filter(c => c.checked).map(c => ({id: c.id})),
|
||||
})
|
||||
.then(response => {
|
||||
onImport(response.result.calendars ? Object.values(response.result.calendars).reduce((busy, c) => [...busy, ...c.busy], []) : [])
|
||||
setFreeBusyLoading(false)
|
||||
}, e => {
|
||||
console.error(e)
|
||||
setFreeBusyLoading(false)
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => void calendarLogin(), [])
|
||||
|
||||
useEffect(() => {
|
||||
if (signedIn) {
|
||||
window.gapi.client.calendar.calendarList.list({
|
||||
'minAccessRole': 'freeBusyReader'
|
||||
})
|
||||
.then(response => {
|
||||
setCalendars(response.result.items.map(item => ({
|
||||
'name': item.summary,
|
||||
'description': item.description,
|
||||
'id': item.id,
|
||||
'color': item.backgroundColor,
|
||||
'checked': item.primary === true,
|
||||
})))
|
||||
})
|
||||
.catch(e => {
|
||||
console.error(e)
|
||||
signOut()
|
||||
})
|
||||
}
|
||||
}, [signedIn])
|
||||
|
||||
return (
|
||||
<>
|
||||
{!signedIn ? (
|
||||
<Center>
|
||||
<Button
|
||||
onClick={() => signIn()}
|
||||
isLoading={signedIn === undefined}
|
||||
primaryColor="#4286F5"
|
||||
secondaryColor="#3367BD"
|
||||
icon={<img aria-hidden="true" focusable="false" src={googleLogo} alt="" />}
|
||||
>
|
||||
{t('event:you.google_cal.login')}
|
||||
</Button>
|
||||
</Center>
|
||||
) : (
|
||||
<CalendarList>
|
||||
<Title>
|
||||
<Icon src={googleLogo} alt="" />
|
||||
<strong>{t('event:you.google_cal.login')}</strong>
|
||||
(<LinkButton type="button" onClick={e => {
|
||||
e.preventDefault()
|
||||
signOut()
|
||||
}}>{t('event:you.google_cal.logout')}</LinkButton>)
|
||||
</Title>
|
||||
<Options>
|
||||
{calendars !== undefined && !calendars.every(c => c.checked) && (
|
||||
<LinkButton type="button" onClick={e => {
|
||||
e.preventDefault()
|
||||
setCalendars(calendars.map(c => ({...c, checked: true})))
|
||||
}}>{t('event:you.google_cal.select_all')}</LinkButton>
|
||||
)}
|
||||
{calendars !== undefined && calendars.every(c => c.checked) && (
|
||||
<LinkButton type="button" onClick={e => {
|
||||
e.preventDefault()
|
||||
setCalendars(calendars.map(c => ({...c, checked: false})))
|
||||
}}>{t('event:you.google_cal.select_none')}</LinkButton>
|
||||
)}
|
||||
</Options>
|
||||
{calendars !== undefined ? calendars.map(calendar => (
|
||||
<div key={calendar.id}>
|
||||
<CheckboxInput
|
||||
type="checkbox"
|
||||
role="checkbox"
|
||||
id={calendar.id}
|
||||
color={calendar.color}
|
||||
checked={calendar.checked}
|
||||
onChange={() => setCalendars(calendars.map(c => c.id === calendar.id ? {...c, checked: !c.checked} : c))}
|
||||
/>
|
||||
<CheckboxLabel htmlFor={calendar.id} color={calendar.color} />
|
||||
<CalendarLabel htmlFor={calendar.id}>{calendar.name}</CalendarLabel>
|
||||
</div>
|
||||
)) : (
|
||||
<Loader />
|
||||
)}
|
||||
{calendars !== undefined && (
|
||||
<>
|
||||
<Info>{t('event:you.google_cal.info')}</Info>
|
||||
<Button
|
||||
small
|
||||
isLoading={freeBusyLoading}
|
||||
disabled={freeBusyLoading}
|
||||
onClick={() => importAvailability()}
|
||||
>{t('event:you.google_cal.button')}</Button>
|
||||
</>
|
||||
)}
|
||||
</CalendarList>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default GoogleCalendar
|
||||
122
frontend/src/components/GoogleCalendar/GoogleCalendar.styles.js
Normal file
122
frontend/src/components/GoogleCalendar/GoogleCalendar.styles.js
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
import { styled } from 'goober'
|
||||
|
||||
export const CalendarList = styled('div')`
|
||||
width: 100%;
|
||||
& > div {
|
||||
display: flex;
|
||||
margin: 2px 0;
|
||||
}
|
||||
`
|
||||
|
||||
export const CheckboxInput = styled('input')`
|
||||
height: 0px;
|
||||
width: 0px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: 0;
|
||||
font-size: 0;
|
||||
transform: scale(0);
|
||||
position: absolute;
|
||||
|
||||
&:checked + label::after {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
&[disabled] + label {
|
||||
opacity: .6;
|
||||
}
|
||||
&[disabled] + label:after {
|
||||
border: 2px solid var(--text);
|
||||
background-color: var(--text);
|
||||
}
|
||||
`
|
||||
|
||||
export const CheckboxLabel = styled('label')`
|
||||
display: inline-block;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
min-width: 24px;
|
||||
position: relative;
|
||||
border-radius: 3px;
|
||||
transition: background-color 0.2s, box-shadow 0.2s;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
border: 2px solid var(--text);
|
||||
border-radius: 2px;
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 3px;
|
||||
}
|
||||
&::after {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
border: 2px solid ${props => props.color || 'var(--primary)'};
|
||||
background-color: ${props => props.color || 'var(--primary)'};
|
||||
border-radius: 2px;
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 3px;
|
||||
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBmaWxsPSIjRkZGRkZGIiBkPSJNMjEsN0w5LDE5TDMuNSwxMy41TDQuOTEsMTIuMDlMOSwxNi4xN0wxOS41OSw1LjU5TDIxLDdaIiAvPjwvc3ZnPg==');
|
||||
background-size: 16px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
opacity: 0;
|
||||
transform: scale(.5);
|
||||
transition: opacity 0.15s, transform 0.15s;
|
||||
}
|
||||
`
|
||||
|
||||
export const CalendarLabel = styled('label')`
|
||||
margin-left: .6em;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
line-height: 24px;
|
||||
`
|
||||
|
||||
export const Info = styled('div')`
|
||||
font-size: 14px;
|
||||
opacity: .6;
|
||||
font-weight: 500;
|
||||
padding: 14px 0 10px;
|
||||
`
|
||||
|
||||
export const Options = styled('div')`
|
||||
font-size: 14px;
|
||||
padding: 0 0 5px;
|
||||
`
|
||||
|
||||
export const Title = styled('p')`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
& strong {
|
||||
margin-right: 1ex;
|
||||
}
|
||||
`
|
||||
|
||||
export const Icon = styled('img')`
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
margin-right: 12px;
|
||||
filter: invert(1);
|
||||
`
|
||||
|
||||
export const LinkButton = styled('button')`
|
||||
font: inherit;
|
||||
color: var(--primary);
|
||||
border: 0;
|
||||
background: none;
|
||||
text-decoration: underline;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: inline;
|
||||
cursor: pointer;
|
||||
appearance: none;
|
||||
`
|
||||
Loading…
Add table
Add a link
Reference in a new issue