Skip to content

Commit

Permalink
Add DashWind dashboard (#5)
Browse files Browse the repository at this point in the history
* edit form submit buttons width

* add reduxToolkit, react-redux, next redux wrapper packages

* add dash-wind components converted to typescript

* change all Next <Image /> back to HTML <img />

* edit store.ts to follow next-redux-wrapper docs

* add next-redux-wrapper provider to _app.tsx

* move to store.ts

* swap out react-redux imports for next-react-redux imports

* move FormSteps type to its own ts file

* add login/register/forgot-password/welcome pages and edit link routing

* small edits

* add deps to useEffect deps array

* add page layout for dashboard with header and side nav

* add 404 fallback page

* backup original dapp onboarding flow - probably delete later

* remove some commented code

* routing and icon edit

* edit routes, comment out some sidebar routs, rename 404 page so it will work

* edit route

* add all dashboard pages

* add all missing deps to useEffect deps arrays

* add more deps to deps arrays

* es-lint ignore img elements
  • Loading branch information
Spencer-Sch authored Dec 1, 2023
1 parent ba73c57 commit 8cca1fd
Show file tree
Hide file tree
Showing 119 changed files with 4,983 additions and 20 deletions.
5 changes: 3 additions & 2 deletions packages/nextjs/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable @next/next/no-img-element */
import React, { useCallback, useRef, useState } from "react";
import Image from "next/image";
// import Image from "next/image";
import Link from "next/link";
import { useRouter } from "next/router";
import { Bars3Icon, BugAntIcon } from "@heroicons/react/24/outline";
Expand Down Expand Up @@ -88,7 +89,7 @@ export const Header = () => {
</div>
<Link href="/" passHref className="hidden lg:flex items-center gap-2 ml-4 mr-6 shrink-0">
<div className="flex relative w-10 h-10">
<Image alt="SE2 logo" className="cursor-pointer" fill src="/logo.svg" />
<img alt="SE2 logo" className="cursor-pointer" src="/logo.svg" />
</div>
<div className="flex flex-col">
<span className="font-bold leading-tight">Scaffold-ETH</span>
Expand Down
43 changes: 43 additions & 0 deletions packages/nextjs/components/dash-wind/app/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import axios, { AxiosError, AxiosResponse } from "axios";

const checkAuth = () => {
/* Getting token value stored in localstorage, if token is not present we will open login page
for all internal dashboard routes */
const TOKEN = localStorage.getItem("token");
const PUBLIC_ROUTES = ["login", "forgot-password", "register", "documentation"];

const isPublicPage = PUBLIC_ROUTES.some(r => window.location.href.includes(r));

if (!TOKEN && !isPublicPage) {
window.location.href = "/login";
return;
} else {
axios.defaults.headers.common["Authorization"] = `Bearer ${TOKEN}`;

axios.interceptors.request.use(
function (config) {
// UPDATE: Add this code to show global loading indicator
document.body.classList.add("loading-indicator");
return config;
},
function (error: AxiosError) {
return Promise.reject(error);
},
);

axios.interceptors.response.use(
function (response: AxiosResponse) {
// UPDATE: Add this code to hide global loading indicator
document.body.classList.remove("loading-indicator");
return response;
},
function (error: AxiosError) {
document.body.classList.remove("loading-indicator");
return Promise.reject(error);
},
);
return TOKEN;
}
};

export default checkAuth;
20 changes: 20 additions & 0 deletions packages/nextjs/components/dash-wind/app/init.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import axios from "axios";

const initializeApp = () => {
// Setting base URL for all API request via axios
axios.defaults.baseURL = process.env.REACT_APP_BASE_URL;

if (!process.env.NODE_ENV || process.env.NODE_ENV === "development") {
// dev code
} else {
// Prod build code

// Removing console.log from prod
// console.log = () => {};
console.log = () => null;

// init analytics here
}
};

export default initializeApp;
62 changes: 62 additions & 0 deletions packages/nextjs/components/dash-wind/app/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import headerSlice from "../features/common/headerSlice";
import modalSlice from "../features/common/modalSlice";
import rightDrawerSlice from "../features/common/rightDrawerSlice";
import leadsSlice from "../features/leads/leadSlice";
import { Reducer, ThunkAction, configureStore } from "@reduxjs/toolkit";
import { createWrapper } from "next-redux-wrapper";
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import { Action } from "redux";

interface CombinedReducer {
header: Reducer<{
pageTitle: string;
noOfNotifications: number;
newNotificationMessage: string;
newNotificationStatus: number;
}>;
rightDrawer: Reducer<{
header: string;
isOpen: boolean;
bodyType: string;
extraObject: Record<string, any>;
}>;
modal: Reducer<{
title: string;
isOpen: boolean;
bodyType: string;
size: string;
extraObject: Record<string, any>;
}>;
lead: Reducer<{
isLoading: boolean;
leads: never[];
}>;
}

const combinedReducer: CombinedReducer = {
header: headerSlice,
rightDrawer: rightDrawerSlice,
modal: modalSlice,
lead: leadsSlice,
};

// export default configureStore({
// reducer: combinedReducer,
// });
const store = configureStore({
reducer: combinedReducer,
});

// const makeStore = context => store;
const makeStore = () => store;

export type MyStore = ReturnType<typeof makeStore>;
export type MyState = ReturnType<MyStore["getState"]>;
export type MyDispatch = MyStore["dispatch"];
// export type MyDispatch = typeof store.dispatch;
export type MyThunk<ReturnType = void> = ThunkAction<ReturnType, MyState, unknown, Action>;

export const useMyDispatch = () => useDispatch<MyDispatch>();
export const useMySelector: TypedUseSelectorHook<MyState> = useSelector;

export const wrapper = createWrapper<MyStore>(makeStore);
178 changes: 178 additions & 0 deletions packages/nextjs/components/dash-wind/components/CalendarView/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import { useEffect, useState } from "react";
import { CalendarDetails, CalendarEvents } from "../../types/CalendarTypes";
import { getTheme } from "./util";
import moment from "moment";
import ChevronLeftIcon from "@heroicons/react/24/solid/ChevronLeftIcon";
import ChevronRightIcon from "@heroicons/react/24/solid/ChevronRightIcon";

interface props {
calendarEvents: CalendarEvents;
addNewEvent: (date: moment.Moment) => void;
openDayDetail: (detail: CalendarDetails) => void;
}

const CalendarView: React.FC<props> = ({ calendarEvents, addNewEvent, openDayDetail }) => {
const today = moment().startOf("day");
const weekdays = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"];
const colStartClasses = [
"",
"col-start-2",
"col-start-3",
"col-start-4",
"col-start-5",
"col-start-6",
"col-start-7",
];

const [firstDayOfMonth, setFirstDayOfMonth] = useState<moment.Moment>(moment().startOf("month"));
const [events, setEvents] = useState<[] | CalendarEvents>([]);
const [, setCurrMonth] = useState<string>(() => moment(today).format("MMM-yyyy"));

useEffect(() => {
setEvents(calendarEvents);
}, [calendarEvents]);

const allDaysInMonth = () => {
const start: moment.Moment = moment(firstDayOfMonth).startOf("week");
const end: moment.Moment = moment(moment(firstDayOfMonth).endOf("month")).endOf("week");
const days: moment.Moment[] = [];
let day: moment.Moment = start;
while (day <= end) {
days.push(day);
day = day.clone().add(1, "d");
}
return days;
};

const getEventsForCurrentDate = (date: moment.Moment): CalendarEvents => {
let filteredEvents = events.filter(e => {
if ("startTime" in e) {
return moment(date).isSame(moment(e.startTime), "day");
}
return false;
});
if (filteredEvents.length > 2) {
const originalLength = filteredEvents.length;
filteredEvents = filteredEvents.slice(0, 2);
filteredEvents.push({ title: `${originalLength - 2} more`, theme: "MORE" });
}
return filteredEvents;
};

const openAllEventsDetail = (date: moment.Moment, theme: string) => {
if (theme != "MORE") return 1;
const filteredEvents = events
.filter(e => {
if ("startTime" in e) {
return moment(date).isSame(moment(e.startTime), "day");
}
return false;
})
.map(e => {
return { title: e.title, theme: e.theme };
});
openDayDetail({ filteredEvents, title: moment(date).format("D MMM YYYY") });
};

const isToday = (date: moment.Moment) => {
return moment(date).isSame(moment(), "day");
};

const isDifferentMonth = (date: moment.Moment) => {
return moment(date).month() != moment(firstDayOfMonth).month();
};

// const getPrevMonth = (event) => {
const getPrevMonth = () => {
const firstDayOfPrevMonth = moment(firstDayOfMonth).add(-1, "M").startOf("month");
setFirstDayOfMonth(firstDayOfPrevMonth);
setCurrMonth(moment(firstDayOfPrevMonth).format("MMM-yyyy"));
};

// const getCurrentMonth = (event) => {
const getCurrentMonth = () => {
const firstDayOfCurrMonth = moment().startOf("month");
setFirstDayOfMonth(firstDayOfCurrMonth);
setCurrMonth(moment(firstDayOfCurrMonth).format("MMM-yyyy"));
};

// const getNextMonth = (event: any) => {
const getNextMonth = () => {
const firstDayOfNextMonth = moment(firstDayOfMonth).add(1, "M").startOf("month");
setFirstDayOfMonth(firstDayOfNextMonth);
setCurrMonth(moment(firstDayOfNextMonth).format("MMM-yyyy"));
};

return (
<>
<div className="w-full bg-base-100 p-4 rounded-lg">
<div className="flex items-center justify-between">
<div className="flex justify-normal gap-2 sm:gap-4">
<p className="font-semibold text-xl w-48">
{moment(firstDayOfMonth).format("MMMM yyyy").toString()}
<span className="text-xs ml-2 ">Beta</span>
</p>

<button className="btn btn-square btn-sm btn-ghost" onClick={getPrevMonth}>
<ChevronLeftIcon className="w-5 h-5" />
</button>
<button className="btn btn-sm btn-ghost normal-case" onClick={getCurrentMonth}>
Current Month
</button>
<button className="btn btn-square btn-sm btn-ghost" onClick={getNextMonth}>
<ChevronRightIcon className="w-5 h-5" />
</button>
</div>
<div>
<button className="btn btn-sm btn-ghost btn-outline normal-case" onClick={() => addNewEvent}>
Add New Event
</button>
</div>
</div>
<div className="my-4 divider" />
<div className="grid grid-cols-7 gap-6 sm:gap-12 place-items-center">
{weekdays.map((day, key) => {
return (
<div className="text-xs capitalize" key={key}>
{day}
</div>
);
})}
</div>

<div className="grid grid-cols-7 mt-1 place-items-center">
{allDaysInMonth().map((day, idx) => {
return (
<div key={idx} className={colStartClasses[moment(day).day()] + " border border-solid w-full h-28 "}>
<p
// className={`inline-block flex items-center justify-center h-8 w-8 rounded-full mx-1 mt-1 text-sm cursor-pointer hover:bg-base-300 ${
className={`flex items-center justify-center h-8 w-8 rounded-full mx-1 mt-1 text-sm cursor-pointer hover:bg-base-300 ${
isToday(day) && " bg-blue-100 dark:bg-blue-400 dark:hover:bg-base-300 dark:text-white"
} ${isDifferentMonth(day) && " text-slate-400 dark:text-slate-600"}`}
onClick={() => addNewEvent(day)}
>
{" "}
{moment(day).format("D")}
</p>
{getEventsForCurrentDate(day).map((e, k) => {
return (
<p
key={k}
onClick={() => openAllEventsDetail(day, e.theme)}
className={`text-xs px-2 mt-1 truncate ${getTheme(e.theme) || ""}`}
// className={`text-xs px-2 mt-1 truncate ${THEME_BG[e.theme] || ""}`}
>
{e.title}
</p>
);
})}
</div>
);
})}
</div>
</div>
</>
);
};

export default CalendarView;
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// import moment from "moment";
// const moment = require("moment");

export function getTheme(theme: string) {
const THEME_BG = CALENDAR_EVENT_STYLE;
switch (theme) {
case "BLUE":
return THEME_BG.BLUE;
case "GREEN":
return THEME_BG.GREEN;
case "PURPLE":
return THEME_BG.PURPLE;
case "ORANGE":
return THEME_BG.ORANGE;
case "PINK":
return THEME_BG.PINK;
case "MORE":
return THEME_BG.MORE;
}
}

export const CALENDAR_EVENT_STYLE = Object.freeze({
BLUE: "bg-blue-200 dark:bg-blue-600 dark:text-blue-100",
GREEN: "bg-green-200 dark:bg-green-600 dark:text-green-100",
PURPLE: "bg-purple-200 dark:bg-purple-600 dark:text-purple-100",
ORANGE: "bg-orange-200 dark:bg-orange-600 dark:text-orange-100",
PINK: "bg-pink-200 dark:bg-pink-600 dark:text-pink-100",
MORE: "hover:underline cursor-pointer font-medium ",
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { ReactNode } from "react";
import Subtitle from "../Typography/Subtitle";

interface props {
title: string;
children: ReactNode;
topMargin?: string;
TopSideButtons?: JSX.Element;
}

function TitleCard({ title, children, topMargin, TopSideButtons }: props) {
return (
<div className={"card w-full p-6 bg-base-100 shadow-xl " + (topMargin || "mt-6")}>
{/* Title for Card */}
<Subtitle styleClass={TopSideButtons ? "inline-block" : ""}>
{title}

{/* Top side button, show only if present */}
{TopSideButtons && <div className="inline-block float-right">{TopSideButtons}</div>}
</Subtitle>

<div className="divider mt-2"></div>

{/** Card Body */}
<div className="h-full w-full pb-6 bg-base-100">{children}</div>
</div>
);
}

export default TitleCard;
Loading

0 comments on commit 8cca1fd

Please sign in to comment.