@openstack_dev/gatsby-theme-marketing-oif-core 1.0.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 (93) hide show
  1. package/.eslintrc.json +27 -0
  2. package/.github/workflows/eslint.yml +15 -0
  3. package/.husky/pre-commit +4 -0
  4. package/.nvmrc +1 -0
  5. package/LICENSE.md +201 -0
  6. package/README.md +10 -0
  7. package/babel.config.json +12 -0
  8. package/gatsby-browser.js +5 -0
  9. package/gatsby-config.js +217 -0
  10. package/gatsby-node.js +162 -0
  11. package/gatsby-ssr.js +50 -0
  12. package/package.json +154 -0
  13. package/src/cms/cms-utils.js +8 -0
  14. package/src/cms/cms.js +12 -0
  15. package/src/cms/config/collections/configurationsCollection/announcementBanner/index.js +59 -0
  16. package/src/cms/config/collections/configurationsCollection/announcementBanner/typeDefs.js +11 -0
  17. package/src/cms/config/collections/configurationsCollection/footer/index.js +158 -0
  18. package/src/cms/config/collections/configurationsCollection/footer/typeDefs.js +37 -0
  19. package/src/cms/config/collections/configurationsCollection/index.js +16 -0
  20. package/src/cms/config/collections/configurationsCollection/navbar/index.js +62 -0
  21. package/src/cms/config/collections/configurationsCollection/navbar/typeDefs.js +13 -0
  22. package/src/cms/config/collections/configurationsCollection/siteSettings/index.js +117 -0
  23. package/src/cms/config/collections/configurationsCollection/siteSettings/typeDefs.js +15 -0
  24. package/src/cms/config/collections/configurationsCollection/typeDefs.js +9 -0
  25. package/src/cms/config/collections/typeDefs.js +5 -0
  26. package/src/cms/config/fields.js +268 -0
  27. package/src/cms/config/index.js +35 -0
  28. package/src/cms/config/patterns.js +51 -0
  29. package/src/cms/preview-templates/.gitkeep +0 -0
  30. package/src/cms/widgets/.gitkeep +0 -0
  31. package/src/components/AnnouncementBanner/index.js +26 -0
  32. package/src/components/AnnouncementBanner/index.module.scss +131 -0
  33. package/src/components/AnnouncementBanner/template.js +40 -0
  34. package/src/components/Footer/index.js +40 -0
  35. package/src/components/Footer/index.module.scss +59 -0
  36. package/src/components/Footer/template.js +55 -0
  37. package/src/components/Header/index.js +8 -0
  38. package/src/components/Header/template.js +5 -0
  39. package/src/components/Layout.js +32 -0
  40. package/src/components/Link.js +41 -0
  41. package/src/components/Navbar/index.js +460 -0
  42. package/src/components/Navbar/index.module.scss +301 -0
  43. package/src/components/SponsoredProjectsNav/index.js +22 -0
  44. package/src/components/SponsoredProjectsNav/index.module.scss +7 -0
  45. package/src/components/SubscribeForm/index.js +47 -0
  46. package/src/components/SubscribeForm/index.module.scss +114 -0
  47. package/src/components/Tracking/custom-bing-tracker.js +5 -0
  48. package/src/components/Tracking/custom-google-tracker.js +51 -0
  49. package/src/components/head-components.js +20 -0
  50. package/src/components/svgs/RightArrow.jsx +9 -0
  51. package/src/content/announcement-banner/OpenInfrastructureFoundation-icon-RGB.svg +1 -0
  52. package/src/content/announcement-banner/index.json +8 -0
  53. package/src/content/footer/index.json +139 -0
  54. package/src/content/navbar/index.json +304 -0
  55. package/src/content/site-settings/index.json +1 -0
  56. package/src/images/icon.png +0 -0
  57. package/src/images/openstack-logo-full.svg +57 -0
  58. package/src/images/openstack-logo-vert.svg +57 -0
  59. package/src/images/right-arrow.svg +3 -0
  60. package/src/pages/404.js +49 -0
  61. package/src/pages/auth/[...].js +36 -0
  62. package/src/pages/index.js +14 -0
  63. package/src/reducers/.gitkeep +0 -0
  64. package/src/reducers/index.js +5 -0
  65. package/src/routes/.gitkeep +0 -0
  66. package/src/routes/authorization-callback-route.js +71 -0
  67. package/src/routes/login-callback-route.js +62 -0
  68. package/src/routes/logout-callback-route.js +72 -0
  69. package/src/state/.gitkeep +0 -0
  70. package/src/state/ReduxWrapper.js +29 -0
  71. package/src/state/storage.js +21 -0
  72. package/src/state/store.js +43 -0
  73. package/src/templates/.gitkeep +0 -0
  74. package/src/theme.js +36 -0
  75. package/src/utils/cacheUtils.js +48 -0
  76. package/src/utils/cssUtils.js +62 -0
  77. package/src/utils/envVariables.js +52 -0
  78. package/src/utils/expiredToken.js +15 -0
  79. package/src/utils/filePath.js +95 -0
  80. package/static/admin/admin.css +3 -0
  81. package/static/fonts/fonts.css +65 -0
  82. package/static/fonts/nunito-sans/nunito-sans-v12-latin-300.woff +0 -0
  83. package/static/fonts/nunito-sans/nunito-sans-v12-latin-300.woff2 +0 -0
  84. package/static/fonts/nunito-sans/nunito-sans-v12-latin-300italic.woff +0 -0
  85. package/static/fonts/nunito-sans/nunito-sans-v12-latin-300italic.woff2 +0 -0
  86. package/static/fonts/nunito-sans/nunito-sans-v12-latin-600.woff +0 -0
  87. package/static/fonts/nunito-sans/nunito-sans-v12-latin-600.woff2 +0 -0
  88. package/static/fonts/nunito-sans/nunito-sans-v12-latin-600italic.woff +0 -0
  89. package/static/fonts/nunito-sans/nunito-sans-v12-latin-600italic.woff2 +0 -0
  90. package/static/fonts/nunito-sans/nunito-sans-v12-latin-700.woff +0 -0
  91. package/static/fonts/nunito-sans/nunito-sans-v12-latin-700.woff2 +0 -0
  92. package/static/fonts/nunito-sans/nunito-sans-v12-latin-700italic.woff +0 -0
  93. package/static/fonts/nunito-sans/nunito-sans-v12-latin-700italic.woff2 +0 -0
