Feature: Create, list & edit job advertisements
This commit is contained in:
@@ -1,2 +0,0 @@
|
|||||||
import AdminHeader from "./AdminHeader";
|
|
||||||
export default AdminHeader;
|
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import React from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import AdminSidebarLink from "./AdminSidebarLink";
|
||||||
|
|
||||||
|
interface AdminSidebarProps {
|
||||||
|
path: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SideBar = styled.nav`
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column nowrap;
|
||||||
|
align-self: stretch;
|
||||||
|
margin-right: 1rem;
|
||||||
|
|
||||||
|
@media screen and (max-width: 800px - 1px) {
|
||||||
|
margin-right: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const AdminSidebar: React.FC<AdminSidebarProps> = ({ path }) => (
|
||||||
|
<SideBar>
|
||||||
|
<AdminSidebarLink to="/admin" path={path}>Home</AdminSidebarLink>
|
||||||
|
<AdminSidebarLink to="/admin/events" path={path}>Events</AdminSidebarLink>
|
||||||
|
<AdminSidebarLink to="/admin/feed" path={path}>Feed</AdminSidebarLink>
|
||||||
|
<AdminSidebarLink to="/admin/signups" path={path}>Signup forms</AdminSidebarLink>
|
||||||
|
<AdminSidebarLink to="/admin/jobads" path={path}>Job advertisements</AdminSidebarLink>
|
||||||
|
<AdminSidebarLink to="https://static.sika.sik.party/admin" path={path}>Files</AdminSidebarLink>
|
||||||
|
<AdminSidebarLink id="admin-sidebar-logout" to="/admin/logout" path={path}>Logout</AdminSidebarLink>
|
||||||
|
</SideBar>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default AdminSidebar;
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
@import "../../assets/scss/globals";
|
|
||||||
|
|
||||||
.admin-sidebar {
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column nowrap;
|
|
||||||
align-self: stretch;
|
|
||||||
margin-right: 1rem;
|
|
||||||
|
|
||||||
@media screen and (max-width: 800px - 1px) {
|
|
||||||
margin-right: 0;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import "./AdminSidebar.scss";
|
|
||||||
import AdminSidebarLink from "../AdminSidebarLink";
|
|
||||||
|
|
||||||
export interface AdminSidebarProps {
|
|
||||||
path: string;
|
|
||||||
}
|
|
||||||
export interface AdminSidebarState {}
|
|
||||||
|
|
||||||
class AdminSidebar extends React.Component<AdminSidebarProps, AdminSidebarState> {
|
|
||||||
render() {
|
|
||||||
const { path } = this.props;
|
|
||||||
return (
|
|
||||||
<div className="admin-sidebar">
|
|
||||||
<AdminSidebarLink to="/admin" path={path}>Home</AdminSidebarLink>
|
|
||||||
<AdminSidebarLink to="/admin/events" path={path}>Events</AdminSidebarLink>
|
|
||||||
<AdminSidebarLink to="/admin/feed" path={path}>Feed</AdminSidebarLink>
|
|
||||||
<AdminSidebarLink to="/admin/signups" path={path}>Signup forms</AdminSidebarLink>
|
|
||||||
<AdminSidebarLink to="https://static.sika.sik.party/admin" path={path}>Files</AdminSidebarLink>
|
|
||||||
<AdminSidebarLink id="admin-sidebar-logout" to="/admin/logout" path={path}>Logout</AdminSidebarLink>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AdminSidebar;
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
import AdminSidebar from "./AdminSidebar";
|
|
||||||
export default AdminSidebar;
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { JobAd, getJobAd } from "@models/JobAd";
|
||||||
|
|
||||||
|
const useFetchJobAd = (id: number) => {
|
||||||
|
const [jobAd, setJobAd] = useState<JobAd>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getJobAd(id)
|
||||||
|
.then(res => setJobAd(res))
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return jobAd;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useFetchJobAd;
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { JobAd, getJobAds, GetJobAdsOptions } from "@models/JobAd";
|
||||||
|
|
||||||
|
const useFetchJobAds = (options: GetJobAdsOptions = {}) => {
|
||||||
|
const [jobAds, setJobAds] = useState<JobAd[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getJobAds(options)
|
||||||
|
.then(res => setJobAds(res))
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return jobAds;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useFetchJobAds;
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
import qs from "query-string";
|
||||||
|
import { getAuthHeader } from "@utils/auth";
|
||||||
|
const url = `${process.env.API_URL}/jobads/`;
|
||||||
|
|
||||||
|
export interface JobAd {
|
||||||
|
id: number;
|
||||||
|
title_fi: string;
|
||||||
|
title_en: string;
|
||||||
|
description_fi: string;
|
||||||
|
description_en: string;
|
||||||
|
content_fi: string;
|
||||||
|
content_en: string;
|
||||||
|
autohide_at: Date;
|
||||||
|
autohide_enabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetJobAdsOptions {
|
||||||
|
onlyNonPast?: boolean;
|
||||||
|
limit?: number;
|
||||||
|
auth?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getJobAds = async (options: GetJobAdsOptions = {}): Promise<JobAd[]> => {
|
||||||
|
const { onlyNonPast, limit, auth } = options;
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
since: onlyNonPast ? (new Date()).toISOString() : undefined,
|
||||||
|
limit,
|
||||||
|
};
|
||||||
|
const search = qs.stringify(params);
|
||||||
|
const headers = auth ? { "Authorization": getAuthHeader() } : null;
|
||||||
|
const resp = await axios.get(`${url}?${search}`, {
|
||||||
|
headers
|
||||||
|
});
|
||||||
|
return resp.data["results"];
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getJobAd = async (id: number, auth = false): Promise<JobAd> => {
|
||||||
|
try {
|
||||||
|
const headers = auth ? { "Authorization": getAuthHeader() } : null;
|
||||||
|
const resp = await axios.get(`${url}${id}/`, {
|
||||||
|
headers
|
||||||
|
});
|
||||||
|
return resp.data;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createJobAd = async (data: any): Promise<JobAd> => {
|
||||||
|
try {
|
||||||
|
const resp = await axios.post(url, data, {
|
||||||
|
headers: {
|
||||||
|
"Authorization": getAuthHeader(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return resp.data;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateJobAd = async (data: any): Promise<JobAd> => {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
-14
@@ -1,21 +1,18 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
import CorporatePageView from "@views/CorporatePage/CorporatePageView";
|
import CorporatePageView from "@views/CorporatePage/CorporatePageView";
|
||||||
|
import useFetchJobAds from "@hooks/useFetchJobAds";
|
||||||
|
|
||||||
export interface CorporatePageProps {}
|
const CorporatePage: React.FC = () => {
|
||||||
export interface CorporatePageState {}
|
const jobAds = useFetchJobAds();
|
||||||
|
return (
|
||||||
class CorporatePage extends React.Component<CorporatePageProps, CorporatePageState> {
|
<>
|
||||||
render() {
|
<Helmet>
|
||||||
return (
|
<link rel="canonical" href="https://sik.ayy.fi/yritysyhteistyo" />
|
||||||
<>
|
</Helmet>
|
||||||
<Helmet>
|
<CorporatePageView jobAds={jobAds} />
|
||||||
<link rel="canonical" href="https://sik.ayy.fi/yritysyhteistyo" />
|
</>
|
||||||
</Helmet>
|
);
|
||||||
<CorporatePageView />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CorporatePage;
|
export default CorporatePage;
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import Anchor from "@components/Anchor";
|
|||||||
import "./AdminFeedPage.scss";
|
import "./AdminFeedPage.scss";
|
||||||
import { StaticContext } from "@server/StaticContext";
|
import { StaticContext } from "@server/StaticContext";
|
||||||
import { Post, getFeed } from "@models/Feed";
|
import { Post, getFeed } from "@models/Feed";
|
||||||
import { getEvents } from "@models/Event";
|
|
||||||
import { formatRelative } from "date-fns";
|
import { formatRelative } from "date-fns";
|
||||||
import { th } from "date-fns/esm/locale";
|
import { th } from "date-fns/esm/locale";
|
||||||
import AddIcon from "@assets/img/add-icon.png";
|
import AddIcon from "@assets/img/add-icon.png";
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ class AdminFrontPage extends React.Component<AdminFrontPageProps, AdminFrontPage
|
|||||||
<h1>SIK Admin</h1>
|
<h1>SIK Admin</h1>
|
||||||
<Anchor to="/admin/events">Events</Anchor>
|
<Anchor to="/admin/events">Events</Anchor>
|
||||||
<Anchor to="/admin/feed">Feed</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>
|
<Anchor to="https:https://static.sika.sik.party/admin">Files</Anchor>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Helmet } from "react-helmet";
|
||||||
|
import { formatRelative } from "date-fns";
|
||||||
|
import useFetchJobAds from "@hooks/useFetchJobAds";
|
||||||
|
import { JobAd } from "@models/JobAd";
|
||||||
|
import Anchor from "@components/Anchor";
|
||||||
|
import AddIcon from "@assets/img/add-icon.png";
|
||||||
|
|
||||||
|
const URL = "/admin/jobads"
|
||||||
|
|
||||||
|
const renderAddLink = () => (
|
||||||
|
<Anchor className="add-link" to={`${URL}/create`}>
|
||||||
|
<img src={AddIcon} /> Create Ad
|
||||||
|
</Anchor>
|
||||||
|
)
|
||||||
|
|
||||||
|
const renderData = (jobAds: JobAd[]) => {
|
||||||
|
|
||||||
|
if (!jobAds || jobAds.length === 0) {
|
||||||
|
return <div>No advertisements.</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Title</th>
|
||||||
|
<th>Description</th>
|
||||||
|
<th>Autohide</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{jobAds.map(ad => (
|
||||||
|
<tr key={ad.id}>
|
||||||
|
<td><Anchor to={`${URL}/${ad.id}`}>{ad.title_fi}</Anchor></td>
|
||||||
|
<td>{ad.description_fi}</td>
|
||||||
|
<td>
|
||||||
|
{ad.autohide_enabled ?
|
||||||
|
formatRelative(new Date(ad.autohide_at), new Date())
|
||||||
|
: "Disabled"}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const AdminJobAdPage: React.FC = () => {
|
||||||
|
const jobAds = useFetchJobAds({ auth: true });
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Helmet>
|
||||||
|
<link rel="canonical" href={`https://sik.ayy.fi/${URL}`} />
|
||||||
|
</Helmet>
|
||||||
|
<h1>Job advertisements</h1>
|
||||||
|
{renderAddLink()}
|
||||||
|
{renderData(jobAds)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AdminJobAdPage;
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
import "./FeedCreatePage.scss";
|
|
||||||
import { isAuthenticated } from "@utils/auth";
|
|
||||||
import Form from "react-jsonschema-form";
|
import Form from "react-jsonschema-form";
|
||||||
|
import { RouteComponentProps } from "react-router-dom";
|
||||||
|
import "./FeedCreatePage.scss";
|
||||||
import { Tag, getTags } from "@models/Tag";
|
import { Tag, getTags } from "@models/Tag";
|
||||||
import { createPost, getPost, updatePost } from "@models/Feed";
|
import { createPost, getPost, updatePost } from "@models/Feed";
|
||||||
import DatetimeWidget from "@components/Widgets/DatetimeWidget/DatetimeWidget";
|
import DatetimeWidget from "@components/Widgets/DatetimeWidget/DatetimeWidget";
|
||||||
@@ -11,16 +11,12 @@ const widgets = {
|
|||||||
datetime: DatetimeWidget,
|
datetime: DatetimeWidget,
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface FeedCreatePageProps {
|
interface MatchParams {
|
||||||
history: {
|
id?: string;
|
||||||
push: (to: string) => void;
|
|
||||||
};
|
|
||||||
match: {
|
|
||||||
params: {
|
|
||||||
id?: number;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FeedCreatePageProps = RouteComponentProps<MatchParams>;
|
||||||
|
|
||||||
export interface FeedCreatePageState {
|
export interface FeedCreatePageState {
|
||||||
tags: Tag[];
|
tags: Tag[];
|
||||||
error?: string;
|
error?: string;
|
||||||
|
|||||||
@@ -0,0 +1,183 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { Helmet } from "react-helmet";
|
||||||
|
import Form from "react-jsonschema-form";
|
||||||
|
import { RouteComponentProps } from "react-router-dom";
|
||||||
|
import { JobAd, getJobAd, createJobAd, updateJobAd } from "@models/JobAd";
|
||||||
|
import DatetimeWidget from "@components/Widgets/DatetimeWidget/DatetimeWidget";
|
||||||
|
import SectionDividerWidget from "@components/Widgets/SectionDividerWidget/SectionDividerWidget";
|
||||||
|
import MarkdownEditorWidget from "@components/Widgets/MarkdownEditorWidget";
|
||||||
|
|
||||||
|
const widgets = {
|
||||||
|
datetime: DatetimeWidget,
|
||||||
|
section_divider: SectionDividerWidget,
|
||||||
|
markdownEditor: MarkdownEditorWidget
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildSchema = (title: string) => {
|
||||||
|
const date = new Date();
|
||||||
|
const monthFromNow = new Date().setDate(date.getDate() + 30)
|
||||||
|
|
||||||
|
const schema = {
|
||||||
|
title,
|
||||||
|
type: "object",
|
||||||
|
required: ["title_fi", "title_en", "description_fi", "description_en", "content_fi", "content_en", "autohide_at", "autohide_enabled", "visible"],
|
||||||
|
properties: {
|
||||||
|
visible: {
|
||||||
|
type: "boolean",
|
||||||
|
title: "Visible",
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
autohide_enabled: {
|
||||||
|
type: "boolean",
|
||||||
|
title: "Autohide enabled",
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
autohide_at: {
|
||||||
|
type: "string",
|
||||||
|
title: "Autohide time",
|
||||||
|
default: monthFromNow,
|
||||||
|
},
|
||||||
|
finnish_section_divider: {
|
||||||
|
title: "Finnish",
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
title_fi: {
|
||||||
|
type: "string",
|
||||||
|
title: "Title",
|
||||||
|
default: ""
|
||||||
|
},
|
||||||
|
description_fi: {
|
||||||
|
type: "string",
|
||||||
|
title: "Description",
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
content_fi: {
|
||||||
|
type: "string",
|
||||||
|
title: "Content",
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
english_section_divider: {
|
||||||
|
title: "English",
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
title_en: {
|
||||||
|
type: "string",
|
||||||
|
title: "Title",
|
||||||
|
default: ""
|
||||||
|
},
|
||||||
|
description_en: {
|
||||||
|
type: "string",
|
||||||
|
title: "Description",
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
content_en: {
|
||||||
|
type: "string",
|
||||||
|
title: "Content",
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildUISchema = (formData: JobAd) => ({
|
||||||
|
content_fi: {
|
||||||
|
"ui:widget": "markdownEditor",
|
||||||
|
},
|
||||||
|
content_en: {
|
||||||
|
"ui:widget": "markdownEditor",
|
||||||
|
},
|
||||||
|
autohide_at: {
|
||||||
|
"ui:widget": formData && formData.autohide_enabled ? "datetime" : "hidden",
|
||||||
|
},
|
||||||
|
finnish_section_divider: {
|
||||||
|
"ui:widget": "section_divider",
|
||||||
|
"ui:options": {
|
||||||
|
label: false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
english_section_divider: {
|
||||||
|
"ui:widget": "section_divider",
|
||||||
|
"ui:options": {
|
||||||
|
label: false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
interface MatchParams {
|
||||||
|
id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type JobAdCreatePageProps = RouteComponentProps<MatchParams>;
|
||||||
|
|
||||||
|
const JobAdCreatePage: React.FC<JobAdCreatePageProps> = ({ match: { params: { id } } }) => {
|
||||||
|
|
||||||
|
const [formData, setFormData] = useState<JobAd>(null);
|
||||||
|
useEffect(() => {
|
||||||
|
const jobId = Number(id);
|
||||||
|
if (jobId !== undefined) {
|
||||||
|
getJobAd(jobId, true)
|
||||||
|
.then(res => setFormData(res))
|
||||||
|
}
|
||||||
|
}, [id])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const [error, setError] = useState<string>(null);
|
||||||
|
const [statusMessage, setStatusMessage] = useState<string>(null);
|
||||||
|
|
||||||
|
const onSubmit = async (data) => {
|
||||||
|
try {
|
||||||
|
const payload = data.formData;
|
||||||
|
if (payload.id === undefined) {
|
||||||
|
const resp = await createJobAd(payload);
|
||||||
|
setStatusMessage("Post created successfully");
|
||||||
|
setFormData(resp);
|
||||||
|
} else {
|
||||||
|
const resp = await updateJobAd(payload);
|
||||||
|
setStatusMessage("Post updated successfully");
|
||||||
|
setFormData(resp);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onError = (data) => {
|
||||||
|
console.error("error, data:");
|
||||||
|
console.log(data);
|
||||||
|
}
|
||||||
|
const onChange = (data) => setFormData(data.formData);
|
||||||
|
const onFocus = () => setStatusMessage(null);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const title = formData?.id
|
||||||
|
? `Edit Ad "${formData.title_fi}"`
|
||||||
|
: "Create Ad";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="post-create-page">
|
||||||
|
<Helmet>
|
||||||
|
<link rel="canonical" href="https://sik.ayy.fi/admin/jobads/create" />
|
||||||
|
</Helmet>
|
||||||
|
<h1>{title}</h1>
|
||||||
|
{statusMessage && <div className="success">{statusMessage}</div>}
|
||||||
|
<Form
|
||||||
|
schema={buildSchema(formData?.id ? formData.title_fi : "New Post") as any}
|
||||||
|
uiSchema={buildUISchema(formData)}
|
||||||
|
formData={formData}
|
||||||
|
idPrefix="rjsf"
|
||||||
|
widgets={widgets as any}
|
||||||
|
onChange={onChange}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
onError={onError}
|
||||||
|
onFocus={onFocus} />
|
||||||
|
{error && <div className="error">{error}</div>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default JobAdCreatePage;
|
||||||
@@ -25,6 +25,9 @@ import StudiesPage from "./pages/StudiesPage";
|
|||||||
import CorporatePage from "./pages/CorporatePage";
|
import CorporatePage from "./pages/CorporatePage";
|
||||||
import InEnglishPage from "./pages/InEnglishPage";
|
import InEnglishPage from "./pages/InEnglishPage";
|
||||||
import EventPage from "./pages/EventPage";
|
import EventPage from "./pages/EventPage";
|
||||||
|
import JobAd from "@views/CorporatePage/JobAdView";
|
||||||
|
import AdminJobAdPage from "@pages/admin/AdminJobAdPage";
|
||||||
|
import JobAdCreatePage from "@pages/admin/JobAdCreatePage";
|
||||||
|
|
||||||
const renderPage = (Page) => (props): JSX.Element => {
|
const renderPage = (Page) => (props): JSX.Element => {
|
||||||
return <CommonPage page={Page} {...props} />;
|
return <CommonPage page={Page} {...props} />;
|
||||||
@@ -49,6 +52,7 @@ const commonRoutes = [
|
|||||||
{ path: "/opinnot_ja_ura", page: StudiesPage },
|
{ path: "/opinnot_ja_ura", page: StudiesPage },
|
||||||
{ path: "/yritysyhteistyo", page: CorporatePage },
|
{ path: "/yritysyhteistyo", page: CorporatePage },
|
||||||
{ path: "/yhteystiedot", page: ContactsPage },
|
{ path: "/yhteystiedot", page: ContactsPage },
|
||||||
|
{ path: "/jobads/:id", page: JobAd },
|
||||||
{ path: "/in_english", page: InEnglishPage },
|
{ path: "/in_english", page: InEnglishPage },
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -66,6 +70,9 @@ const adminRoutes = [
|
|||||||
{ path: "/admin/signups", page: AdminSignupPage },
|
{ path: "/admin/signups", page: AdminSignupPage },
|
||||||
{ path: "/admin/signups/create", page: SignupCreatePage },
|
{ path: "/admin/signups/create", page: SignupCreatePage },
|
||||||
{ path: "/admin/signups/:id", page: SignupCreatePage },
|
{ path: "/admin/signups/:id", page: SignupCreatePage },
|
||||||
|
{ path: "/admin/jobads", page: AdminJobAdPage },
|
||||||
|
{ path: "/admin/jobads/create", page: JobAdCreatePage },
|
||||||
|
{ path: "/admin/jobads/:id", page: JobAdCreatePage },
|
||||||
{ path: "/admin/logout", page: AdminLogoutPage },
|
{ path: "/admin/logout", page: AdminLogoutPage },
|
||||||
{ path: "/admin", page: AdminFrontPage },
|
{ path: "/admin", page: AdminFrontPage },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import CorporatePageHero from "./CorporatePageHero";
|
import CorporatePageHero from "./CorporatePageHero";
|
||||||
import { CTASection, TextSection, PageLink } from "@components/index";
|
import { CTASection, TextSection, PageLink } from "@components/index";
|
||||||
|
import { JobAd } from "@models/JobAd";
|
||||||
|
import JobAdList from "./JobAdList";
|
||||||
|
|
||||||
const CorporatePageView: React.FC = () => (
|
interface CorporatePageViewProps {
|
||||||
|
jobAds: JobAd[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const CorporatePageView: React.FC<CorporatePageViewProps> = ({ jobAds }) => (
|
||||||
<>
|
<>
|
||||||
<CorporatePageHero />
|
<CorporatePageHero />
|
||||||
<TextSection>
|
<TextSection>
|
||||||
@@ -66,6 +72,7 @@ const CorporatePageView: React.FC = () => (
|
|||||||
<TextSection>
|
<TextSection>
|
||||||
<h3 id="tyopaikat">Työpaikkailmoitukset</h3>
|
<h3 id="tyopaikat">Työpaikkailmoitukset</h3>
|
||||||
<div>
|
<div>
|
||||||
|
<JobAdList jobAds={jobAds} />
|
||||||
<p>Voit saada yrityksesi työpaikkailmoituksen listalle lähettämällä sen osoitteeseen <a style={{color: "black"}} href="mailto:sik-yritys@list.ayy.fi">sik-yritys@list.ayy.fi</a> </p>
|
<p>Voit saada yrityksesi työpaikkailmoituksen listalle lähettämällä sen osoitteeseen <a style={{color: "black"}} href="mailto:sik-yritys@list.ayy.fi">sik-yritys@list.ayy.fi</a> </p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import React from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import { JobAd } from "@models/JobAd";
|
||||||
|
import Anchor from "@components/Anchor";
|
||||||
|
|
||||||
|
interface JobAdListProps {
|
||||||
|
jobAds: JobAd[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const List = styled.ul`
|
||||||
|
li {
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const JobAdList: React.FC<JobAdListProps> = ({ jobAds }) => (
|
||||||
|
<List>
|
||||||
|
{jobAds.map((ad) => (
|
||||||
|
<li key={ad.id}>
|
||||||
|
<Anchor to={`/jobads/${ad.id}`}>
|
||||||
|
{ad.title_fi}
|
||||||
|
</Anchor>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
export default JobAdList;
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
import React from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import { Helmet } from "react-helmet";
|
||||||
|
import { RouteComponentProps } from "react-router-dom";
|
||||||
|
import ReactMarkdown from "react-markdown";
|
||||||
|
import { colors } from "@theme/colors";
|
||||||
|
import useFetchJobAd from "@hooks/useFetchJobAd";
|
||||||
|
|
||||||
|
const StyledSection = styled.section`
|
||||||
|
margin: 2rem auto;
|
||||||
|
max-width: 1000px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
& > h1 {
|
||||||
|
color: ${colors.darkBlue};
|
||||||
|
}
|
||||||
|
|
||||||
|
& > div > img {
|
||||||
|
height: auto;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > p {
|
||||||
|
color: ${colors.orange1};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Content = styled.div`
|
||||||
|
margin-top: 24px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: ${colors.black};
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h3 {
|
||||||
|
color: ${colors.orange2};
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: ${colors.blue1};
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: ${colors.lightBlue};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
tr {
|
||||||
|
vertical-align: top;
|
||||||
|
|
||||||
|
td {
|
||||||
|
word-break: break-word;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
td:first-of-type {
|
||||||
|
word-break: unset;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
|
||||||
|
interface MatchParams {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type JobAdProps = RouteComponentProps<MatchParams>
|
||||||
|
|
||||||
|
const JobAdView: React.FC<JobAdProps> = ({ match: { params: { id } } }) => {
|
||||||
|
const jobAd = useFetchJobAd(Number(id));
|
||||||
|
if (!jobAd) return <div>Loading</div>
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Helmet>
|
||||||
|
<link rel="canonical" href="https://sik.ayy.fi/INSERT_PATH_HERE!" />
|
||||||
|
</Helmet>
|
||||||
|
|
||||||
|
<StyledSection>
|
||||||
|
<h1>{jobAd.title_fi}</h1>
|
||||||
|
<p>{jobAd.description_fi}</p>
|
||||||
|
<Content>
|
||||||
|
<ReactMarkdown source={jobAd.content_fi} escapeHtml={false} />
|
||||||
|
</Content>
|
||||||
|
</StyledSection>
|
||||||
|
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default JobAdView;
|
||||||
@@ -30,6 +30,9 @@
|
|||||||
"@components/*": [
|
"@components/*": [
|
||||||
"src/components/*"
|
"src/components/*"
|
||||||
],
|
],
|
||||||
|
"@hooks/*": [
|
||||||
|
"src/hooks/*"
|
||||||
|
],
|
||||||
"@models/*": [
|
"@models/*": [
|
||||||
"src/models/*"
|
"src/models/*"
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user