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 { Helmet } from "react-helmet";
|
||||
import CorporatePageView from "@views/CorporatePage/CorporatePageView";
|
||||
import useFetchJobAds from "@hooks/useFetchJobAds";
|
||||
|
||||
export interface CorporatePageProps {}
|
||||
export interface CorporatePageState {}
|
||||
|
||||
class CorporatePage extends React.Component<CorporatePageProps, CorporatePageState> {
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<link rel="canonical" href="https://sik.ayy.fi/yritysyhteistyo" />
|
||||
</Helmet>
|
||||
<CorporatePageView />
|
||||
</>
|
||||
);
|
||||
}
|
||||
const CorporatePage: React.FC = () => {
|
||||
const jobAds = useFetchJobAds();
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<link rel="canonical" href="https://sik.ayy.fi/yritysyhteistyo" />
|
||||
</Helmet>
|
||||
<CorporatePageView jobAds={jobAds} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default CorporatePage;
|
||||
|
||||
@@ -4,7 +4,6 @@ import Anchor from "@components/Anchor";
|
||||
import "./AdminFeedPage.scss";
|
||||
import { StaticContext } from "@server/StaticContext";
|
||||
import { Post, getFeed } from "@models/Feed";
|
||||
import { getEvents } from "@models/Event";
|
||||
import { formatRelative } from "date-fns";
|
||||
import { th } from "date-fns/esm/locale";
|
||||
import AddIcon from "@assets/img/add-icon.png";
|
||||
|
||||
@@ -16,6 +16,7 @@ class AdminFrontPage extends React.Component<AdminFrontPageProps, AdminFrontPage
|
||||
<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>
|
||||
);
|
||||
|
||||
@@ -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 { Helmet } from "react-helmet";
|
||||
import "./FeedCreatePage.scss";
|
||||
import { isAuthenticated } from "@utils/auth";
|
||||
import Form from "react-jsonschema-form";
|
||||
import { RouteComponentProps } from "react-router-dom";
|
||||
import "./FeedCreatePage.scss";
|
||||
import { Tag, getTags } from "@models/Tag";
|
||||
import { createPost, getPost, updatePost } from "@models/Feed";
|
||||
import DatetimeWidget from "@components/Widgets/DatetimeWidget/DatetimeWidget";
|
||||
@@ -11,16 +11,12 @@ const widgets = {
|
||||
datetime: DatetimeWidget,
|
||||
};
|
||||
|
||||
export interface FeedCreatePageProps {
|
||||
history: {
|
||||
push: (to: string) => void;
|
||||
};
|
||||
match: {
|
||||
params: {
|
||||
id?: number;
|
||||
};
|
||||
};
|
||||
interface MatchParams {
|
||||
id?: string;
|
||||
}
|
||||
|
||||
type FeedCreatePageProps = RouteComponentProps<MatchParams>;
|
||||
|
||||
export interface FeedCreatePageState {
|
||||
tags: Tag[];
|
||||
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 InEnglishPage from "./pages/InEnglishPage";
|
||||
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 => {
|
||||
return <CommonPage page={Page} {...props} />;
|
||||
@@ -49,6 +52,7 @@ const commonRoutes = [
|
||||
{ path: "/opinnot_ja_ura", page: StudiesPage },
|
||||
{ path: "/yritysyhteistyo", page: CorporatePage },
|
||||
{ path: "/yhteystiedot", page: ContactsPage },
|
||||
{ path: "/jobads/:id", page: JobAd },
|
||||
{ path: "/in_english", page: InEnglishPage },
|
||||
];
|
||||
|
||||
@@ -66,6 +70,9 @@ const adminRoutes = [
|
||||
{ path: "/admin/signups", page: AdminSignupPage },
|
||||
{ path: "/admin/signups/create", 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", page: AdminFrontPage },
|
||||
];
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import React from "react";
|
||||
import CorporatePageHero from "./CorporatePageHero";
|
||||
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 />
|
||||
<TextSection>
|
||||
@@ -66,6 +72,7 @@ const CorporatePageView: React.FC = () => (
|
||||
<TextSection>
|
||||
<h3 id="tyopaikat">Työpaikkailmoitukset</h3>
|
||||
<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>
|
||||
</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/*": [
|
||||
"src/components/*"
|
||||
],
|
||||
"@hooks/*": [
|
||||
"src/hooks/*"
|
||||
],
|
||||
"@models/*": [
|
||||
"src/models/*"
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user