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 + + +
+
+
+ +

*privacy not included

+
+ +
+
+
+
+ Home + Toys & Games + Smart Home + Entertainment + Wearables + Health & Exercise + Pets + About the Guide +
+
+ +
{% 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 %} -
{{product.name}}
-{% endfor %} +{% block body-id %}home{% endblock %} + +{% block guts %} + +
+

Buyer's guide homepage!

+ + {% for product in products %} +
{{product.name}}
+ {% 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 + } +}];