Skip to content

Commit

Permalink
feat(pano): add create post form with modal route ux (#638)
Browse files Browse the repository at this point in the history
Add modal based UX for pano post creation. Uses [parallel routes] and
"[intercepting routes]" patterns from next.js to render a modal if we go
to `/post/create` route from application using a link, and render the
form directly if user lands on that route directly by either clicking to
that link from somewhere else, or reloading at that route.

_(an example implementation from next.js:
https://github.com/vercel-labs/nextgram)_


https://github.com/kamp-us/monorepo/assets/1783869/e56072c3-a938-4cc6-b228-bee5d11d9349

### Checklist

- [ ] discord username: `umut`
- [ ] Closes #613 
- [ ] A descriptive and understandable title: The PR title should
clearly describe the nature and purpose of the changes. The PR title
should be the first thing displayed when the PR is opened. And it should
follow the semantic commit rules, and should include the
app/package/service name in the title. For example, a title like
"docs(@kampus-apps/pano): Add README.md" can be used.
- [ ] Related file selection: Only relevant files should be touched and
no other files should be affected.
- [ ] I ran `npx turbo run` at the root of the repository, and build was
successful.
- [ ] I installed the npm packages using `npm install --save-exact
<package>` so my package is pinned to a specific npm version. Leave
empty if no package was installed. Leave empty if no package was
installed with this PR.

[parallel routes]:
https://nextjs.org/docs/app/building-your-application/routing/parallel-routes
[intercepting routes]:
https://nextjs.org/docs/app/building-your-application/routing/intercepting-routes
  • Loading branch information
usirin authored Aug 17, 2023
1 parent c68a735 commit c48b731
Show file tree
Hide file tree
Showing 19 changed files with 774 additions and 151 deletions.
19 changes: 19 additions & 0 deletions apps/kampus/app/pano/@modal/(.)post/create/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"use client";

import { useRouter } from "next/navigation";

import { Dialog, DialogContent } from "@kampus/ui";

import { CreatePanoPostForm } from "~/app/pano/CreatePanoPostForm";

export default function CreatePost({ searchParams }: { searchParams: { conn: string } }) {
const router = useRouter();

return (
<Dialog open onOpenChange={() => router.back()}>
<DialogContent className="sm:max-w-[425px]">
<CreatePanoPostForm connectionID={searchParams.conn} onCompleted={() => router.back()} />
</DialogContent>
</Dialog>
);
}
3 changes: 3 additions & 0 deletions apps/kampus/app/pano/@modal/(.)post/default.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Default() {
return null;
}
3 changes: 3 additions & 0 deletions apps/kampus/app/pano/@modal/default.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Default() {
return null;
}
143 changes: 143 additions & 0 deletions apps/kampus/app/pano/CreatePanoPostForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
"use client";

import { graphql, useMutation } from "react-relay";
import { z } from "zod";

import {
Button,
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
Input,
Textarea,
useForm,
} from "@kampus/ui";

const mutation = graphql`
mutation CreatePanoPostFormMutation(
$title: String!
$content: String
$url: String
$connections: [ID!]!
) {
createPanoPost(input: { url: $url, title: $title, content: $content }) {
edge @prependEdge(connections: $connections) {
cursor
node {
...PostItem_post
}
}
error {
... on UserError {
message
}
}
}
}
`;

const formSchema = z.object({
title: z.string().min(5, { message: "Başlık en az 5 karakterden oluşmalıdır" }),
url: z.string().url({ message: "URL duzgun degil" }),
content: z.string().optional(),
});

type FormSchema = z.infer<typeof formSchema>;

interface Props {
connectionID?: string;
onCompleted?: () => void;
}

export function CreatePanoPostForm(props: Props) {
const [commit, isInFlight] = useMutation(mutation);

const form = useForm(formSchema, {
defaultValues: {
title: "",
url: "",
content: "",
},
});

const onSubmit = (values: FormSchema) => {
commit({
variables: {
title: values.title,
url: values.url,
content: values.content,
connections: [props.connectionID].filter(Boolean),
},
onError: (error) => {
console.error(error);
},
onCompleted: () => {
props.onCompleted?.();
},
});
};

const onCancel = () => {
props.onCompleted?.();
};

return (
<Form {...form}>
{/* eslint-disable-next-line @typescript-eslint/no-misused-promises */}
<form className="flex flex-col gap-2" onSubmit={form.handleSubmit(onSubmit)}>
<FormField
control={form.control}
name="url"
render={(props) => (
<FormItem>
<FormLabel>URL</FormLabel>
<FormControl>
<Input placeholder="https://ornek.com" {...props.field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="title"
render={(props) => (
<FormItem>
<FormLabel>Başlık</FormLabel>
<FormControl>
<Input placeholder="Ornek baslik" {...props.field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="content"
render={(props) => (
<FormItem>
<FormLabel>Icerik</FormLabel>
<FormControl>
<Textarea placeholder="Ornek hakkindaki dusuncelerim .." {...props.field} />
</FormControl>
<FormDescription>Eklemek istediklerini ekleyebilirsin ..</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<div className="flex justify-end gap-1.5">
<Button onClick={onCancel} variant="outline" type="reset">
Iptal
</Button>
<Button type="submit" disabled={isInFlight}>
Gonder
</Button>
</div>
</form>
</Form>
);
}
78 changes: 40 additions & 38 deletions apps/kampus/app/pano/PanoFeed.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Suspense, useCallback } from "react";
import { Suspense } from "react";
import Link from "next/link";
import { graphql, useFragment, usePaginationFragment } from "react-relay";

import { Button } from "@kampus/ui";
Expand Down Expand Up @@ -50,45 +51,46 @@ export function PanoFeed(props: Props) {

const feed = data.panoFeed;

const loadPrevPage = useCallback(() => {
if (hasPrevious) {
loadPrevious(10);
}
}, [hasPrevious, loadPrevious]);

const loadNextPage = useCallback(() => {
if (hasNext) {
loadNext(10);
}
}, [hasNext, loadNext]);

return (
<Suspense fallback="loading">
<section className="flex flex-col gap-4">
{feed?.edges?.map((edge) => {
if (!edge?.node) {
return null;
}
<>
<Button variant="outline" asChild>
<Link
href={{
pathname: `/post/create`,
search: `foo=bar`,
}}
as="post/create"
>
New post
</Link>
</Button>
<Suspense fallback="loading">
<section className="flex flex-col gap-4">
{feed?.edges?.map((edge) => {
if (!edge?.node) {
return null;
}

return (
<PostItem
key={edge.node.id}
post={edge.node}
viewerRef={viewer}
postConnectionId={data.panoFeed?.__id}
/>
);
})}
return (
<PostItem
key={edge.node.id}
post={edge.node}
viewerRef={viewer}
postConnectionId={data.panoFeed?.__id}
/>
);
})}

<div className="flex gap-2">
<Button variant="secondary" onClick={loadPrevPage} disabled={!hasPrevious}>
{"< Prev"}
</Button>
<Button variant="secondary" onClick={loadNextPage} disabled={!hasNext}>
{"Next >"}
</Button>
</div>
</section>
</Suspense>
<div className="flex gap-2">
<Button variant="secondary" onClick={() => loadPrevious(10)} disabled={!hasPrevious}>
{"< Prev"}
</Button>
<Button variant="secondary" onClick={() => loadNext(10)} disabled={!hasNext}>
{"Next >"}
</Button>
</div>
</section>
</Suspense>
</>
);
}
Loading

0 comments on commit c48b731

Please sign in to comment.