diff --git a/app/Http/Controllers/ThemesController.php b/app/Http/Controllers/ThemesController.php index 2818af3..d8ec5d2 100644 --- a/app/Http/Controllers/ThemesController.php +++ b/app/Http/Controllers/ThemesController.php @@ -3,11 +3,52 @@ namespace App\Http\Controllers; use App\Models\Theme; +use Illuminate\Http\Request; use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\Http; use Inertia\Inertia; class ThemesController extends Controller { + public function create() + { + return Inertia::render('themes/create'); + } + + public function store(Request $request) + { + $request->validate([ + 'url' => ['required', 'url'], + ]); + + $response = Http::get($request->url); + + if ($response->failed()) { + return back()->withErrors(['url' => 'Could not fetch registry from the provided URL.']); + } + + $data = $response->json(); + + if (empty($data) || ! isset($data['name'])) { + return back()->withErrors(['url' => 'Invalid registry JSON format.']); + } + + // Check if theme already exists + if (Theme::where('name', $data['name'])->exists()) { + return back()->withErrors(['url' => "A theme named [{$data['name']}] already exists."]); + } + + $theme = Theme::fromRegistry($data); + $theme->user_id = auth()->id(); + $theme->save(); + + Cache::forget('themes:total_count'); + Cache::forget('themes:available_categories'); + + return redirect()->route('themes.show', $theme->name) + ->with('success', 'Theme created successfully.'); + } + public function index() { $availableCategories = Cache::remember('themes:available_categories', 3600, fn () => Theme::query() diff --git a/resources/js/pages/themes/create.tsx b/resources/js/pages/themes/create.tsx new file mode 100644 index 0000000..23c19f3 --- /dev/null +++ b/resources/js/pages/themes/create.tsx @@ -0,0 +1,93 @@ +import { Head, useForm } from '@inertiajs/react'; +import { Loader2 } from 'lucide-react'; +import Heading from '@/components/heading'; +import { Button } from '@/components/ui/button'; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from '@/components/ui/card'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import MainLayout from '@/layouts/main-layout'; +import MainWrapper from '@/layouts/main/main-wrapper'; +import { store } from '@/routes/themes'; + +export default function ThemeCreate() { + const { data, setData, post, processing, errors } = useForm({ + url: '', + }); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + post(store().url); + }; + + return ( + + + +
+ + + +
+ + Import from URL + + Enter a valid shadcn registry JSON URL (e.g. + from tweakcn.com). + + + +
+ + + setData('url', e.target.value) + } + required + autoFocus + /> + {errors.url && ( +

+ {errors.url} +

+ )} +
+
+ + + +
+
+ +
+

Example URLs:

+
    +
  • https://tweakcn.com/r/themes/neo-brutalism.json
  • +
  • https://tweakcn.com/r/themes/modern-dark.json
  • +
+
+
+
+ ); +} + +ThemeCreate.layout = MainLayout; diff --git a/resources/js/pages/themes/index.tsx b/resources/js/pages/themes/index.tsx index d19b56d..8b59742 100644 --- a/resources/js/pages/themes/index.tsx +++ b/resources/js/pages/themes/index.tsx @@ -6,7 +6,7 @@ import MainWrapper from '@/layouts/main/main-wrapper'; import MainThemeCard from '@/layouts/main/theme/main-theme-card'; import { MainThemeSearch } from '@/layouts/main/theme/main-theme-search'; import MainLayout from '@/layouts/main-layout'; -import { show } from '@/routes/themes'; +import { create, show } from '@/routes/themes'; import type { PaginatedData, Registry } from '@/types'; function ThemesIndex({ @@ -28,15 +28,17 @@ function ThemesIndex({ description={`Choose from ${totalThemesCount} themes to customize your site's look and feel. Preview, install, and manage them all in one place.`} />
- + + +
diff --git a/routes/web.php b/routes/web.php index d6d4785..91c409d 100644 --- a/routes/web.php +++ b/routes/web.php @@ -13,8 +13,12 @@ Route::get('/', HomePageController::class)->name('home'); Route::get('/pricing', [SubscriptionController::class, 'index'])->name('pricing'); + Route::get('/themes', [ThemesController::class, 'index'])->name('themes.index'); +Route::get('/themes/create', [ThemesController::class, 'create'])->name('themes.create'); +Route::post('/themes', [ThemesController::class, 'store'])->name('themes.store'); Route::get('/themes/{theme}', [ThemesController::class, 'show'])->name('themes.show'); + Route::get('/fonts', [FontsController::class, 'index'])->name('fonts.index'); Route::get('/animate-css', [AnimateController::class, 'index'])->name('animate-css.index');