'use client'; import { useStatus } from '@/context/status'; import { PageWrapper } from '../shared/page-wrapper'; import { // SERVICE_DETAILS, ServiceId, } from '../../../../core/src/utils/constants'; import { useUserData } from '@/context/userData'; import { restrictToVerticalAxis } from '@dnd-kit/modifiers'; import { arrayMove, SortableContext, verticalListSortingStrategy, useSortable, } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import { IconButton } from '../ui/button'; import { FiArrowLeft, FiArrowRight, FiSettings } from 'react-icons/fi'; import { Switch } from '../ui/switch'; import { Modal } from '../ui/modal'; import { useState, useEffect } from 'react'; import { DndContext, useSensors, PointerSensor, TouchSensor, useSensor, } from '@dnd-kit/core'; import TemplateOption from '../shared/template-option'; import { Button } from '../ui/button'; import MarkdownLite from '../shared/markdown-lite'; import { Alert } from '../ui/alert'; import { useMenu } from '@/context/menu'; import { PageControls } from '../shared/page-controls'; import { SettingsCard } from '../shared/settings-card'; import { TextInput } from '../ui/text-input'; export function ServicesMenu() { return ( <> > ); } // we show all services, along with its signUpText and a setting icon button, and switch to enable/disable the service. // this will be in a sortable lis twith dnd, similar to the addons menu. // when the setting icon button is clicked, it will open a modal with all the credentials (option definitions) for the service // function Content() { const { status } = useStatus(); if (!status) return null; const { setUserData, userData } = useUserData(); const { setSelectedMenu, nextMenu, previousMenu } = useMenu(); const [modalOpen, setModalOpen] = useState(false); const [modalService, setModalService] = useState(null); const [modalValues, setModalValues] = useState>({}); const [isDragging, setIsDragging] = useState(false); // DND logic function handleDragEnd(event: any) { const { active, over } = event; if (!over) return; if (active.id !== over.id) { setUserData((prev) => { const services = prev.services ?? []; const oldIndex = services.findIndex((s) => s.id === active.id); const newIndex = services.findIndex((s) => s.id === over.id); const newServices = arrayMove(services, oldIndex, newIndex); return { ...prev, services: newServices }; }); } setIsDragging(false); } function handleDragStart(event: any) { setIsDragging(true); } // Modal handlers const handleServiceClick = (service: ServiceId) => { setModalService(service); const svc = userData.services?.find((s) => s.id === service); setModalValues(svc?.credentials || {}); setModalOpen(true); }; const handleModalClose = () => { setModalOpen(false); setModalService(null); setModalValues({}); }; const handleModalSubmit = (values: Record) => { setUserData((prev) => { const newUserData = { ...prev }; newUserData.services = (newUserData.services ?? []).map((service) => { if (service.id === modalService) { return { ...service, enabled: true, credentials: values, }; } return service; }); return newUserData; }); handleModalClose(); }; const handleModalValuesChange = (newValues: Record) => { setModalValues((prevValues) => ({ ...prevValues, ...newValues, })); }; useEffect(() => { const allServiceIds: ServiceId[] = Object.keys( status.settings.services ) as ServiceId[]; const currentServices = userData.services ?? []; // Remove any services not in SERVICE_DETAILS and apply forced/default credentials let filtered = currentServices.filter((s) => allServiceIds.includes(s.id)); // Add any missing services from SERVICE_DETAILS const missing = allServiceIds.filter( (id) => !filtered.some((s) => s.id === id) ); if (missing.length > 0 || filtered.length !== currentServices.length) { const toAdd = missing.map((id) => { const svcMeta = status.settings.services[id]!; const credentials: Record = {}; let enabled = false; return { id, enabled, credentials, }; }); setUserData((prev: any) => ({ ...prev, services: [...filtered, ...toAdd], })); } }, [status.settings.services]); const sensors = useSensors( useSensor(PointerSensor), useSensor(TouchSensor, { activationConstraint: { delay: 150, tolerance: 8, }, }) ); useEffect(() => { function preventTouchMove(e: TouchEvent) { if (isDragging) { e.preventDefault(); } } function handleDragEnd() { setIsDragging(false); } if (isDragging) { document.body.addEventListener('touchmove', preventTouchMove, { passive: false, }); // Add listeners for when drag ends outside context document.addEventListener('pointerup', handleDragEnd); document.addEventListener('touchend', handleDragEnd); } else { document.body.removeEventListener('touchmove', preventTouchMove); } // Cleanup return () => { document.body.removeEventListener('touchmove', preventTouchMove); document.removeEventListener('pointerup', handleDragEnd); document.removeEventListener('touchend', handleDragEnd); }; }, [isDragging]); const invalidServices = userData.services ?.filter((service) => { const svcMeta = status.settings.services[service.id]; if (!svcMeta) return false; // Check if any required credential is missing return ( service.enabled && svcMeta.credentials.some((cred) => !service.credentials?.[cred.id]) ); }) .map((service) => status.settings.services[service.id]?.name) ?? []; // Render return ( <> Services Provide credentials for any services you want to use. {invalidServices && invalidServices.length > 0 && ( The following services are missing credentials: {invalidServices.map((service) => ( {service} ))} > } /> )} s.id) || []} strategy={verticalListSortingStrategy} > {(userData.services?.length ?? 0) === 0 ? ( Looks like you don't have any services configured. Add and configure services above. ) : ( userData.services?.map((service, idx) => { const svcMeta = status.settings.services[service.id]!; return ( handleServiceClick(service.id)} onToggleEnabled={(v: boolean) => { setUserData((prev) => { return { ...prev, services: (prev.services ?? []).map((s) => s.id === service.id ? { ...s, enabled: v } : s ), }; }); }} /> ); }) )} Get your API Key from{' '} here } value={userData.rpdbApiKey} onValueChange={(v) => { setUserData((prev) => ({ ...prev, rpdbApiKey: v, })); }} /> > ); } function SortableServiceItem({ service, meta, onEdit, onToggleEnabled, }: { service: any; meta: any; onEdit: () => void; onToggleEnabled: (v: boolean) => void; }) { const { attributes, listeners, setNodeRef, transform, transition, isDragging, } = useSortable({ id: service.id }); const style = { transform: CSS.Transform.toString(transform), transition, opacity: isDragging ? 0.5 : 1, }; const disableEdit = meta.credentials.every((cred: any) => { return cred.forced; }); return ( {meta?.name || service.id} {meta?.signUpText} } intent="primary-outline" onClick={onEdit} disabled={disableEdit} /> ); } function ServiceModal({ open, onOpenChange, serviceId, values, onChange, onSubmit, onClose, }: { open: boolean; onOpenChange: (v: boolean) => void; serviceId: ServiceId | null; values: Record; onChange: (v: Record) => void; onSubmit: (v: Record) => void; onClose: () => void; }) { const { status } = useStatus(); if (!status) return null; if (!serviceId) return null; const meta = status.settings.services[serviceId]!; const credentials = meta.credentials || []; const handleCredentialChange = (optId: string, newValue: any) => { // Create a new object with all existing values plus the updated one const updatedValues = { ...values, [optId]: newValue, }; onChange(updatedValues); }; return ( { e.preventDefault(); onSubmit(values); }} > {credentials.map((opt) => ( handleCredentialChange(opt.id, v)} /> ))} Cancel Save ); }
Provide credentials for any services you want to use.