Browse Source

reduce repo size some more

imwald
Silberengel 3 weeks ago
parent
commit
18bec8a28b
  1. 7
      knip.json
  2. 1
      package.json
  3. 2
      src/components/FavoriteRelaysSetting/provider.tsx
  4. 2
      src/components/KeyboardShortcutsHelp/index.tsx
  5. 2
      src/components/LatestFromFollowsSection/index.tsx
  6. 28
      src/components/Note/MarkdownArticle/preprocessMarkup.ts
  7. 2
      src/components/PostEditor/PostTextarea/Mention/NeventNaddrPickerDialog.tsx
  8. 2
      src/components/PostEditor/PostTextarea/Mention/suggestion.ts
  9. 383
      src/components/ScheduleVideoCallDialog/ScheduleInPersonMeetingSingleDialog.tsx
  10. 294
      src/components/ScheduleVideoCallDialog/ScheduleVideoCallSingleDialog.tsx
  11. 2
      src/components/ScheduleVideoCallDialog/index.tsx

7
knip.json

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
{
"$schema": "https://unpkg.com/knip@5/schema.json",
"entry": ["electron/preload.cjs!", "nip66-cron/index.mjs!"],
"ignore": ["src/global-polyfill-types.d.ts", "src/types/**/*.d.ts"],
"ignoreBinaries": ["tsx", "electron", "electron-builder"],
"ignoreDependencies": ["ws"]
}

1
package.json

@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
"dev:refresh": "rm -rf node_modules/.vite && vite --host",
"build": "tsc -b && vite build",
"lint": "eslint .",
"knip": "npx --yes knip@5",
"format": "prettier --write .",
"preview": "vite preview",
"test": "vitest",

2
src/components/FavoriteRelaysSetting/provider.tsx

