diff --git a/network-api/networkapi/buyersguide/factory.py b/network-api/networkapi/buyersguide/factory.py
index f4d934b2235..da17bb67d21 100644
--- a/network-api/networkapi/buyersguide/factory.py
+++ b/network-api/networkapi/buyersguide/factory.py
@@ -31,12 +31,19 @@ class Meta:
camera = Faker('boolean')
microphone = Faker('boolean')
location = Faker('boolean')
+ uses_encryption = Faker('boolean')
+ privacy_policy = Faker('url')
+ share_data = Faker('boolean')
+ must_change_default_password = Faker('boolean')
+ security_updates = Faker('boolean')
need_account = Faker('boolean')
- privacy_controls = Faker('boolean')
delete_data = Faker('boolean')
- share_data = Faker('boolean')
child_rules = Faker('boolean')
- privacy_policy = Faker('url')
+ manage_security = Faker('boolean')
+ customer_support_easy = Faker('boolean')
+ phone_number = Faker('phone_number')
+ live_chat = Faker('url')
+ email = Faker('email')
worst_case = Faker('sentence')
@post_generation
diff --git a/network-api/networkapi/buyersguide/migrations/0002_auto_20180910_2037.py b/network-api/networkapi/buyersguide/migrations/0002_auto_20180910_2037.py
new file mode 100644
index 00000000000..98c4d92ea61
--- /dev/null
+++ b/network-api/networkapi/buyersguide/migrations/0002_auto_20180910_2037.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.14 on 2018-09-10 20:37
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('buyersguide', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='product',
+ name='privacy_controls',
+ ),
+ migrations.AddField(
+ model_name='product',
+ name='customer_support_easy',
+ field=models.NullBooleanField(help_text='Makes it easy to contact customer support?'),
+ ),
+ migrations.AddField(
+ model_name='product',
+ name='email',
+ field=models.CharField(blank='True', help_text='Email', max_length=100),
+ ),
+ migrations.AddField(
+ model_name='product',
+ name='live_chat',
+ field=models.CharField(blank='True', help_text='Live Chat', max_length=100),
+ ),
+ migrations.AddField(
+ model_name='product',
+ name='manage_security',
+ field=models.NullBooleanField(help_text='Manages security vulnerabilities?'),
+ ),
+ migrations.AddField(
+ model_name='product',
+ name='must_change_default_password',
+ field=models.NullBooleanField(help_text='Must change a default password?'),
+ ),
+ migrations.AddField(
+ model_name='product',
+ name='phone_number',
+ field=models.CharField(blank='True', help_text='Phone Number', max_length=100),
+ ),
+ migrations.AddField(
+ model_name='product',
+ name='security_updates',
+ field=models.NullBooleanField(help_text='Security updates?'),
+ ),
+ migrations.AddField(
+ model_name='product',
+ name='uses_encryption',
+ field=models.NullBooleanField(help_text='Does the product use encryption?'),
+ ),
+ migrations.AlterField(
+ model_name='product',
+ name='price',
+ field=models.CharField(blank='True', help_text='Price', max_length=100),
+ ),
+ ]
diff --git a/network-api/networkapi/buyersguide/models.py b/network-api/networkapi/buyersguide/models.py
index 7712cb0c2c7..1366ba9f946 100644
--- a/network-api/networkapi/buyersguide/models.py
+++ b/network-api/networkapi/buyersguide/models.py
@@ -12,6 +12,8 @@ def get_product_image_upload_path(instance, filename):
)
+# https://docs.google.com/document/d/1jtWOVqH20qMYRSwvb2rHzPNTrWIoPs8EbWR25r9iyi4/edit
+
class Product(models.Model):
"""
A thing you can buy in stores and our review of it
@@ -22,6 +24,7 @@ class Product(models.Model):
help_text='Name of Product',
blank="True",
)
+
company = models.CharField(
max_length=100,
help_text='Name of Company',
@@ -33,51 +36,108 @@ class Product(models.Model):
help_text='Description of the product',
blank="True"
)
+
url = models.URLField(
max_length=2048,
help_text='Link to this product page',
blank="True",
)
+
price = models.CharField(
max_length=100,
- help_text='Price range',
+ help_text='Price',
blank="True",
)
+
image = models.FileField(
max_length=2048,
help_text='Image representing this prodct',
upload_to=get_product_image_upload_path,
blank=True,
)
+
+ # Can it spy on me?
+
camera = models.NullBooleanField(
help_text='Does this product have or access a camera?',
)
+
microphone = models.NullBooleanField(
help_text='Does this product have or access a microphone?',
)
+
location = models.NullBooleanField(
help_text='Does this product access your location?',
)
+
+ # What does it know about me?
+
+ uses_encryption = models.NullBooleanField(
+ help_text='Does the product use encryption?',
+ )
+
+ privacy_policy = models.URLField(
+ help_text='Link to privacy policy for this product',
+ max_length=2048,
+ blank="True",
+ )
+
+ share_data = models.NullBooleanField(
+ help_text='Does the maker share data with other companies?',
+ )
+
+ # Can I control it?
+
+ must_change_default_password = models.NullBooleanField(
+ help_text='Must change a default password?',
+ )
+
+ security_updates = models.NullBooleanField(
+ help_text='Security updates?',
+ )
+
need_account = models.NullBooleanField(
help_text='Do you need an account to use this product?',
)
- privacy_controls = models.NullBooleanField(
- help_text='Do users have access to privacy controls?',
- )
+
delete_data = models.NullBooleanField(
help_text='Can you request data be deleted?',
)
- share_data = models.NullBooleanField(
- help_text='Does the maker share data with other companies?',
- )
+
child_rules = models.NullBooleanField(
help_text='Are there rules for children?',
)
- privacy_policy = models.URLField(
- help_text='Link to privacy policy for this product',
- max_length=2048,
+
+ # Company shows it cares about its customers?
+
+ manage_security = models.NullBooleanField(
+ help_text='Manages security vulnerabilities?',
+ )
+
+ customer_support_easy = models.NullBooleanField(
+ help_text='Makes it easy to contact customer support?',
+ )
+
+ phone_number = models.CharField(
+ max_length=100,
+ help_text='Phone Number',
+ blank="True",
+ )
+
+ live_chat = models.CharField(
+ max_length=100,
+ help_text='Live Chat',
blank="True",
)
+
+ email = models.CharField(
+ max_length=100,
+ help_text='Email',
+ blank="True",
+ )
+
+ # What could happen if something went wrong?
+
worst_case = models.CharField(
max_length=5000,
help_text="What's the worst thing that could happen by using this product?",
diff --git a/network-api/networkapi/buyersguide/templates/about.html b/network-api/networkapi/buyersguide/templates/about.html
new file mode 100644
index 00000000000..1e7902c0efd
--- /dev/null
+++ b/network-api/networkapi/buyersguide/templates/about.html
@@ -0,0 +1,11 @@
+{% extends "./bg_base.html" %}
+
+{% block body-id %}about{% endblock %}
+
+{% block guts %}
+
+
+
About page goes here!
+
+
+{% endblock %}
diff --git a/network-api/networkapi/buyersguide/templates/bg_base.html b/network-api/networkapi/buyersguide/templates/bg_base.html
new file mode 100644
index 00000000000..ed6b2bca7fc
--- /dev/null
+++ b/network-api/networkapi/buyersguide/templates/bg_base.html
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Mozilla - *privacy not included
+
+
+
+
+
+
Mozilla
+
*privacy not included
+
+
+
+
+
+
+ {% block guts %}{% endblock %}
+
+
+
+
+
diff --git a/network-api/networkapi/buyersguide/templates/buyersguide_home.html b/network-api/networkapi/buyersguide/templates/buyersguide_home.html
index 99a16df1e3c..5cc48dd9902 100644
--- a/network-api/networkapi/buyersguide/templates/buyersguide_home.html
+++ b/network-api/networkapi/buyersguide/templates/buyersguide_home.html
@@ -1,5 +1,15 @@
-Buyer's guide homepage!
+{% extends "./bg_base.html" %}
-{% for product in products %}
-
-{% endfor %}
+{% block body-id %}home{% endblock %}
+
+{% block guts %}
+
+
+
Buyer's guide homepage!
+
+ {% for product in products %}
+
+ {% endfor %}
+
+
+{% endblock %}
diff --git a/network-api/networkapi/buyersguide/templates/product_page.html b/network-api/networkapi/buyersguide/templates/product_page.html
index 8ae5bba718e..09c3d92ca5a 100644
--- a/network-api/networkapi/buyersguide/templates/product_page.html
+++ b/network-api/networkapi/buyersguide/templates/product_page.html
@@ -1,19 +1,31 @@
-{{product.name}}
+{% extends "./bg_base.html" %}
-
-
- {{product.company}}
+{% block body-id %}TODO-CATEGORY{% endblock %}
+
+{% block guts %}
+
+
+
{{product.name}}
+
+
+
+
+
+ {{product.company}}
+
+
{{product.blurb}}
+
{{product.url}}
+
{{product.price}}
+
Camera: {{product.camera}}
+
Microphone: {{product.microphone}}
+
Location: {{product.location}}
+
Need account: {{product.need_account}}
+
Privacy Controls: {{product.privacy_controls}}
+
Delete Data: {{product.delete_data}}
+
Share Data: {{product.share_data}}
+
Child rules: {{product.child_rules}}
+
Privacy Policy: {{product.privacy_policy}}
+
Worst case scenario: {{product.worst_case}}
-
{{product.blurb}}
-
{{product.url}}
-
{{product.price}}
-
Camera: {{product.camera}}
-
Microphone: {{product.microphone}}
-
Location: {{product.location}}
-
Need account: {{product.need_account}}
-
Privacy Controls: {{product.privacy_controls}}
-
Delete Data: {{product.delete_data}}
-
Share Data: {{product.share_data}}
-
Child rules: {{product.child_rules}}
-
Privacy Policy: {{product.privacy_policy}}
-
Worst case scenario: {{product.worst_case}}
+
+{% endblock %}
diff --git a/network-api/networkapi/buyersguide/urls.py b/network-api/networkapi/buyersguide/urls.py
index f0eebbff756..0cf29d4ceb9 100644
--- a/network-api/networkapi/buyersguide/urls.py
+++ b/network-api/networkapi/buyersguide/urls.py
@@ -4,5 +4,6 @@
urlpatterns = [
url(r'^$', views.buyersguide_home, name='buyersguide-home'),
- url(r'^(?P
[\w\ ]+)/', views.product_view, name='product-view'),
+ url(r'^about/', views.about_view, name='about-view'),
+ url(r'^product/(?P[\w\ ]+)/', views.product_view, name='product-view'),
]
diff --git a/network-api/networkapi/buyersguide/views.py b/network-api/networkapi/buyersguide/views.py
index 1def9a0a7be..cb43bb3399f 100644
--- a/network-api/networkapi/buyersguide/views.py
+++ b/network-api/networkapi/buyersguide/views.py
@@ -16,3 +16,8 @@ def buyersguide_home(request):
def product_view(request, productname):
product = Product.objects.get(name__iexact=productname)
return render(request, 'product_page.html', {'product': product, 'mediaUrl': settings.MEDIA_URL})
+
+
+@login_required
+def about_view(request):
+ return render(request, 'about.html')
diff --git a/package.json b/package.json
index 274e0b462a7..972e61a8291 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,7 @@
"build:images": "shx rm -rf network-api/networkapi/frontend/_images && shx cp -r source/images network-api/networkapi/frontend/_images",
"build:js-uncompressed": "webpack",
"build:js": "webpack -p",
- "build:sass": "shx mkdir -p network-api/networkapi/frontend/_css && node-sass source/sass/main.scss network-api/networkapi/frontend/_css/main.compiled.css && npm run autoprefix",
+ "build:sass": "shx mkdir -p network-api/networkapi/frontend/_css && node-sass source/sass/main.scss network-api/networkapi/frontend/_css/main.compiled.css && node-sass source/sass/buyers-guide/bg-main.scss network-api/networkapi/frontend/_css/buyers-guide.compiled.css && npm run autoprefix",
"build": "npm run build:common && npm run build:js",
"build-uncompressed": "npm run build:common && npm run build:js-uncompressed",
"heroku-postbuild": "npm run build",
@@ -23,7 +23,7 @@
"server": "pipenv run python network-api/manage.py runserver",
"start": "npm i && npm run build-uncompressed && run-p server watch:**",
"snyk": "snyk test --file=package.json",
- "test:eslint": "eslint --config ./.eslintrc.yaml scripts/**/*.js source/js/**/*.js source/js/components/**/*.jsx",
+ "test:eslint": "eslint --config ./.eslintrc.yaml scripts/**/*.js source/js/**/*.js source/js/components/**/*.jsx webpack.config.js",
"test:scss": "stylelint \"source/sass/**/*.scss\" \"source/js/components/**/*.scss\" --syntax scss",
"test": "npm run build && run-s test:**",
"watch:images": "chokidar 'source/images/**/*' -c 'npm run build:images'",
diff --git a/source/images/glyphs/email.svg b/source/images/glyphs/email.svg
new file mode 100644
index 00000000000..34ec0e2d8f3
--- /dev/null
+++ b/source/images/glyphs/email.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/source/images/glyphs/fb.svg b/source/images/glyphs/fb.svg
new file mode 100644
index 00000000000..64dc50a0a01
--- /dev/null
+++ b/source/images/glyphs/fb.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/source/js/buyers-guide/bg-main.js b/source/js/buyers-guide/bg-main.js
new file mode 100644
index 00000000000..728ee8511ec
--- /dev/null
+++ b/source/js/buyers-guide/bg-main.js
@@ -0,0 +1,21 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+
+import Creepometer from './components/creepometer/creepometer.jsx';
+
+let main = {
+ init() {
+ this.injectReactComponents();
+ },
+
+ // Embed various React components based on the existence of containers within the current page
+ injectReactComponents() {
+ if (document.querySelectorAll(`.creepometer`)) {
+ Array.from(document.querySelectorAll(`.creepometer`)).forEach(element => {
+ ReactDOM.render(, element);
+ });
+ }
+ }
+};
+
+main.init();
diff --git a/source/js/buyers-guide/components/creepometer/creepometer.jsx b/source/js/buyers-guide/components/creepometer/creepometer.jsx
new file mode 100644
index 00000000000..535ef569a2a
--- /dev/null
+++ b/source/js/buyers-guide/components/creepometer/creepometer.jsx
@@ -0,0 +1,15 @@
+import React from 'react';
+
+export default class Creepometer extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {};
+ }
+
+ render() {
+ return (
+ Creepometer 😬
+ );
+ }
+}
diff --git a/source/js/buyers-guide/components/creepometer/creepometer.scss b/source/js/buyers-guide/components/creepometer/creepometer.scss
new file mode 100644
index 00000000000..4a1fc320878
--- /dev/null
+++ b/source/js/buyers-guide/components/creepometer/creepometer.scss
@@ -0,0 +1,3 @@
+.creepometer {
+ border: 1px solid papayawhip;
+}
diff --git a/source/js/main.js b/source/js/main.js
index eee73aac16d..2c1ebbf9a38 100644
--- a/source/js/main.js
+++ b/source/js/main.js
@@ -322,4 +322,3 @@ let main = {
};
main.init();
-
diff --git a/source/sass/buyers-guide/bg-main.scss b/source/sass/buyers-guide/bg-main.scss
new file mode 100644
index 00000000000..60b2648034f
--- /dev/null
+++ b/source/sass/buyers-guide/bg-main.scss
@@ -0,0 +1,111 @@
+// Bootstrap
+
+@import '../../../node_modules/mofo-bootstrap/dest/css/mofo-bootstrap';
+@import '../../../node_modules/mofo-bootstrap/src/scss/custom/_colors';
+@import '../../../node_modules/bootstrap/scss/_variables';
+@import '../../../node_modules/bootstrap/scss/mixins';
+
+// Breakpoints (imported from Bootstrap)
+
+$bp-xs: #{map-get($grid-breakpoints, xs)}; // < 576px
+$bp-sm: #{map-get($grid-breakpoints, sm)}; // >= 576px
+$bp-md: #{map-get($grid-breakpoints, md)}; // >= 768px
+$bp-lg: #{map-get($grid-breakpoints, lg)}; // >= 992px
+$bp-xl: #{map-get($grid-breakpoints, xl)}; // >= 1200px
+
+//Site-wide
+
+@import '../colors';
+@import '../mixins';
+
+// React Components
+
+@import '../../js/buyers-guide/components/creepometer/creepometer';
+
+// Non-React Components
+
+@import './components/future-non-react-component';
+
+.btn-blue {
+ color: $white;
+ background: #3302ff;
+
+ &:hover {
+ color: $white;
+ background: #4a90e2;
+ }
+}
+
+// Misc
+
+@import '../resets';
+@import '../type';
+@import '../buttons';
+@import '../global';
+@import '../utilities';
+
+// Header
+
+.moz-logo {
+ background-image: url("../_images/mozilla-m.svg");
+ flex-shrink: 0;
+ width: 28px;
+ height: 28px;
+
+ @include media-breakpoint-up(md) {
+ width: 97px;
+ background-image: url("../_images/mozilla-on-black.svg");
+ }
+}
+
+.primary-nav {
+ overflow-x: scroll;
+ white-space: nowrap;
+ border-top: 1px solid #ccc;
+ border-bottom: 1px solid #ccc;
+
+ a {
+ display: inline-block;
+ font-family: "Nunito Sans";
+ font-size: 17px;
+ color: #909090;
+ font-weight: 900;
+ margin-right: 20px;
+ padding: 10px 0 6px;
+ text-decoration: none;
+
+ &:hover {
+ color: $black;
+ }
+ }
+}
+
+#pni-home .nav-home,
+#pni-about .nav-about {
+ border-bottom: 4px solid $black;
+ color: $black;
+}
+
+.social-button {
+ display: block;
+ width: 25px;
+ height: 25px;
+ margin: 0 12px;
+ background-size: contain;
+ background-repeat: no-repeat;
+ background-position: center;
+}
+
+.social-button-fb {
+ background-image: url("/_images/glyphs/fb.svg");
+}
+
+.social-button-twitter {
+ background-image: url("/_images/glyphs/twitter.svg");
+}
+
+.social-button-email {
+ background-image: url("/_images/glyphs/email.svg");
+}
+
+// View specific
diff --git a/source/sass/buyers-guide/components/future-non-react-component.scss b/source/sass/buyers-guide/components/future-non-react-component.scss
new file mode 100644
index 00000000000..70b786d12ed
--- /dev/null
+++ b/source/sass/buyers-guide/components/future-non-react-component.scss
@@ -0,0 +1 @@
+// TODO
diff --git a/webpack.config.js b/webpack.config.js
index 7f5e7c3eebc..0f0f69f16b9 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -1,17 +1,28 @@
-module.exports = {
+let loaders = [
+ {
+ test: /\.js(x?)$/,
+ exclude: /node_modules/,
+ loader: `babel-loader`,
+ query: {
+ presets: [`es2015`, `react`]
+ }
+ }
+];
+
+module.exports = [{
entry: `./source/js/main.js`,
output: {
filename: `./network-api/networkapi/frontend/_js/main.compiled.js`
},
module: {
- loaders: [
- {
- test: /\.js(x?)$/,
- exclude: /node_modules/,
- loader: `babel-loader`,
- query: {
- presets: [`es2015`, `react`]
- }
- }
- ]
- }};
+ loaders: loaders
+ }
+}, {
+ entry: `./source/js/buyers-guide/bg-main.js`,
+ output: {
+ filename: `./network-api/networkapi/frontend/_js/bg-main.compiled.js`
+ },
+ module: {
+ loaders: loaders
+ }
+}];