t3-blog
taxonomy
t3-blog | taxonomy | |
---|---|---|
1 | 40 | |
9 | 17,727 | |
- | 2.5% | |
6.2 | 0.0 | |
4 months ago | 4 days ago | |
TypeScript | TypeScript | |
- | MIT License |
Stars - the number of stars that a project has on GitHub. Growth - month over month growth in stars.
Activity is a relative number indicating how actively a project is being developed. Recent commits have higher weight than older ones.
For example, an activity of 9.0 indicates that a project is amongst the top 10% of the most actively developed projects that we are tracking.
t3-blog
-
T3 stack with app router and supabase
// app/(main)/profile/new/page.tsx "use client"; import { Button } from "@/components/ui/button"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; import { useFieldArray, useForm } from "react-hook-form"; import { z } from "zod"; import { zodResolver } from "@hookform/resolvers/zod"; import { newProfileSchema } from "@/lib/validators/newProfile"; import { TrashIcon } from "@heroicons/react/24/outline"; import { useRouter } from "next/navigation"; export type NewProfileInput = z.infer; export default function NewProfileForm() { const form = useForm({ resolver: zodResolver(newProfileSchema), defaultValues: { firstName: "", lastName: "", role: undefined, skills: [{ name: "" }], bio: "", github: "", linkedin: "", website: "", }, }); const { fields, append, remove } = useFieldArray({ control: form.control, name: "skills", }); const watchSkills = form.watch("skills"); const router = useRouter(); const onSubmit = async (data: NewProfileInput) => { console.log(data); }; return ( Create Profile Yabba dabba doo ( First name )} /> ( Last name )} /> ( Role Full Stack Frontend Backend Design )} /> ( Skills {fields.map((field, index) => ( {fields.length > 1 && ( remove(index)} /> )} {form.formState.errors.skills?.[index]?.name && ( This can't be empty )} ))} !field.name)} onClick={() => append({ name: "" })} className="max-w-min" > Add Skill )} /> ( Bio )} />
( Github Username )} /> ( Linkedin Username )} />( Website )} /> Submit ); }Enter fullscreen mode Exit fullscreen modeContext and Private Procedure
Now we have the form but we need to push this data to the database when the user submits it. To do that we need to create a new trpc procedure. Before we actually write the procedure there are a few things we need to add.
The first step is to add the userId to the trpc context. This will make things a lot quicker because it means we don't have to pass the userId from the client. For example if we want to get the current user's posts, instead of passing the current user id to the server, we can just call the function with no props and it already knows who the user is.
Navigate to the
server/api/trpc.ts
file and import the supabase client:
// server/api/trpc.ts ... import { createClient } from "@/utils/supabase/server"; import { cookies } from "next/headers"; ...
Enter fullscreen mode Exit fullscreen modeNow inside the
createTRPCContext
function we need to pass the userId to the context like this:
// server/api/trpc.ts ... export const createTRPCContext = async (opts: { headers: Headers }) => { const supabase = createClient(cookies()); const { data: { user }, } = await supabase.auth.getUser(); return { user, db, ...opts, }; }; ...
Enter fullscreen mode Exit fullscreen modeThe next step is to create a new type of procedure called private procedure, which is a procedure that can only be called by authenticated users. This is helpful because it means that we know there is a userId for our queries. If we were to use a public procedure we would have to assert that we know the user is authenticated and handle that on the client which is a lot more complicated. To do this add the following code at the bottom of the file:
// server/api/trpc.ts ... const enforceUserIsAuthed = t.middleware(async ({ ctx, next }) => { if (!ctx.user) { throw new TRPCError({ code: "UNAUTHORIZED", }); } return next({ ctx: { user: ctx.user, }, }); }); export const privateProcedure = t.procedure.use(enforceUserIsAuthed); ...
Enter fullscreen mode Exit fullscreen modeProcedure
Now we can get started writing the procedure. Go to
server/api/routers
and you should see aposts.ts
file. Delete that file and create a new one calledprofiles.ts
. Inside that file add the following code:
// server/api/routers/profiles.ts import { z } from "zod"; import { createTRPCRouter, privateProcedure } from "@/server/api/trpc"; import { RoleType } from "@prisma/client"; export const profileRouter = createTRPCRouter({ create: privateProcedure .input( z.object({ firstName: z.string(), lastName: z.string(), role: z.nativeEnum(RoleType), skills: z.array(z.string()), bio: z.string(), github: z.string(), linkedin: z.string(), website: z.union([z.literal(""), z.string().trim().url()]), }), ) .mutation(async ({ ctx, input }) => { const user = await ctx.db.profile.create({ data: { id: ctx.user.id, email: ctx.user.email!, firstName: input.firstName, lastName: input.lastName, role: input.role, skills: input.skills, bio: input.bio, github: input.github, linkedin: input.linkedin, website: input.website, }, }); return user; }), });
Enter fullscreen mode Exit fullscreen modeNow we need to expose this router to the client. Go to
server/api/root.ts
and add the following code:
// server/api/root.ts import { profileRouter } from "@/server/api/routers/profiles"; import { createTRPCRouter } from "@/server/api/trpc"; export const appRouter = createTRPCRouter({ profiles: profileRouter, }); export type AppRouter = typeof appRouter;
Enter fullscreen mode Exit fullscreen modeNow we need to call the mutation on submit on form submission. Before we do that though, lets create a utility function to capitalize just the first letter of the string, so all of the names are uniform. In
lib/utils.ts
add the following function:
// lib/utils.ts ... export function capitalizeFirstLetter(inputString: string) { const lowercaseString = inputString.toLowerCase(); const capitalizedString = lowercaseString.charAt(0).toUpperCase() + lowercaseString.slice(1); return capitalizedString; } ...
Enter fullscreen mode Exit fullscreen modeNow we can use our mutation. Add the following code to
app/(main)/profile/new/page.tsx
:
// app/(main)/profile/new/page.tsx const router = useRouter(); const { mutate } = api.profiles.create.useMutation({ onSuccess: () => { router.push("/profile"); }, onError: (e) => { const errorMessage = e.data?.zodError?.fieldErrors.content; console.error("Error creating investment:", errorMessage); }, }); const onSubmit = async (data: NewProfileInput) => { const skillsList = data.skills.map((skill) => skill.name); mutate({ firstName: capitalizeFirstLetter(data.firstName), lastName: capitalizeFirstLetter(data.lastName), role: data.role, skills: skillsList, bio: data.bio, github: data.github, linkedin: data.linkedin, website: data.website, }); };
Enter fullscreen mode Exit fullscreen modeNow when you submit the form you should be redirected to a profile page and when you check the table in supabase the new data should be there. Now that we have the data, let's make it so we can view it.
Querying data
Go back to
server/api/routers/profiles.ts
and add the following query below your mutation:
// server/api/routers/profiles.ts ... getCurrent: privateProcedure.query(async ({ ctx }) => { const profile = await ctx.db.profile.findUnique({ where: { id: ctx.user.id! }, }); return profile; }), ...
Enter fullscreen mode Exit fullscreen modeCreate a new page in the profile folder and add the following code:
// app/(main)/profile/page.tsx import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter, } from "@/components/ui/card"; import { capitalizeFirstLetter } from "@/lib/utils"; import { api } from "@/trpc/server"; export default async function ProfilePage() { const profile = await api.profiles.getCurrent.query(); if (!profile) return null; return (
{profile.firstName} {profile.firstName} {profile.email}); }Role: {capitalizeFirstLetter(profile.role)}
Skills:
{profile.skills.map((skill, index) => ({skill}
))}Bio: {profile.bio}
Github: {profile.github}
Linkedin: {profile.linkedin}
Website: {profile.website}
Enter fullscreen mode Exit fullscreen modeThis isn't styled very well, its just supposed to show how you can fetch the data on the server side with trpc. Notice that we are importing the api from the server folder not the react folder. This means we cant use hooks like usequery and usemutation because we are in a server component.
Conclusion
This app doesn't really do anything, it is just supposed to get you started where everything is configured properly to design a cool web application. You can add to your prisma schema and start doing more complicated queries and mutations with the same principles used for just a simple profile.
All of the code for this project will be at this repo.
Thanks for reading!
P.S. If you are a software developer in college shoot me an email at [email protected]. I am going to start building a website for college students to find other developers in their area to build projects together. If you want to help me build it or use it or provide feedback or whatever I would love to chat.
taxonomy
-
T3 stack with app router and supabase
I am building this app with inspiration from Taxonomy and Acme corp so a lot of the design comes from there.
-
5 Github Repositories To Master Next.js 😎
View on GitHub
-
Navigating Uncharted Waters: Building the MVP and Finding the Right Audience (Part 2)
Now, I must confess that I'm primarily a back-end engineer, and crafting a beautiful front-end was, well, a bit of an adventure. To keep things straightforward, I opted for simplicity. I figured I could enhance it as my customer base grew, as the real value, of course, lies in the robustness of the API and its backend. Looking back, I might consider using a template like https://tx.shadcn.com/ if I were to start all over again. It appears to be a great starting point for any SaaS, but the setup might pose some challenges.
-
Nextjs beginner
Check out these two GitHub repositories: https://github.com/sadmann7/skateshop/ and https://github.com/shadcn/taxonomy/. They both use Next.js 13 and have well-structured folders and well-written code. Next.js provides performance optimization. To improve it further, avoid loading unnecessary items and use a CDN for fast content delivery. Both not loading unnecessary content & lazy loading) and CDN is 2 individual topics to dive into.
-
Migrating from Next Auth and Prisma to Supabase for a Stripe Integration - Thoughts?
I've also been inspired by this repo, but the Vercel template seems more achievable for me at this point.
-
Integrating Stripe Functionality from an Existing Next.js Repo into My Own Application
In addition to the repository I mentioned in my original post, I've also been inspired by another repository (Taxonomy Repo) which has implemented a similar Stripe integration. However, this one is a bit more complex and I'm finding the Vercel template to be more achievable for my current skill level.
-
Need Help with Stripe Webhook Integration in Next.js App Using App Router
I'm currently working on a Next.js project where I'm trying to integrate Stripe for payments and webhooks. I've been following the Taxonomy GitHub repo for guidance, particularly for setting up the app router and the lib files.
- How to take my React knowledge to the next level?
- App Router example repos
- Tailwind in Large Enterprise Projects
What are some alternatives?
FormalGrammars - Context-free and linear grammars in Haskell (parsing, pretty-printing, embedded DSL)
dub - Open-source link management infrastructure.
TaxonomyTools - Tools to process and visualize NCBI taxonomy data
bioinformatics-toolkit - A collection of bioinformatics algorithms
hPDB - PDB parser in Haskell
ts-faker - Generate fake data using TypeScript interfaces
tailwindcss.com - The Tailwind CSS documentation website.
trpc - 🧙♀️ Move Fast and Break Nothing. End-to-end typesafe APIs made easy.
hemokit - Haskell library for the Emotiv EEG, inspired by the Emokit code
rank-product - Collects the functions pertaining to finding the rank product of a data set as well as the associated p-value.
website - Website and documentation for Radix.
phybin - Binning (Newick) Phylogenetic Trees by Topology