@@ -0,0 +1,117 @@
1
+ import {
2
+ stringField,
3
+ textField,
4
+ imageField,
5
+ selectField,
6
+ selectOption,
7
+ objectField,
8
+ fileField
9
+ } from "../../../fields";
10
+
11
+ import {
12
+ SITE_SETTINGS_FILE_PATH,
13
+ CMS_FONT_FILE_PATH
14
+ } from "@utils/filePath";
15
+
16
+ const FONT_FORMATS = {
17
+ truetype: "ttf",
18
+ opentype: "otf",
19
+ woff: "woff",
20
+ woff2: "woff2",
21
+ eot: "eot"
22
+ };
23
+
24
+ const getFontFormatOptions = () =>
25
+ Object.entries(FONT_FORMATS).map(([key, value]) => selectOption({ label: value, value: value }));
26
+
27
+ const siteSettings = {
28
+ label: "Site Settings",
29
+ name: "site-settings",
30
+ file: SITE_SETTINGS_FILE_PATH,
31
+ fields: [
32
+ objectField({
33
+ label: "Site Metadata",
34
+ name: "siteMetadata",
35
+ fields: [
36
+ stringField({
37
+ label: "Title",
38
+ name: "title",
39
+ required: false
40
+ }),
41
+ textField({
42
+ label: "Description",
43
+ name: "description",
44
+ required: false
45
+ }),
46
+ imageField({
47
+ label: "Image",
48
+ name: "image",
49
+ required: false
50
+ })
51
+ ]
52
+ }),
53
+ objectField({
54
+ label: "Favicon",
55
+ name: "favicon",
56
+ fields: [
57
+ imageField({
58
+ label: "Image for favicon generation (squared, at least 512x512)",
59
+ name: "asset",
60
+ required: false
61
+ })
62
+ ]
63
+ }),
64
+ objectField({
65
+ label: "Site Font",
66
+ name: "siteFont",
67
+ fields: [
68
+ textField({
69
+ label: "Font Name",
70
+ name: "fontFamily",
71
+ required: false,
72
+ default: "Nunito Sans"
73
+ }),
74
+ objectField({
75
+ label: "Regular Font",
76
+ name: "regularFont",
77
+ fields: [
78
+ fileField({
79
+ label: "Font File",
80
+ name: "fontFile",
81
+ required: false,
82
+ media_folder: CMS_FONT_FILE_PATH,
83
+ }),
84
+ selectField({
85
+ label: "Font Format",
86
+ name: "fontFormat",
87
+ multiple: false,
88
+ required: false,
89
+ options: getFontFormatOptions()
90
+ })
91
+ ],
92
+ }),
93
+ objectField({
94
+ label: "Bold Font",
95
+ name: "boldFont",
96
+ fields: [
97
+ fileField({
98
+ label: "Font File",
99
+ name: "fontFile",
100
+ media_folder: CMS_FONT_FILE_PATH,
101
+ required: false,
102
+ }),
103
+ selectField({
104
+ label: "Font Format",
105
+ name: "fontFormat",
106
+ multiple: false,
107
+ required: false,
108
+ options: getFontFormatOptions()
109
+ })
110
+ ],
111
+ }),
112
+ ]
113
+ }),
114
+ ]
115
+ };
116
+
117
+ export default siteSettings;
@@ -0,0 +1,15 @@
1
+
2
+ module.exports = `
3
+ type SiteMetadata {
4
+ title: String
5
+ description: String
6
+ image: File @fileByRelativePath
7
+ }
8
+ type Favicon {
9
+ asset: File @fileByRelativePath
10
+ }
11
+ type SiteSettingsJson implements Node {
12
+ siteMetadata: SiteMetadata
13
+ favicon: Favicon
14
+ }
15
+ `;
@@ -0,0 +1,9 @@
1
+ const siteSettingsTypeDefs = require("./siteSettings/typeDefs");
2
+ const footerTypeDefs = require("./footer/typeDefs");
3
+ const announcementBannerTypeDefs = require("./announcementBanner/typeDefs");
4
+
5
+ module.exports = [
6
+ siteSettingsTypeDefs,
7
+ footerTypeDefs,
8
+ announcementBannerTypeDefs
9
+ ].join("");
@@ -0,0 +1,5 @@
1
+ const configurationsCollectionTypeDefs = require("./configurationsCollection/typeDefs");
2
+
3
+ module.exports = [
4
+ configurationsCollectionTypeDefs,
5
+ ].join("");
@@ -0,0 +1,268 @@
1
+ import {
2
+ imageWithAltFieldset,
3
+ linkImageFieldset
4
+ } from "./patterns";
5
+
6
+ export const hiddenField = ({
7
+ label = "Hidden",
8
+ name = "hidden",
9
+ required = false,
10
+ ...rest
11
+ } = {}) => ({
12
+ label,
13
+ name,
14
+ required,
15
+ widget: "hidden",
16
+ ...rest
17
+ });
18
+
19
+
20
+ export const booleanField = ({
21
+ label = "Boolean",
22
+ name = "boolean",
23
+ required = false,
24
+ ...rest
25
+ } = {}) => ({
26
+ label,
27
+ name,
28
+ required,
29
+ widget: "boolean",
30
+ ...rest
31
+ });
32
+
33
+ export const numberField = ({
34
+ label = "Number",
35
+ name = "number",
36
+ required = false,
37
+ valueType,
38
+ ...rest
39
+ } = {}) => ({
40
+ label,
41
+ name,
42
+ required,
43
+ widget: "number",
44
+ ...(valueType ? { value_type: valueType } : {}),
45
+ ...rest
46
+ });
47
+
48
+ export const stringField = ({
49
+ label = "String",
50
+ name = "string",
51
+ required = false,
52
+ ...rest
53
+ } = {}) => ({
54
+ label,
55
+ name,
56
+ required,
57
+ widget: "string",
58
+ ...rest
59
+ });
60
+
61
+ export const textField = ({
62
+ label = "Text",
63
+ name = "text",
64
+ required = false,
65
+ ...rest
66
+ } = {}) => ({
67
+ label,
68
+ name,
69
+ required,
70
+ widget: "text",
71
+ ...rest
72
+ });
73
+
74
+ export const markdownField = ({
75
+ label = "Markdown",
76
+ name = "markdown",
77
+ required = false,
78
+ ...rest
79
+ } = {}) => ({
80
+ label,
81
+ name,
82
+ required,
83
+ widget: "markdown",
84
+ ...rest
85
+ });
86
+
87
+ export const imageField = ({
88
+ label = "Image",
89
+ name = "image",
90
+ required = false,
91
+ ...rest
92
+ } = {}) => ({
93
+ label,
94
+ name,
95
+ required,
96
+ widget: "image",
97
+ ...rest
98
+ });
99
+
100
+ export const fileField = ({
101
+ label = "File",
102
+ name = "file",
103
+ required = false,
104
+ ...rest
105
+ } = {}) => ({
106
+ label,
107
+ name,
108
+ required,
109
+ widget: "file",
110
+ ...rest
111
+ });
112
+
113
+ export const selectField = ({
114
+ label = "Select",
115
+ name = "select",
116
+ options = [],
117
+ ...rest
118
+ } = {}) => ({
119
+ label,
120
+ name,
121
+ widget: "select",
122
+ options,
123
+ ...rest
124
+ });
125
+
126
+ export const selectOption = ({
127
+ label = "SelectOption",
128
+ value,
129
+ ...rest
130
+ } = {}) => ({
131
+ label,
132
+ value,
133
+ ...rest
134
+ });
135
+
136
+ /*
137
+ {label: "Button", name: "button", widget: object, required: false, fields: [
138
+ {label: "Text", name: "text", widget: string, required: false},
139
+ {label: "Link", name: "link", widget: string, required: false}
140
+ ]},
141
+ */
142
+
143
+ export const buttonField = ({
144
+ label = "Button",
145
+ name = "button",
146
+ required = false,
147
+ ...rest
148
+ } = {}) => objectField({
149
+ label,
150
+ name,
151
+ required,
152
+ summary: "{{fields.text}} link: {{fields.link}}",
153
+ fields: [
154
+ stringField({
155
+ label: "Text",
156
+ name: "text",
157
+ required: false
158
+ }),
159
+ stringField({
160
+ label: "Link",
161
+ name: "link",
162
+ required: false
163
+ })
164
+ ],
165
+ ...rest
166
+ });
167
+
168
+ export const objectField = ({
169
+ label = "Object",
170
+ name = "object",
171
+ required = false,
172
+ fields = [],
173
+ ...rest
174
+ } = {}) => ({
175
+ label,
176
+ name,
177
+ required,
178
+ widget: "object",
179
+ fields,
180
+ ...rest
181
+ });
182
+
183
+ export const listField = ({
184
+ label = "List",
185
+ name = "list",
186
+ required = false,
187
+ fields = [],
188
+ ...rest
189
+ } = {}) => ({
190
+ label,
191
+ name,
192
+ required,
193
+ widget: "list",
194
+ fields,
195
+ ...rest
196
+ });
197
+
198
+ export const imageWithAltField = ({
199
+ label = "Image",
200
+ name = "image",
201
+ required = false,
202
+ imageRequired = false,
203
+ ...rest
204
+ } = {}) => objectField({
205
+ label,
206
+ name,
207
+ required,
208
+ fields: [
209
+ ...imageWithAltFieldset({
210
+ imageRequired
211
+ })
212
+ ],
213
+ ...rest
214
+ });
215
+
216
+ export const imagesField = ({
217
+ label = "Images",
218
+ name = "images",
219
+ required = false,
220
+ imageRequired = false,
221
+ ...rest
222
+ } = {}) => listField({
223
+ label,
224
+ name,
225
+ required,
226
+ fields: [
227
+ ...imageWithAltFieldset({
228
+ imageRequired
229
+ })
230
+ ],
231
+ ...rest
232
+ });
233
+
234
+ export const linkImageField = ({
235
+ label = "Link Image",
236
+ name = "link-image",
237
+ required = false,
238
+ imageRequired = false,
239
+ ...rest
240
+ } = {}) => objectField({
241
+ label,
242
+ name,
243
+ required,
244
+ fields: [
245
+ ...linkImageFieldset({
246
+ imageRequired
247
+ })
248
+ ],
249
+ ...rest
250
+ });
251
+
252
+ export const linkImagesField = ({
253
+ label = "Link Images",
254
+ name = "link-images",
255
+ required = false,
256
+ imageRequired = false,
257
+ ...rest
258
+ } = {}) => listField({
259
+ label,
260
+ name,
261
+ required,
262
+ fields: [
263
+ ...linkImageFieldset({
264
+ imageRequired
265
+ })
266
+ ],
267
+ ...rest
268
+ });
@@ -0,0 +1,35 @@
1
+ import configurationsCollection from "./collections/configurationsCollection";
2
+
3
+ const CMS_BACKEND_REPO = process.env.GATSBY_CMS_BACKEND_REPO;
4
+ const CMS_BACKEND_BRANCH = process.env.GATSBY_CMS_BACKEND_BRANCH || "main";
5
+
6
+ export const collections = [
7
+ configurationsCollection,
8
+ ];
9
+
10
+ const config = {
11
+ backend: {
12
+ name: "github",
13
+ repo: CMS_BACKEND_REPO,
14
+ branch: CMS_BACKEND_BRANCH,
15
+ commit_messages: {
16
+ create: "Create {{collection}} “{{slug}}”",
17
+ update: "Update {{collection}} “{{slug}}”",
18
+ delete: "Delete {{collection}} “{{slug}}”",
19
+ uploadMedia: "[skip ci] Upload “{{path}}”",
20
+ deleteMedia: "[skip ci] Delete “{{path}}”",
21
+ }
22
+ },
23
+ // It is not required to set `load_config_file` if the `config.yml` file is
24
+ // missing, but will improve performance and avoid a load error.
25
+ load_config_file: false,
26
+ media_folder: "static/img",
27
+ public_folder: "/img",
28
+ collections: collections
29
+ };
30
+
31
+ if (!CMS_BACKEND_REPO || !CMS_BACKEND_BRANCH) {
32
+ config.local_backend = true;
33
+ }
34
+
35
+ export default config;
@@ -0,0 +1,51 @@
1
+ import {
2
+ stringField,
3
+ imageField,
4
+ } from "./fields";
5
+
6
+ export const collectionDefaults = ({
7
+ label,
8
+ name,
9
+ editor = { preview: false }
10
+ }) => ({
11
+ label,
12
+ name,
13
+ /**
14
+ * @see https://decapcms.org/docs/beta-features/#folder-collections-media-and-public-folder
15
+ */
16
+ media_folder: "",
17
+ public_folder: "",
18
+ editor: editor
19
+ });
20
+
21
+ export const imageWithAltFieldset = ({
22
+ imageRequired = false
23
+ } = {}) => ([
24
+ imageField({
25
+ label: "Src",
26
+ name: "src",
27
+ required: imageRequired
28
+ // removing allowance of empty value
29
+ // default: ""
30
+ }),
31
+ stringField({
32
+ label: "Alt",
33
+ name: "alt",
34
+ required: false
35
+ // removing allowance of empty value
36
+ // default: ""
37
+ })
38
+ ]);
39
+
40
+ export const linkImageFieldset = ({
41
+ imageRequired = false
42
+ } = {}) => ([
43
+ ...imageWithAltFieldset({
44
+ imageRequired
45
+ }),
46
+ stringField({
47
+ label: "Link",
48
+ name: "link",
49
+ required: false
50
+ })
51
+ ]);
File without changes
File without changes
@@ -0,0 +1,26 @@
1
+ import React from "react";
2
+ import { useStaticQuery, graphql } from "gatsby";
3
+ import Template from "./template";
4
+
5
+ const dataQuery = graphql`
6
+ query {
7
+ announcementBannerJson {
8
+ title,
9
+ logo {
10
+ publicURL
11
+ },
12
+ text,
13
+ cta_title,
14
+ cta_label,
15
+ cta_link
16
+ }
17
+ }
18
+ `;
19
+
20
+ const AnnouncementBanner = () => {
21
+ const {announcementBannerJson} = useStaticQuery(dataQuery);
22
+
23
+ return <Template data={announcementBannerJson} />;
24
+ };
25
+
26
+ export default AnnouncementBanner;
@@ -0,0 +1,131 @@
1
+ .wrapper {
2
+ background-color: #000000;
3
+ color: #ffffff;
4
+ font-size: 12px;
5
+ font-family: "Open Sans", Helvetica, Arial, sans-serif !important;
6
+ padding: 30px 40px;
7
+ max-width: 1500px;
8
+ border-radius: 4px;
9
+ justify-content: space-evenly;
10
+ margin: auto;
11
+ position: relative;
12
+
13
+ @media (min-width: 900px) {
14
+ width: 85%;
15
+ }
16
+
17
+ .header {
18
+ text-align: center;
19
+
20
+ @media (min-width: 900px) {
21
+ text-align: left;
22
+ }
23
+
24
+ .title {
25
+ font-size: 35px;
26
+ display: inline-block;
27
+ bottom: 6px;
28
+ position: relative;
29
+ font-family: "Jura", "Helvetica", sans-serif;
30
+ font-weight: 700;
31
+ vertical-align: middle;
32
+ margin-bottom: 10px;
33
+ margin-top: 20px;
34
+ }
35
+
36
+ .logo {
37
+ position: relative;
38
+ height: 50px;
39
+ width: 50px;
40
+ display: block;
41
+ margin: 0 auto 10px;
42
+
43
+ @media (min-width: 1024px) {
44
+ top: 20px;
45
+ margin: 0 20px 0 0;
46
+ display: inline;
47
+ }
48
+ }
49
+ }
50
+
51
+ .text {
52
+ padding-top: 15px;
53
+ font-size: 18px;
54
+ line-height: 25px;
55
+ font-family: "Roboto", "Helvetica", sans-serif;
56
+ font-style: normal;
57
+ font-weight: normal;
58
+ margin: 0 0 10px;
59
+ }
60
+
61
+ .ctaTitle {
62
+ font-family: "Roboto", "Helvetica", sans-serif;
63
+ font-style: normal;
64
+ font-size: 18px;
65
+ line-height: 25px;
66
+ text-align: center;
67
+ margin-bottom: 15px;
68
+ font-weight: bold;
69
+ margin-top: 10%;
70
+ }
71
+
72
+ .cta {
73
+ background-color: #39ae4a;
74
+ width: 250px;
75
+ font-size: 20px;
76
+ min-width: 155px;
77
+ padding: 1rem;
78
+ border-radius: 50px;
79
+ margin-left: auto;
80
+ margin-right: auto;
81
+ text-align: center;
82
+ white-space: nowrap;
83
+ display: block;
84
+ color: white;
85
+ text-decoration: none;
86
+
87
+ &:hover {
88
+ background-color: white;
89
+ color: black;
90
+
91
+ .img {
92
+ path {
93
+ stroke: black;
94
+ }
95
+ }
96
+ }
97
+
98
+ .img {
99
+ margin-left: 10px;
100
+ }
101
+ }
102
+
103
+ .bottomBar {
104
+ bottom: 0;
105
+ left: 0;
106
+ display: flex;
107
+ height: 7px;
108
+ position: absolute;
109
+ width: 100%;
110
+
111
+ div:nth-child(1) {
112
+ background-color: #e61e24;
113
+ width: 25%;
114
+ }
115
+
116
+ div:nth-child(2) {
117
+ background-color: #f4a93a;
118
+ width: 25%;
119
+ }
120
+
121
+ div:nth-child(3) {
122
+ background-color: #28a4db;
123
+ width: 25%;
124
+ }
125
+
126
+ div:nth-child(4) {
127
+ background-color: #39ae4a;
128
+ width: 25%;
129
+ }
130
+ }
131
+ }
@@ -0,0 +1,40 @@
1
+ import React from "react";
2
+ import { Grid } from "@mui/material";
3
+ import '@fortawesome/fontawesome-free/css/all.css';
4
+ import RightArrow from '../svgs/RightArrow';
5
+ import styles from "./index.module.scss";
6
+
7
+ const Template = ({ data }) => {
8
+ const {title, logo, text, cta_title, cta_label, cta_link} = data || {};
9
+
10
+ return (
11
+ <div className={styles.wrapper}>
12
+ <Grid container>
13
+ <Grid item sm={12} md={8}>
14
+ <div className={styles.header}>
15
+ <img className={styles.logo} src={logo?.publicURL} alt="logo" />
16
+ <h2 className={styles.title}> {title} </h2>
17
+ </div>
18
+ <p className={styles.text}> {text} </p>
19
+ </Grid>
20
+ <Grid item sm={12} md={4}>
21
+ <h3 className={styles.ctaTitle}> {cta_title} </h3>
22
+ <a className={styles.cta} href={cta_link}>
23
+ {cta_label}
24
+ <span className={styles.img}>
25
+ <RightArrow />
26
+ </span>
27
+ </a>
28
+ </Grid>
29
+ </Grid>
30
+ <div className={styles.bottomBar}>
31
+ <div />
32
+ <div />
33
+ <div />
34
+ <div />
35
+ </div>
36
+ </div>
37
+ );
38
+ };
39
+
40
+ export default Template;