Skip to content

Commit 8ebf160

Browse files
committed
Content Recs
1 parent 93c450e commit 8ebf160

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+6191
-378
lines changed

.eslintrc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"extends": "next/core-web-vitals"
2+
"extends": "next"
33
}

README.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,29 @@ This project makes uses of NextJs, Optimizely Fullstack and the JS SDK. I want
99

1010
[![Netlify Status](https://api.netlify.com/api/v1/badges/19b26768-2571-46e7-89b3-eefc07ec35c2/deploy-status)](https://app.netlify.com/sites/optimizely-demo/deploys)
1111

12+
## Api
13+
14+
- [API](https://optimizely-demo.netlify.app/.netlify/functions/admin)
15+
1216
## How To Use ☄️
1317

14-
First, run the development server:
18+
**Website**: First, run the development server:
1519

1620
```bash
1721
npm run dev
1822
```
1923

24+
The site will load on `http:localhost:3000`
25+
26+
**API**: To update or run the admin API run:
27+
28+
```bash
29+
npm run start-api
30+
```
31+
32+
The API will load on `http://localhost:9000/admin`
33+
34+
2035
Click this button to create a new Github repo, new Netlify project and deploy this example yourself
2136

2237
[![Deploy to Netlify Button](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/jondjones-poc/Fullstack-demo)

component/ABComponent.js

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,15 @@ const sectionStyle = (backgroundColor) => (
1818

1919
const ABComponent = ({...props}) => {
2020

21-
const { backgroundColor, bannerText } = props;
22-
23-
const addEvent = () => {
24-
console.log("AB Component Event Example")
25-
props.optimizelyClient.track('button_click', props.userId);
26-
}
21+
const { backgroundColor, bannerText, buttonUrl } = props;
2722

2823
return (
2924

3025
<div className="container">
3126
<header>
3227
<h2>
3328
<strong>
34-
A/B/C Experiment
29+
A/B/C Experiment
3530
</strong>
3631
</h2>
3732
</header>
@@ -45,7 +40,7 @@ const ABComponent = ({...props}) => {
4540
{bannerText} today!
4641
</div>
4742

48-
<a className={styles.btnBgstroke} onClick={addEvent} >
43+
<a className={styles.btnBgstroke} href={buttonUrl} >
4944
Buy now
5045
</a>
5146

component/Content.js

Lines changed: 0 additions & 24 deletions
This file was deleted.

component/ContentRecommendations.js

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { useEffect, useState, useCallback } from "react";
2+
3+
const imageStyle = {
4+
backgroundRepeat: 'no-repeat',
5+
width: '100%',
6+
height: '100%',
7+
};
8+
9+
const ContentRecommendations= ({...props}) => {
10+
11+
const { contentRecsKey } = props;
12+
const recommendations = useContentRecommendations()
13+
14+
return (
15+
<>
16+
<script
17+
className="idio-recommendations"
18+
data-api-key={contentRecsKey}
19+
data-rpp="5"
20+
type="text/x-mustache"
21+
/>
22+
23+
{recommendations.length > 0 && (
24+
<div>
25+
<ul className="divided">
26+
<ContentRecommendationsList recommendations={recommendations} />
27+
</ul>
28+
</div>
29+
)}
30+
</>
31+
)
32+
}
33+
34+
function ContentRecommendationsList({ recommendations }) {
35+
const formatDate = (date) =>
36+
new Date(date).toLocaleString('en', { dateStyle: 'medium' })
37+
38+
return recommendations.map((recommendation) => (
39+
<li key={recommendation.id}>
40+
<article className="box excerpt" >
41+
<header>
42+
<span className="date">
43+
{formatDate(recommendation.published)}
44+
</span>
45+
<h3>
46+
<a href="#">
47+
{recommendation.title}
48+
</a>
49+
</h3>
50+
</header>
51+
<p>
52+
<img alt={recommendation.title} src={recommendation.main_image_url} style={imageStyle} />
53+
</p>
54+
<p>{recommendation.abstract}</p>
55+
</article>
56+
</li>
57+
))
58+
}
59+
60+
function useContentRecommendations() {
61+
const [recommendations, setRecommendations] = useState([])
62+
63+
useIdioProxy(setRecommendations)
64+
useIdioPersonalizationScript()
65+
66+
return recommendations
67+
}
68+
69+
70+
function useIdioProxy(setRecommendations) {
71+
const handleAjaxResponse = useCallback(
72+
(data, statusCode) => {
73+
if (statusCode !== 200) {
74+
return
75+
}
76+
77+
setRecommendations(data.content)
78+
},
79+
[setRecommendations]
80+
)
81+
82+
useEffect(() => {
83+
window.idio = new Proxy(
84+
{},
85+
{
86+
get(target, property) {
87+
return property === 'r0' ? handleAjaxResponse : target[property]
88+
},
89+
}
90+
)
91+
92+
return () => delete window.idio
93+
}, [handleAjaxResponse])
94+
}
95+
96+
97+
function useIdioPersonalizationScript() {
98+
useEffect(() => {
99+
const script = document.createElement('script')
100+
script.async = true
101+
script.src = 'https://s.idio.co/ip.js'
102+
103+
document
104+
.querySelector('script')
105+
.insertAdjacentElement('beforebegin', script)
106+
107+
return () => script.remove()
108+
}, [])
109+
}
110+
111+
export default ContentRecommendations

component/FeatureFlagComponent.js

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,37 @@ const featureFlagStyle = {
1111
backgroundSize: '100%'
1212
}
1313

14-
const imageStyle = {
15-
width: '100%'
16-
};
17-
1814
const FeatureFlagComponent = ({...props}) => {
1915

20-
const { userId, optimizelyClient, clientId } = props;
16+
const { userId, optimizelyClient, clientId, isFeatureEnabled } = props;
2117

22-
const addEvent = () => {
18+
const addEvent = (optimizelyClient, userId) => {
2319
optimizelyClient?.track('button_click', userId);
20+
console.log('feature_flag_component_click')
2421
}
2522

2623
return (
27-
<div className="container" id="feature-flag" onClick={featureFlagStyle} style={imageStyle}>
28-
<img src={`images/${clientId}/feature.png`} style={imageStyle} alt="feature-flag" />
24+
<section id="features">
25+
<div className="container" id="feature-container">
26+
<header>
27+
<h2>
28+
<strong>
29+
{"Feature Flag Example: " + isFeatureEnabled}
30+
</strong>
31+
</h2>
32+
</header>
33+
{isFeatureEnabled &&
34+
<div className="container"
35+
id="feature-flag"
36+
onClick={() => addEvent(optimizelyClient, userId)} style={featureFlagStyle}>
37+
38+
<img src={`images/${clientId}/feature.png`}
39+
style={{width: '100%'}}
40+
alt="feature-flag" />
41+
</div>
42+
}
2943
</div>
44+
</section>
3045
)
3146
}
3247

component/MultiArmBanditComponent.js

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,6 @@
11
import React from 'react'
22
import Link from "next/link";
33

4-
const sectionStyle = {
5-
width: '100%'
6-
};
7-
8-
const imageStyle = {
9-
width: '100%'
10-
};
11-
124
const MultiArmBanditComponent = ({...props}) => {
135

146
const { userId, postId, optimizelyClient, clientId } = props;
@@ -28,12 +20,14 @@ const MultiArmBanditComponent = ({...props}) => {
2820
</h2>
2921
</header>
3022

31-
<div id="multi-arm-bandit" style={sectionStyle}>
23+
<div id="multi-arm-bandit" style={{width: '100%'}}>
3224
<Link href="/">
3325
<a onClick={() => bannerClicked(optimizelyClient)}>
34-
<img src={`images/${clientId}/${postId}.png`} style={imageStyle} alt="multi-arm bandit" />
26+
<img src={`images/${clientId}/${postId}.png`}
27+
style={{width: '100%'}}
28+
alt="multi-arm bandit" />
3529
</a>
36-
</Link>{" "}
30+
</Link>
3731
</div>
3832
</div>
3933
)
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { useEffect, useState } from "react";
2+
import { fetchContentfulEntry } from '../utils/contentfulConnector';
3+
4+
const VariationContainerRenderer = ({...props}) => {
5+
6+
let [ variationItem, setVariationContent ] = useState('');
7+
8+
const { item, variationId, userId, optimizelyClient } = props;
9+
const { content } = item;
10+
11+
const entityId = content?.fields?.meta[variationId];
12+
13+
useEffect(() => {
14+
const fetchData = async () => {
15+
const result = await fetchContentfulEntry(entityId);
16+
setVariationContent(result);
17+
}
18+
fetchData().catch(console.error);
19+
}, [entityId]);
20+
21+
const buttonClicked = (optimizelyClient, userId, entityId) => {
22+
optimizelyClient?.track('blog_post_click', userId, {variation: entityId});
23+
console.log(`Blog post ${entityId} clicked`)
24+
}
25+
26+
return (
27+
<article className="box post">
28+
<header>
29+
<h2>
30+
{variationItem.title}
31+
</h2>
32+
</header>
33+
34+
<a href="#" className="image featured">
35+
<img src={variationItem?.heroImage?.fields.file.url} alt={item.slug} />
36+
</a>
37+
38+
<div className="header" dangerouslySetInnerHTML={{ __html: variationItem.content }} />
39+
40+
<ul className="actions" >
41+
<li>
42+
<div className="button icon solid fa-file" onClick={() => buttonClicked(optimizelyClient, userId, entityId)}>
43+
Continue Reading...
44+
</div>
45+
</li>
46+
</ul>
47+
</article>
48+
)
49+
}
50+
51+
export default VariationContainerRenderer;

dist-functions/admin.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

functions/admin.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
const data = require('./data.json');
2+
3+
const headers = {
4+
'content-type': 'application/json' ,
5+
'Access-Control-Allow-Origin': "*"
6+
};
7+
8+
exports.handler = async (event, context) => {
9+
10+
const sdkKey = process.env.NEXT_PUBLIC_API_KEY;
11+
const projectId = process.env.NEXT_PUBLIC_DEFAULT_PROJECT_ID;
12+
13+
const axios = require('axios');
14+
axios.defaults.headers.common['Authorization'] = sdkKey;
15+
16+
axios.get(`https://api.optimizely.com/v2/environments?project_id=${projectId}`)
17+
.then((response) => {
18+
response.data.map(x => {
19+
return {
20+
name: x.name,
21+
datafile: JSON.stringify(x.datafile)
22+
};
23+
});
24+
}).catch((e) => {
25+
console.log(e);
26+
});
27+
28+
return {
29+
body: JSON.stringify(data),
30+
statusCode: 200,
31+
headers: headers
32+
};
33+
};

0 commit comments

Comments
 (0)