Compare commits
8 Commits
update
...
9b48661a6a
| Author | SHA1 | Date | |
|---|---|---|---|
| 9b48661a6a | |||
| 4916a2fc7b | |||
| b0fb481cf2 | |||
| 495bd52c5b | |||
| bf2085fcce | |||
| 61e016d829 | |||
| e77eda0220 | |||
| fc4fab9478 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -46,3 +46,4 @@ yarn-error.log*
|
||||
.idea
|
||||
# clerk configuration (can include secrets)
|
||||
/.clerk/
|
||||
.worktrees
|
||||
|
||||
34
README.md
34
README.md
@@ -1,29 +1,13 @@
|
||||
# Create T3 App
|
||||
# My Personal Website
|
||||
|
||||
This is a [T3 Stack](https://create.t3.gg/) project bootstrapped with `create-t3-app`.
|
||||
## Using:
|
||||
|
||||
## What's next? How do I make an app with this?
|
||||
- nextjs
|
||||
- trpc
|
||||
- neon
|
||||
- uploadthing
|
||||
- drizzle
|
||||
- gsap
|
||||
- openai
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
- [Next.js](https://nextjs.org)
|
||||
- [NextAuth.js](https://next-auth.js.org)
|
||||
- [Prisma](https://prisma.io)
|
||||
- [Drizzle](https://orm.drizzle.team)
|
||||
- [Tailwind CSS](https://tailwindcss.com)
|
||||
- [tRPC](https://trpc.io)
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about the [T3 Stack](https://create.t3.gg/), take a look at the following resources:
|
||||
|
||||
- [Documentation](https://create.t3.gg/)
|
||||
- [Learn the T3 Stack](https://create.t3.gg/en/faq#what-learning-resources-are-currently-available) — Check out these awesome tutorials
|
||||
|
||||
You can check out the [create-t3-app GitHub repository](https://github.com/t3-oss/create-t3-app) — your feedback and contributions are welcome!
|
||||
|
||||
## How do I deploy this?
|
||||
|
||||
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.
|
||||
|
||||
7
bun.lock
7
bun.lock
@@ -40,6 +40,7 @@
|
||||
"@radix-ui/react-toggle-group": "^1.1.11",
|
||||
"@radix-ui/react-tooltip": "^1.2.8",
|
||||
"@t3-oss/env-nextjs": "^0.13.10",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@tanstack/react-query": "^5.90.21",
|
||||
"@tanstack/react-query-next-experimental": "^5.91.0",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
@@ -777,6 +778,8 @@
|
||||
|
||||
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.2.1", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.2.1", "@tailwindcss/oxide": "4.2.1", "postcss": "^8.5.6", "tailwindcss": "4.2.1" } }, "sha512-OEwGIBnXnj7zJeonOh6ZG9woofIjGrd2BORfvE5p9USYKDCZoQmfqLcfNiRWoJlRWLdNPn2IgVZuWAOM4iTYMw=="],
|
||||
|
||||
"@tailwindcss/typography": ["@tailwindcss/typography@0.5.19", "", { "dependencies": { "postcss-selector-parser": "6.0.10" }, "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" } }, "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg=="],
|
||||
|
||||
"@tanstack/query-core": ["@tanstack/query-core@5.90.20", "", {}, "sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg=="],
|
||||
|
||||
"@tanstack/react-query": ["@tanstack/react-query@5.90.21", "", { "dependencies": { "@tanstack/query-core": "5.90.20" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg=="],
|
||||
@@ -1903,7 +1906,7 @@
|
||||
|
||||
"postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="],
|
||||
|
||||
"postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="],
|
||||
"postcss-selector-parser": ["postcss-selector-parser@6.0.10", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w=="],
|
||||
|
||||
"postgres": ["postgres@3.4.8", "", {}, "sha512-d+JFcLM17njZaOLkv6SCev7uoLaBtfK86vMUXhW1Z4glPWh4jozno9APvW/XKFJ3CCxVoC7OL38BqRydtu5nGg=="],
|
||||
|
||||
@@ -2571,6 +2574,8 @@
|
||||
|
||||
"shadcn/diff": ["diff@8.0.3", "", {}, "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ=="],
|
||||
|
||||
"shadcn/postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="],
|
||||
|
||||
"shadcn/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"sharp/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"@radix-ui/react-toggle-group": "^1.1.11",
|
||||
"@radix-ui/react-tooltip": "^1.2.8",
|
||||
"@t3-oss/env-nextjs": "^0.13.10",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@tanstack/react-query": "^5.90.21",
|
||||
"@tanstack/react-query-next-experimental": "^5.91.0",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { isAdmin } from "~/app/actions"
|
||||
|
||||
export default async function AdminWrap({children,}: Readonly<{ children: React.ReactNode }>) {
|
||||
if (await isAdmin()) {
|
||||
// "use client"
|
||||
// import { isAdmin } from "~/app/actions"
|
||||
import { useUser } from "@clerk/nextjs"
|
||||
import { env } from "~/env"
|
||||
export default function AdminWrap({children,}: Readonly<{ children: React.ReactNode }>) {
|
||||
const user = useUser();
|
||||
if (user.isSignedIn && user.user.id == env.NEXT_PUBLIC_ADMIN_USER_CLERK_ID) {
|
||||
return <>{children}</>
|
||||
}
|
||||
return (<></>)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client"
|
||||
import Link from "next/link"
|
||||
import AdminWrap from "./AdminWrap"
|
||||
import { Show, SignInButton, SignOutButton, SignUpButton, UserButton } from "@clerk/nextjs"
|
||||
import { ClerkLoaded, Show, SignInButton, SignOutButton, SignUpButton, UserButton } from "@clerk/nextjs"
|
||||
import { Button } from "~/components/ui/button"
|
||||
import { ThemeSwitch } from "./ThemeSwitch"
|
||||
|
||||
@@ -39,13 +40,15 @@ export default function TopNav() {
|
||||
</Button>
|
||||
</Show>
|
||||
<ThemeSwitch />
|
||||
<Show when="signed-in">
|
||||
<Button asChild className="flex h-10 lg:h-full cursor-pointer lg:w-20 content-center" variant={"outline"}>
|
||||
<div>
|
||||
<UserButton />
|
||||
</div>
|
||||
</Button>
|
||||
</Show>
|
||||
<ClerkLoaded>
|
||||
<Show when="signed-in">
|
||||
<Button asChild className="flex h-10 lg:h-full cursor-pointer lg:w-20 content-center" variant={"outline"}>
|
||||
<div>
|
||||
<UserButton />
|
||||
</div>
|
||||
</Button>
|
||||
</Show>
|
||||
</ClerkLoaded>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@@ -47,7 +47,7 @@ export default function CvPage() {
|
||||
entires[0].filter((e) => { return e.categoryId == cat.id }).length > 0 ? (
|
||||
<>
|
||||
{entires[0].filter((e) => { return e.categoryId == cat.id }).map((entry) => (
|
||||
<CollapsibleForm entityName="Entry" form={CreateUpdateCvEntryForm} entity={entry} entityLabelIndex="title" />
|
||||
<CollapsibleForm key={entry.id} entityName="Entry" form={CreateUpdateCvEntryForm} entity={entry} entityLabelIndex="title" />
|
||||
))}
|
||||
</>
|
||||
) : (<></>)
|
||||
|
||||
@@ -8,19 +8,23 @@ import type { IterableElement } from 'type-fest'
|
||||
import { entitySchemas, makeOnSuccess } from "~/lib/utils";
|
||||
import { useEffect, useState } from "react";
|
||||
import type { RouterOutputs } from '~/server/routers/_app';
|
||||
import { SelectFormField, TextInputFormField } from '~/app/_components/Form/Fields'
|
||||
import { SelectFormField, TextInputFormField, MdeFormField } from '~/app/_components/Form/Fields'
|
||||
import { FormScaffold } from '~/app/_components/Form/Components';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import { useTheme } from 'next-themes';
|
||||
import { makeUseRelationShipWithNameIndex } from '~/lib/hooks';
|
||||
import { FormMutationContextProvider } from '~/app/_components/Form/Components/MutationProvider';
|
||||
export default function CreateUpdateProjectForm(params: { className?: string, entity?: IterableElement<RouterOutputs['project']['select']> }) {
|
||||
const [id, setId] = useState<string | undefined>(params.entity ? params.entity.id : undefined)
|
||||
const { theme } = useTheme()
|
||||
const schemas = entitySchemas('project')
|
||||
const { data: stacks, id: stackId, name: stackName, success: stacksSuccess, error: stackError } = makeUseRelationShipWithNameIndex('stackItems')(trpc.techStack.select.useQuery({}), id, (items) => { return items ? items.join('-') : "" })
|
||||
const form = useForm<z.infer<typeof schemas.insert>>({
|
||||
resolver: zodResolver(schemas.insert),
|
||||
defaultValues: {
|
||||
id: id ? id : crypto.randomUUID(),
|
||||
title: params.entity ? params.entity.title : "",
|
||||
description: params.entity ? params.entity.description : "",
|
||||
stackId: params.entity ? params.entity.stackId : stacksSuccess ? stacks?.at(0)?.id : "",
|
||||
releaseStatus: params.entity ? params.entity.releaseStatus : "unreleased",
|
||||
releaseLink: params.entity ? params.entity.releaseLink : "",
|
||||
@@ -64,6 +68,7 @@ export default function CreateUpdateProjectForm(params: { className?: string, en
|
||||
}
|
||||
</SelectFormField>
|
||||
<TextInputFormField control={form.control} name='title' label='Title' />
|
||||
<MdeFormField control={form.control} name='description' label='Description' dataColorMode={(theme as "dark" | "light") ?? "dark"} />
|
||||
<SelectFormField control={form.control} name='sourceType' label='Source Type' defaultValue={'open'} placeholder='open' >
|
||||
<SelectItem value="open"> open </SelectItem>
|
||||
<SelectItem value="closed"> closed </SelectItem>
|
||||
|
||||
@@ -38,7 +38,7 @@ export default function ProjectList() {
|
||||
techStacks[0].filter((e) => { return e.id == project.stackId }).length > 0 ? (
|
||||
<>
|
||||
{techStacks[0].filter((e) => { return e.id == project.stackId }).map((stack) => (
|
||||
<CollapsibleForm entityName="Stack" form={CreateUpdateStackForm} entity={stack} entityLabelIndex="stackItems"/>
|
||||
<CollapsibleForm key={stack.id} entityName="Stack" form={CreateUpdateStackForm} entity={stack} entityLabelIndex="stackItems"/>
|
||||
))}
|
||||
</>
|
||||
) : (<></>)
|
||||
|
||||
@@ -1,12 +1,93 @@
|
||||
'use client'
|
||||
|
||||
import { usePathname } from "next/navigation"
|
||||
import { trpc } from "~/app/_trpc/Client";
|
||||
import * as Card from "~/components/ui/card";
|
||||
import { Badge } from "~/components/ui/badge";
|
||||
import { StackBadge } from "~/components/StackBadge";
|
||||
import Markdown from "react-markdown";
|
||||
|
||||
export default function ProjectsPage() {
|
||||
const { data: projects, isLoading } = trpc.projectv2.listWithStack.useQuery();
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex justify-center items-center min-h-[200px] text-muted-foreground">
|
||||
Loading...
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!projects?.length) {
|
||||
return (
|
||||
<div className="flex justify-center items-center min-h-[200px] text-muted-foreground">
|
||||
No projects yet.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Page() {
|
||||
const pathName = usePathname()
|
||||
return (
|
||||
<div>
|
||||
{pathName}
|
||||
<div className="w-full max-w-4xl mx-auto px-4 py-8 flex flex-col gap-4">
|
||||
{projects.map((project) => (
|
||||
<Card.Card key={project.id}>
|
||||
<Card.CardHeader>
|
||||
<div className="flex items-start justify-between gap-2 flex-wrap">
|
||||
<Card.CardTitle>{project.title}</Card.CardTitle>
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{project.sourceType && (
|
||||
<Badge variant={project.sourceType === "open" ? "secondary" : "outline"}>
|
||||
{project.sourceType === "open" ? "Open Source" : "Closed Source"}
|
||||
</Badge>
|
||||
)}
|
||||
{project.releaseStatus && (
|
||||
<Badge variant={project.releaseStatus === "released" ? "default" : "outline"}>
|
||||
{project.releaseStatus === "released" ? "Released" : "Unreleased"}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Card.CardHeader>
|
||||
{(project.description || project.sourceLink || project.releaseLink || project.techStack?.stackItems?.length) && (
|
||||
<Card.CardContent className="flex flex-col gap-3">
|
||||
{project.description && (
|
||||
<div className="prose prose-sm dark:prose-invert max-w-none text-muted-foreground">
|
||||
<Markdown>{project.description}</Markdown>
|
||||
</div>
|
||||
)}
|
||||
{(project.sourceLink || project.releaseLink) && (
|
||||
<div className="flex gap-3 flex-wrap">
|
||||
{project.sourceLink && (
|
||||
<a
|
||||
href={project.sourceLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm text-muted-foreground hover:text-foreground underline underline-offset-4 transition-colors"
|
||||
>
|
||||
Source
|
||||
</a>
|
||||
)}
|
||||
{project.releaseLink && (
|
||||
<a
|
||||
href={project.releaseLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm text-muted-foreground hover:text-foreground underline underline-offset-4 transition-colors"
|
||||
>
|
||||
Live
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{project.techStack?.stackItems && project.techStack.stackItems.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{project.techStack.stackItems.map((item) => (
|
||||
<StackBadge key={item} item={item} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</Card.CardContent>
|
||||
)}
|
||||
</Card.Card>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
156
src/components/StackBadge.tsx
Normal file
156
src/components/StackBadge.tsx
Normal file
@@ -0,0 +1,156 @@
|
||||
import { Badge } from "~/components/ui/badge";
|
||||
|
||||
interface SvglIcon {
|
||||
light: string;
|
||||
dark: string;
|
||||
}
|
||||
|
||||
const STACK_META: Record<string, { label: string; icon?: SvglIcon }> = {
|
||||
drizzle: {
|
||||
label: "Drizzle ORM",
|
||||
icon: {
|
||||
light: "https://svgl.app/library/drizzle-orm_light.svg",
|
||||
dark: "https://svgl.app/library/drizzle-orm_dark.svg",
|
||||
},
|
||||
},
|
||||
postgres: {
|
||||
label: "PostgreSQL",
|
||||
icon: {
|
||||
light: "https://svgl.app/library/postgresql.svg",
|
||||
dark: "https://svgl.app/library/postgresql.svg",
|
||||
},
|
||||
},
|
||||
nextjs: {
|
||||
label: "Next.js",
|
||||
icon: {
|
||||
light: "https://svgl.app/library/nextjs_icon_dark.svg",
|
||||
dark: "https://svgl.app/library/nextjs_icon_dark.svg",
|
||||
},
|
||||
},
|
||||
react: {
|
||||
label: "React",
|
||||
icon: {
|
||||
light: "https://svgl.app/library/react_light.svg",
|
||||
dark: "https://svgl.app/library/react_dark.svg",
|
||||
},
|
||||
},
|
||||
servercomponents: { label: "Server Components" },
|
||||
php: {
|
||||
label: "PHP",
|
||||
icon: {
|
||||
light: "https://svgl.app/library/php.svg",
|
||||
dark: "https://svgl.app/library/php_dark.svg",
|
||||
},
|
||||
},
|
||||
laravel: {
|
||||
label: "Laravel",
|
||||
icon: {
|
||||
light: "https://svgl.app/library/laravel.svg",
|
||||
dark: "https://svgl.app/library/laravel.svg",
|
||||
},
|
||||
},
|
||||
reactnative: {
|
||||
label: "React Native",
|
||||
icon: {
|
||||
light: "https://svgl.app/library/react_light.svg",
|
||||
dark: "https://svgl.app/library/react_dark.svg",
|
||||
},
|
||||
},
|
||||
"react-native": {
|
||||
label: "React Native",
|
||||
icon: {
|
||||
light: "https://svgl.app/library/react_light.svg",
|
||||
dark: "https://svgl.app/library/react_dark.svg",
|
||||
},
|
||||
},
|
||||
expo: {
|
||||
label: "Expo",
|
||||
icon: {
|
||||
light: "https://svgl.app/library/expo.svg",
|
||||
dark: "https://svgl.app/library/expo.svg",
|
||||
},
|
||||
},
|
||||
mysql: {
|
||||
label: "MySQL",
|
||||
icon: {
|
||||
light: "https://svgl.app/library/mysql-icon-light.svg",
|
||||
dark: "https://svgl.app/library/mysql-icon-dark.svg",
|
||||
},
|
||||
},
|
||||
nginx: {
|
||||
label: "Nginx",
|
||||
icon: {
|
||||
light: "https://svgl.app/library/nginx.svg",
|
||||
dark: "https://svgl.app/library/nginx.svg",
|
||||
},
|
||||
},
|
||||
protobuf: { label: "Protobuf" },
|
||||
grpc: { label: "gRPC" },
|
||||
java: {
|
||||
label: "Java",
|
||||
icon: {
|
||||
light: "https://svgl.app/library/java.svg",
|
||||
dark: "https://svgl.app/library/java.svg",
|
||||
},
|
||||
},
|
||||
graalvm: { label: "GraalVM" },
|
||||
spring: {
|
||||
label: "Spring",
|
||||
icon: {
|
||||
light: "https://svgl.app/library/spring.svg",
|
||||
dark: "https://svgl.app/library/spring.svg",
|
||||
},
|
||||
},
|
||||
aws: {
|
||||
label: "AWS",
|
||||
icon: {
|
||||
light: "https://svgl.app/library/aws_light.svg",
|
||||
dark: "https://svgl.app/library/aws_dark.svg",
|
||||
},
|
||||
},
|
||||
s3: { label: "Amazon S3" },
|
||||
linux: {
|
||||
label: "Linux",
|
||||
icon: {
|
||||
light: "https://svgl.app/library/linux.svg",
|
||||
dark: "https://svgl.app/library/linux.svg",
|
||||
},
|
||||
},
|
||||
debian: { label: "Debian" },
|
||||
htmx: { label: "HTMX" },
|
||||
neon: {
|
||||
label: "Neon",
|
||||
icon: {
|
||||
light: "https://svgl.app/library/neon.svg",
|
||||
dark: "https://svgl.app/library/neon.svg",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export function StackBadge({ item }: { item: string }) {
|
||||
const meta = STACK_META[item] ?? { label: item };
|
||||
|
||||
return (
|
||||
<Badge variant="outline">
|
||||
{meta.icon && (
|
||||
<>
|
||||
<img
|
||||
src={meta.icon.light}
|
||||
alt=""
|
||||
width={12}
|
||||
height={12}
|
||||
className="dark:hidden shrink-0"
|
||||
/>
|
||||
<img
|
||||
src={meta.icon.dark}
|
||||
alt=""
|
||||
width={12}
|
||||
height={12}
|
||||
className="hidden dark:block shrink-0"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{meta.label}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
@@ -38,6 +38,7 @@ export const env = createEnv({
|
||||
* `NEXT_PUBLIC_`.
|
||||
*/
|
||||
client: {
|
||||
NEXT_PUBLIC_ADMIN_USER_CLERK_ID: z.string(),
|
||||
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string()
|
||||
// NEXT_PUBLIC_CLIENTVAR: z.string(),
|
||||
},
|
||||
@@ -63,6 +64,7 @@ export const env = createEnv({
|
||||
POSTGRES_URL_NO_SSL: process.env.POSTGRES_URL_NO_SSL,
|
||||
POSTGRES_PRISMA_URL: process.env.POSTGRES_PRISMA_URL,
|
||||
ADMIN_USER_CLERK_ID: process.env.ADMIN_USER_CLERK_ID,
|
||||
NEXT_PUBLIC_ADMIN_USER_CLERK_ID: process.env.NEXT_PUBLIC_ADMIN_USER_CLERK_ID,
|
||||
CLERK_SECRET_KEY: process.env.CLERK_SECRET_KEY,
|
||||
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,
|
||||
NODE_ENV: process.env.NODE_ENV,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { env } from "~/env";
|
||||
const isTenantAdminRoute = createRouteMatcher(['/admin(.*)'])
|
||||
export default clerkMiddleware(async (auth,req) => {
|
||||
if (isTenantAdminRoute(req)) {
|
||||
console.log("running clerk middleware");
|
||||
let userid = (await auth()).userId
|
||||
if (userid != env.ADMIN_USER_CLERK_ID) {
|
||||
await auth.protect()
|
||||
|
||||
@@ -55,13 +55,14 @@ export const cvEntryRelations = relations(cvEntry, ({one}) => ({
|
||||
|
||||
export const sourceTypeEnum = pgEnum('source_type',['open','closed'])
|
||||
export const releaseStatus = pgEnum('release_status',['released','unreleased'])
|
||||
export const stackItemEnum = pgEnum('stack_item',['drizzle','postgres','nextjs','react','servercomponents','php','laravel','reactnative','expo','mysql','nginx','protobuf','grpc'])
|
||||
export const stackItemEnum = pgEnum('stack_item',['drizzle','postgres','nextjs','react','servercomponents','php','laravel','reactnative','expo','mysql','nginx','protobuf','grpc','java','graalvm','spring','aws','s3','react-native','linux','debian','htmx','neon'])
|
||||
|
||||
export const project = createTable(
|
||||
"project",
|
||||
(d) => ({
|
||||
id: d.uuid().primaryKey().notNull(),
|
||||
title: d.varchar({length: 50}).notNull(),
|
||||
description: d.text(),
|
||||
sourceType: sourceTypeEnum(),
|
||||
sourceLink: d.varchar({length: 200}),
|
||||
releaseStatus: releaseStatus(),
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { router } from "~/server/trpc";
|
||||
import { publicProcedure, router } from "~/server/trpc";
|
||||
import { db } from "~/server/db";
|
||||
|
||||
export const projectRouter = router({
|
||||
listWithStack: publicProcedure.query(async () => {
|
||||
return db.query.project.findMany({
|
||||
with: {
|
||||
techStack: true,
|
||||
},
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { Config } from "tailwindcss"
|
||||
const config = {
|
||||
plugins: [
|
||||
require("tailwindcss-motion")
|
||||
require("tailwindcss-motion"),
|
||||
require("@tailwindcss/typography")
|
||||
]
|
||||
} satisfies Config
|
||||
|
||||
|
||||
Reference in New Issue
Block a user