mirror of
https://github.com/wasp-lang/open-saas.git
synced 2025-03-29 11:12:19 +01:00
new icons, demo app improvement (#14)
* new icons, demo app improvement * added prettier
This commit is contained in:
parent
d6f89ad9f0
commit
ffd4bd1d42
8
app/.prettierrc
Normal file
8
app/.prettierrc
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"trailingComma": "es5",
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"endOfLine": "lf",
|
||||
"tabWidth": 2,
|
||||
"jsxSingleQuote": true
|
||||
}
|
@ -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")
|
||||
],
|
||||
}
|
||||
|
||||
|
@ -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 (
|
||||
@ -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 () => {
|
||||
@ -53,11 +62,17 @@ 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
|
||||
@ -65,31 +80,42 @@ function Todo({ id, isDone, description, time }: TodoProps) {
|
||||
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);
|
||||
@ -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));
|
||||
@ -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'
|
||||
@ -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>
|
||||
@ -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>
|
||||
@ -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>
|
||||
))}
|
||||
@ -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>
|
||||
))}
|
||||
@ -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]);
|
||||
|
||||
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user