Skip to content

Commit

Permalink
Merge pull request #63 from CodendDev/final-touch
Browse files Browse the repository at this point in the history
Final touch
  • Loading branch information
maksimowiczm authored Jan 19, 2024
2 parents 757f957 + 9f2d68e commit 7eed147
Show file tree
Hide file tree
Showing 39 changed files with 481 additions and 102 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/main-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ jobs:
name: Push image on Azure registry
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Login to Azure registry
uses: docker/login-action@v3
with:
Expand Down
2 changes: 0 additions & 2 deletions LICENSE.avatars
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
https://github.com/alohe/avatars

MIT License

Copyright (c) 2022 Alohe
Expand Down
399 changes: 396 additions & 3 deletions LICENSE.background

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,7 @@ npm run dev
Visit http://127.0.0.1:3000 in your browser.

You have also the access to api server at http://127.0.0.1:8080

**Credits**
* avatars - https://github.com/alohe/avatars
* background - https://www.svgbackgrounds.com/
22 changes: 15 additions & 7 deletions app/api/axios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,29 @@ import * as https from "https";
import * as process from "process";

export function getAxiosInstance(token?: string): AxiosInstance {
const DEVELOPMENT = process.env.SETTINGS === "DEVELOPMENT";
const url = process.env.API_SERVER;
if (url === undefined) {
if (!url) {
throw new Error("API server address is undefined.");
}

const httpSecureRegex = new RegExp(/^https:\/\//);
if (!DEVELOPMENT && !httpSecureRegex.test(url)) {
throw new Error(
"API server address must use SSL protocol. " +
"If you want to use http set environmental variable SETTINGS=DEVELOPMENT"
);
}

const authorizationHeaders = token
? {
authorization: token,
}
: {};

const httpsAgent =
process.env.SETTINGS === "DEVELOPMENT"
? new https.Agent({ rejectUnauthorized: false })
: undefined;
const httpsAgent = DEVELOPMENT
? new https.Agent({ rejectUnauthorized: false })
: undefined;

return axios.create({
baseURL: url,
Expand All @@ -46,12 +54,12 @@ export function getApiErrorsFromError(
return undefined;
}

if (err.response === undefined) {
if (!err.response) {
return undefined;
}

const apiErrors = err.response.data.errors;
if (apiErrors === undefined) {
if (!apiErrors) {
return undefined;
}

Expand Down
2 changes: 1 addition & 1 deletion app/components/board/ProjectBoard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { ProjectTaskStatusContainerLoading } from "~/components/board/ProjectTas
import { Button, Tooltip, useDisclosure } from "@nextui-org/react";
import { AiOutlinePlus } from "react-icons/ai/index.js";
import { EditStatusModal } from "~/components/projectTaskStatus/EditStatusModal";
import { StatusesContext } from "~/routes/project.$projectId/route";
import { StatusesContext } from "~/routes/project.$projectId";
import { ProjectBoardDnd } from "~/components/board/ProjectBoardDnd";

interface SelectedProjectBoardTaskContextType {
Expand Down
2 changes: 1 addition & 1 deletion app/components/board/ProjectBoardDnd.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import React, {
useState,
} from "react";
import { BoardQueryContext } from "~/components/board/ProjectBoardFilter";
import { StatusesContext } from "~/routes/project.$projectId/route";
import { StatusesContext } from "~/routes/project.$projectId";

import {
DragDropContext,
Expand Down
5 changes: 2 additions & 3 deletions app/components/members/ProjectMembersList.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import React, { useState } from "react";

import { UserDetails, UserRole } from "~/api/types/baseEntitiesTypes";
import type { UserDetails, UserRole } from "~/api/types/baseEntitiesTypes";
import { Button, Input, useDisclosure, User } from "@nextui-org/react";
import { IoMdRemoveCircleOutline } from "react-icons/io/index.js";
import { IoMdRemoveCircleOutline, IoMdAdd } from "react-icons/io/index.js";
import DeleteModal from "~/components/shared/modals/DeleteModal";
import { useFetcher } from "@remix-run/react";
import { IoMdAdd } from "react-icons/io/index.js";

interface ProjectMembersListProps {
members: UserDetails[];
Expand Down
6 changes: 2 additions & 4 deletions app/components/projectNavigation/ProjectNavigationList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@ import {
} from "@nextui-org/react";
import React, { useContext, useState } from "react";
import { Link, useLocation, useNavigate, useSubmit } from "@remix-run/react";
import { AiOutlineFileSearch } from "react-icons/ai/index.js";
import { AiOutlineFileSearch, AiOutlinePlus } from "react-icons/ai/index.js";
import { ProjectNavigationBarContext } from "~/components/projectNavigation/ProjectNavigationBar";
import { FaStar, FaRegStar } from "react-icons/fa/index.js";
import { AiOutlinePlus } from "react-icons/ai/index.js";
import { FaRegEdit } from "react-icons/fa/index.js";
import { FaStar, FaRegStar, FaRegEdit } from "react-icons/fa/index.js";
import ClickableDiv from "~/components/utils/ClickableDiv";

type ProjectNavigationListProps = {
Expand Down
5 changes: 1 addition & 4 deletions app/components/taskSidebar/TaskDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@ import {
} from "~/components/taskSidebar/TaskDetailsSelectInputs";
import DeleteModal from "~/components/shared/modals/DeleteModal";
import { emptyTask } from "~/api/types/projectTaskTypes";
import {
MembersContext,
StatusesContext,
} from "~/routes/project.$projectId/route";
import { MembersContext, StatusesContext } from "~/routes/project.$projectId";
import { IoIosCloseCircle } from "react-icons/io/index.js";

interface TaskDetailsProps {
Expand Down
21 changes: 0 additions & 21 deletions app/components/utils/DndProviderWrapper.tsx

This file was deleted.

2 changes: 0 additions & 2 deletions app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
Outlet,
Scripts,
ScrollRestoration,
useRouteError,
} from "@remix-run/react";
import styles from "./tailwind.css";
import { NextUIProvider } from "@nextui-org/react";
Expand All @@ -15,7 +14,6 @@ import CustomError from "~/components/errors/CustomError";
export const links: LinksFunction = () => [{ rel: "stylesheet", href: styles }];

export function ErrorBoundary() {
const error = useRouteError();
return (
<html className="m-0 h-full w-full">
<head>
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
} from "~/routes/api/projectTask/projectTaskGetDeletePutAction";
import React from "react";
import { ActionFunctionArgs, redirect } from "@remix-run/node";
import BoardTaskSidebar from "~/routes/project.$projectId.board.$sprintId.$projectTaskId.$taskType/route";
import BoardTaskSidebar from "~/routes/project.$projectId.board.$sprintId.$projectTaskId.$taskType";

export const action = async (args: ActionFunctionArgs) => {
const response = await ProjectTaskAction(args);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import getToken from "~/actions/getToken";
import { getBacklog, getBoard } from "~/api/methods/project";
import { defer, redirect } from "@remix-run/node";
import { jwtDecode } from "jwt-decode";
import SelectedSprintBoardPage from "~/routes/project.$projectId.board.$sprintId/route";
import SelectedSprintBoardPage from "~/routes/project.$projectId.board.$sprintId";

export const loader = async ({ params, request }: LoaderFunctionArgs) => {
const token = await getToken(request);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState } from "react";
import { loader as BoardLoader } from "~/routes/project.$projectId.board/route";
import { loader as BoardLoader } from "~/routes/project.$projectId.board";
import { Outlet, useLoaderData, useLocation } from "@remix-run/react";
import {
BoardQueryContext,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { Outlet, useLoaderData } from "@remix-run/react";
import { defer, LoaderFunctionArgs, redirect } from "@remix-run/node";
import type { LoaderFunctionArgs } from "@remix-run/node";
import { json, redirect } from "@remix-run/node";
import getToken from "~/actions/getToken";
import { getMembers, getProject } from "~/api/methods/project";
import { getProjectTaskStatuses } from "~/api/methods/projectTaskStauses";
import { createContext } from "react";
import { ProjectTaskStatus, UserDetails } from "~/api/types/baseEntitiesTypes";
import type {
ProjectTaskStatus,
UserDetails,
} from "~/api/types/baseEntitiesTypes";

export const loader = async ({ params, request }: LoaderFunctionArgs) => {
const token = await getToken(request);
Expand All @@ -13,25 +17,24 @@ export const loader = async ({ params, request }: LoaderFunctionArgs) => {
return redirect("/user/login");
}

const projectTaskStatusesPromise = getProjectTaskStatuses({
const project = await getProject({ projectId, token });
if (!project) {
return redirect("/project");
}

const projectTaskStatuses = (await getProjectTaskStatuses({
projectId,
token,
});
const projectMembersPromise = getMembers({ projectId, token });
const [projectTaskStatuses, projectMembers] = await Promise.all([
projectTaskStatusesPromise,
projectMembersPromise,
]);
const project = await getProject({ projectId, token });
}))!;
const projectMembers = (await getMembers({ projectId, token }))!;

return defer({ projectTaskStatuses, projectMembers, project });
return json({ projectTaskStatuses, projectMembers, project });
};

export const StatusesContext = createContext<ProjectTaskStatus[]>([]);
export const MembersContext = createContext<UserDetails[]>([]);

export default function ProjectPage() {
// @ts-ignore
const { projectTaskStatuses, projectMembers, project } =
useLoaderData<typeof loader>();

Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useContext } from "react";
import { MembersContext } from "~/routes/project.$projectId/route";
import { MembersContext } from "~/routes/project.$projectId";
import { useOutletContext } from "react-router";
import { ActionFunctionArgs, redirect } from "@remix-run/node";
import getToken from "~/actions/getToken";
Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AccountDetails } from "~/components/accountDetails/AccountDetails";
import { useContext } from "react";
import { UserDetailsContext } from "~/routes/project/route";
import { UserDetailsContext } from "~/routes/project";

export default function UserAccountDetailsPage() {
const { userDetails } = useContext(UserDetailsContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
UpdateUserDetails,
} from "~/api/methods/user";
import { useContext } from "react";
import { UserDetailsContext } from "~/routes/project/route";
import { UserDetailsContext } from "~/routes/project";

export const action = async ({ request }: LoaderFunctionArgs) => {
const token = await getToken(request);
Expand All @@ -34,9 +34,9 @@ export const action = async ({ request }: LoaderFunctionArgs) => {
const returnUrl = formData.returnUrl.toString();

if (notifications) {
enableAllUserNotifications({ token });
await enableAllUserNotifications({ token });
} else {
disableAllUserNotifications({ token });
await disableAllUserNotifications({ token });
}

return redirect(returnUrl);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import ProjectPage, {
action as BarAction,
loader as BarLoader,
} from "~/routes/project/route";
} from "~/routes/project";

export const action = BarAction;

Expand Down
47 changes: 23 additions & 24 deletions app/routes/user.login/route.tsx → app/routes/user.login.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useState } from "react";
import { Button, Checkbox, Input, Link, Spacer } from "@nextui-org/react";
import { useActionData, useNavigate } from "@remix-run/react";
import React, { useEffect, useState } from "react";
import { Button, Checkbox, Input, Spacer } from "@nextui-org/react";
import { useFetcher } from "@remix-run/react";
import type { ActionFunctionArgs } from "@remix-run/node";
import type { handleLoginRequest } from "~/actions/handleLogin";
import handleLogin from "~/actions/handleLogin";
Expand All @@ -14,7 +14,7 @@ export const action = async ({ request }: ActionFunctionArgs) => {
const formData = await request.formData();

const data = Object.fromEntries(formData);
if (data.email === undefined || data.password === undefined) {
if (!data.email || !data.password) {
return errorHandler([]);
}

Expand All @@ -28,41 +28,38 @@ export const action = async ({ request }: ActionFunctionArgs) => {
return handleLogin(loginRequest);
};

export default function Login() {
const navigate = useNavigate();
const handleForget = (e: React.MouseEvent) => {
e.preventDefault();
navigate("/user/forget");
};
interface LoginErrors {
errors?: ApiErrorResponse[];
}

const actionData = useActionData<typeof action>() as unknown as {
errors?: ApiErrorResponse[];
};
const error = actionData?.errors?.hasError(
ApiErrors.LoginErrors.InvalidEmailOrPassword
);
export default function Login() {
const fetcher = useFetcher();
const data = fetcher.data as LoginErrors | undefined;
const error =
data?.errors?.hasError(ApiErrors.LoginErrors.InvalidEmailOrPassword) ??
false;

const [checked, setChecked] = useState<boolean>(false);
return (
<form className="max-w-lg grow px-5" method="post">
<fetcher.Form className="max-w-lg grow px-5" method="post">
<Input
color={error ? "danger" : "default"}
variant="faded"
size="lg"
type="email"
label="Email"
name="email"
isRequired={true}
isInvalid={error}
/>
<Spacer y={5} />
<Input
color={error ? "danger" : "default"}
variant="faded"
size="lg"
type="password"
label="Password"
name="password"
isRequired={true}
isInvalid={error}
errorMessage={error && "Invalid email or password"}
/>
<div className="my-5 flex justify-between align-middle">
Expand All @@ -74,13 +71,15 @@ export default function Login() {
Remember me
</Checkbox>
<input type="hidden" name="remember" value={checked.toString()} />
<Link size="lg" href="/user/forget" onClick={handleForget}>
Forgot password
</Link>
</div>
<Button size="lg" color="primary" type="submit">
<Button
size="lg"
color="primary"
type="submit"
isLoading={fetcher.state !== "idle"}
>
Login
</Button>
</form>
</fetcher.Form>
);
}
File renamed without changes.
File renamed without changes.
Loading

0 comments on commit 7eed147

Please sign in to comment.