Skip to content

Commit

Permalink
🏗️ Add frontend boilerplate
Browse files Browse the repository at this point in the history
  • Loading branch information
svenvandescheur committed May 7, 2024
1 parent bec3bb1 commit 9a121af
Show file tree
Hide file tree
Showing 26 changed files with 484 additions and 4 deletions.
2 changes: 2 additions & 0 deletions backend/src/openarchiefbeheer/conf/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
#

INSTALLED_APPS = [
"corsheaders",
"django.contrib.auth",
"django.contrib.sessions",
"django.contrib.contenttypes",
Expand Down Expand Up @@ -128,6 +129,7 @@
]

MIDDLEWARE = [
"corsheaders.middleware.CorsMiddleware",
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
# 'django.middleware.locale.LocaleMiddleware',
Expand Down
8 changes: 8 additions & 0 deletions frontend/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
28 changes: 28 additions & 0 deletions frontend/.storybook/decorators.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { StoryContext, StoryFn } from "@storybook/react";
import * as React from "react";
import { RouterProvider, createBrowserRouter } from "react-router-dom";

import App from "../src/App";

/**
* Decorators providing React Router integration (RouterProvider), optionally: when using this decorator a parameter
* "loader" can be specified providing React Router `LoaderFunction` for this `StoryFn`.
*/
export const ReactRouterDecorator = (
Story: StoryFn,
{ parameters }: StoryContext,
) => {
const router = createBrowserRouter([
{
path: "*",
element: <Story />,
loader: parameters.loader,
},
]);

return (
<App>
<RouterProvider router={router} />
</App>
);
};
16 changes: 16 additions & 0 deletions frontend/.storybook/preview-body.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<style>
html,
body,
.sb-show-main,
#storybook-root {
height: 100%;
}

body {
margin: 0;
}

.sb-show-main {
padding: 0!important;
}
</style>
120 changes: 120 additions & 0 deletions frontend/bin/create_page.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#!/bin/bash

PAGES_DIR="src/pages"
INDEX_FILE="$PAGES_DIR/index.ts"

page_name=$1
page_name_lowercase=$(echo "$page_name" | tr '[:upper:]' '[:lower:]')
capitalized_page_name=$(echo "$page_name" | awk '{print toupper(substr($0, 1, 1)) tolower(substr($0, 2))}')
page_dir="$PAGES_DIR/$page_name_lowercase"

# Function to check if a directory exists
function directory_exists() {
[ -d $1 ]
}

# Function to create a directory if it doesn't exist
function create_directory() {
if ! directory_exists $1; then
mkdir -p $1
fi
}

# Function to create the index.ts file
function create_index_file() {
echo "export * from \"./$capitalized_page_name\";" > "$2/index.ts"
}

# Function to create the CSS file
function create_css_file() {
echo ".${capitalized_page_name}Page {" > "$2/$capitalized_page_name.css"
echo " /* Rules here. */" >> "$2/$capitalized_page_name.css"
echo "}" >> "$2/$capitalized_page_name.css"
}

# Function to create the stories.tsx file
function create_stories_file() {
cat > "$2/$capitalized_page_name.stories.tsx" <<EOF
import type { Meta, StoryObj } from "@storybook/react";
import { ${capitalized_page_name}Page } from "./$capitalized_page_name";
const meta: Meta<typeof ${capitalized_page_name}Page> = {
title: "Pages/${capitalized_page_name}",
component: ${capitalized_page_name}Page,
};
export default meta;
type Story = StoryObj<typeof meta>;
export const ${page_name_lowercase}Page: Story = {
args: {
children: "The quick brown fox jumps over the lazy dog.",
},
};
EOF
}

# Function to create the page file
function create_page_file() {
cat > "$2/$capitalized_page_name.tsx" <<EOF
import React from "react";
import "./$capitalized_page_name.css";
export type ${capitalized_page_name}Props = React.ComponentProps<"main"> & {
// Props here.
};
/**
* ${capitalized_page_name} page
*/
export function ${capitalized_page_name}Page({ children, ...props }: ${capitalized_page_name}Props) {
return (
<main className="${capitalized_page_name}Page" {...props}>
{children}
</main>
);
}
EOF
}

