Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature - Resources admin dashboard #106

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"presets": ["next/babel"],
"plugins": [["styled-components", { "ssr": true }]]
"presets": ["next/babel"],
"plugins": [["styled-components", { "ssr": true }]]
}
5 changes: 4 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@
"react/jsx-props-no-spreading": "off",
"react/react-in-jsx-scope": "off",
"react/prop-types": "off",
"react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }]
"react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],

"no-unused-vars": "off",
"no-empty-pattern": "off"
},
"settings": {
"react": {
Expand Down
3 changes: 2 additions & 1 deletion .prettierrc.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"printWidth": 85,
"semi": true,
"tabWidth": 2
"tabWidth": 2,
"singleQuote": false
}
9 changes: 6 additions & 3 deletions cache/cache.js

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions components/HorizontalSeparator.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import styled from "styled-components";

export default styled.hr`
display: block;
height: 1px;
border: 0;
border-top: 1px solid black;
margin: 1em 0;
padding: 0;
margin: 3rem 0;
width: 100%;
`;
1 change: 0 additions & 1 deletion components/ImageInfoLayout.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { media } from "../utils";
import { baseTheme } from "../theme";

/**
*
* Student Resource Component
* @prop {string} titleText - resource title
* @prop {string} descriptionText - resource description
Expand Down
3 changes: 2 additions & 1 deletion components/Input.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ export const Input = ({
/>
);

const SInput = styled.input`
// used when ref needs to be passed as prop
export const SInput = styled.input`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing this doesn't work with just the Input? I think if you want a ref as a prop you can make it on the original element and pass it as optional?

${({ theme }) => `
font-size: ${theme.size.default};
transition: ${theme.transitions.cubicBezier};
Expand Down
44 changes: 44 additions & 0 deletions components/MDEditor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { useCallback, useState } from "react";
import "easymde/dist/easymde.min.css";

export default function MDEditor({ initialValue, mdeRecordSetter }) {
const [mde, setMde] = useState(null);

const injectEditor = useCallback(
async (element) => {
if (element == null) return;

// create and add editor
element.innerHTML = "";
const editor = document.createElement("textarea");
element.append(editor);

// dynamic import to to support SSR
const EasyMDE = (await import("easymde")).default;
const easyMDE = new EasyMDE({
element: editor,
showIcons: [
"strikethrough",
"code",
"table",
"horizontal-rule",
"undo",
"redo",
],
initialValue,
placeholder: "Start typing...",
});

setMde(easyMDE);
mdeRecordSetter(easyMDE);
},
[initialValue, mdeRecordSetter]
);

return (
<div>
<p>Remember you can preview with the toolbar fourth column</p>
<div ref={injectEditor} />
</div>
);
}
87 changes: 87 additions & 0 deletions components/resourcesAdmin/BlogTemplateRow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import styled from "styled-components";
import { Button } from "../Button";
import { Input } from "../Input";
import { latestTimeAgo } from "../../utils/methods/time";

export default function BlogTemplateRow({ blog }) {
return (
<BlogTemplateRowWrapper>
<BlogInformationContainer>
<Input disabled type="checkbox" />

<ResourceProjectTitle href={`/resources/admin/project/blog/${blog.id}`}>
{blog.name}
</ResourceProjectTitle>
<ModifiedTimeStamp>
Last opened {latestTimeAgo(blog.lastSaved)} ago
</ModifiedTimeStamp>
</BlogInformationContainer>

<BlogControlContainer>
<Button onClick={() => alert("Coming soon")}>Copy</Button>
<Button onClick={() => alert("Coming soon")}>Archive</Button>
<Button onClick={() => alert("Coming soon")}>Trash</Button>
</BlogControlContainer>
</BlogTemplateRowWrapper>
);
}

const BlogTemplateRowWrapper = styled.section`
display: flex;
justify-content: space-between;

min-height: 2vh;
background-color: whitesmoke;
margin-bottom: 0.1rem;
padding: 0.3rem;
color: black;
border-left: 1px solid white;
border-right: 1px solid white;
overflow-x: auto;

&:last-child {
margin-bottom: 0;
}
`;

const BlogInformationContainer = styled.section`
display: flex;
align-items: center;

* {
margin-right: 1.2rem;

&:last-child {
margin-right: 0;
}
}
`;

export const ModifiedTimeStamp = styled.div`
font-size: 1.1rem;
`;

const BlogControlContainer = styled.section`
display: flex;
align-items: center;

* {
margin-right: 0.2rem;

&:last-child {
margin-right: 0;
}
}
`;

const ResourceProjectTitle = styled.a`
font-size: 1.2rem;
margin-right: 4rem;
font-weight: bold;
color: #3e70bb;

&:hover {
cursor: pointer;
text-decoration: underline;
}
`;
7 changes: 7 additions & 0 deletions components/resourcesAdmin/JobOpportunityRow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function JobOpportunityRow() {
return (
<div>
<div>JobOpportunityRow</div>
</div>
);
}
34 changes: 34 additions & 0 deletions contexts/AdminProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import axios from "axios";
import { createContext, useContext, useEffect, useState } from "react";

const Context = createContext();

export function AdminProvider({ children }) {
const [adminUser, setAdminUser] = useState(null);

// attempt to log user in right after page load
useEffect(() => {
async function f() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a little weird naming? if this is temporary probably fine

const res = await axios.get("/api/admin/instant-log-in");
// eslint-disable-next-line no-console
console.log(res.data);

if (res.data.loggedIn) {
const { user } = res.data;

setAdminUser(user);
}
}
f();
}, []);

return (
<Context.Provider value={{ adminUser, setAdminUser }}>
{children}
</Context.Provider>
);
}

export function useAdmin() {
return useContext(Context);
}
64 changes: 64 additions & 0 deletions contexts/ResourcesAdminProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { createContext, useContext, useEffect } from "react";
import { LOCAL_STORAGE_KEYS } from "../utils/constants";
import { simpleUUID } from "../utils/methods/general";
import useLocalStorage from "../utils/hooks/useLocalStorage";

const Context = createContext();

const defaultBlogProjects = Array.from({ length: 6 }, (_v, i) => {
return {
id: simpleUUID(7),
name: `blog-project-${i + 1}`,
lastSaved: Date.now(),

// blog contents
title: "",
date: "",
releaseBatch: "",
authors: [],
tags: [],
body: "",
};
});
const defaultJobProjects = Array.from({ length: 3 }, (v, i) => {
return {
id: simpleUUID(7),
name: `job-opportunity-project-${i + 1}`,
lastSaved: Date.now(),
};
});

export function ResourcesAdminProvider({ children }) {
/**
* @TODO : remove entirely, only store important project info
* (title, last modified) here.
* then on each route, store each blog/resource independently
* in LS
* This so there isnt 1 huge LS value
*/
const [blogTemplates, setBlogTemplates] = useLocalStorage(
LOCAL_STORAGE_KEYS.AdminBlogProjects,
defaultBlogProjects
);
const [jobOpportunityTemplates, setJobOpportunityTemplates] = useLocalStorage(
LOCAL_STORAGE_KEYS.AdminJobOppProject,
defaultJobProjects
);

return (
<Context.Provider
value={{
blogTemplates,
setBlogTemplates,
jobOpportunityTemplates,
setJobOpportunityTemplates,
}}
>
{children}
</Context.Provider>
);
}

