Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Related pubs in form #910

Merged
merged 25 commits into from
Feb 5, 2025
Merged

Related pubs in form #910

merged 25 commits into from
Feb 5, 2025

Conversation

allisonking
Copy link
Contributor

@allisonking allisonking commented Jan 21, 2025

Issue(s) Resolved

Most of #791

High-level Explanation of PR

Adds related pub configuration to the external fill form.

Test Plan

  1. Add a relationship field with any core schema type. I haven't tested them all yet, but I know that String, StringArray, Null, and Email all work. And I know that MemberSelect does NOT currently work.
  2. Add this relationship field to a form via the form builder
  3. You should be able to configure both the relationship and the value
  4. Now go to the external fill page for this form. It should display your 'pub relation' fields

image

  1. You should be able to link other existing pubs

image

  1. And also configure the 'value' for the relationship
    image
    image

  2. This should work for more complex fields like StringArrays too and include validation on the 'value' field
    image
    image

  3. Save the form. Visiting the form again should load your values (you can visit again by visiting the url http://localhost:3000/c/{communitySlug}/public/forms/{formSlug}/fill?pubId={pubId})

  4. You can also visit the pub page for this created pub and see your related pubs
    image

Screenshots (if applicable)

Notes

Known issues:

  • MemberSelect doesn't work as related pub value

@allisonking allisonking force-pushed the aking/791/related-pubs-in-form branch from 0648f6c to 3751834 Compare January 21, 2025 21:10
@@ -175,6 +176,18 @@ export const FormElement = ({
);
}

if (element.isRelation) {
const valueComponent = input;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a small hope this would Just Work, but it doesn't :) We want to render an "wrapper" component and an "inner" component
image

The 'wrapper' component is the 'Pub Relations' box, and the 'inner' component is the popover 'Role' in this screenshot. Without taking any of the changes in this PR into account, FormElement would return the contents of 'Role', which is what we want, just in the popover instead, so I stashed that as valueComponent here. Then render the 'wrapper' as RelatedPubsElement.

This doesn't actually work though because the value of the form is going to be an array now—something like {'croccroc:author': [ {value: 'admin', relatedPubId: 'xxx'} ]} so currently we end up rendering
image.

The only way I can think of to get this to work is to render a separate component inside RelatedPubsElement based on the CoreSchemaType that is a lot like FormElement.tsx but doesn't make all the same assumptions, and might not use i.e. TextInputElement.tsx since that tries to get the field value directly when really we need to index into the array.

maybe I'm missing a simpler way to do this though??

@allisonking allisonking force-pushed the aking/791/related-pubs-in-form branch from 7a509bb to 1995b69 Compare January 23, 2025 16:44
@@ -95,49 +96,12 @@ const elementPanelTitles: Record<PanelState["state"], string> = {
editingButton: "Edit Submission Button",
};

const PanelHeader = ({ state }: { state: PanelState["state"] }) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved all the side panel stuff to another component so I could reuse it in the form fill page. I think this would be better off using a drawer or maybe even the new side bar component from shadcn, but for now I reused what we had to keep things consistent

@@ -1,48 +1,24 @@
import { defaultComponent } from "schemas";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored this to be a wrapper around PubFieldFormElement which now takes a lot of the inside of this function. This is because I needed to reuse the parts in PubFieldFormElement to render the inner value component:

image

@@ -0,0 +1,172 @@
import { defaultComponent } from "schemas";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pretty much directly pulled from FormElement.tsx

(pv) => pv.relatedPubId
);

if (pubValuesWithRelations.length) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this might result in an inconsistent return—should I update replacePubRelationsBySlug to also return something like what the .insertInto("pub_values") below does and then combine them?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think that if this works then just leave it, i'm currently rewriting a bunch of this anyway!

body = error[0]?.message;
const firstErrorIndex = error.findIndex((e) => !!e);
const firstError = error[firstErrorIndex];
body = firstError?.message ?? `Error with value at index ${firstErrorIndex}`;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this isn't a great error message, but I wasn't sure a better way to get something to show up when there is an error with a value in one of the array fields. This shows up as
image
when the 'value' field is empty

Comment on lines -64 to +72
demoComponent: ({ element }) => (
<Input
placeholder={
element.schemaName === CoreSchemaType.String ? "For short text" : "For numbers"
}
type={element.schemaName === CoreSchemaType.String ? "text" : "number"}
/>
),
demoComponent: ({ element }) => {
const isNumber = element.schemaName === CoreSchemaType.Number;
return (
<Input
placeholder={isNumber ? "For numbers" : "For short text"}
type={isNumber ? "number" : "text"}
/>
);
},
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

email and url were showing up as number input components in the demo portion

@allisonking
Copy link
Contributor Author

This is ready for review but MemberSelect doesn't work—if chosen, there'll be an error that I haven't put a lot of time into investigating yet, but I suspect it may be thorny! So I'm thinking I'll split that out into a separate chunk of work.

@allisonking allisonking marked this pull request as ready for review February 3, 2025 15:06
Copy link
Member

@tefkah tefkah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really really nice, great work! I left some suggestions about slightly nicer error displays for the values and using pubtitles in a bunch of places, if you incorporate those im happy to merge it!

return (
<div className="flex items-center gap-2">
<span>{row.original.title || row.original.id}</span>
</div>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should use the PubTitle here, rather than the id as fallback. Is somewhat clearer, and I don't think we want to show external users these ids

Suggested change
</div>
<span>{getPubTitle(row.original)}</span>


import type { GetPubsResult } from "~/lib/server";
import { PanelHeader, SidePanel } from "~/app/components/SidePanel";
import { DataTable } from "../DataTable/v2/DataTable";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see below

Suggested change
import { DataTable } from "../DataTable/v2/DataTable";
import { getPubTitle } from "~/lib/pubs";

Comment on lines 70 to 79
const configLabel = "label" in element.config ? element.config.label : undefined;
const label = configLabel || element.label || slug;

const { watch } = useFormContext();
const [isPopoverOpen, setPopoverIsOpen] = useState(false);
const value = watch(slug);
const showValue = value != null && value !== "" && !isPopoverOpen;

if (element.component === null) {
return null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this way we have slightly better error reporting

Suggested change
const configLabel = "label" in element.config ? element.config.label : undefined;
const label = configLabel || element.label || slug;
const { watch } = useFormContext();
const [isPopoverOpen, setPopoverIsOpen] = useState(false);
const value = watch(slug);
const showValue = value != null && value !== "" && !isPopoverOpen;
if (element.component === null) {
return null;
}: PubFieldFormElementProps & { slug: RelatedPubValueSlug }) => {
const configLabel = "label" in element.config ? element.config.label : undefined;
const label = configLabel || element.label || slug;
const { watch, formState } = useFormContext<FormValue>();
const [isPopoverOpen, setPopoverIsOpen] = useState(false);
const value = watch(slug);
const showValue = value != null && value !== "" && !isPopoverOpen;
const [baseSlug, index] = slug.split(".");
const valueError = formState.errors[baseSlug]?.[parseInt(index)]?.value;
if (element.component === null) {
return null;

Comment on lines 15 to 36
import { MultiBlock } from "ui/multiblock";
import { Popover, PopoverContent, PopoverTrigger } from "ui/popover";

import type { PubFieldFormElementProps } from "../PubFieldFormElement";
import type { ElementProps } from "../types";
import type { GetPubsResult } from "~/lib/server";
import { AddRelatedPubsPanel } from "~/app/components/forms/AddRelatedPubsPanel";
import { useContextEditorContext } from "../../ContextEditor/ContextEditorContext";
import { useFormElementToggleContext } from "../FormElementToggleContext";
import { PubFieldFormElement } from "../PubFieldFormElement";

const RelatedPubBlock = ({
pub,
onRemove,
valueComponentProps,
slug,
}: {
pub: Pick<GetPubsResult[number], "title" | "id">;
onRemove: () => void;
valueComponentProps: PubFieldFormElementProps;
slug: string;
}) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

necessities for the error reporting down below

Suggested change
import { MultiBlock } from "ui/multiblock";
import { Popover, PopoverContent, PopoverTrigger } from "ui/popover";
import type { PubFieldFormElementProps } from "../PubFieldFormElement";
import type { ElementProps } from "../types";
import type { GetPubsResult } from "~/lib/server";
import { AddRelatedPubsPanel } from "~/app/components/forms/AddRelatedPubsPanel";
import { useContextEditorContext } from "../../ContextEditor/ContextEditorContext";
import { useFormElementToggleContext } from "../FormElementToggleContext";
import { PubFieldFormElement } from "../PubFieldFormElement";
const RelatedPubBlock = ({
pub,
onRemove,
valueComponentProps,
slug,
}: {
pub: Pick<GetPubsResult[number], "title" | "id">;
onRemove: () => void;
valueComponentProps: PubFieldFormElementProps;
slug: string;
}) => {
import { Plus, Trash, TriangleAlert } from "ui/icon";
import { MultiBlock } from "ui/multiblock";
import { Popover, PopoverContent, PopoverTrigger } from "ui/popover";
import { cn } from "utils";
import type { PubFieldFormElementProps } from "../PubFieldFormElement";
import type { ElementProps } from "../types";
import type { GetPubsResult } from "~/lib/server";
import { AddRelatedPubsPanel } from "~/app/components/forms/AddRelatedPubsPanel";
import { getPubTitle } from "~/lib/pubs";
import { useContextEditorContext } from "../../ContextEditor/ContextEditorContext";
import { useFormElementToggleContext } from "../FormElementToggleContext";
import { PubFieldFormElement } from "../PubFieldFormElement";
type RelatedPubValueSlug = `${string}.${number}.value`;
const RelatedPubBlock = ({
pub,
onRemove,
valueComponentProps,
slug,
}: {
pub: Pick<GetPubsResult[number], "title" | "id" | "createdAt" | "pubType" | "values">;
onRemove: () => void;
valueComponentProps: PubFieldFormElementProps;
slug: RelatedPubValueSlug;

remove(index);
};
const innerSlug = `${slug}.${index}.value`;
return (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

final part for the error reporting above

Suggested change
return (
const innerSlug =
`${slug}.${index}.value` as const;

<div className="flex items-center justify-between rounded border border-l-[12px] border-l-emerald-100 p-3">
<div className="flex flex-col items-start gap-1 text-sm">
<span className="font-semibold">{title || id}</span>
<ConfigureRelatedValue {...valueComponentProps} slug={slug} />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should use the pubtitle here as well

Suggested change
<ConfigureRelatedValue {...valueComponentProps} slug={slug} />
<span className="font-semibold">{getPubTitle(pub)}</span>

(pv) => pv.relatedPubId
);

if (pubValuesWithRelations.length) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think that if this works then just leave it, i'm currently rewriting a bunch of this anyway!

@allisonking
Copy link
Contributor Author

thanks for the great improvements @tefkah ! going to merge this now, but flagging that not all of the value inputs work yet, especially member select. but those should be fixed by #926 and #924

@allisonking allisonking merged commit ecc17fa into main Feb 5, 2025
6 checks passed
@allisonking allisonking deleted the aking/791/related-pubs-in-form branch February 5, 2025 22:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants