From ca71512cfc8ef0c1df9b5fd408fc413d5af6d63a Mon Sep 17 00:00:00 2001 From: eric Date: Thu, 28 Sep 2023 10:37:22 -0400 Subject: [PATCH 01/13] Repeating evaluator invites --- core/app/c/[communitySlug]/LoginSwitcher.tsx | 4 +- .../c/[communitySlug]/pubs/[pubId]/page.tsx | 2 +- .../stages/components/Assign.tsx | 11 +- core/lib/server/autosuggest.ts | 36 ++- core/lib/server/email.ts | 16 +- core/lib/types.ts | 6 +- core/pages/api/v0/[...ts-rest].ts | 8 +- .../prisma/exampleCommunitySeeds/unjournal.ts | 6 +- .../migration.sql | 13 + core/prisma/schema.prisma | 4 +- core/prisma/seed.ts | 17 +- .../evaluations/app/actions/manage/actions.ts | 20 +- .../app/actions/manage/emailForm.tsx | 255 ++++++++++++++---- integrations/evaluations/lib/metadata.ts | 182 ------------- .../contracts/src/resources/integrations.ts | 30 ++- packages/sdk/src/client.ts | 35 ++- packages/sdk/src/react/IntegrationAvatar.tsx | 4 +- .../sdk/src/react/IntegrationLayoutHeader.tsx | 2 +- packages/ui/src/icon.tsx | 2 +- 19 files changed, 370 insertions(+), 283 deletions(-) create mode 100644 core/prisma/migrations/20230928124422_user_name_first_last/migration.sql delete mode 100644 integrations/evaluations/lib/metadata.ts diff --git a/core/app/c/[communitySlug]/LoginSwitcher.tsx b/core/app/c/[communitySlug]/LoginSwitcher.tsx index 7a6c87a08e..36b8fe9088 100644 --- a/core/app/c/[communitySlug]/LoginSwitcher.tsx +++ b/core/app/c/[communitySlug]/LoginSwitcher.tsx @@ -11,10 +11,10 @@ export default async function LoginSwitcher() {
- {loginData.name[0]} + {loginData.firstName[0]}
-
{loginData.name}
+
{loginData.firstName}
{loginData.email}
diff --git a/core/app/c/[communitySlug]/pubs/[pubId]/page.tsx b/core/app/c/[communitySlug]/pubs/[pubId]/page.tsx index e5fbb277c1..c49e3e5364 100644 --- a/core/app/c/[communitySlug]/pubs/[pubId]/page.tsx +++ b/core/app/c/[communitySlug]/pubs/[pubId]/page.tsx @@ -89,7 +89,7 @@ export default async function Page({
- {user.name[0]} + {user.firstName[0]}
); diff --git a/core/app/c/[communitySlug]/stages/components/Assign.tsx b/core/app/c/[communitySlug]/stages/components/Assign.tsx index c10fca0d45..e70fd850d1 100644 --- a/core/app/c/[communitySlug]/stages/components/Assign.tsx +++ b/core/app/c/[communitySlug]/stages/components/Assign.tsx @@ -92,17 +92,20 @@ export default function Assign(props: Props) { height={20} />
- {user.name} + + {user.firstName} {user.lastName} + - Assign {getTitle(props.pub)} to {user.name}? + Assign {getTitle(props.pub)} to {user.firstName}{" "} + {user.lastName}? - {user.name} will be notified that they have been assigned to - this Pub. + {user.firstName} {user.lastName} will be notified that they + have been assigned to this Pub. + ); +}; + +type EvaluatorInviteProps = { + control: Control; + item: SuggestedMembersQuery & { key: string }; + index: number; + onRemove: (index: number) => void; + onSuggest: (index: number, query: SuggestedMembersQuery) => void; +}; + +const EvaluatorInvite = (props: EvaluatorInviteProps) => { + const value = useWatch>({ + control: props.control, + name: `invites.${props.index}`, + }); + return ( +
+ ( + + {props.index === 0 && ( + <> + Email Address + + The email of the evaluator you'd like to invite. + + + )} + + + + + + )} + /> + ( + + {props.index === 0 && ( + <> + First Name + + The first name of the evaluator you'd like to invite. + + + )} + + + + + + )} + /> + ( + + {props.index === 0 && ( + <> + Last Name + + The last name of the evaluator you'd like to invite. + + + )} + + + + + + )} + /> + props.onSuggest(props.index, value as SuggestedMembersQuery)} + /> + +
+ ); +}; + export function EmailForm(props: Props) { const { toast } = useToast(); + const [suggestPending, startTransition] = useTransition(); const form = useForm>({ mode: "onChange", reValidateMode: "onChange", // TODO: generate fields using instance's configured PubType resolver: zodResolver(schema), - defaultValues: { - email: "", - name: "", - }, + // defaultValues: { + // invites: [ + // { email: "", firstName: "", lastName: "" }, + // { email: "", firstName: "", lastName: "" }, + // ], + // }, + }); + const { + fields: invites, + update, + remove, + append, + } = useFieldArray({ + control: form.control, + name: "invites", + keyName: "key", }); const [persistedValues, persist] = useLocalStorage>(props.instanceId); @@ -58,8 +192,9 @@ export function EmailForm(props: Props) { props.instanceId, props.pub.id, props.pub.values["unjournal:title"] as string, - values.email, - values.name + values.invites[0].email, + values.invites[0].firstName, + values.invites[0].lastName ); if ("error" in result && typeof result.error === "string") { toast({ @@ -72,17 +207,43 @@ export function EmailForm(props: Props) { title: "Success", description: "The email was sent successfully", }); - form.reset(); + } + }; + + const onSuggest = async (index: number, query: SuggestedMembersQuery) => { + console.log(query); + const result = await suggest(props.instanceId, query); + if ("error" in result && typeof result.error === "string") { + toast({ + title: "Error", + description: result.error, + variant: "destructive", + }); + } else if (Array.isArray(result)) { + if (result.length > 0) { + const [user] = result; + update(index, { ...values.invites[index], ...user }); + toast({ + title: "Success", + description: "A user was suggested", + }); + } else { + toast({ + title: "No matches found", + description: + "A user was not found for the given email, first name, or last name", + }); + } } }; // Load the persisted values. - const { reset } = form; - useEffect(() => { - // `keepDefaultValues` is set to true to prevent the form from - // validating fields that were not filled during the previous session. - reset(persistedValues, { keepDefaultValues: true }); - }, [reset]); + // const { reset } = form; + // useEffect(() => { + // // `keepDefaultValues` is set to true to prevent the form from + // // validating fields that were not filled during the previous session. + // reset(persistedValues, { keepDefaultValues: true }); + // }, [reset]); // Persist form values to local storage. This operation is debounced by // the timeout passed to . @@ -102,39 +263,29 @@ export function EmailForm(props: Props) { {props.pub.values["unjournal:title"] as string}". - - ( - - Email Address - - - - - The email of the evaluator you'd like to invite. - - - - )} - /> - ( - - Reviewer Name - - - - - The name of the evaluator you'd like to invite. - - - - )} - /> + + {invites.map((item, index) => ( + + ))} + @@ -120,20 +135,21 @@ export default function SettingsForm({ name: initName, email: initEmail, slug }: /> - {!emailIsLoading && (emailError ? ( -
- {emailError} -
- ) : emailSuccess && ( -
- You will need to confirm this change by clicking a link sent to the new - email address. -
- ))} + {!emailIsLoading && + (emailError ? ( +
{emailError}
+ ) : ( + emailSuccess && ( +
+ You will need to confirm this change by clicking a link sent to + the new email address. +
+ ) + ))}

Click below to receive an email with a secure link for reseting yor password. diff --git a/core/app/(user)/settings/page.tsx b/core/app/(user)/settings/page.tsx index dcc7f4b4ba..8d748fc652 100644 --- a/core/app/(user)/settings/page.tsx +++ b/core/app/(user)/settings/page.tsx @@ -9,7 +9,12 @@ export default async function Page() { } return (

- +
); } diff --git a/core/app/api/user/route.ts b/core/app/api/user/route.ts index d139a17106..a877ea9697 100644 --- a/core/app/api/user/route.ts +++ b/core/app/api/user/route.ts @@ -7,19 +7,21 @@ import { getLoginId } from "lib/auth/loginId"; import { BadRequestError, ForbiddenError, UnauthorizedError, handleErrors } from "~/lib/server"; export type UserPostBody = { - name: string; + firstName: string; + lastName?: string; email: string; password: string; }; export type UserPutBody = { - name: string; + firstName: string; + lastName: string; }; export async function POST(req: NextRequest) { return await handleErrors(async () => { const submittedData: UserPostBody = await req.json(); - const { name, email, password } = submittedData; + const { firstName, lastName, email, password } = submittedData; const supabase = getServerSupabase(); const { data, error } = await supabase.auth.signUp({ email, @@ -54,29 +56,32 @@ export async function POST(req: NextRequest) { await prisma.user.create({ data: { id: data.user.id, - slug: `${slugifyString(name)}-${generateHash(4, "0123456789")}`, - name, + slug: `${slugifyString(firstName)}${ + lastName ? `-${slugifyString(lastName)}` : "" + }-${generateHash(4, "0123456789")}`, + firstName, + lastName: lastName || undefined, email, }, }); return NextResponse.json({}, { status: 201 }); - }) + }); } export async function PUT(req: NextRequest) { return await handleErrors(async () => { const loginId = await getLoginId(req); if (!loginId) { - throw new UnauthorizedError() + throw new UnauthorizedError(); } const submittedData: UserPutBody = await req.json(); - const { name } = submittedData; + const { firstName, lastName } = submittedData; const currentData = await prisma.user.findUnique({ where: { id: loginId }, }); if (!currentData) { - throw new BadRequestError('Unable to find user') + throw new BadRequestError("Unable to find user"); } const slugSuffix = getSlugSuffix(currentData.slug); await prisma.user.update({ @@ -84,12 +89,13 @@ export async function PUT(req: NextRequest) { id: loginId, }, data: { - slug: `${slugifyString(name)}-${slugSuffix}`, - name, + slug: `${slugifyString(firstName)}-${slugSuffix}`, + firstName, + lastName, }, }); return NextResponse.json({}, { status: 200 }); - }) + }); } // Used to determine if an email is available when a user attempts to change theirs @@ -97,25 +103,25 @@ export async function GET(req: NextRequest) { return await handleErrors(async () => { const loginId = await getLoginId(req); if (!loginId) { - throw new UnauthorizedError() + throw new UnauthorizedError(); } - const email = req.nextUrl.searchParams.get('email') + const email = req.nextUrl.searchParams.get("email"); if (!email) { - throw new BadRequestError() + throw new BadRequestError(); } const emailUsed = await prisma.user.findUnique({ where: { - email - } - }) + email, + }, + }); if (emailUsed) { - throw new ForbiddenError('Email already in use') + throw new ForbiddenError("Email already in use"); } - return NextResponse.json({message: "Email is available"}, { status: 200 }) - }) + return NextResponse.json({ message: "Email is available" }, { status: 200 }); + }); } diff --git a/core/app/c/[communitySlug]/LoginSwitcher.tsx b/core/app/c/[communitySlug]/LoginSwitcher.tsx index 36b8fe9088..addc3716f5 100644 --- a/core/app/c/[communitySlug]/LoginSwitcher.tsx +++ b/core/app/c/[communitySlug]/LoginSwitcher.tsx @@ -1,6 +1,7 @@ import { getLoginData } from "~/lib/auth/loginData"; import { Avatar, AvatarFallback, AvatarImage } from "ui"; import LogoutButton from "./LogoutButton"; +import Link from "next/link"; export default async function LoginSwitcher() { const loginData = await getLoginData(); @@ -8,19 +9,18 @@ export default async function LoginSwitcher() { return null; } return ( -
+
- - {loginData.firstName[0]} + + + {loginData.firstName[0]} + -
+
{loginData.firstName}
{loginData.email}
-
- -
); } diff --git a/core/app/c/[communitySlug]/stages/components/Assign.tsx b/core/app/c/[communitySlug]/stages/components/Assign.tsx index e70fd850d1..eaff45b1f8 100644 --- a/core/app/c/[communitySlug]/stages/components/Assign.tsx +++ b/core/app/c/[communitySlug]/stages/components/Assign.tsx @@ -20,7 +20,7 @@ import { PubPayload, StagePayload, StagePayloadMoveConstraintDestination, - User, + UserLoginData, } from "~/lib/types"; import { assign } from "./lib/actions"; @@ -28,7 +28,7 @@ type Props = { pub: PubPayload; stages: StagePayloadMoveConstraintDestination[]; stage: StagePayload; - loginData: User; + loginData: UserLoginData; users: PermissionPayloadUser[]; }; @@ -86,8 +86,10 @@ export default function Assign(props: Props) { From c1918cda8cd7df43bbe0c351b84dacf761455728 Mon Sep 17 00:00:00 2001 From: Eric McDaniel Date: Wed, 4 Oct 2023 14:36:15 -0400 Subject: [PATCH 05/13] Add job scheduler (#124) * Repeating evaluator invites * Add job queue * Add createJob endpoint * Add dotenv to flock * rename flock->jobs in repo for clarity * Add jobs client * normalize->parse * Removed unused zod schema * Update render.yaml Co-authored-by: Kalil Smith-Nuevelle * Use z.coerce.date instead of z.string --------- Co-authored-by: Kalil Smith-Nuevelle --- core/lib/server/jobs.ts | 32 ++ core/package.json | 1 + core/pages/api/v0/[...ts-rest].ts | 11 +- .../evaluations/app/actions/manage/actions.ts | 25 +- .../app/actions/manage/emailForm.tsx | 1 + .../submissions/app/actions/submit/actions.ts | 1 - jobs/.env.template | 3 + jobs/index.ts | 34 ++ jobs/package.json | 17 + .../contracts/src/resources/integrations.ts | 27 +- packages/sdk/src/client.ts | 35 +- pnpm-lock.yaml | 520 +++++++++++++++++- pnpm-workspace.yaml | 1 + render.yaml | 12 +- 14 files changed, 682 insertions(+), 38 deletions(-) create mode 100644 core/lib/server/jobs.ts create mode 100644 jobs/.env.template create mode 100644 jobs/index.ts create mode 100644 jobs/package.json diff --git a/core/lib/server/jobs.ts b/core/lib/server/jobs.ts new file mode 100644 index 0000000000..d409b7532f --- /dev/null +++ b/core/lib/server/jobs.ts @@ -0,0 +1,32 @@ +import { JobOptions, SendEmailRequestBody } from "contracts"; +import { makeWorkerUtils, Job } from "graphile-worker"; + +export type JobsClient = { + sendEmail( + instanceId: string, + email: SendEmailRequestBody, + jobOptions: JobOptions + ): Promise; +}; + +export const makeJobsClient = async () => { + const workerUtils = await makeWorkerUtils({ + connectionString: process.env.DATABASE_URL, + }); + await workerUtils.migrate(); + return { + async sendEmail(instanceId: string, email: SendEmailRequestBody, jobOptions: JobOptions) { + const job = await workerUtils.addJob("sendEmail", [instanceId, email], jobOptions); + return job; + }, + }; +}; + +let jobsClient: JobsClient; + +export const getJobsClient = async () => { + if (!jobsClient) { + jobsClient = await makeJobsClient(); + } + return jobsClient; +}; diff --git a/core/package.json b/core/package.json index 16760fc0f1..89ff0c3ba5 100644 --- a/core/package.json +++ b/core/package.json @@ -30,6 +30,7 @@ "contracts": "workspace:*", "diacritics": "^1.3.0", "eta": "^3.1.1", + "graphile-worker": "^0.15.0", "jsonwebtoken": "^9.0.0", "next": "13.5.2", "next-connect": "^1.0.0", diff --git a/core/pages/api/v0/[...ts-rest].ts b/core/pages/api/v0/[...ts-rest].ts index 7b5c862e9f..f0c5b891b0 100644 --- a/core/pages/api/v0/[...ts-rest].ts +++ b/core/pages/api/v0/[...ts-rest].ts @@ -1,16 +1,17 @@ import { createNextRoute, createNextRouter } from "@ts-rest/next"; import { api } from "contracts"; +import { compareAPIKeys, getBearerToken } from "~/lib/auth/api"; import { createPub, getMembers, getPub, + getPubType, tsRestHandleErrors, updatePub, - getPubType, } from "~/lib/server"; import { emailUser } from "~/lib/server/email"; +import { getJobsClient } from "~/lib/server/jobs"; import { validateToken } from "~/lib/server/token"; -import { compareAPIKeys, getBearerToken } from "~/lib/auth/api"; const checkAuthentication = (authHeader: string) => { const apiKey = getBearerToken(authHeader); @@ -61,6 +62,12 @@ const integrationsRouter = createNextRoute(api.integrations, { const pub = await getPubType(params.pubTypeId); return { status: 200, body: pub }; }, + scheduleEmail: async ({ headers, params, body, query }) => { + checkAuthentication(headers.authorization); + const jobs = await getJobsClient(); + const job = await jobs.sendEmail(params.instanceId, body, query); + return { status: 202, body: job }; + }, }); const router = { diff --git a/integrations/evaluations/app/actions/manage/actions.ts b/integrations/evaluations/app/actions/manage/actions.ts index 561679f36b..0ddb2f5a9a 100644 --- a/integrations/evaluations/app/actions/manage/actions.ts +++ b/integrations/evaluations/app/actions/manage/actions.ts @@ -1,8 +1,6 @@ "use server"; import { SuggestedMembersQuery } from "@pubpub/sdk"; -import { error } from "console"; -import { string } from "zod"; import { client } from "~/lib/pubpub"; export const manage = async ( @@ -14,15 +12,22 @@ export const manage = async ( lastName: string ) => { try { - const info = await client.sendEmail(instanceId, { - to: { - firstName, - lastName, - email, + const info = await client.scheduleEmail( + instanceId, + { + to: { + firstName, + lastName, + email, + }, + subject: "You've been invited to review a submission on PubPub", + message: `Hello {{user.firstName}} {{user.lastName}}! You've been invited to evaluate ${pubTitle} on PubPub.`, }, - subject: "You've been invited to review a submission on PubPub", - message: `Hello {{user.firstName}} {{user.lastName}}! You've been invited to evaluate ${pubTitle} on PubPub.`, - }); + { + key: `${instanceId}-${pubId}-invite-${email}`, + runAt: new Date().toISOString(), + } + ); return info; } catch (error) { return { error: error.message }; diff --git a/integrations/evaluations/app/actions/manage/emailForm.tsx b/integrations/evaluations/app/actions/manage/emailForm.tsx index f1357af0c5..fa5d176f0b 100644 --- a/integrations/evaluations/app/actions/manage/emailForm.tsx +++ b/integrations/evaluations/app/actions/manage/emailForm.tsx @@ -207,6 +207,7 @@ export function EmailForm(props: Props) { title: "Success", description: "The email was sent successfully", }); + // form.reset(); } }; diff --git a/integrations/submissions/app/actions/submit/actions.ts b/integrations/submissions/app/actions/submit/actions.ts index 1826e126a5..44a994c444 100644 --- a/integrations/submissions/app/actions/submit/actions.ts +++ b/integrations/submissions/app/actions/submit/actions.ts @@ -1,7 +1,6 @@ "use server"; import { PubValues } from "@pubpub/sdk"; -import { assert } from "utils"; import { findInstance } from "~/lib/instance"; import { makePubFromDoi, makePubFromTitle, makePubFromUrl } from "~/lib/metadata"; import { client } from "~/lib/pubpub"; diff --git a/jobs/.env.template b/jobs/.env.template new file mode 100644 index 0000000000..c998c90202 --- /dev/null +++ b/jobs/.env.template @@ -0,0 +1,3 @@ +API_KEY="super_secret_key" +DATABASE_URL="postgresql://postgres:postgres@localhost:54322/postgres" +PUBPUB_URL="http://localhost:3000" \ No newline at end of file diff --git a/jobs/index.ts b/jobs/index.ts new file mode 100644 index 0000000000..2c538a115b --- /dev/null +++ b/jobs/index.ts @@ -0,0 +1,34 @@ +import { run, JobHelpers, Task } from "graphile-worker"; +import { Client, makeClient } from "@pubpub/sdk"; + +const client = makeClient({}); + +const makeTaskList = (client: Client<{}>) => { + const sendEmail = (async ( + payload: Parameters<(typeof client)["sendEmail"]>, + helpers: JobHelpers + ) => { + const [instanceId, body] = payload; + const info = await client.sendEmail(instanceId, body); + helpers.logger.info(`Sent email`, info); + }) as Task; + return { sendEmail }; +}; + +const main = async () => { + try { + const runner = await run({ + connectionString: process.env.DATABASE_URL, + concurrency: 5, + noHandleSignals: false, + pollInterval: 1000, + taskList: makeTaskList(client), + }); + await runner.promise; + } catch (err) { + console.error(err); + process.exit(1); + } +}; + +main(); diff --git a/jobs/package.json b/jobs/package.json new file mode 100644 index 0000000000..e683f20d6f --- /dev/null +++ b/jobs/package.json @@ -0,0 +1,17 @@ +{ + "name": "jobs", + "version": "0.0.0", + "private": true, + "scripts": { + "start": "tsx index.ts", + "dev": "dotenv -e .env.local tsx index.ts" + }, + "dependencies": { + "@pubpub/sdk": "workspace:*", + "graphile-worker": "^0.15.0", + "tsx": "^3.13.0" + }, + "devDependencies": { + "dotenv-cli": "^7.2.1" + } +} diff --git a/packages/contracts/src/resources/integrations.ts b/packages/contracts/src/resources/integrations.ts index 204255a077..607626c9d6 100644 --- a/packages/contracts/src/resources/integrations.ts +++ b/packages/contracts/src/resources/integrations.ts @@ -138,7 +138,7 @@ export const SendEmailResponseBody = z.object({ }); export type SendEmailResponseBody = z.infer; -// PubType Types +// PubType types export const GetPubTypeResponseBody = z.object({ id: z.string(), @@ -165,6 +165,20 @@ export const GetPubTypeResponseBody = z.object({ export type GetPubTypeResponseBody = z.infer; +// Job types + +export const JobOptions = z.object({ + key: z.string().optional(), + runAt: z.coerce.date(), + maxAttempts: z.number().optional(), +}); +export type JobOptions = z.infer; + +export const ScheduleEmailResponseBody = z.object({ + key: z.string().nullable(), +}); +export type ScheduleEmailResponseBody = z.infer; + const contract = initContract(); export const integrationsApi = contract.router( @@ -292,6 +306,17 @@ export const integrationsApi = contract.router( 200: GetPubTypeResponseBody, }, }, + scheduleEmail: { + method: "POST", + path: "/:instanceId/email/schedule", + summary: "Schedule an email to be sent at some point in the future", + description: "", + body: SendEmailRequestBody, + query: JobOptions, + responses: { + 202: ScheduleEmailResponseBody, + }, + }, // TODO implement these endpoints // getAllMembers: { // method: "GET", diff --git a/packages/sdk/src/client.ts b/packages/sdk/src/client.ts index 4ccc69f396..a4ea1ec5fd 100644 --- a/packages/sdk/src/client.ts +++ b/packages/sdk/src/client.ts @@ -1,16 +1,18 @@ -import { GetFieldType, initClient } from "@ts-rest/core"; +import { initClient } from "@ts-rest/core"; import { CreatePubRequestBody, CreatePubResponseBody, GetPubResponseBody, GetPubTypeResponseBody, - UpdatePubRequestBody, - UpdatePubResponseBody, + JobOptions, + ScheduleEmailResponseBody, SendEmailRequestBody, SendEmailResponseBody, + SuggestedMember, + UpdatePubRequestBody, + UpdatePubResponseBody, User, api, - SuggestedMember, } from "contracts"; import { Manifest, ManifestJson } from "./manifest"; @@ -95,6 +97,7 @@ export type SuggestedMembersQuery = | { firstName: string; lastName: string }; export type Client = { + // TODO: Derive these return types from contract auth(instanceId: string, token: string): Promise; createPub(instanceId: string, pub: CreatePubRequestBody): Promise; getPub(instanceId: string, pubId: string, depth?: number): Promise; @@ -105,6 +108,11 @@ export type Client = { query: SuggestedMembersQuery ): Promise; getPubType(instanceId: string, pubTypeId: string): Promise; + scheduleEmail( + instanceId: string, + email: SendEmailRequestBody, + jobOptions: JobOptions + ): Promise; }; /** @@ -241,5 +249,24 @@ export const makeClient = (manifest: T): Client => { throw new Error("Request failed", { cause }); } }, + async scheduleEmail(instanceId, email, jobOptions) { + try { + const response = await client.scheduleEmail({ + headers: { + authorization: `Bearer ${process.env.API_KEY}`, + }, + params: { instanceId }, + body: email, + query: jobOptions, + cache: "no-cache", + }); + if (response.status === 202) { + return response.body; + } + throw new Error("Failed to schedule email", { cause: response }); + } catch (cause) { + throw new Error("Request failed", { cause }); + } + }, }; }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bac2182d62..b086f2a567 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -80,6 +80,9 @@ importers: eta: specifier: ^3.1.1 version: 3.1.1 + graphile-worker: + specifier: ^0.15.0 + version: 0.15.0 jsonwebtoken: specifier: ^9.0.0 version: 9.0.0 @@ -333,6 +336,22 @@ importers: specifier: 5.1.3 version: 5.1.3 + jobs: + dependencies: + '@pubpub/sdk': + specifier: workspace:* + version: link:../packages/sdk + graphile-worker: + specifier: ^0.15.0 + version: 0.15.0 + tsx: + specifier: ^3.13.0 + version: 3.13.0 + devDependencies: + dotenv-cli: + specifier: ^7.2.1 + version: 7.2.1 + packages/contracts: dependencies: utils: @@ -1944,6 +1963,204 @@ packages: dependencies: '@jridgewell/trace-mapping': 0.3.9 + /@esbuild/android-arm64@0.18.20: + resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: false + optional: true + + /@esbuild/android-arm@0.18.20: + resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: false + optional: true + + /@esbuild/android-x64@0.18.20: + resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: false + optional: true + + /@esbuild/darwin-arm64@0.18.20: + resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@esbuild/darwin-x64@0.18.20: + resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@esbuild/freebsd-arm64@0.18.20: + resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: false + optional: true + + /@esbuild/freebsd-x64@0.18.20: + resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: false + optional: true + + /@esbuild/linux-arm64@0.18.20: + resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@esbuild/linux-arm@0.18.20: + resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@esbuild/linux-ia32@0.18.20: + resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@esbuild/linux-loong64@0.18.20: + resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@esbuild/linux-mips64el@0.18.20: + resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@esbuild/linux-ppc64@0.18.20: + resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@esbuild/linux-riscv64@0.18.20: + resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@esbuild/linux-s390x@0.18.20: + resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@esbuild/linux-x64@0.18.20: + resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@esbuild/netbsd-x64@0.18.20: + resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: false + optional: true + + /@esbuild/openbsd-x64@0.18.20: + resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: false + optional: true + + /@esbuild/sunos-x64@0.18.20: + resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: false + optional: true + + /@esbuild/win32-arm64@0.18.20: + resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@esbuild/win32-ia32@0.18.20: + resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@esbuild/win32-x64@0.18.20: + resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@faker-js/faker@5.5.3: resolution: {integrity: sha512-R11tGE6yIFwqpaIqcfkcg7AICXzFg14+5h5v0TfF/9+RMDL6jhzCy/pxHVOfbALGdtVYdt6JdR21tuxEgl34dw==} dev: false @@ -2006,6 +2223,10 @@ packages: react: 18.2.0 dev: false + /@graphile/logger@0.2.0: + resolution: {integrity: sha512-jjcWBokl9eb1gVJ85QmoaQ73CQ52xAaOCF29ukRbYNl6lY+ts0ErTaDYOBlejcbUs2OpaiqYLO5uDhyLFzWw4w==} + dev: false + /@hookform/resolvers@3.3.1(react-hook-form@7.46.1): resolution: {integrity: sha512-K7KCKRKjymxIB90nHDQ7b9nli474ru99ZbqxiqDAWYsYhOsU3/4qLxW91y+1n04ic13ajjZ66L3aXbNef8PELQ==} peerDependencies: @@ -4048,6 +4269,12 @@ packages: /@tsconfig/node16@1.0.4: resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + /@types/debug@4.1.9: + resolution: {integrity: sha512-8Hz50m2eoS56ldRlepxSBa6PWEVCtzUo/92HgLc2qTMnotJNIm7xP+UZhyWoYsyOdd5dxZ+NZLb24rsKyFs2ow==} + dependencies: + '@types/ms': 0.7.32 + dev: false + /@types/diacritics@1.3.1: resolution: {integrity: sha512-tAH+RY51Zbz7ZSzN7yxQBKEue78U6weZ1UUBNjFoitoLbJGFJCKI7KVHwGsnYo4s2xSFr9KGEkjst2FolpYqyA==} dev: true @@ -4107,6 +4334,10 @@ packages: resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} dev: true + /@types/ms@0.7.32: + resolution: {integrity: sha512-xPSg0jm4mqgEkNhowKgZFBNtwoEwF6gJ4Dhww+GFpm3IgtNseHQZ5IqdNwnquZEoANxyDAKDRAdVo4Z72VvD/g==} + dev: false + /@types/node@12.20.55: resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} dev: true @@ -4131,10 +4362,22 @@ packages: resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} dev: true + /@types/parse-json@4.0.0: + resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==} + dev: false + /@types/parse5@6.0.3: resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==} dev: false + /@types/pg@8.10.3: + resolution: {integrity: sha512-BACzsw64lCZesclRpZGu55tnqgFAYcrCBP92xLh1KLypZLCOsvJTSTgaoFVTy3lCys/aZTQzfeDxtjwrvdzL2g==} + dependencies: + '@types/node': 20.4.8 + pg-protocol: 1.6.0 + pg-types: 4.0.1 + dev: false + /@types/phoenix@1.6.0: resolution: {integrity: sha512-qwfpsHmFuhAS/dVd4uBIraMxRd56vwBUYQGZ6GpXnFuM2XMRFJbIyruFKKlW2daQliuYZwe0qfn/UjFCDKic5g==} dev: false @@ -4268,7 +4511,6 @@ packages: /ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - dev: true /ansi-regex@6.0.1: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} @@ -4520,7 +4762,11 @@ packages: /buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - dev: true + + /buffer-writer@2.0.0: + resolution: {integrity: sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==} + engines: {node: '>=4'} + dev: false /bufferutil@4.0.7: resolution: {integrity: sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==} @@ -4552,6 +4798,11 @@ packages: resolution: {integrity: sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==} dev: false + /callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + dev: false + /camelcase-css@2.0.1: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} @@ -4712,7 +4963,6 @@ packages: string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 7.0.0 - dev: true /clone@1.0.4: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} @@ -4820,6 +5070,17 @@ packages: browserslist: 4.21.10 dev: true + /cosmiconfig@7.1.0: + resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} + engines: {node: '>=10'} + dependencies: + '@types/parse-json': 4.0.0 + import-fresh: 3.3.0 + parse-json: 5.2.0 + path-type: 4.0.0 + yaml: 1.10.2 + dev: false + /create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} @@ -5040,7 +5301,6 @@ packages: /emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - dev: true /emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} @@ -5063,7 +5323,6 @@ packages: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} dependencies: is-arrayish: 0.2.1 - dev: true /error-stack-parser@2.1.4: resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==} @@ -5165,6 +5424,36 @@ packages: ext: 1.7.0 dev: false + /esbuild@0.18.20: + resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': 0.18.20 + '@esbuild/android-arm64': 0.18.20 + '@esbuild/android-x64': 0.18.20 + '@esbuild/darwin-arm64': 0.18.20 + '@esbuild/darwin-x64': 0.18.20 + '@esbuild/freebsd-arm64': 0.18.20 + '@esbuild/freebsd-x64': 0.18.20 + '@esbuild/linux-arm': 0.18.20 + '@esbuild/linux-arm64': 0.18.20 + '@esbuild/linux-ia32': 0.18.20 + '@esbuild/linux-loong64': 0.18.20 + '@esbuild/linux-mips64el': 0.18.20 + '@esbuild/linux-ppc64': 0.18.20 + '@esbuild/linux-riscv64': 0.18.20 + '@esbuild/linux-s390x': 0.18.20 + '@esbuild/linux-x64': 0.18.20 + '@esbuild/netbsd-x64': 0.18.20 + '@esbuild/openbsd-x64': 0.18.20 + '@esbuild/sunos-x64': 0.18.20 + '@esbuild/win32-arm64': 0.18.20 + '@esbuild/win32-ia32': 0.18.20 + '@esbuild/win32-x64': 0.18.20 + dev: false + /escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} engines: {node: '>=6'} @@ -5441,7 +5730,6 @@ packages: /get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - dev: true /get-intrinsic@1.2.1: resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==} @@ -5473,6 +5761,12 @@ packages: get-intrinsic: 1.2.1 dev: true + /get-tsconfig@4.7.2: + resolution: {integrity: sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==} + dependencies: + resolve-pkg-maps: 1.0.0 + dev: false + /github-slugger@1.5.0: resolution: {integrity: sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==} dev: false @@ -5563,6 +5857,24 @@ packages: resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} dev: true + /graphile-worker@0.15.0: + resolution: {integrity: sha512-3MZiamF1iJc3edd32bJyLSJbFK5I3WfVMDuK+s/4TPJJhzAXhiLOimYMqFUeRxg+KPUhM13l8tcV80lpn+4rFQ==} + engines: {node: '>=14.0.0'} + hasBin: true + dependencies: + '@graphile/logger': 0.2.0 + '@types/debug': 4.1.9 + '@types/pg': 8.10.3 + chokidar: 3.5.3 + cosmiconfig: 7.1.0 + json5: 2.2.3 + pg: 8.11.3 + tslib: 2.6.1 + yargs: 17.7.2 + transitivePeerDependencies: + - pg-native + dev: false + /hard-rejection@2.1.0: resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} engines: {node: '>=6'} @@ -5779,6 +6091,14 @@ packages: resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} dev: false + /import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + dev: false + /imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -5853,7 +6173,6 @@ packages: /is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - dev: true /is-bigint@1.0.4: resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} @@ -5931,7 +6250,6 @@ packages: /is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - dev: true /is-fullwidth-code-point@4.0.0: resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} @@ -6215,7 +6533,6 @@ packages: /json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - dev: true /json-pointer@0.6.2: resolution: {integrity: sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==} @@ -7096,6 +7413,10 @@ packages: resolution: {integrity: sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==} dev: false + /obuf@1.1.2: + resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} + dev: false + /once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: @@ -7188,6 +7509,17 @@ packages: engines: {node: '>=6'} dev: true + /packet-reader@1.0.0: + resolution: {integrity: sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==} + dev: false + + /parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + dependencies: + callsites: 3.1.0 + dev: false + /parse-entities@2.0.0: resolution: {integrity: sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==} dependencies: @@ -7217,7 +7549,6 @@ packages: error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 - dev: true /parse5-sax-parser@7.0.0: resolution: {integrity: sha512-5A+v2SNsq8T6/mG3ahcz8ZtQ0OUFTatxPbeidoMB7tkJSGDY3tdfl4MHovtLQHkEn5CGxijNWRQHhRQ6IRpXKg==} @@ -7266,7 +7597,88 @@ packages: /path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} - dev: true + + /pg-cloudflare@1.1.1: + resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==} + requiresBuild: true + dev: false + optional: true + + /pg-connection-string@2.6.2: + resolution: {integrity: sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==} + dev: false + + /pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + dev: false + + /pg-numeric@1.0.2: + resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==} + engines: {node: '>=4'} + dev: false + + /pg-pool@3.6.1(pg@8.11.3): + resolution: {integrity: sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==} + peerDependencies: + pg: '>=8.0' + dependencies: + pg: 8.11.3 + dev: false + + /pg-protocol@1.6.0: + resolution: {integrity: sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==} + dev: false + + /pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.0 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + dev: false + + /pg-types@4.0.1: + resolution: {integrity: sha512-hRCSDuLII9/LE3smys1hRHcu5QGcLs9ggT7I/TCs0IE+2Eesxi9+9RWAAwZ0yaGjxoWICF/YHLOEjydGujoJ+g==} + engines: {node: '>=10'} + dependencies: + pg-int8: 1.0.1 + pg-numeric: 1.0.2 + postgres-array: 3.0.2 + postgres-bytea: 3.0.0 + postgres-date: 2.0.1 + postgres-interval: 3.0.0 + postgres-range: 1.1.3 + dev: false + + /pg@8.11.3: + resolution: {integrity: sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==} + engines: {node: '>= 8.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true + dependencies: + buffer-writer: 2.0.0 + packet-reader: 1.0.0 + pg-connection-string: 2.6.2 + pg-pool: 3.6.1(pg@8.11.3) + pg-protocol: 1.6.0 + pg-types: 2.2.0 + pgpass: 1.0.5 + optionalDependencies: + pg-cloudflare: 1.1.1 + dev: false + + /pgpass@1.0.5: + resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} + dependencies: + split2: 4.2.0 + dev: false /picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} @@ -7381,6 +7793,54 @@ packages: picocolors: 1.0.0 source-map-js: 1.0.2 + /postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + dev: false + + /postgres-array@3.0.2: + resolution: {integrity: sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==} + engines: {node: '>=12'} + dev: false + + /postgres-bytea@1.0.0: + resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} + engines: {node: '>=0.10.0'} + dev: false + + /postgres-bytea@3.0.0: + resolution: {integrity: sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==} + engines: {node: '>= 6'} + dependencies: + obuf: 1.1.2 + dev: false + + /postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + dev: false + + /postgres-date@2.0.1: + resolution: {integrity: sha512-YtMKdsDt5Ojv1wQRvUhnyDJNSr2dGIC96mQVKz7xufp07nfuFONzdaowrMHjlAzY6GDLd4f+LUHHAAM1h4MdUw==} + engines: {node: '>=12'} + dev: false + + /postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + dependencies: + xtend: 4.0.2 + dev: false + + /postgres-interval@3.0.0: + resolution: {integrity: sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==} + engines: {node: '>=12'} + dev: false + + /postgres-range@1.1.3: + resolution: {integrity: sha512-VdlZoocy5lCP0c/t66xAfclglEapXPCIVhqqJRncYpvbCgImF0w67aPKfbqUMr72tO2k5q0TdTZwCLjPTI6C9g==} + dev: false + /postman-collection@4.2.1: resolution: {integrity: sha512-DFLt3/yu8+ldtOTIzmBUctoupKJBOVK4NZO0t68K2lIir9smQg7OdQTBjOXYy+PDh7u0pSDvD66tm93eBHEPHA==} engines: {node: '>=10'} @@ -7849,7 +8309,6 @@ packages: /require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} - dev: true /require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} @@ -7864,6 +8323,11 @@ packages: resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} dev: false + /resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + dev: false + /resolve-from@5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} @@ -7873,6 +8337,10 @@ packages: resolution: {integrity: sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==} dev: false + /resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + dev: false + /resolve@1.22.4: resolution: {integrity: sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==} hasBin: true @@ -8100,7 +8568,6 @@ packages: dependencies: buffer-from: 1.1.2 source-map: 0.6.1 - dev: true /source-map@0.5.6: resolution: {integrity: sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==} @@ -8148,6 +8615,11 @@ packages: resolution: {integrity: sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==} dev: true + /split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + dev: false + /sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} dev: true @@ -8207,7 +8679,6 @@ packages: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - dev: true /string-width@5.1.2: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} @@ -8257,7 +8728,6 @@ packages: engines: {node: '>=8'} dependencies: ansi-regex: 5.0.1 - dev: true /strip-ansi@7.1.0: resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} @@ -8536,6 +9006,17 @@ packages: /tslib@2.6.1: resolution: {integrity: sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==} + /tsx@3.13.0: + resolution: {integrity: sha512-rjmRpTu3as/5fjNq/kOkOtihgLxuIz6pbKdj9xwP4J5jOLkBxw/rjN5ANw+KyrrOXV5uB7HC8+SrrSJxT65y+A==} + hasBin: true + dependencies: + esbuild: 0.18.20 + get-tsconfig: 4.7.2 + source-map-support: 0.5.21 + optionalDependencies: + fsevents: 2.3.3 + dev: false + /tty-table@4.2.1: resolution: {integrity: sha512-xz0uKo+KakCQ+Dxj1D/tKn2FSyreSYWzdkL/BYhgN6oMW808g8QRMuh1atAV9fjTPbWBjfbkKQpI/5rEcnAc7g==} engines: {node: '>=8.0.0'} @@ -9159,7 +9640,6 @@ packages: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 - dev: true /wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -9184,6 +9664,11 @@ packages: engines: {node: '>= 10'} dev: false + /xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + dev: false + /y18n@4.0.3: resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} dev: true @@ -9191,7 +9676,6 @@ packages: /y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} - dev: true /yaeti@0.0.6: resolution: {integrity: sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==} @@ -9228,7 +9712,6 @@ packages: /yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} - dev: true /yargs@15.4.1: resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} @@ -9258,7 +9741,6 @@ packages: string-width: 4.2.3 y18n: 5.0.8 yargs-parser: 21.1.1 - dev: true /yn@3.1.1: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 0c92916856..e0417a6c18 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,5 +1,6 @@ packages: - "core" + - "jobs" - "tsconfig" - "integrations/*" - "packages/*" diff --git a/render.yaml b/render.yaml index a3fa18c9bb..b2df0b8746 100644 --- a/render.yaml +++ b/render.yaml @@ -11,6 +11,16 @@ services: preDeployCommand: pnpm --filter core migrate-deploy envVars: - fromGroup: pubpub-core-prod-env + # flock + - type: worker + name: flock + region: ohio + runtime: node + rootDir: . + buildCommand: ./bin/render-build.sh jobs + startCommand: pnpm --filter jobs start + envVars: + - fromGroup: pubpub-flock-prod-env # integration-instances - type: redis name: integration-instances @@ -51,4 +61,4 @@ databases: - name: core plan: standard postgresMajorVersion: 15 - region: ohio \ No newline at end of file + region: ohio From ba7c0b08b6f1e8697cb4e8556ba2841302ca7e08 Mon Sep 17 00:00:00 2001 From: Qwelian D Tanner Date: Tue, 10 Oct 2023 10:35:46 -0400 Subject: [PATCH 06/13] Email Templates (#122) * basic tepmlate thiingy * can edit template * save to localStorage * can pull from stroage * TODO: save * rebase into early template work * make mssg a object * put template in email thing * can send template to que * TODO: put tmeplate in redis * template per person * ignore the onSubmit * push recent changes * reduce code * rebase br * rmv unused div * using rich text * ok fixted it * push dialog changes * undo changes * run npm i --------- Co-authored-by: qweliant --- .../evaluations/app/actions/manage/actions.ts | 10 +- .../app/actions/manage/emailForm.tsx | 108 ++++++++++++++++-- .../evaluations/app/actions/manage/page.tsx | 4 +- .../evaluations/app/configure/actions.ts | 8 +- .../evaluations/app/configure/configure.tsx | 81 +++++++++++-- .../evaluations/app/configure/page.tsx | 8 +- integrations/evaluations/lib/instance.ts | 2 + packages/ui/src/dialog.tsx | 5 +- packages/ui/src/icon.tsx | 3 +- 9 files changed, 200 insertions(+), 29 deletions(-) diff --git a/integrations/evaluations/app/actions/manage/actions.ts b/integrations/evaluations/app/actions/manage/actions.ts index 0ddb2f5a9a..03bcef5f56 100644 --- a/integrations/evaluations/app/actions/manage/actions.ts +++ b/integrations/evaluations/app/actions/manage/actions.ts @@ -9,7 +9,8 @@ export const manage = async ( pubTitle: string, email: string, firstName: string, - lastName: string + lastName: string, + template: { subject: string; message: string } ) => { try { const info = await client.scheduleEmail( @@ -20,14 +21,17 @@ export const manage = async ( lastName, email, }, - subject: "You've been invited to review a submission on PubPub", - message: `Hello {{user.firstName}} {{user.lastName}}! You've been invited to evaluate ${pubTitle} on PubPub.`, + subject: template.subject ?? "You've been invited to review a submission on PubPub", + message: + template.message ?? + `Hello {{user.firstName}} {{user.lastName}}! You've been invited to evaluate ${pubTitle} on PubPub.`, }, { key: `${instanceId}-${pubId}-invite-${email}`, runAt: new Date().toISOString(), } ); + return info; } catch (error) { return { error: error.message }; diff --git a/integrations/evaluations/app/actions/manage/emailForm.tsx b/integrations/evaluations/app/actions/manage/emailForm.tsx index fa5d176f0b..3effbb21f5 100644 --- a/integrations/evaluations/app/actions/manage/emailForm.tsx +++ b/integrations/evaluations/app/actions/manage/emailForm.tsx @@ -2,7 +2,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { GetPubResponseBody, SuggestedMembersQuery } from "@pubpub/sdk"; -import { useEffect, useTransition } from "react"; +import { useEffect, useState, useTransition } from "react"; import { Control, useFieldArray, useForm, useWatch } from "react-hook-form"; import { Button, @@ -12,6 +12,11 @@ import { CardFooter, CardHeader, CardTitle, + Dialog, + DialogClose, + DialogTitle, + DialogContent, + DialogTrigger, Form, FormControl, FormDescription, @@ -21,16 +26,21 @@ import { FormMessage, Icon, Input, + Textarea, useLocalStorage, useToast, } from "ui"; import { cn } from "utils"; import * as z from "zod"; -import { suggest, manage } from "./actions"; +import { manage, suggest } from "./actions"; type Props = { instanceId: string; pub: GetPubResponseBody; + template?: { + subject: string; + message: string; + }; }; // TODO: generate fields using instance's configured PubType @@ -41,6 +51,10 @@ const schema = z.object({ email: z.string().email("Enter a valid email address"), firstName: z.string().min(1, "First name is required"), lastName: z.string().min(1, "Last name is required"), + template: z.object({ + subject: z.string(), + message: z.string(), + }), }) ), }); @@ -82,10 +96,13 @@ type EvaluatorInviteProps = { }; const EvaluatorInvite = (props: EvaluatorInviteProps) => { + const [open, setOpen] = useState(false); + const value = useWatch>({ control: props.control, name: `invites.${props.index}`, }); + return (
{ query={value as SuggestedMembersQuery} onClick={() => props.onSuggest(props.index, value as SuggestedMembersQuery)} /> + + + + + +
+ Edit Template + + + +
+
+ ( + + Subject + + + + + This is the default subject line for the email. You can + change it by entering text above. + + + + )} + /> +
+
+ Email Message + ( + + +