Implement Feed admin form
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
import * as React from "react";
|
||||
|
||||
export interface DatetimeWidgetProps {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
required: boolean;
|
||||
disabled: boolean;
|
||||
}
|
||||
export interface DatetimeWidgetState {}
|
||||
|
||||
class DatetimeWidget extends React.Component<DatetimeWidgetProps, DatetimeWidgetState> {
|
||||
render() {
|
||||
const { value, onChange, required, disabled } = this.props;
|
||||
|
||||
let date, time;
|
||||
if (value && value.length !== 0) {
|
||||
let rest;
|
||||
[date, rest] = value.split("T");
|
||||
time = rest.slice(0, 5);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="datetime-widget">
|
||||
<input
|
||||
type="date"
|
||||
onChange={(event) => onChange(`${event.target.value}T${time}`)}
|
||||
required={required}
|
||||
disabled={disabled}
|
||||
value={date} />
|
||||
<input
|
||||
type="time"
|
||||
onChange={(event) => onChange(`${date}T${event.target.value}:00`)}
|
||||
required={required}
|
||||
disabled={disabled}
|
||||
value={time} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DatetimeWidget;
|
||||
@@ -0,0 +1,2 @@
|
||||
import DatetimeWidget from "./DatetimeWidget";
|
||||
export default DatetimeWidget;
|
||||
+6
-1
@@ -12,6 +12,7 @@ export interface Event {
|
||||
end_time: string;
|
||||
tags: number[];
|
||||
tag_id: number[];
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
export async function getEvents(options: any = {}): Promise<Event[]> {
|
||||
@@ -42,7 +43,11 @@ export async function getEvent(id: number): Promise<Event> {
|
||||
|
||||
export async function createEvent(data): Promise<Event> {
|
||||
try {
|
||||
const resp = await axios.post(url, data);
|
||||
const resp = await axios.post(url, data, {
|
||||
headers: {
|
||||
"Authorization": getAuthHeader(),
|
||||
},
|
||||
});
|
||||
return resp.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
+46
-2
@@ -1,16 +1,21 @@
|
||||
import axios from "axios";
|
||||
import { getAuthHeader } from "../auth";
|
||||
|
||||
const url = `${process.env.API_URL}/feed/`;
|
||||
|
||||
export interface Feed {
|
||||
export interface Post {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
content: string;
|
||||
publish_time: string;
|
||||
autohide: string;
|
||||
tag_id: number[];
|
||||
tags: number[];
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
export async function getFeed(): Promise<Feed[]> {
|
||||
export async function getFeed(): Promise<Post[]> {
|
||||
try {
|
||||
const resp = await axios.get(url);
|
||||
return resp.data["results"];
|
||||
@@ -19,3 +24,42 @@ export async function getFeed(): Promise<Feed[]> {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPost(id: number): Promise<Post> {
|
||||
try {
|
||||
const resp = await axios.get(`${url}${id}/`);
|
||||
return resp.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
export async function createPost(data): Promise<Post> {
|
||||
try {
|
||||
const resp = await axios.post(url, data, {
|
||||
headers: {
|
||||
"Authorization": getAuthHeader(),
|
||||
},
|
||||
});
|
||||
return resp.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
export async function updatePost(data): Promise<Post> {
|
||||
try {
|
||||
const putUrl = `${url}${data.id}/`;
|
||||
const resp = await axios.put(putUrl, data, {
|
||||
headers: {
|
||||
"Authorization": getAuthHeader(),
|
||||
},
|
||||
});
|
||||
return resp.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import Helmet from "react-helmet";
|
||||
import { Link } from "react-router-dom";
|
||||
import "./AdminFeedPage.scss";
|
||||
import { StaticContext } from "../../server/StaticContext";
|
||||
import{Feed, getFeed} from "../../models/Feed";
|
||||
import { Post, getFeed} from "../../models/Feed";
|
||||
import { getEvents } from "../../models/Event";
|
||||
import { formatRelative } from "date-fns";
|
||||
import { th } from "date-fns/esm/locale";
|
||||
@@ -14,7 +14,7 @@ export interface AdminFeedPageProps {
|
||||
staticContext: StaticContext;
|
||||
}
|
||||
export interface AdminFeedPageState {
|
||||
feed: Feed[];
|
||||
feed: Post[];
|
||||
error?: string;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ class AdminFeedPage extends React.Component<AdminFeedPageProps, AdminFeedPageSta
|
||||
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 Feed[];
|
||||
const feed = staticContext.resolutions.getFeed as Post[];
|
||||
this.state = {
|
||||
feed,
|
||||
};
|
||||
|
||||
@@ -5,28 +5,10 @@ import { isAuthenticated } from "../../auth";
|
||||
import Form from "react-jsonschema-form";
|
||||
import { Tag, getTags } from "../../models/Tag";
|
||||
import { createEvent, getEvent, updateEvent } from "../../models/Event";
|
||||
|
||||
const DateTimeWidget = ({ value, onChange, required }) => {
|
||||
const [date, rest] = value.split("T");
|
||||
const time = rest.slice(0, 5);
|
||||
return (
|
||||
<div className="datetime-widget">
|
||||
<input
|
||||
type="date"
|
||||
onChange={(event) => onChange(`${event.target.value}T${time}`)}
|
||||
required={required}
|
||||
value={date} />
|
||||
<input
|
||||
type="time"
|
||||
onChange={(event) => onChange(`${date}T${event.target.value}:00`)}
|
||||
required={required}
|
||||
value={time} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
import DatetimeWidget from "../../components/DatetimeWidget";
|
||||
|
||||
const widgets = {
|
||||
datetime: DateTimeWidget,
|
||||
datetime: DatetimeWidget,
|
||||
};
|
||||
|
||||
export interface EventCreatePageProps {
|
||||
@@ -103,12 +85,14 @@ class EventCreatePage extends React.Component<EventCreatePageProps, EventCreateP
|
||||
payload.tag_id = payload.tags;
|
||||
if (payload.id === undefined) {
|
||||
const resp = await createEvent(payload);
|
||||
resp.tags = resp.tag_id;
|
||||
this.setState({
|
||||
formData: resp,
|
||||
statusMessage: "Event created successfully",
|
||||
});
|
||||
} else {
|
||||
const resp = await updateEvent(payload);
|
||||
resp.tags = resp.tag_id;
|
||||
this.setState({
|
||||
formData: resp,
|
||||
statusMessage: "Event updated successfully.",
|
||||
@@ -127,10 +111,9 @@ class EventCreatePage extends React.Component<EventCreatePageProps, EventCreateP
|
||||
}
|
||||
|
||||
onChange = (data) => {
|
||||
console.log("changed, data:");
|
||||
console.log(data);
|
||||
this.setState({
|
||||
formData: data.formData,
|
||||
statusMessage: null,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
@import "../../assets/scss/globals";
|
||||
|
||||
.post-create-page {
|
||||
width: 100%;
|
||||
|
||||
fieldset {
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
option {
|
||||
padding: 4px 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
textarea {
|
||||
padding: 0.5rem 0.5rem;
|
||||
border: none;
|
||||
overflow: visible;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
textarea,
|
||||
select,
|
||||
.datetime-widget {
|
||||
width: 20rem;
|
||||
|
||||
@media screen and (max-width: 800px - 1px) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
legend {
|
||||
font-weight: bold;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: $blue;
|
||||
color: $white;
|
||||
padding: 0.5rem 1rem;
|
||||
border: none;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
label {
|
||||
display: flex;
|
||||
|
||||
input {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.datetime-widget {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
import * as React from "react";
|
||||
import Helmet from "react-helmet";
|
||||
import "./FeedCreatePage.scss";
|
||||
import { isAuthenticated } from "../../auth";
|
||||
import Form from "react-jsonschema-form";
|
||||
import { Tag, getTags } from "../../models/Tag";
|
||||
import { createPost, getPost, updatePost } from "../../models/Feed";
|
||||
import DatetimeWidget from "../../components/DatetimeWidget";
|
||||
|
||||
const widgets = {
|
||||
datetime: DatetimeWidget,
|
||||
};
|
||||
|
||||
export interface FeedCreatePageProps {
|
||||
history: {
|
||||
push: (to: string) => void;
|
||||
};
|
||||
match: {
|
||||
params: {
|
||||
id?: number;
|
||||
};
|
||||
};
|
||||
}
|
||||
export interface FeedCreatePageState {
|
||||
tags: Tag[];
|
||||
error?: string;
|
||||
statusMessage?: string;
|
||||
formData: any;
|
||||
}
|
||||
|
||||
class FeedCreatePage extends React.Component<FeedCreatePageProps, FeedCreatePageState> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
tags: [],
|
||||
formData: {},
|
||||
};
|
||||
|
||||
this.fetchTags();
|
||||
|
||||
const id = props.match.params.id;
|
||||
if (id !== undefined) {
|
||||
this.fetchInitialFormData(id);
|
||||
}
|
||||
}
|
||||
|
||||
fetchInitialFormData = async (id) => {
|
||||
try {
|
||||
const data = await getPost(id);
|
||||
data.tags = data.tag_id;
|
||||
this.setState({
|
||||
formData: data,
|
||||
});
|
||||
} catch (err) {
|
||||
this.setState({
|
||||
error: String(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fetchTags = async () => {
|
||||
const getTagsPromise = getTags();
|
||||
try {
|
||||
const tags = await getTagsPromise;
|
||||
this.setState({
|
||||
tags,
|
||||
});
|
||||
return getTagsPromise;
|
||||
} catch (err) {
|
||||
this.setState({
|
||||
error: String(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onSubmit = async (data) => {
|
||||
console.log("submitted, data:");
|
||||
console.log(data);
|
||||
|
||||
try {
|
||||
const payload = data.formData;
|
||||
payload.tag_id = payload.tags;
|
||||
payload.autohide = payload.autohide || new Date();
|
||||
|
||||
if (payload.id === undefined) {
|
||||
const resp = await createPost(payload);
|
||||
resp.tags = resp.tag_id;
|
||||
this.setState({
|
||||
formData: resp,
|
||||
statusMessage: "Post created successfully",
|
||||
});
|
||||
} else {
|
||||
const resp = await updatePost(payload);
|
||||
resp.tags = resp.tag_id;
|
||||
this.setState({
|
||||
formData: resp,
|
||||
statusMessage: "Post updated successfully.",
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
this.setState({
|
||||
error: String(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onError = (data) => {
|
||||
console.error("error, data:");
|
||||
console.log(data);
|
||||
}
|
||||
|
||||
onChange = (data) => {
|
||||
this.setState({
|
||||
formData: data.formData,
|
||||
statusMessage: null,
|
||||
});
|
||||
}
|
||||
|
||||
buildSchema = () => {
|
||||
const { tags, error, formData } = this.state;
|
||||
|
||||
const date = new Date();
|
||||
const currentDatetime = date.toISOString();
|
||||
|
||||
const schema = {
|
||||
title: formData.id ? formData.title : "New Post",
|
||||
type: "object",
|
||||
required: ["title", "description", "content", "publish_time"],
|
||||
properties: {
|
||||
title: {
|
||||
type: "string",
|
||||
title: "Title",
|
||||
default: ""
|
||||
},
|
||||
tags: {
|
||||
type: "array",
|
||||
title: "Post tags",
|
||||
items: {
|
||||
type: "number",
|
||||
enum: tags.map(t => t.id),
|
||||
enumNames: tags.map(t => t.name),
|
||||
},
|
||||
uniqueItems: true,
|
||||
default: [],
|
||||
},
|
||||
description: {
|
||||
type: "string",
|
||||
title: "Description",
|
||||
default: "",
|
||||
},
|
||||
content: {
|
||||
type: "string",
|
||||
title: "Content",
|
||||
default: "",
|
||||
},
|
||||
publish_time: {
|
||||
type: "string",
|
||||
title: "Publish time",
|
||||
default: currentDatetime,
|
||||
},
|
||||
autohide_enabled: {
|
||||
type: "boolean",
|
||||
title: "Autohide enabled",
|
||||
default: false,
|
||||
},
|
||||
autohide: {
|
||||
type: "string",
|
||||
title: "Autohide time",
|
||||
default: "",
|
||||
},
|
||||
visible: {
|
||||
type: "boolean",
|
||||
title: "Visible",
|
||||
default: true,
|
||||
}
|
||||
}
|
||||
};
|
||||
return schema;
|
||||
}
|
||||
|
||||
buildUISchema = () => {
|
||||
const { formData } = this.state;
|
||||
const { autohide_enabled } = formData;
|
||||
const uiSchema = {
|
||||
content: {
|
||||
"ui:widget": "textarea",
|
||||
},
|
||||
publish_time: {
|
||||
"ui:widget": "datetime",
|
||||
},
|
||||
autohide: {
|
||||
"ui:widget": autohide_enabled ? "datetime" : "hidden",
|
||||
},
|
||||
};
|
||||
return uiSchema;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { error, formData, statusMessage } = this.state;
|
||||
const schema = this.buildSchema();
|
||||
const uiSchema = this.buildUISchema();
|
||||
|
||||
const title = formData.id
|
||||
? `Edit Post "${formData.title}"`
|
||||
: "Create Post";
|
||||
|
||||
return (
|
||||
<div className="post-create-page">
|
||||
<Helmet>
|
||||
<link rel="canonical" href="https://sik.ayy.fi/admin/feed/create" />
|
||||
</Helmet>
|
||||
<h1>{title}</h1>
|
||||
{ statusMessage && <div className="success">{ statusMessage }</div>}
|
||||
<Form schema={schema}
|
||||
uiSchema={uiSchema}
|
||||
formData={formData}
|
||||
idPrefix="rjsf"
|
||||
widgets={widgets}
|
||||
onChange={this.onChange}
|
||||
onSubmit={this.onSubmit}
|
||||
onError={this.onError} />
|
||||
{ error && <div className="error">{error}</div> }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default FeedCreatePage;
|
||||
@@ -0,0 +1,2 @@
|
||||
import FeedCreatePage from "./FeedCreatePage";
|
||||
export default FeedCreatePage;
|
||||
@@ -4,7 +4,7 @@ import "./FrontPage.scss";
|
||||
import appStore from "../../stores/AppStore";
|
||||
import Card from "../../components/Card";
|
||||
import { Event, getEvents } from "../../models/Event";
|
||||
import { Feed, getFeed } from "../../models/Feed";
|
||||
import { Post, getFeed } from "../../models/Feed";
|
||||
import { StaticContext } from "../../server/StaticContext";
|
||||
|
||||
// @ts-ignore
|
||||
@@ -27,7 +27,7 @@ interface FrontPageProps {
|
||||
|
||||
interface FrontPageState {
|
||||
events: Event[];
|
||||
feed: Feed[];
|
||||
feed: Post[];
|
||||
}
|
||||
|
||||
class FrontPage extends React.Component<FrontPageProps, FrontPageState> {
|
||||
@@ -42,7 +42,7 @@ class FrontPage extends React.Component<FrontPageProps, FrontPageState> {
|
||||
normally. See server/index.ts. */
|
||||
if (staticContext.resolutions.getEvents) {
|
||||
const events = staticContext.resolutions.getEvents as Event[];
|
||||
const feed = staticContext.resolutions.getFeed as Feed[];
|
||||
const feed = staticContext.resolutions.getFeed as Post[];
|
||||
this.state = {
|
||||
events,
|
||||
feed,
|
||||
|
||||
@@ -16,6 +16,7 @@ import AdminLoginPage from "./pages/AdminLoginPage";
|
||||
import { getTokenCookie } from "./auth";
|
||||
import AdminLogoutPage from "./pages/AdminLogoutPage";
|
||||
import EventCreatePage from "./pages/EventCreatePage";
|
||||
import FeedCreatePage from "./pages/FeedCreatePage";
|
||||
import ContactsPage from "./pages/ContactsPage";
|
||||
|
||||
const renderPage = (Page) => (props): JSX.Element => {
|
||||
@@ -43,6 +44,8 @@ const adminLoginRoutes = [
|
||||
const adminRoutes = [
|
||||
{ path: "/admin/events", page: AdminEventPage },
|
||||
{ path: "/admin/feed", page: AdminFeedPage },
|
||||
{ path: "/admin/feed/create", page: FeedCreatePage },
|
||||
{ path: "/admin/feed/:id", page: FeedCreatePage },
|
||||
{ path: "/admin/events/create", page: EventCreatePage},
|
||||
{ path: "/admin/events/:id", page: EventCreatePage},
|
||||
{ path: "/admin/logout", page: AdminLogoutPage },
|
||||
|
||||
Reference in New Issue
Block a user