Spaces:
Running
### Identified Issues and Fixes
Browse filesI've reviewed the entire project setup. Overall, it's a solid foundation for a QR template processing app—nice use of Next.js App Router, Prisma, Sharp, and shadcn-ui. However, there are several bugs, missing dependencies, incomplete implementations, and minor inconsistencies that would prevent it from running cleanly. I'll summarize the key issues categorized by type, then provide patched code snippets for the affected files. These fixes make it fully functional: copy-paste ready after adding the missing deps.
#### 1. **Missing Dependencies**
- `react-dropzone`, `react-resizable`, `jszip` are referenced but not in `package.json`.
- shadcn-ui components (e.g., `Toaster`, `Card`, `Label`) need CLI init/add.
- Fix: Update `package.json` and run shadcn commands (as noted).
**Updated package.json** (add to "dependencies"):
```json
{
... // existing
"dependencies": {
... // existing
"jszip": "^3.10.1",
"react-dropzone": "^14.2.3",
"react-resizable": "^3.0.5"
}
}
```
After `npm install`, run:
```
npx shadcn-ui@latest init
npx shadcn-ui@latest add button input label card toaster
```
#### 2. **Runtime Errors / Logic Bugs**
- **app/templates/page.tsx**: `useTemplate(savedTemplate)` expects a full template object, but `setSavedTemplate(data.id)` sets it to a string (ID only). Later, `template.id` is undefined in `handleUpload`. Also, `updateTemplate(formData)` in `handleSaveTemplate` overwrites without ID.
- Fix: Set `savedTemplate` to the full `data` object. Merge ID into formData before updating.
- **components/UploadZone.tsx**: `Object.keys(onFilesChange)` treats the setter function as an object (crash). No prop for current files to display list.
- Fix: Pass `files` prop from parent, map over it for the list.
- **components/TemplateForm.tsx**: `handleChange` uses `parseFloat` on all fields, including `name` (turns string to NaN).
- Fix: Conditional parsing (skip for `name`).
- **app/api/jobs/route.ts**: POST returns ZIP stream, but client does `res.json()` (fails on binary). Processing + ZIP in POST is inefficient; better to process in POST, return job ID, then GET `/download` for ZIP.
- Fix: Move ZIP to GET route. POST returns JSON `{ id }`. Client redirects after.
- **lib/sharpProcessor.ts**: `FILES_ROOT` undefined (missing import). `env` lib doesn't exist.
- Fix: Create `lib/env.ts` to load from `process.env`.
- **components/QRPreview.tsx**: Drag not implemented (only resize). Inline styles + `useEffect` redundant.
- Fix: Remove redundant `useEffect`. Add basic drag with `react-draggable` (optional dep, but noted—add if desired).
- **app/layout.tsx**: `Toaster` import assumes it's added via shadcn (now fixed above).
- **app/api/jobs/[id]/download/route.ts**: `Readable` setup incomplete (`_read` noop, but push works). Assumes files exist.
- Fix: Minor tweak for stream.
#### 3. **Type/Syntax Warnings**
- TS errors: Missing types for formData in API (e.g., `any`). Hook `useTemplate` initial state typing.
- Fix: Add basic types.
- Prisma: `@map("jobs")` on model (unnecessary, remove).
- Tailwind: Content paths include `./pages/` and `./src/` (unused; clean up).
#### 4. **Other Improvements**
- **Security/Prod**: `NEXTAUTH_SECRET` in `.env.example` but unused (remove if no auth). `FILES_ROOT` should be absolute path in prod.
- **UX**: Add loading state in `handleUpload`. Poll job status if processing is async (future: WebSockets).
- **Test Flow**: After fixes, create template → resize box in preview → upload 2 PNG QRs → process → auto-download ZIP with templated images.
Run `npm run dev` after fixes; it should work on `localhost:3000/templates`.
### Patched Files
Here are only the changed files (replace contents). Others are fine.
#### lib/env.ts (New File)
```ts
export const FILES_ROOT = process.env.FILES_ROOT || './data';
```
#### lib/useTemplate.ts (Add Types)
```ts
import { useState } from 'react';
interface Template {
id?: string;
name: string;
dpi: number;
canvasWidthMm: number;
canvasHeightMm: number;
qrBoxX: number;
qrBoxY: number;
qrBoxWidthMm: number;
qrBoxHeightMm: number;
}
export function useTemplate(initialTemplate: Template | null) {
const [template, setTemplate] = useState<Template | null>(initialTemplate);
const updateTemplate = (updates: Partial<Template>) => {
setTemplate(prev => prev ? ({ ...prev, ...updates }) : updates as Template);
};
return { template, updateTemplate };
}
```
#### prisma/schema.prisma (Remove Unnecessary Map)
```prisma
// ... existing, remove @@map("jobs") from Job model
model Job {
id String @id @default (cuid())
templateId String
name String @default ("Bulk Upload")
status JobStatus @default (PENDING)
createdAt DateTime @default (now())
updatedAt DateTime @updatedAt
template Template @relation (fields: [templateId], references: [id])
files JobFile[]
// Remove: @@map("jobs")
}
```
#### tailwind.config.js (Clean Content Paths)
```js
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ["class"],
content: [
'./app/**/*.{ts,tsx}',
'./components/**/*.{ts,tsx}',
],
// ... rest unchanged
}
```
#### app/templates/page.tsx
```tsx
"use client";
import { useState, useRef } from "react";
import TemplateForm from "@/components/TemplateForm";
import QRPreview from "@/components/QRPreview";
import UploadZone from "@/components/UploadZone";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { useTemplate } from "@/lib/useTemplate";
import type { Template } from "@/lib/useTemplate";
export default function TemplatesPage() {
const [savedTemplate, setSavedTemplate] = useState<Template | null>(null);
const [uploadFiles, setUploadFiles] = useState<File[]>([]);
const previewRef = useRef<HTMLDivElement>(null);
const { template, updateTemplate } = useTemplate(savedTemplate);
const handleSaveTemplate = async (formData: Omit<Template, 'id'>) => {
const res = await fetch("/api/templates", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(formData),
});
const data = await res.json() as Template;
const fullTemplate = { ...formData, id: data.id };
setSavedTemplate(fullTemplate);
updateTemplate(fullTemplate);
};
const handleUpload = async () => {
if (!template || uploadFiles.length === 0) return;
const formData = new FormData();
uploadFiles.forEach((file) => formData.append("files", file));
formData.append("templateId", template.id!);
const res = await fetch("/api/jobs", { method: "POST", body: formData });
if (res.ok) {
const job = await res.json();
// Poll or direct download
window.location.href = `/api/jobs/${job.id}/download`;
}
};
return (
<div className="container mx-auto p-4 space-y-6">
<Card>
<CardHeader>
<CardTitle>Define Template</CardTitle>
</CardHeader>
<CardContent>
<TemplateForm onSubmit={handleSaveTemplate} />
</CardContent>
</Card>
{template && (
<>
<Card>
<CardHeader>
<CardTitle>Live Preview</CardTitle>
</CardHeader>
<CardContent>
<QRPreview
ref={previewRef}
template={template}
onBoxUpdate={updateTemplate}
/>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Bulk Upload QRs</CardTitle>
</CardHeader>
<CardContent>
<UploadZone files={uploadFiles} onFilesChange={setUploadFiles} />
<Button onClick={handleUpload} className="mt-4" disabled={uploadFiles.length === 0}>
Process & Download ZIP
</Button>
</CardContent>
</Card>
</>
)}
</div>
);
}
```
#### components/TemplateForm.tsx
```tsx
"use client";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import type { Template } from "@/lib/useTemplate";
type FormData = Omit<Template, 'id'>;
export default function TemplateForm({ onSubmit }: { onSubmit: (data: FormData) => void }) {
const [formData, setFormData] = useState<FormData>({
name: "",
dpi: 300,
canvasWidthMm: 65,
canvasHeightMm: 140,
qrBoxX: 0,
qrBoxY: 0,
qrBoxWidthMm: 50,
qrBoxHeightMm: 50,
});
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.name === 'name' ? e.target.value : parseFloat(e.target.value) || 0;
setFormData({ ...formData, [e.target.name]: value });
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onSubmit(formData);
};
return (
<form onSubmit={handleSubmit} className="space-y-4">
{/* ... rest unchanged */}
</form>
);
}
```
#### components/QRPreview.tsx (Remove Redundant useEffect, Basic Drag Note)
```tsx
"use client";
import { forwardRef, useRef } from "react";
import { Resizable } from "react-resizable";
// Optional: import Draggable from 'react-draggable'; // npm install react-draggable
interface QRPreviewProps {
template: any;
onBoxUpdate: (updates: any) => void;
}
const QRPreview = forwardRef<HTMLDivElement, QRPreviewProps>(({ template, onBoxUpdate }, ref) => {
const boxRef = useRef<HTMLDivElement>(null);
const scale = template.dpi / 25.4;
const canvasWidthPx = template.canvasWidthMm * scale;
const canvasHeightPx = template.canvasHeightMm * scale;
const boxXPx = template.qrBoxX * scale;
const boxYPx = template.qrBoxY * scale;
const boxWidthPx = template.qrBoxWidthMm * scale;
const boxHeightPx = template.qrBoxHeightMm * scale;
const handleResize = (e: any, { size }: any) => {
onBoxUpdate({
...template,
qrBoxWidthMm: size.width / scale,
- README.md +7 -4
- index.html +210 -18
- templates.html +269 -0
|
@@ -1,10 +1,13 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
|
| 4 |
-
colorFrom: pink
|
| 5 |
colorTo: gray
|
|
|
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: QR Wizardry Portal ✨
|
| 3 |
+
colorFrom: blue
|
|
|
|
| 4 |
colorTo: gray
|
| 5 |
+
emoji: 🐳
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
| 8 |
+
tags:
|
| 9 |
+
- deepsite-v3
|
| 10 |
---
|
| 11 |
|
| 12 |
+
# Welcome to your new DeepSite project!
|
| 13 |
+
This project was created with [DeepSite](https://deepsite.hf.co).
|
|
@@ -1,19 +1,211 @@
|
|
| 1 |
-
<!
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
</html>
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en" class="dark">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>QR Wizardry Portal</title>
|
| 7 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
+
<script src="https://unpkg.com/feather-icons"></script>
|
| 9 |
+
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
|
| 10 |
+
<script>
|
| 11 |
+
tailwind.config = {
|
| 12 |
+
darkMode: 'class',
|
| 13 |
+
theme: {
|
| 14 |
+
extend: {
|
| 15 |
+
colors: {
|
| 16 |
+
primary: {
|
| 17 |
+
50: '#eff6ff',
|
| 18 |
+
100: '#dbeafe',
|
| 19 |
+
200: '#bfdbfe',
|
| 20 |
+
300: '#93c5fd',
|
| 21 |
+
400: '#60a5fa',
|
| 22 |
+
500: '#3b82f6',
|
| 23 |
+
600: '#2563eb',
|
| 24 |
+
700: '#1d4ed8',
|
| 25 |
+
800: '#1e40af',
|
| 26 |
+
900: '#1e3a8a',
|
| 27 |
+
}
|
| 28 |
+
}
|
| 29 |
+
}
|
| 30 |
+
}
|
| 31 |
+
}
|
| 32 |
+
</script>
|
| 33 |
+
<style>
|
| 34 |
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
| 35 |
+
body {
|
| 36 |
+
font-family: 'Inter', sans-serif;
|
| 37 |
+
background-color: #0f172a;
|
| 38 |
+
color: #e2e8f0;
|
| 39 |
+
}
|
| 40 |
+
.gradient-text {
|
| 41 |
+
background: linear-gradient(90deg, #3b82f6 0%, #93c5fd 100%);
|
| 42 |
+
-webkit-background-clip: text;
|
| 43 |
+
background-clip: text;
|
| 44 |
+
color: transparent;
|
| 45 |
+
}
|
| 46 |
+
</style>
|
| 47 |
+
</head>
|
| 48 |
+
<body class="min-h-screen">
|
| 49 |
+
<div class="relative overflow-hidden">
|
| 50 |
+
<!-- Animated background -->
|
| 51 |
+
<div class="absolute inset-0 bg-gradient-to-br from-gray-900 to-blue-900 opacity-50"></div>
|
| 52 |
+
<div class="absolute inset-0 bg-[url('https://static.photos/abstract/1200x630/42')] bg-cover bg-center opacity-10"></div>
|
| 53 |
+
|
| 54 |
+
<!-- Main content -->
|
| 55 |
+
<div class="relative z-10 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
|
| 56 |
+
<!-- Header -->
|
| 57 |
+
<header class="flex flex-col items-center text-center mb-16">
|
| 58 |
+
<div class="flex items-center justify-center mb-6">
|
| 59 |
+
<div class="w-16 h-16 rounded-full bg-primary-500 flex items-center justify-center shadow-lg shadow-primary-500/20">
|
| 60 |
+
<i data-feather="maximize-2" class="text-white w-8 h-8"></i>
|
| 61 |
+
</div>
|
| 62 |
+
</div>
|
| 63 |
+
<h1 class="text-4xl md:text-6xl font-bold mb-4">
|
| 64 |
+
<span class="gradient-text">QR Wizardry</span> Portal
|
| 65 |
+
</h1>
|
| 66 |
+
<p class="text-xl text-gray-400 max-w-2xl">
|
| 67 |
+
Magically transform your QR codes with our powerful template processing system
|
| 68 |
+
</p>
|
| 69 |
+
<div class="mt-8 flex gap-4">
|
| 70 |
+
<a href="/templates" class="px-6 py-3 bg-primary-600 hover:bg-primary-700 text-white font-medium rounded-lg shadow-md transition-all duration-300 transform hover:-translate-y-1">
|
| 71 |
+
Start Crafting
|
| 72 |
+
</a>
|
| 73 |
+
<a href="#features" class="px-6 py-3 bg-gray-800 hover:bg-gray-700 text-gray-200 font-medium rounded-lg transition-all">
|
| 74 |
+
Learn More
|
| 75 |
+
</a>
|
| 76 |
+
</div>
|
| 77 |
+
</header>
|
| 78 |
+
|
| 79 |
+
<!-- Features -->
|
| 80 |
+
<section id="features" class="mb-24">
|
| 81 |
+
<h2 class="text-3xl font-bold text-center mb-12 gradient-text">Magical Features</h2>
|
| 82 |
+
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
|
| 83 |
+
<!-- Feature 1 -->
|
| 84 |
+
<div class="bg-gray-800/50 backdrop-blur-sm p-6 rounded-xl border border-gray-700 hover:border-primary-500 transition-all">
|
| 85 |
+
<div class="w-12 h-12 rounded-lg bg-primary-500/20 flex items-center justify-center mb-4">
|
| 86 |
+
<i data-feather="edit-3" class="text-primary-400"></i>
|
| 87 |
+
</div>
|
| 88 |
+
<h3 class="text-xl font-semibold mb-2">Template Designer</h3>
|
| 89 |
+
<p class="text-gray-400">Create beautiful QR code templates with precise positioning and sizing controls.</p>
|
| 90 |
+
</div>
|
| 91 |
+
|
| 92 |
+
<!-- Feature 2 -->
|
| 93 |
+
<div class="bg-gray-800/50 backdrop-blur-sm p-6 rounded-xl border border-gray-700 hover:border-primary-500 transition-all">
|
| 94 |
+
<div class="w-12 h-12 rounded-lg bg-primary-500/20 flex items-center justify-center mb-4">
|
| 95 |
+
<i data-feather="upload-cloud" class="text-primary-400"></i>
|
| 96 |
+
</div>
|
| 97 |
+
<h3 class="text-xl font-semibold mb-2">Bulk Processing</h3>
|
| 98 |
+
<p class="text-gray-400">Upload multiple QR codes at once and process them with your template automatically.</p>
|
| 99 |
+
</div>
|
| 100 |
+
|
| 101 |
+
<!-- Feature 3 -->
|
| 102 |
+
<div class="bg-gray-800/50 backdrop-blur-sm p-6 rounded-xl border border-gray-700 hover:border-primary-500 transition-all">
|
| 103 |
+
<div class="w-12 h-12 rounded-lg bg-primary-500/20 flex items-center justify-center mb-4">
|
| 104 |
+
<i data-feather="download" class="text-primary-400"></i>
|
| 105 |
+
</div>
|
| 106 |
+
<h3 class="text-xl font-semibold mb-2">Instant Download</h3>
|
| 107 |
+
<p class="text-gray-400">Get your processed QR codes delivered in a neat ZIP file ready for use.</p>
|
| 108 |
+
</div>
|
| 109 |
+
</div>
|
| 110 |
+
</section>
|
| 111 |
+
|
| 112 |
+
<!-- Preview -->
|
| 113 |
+
<section class="mb-24">
|
| 114 |
+
<div class="bg-gradient-to-r from-primary-900/20 to-blue-900/20 p-1 rounded-xl">
|
| 115 |
+
<div class="bg-gray-900 rounded-lg p-8">
|
| 116 |
+
<div class="flex flex-col md:flex-row gap-8 items-center">
|
| 117 |
+
<div class="flex-1">
|
| 118 |
+
<h2 class="text-3xl font-bold mb-4 gradient-text">See It in Action</h2>
|
| 119 |
+
<p class="text-gray-400 mb-6">Watch how we transform a basic QR code into a beautifully designed template.</p>
|
| 120 |
+
<ul class="space-y-3 text-gray-300">
|
| 121 |
+
<li class="flex items-start">
|
| 122 |
+
<i data-feather="check" class="text-primary-500 mr-2 mt-1 w-4 h-4"></i>
|
| 123 |
+
<span>Pixel-perfect positioning</span>
|
| 124 |
+
</li>
|
| 125 |
+
<li class="flex items-start">
|
| 126 |
+
<i data-feather="check" class="text-primary-500 mr-2 mt-1 w-4 h-4"></i>
|
| 127 |
+
<span>High-resolution output</span>
|
| 128 |
+
</li>
|
| 129 |
+
<li class="flex items-start">
|
| 130 |
+
<i data-feather="check" class="text-primary-500 mr-2 mt-1 w-4 h-4"></i>
|
| 131 |
+
<span>Batch processing</span>
|
| 132 |
+
</li>
|
| 133 |
+
</ul>
|
| 134 |
+
</div>
|
| 135 |
+
<div class="flex-1">
|
| 136 |
+
<div class="relative">
|
| 137 |
+
<div class="absolute -inset-4 bg-gradient-to-r from-primary-500 to-blue-400 rounded-xl blur opacity-20"></div>
|
| 138 |
+
<div class="relative bg-gray-800 rounded-lg overflow-hidden border border-gray-700">
|
| 139 |
+
<div class="aspect-video bg-gray-900/50 flex items-center justify-center">
|
| 140 |
+
<div class="text-center p-6">
|
| 141 |
+
<i data-feather="play" class="w-16 h-16 text-primary-500 mx-auto"></i>
|
| 142 |
+
<p class="mt-2 text-gray-400">Template Preview</p>
|
| 143 |
+
</div>
|
| 144 |
+
</div>
|
| 145 |
+
</div>
|
| 146 |
+
</div>
|
| 147 |
+
</div>
|
| 148 |
+
</div>
|
| 149 |
+
</div>
|
| 150 |
+
</div>
|
| 151 |
+
</section>
|
| 152 |
+
</div>
|
| 153 |
+
|
| 154 |
+
<!-- Footer -->
|
| 155 |
+
<footer class="border-t border-gray-800 py-8">
|
| 156 |
+
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
| 157 |
+
<div class="flex flex-col md:flex-row justify-between items-center">
|
| 158 |
+
<div class="flex items-center space-x-2 mb-4 md:mb-0">
|
| 159 |
+
<div class="w-8 h-8 rounded-full bg-primary-500 flex items-center justify-center">
|
| 160 |
+
<i data-feather="maximize-2" class="text-white w-4 h-4"></i>
|
| 161 |
+
</div>
|
| 162 |
+
<span class="text-xl font-bold">QR Wizardry</span>
|
| 163 |
+
</div>
|
| 164 |
+
<div class="flex space-x-6">
|
| 165 |
+
<a href="#" class="text-gray-400 hover:text-primary-400 transition-colors">
|
| 166 |
+
<i data-feather="github"></i>
|
| 167 |
+
</a>
|
| 168 |
+
<a href="#" class="text-gray-400 hover:text-primary-400 transition-colors">
|
| 169 |
+
<i data-feather="twitter"></i>
|
| 170 |
+
</a>
|
| 171 |
+
<a href="#" class="text-gray-400 hover:text-primary-400 transition-colors">
|
| 172 |
+
<i data-feather="linkedin"></i>
|
| 173 |
+
</a>
|
| 174 |
+
</div>
|
| 175 |
+
</div>
|
| 176 |
+
<div class="mt-8 text-center text-gray-500 text-sm">
|
| 177 |
+
© 2023 QR Wizardry Portal. All rights reserved.
|
| 178 |
+
</div>
|
| 179 |
+
</div>
|
| 180 |
+
</footer>
|
| 181 |
+
</div>
|
| 182 |
+
|
| 183 |
+
<script>
|
| 184 |
+
feather.replace();
|
| 185 |
+
// Simple animation for cards on scroll
|
| 186 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 187 |
+
const cards = document.querySelectorAll('.bg-gray-800\\/50');
|
| 188 |
+
const observer = new IntersectionObserver((entries) => {
|
| 189 |
+
entries.forEach(entry => {
|
| 190 |
+
if (entry.isIntersecting) {
|
| 191 |
+
entry.target.classList.add('animate-fadeIn');
|
| 192 |
+
}
|
| 193 |
+
});
|
| 194 |
+
}, { threshold: 0.1 });
|
| 195 |
+
|
| 196 |
+
cards.forEach(card => {
|
| 197 |
+
observer.observe(card);
|
| 198 |
+
});
|
| 199 |
+
});
|
| 200 |
+
</script>
|
| 201 |
+
<style>
|
| 202 |
+
@keyframes fadeIn {
|
| 203 |
+
from { opacity: 0; transform: translateY(20px); }
|
| 204 |
+
to { opacity: 1; transform: translateY(0); }
|
| 205 |
+
}
|
| 206 |
+
.animate-fadeIn {
|
| 207 |
+
animation: fadeIn 0.6s ease-out forwards;
|
| 208 |
+
}
|
| 209 |
+
</style>
|
| 210 |
+
</body>
|
| 211 |
</html>
|
|
@@ -0,0 +1,269 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en" class="dark">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Template Designer | QR Wizardry</title>
|
| 7 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
+
<script src="https://unpkg.com/feather-icons"></script>
|
| 9 |
+
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
|
| 10 |
+
<script src="https://unpkg.com/react-dropzone@14.2.3/dist/react-dropzone.js"></script>
|
| 11 |
+
<script src="https://unpkg.com/react-resizable@3.0.5/dist/react-resizable.js"></script>
|
| 12 |
+
<script>
|
| 13 |
+
tailwind.config = {
|
| 14 |
+
darkMode: 'class',
|
| 15 |
+
theme: {
|
| 16 |
+
extend: {
|
| 17 |
+
colors: {
|
| 18 |
+
primary: {
|
| 19 |
+
50: '#eff6ff',
|
| 20 |
+
100: '#dbeafe',
|
| 21 |
+
200: '#bfdbfe',
|
| 22 |
+
300: '#93c5fd',
|
| 23 |
+
400: '#60a5fa',
|
| 24 |
+
500: '#3b82f6',
|
| 25 |
+
600: '#2563eb',
|
| 26 |
+
700: '#1d4ed8',
|
| 27 |
+
800: '#1e40af',
|
| 28 |
+
900: '#1e3a8a',
|
| 29 |
+
}
|
| 30 |
+
}
|
| 31 |
+
}
|
| 32 |
+
}
|
| 33 |
+
}
|
| 34 |
+
</script>
|
| 35 |
+
<style>
|
| 36 |
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
| 37 |
+
body {
|
| 38 |
+
font-family: 'Inter', sans-serif;
|
| 39 |
+
background-color: #0f172a;
|
| 40 |
+
color: #e2e8f0;
|
| 41 |
+
}
|
| 42 |
+
.resize-handle {
|
| 43 |
+
position: absolute;
|
| 44 |
+
width: 10px;
|
| 45 |
+
height: 10px;
|
| 46 |
+
background: #3b82f6;
|
| 47 |
+
right: -5px;
|
| 48 |
+
bottom: -5px;
|
| 49 |
+
cursor: se-resize;
|
| 50 |
+
border-radius: 2px;
|
| 51 |
+
}
|
| 52 |
+
.dropzone {
|
| 53 |
+
border: 2px dashed #334155;
|
| 54 |
+
transition: all 0.3s ease;
|
| 55 |
+
}
|
| 56 |
+
.dropzone.active {
|
| 57 |
+
border-color: #3b82f6;
|
| 58 |
+
background-color: #1e3a8a20;
|
| 59 |
+
}
|
| 60 |
+
</style>
|
| 61 |
+
</head>
|
| 62 |
+
<body class="min-h-screen">
|
| 63 |
+
<div class="relative">
|
| 64 |
+
<!-- Navigation -->
|
| 65 |
+
<nav class="bg-gray-900/80 backdrop-blur-sm border-b border-gray-800">
|
| 66 |
+
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
| 67 |
+
<div class="flex items-center justify-between h-16">
|
| 68 |
+
<div class="flex items-center">
|
| 69 |
+
<div class="flex-shrink-0 flex items-center">
|
| 70 |
+
<i data-feather="maximize-2" class="text-primary-500 w-6 h-6"></i>
|
| 71 |
+
<span class="ml-2 font-bold">QR Wizardry</span>
|
| 72 |
+
</div>
|
| 73 |
+
</div>
|
| 74 |
+
<div class="hidden md:block">
|
| 75 |
+
<div class="ml-10 flex items-baseline space-x-4">
|
| 76 |
+
<a href="/" class="text-gray-400 hover:text-white px-3 py-2 rounded-md text-sm font-medium">
|
| 77 |
+
Home
|
| 78 |
+
</a>
|
| 79 |
+
<a href="#" class="bg-gray-800 text-white px-3 py-2 rounded-md text-sm font-medium">
|
| 80 |
+
Templates
|
| 81 |
+
</a>
|
| 82 |
+
<a href="#" class="text-gray-400 hover:text-white px-3 py-2 rounded-md text-sm font-medium">
|
| 83 |
+
Documentation
|
| 84 |
+
</a>
|
| 85 |
+
</div>
|
| 86 |
+
</div>
|
| 87 |
+
</div>
|
| 88 |
+
</div>
|
| 89 |
+
</nav>
|
| 90 |
+
|
| 91 |
+
<!-- Main content -->
|
| 92 |
+
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
| 93 |
+
<div class="mb-8">
|
| 94 |
+
<h1 class="text-3xl font-bold mb-2">Template Designer</h1>
|
| 95 |
+
<p class="text-gray-400">Create your perfect QR code template with our intuitive designer</p>
|
| 96 |
+
</div>
|
| 97 |
+
|
| 98 |
+
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
| 99 |
+
<!-- Template Form -->
|
| 100 |
+
<div class="bg-gray-900/50 border border-gray-800 rounded-xl p-6">
|
| 101 |
+
<h2 class="text-xl font-semibold mb-4 flex items-center">
|
| 102 |
+
<i data-feather="edit-3" class="w-5 h-5 mr-2 text-primary-500"></i>
|
| 103 |
+
Template Settings
|
| 104 |
+
</h2>
|
| 105 |
+
<form>
|
| 106 |
+
<div class="space-y-4">
|
| 107 |
+
<div>
|
| 108 |
+
<label class="block text-sm font-medium text-gray-300 mb-1">Template Name</label>
|
| 109 |
+
<input type="text" class="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-2 focus:ring-2 focus:ring-primary-500 focus:border-transparent" placeholder="My Awesome Template">
|
| 110 |
+
</div>
|
| 111 |
+
|
| 112 |
+
<div class="grid grid-cols-2 gap-4">
|
| 113 |
+
<div>
|
| 114 |
+
<label class="block text-sm font-medium text-gray-300 mb-1">DPI</label>
|
| 115 |
+
<input type="number" value="300" class="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-2 focus:ring-2 focus:ring-primary-500 focus:border-transparent">
|
| 116 |
+
</div>
|
| 117 |
+
<div>
|
| 118 |
+
<label class="block text-sm font-medium text-gray-300 mb-1">Canvas Width (mm)</label>
|
| 119 |
+
<input type="number" value="65" class="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-2 focus:ring-2 focus:ring-primary-500 focus:border-transparent">
|
| 120 |
+
</div>
|
| 121 |
+
</div>
|
| 122 |
+
|
| 123 |
+
<div class="grid grid-cols-2 gap-4">
|
| 124 |
+
<div>
|
| 125 |
+
<label class="block text-sm font-medium text-gray-300 mb-1">Canvas Height (mm)</label>
|
| 126 |
+
<input type="number" value="140" class="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-2 focus:ring-2 focus:ring-primary-500 focus:border-transparent">
|
| 127 |
+
</div>
|
| 128 |
+
<div>
|
| 129 |
+
<label class="block text-sm font-medium text-gray-300 mb-1">QR Box Width (mm)</label>
|
| 130 |
+
<input type="number" value="50" class="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-2 focus:ring-2 focus:ring-primary-500 focus:border-transparent">
|
| 131 |
+
</div>
|
| 132 |
+
</div>
|
| 133 |
+
|
| 134 |
+
<div class="grid grid-cols-2 gap-4">
|
| 135 |
+
<div>
|
| 136 |
+
<label class="block text-sm font-medium text-gray-300 mb-1">QR Box Height (mm)</label>
|
| 137 |
+
<input type="number" value="50" class="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-2 focus:ring-2 focus:ring-primary-500 focus:border-transparent">
|
| 138 |
+
</div>
|
| 139 |
+
<div>
|
| 140 |
+
<label class="block text-sm font-medium text-gray-300 mb-1">QR Position X (mm)</label>
|
| 141 |
+
<input type="number" value="0" class="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-2 focus:ring-2 focus:ring-primary-500 focus:border-transparent">
|
| 142 |
+
</div>
|
| 143 |
+
</div>
|
| 144 |
+
|
| 145 |
+
<div>
|
| 146 |
+
<label class="block text-sm font-medium text-gray-300 mb-1">QR Position Y (mm)</label>
|
| 147 |
+
<input type="number" value="0" class="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-2 focus:ring-2 focus:ring-primary-500 focus:border-transparent">
|
| 148 |
+
</div>
|
| 149 |
+
|
| 150 |
+
<div class="pt-4">
|
| 151 |
+
<button type="submit" class="w-full bg-primary-600 hover:bg-primary-700 text-white font-medium py-2 px-4 rounded-lg transition-colors">
|
| 152 |
+
Save Template
|
| 153 |
+
</button>
|
| 154 |
+
</div>
|
| 155 |
+
</div>
|
| 156 |
+
</form>
|
| 157 |
+
</div>
|
| 158 |
+
|
| 159 |
+
<!-- Preview Section -->
|
| 160 |
+
<div class="space-y-6">
|
| 161 |
+
<!-- QR Preview -->
|
| 162 |
+
<div class="bg-gray-900/50 border border-gray-800 rounded-xl p-6">
|
| 163 |
+
<h2 class="text-xl font-semibold mb-4 flex items-center">
|
| 164 |
+
<i data-feather="eye" class="w-5 h-5 mr-2 text-primary-500"></i>
|
| 165 |
+
Live Preview
|
| 166 |
+
</h2>
|
| 167 |
+
<div class="relative border-2 border-dashed border-gray-700 p-4 rounded-lg">
|
| 168 |
+
<div class="bg-white relative" style="width: 300px; height: 646px;">
|
| 169 |
+
<div style="position: absolute; left: 0px; top: 0px; width: 231px; height: 231px; border: 1px dashed gray;">
|
| 170 |
+
<div class="bg-gray-200 w-full h-full cursor-move flex items-center justify-center text-gray-600 text-sm">
|
| 171 |
+
QR Code Position
|
| 172 |
+
<div class="resize-handle"></div>
|
| 173 |
+
</div>
|
| 174 |
+
</div>
|
| 175 |
+
</div>
|
| 176 |
+
</div>
|
| 177 |
+
</div>
|
| 178 |
+
|
| 179 |
+
<!-- Upload Section -->
|
| 180 |
+
<div class="bg-gray-900/50 border border-gray-800 rounded-xl p-6">
|
| 181 |
+
<h2 class="text-xl font-semibold mb-4 flex items-center">
|
| 182 |
+
<i data-feather="upload" class="w-5 h-5 mr-2 text-primary-500"></i>
|
| 183 |
+
Upload QR Codes
|
| 184 |
+
</h2>
|
| 185 |
+
<div class="dropzone rounded-lg p-8 text-center cursor-pointer mb-4">
|
| 186 |
+
<input type="file" class="hidden" multiple accept="image/*">
|
| 187 |
+
<i data-feather="upload-cloud" class="w-12 h-12 mx-auto text-primary-500 mb-3"></i>
|
| 188 |
+
<p class="text-gray-400 mb-1">Drag & drop your QR code images here</p>
|
| 189 |
+
<p class="text-sm text-gray-500">or click to select files</p>
|
| 190 |
+
</div>
|
| 191 |
+
|
| 192 |
+
<div class="border border-gray-700 rounded-lg p-4 mb-4">
|
| 193 |
+
<h3 class="text-sm font-medium text-gray-300 mb-2">Selected Files</h3>
|
| 194 |
+
<div class="text-sm text-gray-400 italic">No files selected</div>
|
| 195 |
+
</div>
|
| 196 |
+
|
| 197 |
+
<button class="w-full bg-primary-600 hover:bg-primary-700 text-white font-medium py-2 px-4 rounded-lg transition-colors flex items-center justify-center">
|
| 198 |
+
<i data-feather="zap" class="w-4 h-4 mr-2"></i>
|
| 199 |
+
Process & Download
|
| 200 |
+
</button>
|
| 201 |
+
</div>
|
| 202 |
+
</div>
|
| 203 |
+
</div>
|
| 204 |
+
</main>
|
| 205 |
+
|
| 206 |
+
<!-- Footer -->
|
| 207 |
+
<footer class="border-t border-gray-800 py-8 mt-12">
|
| 208 |
+
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
| 209 |
+
<div class="flex flex-col md:flex-row justify-between items-center">
|
| 210 |
+
<div class="flex items-center space-x-2 mb-4 md:mb-0">
|
| 211 |
+
<div class="w-8 h-8 rounded-full bg-primary-500 flex items-center justify-center">
|
| 212 |
+
<i data-feather="maximize-2" class="text-white w-4 h-4"></i>
|
| 213 |
+
</div>
|
| 214 |
+
<span class="text-lg font-bold">QR Wizardry</span>
|
| 215 |
+
</div>
|
| 216 |
+
<div class="text-sm text-gray-500">
|
| 217 |
+
© 2023 QR Wizardry Portal. All rights reserved.
|
| 218 |
+
</div>
|
| 219 |
+
</div>
|
| 220 |
+
</div>
|
| 221 |
+
</footer>
|
| 222 |
+
</div>
|
| 223 |
+
|
| 224 |
+
<script>
|
| 225 |
+
feather.replace();
|
| 226 |
+
|
| 227 |
+
// Simple dropzone interaction
|
| 228 |
+
document.querySelectorAll('.dropzone').forEach(dropzone => {
|
| 229 |
+
const input = dropzone.querySelector('input');
|
| 230 |
+
|
| 231 |
+
dropzone.addEventListener('click', () => {
|
| 232 |
+
input.click();
|
| 233 |
+
});
|
| 234 |
+
|
| 235 |
+
['dragenter', 'dragover'].forEach(eventName => {
|
| 236 |
+
dropzone.addEventListener(eventName, (e) => {
|
| 237 |
+
e.preventDefault();
|
| 238 |
+
dropzone.classList.add('active');
|
| 239 |
+
});
|
| 240 |
+
});
|
| 241 |
+
|
| 242 |
+
['dragleave', 'drop'].forEach(eventName => {
|
| 243 |
+
dropzone.addEventListener(eventName, (e) => {
|
| 244 |
+
e.preventDefault();
|
| 245 |
+
dropzone.classList.remove('active');
|
| 246 |
+
});
|
| 247 |
+
});
|
| 248 |
+
|
| 249 |
+
input.addEventListener('change', () => {
|
| 250 |
+
const filesList = input.files;
|
| 251 |
+
if (filesList.length > 0) {
|
| 252 |
+
const filesContainer = dropzone.nextElementSibling.querySelector('div');
|
| 253 |
+
filesContainer.innerHTML = '';
|
| 254 |
+
|
| 255 |
+
Array.from(filesList).forEach(file => {
|
| 256 |
+
const fileElement = document.createElement('div');
|
| 257 |
+
fileElement.className = 'flex justify-between items-center py-1';
|
| 258 |
+
fileElement.innerHTML = `
|
| 259 |
+
<span class="truncate">${file.name}</span>
|
| 260 |
+
<span class="text-gray-500 text-xs">${(file.size / 1024).toFixed(1)} KB</span>
|
| 261 |
+
`;
|
| 262 |
+
filesContainer.appendChild(fileElement);
|
| 263 |
+
});
|
| 264 |
+
}
|
| 265 |
+
});
|
| 266 |
+
});
|
| 267 |
+
</script>
|
| 268 |
+
</body>
|
| 269 |
+
</html>
|