diff --git a/README.md b/README.md index 70fa2e0..f400f45 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ CREATE TABLE IF NOT EXISTS task_lists ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), name text NOT NULL, data jsonb NOT NULL, + variables jsonb NOT NULL, created_at timestamptz DEFAULT now(), user_id uuid REFERENCES auth.users(id), is_example boolean DEFAULT false @@ -272,6 +273,62 @@ Built with: - Supabase for database and authentication - React Quill for rich text editing +## Production + +You should: +- be logged into the VPS through SSH. +- have node, npm, and git installed on your machine. +- you should have pm2 installed globally and running as a systemd service. +- have the repo cloned onto the server. +- cd into the cloned repo folder on the server. + +Install dependencies + +Install dependancies and build the app + +```bash +npm install && npm run build +``` + +Run this command to run the app using pm2 + +```bash +pm2 start npm --name task-list-advanced -- run preview +``` + +Run this command to save and ensure the app runs again when the server reboots + +```bash +pm2 save +``` + +### Updated the codebase from the repo + +Stop the app + +```bash +pm2 stop task-list-advanced +``` + +Update the codebase + +```bash +git pull origin +``` + +Install any new dependancies and rebuild the app + +```bash +npm install && npm run build +``` + +Restart the app + +```bash +pm2 start task-list-advanced +``` + + ## See also my Youtube Channel URL: https://www.youtube.com/@DIYSmartCode

