--- import { AttributeCategory, Currency, EventType, VerificationStatus, VerificationStepStatus, } from '@prisma/client' import { Icon } from 'astro-icon/components' import { actions, isInputError } from 'astro:actions' import MyPicture from '../../../../components/MyPicture.astro' import { serviceVisibilities } from '../../../../constants/serviceVisibility' import BaseLayout from '../../../../layouts/BaseLayout.astro' import { cn } from '../../../../lib/cn' import { prisma } from '../../../../lib/prisma' import { ACCEPTED_IMAGE_TYPES } from '../../../../lib/zodUtils' const { slug } = Astro.params const serviceResult = Astro.getActionResult(actions.admin.service.update) const eventCreateResult = Astro.getActionResult(actions.admin.event.create) const eventToggleResult = Astro.getActionResult(actions.admin.event.toggle) const eventDeleteResult = Astro.getActionResult(actions.admin.event.delete) const eventUpdateResult = Astro.getActionResult(actions.admin.event.update) const verificationStepCreateResult = Astro.getActionResult(actions.admin.verificationStep.create) const verificationStepUpdateResult = Astro.getActionResult(actions.admin.verificationStep.update) const verificationStepDeleteResult = Astro.getActionResult(actions.admin.verificationStep.delete) Astro.locals.banners.addIfSuccess(serviceResult, 'Service updated successfully') Astro.locals.banners.addIfSuccess(eventCreateResult, 'Event created successfully') Astro.locals.banners.addIfSuccess(eventToggleResult, 'Event visibility updated successfully') Astro.locals.banners.addIfSuccess(eventDeleteResult, 'Event deleted successfully') Astro.locals.banners.addIfSuccess(eventUpdateResult, 'Event updated successfully') Astro.locals.banners.addIfSuccess(verificationStepCreateResult, 'Verification step added successfully') Astro.locals.banners.addIfSuccess(verificationStepUpdateResult, 'Verification step updated successfully') Astro.locals.banners.addIfSuccess(verificationStepDeleteResult, 'Verification step deleted successfully') if (serviceResult && !serviceResult.error && slug !== serviceResult.data.service.slug) { return Astro.redirect(`/admin/services/${serviceResult.data.service.slug}/edit`) } const serviceInputErrors = isInputError(serviceResult?.error) ? serviceResult.error.fields : {} const eventInputErrors = isInputError(eventCreateResult?.error) ? eventCreateResult.error.fields : {} const eventUpdateInputErrors = isInputError(eventUpdateResult?.error) ? eventUpdateResult.error.fields : {} const verificationStepInputErrors = isInputError(verificationStepCreateResult?.error) ? verificationStepCreateResult.error.fields : {} const verificationStepUpdateInputErrors = isInputError(verificationStepUpdateResult?.error) ? verificationStepUpdateResult.error.fields : {} if (!slug) return Astro.rewrite('/404') const service = await Astro.locals.banners.try('Error fetching service', () => prisma.service.findUnique({ where: { slug }, include: { attributes: { select: { attribute: { select: { id: true, }, }, }, }, categories: { select: { id: true, }, }, events: { orderBy: { startedAt: 'desc', }, }, verificationRequests: { select: { id: true, user: { select: { id: true, name: true, displayName: true, }, }, createdAt: true, }, orderBy: { createdAt: 'desc', }, }, verificationSteps: { orderBy: { createdAt: 'desc', }, }, contactMethods: { orderBy: { label: 'asc', }, }, _count: { select: { verificationRequests: true, }, }, }, }) ) if (!service) return Astro.rewrite('/404') const categories = await Astro.locals.banners.try( 'Error fetching categories', () => prisma.category.findMany({ orderBy: { name: 'asc' }, }), [] ) const attributes = await Astro.locals.banners.try( 'Error fetching attributes', () => prisma.attribute.findMany({ orderBy: { category: 'asc' }, }), [] ) const inputBaseClasses = 'w-full rounded-md border-zinc-600 bg-zinc-700/80 p-2 text-zinc-200 placeholder-zinc-400 focus:border-sky-500 focus:ring-1 focus:ring-sky-500 text-sm' const labelBaseClasses = 'block text-sm font-medium text-zinc-300 mb-1' const errorTextClasses = 'mt-1 text-xs text-red-400' const checkboxLabelClasses = 'inline-flex items-center text-sm text-zinc-300' const checkboxInputClasses = 'rounded-sm border-zinc-500 bg-zinc-700 text-sky-500 focus:ring-sky-500 focus:ring-offset-zinc-800' // Button style constants const buttonPrimaryClasses = 'inline-flex items-center justify-center rounded-md border border-transparent bg-sky-600 px-3 py-1.5 text-sm font-medium text-white shadow-sm hover:bg-sky-700 focus:outline-none focus:ring-2 focus:ring-sky-500 focus:ring-offset-2 focus:ring-offset-zinc-900' const buttonSmallBaseClasses = 'rounded-md px-2 py-1 text-xs font-medium' const buttonSmallPrimaryClasses = cn( buttonSmallBaseClasses, 'text-sky-400 hover:bg-sky-700/30 hover:text-sky-300' ) const buttonSmallSecondaryClasses = cn( buttonSmallBaseClasses, 'text-zinc-400 hover:bg-zinc-700/50 hover:text-zinc-300' ) const buttonSmallDestructiveClasses = cn( buttonSmallBaseClasses, 'text-red-400 hover:bg-red-700/30 hover:text-red-300' ) const buttonSmallWarningClasses = cn( buttonSmallBaseClasses, 'text-yellow-400 hover:bg-yellow-700/30 hover:text-yellow-300' ) ---

