Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions functions/src/api/routes/faq/faq.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export const faqRoutes = (fastify: FastifyInstance, options: any, done: () => an
{
schema: {
tags: ['faq'],
summary:
description:
'Add a new item in the FAQ. If the question id is provided, and the question with the same id already exist, it will be updated instead of created.',
body: Faq,
response: {
Expand Down Expand Up @@ -78,7 +78,7 @@ export const faqRoutes = (fastify: FastifyInstance, options: any, done: () => an
{
schema: {
tags: ['faq'],
summary:
description:
'Get the FAQ details (publicly used by OpenPlanner public frontend). If you pass a faqPrivateId, it will only return the private FAQ for that ID',
response: {
200: GetReply,
Expand Down
2 changes: 1 addition & 1 deletion functions/src/api/routes/sessions/sessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export const sessionsRoutes = (fastify: FastifyInstance, options: any, done: ()
'/v1/:eventId/sessions/:sessionId/shortvid',
{
schema: {
tags: ['session'],
tags: ['sessions'],
summary: 'Generate the session announcement video using shortvid.io API.',
querystring: {
type: 'object',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,8 @@ export const sessionsSpeakers = (fastify: FastifyInstance, options: any, done: (
{
schema: {
tags: ['speakers', 'sessions'],
summary:
summary: 'Write sessions and speakers',
description:
'Overwrite sessions and speakers: if any data exist before, each filed given in the body will rewrite the corresponding data. ' +
'Tracks, formats and categories will only be created if none exist before and if you provide an id and a name. If track, format or category does exist, the ID will be matched again the trackName or the trackId, same for categories and formats.',
body: SpeakersSessionsType,
Expand Down
5 changes: 3 additions & 2 deletions functions/src/api/swagger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const registerSwagger = (fastify: FastifyInstance) => {
fastify.register(FastifySwagger, {
swagger: {
info: {
title: 'OpenPlanner API',
title: 'OpenPlanner API Documentation',
version: '1.0.0',
},
host: 'api.openplanner.fr/',
Expand All @@ -30,8 +30,9 @@ export const registerSwagger = (fastify: FastifyInstance) => {
fastify.register(FastifySwaggerUi, {
routePrefix: '/',
uiConfig: {
docExpansion: 'full',
docExpansion: 'list',
deepLinking: false,
tryItOutEnabled: true,
},
})
}
82 changes: 82 additions & 0 deletions src/components/form/TextFieldElementPrivate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import * as React from 'react'
import { TextFieldElement, TextFieldElementProps } from 'react-hook-form-mui'
import { Box, IconButton, InputAdornment } from '@mui/material'
import { useController } from 'react-hook-form'
import { ContentCopy, Visibility, VisibilityOff } from '@mui/icons-material'
import { useCopyToClipboard } from '../../context/copyToClipboardHook'
import { useState } from 'react'

export type TextFieldElementPrivateProps = {
required?: boolean
fullWidth?: boolean
name: string
id: string
label: string
helperText?: string
disabled?: boolean
visibleChars?: number
showFullText?: boolean
}

export const TextFieldElementPrivate = (props: TextFieldElementPrivateProps & TextFieldElementProps) => {
const { field } = useController({ name: props.name })
const copyAction = useCopyToClipboard()
const [showFullText, setShowFullText] = useState(props.showFullText === undefined ? false : props.showFullText)
const visibleChars = props.visibleChars || 8

const copyToClipboard = () => {
copyAction(field.value)
}

const toggleVisibility = () => {
setShowFullText(!showFullText)
}

const getMaskedValue = () => {
if (!field.value) return ''
if (showFullText) return field.value

const valueStr = String(field.value)
const halfVisible = Math.floor(visibleChars / 2)

if (valueStr.length <= visibleChars) return valueStr

const firstPart = valueStr.substring(0, halfVisible)
const lastPart = valueStr.substring(valueStr.length - halfVisible)
const middleBullets = '•'.repeat(Math.max(10, valueStr.length - visibleChars))

return firstPart + middleBullets + lastPart
}

return (
<TextFieldElement
margin="normal"
required={props.required}
fullWidth={props.fullWidth}
id={props.id}
label={props.label}
name={props.name}
variant="filled"
disabled={props.disabled}
helperText={props.helperText}
value={getMaskedValue()}
onChange={(e) => field.onChange(e.target.value)}
type={'text'}
InputProps={{
value: getMaskedValue(),
endAdornment: (
<Box display="flex" alignItems="center" ml={1}>
<InputAdornment position="end">
<IconButton aria-label="toggle visibility" onClick={toggleVisibility} edge="end">
{showFullText ? <VisibilityOff /> : <Visibility />}
</IconButton>
<IconButton aria-label="copy text" onClick={copyToClipboard} edge="end">
<ContentCopy />
</IconButton>
</InputAdornment>
</Box>
),
}}
/>
)
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import * as React from 'react'
import { TextFieldElement } from 'react-hook-form-mui'
import { Box, Button, IconButton, InputAdornment } from '@mui/material'
import { generateApiKey } from '../../utils/generateApiKey'
import { useController } from 'react-hook-form'
import { ContentCopy } from '@mui/icons-material'
import { useCopyToClipboard } from '../../context/copyToClipboardHook'

import { TextFieldElementPrivate } from './TextFieldElementPrivate'
export type TextFieldElementWithGenerateApiKeyButtonProps = {
required: boolean
fullWidth: boolean
Expand All @@ -26,7 +24,7 @@ export const TextFieldElementWithGenerateApiKeyButton = (props: TextFieldElement

return (
<>
<TextFieldElement
<TextFieldElementPrivate
margin="normal"
required
fullWidth
Expand Down
13 changes: 12 additions & 1 deletion src/events/page/api/Api.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { useFaqs } from '../../../services/hooks/useFaqs'
import { FirestoreQueryLoaderAndErrorDisplay } from '../../../components/FirestoreQueryLoaderAndErrorDisplay'
import { TypographyCopyable } from '../../../components/TypographyCopyable'
import { Link } from 'wouter'
import { TextFieldElementPrivate } from '../../../components/form/TextFieldElementPrivate'

const schema = yup
.object({
Expand Down Expand Up @@ -88,12 +89,22 @@ export const API = ({ event }: APIProps) => {
</Typography>
<EventStaticApiFilePaths event={event} />

<Typography fontWeight="600" mt={2}>
<Typography fontWeight="600" mt={5}>
Dynamic API (slow, not cached, read/write, work in progress)
</Typography>
<Typography fontWeight="600" mt={2} component="a" href="https://api.openplanner.fr/">
Docs
</Typography>
<TextFieldElementPrivate
margin="normal"
fullWidth
id="id"
label="Event ID"
name="id"
disabled={true}
showFullText={true}
value={event.id}
/>
<TextFieldElementWithGenerateApiKeyButton
required={true}
fullWidth={true}
Expand Down
7 changes: 4 additions & 3 deletions src/events/page/settings/EventSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { FormContainer, TextFieldElement, useForm } from 'react-hook-form-mui'
import LoadingButton from '@mui/lab/LoadingButton'
import { doc } from 'firebase/firestore'
import { DateTime } from 'luxon'
import { useEffect, useState } from 'react'
import { useState } from 'react'
import { useLocation } from 'wouter'
import * as yup from 'yup'
import { ConfirmDialog } from '../../../components/ConfirmDialog'
Expand All @@ -24,6 +24,7 @@ import { mapEventSettingsFormToMutateObject } from './mapEventSettingsFormToMuta
import { SaveShortcut } from '../../../components/form/SaveShortcut'
import { EventSettingsFormatCategoriesGrid } from './EventSettingsFormatCategoriesGrid'
import { ImageTextFieldElement } from '../../../components/form/ImageTextFieldElement'
import { TextFieldElementPrivate } from '../../../components/form/TextFieldElementPrivate'

const schema = yup
.object({
Expand Down Expand Up @@ -211,7 +212,7 @@ export const EventSettings = ({ event }: EventSettingsProps) => {
Other stuffs
</Typography>

<TextFieldElement
<TextFieldElementPrivate
margin="normal"
fullWidth
id="openAPIKey"
Expand All @@ -221,7 +222,7 @@ export const EventSettings = ({ event }: EventSettingsProps) => {
disabled={formState.isSubmitting}
/>

<TextFieldElement
<TextFieldElementPrivate
margin="normal"
fullWidth
id="gladiaAPIKey"
Expand Down
23 changes: 21 additions & 2 deletions src/public/event/PublicEventSchedule.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Box } from '@mui/material'
import { Box, Typography } from '@mui/material'
import { DateTime } from 'luxon'
import { JsonSession, JsonPublicOutput } from '../../../functions/src/api/routes/deploy/updateWebsiteActions/jsonTypes'
import { useLocation, useRoute } from 'wouter'
Expand Down Expand Up @@ -59,7 +59,16 @@ export const PublicEventSchedule = ({ eventId, event }: PublicEventScheduleProps
const currentSessions = sessionsByDay.get(selectedDay) || []

return (
<Box display="flex" flexDirection="column" gap={2} p={0} mt={2} justifyContent="center" alignItems="center">
<Box
display="flex"
flexDirection="column"
gap={2}
p={0}
mt={2}
justifyContent="center"
alignItems="center"
sx={{ minHeight: 'calc(100vh - 124px)' }} // Subtract margin-top value to prevent overflow
>
<ScheduleHeader
eventName={event.event.name}
logoUrl={event.event.logoUrl}
Expand All @@ -75,6 +84,16 @@ export const PublicEventSchedule = ({ eventId, event }: PublicEventScheduleProps
speakersData={event.speakers}
categories={event.event.categories}
/>

<Box
sx={{
marginTop: 'auto',
py: 2,
}}>
<Typography variant="caption" color="text.secondary">
Updated on {DateTime.fromISO(event.generatedAt).toLocaleString(DateTime.DATETIME_FULL)}
</Typography>
</Box>
</Box>
)
}