├── .env.example ├── .eslintrc.cjs ├── .gitignore ├── README.md ├── components.json ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.cjs ├── prettier.config.js ├── prisma └── schema.prisma ├── public └── favicon.ico ├── src ├── app │ ├── (auth) │ │ ├── actions.ts │ │ ├── auth │ │ │ └── callback │ │ │ │ └── route.ts │ │ ├── layout.tsx │ │ ├── login │ │ │ └── page.tsx │ │ └── signup │ │ │ └── page.tsx │ ├── (main) │ │ ├── layout.tsx │ │ ├── page.tsx │ │ └── profile │ │ │ ├── new │ │ │ └── page.tsx │ │ │ └── page.tsx │ ├── api │ │ └── trpc │ │ │ └── [trpc] │ │ │ └── route.ts │ └── layout.tsx ├── components │ ├── auth │ │ └── OauthButton.tsx │ ├── navbar │ │ ├── AuthComponent.tsx │ │ ├── Navbar.tsx │ │ └── ProfileButton.tsx │ ├── theme-provider.tsx │ └── ui │ │ ├── avatar.tsx │ │ ├── button.tsx │ │ ├── card.tsx │ │ ├── form.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── select.tsx │ │ └── textarea.tsx ├── env.js ├── lib │ ├── utils.ts │ └── validators │ │ └── newProfile.ts ├── middleware.ts ├── server │ ├── api │ │ ├── root.ts │ │ ├── routers │ │ │ └── profiles.ts │ │ └── trpc.ts │ └── db.ts ├── styles │ └── globals.css ├── trpc │ ├── react.tsx │ ├── server.ts │ └── shared.ts └── utils │ └── supabase │ ├── client.ts │ ├── middleware.ts │ └── server.ts ├── tailwind.config.ts └── tsconfig.json /.env.example: -------------------------------------------------------------------------------- 1 | DATABASE_URL="file:./db.sqlite" 2 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import("eslint").Linter.Config} */ 2 | const config = { 3 | parser: "@typescript-eslint/parser", 4 | parserOptions: { 5 | project: true, 6 | }, 7 | plugins: ["@typescript-eslint"], 8 | extends: [ 9 | "next/core-web-vitals", 10 | "plugin:@typescript-eslint/recommended-type-checked", 11 | "plugin:@typescript-eslint/stylistic-type-checked", 12 | ], 13 | rules: { 14 | // These opinionated rules are enabled in stylistic-type-checked above. 15 | // Feel free to reconfigure them to your own preference. 16 | "@typescript-eslint/array-type": "off", 17 | "@typescript-eslint/consistent-type-definitions": "off", 18 | 19 | "@typescript-eslint/consistent-type-imports": [ 20 | "warn", 21 | { 22 | prefer: "type-imports", 23 | fixStyle: "inline-type-imports", 24 | }, 25 | ], 26 | "@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }], 27 | "@typescript-eslint/require-await": "off", 28 | "@typescript-eslint/no-misused-promises": [ 29 | "error", 30 | { 31 | checksVoidReturn: { attributes: false }, 32 | }, 33 | ], 34 | }, 35 | ignorePatterns: ["src/components/ui", "src/utils/supabase"], 36 | }; 37 | 38 | module.exports = config; 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # database 12 | /prisma/db.sqlite 13 | /prisma/db.sqlite-journal 14 | 15 | # next.js 16 | /.next/ 17 | /out/ 18 | next-env.d.ts 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # local env files 34 | # do not commit any .env files to git, except for the .env.example file. https://create.t3.gg/en/usage/env-variables#using-environment-variables 35 | .env 36 | .env*.local 37 | 38 | # vercel 39 | .vercel 40 | 41 | # typescript 42 | *.tsbuildinfo 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Create T3 App 2 | 3 | This is a [T3 Stack](https://create.t3.gg/) project bootstrapped with `create-t3-app`. 4 | 5 | ## What's next? How do I make an app with this? 6 | 7 | We try to keep this project as simple as possible, so you can start with just the scaffolding we set up for you, and add additional things later when they become necessary. 8 | 9 | If you are not familiar with the different technologies used in this project, please refer to the respective docs. If you still are in the wind, please join our [Discord](https://t3.gg/discord) and ask for help. 10 | 11 | - [Next.js](https://nextjs.org) 12 | - [NextAuth.js](https://next-auth.js.org) 13 | - [Prisma](https://prisma.io) 14 | - [Tailwind CSS](https://tailwindcss.com) 15 | - [tRPC](https://trpc.io) 16 | 17 | ## Learn More 18 | 19 | To learn more about the [T3 Stack](https://create.t3.gg/), take a look at the following resources: 20 | 21 | - [Documentation](https://create.t3.gg/) 22 | - [Learn the T3 Stack](https://create.t3.gg/en/faq#what-learning-resources-are-currently-available) — Check out these awesome tutorials 23 | 24 | You can check out the [create-t3-app GitHub repository](https://github.com/t3-oss/create-t3-app) — your feedback and contributions are welcome! 25 | 26 | ## How do I deploy this? 27 | 28 | Follow our deployment guides for [Vercel](https://create.t3.gg/en/deployment/vercel), [Netlify](https://create.t3.gg/en/deployment/netlify) and [Docker](https://create.t3.gg/en/deployment/docker) for more information. 29 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/styles/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful 3 | * for Docker builds. 4 | */ 5 | await import("./src/env.js"); 6 | 7 | /** @type {import("next").NextConfig} */ 8 | const config = {}; 9 | 10 | export default config; 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "devlinkreal", 3 | "version": "0.1.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "build": "next build", 8 | "db:push": "prisma db push", 9 | "db:studio": "prisma studio", 10 | "dev": "next dev", 11 | "postinstall": "prisma generate", 12 | "lint": "next lint", 13 | "start": "next start" 14 | }, 15 | "dependencies": { 16 | "@heroicons/react": "^2.1.1", 17 | "@hookform/resolvers": "^3.3.3", 18 | "@prisma/client": "^5.6.0", 19 | "@radix-ui/react-avatar": "^1.0.4", 20 | "@radix-ui/react-label": "^2.0.2", 21 | "@radix-ui/react-select": "^2.0.0", 22 | "@radix-ui/react-slot": "^1.0.2", 23 | "@supabase/ssr": "^0.0.10", 24 | "@supabase/supabase-js": "^2.39.2", 25 | "@t3-oss/env-nextjs": "^0.7.1", 26 | "@tanstack/react-query": "^4.36.1", 27 | "@trpc/client": "^10.43.6", 28 | "@trpc/next": "^10.43.6", 29 | "@trpc/react-query": "^10.43.6", 30 | "@trpc/server": "^10.43.6", 31 | "class-variance-authority": "^0.7.0", 32 | "clsx": "^2.1.0", 33 | "lucide-react": "^0.303.0", 34 | "next": "^14.0.4", 35 | "next-themes": "^0.2.1", 36 | "react": "18.2.0", 37 | "react-dom": "18.2.0", 38 | "react-hook-form": "^7.49.2", 39 | "react-icons": "^4.12.0", 40 | "server-only": "^0.0.1", 41 | "superjson": "^2.2.1", 42 | "tailwind-merge": "^2.2.0", 43 | "tailwindcss-animate": "^1.0.7", 44 | "zod": "^3.22.4" 45 | }, 46 | "devDependencies": { 47 | "@types/eslint": "^8.44.7", 48 | "@types/node": "^18.17.0", 49 | "@types/react": "^18.2.37", 50 | "@types/react-dom": "^18.2.15", 51 | "@typescript-eslint/eslint-plugin": "^6.11.0", 52 | "@typescript-eslint/parser": "^6.11.0", 53 | "autoprefixer": "^10.4.14", 54 | "eslint": "^8.54.0", 55 | "eslint-config-next": "^14.0.4", 56 | "postcss": "^8.4.31", 57 | "prettier": "^3.1.0", 58 | "prettier-plugin-tailwindcss": "^0.5.7", 59 | "prisma": "^5.6.0", 60 | "tailwindcss": "^3.3.5", 61 | "typescript": "^5.1.6" 62 | }, 63 | "ct3aMetadata": { 64 | "initVersion": "7.25.1" 65 | }, 66 | "packageManager": "npm@10.2.4" 67 | } 68 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | 8 | module.exports = config; 9 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('prettier').Config & import('prettier-plugin-tailwindcss').PluginOptions} */ 2 | const config = { 3 | plugins: ["prettier-plugin-tailwindcss"], 4 | }; 5 | 6 | export default config; 7 | -------------------------------------------------------------------------------- /prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | generator client { 2 | provider = "prisma-client-js" 3 | } 4 | 5 | datasource db { 6 | provider = "postgresql" 7 | url = env("DATABASE_URL") 8 | } 9 | 10 | enum RoleType { 11 | FRONTEND 12 | BACKEND 13 | FULLSTACK 14 | DESIGN 15 | } 16 | 17 | model Profile { 18 | id String @id 19 | createdAt DateTime @default(now()) @map("created_at") 20 | email String @unique 21 | firstName String 22 | lastName String 23 | role RoleType 24 | skills String[] 25 | bio String 26 | github String? 27 | linkedin String? 28 | website String? 29 | } -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- 1 |  h6  (�00 h&�(  ���L���A���$������������5����������A����������������������b�������������������� 2 | �����������������������}���0����������b�����������������l����������_�������2����������_���X���\�������������������������R������m�����������������������L�������������������G���N������������������������������Q�������������A���O���������������������������������������|������( @ ������B���h���n���R������f�����������;��� ���������������������������������%��������������J����������������������������������������������O��������������J�������������������������f���_����������������������>��������������J������������������)��� ��������������������������������J������������������"������������������_��������������J���{����������=�������������������������J���{���5�����������������������������J��������������������������J�����������������������������J���i�������������������������J���%���������������3��������������J���4���9������U�����������������������������J���S�����������5���*���������������������������������J���������������������1���8������������������������J��� ������������������,�����������������J��� 3 | ������������������(��������������J��� �������������������$������<���<�������������������������!�������������������������V���a���a���a���a���a���a���a���a���a���a���a���a���a���a���a���a���a���a���+���������������������������������������������������������������������������������������������������������-�������������������������������������������������������������������������������������������������������������(�������������������������[���h���h���h���h���h���h���h���h���h���h���h���h���h���h���h���h���h���h���h���h���M��������������� 4 | (0` ������ ������"���%���$������������ ���J���J���J���;������ ���g��������������������������������B��� ���+������������������������b�����������������������������������������������������.������������������������������������������������������������������������������������;������.��������������������������������������������������������������������������������������������O������.���������������������������������������������������O���$������!���2������������������������������#���.����������������������:�����������������������j������!����������������������������.��������������������������������������������N�������������������������G���.����������������������)�������������������x������)�������������������������.����������������������y��������������� ����������������������&���.���������������������� 5 | ���{�������������!�������������������J���.���������������������� 6 | ���y���A����������������������_���.�����������������������������������������e���.�����������������������������������������]���.����������������������%�������������������G���.��������������������������������������������!���.����������������������5�������������������������.��������������������������������������������5���.����������������������������&������ ���,��������������������������.�������������������������C�����������8������ ���������������������������������.����������������������L�������������������"������y�����������������������8������.������������������������������������������������������������������������*���.����������������������$��������������������������.������u���������.�������������������������&�����������������������������������.���������������������������������������������������.����������������������*��������������������������&���.�������������������������-��������������������������������d���d���d���O��� 7 | ���%�����������������������������1��������������������������������4����������������������������� ������!���!���!���!���!���!���!���!���!���!���!���!���!���!���!���!���!���!���!���!���!���!���!���!���!���!���!���������.�����������������������������U����������������������������������������������������������������������������������������������������������������������0���8�����������������������������a���������������������������������������������������������������������������������������������������������������������������������<�������������������������� ���a����������������������������������������������������������������������������������������������������������������������������������9�������������������������� ���X�������������������������������������������������������������������������������������������������������������������������������������<������������������1��� ���!���#���#���#���#���#���#���#���#���#���#���#���#���#���#���#���#���#���#���#���#���#���#���#���#���#���#���#���#���#���#��� ��� ��������������������� -------------------------------------------------------------------------------- /src/app/(auth)/actions.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { createClient } from "@/utils/supabase/server"; 4 | import { cookies, headers } from "next/headers"; 5 | import type { SignupInput } from "./signup/page"; 6 | import type { LoginInput } from "./login/page"; 7 | 8 | const supabase = createClient(cookies()); 9 | const origin = headers().get("origin"); 10 | 11 | export const signUp = async (data: SignupInput) => { 12 | "use server"; 13 | 14 | const { error } = await supabase.auth.signUp({ 15 | email: data.email, 16 | password: data.password, 17 | options: { 18 | emailRedirectTo: `${origin}/auth/callback`, 19 | }, 20 | }); 21 | 22 | if (error) { 23 | return { 24 | error: error.message, 25 | }; 26 | } 27 | }; 28 | 29 | export const signIn = async (data: LoginInput) => { 30 | "use server"; 31 | 32 | const { error } = await supabase.auth.signInWithPassword({ 33 | email: data.email, 34 | password: data.password, 35 | }); 36 | if (error) { 37 | return { 38 | error: error.message, 39 | }; 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /src/app/(auth)/auth/callback/route.ts: -------------------------------------------------------------------------------- 1 | import { createClient } from "@/utils/supabase/server"; 2 | import { NextResponse } from "next/server"; 3 | import { cookies } from "next/headers"; 4 | 5 | export async function GET(request: Request) { 6 | const requestUrl = new URL(request.url); 7 | const code = requestUrl.searchParams.get("code"); 8 | 9 | if (code) { 10 | const cookieStore = cookies(); 11 | const supabase = createClient(cookieStore); 12 | await supabase.auth.exchangeCodeForSession(code); 13 | } 14 | 15 | // URL to redirect to after sign in process completes 16 | return NextResponse.redirect(requestUrl.origin); 17 | } 18 | -------------------------------------------------------------------------------- /src/app/(auth)/layout.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { createClient } from "@/utils/supabase/server"; 3 | import { cookies } from "next/headers"; 4 | import { redirect } from "next/navigation"; 5 | import Link from "next/link"; 6 | 7 | const RootLayout: React.FC<{ children: React.ReactNode }> = async ({ 8 | children, 9 | }) => { 10 | const supabase = createClient(cookies()); 11 | 12 | const { 13 | data: { user }, 14 | } = await supabase.auth.getUser(); 15 | 16 | if (user) { 17 | redirect("/"); 18 | } 19 | return ( 20 | <> 21 | 22 |

devlink

23 | 24 | {children} 25 | 26 | ); 27 | }; 28 | 29 | export default RootLayout; 30 | -------------------------------------------------------------------------------- /src/app/(auth)/login/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import Link from "next/link"; 4 | import { Button } from "@/components/ui/button"; 5 | import { 6 | Form, 7 | FormControl, 8 | FormField, 9 | FormItem, 10 | FormLabel, 11 | FormMessage, 12 | } from "@/components/ui/form"; 13 | 14 | import { Input } from "@/components/ui/input"; 15 | import { useForm } from "react-hook-form"; 16 | import { z } from "zod"; 17 | import { zodResolver } from "@hookform/resolvers/zod"; 18 | import React, { useState } from "react"; 19 | 20 | import { signIn } from "../actions"; 21 | import OauthButton from "@/components/auth/OauthButton"; 22 | 23 | const registerSchema = z.object({ 24 | email: z.string().email(), 25 | password: z.string().min(6).max(100), 26 | }); 27 | 28 | export type LoginInput = z.infer; 29 | 30 | export default function Login() { 31 | const form = useForm({ 32 | resolver: zodResolver(registerSchema), 33 | defaultValues: { 34 | email: "", 35 | password: "", 36 | }, 37 | }); 38 | 39 | const [error, setError] = useState(null); 40 | 41 | const onSubmit = async (data: LoginInput) => { 42 | const result = await signIn(data); 43 | if (result?.error) { 44 | setError(result.error); 45 | } 46 | }; 47 | 48 | return ( 49 |
50 |
51 |
52 |
53 |
54 |

Sign in

55 |
56 | 60 | ( 64 | 65 | 66 | Email Address 67 | 68 | 69 | 74 | 75 | 76 | 77 | )} 78 | /> 79 | ( 83 | 84 | 85 | Password 86 | 87 | 88 | 94 | 95 | 96 | 97 | )} 98 | /> 99 | 102 | {error && ( 103 |
104 |

105 | {error} 106 |

107 |
108 | )} 109 | 110 | 111 |
112 |
113 |