Editing {service.name} [{service.id}]

View Service Page

Service Details

{serviceInputErrors.name &&

{serviceInputErrors.name.join(', ')}

}
{ serviceInputErrors.description && (

{serviceInputErrors.description.join(', ')}

) }
{serviceInputErrors.slug &&

{serviceInputErrors.slug.join(', ')}

}
{ serviceInputErrors.serviceUrls && (

{serviceInputErrors.serviceUrls.join(', ')}

) }
{ serviceInputErrors.tosUrls && (

{serviceInputErrors.tosUrls.join(', ')}

) }
{ serviceInputErrors.onionUrls && (

{serviceInputErrors.onionUrls.join(', ')}

) }
{/* Assuming i2pUrls might have errors, add error display if schema supports it */} { /* serviceInputErrors.i2pUrls && (

{serviceInputErrors.i2pUrls.join(', ')}

) */ }

Leave empty to keep the current image. Square images (e.g., 256x256) recommended.

{ serviceInputErrors.imageFile && (

{serviceInputErrors.imageFile.join(', ')}

) }
{ service.imageUrl ? (
) : (
) }
{ categories.map((category) => ( )) }
{ serviceInputErrors.categories && (

{serviceInputErrors.categories.join(', ')}

) }
{ serviceInputErrors.kycLevel && (

{serviceInputErrors.kycLevel.join(', ')}

) }
{ Object.values(AttributeCategory).map((categoryValue) => (

{categoryValue}

{attributes .filter((attr) => attr.category === categoryValue) .map((attr) => ( ))}
{serviceInputErrors.attributes && (

{serviceInputErrors.attributes.join(', ')}

)}
)) }
{ serviceInputErrors.verificationStatus && (

{serviceInputErrors.verificationStatus.join(', ')}

) }
{ Object.values(Currency).map((currency) => ( )) }
{ serviceInputErrors.acceptedCurrencies && (

{serviceInputErrors.acceptedCurrencies.join(', ')}

) }
{ serviceInputErrors.verificationSummary && (

{serviceInputErrors.verificationSummary.join(', ')}

) }
{ serviceInputErrors.verificationProofMd && (

{serviceInputErrors.verificationProofMd.join(', ')}

) }
{ serviceInputErrors.referral && (

{serviceInputErrors.referral.join(', ')}

) }
{ serviceVisibilities.map((visibility) => (
)) }
{ serviceInputErrors.serviceVisibility && (

{serviceInputErrors.serviceVisibility.join(', ')}

) }

Events

{ service.events.length > 0 && (

Existing Events

{service.events.map((event) => (
{event.title} {event.type} {!event.visible && ( Hidden )}

{event.content}

Started: {new Date(event.startedAt).toLocaleDateString()} Ended: {event.endedAt ? event.endedAt === event.startedAt ? '1-time event' : new Date(event.endedAt).toLocaleDateString() : 'Ongoing'} {event.source && Source: {event.source}}
{/* Edit Event Form - Hidden by default */}
))}
) }

Add New Event

{eventInputErrors.title &&

{eventInputErrors.title.join(', ')}

}
{ eventInputErrors.content && (

{eventInputErrors.content.join(', ')}

) }
{ eventInputErrors.startedAt && (

{eventInputErrors.startedAt.join(', ')}

) }
{ eventInputErrors.endedAt && (

{eventInputErrors.endedAt.join(', ')}

) }

End Date Options:

  • Leave empty: Event is ongoing.
  • Same as start date: One-time event.
  • Future date: Event with specific end date.
{ eventInputErrors.source && (

{eventInputErrors.source.join(', ')}

) }
{eventInputErrors.type &&

{eventInputErrors.type.join(', ')}

}

Verification Steps

{ service.verificationSteps.length > 0 && (

Submitted Steps

{service.verificationSteps.map((step) => (
{step.title} {step.status.replace('_', ' ')}

{step.description}

{step.evidenceMd && (

Evidence provided (see edit form)

)}

Created: {new Date(step.createdAt).toLocaleDateString()}

{/* Edit Verification Step Form - Hidden by default */}
))}
) }

Add New Verification Step

{ verificationStepInputErrors.title && (

{verificationStepInputErrors.title.join(', ')}

) }
{ verificationStepInputErrors.description && (

{verificationStepInputErrors.description.join(', ')}

) }
{ verificationStepInputErrors.evidenceMd && (

{verificationStepInputErrors.evidenceMd.join(', ')}

) }
{ verificationStepInputErrors.status && (

{verificationStepInputErrors.status.join(', ')}

) }

Verification Requests {service._count.verificationRequests}

{ service.verificationRequests.length > 0 ? (
{service.verificationRequests.map((request) => ( ))}
User Requested At
{request.user.displayName ?? request.user.name} {new Date(request.createdAt).toLocaleString()}
) : (

No verification requests yet.

) }

Contact Methods

{ service.contactMethods.length > 0 && (

Existing Contact Methods

{service.contactMethods.map((method) => (
{method.label}

{method.value}

{method.info &&

{method.info}

}
{/* Edit Contact Method Form - Hidden by default */}
))}
) }

Add New Contact Method