Unify admin list pages

This commit is contained in:
Aarni Halinen
2020-11-24 21:46:12 +02:00
parent 0f432e1b9f
commit c025c59f4e
16 changed files with 218 additions and 427 deletions
+7 -1
View File
@@ -23,7 +23,13 @@ export interface Event {
signupForm: SignupForm[];
}
export async function getEvents(options: any = {}): Promise<Event[]> {
interface Options {
onlyNonPast?: boolean;
limit?: number;
auth?: boolean;
}
export async function getEvents(options: Options = {}): Promise<Event[]> {
const { onlyNonPast, limit, auth } = options;
try {
const params = {
+7 -2
View File
@@ -20,10 +20,15 @@ export interface Post {
autohide_enabled: boolean;
}
interface Options {
auth?: boolean;
}
export async function getFeed(): Promise<Post[]> {
export async function getFeed(options: Options = {}): Promise<Post[]> {
const { auth } = options;
const headers = auth ? { "Authorization": getAuthHeader() } : null;
try {
const resp = await axios.get(url);
const resp = await axios.get(url, { headers });
return resp.data["results"];
} catch (err) {
console.error(err);
-20
View File
@@ -1,20 +0,0 @@
@import "../../assets/scss/globals";
.admin-event-page {
table {
border-collapse: collapse;
}
table,
th,
td {
border: 1px solid color(white1);
padding: 0.5rem;
a {
color: color(orange1);
text-decoration: underline;
}
}
}
+50 -105
View File
@@ -1,118 +1,63 @@
import React from "react";
import React, { useEffect, useState } from "react";
import { Helmet } from "react-helmet";
import Anchor from "@components/Anchor";
import { formatRelative } from "date-fns";
import "./AdminEventPage.scss";
import { Event, getEvents } from "@models/Event";
import { StaticContext } from "@server/StaticContext";
import AdminListCommon from "./AdminListCommon";
import Anchor from "@components/Anchor";
import AddIcon from "@assets/img/add-icon.png";
import { Event, getEvents } from "@models/Event";
const URL = "/admin/events"
export interface AdminEventPageProps {
staticContext: StaticContext;
}
export interface AdminEventPageState {
events: Event[];
error?: string;
}
const renderAddLink = () => (
<Anchor className="add-link" to={`${URL}/create`}>
<img src={AddIcon} /> Create event
</Anchor>
)
class AdminEventPage extends React.Component<AdminEventPageProps, AdminEventPageState> {
constructor(props: AdminEventPageProps) {
super(props);
const { staticContext } = props;
if (staticContext) {
/* The static context is an object that manages promises when
rendering on the server. If staticContext exists, that means
we have to store all promises in it. Otherwise, operate
normally. See server/index.ts. */
if (staticContext.resolutions.getEvents) {
const events = staticContext.resolutions.getEvents as Event[];
this.state = {
events,
};
} else {
this.state = {
events: [],
};
const promiseEvents = this.fetchEvents();
staticContext.promises.getEvents = promiseEvents;
}
} else {
this.state = {
events: [],
};
this.fetchEvents();
}
const renderData = (events: Event[]) => {
if (!events || events.length === 0) {
return <div>No events.</div>;
}
fetchEvents = async () => {
const getEventsPromise = getEvents({ auth: true });
try {
const events = await getEventsPromise;
this.setState({
events,
});
return getEventsPromise;
} catch (err) {
this.setState({
error: String(err),
});
}
}
renderAddLink = () => (
<Anchor className="add-link" to="/admin/events/create">
<img src={AddIcon} /> Create event
</Anchor>
)
renderData = () => {
const { events, error } = this.state;
if (error) {
return <div className="error">{error}</div>;
}
if (!events || events.length === 0) {
return <div>No events.</div>;
}
return (
<table>
<thead>
<tr>
<th>Title</th>
<th>Start time</th>
<th>End time</th>
return (
<table>
<thead>
<tr>
<th>Title</th>
<th>Start time</th>
<th>End time</th>
</tr>
</thead>
<tbody>
{events.map(event => (
<tr key={event.id}>
<td><Anchor to={`${URL}/${event.id}`}>{event.title_fi}</Anchor></td>
<td>{formatRelative(new Date(event.start_time), new Date())}</td>
<td>{formatRelative(new Date(event.end_time), new Date())}</td>
</tr>
</thead>
<tbody>
{events.map(event => (
<tr key={event.id}>
<td><Anchor to={`/admin/events/${event.id}`}>{event.title_fi}</Anchor></td>
<td>{formatRelative(new Date(event.start_time), new Date())}</td>
<td>{formatRelative(new Date(event.end_time), new Date())}</td>
</tr>
))}
</tbody>
</table>
);
}
))}
</tbody>
</table>
);
}
const AdminEventPage: React.FC = () => {
const [events, setEvents] = useState<Event[]>(null);
render() {
return (
<div className="admin-event-page">
<Helmet>
<link rel="canonical" href="https://sik.ayy.fi/admin/events" />
</Helmet>
<h1>Events</h1>
{this.renderAddLink()}
{this.renderData()}
</div>
);
}
useEffect(() => {
getEvents({ auth: true })
.then(res => setEvents(res))
}, []);
return (
<AdminListCommon>
<Helmet>
<link rel="canonical" href={`https://sik.ayy.fi/${URL}`} />
</Helmet>
<h1>Feed</h1>
{renderAddLink()}
{renderData(events)}
</AdminListCommon>
);
}
export default AdminEventPage;
-20
View File
@@ -1,20 +0,0 @@
@import "../../assets/scss/globals";
.admin-feed-page {
table {
border-collapse: collapse;
}
table,
th,
td {
border: 1px solid color(white1);
padding: 0.5rem;
a {
color: color(orange1);
text-decoration: underline;
}
}
}
+52 -104
View File
@@ -1,117 +1,65 @@
import React from "react";
import React, { useEffect, useState } from "react";
import { Helmet } from "react-helmet";
import Anchor from "@components/Anchor";
import "./AdminFeedPage.scss";
import { StaticContext } from "@server/StaticContext";
import { Post, getFeed } from "@models/Feed";
import { formatRelative } from "date-fns";
import { th } from "date-fns/esm/locale";
import AdminListCommon from "./AdminListCommon";
import Anchor from "@components/Anchor";
import AddIcon from "@assets/img/add-icon.png";
import { Post, getFeed } from "@models/Feed";
interface AdminFeedPageProps {
staticContext: StaticContext;
}
interface AdminFeedPageState {
feed: Post[];
error?: string;
}
const URL = "/admin/feed"
class AdminFeedPage extends React.Component<AdminFeedPageProps, AdminFeedPageState> {
constructor(props: AdminFeedPageProps) {
super(props);
const { staticContext } = props;
if (staticContext) {
/* The static context is an object that manages promises when
rendering on the server. If staticContext exists, that means
we have to store all promises in it. Otherwise, operate
normally. See server/index.ts. */
if (staticContext.resolutions.getFeed) {
const feed = staticContext.resolutions.getFeed as Post[];
this.state = {
feed,
};
} else {
this.state = {
feed: [],
};
const promiseFeed = this.fetchFeed();
staticContext.promises.getFeed = promiseFeed;
}
} else {
this.state = {
feed: [],
};
this.fetchFeed();
}
const renderAddLink = () => (
<Anchor className="add-link" to={`${URL}/create`}>
<img src={AddIcon} /> Create post
</Anchor>
)
const renderData = (feed: Post[]) => {
if (!feed || feed.length === 0) {
return <div>No posts.</div>;
}
fetchFeed = async () => {
const getFeedPromise = getFeed();
try {
const feed = await getFeedPromise;
this.setState({
feed,
});
return getFeedPromise;
} catch (err) {
this.setState({
error: String(err),
});
}
}
renderAddLink = () => (
<Anchor className="add-link" to="/admin/feed/create">
<img src={AddIcon} /> Create post
</Anchor>
)
renderData = () => {
const { feed, error } = this.state;
if (error) {
return <div className="error">{error}</div>;
}
if (!feed || feed.length === 0) {
return <div>No posts.</div>;
}
return (
<table>
<thead>
<tr>
<th>Title</th>
<th>Description</th>
<th>Publish time</th>
return (
<table>
<thead>
<tr>
<th>Title</th>
<th>Description</th>
<th>Publish time</th>
</tr>
</thead>
<tbody>
{feed.map(post => (
<tr key={post.id}>
<td><Anchor to={`${URL}/${post.id}`}>{post.title_fi}</Anchor></td>
<td>{post.description_fi}</td>
<td>{formatRelative(new Date(post.publish_time), new Date())}</td>
</tr>
</thead>
<tbody>
{feed.map(post => (
<tr key={post.id}>
<td><Anchor to={`/admin/feed/${post.id}`}>{post.title_fi}</Anchor></td>
<td>{post.description_fi}</td>
<td>{formatRelative(new Date(post.publish_time), new Date())}</td>
</tr>
))}
</tbody>
</table>
);
}
))}
</tbody>
</table>
);
}
render() {
return (
<div className="admin-feed-page">
<Helmet>
<link rel="canonical" href="https://sik.ayy.fi/admin/feed" />
</Helmet>
<h1>Feed</h1>
{this.renderAddLink()}
{this.renderData()}
</div>
);
}
const AdminFeedPage: React.FC = () => {
const [forms, setForms] = useState<Post[]>(null);
useEffect(() => {
getFeed({ auth: true })
.then(res => setForms(res))
}, []);
return (
<AdminListCommon>
<Helmet>
<link rel="canonical" href={`https://sik.ayy.fi/${URL}`} />
</Helmet>
<h1>Feed</h1>
{renderAddLink()}
{renderData(forms)}
</AdminListCommon>
);
}
export default AdminFeedPage;
-6
View File
@@ -1,6 +0,0 @@
.admin-front-page {
a {
text-decoration: underline;
display: block;
}
}
-26
View File
@@ -1,26 +0,0 @@
import React from "react";
import { Helmet } from "react-helmet";
import Anchor from "@components/Anchor";
import "./AdminFrontPage.scss";
export interface AdminFrontPageProps { }
export interface AdminFrontPageState { }
class AdminFrontPage extends React.Component<AdminFrontPageProps, AdminFrontPageState> {
render() {
return (
<div className="admin-front-page">
<Helmet>
<link rel="canonical" href="https://sik.ayy.fi/admin" />
</Helmet>
<h1>SIK Admin</h1>
<Anchor to="/admin/events">Events</Anchor>
<Anchor to="/admin/feed">Feed</Anchor>
<Anchor to="/admin/jobads">Job advertisements</Anchor>
<Anchor to="https:https://static.sika.sik.party/admin">Files</Anchor>
</div>
);
}
}
export default AdminFrontPage;
+13
View File
@@ -0,0 +1,13 @@
import React from "react";
import { Helmet } from "react-helmet";
const AdminFrontView: React.FC = () => (
<main>
<Helmet>
<link rel="canonical" href="https://sik.ayy.fi/admin" />
</Helmet>
<h1>SIK Admin</h1>
</main>
);
export default AdminFrontView;
+3 -2
View File
@@ -1,6 +1,7 @@
import React from "react";
import { Helmet } from "react-helmet";
import { formatRelative } from "date-fns";
import AdminListCommon from "./AdminListCommon";
import useFetchJobAds from "@hooks/useFetchJobAds";
import { JobAd } from "@models/JobAd";
import Anchor from "@components/Anchor";
@@ -49,14 +50,14 @@ const renderData = (jobAds: JobAd[]) => {
const AdminJobAdPage: React.FC = () => {
const jobAds = useFetchJobAds({ auth: true });
return (
<div>
<AdminListCommon>
<Helmet>
<link rel="canonical" href={`https://sik.ayy.fi/${URL}`} />
</Helmet>
<h1>Job advertisements</h1>
{renderAddLink()}
{renderData(jobAds)}
</div>
</AdminListCommon>
)
}
+22
View File
@@ -0,0 +1,22 @@
import styled from "styled-components";
import { colors } from "@theme/colors";
const Main = styled.main`
table {
border-collapse: collapse;
}
table,
th,
td {
border: 1px solid ${colors.white};
padding: 0.5rem;
a {
color: ${colors.orange1};
text-decoration: underline;
}
}
`;
export default Main;
-5
View File
@@ -1,5 +0,0 @@
.admin-login-page {
input {
display: block;
}
}
+11 -5
View File
@@ -1,11 +1,17 @@
import React from "react";
import styled from "styled-components";
import { Helmet } from "react-helmet";
import { Redirect } from "react-router-dom";
import qs from "query-string";
import { generateToken, setTokenCookie, isAuthenticated } from "@utils/auth";
import "./AdminLoginPage.scss";
export interface AdminLoginPageProps {
const Main = styled.main`
input {
display: block;
}
`;
interface AdminLoginPageProps {
history: {
push: (to: string | string[]) => void;
};
@@ -13,7 +19,7 @@ export interface AdminLoginPageProps {
search: string;
};
}
export interface AdminLoginPageState {
interface AdminLoginPageState {
username: string;
password: string;
isAuthenticated: boolean;
@@ -104,7 +110,7 @@ class AdminLoginPage extends React.Component<AdminLoginPageProps, AdminLoginPage
}
return (
<div className="admin-login-page">
<Main>
<Helmet>
<link rel="canonical" href="https://sik.ayy.fi/admin/login" />
</Helmet>
@@ -122,7 +128,7 @@ class AdminLoginPage extends React.Component<AdminLoginPageProps, AdminLoginPage
<input id="login-submit" type="submit" value="Log in" />
</form>
{ this.renderError() }
</div>
</Main>
);
}
}
-26
View File
@@ -1,26 +0,0 @@
@import "../../assets/scss/globals";
.signup-create-page {
a {
color: color(orange1);
text-decoration: underline;
}
}
.admin-signup-page {
table {
border-collapse: collapse;
}
table,
th,
td {
border: 1px solid color(white1);
padding: 0.5rem;
a {
color: color(orange1);
text-decoration: underline;
}
}
}
+51 -103
View File
@@ -1,117 +1,65 @@
import React from "react";
import React, { useEffect, useState } from "react";
import { Helmet } from "react-helmet";
import { formatRelative } from "date-fns";
import AdminListCommon from "./AdminListCommon";
import Anchor from "@components/Anchor";
import "./AdminSignupPage.scss";
import { SignupForm, getForms } from "@models/SignupForm";
import { StaticContext } from "@server/StaticContext";
import AddIcon from "@assets/img/add-icon.png";
import { SignupForm, getForms } from "@models/SignupForm";
export interface AdminSignupPageProps {
staticContext: StaticContext;
}
export interface AdminSignupPageState {
signupForms: SignupForm[];
error?: string;
}
const URL = "/admin/signups"
class AdminSignupPage extends React.Component<AdminSignupPageProps, AdminSignupPageState> {
constructor(props: AdminSignupPageProps) {
super(props);
const { staticContext } = props;
if (staticContext) {
/* The static context is an object that manages promises when
rendering on the server. If staticContext exists, that means
we have to store all promises in it. Otherwise, operate
normally. See server/index.ts. */
if (staticContext.resolutions.getSignupForms) {
const signupForms = staticContext.resolutions.getSignupForms as SignupForm[];
this.state = {
signupForms,
};
} else {
this.state = {
signupForms: [],
};
const promiseSignupForms = this.fetchSignupForms();
staticContext.promises.getSignupForms = promiseSignupForms;
}
} else {
this.state = {
signupForms: [],
};
this.fetchSignupForms();
}
const renderAddLink = () => (
<Anchor className="add-link" to={`${URL}/create`}>
<img src={AddIcon} /> Create signup form
</Anchor>
)
const renderData = (signupForms: SignupForm[]) => {
if (!signupForms || signupForms.length === 0) {
return <div>No signup forms.</div>;
}
fetchSignupForms = async () => {
const getSignupFormsPromise = getForms(true);
try {
const signupForms = await getSignupFormsPromise;
this.setState({
signupForms,
});
return getSignupFormsPromise;
} catch (err) {
this.setState({
error: String(err),
});
}
}
renderAddLink = () => (
<Anchor className="add-link" to="/admin/signups/create">
<img src={AddIcon} /> Create signup form
</Anchor>
)
renderData = () => {
const { signupForms, error } = this.state;
if (error) {
return <div className="error">{error}</div>;
}
if (!signupForms || signupForms.length === 0) {
return <div>No signup forms.</div>;
}
return (
<table>
<thead>
<tr>
<th>Title</th>
<th>Start time</th>
<th>End time</th>
return (
<table>
<thead>
<tr>
<th>Title</th>
<th>Start time</th>
<th>End time</th>
</tr>
</thead>
<tbody>
{signupForms.map(signupForm => (
<tr key={signupForm.id}>
<td><Anchor to={`${URL}/${signupForm.id}`}>{signupForm.title_fi}</Anchor></td>
<td>{formatRelative(new Date(signupForm.start_time), new Date())}</td>
<td>{formatRelative(new Date(signupForm.end_time), new Date())}</td>
</tr>
</thead>
<tbody>
{signupForms.map(signupForm => (
<tr key={signupForm.id}>
<td><Anchor to={`/admin/signups/${signupForm.id}`}>{signupForm.title_fi}</Anchor></td>
<td>{formatRelative(new Date(signupForm.start_time), new Date())}</td>
<td>{formatRelative(new Date(signupForm.end_time), new Date())}</td>
</tr>
))}
</tbody>
</table>
);
}
))}
</tbody>
</table>
);
}
render() {
return (
<div className="admin-signup-page">
<Helmet>
<link rel="canonical" href="https://sik.ayy.fi/admin/events" />
</Helmet>
<h1>Sign-up forms</h1>
{this.renderAddLink()}
{this.renderData()}
</div>
);
}
const AdminSignupPage: React.FC = () => {
const [forms, setForms] = useState<SignupForm[]>(null);
useEffect(() => {
getForms(true)
.then(res => setForms(res))
}, []);
return (
<AdminListCommon>
<Helmet>
<link rel="canonical" href={`https://sik.ayy.fi/${URL}`} />
</Helmet>
<h1>Sign-up forms</h1>
{renderAddLink()}
{renderData(forms)}
</AdminListCommon>
);
}
export default AdminSignupPage;
+2 -2
View File
@@ -1,5 +1,5 @@
import React from "react";
import { Switch, Route, Redirect } from "react-router-dom";
import { Switch, Route } from "react-router-dom";
import { Helmet } from "react-helmet";
import FrontPage from "./pages/FrontPage";
import GuildPage from "./pages/GuildPage";
@@ -7,7 +7,7 @@ import NotFoundPage from "./pages/NotFoundPage";
import CommonPage from "./pages/CommonPage";
import JsonLD from "@components/JsonLD";
import "./index.scss";
import AdminFrontPage from "./pages/admin/AdminFrontPage";
import AdminFrontPage from "./pages/admin/AdminFrontView";
import AdminEventPage from "./pages/admin/AdminEventPage";
import AdminFeedPage from "./pages/admin/AdminFeedPage";
import AdminCommonPage from "./pages/admin/AdminCommonPage";