mirror of
https://github.com/johndoe6345789/snippet-pastebin.git
synced 2026-04-24 13:34:55 +00:00
Convert to Next.js - core setup complete
Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com>
This commit is contained in:
10
.env.example
10
.env.example
@@ -1,8 +1,12 @@
|
|||||||
# Frontend Configuration
|
# Frontend Configuration
|
||||||
# Flask Backend URL - If set, the app will automatically use Flask backend instead of IndexedDB
|
# Flask Backend URL - If set, the app will automatically use Flask backend instead of IndexedDB
|
||||||
# Development: VITE_FLASK_BACKEND_URL=http://localhost:5000
|
# Development: NEXT_PUBLIC_FLASK_BACKEND_URL=http://localhost:5000
|
||||||
# Production: VITE_FLASK_BACKEND_URL=https://backend.example.com
|
# Production: NEXT_PUBLIC_FLASK_BACKEND_URL=https://backend.example.com
|
||||||
VITE_FLASK_BACKEND_URL=
|
NEXT_PUBLIC_FLASK_BACKEND_URL=
|
||||||
|
|
||||||
|
# Base path for deployment (e.g., for GitHub Pages)
|
||||||
|
# Leave empty for root deployment
|
||||||
|
NEXT_PUBLIC_BASE_PATH=
|
||||||
|
|
||||||
# Backend Configuration (for backend/app.py)
|
# Backend Configuration (for backend/app.py)
|
||||||
# CORS Allowed Origins - Comma-separated list of allowed frontend URLs
|
# CORS Allowed Origins - Comma-separated list of allowed frontend URLs
|
||||||
|
|||||||
3
.eslintrc.json
Normal file
3
.eslintrc.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": ["next/core-web-vitals", "next/typescript"]
|
||||||
|
}
|
||||||
9
.github/workflows/deploy-pages.yml
vendored
9
.github/workflows/deploy-pages.yml
vendored
@@ -31,11 +31,12 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
- name: Build
|
- name: Build Next.js
|
||||||
run: npm run build
|
run: npm run build
|
||||||
env:
|
env:
|
||||||
VITE_FLASK_BACKEND_URL: ${{ vars.VITE_FLASK_BACKEND_URL || '' }}
|
BUILD_STATIC: 'true'
|
||||||
VITE_BASE_PATH: ${{ vars.VITE_BASE_PATH || '/' }}
|
NEXT_PUBLIC_FLASK_BACKEND_URL: ${{ vars.NEXT_PUBLIC_FLASK_BACKEND_URL || '' }}
|
||||||
|
NEXT_PUBLIC_BASE_PATH: ${{ vars.NEXT_PUBLIC_BASE_PATH || '' }}
|
||||||
|
|
||||||
- name: Setup Pages
|
- name: Setup Pages
|
||||||
uses: actions/configure-pages@v4
|
uses: actions/configure-pages@v4
|
||||||
@@ -43,7 +44,7 @@ jobs:
|
|||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-pages-artifact@v3
|
uses: actions/upload-pages-artifact@v3
|
||||||
with:
|
with:
|
||||||
path: './dist'
|
path: './out'
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
4
.github/workflows/docker-publish.yml
vendored
4
.github/workflows/docker-publish.yml
vendored
@@ -117,5 +117,5 @@ jobs:
|
|||||||
cache-from: type=gha,scope=frontend
|
cache-from: type=gha,scope=frontend
|
||||||
cache-to: type=gha,mode=max,scope=frontend
|
cache-to: type=gha,mode=max,scope=frontend
|
||||||
build-args: |
|
build-args: |
|
||||||
VITE_FLASK_BACKEND_URL=${{ vars.VITE_FLASK_BACKEND_URL || '' }}
|
NEXT_PUBLIC_FLASK_BACKEND_URL=${{ vars.NEXT_PUBLIC_FLASK_BACKEND_URL || '' }}
|
||||||
VITE_BASE_PATH=${{ vars.VITE_BASE_PATH || '/' }}
|
NEXT_PUBLIC_BASE_PATH=${{ vars.NEXT_PUBLIC_BASE_PATH || '' }}
|
||||||
|
|||||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -13,6 +13,12 @@ dist-ssr
|
|||||||
*-dist
|
*-dist
|
||||||
*.local
|
*.local
|
||||||
|
|
||||||
|
# Next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
next-env.d.ts
|
||||||
|
.vercel
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.vscode/*
|
.vscode/*
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
@@ -25,6 +31,8 @@ dist-ssr
|
|||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
.env
|
.env
|
||||||
|
.env.local
|
||||||
|
.env*.local
|
||||||
**/agent-eval-report*
|
**/agent-eval-report*
|
||||||
packages
|
packages
|
||||||
pids
|
pids
|
||||||
|
|||||||
29
Dockerfile
29
Dockerfile
@@ -3,26 +3,37 @@ FROM node:20-alpine AS builder
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy package files
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
RUN npm ci
|
RUN npm ci
|
||||||
|
|
||||||
|
# Copy source code
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
ARG VITE_FLASK_BACKEND_URL
|
# Build arguments for environment variables
|
||||||
ENV VITE_FLASK_BACKEND_URL=$VITE_FLASK_BACKEND_URL
|
ARG NEXT_PUBLIC_FLASK_BACKEND_URL
|
||||||
|
ARG NEXT_PUBLIC_BASE_PATH
|
||||||
ARG VITE_BASE_PATH
|
ENV NEXT_PUBLIC_FLASK_BACKEND_URL=$NEXT_PUBLIC_FLASK_BACKEND_URL
|
||||||
ENV VITE_BASE_PATH=$VITE_BASE_PATH
|
ENV NEXT_PUBLIC_BASE_PATH=$NEXT_PUBLIC_BASE_PATH
|
||||||
|
|
||||||
|
# Build Next.js app
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
# Production stage
|
# Production stage
|
||||||
FROM nginx:alpine
|
FROM node:20-alpine AS runner
|
||||||
|
|
||||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
WORKDIR /app
|
||||||
|
|
||||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
|
# Copy necessary files from builder
|
||||||
|
COPY --from=builder /app/public ./public
|
||||||
|
COPY --from=builder /app/.next/standalone ./
|
||||||
|
COPY --from=builder /app/.next/static ./.next/static
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
ENV PORT=3000
|
||||||
|
ENV HOSTNAME="0.0.0.0"
|
||||||
|
|
||||||
|
CMD ["node", "server.js"]
|
||||||
|
|||||||
89
app/PageLayout.tsx
Normal file
89
app/PageLayout.tsx
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { Code } from '@phosphor-icons/react';
|
||||||
|
import { Navigation } from '@/components/layout/navigation/Navigation';
|
||||||
|
import { NavigationSidebar } from '@/components/layout/navigation/NavigationSidebar';
|
||||||
|
import { useNavigation } from '@/components/layout/navigation/useNavigation';
|
||||||
|
import { BackendIndicator } from '@/components/layout/BackendIndicator';
|
||||||
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
export function PageLayout({ children }: { children: ReactNode }) {
|
||||||
|
const { menuOpen } = useNavigation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-background">
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 opacity-[0.03] pointer-events-none"
|
||||||
|
style={{
|
||||||
|
backgroundImage: `
|
||||||
|
repeating-linear-gradient(
|
||||||
|
0deg,
|
||||||
|
transparent,
|
||||||
|
transparent 40px,
|
||||||
|
oklch(0.75 0.18 200) 40px,
|
||||||
|
oklch(0.75 0.18 200) 41px
|
||||||
|
),
|
||||||
|
repeating-linear-gradient(
|
||||||
|
90deg,
|
||||||
|
transparent,
|
||||||
|
transparent 40px,
|
||||||
|
oklch(0.75 0.18 200) 40px,
|
||||||
|
oklch(0.75 0.18 200) 41px
|
||||||
|
)
|
||||||
|
`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<NavigationSidebar />
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
initial={false}
|
||||||
|
animate={{ marginLeft: menuOpen ? 320 : 0 }}
|
||||||
|
transition={{ type: 'spring', damping: 30, stiffness: 300 }}
|
||||||
|
className="relative z-10"
|
||||||
|
>
|
||||||
|
<header className="border-b border-border bg-background/90 backdrop-blur-md sticky top-0 z-20">
|
||||||
|
<div className="container mx-auto px-6 py-6">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, x: -20 }}
|
||||||
|
animate={{ opacity: 1, x: 0 }}
|
||||||
|
transition={{ duration: 0.4 }}
|
||||||
|
className="flex items-center gap-3"
|
||||||
|
>
|
||||||
|
<Navigation />
|
||||||
|
<div className="h-10 w-10 rounded-lg bg-gradient-to-br from-primary to-accent flex items-center justify-center">
|
||||||
|
<Code className="h-5 w-5 text-primary-foreground" weight="bold" />
|
||||||
|
</div>
|
||||||
|
<h1 className="text-2xl font-bold tracking-tight bg-gradient-to-r from-primary via-accent to-primary bg-clip-text text-transparent">
|
||||||
|
CodeSnippet
|
||||||
|
</h1>
|
||||||
|
</motion.div>
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, x: 20 }}
|
||||||
|
animate={{ opacity: 1, x: 0 }}
|
||||||
|
transition={{ duration: 0.4, delay: 0.1 }}
|
||||||
|
>
|
||||||
|
<BackendIndicator />
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main className="container mx-auto px-6 py-8">
|
||||||
|
{children}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer className="border-t border-border mt-24">
|
||||||
|
<div className="container mx-auto px-6 py-8">
|
||||||
|
<div className="text-center text-sm text-muted-foreground">
|
||||||
|
<p>Save, organize, and share your code snippets with beautiful syntax highlighting and live execution</p>
|
||||||
|
<p className="mt-2 text-xs">Supports React preview and Python execution via Pyodide</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
43
app/atoms/page.tsx
Normal file
43
app/atoms/page.tsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { AtomsSection } from '@/components/atoms/AtomsSection';
|
||||||
|
import type { Snippet } from '@/lib/types';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
import { createSnippet } from '@/lib/db';
|
||||||
|
import { PageLayout } from '../PageLayout';
|
||||||
|
|
||||||
|
export default function AtomsPage() {
|
||||||
|
const handleSaveSnippet = useCallback(async (snippetData: Omit<Snippet, 'id' | 'createdAt' | 'updatedAt'>) => {
|
||||||
|
try {
|
||||||
|
const newSnippet: Snippet = {
|
||||||
|
...snippetData,
|
||||||
|
id: Date.now().toString(),
|
||||||
|
createdAt: Date.now(),
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
};
|
||||||
|
await createSnippet(newSnippet);
|
||||||
|
toast.success('Component saved as snippet!');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to save snippet:', error);
|
||||||
|
toast.error('Failed to save snippet');
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageLayout>
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.4 }}
|
||||||
|
>
|
||||||
|
<div className="mb-8">
|
||||||
|
<h2 className="text-3xl font-bold tracking-tight mb-2">Atoms</h2>
|
||||||
|
<p className="text-muted-foreground">Fundamental building blocks - basic HTML elements styled as reusable components</p>
|
||||||
|
</div>
|
||||||
|
<AtomsSection onSaveSnippet={handleSaveSnippet} />
|
||||||
|
</motion.div>
|
||||||
|
</PageLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
60
app/demo/page.tsx
Normal file
60
app/demo/page.tsx
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { SplitScreenEditor } from '@/components/features/snippet-editor/SplitScreenEditor';
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { Sparkle } from '@phosphor-icons/react';
|
||||||
|
import { DEMO_CODE } from '@/pages/demo-constants';
|
||||||
|
import { DemoFeatureCards } from '@/pages/DemoFeatureCards';
|
||||||
|
import { PageLayout } from '../PageLayout';
|
||||||
|
|
||||||
|
export default function DemoPage() {
|
||||||
|
const [code, setCode] = useState(DEMO_CODE);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageLayout>
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.4 }}
|
||||||
|
className="space-y-8"
|
||||||
|
>
|
||||||
|
<div className="mb-8">
|
||||||
|
<div className="flex items-center gap-3 mb-2">
|
||||||
|
<div className="h-10 w-10 rounded-lg bg-gradient-to-br from-accent to-primary flex items-center justify-center">
|
||||||
|
<Sparkle className="h-5 w-5 text-primary-foreground" weight="fill" />
|
||||||
|
</div>
|
||||||
|
<h2 className="text-3xl font-bold tracking-tight">Split-Screen Demo</h2>
|
||||||
|
</div>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
Experience live React component editing with real-time preview. Edit the code on the left and watch it update instantly on the right.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Card className="border-accent/20 bg-card/50 backdrop-blur">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<Sparkle className="h-5 w-5 text-accent" weight="fill" />
|
||||||
|
Interactive Code Editor
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
This editor supports JSX, TSX, JavaScript, and TypeScript with live preview.
|
||||||
|
Try switching between Code, Split, and Preview modes using the buttons above the editor.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<SplitScreenEditor
|
||||||
|
value={code}
|
||||||
|
onChange={setCode}
|
||||||
|
language="JSX"
|
||||||
|
height="600px"
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<DemoFeatureCards />
|
||||||
|
</motion.div>
|
||||||
|
</PageLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
92
app/globals.css
Normal file
92
app/globals.css
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
@import 'tailwindcss';
|
||||||
|
@import "tw-animate-css";
|
||||||
|
|
||||||
|
@import '../src/styles/theme.css';
|
||||||
|
|
||||||
|
/*
|
||||||
|
The default border color has changed to `currentColor` in Tailwind CSS v4,
|
||||||
|
so we've added these compatibility styles to make sure everything still
|
||||||
|
looks the same as it did with Tailwind CSS v3.
|
||||||
|
*/
|
||||||
|
@layer base {
|
||||||
|
*,
|
||||||
|
::after,
|
||||||
|
::before,
|
||||||
|
::backdrop,
|
||||||
|
::file-selector-button {
|
||||||
|
border-color: var(--color-gray-200, currentColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
@apply border-border
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--radius: 0.5rem;
|
||||||
|
--background: oklch(0.08 0.01 265);
|
||||||
|
--foreground: oklch(0.95 0.01 265);
|
||||||
|
--card: oklch(0.15 0.01 265);
|
||||||
|
--card-foreground: oklch(0.98 0 0);
|
||||||
|
--popover: oklch(0.15 0.01 265);
|
||||||
|
--popover-foreground: oklch(0.98 0 0);
|
||||||
|
--primary: oklch(0.35 0.15 265);
|
||||||
|
--primary-foreground: oklch(0.98 0 0);
|
||||||
|
--secondary: oklch(0.25 0.01 265);
|
||||||
|
--secondary-foreground: oklch(0.95 0.01 265);
|
||||||
|
--muted: oklch(0.20 0.01 265);
|
||||||
|
--muted-foreground: oklch(0.65 0.01 265);
|
||||||
|
--accent: oklch(0.75 0.15 195);
|
||||||
|
--accent-foreground: oklch(0.15 0.01 265);
|
||||||
|
--destructive: oklch(0.577 0.245 27.325);
|
||||||
|
--destructive-foreground: oklch(0.98 0 0);
|
||||||
|
--border: oklch(0.25 0.01 265);
|
||||||
|
--input: oklch(0.28 0.02 265);
|
||||||
|
--ring: oklch(0.75 0.15 195);
|
||||||
|
--chart-1: oklch(0.70 0.20 10);
|
||||||
|
--chart-2: oklch(0.70 0.20 160);
|
||||||
|
--chart-3: oklch(0.70 0.20 200);
|
||||||
|
--chart-4: oklch(0.70 0.20 240);
|
||||||
|
--chart-5: oklch(0.70 0.20 280);
|
||||||
|
}
|
||||||
|
|
||||||
|
@theme {
|
||||||
|
--color-background: var(--background);
|
||||||
|
--color-foreground: var(--foreground);
|
||||||
|
--color-card: var(--card);
|
||||||
|
--color-card-foreground: var(--card-foreground);
|
||||||
|
--color-popover: var(--popover);
|
||||||
|
--color-popover-foreground: var(--popover-foreground);
|
||||||
|
--color-primary: var(--primary);
|
||||||
|
--color-primary-foreground: var(--primary-foreground);
|
||||||
|
--color-secondary: var(--secondary);
|
||||||
|
--color-secondary-foreground: var(--secondary-foreground);
|
||||||
|
--color-muted: var(--muted);
|
||||||
|
--color-muted-foreground: var(--muted-foreground);
|
||||||
|
--color-accent: var(--accent);
|
||||||
|
--color-accent-foreground: var(--accent-foreground);
|
||||||
|
--color-destructive: var(--destructive);
|
||||||
|
--color-destructive-foreground: var(--destructive-foreground);
|
||||||
|
--color-border: var(--border);
|
||||||
|
--color-input: var(--input);
|
||||||
|
--color-ring: var(--ring);
|
||||||
|
|
||||||
|
--radius-sm: calc(var(--radius) * 0.5);
|
||||||
|
--radius-md: var(--radius);
|
||||||
|
--radius-lg: calc(var(--radius) * 1.5);
|
||||||
|
--radius-xl: calc(var(--radius) * 2);
|
||||||
|
--radius-2xl: calc(var(--radius) * 3);
|
||||||
|
--radius-full: 9999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
font-family: var(--font-inter), sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: var(--font-inter), sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
code, pre {
|
||||||
|
font-family: var(--font-jetbrains-mono), monospace;
|
||||||
|
}
|
||||||
40
app/layout.tsx
Normal file
40
app/layout.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import type { Metadata } from 'next';
|
||||||
|
import { Inter, JetBrains_Mono, Bricolage_Grotesque } from 'next/font/google';
|
||||||
|
import './globals.css';
|
||||||
|
import { Providers } from './providers';
|
||||||
|
|
||||||
|
const inter = Inter({
|
||||||
|
subsets: ['latin'],
|
||||||
|
variable: '--font-inter',
|
||||||
|
});
|
||||||
|
|
||||||
|
const jetbrainsMono = JetBrains_Mono({
|
||||||
|
subsets: ['latin'],
|
||||||
|
variable: '--font-jetbrains-mono',
|
||||||
|
});
|
||||||
|
|
||||||
|
const bricolageGrotesque = Bricolage_Grotesque({
|
||||||
|
subsets: ['latin'],
|
||||||
|
variable: '--font-bricolage-grotesque',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'CodeSnippet - Share & Run Code (Python, React & More)',
|
||||||
|
description: 'Save, organize, and share your code snippets with beautiful syntax highlighting and live execution',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}>) {
|
||||||
|
return (
|
||||||
|
<html lang="en" suppressHydrationWarning>
|
||||||
|
<body className={`${inter.variable} ${jetbrainsMono.variable} ${bricolageGrotesque.variable}`}>
|
||||||
|
<Providers>
|
||||||
|
{children}
|
||||||
|
</Providers>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
43
app/molecules/page.tsx
Normal file
43
app/molecules/page.tsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { MoleculesSection } from '@/components/molecules/MoleculesSection';
|
||||||
|
import type { Snippet } from '@/lib/types';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
import { createSnippet } from '@/lib/db';
|
||||||
|
import { PageLayout } from '../PageLayout';
|
||||||
|
|
||||||
|
export default function MoleculesPage() {
|
||||||
|
const handleSaveSnippet = useCallback(async (snippetData: Omit<Snippet, 'id' | 'createdAt' | 'updatedAt'>) => {
|
||||||
|
try {
|
||||||
|
const newSnippet: Snippet = {
|
||||||
|
...snippetData,
|
||||||
|
id: Date.now().toString(),
|
||||||
|
createdAt: Date.now(),
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
};
|
||||||
|
await createSnippet(newSnippet);
|
||||||
|
toast.success('Component saved as snippet!');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to save snippet:', error);
|
||||||
|
toast.error('Failed to save snippet');
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageLayout>
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.4 }}
|
||||||
|
>
|
||||||
|
<div className="mb-8">
|
||||||
|
<h2 className="text-3xl font-bold tracking-tight mb-2">Molecules</h2>
|
||||||
|
<p className="text-muted-foreground">Simple combinations of atoms that work together as functional units</p>
|
||||||
|
</div>
|
||||||
|
<MoleculesSection onSaveSnippet={handleSaveSnippet} />
|
||||||
|
</motion.div>
|
||||||
|
</PageLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
43
app/organisms/page.tsx
Normal file
43
app/organisms/page.tsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { OrganismsSection } from '@/components/organisms/OrganismsSection';
|
||||||
|
import type { Snippet } from '@/lib/types';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
import { createSnippet } from '@/lib/db';
|
||||||
|
import { PageLayout } from '../PageLayout';
|
||||||
|
|
||||||
|
export default function OrganismsPage() {
|
||||||
|
const handleSaveSnippet = useCallback(async (snippetData: Omit<Snippet, 'id' | 'createdAt' | 'updatedAt'>) => {
|
||||||
|
try {
|
||||||
|
const newSnippet: Snippet = {
|
||||||
|
...snippetData,
|
||||||
|
id: Date.now().toString(),
|
||||||
|
createdAt: Date.now(),
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
};
|
||||||
|
await createSnippet(newSnippet);
|
||||||
|
toast.success('Component saved as snippet!');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to save snippet:', error);
|
||||||
|
toast.error('Failed to save snippet');
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageLayout>
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.4 }}
|
||||||
|
>
|
||||||
|
<div className="mb-8">
|
||||||
|
<h2 className="text-3xl font-bold tracking-tight mb-2">Organisms</h2>
|
||||||
|
<p className="text-muted-foreground">Complex UI components composed of molecules and atoms</p>
|
||||||
|
</div>
|
||||||
|
<OrganismsSection onSaveSnippet={handleSaveSnippet} />
|
||||||
|
</motion.div>
|
||||||
|
</PageLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
23
app/page.tsx
Normal file
23
app/page.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { SnippetManagerRedux } from '@/components/SnippetManagerRedux';
|
||||||
|
import { PageLayout } from './PageLayout';
|
||||||
|
|
||||||
|
export default function HomePage() {
|
||||||
|
return (
|
||||||
|
<PageLayout>
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.4 }}
|
||||||
|
>
|
||||||
|
<div className="mb-8">
|
||||||
|
<h2 className="text-3xl font-bold tracking-tight mb-2">My Snippets</h2>
|
||||||
|
<p className="text-muted-foreground">Save, organize, and share your code snippets</p>
|
||||||
|
</div>
|
||||||
|
<SnippetManagerRedux />
|
||||||
|
</motion.div>
|
||||||
|
</PageLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
42
app/providers.tsx
Normal file
42
app/providers.tsx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import '@github/spark/spark';
|
||||||
|
import { ErrorBoundary } from 'react-error-boundary';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import { Toaster } from '@/components/ui/sonner';
|
||||||
|
import { store } from '@/store';
|
||||||
|
import { ErrorFallback } from '@/components/error/ErrorFallback';
|
||||||
|
import { NavigationProvider } from '@/components/layout/navigation/NavigationProvider';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { loadStorageConfig } from '@/lib/storage';
|
||||||
|
|
||||||
|
const logErrorToConsole = (error: Error, info: { componentStack?: string }) => {
|
||||||
|
console.error('Application Error:', error);
|
||||||
|
if (info.componentStack) {
|
||||||
|
console.error('Component Stack:', info.componentStack);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function StorageInitializer() {
|
||||||
|
useEffect(() => {
|
||||||
|
loadStorageConfig();
|
||||||
|
}, []);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Providers({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<Provider store={store}>
|
||||||
|
<ErrorBoundary
|
||||||
|
FallbackComponent={ErrorFallback}
|
||||||
|
onError={logErrorToConsole}
|
||||||
|
>
|
||||||
|
<NavigationProvider>
|
||||||
|
<StorageInitializer />
|
||||||
|
{children}
|
||||||
|
<Toaster />
|
||||||
|
</NavigationProvider>
|
||||||
|
</ErrorBoundary>
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
105
app/settings/page.tsx
Normal file
105
app/settings/page.tsx
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { PersistenceSettings } from '@/components/demo/PersistenceSettings';
|
||||||
|
import { SchemaHealthCard } from '@/components/settings/SchemaHealthCard';
|
||||||
|
import { BackendAutoConfigCard } from '@/components/settings/BackendAutoConfigCard';
|
||||||
|
import { StorageBackendCard } from '@/components/settings/StorageBackendCard';
|
||||||
|
import { DatabaseStatsCard } from '@/components/settings/DatabaseStatsCard';
|
||||||
|
import { StorageInfoCard } from '@/components/settings/StorageInfoCard';
|
||||||
|
import { DatabaseActionsCard } from '@/components/settings/DatabaseActionsCard';
|
||||||
|
import { useSettingsState } from '@/hooks/useSettingsState';
|
||||||
|
import { PageLayout } from '../PageLayout';
|
||||||
|
|
||||||
|
export default function SettingsPage() {
|
||||||
|
const {
|
||||||
|
stats,
|
||||||
|
loading,
|
||||||
|
storageBackend,
|
||||||
|
setStorageBackend,
|
||||||
|
flaskUrl,
|
||||||
|
setFlaskUrl,
|
||||||
|
flaskConnectionStatus,
|
||||||
|
setFlaskConnectionStatus,
|
||||||
|
testingConnection,
|
||||||
|
envVarSet,
|
||||||
|
schemaHealth,
|
||||||
|
checkingSchema,
|
||||||
|
handleExport,
|
||||||
|
handleImport,
|
||||||
|
handleClear,
|
||||||
|
handleSeed,
|
||||||
|
formatBytes,
|
||||||
|
handleTestConnection,
|
||||||
|
handleSaveStorageConfig,
|
||||||
|
handleMigrateToFlask,
|
||||||
|
handleMigrateToIndexedDB,
|
||||||
|
checkSchemaHealth,
|
||||||
|
} = useSettingsState();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageLayout>
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.4 }}
|
||||||
|
>
|
||||||
|
<div className="mb-8">
|
||||||
|
<h2 className="text-3xl font-bold tracking-tight mb-2">Settings</h2>
|
||||||
|
<p className="text-muted-foreground">Manage your database and application settings</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-6 max-w-3xl">
|
||||||
|
<PersistenceSettings />
|
||||||
|
|
||||||
|
<SchemaHealthCard
|
||||||
|
schemaHealth={schemaHealth}
|
||||||
|
checkingSchema={checkingSchema}
|
||||||
|
onClear={handleClear}
|
||||||
|
onCheckSchema={checkSchemaHealth}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<BackendAutoConfigCard
|
||||||
|
envVarSet={envVarSet}
|
||||||
|
flaskUrl={flaskUrl}
|
||||||
|
flaskConnectionStatus={flaskConnectionStatus}
|
||||||
|
testingConnection={testingConnection}
|
||||||
|
onTestConnection={handleTestConnection}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<StorageBackendCard
|
||||||
|
storageBackend={storageBackend}
|
||||||
|
flaskUrl={flaskUrl}
|
||||||
|
flaskConnectionStatus={flaskConnectionStatus}
|
||||||
|
testingConnection={testingConnection}
|
||||||
|
envVarSet={envVarSet}
|
||||||
|
onStorageBackendChange={setStorageBackend}
|
||||||
|
onFlaskUrlChange={(url) => {
|
||||||
|
setFlaskUrl(url);
|
||||||
|
setFlaskConnectionStatus('unknown');
|
||||||
|
}}
|
||||||
|
onTestConnection={handleTestConnection}
|
||||||
|
onSaveConfig={handleSaveStorageConfig}
|
||||||
|
onMigrateToFlask={handleMigrateToFlask}
|
||||||
|
onMigrateToIndexedDB={handleMigrateToIndexedDB}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DatabaseStatsCard
|
||||||
|
loading={loading}
|
||||||
|
stats={stats}
|
||||||
|
formatBytes={formatBytes}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<StorageInfoCard storageType={stats?.storageType} />
|
||||||
|
|
||||||
|
<DatabaseActionsCard
|
||||||
|
onExport={handleExport}
|
||||||
|
onImport={handleImport}
|
||||||
|
onSeed={handleSeed}
|
||||||
|
onClear={handleClear}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</PageLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
43
app/templates/page.tsx
Normal file
43
app/templates/page.tsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { TemplatesSection } from '@/components/templates/TemplatesSection';
|
||||||
|
import type { Snippet } from '@/lib/types';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
import { createSnippet } from '@/lib/db';
|
||||||
|
import { PageLayout } from '../PageLayout';
|
||||||
|
|
||||||
|
export default function TemplatesPage() {
|
||||||
|
const handleSaveSnippet = useCallback(async (snippetData: Omit<Snippet, 'id' | 'createdAt' | 'updatedAt'>) => {
|
||||||
|
try {
|
||||||
|
const newSnippet: Snippet = {
|
||||||
|
...snippetData,
|
||||||
|
id: Date.now().toString(),
|
||||||
|
createdAt: Date.now(),
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
};
|
||||||
|
await createSnippet(newSnippet);
|
||||||
|
toast.success('Component saved as snippet!');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to save snippet:', error);
|
||||||
|
toast.error('Failed to save snippet');
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageLayout>
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.4 }}
|
||||||
|
>
|
||||||
|
<div className="mb-8">
|
||||||
|
<h2 className="text-3xl font-bold tracking-tight mb-2">Templates</h2>
|
||||||
|
<p className="text-muted-foreground">Page-level layouts that combine organisms into complete interfaces</p>
|
||||||
|
</div>
|
||||||
|
<TemplatesSection onSaveSnippet={handleSaveSnippet} />
|
||||||
|
</motion.div>
|
||||||
|
</PageLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
args:
|
args:
|
||||||
- VITE_FLASK_BACKEND_URL=https://backend.example.com
|
- NEXT_PUBLIC_FLASK_BACKEND_URL=https://backend.example.com
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
args:
|
args:
|
||||||
- VITE_FLASK_BACKEND_URL=http://localhost:5000
|
- NEXT_PUBLIC_FLASK_BACKEND_URL=http://localhost:5000
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|||||||
49
next.config.ts
Normal file
49
next.config.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import type { NextConfig } from 'next';
|
||||||
|
|
||||||
|
const nextConfig: NextConfig = {
|
||||||
|
// Output as static HTML for GitHub Pages, or standalone for Docker
|
||||||
|
output: process.env.BUILD_STATIC ? 'export' : 'standalone',
|
||||||
|
|
||||||
|
// Base path for GitHub Pages deployment
|
||||||
|
// Set to '/' for custom domain or root deployment
|
||||||
|
// Set to '/repo-name/' for GitHub Pages at username.github.io/repo-name/
|
||||||
|
basePath: process.env.NEXT_PUBLIC_BASE_PATH || '',
|
||||||
|
|
||||||
|
// Configure webpack for browser-only modules
|
||||||
|
webpack: (config, { isServer }) => {
|
||||||
|
if (!isServer) {
|
||||||
|
// Externalize node modules for browser
|
||||||
|
config.resolve.fallback = {
|
||||||
|
...config.resolve.fallback,
|
||||||
|
fs: false,
|
||||||
|
path: false,
|
||||||
|
crypto: false,
|
||||||
|
'node:url': false,
|
||||||
|
'node:fs': false,
|
||||||
|
'node:fs/promises': false,
|
||||||
|
'node:vm': false,
|
||||||
|
'node:path': false,
|
||||||
|
'node:crypto': false,
|
||||||
|
'node:child_process': false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Environment variables that should be available on the client
|
||||||
|
env: {
|
||||||
|
NEXT_PUBLIC_FLASK_BACKEND_URL: process.env.NEXT_PUBLIC_FLASK_BACKEND_URL || '',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Image optimization
|
||||||
|
images: {
|
||||||
|
unoptimized: true, // Required for static export
|
||||||
|
},
|
||||||
|
|
||||||
|
// Experimental features
|
||||||
|
experimental: {
|
||||||
|
optimizePackageImports: ['@radix-ui/react-icons', '@phosphor-icons/react'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextConfig;
|
||||||
4454
package-lock.json
generated
4454
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
20
package.json
20
package.json
@@ -2,14 +2,12 @@
|
|||||||
"name": "spark-template",
|
"name": "spark-template",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "next dev",
|
||||||
"kill": "fuser -k 5000/tcp",
|
"build": "next build",
|
||||||
"build": "tsc -b --noCheck && vite build",
|
"start": "next start",
|
||||||
"lint": "eslint .",
|
"lint": "next lint",
|
||||||
"optimize": "vite optimize",
|
"kill": "fuser -k 5000/tcp"
|
||||||
"preview": "vite preview"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/standalone": "^7.28.6",
|
"@babel/standalone": "^7.28.6",
|
||||||
@@ -48,7 +46,6 @@
|
|||||||
"@radix-ui/react-tooltip": "^1.1.8",
|
"@radix-ui/react-tooltip": "^1.1.8",
|
||||||
"@reduxjs/toolkit": "^2.11.2",
|
"@reduxjs/toolkit": "^2.11.2",
|
||||||
"@tailwindcss/container-queries": "^0.1.1",
|
"@tailwindcss/container-queries": "^0.1.1",
|
||||||
"@tailwindcss/vite": "^4.1.11",
|
|
||||||
"@tanstack/react-query": "^5.83.1",
|
"@tanstack/react-query": "^5.83.1",
|
||||||
"@types/sql.js": "^1.4.9",
|
"@types/sql.js": "^1.4.9",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
@@ -61,6 +58,7 @@
|
|||||||
"input-otp": "^1.4.2",
|
"input-otp": "^1.4.2",
|
||||||
"lucide-react": "^0.484.0",
|
"lucide-react": "^0.484.0",
|
||||||
"marked": "^15.0.7",
|
"marked": "^15.0.7",
|
||||||
|
"next": "16.1.3",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"octokit": "^4.1.2",
|
"octokit": "^4.1.2",
|
||||||
"pyodide": "^0.29.1",
|
"pyodide": "^0.29.1",
|
||||||
@@ -87,15 +85,13 @@
|
|||||||
"@tailwindcss/postcss": "^4.1.8",
|
"@tailwindcss/postcss": "^4.1.8",
|
||||||
"@types/react": "^19.0.10",
|
"@types/react": "^19.0.10",
|
||||||
"@types/react-dom": "^19.0.4",
|
"@types/react-dom": "^19.0.4",
|
||||||
"@vitejs/plugin-react-swc": "^4.2.2",
|
|
||||||
"eslint": "^9.28.0",
|
"eslint": "^9.28.0",
|
||||||
|
"eslint-config-next": "^16.1.3",
|
||||||
"eslint-plugin-react-hooks": "^5.2.0",
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.19",
|
|
||||||
"globals": "^16.0.0",
|
"globals": "^16.0.0",
|
||||||
"tailwindcss": "^4.1.11",
|
"tailwindcss": "^4.1.11",
|
||||||
"typescript": "~5.7.2",
|
"typescript": "~5.7.2",
|
||||||
"typescript-eslint": "^8.38.0",
|
"typescript-eslint": "^8.38.0"
|
||||||
"vite": "^7.2.6"
|
|
||||||
},
|
},
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
"packages": [
|
"packages": [
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export function BackendIndicator() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const config = getStorageConfig()
|
const config = getStorageConfig()
|
||||||
setBackend(config.backend)
|
setBackend(config.backend)
|
||||||
setIsEnvConfigured(Boolean(import.meta.env.VITE_FLASK_BACKEND_URL))
|
setIsEnvConfigured(Boolean(process.env.NEXT_PUBLIC_FLASK_BACKEND_URL))
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
if (backend === 'indexeddb') {
|
if (backend === 'indexeddb') {
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
import { motion } from 'framer-motion'
|
'use client';
|
||||||
import { Link, useLocation } from 'react-router-dom'
|
|
||||||
import { X } from '@phosphor-icons/react'
|
import { motion } from 'framer-motion';
|
||||||
import { Button } from '@/components/ui/button'
|
import Link from 'next/link';
|
||||||
import { cn } from '@/lib/utils'
|
import { usePathname } from 'next/navigation';
|
||||||
import { navigationItems } from './navigation-items'
|
import { X } from '@phosphor-icons/react';
|
||||||
import { useNavigation } from './useNavigation'
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import { navigationItems } from './navigation-items';
|
||||||
|
import { useNavigation } from './useNavigation';
|
||||||
|
|
||||||
export function NavigationSidebar() {
|
export function NavigationSidebar() {
|
||||||
const { menuOpen, setMenuOpen } = useNavigation()
|
const { menuOpen, setMenuOpen } = useNavigation();
|
||||||
const location = useLocation()
|
const pathname = usePathname();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.aside
|
<motion.aside
|
||||||
@@ -31,11 +34,11 @@ export function NavigationSidebar() {
|
|||||||
<nav className="p-4">
|
<nav className="p-4">
|
||||||
<ul className="space-y-2">
|
<ul className="space-y-2">
|
||||||
{navigationItems.map((item) => {
|
{navigationItems.map((item) => {
|
||||||
const Icon = item.icon
|
const Icon = item.icon;
|
||||||
const isActive = location.pathname === item.path
|
const isActive = pathname === item.path;
|
||||||
return (
|
return (
|
||||||
<li key={item.path}>
|
<li key={item.path}>
|
||||||
<Link to={item.path}>
|
<Link href={item.path}>
|
||||||
<Button
|
<Button
|
||||||
variant={isActive ? 'secondary' : 'ghost'}
|
variant={isActive ? 'secondary' : 'ghost'}
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -48,7 +51,7 @@ export function NavigationSidebar() {
|
|||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
)
|
);
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
@@ -60,5 +63,5 @@ export function NavigationSidebar() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</motion.aside>
|
</motion.aside>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export function BackendAutoConfigCard({
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between py-2">
|
<div className="flex items-center justify-between py-2">
|
||||||
<span className="text-sm text-muted-foreground">Configuration Source</span>
|
<span className="text-sm text-muted-foreground">Configuration Source</span>
|
||||||
<code className="text-sm font-mono bg-muted px-2 py-1 rounded">VITE_FLASK_BACKEND_URL</code>
|
<code className="text-sm font-mono bg-muted px-2 py-1 rounded">NEXT_PUBLIC_FLASK_BACKEND_URL</code>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between py-2">
|
<div className="flex items-center justify-between py-2">
|
||||||
<span className="text-sm text-muted-foreground">Status</span>
|
<span className="text-sm text-muted-foreground">Status</span>
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export function StorageBackendCard({
|
|||||||
<AlertDescription className="flex items-center gap-2">
|
<AlertDescription className="flex items-center gap-2">
|
||||||
<CloudCheck weight="fill" size={16} className="text-accent" />
|
<CloudCheck weight="fill" size={16} className="text-accent" />
|
||||||
<span>
|
<span>
|
||||||
Storage backend is configured via <code className="px-1.5 py-0.5 rounded bg-muted text-xs font-mono">VITE_FLASK_BACKEND_URL</code> environment variable and cannot be changed here.
|
Storage backend is configured via <code className="px-1.5 py-0.5 rounded bg-muted text-xs font-mono">NEXT_PUBLIC_FLASK_BACKEND_URL</code> environment variable and cannot be changed here.
|
||||||
</span>
|
</span>
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export function useStorageConfig() {
|
|||||||
|
|
||||||
const loadConfig = useCallback(() => {
|
const loadConfig = useCallback(() => {
|
||||||
const config = loadStorageConfig()
|
const config = loadStorageConfig()
|
||||||
const envFlaskUrl = import.meta.env.VITE_FLASK_BACKEND_URL
|
const envFlaskUrl = process.env.NEXT_PUBLIC_FLASK_BACKEND_URL
|
||||||
const isEnvSet = Boolean(envFlaskUrl)
|
const isEnvSet = Boolean(envFlaskUrl)
|
||||||
|
|
||||||
setEnvVarSet(isEnvSet)
|
setEnvVarSet(isEnvSet)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export interface StorageConfig {
|
|||||||
const STORAGE_CONFIG_KEY = 'codesnippet-storage-config'
|
const STORAGE_CONFIG_KEY = 'codesnippet-storage-config'
|
||||||
|
|
||||||
function getDefaultConfig(): StorageConfig {
|
function getDefaultConfig(): StorageConfig {
|
||||||
const flaskUrl = import.meta.env.VITE_FLASK_BACKEND_URL
|
const flaskUrl = process.env.NEXT_PUBLIC_FLASK_BACKEND_URL
|
||||||
|
|
||||||
if (flaskUrl) {
|
if (flaskUrl) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,34 +1,44 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
||||||
"target": "ES2020",
|
"target": "ES2020",
|
||||||
"useDefineForClassFields": true,
|
|
||||||
"lib": [
|
"lib": [
|
||||||
"ES2020",
|
"ES2020",
|
||||||
"DOM",
|
"DOM",
|
||||||
"DOM.Iterable"
|
"DOM.Iterable"
|
||||||
],
|
],
|
||||||
|
"jsx": "preserve",
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"skipLibCheck": true,
|
|
||||||
"strictNullChecks": true,
|
|
||||||
/* Bundler mode */
|
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"allowImportingTsExtensions": true,
|
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"allowJs": true,
|
||||||
"moduleDetection": "force",
|
"skipLibCheck": true,
|
||||||
|
"strict": false,
|
||||||
|
"strictNullChecks": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"jsx": "react-jsx",
|
"esModuleInterop": true,
|
||||||
/* Linting */
|
"isolatedModules": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"incremental": true,
|
||||||
"noUncheckedSideEffectImports": true,
|
"noUncheckedSideEffectImports": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"allowImportingTsExtensions": false,
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": [
|
"@/*": [
|
||||||
"./src/*"
|
"./src/*"
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src"
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".next/types/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user