OR

114 |
115 |
116 | 117 | 118 |

119 | Don't have an account? Sign up 120 |

121 |
122 |
123 |
124 |
125 | ); 126 | } 127 | -------------------------------------------------------------------------------- /src/app/(auth)/signup/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import Link from "next/link"; 4 | import { Button } from "@/components/ui/button"; 5 | import { 6 | Form, 7 | FormControl, 8 | FormField, 9 | FormItem, 10 | FormLabel, 11 | FormMessage, 12 | } from "@/components/ui/form"; 13 | 14 | import { Input } from "@/components/ui/input"; 15 | import { useForm } from "react-hook-form"; 16 | import { z } from "zod"; 17 | import { zodResolver } from "@hookform/resolvers/zod"; 18 | import React, { useState } from "react"; 19 | 20 | import { signUp } from "../actions"; 21 | import OauthButton from "@/components/auth/OauthButton"; 22 | 23 | const registerSchema = z.object({ 24 | email: z.string().email(), 25 | password: z.string().min(6).max(100), 26 | }); 27 | 28 | export type SignupInput = z.infer; 29 | 30 | export default function Login() { 31 | const form = useForm({ 32 | resolver: zodResolver(registerSchema), 33 | defaultValues: { 34 | email: "", 35 | password: "", 36 | }, 37 | }); 38 | 39 | const [error, setError] = useState(null); 40 | const [success, setSuccess] = useState(null); 41 | 42 | const onSubmit = async (data: SignupInput) => { 43 | setSuccess("Check your email for further instructions"); 44 | const result = await signUp(data); 45 | if (result?.error) { 46 | setSuccess(null); 47 | setError(result.error); 48 | } 49 | }; 50 | 51 | return ( 52 |
53 |
54 |
55 |
56 |
57 |

Sign up

58 |
59 | 63 | ( 67 | 68 | 69 | Email Address 70 | 71 | 72 | 77 | 78 | 79 | 80 | )} 81 | /> 82 | ( 86 | 87 | 88 | Password 89 | 90 | 91 | 97 | 98 | 99 | 100 | )} 101 | /> 102 | 105 | {success && ( 106 |
107 |

108 | {success} 109 |

110 |
111 | )} 112 | {error && ( 113 |
114 |

115 | {error} 116 |

117 |
118 | )} 119 | 120 | 121 |
122 |
123 |