export function useResourcesAdmin() {
return useContext(Context);
}
12 changes: 8 additions & 4 deletions lib/cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,14 @@ function getOpportunities() {
}

const cache = `
export const posts = ${getProjects()};
export const authors = ${getAuthors()};
export const opportunities = ${getOpportunities()};
`;
const postsWithoutId = ${getProjects()};
export const posts = postsWithoutId.map(post => {
return {...post, id: Math.random()}
})

export const authors = ${getAuthors()};
export const opportunities = ${getOpportunities()};
`.trim();

try {
fs.readdirSync("cache");
Expand Down
25 changes: 25 additions & 0 deletions lib/envs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// eslint-disable-next-line no-undef
require("dotenv").config();

// eslint-disable-next-line no-undef
const { JWT_SECRET_KEY, GITHUB_KEY, REPO_OWNER, MAIN_REPO } = process.env;

const ENVS = {
JWT_SECRET_KEY,
GITHUB_KEY,
};
// console.log(ENVS);

for (const [k, val] of Object.entries(ENVS)) {
if (val == null) throw new Error(`undef. env var: ${k}, val: ${val}`);
}

ENVS["REPO_OWNER"] = REPO_OWNER;
ENVS["MAIN_REPO"] = MAIN_REPO;

for (const [k, val] of Object.entries(ENVS)) {
// eslint-disable-next-line no-console
if (val == null) console.warn(`undef. env var: ${k}, val: ${val}`);
}

export { ENVS };
7 changes: 7 additions & 0 deletions lib/octokit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Octokit } from "octokit";
import { ENVS } from "./envs";

// console.log(ENVS.GITHUB_FGPAT);
export default new Octokit({
auth: ENVS.GITHUB_KEY,
});
19 changes: 19 additions & 0 deletions middlewares/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { verify } from "jsonwebtoken";
import { ENVS } from "../lib/envs";
import { COOKIE_KEYS } from "../utils/constants";

export function validateToken(req, res) {
const { [COOKIE_KEYS.AccessToken]: accessToken } = req.cookies;

if (accessToken != null) {
try {
// eslint-disable-next-line no-undef
const decodedToken = verify(accessToken, ENVS.JWT_SECRET_KEY);

req.user = decodedToken.user;
} catch (err) {
// console.log("thee", err);
// console.log("err done");
}
}
}
Loading