diff --git a/src/App.tsx b/src/App.tsx index c52247f..32f6771 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,16 +6,20 @@ import { useAuth } from './hooks/useAuth'; import { Header } from './components/Header'; import { TaskInput } from './components/TaskInput'; import { TaskListSection } from './components/TaskListSection'; +import { VariableListSection } from './components/VariableListSection'; import { Footer } from './components/Footer'; import { ConfirmationModal } from './components/ConfirmationModal'; import { SettingsModal } from './components/SettingsModal'; import { HelpModal } from './components/HelpModal'; import { ErrorNotification } from './components/ErrorNotification'; -import { IntroModal } from './components/IntroModal'; +// import { IntroModal } from './components/IntroModal'; import { Tour } from './components/tour/Tour'; import { AuthModal } from './components/auth/AuthModal'; import { AdminDashboard } from './components/admin/AdminDashboard'; import { supabase } from './lib/supabase'; +import { Task } from './types/task'; +import { useVariables, Variable } from './context/variableContext'; +import { ImportDataType, isImportDataType } from './types/importData'; export default function App() { const [settings, setSettings] = useSettings(); @@ -31,8 +35,6 @@ export default function App() { reorderTasks } = useTasks(); - const [ipaddress, setIpaddress] = useState(''); - const [username, setUsername] = useState(''); const [showConfirmationModal, setShowConfirmationModal] = useState(false); const [showSettingsModal, setShowSettingsModal] = useState(false); const [showHelpModal, setShowHelpModal] = useState(false); @@ -45,6 +47,8 @@ export default function App() { return !hasSeenTour && !settings.googleApiKey; }); + const { setVariables, setMergingVariables } = useVariables(); + useEffect(() => { // Check if this is the first user const checkFirstUser = async () => { @@ -120,24 +124,37 @@ export default function App() { return false; }; - const mergeData = (e) => { - e.preventDefault(); - console.log(ipaddress, username); - } + const handleClearAll = () => { + setTasks([]); + setVariables([]); + setMergingVariables(false); + } if (showAdminDashboard && isAdmin) { return ( setShowAdminDashboard(false)} onError={setError} - onEditList={(list) => { - setTasks(list.data); - setShowAdminDashboard(false); - }} /> ); } + const handleOnImport = (data: ImportDataType | Task[]) => { + if (isImportDataType(data)) { + setTasks(data.tasks); + setVariables(data.variables); + } else { + setTasks(data); + setVariables([]); + } + setMergingVariables(false); + }; + + const handleImportTaskList = (tasks: Task[], variables: Variable[]) => { + setTasks(tasks); + setVariables(variables); + } + return ( <>
@@ -153,9 +170,10 @@ export default function App() { onSettingsClick={() => setShowSettingsModal(true)} onAdminClick={() => setShowAdminDashboard(true)} tasks={tasks} - onImport={setTasks} - isAdmin={isAdmin} + onImport={handleOnImport} onError={setError} + onClear={handleClearAll} + isAdmin={isAdmin} />
@@ -167,28 +185,14 @@ export default function App() { onDuplicate={duplicateTask} onReorder={reorderTasks} onCheckAllSubTasks={checkAllSubTasks} - onImportTaskList={setTasks} + onImportTaskList={handleImportTaskList} googleApiKey={settings.googleApiKey} onError={setError} isAdmin={isAdmin} /> -
-
- -
-
- -
-
- -
+
+
diff --git a/src/components/AITaskGenerator.tsx b/src/components/AITaskGenerator.tsx index fb0acba..127de6c 100644 --- a/src/components/AITaskGenerator.tsx +++ b/src/components/AITaskGenerator.tsx @@ -2,10 +2,11 @@ import React, { useState } from 'react'; import { Send, Paperclip } from 'lucide-react'; import { generateTasks } from '../services/aiService'; import { ChatMessage } from '../types/chat'; +import { Variable } from '../types/variable'; interface AITaskGeneratorProps { apiKey: string; - onTasksGenerated: (tasks: any[]) => void; + onTasksGenerated: (tasks: any[], variables: Variable[]) => void; onError: (error: string) => void; } @@ -78,7 +79,7 @@ export function AITaskGenerator({ apiKey, onTasksGenerated, onError }: AITaskGen createdAt: new Date(task.createdAt || new Date()), id: task.id || crypto.randomUUID() })); - onTasksGenerated(newTasks); + onTasksGenerated(newTasks, parsedData.data.variables); setChatInput(''); setSelectedFile(null); setSelectedFileName(null); diff --git a/src/components/DescriptionModal.tsx b/src/components/DescriptionModal.tsx index 540214a..3548403 100644 --- a/src/components/DescriptionModal.tsx +++ b/src/components/DescriptionModal.tsx @@ -35,4 +35,4 @@ export function DescriptionModal({ content, onClose }: DescriptionModalProps) {
); -} +} diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 99872bf..b3be0a4 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,27 +1,31 @@ import React, { useState } from 'react'; -import { CheckSquare, Settings, Shield, Download, Save, Upload } from 'lucide-react'; +import { CheckSquare, Settings, Shield, Download, Save, Upload, XSquare } from 'lucide-react'; import { Task } from '../types/task'; import { ExportModal } from './ExportModal'; import { SaveModal } from './SaveModal'; import { saveTaskList } from '../services/taskListService'; +import { useVariables } from '../context/variableContext'; +import { ImportDataType } from '../types/importData'; interface HeaderProps { onLogoClick: () => void; onSettingsClick: () => void; onAdminClick: () => void; tasks: Task[]; - onImport: (tasks: Task[]) => void; + onImport: (data: ImportDataType) => void; onError: (error: string) => void; + onClear: () => void; isAdmin?: boolean; } -export function Header({ onLogoClick, onSettingsClick, onAdminClick, tasks, onImport, onError, isAdmin }: HeaderProps) { +export function Header({ onLogoClick, onSettingsClick, onAdminClick, tasks, onImport, onError, onClear, isAdmin }: HeaderProps) { const [showExportModal, setShowExportModal] = useState(false); const [showSaveModal, setShowSaveModal] = useState(false); const [saving, setSaving] = useState(false); - const [name, setName] = useState(''); - const handleSave = async (name: string) => { + const { variables, setMergingVariables } = useVariables(); + + const handleSave = async (name: string, isExample: boolean) => { if (!name.trim()) { onError('Please enter a name for the list'); return; @@ -34,7 +38,7 @@ export function Header({ onLogoClick, onSettingsClick, onAdminClick, tasks, onIm setSaving(true); try { - await saveTaskList(name, tasks, false); + await saveTaskList(name, tasks, variables, isExample); // onSave(); } catch (error) { console.error('Error saving list:', error); @@ -45,7 +49,7 @@ export function Header({ onLogoClick, onSettingsClick, onAdminClick, tasks, onIm }; const handleExport = (name: string) => { - const dataStr = JSON.stringify({ name, data: tasks }, null, 2); + const dataStr = JSON.stringify({ name, data: { tasks, variables } }, null, 2); const dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr); const sanitizedName = name.replace(/[^a-z0-9]/gi, '_').toLowerCase(); @@ -71,6 +75,7 @@ export function Header({ onLogoClick, onSettingsClick, onAdminClick, tasks, onIm if (parsed.data) { onImport(parsed.data); } + setMergingVariables(false); } catch (error) { console.error('Error parsing imported file:', error); } @@ -90,17 +95,7 @@ export function Header({ onLogoClick, onSettingsClick, onAdminClick, tasks, onIm
- {isAdmin && ( - - )} + + {isAdmin && ( + + )} +
{isAdmin && ( +
+
+
+ {variables.filter(variable => variable.token.length > 0).map(variable => ( + + ))} +
+
+ + +
+
+ + + ); +} \ No newline at end of file diff --git a/src/components/RichTextEditor.tsx b/src/components/RichTextEditor.tsx index 6d41322..3d58260 100644 --- a/src/components/RichTextEditor.tsx +++ b/src/components/RichTextEditor.tsx @@ -21,14 +21,14 @@ export function RichTextEditor({ value, onChange }: RichTextEditorProps) { modules={{ toolbar: [ ['bold', 'italic', 'underline', 'strike'], - [{ 'header': [2, 3, false] }], + [{ 'header': [1, 2, 3, false] }], [{ 'list': 'ordered'}, { 'list': 'bullet' }], ['link', 'image'], ['clean'] ], }} /> -