Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
ac80f23
Add Cloudflare Pages deployment integration
Gerome-Elassaad Nov 18, 2025
fbba2a2
Update deployment icons to use local SVG assets
Gerome-Elassaad Nov 18, 2025
5fab427
Update Cloudflare deployment icon to use local SVG asset
Gerome-Elassaad Nov 18, 2025
6828b31
Fix deployment icons in PreviewHeader and DeployDialog
Gerome-Elassaad Nov 18, 2025
f316193
Simplify left-side buttons in PreviewHeader
Gerome-Elassaad Nov 18, 2025
372edfe
Fix IconButton border centering and complete CodeModeHeader updates
Gerome-Elassaad Nov 18, 2025
dfe6b91
Standardize all button sizes to w-8 h-8
Gerome-Elassaad Nov 18, 2025
a134b65
Fix settings icon button dropdown functionality
Gerome-Elassaad Nov 18, 2025
7709ca9
Add settings dropdown functionality to PreviewHeader
Gerome-Elassaad Nov 18, 2025
aa976a9
Standardize More Deploy and Publish button sizes
Gerome-Elassaad Nov 18, 2025
4ac107a
updated icons
Gerome-Elassaad Nov 18, 2025
254ed85
Unify left-side button layout in PreviewHeader
Gerome-Elassaad Nov 18, 2025
652a1f9
Ensure consistent button styling in PreviewHeader
Gerome-Elassaad Nov 18, 2025
fdd8cc6
Remove terminal button from CodeModeHeader
Gerome-Elassaad Nov 18, 2025
5d333e6
updated preview header
Gerome-Elassaad Nov 18, 2025
94160a6
updated colors and dark mode errors
Gerome-Elassaad Nov 18, 2025
09755cd
Fix API route to handle 429 rate limiting status code
Gerome-Elassaad Nov 18, 2025
e8f6c81
Fix linting error in useUpdateCheck hook
Gerome-Elassaad Nov 18, 2025
a5f6cae
Fix client-side update check to handle 429 rate limiting
Gerome-Elassaad Nov 18, 2025
60bcbf9
chore: release version 1.0.9
Gerome-Elassaad Nov 18, 2025
dbc7dae
docs: update changelog with all changes since v1.0.8
Gerome-Elassaad Nov 18, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
# Build/Lint/Test Commands

## Build Commands
- `npm run build` - Production build with Remix Vite
- `npm run dev` - Development server with hot reload
- `npm run typecheck` - TypeScript type checking
- `pnpm run build` - Production build with Remix Vite
- `pnpm run dev` - Development server with hot reload
- `pnpm run typecheck` - TypeScript type checking

## Test Commands
- `npm run test` - Run all tests once with Vitest
- `npm run test:watch` - Run tests in watch mode
- `pnpm run test` - Run all tests once with Vitest
- `pnpm run test:watch` - Run tests in watch mode
- `vitest --run path/to/test.spec.ts` - Run single test file

## Lint Commands
- `npm run lint` - ESLint check with caching
- `npm run lint:fix` - Auto-fix ESLint + Prettier formatting
- `pnpm run lint` - ESLint check with caching
- `pnpm run lint:fix` - Auto-fix ESLint + Prettier formatting

# Code Style Guidelines

