-
New Project
-
setName(e.target.value)}
- placeholder="Project Name"
+
+ {/* Backdrop */}
+
-
-
+
+ {/* Modal */}
+
+
+
+
New Project
+
+
+
+
+ setName(e.target.value)}
+ onKeyDown={e => e.key === 'Enter' && handleCreate()}
+ placeholder="my-awesome-game"
+ autoFocus
+ className="w-full bg-[#0a0a0a] border border-[#333] rounded-lg px-4 py-3 text-white placeholder-[#666] focus:outline-none focus:border-purple-500 transition-colors"
+ />
+
+
+
+
+
+
+
+
);
};
diff --git a/src/components/aethex/settings-panel.tsx b/src/components/aethex/settings-panel.tsx
new file mode 100644
index 0000000..f389323
--- /dev/null
+++ b/src/components/aethex/settings-panel.tsx
@@ -0,0 +1,320 @@
+"use client";
+
+import { useState } from "react";
+import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Switch } from "@/components/ui/switch";
+import { Slider } from "@/components/ui/slider";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { ScrollArea } from "@/components/ui/scroll-area";
+import {
+ Settings,
+ Palette,
+ Code,
+ Keyboard,
+ Zap,
+ Cloud,
+ User,
+ Monitor,
+} from "lucide-react";
+import { cn } from "@/lib/utils";
+
+interface SettingsPanelProps {
+ open: boolean;
+ onClose: () => void;
+}
+
+export function SettingsPanel({ open, onClose }: SettingsPanelProps) {
+ const [settings, setSettings] = useState({
+ theme: "dark",
+ fontSize: 14,
+ tabSize: 2,
+ wordWrap: true,
+ autoSave: true,
+ autoSaveDelay: 1000,
+ minimap: true,
+ lineNumbers: true,
+ bracketMatching: true,
+ aiModel: "claude-3.5",
+ aiAutoComplete: true,
+ syncEnabled: true,
+ });
+
+ const updateSetting = (key: string, value: any) => {
+ setSettings(prev => ({ ...prev, [key]: value }));
+ };
+
+ return (
+
+ );
+}
diff --git a/src/components/aethex/status-bar.tsx b/src/components/aethex/status-bar.tsx
new file mode 100644
index 0000000..962d958
--- /dev/null
+++ b/src/components/aethex/status-bar.tsx
@@ -0,0 +1,182 @@
+"use client";
+
+import { useState, useEffect } from "react";
+import { toast } from "sonner";
+import {
+ GitBranch,
+ AlertCircle,
+ AlertTriangle,
+ CheckCircle2,
+ Wifi,
+ WifiOff,
+ Cloud,
+ CloudOff,
+ Cpu,
+ Bell,
+ Zap,
+} from "lucide-react";
+import { cn } from "@/lib/utils";
+
+interface StatusBarProps {
+ platform?: "roblox" | "uefn" | "spatial" | "web";
+ fileName?: string;
+ cursorPosition?: { line: number; column: number };
+ language?: string;
+}
+
+const platformConfig = {
+ roblox: { color: "bg-red-500", label: "Roblox Studio" },
+ uefn: { color: "bg-purple-500", label: "UEFN" },
+ spatial: { color: "bg-emerald-500", label: "Spatial" },
+ web: { color: "bg-blue-500", label: "Web" },
+};
+
+export function StatusBar({
+ platform = "roblox",
+ fileName,
+ cursorPosition = { line: 1, column: 1 },
+ language = "Lua",
+}: StatusBarProps) {
+ const [isConnected, setIsConnected] = useState(true);
+ const [errors, setErrors] = useState(0);
+ const [warnings, setWarnings] = useState(2);
+ const [syncStatus, setSyncStatus] = useState<"synced" | "syncing" | "offline">("synced");
+
+ // Simulated connection status
+ useEffect(() => {
+ const interval = setInterval(() => {
+ setIsConnected(Math.random() > 0.1);
+ }, 5000);
+ return () => clearInterval(interval);
+ }, []);
+
+ const config = platformConfig[platform];
+
+ return (
+
+ );
+}
diff --git a/src/components/ui/accordion.tsx b/src/components/ui/accordion.tsx
index 1fa55cd..f265771 100644
--- a/src/components/ui/accordion.tsx
+++ b/src/components/ui/accordion.tsx
@@ -1,6 +1,6 @@
import { ComponentProps } from "react"
import * as AccordionPrimitive from "@radix-ui/react-accordion"
-import ChevronDownIcon from "lucide-react/dist/esm/icons/chevron-down"
+import { ChevronDown as ChevronDownIcon } from "lucide-react"
import { cn } from "@/lib/utils"
diff --git a/src/components/ui/breadcrumb.tsx b/src/components/ui/breadcrumb.tsx
index fb8b2ec..8d0f2a4 100644
--- a/src/components/ui/breadcrumb.tsx
+++ b/src/components/ui/breadcrumb.tsx
@@ -1,7 +1,6 @@
import { ComponentProps } from "react"
import { Slot } from "@radix-ui/react-slot"
-import ChevronRight from "lucide-react/dist/esm/icons/chevron-right"
-import MoreHorizontal from "lucide-react/dist/esm/icons/more-horizontal"
+import { ChevronRight, MoreHorizontal } from "lucide-react"
import { cn } from "@/lib/utils"
diff --git a/src/components/ui/calendar.tsx b/src/components/ui/calendar.tsx
index 426ef4c..fca4b04 100644
--- a/src/components/ui/calendar.tsx
+++ b/src/components/ui/calendar.tsx
@@ -1,6 +1,5 @@
import { ComponentProps } from "react"
-import ChevronLeft from "lucide-react/dist/esm/icons/chevron-left"
-import ChevronRight from "lucide-react/dist/esm/icons/chevron-right"
+import { ChevronLeft, ChevronRight } from "lucide-react"
import { DayPicker } from "react-day-picker"
import { cn } from "@/lib/utils"
@@ -60,10 +59,14 @@ function Calendar({
}}
components={{
PreviousMonthButton: ({ className, ...props }) => (
-
+
),
NextMonthButton: ({ className, ...props }) => (
-
+
),
}}
{...props}
diff --git a/src/components/ui/carousel.tsx b/src/components/ui/carousel.tsx
index 0e76744..497c56d 100644
--- a/src/components/ui/carousel.tsx
+++ b/src/components/ui/carousel.tsx
@@ -4,8 +4,7 @@ import { ComponentProps, createContext, useCallback, useContext, useEffect, useS
import useEmblaCarousel, {
type UseEmblaCarouselType,
} from "embla-carousel-react"
-import ArrowLeft from "lucide-react/dist/esm/icons/arrow-left"
-import ArrowRight from "lucide-react/dist/esm/icons/arrow-right"
+import { ArrowLeft, ArrowRight } from "lucide-react"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
diff --git a/src/components/ui/command.tsx b/src/components/ui/command.tsx
index 07eefef..cb8a852 100644
--- a/src/components/ui/command.tsx
+++ b/src/components/ui/command.tsx
@@ -2,7 +2,7 @@
import { ComponentProps } from "react"
import { Command as CommandPrimitive } from "cmdk"
-import SearchIcon from "lucide-react/dist/esm/icons/search"
+import { Search as SearchIcon } from "lucide-react"
import { cn } from "@/lib/utils"
import {
diff --git a/src/components/ui/context-menu.tsx b/src/components/ui/context-menu.tsx
index b4542b0..6b2ef09 100644
--- a/src/components/ui/context-menu.tsx
+++ b/src/components/ui/context-menu.tsx
@@ -1,253 +1,185 @@
"use client"
-import { ComponentProps } from "react"
-import {
- Root as ContextMenuRoot,
- Trigger as ContextMenuTrigger,
- Content as ContextMenuContent,
- Item as ContextMenuItem,
- Separator as ContextMenuSeparator,
- CheckboxItem as ContextMenuCheckboxItem,
- RadioGroup as ContextMenuRadioGroup,
- RadioItem as ContextMenuRadioItem,
- Sub as ContextMenuSub,
- SubTrigger as ContextMenuSubTrigger,
- SubContent as ContextMenuSubContent,
- Label as ContextMenuLabel,
- Group as ContextMenuGroup,
-} from "@radix-ui/react-context-menu"
-import CheckIcon from "lucide-react/dist/esm/icons/check";
-import ChevronRightIcon from "lucide-react/dist/esm/icons/chevron-right"
-import CircleIcon from "lucide-react/dist/esm/icons/circle"
+import * as React from "react"
+import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
+import { Check, ChevronRight, Circle } from "lucide-react"
import { cn } from "@/lib/utils"
-function ContextMenu({
- ...props
-}: ComponentProps
) {
- return
-}
+const ContextMenu = ContextMenuPrimitive.Root
-function ContextMenuTrigger({
- ...props
-}: ComponentProps) {
- return (
-
- )
-}
+const ContextMenuTrigger = ContextMenuPrimitive.Trigger
-function ContextMenuGroup({
- ...props
-}: ComponentProps) {
- return (
-
- )
-}
+const ContextMenuGroup = ContextMenuPrimitive.Group
-function ContextMenuPortal({
- ...props
-}: ComponentProps) {
- return (
-
- )
-}
+const ContextMenuPortal = ContextMenuPrimitive.Portal
-function ContextMenuSub({
- ...props
-}: ComponentProps) {
- return
-}
+const ContextMenuSub = ContextMenuPrimitive.Sub
-function ContextMenuRadioGroup({
- ...props
-}: ComponentProps) {
- return (
-
- )
-}
+const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup
-function ContextMenuSubTrigger({
- className,
- inset,
- children,
- ...props
-}: ComponentProps & {
- inset?: boolean
-}) {
- return (
- ,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, children, ...props }, ref) => (
+
+ {children}
+
+
+))
+ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName
+
+const ContextMenuSubContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName
+
+const ContextMenuContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
- {children}
-
-
- )
-}
-
-function ContextMenuSubContent({
- className,
- ...props
-}: ComponentProps) {
- return (
-
- )
-}
+
+))
+ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName
-function ContextMenuContent({
+const ContextMenuItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, ...props }, ref) => (
+
+))
+ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName
+
+const ContextMenuCheckboxItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, checked, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+))
+ContextMenuCheckboxItem.displayName =
+ ContextMenuPrimitive.CheckboxItem.displayName
+
+const ContextMenuRadioItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+))
+ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName
+
+const ContextMenuLabel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, ...props }, ref) => (
+
+))
+ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName
+
+const ContextMenuSeparator = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName
+
+const ContextMenuShortcut = ({
className,
...props
-}: ComponentProps) {
- return (
-
-
-
- )
-}
-
-function ContextMenuItem({
- className,
- inset,
- variant = "default",
- ...props
-}: ComponentProps & {
- inset?: boolean
- variant?: "default" | "destructive"
-}) {
- return (
-
- )
-}
-
-function ContextMenuCheckboxItem({
- className,
- children,
- checked,
- ...props
-}: ComponentProps) {
- return (
-
-
-
-
-
-
- {children}
-
- )
-}
-
-function ContextMenuRadioItem({
- className,
- children,
- ...props
-}: ComponentProps) {
- return (
-
-
-
-
-
-
- {children}
-
- )
-}
-
-function ContextMenuLabel({
- className,
- inset,
- ...props
-}: ComponentProps & {
- inset?: boolean
-}) {
- return (
-
- )
-}
-
-function ContextMenuSeparator({
- className,
- ...props
-}: ComponentProps) {
- return (
-
- )
-}
-
-function ContextMenuShortcut({
- className,
- ...props
-}: ComponentProps<"span">) {
+}: React.HTMLAttributes) => {
return (
)
}
+ContextMenuShortcut.displayName = "ContextMenuShortcut"
export {
ContextMenu,
diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx
index 3c8012c..fc05e96 100644
--- a/src/components/ui/dropdown-menu.tsx
+++ b/src/components/ui/dropdown-menu.tsx
@@ -2,9 +2,7 @@
import { ComponentProps } from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
-import CheckIcon from "lucide-react/dist/esm/icons/check"
-import ChevronRightIcon from "lucide-react/dist/esm/icons/chevron-right"
-import CircleIcon from "lucide-react/dist/esm/icons/circle"
+import { Check as CheckIcon, ChevronRight as ChevronRightIcon, Circle as CircleIcon } from "lucide-react"
import { cn } from "@/lib/utils"
diff --git a/src/components/ui/input-otp.tsx b/src/components/ui/input-otp.tsx
index dd69e58..0f6f30d 100644
--- a/src/components/ui/input-otp.tsx
+++ b/src/components/ui/input-otp.tsx
@@ -2,7 +2,7 @@
import { ComponentProps, useContext } from "react"
import { OTPInput, OTPInputContext } from "input-otp"
-import MinusIcon from "lucide-react/dist/esm/icons/minus"
+import { Minus as MinusIcon } from "lucide-react"
import { cn } from "@/lib/utils"
diff --git a/src/components/ui/menubar.tsx b/src/components/ui/menubar.tsx
index 951c0fc..6c185dd 100644
--- a/src/components/ui/menubar.tsx
+++ b/src/components/ui/menubar.tsx
@@ -1,8 +1,6 @@
import { ComponentProps } from "react"
import * as MenubarPrimitive from "@radix-ui/react-menubar"
-import CheckIcon from "lucide-react/dist/esm/icons/check"
-import ChevronRightIcon from "lucide-react/dist/esm/icons/chevron-right"
-import CircleIcon from "lucide-react/dist/esm/icons/circle"
+import { Check as CheckIcon, ChevronRight as ChevronRightIcon, Circle as CircleIcon } from "lucide-react"
import { cn } from "@/lib/utils"
diff --git a/src/components/ui/navigation-menu.tsx b/src/components/ui/navigation-menu.tsx
index 559f29e..15ec507 100644
--- a/src/components/ui/navigation-menu.tsx
+++ b/src/components/ui/navigation-menu.tsx
@@ -1,7 +1,7 @@
import { ComponentProps } from "react"
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
import { cva } from "class-variance-authority"
-import ChevronDownIcon from "lucide-react/dist/esm/icons/chevron-down"
+import { ChevronDown as ChevronDownIcon } from "lucide-react"
import { cn } from "@/lib/utils"
diff --git a/src/components/ui/pagination.tsx b/src/components/ui/pagination.tsx
index 7773bcf..abd75c3 100644
--- a/src/components/ui/pagination.tsx
+++ b/src/components/ui/pagination.tsx
@@ -1,7 +1,5 @@
import { ComponentProps } from "react"
-import ChevronLeftIcon from "lucide-react/dist/esm/icons/chevron-left"
-import ChevronRightIcon from "lucide-react/dist/esm/icons/chevron-right"
-import MoreHorizontalIcon from "lucide-react/dist/esm/icons/more-horizontal"
+import { ChevronLeft as ChevronLeftIcon, ChevronRight as ChevronRightIcon, MoreHorizontal as MoreHorizontalIcon } from "lucide-react"
import { cn } from "@/lib/utils"
import { Button, buttonVariants } from "@/components/ui/button"
diff --git a/src/components/ui/radio-group.tsx b/src/components/ui/radio-group.tsx
index 5d0e8f9..837ab80 100644
--- a/src/components/ui/radio-group.tsx
+++ b/src/components/ui/radio-group.tsx
@@ -2,7 +2,7 @@
import { ComponentProps } from "react"
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
-import CircleIcon from "lucide-react/dist/esm/icons/circle"
+import { Circle as CircleIcon } from "lucide-react"
import { cn } from "@/lib/utils"
diff --git a/src/components/ui/resizable.tsx b/src/components/ui/resizable.tsx
index 08a2bea..e71219d 100644
--- a/src/components/ui/resizable.tsx
+++ b/src/components/ui/resizable.tsx
@@ -1,5 +1,4 @@
import { ComponentProps } from "react"
-import GripVerticalIcon from "lucide-react/dist/esm/icons/grip-vertical"
import * as ResizablePrimitive from "react-resizable-panels"
import { cn } from "@/lib/utils"
@@ -12,7 +11,7 @@ function ResizablePanelGroup({
div]:rotate-90",
+ "relative flex w-px cursor-col-resize items-center justify-center bg-white/5 hover:bg-purple-500/50 active:bg-purple-500 transition-colors focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-hidden data-[panel-group-orientation=vertical]:h-px data-[panel-group-orientation=vertical]:w-full data-[panel-group-orientation=vertical]:cursor-row-resize",
className
)}
{...props}
- >
- {withHandle && (
-
-
-
- )}
-
+ />
)
}
diff --git a/src/components/ui/sidebar.tsx b/src/components/ui/sidebar.tsx
index 2e2c176..43e75d4 100644
--- a/src/components/ui/sidebar.tsx
+++ b/src/components/ui/sidebar.tsx
@@ -3,7 +3,7 @@
import { CSSProperties, ComponentProps, createContext, useCallback, useContext, useEffect, useMemo, useState } from "react"
import { Slot } from "@radix-ui/react-slot"
import { VariantProps, cva } from "class-variance-authority"
-import PanelLeftIcon from "lucide-react/dist/esm/icons/panel-left"
+import { PanelLeft as PanelLeftIcon } from "lucide-react"
import { useIsMobile } from "@/hooks/use-mobile"
import { cn } from "@/lib/utils"
diff --git a/src/components/ui/sonner.tsx b/src/components/ui/sonner.tsx
index 594e0ae..1b8165f 100644
--- a/src/components/ui/sonner.tsx
+++ b/src/components/ui/sonner.tsx
@@ -1,13 +1,13 @@
import { useTheme } from "next-themes"
-import { CSSProperties } from "react"
-import { Toaster as Sonner, ToasterProps } from "sonner"
+import { CSSProperties, ComponentProps } from "react"
+import { Toaster as Sonner } from "sonner"
-const Toaster = ({ ...props }: ToasterProps) => {
+const Toaster = ({ ...props }: ComponentProps) => {
const { theme = "system" } = useTheme()
return (
-
+ {}} currentCode="" currentPlatform="roblox" />
diff --git a/src/hooks/use-projects.ts b/src/hooks/use-projects.ts
new file mode 100644
index 0000000..4187ebd
--- /dev/null
+++ b/src/hooks/use-projects.ts
@@ -0,0 +1,95 @@
+'use client';
+
+import { useEffect, useState } from 'react';
+import { getSupabase } from '@/lib/supabase/client';
+import { useSupabaseAuth } from './use-supabase';
+import type { Database } from '@/lib/supabase/types';
+
+type Project = Database['public']['Tables']['projects']['Row'];
+type ProjectInsert = Database['public']['Tables']['projects']['Insert'];
+
+export function useProjects() {
+ const { user } = useSupabaseAuth();
+ const [projects, setProjects] = useState([]);
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ if (!user) {
+ setProjects([]);
+ setLoading(false);
+ return;
+ }
+
+ const fetchProjects = async () => {
+ const supabase = getSupabase();
+ const { data, error } = await supabase
+ .from('projects')
+ .select('*')
+ .eq('user_id', user.id)
+ .order('updated_at', { ascending: false });
+
+ if (error) {
+ console.error('Error fetching projects:', error);
+ } else {
+ setProjects(data ?? []);
+ }
+ setLoading(false);
+ };
+
+ fetchProjects();
+ }, [user]);
+
+ const createProject = async (project: Omit) => {
+ if (!user) return { error: new Error('Not authenticated') };
+
+ const supabase = getSupabase();
+ const { data, error } = await supabase
+ .from('projects')
+ .insert({ ...project, user_id: user.id })
+ .select()
+ .single();
+
+ if (!error && data) {
+ setProjects((prev) => [data, ...prev]);
+ }
+
+ return { data, error };
+ };
+
+ const updateProject = async (id: string, updates: Partial) => {
+ const supabase = getSupabase();
+ const { data, error } = await supabase
+ .from('projects')
+ .update({ ...updates, updated_at: new Date().toISOString() })
+ .eq('id', id)
+ .select()
+ .single();
+
+ if (!error && data) {
+ setProjects((prev) =>
+ prev.map((p) => (p.id === id ? data : p))
+ );
+ }
+
+ return { data, error };
+ };
+
+ const deleteProject = async (id: string) => {
+ const supabase = getSupabase();
+ const { error } = await supabase.from('projects').delete().eq('id', id);
+
+ if (!error) {
+ setProjects((prev) => prev.filter((p) => p.id !== id));
+ }
+
+ return { error };
+ };
+
+ return {
+ projects,
+ loading,
+ createProject,
+ updateProject,
+ deleteProject,
+ };
+}
diff --git a/src/hooks/use-supabase.ts b/src/hooks/use-supabase.ts
new file mode 100644
index 0000000..c12c0d8
--- /dev/null
+++ b/src/hooks/use-supabase.ts
@@ -0,0 +1,136 @@
+'use client';
+
+import { useEffect, useState } from 'react';
+import { User, Session, AuthChangeEvent } from '@supabase/supabase-js';
+import { getSupabase } from '@/lib/supabase/client';
+import type { Database } from '@/lib/supabase/types';
+
+type Profile = Database['public']['Tables']['profiles']['Row'];
+
+export function useSupabaseAuth() {
+ const [user, setUser] = useState(null);
+ const [session, setSession] = useState(null);
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ const supabase = getSupabase();
+
+ // Get initial session
+ const getInitialSession = async () => {
+ const { data } = await supabase.auth.getSession();
+ setSession(data.session);
+ setUser(data.session?.user ?? null);
+ setLoading(false);
+ };
+
+ getInitialSession();
+
+ // Listen for auth changes
+ const {
+ data: { subscription },
+ } = supabase.auth.onAuthStateChange((_event: AuthChangeEvent, session: Session | null) => {
+ setSession(session);
+ setUser(session?.user ?? null);
+ setLoading(false);
+ });
+
+ return () => subscription.unsubscribe();
+ }, []);
+
+ const signIn = async (email: string, password: string) => {
+ const supabase = getSupabase();
+ const { data, error } = await supabase.auth.signInWithPassword({
+ email,
+ password,
+ });
+ return { data, error };
+ };
+
+ const signUp = async (email: string, password: string) => {
+ const supabase = getSupabase();
+ const { data, error } = await supabase.auth.signUp({
+ email,
+ password,
+ });
+ return { data, error };
+ };
+
+ const signInWithOAuth = async (provider: 'github' | 'google' | 'discord') => {
+ const supabase = getSupabase();
+ const { data, error } = await supabase.auth.signInWithOAuth({
+ provider,
+ options: {
+ redirectTo: `${window.location.origin}/auth/callback`,
+ },
+ });
+ return { data, error };
+ };
+
+ const signOut = async () => {
+ const supabase = getSupabase();
+ const { error } = await supabase.auth.signOut();
+ return { error };
+ };
+
+ return {
+ user,
+ session,
+ loading,
+ signIn,
+ signUp,
+ signInWithOAuth,
+ signOut,
+ };
+}
+
+export function useProfile() {
+ const { user } = useSupabaseAuth();
+ const [profile, setProfile] = useState(null);
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ if (!user) {
+ setProfile(null);
+ setLoading(false);
+ return;
+ }
+
+ const fetchProfile = async () => {
+ const supabase = getSupabase();
+ const { data, error } = await supabase
+ .from('profiles')
+ .select('*')
+ .eq('id', user.id)
+ .single();
+
+ if (error) {
+ console.error('Error fetching profile:', error);
+ } else {
+ setProfile(data);
+ }
+ setLoading(false);
+ };
+
+ fetchProfile();
+ }, [user]);
+
+ const updateProfile = async (updates: Partial) => {
+ if (!user) return { error: new Error('Not authenticated') };
+
+ const supabase = getSupabase();
+ const { data, error } = await supabase
+ .from('profiles')
+ .update({ ...updates, updated_at: new Date().toISOString() })
+ .eq('id', user.id)
+ .select()
+ .single();
+
+ if (!error && data) {
+ setProfile(data);
+ }
+
+ return { data, error };
+ };
+
+ return { profile, loading, updateProfile };
+}
diff --git a/src/lib/supabase/client.ts b/src/lib/supabase/client.ts
new file mode 100644
index 0000000..de810a3
--- /dev/null
+++ b/src/lib/supabase/client.ts
@@ -0,0 +1,18 @@
+import { createBrowserClient } from '@supabase/ssr';
+
+export function createClient() {
+ return createBrowserClient(
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
+ );
+}
+
+// Singleton instance for client-side usage
+let supabaseInstance: ReturnType | null = null;
+
+export function getSupabase() {
+ if (!supabaseInstance) {
+ supabaseInstance = createClient();
+ }
+ return supabaseInstance;
+}
diff --git a/src/lib/supabase/middleware.ts b/src/lib/supabase/middleware.ts
new file mode 100644
index 0000000..c7fa072
--- /dev/null
+++ b/src/lib/supabase/middleware.ts
@@ -0,0 +1,36 @@
+import { createServerClient } from '@supabase/ssr';
+import { NextResponse, type NextRequest } from 'next/server';
+
+export async function updateSession(request: NextRequest) {
+ let supabaseResponse = NextResponse.next({
+ request,
+ });
+
+ const supabase = createServerClient(
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
+ {
+ cookies: {
+ getAll() {
+ return request.cookies.getAll();
+ },
+ setAll(cookiesToSet) {
+ cookiesToSet.forEach(({ name, value }) =>
+ request.cookies.set(name, value)
+ );
+ supabaseResponse = NextResponse.next({
+ request,
+ });
+ cookiesToSet.forEach(({ name, value, options }) =>
+ supabaseResponse.cookies.set(name, value, options)
+ );
+ },
+ },
+ }
+ );
+
+ // Refresh session if expired
+ await supabase.auth.getUser();
+
+ return supabaseResponse;
+}
diff --git a/src/lib/supabase/server.ts b/src/lib/supabase/server.ts
new file mode 100644
index 0000000..04e19cf
--- /dev/null
+++ b/src/lib/supabase/server.ts
@@ -0,0 +1,28 @@
+import { createServerClient, type CookieOptions } from '@supabase/ssr';
+import { cookies } from 'next/headers';
+
+export async function createClient() {
+ const cookieStore = await cookies();
+
+ return createServerClient(
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
+ {
+ cookies: {
+ getAll() {
+ return cookieStore.getAll();
+ },
+ setAll(cookiesToSet) {
+ try {
+ cookiesToSet.forEach(({ name, value, options }) =>
+ cookieStore.set(name, value, options)
+ );
+ } catch {
+ // The `setAll` method was called from a Server Component.
+ // This can be ignored if you have middleware refreshing user sessions.
+ }
+ },
+ },
+ }
+ );
+}
diff --git a/src/lib/supabase/types.ts b/src/lib/supabase/types.ts
new file mode 100644
index 0000000..2777460
--- /dev/null
+++ b/src/lib/supabase/types.ts
@@ -0,0 +1,143 @@
+// Database types - Generate with: npx supabase gen types typescript --project-id YOUR_PROJECT_ID > src/lib/supabase/types.ts
+
+export type Json =
+ | string
+ | number
+ | boolean
+ | null
+ | { [key: string]: Json | undefined }
+ | Json[];
+
+export interface Database {
+ public: {
+ Tables: {
+ profiles: {
+ Row: {
+ id: string;
+ username: string | null;
+ avatar_url: string | null;
+ created_at: string;
+ updated_at: string;
+ subscription_tier: 'free' | 'studio' | 'pro' | 'enterprise';
+ translation_count: number;
+ };
+ Insert: {
+ id: string;
+ username?: string | null;
+ avatar_url?: string | null;
+ created_at?: string;
+ updated_at?: string;
+ subscription_tier?: 'free' | 'studio' | 'pro' | 'enterprise';
+ translation_count?: number;
+ };
+ Update: {
+ id?: string;
+ username?: string | null;
+ avatar_url?: string | null;
+ created_at?: string;
+ updated_at?: string;
+ subscription_tier?: 'free' | 'studio' | 'pro' | 'enterprise';
+ translation_count?: number;
+ };
+ };
+ projects: {
+ Row: {
+ id: string;
+ user_id: string;
+ name: string;
+ description: string | null;
+ platforms: string[];
+ created_at: string;
+ updated_at: string;
+ is_public: boolean;
+ };
+ Insert: {
+ id?: string;
+ user_id: string;
+ name: string;
+ description?: string | null;
+ platforms?: string[];
+ created_at?: string;
+ updated_at?: string;
+ is_public?: boolean;
+ };
+ Update: {
+ id?: string;
+ user_id?: string;
+ name?: string;
+ description?: string | null;
+ platforms?: string[];
+ created_at?: string;
+ updated_at?: string;
+ is_public?: boolean;
+ };
+ };
+ files: {
+ Row: {
+ id: string;
+ project_id: string;
+ name: string;
+ path: string;
+ content: string | null;
+ language: string;
+ created_at: string;
+ updated_at: string;
+ };
+ Insert: {
+ id?: string;
+ project_id: string;
+ name: string;
+ path: string;
+ content?: string | null;
+ language: string;
+ created_at?: string;
+ updated_at?: string;
+ };
+ Update: {
+ id?: string;
+ project_id?: string;
+ name?: string;
+ path?: string;
+ content?: string | null;
+ language?: string;
+ created_at?: string;
+ updated_at?: string;
+ };
+ };
+ translations: {
+ Row: {
+ id: string;
+ user_id: string;
+ source_platform: string;
+ target_platform: string;
+ source_code: string;
+ translated_code: string;
+ created_at: string;
+ };
+ Insert: {
+ id?: string;
+ user_id: string;
+ source_platform: string;
+ target_platform: string;
+ source_code: string;
+ translated_code: string;
+ created_at?: string;
+ };
+ Update: {
+ id?: string;
+ user_id?: string;
+ source_platform?: string;
+ target_platform?: string;
+ source_code?: string;
+ translated_code?: string;
+ created_at?: string;
+ };
+ };
+ };
+ Views: Record;
+ Functions: Record;
+ Enums: {
+ subscription_tier: 'free' | 'studio' | 'pro' | 'enterprise';
+ };
+ };
+}
diff --git a/src/lib/templates.ts b/src/lib/templates.ts
index a732444..dfbb7a7 100644
--- a/src/lib/templates.ts
+++ b/src/lib/templates.ts
@@ -2,6 +2,7 @@ import { PlatformId } from './platforms';
import { uefnTemplates } from './templates-uefn';
import { spatialTemplates } from './templates-spatial';
+export interface ScriptTemplate {
id: string;
name: string;
description: string;
diff --git a/src/lib/translation-engine.ts b/src/lib/translation-engine.ts
index 82f7eef..16ad99a 100644
--- a/src/lib/translation-engine.ts
+++ b/src/lib/translation-engine.ts
@@ -77,7 +77,7 @@ Example:
/**
* Platform-specific translation rules
*/
-const platformTranslationRules: Record> = {
+const platformTranslationRules: Record = {
'roblox-to-uefn': [
'game:GetService() → Use Verse imports',
'Instance.new() → object{} syntax in Verse',
@@ -145,7 +145,7 @@ async function translateWithClaudeAPI(
): Promise {
try {
// Check if API key is configured
- const apiKey = import.meta.env.VITE_CLAUDE_API_KEY || process.env.NEXT_PUBLIC_CLAUDE_API_KEY;
+ const apiKey = process.env.NEXT_PUBLIC_CLAUDE_API_KEY;
if (!apiKey) {
console.warn('Claude API key not configured, using mock translation');
diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts
index 037b90c..b1d5bdf 100644
--- a/src/lib/utils/index.ts
+++ b/src/lib/utils/index.ts
@@ -1 +1 @@
-export * from "./utils";
\ No newline at end of file
+export * from "../utils";
\ No newline at end of file
diff --git a/src/types/lucide-react.d.ts b/src/types/lucide-react.d.ts
index fc71827..a0144c0 100644
--- a/src/types/lucide-react.d.ts
+++ b/src/types/lucide-react.d.ts
@@ -17,7 +17,6 @@ declare module 'lucide-react/dist/esm/icons/circle' {
const CircleIcon: React.FC>;
export default CircleIcon;
}
-}
declare module 'lucide-react/dist/esm/icons/chevron-left' {
const ChevronLeft: any;
export default ChevronLeft;
diff --git a/store/editor-store.ts b/store/editor-store.ts
index fac7e5a..3f75d64 100644
--- a/store/editor-store.ts
+++ b/store/editor-store.ts
@@ -32,6 +32,7 @@ interface EditorStore {
updateFileContent: (fileId: string, content: string) => void;
saveFile: (fileId: string) => void;
saveAllFiles: () => void;
+ moveFile: (fileId: string, targetFolderId: string) => void;
}
export const useEditorStore = create((set, get) => ({
@@ -188,4 +189,47 @@ export const useEditorStore = create((set, get) => ({
});
}, 500);
},
+
+ moveFile: (fileId: string, targetFolderId: string) => {
+ const { files } = get();
+
+ // Helper to find and remove a file from tree
+ const findAndRemove = (nodes: FileNode[], id: string): { nodes: FileNode[]; removed: FileNode | null } => {
+ let removed: FileNode | null = null;
+ const newNodes = nodes.filter(n => {
+ if (n.id === id) {
+ removed = n;
+ return false;
+ }
+ return true;
+ }).map(n => {
+ if (n.children && !removed) {
+ const result = findAndRemove(n.children, id);
+ removed = result.removed;
+ return { ...n, children: result.nodes };
+ }
+ return n;
+ });
+ return { nodes: newNodes, removed };
+ };
+
+ // Helper to add file to target folder
+ const addToFolder = (nodes: FileNode[], folderId: string, file: FileNode): FileNode[] => {
+ return nodes.map(n => {
+ if (n.id === folderId && n.type === 'folder') {
+ return { ...n, children: [...(n.children || []), file] };
+ }
+ if (n.children) {
+ return { ...n, children: addToFolder(n.children, folderId, file) };
+ }
+ return n;
+ });
+ };
+
+ const { nodes: afterRemove, removed } = findAndRemove(files, fileId);
+ if (removed) {
+ const newFiles = addToFolder(afterRemove, targetFolderId, removed);
+ set({ files: newFiles });
+ }
+ },
}));
diff --git a/supabase/migrations/001_initial_schema.sql b/supabase/migrations/001_initial_schema.sql
new file mode 100644
index 0000000..de623e3
--- /dev/null
+++ b/supabase/migrations/001_initial_schema.sql
@@ -0,0 +1,240 @@
+-- AeThex Studio Initial Database Schema
+-- Run this in Supabase SQL Editor to set up your database
+
+-- Enable RLS (Row Level Security)
+alter default privileges revoke execute on functions from public;
+
+-- Create subscription tier enum
+create type subscription_tier as enum ('free', 'studio', 'pro', 'enterprise');
+
+-- ============================================
+-- PROFILES TABLE
+-- ============================================
+create table public.profiles (
+ id uuid references auth.users on delete cascade primary key,
+ username text unique,
+ avatar_url text,
+ created_at timestamptz default now() not null,
+ updated_at timestamptz default now() not null,
+ subscription_tier subscription_tier default 'free' not null,
+ translation_count integer default 0 not null
+);
+
+-- Enable RLS
+alter table public.profiles enable row level security;
+
+-- Policies
+create policy "Users can view their own profile"
+ on public.profiles for select
+ using (auth.uid() = id);
+
+create policy "Users can update their own profile"
+ on public.profiles for update
+ using (auth.uid() = id);
+
+-- Auto-create profile on signup
+create function public.handle_new_user()
+returns trigger
+language plpgsql
+security definer set search_path = ''
+as $$
+begin
+ insert into public.profiles (id, username, avatar_url)
+ values (
+ new.id,
+ new.raw_user_meta_data ->> 'username',
+ new.raw_user_meta_data ->> 'avatar_url'
+ );
+ return new;
+end;
+$$;
+
+create trigger on_auth_user_created
+ after insert on auth.users
+ for each row execute procedure public.handle_new_user();
+
+-- ============================================
+-- PROJECTS TABLE
+-- ============================================
+create table public.projects (
+ id uuid default gen_random_uuid() primary key,
+ user_id uuid references public.profiles(id) on delete cascade not null,
+ name text not null,
+ description text,
+ platforms text[] default '{}' not null,
+ created_at timestamptz default now() not null,
+ updated_at timestamptz default now() not null,
+ is_public boolean default false not null
+);
+
+-- Enable RLS
+alter table public.projects enable row level security;
+
+-- Policies
+create policy "Users can view their own projects"
+ on public.projects for select
+ using (auth.uid() = user_id);
+
+create policy "Users can view public projects"
+ on public.projects for select
+ using (is_public = true);
+
+create policy "Users can insert their own projects"
+ on public.projects for insert
+ with check (auth.uid() = user_id);
+
+create policy "Users can update their own projects"
+ on public.projects for update
+ using (auth.uid() = user_id);
+
+create policy "Users can delete their own projects"
+ on public.projects for delete
+ using (auth.uid() = user_id);
+
+-- Indexes
+create index projects_user_id_idx on public.projects(user_id);
+create index projects_is_public_idx on public.projects(is_public) where is_public = true;
+
+-- ============================================
+-- FILES TABLE
+-- ============================================
+create table public.files (
+ id uuid default gen_random_uuid() primary key,
+ project_id uuid references public.projects(id) on delete cascade not null,
+ name text not null,
+ path text not null,
+ content text,
+ language text not null,
+ created_at timestamptz default now() not null,
+ updated_at timestamptz default now() not null,
+
+ unique(project_id, path)
+);
+
+-- Enable RLS
+alter table public.files enable row level security;
+
+-- Policies
+create policy "Users can view files in their projects"
+ on public.files for select
+ using (
+ exists (
+ select 1 from public.projects
+ where projects.id = files.project_id
+ and projects.user_id = auth.uid()
+ )
+ );
+
+create policy "Users can view files in public projects"
+ on public.files for select
+ using (
+ exists (
+ select 1 from public.projects
+ where projects.id = files.project_id
+ and projects.is_public = true
+ )
+ );
+
+create policy "Users can insert files to their projects"
+ on public.files for insert
+ with check (
+ exists (
+ select 1 from public.projects
+ where projects.id = files.project_id
+ and projects.user_id = auth.uid()
+ )
+ );
+
+create policy "Users can update files in their projects"
+ on public.files for update
+ using (
+ exists (
+ select 1 from public.projects
+ where projects.id = files.project_id
+ and projects.user_id = auth.uid()
+ )
+ );
+
+create policy "Users can delete files from their projects"
+ on public.files for delete
+ using (
+ exists (
+ select 1 from public.projects
+ where projects.id = files.project_id
+ and projects.user_id = auth.uid()
+ )
+ );
+
+-- Indexes
+create index files_project_id_idx on public.files(project_id);
+
+-- ============================================
+-- TRANSLATIONS TABLE
+-- ============================================
+create table public.translations (
+ id uuid default gen_random_uuid() primary key,
+ user_id uuid references public.profiles(id) on delete cascade not null,
+ source_platform text not null,
+ target_platform text not null,
+ source_code text not null,
+ translated_code text not null,
+ created_at timestamptz default now() not null
+);
+
+-- Enable RLS
+alter table public.translations enable row level security;
+
+-- Policies
+create policy "Users can view their own translations"
+ on public.translations for select
+ using (auth.uid() = user_id);
+
+create policy "Users can insert their own translations"
+ on public.translations for insert
+ with check (auth.uid() = user_id);
+
+-- Indexes
+create index translations_user_id_idx on public.translations(user_id);
+create index translations_created_at_idx on public.translations(created_at desc);
+
+-- ============================================
+-- HELPER FUNCTIONS
+-- ============================================
+
+-- Increment translation count for user
+create function public.increment_translation_count(user_uuid uuid)
+returns void
+language plpgsql
+security definer
+as $$
+begin
+ update public.profiles
+ set translation_count = translation_count + 1,
+ updated_at = now()
+ where id = user_uuid;
+end;
+$$;
+
+-- Updated at trigger function
+create function public.handle_updated_at()
+returns trigger
+language plpgsql
+as $$
+begin
+ new.updated_at = now();
+ return new;
+end;
+$$;
+
+-- Apply updated_at triggers
+create trigger set_profiles_updated_at
+ before update on public.profiles
+ for each row execute procedure public.handle_updated_at();
+
+create trigger set_projects_updated_at
+ before update on public.projects
+ for each row execute procedure public.handle_updated_at();
+
+create trigger set_files_updated_at
+ before update on public.files
+ for each row execute procedure public.handle_updated_at();
diff --git a/tailwind.config.js b/tailwind.config.js
deleted file mode 100644
index 0c2b217..0000000
--- a/tailwind.config.js
+++ /dev/null
@@ -1,153 +0,0 @@
-import fs from "fs";
-
-/** @type {import('tailwindcss').Config} */
-
-let theme = {};
-try {
- const themePath = "./theme.json";
-
- if (fs.existsSync(themePath)) {
- theme = JSON.parse(fs.readFileSync(themePath, "utf-8"));
- }
-} catch (err) {
- // Silently fall back to empty theme object if custom theme cannot be loaded
- theme = {};
-}
-const defaultTheme = {
- container: {
- center: true,
- padding: "2rem",
- },
- extend: {
- screens: {
- coarse: { raw: "(pointer: coarse)" },
- fine: { raw: "(pointer: fine)" },
- pwa: { raw: "(display-mode: standalone)" },
- },
- colors: {
- neutral: {
- 1: "var(--color-neutral-1)",
- 2: "var(--color-neutral-2)",
- 3: "var(--color-neutral-3)",
- 4: "var(--color-neutral-4)",
- 5: "var(--color-neutral-5)",
- 6: "var(--color-neutral-6)",
- 7: "var(--color-neutral-7)",
- 8: "var(--color-neutral-8)",
- 9: "var(--color-neutral-9)",
- 10: "var(--color-neutral-10)",
- 11: "var(--color-neutral-11)",
- 12: "var(--color-neutral-12)",
- a1: "var(--color-neutral-a1)",
- a2: "var(--color-neutral-a2)",
- a3: "var(--color-neutral-a3)",
- a4: "var(--color-neutral-a4)",
- a5: "var(--color-neutral-a5)",
- a6: "var(--color-neutral-a6)",
- a7: "var(--color-neutral-a7)",
- a8: "var(--color-neutral-a8)",
- a9: "var(--color-neutral-a9)",
- a10: "var(--color-neutral-a10)",
- a11: "var(--color-neutral-a11)",
- a12: "var(--color-neutral-a12)",
- contrast: "var(--color-neutral-contrast)",
- },
- accent: {
- 1: "var(--color-accent-1)",
- 2: "var(--color-accent-2)",
- 3: "var(--color-accent-3)",
- 4: "var(--color-accent-4)",
- 5: "var(--color-accent-5)",
- 6: "var(--color-accent-6)",
- 7: "var(--color-accent-7)",
- 8: "var(--color-accent-8)",
- 9: "var(--color-accent-9)",
- 10: "var(--color-accent-10)",
- 11: "var(--color-accent-11)",
- 12: "var(--color-accent-12)",
- contrast: "var(--color-accent-contrast)",
- },
- "accent-secondary": {
- 1: "var(--color-accent-secondary-1)",
- 2: "var(--color-accent-secondary-2)",
- 3: "var(--color-accent-secondary-3)",
- 4: "var(--color-accent-secondary-4)",
- 5: "var(--color-accent-secondary-5)",
- 6: "var(--color-accent-secondary-6)",
- 7: "var(--color-accent-secondary-7)",
- 8: "var(--color-accent-secondary-8)",
- 9: "var(--color-accent-secondary-9)",
- 10: "var(--color-accent-secondary-10)",
- 11: "var(--color-accent-secondary-11)",
- 12: "var(--color-accent-secondary-12)",
- contrast: "var(--color-accent-secondary-contrast)",
- },
- fg: {
- DEFAULT: "var(--color-fg)",
- secondary: "var(--color-fg-secondary)",
- },
- bg: {
- DEFAULT: "var(--color-bg)",
- inset: "var(--color-bg-inset)",
- overlay: "var(--color-bg-overlay)",
- },
- "focus-ring": "var(--color-focus-ring)",
- },
- borderRadius: {
- sm: "var(--radius-sm)",
- md: "var(--radius-md)",
- lg: "var(--radius-lg)",
- xl: "var(--radius-xl)",
- "2xl": "var(--radius-2xl)",
- full: "var(--radius-full)",
- },
- },
- spacing: {
- px: "var(--size-px)",
- 0: "var(--size-0)",
- 0.5: "var(--size-0-5)",
- 1: "var(--size-1)",
- 1.5: "var(--size-1-5)",
- 2: "var(--size-2)",
- 2.5: "var(--size-2-5)",
- 3: "var(--size-3)",
- 3.5: "var(--size-3-5)",
- 4: "var(--size-4)",
- 5: "var(--size-5)",
- 6: "var(--size-6)",
- 7: "var(--size-7)",
- 8: "var(--size-8)",
- 9: "var(--size-9)",
- 10: "var(--size-10)",
- 11: "var(--size-11)",
- 12: "var(--size-12)",
- 14: "var(--size-14)",
- 16: "var(--size-16)",
- 20: "var(--size-20)",
- 24: "var(--size-24)",
- 28: "var(--size-28)",
- 32: "var(--size-32)",
- 36: "var(--size-36)",
- 40: "var(--size-40)",
- 44: "var(--size-44)",
- 48: "var(--size-48)",
- 52: "var(--size-52)",
- 56: "var(--size-56)",
- 60: "var(--size-60)",
- 64: "var(--size-64)",
- 72: "var(--size-72)",
- 80: "var(--size-80)",
- 96: "var(--size-96)",
- },
- darkMode: ["selector", '[data-appearance="dark"]'],
-}
-
-export default {
- content: [
- "./index.html",
- "./src/**/*.{js,ts,jsx,tsx}",
- "./app/**/*.{js,ts,jsx,tsx}",
- ],
- theme: { ...defaultTheme, ...theme },
- plugins: [require("tailwindcss-animate")],
-};
\ No newline at end of file
diff --git a/tailwind.config.ts b/tailwind.config.ts
index cfba1eb..a71585f 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -6,6 +6,7 @@ const config: Config = {
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
+ "./src/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
diff --git a/tsconfig.json b/tsconfig.json
index 6e1e4e3..1658d14 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -20,9 +20,9 @@
}
],
"paths": {
- "@/*": ["./*"]
+ "@/*": ["./src/*", "./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
- "exclude": ["node_modules"]
+ "exclude": ["node_modules", "**/*.test.ts", "**/*.test.tsx", "**/test/**", "**/__tests__/**", "vitest.config.ts", "vite.config.ts"]
}