OR

124 |
125 |
126 | 127 | 128 |

129 | Already have an account? Sign in 130 |

131 |
132 |
133 |
134 |
135 | ); 136 | } 137 | -------------------------------------------------------------------------------- /src/app/(main)/layout.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Navbar from "@/components/navbar/Navbar"; 3 | import AuthComponent from "@/components/navbar/AuthComponent"; 4 | 5 | const RootLayout: React.FC<{ children: React.ReactNode }> = async ({ 6 | children, 7 | }) => { 8 | return ( 9 | <> 10 | 11 | 12 | 13 | {children} 14 | 15 | ); 16 | }; 17 | 18 | export default RootLayout; 19 | -------------------------------------------------------------------------------- /src/app/(main)/page.tsx: -------------------------------------------------------------------------------- 1 | import { createClient } from "@/utils/supabase/server"; 2 | import Link from "next/link"; 3 | import { cookies } from "next/headers"; 4 | import { redirect } from "next/navigation"; 5 | import { Button } from "@/components/ui/button"; 6 | 7 | export default async function AuthButton() { 8 | const supabase = createClient(cookies()); 9 | 10 | const { 11 | data: { user }, 12 | } = await supabase.auth.getUser(); 13 | 14 | const signOut = async () => { 15 | "use server"; 16 | 17 | const supabase = createClient(cookies()); 18 | await supabase.auth.signOut(); 19 | return redirect("/login"); 20 | }; 21 | 22 | return user ? ( 23 |
24 | Hey, {user.email}! 25 |
26 | 29 |
30 | 31 |
32 | ) : ( 33 | 37 | Login sch00ol 38 | 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /src/app/(main)/profile/new/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Button } from "@/components/ui/button"; 4 | import { 5 | Form, 6 | FormControl, 7 | FormField, 8 | FormItem, 9 | FormLabel, 10 | FormMessage, 11 | } from "@/components/ui/form"; 12 | 13 | import { Input } from "@/components/ui/input"; 14 | import { 15 | Card, 16 | CardContent, 17 | CardDescription, 18 | CardHeader, 19 | CardTitle, 20 | } from "@/components/ui/card"; 21 | import { 22 | Select, 23 | SelectContent, 24 | SelectItem, 25 | SelectTrigger, 26 | SelectValue, 27 | } from "@/components/ui/select"; 28 | 29 | import { Textarea } from "@/components/ui/textarea"; 30 | 31 | import { useFieldArray, useForm } from "react-hook-form"; 32 | import type { z } from "zod"; 33 | import { zodResolver } from "@hookform/resolvers/zod"; 34 | import { newProfileSchema } from "@/lib/validators/newProfile"; 35 | 36 | import { TrashIcon } from "@heroicons/react/24/outline"; 37 | import { useRouter } from "next/navigation"; 38 | import { api } from "@/trpc/react"; 39 | import { capitalizeFirstLetter } from "@/lib/utils"; 40 | 41 | export type NewProfileInput = z.infer; 42 | 43 | export default function NewProfileForm() { 44 | const form = useForm({ 45 | resolver: zodResolver(newProfileSchema), 46 | defaultValues: { 47 | firstName: "", 48 | lastName: "", 49 | role: undefined, 50 | skills: [{ name: "" }], 51 | bio: "", 52 | github: "", 53 | linkedin: "", 54 | website: "", 55 | }, 56 | }); 57 | 58 | const { fields, append, remove } = useFieldArray({ 59 | control: form.control, 60 | name: "skills", 61 | }); 62 | 63 | const watchSkills = form.watch("skills"); 64 | 65 | const router = useRouter(); 66 | 67 | const { mutate } = api.profiles.create.useMutation({ 68 | onSuccess: () => { 69 | router.push("/profile"); 70 | }, 71 | onError: (e) => { 72 | const errorMessage = e.data?.zodError?.fieldErrors.content; 73 | console.error("Error creating investment:", errorMessage); 74 | }, 75 | }); 76 | 77 | const onSubmit = async (data: NewProfileInput) => { 78 | const skillsList = data.skills.map((skill) => skill.name); 79 | mutate({ 80 | firstName: capitalizeFirstLetter(data.firstName), 81 | lastName: capitalizeFirstLetter(data.lastName), 82 | role: data.role, 83 | skills: skillsList, 84 | bio: data.bio, 85 | github: data.github, 86 | linkedin: data.linkedin, 87 | website: data.website, 88 | }); 89 | }; 90 | 91 | return ( 92 |
93 | 94 | 95 | Create Profile 96 | Yabba dabba doo 97 | 98 | 99 |
100 | 104 |
105 | ( 109 | 110 | First name 111 | 112 | 113 | 114 | 115 | 116 | )} 117 | /> 118 | ( 122 | 123 | Last name 124 | 125 | 126 | 127 | 128 | 129 | )} 130 | /> 131 |
132 | ( 136 | 137 | Role 138 | 154 | 155 | 156 | )} 157 | /> 158 | 159 | ( 163 | 164 | Skills 165 |
166 | {fields.map((field, index) => ( 167 |
168 |
169 | 170 | 177 | 178 | {fields.length > 1 && ( 179 | remove(index)} 182 | /> 183 | )} 184 |
185 | 186 | {form.formState.errors.skills?.[index]?.name && ( 187 |

188 | This can't be empty 189 |

190 | )} 191 |
192 | ))} 193 |
194 | 202 |
203 | )} 204 | /> 205 | 206 | ( 210 | 211 | Bio 212 | 213 |