@ -7,7 +7,7 @@ type TRelaySetsSettingComponentContext = { @@ -7,7 +7,7 @@ type TRelaySetsSettingComponentContext = {
setExpandedRelaySetId: React.Dispatch<React.SetStateAction<string | null>>
}
export const RelaySetsSettingComponentContext = createContext<
const RelaySetsSettingComponentContext = createContext<
TRelaySetsSettingComponentContext | undefined
>(undefined)

2
src/components/KeyboardShortcutsHelp/index.tsx

@ -24,8 +24,6 @@ import { @@ -24,8 +24,6 @@ import {
import { useTranslation } from 'react-i18next'
import readmeMarkdown from '../../../README.md?raw'
export { useKeyboardShortcutsHelp } from '@/contexts/keyboard-shortcuts-help-context'
function Kbd({ children }: { children: ReactNode }) {
return (
<kbd className="pointer-events-none inline-flex h-6 min-w-[1.25rem] shrink-0 items-center justify-center rounded border border-border bg-muted px-1.5 font-mono text-[11px] font-medium text-muted-foreground">

2
src/components/LatestFromFollowsSection/index.tsx

@ -35,7 +35,7 @@ import UserAvatar from '../UserAvatar' @@ -35,7 +35,7 @@ import UserAvatar from '../UserAvatar'
import Username from '../Username'
/** Curated follow list for guests (hex from npub). */
export const RECOMMENDED_FOLLOW_CURATOR_NPUB =
const RECOMMENDED_FOLLOW_CURATOR_NPUB =
'npub1m4ny6hjqzepn4rxknuq94c2gpqzr29ufkkw7ttcxyak7v43n6vvsajc2jl' as const
const MAX_FOLLOWS = 1000

28
src/components/Note/MarkdownArticle/preprocessMarkup.ts

@ -1,4 +1,3 @@ @@ -1,4 +1,3 @@
import { NOSTR_URI_INLINE_REGEX } from '@/lib/content-patterns'
import { isImage, isVideo, isAudio } from '@/lib/url'
import { URL_REGEX, YOUTUBE_URL_REGEX } from '@/constants'
import { isSpotifyOpenUrl } from '@/lib/spotify-url'
@ -231,30 +230,3 @@ export function preprocessAsciidocMediaLinks(content: string): string { @@ -231,30 +230,3 @@ export function preprocessAsciidocMediaLinks(content: string): string {
return processed
}
/**
* Post-process content to convert nostr: links and hashtags
* This should be applied AFTER markup processing
*/
export function postProcessNostrLinks(content: string): string {
let processed = content
// Convert nostr: prefixed links to embedded format
// nostr:npub1... -> [nostr:npub1...]
// nostr:note1... -> [nostr:note1...]
// etc.
const nostrRegex = new RegExp(NOSTR_URI_INLINE_REGEX.source, NOSTR_URI_INLINE_REGEX.flags)
processed = processed.replace(nostrRegex, (match) => {
// Already in a link? Don't double-wrap
// Check if it's already in markdown link syntax [text](nostr:...)
// or AsciiDoc link syntax link:nostr:...[text]
return match // Keep as is for now, will be processed by the parser
})
// Convert hashtags to links
// #tag -> link:/notes?t=tag[#tag] (for AsciiDoc) or [#tag](/notes?t=tag) (for Markdown)
// But only if not already in a link
// We'll handle this in the rendering phase to avoid breaking markup
return processed
}

2
src/components/PostEditor/PostTextarea/Mention/NeventNaddrPickerDialog.tsx

@ -30,7 +30,7 @@ type NeventNaddrPickerDialogProps = { @@ -30,7 +30,7 @@ type NeventNaddrPickerDialogProps = {
initialMode?: PickerSearchMode
}
export function NeventNaddrPickerDialog({
function NeventNaddrPickerDialog({
open,
onOpenChange,
onSelect,

2
src/components/PostEditor/PostTextarea/Mention/suggestion.ts

@ -10,8 +10,6 @@ import tippy, { GetReferenceClientRect, Instance, Props } from 'tippy.js' @@ -10,8 +10,6 @@ import tippy, { GetReferenceClientRect, Instance, Props } from 'tippy.js'
import MentionList, { MentionListHandle, MentionListProps, type MentionListItem } from './MentionList'
import { NEVENT_NADDR_PICKER_ID } from './constants'
export { NEVENT_NADDR_PICKER_ID } from './constants'
export type { PickerSearchMode }
const MENTION_EXTENSION_NAME = 'mention'

383
src/components/ScheduleVideoCallDialog/ScheduleInPersonMeetingSingleDialog.tsx

@ -1,383 +0,0 @@ @@ -1,383 +0,0 @@
import { Button } from '@/components/ui/button'
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle
} from '@/components/ui/dialog'
import { DateTimePicker } from '@/components/ui/DateTimePicker'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
import { Textarea } from '@/components/ui/textarea'
import {
createInPersonCalendarEventDraftEvent,
createInPersonDateBasedCalendarEventDraftEvent,
createPublicMessageDraftEvent
} from '@/lib/draft-event'
import { getNoteBech32Id } from '@/lib/event'
import { randomString } from '@/lib/random'
import { useNostr } from '@/providers/NostrProvider'
import { MapPin } from 'lucide-react'
import { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
import { CalendarEventPreview } from './CalendarEventPreview'
function parseTopicTags(value: string): string[] {
return [
...new Set(
value
.trim()
.split(/[\s,]+/)
.map((s) => s.replace(/^#+/, '').trim())
.filter(Boolean)
)
]
}
export function ScheduleInPersonMeetingSingleDialog({
inviteePubkey,
open,
onOpenChange
}: {
inviteePubkey: string
open: boolean
onOpenChange: (open: boolean) => void
}) {
const { t } = useTranslation()
const { publish } = useNostr()
const [eventType, setEventType] = useState<'time' | 'date'>('time')
const [title, setTitle] = useState('')
const [startDatetime, setStartDatetime] = useState('')
const [endDatetime, setEndDatetime] = useState('')
const [startDateStr, setStartDateStr] = useState('')
const [endDateStr, setEndDateStr] = useState('')
const [location, setLocation] = useState('')
const [summary, setSummary] = useState('')
const [topics, setTopics] = useState('')
const [image, setImage] = useState('')
const [submitting, setSubmitting] = useState(false)
const formValid = useMemo(() => {
if (eventType === 'date') {
if (!startDateStr.trim()) return false
if (endDateStr.trim() && endDateStr <= startDateStr) return false
return true
}
if (!startDatetime.trim()) return false
const startUnix = Math.floor(new Date(startDatetime).getTime() / 1000)
const endUnix = endDatetime.trim()
? Math.floor(new Date(endDatetime).getTime() / 1000)
: undefined
if (endUnix != null && endUnix <= startUnix) return false
return true
}, [eventType, startDatetime, endDatetime, startDateStr, endDateStr])
const previewDraft = useMemo(() => {
if (!formValid) return null
const d = 'preview'
if (eventType === 'date') {
if (!startDateStr.trim()) return null
if (endDateStr.trim() && endDateStr <= startDateStr) return null
return createInPersonDateBasedCalendarEventDraftEvent({
d,
title: title.trim() || t('In-person meeting'),
start: startDateStr,
end: endDateStr.trim() || undefined,
location: location.trim() || undefined,
summary: summary.trim() || undefined,
image: image.trim() || undefined,
topics: parseTopicTags(topics),
participants: [inviteePubkey]
})
}
if (!startDatetime.trim()) return null
const startDate = new Date(startDatetime)
const startUnix = Math.floor(startDate.getTime() / 1000)
const endUnix = endDatetime.trim()
? Math.floor(new Date(endDatetime).getTime() / 1000)
: undefined
if (endUnix != null && endUnix <= startUnix) return null
return createInPersonCalendarEventDraftEvent({
d,
title: title.trim() || t('In-person meeting'),
start: startUnix,
end: endUnix,
location: location.trim() || undefined,
summary: summary.trim() || undefined,
image: image.trim() || undefined,
topics: parseTopicTags(topics),
participants: [inviteePubkey]
})
}, [
eventType,
title,
startDatetime,
endDatetime,
startDateStr,
endDateStr,
location,
summary,
topics,
image,
inviteePubkey,
t,
formValid
])
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (!formValid) return
if (eventType === 'date') {
if (!startDateStr.trim()) {
toast.error(t('Please set a start date'))
return
}
if (endDateStr.trim() && endDateStr <= startDateStr) {
toast.error(t('End date must be after start date'))
return
}
} else {
if (!startDatetime.trim()) {
toast.error(t('Please set a start time'))
return
}
const startDate = new Date(startDatetime)
const startUnix = Math.floor(startDate.getTime() / 1000)
const endUnix = endDatetime.trim()
? Math.floor(new Date(endDatetime).getTime() / 1000)
: undefined
if (endUnix != null && endUnix <= startUnix) {
toast.error(t('End time must be after start time'))
return
}
}
setSubmitting(true)
try {
const d = `jumble-inperson-${randomString(12)}`
const calendarDraft =
eventType === 'date'
? createInPersonDateBasedCalendarEventDraftEvent({
d,
title: title.trim() || t('In-person meeting'),
start: startDateStr,
end: endDateStr.trim() || undefined,
location: location.trim() || undefined,
summary: summary.trim() || undefined,
image: image.trim() || undefined,
topics: parseTopicTags(topics),
participants: [inviteePubkey]
})
: createInPersonCalendarEventDraftEvent({
d,
title: title.trim() || t('In-person meeting'),
start: Math.floor(new Date(startDatetime).getTime() / 1000),
end: endDatetime.trim()
? Math.floor(new Date(endDatetime).getTime() / 1000)
: undefined,
location: location.trim() || undefined,
summary: summary.trim() || undefined,
image: image.trim() || undefined,
topics: parseTopicTags(topics),
participants: [inviteePubkey]
})
const calendarEvent = await publish(calendarDraft)
const naddr = getNoteBech32Id(calendarEvent)
const messageContent = `${t("You're invited to an in-person meeting.")} nostr:${naddr}`
const pmDraft = await createPublicMessageDraftEvent(
messageContent,
[inviteePubkey],
{ addClientTag: true }
)
await publish(pmDraft)
toast.success(t('Meeting created and invite sent'))
onOpenChange(false)
setEventType('time')
setTitle('')
setStartDatetime('')
setEndDatetime('')
setStartDateStr('')
setEndDateStr('')
setLocation('')
setSummary('')
setTopics('')
setImage('')
} catch (err) {
toast.error(err instanceof Error ? err.message : t('Failed to create meeting'))
} finally {
setSubmitting(false)
}
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-md max-h-[90vh] flex flex-col p-6">
<DialogHeader className="shrink-0">
<DialogTitle className="flex items-center gap-2">
<MapPin className="size-5" />
{t('Schedule in-person meeting')}
</DialogTitle>
<DialogDescription>
{t('Required: start time or start date. Optional: title, end, location, summary, topics, image.')}
</DialogDescription>
</DialogHeader>
<form onSubmit={handleSubmit} className="flex flex-col min-h-0 flex-1">
<div className="flex-1 min-h-0 overflow-y-auto space-y-4 pr-1">
<div>
<Label>{t('Event type')}</Label>
<RadioGroup
value={eventType}
onValueChange={(v) => setEventType(v as 'time' | 'date')}
className="mt-2 flex gap-4"
>
<label className="flex items-center gap-2 cursor-pointer">
<RadioGroupItem value="time" id="inperson-type-time" />
<span className="text-sm">{t('Time-based')}</span>
</label>
<label className="flex items-center gap-2 cursor-pointer">
<RadioGroupItem value="date" id="inperson-type-date" />
<span className="text-sm">{t('Date-based (all-day)')}</span>
</label>
</RadioGroup>
</div>
<div>
<Label htmlFor="inperson-title">
{t('Title')} <span className="text-muted-foreground font-normal">({t('optional')})</span>
</Label>
<Input
id="inperson-title"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder={t('In-person meeting')}
className="mt-1"
/>
</div>
{eventType === 'date' ? (
<>
<div>
<Label htmlFor="inperson-start-date">{t('Start date')} *</Label>
<Input
id="inperson-start-date"
type="date"
value={startDateStr}
onChange={(e) => setStartDateStr(e.target.value)}
className="mt-1"
required={eventType === 'date'}
/>
</div>
<div>
<Label htmlFor="inperson-end-date">
{t('End date')} <span className="text-muted-foreground font-normal">({t('optional')})</span>
</Label>
<Input
id="inperson-end-date"
type="date"
value={endDateStr}
onChange={(e) => setEndDateStr(e.target.value)}
className="mt-1"
/>
</div>
</>
) : (
<>
<DateTimePicker
id="inperson-start"
value={startDatetime}
onChange={setStartDatetime}
label={t('Start')}
labelSuffix="*"
required={eventType === 'time'}
/>
<DateTimePicker
id="inperson-end"
value={endDatetime}
onChange={setEndDatetime}
label={t('End')}
labelSuffix={
<span className="text-muted-foreground font-normal">({t('optional')})</span>
}
/>
</>
)}
<div>
<Label htmlFor="inperson-location">
{t('Location')} <span className="text-muted-foreground font-normal">({t('optional')})</span>
</Label>
<Input
id="inperson-location"
value={location}
onChange={(e) => setLocation(e.target.value)}
placeholder={t('Address, venue, or place')}
className="mt-1"
/>
</div>
<div>
<Label htmlFor="inperson-summary">
{t('Summary')} <span className="text-muted-foreground font-normal">({t('optional')})</span>
</Label>
<Textarea
id="inperson-summary"
value={summary}
onChange={(e) => setSummary(e.target.value)}
placeholder={t('Brief description of the event')}
className="mt-1 min-h-[60px]"
/>
</div>
<div>
<Label htmlFor="inperson-topics">
{t('Topics')} <span className="text-muted-foreground font-normal">({t('optional')})</span>
</Label>
<Input
id="inperson-topics"
value={topics}
onChange={(e) => setTopics(e.target.value)}
placeholder={t('e.g. meetup, conference')}
className="mt-1"
/>
</div>
<div>
<Label htmlFor="inperson-image">
{t('Image URL')} <span className="text-muted-foreground font-normal">({t('optional')})</span>
</Label>
<Input
id="inperson-image"
type="url"
value={image}
onChange={(e) => setImage(e.target.value)}
placeholder={t('Optional image for the event')}
className="mt-1"
/>
</div>
{formValid && previewDraft && (
<div className="min-h-0 shrink-0">
<Label className="mb-1 block">{t('Preview')}</Label>
<CalendarEventPreview draft={previewDraft} />
</div>
)}
</div>
<DialogFooter className="shrink-0 pt-2 border-t mt-2">
<Button
type="button"
variant="outline"
onClick={() => onOpenChange(false)}
disabled={submitting}
>
{t('Cancel')}
</Button>
<Button type="submit" disabled={submitting || !formValid}>
{submitting ? t('Creating…') : t('Create and send invite')}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
)
}

294
src/components/ScheduleVideoCallDialog/ScheduleVideoCallSingleDialog.tsx

@ -1,294 +0,0 @@ @@ -1,294 +0,0 @@
import { Button } from '@/components/ui/button'
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle
} from '@/components/ui/dialog'
import { DateTimePicker } from '@/components/ui/DateTimePicker'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Textarea } from '@/components/ui/textarea'
import {
createCalendarEventDraftEvent,
createPublicMessageDraftEvent
} from '@/lib/draft-event'
import { getNoteBech32Id } from '@/lib/event'
import { buildHiveTalkJoinUrl, roomIdForScheduledCall } from '@/lib/hivetalk'
import { randomString } from '@/lib/random'
import { useNostr } from '@/providers/NostrProvider'
import { Calendar } from 'lucide-react'
import { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
import { CalendarEventPreview } from './CalendarEventPreview'
function parseTopicTags(value: string): string[] {
return [
...new Set(
value
.trim()
.split(/[\s,]+/)
.map((s) => s.replace(/^#+/, '').trim())
.filter(Boolean)
)
]
}
export function ScheduleVideoCallSingleDialog({
inviteePubkey,
open,
onOpenChange
}: {
inviteePubkey: string
open: boolean
onOpenChange: (open: boolean) => void
}) {
const { t } = useTranslation()
const { publish } = useNostr()
const joinAsName = 'Guest'
const [title, setTitle] = useState('')
const [startDatetime, setStartDatetime] = useState('')
const [endDatetime, setEndDatetime] = useState('')
const [locationUrl, setLocationUrl] = useState('')
const [summary, setSummary] = useState('')
const [topics, setTopics] = useState('')
const [image, setImage] = useState('')
const [submitting, setSubmitting] = useState(false)
const formValid = useMemo(() => {
if (!startDatetime.trim()) return false
const startUnix = Math.floor(new Date(startDatetime).getTime() / 1000)
const endUnix = endDatetime.trim()
? Math.floor(new Date(endDatetime).getTime() / 1000)
: undefined
if (endUnix != null && endUnix <= startUnix) return false
return true
}, [startDatetime, endDatetime])
const previewDraft = useMemo(() => {
if (!formValid) return null
if (!startDatetime.trim()) return null
const startDate = new Date(startDatetime)
const startUnix = Math.floor(startDate.getTime() / 1000)
const endUnix = endDatetime.trim()
? Math.floor(new Date(endDatetime).getTime() / 1000)
: undefined
if (endUnix != null && endUnix <= startUnix) return null
const d = 'preview'
const roomId = roomIdForScheduledCall(d)
const defaultJoinUrl = buildHiveTalkJoinUrl({ room: roomId, name: joinAsName })
const joinUrl = locationUrl.trim() || defaultJoinUrl
return createCalendarEventDraftEvent({
d,
title: title.trim() || t('Video call'),
start: startUnix,
end: endUnix,
locationUrl: joinUrl,
summary: summary.trim() || undefined,
image: image.trim() || undefined,
topics: parseTopicTags(topics),
participants: [inviteePubkey]
})
}, [
title,
startDatetime,
endDatetime,
locationUrl,
summary,
topics,
image,
inviteePubkey,
joinAsName,
t,
formValid
])
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (!formValid) return
if (!startDatetime.trim()) {
toast.error(t('Please set a start time'))
return
}
setSubmitting(true)
try {
const startDate = new Date(startDatetime)
const startUnix = Math.floor(startDate.getTime() / 1000)
const endUnix = endDatetime.trim()
? Math.floor(new Date(endDatetime).getTime() / 1000)
: undefined
if (endUnix != null && endUnix <= startUnix) {
toast.error(t('End time must be after start time'))
setSubmitting(false)
return
}
const d = `jumble-cal-${randomString(12)}`
const roomId = roomIdForScheduledCall(d)
const defaultJoinUrl = buildHiveTalkJoinUrl({
room: roomId,
name: joinAsName
})
const joinUrl = locationUrl.trim() || defaultJoinUrl
const calendarDraft = createCalendarEventDraftEvent({
d,
title: title.trim() || t('Video call'),
start: startUnix,
end: endUnix,
locationUrl: joinUrl,
summary: summary.trim() || undefined,
image: image.trim() || undefined,
topics: parseTopicTags(topics),
participants: [inviteePubkey]
})
const calendarEvent = await publish(calendarDraft)
const naddr = getNoteBech32Id(calendarEvent)
const messageContent = `${t("You're invited to a scheduled video call.")} nostr:${naddr}`
const pmDraft = await createPublicMessageDraftEvent(
messageContent,
[inviteePubkey],
{ addClientTag: true }
)
await publish(pmDraft)
toast.success(t('Scheduled call created and invite sent'))
onOpenChange(false)
setTitle('')
setStartDatetime('')
setEndDatetime('')
setLocationUrl('')
setSummary('')
setTopics('')
setImage('')
} catch (err) {
toast.error(err instanceof Error ? err.message : t('Failed to schedule call'))
} finally {
setSubmitting(false)
}
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-md max-h-[90vh] flex flex-col p-6">
<DialogHeader className="shrink-0">
<DialogTitle className="flex items-center gap-2">
<Calendar className="size-5" />
{t('Schedule video call')}
</DialogTitle>
<DialogDescription>
{t('Required: start time. Join link defaults to HiveTalk. Optional: title, end, summary, topics, image.')}
</DialogDescription>
</DialogHeader>
<form onSubmit={handleSubmit} className="flex flex-col min-h-0 flex-1">
<div className="flex-1 min-h-0 overflow-y-auto space-y-4 pr-1">
<div>
<Label htmlFor="schedule-call-title">
{t('Title')} <span className="text-muted-foreground font-normal">({t('optional')})</span>
</Label>
<Input
id="schedule-call-title"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder={t('Video call')}
className="mt-1"
/>
</div>
<DateTimePicker
id="schedule-call-start"
value={startDatetime}
onChange={setStartDatetime}
label={t('Start')}
labelSuffix="*"
required
/>
<DateTimePicker
id="schedule-call-end"
value={endDatetime}
onChange={setEndDatetime}
label={t('End')}
labelSuffix={
<span className="text-muted-foreground font-normal">({t('optional')})</span>
}
/>
<div>
<Label htmlFor="schedule-call-location">
{t('Join link')} <span className="text-muted-foreground font-normal">({t('optional')})</span>
</Label>
<Input
id="schedule-call-location"
type="url"
value={locationUrl}
onChange={(e) => setLocationUrl(e.target.value)}
placeholder={t('Leave empty for HiveTalk, or paste Zoom / Teams / other link')}
className="mt-1"
/>
</div>
<div>
<Label htmlFor="schedule-call-summary">
{t('Summary')} <span className="text-muted-foreground font-normal">({t('optional')})</span>
</Label>
<Textarea
id="schedule-call-summary"
value={summary}
onChange={(e) => setSummary(e.target.value)}
placeholder={t('Brief description of the event')}
className="mt-1 min-h-[60px]"
/>
</div>
<div>
<Label htmlFor="schedule-call-topics">
{t('Topics')} <span className="text-muted-foreground font-normal">({t('optional')})</span>
</Label>
<Input
id="schedule-call-topics"
value={topics}
onChange={(e) => setTopics(e.target.value)}
placeholder={t('e.g. meetup, conference')}
className="mt-1"
/>
</div>
<div>
<Label htmlFor="schedule-call-image">
{t('Image URL')} <span className="text-muted-foreground font-normal">({t('optional')})</span>
</Label>
<Input
id="schedule-call-image"
type="url"
value={image}
onChange={(e) => setImage(e.target.value)}
placeholder={t('Optional image for the event')}
className="mt-1"
/>
</div>
{formValid && previewDraft && (
<div className="min-h-0 shrink-0">
<Label className="mb-1 block">{t('Preview')}</Label>
<CalendarEventPreview draft={previewDraft} />
</div>
)}
</div>
<DialogFooter className="shrink-0 pt-2 border-t mt-2">
<Button
type="button"
variant="outline"
onClick={() => onOpenChange(false)}
disabled={submitting}
>
{t('Cancel')}
</Button>
<Button type="submit" disabled={submitting || !formValid}>
{submitting ? t('Scheduling…') : t('Schedule and send invite')}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
)
}

2
src/components/ScheduleVideoCallDialog/index.tsx

@ -1,4 +1,2 @@ @@ -1,4 +1,2 @@
export { ScheduleVideoCallDialog } from './ScheduleVideoCallDialog'
export { ScheduleVideoCallSingleDialog } from './ScheduleVideoCallSingleDialog'
export { ScheduleInPersonMeetingDialog } from './ScheduleInPersonMeetingDialog'
export { ScheduleInPersonMeetingSingleDialog } from './ScheduleInPersonMeetingSingleDialog'

Loading…
Cancel
Save