dev: hello world
This commit is contained in:
commit
6037fed3f4
145 changed files with 16848 additions and 0 deletions
15
contexts/globalContextProvider.tsx
Normal file
15
contexts/globalContextProvider.tsx
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { UserProvider } from "./user.context";
|
||||
import { ToastContextProvider } from "./toast.context";
|
||||
import { ThemeContextProvider } from "./theme.context";
|
||||
|
||||
const GlobalContextProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
return (
|
||||
<UserProvider>
|
||||
<ToastContextProvider>
|
||||
<ThemeContextProvider>{children}</ThemeContextProvider>
|
||||
</ToastContextProvider>
|
||||
</UserProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default GlobalContextProvider;
|
||||
144
contexts/theme.context.tsx
Normal file
144
contexts/theme.context.tsx
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
import React, { createContext, useCallback, useReducer, useEffect } from "react";
|
||||
// constants
|
||||
import {
|
||||
TOGGLE_SIDEBAR,
|
||||
REHYDRATE_THEME,
|
||||
SET_ISSUE_VIEW,
|
||||
SET_GROUP_BY_PROPERTY,
|
||||
} from "constants/theme.context.constants";
|
||||
// components
|
||||
import ToastAlert from "components/toast-alert";
|
||||
|
||||
export const themeContext = createContext<ContextType>({} as ContextType);
|
||||
|
||||
// types
|
||||
import type { IIssue, NestedKeyOf } from "types";
|
||||
|
||||
type Theme = {
|
||||
collapsed: boolean;
|
||||
issueView: "list" | "kanban" | null;
|
||||
groupByProperty: NestedKeyOf<IIssue> | null;
|
||||
};
|
||||
|
||||
type ReducerActionType = {
|
||||
type:
|
||||
| typeof TOGGLE_SIDEBAR
|
||||
| typeof REHYDRATE_THEME
|
||||
| typeof SET_ISSUE_VIEW
|
||||
| typeof SET_GROUP_BY_PROPERTY;
|
||||
payload?: Partial<Theme>;
|
||||
};
|
||||
|
||||
type ContextType = {
|
||||
collapsed: boolean;
|
||||
issueView: "list" | "kanban" | null;
|
||||
groupByProperty: NestedKeyOf<IIssue> | null;
|
||||
toggleCollapsed: () => void;
|
||||
setIssueView: (display: "list" | "kanban") => void;
|
||||
setGroupByProperty: (property: NestedKeyOf<IIssue> | null) => void;
|
||||
};
|
||||
|
||||
type StateType = Theme;
|
||||
type ReducerFunctionType = (state: StateType, action: ReducerActionType) => StateType;
|
||||
|
||||
export const initialState: StateType = {
|
||||
collapsed: false,
|
||||
issueView: null,
|
||||
groupByProperty: null,
|
||||
};
|
||||
|
||||
export const reducer: ReducerFunctionType = (state, action) => {
|
||||
const { type, payload } = action;
|
||||
|
||||
switch (type) {
|
||||
case TOGGLE_SIDEBAR:
|
||||
const newState = {
|
||||
...state,
|
||||
collapsed: !state.collapsed,
|
||||
};
|
||||
localStorage.setItem("theme", JSON.stringify(newState));
|
||||
return newState;
|
||||
case REHYDRATE_THEME: {
|
||||
let newState: any = localStorage.getItem("theme");
|
||||
if (newState !== null) {
|
||||
newState = JSON.parse(newState);
|
||||
}
|
||||
return { ...initialState, ...newState };
|
||||
}
|
||||
case SET_ISSUE_VIEW: {
|
||||
const newState = {
|
||||
...state,
|
||||
issueView: payload?.issueView || "list",
|
||||
};
|
||||
localStorage.setItem("theme", JSON.stringify(newState));
|
||||
return {
|
||||
...state,
|
||||
...newState,
|
||||
};
|
||||
}
|
||||
case SET_GROUP_BY_PROPERTY: {
|
||||
const newState = {
|
||||
...state,
|
||||
groupByProperty: payload?.groupByProperty || null,
|
||||
};
|
||||
localStorage.setItem("theme", JSON.stringify(newState));
|
||||
return {
|
||||
...state,
|
||||
...newState,
|
||||
};
|
||||
}
|
||||
default: {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const ThemeContextProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
|
||||
const toggleCollapsed = useCallback(() => {
|
||||
dispatch({
|
||||
type: TOGGLE_SIDEBAR,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const setIssueView = useCallback((display: "list" | "kanban") => {
|
||||
dispatch({
|
||||
type: SET_ISSUE_VIEW,
|
||||
payload: {
|
||||
issueView: display,
|
||||
},
|
||||
});
|
||||
}, []);
|
||||
|
||||
const setGroupByProperty = useCallback((property: NestedKeyOf<IIssue> | null) => {
|
||||
dispatch({
|
||||
type: SET_GROUP_BY_PROPERTY,
|
||||
payload: {
|
||||
groupByProperty: property,
|
||||
},
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch({
|
||||
type: REHYDRATE_THEME,
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<themeContext.Provider
|
||||
value={{
|
||||
collapsed: state.collapsed,
|
||||
toggleCollapsed,
|
||||
issueView: state.issueView,
|
||||
setIssueView,
|
||||
groupByProperty: state.groupByProperty,
|
||||
setGroupByProperty,
|
||||
}}
|
||||
>
|
||||
<ToastAlert />
|
||||
{children}
|
||||
</themeContext.Provider>
|
||||
);
|
||||
};
|
||||
101
contexts/toast.context.tsx
Normal file
101
contexts/toast.context.tsx
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
import React, { createContext, useCallback, useReducer } from "react";
|
||||
// uuid
|
||||
import { v4 as uuid } from "uuid";
|
||||
// constants
|
||||
import { SET_TOAST_ALERT, REMOVE_TOAST_ALERT } from "constants/toast.context.constants";
|
||||
// components
|
||||
import ToastAlert from "components/toast-alert";
|
||||
|
||||
export const toastContext = createContext<ContextType>({} as ContextType);
|
||||
|
||||
// types
|
||||
type ToastAlert = {
|
||||
id: string;
|
||||
title: string;
|
||||
message?: string;
|
||||
type: "success" | "error" | "warning" | "info";
|
||||
};
|
||||
|
||||
type ReducerActionType = {
|
||||
type: typeof SET_TOAST_ALERT | typeof REMOVE_TOAST_ALERT;
|
||||
payload: ToastAlert;
|
||||
};
|
||||
|
||||
type ContextType = {
|
||||
alerts?: ToastAlert[];
|
||||
removeAlert: (id: string) => void;
|
||||
setToastAlert: (data: {
|
||||
title: string;
|
||||
type?: "success" | "error" | "warning" | "info" | undefined;
|
||||
message?: string | undefined;
|
||||
}) => void;
|
||||
};
|
||||
|
||||
type StateType = {
|
||||
toastAlerts?: ToastAlert[];
|
||||
};
|
||||
|
||||
type ReducerFunctionType = (state: StateType, action: ReducerActionType) => StateType;
|
||||
|
||||
export const initialState: StateType = {
|
||||
toastAlerts: [],
|
||||
};
|
||||
|
||||
export const reducer: ReducerFunctionType = (state, action) => {
|
||||
const { type, payload } = action;
|
||||
|
||||
switch (type) {
|
||||
case SET_TOAST_ALERT:
|
||||
return {
|
||||
...state,
|
||||
toastAlerts: [...(state.toastAlerts ?? []), payload],
|
||||
};
|
||||
case REMOVE_TOAST_ALERT:
|
||||
return {
|
||||
...state,
|
||||
toastAlerts: state.toastAlerts?.filter((toastAlert) => toastAlert.id !== payload.id),
|
||||
};
|
||||
default: {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const ToastContextProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
|
||||
const removeAlert = useCallback((id: string) => {
|
||||
dispatch({
|
||||
type: REMOVE_TOAST_ALERT,
|
||||
payload: { id, title: "", message: "", type: "success" },
|
||||
});
|
||||
}, []);
|
||||
|
||||
const setToastAlert = useCallback(
|
||||
(data: {
|
||||
title: string;
|
||||
type?: "success" | "error" | "warning" | "info";
|
||||
message?: string;
|
||||
}) => {
|
||||
const id = uuid();
|
||||
const { title, type, message } = data;
|
||||
dispatch({
|
||||
type: SET_TOAST_ALERT,
|
||||
payload: { id, title, message, type: type ?? "success" },
|
||||
});
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
removeAlert(id);
|
||||
clearTimeout(timer);
|
||||
}, 5000);
|
||||
},
|
||||
[removeAlert]
|
||||
);
|
||||
|
||||
return (
|
||||
<toastContext.Provider value={{ setToastAlert, removeAlert, alerts: state.toastAlerts }}>
|
||||
<ToastAlert />
|
||||
{children}
|
||||
</toastContext.Provider>
|
||||
);
|
||||
};
|
||||
154
contexts/user.context.tsx
Normal file
154
contexts/user.context.tsx
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
import React, { createContext, ReactElement, useEffect, useState, useCallback } from "react";
|
||||
// next
|
||||
import Router from "next/router";
|
||||
import { useRouter } from "next/router";
|
||||
// swr
|
||||
import useSWR from "swr";
|
||||
// services
|
||||
import userService from "lib/services/user.service";
|
||||
import issuesServices from "lib/services/issues.services";
|
||||
import stateServices from "lib/services/state.services";
|
||||
import sprintsServices from "lib/services/cycles.services";
|
||||
import projectServices from "lib/services/project.service";
|
||||
import workspaceService from "lib/services/workspace.service";
|
||||
// constants
|
||||
import {
|
||||
CURRENT_USER,
|
||||
PROJECTS_LIST,
|
||||
USER_WORKSPACES,
|
||||
USER_WORKSPACE_INVITATIONS,
|
||||
PROJECT_ISSUES_LIST,
|
||||
STATE_LIST,
|
||||
CYCLE_LIST,
|
||||
} from "constants/fetch-keys";
|
||||
|
||||
// types
|
||||
import type { KeyedMutator } from "swr";
|
||||
import type { IUser, IWorkspace, IProject, IIssue, IssueResponse, ICycle, IState } from "types";
|
||||
interface IUserContextProps {
|
||||
user?: IUser;
|
||||
isUserLoading: boolean;
|
||||
mutateUser: KeyedMutator<IUser>;
|
||||
activeWorkspace?: IWorkspace;
|
||||
mutateWorkspaces: KeyedMutator<IWorkspace[]>;
|
||||
workspaces?: IWorkspace[];
|
||||
projects?: IProject[];
|
||||
setActiveProject: React.Dispatch<React.SetStateAction<IProject | undefined>>;
|
||||
mutateProjects: KeyedMutator<IProject[]>;
|
||||
activeProject?: IProject;
|
||||
issues?: IssueResponse;
|
||||
mutateIssues: KeyedMutator<IssueResponse>;
|
||||
sprints?: ICycle[];
|
||||
mutateSprints: KeyedMutator<ICycle[]>;
|
||||
states?: IState[];
|
||||
mutateStates: KeyedMutator<IState[]>;
|
||||
}
|
||||
|
||||
export const UserContext = createContext<IUserContextProps>({} as IUserContextProps);
|
||||
|
||||
export const UserProvider = ({ children }: { children: ReactElement }) => {
|
||||
const router = useRouter();
|
||||
|
||||
const { projectId } = router.query;
|
||||
|
||||
const [activeWorkspace, setActiveWorkspace] = useState<IWorkspace | undefined>();
|
||||
const [activeProject, setActiveProject] = useState<IProject | undefined>();
|
||||
|
||||
// API to fetch user information
|
||||
const { data, error, mutate } = useSWR<IUser>(CURRENT_USER, () => userService.currentUser(), {
|
||||
shouldRetryOnError: false,
|
||||
});
|
||||
|
||||
const {
|
||||
data: workspaces,
|
||||
error: workspaceError,
|
||||
mutate: mutateWorkspaces,
|
||||
} = useSWR<IWorkspace[]>(
|
||||
data ? USER_WORKSPACES : null,
|
||||
data ? () => workspaceService.userWorkspaces() : null,
|
||||
{
|
||||
shouldRetryOnError: false,
|
||||
}
|
||||
);
|
||||
|
||||
const { data: projects, mutate: mutateProjects } = useSWR<IProject[]>(
|
||||
activeWorkspace ? PROJECTS_LIST(activeWorkspace.slug) : null,
|
||||
activeWorkspace ? () => projectServices.getProjects(activeWorkspace.slug) : null
|
||||
);
|
||||
|
||||
const { data: issues, mutate: mutateIssues } = useSWR<IssueResponse>(
|
||||
activeWorkspace && activeProject
|
||||
? PROJECT_ISSUES_LIST(activeWorkspace.slug, activeProject.id)
|
||||
: null,
|
||||
activeWorkspace && activeProject
|
||||
? () => issuesServices.getIssues(activeWorkspace.slug, activeProject.id)
|
||||
: null
|
||||
);
|
||||
|
||||
const { data: states, mutate: mutateStates } = useSWR<IState[]>(
|
||||
activeWorkspace && activeProject ? STATE_LIST(activeProject.id) : null,
|
||||
activeWorkspace && activeProject
|
||||
? () => stateServices.getStates(activeWorkspace.slug, activeProject.id)
|
||||
: null
|
||||
);
|
||||
|
||||
const { data: sprints, mutate: mutateSprints } = useSWR<ICycle[]>(
|
||||
activeWorkspace && activeProject ? CYCLE_LIST(activeProject.id) : null,
|
||||
activeWorkspace && activeProject
|
||||
? () => sprintsServices.getCycles(activeWorkspace.slug, activeProject.id)
|
||||
: null
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!projects) return;
|
||||
const activeProject = projects.find((project) => project.id === projectId);
|
||||
setActiveProject(activeProject ?? projects[0]);
|
||||
}, [projectId, projects]);
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.last_workspace_id) {
|
||||
const workspace = workspaces?.find((item) => item.id === data?.last_workspace_id);
|
||||
if (workspace) {
|
||||
setActiveWorkspace(workspace);
|
||||
} else {
|
||||
const workspace = workspaces?.[0];
|
||||
setActiveWorkspace(workspace);
|
||||
userService.updateUser({ last_workspace_id: workspace?.id });
|
||||
}
|
||||
} else if (data) {
|
||||
const workspace = workspaces?.[0];
|
||||
setActiveWorkspace(workspace);
|
||||
userService.updateUser({ last_workspace_id: workspace?.id });
|
||||
}
|
||||
}, [data, workspaces]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!workspaces) return;
|
||||
if (workspaces.length === 0) Router.push("/invitations");
|
||||
}, [workspaces]);
|
||||
|
||||
return (
|
||||
<UserContext.Provider
|
||||
value={{
|
||||
user: error ? undefined : data,
|
||||
isUserLoading: !(!!data || !!error),
|
||||
mutateUser: mutate,
|
||||
activeWorkspace: workspaceError ? undefined : activeWorkspace,
|
||||
mutateWorkspaces: mutateWorkspaces,
|
||||
workspaces: workspaceError ? undefined : workspaces,
|
||||
projects,
|
||||
mutateProjects: mutateProjects,
|
||||
activeProject,
|
||||
issues,
|
||||
mutateIssues,
|
||||
sprints,
|
||||
mutateSprints,
|
||||
states,
|
||||
mutateStates,
|
||||
setActiveProject,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</UserContext.Provider>
|
||||
);
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue