Persist 'projectId' between page reloads

This commit is contained in:
0x1eef 2024-03-12 05:48:57 -03:00
parent 510abf3031
commit e1fb2875ae
8 changed files with 59 additions and 63 deletions

View file

@ -1,2 +1,3 @@
import { createContext } from "react";
export const ParamContext = createContext<Record<string, string>>({});
export const CookieContext = createContext<Record<string, string>>({});

View file

@ -1,6 +1,6 @@
import { PropsWithChildren } from "react";
import { ApolloClient, ApolloProvider, InMemoryCache } from "@apollo/client";
import { ParamContext } from "~/Context";
import { ParamContext, CookieContext } from "~/Context";
export function App({ children }: PropsWithChildren<{}>) {
const client = new ApolloClient({
@ -13,9 +13,14 @@ export function App({ children }: PropsWithChildren<{}>) {
.split(",")
.map(e => e.split("=")),
);
const cookies = Object.fromEntries(
document.cookie.split(";").map(e => e.split("=")),
);
return (
<ParamContext.Provider value={params}>
<ApolloProvider client={client}>{children}</ApolloProvider>
</ParamContext.Provider>
<CookieContext.Provider value={cookies}>
<ParamContext.Provider value={params}>
<ApolloProvider client={client}>{children}</ApolloProvider>
</ParamContext.Provider>
</CookieContext.Provider>
);
}

View file

@ -1,5 +1,5 @@
import { useContext } from "react";
import { ParamContext } from "~/Context";
import { ParamContext, CookieContext } from "~/Context";
import { Maybe } from "~/types/schema";
import { ProjectSelect } from "~/components/ProjectSelect";
const BASE_CLASSNAMES = ["block", "w-3/4", "no-underline", "p-3", "mt-2"];
@ -32,6 +32,7 @@ const find = (path: string, bar: Bar): Maybe<Item> => {
export function NavBar() {
const params = useContext(ParamContext);
const cookies = useContext(CookieContext);
const bar: Bar = {
Tasks: [
{ text: "All tasks", href: "/tasks/" },
@ -69,13 +70,15 @@ export function NavBar() {
</>
);
})}
<h3>Filters</h3>
<h3>Scope</h3>
<ProjectSelect
selected={params.projectId}
selected={params.projectId || cookies.projectId}
onChange={project => {
if (project) {
document.cookie = `projectId=${project.id}; path=/`;
location.hash = `projectId=${project.id}`;
} else {
document.cookie = `projectId=; path=/; max-age=0`;
location.hash = "";
}
location.reload();

View file

@ -17,7 +17,7 @@ const ACTIVE_CLASSNAME = [
"border-solid",
"border-secondary",
"text-primary",
"bg-secondary"
"bg-secondary",
].join(" ");
type Props = {
@ -36,7 +36,7 @@ export const Select = ({ onChange, options, selected, placeholder }: Props) => {
const selectOptions = [{ label: placeholder, value: "" }, ...options];
const [isOpen, setIsOpen] = useState<boolean>(false);
const [option, setOption] = useState<Option>(
options.find(o => o.value === selected) || selectOptions[0]
options.find(o => o.value === selected) || selectOptions[0],
);
const onClick = (option: Option) => {
if (isOpen) {
@ -49,21 +49,21 @@ export const Select = ({ onChange, options, selected, placeholder }: Props) => {
};
const getClassName = (o1: Option, o2: Option) => {
if (o1.value === o2.value) {
return ACTIVE_CLASSNAME
return ACTIVE_CLASSNAME;
} else {
return (!isOpen && o1.value !== o2.value) ? "hidden" : LI_CLASSNAME;
return !isOpen && o1.value !== o2.value ? "hidden" : LI_CLASSNAME;
}
}
};
useEffect(() => {
const onDocumentClick = () => {
if (isOpen) {
setIsOpen(false)
setIsOpen(false);
}
}
document.body.addEventListener('click', onDocumentClick)
return () => document.body.removeEventListener('click', onDocumentClick)
}, [isOpen])
};
document.body.addEventListener("click", onDocumentClick);
return () => document.body.removeEventListener("click", onDocumentClick);
}, [isOpen]);
return (
<ul>
@ -71,7 +71,7 @@ export const Select = ({ onChange, options, selected, placeholder }: Props) => {
<li
key={i}
data-value={o.value}
onClick={(e) => [e.stopPropagation(), onClick(o)]}
onClick={e => [e.stopPropagation(), onClick(o)]}
className={getClassName(o, option)}
>
{o.label}

View file

@ -33,7 +33,7 @@ export function Tabs({ defaultLabel, labels, onChange }: Props) {
<li className={classnames(classNames)}>
<a
href={location.hash}
onClick={(e) => [e.preventDefault(), setActive(tab)]}
onClick={e => [e.preventDefault(), setActive(tab)]}
className="block p-2 text-smaller no-underline"
>
{tab.label}

View file

@ -1,11 +1,10 @@
import { useEffect, useState, useContext } from "react";
import { ParamContext } from "~/Context";
import { ParamContext, CookieContext } from "~/Context";
import { useForm } from "react-hook-form";
import { useCreateTask } from "~/hooks/mutations/useCreateTask";
import { useUpdateTask } from "~/hooks/mutations/useUpdateTask";
import { useFindTask } from "~/hooks/queries/useFindTask";
import { useProjects } from "~/hooks/queries/useProjects";
import { Task, Project, TaskInput, Maybe } from "~/types/schema";
import { Task, TaskInput, Maybe } from "~/types/schema";
import { rendermd } from "~/lib/markdown-utils";
import { NavBar } from "~/components/NavBar";
import { Tabs, Tab } from "~/components/Tabs";
@ -25,33 +24,32 @@ const DEFAULT_TASK_CONTENT = [
export function Task() {
const params = useContext(ParamContext);
const taskId = params.id ? parseInt(params.id) : null;
const cookies = useContext(CookieContext);
const projectId: Maybe<number> = Number(
params.projectId || cookies.projectId,
);
const taskId = params.id ? Number(params.id) : null;
const {
register,
handleSubmit,
watch,
setValue: set,
} = useForm<TaskInput>({
defaultValues: { projectId: 1 },
});
} = useForm<TaskInput>({});
const [isEditable, setIsEditable] = useState<boolean>(!taskId);
const createTask = useCreateTask();
const updateTask = useUpdateTask();
const [createTask, updateTask] = [useCreateTask(), useUpdateTask()];
const { data: taskData, loading: findingTask } = useFindTask(Number(taskId));
const { data: projectsData, loading: findingProjects } = useProjects();
const task = taskData?.findTask;
const projects = projectsData?.projects;
const content = watch("content");
const projectId: Maybe<number> = watch("projectId");
const onSave = (input: TaskInput) => {
const onSave = async (input: TaskInput) => {
if (taskId) {
updateTask({ variables: { taskId, input } }).then(
() => (location.href = "/tasks"),
);
} else {
createTask({ variables: { input } }).then(
() => (location.href = "/tasks"),
);
const res = await createTask({ variables: { input } });
const payload = res?.data?.createTask;
const { errors } = payload;
errors.length ? alert(errors) : (location.href = "/tasks");
}
};
@ -61,10 +59,12 @@ export function Task() {
}, []);
useEffect(() => {
set("projectId", task?.project?.id);
if (task?.project?.id) {
set("projectId", task?.project?.id);
}
}, [task?.project?.id]);
if (findingProjects || findingTask) {
if (findingTask) {
return null;
}
@ -76,23 +76,12 @@ export function Task() {
<div className="w-3/4">
<h1>{task ? "Edit task" : "New task"}</h1>
<form onSubmit={handleSubmit(onSave)}>
<select
{...register("projectId")}
className="p-3 w-3/4 mb-3"
value={projectId}
onChange={event => {
const v: string = event.target.value;
set("projectId", Number(v));
}}
>
{projects.map((project: Project, key: number) => {
return (
<option key={key} value={project.id}>
{project.name}
</option>
);
<input
type="hidden"
{...register("projectId", {
value: projectId,
})}
</select>
/>
<input
className="p-3 flex w-3/4 mb-3"
type="text"

View file

@ -1,5 +1,5 @@
import { useEffect, useContext } from "react";
import { ParamContext } from "~/Context";
import { ParamContext, CookieContext } from "~/Context";
import { NavBar } from "~/components/NavBar";
import { Group } from "~/components/Group";
import { TaskStatus, Maybe } from "~/types/schema";
@ -7,9 +7,11 @@ import { useTasks } from "~/hooks/queries/useTasks";
export function Tasks() {
const params = useContext(ParamContext);
const projectId: Maybe<number> = params.projectId
? parseInt(params.projectId)
: null;
const cookies = useContext(CookieContext);
const projectId: Maybe<number> =
params.projectId || cookies.projectId
? Number(params.projectId || cookies.projectId)
: null;
useEffect(() => {
document.title = "Tasks";

View file

@ -1,8 +1,4 @@
import {
CreateTaskPayload,
MutationCreateTaskArgs,
TaskInput,
} from "~/types/schema";
import { TaskInput } from "~/types/schema";
import { gql, useMutation } from "@apollo/client";
const GQL = gql`
@ -18,7 +14,7 @@ type TArgs = {
};
export function useCreateTask() {
const [create] = useMutation<CreateTaskPayload, MutationCreateTaskArgs>(GQL);
const [create] = useMutation(GQL);
return ({ variables: { input }, ...rest }: TArgs) => {
const projectId = Number(input.projectId);
const variables = { input: { ...input, ...{ projectId } } };