diff --git a/src/actions/index.ts b/src/actions/index.ts index 991e642..b14d191 100644 --- a/src/actions/index.ts +++ b/src/actions/index.ts @@ -1,333 +1,9 @@ -import { ActionError, defineAction } from "astro:actions"; -import { z } from "astro/zod"; -import { AtUri } from "@atproto/api"; - -import { getAtmosphereCommunityDid } from "../lib/community/atmosphere"; -import { resolveHandleToDid } from "../lib/community/identity"; -import { type JoinStatusCode } from "../lib/community/join-status"; -import { - type ShareStatusCode, - type UnshareStatusCode, -} from "../lib/community/share-status"; -import { runJoin, runLeave } from "../lib/community/membership-mutations"; -import { parseSharedDocumentRef } from "../lib/community/shared-content"; -import { - getRepoRecordByUri, - getShareCandidateByUri, -} from "../lib/community/share-candidates"; -import { - OpenSocialCommunityError, - getMembership, -} from "../lib/opensocial/membership"; -import { - SHARED_CONTENT_COLLECTION, - shareContentWithCommunity, - unshareContentWithCommunity, -} from "../lib/opensocial/content-sharing"; -import { - RSVP_STATUS_GOING, - RSVP_STATUS_NOT_GOING, - setRsvpStatus, - type RsvpStatus, -} from "../lib/rsvps"; - - -function joinPayload(status: JoinStatusCode, community?: string) { - return { status, community }; -} - -const EVENT_COLLECTION = "community.lexicon.calendar.event"; - -function isValidEventUri(uri: string): boolean { - try { - const parsed = new AtUri(uri); - return ( - parsed.host.startsWith("did:") && - parsed.collection === EVENT_COLLECTION && - parsed.rkey.length > 0 - ); - } catch { - return false; - } -} - -function parseSharedContentRecordUri( - uri: string, - communityDid: string, -): AtUri | null { - try { - const parsed = new AtUri(uri); - return parsed.host === communityDid && - parsed.collection === SHARED_CONTENT_COLLECTION && - parsed.rkey.length > 0 - ? parsed - : null; - } catch { - return null; - } -} - -const FORM_STATUS_TO_RSVP_STATUS: Record<"going" | "notgoing", RsvpStatus> = { - going: RSVP_STATUS_GOING, - notgoing: RSVP_STATUS_NOT_GOING, -}; - -function throwActionError( - code: "UNAUTHORIZED" | "FORBIDDEN" | "BAD_REQUEST" | "INTERNAL_SERVER_ERROR", - message: string, -): never { - throw new ActionError({ code, message }); -} - -function isPermissionError(error: unknown): boolean { - const maybeError = error as { - status?: number; - error?: string; - message?: string; - }; - const text = `${maybeError.error ?? ""} ${ - maybeError.message ?? "" - }`.toLowerCase(); - return ( - maybeError.status === 401 || - maybeError.status === 403 || - text.includes("scope") - ); -} +import { membershipActions } from "./membership"; +import { sharingActions } from "./sharing"; +import { rsvpActions } from "./rsvp"; export const server = { - joinAtmosphereCommunity: defineAction({ - accept: "form", - handler: async (_input, ctx) => { - const loggedInUser = ctx.locals.loggedInUser; - if (!loggedInUser) { - return joinPayload("signin"); - } - const status = await runJoin(loggedInUser, await getAtmosphereCommunityDid()); - return joinPayload(status); - }, - }), - - // Listing-page join for any opensocial community, addressed by handle. - joinOpenSocialCommunity: defineAction({ - accept: "form", - input: z.object({ handle: z.string().min(1) }), - handler: async (input, ctx) => { - const loggedInUser = ctx.locals.loggedInUser; - if (!loggedInUser) { - return joinPayload("signin", input.handle); - } - let communityDid: string; - try { - communityDid = await resolveHandleToDid(input.handle); - } catch { - return joinPayload("missing", input.handle); - } - const status = await runJoin(loggedInUser, communityDid); - return joinPayload(status, input.handle); - }, - }), - - // Listing-page leave for any opensocial community, addressed by handle. - leaveOpenSocialCommunity: defineAction({ - accept: "form", - input: z.object({ handle: z.string().min(1) }), - handler: async (input, ctx) => { - const loggedInUser = ctx.locals.loggedInUser; - if (!loggedInUser) { - return joinPayload("signin", input.handle); - } - let communityDid: string; - try { - communityDid = await resolveHandleToDid(input.handle); - } catch { - return joinPayload("missing", input.handle); - } - const status = await runLeave(loggedInUser, communityDid); - return joinPayload(status, input.handle); - }, - }), - - leaveAtmosphereCommunity: defineAction({ - accept: "form", - handler: async (_input, ctx) => { - const loggedInUser = ctx.locals.loggedInUser; - if (!loggedInUser) { - return joinPayload("signin"); - } - const status = await runLeave(loggedInUser, await getAtmosphereCommunityDid()); - return joinPayload(status); - }, - }), - - shareAtmosphereContent: defineAction({ - accept: "form", - input: z.object({ - candidateUri: z.string().min(1), - sourceRepo: z.string().min(1).optional(), - }), - handler: async (input, ctx): Promise<{ status: ShareStatusCode }> => { - const loggedInUser = ctx.locals.loggedInUser; - if (!loggedInUser) { - return { status: "signin" }; - } - - try { - const communityDid = await getAtmosphereCommunityDid(); - const membership = await getMembership({ - communityDid, - userDid: loggedInUser.did, - }); - if (!membership.isMember && !membership.isAdmin) { - return { status: "not-member" }; - } - - const sourceRepo = input.sourceRepo ?? loggedInUser.handle; - const candidate = await getShareCandidateByUri( - sourceRepo, - input.candidateUri, - ); - if (!candidate) { - return { status: "not-found" }; - } - - await shareContentWithCommunity({ - communityDid, - userDid: loggedInUser.did, - candidate, - }); - return { status: "ok" }; - } catch (err) { - if (err instanceof OpenSocialCommunityError) { - if (err.code === "PermissionDenied") { - return { status: "no-permission" }; - } - console.warn("[shareAtmosphereContent] OpenSocial error", err); - return { status: "failed" }; - } - if (isPermissionError(err)) { - return { status: "no-permission" }; - } - console.warn("[shareAtmosphereContent] unexpected error", err); - return { status: "failed" }; - } - }, - }), - - unshareAtmosphereContent: defineAction({ - accept: "form", - input: z.object({ - shareRecordUri: z.string().min(1), - }), - handler: async ( - input, - ctx, - ): Promise<{ status: UnshareStatusCode; shareRecordUri: string }> => { - const loggedInUser = ctx.locals.loggedInUser; - if (!loggedInUser) { - return { status: "signin", shareRecordUri: input.shareRecordUri }; - } - - try { - const communityDid = await getAtmosphereCommunityDid(); - const parsedShareRecordUri = parseSharedContentRecordUri( - input.shareRecordUri, - communityDid, - ); - if (!parsedShareRecordUri) { - return { status: "not-found", shareRecordUri: input.shareRecordUri }; - } - - const response = await getRepoRecordByUri(input.shareRecordUri); - if (!response || typeof response.value !== "object" || response.value === null) { - return { status: "not-found", shareRecordUri: input.shareRecordUri }; - } - - const sharedRecord = parseSharedDocumentRef( - response.value as Record, - { - source: communityDid, - shareRecordUri: response.uri, - shareRecordRkey: parsedShareRecordUri.rkey, - }, - ); - if (!sharedRecord) { - return { status: "not-found", shareRecordUri: input.shareRecordUri }; - } - - const isOriginalSharer = sharedRecord.sharedBy === loggedInUser.did; - if (!isOriginalSharer) { - const membership = await getMembership({ - communityDid, - userDid: loggedInUser.did, - }); - if (!membership.isAdmin) { - return { status: "not-author", shareRecordUri: input.shareRecordUri }; - } - } - - await unshareContentWithCommunity({ - communityDid, - userDid: loggedInUser.did, - shareRecordRkey: sharedRecord.shareRecordRkey, - }); - return { status: "ok", shareRecordUri: input.shareRecordUri }; - } catch (err) { - if (err instanceof OpenSocialCommunityError) { - if (err.code === "RecordNotFound") { - return { status: "missing", shareRecordUri: input.shareRecordUri }; - } - if (err.code === "PermissionDenied") { - return { status: "no-permission", shareRecordUri: input.shareRecordUri }; - } - console.warn("[unshareAtmosphereContent] OpenSocial error", err); - return { status: "failed", shareRecordUri: input.shareRecordUri }; - } - if (isPermissionError(err)) { - return { status: "no-permission", shareRecordUri: input.shareRecordUri }; - } - console.warn("[unshareAtmosphereContent] unexpected error", err); - return { status: "failed", shareRecordUri: input.shareRecordUri }; - } - }, - }), - - - rsvpEvent: defineAction({ - accept: "form", - input: z.object({ - eventUri: z.string(), - eventCid: z.string(), - eventName: z.string().optional(), - status: z.enum(["going", "notgoing"]), - }), - handler: async (input, ctx) => { - const loggedInUser = ctx.locals.loggedInUser; - - if (!loggedInUser) { - throwActionError("UNAUTHORIZED", "You need to sign in to RSVP."); - } - - if (!isValidEventUri(input.eventUri) || input.eventCid.length === 0) { - throwActionError("BAD_REQUEST", "That RSVP request is invalid."); - } - - try { - await setRsvpStatus( - loggedInUser, - { uri: input.eventUri, cid: input.eventCid }, - FORM_STATUS_TO_RSVP_STATUS[input.status], - ); - } catch (error) { - throwActionError( - isPermissionError(error) ? "FORBIDDEN" : "INTERNAL_SERVER_ERROR", - isPermissionError(error) - ? "Your login is missing permission to RSVP to events." - : "We couldn't update your RSVP right now. Please try again.", - ); - } - - return { status: input.status, eventName: input.eventName ?? null }; - }, - }), + ...membershipActions, + ...sharingActions, + ...rsvpActions, }; diff --git a/src/actions/membership.ts b/src/actions/membership.ts new file mode 100644 index 0000000..e41d800 --- /dev/null +++ b/src/actions/membership.ts @@ -0,0 +1,168 @@ +import { ActionError, defineAction } from "astro:actions"; +import { z } from "astro/zod"; + +import { getAtmosphereCommunityDid } from "../lib/community/atmosphere"; +import { resolveHandleToDid } from "../lib/community/identity"; +import { type JoinOutcomeCode } from "../lib/community/join-status"; +import { runJoin, runLeave } from "../lib/community/membership-mutations"; +import { OpenSocialCommunityError } from "../lib/opensocial/client"; + +interface AtmosphereJoinResult { + outcome: JoinOutcomeCode; +} + +interface ListingJoinResult { + outcome: JoinOutcomeCode; + community: string; +} + +export const membershipActions = { + joinAtmosphereCommunity: defineAction({ + accept: "form", + handler: async (_input, ctx): Promise => { + const loggedInUser = ctx.locals.loggedInUser; + if (!loggedInUser) { + throw new ActionError({ + code: "UNAUTHORIZED", + message: "Sign in first, then try again.", + }); + } + + try { + const outcome = await runJoin( + loggedInUser, + await getAtmosphereCommunityDid(), + ); + return { outcome }; + } catch (error) { + throw toMembershipActionError(error); + } + }, + }), + + // Listing-page join for any opensocial community, addressed by handle. + joinOpenSocialCommunity: defineAction({ + accept: "form", + input: z.object({ handle: z.string().min(1) }), + handler: async (input, ctx): Promise => { + const loggedInUser = ctx.locals.loggedInUser; + if (!loggedInUser) { + throw new ActionError({ + code: "UNAUTHORIZED", + message: "Sign in first, then try again.", + }); + } + let communityDid: string; + try { + communityDid = await resolveHandleToDid(input.handle); + } catch { + throw new ActionError({ + code: "INTERNAL_SERVER_ERROR", + message: "We couldn't verify that community right now. Try again later.", + }); + } + try { + const outcome = await runJoin(loggedInUser, communityDid); + return { outcome, community: input.handle }; + } catch (error) { + throw toMembershipActionError(error); + } + }, + }), + + // Listing-page leave for any opensocial community, addressed by handle. + leaveOpenSocialCommunity: defineAction({ + accept: "form", + input: z.object({ handle: z.string().min(1) }), + handler: async (input, ctx): Promise => { + const loggedInUser = ctx.locals.loggedInUser; + if (!loggedInUser) { + throw new ActionError({ + code: "UNAUTHORIZED", + message: "Sign in first, then try again.", + }); + } + let communityDid: string; + try { + communityDid = await resolveHandleToDid(input.handle); + } catch { + throw new ActionError({ + code: "INTERNAL_SERVER_ERROR", + message: "We couldn't verify that community right now. Try again later.", + }); + } + try { + const outcome = await runLeave(loggedInUser, communityDid); + return { outcome, community: input.handle }; + } catch (error) { + throw toMembershipActionError(error); + } + }, + }), + + leaveAtmosphereCommunity: defineAction({ + accept: "form", + handler: async (_input, ctx): Promise => { + const loggedInUser = ctx.locals.loggedInUser; + if (!loggedInUser) { + throw new ActionError({ + code: "UNAUTHORIZED", + message: "Sign in first, then try again.", + }); + } + + try { + const outcome = await runLeave( + loggedInUser, + await getAtmosphereCommunityDid(), + ); + return { outcome }; + } catch (error) { + throw toMembershipActionError(error); + } + }, + }), +}; + +function toMembershipActionError(error: unknown): ActionError { + if (error instanceof ActionError) { + return error; + } + + if (error instanceof OpenSocialCommunityError) { + if (error.code === "CommunityNotFound") { + return new ActionError({ + code: "NOT_FOUND", + message: "Couldn't find that community right now. Try again later.", + }); + } + } + + if (isPermissionError(error)) { + return new ActionError({ + code: "FORBIDDEN", + message: + "Your login needs community membership permission. Log out and back in, then try again.", + }); + } + + console.warn("[membershipActions] unexpected error", error); + return new ActionError({ + code: "INTERNAL_SERVER_ERROR", + message: "Something went wrong. Try again later.", + }); +} + +function isPermissionError(error: unknown): boolean { + const maybeError = error as { + status?: number; + error?: string; + message?: string; + }; + const text = `${maybeError.error ?? ""} ${maybeError.message ?? ""}`.toLowerCase(); + return ( + maybeError.status === 401 || + maybeError.status === 403 || + text.includes("scope") + ); +} diff --git a/src/actions/rsvp.ts b/src/actions/rsvp.ts new file mode 100644 index 0000000..270c5c7 --- /dev/null +++ b/src/actions/rsvp.ts @@ -0,0 +1,95 @@ +import { ActionError, defineAction } from "astro:actions"; +import { z } from "astro/zod"; +import { AtUri } from "@atproto/api"; + +import { type RsvpOutcomeCode } from "../lib/community/rsvp-status"; +import { + RSVP_STATUS_GOING, + RSVP_STATUS_NOT_GOING, + setRsvpStatus, + type RsvpStatus, +} from "../lib/rsvps"; + +const EVENT_COLLECTION = "community.lexicon.calendar.event"; + +function isValidEventUri(uri: string): boolean { + try { + const parsed = new AtUri(uri); + return ( + parsed.host.startsWith("did:") && + parsed.collection === EVENT_COLLECTION && + parsed.rkey.length > 0 + ); + } catch { + return false; + } +} + +const FORM_STATUS_TO_RSVP_STATUS: Record<"going" | "notgoing", RsvpStatus> = { + going: RSVP_STATUS_GOING, + notgoing: RSVP_STATUS_NOT_GOING, +}; + +function isPermissionError(error: unknown): boolean { + const maybeError = error as { + status?: number; + error?: string; + message?: string; + }; + const text = `${maybeError.error ?? ""} ${ + maybeError.message ?? "" + }`.toLowerCase(); + return ( + maybeError.status === 401 || + maybeError.status === 403 || + text.includes("scope") + ); +} + +export const rsvpActions = { + rsvpEvent: defineAction({ + accept: "form", + input: z.object({ + eventUri: z.string(), + eventCid: z.string(), + eventName: z.string().optional(), + status: z.enum(["going", "notgoing"]), + }), + handler: async (input, ctx): Promise<{ outcome: RsvpOutcomeCode; eventName: string | null }> => { + const loggedInUser = ctx.locals.loggedInUser; + + if (!loggedInUser) { + throw new ActionError({ + code: "UNAUTHORIZED", + message: "You need to sign in to RSVP.", + }); + } + + if (!isValidEventUri(input.eventUri) || input.eventCid.length === 0) { + throw new ActionError({ code: "BAD_REQUEST", message: "That RSVP request is invalid." }); + } + + try { + await setRsvpStatus( + loggedInUser, + { uri: input.eventUri, cid: input.eventCid }, + FORM_STATUS_TO_RSVP_STATUS[input.status], + ); + } catch (error) { + if (isPermissionError(error)) { + throw new ActionError({ + code: "FORBIDDEN", + message: "Your login is missing permission to RSVP to events.", + }); + } + console.warn("[rsvpEvent] unexpected error", error); + throw new ActionError({ + code: "INTERNAL_SERVER_ERROR", + message: "We couldn't update your RSVP right now. Please try again.", + }); + } + + return { outcome: input.status, eventName: input.eventName ?? null }; + }, + }), +}; diff --git a/src/actions/sharing.ts b/src/actions/sharing.ts new file mode 100644 index 0000000..cfbfebf --- /dev/null +++ b/src/actions/sharing.ts @@ -0,0 +1,234 @@ +import { ActionError, defineAction } from "astro:actions"; +import { z } from "astro/zod"; +import { AtUri } from "@atproto/api"; + +import { getAtmosphereCommunityDid } from "../lib/community/atmosphere"; +import { + type ShareOutcomeCode, + type UnshareOutcomeCode, +} from "../lib/community/share-status"; +import { parseSharedDocumentRef } from "../lib/community/shared-content"; +import { + getRepoRecordByUri, + getShareCandidateByUri, +} from "../lib/community/share-candidates"; +import { getMembership } from "../lib/opensocial/membership"; +import { OpenSocialCommunityError } from "../lib/opensocial/client"; +import { + SHARED_CONTENT_COLLECTION, + shareContentWithCommunity, + unshareContentWithCommunity, +} from "../lib/opensocial/content-sharing"; + +function parseSharedContentRecordUri( + uri: string, + communityDid: string, +): AtUri | null { + try { + const parsed = new AtUri(uri); + return parsed.host === communityDid && + parsed.collection === SHARED_CONTENT_COLLECTION && + parsed.rkey.length > 0 + ? parsed + : null; + } catch { + return null; + } +} + +function isPermissionError(error: unknown): boolean { + const maybeError = error as { + status?: number; + error?: string; + message?: string; + }; + const text = `${maybeError.error ?? ""} ${ + maybeError.message ?? "" + }`.toLowerCase(); + return ( + maybeError.status === 401 || + maybeError.status === 403 || + text.includes("scope") + ); +} + +export const sharingActions = { + shareAtmosphereContent: defineAction({ + accept: "form", + input: z.object({ + candidateUri: z.string().min(1), + sourceRepo: z.string().min(1).optional(), + }), + handler: async (input, ctx): Promise<{ outcome: ShareOutcomeCode }> => { + const loggedInUser = ctx.locals.loggedInUser; + if (!loggedInUser) { + throw new ActionError({ + code: "UNAUTHORIZED", + message: "Sign in first, then try sharing again.", + }); + } + + try { + const communityDid = await getAtmosphereCommunityDid(); + const membership = await getMembership({ + communityDid, + userDid: loggedInUser.did, + }); + if (!membership.isMember && !membership.isAdmin) { + return { outcome: "not-member" }; + } + + const sourceRepo = input.sourceRepo ?? loggedInUser.handle; + const candidate = await getShareCandidateByUri( + sourceRepo, + input.candidateUri, + ); + if (!candidate) { + throw new ActionError({ + code: "NOT_FOUND", + message: "That item couldn't be shared. Pick another one and try again.", + }); + } + + await shareContentWithCommunity({ + communityDid, + userDid: loggedInUser.did, + candidate, + }); + return { outcome: "ok" }; + } catch (err) { + if (err instanceof ActionError) { + throw err; + } + if (err instanceof OpenSocialCommunityError) { + if (err.code === "PermissionDenied") { + throw new ActionError({ + code: "FORBIDDEN", + message: + "Your login needs community content permission. Log out and back in, then try again.", + }); + } + } + if (isPermissionError(err)) { + throw new ActionError({ + code: "FORBIDDEN", + message: + "Your login needs community content permission. Log out and back in, then try again.", + }); + } + console.warn("[shareAtmosphereContent] unexpected error", err); + throw new ActionError({ + code: "INTERNAL_SERVER_ERROR", + message: + "Something went wrong while updating community content. Try again later.", + }); + } + }, + }), + + unshareAtmosphereContent: defineAction({ + accept: "form", + input: z.object({ + shareRecordUri: z.string().min(1), + }), + handler: async ( + input, + ctx, + ): Promise<{ outcome: UnshareOutcomeCode; shareRecordUri: string }> => { + const loggedInUser = ctx.locals.loggedInUser; + if (!loggedInUser) { + throw new ActionError({ + code: "UNAUTHORIZED", + message: "Sign in first, then try unsharing again.", + }); + } + + try { + const communityDid = await getAtmosphereCommunityDid(); + const parsedShareRecordUri = parseSharedContentRecordUri( + input.shareRecordUri, + communityDid, + ); + if (!parsedShareRecordUri) { + throw new ActionError({ + code: "BAD_REQUEST", + message: "That item couldn't be removed. Refresh the page and try again.", + }); + } + + const response = await getRepoRecordByUri(input.shareRecordUri); + if (!response || typeof response.value !== "object" || response.value === null) { + throw new ActionError({ + code: "NOT_FOUND", + message: "That item couldn't be removed. Refresh the page and try again.", + }); + } + + const sharedRecord = parseSharedDocumentRef( + response.value as Record, + { + source: communityDid, + shareRecordUri: response.uri, + shareRecordRkey: parsedShareRecordUri.rkey, + }, + ); + if (!sharedRecord) { + throw new ActionError({ + code: "NOT_FOUND", + message: "That item couldn't be removed. Refresh the page and try again.", + }); + } + + const isOriginalSharer = sharedRecord.sharedBy === loggedInUser.did; + if (!isOriginalSharer) { + const membership = await getMembership({ + communityDid, + userDid: loggedInUser.did, + }); + if (!membership.isAdmin) { + throw new ActionError({ + code: "FORBIDDEN", + message: "Only the original sharer can remove that item.", + }); + } + } + + await unshareContentWithCommunity({ + communityDid, + userDid: loggedInUser.did, + shareRecordRkey: sharedRecord.shareRecordRkey, + }); + return { outcome: "ok", shareRecordUri: input.shareRecordUri }; + } catch (err) { + if (err instanceof ActionError) { + throw err; + } + if (err instanceof OpenSocialCommunityError) { + if (err.code === "RecordNotFound") { + return { outcome: "missing", shareRecordUri: input.shareRecordUri }; + } + if (err.code === "PermissionDenied") { + throw new ActionError({ + code: "FORBIDDEN", + message: + "Your login needs community content permission. Log out and back in, then try again.", + }); + } + } + if (isPermissionError(err)) { + throw new ActionError({ + code: "FORBIDDEN", + message: + "Your login needs community content permission. Log out and back in, then try again.", + }); + } + console.warn("[unshareAtmosphereContent] unexpected error", err); + throw new ActionError({ + code: "INTERNAL_SERVER_ERROR", + message: + "Something went wrong while updating community content. Try again later.", + }); + } + }, + }), +}; diff --git a/src/components/communities/JoinStatusNotice.astro b/src/components/communities/JoinStatusNotice.astro index d188d1b..7a3b9c7 100644 --- a/src/components/communities/JoinStatusNotice.astro +++ b/src/components/communities/JoinStatusNotice.astro @@ -1,16 +1,18 @@ --- import { - getJoinStatusCopy, - type JoinStatusCode, + getJoinOutcomeCopy, + type JoinOutcomeCode, } from "../../lib/community/join-status"; +import type { CommunityActionNotice } from "../../lib/community/action-error-copy"; interface Props { - status?: JoinStatusCode | string | null; + outcome?: JoinOutcomeCode | null; + error?: CommunityActionNotice | null; communityName?: string | null; } -const { status, communityName } = Astro.props; -const notice = getJoinStatusCopy(status); +const { outcome, error, communityName } = Astro.props; +const notice = error ?? getJoinOutcomeCopy(outcome); if (!notice) { return; diff --git a/src/components/content/SharableContentIcon.astro b/src/components/content/ShareableContentIcon.astro similarity index 99% rename from src/components/content/SharableContentIcon.astro rename to src/components/content/ShareableContentIcon.astro index 3871f30..a7e2ff9 100644 --- a/src/components/content/SharableContentIcon.astro +++ b/src/components/content/ShareableContentIcon.astro @@ -1,10 +1,7 @@ - --- - interface Props { kind: 'event' | 'document'; } - --- {Astro.props.kind === 'event' ? ( diff --git a/src/components/content/ShareableContentList.astro b/src/components/content/ShareableContentList.astro index e8faa5c..fa49690 100644 --- a/src/components/content/ShareableContentList.astro +++ b/src/components/content/ShareableContentList.astro @@ -1,7 +1,7 @@ --- import type { ShareCandidateList } from '../../lib/community/share-candidates'; import { actions } from 'astro:actions'; -import SharableContentIcon from './SharableContentIcon.astro'; +import ShareableContentIcon from './ShareableContentIcon.astro'; interface Props { candidates: ShareCandidateList | null; @@ -75,7 +75,7 @@ function formatDate(date: Date | undefined): string { const rowUrl = candidate.previewUrl ?? candidate.uri; return (