feat: initial commit

This commit is contained in:
2024-01-14 15:56:43 +01:00
parent 4ca950eb63
commit 82f0113eb9
41 changed files with 2198 additions and 217 deletions

8
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

5
.idea/codeStyles/codeStyleConfig.xml generated Normal file
View File

@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

7
.idea/discord.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="PROJECT_FILES" />
<option name="description" value="" />
</component>
</project>

View File

@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

6
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/sern-bin.iml" filepath="$PROJECT_DIR$/.idea/sern-bin.iml" />
</modules>
</component>
</project>

9
.idea/sern-bin.iml generated Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@@ -0,0 +1,6 @@
CREATE TABLE IF NOT EXISTS "code" (
"id" text PRIMARY KEY NOT NULL,
"code" text NOT NULL,
"lang" text NOT NULL,
"authorId" text NOT NULL
);

View File

@@ -0,0 +1 @@
ALTER TABLE "code" ADD COLUMN "fileName" text NOT NULL;

View File

@@ -0,0 +1 @@
ALTER TABLE "code" ADD COLUMN "description" text NOT NULL;

View File

@@ -0,0 +1,49 @@
{
"id": "4402c683-875f-4d90-a284-660e23a1a83d",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "5",
"dialect": "pg",
"tables": {
"code": {
"name": "code",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"code": {
"name": "code",
"type": "text",
"primaryKey": false,
"notNull": true
},
"lang": {
"name": "lang",
"type": "text",
"primaryKey": false,
"notNull": true
},
"authorId": {
"name": "authorId",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {},
"schemas": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -0,0 +1,55 @@
{
"id": "ae876484-7aa1-4b33-bf67-b322a3ef4a0d",
"prevId": "4402c683-875f-4d90-a284-660e23a1a83d",
"version": "5",
"dialect": "pg",
"tables": {
"code": {
"name": "code",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"fileName": {
"name": "fileName",
"type": "text",
"primaryKey": false,
"notNull": true
},
"code": {
"name": "code",
"type": "text",
"primaryKey": false,
"notNull": true
},
"lang": {
"name": "lang",
"type": "text",
"primaryKey": false,
"notNull": true
},
"authorId": {
"name": "authorId",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {},
"schemas": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -0,0 +1,61 @@
{
"id": "ac31cb3c-c01e-480d-bfeb-4eee84014495",
"prevId": "ae876484-7aa1-4b33-bf67-b322a3ef4a0d",
"version": "5",
"dialect": "pg",
"tables": {
"code": {
"name": "code",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"fileName": {
"name": "fileName",
"type": "text",
"primaryKey": false,
"notNull": true
},
"code": {
"name": "code",
"type": "text",
"primaryKey": false,
"notNull": true
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": true
},
"lang": {
"name": "lang",
"type": "text",
"primaryKey": false,
"notNull": true
},
"authorId": {
"name": "authorId",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {},
"schemas": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -0,0 +1,27 @@
{
"version": "5",
"dialect": "pg",
"entries": [
{
"idx": 0,
"version": "5",
"when": 1705152102873,
"tag": "0000_ambiguous_mach_iv",
"breakpoints": true
},
{
"idx": 1,
"version": "5",
"when": 1705163965392,
"tag": "0001_complete_master_chief",
"breakpoints": true
},
{
"idx": 2,
"version": "5",
"when": 1705241267004,
"tag": "0002_lovely_inhumans",
"breakpoints": true
}
]
}

View File

@@ -6,19 +6,42 @@
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "next lint" "lint": "next lint",
"migrate": "yarn generateMigrations && node --loader ts-node/esm src/db/migrations.mts",
"generateMigrations": "drizzle-kit generate:pg --schema ./src/db/schema.ts"
}, },
"dependencies": { "dependencies": {
"@clerk/nextjs": "^4.29.3",
"@mantine/core": "^7.4.1",
"@mantine/hooks": "^7.4.1",
"@mantine/notifications": "^7.4.1",
"@monaco-editor/react": "^4.6.0",
"bottleneck": "^2.19.5",
"clsx": "^2.1.0",
"dotenv": "^16.3.1",
"drizzle-orm": "^0.29.3",
"lru-cache": "^10.1.0",
"next": "14.0.4",
"next-nprogress-bar": "^2.1.2",
"postgres": "^3.4.3",
"react": "^18", "react": "^18",
"react-dom": "^18", "react-dom": "^18",
"next": "14.0.4" "react-icons": "^5.0.1",
"util-utils": "^1.0.3",
"zod": "^3.22.4",
"zustand": "^4.4.7"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^5",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^18", "@types/react": "^18",
"@types/react-dom": "^18", "@types/react-dom": "^18",
"drizzle-kit": "^0.20.12",
"eslint": "^8", "eslint": "^8",
"eslint-config-next": "14.0.4" "eslint-config-next": "14.0.4",
"postcss": "^8.4.33",
"postcss-preset-mantine": "^1.12.3",
"postcss-simple-vars": "^7.0.1",
"ts-node": "^10.9.2",
"typescript": "^5"
} }
} }

14
postcss.config.js Normal file
View File

@@ -0,0 +1,14 @@
module.exports = {
plugins: {
'postcss-preset-mantine': {},
'postcss-simple-vars': {
variables: {
'mantine-breakpoint-xs': '36em',
'mantine-breakpoint-sm': '48em',
'mantine-breakpoint-md': '62em',
'mantine-breakpoint-lg': '75em',
'mantine-breakpoint-xl': '88em',
},
},
},
};

BIN
public/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>

Before

Width:  |  Height:  |  Size: 629 B

View File

@@ -1,107 +1,5 @@
:root { :root {
--max-width: 1100px;
--border-radius: 12px;
--font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono', --font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono',
'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro', 'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro',
'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace; 'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace;
}
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
--primary-glow: conic-gradient(
from 180deg at 50% 50%,
#16abff33 0deg,
#0885ff33 55deg,
#54d6ff33 120deg,
#0071ff33 160deg,
transparent 360deg
);
--secondary-glow: radial-gradient(
rgba(255, 255, 255, 1),
rgba(255, 255, 255, 0)
);
--tile-start-rgb: 239, 245, 249;
--tile-end-rgb: 228, 232, 233;
--tile-border: conic-gradient(
#00000080,
#00000040,
#00000030,
#00000020,
#00000010,
#00000010,
#00000080
);
--callout-rgb: 238, 240, 241;
--callout-border-rgb: 172, 175, 176;
--card-rgb: 180, 185, 188;
--card-border-rgb: 131, 134, 135;
}
@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
--primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0));
--secondary-glow: linear-gradient(
to bottom right,
rgba(1, 65, 255, 0),
rgba(1, 65, 255, 0),
rgba(1, 65, 255, 0.3)
);
--tile-start-rgb: 2, 13, 46;
--tile-end-rgb: 2, 5, 19;
--tile-border: conic-gradient(
#ffffff80,
#ffffff40,
#ffffff30,
#ffffff20,
#ffffff10,
#ffffff10,
#ffffff80
);
--callout-rgb: 20, 20, 20;
--callout-border-rgb: 108, 108, 108;
--card-rgb: 100, 100, 100;
--card-border-rgb: 200, 200, 200;
}
}
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
html,
body {
max-width: 100vw;
overflow-x: hidden;
}
body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
}
a {
color: inherit;
text-decoration: none;
}
@media (prefers-color-scheme: dark) {
html {
color-scheme: dark;
}
}

View File

@@ -1,12 +1,19 @@
import type { Metadata } from 'next' import type { Metadata } from 'next'
import { Inter } from 'next/font/google' import { Inter } from 'next/font/google'
import './globals.css' import './globals.css'
import {ClerkLoaded, ClerkLoading, ClerkProvider} from "@clerk/nextjs";
import {ColorSchemeScript, LoadingOverlay, MantineProvider} from '@mantine/core';
import {Notifications} from "@mantine/notifications";
import '@mantine/core/styles.css';
import '@mantine/notifications/styles.css';
import Providers from "@/providers";
import NavBar from "@/components/NavBar/NavBar";
const inter = Inter({ subsets: ['latin'] }) const inter = Inter({ subsets: ['latin'] })
export const metadata: Metadata = { export const metadata: Metadata = {
title: 'Create Next App', title: 'sern bin',
description: 'Generated by create next app', description: 'A sern-centric snippet sharing platform'
} }
export default function RootLayout({ export default function RootLayout({
@@ -15,8 +22,29 @@ export default function RootLayout({
children: React.ReactNode children: React.ReactNode
}) { }) {
return ( return (
<html lang="en"> <ClerkProvider>
<body className={inter.className}>{children}</body> <html lang="en">
</html> <head>
<ColorSchemeScript />
<link rel="icon" href="/logo.png" />
</head>
<body className={inter.className}>
<MantineProvider>
<Providers>
<ClerkLoading>
<LoadingOverlay visible />
</ClerkLoading>
<ClerkLoaded>
<Notifications limit={10} />
<NavBar />
<div style={{ margin: '0 10px 0' }}>
{children}
</div>
</ClerkLoaded>
</Providers>
</MantineProvider>
</body>
</html>
</ClerkProvider>
) )
} }

7
src/app/me/page.tsx Normal file
View File

@@ -0,0 +1,7 @@
export default function Page() {
return (
<div>
<h1>Sorry, but this is WIP right now!</h1>
</div>
)
}

View File

@@ -1,95 +1,8 @@
import Image from 'next/image' import {auth} from "@clerk/nextjs";
import styles from './page.module.css' import CreateSnippet from "@/components/CreateSnippet/CreateSnippet";
import SignInPrompt from "@/components/SignInPrompt/SignInPrompt";
export default function Home() { export default function Home() {
return ( const { userId } = auth()
<main className={styles.main}> return userId ? <CreateSnippet /> : <SignInPrompt />
<div className={styles.description}>
<p>
Get started by editing&nbsp;
<code className={styles.code}>src/app/page.tsx</code>
</p>
<div>
<a
href="https://vercel.com?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
By{' '}
<Image
src="/vercel.svg"
alt="Vercel Logo"
className={styles.vercelLogo}
width={100}
height={24}
priority
/>
</a>
</div>
</div>
<div className={styles.center}>
<Image
className={styles.logo}
src="/next.svg"
alt="Next.js Logo"
width={180}
height={37}
priority
/>
</div>
<div className={styles.grid}>
<a
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
className={styles.card}
target="_blank"
rel="noopener noreferrer"
>
<h2>
Docs <span>-&gt;</span>
</h2>
<p>Find in-depth information about Next.js features and API.</p>
</a>
<a
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
className={styles.card}
target="_blank"
rel="noopener noreferrer"
>
<h2>
Learn <span>-&gt;</span>
</h2>
<p>Learn about Next.js in an interactive course with&nbsp;quizzes!</p>
</a>
<a
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
className={styles.card}
target="_blank"
rel="noopener noreferrer"
>
<h2>
Templates <span>-&gt;</span>
</h2>
<p>Explore starter templates for Next.js.</p>
</a>
<a
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
className={styles.card}
target="_blank"
rel="noopener noreferrer"
>
<h2>
Deploy <span>-&gt;</span>
</h2>
<p>
Instantly deploy your Next.js site to a shareable URL with Vercel.
</p>
</a>
</div>
</main>
)
} }

7
src/app/s/[id]/index.css Normal file
View File

@@ -0,0 +1,7 @@
.snippetDescription h1, p {
margin: 0;
}
.snippetDescription p:last-of-type {
margin-bottom: 10px;
}

29
src/app/s/[id]/page.tsx Normal file
View File

@@ -0,0 +1,29 @@
import './index.css'
import db from '@/db/index'
import * as schema from '@/db/schema'
import {eq} from "drizzle-orm";
import {redirect} from "next/navigation";
import {clerkClient} from "@clerk/nextjs";
import MonacoEditor from "@/components/MonacoEditor/MonacoEditor";
export default async function Page({ params }: { params: { id: string } }) {
const query = await db.query.code.findFirst({
where: eq(schema.code.id, params.id)
}).execute()
if (!query) {
redirect('/s-notfound')
}
const user = await clerkClient.users.getUser(query.authorId)
return (
<div>
<div className='snippetDescription'>
<div style={{ display: 'flex' }}>
<h1>File {query.fileName} by {user.username}</h1>
</div>
<p>Description: {query.description}</p>
</div>
<MonacoEditor readOnly={true} defaultValue={query.code} defaultLanguage={query.lang} />
</div>
)
}

View File

@@ -0,0 +1,79 @@
'use client'
import {Button, Select, TextInput} from "@mantine/core";
import MonacoEditor from "@/components/MonacoEditor/MonacoEditor";
import { useRouter } from "next-nprogress-bar";
import {useUser} from "@clerk/nextjs";
import {useEffect, useState} from "react";
import {ICreateApiRequest} from "@/utils/types";
import {notifications} from "@mantine/notifications";
import {createSnippet} from "@/components/CreateSnippet/createSnippet";
import styles from './index.module.css';
export default function CreateSnippet() {
const user = useUser().user!
const router = useRouter()
const [buttonLoading, setButtonLoading] = useState(false)
const [editorText, setEditorText] = useState('')
const [snippet, setSnippet] = useState<ICreateApiRequest>({
fileName: '',
description: '',
authorId: user.id,
lang: 'typescript',
code: ''
})
useEffect(() => {
setSnippet({ ...snippet, code: editorText })
console.log(snippet)
}, [editorText]);
return (
<main>
<div className={styles.inputs}>
<TextInput
placeholder={'The file name of your snippet'}
style={{ flexGrow: .1 }}
onChange={(ev) => setSnippet({ ...snippet, fileName: ev.currentTarget.value })}
/>
<TextInput
placeholder={'Brief description of your snippet'}
style={{ flexGrow: 4 }}
onChange={(ev) => setSnippet({ ...snippet, description: ev.currentTarget.value })}
/>
<Select
placeholder="Select language"
data={['Typescript', 'Javascript']}
onChange={(ev) => setSnippet({ ...snippet, lang: ev as "javascript" | "typescript" })}
/>
<Button
variant={'filled'}
style={{ flexGrow: .1 }}
color={'blue'}
loading={buttonLoading}
onClick={() => {
setButtonLoading(true)
createSnippet(snippet).catch(e => {
if (e.message === 'fillFields') {
notifications.show({
title: 'Something went wrong',
message: 'Please fill in all the fields',
color: 'red',
})
}
}).then(id => {
if (id) {
router.push(`/s/${id}`)
}
})
setButtonLoading(false)
}}
>
Create
</Button>
</div>
<div className={styles.monaco}>
<MonacoEditor setEditorText={setEditorText} readOnly={false} />
</div>
</main>
)
}

View File

@@ -0,0 +1,20 @@
'use server'
import {ICreateApiRequest} from "@/utils/types";
import db from "@/db";
import * as schema from "@/db/schema";
import {randomString} from "util-utils";
export async function createSnippet(snippet: ICreateApiRequest) {
if (Object.values(snippet).some(e => typeof e === 'string' && e.trim() === '')) throw new Error('fillFields');
const query = await db
.insert(schema.code)
.values({
id: randomString(8),
...snippet
})
.returning({
snippetId: schema.code.id
})
.execute();
return query[0].snippetId;
}

View File

@@ -0,0 +1,13 @@
.inputs {
margin-bottom: 20px;
display: flex;
gap: 5px;
width: 100%;
}
.monaco {
width: 100%;
height: 100%;
border-radius: 5px;
border: 1px solid #ccc;
}

View File

@@ -0,0 +1,29 @@
'use client'
import {Editor} from "@monaco-editor/react";
import {useUser} from "@clerk/nextjs";
export default function MonacoEditor(props: Props) {
const clerk = useUser()
return clerk.isLoaded && <Editor
height={props.height || '40vw'}
options={{
readOnly: props.readOnly,
}}
defaultLanguage={props.defaultLanguage || 'typescript'}
onChange={(ev) => {
if (props.setEditorText) {
props.setEditorText(ev!)
}
}}
defaultValue={props.defaultValue}
/>
}
type Props = {
setEditorText?: (value: string) => void;
readOnly: boolean;
height?: string;
defaultValue?: string;
defaultLanguage?: string;
};

View File

@@ -0,0 +1,74 @@
'use client'
import { useState } from 'react';
import {
Container,
Group,
Menu,
Burger,
} from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import classes from './index.module.css';
import {UserButton} from "@clerk/nextjs";
import Image from "next/image";
import Logo from '../../../public/logo.png';
import Link from 'next/link';
import {useRouter} from "next-nprogress-bar";
import {usePathname} from "next/navigation";
/*const user = {
name: 'Jane Spoonfighter',
email: 'janspoon@fighter.dev',
image: 'https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/avatars/avatar-5.png',
};*/
const links = [
{ link: '/', label: 'Create' },
{ link: '/me', label: 'My snippets' },
];
export default function NavBar() {
const router = useRouter()
const pathname = usePathname()
const [opened, { toggle }] = useDisclosure(false);
const [active, setActive] = useState(links.filter((link) => link.link === pathname)[0]?.link);
const items = links.map((link) => (
<Link
key={link.label}
href={link.link}
className={classes.link}
data-active={active === link.link || undefined}
onClick={(event) => {
event.preventDefault();
setActive(link.link);
router.push(link.link)
}}
>
{link.label}
</Link>
));
return (
<div className={classes.header}>
<Container size="md">
<Group justify="space-between">
<Image src={Logo} alt={'Logo'} height={'35'} />
<Container size="md" className={classes.inner}>
<Group gap={5} visibleFrom="xs">
{items}
</Group>
<Burger opened={opened} onClick={toggle} hiddenFrom="xs" size="sm" />
</Container>
<Menu
width={260}
position="bottom-end"
withinPortal
>
<Menu.Target>
<UserButton />
</Menu.Target>
</Menu>
</Group>
</Container>
</div>
);
}

View File

@@ -0,0 +1,40 @@
.header {
height: rem(56px);
background-color: var(--mantine-color-body);
border-bottom: rem(1px) solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
position: sticky;
margin-bottom: 5px;
}
.tabsList {
&::before {
display: none;
}
}
.inner {
height: rem(56px);
display: flex;
justify-content: space-between;
align-items: center;
}
.link {
display: block;
line-height: 1;
padding: rem(8px) rem(12px);
border-radius: var(--mantine-radius-sm);
text-decoration: none;
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-0));
font-size: var(--mantine-font-size-sm);
font-weight: 500;
@mixin hover {
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6));
}
[data-mantine-color-scheme] &[data-active] {
background-color: var(--mantine-color-blue-filled);
color: var(--mantine-color-white);
}
}

View File

@@ -0,0 +1,10 @@
import {SignIn} from "@clerk/nextjs";
export default function SignInPrompt() {
return (
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', flexDirection: 'column' }}>
<h1>Hewwo! Please sign in first!</h1>
<SignIn />
</div>
)
}

11
src/db/index.ts Normal file
View File

@@ -0,0 +1,11 @@
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
import * as schema from './schema';
export * as schema from './schema';
import dotenv from 'dotenv'
dotenv.config({
path: '.env.local'
})
const client = postgres(process.env.DATABASE_URL!);
export default drizzle(client, { schema });

19
src/db/migrations.mts Normal file
View File

@@ -0,0 +1,19 @@
import { drizzle } from "drizzle-orm/postgres-js/driver";
import { migrate } from "drizzle-orm/postgres-js/migrator";
import postgres from "postgres";
import dotenv from 'dotenv'
dotenv.config({
path: '.env.local'
})
// this will automatically run needed migrations on the database
const migrationClient = postgres(process.env.DATABASE_URL!, { max: 1 });
migrate(drizzle(migrationClient), { migrationsFolder: "./drizzle" })
.then(() => {
console.log("Migrations complete!");
process.exit(0);
})
.catch((err) => {
console.error("Migrations failed!", err);
process.exit(1);
});

13
src/db/schema.ts Normal file
View File

@@ -0,0 +1,13 @@
import {
pgTable,
text,
} from "drizzle-orm/pg-core";
export const code = pgTable('code', {
id: text('id').primaryKey(),
fileName: text('fileName').notNull(),
code: text('code').notNull(),
description: text('description').notNull(),
lang: text('lang').notNull(),
authorId: text('authorId').notNull()
})

12
src/middleware.ts Normal file
View File

@@ -0,0 +1,12 @@
import { authMiddleware } from "@clerk/nextjs";
// This example protects all routes including api/trpc routes
// Please edit this to allow other routes to be public as needed.
// See https://clerk.com/docs/references/nextjs/auth-middleware for more information about configuring your Middleware
export default authMiddleware({
publicRoutes: ['/']
});
export const config = {
matcher: ["/((?!.+\\.[\\w]+$|_next).*)", "/(api|trpc)(.*)"],
};

18
src/providers.tsx Normal file
View File

@@ -0,0 +1,18 @@
'use client';
import { AppProgressBar as ProgressBar } from 'next-nprogress-bar';
const Providers = ({ children }: { children: React.ReactNode }) => {
return (
<>
{children}
<ProgressBar
height="4px"
color="#fffd00"
options={{ showSpinner: false }}
/>
</>
)
};
export default Providers;

9
src/utils/types.ts Normal file
View File

@@ -0,0 +1,9 @@
import { z } from 'zod';
export const ZCreateApiRequest = z.object({
fileName: z.string(),
description: z.string(),
authorId: z.string(),
lang: z.enum(['javascript', 'typescript']),
code: z.string(),
});
export type ICreateApiRequest = z.infer<typeof ZCreateApiRequest>;

View File

@@ -1,6 +1,6 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "es5", "target": "es6",
"lib": ["dom", "dom.iterable", "esnext"], "lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,

1482
yarn.lock

File diff suppressed because it is too large Load Diff