Skip to content

Add form switcher #1160

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

Open
wants to merge 28 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
824ba51
Add form switcher
kalilsn Apr 15, 2025
00807da
Update capabilities
kalilsn Apr 16, 2025
7160549
Use membership formIds to calculate permissions and list of forms in …
kalilsn Apr 21, 2025
bc44327
Restrict related pubs table by selected form
kalilsn Apr 21, 2025
4995854
Fix bugs and add form switcher to create page
kalilsn Apr 21, 2025
3e09375
Remove unused component
kalilsn Apr 21, 2025
c7b2919
Validate that updated fields are present in the form when creating/up…
kalilsn Apr 22, 2025
810bff2
Fix relation overwrite bug
kalilsn Apr 22, 2025
05289b7
Add form slug to pub update/create calls in tests
kalilsn Apr 22, 2025
b17ba4f
Add label for form switcher to edit form
kalilsn Apr 22, 2025
48c237f
Remove old duplicate authorization check
kalilsn Apr 23, 2025
723bf7b
Fix getAuthorizedUpdateForms query to select editPubWithForm capabili…
kalilsn Apr 23, 2025
2aa371a
Use new capabilties functions on external form pages
kalilsn Apr 23, 2025
89de7c9
Remove unnecessary admin check
kalilsn Apr 23, 2025
d352258
Always use highest role in non-usercan permissions checks
kalilsn Apr 23, 2025
e5b5b13
Try waiting for a second after saving form
kalilsn Apr 23, 2025
94fb175
Wait longer
kalilsn Apr 23, 2025
a9a6bcd
Merge branch 'main' into kalilsn/form-switcher
kalilsn May 6, 2025
e10c343
Fix tests
kalilsn May 6, 2025
23e5c0a
Remove timeout
kalilsn May 6, 2025
804498e
Merge branch 'main' into kalilsn/form-switcher
kalilsn May 7, 2025
607b6ab
Require form and pub pub_type agreement in pub memberships
kalilsn May 7, 2025
cbc4cdc
Add superadmin override to usercan
kalilsn May 12, 2025
070d4b6
Merge branch 'main' into kalilsn/form-switcher
kalilsn May 12, 2025
83dd0ba
Fix some more pubtype/form agreement issues in tests
kalilsn May 12, 2025
c53c390
Factor out member dialog fixture
kalilsn May 12, 2025
9e4205f
Fix mock so usercan doesn't error in db tests
kalilsn May 12, 2025
f24140d
Fix logged in checks
kalilsn May 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions core/app/api/v0/c/[communitySlug]/site/[...ts-rest]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ const handler = createNextHandler(
create: async ({ body }) => {
const { authorization, community, lastModifiedBy } = await checkAuthorization({
token: { scope: ApiAccessScope.pub, type: ApiAccessType.write },
// TODO: figure out capability here
// TODO: refactor so we call userCanCreatePub here
cookies: false,
});

Expand Down Expand Up @@ -423,10 +423,8 @@ const handler = createNextHandler(
update: async ({ params, body }) => {
const { user, community, lastModifiedBy } = await checkAuthorization({
token: { scope: ApiAccessScope.pub, type: ApiAccessType.write },
cookies: {
capability: Capabilities.updatePubValues,
target: { type: MembershipType.pub, pubId: params.pubId as PubsId },
},
// TODO: refactor so we call userCanEditPub here
cookies: false,
});

const { exists } = await doesPubExist(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ export const RequestLink = ({
}: {
formSlug: string;
token: string;
pubId: PubsId;
pubId?: PubsId;
}) => {
const useRequestLink = useServerAction(actions.inviteUserToForm);
const sendNewFormLink = useServerAction(actions.sendNewFormLink);
const { id: communityId } = useCommunity();

const requestLink = useCallback(async () => {
const link = await useRequestLink({ slug: formSlug, token, pubId, communityId });
const link = await sendNewFormLink({ slug: formSlug, token, pubId, communityId });

if (link && link.error) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ import { SaveStatus } from "~/app/components/pubs/PubEditor/SaveStatus";
import { db } from "~/kysely/database";
import { getLoginData } from "~/lib/authentication/loginData";
import { getCommunityRole } from "~/lib/authentication/roles";
import { userCanCreatePub, userCanEditPub } from "~/lib/authorization/capabilities";
import { findCommunityBySlug } from "~/lib/server/community";
import { getForm, userHasPermissionToForm } from "~/lib/server/form";
import { getForm } from "~/lib/server/form";
import { getPubsWithRelatedValues } from "~/lib/server/pub";
import { getPubTypesForCommunity } from "~/lib/server/pubtype";
import { capitalize } from "~/lib/string";
import { ExternalFormWrapper } from "./ExternalFormWrapper";
import { RequestLink } from "./RequestLink";
import { handleFormToken } from "./utils";

const NotFound = ({ children }: { children: ReactNode }) => {
return <div className="w-full pt-8 text-center">{children}</div>;
Expand Down Expand Up @@ -163,6 +163,10 @@ export default async function FormPage(props: {
return <NotFound>No form found</NotFound>;
}

if (searchParams.pubId && !pub) {
return <NotFound>Pub not found</NotFound>;
}

if (!user && !session) {
if (form.access === "public") {
// redirect user to signup/login
Expand All @@ -171,14 +175,6 @@ export default async function FormPage(props: {
);
}

const result = await handleFormToken({
params,
searchParams,
onExpired: ({ params, searchParams, result }) => {
return;
},
});

return (
Comment on lines 176 to 178
Copy link
Member

Choose a reason for hiding this comment

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

nice that we can get rid of this logic, i totally forgot it was there! good that we can just rely on the magic link/invite functionality now rather than this weird edge case

<ExpiredTokenPage
params={params}
Expand All @@ -203,11 +199,19 @@ export default async function FormPage(props: {

// all other roles always have access to the form
if (role === MemberRole.contributor && form.access !== FormAccessType.public) {
const memberHasAccessToForm = await userHasPermissionToForm({
formSlug: params.formSlug,
userId: user.id,
pubId: pub?.id,
});
const memberHasAccessToForm = pub
? await userCanEditPub({
formSlug: params.formSlug,
userId: user.id,
pubId: pub.id,
})
: await userCanCreatePub({
userId: user.id,
communityId: community.id,
// When the form is in create mode (there's no pub), just pass the form's pubType since
// the user doesn't control the type.
pubTypeId: form.pubTypeId,
});

if (!memberHasAccessToForm) {
// TODO: show no access page
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const updateForm = defineServerAction(async function updateForm({
formId: FormsId;
name: string;
}) {
const user = await getLoginData();
const { user } = await getLoginData();

if (!user) {
return {
Expand Down
2 changes: 1 addition & 1 deletion core/app/c/[communitySlug]/forms/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const createForm = defineServerAction(async function createForm(
slug: string,
communityId: CommunitiesId
) {
const user = await getLoginData();
const { user } = await getLoginData();

if (!user) {
return {
Expand Down
4 changes: 2 additions & 2 deletions core/app/c/[communitySlug]/members/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { getPageLoginData } from "~/lib/authentication/loginData";
import { userCan } from "~/lib/authorization/capabilities";
import { compareMemberRoles } from "~/lib/authorization/rolesRanking";
import { findCommunityBySlug } from "~/lib/server/community";
import { getMembershipForms } from "~/lib/server/form";
import { getSimpleForms } from "~/lib/server/form";
import { selectAllCommunityMemberships } from "~/lib/server/member";
import { addMember, createUserWithCommunityMembership } from "./actions";
import { MemberTable } from "./MemberTable";
Expand Down Expand Up @@ -54,7 +54,7 @@ export default async function Page(props: {
const page = parseInt(searchParams.page ?? "1", 10);
const [members, availableForms] = await Promise.all([
selectAllCommunityMemberships({ communityId: community.id }).execute(),
getMembershipForms(),
getSimpleForms(),
]);

if (!members.length && page !== 1) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,17 @@
import type { ColumnDef } from "@tanstack/react-table";
import type { ReactNode } from "react";

import React, { useMemo, useState } from "react";
import { useMemo, useState } from "react";
import Link from "next/link";
import { usePathname } from "next/navigation";

import type { ActionInstances, PubTypes, Stages } from "db/public";
import type { ProcessedPubWithForm } from "contracts";
import type { PubTypes, Stages } from "db/public";
import { Badge } from "ui/badge";
import { Button } from "ui/button";
import { Checkbox } from "ui/checkbox";
import { DataTableColumnHeader } from "ui/data-table";

import type { PageContext } from "~/app/components/ActionUI/PubsRunActionDropDownMenu";
import type { FullProcessedPub } from "~/lib/server/pub";
import { PubsRunActionDropDownMenu } from "~/app/components/ActionUI/PubsRunActionDropDownMenu";
import { DataTable } from "~/app/components/DataTable/DataTable";
import { dateFormatOptions } from "~/lib/dates";
import { getPubTitle } from "~/lib/pubs";
Expand Down Expand Up @@ -95,14 +93,29 @@ const getRelatedPubsColumns = (relatedPubActionsDropdowns: Record<string, ReactN
return relatedPubActionsDropdowns[row.original.id];
},
},
] as const satisfies ColumnDef<FullProcessedPub, unknown>[];
] as const satisfies ColumnDef<
ProcessedPubWithForm<{
withRelatedPubs: true;
withStage: true;
withPubType: true;
withMembers: true;
withStageActionInstances: true;
}>,
unknown
>[];
};

const Table = ({
pubs,
relatedPubActionsDropdowns,
}: {
pubs: FullProcessedPub[];
pubs: ProcessedPubWithForm<{
withRelatedPubs: true;
withStage: true;
withPubType: true;
withMembers: true;
withStageActionInstances: true;
}>[];
relatedPubActionsDropdowns: Record<string, ReactNode>;
}) => {
const columns = getRelatedPubsColumns(relatedPubActionsDropdowns);
Expand All @@ -114,11 +127,29 @@ export const RelatedPubsTable = ({
pub,
relatedPubActionsDropdowns,
}: {
pub: FullProcessedPub;
pub: ProcessedPubWithForm<{
withRelatedPubs: true;
withStage: true;
withPubType: true;
withMembers: true;
withStageActionInstances: true;
}>;
relatedPubActionsDropdowns: Record<string, ReactNode>;
}) => {
const groupedBySlug = useMemo(() => {
const grouped: Record<string, { pub: FullProcessedPub; fieldName: string }[]> = {};
const grouped: Record<
string,
{
pub: ProcessedPubWithForm<{
withRelatedPubs: true;
withStage: true;
withPubType: true;
withMembers: true;
withStageActionInstances: true;
}>;
fieldName: string;
}[]
> = {};
for (const value of pub.values) {
const { relatedPub, fieldSlug, fieldName } = value;
if (relatedPub) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { ProcessedPubWithForm } from "contracts";
import { Info } from "ui/icon";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "ui/tooltip";

import type { PageContext } from "~/app/components/ActionUI/PubsRunActionDropDownMenu";
import type { FullProcessedPub } from "~/lib/server";
import { PubsRunActionDropDownMenu } from "~/app/components/ActionUI/PubsRunActionDropDownMenu";
import { RelatedPubsTable } from "./RelatedPubsTable";

Expand All @@ -24,7 +24,16 @@ const NoActions = () => {
);
};

const getRelatedPubRunActionsDropdowns = (row: FullProcessedPub, pageContext: PageContext) => {
const getRelatedPubRunActionsDropdowns = (
row: ProcessedPubWithForm<{
withRelatedPubs: true;
withStage: true;
withPubType: true;
withMembers: true;
withStageActionInstances: true;
}>,
pageContext: PageContext
) => {
return row.stage && row.stage?.actionInstances.length > 0 ? (
<PubsRunActionDropDownMenu
actionInstances={row.stage.actionInstances}
Expand All @@ -38,7 +47,13 @@ const getRelatedPubRunActionsDropdowns = (row: FullProcessedPub, pageContext: Pa
};

type Props = {
pub: FullProcessedPub;
pub: ProcessedPubWithForm<{
withRelatedPubs: true;
withStage: true;
withPubType: true;
withMembers: true;
withStageActionInstances: true;
}>;
pageContext: PageContext;
};

Expand Down
Loading
Loading