Before building any page in the dashboard, we can start to build an index page first to be used as the foundation of the dashboard upon which other pages will be built. We can design this index page to have header, sidebar, and footer and a main part to include different page.
As in React, in Nano JSX, to combine different components together, we need to pass children components as props for parent components and render all components in the last child.
// controllers/users/post.js
import post from "../../views/users/post.jsx";
class Post{
async getPage(req, res){
const config = req.mysetting();
config.page_title = "Post Page";
config.route = "/users/post";
config.username = (await req.mysession.get("user")).title;
const html = await post(config);
res.send(html);
}
}
export default new Post();
// views/users/post.jsx
/** @jsx h */
import { h, renderSSR } from "../../deps.ts";
import Index from "./index.jsx";
function PostJsx(props){
return(
<section class="Post">
Post
</section>
)
}
export default function Post(props){
props.pageInner = PostJsx;
const html = renderSSR(<Index data={ props } />);
return `<!DOCTYPE html>${ html }`;
}
// views/users/index.jsx
/** @jsx h */
import { h } from "../../deps.ts";
import Base from "../base.jsx";
function IndexJsx(props){
const Page = props.data.pageInner;
return(
<section class="Index">
<link rel="stylesheet" href="/css/users/index.css" />
<header>
<div class="inner region">
<div class="title">{props.data.page_title}</div>
<form action="/admin/search" method="post">
<select name="admin_search">
<option>All</option>
<option>Category</option>
</select>
<input type="text" name="admin_q" required placeholder="Search" />
<input type="submit" value="Search" />
</form>
<div class="logout"><span>{props.data.username}</span> | <a href="/">Home</a> | <a href="/logout">Logout</a></div>
</div>
</header>
<div class="main region">
<div class="sidebar">
<div class="inner">
<a href="/admin/post"><img src="/images/post.png" /></a>
<a href="/admin/post">Post</a>
<a href="/admin/category"><img src="/images/category.png" /></a>
<a href="/admin/category">Category</a>
<a href="/admin/upload"><img src="/images/upload.png" /></a>
<a href="/admin/upload">Upload</a>
<a href="/admin/user"><img src="/images/users.png" /></a>
<a href="/admin/user">User</a>
<a href="/admin/setting"><img src="/images/setting.png" /></a>
<a href="/admin/setting">Setting</a>
</div>
</div>
<div class="content">
<Page data={props.data} />
</div>
</div>
<div class="footer region">
<div class="info">Total amount of items: {props.data.count}</div>
<ul class="list">
</ul>
<div class="pagination" dangerouslySetInnerHTML={{__html: `
<img onclick="paginate('${props.data.route}')" src="/images/load-more.png" />
`}}/>
<div class="credit">© <a href="https://khmerweb.vercel.app/">Khmer Web 2022</a></div>
</div>
</section>
)
}
export default function Index(props){
props.data.page = IndexJsx;
return(<Base data={ props.data } />);
}
// views/base.jsx
/** @jsx h */
import { h } from "../deps.ts";
export default function Base(props){
const Page = props.data.page;
return(
<html>
<head>
<meta charSet="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>{props.data.site_title} | {props.data.page_title}</title>
<link href="/images/siteLogo.png" rel="icon" />
<link href="/css/style.css" rel="stylesheet" />
<link href="/fonts/setup.css" rel="stylesheet" />
<script src="/js/jquery.js"></script>
</head>
<body>
<Page data={props.data} />
</body>
</html>
)
}
/* static/styles/admin/index.css */
.Index header{
background: var(--background-dark);
border-bottom: 7px solid white;
margin-bottom: 20px;
}
.Index header .inner{
padding: 5px 0;
}
.Index header .inner .title{
font: 30px/1.5 StardosStencil, Limonf3;
color: orange;
}
.Index header .inner{
display: grid;
grid-template-columns: 25% auto 25%;
align-items: center;
}
.Index header .inner form{
display: grid;
grid-template-columns: 20% auto 20%;
}
.Index header .inner form input,
.Index header .inner form select{
font: var(--body-font);
padding: 2px 5px;
}
.Index header .inner .logout{
text-align: right;
color: white;
}
.Index header .inner .logout a{
color: white;
}
.Index .main{
display: grid;
grid-template-columns: calc(25% - 10px) 75%;
grid-gap: 10px;
}
.Index .main .sidebar{
background: rgb(182, 182, 182);
padding: 20px;
}
.Index .main .sidebar .inner{
display: grid;
grid-template-columns: 25% auto;
grid-gap: 20px 10px;
align-items: center;
}
.Index .main .sidebar .inner img{
width: 100%;
float: left;
}
.Index .main .sidebar .inner a{
font: 18px/1.5 StardosStencil, Bayon;
}
.Index .footer .info{
margin-top: 10px;
background: rgb(182, 182, 182);
text-align: center;
padding: 10px;
color: rgb(88, 88, 88);;
}
.Index .footer ul{
list-style-type: none;
display: grid;
grid-template-columns: calc(50% - 5px) calc(50% - 5px);
grid-gap: 10px;
padding: 10px 0;
}
.Index .footer ul li{
display: grid;
grid-template-columns: 25% 60% 15%;
background: rgb(182, 182, 182);
align-items: center;
}
.Index .footer ul li .thumb{
display: block;
position: relative;
padding-top: 56.25%;
}
.Index .footer ul li .thumb img{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
float: left;
}
.Index .footer ul li .thumb .play-icon{
position: absolute;
width: 25%;
height: auto;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
}
.Index .footer ul li .title{
padding: 5px 10px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.Index .footer ul li .title a{
font: 17px/1.5 StardosStencil, Limonf3;
}
.Index .footer ul li .edit img{
width: 35px;
margin-right: 5px;
float: left;
visibility: hidden;
}
.Index .footer ul li:hover .edit img{
visibility: visible;
}
.Index .footer .pagination{
text-align: center;
background: rgb(182, 182, 182);
padding: 5px 0 0;
}
.Index .footer .pagination img:hover{
cursor: pointer;
opacity: .7;
}
.Index .footer .credit{
text-align: center;
padding: 30px 0;
}
GitHub: https://github.com/Sokhavuth/opine-job
Deno Deploy: https://khmerweb-job.deno.dev/users/post