This commit is contained in:
ct
2025-06-13 09:44:18 +08:00
parent 8d6e86ebb3
commit 2fd4d42aec
54 changed files with 9917 additions and 184 deletions

View File

@@ -4,6 +4,7 @@ import { createInertiaApp } from '@inertiajs/react';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
import { createRoot } from 'react-dom/client';
import { initializeTheme } from './hooks/use-appearance';
import { AxiosProvider } from './plugins/AxiosContext';
const appName = import.meta.env.VITE_APP_NAME || 'Laravel';
@@ -13,7 +14,15 @@ createInertiaApp({
setup({ el, App, props }) {
const root = createRoot(el);
root.render(<App {...props} />);
const app = (
<>
<AxiosProvider>
<App {...props} />
</AxiosProvider>
</>
);
root.render(app);
},
progress: {
color: '#4B5563',

View File

@@ -1,5 +1,7 @@
import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet';
import useMediaStore from '@/stores/MediaStore';
import { Coins, Edit3, Plus } from 'lucide-react';
import { useEffect } from 'react';
interface EditSidebarProps {
isOpen: boolean;
@@ -7,6 +9,22 @@ interface EditSidebarProps {
}
export default function EditSidebar({ isOpen, onClose }: EditSidebarProps) {
const { memes, backgrounds, isFetchingMemes, isFetchingBackgrounds, currentTab, setCurrentTab, fetchMemes, fetchBackgrounds } = useMediaStore();
// Fetch media based on currentTab when it changes and data isn't fetched yet
useEffect(() => {
if (currentTab === 'memes' && memes.length === 0 && !isFetchingMemes) {
fetchMemes();
} else if (currentTab === 'backgrounds' && backgrounds.length === 0 && !isFetchingBackgrounds) {
fetchBackgrounds();
}
}, [currentTab, memes.length, backgrounds.length, isFetchingMemes, isFetchingBackgrounds, fetchMemes, fetchBackgrounds]);
// Determine display states
const isFetching = currentTab === 'memes' ? isFetchingMemes : isFetchingBackgrounds;
const media = currentTab === 'memes' ? memes : backgrounds;
const mediaType = currentTab === 'memes' ? 'memes' : 'backgrounds';
return (
<Sheet open={isOpen} onOpenChange={(open) => !open && onClose()}>
<SheetContent side="right" className="w-80 overflow-y-auto">
@@ -20,7 +38,12 @@ export default function EditSidebar({ isOpen, onClose }: EditSidebarProps) {
<div className="space-y-6">
{/* Background and Meme Selection */}
<div className="grid grid-cols-2 gap-4">
<div className="rounded-lg border-2 border-gray-300 p-3 text-center">
<div
className={`cursor-pointer rounded-lg border-2 p-3 text-center ${
currentTab === 'backgrounds' ? 'border-blue-500' : 'border-gray-300'
}`}
onClick={() => setCurrentTab('backgrounds')}
>
<div className="mb-2 h-16 w-full overflow-hidden rounded bg-blue-600">
<img
src="/placeholder.svg?height=64&width=120"
@@ -32,7 +55,12 @@ export default function EditSidebar({ isOpen, onClose }: EditSidebarProps) {
</div>
<span className="text-sm font-medium">Background</span>
</div>
<div className="rounded-lg border-2 border-gray-300 p-3 text-center">
<div
className={`cursor-pointer rounded-lg border-2 p-3 text-center ${
currentTab === 'memes' ? 'border-blue-500' : 'border-gray-300'
}`}
onClick={() => setCurrentTab('memes')}
>
<div className="mb-2 h-16 w-full overflow-hidden rounded bg-gray-200">
<img
src="/placeholder.svg?height=64&width=120"
@@ -49,7 +77,6 @@ export default function EditSidebar({ isOpen, onClose }: EditSidebarProps) {
{/* AI Background Search */}
<div>
<h3 className="mb-4 text-lg font-medium">Search for backgrounds using AI</h3>
<div className="mb-4 rounded-lg border-2 border-dashed border-gray-300 p-6 text-center">
<div className="mx-auto mb-3 flex h-12 w-12 items-center justify-center rounded-full border-2 border-gray-400">
<Plus className="h-6 w-6" />
@@ -64,68 +91,24 @@ export default function EditSidebar({ isOpen, onClose }: EditSidebarProps) {
</div>
</div>
{/* Meme Templates Grid */}
<div className="grid grid-cols-2 gap-3">
<div className="aspect-square overflow-hidden rounded-lg bg-gray-100">
<img
src="/placeholder.svg?height=150&width=150"
alt="Creepy face meme"
width={150}
height={150}
className="h-full w-full object-cover"
/>
{/* Media Grid */}
{isFetching && <div>Loading {mediaType}...</div>}
{!isFetching && media.length === 0 && <div>No {mediaType} available</div>}
{!isFetching && media.length > 0 && (
<div className="grid grid-cols-2 gap-3">
{media.map((item, index) => (
<div key={index} className="aspect-square overflow-hidden rounded-lg bg-gray-100">
<img
src={item.imageUrl}
alt={currentTab === 'memes' ? 'Meme' : 'Background'}
width={150}
height={150}
className="h-full w-full object-cover"
/>
</div>
))}
</div>
<div className="relative aspect-square overflow-hidden rounded-lg bg-gray-100">
<img
src="/placeholder.svg?height=150&width=150"
alt="Confused person meme"
width={150}
height={150}
className="h-full w-full object-cover"
/>
<div className="absolute top-2 right-2 text-sm font-bold text-black">???</div>
<div className="absolute bottom-2 left-2 text-sm font-bold text-black">???</div>
</div>
<div className="aspect-square overflow-hidden rounded-lg bg-gray-100">
<img
src="/placeholder.svg?height=150&width=150"
alt="Woody meme"
width={150}
height={150}
className="h-full w-full object-cover"
/>
</div>
<div className="aspect-square overflow-hidden rounded-lg bg-gray-100">
<img
src="/placeholder.svg?height=150&width=150"
alt="Doge meme"
width={150}
height={150}
className="h-full w-full object-cover"
/>
</div>
<div className="relative aspect-square overflow-hidden rounded-lg bg-blue-900">
<img
src="/placeholder.svg?height=150&width=150"
alt="Stock market meme"
width={150}
height={150}
className="h-full w-full object-cover"
/>
<div className="absolute inset-0 flex items-center justify-center bg-blue-900/50">
<span className="text-xs text-white">📈 28%</span>
</div>
</div>
<div className="aspect-square overflow-hidden rounded-lg bg-yellow-100">
<img
src="/placeholder.svg?height=150&width=150"
alt="Room meme"
width={150}
height={150}
className="h-full w-full object-cover"
/>
</div>
</div>
)}
</div>
</SheetContent>
</Sheet>

View File

@@ -1,7 +1,6 @@
import AppLayout from '@/layouts/app-layout';
import { type BreadcrumbItem } from '@/types';
import { Head } from '@inertiajs/react';
import VideoTimelineViewer from './partials/VideoTimelineViewer';
const breadcrumbs: BreadcrumbItem[] = [
{
@@ -14,9 +13,7 @@ export default function Dashboard() {
return (
<AppLayout breadcrumbs={breadcrumbs}>
<Head title="Dashboard" />
<div className="flex h-full flex-1 flex-col gap-4 rounded-xl p-4">
<VideoTimelineViewer></VideoTimelineViewer>
</div>
<div className="flex h-full flex-1 flex-col gap-4 rounded-xl p-4"></div>
</AppLayout>
);
}

View File

@@ -1,27 +1,23 @@
export default function ComingSoon() {
return (
<div className="flex min-h-screen flex-col items-center justify-center bg-white px-4 text-center">
<div className="max-w-2xl space-y-5">
<div className="grid space-y-3 items-center justify-center">
<h1 className="ml-2 text-6xl text-black font-bold">Video² AI</h1>
return (
<div className="flex min-h-screen flex-col items-center justify-center bg-white px-4 text-center">
<div className="max-w-2xl space-y-5">
<div className="grid items-center justify-center space-y-3">
<h1 className="font-display ml-0 text-2xl tracking-wide md:ml-3 md:text-4xl">MEMEAIGEN</h1>
<div className="space-y-1" data-nosnippet="true">
<div className="inline-block rounded-full bg-neutral-100 px-3 py-1 text-sm font-medium text-neutral-700">
Coming Soon
</div>
<div className="space-y-1" data-nosnippet="true">
<div className="inline-block rounded-full bg-neutral-100 px-3 py-1 text-sm font-medium text-neutral-700">Coming Soon</div>
</div>
</div>
<div className="space-y-3">
<h2 className="mx-auto max-w-lg text-neutral-500 md:text-2xl">Make video memes with AI</h2>
<p className="font-medium text-neutral-600">memeaigen.com</p>
</div>
</div>
<footer className="absolute bottom-4 text-sm text-neutral-400">© {new Date().getFullYear()} Meme AI Gen</footer>
</div>
<div className="space-y-3">
<h2 className="mx-auto max-w-lg text-gray-500 text-2xl">
We amplify the power of video creation for everyone</h2>
<p className="text-neutral-600 font-medium">video2.ai</p>
</div>
</div>
<footer className="absolute bottom-4 text-sm text-gray-400">© {new Date().getFullYear()} Video² AI</footer>
</div>
)
);
}

View File

@@ -0,0 +1,9 @@
// resources/js/Plugins/AxiosContext.js
import { createContext, useContext } from 'react';
import axiosInstance from './axios-plugin';
const AxiosContext = createContext();
export const AxiosProvider = ({ children }) => <AxiosContext.Provider value={axiosInstance}>{children}</AxiosContext.Provider>;
export const useAxios = () => useContext(AxiosContext);

View File

@@ -0,0 +1,23 @@
import axios from 'axios';
import { toast } from 'sonner';
const axiosInstance = axios.create({
withCredentials: true,
xsrfHeaderName: 'X-XSRF-TOKEN',
xsrfCookieName: 'XSRF-TOKEN',
});
axiosInstance.interceptors.response.use(
(response) => response,
(error) => {
//console.log(error);
if (error.response && error.response.status === 500) {
if (error?.response?.data?.message?.length > 0) {
toast.error(error.response.data.message + ' Please try again later.');
}
}
return Promise.reject(error);
},
);
export default axiosInstance;

View File

@@ -1,37 +0,0 @@
import axios from 'axios';
import { toast } from 'sonner';
import { route } from 'ziggy-js';
import { create } from 'zustand';
const useAdminVideoTimelineStore = create((set) => ({
videoTimelines: [],
isLoadingVideoTimelines: false,
getVideoTimelines: async (uuid) => {
try {
set({ isLoadingVideoTimelines: true });
const response = await axios.get(route('render.elements.get', { uuid }));
// Just set the elements directly without any mapping
set({
videoTimelines: response.data.success.data.video_elements || [],
isLoadingVideoTimelines: false,
});
return response.data.success.data.video_elements || [];
} catch (error) {
set({ isLoadingVideoTimelines: false });
if (axios.isAxiosError(error) && error.response?.data?.error?.message) {
toast(error.response.data.error.message);
} else {
toast('Failed to load video timeline');
}
return [];
}
},
}));
export default useAdminVideoTimelineStore;

View File

@@ -0,0 +1,93 @@
import axiosInstance from '@/plugins/axios-plugin';
import { mountStoreDevtool } from 'simple-zustand-devtools';
import { toast } from 'sonner';
import { route } from 'ziggy-js';
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
const useMediaStore = create(
devtools((set, get) => ({
currentTab: 'memes',
memes: [],
backgrounds: [],
isFetchingMemes: false,
isFetchingBackgrounds: false,
setCurrentTab: (tab) => {
set({ currentTab: tab });
},
// Fetch memes (overlays)
fetchMemes: async () => {
set({ isFetchingMemes: true });
try {
const response = await axiosInstance.post(route('api.app.memes'));
if (response?.data?.success?.data?.memes) {
set({
memes: response.data.success.data.memes,
isFetchingMemes: false,
});
return response.data.success.data.memes;
} else {
throw 'Invalid API response';
}
} catch (error) {
console.error('Error fetching memes:', error);
set({ isFetchingMemes: false });
if (error?.response?.data?.error?.message?.length > 0) {
toast.error(error.response.data.error.message);
}
throw error;
}
},
// Fetch backgrounds
fetchBackgrounds: async () => {
set({ isFetchingBackgrounds: true });
try {
const response = await axiosInstance.post(route('api.app.background'));
if (response?.data?.success?.data?.backgrounds) {
set({
backgrounds: response.data.success.data.backgrounds,
isFetchingBackgrounds: false,
});
return response.data.success.data.backgrounds;
} else {
throw 'Invalid API response';
}
} catch (error) {
console.error('Error fetching backgrounds:', error);
set({ isFetchingBackgrounds: false });
if (error?.response?.data?.error?.message?.length > 0) {
toast.error(error.response.data.error.message);
}
throw error;
}
},
// Reset store to default state
restoreMemeStateToDefault: () => {
console.log('restoreMemeStateToDefault');
set({
memes: [],
backgrounds: [],
isFetchingMemes: false,
isFetchingBackgrounds: false,
});
},
})),
{
name: 'MemeStore',
store: 'MemeStore',
},
);
if (import.meta.env.APP_ENV === 'local') {
mountStoreDevtool('MediaStore', useMediaStore);
}
export default useMediaStore;

View File

@@ -77,4 +77,8 @@ const useLocalSettingsStore = create(
)
);
if (import.meta.env.APP_ENV === "local") {
mountStoreDevtool("LocalSettingsStore", useLocalSettingsStore);
}
export default useLocalSettingsStore;

View File

@@ -1,4 +1,4 @@
const Ziggy = {"url":"https:\/\/memeaigen.test","port":null,"defaults":{},"routes":{"horizon.stats.index":{"uri":"horizon\/api\/stats","methods":["GET","HEAD"]},"horizon.workload.index":{"uri":"horizon\/api\/workload","methods":["GET","HEAD"]},"horizon.masters.index":{"uri":"horizon\/api\/masters","methods":["GET","HEAD"]},"horizon.monitoring.index":{"uri":"horizon\/api\/monitoring","methods":["GET","HEAD"]},"horizon.monitoring.store":{"uri":"horizon\/api\/monitoring","methods":["POST"]},"horizon.monitoring-tag.paginate":{"uri":"horizon\/api\/monitoring\/{tag}","methods":["GET","HEAD"],"parameters":["tag"]},"horizon.monitoring-tag.destroy":{"uri":"horizon\/api\/monitoring\/{tag}","methods":["DELETE"],"wheres":{"tag":".*"},"parameters":["tag"]},"horizon.jobs-metrics.index":{"uri":"horizon\/api\/metrics\/jobs","methods":["GET","HEAD"]},"horizon.jobs-metrics.show":{"uri":"horizon\/api\/metrics\/jobs\/{id}","methods":["GET","HEAD"],"parameters":["id"]},"horizon.queues-metrics.index":{"uri":"horizon\/api\/metrics\/queues","methods":["GET","HEAD"]},"horizon.queues-metrics.show":{"uri":"horizon\/api\/metrics\/queues\/{id}","methods":["GET","HEAD"],"parameters":["id"]},"horizon.jobs-batches.index":{"uri":"horizon\/api\/batches","methods":["GET","HEAD"]},"horizon.jobs-batches.show":{"uri":"horizon\/api\/batches\/{id}","methods":["GET","HEAD"],"parameters":["id"]},"horizon.jobs-batches.retry":{"uri":"horizon\/api\/batches\/retry\/{id}","methods":["POST"],"parameters":["id"]},"horizon.pending-jobs.index":{"uri":"horizon\/api\/jobs\/pending","methods":["GET","HEAD"]},"horizon.completed-jobs.index":{"uri":"horizon\/api\/jobs\/completed","methods":["GET","HEAD"]},"horizon.silenced-jobs.index":{"uri":"horizon\/api\/jobs\/silenced","methods":["GET","HEAD"]},"horizon.failed-jobs.index":{"uri":"horizon\/api\/jobs\/failed","methods":["GET","HEAD"]},"horizon.failed-jobs.show":{"uri":"horizon\/api\/jobs\/failed\/{id}","methods":["GET","HEAD"],"parameters":["id"]},"horizon.retry-jobs.show":{"uri":"horizon\/api\/jobs\/retry\/{id}","methods":["POST"],"parameters":["id"]},"horizon.jobs.show":{"uri":"horizon\/api\/jobs\/{id}","methods":["GET","HEAD"],"parameters":["id"]},"horizon.index":{"uri":"horizon\/{view?}","methods":["GET","HEAD"],"wheres":{"view":"(.*)"},"parameters":["view"]},"sanctum.csrf-cookie":{"uri":"sanctum\/csrf-cookie","methods":["GET","HEAD"]},"dashboard":{"uri":"dashboard","methods":["GET","HEAD"]},"admin.dashboard":{"uri":"admin","methods":["GET","HEAD"]},"profile.edit":{"uri":"settings\/profile","methods":["GET","HEAD"]},"profile.update":{"uri":"settings\/profile","methods":["PATCH"]},"profile.destroy":{"uri":"settings\/profile","methods":["DELETE"]},"password.edit":{"uri":"settings\/password","methods":["GET","HEAD"]},"password.update":{"uri":"settings\/password","methods":["PUT"]},"appearance":{"uri":"settings\/appearance","methods":["GET","HEAD"]},"register":{"uri":"register","methods":["GET","HEAD"]},"login":{"uri":"login","methods":["GET","HEAD"]},"password.request":{"uri":"forgot-password","methods":["GET","HEAD"]},"password.email":{"uri":"forgot-password","methods":["POST"]},"password.reset":{"uri":"reset-password\/{token}","methods":["GET","HEAD"],"parameters":["token"]},"password.store":{"uri":"reset-password","methods":["POST"]},"verification.notice":{"uri":"verify-email","methods":["GET","HEAD"]},"verification.verify":{"uri":"verify-email\/{id}\/{hash}","methods":["GET","HEAD"],"parameters":["id","hash"]},"verification.send":{"uri":"email\/verification-notification","methods":["POST"]},"password.confirm":{"uri":"confirm-password","methods":["GET","HEAD"]},"logout":{"uri":"logout","methods":["POST"]},"home":{"uri":"\/","methods":["GET","HEAD"]},"test":{"uri":"tests","methods":["GET","HEAD"]},"storage.local":{"uri":"storage\/{path}","methods":["GET","HEAD"],"wheres":{"path":".*"},"parameters":["path"]}}};
const Ziggy = {"url":"https:\/\/memeaigen.com","port":null,"defaults":{},"routes":{"horizon.stats.index":{"uri":"horizon\/api\/stats","methods":["GET","HEAD"]},"horizon.workload.index":{"uri":"horizon\/api\/workload","methods":["GET","HEAD"]},"horizon.masters.index":{"uri":"horizon\/api\/masters","methods":["GET","HEAD"]},"horizon.monitoring.index":{"uri":"horizon\/api\/monitoring","methods":["GET","HEAD"]},"horizon.monitoring.store":{"uri":"horizon\/api\/monitoring","methods":["POST"]},"horizon.monitoring-tag.paginate":{"uri":"horizon\/api\/monitoring\/{tag}","methods":["GET","HEAD"],"parameters":["tag"]},"horizon.monitoring-tag.destroy":{"uri":"horizon\/api\/monitoring\/{tag}","methods":["DELETE"],"wheres":{"tag":".*"},"parameters":["tag"]},"horizon.jobs-metrics.index":{"uri":"horizon\/api\/metrics\/jobs","methods":["GET","HEAD"]},"horizon.jobs-metrics.show":{"uri":"horizon\/api\/metrics\/jobs\/{id}","methods":["GET","HEAD"],"parameters":["id"]},"horizon.queues-metrics.index":{"uri":"horizon\/api\/metrics\/queues","methods":["GET","HEAD"]},"horizon.queues-metrics.show":{"uri":"horizon\/api\/metrics\/queues\/{id}","methods":["GET","HEAD"],"parameters":["id"]},"horizon.jobs-batches.index":{"uri":"horizon\/api\/batches","methods":["GET","HEAD"]},"horizon.jobs-batches.show":{"uri":"horizon\/api\/batches\/{id}","methods":["GET","HEAD"],"parameters":["id"]},"horizon.jobs-batches.retry":{"uri":"horizon\/api\/batches\/retry\/{id}","methods":["POST"],"parameters":["id"]},"horizon.pending-jobs.index":{"uri":"horizon\/api\/jobs\/pending","methods":["GET","HEAD"]},"horizon.completed-jobs.index":{"uri":"horizon\/api\/jobs\/completed","methods":["GET","HEAD"]},"horizon.silenced-jobs.index":{"uri":"horizon\/api\/jobs\/silenced","methods":["GET","HEAD"]},"horizon.failed-jobs.index":{"uri":"horizon\/api\/jobs\/failed","methods":["GET","HEAD"]},"horizon.failed-jobs.show":{"uri":"horizon\/api\/jobs\/failed\/{id}","methods":["GET","HEAD"],"parameters":["id"]},"horizon.retry-jobs.show":{"uri":"horizon\/api\/jobs\/retry\/{id}","methods":["POST"],"parameters":["id"]},"horizon.jobs.show":{"uri":"horizon\/api\/jobs\/{id}","methods":["GET","HEAD"],"parameters":["id"]},"horizon.index":{"uri":"horizon\/{view?}","methods":["GET","HEAD"],"wheres":{"view":"(.*)"},"parameters":["view"]},"sanctum.csrf-cookie":{"uri":"sanctum\/csrf-cookie","methods":["GET","HEAD"]},"api.app.memes":{"uri":"api\/app\/memes","methods":["POST"]},"api.app.background":{"uri":"api\/app\/background","methods":["POST"]},"dashboard":{"uri":"dashboard","methods":["GET","HEAD"]},"admin.dashboard":{"uri":"admin","methods":["GET","HEAD"]},"profile.edit":{"uri":"settings\/profile","methods":["GET","HEAD"]},"profile.update":{"uri":"settings\/profile","methods":["PATCH"]},"profile.destroy":{"uri":"settings\/profile","methods":["DELETE"]},"password.edit":{"uri":"settings\/password","methods":["GET","HEAD"]},"password.update":{"uri":"settings\/password","methods":["PUT"]},"appearance":{"uri":"settings\/appearance","methods":["GET","HEAD"]},"register":{"uri":"register","methods":["GET","HEAD"]},"login":{"uri":"login","methods":["GET","HEAD"]},"password.request":{"uri":"forgot-password","methods":["GET","HEAD"]},"password.email":{"uri":"forgot-password","methods":["POST"]},"password.reset":{"uri":"reset-password\/{token}","methods":["GET","HEAD"],"parameters":["token"]},"password.store":{"uri":"reset-password","methods":["POST"]},"verification.notice":{"uri":"verify-email","methods":["GET","HEAD"]},"verification.verify":{"uri":"verify-email\/{id}\/{hash}","methods":["GET","HEAD"],"parameters":["id","hash"]},"verification.send":{"uri":"email\/verification-notification","methods":["POST"]},"password.confirm":{"uri":"confirm-password","methods":["GET","HEAD"]},"logout":{"uri":"logout","methods":["POST"]},"home":{"uri":"\/","methods":["GET","HEAD"]},"test":{"uri":"tests","methods":["GET","HEAD"]},"storage.local":{"uri":"storage\/{path}","methods":["GET","HEAD"],"wheres":{"path":".*"},"parameters":["path"]}}};
if (typeof window !== 'undefined' && typeof window.Ziggy !== 'undefined') {
Object.assign(Ziggy.routes, window.Ziggy.routes);
}