diff --git a/playgrounds/app/package.json b/playgrounds/app/package.json index 4510bf7..409d322 100644 --- a/playgrounds/app/package.json +++ b/playgrounds/app/package.json @@ -37,6 +37,7 @@ "solid-color": "^0.0.4", "solid-icons": "^1.1.0", "solid-js": "^1.9.1", + "solid-sonner": "^0.2.8", "tailwind-merge": "^2.5.3", "tailwindcss": "^3.4.3", "tailwindcss-animate": "^1.0.7", diff --git a/playgrounds/app/src/app.tsx b/playgrounds/app/src/app.tsx index 3b11c8d..0a667b6 100644 --- a/playgrounds/app/src/app.tsx +++ b/playgrounds/app/src/app.tsx @@ -1,6 +1,7 @@ +import { Suspense } from 'solid-js' import { Router } from '@solidjs/router' import { FileRoutes } from '@solidjs/start/router' -import { Suspense } from 'solid-js' +import { Toaster } from 'solid-sonner' import Header from '~/components/Header' import Footer from './components/Footer' import '@fontsource/bungee-inline' @@ -15,6 +16,7 @@ export default function App() { {props.children} + > )} > diff --git a/playgrounds/app/src/components/Editor.tsx b/playgrounds/app/src/components/Editor.tsx index dae7cc7..a5abef7 100644 --- a/playgrounds/app/src/components/Editor.tsx +++ b/playgrounds/app/src/components/Editor.tsx @@ -14,7 +14,12 @@ import { } from '~/components/ui/combobox' import { Button } from '~/components/ui/button' import { Tabs, TabsContent, TabsList, TabsTrigger } from '~/components/ui/tabs' -import { TextField, TextFieldLabel, TextFieldTextArea } from '~/components/ui/text-field' +import { + TextField, + TextFieldInput, + TextFieldLabel, + TextFieldTextArea, +} from '~/components/ui/text-field' import { MagicMoveElement } from 'shiki-magic-move/types' import { DropdownMenu, @@ -50,12 +55,17 @@ import { createMemo, createResource, createSignal, onCleanup, Setter, Show } fro import { createHighlighter, bundledThemes, bundledLanguages } from 'shiki' import { ShikiMagicMove } from 'shiki-magic-move/solid' import { AnimationFrameConfig } from '~/types' +import { authFetch } from '~/lib/utils' +import { useNavigate } from '@solidjs/router' +import { authToken } from '~/lib/store' +import { toast } from 'solid-sonner' const animationSeconds = 1 const animationFPS = 10 const animationFrames = animationSeconds * animationFPS interface EditorProps { + snippetId?: string startCode: string setStartCode: Setter endCode: string @@ -82,9 +92,12 @@ interface EditorProps { setLanguage: Setter theme: string setTheme: Setter + // TODO: If the app grows, this logic should be surfaced to the top level route + title: string } export default function Editor(props: EditorProps) { + const navigate = useNavigate() const [selectedTab, setSelectedTab] = createSignal<'snippets' | 'output'>('snippets') const [toggled, setToggled] = createSignal(false) @@ -100,6 +113,8 @@ export default function Editor(props: EditorProps) { const [isGenerating, setIsGenerating] = createSignal(false) const [gifDataUrl, setGifDataUrl] = createSignal('') const [isShowingGifDialog, setIsShowingGifDialog] = createSignal(false) + const [title, setTitle] = createSignal(props.title) + const [isSaving, setIsSaving] = createSignal(false) const [highlighter] = createResource(async () => { const newHighlighter = await createHighlighter({ @@ -492,6 +507,7 @@ export default function Editor(props: EditorProps) { { setIsGenerating(true) setHiddenCode(props.endCode) @@ -517,8 +533,9 @@ export default function Editor(props: EditorProps) { }} > Preview - + + + {/* TODO: If the app grows, this logic should be surfaced to the top level route */} + + + + setTitle(e.currentTarget.value)} + /> + + { + setIsSaving(true) + const body = JSON.stringify({ + title: title(), + codeLeft: props.startCode, + codeRight: props.endCode, + snippetWidth: props.snippetWidth, + yPadding: props.yPadding, + xPadding: props.xPadding, + shadowEnabled: props.shadowEnabled, + shadowOffsetY: props.shadowOffsetY, + shadowBlur: props.shadowBlur, + shadowColor: props.shadowColor, + shadowOpacity: props.shadowOpacity, + bgColor: props.bgColor, + language: props.language, + theme: props.theme, + }) + + let url = '/api/snippets' + let method = 'POST' + + if (props.snippetId) { + url = `/api/snippets/${props.snippetId}` + method = 'PUT' + } + + const result = await authFetch(url, { + method, + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + body, + }) + + if (result.ok) { + const newSnippet = await result.json() + navigate(`/snippets/${newSnippet.id}`) + } else { + // notify with a toast + toast.error('Error creating Snippet') + } + + setIsSaving(false) + }} + > + {isSaving() ? 'Saving...' : 'Save'} + + + diff --git a/playgrounds/app/src/components/SnippetPreview.tsx b/playgrounds/app/src/components/SnippetPreview.tsx new file mode 100644 index 0000000..ea5f631 --- /dev/null +++ b/playgrounds/app/src/components/SnippetPreview.tsx @@ -0,0 +1,33 @@ +import { Highlighter } from 'shiki' +import { ShikiMagicMove } from 'shiki-magic-move/solid' +import { Snippet } from '~/types' + +interface SnippetPreviewProps { + highlighter: Highlighter + snippet: Snippet +} + +export function SnippetPreview(props: SnippetPreviewProps) { + return ( + + + + ) +} diff --git a/playgrounds/app/src/lib/middleware.ts b/playgrounds/app/src/lib/middleware.ts index b1364a3..d7cee9a 100644 --- a/playgrounds/app/src/lib/middleware.ts +++ b/playgrounds/app/src/lib/middleware.ts @@ -7,20 +7,21 @@ import type { APIEvent } from '@solidjs/start/server' export async function getUser({ request }: APIEvent) { const token = request.headers.get('Authorization')?.replace('Bearer ', '') - console.log({ token }) - if (!token) { return null } - const decoded = await decodeToken(token) - - console.log({ decoded }) + try { + const decoded = await decodeToken(token) - const user = await db.query.users.findFirst({ - // @ts-ignore - where: eq(usersTable.id, decoded?.sub?.userId), - }) + const user = await db.query.users.findFirst({ + // @ts-ignore + where: eq(usersTable.id, decoded?.sub?.id), + }) - return user + return user + } catch (error) { + console.error('Error getting user:', error) // Handle the error + return null + } } diff --git a/playgrounds/app/src/lib/utils.ts b/playgrounds/app/src/lib/utils.ts index d220233..cd5a395 100644 --- a/playgrounds/app/src/lib/utils.ts +++ b/playgrounds/app/src/lib/utils.ts @@ -56,8 +56,6 @@ export async function getGithubContributors(owner: string, repo: string): Promis } export async function authFetch(url: string, options?: RequestInit) { - const navigate = useNavigate() - const response = await fetch(url, { ...options, headers: { @@ -67,7 +65,7 @@ export async function authFetch(url: string, options?: RequestInit) { }) if (!response.ok && response.status === 401) { - navigate('/logged-out') + location.href = '/logged-out' } return response diff --git a/playgrounds/app/src/routes/api/oauth.ts b/playgrounds/app/src/routes/api/oauth.ts index 8b7d730..461caed 100644 --- a/playgrounds/app/src/routes/api/oauth.ts +++ b/playgrounds/app/src/routes/api/oauth.ts @@ -9,8 +9,6 @@ import { encodeAccessToken } from '~/lib/jwt' export async function POST(event: APIEvent) { const { code } = await event.request.json() - console.log('client id', process.env.VITE_GITHUB_CLIENT_ID) - const githubResponse = await fetch('https://github.com/login/oauth/access_token', { method: 'POST', headers: { @@ -37,8 +35,6 @@ export async function POST(event: APIEvent) { const githubUser = await githubUserResponse.json() - console.log({ githubUser }) - let user = await db.query.users.findFirst({ where: eq(usersTable.githubId, githubUser.id), }) diff --git a/playgrounds/app/src/routes/api/snippets.ts b/playgrounds/app/src/routes/api/snippets.ts index ecec5ec..f6135b6 100644 --- a/playgrounds/app/src/routes/api/snippets.ts +++ b/playgrounds/app/src/routes/api/snippets.ts @@ -34,6 +34,19 @@ export async function POST(event: APIEvent) { }) } + const requestBody = await event.request.json() + + const isValid = snippetValidator.safeParse(requestBody) + + if (!isValid.success) { + return new Response(JSON.stringify(isValid.error.flatten()), { + status: 400, + headers: { + 'Content-Type': 'application/json', + }, + }) + } + const { title, codeLeft, @@ -49,18 +62,7 @@ export async function POST(event: APIEvent) { bgColor, language, theme, - } = await event.request.json() - - const isValid = snippetValidator.safeParse({ title, codeLeft, codeRight }) - - if (!isValid.success) { - return new Response(JSON.stringify(isValid.error.flatten()), { - status: 400, - headers: { - 'Content-Type': 'application/json', - }, - }) - } + } = requestBody const newSnippet = { id: customNanoid(), @@ -83,6 +85,8 @@ export async function POST(event: APIEvent) { theme, } + console.log({ newSnippet }) + await db.insert(snippetsTable).values(newSnippet) return new Response(JSON.stringify(newSnippet), { diff --git a/playgrounds/app/src/routes/api/snippets/[snippetId].ts b/playgrounds/app/src/routes/api/snippets/[snippetId].ts index 1875a4b..8a9064c 100644 --- a/playgrounds/app/src/routes/api/snippets/[snippetId].ts +++ b/playgrounds/app/src/routes/api/snippets/[snippetId].ts @@ -30,15 +30,12 @@ export async function GET(event: APIEvent) { }) } - return new Response( - JSON.stringify(snippet), - { - status: 200, - headers: { - 'Content-Type': 'application/json', - }, + return new Response(JSON.stringify(snippet), { + status: 200, + headers: { + 'Content-Type': 'application/json', }, - ) + }) } export async function PUT(event: APIEvent) { @@ -130,3 +127,36 @@ export async function PUT(event: APIEvent) { }, }) } + +export async function DELETE(event: APIEvent) { + const user = await getUser(event) + if (!user) { + return new Response(null, { + status: 401, + }) + } + + const snippetId = event.params.snippetId + + if (!snippetId) { + return new Response(null, { + status: 404, + }) + } + + const snippet = await db.query.snippets.findFirst({ + where: eq(snippetsTable.id, snippetId), + }) + + if (!snippet || snippet?.userId !== user.id) { + return new Response(null, { + status: 404, + }) + } + + await db.delete(snippetsTable).where(eq(snippetsTable.id, snippetId)) + + return new Response(null, { + status: 204, + }) +} diff --git a/playgrounds/app/src/routes/oauth.tsx b/playgrounds/app/src/routes/oauth.tsx index 5e1f9c0..9e3db2b 100644 --- a/playgrounds/app/src/routes/oauth.tsx +++ b/playgrounds/app/src/routes/oauth.tsx @@ -30,8 +30,6 @@ export default function OAuth() { const data = await response.json() - console.log(data) - setAuthToken(data.jwt) setUser(data.user) diff --git a/playgrounds/app/src/routes/snippets/[snippetId].tsx b/playgrounds/app/src/routes/snippets/[snippetId].tsx index 06a1341..b103535 100644 --- a/playgrounds/app/src/routes/snippets/[snippetId].tsx +++ b/playgrounds/app/src/routes/snippets/[snippetId].tsx @@ -14,7 +14,7 @@ export default function ViewSnippet({ params }: { params: { snippetId: string } }) return ( - + {}} theme={snippet()!.theme} setTheme={() => {}} + title={snippet()!.title} /> diff --git a/playgrounds/app/src/routes/snippets/index.tsx b/playgrounds/app/src/routes/snippets/index.tsx index 91e2e9d..3305343 100644 --- a/playgrounds/app/src/routes/snippets/index.tsx +++ b/playgrounds/app/src/routes/snippets/index.tsx @@ -1,4 +1,7 @@ +import { A } from '@solidjs/router' +import { createHighlighter, bundledThemes, bundledLanguages } from 'shiki' import { createResource, Show } from 'solid-js' +import { SnippetPreview } from '~/components/SnippetPreview' import { authFetch } from '~/lib/utils' import { Snippet } from '~/types' @@ -9,7 +12,16 @@ export default function Snippets() { return [] } const data = await response.json() - return data + return data.snippets || [] + }) + + const [highlighter] = createResource(async () => { + const newHighlighter = await createHighlighter({ + themes: Object.keys(bundledThemes), + langs: Object.keys(bundledLanguages), + }) + + return newHighlighter }) return ( @@ -17,10 +29,15 @@ export default function Snippets() { Snippets Loading... Error: {snippets.error.message} - - {snippets.latest?.map(snippet => ( - {snippet.title} - ))} + + + {snippets.latest?.map(snippet => ( + + + {snippet.title} + + ))} + No snippets found diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 90a1a3f..b1bbffd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -255,6 +255,9 @@ importers: solid-js: specifier: ^1.9.1 version: 1.9.2 + solid-sonner: + specifier: ^0.2.8 + version: 0.2.8(solid-js@1.9.2) tailwind-merge: specifier: ^2.5.3 version: 2.5.3 @@ -5873,6 +5876,11 @@ packages: peerDependencies: solid-js: ^1.3 + solid-sonner@0.2.8: + resolution: {integrity: sha512-EQ2EIznvHHpAmkYh2CTu0AdCgmPJRJWLGFRWygE8j+vMEfvIV2wotHU5qgWzqzVTG1SODGsay2Lwq6ENWx/rPA==} + peerDependencies: + solid-js: ^1.6.0 + solid-use@0.9.0: resolution: {integrity: sha512-8TGwB4m3qQ7qKo8Lg0pi/ZyyGVmQIjC4sPyxRCH7VPds0BzSsT734PhP3jhR6zMJxoYHM+uoivjq0XdpzXeOJg==} engines: {node: '>=10'} @@ -12911,6 +12919,10 @@ snapshots: transitivePeerDependencies: - supports-color + solid-sonner@0.2.8(solid-js@1.9.2): + dependencies: + solid-js: 1.9.2 + solid-use@0.9.0(solid-js@1.9.2): dependencies: solid-js: 1.9.2
Preview
No snippets found