# Function to update the index.ts file in pages
function update_index_file() {
echo "// Auto-generated file. Do not modify manually." > "$INDEX_FILE"
for page_dir in "$PAGES_DIR"/*/; do
page_name=$(basename "$page_dir")
echo "export * from \"./$page_name\";" >> "$INDEX_FILE"
done
}

# Main script

# Check if $PAGES_DIR directory exists, if not create it
create_directory $PAGES_DIR

# Check if a page name is provided
if [ -z "$1" ]; then
echo "Error: Please provide a page name."
exit 1
fi

# Check if page already exists
if directory_exists $page_dir; then
echo "Error: page '$capitalized_page_name' already exists."
exit 1
fi

# Create page directory
create_directory $page_dir

# Create individual files
create_index_file $capitalized_page_name $page_dir
create_css_file $capitalized_page_name $page_dir
create_stories_file $capitalized_page_name $page_dir
create_page_file $capitalized_page_name $page_dir

# Update pages/index.ts file
update_index_file

echo "page '$capitalized_page_name' created successfully."
5 changes: 3 additions & 2 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@maykin-ui/admin-ui": "github:maykinmedia/admin-ui",
"@maykin-ui/admin-ui": "file:../../maykin-ui/maykin-ui-admin-ui-0.0.5.tgz",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.App {
height: 100%;
}
6 changes: 6 additions & 0 deletions frontend/src/index.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
html,
body,
#root {
height: 100%;
}

body {
margin: 0;
}
9 changes: 8 additions & 1 deletion frontend/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,18 @@ import { RouterProvider, createBrowserRouter } from "react-router-dom";

import App from "./App";
import "./index.css";
import { LandingPage, LoginPage, landingLoader, loginAction } from "./pages";

const router = createBrowserRouter([
{
path: "/",
element: <div>Hello world!</div>,
element: <LandingPage />,
loader: landingLoader,
},
{
path: "/sign-in",
element: <LoginPage />,
action: loginAction,
},
]);

Expand Down
13 changes: 13 additions & 0 deletions frontend/src/lib/api/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { request } from "./request";

/**
* API call for authentication.
* @param username
* @param password
*/
export async function login(username: string, password: string) {
return request("POST", "/auth/login/", {
username,
password,
});
}
14 changes: 14 additions & 0 deletions frontend/src/lib/api/destructionLists.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { request } from "./request";

export type DestructionList = {
// TODO
};

/**
* List destruction lists.
*/
export async function listDestructionLists() {
const response = await request("GET", "/destruction-lists/");
const promise: Promise<DestructionList[]> = response.json();
return promise;
}
23 changes: 23 additions & 0 deletions frontend/src/lib/api/getContextData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { redirect } from "react-router-dom";

/**
* Utility function acting as loader for async api function `fn`. Redirects to sign in page if request fails with status
* 403.
* @param fn
* @param args
*/
export function getContextData(
fn: (...args: unknown[]) => Promise<unknown>,
...args: unknown[]
) {
return async () => {
try {
return await fn(...args);
} catch (e: any) {
if (e?.status === 403) {
return redirect("/sign-in");
}
return e;
}
};
}
40 changes: 40 additions & 0 deletions frontend/src/lib/api/request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { getCookie } from "../cookie/cookie";

/** The base url for all API requests. */
export const BASE_URL = "http://localhost:8000/api/v1";

/**
* Makes an actual fetch request to the API, should be used by all other API implementations.
* @param method
* @param endpoint
* @param data
* @param headers
*/
export async function request(
method: "GET" | "POST",
endpoint: string,
data?: Record<string, unknown>,
headers?: Record<string, string>,
) {
const csrfToken = getCookie("csrftoken");
const url = BASE_URL + endpoint;
const abortController = new AbortController();

const response = await fetch(url, {
credentials: "include",
body: data ? JSON.stringify(data) : undefined,
headers: {
"Content-Type": "application/json",
"X-CSRFToken": csrfToken || "",
...headers,
},
method: method,
signal: abortController.signal,
});

if (response.ok) {
return response;
} else {
throw response;
}
}
26 changes: 26 additions & 0 deletions frontend/src/lib/api/reviewers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { request } from "./request";

export type User = {
pk: number;
username: string;
firstName: string;
lastName: string;
email: string;
role: Role;
};

export type Role = {
name: string;
canStartDestruction: boolean;
canReviewDestruction: boolean;
canViewCaseDetails: boolean;
};

/**
* List all the users that have the permission to review destruction lists.
*/
export async function listReviewers() {
const response = await request("GET", "/reviewers/");
const promise: Promise<User[]> = response.json();
return promise;
}
15 changes: 15 additions & 0 deletions frontend/src/lib/api/zaken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { request } from "./request";

export type Zaak = {
// TODO
};

/**
* Retrieve zaken using the configured ZRC service. For information over the query parameters accepted and the schema of
* the response, look at the '/zaken/api/v1/zaken' list endpoint of Open Zaak.
*/
export async function listZaken() {
const response = await request("GET", "/zaken/");
const promise: Promise<Zaak[]> = response.json();
return promise;
}
Loading

0 comments on commit 9a121af

Please sign in to comment.