Skip to content

Commit

Permalink
new icons, demo app improvement (wasp-lang#14)
Browse files Browse the repository at this point in the history
* new icons, demo app improvement

* added prettier
  • Loading branch information
martinovicdev authored and vincanger committed Jan 8, 2024
1 parent d6f89ad commit ffd4bd1
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 48 deletions.
8 changes: 8 additions & 0 deletions app/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"trailingComma": "es5",
"semi": true,
"singleQuote": true,
"endOfLine": "lf",
"tabWidth": 2,
"jsxSingleQuote": true
}
3 changes: 3 additions & 0 deletions app/main.wasp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ app SaaSTemplate {
("@faker-js/faker", "8.3.1"),
("@google-analytics/data", "4.1.0"),
("openai", "^4.24.1"),
("lucide-react", "0.306.0"),
("prettier", "3.1.1"),
("prettier-plugin-tailwindcss", "0.5.11")
],
}

Expand Down
167 changes: 119 additions & 48 deletions app/src/client/app/DemoAppPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useQuery } from '@wasp/queries';
import getAllTasksByUser from '@wasp/queries/getAllTasksByUser';
import { Task } from '@wasp/entities';
import { CgSpinner } from 'react-icons/cg';
import { XSquare } from 'lucide-react';

export default function DemoAppPage() {
return (
Expand Down Expand Up @@ -35,12 +36,20 @@ export default function DemoAppPage() {
type TodoProps = Pick<Task, 'id' | 'isDone' | 'description' | 'time'>;

function Todo({ id, isDone, description, time }: TodoProps) {
const handleCheckboxChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
await updateTask({ id, isDone: e.currentTarget.checked });
const handleCheckboxChange = async (
e: React.ChangeEvent<HTMLInputElement>
) => {
await updateTask({
id,
isDone: e.currentTarget.checked,
});
};

const handleTimeChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
await updateTask({ id, time: e.currentTarget.value });
await updateTask({
id,
time: e.currentTarget.value,
});
};

const handleDeleteClick = async () => {
Expand All @@ -53,43 +62,60 @@ function Todo({ id, isDone, description, time }: TodoProps) {
<div className='flex items-center gap-3'>
<input
type='checkbox'
className='ml-1 form-checkbox bg-purple-500 checked:bg-purple-300 rounded border-purple-600 duration-200 ease-in-out hover:bg-purple-600 hover:checked:bg-purple-600 focus:ring focus:ring-purple-300 focus:checked:bg-purple-400 focus:ring-opacity-50'
className='ml-1 form-checkbox bg-purple-300 checked:bg-purple-300 rounded border-purple-400 duration-200 ease-in-out hover:bg-purple-400 hover:checked:bg-purple-600 focus:ring focus:ring-purple-300 focus:checked:bg-purple-400 focus:ring-opacity-50 text-black'
checked={isDone}
onChange={handleCheckboxChange}
/>
<span className={`text-slate-600 ${isDone ? 'line-through text-slate-500' : ''}`}>{description}</span>
<span
className={`text-slate-600 ${
isDone ? 'line-through text-slate-500' : ''
}`}
>
{description}
</span>
</div>
<div className='flex items-center gap-2'>
<input
id='time'
type='number'
min={0.5}
step={0.5}
className={`w-15 h-8 text-center text-slate-600 text-xs rounded border border-gray-200 focus:outline-none focus:border-transparent focus:ring-2 focus:ring-purple-300 focus:ring-opacity-50 ${
className={`w-18 h-8 text-center text-slate-600 text-xs rounded border border-gray-200 focus:outline-none focus:border-transparent focus:ring-2 focus:ring-purple-300 focus:ring-opacity-50 ${
isDone && 'pointer-events-none opacity-50'
}`}
value={time}
onChange={handleTimeChange}
/>
<span className={`italic text-slate-600 text-xs ${isDone ? 'text-slate-500' : ''}`}>hrs</span>
<span
className={`italic text-slate-600 text-xs ${
isDone ? 'text-slate-500' : ''
}`}
>
hrs
</span>
</div>
</div>
<div className='flex items-center justify-end w-15'>
<button className={`p-1 ${!isDone ? 'hidden' : ''}`} onClick={handleDeleteClick}>
<button className='p-1' onClick={handleDeleteClick} title='Remove task'>
<XSquare size='20' className='text-red-600 hover:text-red-700' />
</button>
</div>
</div>
);
}

function NewTaskForm({ handleCreateTask }: { handleCreateTask: typeof createTask }) {
function NewTaskForm({
handleCreateTask,
}: {
handleCreateTask: typeof createTask;
}) {
const [description, setDescription] = useState<string>('');
const [todaysHours, setTodaysHours] = useState<string>('8');
const [response, setResponse] = useState<any>(null);
const [isPlanGenerating, setIsPlanGenerating] = useState<boolean>(false);

const { data: tasks, isLoading: isTasksLoading } = useQuery(getAllTasksByUser);
const { data: tasks, isLoading: isTasksLoading } =
useQuery(getAllTasksByUser);

useEffect(() => {
console.log('response', response);
Expand All @@ -107,7 +133,9 @@ function NewTaskForm({ handleCreateTask }: { handleCreateTask: typeof createTask
const handleGeneratePlan = async () => {
try {
setIsPlanGenerating(true);
const response = await generateGptResponse({ hours: todaysHours });
const response = await generateGptResponse({
hours: todaysHours,
});
if (response) {
console.log('response', response);
setResponse(JSON.parse(response));
Expand All @@ -130,6 +158,11 @@ function NewTaskForm({ handleCreateTask }: { handleCreateTask: typeof createTask
placeholder='Enter task description'
value={description}
onChange={(e) => setDescription(e.currentTarget.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
handleSubmit();
}
}}
/>
<button
type='button'
Expand All @@ -146,50 +179,61 @@ function NewTaskForm({ handleCreateTask }: { handleCreateTask: typeof createTask
{tasks!! && tasks.length > 0 ? (
<div className='space-y-4'>
{tasks.map((task: Task) => (
<Todo key={task.id} id={task.id} isDone={task.isDone} description={task.description} time={task.time} />
<Todo
key={task.id}
id={task.id}
isDone={task.isDone}
description={task.description}
time={task.time}
/>
))}
<div className='flex flex-col gap-3'>
<div className='flex items-center justify-between gap-3'>
<label htmlFor='time' className='text-sm text-gray-600 text-nowrap font-semibold'>
How many hours will you work today?
</label>
<input
type='number'
id='time'
step={0.5}
min={1}
max={24}
className='min-w-[7rem] text-gray-800/90 text-center font-medium rounded-md border border-gray-200 bg-yellow-50 hover:bg-yellow-100 shadow-md focus:outline-none focus:border-transparent focus:shadow-none duration-200 ease-in-out hover:shadow-none'
value={todaysHours}
onChange={(e) => setTodaysHours(e.currentTarget.value)}
/>
</div>
</div>
<div className='flex flex-col gap-3'>
<div className='flex items-center justify-between gap-3'>
<label
htmlFor='time'
className='text-sm text-gray-600 dark:text-gray-300 text-nowrap font-semibold'
>
How many hours will you work today?
</label>
<input
type='number'
id='time'
step={0.5}
min={1}
max={24}
className='min-w-[7rem] text-gray-800/90 text-center font-medium rounded-md border border-gray-200 bg-yellow-50 hover:bg-yellow-100 shadow-md focus:outline-none focus:border-transparent focus:shadow-none duration-200 ease-in-out hover:shadow-none'
value={todaysHours}
onChange={(e) => setTodaysHours(e.currentTarget.value)}
/>
</div>
</div>
</div>
) : (
<div className='text-gray-600 text-center'>Add tasks to begin</div>
)}
</div>


<button
type='button'
disabled={ isPlanGenerating || tasks?.length === 0 }
disabled={isPlanGenerating || tasks?.length === 0}
onClick={() => handleGeneratePlan()}
className='flex items-center justify-center min-w-[7rem] font-medium text-gray-800/90 bg-yellow-50 shadow-md ring-1 ring-inset ring-slate-200 py-2 px-4 rounded-md hover:bg-yellow-100 duration-200 ease-in-out focus:outline-none focus:shadow-none hover:shadow-none disabled:opacity-70 disabled:cursor-not-allowed'
>
{isPlanGenerating ?
<>
<CgSpinner className='inline-block mr-2 animate-spin' />
Generating...
</>
:
'Generate Schedule'}
{isPlanGenerating ? (
<>
<CgSpinner className='inline-block mr-2 animate-spin' />
Generating...
</>
) : (
'Generate Schedule'
)}
</button>

{!!response && (
<div className='flex flex-col'>
<h3 className='text-lg font-semibold text-gray-900 dark:text-white'>Today's Schedule</h3>
<h3 className='text-lg font-semibold text-gray-900 dark:text-white'>
Today's Schedule
</h3>

<TaskTable schedule={response.schedule} />
</div>
Expand All @@ -210,11 +254,18 @@ function TaskTable({ schedule }: { schedule: any[] }) {
<tr>
<th
className={`flex items-center justify-between gap-5 py-4 px-3 text-slate-800 border rounded-md border-slate-200 ${
task.priority === 'high' ? 'bg-red-50' : task.priority === 'low' ? 'bg-green-50' : 'bg-yellow-50'
task.priority === 'high'
? 'bg-red-50'
: task.priority === 'low'
? 'bg-green-50'
: 'bg-yellow-50'
}`}
>
<span>{task.name}</span>
<span className='opacity-70 text-xs font-medium italic'> {task.priority} priority</span>
<span className='opacity-70 text-xs font-medium italic'>
{' '}
{task.priority} priority
</span>
</th>
</tr>
</thead>
Expand All @@ -224,7 +275,10 @@ function TaskTable({ schedule }: { schedule: any[] }) {
<td
className={`flex items-center justify-between py-2 px-3 text-slate-600 border rounded-md border-purple-100 bg-purple-50`}
>
<Subtask description={subtask.description} time={subtask.time} />
<Subtask
description={subtask.description}
time={subtask.time}
/>
</td>
</tr>
))}
Expand All @@ -234,7 +288,10 @@ function TaskTable({ schedule }: { schedule: any[] }) {
<td
className={`flex items-center justify-between py-2 px-3 text-slate-600 border rounded-md border-purple-100 bg-purple-50`}
>
<Subtask description={breakItem.description} time={breakItem.time} />
<Subtask
description={breakItem.description}
time={breakItem.time}
/>
</td>
</tr>
))}
Expand All @@ -252,8 +309,10 @@ function Subtask({ description, time }: { description: string; time: number }) {
if (time === 0) return 0;
const hours = Math.floor(time);
const minutes = Math.round((time - hours) * 60);
return `${hours > 0 ? hours + 'hr' : ''} ${minutes > 0 ? minutes + 'min' : ''}`;
}
return `${hours > 0 ? hours + 'hr' : ''} ${
minutes > 0 ? minutes + 'min' : ''
}`;
};

const minutes = useMemo(() => convertHrsToMinutes(time), [time]);

Expand All @@ -265,8 +324,20 @@ function Subtask({ description, time }: { description: string; time: number }) {
checked={isDone}
onChange={(e) => setIsDone(e.currentTarget.checked)}
/>
<span className={`text-slate-600 ${isDone ? 'line-through text-slate-500 opacity-50' : ''}`}>{description}</span>
<span className={`text-slate-600 ${isDone ? 'line-through text-slate-500 opacity-50' : ''}`}>{minutes}</span>
<span
className={`text-slate-600 ${
isDone ? 'line-through text-slate-500 opacity-50' : ''
}`}
>
{description}
</span>
<span
className={`text-slate-600 ${
isDone ? 'line-through text-slate-500 opacity-50' : ''
}`}
>
{minutes}
</span>
</>
);
}

0 comments on commit ffd4bd1

Please sign in to comment.