diff --git a/integrations/evaluations/app/actions/manage/EvaluatorSuggestButton.tsx b/integrations/evaluations/app/actions/manage/EvaluatorSuggestButton.tsx
index b337430d2..b303a280e 100644
--- a/integrations/evaluations/app/actions/manage/EvaluatorSuggestButton.tsx
+++ b/integrations/evaluations/app/actions/manage/EvaluatorSuggestButton.tsx
@@ -1,5 +1,6 @@
import { useTransition } from "react";
-import { Button, Icon } from "ui";
+import { Button } from "ui/button";
+import { Loader2, Wand2 } from "ui/icon";
type Props = {
onClick: () => void;
@@ -16,7 +17,7 @@ export const EvaluatorSuggestButton = (props: Props) => {
}}
disabled={pending}
>
- {pending ? : }
+ {pending ? : }
);
};
diff --git a/integrations/evaluations/app/actions/manage/EvalutorInviteRowStatus.tsx b/integrations/evaluations/app/actions/manage/EvalutorInviteRowStatus.tsx
index 27ead54a5..e4c050fbc 100644
--- a/integrations/evaluations/app/actions/manage/EvalutorInviteRowStatus.tsx
+++ b/integrations/evaluations/app/actions/manage/EvalutorInviteRowStatus.tsx
@@ -1,5 +1,5 @@
import { memo } from "react";
-import { Badge } from "ui";
+import { Badge } from "ui/badge";
import { cn } from "utils";
import { InviteStatus } from "~/lib/types";
diff --git a/integrations/evaluations/app/actions/manage/actions.ts b/integrations/evaluations/app/actions/manage/actions.ts
index 7de372b5c..89d7bd141 100644
--- a/integrations/evaluations/app/actions/manage/actions.ts
+++ b/integrations/evaluations/app/actions/manage/actions.ts
@@ -6,12 +6,20 @@ import { expect } from "utils";
import { getInstanceConfig, getInstanceState, setInstanceState } from "~/lib/instance";
import { client } from "~/lib/pubpub";
import { cookie } from "~/lib/request";
-import { isInvited } from "~/lib/types";
import {
+ EvaluatorWhoAccepted,
+ EvaluatorWithInvite,
+ assertHasAccepted,
+ assertIsInvited,
+ isInvited,
+} from "~/lib/types";
+import {
+ scheduleInvitationReminderEmail,
scheduleNoReplyNotificationEmail,
- scheduleReminderEmail,
sendInviteEmail,
-} from "../../../lib/emails";
+ unscheduleAllDeadlineReminderEmails,
+ unscheduleAllManagerEmails,
+} from "~/lib/emails";
import { InviteFormEvaluator } from "./types";
export const save = async (
@@ -71,7 +79,7 @@ export const save = async (
// Immediately send the invite email.
await sendInviteEmail(instanceId, pubId, evaluator);
// Scehdule a reminder email to person who was invited to evaluate.
- await scheduleReminderEmail(instanceId, instanceConfig, pubId, evaluator);
+ await scheduleInvitationReminderEmail(instanceId, instanceConfig, pubId, evaluator);
// Schedule no-reply notification email to person who invited the
// evaluator.
await scheduleNoReplyNotificationEmail(
@@ -118,15 +126,22 @@ export const remove = async (instanceId: string, pubId: string, userId: string)
const evaluation = pub.children.find(
(child) => child.values[instanceConfig.evaluatorFieldSlug] === userId
);
- // TODO: When an evaluator is removed, we should unschedule reminder
- // email and notification email(s).
+
if (evaluation !== undefined) {
await client.deletePub(instanceId, evaluation.id);
}
if (instanceState !== undefined) {
+ let evaluator = expect(
+ instanceState[userId],
+ `User was not invited to evaluate pub ${pubId}`
+ );
+ assertHasAccepted(evaluator);
+ await unscheduleAllDeadlineReminderEmails(instanceId, pubId, evaluator);
+ await unscheduleAllManagerEmails(instanceId, pubId, evaluator);
delete instanceState[userId];
await setInstanceState(instanceId, pubId, instanceState);
}
+
return { success: true };
} catch (error) {
return { error: error.message };
diff --git a/integrations/evaluations/app/actions/respond/actions.ts b/integrations/evaluations/app/actions/respond/actions.ts
index 357dc1ba6..911934a75 100644
--- a/integrations/evaluations/app/actions/respond/actions.ts
+++ b/integrations/evaluations/app/actions/respond/actions.ts
@@ -8,8 +8,14 @@ import {
sendDeclinedNotificationEmail,
sendRequestedInfoNotification,
unscheduleNoReplyNotificationEmail,
- unscheduleReminderEmail,
+ unscheduleInvitationReminderEmail,
calculateDeadline,
+ schedulePromptEvalBonusReminderEmail,
+ scheduleFinalPromptEvalBonusReminderEmail,
+ scheduleEvaluationReminderEmail,
+ scheduleFinalEvaluationReminderEmail,
+ scheduleFollowUpToFinalEvaluationReminderEmail,
+ sendNoticeOfNoSubmitEmail,
} from "~/lib/emails";
import { getInstanceConfig, getInstanceState, setInstanceState } from "~/lib/instance";
import { cookie } from "~/lib/request";
@@ -39,7 +45,6 @@ export const accept = async (instanceId: string, pubId: string) => {
...evaluator,
status: "accepted",
acceptedAt: new Date().toString(),
- deadline: new Date(Date.now()),
};
const deadline = calculateDeadline(
{
@@ -50,16 +55,40 @@ export const accept = async (instanceId: string, pubId: string) => {
);
evaluator.deadline = deadline;
await setInstanceState(instanceId, pubId, instanceState);
- // Unschedule reminder email.
- await unscheduleReminderEmail(instanceId, pubId, evaluator);
- // Unschedule no-reply notification email.
+ // Unschedule reminder email to evaluator.
+ await unscheduleInvitationReminderEmail(instanceId, pubId, evaluator);
+ // Unschedule no-reply notification email to community manager.
await unscheduleNoReplyNotificationEmail(instanceId, pubId, evaluator);
- // Immediately send accepted notification email.
+ // Immediately send accepted notification email to community manager.
await sendAcceptedNotificationEmail(instanceId, instanceConfig, pubId, evaluator);
// Immediately send accepted email to evaluator.
await sendAcceptedEmail(instanceId, instanceConfig, pubId, evaluator);
- // Schedule no-submit notification email.
+ // Schedule no-submit notification email to community manager.
await scheduleNoSubmitNotificationEmail(instanceId, instanceConfig, pubId, evaluator);
+
+ // schedule prompt evaluation email to evaluator.
+ await schedulePromptEvalBonusReminderEmail(instanceId, instanceConfig, pubId, evaluator);
+ //schedule final prompt eval email to evaluator
+ await scheduleFinalPromptEvalBonusReminderEmail(
+ instanceId,
+ instanceConfig,
+ pubId,
+ evaluator
+ );
+ //schedule eval reminder email to evaluator
+ await scheduleEvaluationReminderEmail(instanceId, instanceConfig, pubId, evaluator);
+ //schedule final eval reminder email to evaluator
+ await scheduleFinalEvaluationReminderEmail(instanceId, instanceConfig, pubId, evaluator);
+ //schedule follow up to final eval reminder email to evaluator
+ await scheduleFollowUpToFinalEvaluationReminderEmail(
+ instanceId,
+ instanceConfig,
+ pubId,
+ evaluator
+ );
+ // schedule no-submit notification email to evalutaor
+ await sendNoticeOfNoSubmitEmail(instanceId, instanceConfig, pubId, evaluator);
+
return { success: true };
} catch (error) {
return { error: error.message };
@@ -85,7 +114,7 @@ export const decline = async (instanceId: string, pubId: string) => {
evaluator = instanceState[user.id] = { ...evaluator, status: "declined" };
await setInstanceState(instanceId, pubId, instanceState);
// Unschedule reminder email.
- await unscheduleReminderEmail(instanceId, pubId, evaluator);
+ await unscheduleInvitationReminderEmail(instanceId, pubId, evaluator);
// Unschedule no-reply notification email.
await unscheduleNoReplyNotificationEmail(instanceId, pubId, evaluator);
// Immediately send declined notification email.
diff --git a/integrations/evaluations/app/actions/respond/respond.tsx b/integrations/evaluations/app/actions/respond/respond.tsx
index cd5d3e0a0..efe0eb6a1 100644
--- a/integrations/evaluations/app/actions/respond/respond.tsx
+++ b/integrations/evaluations/app/actions/respond/respond.tsx
@@ -2,7 +2,8 @@
import { GetPubResponseBody } from "@pubpub/sdk";
import { useCallback } from "react";
-import { Button, toast } from "ui";
+import { Button } from "ui/button";
+import { toast } from "ui/use-toast";
import { accept, contact, decline } from "./actions";
import { InstanceConfig } from "~/lib/types";
import Link from "next/link";
diff --git a/integrations/evaluations/app/configure/configure.tsx b/integrations/evaluations/app/configure/configure.tsx
index c9d442ba8..cfc5a39b8 100644
--- a/integrations/evaluations/app/configure/configure.tsx
+++ b/integrations/evaluations/app/configure/configure.tsx
@@ -2,14 +2,9 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect, useMemo } from "react";
import { useForm } from "react-hook-form";
+import { Button } from "ui/button";
+import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "ui/card";
import {
- Button,
- Card,
- CardContent,
- CardDescription,
- CardFooter,
- CardHeader,
- CardTitle,
Form,
FormControl,
FormDescription,
@@ -17,16 +12,13 @@ import {
FormItem,
FormLabel,
FormMessage,
- Icon,
- Input,
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
- Textarea,
- useToast,
-} from "ui";
+} from "ui/form";
+import { Loader2 } from "ui/icon";
+import { Input } from "ui/input";
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "ui/select";
+import { Textarea } from "ui/textarea";
+
+import { useToast } from "ui/use-toast";
import { cn } from "utils";
import * as z from "zod";
import { InstanceConfig } from "~/lib/types";
@@ -281,7 +273,7 @@ export function Configure(props: Props) {
diff --git a/integrations/evaluations/app/layout.tsx b/integrations/evaluations/app/layout.tsx
index 1a5b02836..5eadca0a4 100644
--- a/integrations/evaluations/app/layout.tsx
+++ b/integrations/evaluations/app/layout.tsx
@@ -1,5 +1,5 @@
import { User } from "@pubpub/sdk";
-import { Toaster } from "ui";
+import { Toaster } from "ui/toaster";
import "ui/styles.css";
import { expect } from "utils";
import { Integration } from "~/lib/Integration";
diff --git a/integrations/evaluations/lib/components/Research.tsx b/integrations/evaluations/lib/components/Research.tsx
index 1a8724379..87a52f612 100644
--- a/integrations/evaluations/lib/components/Research.tsx
+++ b/integrations/evaluations/lib/components/Research.tsx
@@ -1,4 +1,4 @@
-import { Card, CardContent } from "ui";
+import { Card, CardContent } from "ui/card";
export type Props = {
title: string;
diff --git a/integrations/evaluations/lib/emails.ts b/integrations/evaluations/lib/emails.ts
index ea3918e86..0c5da6e5d 100644
--- a/integrations/evaluations/lib/emails.ts
+++ b/integrations/evaluations/lib/emails.ts
@@ -29,6 +29,18 @@ export function calculateDeadline(
}
}
+export function getDeadline(instanceConfig: InstanceConfig, evaluator: EvaluatorWhoAccepted): Date {
+ return evaluator.deadline
+ ? new Date(evaluator.deadline)
+ : calculateDeadline(
+ {
+ deadlineLength: instanceConfig.deadlineLength,
+ deadlineUnit: instanceConfig.deadlineUnit,
+ },
+ new Date(evaluator.acceptedAt)
+ );
+}
+
const notificationFooter =
'This is an automated email sent from Unjournal. Please contact contact@unjournal.org with any questions.
';
@@ -41,6 +53,51 @@ const makeNoReplyJobKey = (instanceId: string, pubId: string, evaluator: Evaluat
const makeNoSubmitJobKey = (instanceId: string, pubId: string, evaluator: EvaluatorWithInvite) =>
`send-email-${instanceId}-${pubId}-${evaluator.userId}-no-submit`;
+const makePromptEvalBonusReminderJobKey = (
+ instanceId: string,
+ pubId: string,
+ evaluator: EvaluatorWhoAccepted
+) => `send-email-${instanceId}-${pubId}-${evaluator.userId}-prompt-eval-bonus-reminder`;
+
+const makeFinalPromptEvalBonusReminderJobKey = (
+ instanceId: string,
+ pubId: string,
+ evaluator: EvaluatorWhoAccepted
+) => `send-email-${instanceId}-${pubId}-${evaluator.userId}-final-prompt-eval-bonus-reminder`;
+
+const makeEvalReminderJobKey = (
+ instanceId: string,
+ pubId: string,
+ evaluator: EvaluatorWhoAccepted
+) => `send-email-${instanceId}-${pubId}-${evaluator.userId}-eval-reminder`;
+
+const makeFinalEvalReminderJobKey = (
+ instanceId: string,
+ pubId: string,
+ evaluator: EvaluatorWhoAccepted
+) => `send-email-${instanceId}-${pubId}-${evaluator.userId}-final-eval-reminder`;
+
+const makeFollowUpToFinalEvalReminderJobKey = (
+ instanceId: string,
+ pubId: string,
+ evaluator: EvaluatorWhoAccepted
+) => `send-email-${instanceId}-${pubId}-${evaluator.userId}-follow-up-to-final-eval-reminder`;
+
+const makeNoticeOfNoSubmitJobKey = (
+ instanceId: string,
+ pubId: string,
+ evaluator: EvaluatorWhoAccepted
+) => `send-email-${instanceId}-${pubId}-${evaluator.userId}-no-submit-notice`;
+
+// emails sent to the evaluation manager
+/**
+ * Schedules an email to the evaluation manager to notify them that an invited evaluator has not responded to the invitation.
+ * @param instanceId
+ * @param instanceConfig
+ * @param pubId
+ * @param evaluator
+ * @returns
+ */
export const scheduleNoReplyNotificationEmail = async (
instanceId: string,
instanceConfig: InstanceConfig,
@@ -77,6 +134,13 @@ ${notificationFooter}`,
);
};
+/**
+ * Unschedules the no reply notification email for the evaluation manager.
+ * @param instanceId
+ * @param pubId
+ * @param evaluator
+ * @returns
+ */
export const unscheduleNoReplyNotificationEmail = (
instanceId: string,
pubId: string,
@@ -86,6 +150,14 @@ export const unscheduleNoReplyNotificationEmail = (
return client.unscheduleEmail(instanceId, jobKey);
};
+/**
+ * Schedules an email to the evaluation manager to notify them that an evaluator has not submitted their evaluation.
+ * @param instanceId
+ * @param instanceConfig
+ * @param pubId
+ * @param evaluator
+ * @returns
+ */
export const scheduleNoSubmitNotificationEmail = async (
instanceId: string,
instanceConfig: InstanceConfig,
@@ -93,15 +165,7 @@ export const scheduleNoSubmitNotificationEmail = async (
evaluator: EvaluatorWhoAccepted
) => {
const jobKey = makeNoSubmitJobKey(instanceId, pubId, evaluator);
- const deadline = evaluator.deadline
- ? new Date(evaluator.deadline)
- : calculateDeadline(
- {
- deadlineLength: instanceConfig.deadlineLength,
- deadlineUnit: instanceConfig.deadlineUnit,
- },
- new Date(evaluator.acceptedAt)
- );
+ const deadline = getDeadline(instanceConfig, evaluator);
const runAt = deadline;
await client.scheduleEmail(
@@ -130,6 +194,13 @@ ${notificationFooter}`,
);
};
+/**
+ * Unschedules the no submit notification email for the evaluation manager.
+ * @param instanceId
+ * @param pubId
+ * @param evaluator
+ * @returns
+ */
export const unscheduleNoSubmitNotificationEmail = (
instanceId: string,
pubId: string,
@@ -139,6 +210,153 @@ export const unscheduleNoSubmitNotificationEmail = (
return client.unscheduleEmail(instanceId, jobKey);
};
+/**
+ * Sends an email to the evaluation manager to notify them that an evaluator has requested more information.
+ * @param instanceId
+ * @param instanceConfig
+ * @param pubId
+ * @param evaluator
+ * @returns
+ */
+export const sendRequestedInfoNotification = (
+ instanceId: string,
+ instanceConfig: InstanceConfig,
+ pubId: string,
+ evaluator: EvaluatorWithInvite
+) => {
+ return client.sendEmail(instanceId, {
+ to: {
+ userId: evaluator.invitedBy,
+ },
+ subject: `[Unjournal] More Information Request for "{{pubs.submission.values["${instanceConfig.titleFieldSlug}"]}}"`,
+ message: `An invited evaluator, {{users.evaluator.firstName}} {{users.evaluator.lastName}}, for "{{pubs.submission.values["${instanceConfig.titleFieldSlug}"]}}", has requested more information. You may contact them at {{users.evaluator.email}}.
+${notificationFooter}`,
+ include: {
+ pubs: {
+ submission: pubId,
+ },
+ users: {
+ evaluator: evaluator.userId,
+ },
+ },
+ });
+};
+
+/**
+ * Sends an email to the evaluation manager to notify them that an evaluator has accepted the invitation to evaluate the pub.
+ * @param instanceId
+ * @param instanceConfig
+ * @param pubId
+ * @param evaluator
+ * @returns
+ */
+export const sendAcceptedNotificationEmail = (
+ instanceId: string,
+ instanceConfig: InstanceConfig,
+ pubId: string,
+ evaluator: EvaluatorWithInvite
+) => {
+ return client.sendEmail(instanceId, {
+ to: {
+ userId: evaluator.invitedBy,
+ },
+ subject: `[Unjournal] Accepted evaluation for "{{pubs.submission.values["${instanceConfig.titleFieldSlug}"]}}"`,
+ message: `An invited evaluator, {{users.evaluator.firstName}} {{users.evaluator.lastName}}, has agreed to evaluate "{{pubs.submission.values["${instanceConfig.titleFieldSlug}"]}}". You may review the status of this and other invitations on the {{extra.manage_link}}.
+${notificationFooter}`,
+ include: {
+ pubs: {
+ submission: pubId,
+ },
+ users: {
+ evaluator: evaluator.userId,
+ },
+ },
+ extra: {
+ manage_link: `Invite Evaluators page`,
+ },
+ });
+};
+
+/**
+ * Sends an email to the evaluation manager to notify them that an evaluator has declined the invitation to evaluate the pub.
+ * @param instanceId
+ * @param instanceConfig
+ * @param pubId
+ * @param evaluator
+ * @returns
+ */
+export const sendDeclinedNotificationEmail = async (
+ instanceId: string,
+ instanceConfig: InstanceConfig,
+ pubId: string,
+ evaluator: EvaluatorWithInvite
+) => {
+ return client.sendEmail(instanceId, {
+ to: {
+ userId: evaluator.invitedBy,
+ },
+ subject: `[Unjournal] Invited evaluator declines to evaluate "{{pubs.submission.values["${instanceConfig.titleFieldSlug}"]}}"`,
+ message: `An invited evaluator, {{users.evaluator.firstName}} {{users.evaluator.lastName}}, has declined to evaluate "{{pubs.submission.values["${instanceConfig.titleFieldSlug}"]}}". You may review the status of this and other invitations on the {{extra.manage_link}}.
+${notificationFooter}`,
+ include: {
+ pubs: {
+ submission: pubId,
+ },
+ users: {
+ evaluator: evaluator.userId,
+ },
+ },
+ extra: {
+ manage_link: `Invite Evaluators page`,
+ },
+ });
+};
+
+/**
+ * Sends an email to the evaluation manager to notify them that an evaluator has submitted their evaluation.
+ * @param instanceId
+ * @param instanceConfig
+ * @param pubId
+ * @param evaluator
+ * @returns
+ */
+export const sendSubmittedNotificationEmail = async (
+ instanceId: string,
+ instanceConfig: InstanceConfig,
+ pubId: string,
+ evaluator: EvaluatorWhoEvaluated
+) => {
+ return client.sendEmail(instanceId, {
+ to: {
+ userId: evaluator.invitedBy,
+ },
+ subject: `[Unjournal] Evaluation submitted for "{{pubs.submission.values["${instanceConfig.titleFieldSlug}"]}}"`,
+ message: `An evaluator, {{users.evaluator.firstName}} {{users.evaluator.lastName}}, has submitted an evaluation for "{{pubs.submission.values["${instanceConfig.titleFieldSlug}"]}}". The submitted evaluation Pub can be viewed here.
+You may review the status of this and other invitations on the {{extra.manage_link}}.
+${notificationFooter}`,
+ include: {
+ pubs: {
+ submission: pubId,
+ },
+ users: {
+ evaluator: evaluator.userId,
+ },
+ },
+ extra: {
+ manage_link: `Invite Evaluators page`,
+ },
+ });
+};
+
+// emails sent to the evaluator
+/**
+ *
+ * Sends an email to the evaluator with the invitation to evaluate the pub.
+ * @param instanceId
+ * @param pubId
+ * @param evaluator
+ * @returns Promise that resolves to the result of the email send operation.
+ */
export const sendInviteEmail = async (
instanceId: string,
pubId: string,
@@ -166,7 +384,14 @@ export const sendInviteEmail = async (
});
};
-export const scheduleReminderEmail = async (
+/**
+ * Schedules an email to the evaluator as a reminder to accept the invitation to evaluate the pub.
+ * @param instanceId
+ * @param instanceConfig
+ * @param pubId
+ * @param evaluator
+ */
+export const scheduleInvitationReminderEmail = async (
instanceId: string,
instanceConfig: InstanceConfig,
pubId: string,
@@ -202,7 +427,14 @@ export const scheduleReminderEmail = async (
);
};
-export const unscheduleReminderEmail = (
+/**
+ * Cancels the scheduled reminder email for the evaluator to accept the invitation to evaluate the pub.
+ * @param instanceId
+ * @param pubId
+ * @param evaluator
+ * @returns
+ */
+export const unscheduleInvitationReminderEmail = (
instanceId: string,
pubId: string,
evaluator: EvaluatorWithInvite
@@ -211,21 +443,21 @@ export const unscheduleReminderEmail = (
return client.unscheduleEmail(instanceId, jobKey);
};
+/**
+ * Sends an email to the evaluator to inform them that their invitation to evaluate the pub has been accepted.
+ * @param instanceId
+ * @param instanceConfig
+ * @param pubId
+ * @param evaluator
+ * @returns
+ */
export const sendAcceptedEmail = async (
instanceId: string,
instanceConfig: InstanceConfig,
pubId: string,
evaluator: EvaluatorWhoAccepted
) => {
- const deadline = evaluator.deadline
- ? new Date(evaluator.deadline)
- : calculateDeadline(
- {
- deadlineLength: instanceConfig.deadlineLength,
- deadlineUnit: instanceConfig.deadlineUnit,
- },
- new Date(evaluator.acceptedAt)
- );
+ const deadline = getDeadline(instanceConfig, evaluator);
await client.sendEmail(instanceId, {
to: {
userId: evaluator.userId,
@@ -261,108 +493,361 @@ export const sendAcceptedEmail = async (
});
};
-export const sendRequestedInfoNotification = (
+/**
+ * Schedules a reminder email to an evaluator for prompt evaluation bonus.
+ * @param instanceId - The ID of the instance.
+ * @param instanceConfig - The configuration of the instance.
+ * @param pubId - The ID of the publication.
+ * @param evaluator - The evaluator who accepted the evaluation.
+ * @returns A promise that resolves when the email is sent.
+ */
+export const schedulePromptEvalBonusReminderEmail = async (
instanceId: string,
instanceConfig: InstanceConfig,
pubId: string,
- evaluator: EvaluatorWithInvite
+ evaluator: EvaluatorWhoAccepted
) => {
- return client.sendEmail(instanceId, {
- to: {
- userId: evaluator.invitedBy,
- },
- subject: `[Unjournal] More Information Request for "{{pubs.submission.values["${instanceConfig.titleFieldSlug}"]}}"`,
- message: `An invited evaluator, {{users.evaluator.firstName}} {{users.evaluator.lastName}}, for "{{pubs.submission.values["${instanceConfig.titleFieldSlug}"]}}", has requested more information. You may contact them at {{users.evaluator.email}}.
-${notificationFooter}`,
- include: {
- pubs: {
- submission: pubId,
+ const deadline = getDeadline(instanceConfig, evaluator);
+ const reminderDeadline = new Date(deadline.getTime() - 21 * (1000 * 60 * 60 * 24));
+ const jobKey = makePromptEvalBonusReminderJobKey(instanceId, pubId, evaluator);
+ const runAt = reminderDeadline;
+
+ return client.scheduleEmail(
+ instanceId,
+ {
+ to: {
+ userId: evaluator.userId,
},
- users: {
- evaluator: evaluator.userId,
+ subject: `[Unjournal] Reminder to evaluate "{{pubs.submission.values["${instanceConfig.titleFieldSlug}"]}}" for prompt evaluation bonus`,
+ message: `Hi {{user.firstName}},
+ Thanks again for agreeing to evaluate "{{pubs.submission.values["${
+ instanceConfig.titleFieldSlug
+ }"]}}" for The Unjournal.
+ This note is a reminder to submit your evaluation by ${reminderDeadline.toLocaleDateString()} to receive a $100 “prompt evaluation bonus,” in addition to your baseline compensation. Please note that after ${new Date(
+ deadline
+ ).toLocaleDateString()} we will consider re-assigning the evaluation, and later submissions may not be eligible for the full baseline compensation.
+ Please submit your evaluation and rating, as well as any specific considerations, using this evaluation form. The form includes instructions and information about the paper/project.
+ If you have any questions, do not hesitate to reach out to me at {{users.invitor.email}}.
+ Once your evaluation has been submitted and reviewed, we will follow up with details about payment and next steps.
+ Thanks and best wishes,
+ {{users.invitor.firstName}} {{users.invitor.lastName}}
+ Unjournal.org
`,
+ include: {
+ pubs: {
+ submission: pubId,
+ },
+ users: {
+ invitor: evaluator.invitedBy,
+ },
+ },
+ extra: {
+ evaluate_link: `{{instance.actions.evaluate}}?instanceId={{instance.id}}&pubId={{pubs.submission.id}}&token={{user.token}}`,
},
},
- });
+ { jobKey, runAt }
+ );
};
-export const sendAcceptedNotificationEmail = (
+/**
+ * Schedules a final reminder email to an evaluator for prompt evaluation bonus.
+ * @param instanceId
+ * @param instanceConfig
+ * @param pubId
+ * @param evaluator
+ * @returns
+ */
+export const scheduleFinalPromptEvalBonusReminderEmail = async (
instanceId: string,
instanceConfig: InstanceConfig,
pubId: string,
- evaluator: EvaluatorWithInvite
+ evaluator: EvaluatorWhoAccepted
) => {
- return client.sendEmail(instanceId, {
- to: {
- userId: evaluator.invitedBy,
- },
- subject: `[Unjournal] Accepted evaluation for "{{pubs.submission.values["${instanceConfig.titleFieldSlug}"]}}"`,
- message: `An invited evaluator, {{users.evaluator.firstName}} {{users.evaluator.lastName}}, has agreed to evaluate "{{pubs.submission.values["${instanceConfig.titleFieldSlug}"]}}". You may review the status of this and other invitations on the {{extra.manage_link}}.
-${notificationFooter}`,
- include: {
- pubs: {
- submission: pubId,
+ const deadline = getDeadline(instanceConfig, evaluator);
+ const reminderDeadline = new Date(deadline.getTime() - 14 * (1000 * 60 * 60 * 24));
+ const jobKey = makeFinalPromptEvalBonusReminderJobKey(instanceId, pubId, evaluator);
+ const runAt = reminderDeadline;
+
+ return client.scheduleEmail(
+ instanceId,
+ {
+ to: {
+ userId: evaluator.userId,
},
- users: {
- evaluator: evaluator.userId,
+ subject: `[Unjournal] Final Reminder: Submit evaluation for prompt bonus "{{pubs.submission.values["${instanceConfig.titleFieldSlug}"]}}"`,
+ message: `Hi {{user.firstName}},
+ This is a final reminder to submit your evaluation for "{{pubs.submission.values["${
+ instanceConfig.titleFieldSlug
+ }"]}}" by the deadline ${reminderDeadline.toLocaleDateString()} to receive the $100 “prompt evaluation bonus.”
+ If you haven't already, please submit your evaluation and rating, as well as any specific considerations, using this evaluation form. The form includes instructions and information about the paper/project.
+ If you have any questions, do not hesitate to reach out to me at {{users.invitor.email}}.
+ Once your evaluation has been submitted and reviewed, we will follow up with details about payment and next steps.
+ Thanks and best wishes,
+ {{users.invitor.firstName}} {{users.invitor.lastName}}
+ Unjournal.org
`,
+ include: {
+ pubs: {
+ submission: pubId,
+ },
+ users: {
+ invitor: evaluator.invitedBy,
+ },
+ },
+ extra: {
+ evaluate_link: `{{instance.actions.evaluate}}?instanceId={{instance.id}}&pubId={{pubs.submission.id}}&token={{user.token}}`,
},
},
- extra: {
- manage_link: `Invite Evaluators page`,
- },
- });
+ { jobKey, runAt }
+ );
};
-export const sendDeclinedNotificationEmail = async (
+/**
+ * Schedules a reminder email to an evaluator to submit their evaluation.
+ * @param instanceId
+ * @param instanceConfig
+ * @param pubId
+ * @param evaluator
+ * @returns
+ */
+export const scheduleEvaluationReminderEmail = async (
instanceId: string,
instanceConfig: InstanceConfig,
pubId: string,
- evaluator: EvaluatorWithInvite
+ evaluator: EvaluatorWhoAccepted
) => {
- return client.sendEmail(instanceId, {
- to: {
- userId: evaluator.invitedBy,
- },
- subject: `[Unjournal] Invited evaluator declines to evaluate "{{pubs.submission.values["${instanceConfig.titleFieldSlug}"]}}"`,
- message: `An invited evaluator, {{users.evaluator.firstName}} {{users.evaluator.lastName}}, has declined to evaluate "{{pubs.submission.values["${instanceConfig.titleFieldSlug}"]}}". You may review the status of this and other invitations on the {{extra.manage_link}}.
-${notificationFooter}`,
- include: {
- pubs: {
- submission: pubId,
+ const deadline = getDeadline(instanceConfig, evaluator);
+ const jobKey = makeEvalReminderJobKey(instanceId, pubId, evaluator);
+ const runAt = new Date(deadline.getTime() - 7 * (1000 * 60 * 60 * 24));
+
+ return client.scheduleEmail(
+ instanceId,
+ {
+ to: {
+ userId: evaluator.userId,
},
- users: {
- evaluator: evaluator.userId,
+ subject: `[Unjournal] Reminder to evaluate "{{pubs.submission.values["${instanceConfig.titleFieldSlug}"]}}" by next week`,
+ message: `Hi {{user.firstName}},
+ Thank you again for agreeing to evaluate "{{pubs.submission.values["${
+ instanceConfig.titleFieldSlug
+ }"]}}" for The Unjournal.
+ This note is a reminder that your evaluation should be submitted by ${new Date(
+ deadline.getTime()
+ ).toLocaleDateString()} (next week). Please note that after that date we will consider re-assigning the evaluation, and later submissions may not be eligible for the full baseline compensation.
+ Please submit your evaluation and rating, as well as any specific considerations, using this evaluation form. The form includes instructions and information about the paper/project.
+ If you have any questions, do not hesitate to reach out to me at {{users.invitor.email}}.
+ Once your evaluation has been submitted and reviewed, we will follow up with details about payment and next steps.
+ Thanks and best wishes,
+ {{users.invitor.firstName}} {{users.invitor.lastName}}
+ Unjournal.org
`,
+ include: {
+ pubs: {
+ submission: pubId,
+ },
+ users: {
+ invitor: evaluator.invitedBy,
+ },
+ },
+ extra: {
+ evaluate_link: `{{instance.actions.evaluate}}?instanceId={{instance.id}}&pubId={{pubs.submission.id}}&token={{user.token}}`,
},
},
- extra: {
- manage_link: `Invite Evaluators page`,
- },
- });
+ { jobKey, runAt }
+ );
};
-export const sendSubmittedNotificationEmail = async (
+/**
+ * Schedules a final reminder email to an evaluator to submit their evaluation.
+ * @param instanceId
+ * @param instanceConfig
+ * @param pubId
+ * @param evaluator
+ * @returns
+ */
+export const scheduleFinalEvaluationReminderEmail = async (
instanceId: string,
instanceConfig: InstanceConfig,
pubId: string,
- evaluator: EvaluatorWhoEvaluated
+ evaluator: EvaluatorWhoAccepted
) => {
- return client.sendEmail(instanceId, {
- to: {
- userId: evaluator.invitedBy,
+ const deadline = getDeadline(instanceConfig, evaluator);
+ const jobKey = makeFinalEvalReminderJobKey(instanceId, pubId, evaluator);
+ const runAt = new Date(deadline.getTime() - 1 * (1000 * 60 * 60 * 24));
+
+ return client.scheduleEmail(
+ instanceId,
+ {
+ to: {
+ userId: evaluator.userId,
+ },
+ subject: `[Unjournal] Final Reminder: Evaluation due tomorrow`,
+ message: `Hi {{user.firstName}},
+ This note is a final reminder that your evaluation for "{{pubs.submission.values["${instanceConfig.titleFieldSlug}"]}}" is due tomorrow. Please make sure to submit your evaluation by the deadline.
+ If you haven't already, please submit your evaluation and rating, as well as any specific considerations, using this evaluation form. The form includes instructions and information about the paper/project.
+ If you have any questions, do not hesitate to reach out to me at {{users.invitor.email}}.
+ Once your evaluation has been submitted and reviewed, we will follow up with details about payment and next steps.
+ Thanks and best wishes,
+ {{users.invitor.firstName}} {{users.invitor.lastName}}
+ Unjournal.org
`,
+ include: {
+ pubs: {
+ submission: pubId,
+ },
+ users: {
+ invitor: evaluator.invitedBy,
+ },
+ },
+ extra: {
+ evaluate_link: `{{instance.actions.evaluate}}?instanceId={{instance.id}}&pubId={{pubs.submission.id}}&token={{user.token}}`,
+ },
},
- subject: `[Unjournal] Evaluation submitted for "{{pubs.submission.values["${instanceConfig.titleFieldSlug}"]}}"`,
- message: `An evaluator, {{users.evaluator.firstName}} {{users.evaluator.lastName}}, has submitted an evaluation for "{{pubs.submission.values["${instanceConfig.titleFieldSlug}"]}}". The submitted evaluation Pub can be viewed here.
-You may review the status of this and other invitations on the {{extra.manage_link}}.
-${notificationFooter}`,
- include: {
- pubs: {
- submission: pubId,
+ { jobKey, runAt }
+ );
+};
+
+/**
+ * Schedules a follow-up to evaluation reminder email to an evaluator.
+ * @param instanceId
+ * @param instanceConfig
+ * @param pubId
+ * @param evaluator
+ * @returns
+ */
+export const scheduleFollowUpToFinalEvaluationReminderEmail = async (
+ instanceId: string,
+ instanceConfig: InstanceConfig,
+ pubId: string,
+ evaluator: EvaluatorWhoAccepted
+) => {
+ const deadline = getDeadline(instanceConfig, evaluator);
+ const jobKey = makeFollowUpToFinalEvalReminderJobKey(instanceId, pubId, evaluator);
+ const runAt = new Date(deadline.getTime() + 6 * (1000 * 60 * 60 * 24));
+
+ return client.scheduleEmail(
+ instanceId,
+ {
+ to: {
+ userId: evaluator.userId,
},
- users: {
- evaluator: evaluator.userId,
+ subject: `[Unjournal] Follow-up: Evaluation overdue, to be reassigned`,
+ message: `Hi {{user.firstName}},
+ This note is a reminder that your evaluation for "{{pubs.submission.values["${
+ instanceConfig.titleFieldSlug
+ }"]}}" is overdue. We are now planning to reassign the evaluation to another evaluator.
+ If you have completed the evaluation but forgot to submit it, please submit your evaluation and rating today using this evaluation form. If we don't hear from you by the end of ${new Date(
+ deadline.getTime() + 7 * (1000 * 60 * 60 * 24)
+ ).toLocaleDateString()}, we will remove you from this assignment and you will no longer be eligible for compensation.
+ If you have any questions, do not hesitate to reach out to me at {{users.invitor.email}}.
+ Thanks and best wishes,
+ {{users.invitor.firstName}} {{users.invitor.lastName}}
+ Unjournal.org
`,
+ include: {
+ pubs: {
+ submission: pubId,
+ },
+ users: {
+ invitor: evaluator.invitedBy,
+ },
+ },
+ extra: {
+ evaluate_link: `{{instance.actions.evaluate}}?instanceId={{instance.id}}&pubId={{pubs.submission.id}}&token={{user.token}}`,
},
},
- extra: {
- manage_link: `Invite Evaluators page`,
+ { jobKey, runAt }
+ );
+};
+
+/**
+ * Schedules a notice of no submit email to an evaluator.
+ * @param instanceId
+ * @param instanceConfig
+ * @param pubId
+ * @param evaluator
+ * @returns
+ */
+export const sendNoticeOfNoSubmitEmail = async (
+ instanceId: string,
+ instanceConfig: InstanceConfig,
+ pubId: string,
+ evaluator: EvaluatorWhoAccepted
+) => {
+ const deadline = getDeadline(instanceConfig, evaluator);
+ const jobKey = makeNoticeOfNoSubmitJobKey(instanceId, pubId, evaluator);
+ const runAt = new Date(deadline.getTime() + 8 * (1000 * 60 * 60 * 24));
+
+ return client.scheduleEmail(
+ instanceId,
+ {
+ to: {
+ userId: evaluator.userId,
+ },
+ subject: `[Unjournal] Evaluation not submitted for "{{pubs.submission.values["${instanceConfig.titleFieldSlug}"]}}"`,
+ message: `Hi {{user.firstName}},
+ This is to inform you that you have not submitted an evaluation for "{{pubs.submission.values["${
+ instanceConfig.titleFieldSlug
+ }"]}}", which was due on ${new Date(deadline.getTime()).toLocaleDateString()}.
+ If you have completed the evaluation but forgot to submit it, please submit your evaluation and rating today using this evaluation form. If we don't hear from you by the end of ${new Date(
+ deadline.getTime()
+ ).toLocaleDateString()}, we will remove you from this assignment and you will no longer be eligible for compensation.
+ If you have any questions, do not hesitate to reach out to me at {{users.invitor.email}}.
+ Thanks and best wishes,
+ {{users.invitor.firstName}} {{users.invitor.lastName}}
+ Unjournal.org
`,
+ include: {
+ pubs: {
+ submission: pubId,
+ },
+ users: {
+ invitor: evaluator.invitedBy,
+ },
+ },
+ extra: {
+ evaluate_link: `{{instance.actions.evaluate}}?instanceId={{instance.id}}&pubId={{pubs.submission.id}}&token={{user.token}}`,
+ },
},
- });
+ { jobKey, runAt }
+ );
+};
+
+/**
+ * Unschedules all the deadline reminder emails.
+ * `client.unscheduleEmail()` returns no-op for emails that have been
+ * sent, allowing us to call it without checking if an error
+ * @param instanceId
+ * @param pubId
+ * @param evaluator
+ * @returns
+ */
+export const unscheduleAllDeadlineReminderEmails = async (
+ instanceId: string,
+ pubId: string,
+ evaluator: EvaluatorWhoAccepted
+) => {
+ const jobKeys = [
+ makePromptEvalBonusReminderJobKey(instanceId, pubId, evaluator),
+ makeFinalPromptEvalBonusReminderJobKey(instanceId, pubId, evaluator),
+ makeEvalReminderJobKey(instanceId, pubId, evaluator),
+ makeFinalEvalReminderJobKey(instanceId, pubId, evaluator),
+ makeFollowUpToFinalEvalReminderJobKey(instanceId, pubId, evaluator),
+ makeNoticeOfNoSubmitJobKey(instanceId, pubId, evaluator),
+ ];
+ return Promise.all(jobKeys.map((jobKey) => client.unscheduleEmail(instanceId, jobKey)));
+};
+
+/**
+ * Unschedules all emails.
+ * @param instanceId
+ * @param pubId
+ * @param evaluator
+ * @returns
+ */
+export const unscheduleAllManagerEmails = async (
+ instanceId: string,
+ pubId: string,
+ evaluator: EvaluatorWithInvite
+) => {
+ const jobKeys = [
+ makeReminderJobKey(instanceId, pubId, evaluator),
+ makeNoReplyJobKey(instanceId, pubId, evaluator),
+ makeNoSubmitJobKey(instanceId, pubId, evaluator),
+ ];
+ return Promise.all(jobKeys.map((jobKey) => client.unscheduleEmail(instanceId, jobKey)));
};
diff --git a/integrations/submissions/app/actions/submit/FetchMetadataButton.tsx b/integrations/submissions/app/actions/submit/FetchMetadataButton.tsx
index 828428d49..d7493014b 100644
--- a/integrations/submissions/app/actions/submit/FetchMetadataButton.tsx
+++ b/integrations/submissions/app/actions/submit/FetchMetadataButton.tsx
@@ -2,16 +2,11 @@
import { useTransition } from "react";
import { useFormContext } from "react-hook-form";
-import {
- Button,
- Icon,
- Tooltip,
- TooltipContent,
- TooltipProvider,
- TooltipTrigger,
- useFormField,
- useToast,
-} from "ui";
+import { Button } from "ui/button";
+import { Loader2, Wand2 } from "ui/icon";
+import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "ui/tooltip";
+import { useFormField } from "ui/form";
+import { useToast } from "ui/use-toast";
import { cn } from "utils";
import { resolveMetadata } from "./actions";
@@ -79,9 +74,9 @@ export const FetchMetadataButton = (props: FetchMetadataButtonProps) => {
disabled={!state.isDirty || state.invalid}
>
{pending ? (
-
+
) : (
-
+
)}
diff --git a/integrations/submissions/app/actions/submit/submit.tsx b/integrations/submissions/app/actions/submit/submit.tsx
index 437104632..2486af026 100644
--- a/integrations/submissions/app/actions/submit/submit.tsx
+++ b/integrations/submissions/app/actions/submit/submit.tsx
@@ -3,14 +3,9 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
+import { Button } from "ui/button";
+import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "ui/card";
import {
- Button,
- Card,
- CardContent,
- CardDescription,
- CardFooter,
- CardHeader,
- CardTitle,
Form,
FormControl,
FormDescription,
@@ -18,12 +13,12 @@ import {
FormItem,
FormLabel,
FormMessage,
- Icon,
- Input,
- Textarea,
- useLocalStorage,
- useToast,
-} from "ui";
+} from "ui/form";
+import { Loader2 } from "ui/icon";
+import { Input } from "ui/input";
+import { Textarea } from "ui/textarea";
+import { useLocalStorage } from "ui/hooks";
+import { useToast } from "ui/use-toast";
import { DOI_REGEX, URL_REGEX, cn, isDoi, normalizeDoi } from "utils";
import * as z from "zod";
import { FetchMetadataButton } from "./FetchMetadataButton";
@@ -230,7 +225,7 @@ export function Submit(props: Props) {
diff --git a/integrations/submissions/app/configure/configure.tsx b/integrations/submissions/app/configure/configure.tsx
index 102a5e587..cb549ffc3 100644
--- a/integrations/submissions/app/configure/configure.tsx
+++ b/integrations/submissions/app/configure/configure.tsx
@@ -2,8 +2,8 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
+import { Button } from "ui/button";
import {
- Button,
Form,
FormControl,
FormDescription,
@@ -11,16 +11,12 @@ import {
FormItem,
FormLabel,
FormMessage,
- Icon,
- Input,
- useToast,
- Card,
- CardHeader,
- CardFooter,
- CardContent,
- CardTitle,
- CardDescription,
-} from "ui";
+} from "ui/form";
+import { Loader2 } from "ui/icon";
+import { Input } from "ui/input";
+import { useToast } from "ui/use-toast";
+import { Card, CardHeader, CardFooter, CardContent, CardTitle, CardDescription } from "ui/card";
+
import { cn } from "utils";
import * as z from "zod";
import { configure } from "./actions";
@@ -104,7 +100,7 @@ export function Configure(props: Props) {
diff --git a/integrations/submissions/app/layout.tsx b/integrations/submissions/app/layout.tsx
index 1a70b5a9d..25dce1620 100644
--- a/integrations/submissions/app/layout.tsx
+++ b/integrations/submissions/app/layout.tsx
@@ -1,7 +1,7 @@
import { User } from "@pubpub/sdk";
import { cookies, headers } from "next/headers";
-import { Toaster } from "ui";
import { env } from "~/lib/env.mjs";
+import { Toaster } from "ui/toaster";
import "ui/styles.css";
import { expect } from "utils";
import { Integration } from "~/lib/Integration";
diff --git a/package.json b/package.json
index 4900379b6..59701ebab 100644
--- a/package.json
+++ b/package.json
@@ -17,7 +17,7 @@
"@babel/preset-react": "^7.22.15",
"@babel/preset-typescript": "^7.22.15",
"@changesets/cli": "^2.26.2",
- "@preconstruct/cli": "^2.8.1",
+ "@preconstruct/cli": "^2.8.3",
"husky": "^8.0.3",
"lint-staged": "^13.2.2",
"prettier": "^2.7.1",
diff --git a/packages/sdk/src/client.ts b/packages/sdk/src/client.ts
index 622ac58e1..4049295f2 100644
--- a/packages/sdk/src/client.ts
+++ b/packages/sdk/src/client.ts
@@ -9,7 +9,6 @@ import {
ScheduleEmailResponseBody,
SendEmailRequestBody,
SendEmailResponseBody,
- UpdatePubRequestBody,
UpdatePubResponseBody,
User,
api,
diff --git a/packages/sdk/src/react/Integration.tsx b/packages/sdk/src/react/Integration.tsx
index 7679df75c..f36210eb7 100644
--- a/packages/sdk/src/react/Integration.tsx
+++ b/packages/sdk/src/react/Integration.tsx
@@ -1,5 +1,5 @@
import * as React from "react";
-import { LocalStorageProvider } from "ui";
+import { LocalStorageProvider } from "ui/hooks";
import { IntegrationLayout } from "./IntegrationLayout";
import { IntegrationProvider, IntegrationProviderProps } from "./IntegrationProvider";
diff --git a/packages/sdk/src/react/IntegrationAvatar.tsx b/packages/sdk/src/react/IntegrationAvatar.tsx
index 659d01c99..046b6ba55 100644
--- a/packages/sdk/src/react/IntegrationAvatar.tsx
+++ b/packages/sdk/src/react/IntegrationAvatar.tsx
@@ -1,5 +1,5 @@
import * as React from "react";
-import { Avatar, AvatarImage, AvatarFallback } from "ui";
+import { Avatar, AvatarImage, AvatarFallback } from "ui/avatar";
type Props = {
firstName: string;
diff --git a/packages/sdk/src/react/generateFormFields.tsx b/packages/sdk/src/react/generateFormFields.tsx
index 0c4a5fb75..faf117eb5 100644
--- a/packages/sdk/src/react/generateFormFields.tsx
+++ b/packages/sdk/src/react/generateFormFields.tsx
@@ -3,20 +3,13 @@ import * as React from "react";
import Ajv, { JSONSchemaType } from "ajv";
import { GetPubTypeResponseBody } from "contracts";
import { Control, ControllerRenderProps } from "react-hook-form";
-import {
- Checkbox,
- Confidence,
- FileUpload,
- FormControl,
- FormDescription,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
- Input,
- Separator,
- Textarea,
-} from "ui";
+import { Checkbox } from "ui/checkbox";
+import { Confidence } from "ui/customRenderers/confidence/confidence";
+import { FileUpload } from "ui/customRenderers/fileUpload/fileUpload";
+import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "ui/form";
+import { Input } from "ui/input";
+import { Separator } from "ui/separator";
+import { Textarea } from "ui/textarea";
import { cn } from "utils";
// a bit of a hack, but allows us to use AJV's JSON schema type
diff --git a/packages/ui/README.md b/packages/ui/README.md
index 396595032..7bbc155b5 100644
--- a/packages/ui/README.md
+++ b/packages/ui/README.md
@@ -1,27 +1,46 @@
-# PubPub UI
+# PubPub UI
+
+## How to import
+
+You need to import the component from the `ui/\` path. For example:
+
+```tsx
+import { Button } from "ui/button";
+import { Loader2 } from "ui/icon";
+```
+
+## How to add a new component
+
+1. Create a file in src, something like `src/component.tsx`, that exports a _named_ Component. Ideally copy-paste something from https://ui.shadcn.com/components
+2. Add that file to `package.json['entryPoints']` as `component.tsx`
+3. Add component to the `package.json['files']` array
+4. In packages/ui, run `pnpm preconstruct fix && pnpm preconstruct dev`
+5. Everything should be good!
## Rationale for our style approach
+
In the past, PubPub has stuck pretty close to the style system offered by the off-the-shelf component library we've used (primarily BlueprintJS). This is convenient because it allowed us to move quickly early on, but has become a liability over time as our design needs to deviate from the Blueprint design, and as the Blueprint library introduces breaking changes.
In v7, we find ourselves with an additional styling objective, which is to allow integrations (both 1st and 3rd party) to easily adopt the styles and design of `core`. Further, we hope to do this without requiring every integration to use the exact same technical stack as `core`.
Other important considerations include site-wide theming capabilities, robust Figma offerings, and performance (this was a noticeable issue with Blueprint as PubPub v6 scaled).
-Other minor considerations include a preference to avoid CSS-in-JS approaches, size of the open source community, sustainability of the library (having a library stop maintenance because it had no business model puts us in a tough position), component-level bundling (*all* of BlueprintJS ships with every page even if we only used a single component), and friendliness with server-side rendering (i.e. no FOUC while it waits for client-side JS to load).
+Other minor considerations include a preference to avoid CSS-in-JS approaches, size of the open source community, sustainability of the library (having a library stop maintenance because it had no business model puts us in a tough position), component-level bundling (_all_ of BlueprintJS ships with every page even if we only used a single component), and friendliness with server-side rendering (i.e. no FOUC while it waits for client-side JS to load).
In experimenting with different component libraries and workflows, a consistent fork in the road kept appearing. We can either:
+
1. pick a React library and require integrations to use React if they want to be styled similarly, or
2. we need to pick an approach that works in vanilla HTML
-The first approach would let us choose something like Chakra, Mantine, or Blueprint. The second approach leaves us essentially choosing between writing all CSS classes ourselves or picking a tool like Tailwind.
+The first approach would let us choose something like Chakra, Mantine, or Blueprint. The second approach leaves us essentially choosing between writing all CSS classes ourselves or picking a tool like Tailwind.
-I feel strongly that integrations shouldn't *have* to use the same stack as us (i.e. React), so spent time focusing on approach (2). Down that path, Tailwind offers a lot that writing our own classes from scratch does not. Writing raw CSS everywhere feels too difficult to maintain for a large project and I think we’d frankly wind up essentially re-implementing tailwind, but with less familiarity for integration developers and worse documentation. Further,
+I feel strongly that integrations shouldn't _have_ to use the same stack as us (i.e. React), so spent time focusing on approach (2). Down that path, Tailwind offers a lot that writing our own classes from scratch does not. Writing raw CSS everywhere feels too difficult to maintain for a large project and I think we’d frankly wind up essentially re-implementing tailwind, but with less familiarity for integration developers and worse documentation. Further,
-- It’s got great documentation with best practices and a huge community
-- It has tons of community Figma boards
-- It lets us offer a simple configuration plugin (i.e. `plugins: [require("pubpub/styles")]`) that defines colors, borders, fonts, etc that could allow integrations to easily get good-enough similarity with little other work.
+- It’s got great documentation with best practices and a huge community
+- It has tons of community Figma boards
+- It lets us offer a simple configuration plugin (i.e. `plugins: [require("pubpub/styles")]`) that defines colors, borders, fonts, etc that could allow integrations to easily get good-enough similarity with little other work.
-So, if we run with the assumption that we'll use Tailwind and integrations have to drop in Tailwind to achieve similar styling, the next question is: Do we build components ourselves or take off the shelf ones? The question feels trivial: we shouldn't try to rebuild complex UI components from scratch. Accessibility is hard. Cross-browser support for edge case interactions is hard.
+So, if we run with the assumption that we'll use Tailwind and integrations have to drop in Tailwind to achieve similar styling, the next question is: Do we build components ourselves or take off the shelf ones? The question feels trivial: we shouldn't try to rebuild complex UI components from scratch. Accessibility is hard. Cross-browser support for edge case interactions is hard.
I spent some time playing with Flowbite, which offers an extensive component library built from native HTML and tailwind css. Flowbite even offers a 1st-party React library of their components. Unfortunately, Flowbite-React seems insufficient for us. It still has a good deal of bugs, does not have complete component coverage, and their experimental theming is doing a JS theming thing instead of just using tailwind config (! So it winds up essentially being the same as Chakra or Mantine, in that the component library bundles its styles with it).
@@ -36,33 +55,37 @@ Because integrations that are written with a different framework won't be able t
This also gives us a lot of flexibility in building components. We can mix and match headless libraries if we want — there's no problem having, for example, both Radix and AriaKit since they aren’t fighting for style. We're not forced into a monolithic decision. Of course, headless component libraries have a lot of overlap, so I expect we would mostly use the same one, but from the perspective integration developers and performance, there's really no "commitment" beyond tailwind.
On top of Tailwind, there is a lot of good community work to draw from:
-- Radix and shadcn/ui
-- AriaKit components
-- HeadlessUI and open source TailwindUI components built on top of it
-- Countless Tailwind component libraries
-- Countless Tailwind Figma boards
+
+- Radix and shadcn/ui
+- AriaKit components
+- HeadlessUI and open source TailwindUI components built on top of it
+- Countless Tailwind component libraries
+- Countless Tailwind Figma boards
The other perk of this approach is that all component styling is kept in pubpub-core, as opposed to deep in some node module. This makes it a bit easier for someone who's trying to port a component into Vue or Svelte and want it to look like PubPub components — the core HTML and tailwind classes are easily found.
In the end, this approach resonates with me because:
-- It feels as close to vanilla CSS as we can get without forcing ourselves to write all our classes and components from scratch.
-- It's performant since it's just CSS at the end of the day (especially compared to monolithic component libraries like Blueprint).
-- It offers integration developers a simple approach to add our tailwind config as a plugin.
-- It allows our components to use headless UI libraries focused on accessibility.
-- We maintain complete control of top-level components and styling.
+
+- It feels as close to vanilla CSS as we can get without forcing ourselves to write all our classes and components from scratch.
+- It's performant since it's just CSS at the end of the day (especially compared to monolithic component libraries like Blueprint).
+- It offers integration developers a simple approach to add our tailwind config as a plugin.
+- It allows our components to use headless UI libraries focused on accessibility.
+- We maintain complete control of top-level components and styling.
The primary tradeoff to all of this is a little more up front work compared to using something like Chakra, but there is so much community offering that I don't feel concerned about our ability to build quickly.
## Links and Resources
+
A list of resources I found helpful in my spike:
-- [CSS Solution Analysis for Polaris Foundations](https://docs.google.com/spreadsheets/d/1rxrRTlbNWiLVu-Q5IK7xh5O1FmWcjyAS2XN7jiPrhYM/edit#gid=0)
- - An in depth analysis of different CSS approaches. A few years old, and notably, current Tailwind has addressed nearly all its issues (multiple themes and build-time performance).
-- [Tailwind](https://tailwindcss.com/)
-- [shadcn/ui](https://ui.shadcn.com/)
-- [Radix](https://www.radix-ui.com/)
-- [Chakra](https://chakra-ui.com/)
-- [Mantine](https://mantine.dev/)
-- [HeadlessUI](https://headlessui.com/)
-- [AriaKit](https://ariakit.org/)
-- [Flowbite](https://flowbite.com/)
-- [Flowbite-React](https://www.flowbite-react.com/)
+
+- [CSS Solution Analysis for Polaris Foundations](https://docs.google.com/spreadsheets/d/1rxrRTlbNWiLVu-Q5IK7xh5O1FmWcjyAS2XN7jiPrhYM/edit#gid=0)
+ - An in depth analysis of different CSS approaches. A few years old, and notably, current Tailwind has addressed nearly all its issues (multiple themes and build-time performance).
+- [Tailwind](https://tailwindcss.com/)
+- [shadcn/ui](https://ui.shadcn.com/)
+- [Radix](https://www.radix-ui.com/)
+- [Chakra](https://chakra-ui.com/)
+- [Mantine](https://mantine.dev/)
+- [HeadlessUI](https://headlessui.com/)
+- [AriaKit](https://ariakit.org/)
+- [Flowbite](https://flowbite.com/)
+- [Flowbite-React](https://www.flowbite-react.com/)
diff --git a/packages/ui/alert/package.json b/packages/ui/alert/package.json
new file mode 100644
index 000000000..07d4e222d
--- /dev/null
+++ b/packages/ui/alert/package.json
@@ -0,0 +1,4 @@
+{
+ "main": "dist/ui-alert.cjs.js",
+ "module": "dist/ui-alert.esm.js"
+}
diff --git a/packages/ui/avatar/package.json b/packages/ui/avatar/package.json
new file mode 100644
index 000000000..beb541505
--- /dev/null
+++ b/packages/ui/avatar/package.json
@@ -0,0 +1,4 @@
+{
+ "main": "dist/ui-avatar.cjs.js",
+ "module": "dist/ui-avatar.esm.js"
+}
diff --git a/packages/ui/badge/package.json b/packages/ui/badge/package.json
new file mode 100644
index 000000000..180d027b2
--- /dev/null
+++ b/packages/ui/badge/package.json
@@ -0,0 +1,4 @@
+{
+ "main": "dist/ui-badge.cjs.js",
+ "module": "dist/ui-badge.esm.js"
+}
diff --git a/packages/ui/button/package.json b/packages/ui/button/package.json
new file mode 100644
index 000000000..1081a7e87
--- /dev/null
+++ b/packages/ui/button/package.json
@@ -0,0 +1,4 @@
+{
+ "main": "dist/ui-button.cjs.js",
+ "module": "dist/ui-button.esm.js"
+}
diff --git a/packages/ui/calandar/package.json b/packages/ui/calandar/package.json
new file mode 100644
index 000000000..513cc6c82
--- /dev/null
+++ b/packages/ui/calandar/package.json
@@ -0,0 +1,4 @@
+{
+ "main": "dist/ui-calandar.cjs.js",
+ "module": "dist/ui-calandar.esm.js"
+}
diff --git a/packages/ui/card/package.json b/packages/ui/card/package.json
new file mode 100644
index 000000000..9fe9a69b3
--- /dev/null
+++ b/packages/ui/card/package.json
@@ -0,0 +1,4 @@
+{
+ "main": "dist/ui-card.cjs.js",
+ "module": "dist/ui-card.esm.js"
+}
diff --git a/packages/ui/checkbox/package.json b/packages/ui/checkbox/package.json
new file mode 100644
index 000000000..19785872b
--- /dev/null
+++ b/packages/ui/checkbox/package.json
@@ -0,0 +1,4 @@
+{
+ "main": "dist/ui-checkbox.cjs.js",
+ "module": "dist/ui-checkbox.esm.js"
+}
diff --git a/packages/ui/collapsible/package.json b/packages/ui/collapsible/package.json
new file mode 100644
index 000000000..7f5b38106
--- /dev/null
+++ b/packages/ui/collapsible/package.json
@@ -0,0 +1,4 @@
+{
+ "main": "dist/ui-collapsible.cjs.js",
+ "module": "dist/ui-collapsible.esm.js"
+}
diff --git a/packages/ui/customRenderers/confidence/confidence/package.json b/packages/ui/customRenderers/confidence/confidence/package.json
new file mode 100644
index 000000000..d7811aacd
--- /dev/null
+++ b/packages/ui/customRenderers/confidence/confidence/package.json
@@ -0,0 +1,4 @@
+{
+ "main": "dist/ui-customRenderers-confidence-confidence.cjs.js",
+ "module": "dist/ui-customRenderers-confidence-confidence.esm.js"
+}
diff --git a/packages/ui/customRenderers/fileUpload/fileUpload/package.json b/packages/ui/customRenderers/fileUpload/fileUpload/package.json
new file mode 100644
index 000000000..115896971
--- /dev/null
+++ b/packages/ui/customRenderers/fileUpload/fileUpload/package.json
@@ -0,0 +1,4 @@
+{
+ "main": "dist/ui-customRenderers-fileUpload-fileUpload.cjs.js",
+ "module": "dist/ui-customRenderers-fileUpload-fileUpload.esm.js"
+}
diff --git a/packages/ui/dialog/package.json b/packages/ui/dialog/package.json
new file mode 100644
index 000000000..6c62c6c49
--- /dev/null
+++ b/packages/ui/dialog/package.json
@@ -0,0 +1,4 @@
+{
+ "main": "dist/ui-dialog.cjs.js",
+ "module": "dist/ui-dialog.esm.js"
+}
diff --git a/packages/ui/dropdown-menu/package.json b/packages/ui/dropdown-menu/package.json
new file mode 100644
index 000000000..19a93e342
--- /dev/null
+++ b/packages/ui/dropdown-menu/package.json
@@ -0,0 +1,4 @@
+{
+ "main": "dist/ui-dropdown-menu.cjs.js",
+ "module": "dist/ui-dropdown-menu.esm.js"
+}
diff --git a/packages/ui/form/package.json b/packages/ui/form/package.json
new file mode 100644
index 000000000..2d77a663e
--- /dev/null
+++ b/packages/ui/form/package.json
@@ -0,0 +1,4 @@
+{
+ "main": "dist/ui-form.cjs.js",
+ "module": "dist/ui-form.esm.js"
+}
diff --git a/packages/ui/hooks/package.json b/packages/ui/hooks/package.json
new file mode 100644
index 000000000..ba1eb5669
--- /dev/null
+++ b/packages/ui/hooks/package.json
@@ -0,0 +1,4 @@
+{
+ "main": "dist/ui-hooks.cjs.js",
+ "module": "dist/ui-hooks.esm.js"
+}
diff --git a/packages/ui/hover-card/package.json b/packages/ui/hover-card/package.json
new file mode 100644
index 000000000..ef697bc22
--- /dev/null
+++ b/packages/ui/hover-card/package.json
@@ -0,0 +1,4 @@
+{
+ "main": "dist/ui-hover-card.cjs.js",
+ "module": "dist/ui-hover-card.esm.js"
+}
diff --git a/packages/ui/icon/package.json b/packages/ui/icon/package.json
new file mode 100644
index 000000000..a76332af5
--- /dev/null
+++ b/packages/ui/icon/package.json
@@ -0,0 +1,4 @@
+{
+ "main": "dist/ui-icon.cjs.js",
+ "module": "dist/ui-icon.esm.js"
+}
diff --git a/packages/ui/input/package.json b/packages/ui/input/package.json
new file mode 100644
index 000000000..420fbd174
--- /dev/null
+++ b/packages/ui/input/package.json
@@ -0,0 +1,4 @@
+{
+ "main": "dist/ui-input.cjs.js",
+ "module": "dist/ui-input.esm.js"
+}
diff --git a/packages/ui/label/package.json b/packages/ui/label/package.json
new file mode 100644
index 000000000..4c9190dc6
--- /dev/null
+++ b/packages/ui/label/package.json
@@ -0,0 +1,4 @@
+{
+ "main": "dist/ui-label.cjs.js",
+ "module": "dist/ui-label.esm.js"
+}
diff --git a/packages/ui/package.json b/packages/ui/package.json
index ac4bc0110..561b6096c 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -4,9 +4,163 @@
"version": "0.0.0",
"main": "dist/ui.cjs.js",
"module": "dist/ui.esm.js",
+ "exports": {
+ "./card": {
+ "module": "./card/dist/ui-card.esm.js",
+ "default": "./card/dist/ui-card.cjs.js"
+ },
+ "./form": {
+ "module": "./form/dist/ui-form.esm.js",
+ "default": "./form/dist/ui-form.cjs.js"
+ },
+ "./icon": {
+ "module": "./icon/dist/ui-icon.esm.js",
+ "default": "./icon/dist/ui-icon.cjs.js"
+ },
+ "./tabs": {
+ "module": "./tabs/dist/ui-tabs.esm.js",
+ "default": "./tabs/dist/ui-tabs.cjs.js"
+ },
+ "./alert": {
+ "module": "./alert/dist/ui-alert.esm.js",
+ "default": "./alert/dist/ui-alert.cjs.js"
+ },
+ "./badge": {
+ "module": "./badge/dist/ui-badge.esm.js",
+ "default": "./badge/dist/ui-badge.cjs.js"
+ },
+ "./input": {
+ "module": "./input/dist/ui-input.esm.js",
+ "default": "./input/dist/ui-input.cjs.js"
+ },
+ "./label": {
+ "module": "./label/dist/ui-label.esm.js",
+ "default": "./label/dist/ui-label.cjs.js"
+ },
+ "./toast": {
+ "module": "./toast/dist/ui-toast.esm.js",
+ "default": "./toast/dist/ui-toast.cjs.js"
+ },
+ "./avatar": {
+ "module": "./avatar/dist/ui-avatar.esm.js",
+ "default": "./avatar/dist/ui-avatar.cjs.js"
+ },
+ "./button": {
+ "module": "./button/dist/ui-button.esm.js",
+ "default": "./button/dist/ui-button.cjs.js"
+ },
+ "./dialog": {
+ "module": "./dialog/dist/ui-dialog.esm.js",
+ "default": "./dialog/dist/ui-dialog.cjs.js"
+ },
+ "./select": {
+ "module": "./select/dist/ui-select.esm.js",
+ "default": "./select/dist/ui-select.cjs.js"
+ },
+ "./popover": {
+ "module": "./popover/dist/ui-popover.esm.js",
+ "default": "./popover/dist/ui-popover.cjs.js"
+ },
+ "./toaster": {
+ "module": "./toaster/dist/ui-toaster.esm.js",
+ "default": "./toaster/dist/ui-toaster.cjs.js"
+ },
+ "./tooltip": {
+ "module": "./tooltip/dist/ui-tooltip.esm.js",
+ "default": "./tooltip/dist/ui-tooltip.cjs.js"
+ },
+ "./calandar": {
+ "module": "./calandar/dist/ui-calandar.esm.js",
+ "default": "./calandar/dist/ui-calandar.cjs.js"
+ },
+ "./checkbox": {
+ "module": "./checkbox/dist/ui-checkbox.esm.js",
+ "default": "./checkbox/dist/ui-checkbox.cjs.js"
+ },
+ "./textarea": {
+ "module": "./textarea/dist/ui-textarea.esm.js",
+ "default": "./textarea/dist/ui-textarea.cjs.js"
+ },
+ "./separator": {
+ "module": "./separator/dist/ui-separator.esm.js",
+ "default": "./separator/dist/ui-separator.cjs.js"
+ },
+ "./use-toast": {
+ "module": "./use-toast/dist/ui-use-toast.esm.js",
+ "default": "./use-toast/dist/ui-use-toast.cjs.js"
+ },
+ "./hooks": {
+ "module": "./hooks/dist/ui-hooks.esm.js",
+ "default": "./hooks/dist/ui-hooks.cjs.js"
+ },
+ "./hover-card": {
+ "module": "./hover-card/dist/ui-hover-card.esm.js",
+ "default": "./hover-card/dist/ui-hover-card.cjs.js"
+ },
+ "./collapsible": {
+ "module": "./collapsible/dist/ui-collapsible.esm.js",
+ "default": "./collapsible/dist/ui-collapsible.cjs.js"
+ },
+ "./dropdown-menu": {
+ "module": "./dropdown-menu/dist/ui-dropdown-menu.esm.js",
+ "default": "./dropdown-menu/dist/ui-dropdown-menu.cjs.js"
+ },
+ "./customRenderers/confidence/confidence": {
+ "module": "./customRenderers/confidence/confidence/dist/ui-customRenderers-confidence-confidence.esm.js",
+ "default": "./customRenderers/confidence/confidence/dist/ui-customRenderers-confidence-confidence.cjs.js"
+ },
+ "./customRenderers/fileUpload/fileUpload": {
+ "module": "./customRenderers/fileUpload/fileUpload/dist/ui-customRenderers-fileUpload-fileUpload.esm.js",
+ "default": "./customRenderers/fileUpload/fileUpload/dist/ui-customRenderers-fileUpload-fileUpload.cjs.js"
+ },
+ "./package.json": "./package.json",
+ "./customRenderers/confidence": {
+ "module": "./customRenderers/confidence/confidence/dist/ui-customRenderers-confidence-confidence.esm.js",
+ "default": "./customRenderers/confidence/confidence/dist/ui-customRenderers-confidence-confidence.cjs.js"
+ },
+ "./customRenderers/fileUpload": {
+ "module": "./customRenderers/fileUpload/fileUpload/dist/ui-customRenderers-fileUpload-fileUpload.esm.js",
+ "default": "./customRenderers/fileUpload/fileUpload/dist/ui-customRenderers-fileUpload-fileUpload.cjs.js"
+ },
+ "./tailwind.config.js": {
+ "module": "./tailwind.config.js",
+ "default": "./tailwind.config.js"
+ },
+ "./styles.css": {
+ "module": "./styles.css",
+ "default": "./styles.css"
+ }
+ },
"files": [
"dist",
- "styles.css"
+ "styles.css",
+ "alert",
+ "avatar",
+ "badge",
+ "button",
+ "calandar",
+ "card",
+ "checkbox",
+ "collapsible",
+ "dialog",
+ "dropdown-menu",
+ "form",
+ "hover-card",
+ "icon",
+ "index",
+ "input",
+ "label",
+ "popover",
+ "select",
+ "separator",
+ "tabs",
+ "textarea",
+ "toast",
+ "toaster",
+ "tooltip",
+ "use-toast",
+ "hooks",
+ "customRenderers"
],
"scripts": {
"type-check": "tsc"
@@ -56,7 +210,58 @@
"react": "^18.2.0",
"react-hook-form": "^7.46.1",
"tsconfig": "workspace:*",
- "typescript": "^4.9.4",
+ "typescript": "^5.3.3",
"zod": "^3.21.4"
+ },
+ "preconstruct": {
+ "exports": {
+ "extra": {
+ "./customRenderers/confidence": {
+ "module": "./customRenderers/confidence/confidence/dist/ui-customRenderers-confidence-confidence.esm.js",
+ "default": "./customRenderers/confidence/confidence/dist/ui-customRenderers-confidence-confidence.cjs.js"
+ },
+ "./customRenderers/fileUpload": {
+ "module": "./customRenderers/fileUpload/fileUpload/dist/ui-customRenderers-fileUpload-fileUpload.esm.js",
+ "default": "./customRenderers/fileUpload/fileUpload/dist/ui-customRenderers-fileUpload-fileUpload.cjs.js"
+ },
+ "./tailwind.config.js": {
+ "module": "./tailwind.config.js",
+ "default": "./tailwind.config.js"
+ },
+ "./styles.css": {
+ "module": "./styles.css",
+ "default": "./styles.css"
+ }
+ }
+ },
+ "entrypoints": [
+ "alert.tsx",
+ "avatar.tsx",
+ "badge.tsx",
+ "button.tsx",
+ "calandar.tsx",
+ "card.tsx",
+ "checkbox.tsx",
+ "collapsible.tsx",
+ "dialog.tsx",
+ "dropdown-menu.tsx",
+ "form.tsx",
+ "hover-card.tsx",
+ "icon.tsx",
+ "input.tsx",
+ "label.tsx",
+ "popover.tsx",
+ "select.tsx",
+ "separator.tsx",
+ "tabs.tsx",
+ "textarea.tsx",
+ "toast.tsx",
+ "toaster.tsx",
+ "tooltip.tsx",
+ "use-toast.tsx",
+ "hooks/index.ts",
+ "customRenderers/confidence/confidence.tsx",
+ "customRenderers/fileUpload/fileUpload.tsx"
+ ]
}
}
diff --git a/packages/ui/popover/package.json b/packages/ui/popover/package.json
new file mode 100644
index 000000000..6d810b0e6
--- /dev/null
+++ b/packages/ui/popover/package.json
@@ -0,0 +1,4 @@
+{
+ "main": "dist/ui-popover.cjs.js",
+ "module": "dist/ui-popover.esm.js"
+}
diff --git a/packages/ui/select/package.json b/packages/ui/select/package.json
new file mode 100644
index 000000000..e3370ae0e
--- /dev/null
+++ b/packages/ui/select/package.json
@@ -0,0 +1,4 @@
+{
+ "main": "dist/ui-select.cjs.js",
+ "module": "dist/ui-select.esm.js"
+}
diff --git a/packages/ui/separator/package.json b/packages/ui/separator/package.json
new file mode 100644
index 000000000..f244fc904
--- /dev/null
+++ b/packages/ui/separator/package.json
@@ -0,0 +1,4 @@
+{
+ "main": "dist/ui-separator.cjs.js",
+ "module": "dist/ui-separator.esm.js"
+}
diff --git a/packages/ui/src/index.tsx b/packages/ui/src/index.tsx
deleted file mode 100644
index 2c851a98b..000000000
--- a/packages/ui/src/index.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-/* util/src/index.tsx */
-
-/* Components */
-export * as Icon from "./icon";
-export * from "./alert";
-export * from "./avatar";
-export * from "./badge";
-export * from "./button";
-export * from "./card";
-export * from "./checkbox";
-export * from "./collapsible";
-export * from "./checkbox";
-export * from "./dialog";
-export * from "./dropdown-menu";
-export * from "./form";
-export * from "./hover-card";
-export * from "./input";
-export * from "./label";
-export * from "./popover";
-export * from "./select";
-export * from "./separator";
-export * from "./tabs";
-export * from "./textarea";
-export * from "./toast";
-export * from "./toaster";
-export * from "./tooltip";
-export * from "./use-toast";
-
-/* Renderers */
-export * from "./customRenderers/confidence/confidence";
-export * from "./customRenderers/fileUpload/fileUpload";
-
-/* Hooks */
-export * from "./hooks";
diff --git a/packages/ui/tabs/package.json b/packages/ui/tabs/package.json
new file mode 100644
index 000000000..27b89bdf0
--- /dev/null
+++ b/packages/ui/tabs/package.json
@@ -0,0 +1,4 @@
+{
+ "main": "dist/ui-tabs.cjs.js",
+ "module": "dist/ui-tabs.esm.js"
+}
diff --git a/packages/ui/textarea/package.json b/packages/ui/textarea/package.json
new file mode 100644
index 000000000..965ab6525
--- /dev/null
+++ b/packages/ui/textarea/package.json
@@ -0,0 +1,4 @@
+{
+ "main": "dist/ui-textarea.cjs.js",
+ "module": "dist/ui-textarea.esm.js"
+}
diff --git a/packages/ui/toast/package.json b/packages/ui/toast/package.json
new file mode 100644
index 000000000..56c4eb416
--- /dev/null
+++ b/packages/ui/toast/package.json
@@ -0,0 +1,4 @@
+{
+ "main": "dist/ui-toast.cjs.js",
+ "module": "dist/ui-toast.esm.js"
+}
diff --git a/packages/ui/toaster/package.json b/packages/ui/toaster/package.json
new file mode 100644
index 000000000..b010e9efa
--- /dev/null
+++ b/packages/ui/toaster/package.json
@@ -0,0 +1,4 @@
+{
+ "main": "dist/ui-toaster.cjs.js",
+ "module": "dist/ui-toaster.esm.js"
+}
diff --git a/packages/ui/tooltip/package.json b/packages/ui/tooltip/package.json
new file mode 100644
index 000000000..c9dd2a863
--- /dev/null
+++ b/packages/ui/tooltip/package.json
@@ -0,0 +1,4 @@
+{
+ "main": "dist/ui-tooltip.cjs.js",
+ "module": "dist/ui-tooltip.esm.js"
+}
diff --git a/packages/ui/use-toast/package.json b/packages/ui/use-toast/package.json
new file mode 100644
index 000000000..ce5a012ae
--- /dev/null
+++ b/packages/ui/use-toast/package.json
@@ -0,0 +1,4 @@
+{
+ "main": "dist/ui-use-toast.cjs.js",
+ "module": "dist/ui-use-toast.esm.js"
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 3134f8a0b..184315e97 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -24,7 +24,7 @@ importers:
specifier: ^2.26.2
version: 2.26.2
'@preconstruct/cli':
- specifier: ^2.8.1
+ specifier: ^2.8.3
version: 2.8.3
husky:
specifier: ^8.0.3
@@ -564,8 +564,8 @@ importers:
specifier: workspace:*
version: link:../../config/tsconfig
typescript:
- specifier: ^4.9.4
- version: 4.9.4
+ specifier: ^5.3.3
+ version: 5.3.3
zod:
specifier: ^3.21.4
version: 3.21.4
@@ -10956,6 +10956,12 @@ packages:
engines: {node: '>=14.17'}
hasBin: true
+ /typescript@5.3.3:
+ resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+ dev: true
+
/unbox-primitive@1.0.2:
resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
dependencies: