iceholidays-frontend 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/stylesheets/iceholidays/frontend/_slick-theme.scss +194 -0
  3. data/app/assets/stylesheets/iceholidays/frontend/_slick.scss +100 -0
  4. data/app/assets/stylesheets/iceholidays/frontend/application.sass.scss +56 -3
  5. data/app/assets/stylesheets/iceholidays/frontend/common.scss +14 -0
  6. data/app/assets/stylesheets/iceholidays/frontend/layout.scss +11 -2
  7. data/app/assets/stylesheets/iceholidays/frontend/utils/_slick_overrides.scss +3 -0
  8. data/app/javascript/api-services/brochure-api.service.ts +17 -0
  9. data/app/javascript/api-services/contact-us-api.service.ts +19 -0
  10. data/app/javascript/api-services/posts-api.service.ts +47 -0
  11. data/app/javascript/api-services/testimonials-api.service.ts +1 -1
  12. data/app/javascript/interfaces/blog.interface.ts +8 -0
  13. data/app/javascript/interfaces/testimonial.interface.ts +1 -1
  14. data/app/javascript/react/App.tsx +1 -1
  15. data/app/javascript/react/components/Destinations.tsx +21 -10
  16. data/app/javascript/react/components/Testimonials.tsx +5 -2
  17. data/app/javascript/react/components/shared/LocationDropdown.tsx +3 -1
  18. data/app/javascript/react/index.js +2 -1
  19. data/app/javascript/react/layouts/MainFooter.tsx +43 -35
  20. data/app/javascript/react/layouts/MainHeader.tsx +80 -32
  21. data/app/javascript/react/layouts/MainLayout.tsx +1 -1
  22. data/app/javascript/react/pages/BlogPage.tsx +82 -66
  23. data/app/javascript/react/pages/BlogShowPage.tsx +50 -35
  24. data/app/javascript/react/pages/ContactUsPage.tsx +128 -101
  25. data/app/javascript/react/pages/ListingPage.tsx +42 -18
  26. data/app/javascript/react/pages/ShowPage.tsx +84 -52
  27. data/app/javascript/react/widgets/FilterPills.tsx +21 -18
  28. data/app/views/iceholidays/frontend/site/index.html.erb +7 -1
  29. data/config/routes.rb +1 -1
  30. data/lib/iceholidays/frontend/version.rb +1 -1
  31. data/public/iceholidays-assets/application.css +304 -9
  32. data/public/iceholidays-assets/application.js +161 -121
  33. data/public/iceholidays-assets/application.js.map +4 -4
  34. data/public/iceholidays-assets/images/TST Ribbon@2x.png +0 -0
  35. data/public/iceholidays-assets/images/TST Ribbon@3x.png +0 -0
  36. metadata +11 -2
@@ -7,6 +7,9 @@ import RibbonSection from "./shared/RibbonSection";
7
7
  import TestimonialsApi from "../../api-services/testimonials-api.service";
8
8
  import { Testimonial } from "../../interfaces/testimonial.interface";
9
9
  import createDOMPurify from 'dompurify'
10
+ import Markdown from "react-markdown";
11
+ import rehypeRaw from "rehype-raw";
12
+ import remarkGfm from "remark-gfm";
10
13
 
11
14
  const DOMPurify = createDOMPurify(window)
12
15
 
@@ -50,11 +53,11 @@ export default class Testimonials extends React.Component {
50
53
  <div className="tour">
51
54
  <img src="/iceholidays-assets/images/logomark.png" className="st-logo"/>
52
55
  <div>
53
- <span> {review.tour} </span>
56
+ <span> {review.title} </span>
54
57
  </div>
55
58
  </div>
56
59
  <div className="comment">
57
- <span dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(review.comment) }} />
60
+ <Markdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]}>{review.comment}</Markdown>
58
61
  </div>
59
62
  <div className="author">
60
63
  <Icon path={mdiFountainPenTip} size={1} className="author-icon" />
@@ -8,10 +8,11 @@ import { mdiFilterVariant } from "@mdi/js";
8
8
  const LocationDropdown = (
9
9
  props: {
10
10
  locations:Location[];
11
+ initialValue:string;
11
12
  selectLocation;
12
13
  }
13
14
  ) => {
14
- const { locations } = props;
15
+ const { locations, initialValue } = props;
15
16
 
16
17
  return (
17
18
  <div className="location-dropdown">
@@ -20,6 +21,7 @@ const LocationDropdown = (
20
21
  suffixIcon={<Icon path={mdiFilterVariant} size="18px" />}
21
22
  variant="borderless"
22
23
  defaultValue="china"
24
+ value={initialValue}
23
25
  options={locations.map((location) => ({label: location.name, value: location.id}))}
24
26
  dropdownStyle={{padding: "15px 20px", borderRadius: "20px", boxShadow: "0px 3.08px 16.47px 0px #00000040"}}
25
27
  optionRender={(option) => (
@@ -1,6 +1,7 @@
1
1
 
2
2
  import App from "./App"
3
3
  import ReactComponent from "./application"
4
+ import MainFooter from "./layouts/MainFooter"
4
5
 
5
6
 
6
- ReactComponent.setup({App})
7
+ ReactComponent.setup({App, MainFooter})
@@ -1,44 +1,52 @@
1
1
  import { Col, Flex, notification, Row, Space } from "antd";
2
2
  import React from "react";
3
- import { Link } from "react-router-dom";
4
3
  import LocationsApi from "../../api-services/locations-api.service";
5
4
  import { Country } from "../../interfaces/country.interface";
6
5
 
7
6
 
8
- const menus = [
9
- {
10
- label: "Home",
11
- link: "/app"
12
- },
13
- {
14
- label: "pdpa",
15
- link: "/app"
16
- },
17
- {
18
- label: "about us",
19
- link: "/app/about-us"
20
- },
21
- {
22
- label: "privacy policy",
23
- link: "/app"
24
- },
25
- {
26
- label: "contact us",
27
- link: "/app/contact-us"
28
- },
29
- {
30
- label: "terms & conditions",
31
- link: "/app"
32
- },
33
- ]
7
+ interface IMainFooterProps {
8
+ PDPA:string;
9
+ PRIVACY:string;
10
+ TNC:string;
11
+ }
34
12
 
35
- export default class MainFooter extends React.Component {
13
+ export default class MainFooter extends React.Component<IMainFooterProps> {
36
14
  api = new LocationsApi;
37
15
 
38
16
  state = {
39
17
  countries: []
40
18
  }
41
19
 
20
+ menus = [
21
+ {
22
+ label: "Home",
23
+ link: "/app"
24
+ },
25
+ {
26
+ label: "pdpa",
27
+ link: this.props.PDPA,
28
+ target: "_blank"
29
+ },
30
+ {
31
+ label: "about us",
32
+ link: "/app/about-us"
33
+ },
34
+ {
35
+ label: "privacy policy",
36
+ link: this.props.PRIVACY,
37
+ target: "_blank"
38
+ },
39
+ {
40
+ label: "contact us",
41
+ link: "/app/contact-us"
42
+ },
43
+ {
44
+ label: "terms & conditions",
45
+ link: this.props.TNC,
46
+ target: "_blank"
47
+ },
48
+ ]
49
+
42
50
  componentDidMount() {
43
51
  this.api.getCountries()
44
52
  .then(locationsData => {
@@ -58,7 +66,7 @@ export default class MainFooter extends React.Component {
58
66
  <div className="link-group-title">Countries</div>
59
67
  <div className="link-group-items">
60
68
  {
61
- this.state.countries.map((country: Country)=> <Link key={country.id} to={`/app/listing?keyword=${country.name}`}>{country.name}</Link> )
69
+ this.state.countries.map((country: Country)=> <a key={country.id} href={`/app/listing?location_id=${country.id}`} target="_self"> {country.name} </a> )
62
70
  }
63
71
  </div>
64
72
  </Space>
@@ -67,7 +75,7 @@ export default class MainFooter extends React.Component {
67
75
  <Space direction="vertical">
68
76
  <div className="link-group-items">
69
77
  {
70
- menus.map(menu => <Link to={menu.link}>{menu.label}</Link>)
78
+ this.menus.map((menu, index) =><a key={index} target={menu.target} href={menu.link}> {menu.label} </a>)
71
79
  }
72
80
  </div>
73
81
  </Space>
@@ -76,13 +84,13 @@ export default class MainFooter extends React.Component {
76
84
  <Space direction="vertical" align="center">
77
85
  <Flex vertical gap="20px" align="center">
78
86
  <div className="link-group-title">Contact Us</div>
79
- <div><Link to="/app/contact-agents">contact agents</Link></div>
87
+ <div><a href="/app/contact-agents">contact agents</a></div>
80
88
  <div className="link-group-title">Follow us on</div>
81
89
  <Flex gap={20} justify="space-between">
82
- <img src="/iceholidays-assets/images/social/ico_fb.png"/>
83
- <img src="/iceholidays-assets/images/social/ico_twitter.png"/>
84
- <img src="/iceholidays-assets/images/social/ico_yt.png"/>
85
- <img src="/iceholidays-assets/images/social/ico_ig.png"/>
90
+ <a target="_blank" href="https://www.facebook.com/thesignaturetour"><img src="/iceholidays-assets/images/social/ico_fb.png"/></a>
91
+ <a target="_blank" style={{pointerEvents: "none"}} href="javascript:void(0)"><img src="/iceholidays-assets/images/social/ico_twitter.png"/></a>
92
+ <a target="_blank" style={{pointerEvents: "none"}} href="javascript:void(0)"><img src="/iceholidays-assets/images/social/ico_yt.png"/></a>
93
+ <a target="_blank" href="https://www.instagram.com/thesignaturetours"><img src="/iceholidays-assets/images/social/ico_ig.png"/></a>
86
94
  </Flex>
87
95
  </Flex>
88
96
  </Space>
@@ -1,9 +1,10 @@
1
1
  import { mdiFileDownload, mdiMenu } from "@mdi/js";
2
2
  import Icon from "@mdi/react";
3
- import { Button, Drawer, Menu } from "antd";
3
+ import { Button, Drawer, Menu, notification } from "antd";
4
4
  import React, { useState } from "react";
5
5
  import type { MenuProps } from 'antd';
6
6
  import { Link, useLocation, useMatch } from "react-router-dom";
7
+ import BrochueApi from "../../api-services/brochure-api.service";
7
8
 
8
9
  type MenuItem = Required<MenuProps>['items'][number];
9
10
 
@@ -40,44 +41,91 @@ const items: MenuItem[] = [
40
41
  },
41
42
  ]
42
43
 
43
- const MainHeader = () => {
44
- const [open, setOpen] = useState(false);
45
- const location = useLocation();
46
- const activeUrlKey = location.pathname.split("/").pop() || "app";
47
- const [current, setCurrent] = useState(activeUrlKey);
48
44
 
49
- const onClick: MenuProps['onClick'] = (e) => {
50
- setCurrent(e.key);
51
- setOpen(false);
52
- };
45
+ function withLocation(Component) {
46
+ return props => <Component {...props} location={useLocation()} />;
47
+ }
48
+
49
+ class MainHeader extends React.Component <{location}> {
50
+ brochureApi = new BrochueApi;
51
+
52
+ state = {
53
+ open: false,
54
+ current: "",
55
+ }
56
+
57
+ constructor(props:any){
58
+ super(props);
59
+ }
60
+
61
+
62
+ componentDidMount() {
63
+ this.setLocation();
64
+ }
65
+
66
+
67
+ downloadBrochure = () => {
68
+ this.brochureApi.getBrochure()
69
+ .then((brochureUrl:any) => {
70
+ // const blob = brochureUrl.blob();
71
+ // const blobURL = URL.createObjectURL(blob);
72
+ var a = document.createElement("a");
73
+ a.href = brochureUrl;
74
+ a.setAttribute('style', "display: none");
75
+
76
+ a.download = "Brochure";
77
+ document.body.appendChild(a);
78
+ a.click();
79
+ })
80
+ .catch(error => {
81
+ notification.error({ message: 'An error occured while getting brochure.'});
82
+ });
83
+
84
+ }
53
85
 
54
- const showDrawer = () => {
55
- setOpen(true);
56
- };
57
86
 
58
- const onClose = () => {
59
- setOpen(false);
87
+ private setLocation(){
88
+ const location = this.props.location
89
+ const activeUrlKey = location.pathname.split("/").pop() || "app";
90
+ this.setState({current: activeUrlKey});
91
+ }
92
+
93
+
94
+ onClick: MenuProps['onClick'] = (e) => {
95
+ this.setState({open: false, current: e.key})
60
96
  };
61
-
62
- return (
63
- <div id="main-header">
64
- <Menu mode="horizontal" items={items} onClick={onClick} selectedKeys={[current]} className="nav-menu"/>
65
97
 
66
- <a href="/app"><img src="/iceholidays-assets/images/logo_mobile.png" className="logo logo_mobile"/></a>
98
+ showDrawer() {
99
+ this.setState({open: true});
100
+ };
67
101
 
68
- <div id="nav-buttons">
69
- <Button id="get-brochure" type="primary"><Icon path={mdiFileDownload} size="18px" /> get brochure</Button>
102
+ onClose() {
103
+ this.setState({open: false});
104
+ };
70
105
 
71
- <Button id="toggle-menu" type="primary" onClick={showDrawer}>
72
- <Icon path={mdiMenu} size="25px" />
73
- </Button>
106
+ render(){
107
+ const {open, current } = this.state;
108
+ return (
109
+ <div id="main-header">
110
+ <Menu mode="horizontal" items={items} onClick={this.onClick} selectedKeys={[current]} className="nav-menu"/>
111
+
112
+ <a href="/app"><img src="/iceholidays-assets/images/logo_mobile.png" className="logo logo_mobile"/></a>
113
+
114
+ <div id="nav-buttons">
115
+ <Button onClick={this.downloadBrochure} id="get-brochure" type="primary"><Icon path={mdiFileDownload} size="18px" /> get brochure</Button>
116
+
117
+ <Button id="toggle-menu" type="primary" onClick={()=>this.showDrawer()}>
118
+ <Icon path={mdiMenu} size="25px" />
119
+ </Button>
120
+ </div>
121
+
122
+ <Drawer title={null} onClose={()=>this.onClose()} open={open} id="nav-drawer">
123
+ <Menu items={items} onClick={this.onClick} selectedKeys={[current]} className="nav-menu_mobile"/>
124
+ </Drawer>
74
125
  </div>
75
-
76
- <Drawer title={null} onClose={onClose} open={open} id="nav-drawer">
77
- <Menu items={items} onClick={onClick} selectedKeys={[current]} className="nav-menu_mobile"/>
78
- </Drawer>
79
- </div>
80
- )
126
+ )
127
+ }
128
+
81
129
  }
82
130
 
83
- export default MainHeader;
131
+ export default withLocation(MainHeader);
@@ -13,7 +13,7 @@ const MainLayout = () => {
13
13
  <Content>
14
14
  <Outlet/>
15
15
  </Content>
16
- <MainFooter/>
16
+ {/* <MainFooter/> */}
17
17
  </Layout>
18
18
  )
19
19
  };
@@ -1,81 +1,97 @@
1
1
  import React from "react";
2
2
  import Headline from "../components/shared/Headline";
3
- import { Button, Card, Col, Row, Space } from "antd";
3
+ import { Button, Card, Col, notification, Row, Space } from "antd";
4
4
  import FilterPills from "../widgets/FilterPills";
5
- import { Link } from "react-router-dom";
5
+ import PostsApi from "../../api-services/posts-api.service";
6
+ import { Blog } from "../../interfaces/blog.interface";
7
+ import Markdown from "react-markdown";
8
+ import rehypeRaw from "rehype-raw";
9
+ import remarkGfm from "remark-gfm";
6
10
  const { Meta } = Card;
7
11
 
8
12
  const bannerPath = '/iceholidays-assets/images/blog.png';
9
- const breadcrumbs = [
10
- { title: 'Home' },
11
- { title: 'Blog' }
13
+ const _breadcrumbs = [
14
+ { title: <a href="/app">Home</a> },
15
+ { title: <a href="/app/blog">Blog</a> }
12
16
  ]
13
17
 
14
- const types = [ "News", "Blogs" ];
15
- const blogs = [
16
- {
17
- id: 1,
18
- cover: "/iceholidays-assets/images/blog1.png",
19
- title: "旅游的意义:探索世界与内心的旅程",
20
- info: "未分类 / 发表评论 / 2024年11月22日",
21
- summary: "旅游不仅拓展视野,也带来人生的启示。它教会我们谦逊、适应变化、享受过程,激发内心力量,感恩遇见,并帮助我们找回初心。"
22
- },
23
- {
24
- id: 2,
25
- cover: "/iceholidays-assets/images/blog1.png",
26
- title: "旅游的意义:探索世界与内心的旅程",
27
- info: "未分类 / 发表评论 / 2024年11月22日",
28
- summary: "旅游不仅拓展视野,也带来人生的启示。它教会我们谦逊、适应变化、享受过程,激发内心力量,感恩遇见,并帮助我们找回初心。"
29
- },
30
- {
31
- id: 3,
32
- cover: "/iceholidays-assets/images/blog1.png",
33
- title: "旅游的意义:探索世界与内心的旅程",
34
- info: "未分类 / 发表评论 / 2024年11月22日",
35
- summary: "旅游不仅拓展视野,也带来人生的启示。它教会我们谦逊、适应变化、享受过程,激发内心力量,感恩遇见,并帮助我们找回初心。"
36
- },
37
- ]
18
+ const types = [ {type: "News", label: "News"},{type: "Blogs", label:"Blogs"} ];
38
19
 
39
- const setFilter = (selectedFilter) => {
40
- console.log("set filter", selectedFilter)
41
- }
42
20
 
21
+ export default class BlogPage extends React.Component {
22
+ postsApi = new PostsApi;
43
23
 
44
- const BlogPage = React.FC = () => (
45
- <div id="blog-page">
46
- <Headline bannerImage={bannerPath} breadcrumbs={breadcrumbs} title="Blog"/>
24
+ state = {
25
+ blogs: [],
26
+ breadcrumbs: _breadcrumbs,
27
+ searchParamsObj: {type: "", year: "", month: ""},
28
+ }
47
29
 
48
- <div id="blog-page_body">
49
- <Space size={12} direction="vertical">
50
- <FilterPills items={types} allOption={true} initialValue={{keyword: "", year: "", month: ""}} selectFilter={(selected)=>setFilter(selected)}></FilterPills>
51
- </Space>
30
+ componentDidMount() {
31
+ this.postsApi.getBlogs()
32
+ .then(blogsData => {
33
+ this.setState({blogs: blogsData})
34
+ })
35
+ .catch(error => {
36
+ notification.error({ message: 'An error occured while loading blogs.'});
37
+ });
38
+ }
52
39
 
53
- <div id="blogs">
54
- <Row wrap gutter={20}>
55
- {
56
- blogs.map(blog => (
57
- <Col key={blog.id} span={8}>
58
- <Link to={`/app/blog/${blog.id}`}>
59
- <Card
60
- hoverable
61
- cover={<img alt="example" src={blog.cover} />}
62
- >
63
- <Meta title={blog.title} description={
64
- <>
65
- <span>{blog.info}</span>
66
- <p>{blog.summary}</p>
67
- <Button color="primary" variant="outlined" block>阅读更多</Button>
68
- </>
69
- } />
70
- </Card>
71
- </Link>
72
- </Col>
73
- ))
74
- }
75
- </Row>
40
+ setFilter = (selectedFilter) => {
41
+ const {breadcrumbs} = this.state;
42
+ breadcrumbs[2] = {title: this.searchParamsToText()};
43
+
44
+ const allHasValues = Object.entries(selectedFilter).every(o => o[1] != "" && o[1] != null);
45
+ if(allHasValues){
46
+ breadcrumbs[1] = {title: <span>Search results</span>};
47
+ }
48
+ this.setState({searchParamsObj: selectedFilter, breadcrumbs})
49
+ }
50
+
51
+
52
+ searchParamsToText() {
53
+ var {type, year, month} = this.state.searchParamsObj;
54
+ const monthYear = (month || year) && `${month} ${year}`;
55
+ const text = [type, monthYear].filter(val => val).join(", ");
56
+ return <span>{ text } </span>
57
+ }
58
+
59
+ render(){
60
+ const {blogs, breadcrumbs, searchParamsObj} = this.state;
61
+ return (
62
+ <div id="blog-page">
63
+ <Headline bannerImage={bannerPath} breadcrumbs={breadcrumbs} title="Blog"/>
64
+
65
+ <div id="blog-page_body">
66
+ <Space size={12} direction="vertical">
67
+ <FilterPills items={types} bindValue="type" allOption={true} initialValue={searchParamsObj} selectFilter={(selected)=>this.setFilter(selected)}></FilterPills>
68
+ </Space>
69
+
70
+ <div id="blogs">
71
+ <Row wrap gutter={20}>
72
+ {
73
+ blogs.map((blog:Blog) => (
74
+ <Col key={blog.id} span={8}>
75
+ <Card cover={<img alt={blog.title} src={blog.thumbnail} />}>
76
+ <Meta title={blog.title} description={
77
+ <>
78
+ {/* <span>{blog.info}</span> */}
79
+ <div className="preview-content">
80
+ <Markdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]}>{blog.content}</Markdown>
81
+ </div>
82
+
83
+ <Button className="view-blog" href={`/app/blog/${blog.id}`} type="link" color="primary" variant="outlined" block>阅读更多</Button>
84
+ </>
85
+ } />
86
+ </Card>
87
+ </Col>
88
+ ))
89
+ }
90
+ </Row>
91
+ </div>
92
+ </div>
76
93
  </div>
77
- </div>
78
- </div>
79
- );
94
+ )
95
+ }
80
96
 
81
- export default BlogPage;
97
+ }
@@ -1,43 +1,58 @@
1
1
  import React from "react";
2
2
  import Headline from "../components/shared/Headline";
3
+ import PostsApi from "../../api-services/posts-api.service";
4
+ import { useParams } from "react-router-dom";
5
+ import { notification } from "antd";
6
+ import Markdown from "react-markdown";
7
+ import rehypeRaw from "rehype-raw";
8
+ import remarkGfm from "remark-gfm";
3
9
 
4
10
  const bannerPath = '/iceholidays-assets/images/blog.png';
5
- const breadcrumbs = [
11
+ const _breadcrumbs = [
6
12
  { title: 'Home' },
7
13
  { title: 'Blog' },
8
- { title: '从斯里兰卡到马尔代夫' },
9
14
  ]
10
15
 
11
- const types = [ "News", "Blogs" ];
12
- const blogs = [
13
- {
14
- cover: "/iceholidays-assets/images/blog1.png",
15
- title: "旅游的意义:探索世界与内心的旅程",
16
- info: "未分类 / 发表评论 / 2024年11月22日",
17
- summary: "旅游不仅拓展视野,也带来人生的启示。它教会我们谦逊、适应变化、享受过程,激发内心力量,感恩遇见,并帮助我们找回初心。"
18
- },
19
- {
20
- cover: "/iceholidays-assets/images/blog1.png",
21
- title: "旅游的意义:探索世界与内心的旅程",
22
- info: "未分类 / 发表评论 / 2024年11月22日",
23
- summary: "旅游不仅拓展视野,也带来人生的启示。它教会我们谦逊、适应变化、享受过程,激发内心力量,感恩遇见,并帮助我们找回初心。"
24
- },
25
- {
26
- cover: "/iceholidays-assets/images/blog1.png",
27
- title: "旅游的意义:探索世界与内心的旅程",
28
- info: "未分类 / 发表评论 / 2024年11月22日",
29
- summary: "旅游不仅拓展视野,也带来人生的启示。它教会我们谦逊、适应变化、享受过程,激发内心力量,感恩遇见,并帮助我们找回初心。"
30
- },
31
- ]
32
-
33
- const BlogShowPage = React.FC = () => (
34
- <div id="blog-show-page">
35
- <Headline bannerImage={bannerPath} breadcrumbs={breadcrumbs} title="Blog"/>
36
-
37
- <div id="blog-page_body">
38
-
39
- </div>
40
- </div>
41
- );
42
-
43
- export default BlogShowPage;
16
+ function withParams(Component) {
17
+ return props => <Component {...props} params={useParams()} />;
18
+ }
19
+
20
+ class BlogShowPage extends React.Component <{params;}> {
21
+ postsApi = new PostsApi;
22
+
23
+ state = {
24
+ breadcrumbs: _breadcrumbs,
25
+ blog: {title: "", thumbnail: "", content: ""},
26
+ }
27
+
28
+ componentDidMount() {
29
+ this.postsApi.getBlog(this.props.params.id)
30
+ .then(blogData => {
31
+ var breadcrumbs = this.state.breadcrumbs;
32
+ breadcrumbs.push({title: blogData.title});
33
+ this.setState({blog: blogData, breadcrumbs})
34
+ })
35
+ .catch(error => {
36
+ notification.error({ message: 'An error occured while loading blog.'});
37
+ });
38
+ }
39
+
40
+
41
+ render(){
42
+ const {blog} = this.state;
43
+ return (
44
+ <div id="blog-show-page">
45
+ <Headline bannerImage={bannerPath} breadcrumbs={this.state.breadcrumbs} title="Blog"/>
46
+
47
+ <div id="blog-show-page_body">
48
+ <h1>{blog.title}</h1>
49
+ <img src={blog.thumbnail}/>
50
+ <Markdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]}>{blog.content}</Markdown>
51
+ </div>
52
+ </div>
53
+ )
54
+ }
55
+ }
56
+
57
+
58
+ export default withParams(BlogShowPage);