Skip to content

Commit

Permalink
refactoring: move onboarding UI from app layout to new /app/onboarding
Browse files Browse the repository at this point in the history
  • Loading branch information
flsilva committed Nov 14, 2023
1 parent 8c34a9e commit 704b818
Show file tree
Hide file tree
Showing 16 changed files with 247 additions and 70 deletions.
2 changes: 1 addition & 1 deletion src/app/(marketing)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default async function MarketingLayout({ children }: { children: React.Re
data: { session },
} = await supabase.auth.getSession();

if (session) redirect('/app/today');
if (session) redirect('/app/onboarding');
/**/

return (
Expand Down
25 changes: 4 additions & 21 deletions src/app/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import { redirect } from 'next/navigation';
import Link from 'next/link';
import { twMerge } from 'tailwind-merge';
import { buttonGreenClassName } from '@/modules/shared/controls/button/buttonClassName';
import { ErrorList } from '@/modules/shared/errors/ErrorList';
import { Header } from '@/modules/app/shared/Header';
import { MainMenu } from '@/modules/app/shared/main-menu/MainMenu';
import { InstallPwaDialog } from '@/modules/shared/pwa/InstallPwaDialog';
import { getProjects } from '@/modules/app/projects/ProjectsRepository';
import { UserProvider } from '@/modules/app/users/UserProvider';
import { UpdateUserTimeZone } from '@/modules/app/users/UpdateUserTimeZone';
import { getUser, UserDto } from '@/modules/app/users/UsersRepository';
Expand All @@ -18,14 +13,16 @@ export default async function AppLayout({
children: React.ReactNode;
dialog: React.ReactNode;
}) {
/*
* Redirect to sign-in if user is not signed in.
*/
let user: UserDto;
try {
user = await getUser();
} catch {
redirect('/auth/sign-in');
}

const { data: projects, errors } = await getProjects();
/**/

return (
<UserProvider user={user}>
Expand All @@ -40,20 +37,6 @@ export default async function AppLayout({
<div className="pb-16">
{children}
{dialog}
{errors && <ErrorList errors={errors} />}
{!errors && (!projects || projects.length === 0) && (
<>
<p className="mt-4 text-sm font-medium text-gray-600">
You don&#39;t have any projects yet.
</p>
<Link
href="/app/projects/new"
className={twMerge(buttonGreenClassName, 'w-fit mt-6')}
>
Create your first!
</Link>
</>
)}
</div>
</div>
</div>
Expand Down
23 changes: 23 additions & 0 deletions src/app/app/onboarding/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import 'server-only';
import Link from 'next/link';
import { redirect } from 'next/navigation';
import { twMerge } from 'tailwind-merge';
import { buttonGreenClassName } from '@/modules/shared/controls/button/buttonClassName';
import { ErrorList } from '@/modules/shared/errors/ErrorList';
import { getProjects } from '@/modules/app/projects/ProjectsRepository';

export default async function Onboarding() {
const { data: projects, errors } = await getProjects();
if (errors) return <ErrorList errors={errors} />;
if (projects && projects.length > 0) redirect('/app/today');

return (
<>
<h2 className="mt-4 text-2xl">Welcome!</h2>
<p className="mt-8 text-sm font-medium">You don&#39;t have any projects yet.</p>
<Link href="/app/projects/new" className={twMerge(buttonGreenClassName, 'w-fit mt-8')}>
Create your first!
</Link>
</>
);
}
2 changes: 1 addition & 1 deletion src/app/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ import 'server-only';
import { redirect } from 'next/navigation';

export default function AppPage() {
redirect('/app/today');
redirect('/app/onboarding');
}
35 changes: 10 additions & 25 deletions src/app/app/projects/[projectId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,31 @@ import 'server-only';
import { notFound } from 'next/navigation';
import { ErrorList } from '@/modules/shared/errors/ErrorList';
import { ProjectPageHeader } from '@/modules/app/projects/ProjectPageHeader';
import { getProjects, getProjectById } from '@/modules/app/projects/ProjectsRepository';
import { getProjects } from '@/modules/app/projects/ProjectsRepository';
import { AddTask } from '@/modules/app/tasks/AddTask';
import { TaskList } from '@/modules/app/tasks/TaskList';
import { getTasksByProject } from '@/modules/app/tasks/TasksRepository';
import { getServerSideUser } from '@/modules/app/users/UsersRepository';
import { TaskStatus } from '@/modules/app/tasks/TaskStatus';
import { NoTasksInProject } from '@/modules/app/projects/NoTasksInProject';

interface ProjectPageProps {
readonly params: { readonly projectId: string };
}

export default async function ProjectPage({ params: { projectId } }: ProjectPageProps) {
const [
{ data: projects, errors: projectsErrors },
{ data: project, errors: projectErrors },
{ data: tasks, errors: tasksErrors },
{ timeZone },
] = await Promise.all([
const [{ data: projects, errors: projectsErrors }] = await Promise.all([
getProjects({ isArchived: false }),
getProjectById({ id: projectId }),
getTasksByProject({ projectId }),
getServerSideUser(),
]);

if (projectsErrors) return <ErrorList errors={projectsErrors} />;
if (projectErrors) return <ErrorList errors={projectErrors} />;
if (tasksErrors) return <ErrorList errors={tasksErrors} />;

if (!project || !projects || !tasks) notFound();
if (!projects) notFound();

return (
<>
<ProjectPageHeader project={project} />
{tasks.length < 1 && (
<p className="mb-12 text-sm font-medium text-gray-600">No tasks in this project.</p>
)}
<TaskList
addTask={<AddTask projectId={project.id} projects={projects} />}
tasks={tasks}
timeZone={timeZone}
/>
<ProjectPageHeader id={projectId} />
<NoTasksInProject id={projectId} />
<TaskList className="mb-6" byProject={projectId} only={TaskStatus.Incomplete} />
<AddTask classname="py-4 my-4" projectId={projectId} projects={projects} />
<TaskList className="mt-6" byProject={projectId} only={TaskStatus.Complete} />
</>
);
}
58 changes: 58 additions & 0 deletions src/app/app/today/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,63 @@ import { TaskList } from '@/modules/app/tasks/TaskList';
import { getTasksDueBy, getTasksDueOn } from '@/modules/app/tasks/TasksRepository';
import { TodayHeader } from '@/modules/app/today/TodayHeader';
import { getServerSideUser } from '@/modules/app/users/UsersRepository';
import { TaskStatus } from '@/modules/app/tasks/TaskStatus';

export default async function TodayPage() {
const [
{ data: projects, errors: projectsErrors },
{ data: tasksOverdue, errors: tasksOverdueErrors },
{ data: tasksDueToday, errors: tasksDueTodayErrors },
{ timeZone },
] = await Promise.all([
getProjects(),
getTasksDueBy({ dueBy: subDays(endOfDay(new Date()), 1), isCompleted: false }),
getTasksDueOn({ dueOn: endOfDay(new Date()), isCompleted: false }),
getServerSideUser(),
]);

if (projectsErrors) return <ErrorList errors={projectsErrors} />;
if (tasksOverdueErrors) return <ErrorList errors={tasksOverdueErrors} />;
if (tasksDueTodayErrors) return <ErrorList errors={tasksDueTodayErrors} />;

return (
<>
<TodayHeader />
{(!tasksOverdue || tasksOverdue.length < 1) &&
(!tasksDueToday || tasksDueToday.length < 1) && (
<p className="mb-12 text-sm font-medium text-gray-600">
No tasks due today. Enjoy your day!
</p>
)}
{projects && projects.length > 0 && (
<>
{tasksOverdue && tasksOverdue.length > 0 && (
<>
<p className="text-xs font-semibold mb-4">Overdue</p>
<TaskList dueBy={subDays(endOfDay(new Date()), 1)} only={TaskStatus.Incomplete} />
</>
)}
{tasksOverdue && tasksOverdue.length > 0 && tasksDueToday && tasksDueToday.length > 0 && (
<p className="text-xs font-semibold mb-4">Today</p>
)}
<TaskList dueOn={endOfDay(new Date())} only={TaskStatus.Incomplete} />
<AddTask defaultDueDate={new Date()} projectId={projects[0].id} projects={projects} />
</>
)}
</>
);
}

/*
import 'server-only';
import { endOfDay, subDays } from 'date-fns';
import { ErrorList } from '@/modules/shared/errors/ErrorList';
import { getProjects } from '@/modules/app/projects/ProjectsRepository';
import { AddTask } from '@/modules/app/tasks/AddTask';
import { TaskList } from '@/modules/app/tasks/TaskList';
import { getTasksDueBy, getTasksDueOn } from '@/modules/app/tasks/TasksRepository';
import { TodayHeader } from '@/modules/app/today/TodayHeader';
import { getServerSideUser } from '@/modules/app/users/UsersRepository';
export default async function TodayPage() {
const [
Expand Down Expand Up @@ -59,3 +116,4 @@ export default async function TodayPage() {
</>
);
}
*/
2 changes: 1 addition & 1 deletion src/app/auth/callback/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ export async function GET(request: NextRequest) {
}

// URL to redirect to after sign in process completes
return NextResponse.redirect(`${requestUrl.origin}/app/today`);
return NextResponse.redirect(`${requestUrl.origin}/app/onboarding`);
}
2 changes: 1 addition & 1 deletion src/app/auth/sign-in/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export default async function SignInPage() {
data: { session },
} = await supabase.auth.getSession();

if (session) redirect('/app/today');
if (session) redirect('/app/onboarding');
/**/

const baseUrl = process.env.NEXT_PUBLIC_URL;
Expand Down
17 changes: 17 additions & 0 deletions src/modules/app/projects/NoTasksInProject.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ErrorList } from '@/modules/shared/errors/ErrorList';
import { getTasks } from '@/modules/app/tasks/TasksRepository';

interface NoTasksInProjectProps {
readonly id: string;
}

export const NoTasksInProject = async ({ id }: NoTasksInProjectProps) => {
const { data: tasks, errors } = await getTasks({
byProject: id,
});

if (errors) return <ErrorList errors={errors} />;
if (!tasks || tasks.length > 0) return null;

return <p className="mb-6 text-sm font-medium text-gray-600">No tasks in this project.</p>;
};
16 changes: 10 additions & 6 deletions src/modules/app/projects/ProjectPageHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
'use client';

import 'client-only';
import { ProjectDto } from './ProjectsRepository';
import { notFound } from 'next/navigation';
import { getProjectById } from './ProjectsRepository';
import { ProjectMutationDropdown } from './ProjectMutationDropdown';
import { FeedbackBox } from '@/modules/shared/box/FeedbackBox';
import { ErrorList } from '@/modules/shared/errors/ErrorList';

export interface ProjectPageHeaderProps {
readonly project: ProjectDto;
readonly id: string;
}

export const ProjectPageHeader = ({ project }: ProjectPageHeaderProps) => {
export const ProjectPageHeader = async ({ id }: ProjectPageHeaderProps) => {
const { data: project, errors } = await getProjectById({ id });

if (errors) return <ErrorList errors={errors} />;
if (!project) return notFound();

return (
<>
<div className="flex flex-col">
Expand Down
4 changes: 0 additions & 4 deletions src/modules/app/projects/ProjectStatus.tsx

This file was deleted.

14 changes: 8 additions & 6 deletions src/modules/app/tasks/AddTask.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
'use client';

import 'client-only';
import { useState } from 'react';
import { Fragment, useState } from 'react';
import { useRouter } from 'next/navigation';
import { Transition } from '@headlessui/react';
import { useWindowSize } from 'usehooks-ts';
import { twMerge } from 'tailwind-merge';
import { ClassNamePropsOptional } from '@/modules/shared/ClassNameProps';
import { buttonLinkClassName } from '@/modules/shared/controls/button/buttonClassName';
import { PlusSignalIcon } from '@/modules/shared/icons/PlusSignalIcon';
import { ProjectDto } from '@/modules/app/projects/ProjectsRepository';
import { TaskForm } from './TaskForm';
import { useRouter } from 'next/navigation';

export interface AddTaskProps {
export interface AddTaskProps extends ClassNamePropsOptional {
readonly classname?: string;
readonly defaultDueDate?: Date | null;
readonly projectId: string;
readonly projects: Array<ProjectDto>;
}

export const AddTask = ({ defaultDueDate, projectId, projects }: AddTaskProps) => {
export const AddTask = ({ classname, defaultDueDate, projectId, projects }: AddTaskProps) => {
const router = useRouter();
const [isAddingTask, setIsAddingTask] = useState(false);
const { width } = useWindowSize();
Expand Down Expand Up @@ -69,7 +71,7 @@ export const AddTask = ({ defaultDueDate, projectId, projects }: AddTaskProps) =
<>
<Transition
show={!isAddingTask}
as="div"
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 -translate-y-[50px]"
enterTo="opacity-100 translate-y-0"
Expand All @@ -79,7 +81,7 @@ export const AddTask = ({ defaultDueDate, projectId, projects }: AddTaskProps) =
>
<button
onClick={addTaskHandler}
className={twMerge(buttonLinkClassName, 'group flex-row self-start')}
className={twMerge(buttonLinkClassName, 'group flex-row self-start', classname)}
>
<PlusSignalIcon
width="1.25rem"
Expand Down
39 changes: 39 additions & 0 deletions src/modules/app/tasks/TaskList.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,41 @@
import { twMerge } from 'tailwind-merge';
import { ClassNamePropsOptional } from '@/modules/shared/ClassNameProps';
import { getServerSideUser } from '@/modules/app/users/UsersRepository';
import { GetTasksProps, getTasks } from './TasksRepository';
import { TaskListItem } from './TaskListItem';
import { ErrorList } from '@/modules/shared/errors/ErrorList';

export interface TaskListProps extends GetTasksProps, ClassNamePropsOptional {}

export const TaskList = async ({ className, ...rest }: TaskListProps) => {
const [{ timeZone }, { data: tasks, errors }] = await Promise.all([
getServerSideUser(),
getTasks(rest),
]);

if (errors) return <ErrorList errors={errors} />;
if (!tasks || tasks.length < 1) return null;

return (
<div className={twMerge('flex flex-col', className)}>
{tasks.map((task) => (
<div key={task.id} className="flex mb-4 last:mb-0">
<TaskListItem
description={task.description || ''}
dueDate={task.dueDate}
id={task.id}
isCompleted={task.isCompleted}
key={task.id}
name={task.name}
timeZone={timeZone}
/>
</div>
))}
</div>
);
};

/*
import { TaskDto } from './TasksRepository';
import { TaskListItem } from './TaskListItem';
Expand Down Expand Up @@ -38,3 +76,4 @@ export const TaskList = ({ addTask, tasks, timeZone }: TaskListProps) => {
</div>
);
};
*/
4 changes: 4 additions & 0 deletions src/modules/app/tasks/TaskStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum TaskStatus {
Complete = 'Complete',
Incomplete = 'Incomplete',
}
Loading

0 comments on commit 704b818

Please sign in to comment.