Expand Down
4 changes: 3 additions & 1 deletion app/components/@settings/core/ControlPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -442,11 +442,13 @@ export const ControlPanel = ({ open, onClose }: ControlPanelProps) => {
</RadixDialog.Overlay>

<RadixDialog.Content
aria-describedby={undefined}
onEscapeKeyDown={handleClose}
onPointerDownOutside={handleClose}
className="relative z-[101]"
>
<RadixDialog.Description className="sr-only">
Application settings and configuration panel
</RadixDialog.Description>
<motion.div
className={classNames(
'w-[1200px] h-[90vh]',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { motion } from 'framer-motion';
import * as RadixDialog from '@radix-ui/react-dialog';
import { classNames } from '~/utils/classNames';
import type { TabType } from '~/components/@settings/core/types';
import { ControlPanelSidebar } from './components/ControlPanelSidebar';
import { ControlPanelContent } from './components/ControlPanelContent';
import { useControlPanelDialog } from './hooks/useControlPanelDialog';

interface ControlPanelDialogProps {
isOpen: boolean;
onClose: () => void;
initialTab?: TabType;
}

export function ControlPanelDialog({ isOpen, onClose, initialTab = 'settings' }: ControlPanelDialogProps) {
const { activeTab, setActiveTab, visibleTabs } = useControlPanelDialog(initialTab);

return (
<RadixDialog.Root open={isOpen} onOpenChange={onClose}>
<RadixDialog.Portal>
<RadixDialog.Overlay asChild>
<motion.div
className="fixed inset-0 z-[9999] bg-black/80 backdrop-blur-sm"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.15 }}
/>
</RadixDialog.Overlay>

<div className="fixed inset-0 flex items-center justify-center z-[9999] modern-scrollbar">
<RadixDialog.Content asChild>
<motion.div
className={classNames(
'w-[90vw] h-[700px] max-w-[1500px] max-h-[85vh]',
'bg-codinit-elements-background-depth-1 border border-codinit-elements-borderColor rounded-xl shadow-2xl',
'flex overflow-hidden focus:outline-none',
)}
initial={{ opacity: 0, scale: 0.95, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: 20 }}
transition={{ duration: 0.2, ease: 'easeOut' }}
>
{/* Close button */}
<RadixDialog.Close asChild>
<button
className={classNames(
'absolute top-2 right-2 z-[10000] flex items-center justify-center',
'w-9 h-9 rounded-lg transition-all duration-200',
'bg-transparent text-codinit-elements-textTertiary',
'hover:bg-codinit-elements-background-depth-2 hover:text-codinit-elements-textPrimary',
'focus:outline-none focus:ring-2 focus:ring-codinit-elements-borderColor',
)}
aria-label="Close settings"
>
<div className="i-heroicons:x-mark-solid w-4 h-4" />
</button>
</RadixDialog.Close>

{/* Sidebar */}
<ControlPanelSidebar activeTab={activeTab} onTabChange={setActiveTab} tabs={visibleTabs} />

{/* Main Content */}
<ControlPanelContent activeTab={activeTab} />
</motion.div>
</RadixDialog.Content>
</div>
</RadixDialog.Portal>
</RadixDialog.Root>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { Suspense, lazy } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { classNames } from '~/utils/classNames';
import { TAB_LABELS } from '~/components/@settings/core/constants';
import type { TabType } from '~/components/@settings/core/types';

// Lazy load all tab components
const ProfileTab = lazy(() =>
import('~/components/@settings/tabs/profile/ProfileTab').then((module) => ({ default: module.default })),
);
const SettingsTab = lazy(() =>
import('~/components/@settings/tabs/settings/SettingsTab').then((module) => ({ default: module.default })),
);
const NotificationsTab = lazy(() =>
import('~/components/@settings/tabs/notifications/NotificationsTab').then((module) => ({ default: module.default })),
);
const FeaturesTab = lazy(() =>
import('~/components/@settings/tabs/features/FeaturesTab').then((module) => ({ default: module.default })),
);
const DataTab = lazy(() =>
import('~/components/@settings/tabs/data/DataTab').then((module) => ({ default: module.DataTab })),
);
const CloudProvidersTab = lazy(() =>
import('~/components/@settings/tabs/providers/cloud/CloudProvidersTab').then((module) => ({
default: module.default,
})),
);
const LocalProvidersTab = lazy(() =>
import('~/components/@settings/tabs/providers/local/LocalProvidersTab').then((module) => ({
default: module.default,
})),
);
const ServiceStatusTab = lazy(() =>
import('~/components/@settings/tabs/providers/status/ServiceStatusTab').then((module) => ({
default: module.default,
})),
);
const ConnectionsTab = lazy(() =>
import('~/components/@settings/tabs/connections/ConnectionsTab').then((module) => ({ default: module.default })),
);
const DebugTab = lazy(() =>
import('~/components/@settings/tabs/debug/DebugTab').then((module) => ({ default: module.default })),
);
const EventLogsTab = lazy(() =>
import('~/components/@settings/tabs/event-logs/EventLogsTab').then((module) => ({ default: module.EventLogsTab })),
);
const UpdateTab = lazy(() =>
import('~/components/@settings/tabs/update/UpdateTab').then((module) => ({ default: module.default })),
);
const TaskManagerTab = lazy(() =>
import('~/components/@settings/tabs/task-manager/TaskManagerTab').then((module) => ({ default: module.default })),
);

interface ControlPanelContentProps {
activeTab: TabType;
}

function LoadingFallback() {
return (
<div className="flex items-center justify-center h-full">
<div className="flex items-center gap-3 text-codinit-elements-textSecondary">
<div className="i-svg-spinners:90-ring-with-bg w-5 h-5 animate-spin" />
<span className="text-sm">Loading...</span>
</div>
</div>
);
}

function TabContent({ tab }: { tab: TabType }) {
switch (tab) {
case 'profile':
return <ProfileTab />;
case 'settings':
return <SettingsTab />;
case 'notifications':
return <NotificationsTab />;
case 'features':
return <FeaturesTab />;
case 'data':
return <DataTab />;
case 'cloud-providers':
return <CloudProvidersTab />;
case 'local-providers':
return <LocalProvidersTab />;
case 'service-status':
return <ServiceStatusTab />;
case 'connection':
return <ConnectionsTab />;
case 'debug':
return <DebugTab />;
case 'event-logs':
return <EventLogsTab />;
case 'update':
return <UpdateTab />;
case 'task-manager':
return <TaskManagerTab />;
default:
return (
<div className="flex items-center justify-center h-full text-codinit-elements-textSecondary">
<div className="text-center">
<div className="i-heroicons:exclamation-triangle w-12 h-12 mx-auto mb-4 opacity-50" />
<p className="text-sm">Tab not found</p>
</div>
</div>
);
}
}

export function ControlPanelContent({ activeTab }: ControlPanelContentProps) {
return (
<div
className={classNames(
'flex-1 pb-10 relative overflow-y-auto flex flex-col',
'bg-codinit-elements-background-depth-1 px-8',
)}
>
{/* Header */}
<div className="bg-codinit-elements-background-depth-1 sticky top-0 z-layer-3 pt-12 mb-4">
<div className="min-h-9 flex flex-col gap-2">
<h2 className="text-lg font-semibold truncate text-codinit-elements-textPrimary">{TAB_LABELS[activeTab]}</h2>
</div>
</div>

{/* Content */}
<div className="flex flex-col gap-6 flex-1">
<AnimatePresence mode="wait">
<motion.div
key={activeTab}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.2, ease: 'easeOut' }}
className="flex-1"
>
<Suspense fallback={<LoadingFallback />}>
<TabContent tab={activeTab} />
</Suspense>
</motion.div>
</AnimatePresence>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { classNames } from '~/utils/classNames';
import { TAB_ICONS, TAB_LABELS } from '~/components/@settings/core/constants';
import type { TabType, TabVisibilityConfig } from '~/components/@settings/core/types';

interface ControlPanelSidebarProps {
activeTab: TabType;
onTabChange: (tab: TabType) => void;
tabs: TabVisibilityConfig[];
}

export function ControlPanelSidebar({ activeTab, onTabChange, tabs }: ControlPanelSidebarProps) {
// Group tabs into primary and secondary sections
const primaryTabs = tabs.slice(0, 8); // First 8 tabs as primary
const secondaryTabs = tabs.slice(8); // Rest as secondary

const renderTabButton = (tab: TabVisibilityConfig) => {
const isActive = activeTab === tab.id;
const iconClass = TAB_ICONS[tab.id];
const label = TAB_LABELS[tab.id];

return (
<li key={tab.id} className="first:mt-0">
<button
id={`settings-item-${tab.id}`}
onClick={() => onTabChange(tab.id)}
className={classNames(
'w-full flex items-center shrink-0 justify-center md:justify-start gap-3 h-[33px] px-2.5 rounded-md text-sm font-medium',
'focus-visible:outline-2 focus-visible:outline-codinit-elements-borderColor',
'transition-colors duration-200',
isActive
? 'bg-codinit-elements-item-backgroundActive text-codinit-elements-textPrimary'
: 'bg-transparent hover:bg-codinit-elements-background-depth-3 text-codinit-elements-textTertiary hover:text-codinit-elements-textPrimary',
)}
>
<span className={classNames('shrink-0 text-base', iconClass)} />
<span className="truncate hidden md:block">{label}</span>
</button>
</li>
);
};

return (
<aside
className={classNames(
'h-full overflow-y-auto flex flex-col',
'max-w-80 w-12 md:w-auto md:min-w-1/8',
'bg-codinit-elements-background-depth-2 border-r border-codinit-elements-borderColor',
'p-2 md:p-4 pt-0 md:pt-0',
)}
>
<div className="flex-1 flex flex-col gap-4">
{/* Primary Settings Section */}
{primaryTabs.length > 0 && (
<section className="flex flex-col">
<div className="sticky top-0 bg-codinit-elements-background-depth-2 z-layer-1 text-codinit-elements-textTertiary text-xs font-semibold hidden md:block p-2 md:p-4">
Settings
</div>
<ul className="flex flex-col gap-1">{primaryTabs.map(renderTabButton)}</ul>
</section>
)}

{/* Secondary Settings Section */}
{secondaryTabs.length > 0 && (
<section className="flex flex-col">
<div className="sticky top-0 bg-codinit-elements-background-depth-2 z-layer-1 text-codinit-elements-textTertiary text-xs font-semibold hidden md:block p-2 md:p-4">
Advanced
</div>
<ul className="flex flex-col gap-1">{secondaryTabs.map(renderTabButton)}</ul>
</section>
)}
</div>
</aside>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useState, useEffect, useMemo } from 'react';
import { useStore } from '@nanostores/react';
import { tabConfigurationStore, developerModeStore } from '~/lib/stores/settings';
import type { TabType, TabVisibilityConfig } from '~/components/@settings/core/types';

export function useControlPanelDialog(initialTab: TabType = 'settings') {
const [activeTab, setActiveTab] = useState<TabType>(initialTab);
const tabConfiguration = useStore(tabConfigurationStore);
const isDeveloperMode = useStore(developerModeStore);

// Get visible tabs based on current configuration
const visibleTabs = useMemo(() => {
const currentWindow = isDeveloperMode ? 'developer' : 'user';
const tabsArray = currentWindow === 'developer' ? tabConfiguration.developerTabs : tabConfiguration.userTabs;

return tabsArray
.filter((tab: TabVisibilityConfig) => tab.visible)
.sort((a: TabVisibilityConfig, b: TabVisibilityConfig) => a.order - b.order);
}, [tabConfiguration, isDeveloperMode]);

// Ensure active tab is valid when configuration changes
useEffect(() => {
if (!visibleTabs.find((tab: TabVisibilityConfig) => tab.id === activeTab)) {
const firstVisibleTab = visibleTabs[0];

if (firstVisibleTab) {
setActiveTab(firstVisibleTab.id);
}
}
}, [visibleTabs, activeTab]);

// Reset to initial tab when dialog opens

useEffect(() => {
if (visibleTabs.find((tab: TabVisibilityConfig) => tab.id === initialTab)) {
setActiveTab(initialTab);
}
}, [initialTab, visibleTabs]);

return {
activeTab,
setActiveTab,
visibleTabs,
isDeveloperMode,
};
}
1 change: 1 addition & 0 deletions app/components/@settings/core/ControlPanelDialog/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ControlPanelDialog } from './ControlPanelDialog';
1 change: 1 addition & 0 deletions app/components/@settings/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Core exports
export { ControlPanel } from './core/ControlPanel';
export { ControlPanelDialog } from './core/ControlPanelDialog';
export type { TabType, TabVisibilityConfig } from './core/types';

// Constants
Expand Down
Loading