Browse Source

ported all old functionality to NuxtJS

Brian Wiborg 1 year ago
parent
commit
ca38a899e0
85 changed files with 6387 additions and 2672 deletions
  1. 3
    0
      .dockerignore
  2. 4
    0
      .gitignore
  3. 19
    0
      Dockerfile
  4. 13
    10
      README.md
  5. 0
    7
      assets/README.md
  6. BIN
      assets/brand_logo.png
  7. 54
    0
      assets/icons/gondola.svg
  8. 54
    0
      assets/icons/rocket.svg
  9. BIN
      assets/logo.png
  10. 1
    1
      assets/style/app.styl
  11. 1
    0
      assets/svg/cup.svg
  12. 1
    0
      assets/svg/flux_x.svg
  13. 1
    0
      assets/svg/rocket.svg
  14. 1
    0
      assets/svg/shield.svg
  15. BIN
      assets/trainstation_dark_small.jpg
  16. 0
    79
      components/Logo.vue
  17. 0
    7
      components/README.md
  18. 108
    0
      components/VehicleIcon.vue
  19. 0
    21
      components/VuetifyLogo.vue
  20. 193
    0
      components/pages/account/LoginForm.vue
  21. 36
    0
      components/pages/account/MyAccount.vue
  22. 91
    0
      components/pages/flux/Report/Step1.vue
  23. 214
    0
      components/pages/flux/Report/Step2.vue
  24. 106
    0
      components/pages/flux/Report/Step3.vue
  25. 88
    0
      components/pages/flux/Report/Step4.vue
  26. 89
    0
      components/pages/flux/Report/index.vue
  27. 51
    0
      components/pages/flux/Stream/FluxItem/FluxItemCondensed.vue
  28. 89
    0
      components/pages/flux/Stream/FluxItem/FluxItemDetailed.vue
  29. 184
    0
      components/pages/flux/Stream/FluxItem/ReportList.vue
  30. 83
    0
      components/pages/flux/Stream/FluxItem/index.vue
  31. 54
    0
      components/pages/flux/Stream/FluxStream.vue
  32. 98
    0
      components/pages/flux/Stream/index.vue
  33. 45
    0
      components/pages/home/Claim.vue
  34. 81
    0
      components/pages/home/Hero.vue
  35. 64
    0
      components/pages/home/Section.vue
  36. 78
    0
      components/picker/datetime/Dialog.vue
  37. 140
    0
      components/picker/datetime/index.vue
  38. 35
    0
      docker-compose.yml
  39. 183
    0
      lang/en.json
  40. 0
    7
      layouts/README.md
  41. 111
    27
      layouts/default.vue
  42. 41
    10
      nuxt.config.js
  43. 2035
    2390
      package-lock.json
  44. 47
    14
      package.json
  45. 0
    6
      pages/README.md
  46. 36
    0
      pages/about.vue
  47. 50
    0
      pages/account.vue
  48. 35
    0
      pages/flux/report.vue
  49. 13
    0
      pages/flux/stream.vue
  50. 63
    0
      pages/imprint.vue
  51. 112
    73
      pages/index.vue
  52. 0
    7
      plugins/README.md
  53. 6
    0
      plugins/vee-validate.js
  54. 3
    2
      plugins/vuetify.js
  55. 4
    0
      server/db.js
  56. 44
    1
      server/index.js
  57. 63
    0
      server/knexfile.js
  58. 45
    0
      server/migrations/20180115190143_initial.js
  59. 30
    0
      server/migrations/20181119214750_rename_date_to_scheduled_departure.js
  60. 30
    0
      server/migrations/20181119862837_rename_delay_to_delay_minutes.js
  61. 18
    0
      server/migrations/20181120195912_new_delay_fields.js
  62. 55
    0
      server/migrations/20181120214753_change_vehicle_type.js
  63. 16
    0
      server/migrations/20181121162901_user_profile_fields.js
  64. 72
    0
      server/migrations/20190208231626_flux.js
  65. 16
    0
      server/migrations/20190311144926_obfuscate-emails.js
  66. 201
    0
      server/routes/flux.js
  67. 66
    0
      server/routes/login.js
  68. 13
    0
      server/schema/exchange.json
  69. 86
    0
      server/schema/flux.json
  70. 14
    0
      server/schema/passwordless.json
  71. 19
    0
      server/utils/auth.js
  72. 53
    0
      server/utils/email.js
  73. 38
    0
      server/utils/jwt.js
  74. 7
    0
      server/utils/math.js
  75. 73
    0
      server/utils/paramParser.js
  76. 53
    0
      server/utils/validate.js
  77. 1
    0
      static/sw.js
  78. 0
    10
      store/README.md
  79. 1
    0
      store/index.js
  80. 119
    0
      store/report.js
  81. 69
    0
      store/stream.js
  82. 93
    0
      store/user.js
  83. 7
    0
      utils/auth.js
  84. 258
    0
      utils/countryCodes.js
  85. 9
    0
      utils/flux.js

+ 3
- 0
.dockerignore View File

@@ -0,0 +1,3 @@
1
+node_modules
2
+npm-debug*
3
+.nuxt

+ 4
- 0
.gitignore View File

@@ -392,3 +392,7 @@ healthchecksdb
392 392
 MigrationBackup/
393 393
 
394 394
 # End of https://www.gitignore.io/api/linux,vim,visualstudio,nuxt
395
+
396
+.env.sh
397
+node_modules/
398
+*.sql.gz

+ 19
- 0
Dockerfile View File

@@ -0,0 +1,19 @@
1
+FROM node:11.13.0-alpine
2
+
3
+RUN mkdir -p /usr/src/flux.fail
4
+WORKDIR /usr/src/flux.fail
5
+
6
+ENV NPM_CONFIG_LOGLEVEL=warn
7
+ENV NUXT_HOST=0.0.0.0
8
+ENV NUXT_PORT=3000
9
+
10
+RUN apk update && apk upgrade
11
+RUN apk add git
12
+
13
+COPY . .
14
+RUN npm install
15
+RUN npm run build
16
+
17
+EXPOSE 3000
18
+
19
+CMD [ "npm", "start" ]

+ 13
- 10
README.md View File

@@ -1,22 +1,25 @@
1 1
 # flux.fail
2 2
 
3
-> An internet community site for reporting and tracking delays in the public transportation system.
3
+> An internet community platform for reporting and tracking delays
4
+> and other disturbances in the public transportation network.
4 5
 
5
-## Build Setup
6
+## Developer Guide
6 7
 
7
-``` bash
8
+```bash
8 9
 # install dependencies
9 10
 $ npm install
10 11
 
11
-# serve with hot reload at localhost:3000
12
-$ npm run dev
12
+# spin up the db comtainer
13
+docker-compose up -d db
13 14
 
14
-# build for production and launch server
15
-$ npm run build
16
-$ npm start
15
+# run the database migrations
16
+npm run migrate
17 17
 
18
-# generate static project
19
-$ npm run generate
18
+# start the dev-server
19
+npm run dev
20
+
21
+# build for production
22
+docker build -t fluxfail-nuxt:latest .
20 23
 ```
21 24
 
22 25
 For detailed explanation on how things work, checkout [Nuxt.js docs](https://nuxtjs.org).

+ 0
- 7
assets/README.md View File

@@ -1,7 +0,0 @@
1
-# ASSETS
2
-
3
-**This directory is not required, you can delete it if you don't want to use it.**
4
-
5
-This directory contains your un-compiled assets such as LESS, SASS, or JavaScript.
6
-
7
-More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#webpacked).

BIN
assets/brand_logo.png View File


+ 54
- 0
assets/icons/gondola.svg View File

@@ -0,0 +1,54 @@
1
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+<svg
3
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
4
+   xmlns:cc="http://creativecommons.org/ns#"
5
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
6
+   xmlns:svg="http://www.w3.org/2000/svg"
7
+   xmlns="http://www.w3.org/2000/svg"
8
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
9
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
10
+   version="1.1"
11
+   width="24"
12
+   height="24"
13
+   viewBox="0 0 24 24"
14
+   id="svg3763"
15
+   sodipodi:docname="gondola.svg"
16
+   inkscape:version="0.92.4 5da689c313, 2019-01-14">
17
+  <metadata
18
+     id="metadata3769">
19
+    <rdf:RDF>
20
+      <cc:Work
21
+         rdf:about="">
22
+        <dc:format>image/svg+xml</dc:format>
23
+        <dc:type
24
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
25
+      </cc:Work>
26
+    </rdf:RDF>
27
+  </metadata>
28
+  <defs
29
+     id="defs3767" />
30
+  <sodipodi:namedview
31
+     pagecolor="#ffffff"
32
+     bordercolor="#666666"
33
+     borderopacity="1"
34
+     objecttolerance="10"
35
+     gridtolerance="10"
36
+     guidetolerance="10"
37
+     inkscape:pageopacity="0"
38
+     inkscape:pageshadow="2"
39
+     inkscape:window-width="1916"
40
+     inkscape:window-height="1041"
41
+     id="namedview3765"
42
+     showgrid="false"
43
+     inkscape:zoom="35.625"
44
+     inkscape:cx="6.3017544"
45
+     inkscape:cy="12"
46
+     inkscape:window-x="0"
47
+     inkscape:window-y="18"
48
+     inkscape:window-maximized="0"
49
+     inkscape:current-layer="svg3763" />
50
+  <path
51
+     d="M18,10H13V7.59L22.12,6.07L21.88,4.59L16.41,5.5C16.46,5.35 16.5,5.18 16.5,5A1.5,1.5 0 0,0 15,3.5A1.5,1.5 0 0,0 13.5,5C13.5,5.35 13.63,5.68 13.84,5.93L13,6.07V5H11V6.41L10.41,6.5C10.46,6.35 10.5,6.18 10.5,6A1.5,1.5 0 0,0 9,4.5A1.5,1.5 0 0,0 7.5,6C7.5,6.36 7.63,6.68 7.83,6.93L1.88,7.93L2.12,9.41L11,7.93V10H6C4.89,10 4,10.9 4,12V18A2,2 0 0,0 6,20H18A2,2 0 0,0 20,18V12A2,2 0 0,0 18,10M6,12H8.25V16H6V12M9.75,16V12H14.25V16H9.75M18,16H15.75V12H18V16Z"
52
+     id="path3761"
53
+     style="fill:#5ccbf0;fill-opacity:1" />
54
+</svg>

+ 54
- 0
assets/icons/rocket.svg View File

@@ -0,0 +1,54 @@
1
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+<svg
3
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
4
+   xmlns:cc="http://creativecommons.org/ns#"
5
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
6
+   xmlns:svg="http://www.w3.org/2000/svg"
7
+   xmlns="http://www.w3.org/2000/svg"
8
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
9
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
10
+   version="1.1"
11
+   width="24"
12
+   height="24"
13
+   viewBox="0 0 24 24"
14
+   id="svg4614"
15
+   sodipodi:docname="rocket.svg"
16
+   inkscape:version="0.92.4 5da689c313, 2019-01-14">
17
+  <metadata
18
+     id="metadata4620">
19
+    <rdf:RDF>
20
+      <cc:Work
21
+         rdf:about="">
22
+        <dc:format>image/svg+xml</dc:format>
23
+        <dc:type
24
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
25
+      </cc:Work>
26
+    </rdf:RDF>
27
+  </metadata>
28
+  <defs
29
+     id="defs4618" />
30
+  <sodipodi:namedview
31
+     pagecolor="#ffffff"
32
+     bordercolor="#666666"
33
+     borderopacity="1"
34
+     objecttolerance="10"
35
+     gridtolerance="10"
36
+     guidetolerance="10"
37
+     inkscape:pageopacity="0"
38
+     inkscape:pageshadow="2"
39
+     inkscape:window-width="1916"
40
+     inkscape:window-height="1041"
41
+     id="namedview4616"
42
+     showgrid="false"
43
+     inkscape:zoom="9.8333333"
44
+     inkscape:cx="-8.6440678"
45
+     inkscape:cy="12"
46
+     inkscape:window-x="0"
47
+     inkscape:window-y="18"
48
+     inkscape:window-maximized="0"
49
+     inkscape:current-layer="svg4614" />
50
+  <path
51
+     d="M2.81,14.12L5.64,11.29L8.17,10.79C11.39,6.41 17.55,4.22 19.78,4.22C19.78,6.45 17.59,12.61 13.21,15.83L12.71,18.36L9.88,21.19L9.17,17.66C7.76,17.66 7.76,17.66 7.05,16.95C6.34,16.24 6.34,16.24 6.34,14.83L2.81,14.12M5.64,16.95L7.05,18.36L4.39,21.03H2.97V19.61L5.64,16.95M4.22,15.54L5.46,15.71L3,18.16V16.74L4.22,15.54M8.29,18.54L8.46,19.78L7.26,21H5.84L8.29,18.54M13,9.5A1.5,1.5 0 0,0 11.5,11A1.5,1.5 0 0,0 13,12.5A1.5,1.5 0 0,0 14.5,11A1.5,1.5 0 0,0 13,9.5Z"
52
+     id="path4612"
53
+     style="fill:#5ccbf0;fill-opacity:1" />
54
+</svg>

BIN
assets/logo.png View File


+ 1
- 1
assets/style/app.styl View File

@@ -1,2 +1,2 @@
1 1
 // Import Vuetify styling
2
-@require '~vuetify/src/stylus/app.styl'
2
+@require '~vuetify/src/stylus/app.styl'

+ 1
- 0
assets/svg/cup.svg View File

@@ -0,0 +1 @@
1
+<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 200 200" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><g><g><circle cx="100" cy="100" r="98.529" style="fill:#5ccbf0;fill-opacity:0.35;"/><path d="M100,0c55.192,0 100,44.808 100,100c0,55.192 -44.808,100 -100,100c-55.192,0 -100,-44.808 -100,-100c0,-55.192 44.808,-100 100,-100Zm0,10c49.672,0 90,40.328 90,90c0,49.672 -40.328,90 -90,90c-49.672,0 -90,-40.328 -90,-90c0,-49.672 40.328,-90 90,-90Z" style="fill:#5ccbf0;"/></g><g><path d="M154.176,44.492l-10.846,0l-7.699,7.525l18.545,0c2.076,0 3.763,1.686 3.763,3.762l0,5.732c0,7.584 -2.609,15.021 -7.349,20.944c-4.046,5.06 -9.432,8.814 -15.575,10.861l-5.306,1.768c-1.969,0.657 -3.036,2.788 -2.381,4.762c0.525,1.576 1.995,2.572 3.571,2.572c0.393,0 0.794,-0.063 1.191,-0.191l5.306,-1.772c7.521,-2.505 14.116,-7.105 19.073,-13.297c5.798,-7.253 8.995,-16.362 8.995,-25.647l0,-5.732c0,-6.224 -5.064,-11.287 -11.288,-11.287Z" style="fill:#ff7816;fill-rule:nonzero;"/><path d="M72.791,95.084l-5.306,-1.764c-6.143,-2.051 -11.529,-5.805 -15.575,-10.865c-4.74,-5.923 -7.349,-13.36 -7.349,-20.944l0,-5.732c0,-2.076 1.687,-3.762 3.763,-3.762l19.444,0l-8.598,-7.525l-10.846,0c-6.224,0 -11.288,5.063 -11.288,11.287l0,5.732c0,9.285 3.197,18.394 8.995,25.647c4.957,6.192 11.552,10.792 19.073,13.301l5.306,1.768c0.397,0.128 0.798,0.191 1.191,0.191c1.576,0 3.046,-0.996 3.571,-2.572c0.655,-1.974 -0.412,-4.105 -2.381,-4.762Z" style="fill:#ff9f00;fill-rule:nonzero;"/><path d="M112.788,117.936l0,5.569c0,2.106 1.655,3.762 3.763,3.762c6.246,0 11.287,5.041 11.287,11.288l0,7.525c0,2.106 -1.654,3.762 -3.762,3.762l-45.652,0c-2.108,0 -3.762,-1.656 -3.762,-3.762l0,-7.525c0,-6.247 5.041,-11.288 11.287,-11.288c2.108,0 3.763,-1.656 3.763,-3.762l0,-5.569c-13.997,-4.44 -24.23,-16.781 -25.886,-31.455l-4.666,-41.989l84.18,0l-4.666,41.989c-1.656,14.674 -11.889,27.015 -25.886,31.455Z" style="fill:#ffd400;fill-rule:nonzero;"/><path d="M124.076,149.842l-22.826,0l0,-105.35l42.09,0l-4.666,41.989c-1.656,14.674 -11.889,27.015 -25.886,31.455l0,5.569c0,2.106 1.655,3.762 3.763,3.762c6.246,0 11.287,5.041 11.287,11.288l0,7.525c0,2.106 -1.654,3.762 -3.762,3.762Z" style="fill:#ff9f00;fill-rule:nonzero;"/><path d="M135.364,146.08l0,22.776l-68.228,0l0,-22.776c0,-2.108 1.655,-3.763 3.763,-3.763l60.702,0c2.108,0 3.763,1.655 3.763,3.763Z" style="fill:#ec5569;fill-rule:nonzero;"/><path d="M135.364,146.08l0,22.776l-34.114,0l0,-26.539l30.351,0c2.108,0 3.763,1.655 3.763,3.763Z" style="fill:#cc2e43;fill-rule:nonzero;"/><path d="M142.889,169.156c0,2.107 -1.655,3.763 -3.763,3.763l-75.752,0c-2.108,0 -3.763,-1.656 -3.763,-3.763c0,-2.107 1.655,-3.762 3.763,-3.762l75.752,0c2.108,0 3.763,1.655 3.763,3.762Z" style="fill:#697c86;fill-rule:nonzero;"/><path d="M142.889,169.156c0,2.107 -1.655,3.763 -3.763,3.763l-37.876,0l0,-7.525l37.876,0c2.108,0 3.763,1.655 3.763,3.762Z" style="fill:#465a61;fill-rule:nonzero;"/></g><g><path d="M81.64,75.469l0,21.616l19.61,0l0.069,-40l-19.679,18.384Z" style="fill:#5ccbf0;"/><path d="M101.317,57.085l-0.067,40l19.524,0l0,-21.579l-19.457,-18.421Z" style="fill:#2a98b7;"/></g></g></svg>

+ 1
- 0
assets/svg/flux_x.svg View File

@@ -0,0 +1 @@
1
+<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 271 284" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><path d="M85,143.377l-85,-93.5l56.333,-49.877l79.334,87.21l79,-87.21l55.625,49.877l-83.625,93.5l83.625,92.5l-55.625,48.123l-79,-84.456l-79.334,84.456l-56.333,-48.123l85,-92.5Z" style="fill:#5ccbf0;"/></svg>

+ 1
- 0
assets/svg/rocket.svg View File

@@ -0,0 +1 @@
1
+<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 200 200" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><g><g><circle cx="100" cy="100" r="98.529" style="fill:#5ccbf0;fill-opacity:0.25;"/><path d="M100,0c55.192,0 100,44.808 100,100c0,55.192 -44.808,100 -100,100c-55.192,0 -100,-44.808 -100,-100c0,-55.192 44.808,-100 100,-100Zm0,10c49.672,0 90,40.328 90,90c0,49.672 -40.328,90 -90,90c-49.672,0 -90,-40.328 -90,-90c0,-49.672 40.328,-90 90,-90Z" style="fill:#5ccbf0;"/></g><g><path d="M61.587,88.426c-8.084,-1.099 -17.515,2.396 -24.025,8.907l-10.871,10.871c-1.229,1.229 -1.482,3.132 -0.611,4.639c0.85,1.488 2.63,2.246 4.323,1.792c6.899,-1.85 16.029,-2.465 18.274,-0.751l12.91,-25.458Z" style="fill:#ff637b;fill-rule:nonzero;"/><path d="M107.895,135.024l-25.496,12.299c1.421,1.935 1.309,8.055 -0.347,19.261c-0.247,1.658 0.61,3.262 2.073,3.994c1.48,0.749 3.272,0.459 4.445,-0.714l10.871,-10.871c6.142,-6.141 9.462,-15.55 8.454,-23.969Z" style="fill:#e63950;fill-rule:nonzero;"/><path d="M86.232,148.635c-8.968,4.837 -10.109,5.109 -11.469,6.033c-1.522,0.978 -3.479,0.761 -4.783,-0.543l-27.178,-27.178c-1.25,-1.251 -1.522,-3.262 -0.544,-4.784c0.924,-1.359 1.033,-2.229 5.925,-11.578l10.763,-0.109l27.178,27.178l0.108,10.981Z" style="fill:#7c8388;fill-rule:nonzero;"/><path d="M86.232,148.635c-8.968,4.837 -10.109,5.109 -11.469,6.033c-1.522,0.978 -3.479,0.761 -4.783,-0.543l-13.59,-13.59l16.307,-16.306l13.426,13.425l0.109,10.981Z" style="fill:#575f64;fill-rule:nonzero;"/><path d="M86.232,148.635l-38.049,-38.05c18.861,-35.603 41.528,-57.835 67.837,-66.531l18.481,18.372l18.045,18.047c-8.696,26.199 -30.873,49.028 -66.314,68.162Z" style="fill:#faecd8;fill-rule:nonzero;"/><path d="M86.232,148.635l-18.971,-18.971l67.24,-67.238l18.045,18.046c-8.696,26.2 -30.873,49.029 -66.314,68.163Z" style="fill:#f4d7af;fill-rule:nonzero;"/><path d="M152.546,80.472c-8.315,-0.598 -17.23,-4.512 -24.459,-11.632l-0.163,-0.163c-7.284,-7.284 -11.361,-16.253 -11.904,-24.624c2.5,-0.87 5.054,-1.577 7.61,-2.174c9.294,-2.12 19.024,-2.61 29.135,-1.522c0.977,0.109 1.901,0.598 2.445,1.359c0.543,0.543 0.925,1.25 0.979,2.066c1.142,10.055 0.543,19.785 -1.577,29.08c-0.597,2.555 -1.25,5.055 -2.066,7.61Z" style="fill:#ff637b;fill-rule:nonzero;"/><path d="M152.546,80.472c-8.315,-0.598 -17.23,-4.512 -24.459,-11.632l27.123,-27.124c0.543,0.543 0.924,1.25 0.979,2.066c1.141,10.056 0.543,19.785 -1.577,29.08c-0.597,2.555 -1.25,5.055 -2.066,7.61Z" style="fill:#e63950;fill-rule:nonzero;"/><path d="M85.579,114.825c-0.054,-0.925 -0.434,-1.739 -1.087,-2.392c-0.652,-0.87 -1.684,-1.467 -2.771,-1.467c-14.133,-0.109 -17.286,4.891 -18.101,5.707c-23.536,23.536 -23.808,35.115 -23.808,36.31c0.054,1.033 0.434,1.957 1.141,2.663c0.054,0.054 0.109,0.109 0.218,0.109c0.544,0.544 1.304,0.87 2.119,0.924c1.957,0.217 12.991,-0.054 36.636,-23.699c5.328,-5.327 5.708,-10.273 5.653,-18.155Z" style="fill:#ff637b;fill-rule:nonzero;"/><path d="M85.579,114.825c0.055,7.882 -0.325,12.828 -5.653,18.155c-23.645,23.645 -34.679,23.916 -36.635,23.699c-0.816,-0.054 -1.576,-0.38 -2.12,-0.924l43.322,-43.322c0.652,0.653 1.032,1.467 1.086,2.392Z" style="fill:#e63950;fill-rule:nonzero;"/></g><g><path d="M92.777,76.372l-15.294,15.274l13.858,13.876l28.351,-28.217l-26.915,-0.933Z" style="fill:#5ccbf0;"/><path d="M119.69,77.303l-28.35,28.219l13.797,13.814l15.268,-15.249l-0.715,-26.784Z" style="fill:#2a98b7;"/></g></g></svg>

+ 1
- 0
assets/svg/shield.svg View File

@@ -0,0 +1 @@
1
+<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 200 200" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><g><g><g><circle cx="100" cy="100" r="98.529" style="fill:#5ccbf0;fill-opacity:0.35;"/><path d="M100,0c55.192,0 100,44.808 100,100c0,55.192 -44.808,100 -100,100c-55.192,0 -100,-44.808 -100,-100c0,-55.192 44.808,-100 100,-100Zm0,10c49.672,0 90,40.328 90,90c0,49.672 -40.328,90 -90,90c-49.672,0 -90,-40.328 -90,-90c0,-49.672 40.328,-90 90,-90Z" style="fill:#5ccbf0;"/></g><g><path d="M155.121,123.345c-3.861,10.465 -9.7,19.564 -17.358,27.042c-8.717,8.512 -20.13,15.275 -33.925,20.1c-0.452,0.157 -0.925,0.287 -1.399,0.382c-0.627,0.124 -1.265,0.191 -1.897,0.2l-0.124,0c-0.674,0 -1.352,-0.068 -2.023,-0.2c-0.475,-0.095 -0.941,-0.225 -1.391,-0.379c-13.811,-4.817 -25.239,-11.577 -33.964,-20.089c-7.661,-7.478 -13.5,-16.571 -17.355,-27.036c-7.011,-19.026 -6.613,-39.986 -6.291,-56.83l0.005,-0.258c0.065,-1.391 0.106,-2.852 0.129,-4.465c0.118,-7.921 6.415,-14.474 14.337,-14.916c16.517,-0.922 29.294,-6.308 40.211,-16.95l0.096,-0.087c1.812,-1.663 4.097,-2.478 6.37,-2.444c2.192,0.028 4.375,0.843 6.123,2.444l0.093,0.087c10.92,10.642 23.697,16.028 40.213,16.95c7.922,0.442 14.22,6.995 14.337,14.916c0.023,1.624 0.065,3.083 0.13,4.465l0.003,0.109c0.32,16.875 0.717,37.877 -6.32,56.959Z" style="fill:#00dd7f;fill-rule:nonzero;"/><path d="M155.121,123.345c-3.861,10.465 -9.7,19.564 -17.358,27.042c-8.717,8.512 -20.13,15.275 -33.925,20.1c-0.452,0.157 -0.925,0.287 -1.399,0.382c-0.627,0.124 -1.265,0.191 -1.897,0.2l0,-143.654c2.192,0.028 4.375,0.843 6.123,2.444l0.093,0.087c10.92,10.642 23.697,16.028 40.213,16.95c7.922,0.442 14.22,6.995 14.337,14.916c0.023,1.624 0.065,3.083 0.13,4.465l0.003,0.109c0.32,16.875 0.717,37.877 -6.32,56.959Z" style="fill:#00aa62;fill-rule:nonzero;"/><path d="M136.218,99.241c0,19.698 -15.994,35.733 -35.676,35.803l-0.126,0c-19.74,0 -35.803,-16.062 -35.803,-35.803c0,-19.74 16.063,-35.802 35.803,-35.802l0.126,0c19.682,0.07 35.676,16.105 35.676,35.802Z" style="fill:#fefefe;fill-rule:nonzero;"/><path d="M136.218,99.241c0,19.698 -15.994,35.733 -35.676,35.803l0,-71.605c19.682,0.07 35.676,16.105 35.676,35.802Z" style="fill:#e0ebef;fill-rule:nonzero;"/><path d="M103.454,113.447l-3.039,-14.926l-15.64,15.718l9.116,8.949l9.563,-9.741Z" style="fill:#5ccbf0;"/><path d="M115.304,83.213l-14.889,15.308l0.127,17.817l23.928,-24.133l-0.332,-8.992l-8.834,0Z" style="fill:#2a98b7;"/><path d="M99.974,113.447l-3.179,-3.12l-0.141,-0.139l-1.387,-1.362l-14.384,-14.122l-7.932,8.079l19.092,18.743l7.931,-8.079Z" style="fill:#5ccbf0;"/></g></g></g></svg>

BIN
assets/trainstation_dark_small.jpg View File


+ 0
- 79
components/Logo.vue View File

@@ -1,79 +0,0 @@
1
-<template>
2
-  <div class="VueToNuxtLogo">
3
-    <div class="Triangle Triangle--two" />
4
-    <div class="Triangle Triangle--one" />
5
-    <div class="Triangle Triangle--three" />
6
-    <div class="Triangle Triangle--four" />
7
-  </div>
8
-</template>
9
-
10
-<style>
11
-.VueToNuxtLogo {
12
-  display: inline-block;
13
-  animation: turn 2s linear forwards 1s;
14
-  transform: rotateX(180deg);
15
-  position: relative;
16
-  overflow: hidden;
17
-  height: 180px;
18
-  width: 245px;
19
-}
20
-
21
-.Triangle {
22
-  position: absolute;
23
-  top: 0;
24
-  left: 0;
25
-  width: 0;
26
-  height: 0;
27
-}
28
-
29
-.Triangle--one {
30
-  border-left: 105px solid transparent;
31
-  border-right: 105px solid transparent;
32
-  border-bottom: 180px solid #41b883;
33
-}
34
-
35
-.Triangle--two {
36
-  top: 30px;
37
-  left: 35px;
38
-  animation: goright 0.5s linear forwards 3.5s;
39
-  border-left: 87.5px solid transparent;
40
-  border-right: 87.5px solid transparent;
41
-  border-bottom: 150px solid #3b8070;
42
-}
43
-
44
-.Triangle--three {
45
-  top: 60px;
46
-  left: 35px;
47
-  animation: goright 0.5s linear forwards 3.5s;
48
-  border-left: 70px solid transparent;
49
-  border-right: 70px solid transparent;
50
-  border-bottom: 120px solid #35495e;
51
-}
52
-
53
-.Triangle--four {
54
-  top: 120px;
55
-  left: 70px;
56
-  animation: godown 0.5s linear forwards 3s;
57
-  border-left: 35px solid transparent;
58
-  border-right: 35px solid transparent;
59
-  border-bottom: 60px solid #fff;
60
-}
61
-
62
-@keyframes turn {
63
-  100% {
64
-    transform: rotateX(0deg);
65
-  }
66
-}
67
-
68
-@keyframes godown {
69
-  100% {
70
-    top: 180px;
71
-  }
72
-}
73
-
74
-@keyframes goright {
75
-  100% {
76
-    left: 70px;
77
-  }
78
-}
79
-</style>

+ 0
- 7
components/README.md View File

@@ -1,7 +0,0 @@
1
-# COMPONENTS
2
-
3
-**This directory is not required, you can delete it if you don't want to use it.**
4
-
5
-The components directory contains your Vue.js Components.
6
-
7
-_Nuxt.js doesn't supercharge these components._

+ 108
- 0
components/VehicleIcon.vue View File

@@ -0,0 +1,108 @@
1
+<template>
2
+  <div>
3
+    <v-icon
4
+      v-if="vId === 1"
5
+      :v-class="vSize"
6
+    >
7
+      directions_bus
8
+    </v-icon>
9
+    <v-icon
10
+      v-if="vId === 2"
11
+      :v-class="vSize"
12
+    >
13
+      train
14
+    </v-icon>
15
+    <v-icon
16
+      v-if="vId === 4"
17
+      :v-class="vSize"
18
+    >
19
+      subway
20
+    </v-icon>
21
+    <v-icon
22
+      v-if="vId === 8"
23
+      :v-class="vSize"
24
+    >
25
+      tram
26
+    </v-icon>
27
+    <img
28
+      v-if="vId === 16"
29
+      class="svg"
30
+      :src="require('~/assets/icons/gondola.svg')"
31
+      :v-class="vSize"
32
+    >
33
+    <v-icon
34
+      v-if="vId === 32"
35
+      :v-class="vSize"
36
+    >
37
+      directions_boat
38
+    </v-icon>
39
+    <v-icon
40
+      v-if="vId === 64"
41
+      :v-class="vSize"
42
+    >
43
+      airplanemode_active
44
+    </v-icon>
45
+    <img
46
+      v-if="vId === 128"
47
+      :src="require('~/assets/icons/rocket.svg')"
48
+      :v-class="vSize"
49
+    >
50
+  </div>
51
+</template>
52
+
53
+<script>
54
+export default {
55
+  props: {
56
+    id: {
57
+      type: Number,
58
+      default: 0
59
+    },
60
+
61
+    size: {
62
+      type: Number,
63
+      default: 2
64
+    }
65
+  },
66
+
67
+  data() {
68
+    return {
69
+      vMap: Object.assign(this.$t('vehicle')),
70
+      vId: this.id,
71
+      vSize: `size${this.size}`,
72
+      vSizes: [ 1, 2, 3 ]
73
+    }
74
+  },
75
+
76
+  watch: {
77
+    size: function (val) {
78
+      this.vSize = `size${this.size}`
79
+    }
80
+  }
81
+}
82
+</script>
83
+
84
+<style scoped>
85
+img path {
86
+  fill: #5ccbf0 !important;
87
+}
88
+.size1 {
89
+  width: 2vh;
90
+  height: auto
91
+}
92
+.size2 {
93
+  width: 4vh;
94
+  height: auto;
95
+  padding-top: 8px;
96
+}
97
+.size3 {
98
+  width: 8vh;
99
+  height: auto
100
+}
101
+.size4 {
102
+  width: 16vh;
103
+  height: auto
104
+}
105
+img {
106
+  width: 32px;
107
+}
108
+</style>

+ 0
- 21
components/VuetifyLogo.vue View File

@@ -1,21 +0,0 @@
1
-<template>
2
-  <img
3
-    class="VuetifyLogo"
4
-    alt="Vuetify Logo"
5
-    src="data:image/png;base64,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"
6
-  >
7
-</template>
8
-
9
-<style>
10
-.VuetifyLogo {
11
-  width: 180px;
12
-  transform: rotateY(560deg);
13
-  animation: turn 3.5s ease-out forwards 1s;
14
-}
15
-
16
-@keyframes turn {
17
-  100% {
18
-    transform: rotateY(0deg);
19
-  }
20
-}
21
-</style>

+ 193
- 0
components/pages/account/LoginForm.vue View File

@@ -0,0 +1,193 @@
1
+<template>
2
+  <v-card
3
+    class="mx-auto"
4
+    max-width="500"
5
+  >
6
+    <v-toolbar>
7
+      <v-toolbar-title>
8
+        {{ $t('accountPage.loginForm.title') }}
9
+      </v-toolbar-title>
10
+    </v-toolbar>
11
+
12
+    <v-card-title class="title font-weight-regular justify-space-between">
13
+      <!-- <v-avatar
14
+        color="primary lighten-2"
15
+        class="subheading white--text"
16
+        size="24"
17
+        v-text="step"
18
+      ></v-avatar> -->
19
+    </v-card-title>
20
+    <v-window v-model="step" touchless>
21
+      <v-window-item :value="1">
22
+        <form
23
+          lazy-validation
24
+          @keydown.enter.prevent="logIn"
25
+        >
26
+          <v-card-text>
27
+            <v-text-field
28
+              ref="emailField"
29
+              v-model="email"
30
+              :rules="emailRules"
31
+              :label="$t('accountPage.loginForm.form.label')"
32
+              :disabled="loading"
33
+              type="email"
34
+              required
35
+            />
36
+            <span class="caption white--text text--darken-1">
37
+              {{ $t('accountPage.loginForm.form.description') }}
38
+            </span>
39
+          </v-card-text>
40
+        </form>
41
+      </v-window-item>
42
+
43
+      <v-window-item :value="2">
44
+        <div class="pa-3 text-xs-center">
45
+          <h3 class="title font-weight-light mb-2">
46
+            {{ $t('accountPage.loginForm.welcome') }}
47
+          </h3>
48
+          <v-img
49
+            class="mb-3"
50
+            contain
51
+            height="64"
52
+            :src="require('@/assets/brand_logo.png')"
53
+          />
54
+          <span class="green--text text--darken-1">
55
+            <strong>
56
+              {{ $t('accountPage.loginForm.checkMail.mailHint') }}
57
+            </strong>
58
+          </span>
59
+          <br><br><hr><br>
60
+          <span class="text--darken-1">
61
+            <i>
62
+              <small>
63
+                {{ $t('accountPage.loginForm.checkMail.spamHint') }}
64
+              </small>
65
+            </i>
66
+          </span>
67
+        </div>
68
+      </v-window-item>
69
+    </v-window>
70
+
71
+    <v-divider />
72
+
73
+    <v-card-actions>
74
+      <v-btn
75
+        :disabled="step === 1"
76
+        flat
77
+        @click="step--"
78
+      >
79
+        {{ $t('generic.form.btn.back') }}
80
+      </v-btn>
81
+      <v-spacer />
82
+      <v-btn
83
+        depressed
84
+        :disabled="loading || isDisabled || step === 2"
85
+        :loading="loading"
86
+        @click="logIn"
87
+      >
88
+        {{ $t('generic.form.btn.submit') }}
89
+        <template v-slot:loader>
90
+          <span class="custom-loader">
91
+            <v-icon light>cached</v-icon>
92
+          </span>
93
+        </template>
94
+      </v-btn>
95
+    </v-card-actions>
96
+  </v-card>
97
+</template>
98
+
99
+<script>
100
+import isEmail from 'validator/lib/isEmail'
101
+
102
+export default {
103
+  data() {
104
+    return {
105
+      step: 1,
106
+      loading: false,
107
+      valid: true,
108
+      email: '',
109
+      emailRules: [
110
+        v => !!v || this.$t('accountPage.loginForm.form.err.email'),
111
+        v => /.+@.+\...+/.test(v) || this.$t('accountPage.loginForm.form.err.email')
112
+      ]
113
+    }
114
+  },
115
+
116
+  computed: {
117
+    isDisabled() {
118
+      return !this.email || !isEmail(this.email)
119
+    }
120
+  },
121
+
122
+  methods: {
123
+    reset() {
124
+      this.loading = false
125
+      this.$refs.emailField.reset()
126
+    },
127
+
128
+    logIn() {
129
+      if (isEmail(this.email)) {
130
+        this.loading = true
131
+        this.$axios.$post(`/api/login/email`, JSON.stringify({
132
+          email: this.email
133
+        }),
134
+        {
135
+          timeout: 4000,
136
+          headers: {
137
+            'content-type': 'application/json'
138
+          }
139
+        })
140
+          .then((res) => {
141
+            this.step = 2
142
+            if (res.status !== 202) {
143
+              this.flash({ message: 'NOT GOOD!', variant: 'danger' })
144
+            } else {
145
+              this.flash({ message: 'FSCKYEAH!', variant: 'success' })
146
+            }
147
+            this.reset()
148
+          })
149
+          .catch(() => this.reset())
150
+      }
151
+    }
152
+  }
153
+}
154
+</script>
155
+
156
+<style>
157
+.custom-loader {
158
+  animation: loader 1s infinite;
159
+  display: flex;
160
+}
161
+@-moz-keyframes loader {
162
+  from {
163
+    transform: rotate(0);
164
+  }
165
+  to {
166
+    transform: rotate(360deg);
167
+  }
168
+}
169
+@-webkit-keyframes loader {
170
+  from {
171
+    transform: rotate(0);
172
+  }
173
+  to {
174
+    transform: rotate(360deg);
175
+  }
176
+}
177
+@-o-keyframes loader {
178
+  from {
179
+    transform: rotate(0);
180
+  }
181
+  to {
182
+    transform: rotate(360deg);
183
+  }
184
+}
185
+@keyframes loader {
186
+  from {
187
+    transform: rotate(0);
188
+  }
189
+  to {
190
+    transform: rotate(360deg);
191
+  }
192
+}
193
+</style>

+ 36
- 0
components/pages/account/MyAccount.vue View File

@@ -0,0 +1,36 @@
1
+<template>
2
+  <div>
3
+    <v-toolbar>
4
+      {{ $t('accountPage.myAccount.title') }}
5
+    </v-toolbar>
6
+    <v-card>
7
+      <v-container>
8
+        <v-layout>
9
+          <v-flex>
10
+            <p>More features coming soon...</p>
11
+            <v-btn block color="yellow darken-1" @click="logout">
12
+              Logout
13
+            </v-btn>
14
+          </v-flex>
15
+        </v-layout>
16
+      </v-container>
17
+    </v-card>
18
+  </div>
19
+</template>
20
+
21
+<script>
22
+export default {
23
+  methods: {
24
+    logout: function () {
25
+      this.$store.commit('user/logout')
26
+      this.$router.push('/')
27
+    }
28
+  }
29
+}
30
+</script>
31
+
32
+<style scoped>
33
+.v-btn{
34
+  color: #212121 !important;
35
+}
36
+</style>

+ 91
- 0
components/pages/flux/Report/Step1.vue View File

@@ -0,0 +1,91 @@
1
+<template>
2
+  <div>
3
+    <v-layout wrap justify-space-around>
4
+      <v-flex xs12 sm8>
5
+        <v-form ref="form">
6
+          <v-container fluid>
7
+            <v-layout wrap justify-space-between>
8
+              <v-flex xs4>
9
+                <v-autocomplete
10
+                  v-model="country"
11
+                  :items="isoCountries"
12
+                  :label="$t('fluxPage.report.form.label.country')"
13
+                  autofocus
14
+                />
15
+              </v-flex>
16
+              <v-flex xs8>
17
+                <v-combobox
18
+                  v-model="city"
19
+                  :label="$t('fluxPage.report.form.label.city')"
20
+                />
21
+              </v-flex>
22
+            </v-layout>
23
+            <v-layout>
24
+              <v-flex xs12>
25
+                <v-text-field
26
+                  v-model="location"
27
+                  :label="$t('fluxPage.report.form.label.location')"
28
+                  @keyup.enter="goNextFluxReportFormPage"
29
+                />
30
+              </v-flex>
31
+            </v-layout>
32
+          </v-container>
33
+        </v-form>
34
+      </v-flex>
35
+    </v-layout>
36
+    <v-card class="mb-5" />
37
+    <v-layout>
38
+      <v-flex class="text-xs-right">
39
+        <v-btn flat @click="cancelReport">
40
+          {{ $t('generic.form.btn.cancel') }}
41
+        </v-btn>
42
+        <v-btn color="#5ccbf0" class="yes" @click="goNextFluxReportFormPage">
43
+          {{ $t('generic.form.btn.next') }}
44
+        </v-btn>
45
+      </v-flex>
46
+    </v-layout>
47
+  </div>
48
+</template>
49
+
50
+<script>
51
+import { isoCountries } from '@/utils/countryCodes'
52
+
53
+export default {
54
+  data() {
55
+    return {
56
+      country: this.$store.state.report.form.country,
57
+      city: this.$store.state.report.form.city,
58
+      location: this.$store.state.report.form.location,
59
+      countryRules: [
60
+        v => !!v || this.$t('fluxPage.report.form.err.required'),
61
+        v => Object.keys(isoCountries).includes(v.toUpperCase()) || this.$t('unknownCountryCode')
62
+      ],
63
+      isRequiredRules: [
64
+        v => !!v || this.$t('fluxPage.report.form.err.required')
65
+      ],
66
+      isoCountries: Object.keys(isoCountries)
67
+    }
68
+  },
69
+
70
+  methods: {
71
+    cancelReport: function () {
72
+      this.$store.commit('report/reset')
73
+      this.$router.push('/flux/stream')
74
+    },
75
+    goNextFluxReportFormPage: function () {
76
+      if (this.$refs.form.validate()) {
77
+        this.$store.commit('report/updateFields', {
78
+          country: this.country,
79
+          city: this.city,
80
+          location: this.location
81
+        })
82
+        this.$store.commit('report/stepForward')
83
+      }
84
+    }
85
+  }
86
+}
87
+</script>
88
+
89
+<style>
90
+
91
+</style>

+ 214
- 0
components/pages/flux/Report/Step2.vue View File

@@ -0,0 +1,214 @@
1
+<template>
2
+  <div>
3
+    <v-form ref="form" @submit.prevent="goNextFluxReportPage">
4
+      <v-layout wrap justify-space-around>
5
+        <v-flex xs4>
6
+          <center>
7
+            <v-text-field
8
+              v-model="line"
9
+              :label="$t('fluxPage.report.form.label.line')"
10
+              autofocus
11
+              @keyup.enter="goNextFluxReportFormPage"
12
+            />
13
+          </center>
14
+        </v-flex>
15
+        <v-flex xs8>
16
+          <center>
17
+            <v-text-field
18
+              v-model="direction"
19
+              :label="$t('fluxPage.report.form.label.direction')"
20
+              autofocus
21
+              @keyup.enter="goNextFluxReportFormPage"
22
+            />
23
+          </center>
24
+        </v-flex>
25
+        <v-flex xs12>
26
+          <center>
27
+            {{ vehicleText }}
28
+          </center>
29
+        </v-flex>
30
+        <v-flex>
31
+          <v-btn
32
+            :outline="isSelectedVehicle(1)"
33
+            flat
34
+            block
35
+            @click="selectVehicle(1)"
36
+          >
37
+            <VehicleIcon
38
+              :id="1"
39
+              :size="2"
40
+            />
41
+          </v-btn>
42
+        </v-flex>
43
+        <v-flex>
44
+          <v-btn
45
+            :outline="isSelectedVehicle(2)"
46
+            flat
47
+            block
48
+            @click="selectVehicle(2)"
49
+          >
50
+            <VehicleIcon
51
+              :id="2"
52
+              :size="2"
53
+            />
54
+          </v-btn>
55
+        </v-flex>
56
+        <v-flex>
57
+          <v-btn
58
+            :outline="isSelectedVehicle(4)"
59
+            flat
60
+            block
61
+            @click="selectVehicle(4)"
62
+          >
63
+            <VehicleIcon
64
+              :id="4"
65
+              :size="2"
66
+            />
67
+          </v-btn>
68
+        </v-flex>
69
+        <v-flex>
70
+          <v-btn
71
+            :outline="isSelectedVehicle(8)"
72
+            flat
73
+            block
74
+            @click="selectVehicle(8)"
75
+          >
76
+            <VehicleIcon
77
+              :id="8"
78
+              :size="2"
79
+            />
80
+          </v-btn>
81
+        </v-flex>
82
+        <v-flex>
83
+          <v-btn
84
+            :outline="isSelectedVehicle(16)"
85
+            flat
86
+            block
87
+            @click="selectVehicle(16)"
88
+          >
89
+            <VehicleIcon
90
+              :id="16"
91
+              :size="2"
92
+            />
93
+          </v-btn>
94
+        </v-flex>
95
+        <v-flex>
96
+          <v-btn
97
+            :outline="isSelectedVehicle(32)"
98
+            flat
99
+            block
100
+            @click="selectVehicle(32)"
101
+          >
102
+            <VehicleIcon
103
+              :id="32"
104
+              :size="2"
105
+            />
106
+          </v-btn>
107
+        </v-flex>
108
+        <v-flex>
109
+          <v-btn
110
+            :outline="isSelectedVehicle(64)"
111
+            flat
112
+            block
113
+            @click="selectVehicle(64)"
114
+          >
115
+            <VehicleIcon
116
+              :id="64"
117
+              :size="2"
118
+            />
119
+          </v-btn>
120
+        </v-flex>
121
+        <v-flex>
122
+          <v-btn
123
+            :outline="isSelectedVehicle(128)"
124
+            flat
125
+            block
126
+            @click="selectVehicle(128)"
127
+          >
128
+            <VehicleIcon
129
+              :id="128"
130
+              :size="2"
131
+            />
132
+          </v-btn>
133
+        </v-flex>
134
+      </v-layout>
135
+    </v-form>
136
+    <v-card class="mb-5" />
137
+    <v-layout>
138
+      <v-flex class="text-xs-right">
139
+        <v-btn flat @click="goPrevFluxReportFormPage">
140
+          {{ $t('generic.form.btn.back') }}
141
+        </v-btn>
142
+        <v-btn color="#5ccbf0" class="yes" @click="goNextFluxReportFormPage">
143
+          {{ $t('generic.form.btn.next') }}
144
+        </v-btn>
145
+      </v-flex>
146
+    </v-layout>
147
+  </div>
148
+</template>
149
+
150
+<script>
151
+import VehicleIcon from '@/components/VehicleIcon.vue'
152
+
153
+export default {
154
+  components: {
155
+    VehicleIcon
156
+  },
157
+
158
+  data() {
159
+    return {
160
+      line: this.$store.state.report.form.line,
161
+      direction: this.$store.state.report.form.direction,
162
+      vehicle: this.$store.state.report.form.vehicle,
163
+      lineRules: [
164
+        v => !!v || this.$t('fluxPage.report.form.err.required')
165
+      ],
166
+      directionRules: [
167
+        v => !!v || this.$t('fluxPage.report.form.err.required')
168
+      ]
169
+    }
170
+  },
171
+
172
+  computed: {
173
+    vehicleText: function () {
174
+      switch (this.vehicle) {
175
+        case 1: { return this.$t('vehicle.bus') }
176
+        case 2: return this.$t('vehicle.train')
177
+        case 4: return this.$t('vehicle.subway')
178
+        case 8: return this.$t('vehicle.tram')
179
+        case 16: return this.$t('vehicle.lift')
180
+        case 32: return this.$t('vehicle.ship')
181
+        case 64: return this.$t('vehicle.airplane')
182
+        case 128: return this.$t('vehicle.rocket')
183
+        default: return this.$t('vehicle.none')
184
+      }
185
+    }
186
+  },
187
+
188
+  methods: {
189
+    goPrevFluxReportFormPage: function () {
190
+      this.$store.commit('report/stepBack')
191
+    },
192
+
193
+    goNextFluxReportFormPage: function () {
194
+      if (this.$refs.form.validate()) {
195
+        this.$store.commit('report/updateFields', {
196
+          line: this.line,
197
+          direction: this.direction,
198
+          vehicle: this.vehicle
199
+        })
200
+        this.$store.commit('report/stepForward')
201
+      }
202
+    },
203
+
204
+    selectVehicle: function (vId) {
205
+      this.vehicle = vId
206
+      this.goNextFluxReportFormPage()
207
+    },
208
+
209
+    isSelectedVehicle: function (vId) {
210
+      return this.fields.vehicle === vId
211
+    }
212
+  }
213
+}
214
+</script>

+ 106
- 0
components/pages/flux/Report/Step3.vue View File

@@ -0,0 +1,106 @@
1
+<template>
2
+  <div>
3
+    <v-layout row wrap justify-space-around>
4
+      <v-flex xs12>
5
+        <center>
6
+          <v-btn-toggle v-model="isArrivalDeparture" multiple>
7
+            <v-btn>
8
+              {{ $t('fluxPage.report.form.label.arrival') }}
9
+            </v-btn>
10
+            <v-btn>
11
+              {{ $t('fluxPage.report.form.label.departure') }}
12
+            </v-btn>
13
+          </v-btn-toggle>
14
+        </center>
15
+      </v-flex>
16
+      <v-flex xs12 py-3>
17
+        <center>
18
+          <span v-if="isArrivalDeparture.includes(0) && isArrivalDeparture.includes(1)">
19
+            {{ $t('fluxPage.report.form.label.arrival') }}
20
+            &amp;
21
+            {{ $t('fluxPage.report.form.label.departure') }}
22
+          </span>
23
+          <span v-else-if="isArrivalDeparture.includes(0)">
24
+            {{ $t('fluxPage.report.form.label.arrival') }}
25
+          </span>
26
+          <span v-else-if="isArrivalDeparture.includes(1)">
27
+            {{ $t('fluxPage.report.form.label.departure') }}
28
+          </span>
29
+          <span v-else>
30
+            {{ $t('generic.missingValue') }}
31
+          </span>
32
+        </center>
33
+      </v-flex>
34
+    </v-layout>
35
+
36
+    <v-layout wrap justify-space-around>
37
+      <v-flex xs12 md6 pa-2>
38
+        <DatetimePicker
39
+          :title="$t('fluxPage.report.form.label.scheduledAt')"
40
+          :date="`${scheduledAt}`"
41
+          @datetimeSelected="scheduledAt = $event"
42
+        />
43
+      </v-flex>
44
+    </v-layout>
45
+
46
+    <v-card class="mb-5" />
47
+    <v-layout>
48
+      <v-flex class="text-xs-right">
49
+        <v-btn flat @click="goPrevFluxReportFormPage">
50
+          {{ $t('generic.form.btn.back') }}
51
+        </v-btn>
52
+        <v-btn :disabled="!isValid" color="#5ccbf0" class="yes" @click="goNextFluxReportFormPage">
53
+          {{ $t('generic.form.btn.next') }}
54
+        </v-btn>
55
+      </v-flex>
56
+    </v-layout>
57
+  </div>
58
+</template>
59
+
60
+<script>
61
+import DatetimePicker from '@/components/picker/datetime'
62
+const moment = require('moment')
63
+
64
+export default {
65
+  components: {
66
+    DatetimePicker
67
+  },
68
+
69
+  data() {
70
+    const isArrivalDeparture = []
71
+    if (this.$store.state.report.form.arrival) {
72
+      isArrivalDeparture.push(0)
73
+    }
74
+    if (this.$store.state.report.form.departure) {
75
+      isArrivalDeparture.push(1)
76
+    }
77
+    return {
78
+      scheduledAt: this.$store.state.report.form.scheduledAt ? this.$store.state.report.form.scheduledAt : `${new Date()}`,
79
+      isArrivalDeparture: isArrivalDeparture
80
+    }
81
+  },
82
+
83
+  computed: {
84
+    isValid: function () {
85
+      if (this.scheduledAt && this.isArrivalDeparture.length > 0) {
86
+        return true
87
+      }
88
+      return false
89
+    }
90
+  },
91
+
92
+  methods: {
93
+    goPrevFluxReportFormPage: function () {
94
+      this.$store.commit('report/stepBack')
95
+    },
96
+    goNextFluxReportFormPage: function () {
97
+      this.$store.commit('report/updateFields', {
98
+        scheduledAt: this.scheduledAt ? moment(this.scheduledAt).startOf('minute').toDate() : '',
99
+        arrival: this.isArrivalDeparture.includes(0),
100
+        departure: this.isArrivalDeparture.includes(1)
101
+      })
102
+      this.$store.commit('report/stepForward')
103
+    }
104
+  }
105
+}
106
+</script>

+ 88
- 0
components/pages/flux/Report/Step4.vue View File

@@ -0,0 +1,88 @@
1
+<template>
2
+  <div>
3
+    <v-layout wrap justify-space-around>
4
+      <v-flex xs12>
5
+        <v-switch
6
+          v-model="cancelled"
7
+          :label="$t('fluxPage.report.form.label.cancelled')"
8
+          color="error"
9
+        >
10
+          Cancelled
11
+        </v-switch>
12
+      </v-flex>
13
+    </v-layout>
14
+
15
+    <v-layout wrap justify-space-around :d-none="cancelled">
16
+      <v-flex xs12 md6 pa-2>
17
+        <DatetimePicker
18
+          v-model="actuallyAt"
19
+          :date="`${actuallyAt}`"
20
+          :title="$t('fluxPage.report.form.label.actuallyAt')"
21
+          @datetimeSelected="actuallyAt = $event"
22
+        />
23
+      </v-flex>
24
+    </v-layout>
25
+
26
+    <v-card class="mb-5" />
27
+    <v-layout>
28
+      <v-flex class="text-xs-right">
29
+        <v-btn flat @click="goPrevFluxReportFormPage">
30
+          {{ $t('generic.form.btn.back') }}
31
+        </v-btn>
32
+        <v-btn :disabled="!isValid" color="green darken-1" class="yes" @click="goNextFluxReportFormPage">
33
+          {{ saveBtnLabel }}
34
+        </v-btn>
35
+      </v-flex>
36
+    </v-layout>
37
+  </div>
38
+</template>
39
+
40
+<script>
41
+import DatetimePicker from '@/components/picker/datetime'
42
+
43
+const moment = require('moment')
44
+
45
+export default {
46
+  components: {
47
+    DatetimePicker
48
+  },
49
+
50
+  data() {
51
+    return {
52
+      actuallyAt: this.$store.state.report.form.actuallyAt ? this.$store.state.report.form.actuallyAt : `${new Date()}`,
53
+      cancelled: this.$store.state.report.form.cancelled
54
+    }
55
+  },
56
+
57
+  computed: {
58
+    isValid: function () {
59
+      if (this.cancelled || this.actuallyAt) {
60
+        return true
61
+      }
62
+      return false
63
+    },
64
+
65
+    saveBtnLabel: function () {
66
+      if (this.$store.state.report.form.id) {
67
+        return this.$t('fluxPage.report.form.submitExisting')
68
+      } else {
69
+        return this.$t('fluxPage.report.form.submitNew')
70
+      }
71
+    }
72
+  },
73
+
74
+  methods: {
75
+    goPrevFluxReportFormPage: function () {
76
+      this.$store.commit('report/stepBack')
77
+    },
78
+
79
+    goNextFluxReportFormPage: function () {
80
+      this.$store.commit('report/updateFields', {
81
+        actuallyAt: this.actuallyAt && !this.cancelled ? moment(this.actuallyAt).startOf('minute').toDate() : '',
82
+        cancelled: this.cancelled
83
+      })
84
+      this.$store.dispatch('report/post')
85
+    }
86
+  }
87
+}
88
+</script>

+ 89
- 0
components/pages/flux/Report/index.vue View File

@@ -0,0 +1,89 @@
1
+<template>
2
+  <v-container>
3
+    <v-layout wrap justify-space-around>
4
+      <v-flex xs12 sm10 lg8 xl6>
5
+        <v-toolbar>
6
+          {{ title }}
7
+          <v-spacer />
8
+          <v-btn icon @click="resetCurrentFluxForm">
9
+            <v-icon>
10
+              cancel
11
+            </v-icon>
12
+          </v-btn>
13
+        </v-toolbar>
14
+        <v-stepper v-model="$store.state.report.windowStep">
15
+          <v-stepper-header>
16
+            <v-stepper-step :complete="$store.state.report.windowStep > 1" step="1">
17
+              {{ $t('fluxPage.report.step.where') }}
18
+            </v-stepper-step>
19
+            <v-divider />
20
+            <v-stepper-step :complete="$store.state.report.windowStep > 2" step="2">
21
+              {{ $t('fluxPage.report.step.what') }}
22
+            </v-stepper-step>
23
+            <v-divider />
24
+            <v-stepper-step :complete="$store.state.report.windowStep > 3" step="3">
25
+              {{ $t('fluxPage.report.step.when') }}
26
+            </v-stepper-step>
27
+            <v-divider />
28
+            <v-stepper-step :complete="$store.state.report.windowStep > 4" step="4">
29
+              {{ $t('fluxPage.report.step.details') }}
30
+            </v-stepper-step>
31
+          </v-stepper-header>
32
+          <v-stepper-items>
33
+            <v-stepper-content step="1">
34
+              <step1 />
35
+            </v-stepper-content>
36
+            <v-stepper-content step="2">
37
+              <step2 />
38
+            </v-stepper-content>
39
+            <v-stepper-content step="3">
40
+              <step3 />
41
+            </v-stepper-content>
42
+            <v-stepper-content step="4">
43
+              <step4 />
44
+            </v-stepper-content>
45
+          </v-stepper-items>
46
+        </v-stepper>
47
+      </v-flex>
48
+    </v-layout>
49
+  </v-container>
50
+</template>
51
+
52
+<script>
53
+import Step1 from './Step1.vue'
54
+import Step2 from './Step2.vue'
55
+import Step3 from './Step3.vue'
56
+import Step4 from './Step4.vue'
57
+
58
+export default {
59
+  components: {
60
+    Step1,
61
+    Step2,
62
+    Step3,
63
+    Step4
64
+  },
65
+
66
+  computed: {
67
+    title: function () {
68
+      if (!this.$store.state.report.form.id) {
69
+        return this.$t('fluxPage.report.title.new')
70
+      } else {
71
+        return this.$t('fluxPage.report.title.edit')
72
+      }
73
+    }
74
+  },
75
+
76
+  methods: {
77
+    resetCurrentFluxForm() {
78
+      this.$store.commit('report/reset')
79
+      this.$router.push('/flux/stream')
80
+    }
81
+  }
82
+}
83
+</script>
84
+
85
+<style>
86
+.yes {
87
+  color: #212121 !important;
88
+}
89
+</style>

+ 51
- 0
components/pages/flux/Stream/FluxItem/FluxItemCondensed.vue View File

@@ -0,0 +1,51 @@
1
+<template>
2
+  <v-layout row pa-2>
3
+    <v-flex xs2 sm1>
4
+      {{ acks }} x
5
+    </v-flex>
6
+
7
+    <v-flex xs10 sm11 class="shorten-text">
8
+      <strong>
9
+        {{ line }} {{ direction }}
10
+      </strong>
11
+      <small>
12
+        ({{ country }}, {{ city }})
13
+      </small>
14
+    </v-flex>
15
+  </v-layout>
16
+</template>
17
+
18
+<script>
19
+export default {
20
+  props: {
21
+    country: {
22
+      type: String,
23
+      default: ''
24
+    },
25
+    city: {
26
+      type: String,
27
+      default: ''
28
+    },
29
+    line: {
30
+      type: String,
31
+      default: ''
32
+    },
33
+    direction: {
34
+      type: String,
35
+      default: ''
36
+    },
37
+    acks: {
38
+      type: Number,
39
+      default: 0
40
+    },
41
+    avgDelay: {
42
+      type: Number,
43
+      default: 0
44
+    }
45
+  }
46
+}
47
+</script>
48
+
49
+<style>
50
+
51
+</style>

+ 89
- 0
components/pages/flux/Stream/FluxItem/FluxItemDetailed.vue View File

@@ -0,0 +1,89 @@
1
+<template>
2
+  <div>
3
+    <v-layout row pa-2>
4
+      <v-flex sm12 class="shorten-text">
5
+        <div class="larger">
6
+          {{ line }} {{ direction }}
7
+        </div>
8
+        <small>
9
+          ({{ country }}, {{ city }})
10
+        </small>
11
+      </v-flex>
12
+    </v-layout>
13
+    <v-layout row wrap px-2 pb-2 class="stream-container">
14
+      <v-flex xs12>
15
+        <ReportList :reports="reports" />
16
+      </v-flex>
17
+    </v-layout>
18
+  </div>
19
+</template>
20
+
21
+<script>
22
+import ReportList from './ReportList.vue'
23
+import { delay } from '@/utils/flux'
24
+const moment = require('moment')
25
+
26
+export default {
27
+  components: {
28
+    ReportList
29
+  },
30
+
31
+  props: {
32
+    country: {
33
+      type: String,
34
+      default: ''
35
+    },
36
+    city: {
37
+      type: String,
38
+      default: ''
39
+    },
40
+    line: {
41
+      type: String,
42
+      default: ''
43
+    },
44
+    direction: {
45
+      type: String,
46
+      default: ''
47
+    },
48
+    acks: {
49
+      type: Number,
50
+      default: 0
51
+    },
52
+    avgDelay: {
53
+      type: Number,
54
+      default: 0
55
+    },
56
+    reports: {
57
+      type: Array,
58
+      default: () => {
59
+        return []
60
+      }
61
+    }
62
+  },
63
+
64
+  data() {
65
+    return {}
66
+  },
67
+
68
+  methods: {
69
+    displayTimeField: function (field) {
70
+      return moment(field).format('YYYY-MM-DD hh:mm')
71
+    },
72
+
73
+    calcDelay: function (report) {
74
+      return delay(report)
75
+    }
76
+  }
77
+}
78
+</script>
79
+
80
+<style scoped>
81
+.larger {
82
+  font-weight: bold;
83
+  font-size: 24px;
84
+}
85
+
86
+.stream-container {
87
+  overflow-x: auto;
88
+}
89
+</style>

+ 184
- 0
components/pages/flux/Stream/FluxItem/ReportList.vue View File

@@ -0,0 +1,184 @@
1
+<template>
2
+  <v-list>
3
+    <div
4
+      v-for="report in reports"
5
+      :key="report.id"
6
+    >
7
+      <v-list-tile>
8
+        <v-list-tile-content>
9
+          <v-list-tile-title>
10
+            <i>
11
+              <small>
12
+                ({{ calcAcks(report) }})
13
+              </small>
14
+            </i>
15
+            &nbsp;
16
+            <small>
17
+              {{ formattedDate(report.scheduledAt) }}
18
+            </small>
19
+            &nbsp;
20
+            {{ formattedTime(report.scheduledAt) }}
21
+            &nbsp;
22
+            {{ report.location }}
23
+          </v-list-tile-title>
24
+          <v-list-tile-sub-title
25
+            v-if="report.cancelled"
26
+          >
27
+            {{ $t('fluxPage.stream.labels.cancelled') }}
28
+          </v-list-tile-sub-title>
29
+          <v-list-tile-sub-title>
30
+            {{ formattedDelay(report) }}
31
+          </v-list-tile-sub-title>
32
+        </v-list-tile-content>
33
+      </v-list-tile>
34
+      <v-layout
35
+        v-if="$store.state.user.id && report.user === $store.state.user.id"
36
+        row
37
+        wrap
38
+        justify-space-between
39
+      >
40
+        <br>
41
+        <v-flex xs4>
42
+          <v-layout row justify-center>
43
+            <v-dialog v-model="deleteOptInDialog" max-width="290">
44
+              <template v-slot:activator="{ on }">
45
+                <v-btn
46
+                  color="red"
47
+                  small
48
+                  block
49
+                  @click.stop="deleteOptInDialog = true"
50
+                >
51
+                  {{ $t('generic.form.btn.delete') }}
52
+                </v-btn>
53
+              </template>
54
+              <v-card>
55
+                <v-card-title class="headline" color="red--text text--darken-1">
56
+                  {{ $t('fluxPage.stream.deleteOptIn.title') }}
57
+                </v-card-title>
58
+                <v-card-text color="red--text text--darken-1">
59
+                  {{ $t('fluxPage.stream.deleteOptIn.info') }}
60
+                </v-card-text>
61
+                <v-card-actions>
62
+                  <v-spacer />
63
+                  <v-btn color="green darken-1 black--text" class="no" @click="deleteOptInDialog = false">
64
+                    {{ $t('generic.form.btn.no') }}
65
+                  </v-btn>
66
+                  <v-btn color="red darken-1" flat class="yes" @click="deleteReport(report.id)">
67
+                    {{ $t('generic.form.btn.yes') }}
68
+                  </v-btn>
69
+                </v-card-actions>
70
+              </v-card>
71
+            </v-dialog>
72
+          </v-layout>
73
+        </v-flex>
74
+        <v-flex xs4>
75
+          <v-btn
76
+            color="#5ccbf0"
77
+            small
78
+            block
79
+            @click.stop="editReport(report)"
80
+          >
81
+            {{ $t('generic.form.btn.edit') }}
82
+          </v-btn>
83
+        </v-flex>
84
+      </v-layout>
85
+      <v-divider />
86
+    </div>
87
+  </v-list>
88
+</template>
89
+
90
+<script>
91
+const moment = require('moment')
92
+
93
+export default {
94
+  props: {
95
+    reports: {
96
+      type: Array,
97
+      default: () => {
98
+        return []
99
+      }
100
+    }
101
+  },
102
+
103
+  data() {
104
+    return {
105
+      deleteOptInDialog: false
106
+    }
107
+  },
108
+
109
+  methods: {
110
+    formattedDate: function (d) {
111
+      return moment(d).format('MMM DD')
112
+    },
113
+
114
+    formattedTime: function (d) {
115
+      return moment(d).format('hh:mm')
116
+    },
117
+
118
+    avgDelay: function (report) {
119
+      const delay = moment(report.actuallyAt).diff(moment(report.scheduledAt)) / 1000 / 60
120
+      const relatedDelays = report.related.map((r) => {
121
+        if (r.cancelled) {
122
+          return 'x'
123
+        }
124
+        return moment(r.actuallyAt).diff(moment(r.scheduledAt)) / 1000 / 60
125
+      }).map((r) => {
126
+        if (r !== 'x') {
127
+          return r
128
+        }
129
+      })
130
+      relatedDelays.push(delay)
131
+      if (relatedDelays.length >= 2) {
132
+        const avg = relatedDelays.reduce(function (a, b) { return a + b })
133
+        return avg / relatedDelays.length
134
+      }
135
+      return relatedDelays[0]
136
+    },
137
+
138
+    formattedDelay: function (report) {
139
+      if (report.cancelled) {
140
+        return ''
141
+      }
142
+      const delay = this.avgDelay(report)
143
+      if (delay === 0) {
144
+        return `${delay} min`
145
+      } else if (delay > 0) {
146
+        return `+${delay} min`
147
+      }
148
+      return `-${delay} min`
149
+    },
150
+
151
+    calcAcks: function (report) {
152
+      return report.related.length + 1
153
+    },
154
+
155
+    editReport: function (report) {
156
+      this.$store.commit('report/edit', report)
157
+      this.$router.push('/flux/report')
158
+    },
159
+
160
+    deleteReport: function (reportId) {
161
+      console.log(reportId) // eslint-disable-line
162
+      this.deleteOptInDialog = false
163
+      this.$store.dispatch('report/delete', reportId)
164
+
165
+      if (this.$store.state.stream.onlyUserReports) {
166
+        this.$store.dispatch('stream/user', this.$store.state.user ? this.$store.state.user.id : '')
167
+      } else {
168
+        this.$store.dispatch('stream/get')
169
+      }
170
+    }
171
+  }
172
+}
173
+</script>
174
+
175
+<style>
176
+.v-list__tile__sub-title {
177
+  padding-left: 80px;
178
+  font-style: italic;
179
+}
180
+
181
+.no {
182
+  color: #212121 !important;
183
+}
184
+</style>

+ 83
- 0
components/pages/flux/Stream/FluxItem/index.vue View File

@@ -0,0 +1,83 @@
1
+<template>
2
+  <div>
3
+    <FluxItemCondensed
4
+      v-if="this.$store.state.stream.selected !== reportKey"
5
+      :country="reports[0].country"
6
+      :city="reports[0].city"
7
+      :line="reports[0].line"
8
+      :direction="reports[0].direction"
9
+      :acks="calcLabelAcks(reports)"
10
+      :avg-delay="42"
11
+    />
12
+    <FluxItemDetailed
13
+      v-if="this.$store.state.stream.selected === reportKey"
14
+      :country="reports[0].country"
15
+      :city="reports[0].city"
16
+      :line="reports[0].line"
17
+      :direction="reports[0].direction"
18
+      :acks="calcLabelAcks(reports)"
19
+      :avg-delay="42"
20
+      :reports="reports"
21
+    />
22
+  </div>
23
+</template>
24
+
25
+<script>
26
+import FluxItemCondensed from './FluxItemCondensed.vue'
27
+import FluxItemDetailed from './FluxItemDetailed.vue'
28
+
29
+export default {
30
+  components: {
31
+    FluxItemCondensed,
32
+    FluxItemDetailed
33
+  },
34
+
35
+  props: {
36
+    reportKey: {
37
+      type: String,
38
+      default: ''
39
+    },
40
+    reports: {
41
+      type: Array,
42
+      default: () => {
43
+        return {}
44
+      }
45
+    }
46
+  },
47
+
48
+  data() {
49
+    return {}
50
+  },
51
+
52
+  computed: {
53
+    isOpen: function () {
54
+      return this.$store.state.stream.selected === this.reportKey
55
+    }
56
+  },
57
+
58
+  created: function () {
59
+    this.$store.commit('stream/select', null)
60
+  },
61
+
62
+  methods: {
63
+    calcReportAcks: function (report) {
64
+      return report.related.length + 1
65
+    },
66
+
67
+    calcLabelAcks: function (reports) {
68
+      return reports.map((report) => {
69
+        return this.calcReportAcks(report)
70
+      }).reduce(function (a, b) { return a + b })
71
+    }
72
+  }
73
+}
74
+</script>
75
+
76
+<style>
77
+/* .layout {
78
+  border: 1px red dashed !important;
79
+}
80
+.flex {
81
+  border: 1px yellow dashed !important;
82
+} */
83
+</style>

+ 54
- 0
components/pages/flux/Stream/FluxStream.vue View File

@@ -0,0 +1,54 @@
1
+<template>
2
+  <no-ssr>
3
+    <v-list>
4
+      <div
5
+        v-for="(reports, i) in this.$store.state.stream.loaded"
6
+        :key="i"
7
+        class="padded"
8
+        @click="toggleSelected(i)"
9
+      >
10
+        <FluxItem
11
+          :report-key="i"
12
+          :reports="[ ...reports ]"
13
+        />
14
+        <v-divider />
15
+      </div>
16
+    </v-list>
17
+  </no-ssr>
18
+</template>
19
+
20
+<script>
21
+import FluxItem from './FluxItem'
22
+
23
+export default {
24
+  components: {
25
+    FluxItem
26
+  },
27
+
28
+  methods: {
29
+    toggleSelected: function (key) {
30
+      if (this.$store.state.stream.selected === key) {
31
+        this.$store.commit('stream/select', null)
32
+      } else {
33
+        this.$store.commit('stream/select', key)
34
+      }
35
+    }
36
+  }
37
+}
38
+</script>
39
+
40
+<style scoped>
41
+.padded {
42
+  padding-left: 8px;
43
+  padding-right: 8px;
44
+}
45
+.shorten-text {
46
+  white-space: nowrap;
47
+  overflow: hidden;
48
+  text-overflow: ellipsis;
49
+}
50
+
51
+.shorten-text:hover {
52
+  overflow: visible;
53
+}
54
+</style>

+ 98
- 0
components/pages/flux/Stream/index.vue View File

@@ -0,0 +1,98 @@
1
+<template>
2
+  <v-container>
3
+    <v-layout row wrap align-content-center justify-space-around>
4
+      <v-flex xs12 sm10 md8 lg6 xl4>
5
+        <v-card>
6
+          <v-toolbar dark>
7
+            <v-btn
8
+              :loading="this.$store.state.stream.loading"
9
+              :disabled="this.$store.state.stream.loading"
10
+              flat
11
+              round
12
+              small
13
+              icon
14
+              @click="refreshCache"
15
+            >
16
+              <v-icon>cached</v-icon>
17
+            </v-btn>
18
+
19
+            <v-toolbar-title v-if="this.$store.state.stream.onlyUserReports">
20
+              {{ $t('fluxPage.stream.title.user') }}
21
+            </v-toolbar-title>
22
+            <v-toolbar-title v-else>
23
+              {{ $t('fluxPage.stream.title.all') }}
24
+            </v-toolbar-title>
25
+
26
+            <v-spacer />
27
+
28
+            <v-btn
29
+              v-if="this.$store.state.user.id"
30
+              icon
31
+              @click="toggleMyReports"
32
+            >
33
+              <v-icon>
34
+                assignment_ind
35
+              </v-icon>
36
+            </v-btn>
37
+            <v-btn
38
+              icon
39
+              @click="$router.push('/flux/report')"
40
+            >
41
+              <v-icon>
42
+                add
43
+              </v-icon>
44
+            </v-btn>
45
+
46
+            <!-- <v-btn icon>
47
+              <v-icon>
48
+                search
49
+              </v-icon>
50
+            </v-btn> -->
51
+          </v-toolbar>
52
+          <FluxStream />
53
+        </v-card>
54
+      </v-flex>
55
+    </v-layout>
56
+  </v-container>
57
+</template>
58
+
59
+<script>
60
+import FluxStream from './FluxStream.vue'
61
+
62
+export default {
63
+  components: {
64
+    FluxStream
65
+  },
66
+
67
+  mounted: function () {
68
+    if (this.$store.state.stream.onlyUserReports) {
69
+      this.$store.dispatch('stream/user', this.$store.state.user ? this.$store.state.user.id : '')
70
+    } else {
71
+      this.$store.dispatch('stream/get')
72
+    }
73
+  },
74
+
75
+  methods: {
76
+    refreshCache() {
77
+      if (this.$store.state.stream.onlyUserReports) {
78
+        this.$store.dispatch('stream/user', this.$store.state.user ? this.$store.state.user.id : '')
79
+      } else {
80
+        this.$store.dispatch('stream/get')
81
+      }
82
+    },
83
+
84
+    toggleMyReports() {
85
+      this.$store.commit('stream/toggleOnlyUserReports')
86
+      if (this.$store.state.stream.onlyUserReports) {
87
+        if (this.$store.state.user) {
88
+          this.$store.dispatch('stream/user', this.$store.state.user.id)
89
+        } else {
90
+          this.$store.dispatch('stream/user', '')
91
+        }
92
+      } else {
93
+        this.$store.dispatch('stream/get')
94
+      }
95
+    }
96
+  }
97
+}
98
+</script>

+ 45
- 0
components/pages/home/Claim.vue View File

@@ -0,0 +1,45 @@
1
+<template>
2
+  <div id="ComponentBanner">
3
+    <v-layout class="hidden-xs-only" align-center column justify-space-around wrap>
4
+      <v-flex xs6>
5
+        <center>
6
+          <h3 v-html="$t('homePage.claim.long')" /> <!-- eslint-disable-line vue/no-v-html-->
7
+        </center>
8
+      </v-flex>
9
+    </v-layout>
10
+    <v-layout class="hidden-sm-and-up" align-center column justify-space-around wrap>
11
+      <center>
12
+        <h4 v-html="$t('homePage.claim.short')" /> <!-- eslint-disable-line vue/no-v-html -->
13
+      </center>
14
+    </v-layout>
15
+  </div>
16
+</template>
17
+
18
+<script>
19
+export default {
20
+}
21
+</script>
22
+
23
+<style scoped>
24
+#ComponentBanner {
25
+  padding-top: 42px;
26
+  padding-bottom: 42px;
27
+  color: #fff;
28
+}
29
+h3 {
30
+  font-family: Oswald;
31
+  font-size: 1.5em;
32
+  line-height: 1;
33
+  font-weight: 320;
34
+  text-transform: uppercase;
35
+  letter-spacing: 2px;
36
+}
37
+h4 {
38
+  font-family: Oswald;
39
+  font-size: 1.2em;
40
+  line-height: 1;
41
+  font-weight: 320;
42
+  text-transform: uppercase;
43
+  letter-spacing: 2px;
44
+}
45
+</style>

+ 81
- 0
components/pages/home/Hero.vue View File

@@ -0,0 +1,81 @@
1
+<template>
2
+  <v-parallax
3
+    dark
4
+    :height="350"
5
+    :src="require('@/assets/trainstation_dark_small.jpg')"
6
+  >
7
+    <v-layout
8
+      align-center
9
+      column
10
+      justify-center
11
+    >
12
+      <div>
13
+        <center>
14
+          <v-img class="hidden-xs-only logo large" :src="require('@/assets/brand_logo.png')" />
15
+          <img class="hidden-sm-and-up logo small" src="~/assets/svg/flux_x.svg">
16
+          <h4 class="subheading hidden-xs-only">
17
+            {{ $t('homePage.slogan.long') }}
18
+          </h4>
19
+          <h5 class="subheading hidden-sm-and-up" v-html="$t('homePage.slogan.short')" /> <!-- eslint-disable-line vue/no-v-html-->
20
+          <br>
21
+          <v-btn
22
+            color="#5ccbf0"
23
+            round
24
+            large
25
+            raised
26
+            @click="goLoginPage"
27
+          >
28
+            <span class="grey--text text--darken-4 login-btn">
29
+              {{ $t('homePage.btn.delayStream') }}
30
+            </span>
31
+          </v-btn>
32
+        </center>
33
+      </div>
34
+    </v-layout>
35
+  </v-parallax>
36
+</template>
37
+
38
+<script>
39
+export default {
40
+  data() {
41
+    return {}
42
+  },
43
+  methods: {
44
+    goLoginPage() {
45
+      this.$router.push('/flux/stream')
46
+      this.drawer = false
47
+    }
48
+  }
49
+}
50
+</script>
51
+
52
+<style scoped>
53
+h1 {
54
+  color: #5ccbf0;
55
+  font-size: 8em;
56
+}
57
+h4 {
58
+  text-transform: uppercase;
59
+  font-weight: 500;
60
+  font-size: 2.5em !important;
61
+  line-height: 1;
62
+}
63
+h5 {
64
+  text-transform: uppercase;
65
+  font-weight: 500;
66
+  font-size: 2em !important;
67
+  line-height: 1;
68
+}
69
+.small {
70
+  width: 75%;
71
+}
72
+.logo {
73
+  margin: 16px;
74
+}
75
+.small {
76
+  width: 128px;
77
+}
78
+.login-btn {
79
+  font-size: 1.2em;
80
+}
81
+</style>

+ 64
- 0
components/pages/home/Section.vue View File

@@ -0,0 +1,64 @@
1
+<template>
2
+  <v-layout wrap row class="section">
3
+    <v-flex xs12 md5>
4
+      <v-layout row wrap fill-height align-center>
5
+        <v-flex>
6
+          <center>
7
+            <img class="svg" :src="svg" :alt="section.svg">
8
+          </center>
9
+        </v-flex>
10
+      </v-layout>
11
+    </v-flex>
12
+
13
+    <v-flex xs12 md7>
14
+      <h1 v-text="section.title" />
15
+      <br>
16
+      <p
17
+        v-for="(value, index) in section.paragraphs"
18
+        :key="index"
19
+        v-text="value"
20
+      />
21
+      <v-flex pa-3>
22
+        <h2 v-text="section.pitchTitle" />
23
+        <div id="pitch">
24
+          <p v-text="section.pitchDescription" />
25
+        </div>
26
+      </v-flex>
27
+    </v-flex>
28
+  </v-layout>
29
+</template>
30
+
31
+<script>
32
+const rocket = require('~/assets/svg/rocket.svg')
33
+const shield = require('~/assets/svg/shield.svg')
34
+const cup = require('~/assets/svg/cup.svg')
35
+
36
+export default {
37
+  props: {
38
+    section: {
39
+      type: Object,
40
+      default: () => ({})
41
+    }
42
+  },
43
+
44
+  computed: {
45
+    svg: function () {
46
+      switch (this.section.svg) {
47
+        case 'rocket': {
48
+          return rocket
49
+        }
50
+        case 'shield': {
51
+          return shield
52
+        }
53
+        case 'cup': {
54
+          return cup
55
+        }
56
+        default: {
57
+          console.log('unknown svg:', this.section.svg) // eslint-disable-line
58
+          return ''
59
+        }
60
+      }
61
+    }
62
+  }
63
+}
64
+</script>

+ 78
- 0
components/picker/datetime/Dialog.vue View File

@@ -0,0 +1,78 @@
1
+<template>
2
+  <div>
3
+    <v-dialog v-model="visible" width="640" persistent>
4
+      <v-card>
5
+        <v-card-actions>
6
+          <v-spacer />
7
+          <v-btn icon @click="$emit('datePick', pickedDateTime)">
8
+            <v-icon>check</v-icon>
9
+          </v-btn>
10
+        </v-card-actions>
11
+
12
+        <v-card-text>
13
+          <center>
14
+            <v-date-picker
15
+              v-model="pickedDate"
16
+            ></v-date-picker>
17
+
18
+            <v-time-picker
19
+              :format="ampm ? '12hr' : '24hr'"
20
+              v-model="pickedTime"
21
+            ></v-time-picker>
22
+          </center>
23
+        </v-card-text>
24
+
25
+        <v-card-actions>
26
+          <v-btn flat @click="resetPick">{{ $t('reset') }}</v-btn>
27
+          <v-btn flat @click="setNow">{{ $t('now') }}</v-btn>
28
+          <v-btn block @click="$emit('datePick', pickedDateTime)">{{ $t('next') }}</v-btn>
29
+        </v-card-actions>
30
+      </v-card>
31
+    </v-dialog>
32
+  </div>
33
+</template>
34
+
35
+<script>
36
+const moment = require('moment')
37
+
38
+export default {
39
+  props: {
40
+    ampm: {
41
+      type: Boolean,
42
+      default: false
43
+    },
44
+    defaultDateTime: {
45
+      type: Date
46
+    },
47
+    visible: {
48
+      type: Boolean,
49
+      default: false
50
+    }
51
+  },
52
+  data () {
53
+    return {
54
+      pickedDate: moment(this.$props.defaultDateTime).format('YYYY-MM-DD'),
55
+      pickedTime: moment(this.$props.defaultDateTime).format('HH:mm')
56
+    }
57
+  },
58
+  computed: {
59
+    pickedDateTime: function () {
60
+      if (this.pickedDate && this.pickedTime) {
61
+        return moment(`${this.pickedDate} ${this.pickedTime}`, 'YYYY-MM-DD HH:mm').toDate()
62
+      } else {
63
+        return null
64
+      }
65
+    }
66
+  },
67
+  methods: {
68
+    resetPick: function () {
69
+      this.pickedDate = ''
70
+      this.pickedTime = ''
71
+    },
72
+    setNow: function () {
73
+      this.pickedDate = moment().format('YYYY-MM-DD')
74
+      this.pickedTime = moment().format('HH:mm')
75
+    }
76
+  }
77
+}
78
+</script>

+ 140
- 0
components/picker/datetime/index.vue View File

@@ -0,0 +1,140 @@
1
+<template>
2
+  <div>
3
+    <v-card>
4
+      <v-toolbar>
5
+        {{ title }}
6
+      </v-toolbar>
7
+
8
+      <v-card-text v-if="timestamp">
9
+        <center>
10
+          {{ timestamp.calendar() }}
11
+        </center>
12
+      </v-card-text>
13
+
14
+      <v-card-actions>
15
+        <v-btn
16
+          v-if="!timestamp"
17
+          block
18
+          @click="setNow"
19
+        >
20
+          {{ $t('generic.form.btn.now') }}
21
+        </v-btn>
22
+        <v-btn
23
+          v-if="timestamp"
24
+          flat
25
+          @click="reset"
26
+        >
27
+          {{ $t('generic.form.btn.reset') }}
28
+        </v-btn>
29
+        <v-btn
30
+          v-if="timestamp"
31
+          block
32
+          @click="pickerVisible = true"
33
+        >
34
+          {{ $t('generic.form.btn.change') }}
35
+        </v-btn>
36
+      </v-card-actions>
37
+    </v-card>
38
+
39
+    <v-dialog v-model="pickerVisible" width="640" persistent>
40
+      <v-card>
41
+        <v-card-actions>
42
+          <v-spacer />
43
+          <v-btn icon @click="closePicker">
44
+            <v-icon>
45
+              check
46
+            </v-icon>
47
+          </v-btn>
48
+        </v-card-actions>
49
+
50
+        <v-card-text>
51
+          <center>
52
+            <v-date-picker
53
+              v-model="pickedDate"
54
+            />
55
+
56
+            <v-time-picker
57
+              v-model="pickedTime"
58
+              format="24hr"
59
+            />
60
+          </center>
61
+        </v-card-text>
62
+
63
+        <v-card-actions>
64
+          <v-btn flat @click="reset">
65
+            {{ $t('reset') }}
66
+          </v-btn>
67
+          <v-btn flat @click="setNow">
68
+            {{ $t('now') }}
69
+          </v-btn>
70
+          <v-btn block @click="closePicker">
71
+            {{ $t('next') }}
72
+          </v-btn>
73
+        </v-card-actions>
74
+      </v-card>
75
+    </v-dialog>
76
+  </div>
77
+</template>
78
+
79
+<script>
80
+const moment = require('moment')
81
+
82
+export default {
83
+  props: {
84
+    title: {
85
+      type: String,
86
+      default: ''
87
+    },
88
+    date: {
89
+      type: String,
90
+      default: () => (new Date())
91
+    }
92
+  },
93
+
94
+  data() {
95
+    return {
96
+      pickedDate: moment(this.date).format('YYYY-MM-DD'),
97
+      pickedTime: moment(this.date).format('HH:mm'),
98
+      pickerVisible: false
99
+    }
100
+  },
101
+
102
+  computed: {
103
+    timestamp: function () {
104
+      if (this.pickedDate && this.pickedTime) {
105
+        return moment(`${this.pickedDate} ${this.pickedTime}`, 'YYYY-MM-DD HH:mm')
106
+      }
107
+      return ''
108
+    }
109
+  },
110
+
111
+  methods: {
112
+    setDate: function (newDate) {
113
+      this.pickedDate = moment(newDate).format('YYYY-MM-DD')
114
+    },
115
+    setTime: function (newTime) {
116
+      this.pickedTime = moment(newTime).format('HH:mm')
117
+    },
118
+    setNow: function () {
119
+      const now = moment()
120
+      this.setDate(now)
121
+      this.setTime(now)
122
+      this.emitTimestamp()
123
+    },
124
+    reset: function () {
125
+      this.pickedDate = null
126
+      this.pickedTime = null
127
+      this.emitTimestamp()
128
+    },
129
+    closePicker: function () {
130
+      this.pickerVisible = false
131
+      this.emitTimestamp()
132
+    },
133
+    emitTimestamp: function () {
134
+      this.$emit(
135
+        'datetimeSelected', this.timestamp
136
+      )
137
+    }
138
+  }
139
+}
140
+</script>

+ 35
- 0
docker-compose.yml View File

@@ -0,0 +1,35 @@
1
+version: '3.1'
2
+
3
+services:
4
+  db:
5
+    image: postgres
6
+    container_name: fluxfail-postgres
7
+    restart: always
8
+    environment:
9
+      POSTGRES_USER: fluxfail
10
+      POSTGRES_DB: fluxfail
11
+    ports:
12
+      - 127.0.0.1:5432:5432
13
+
14
+  app:
15
+    image: fluxfail-nuxt:latest
16
+    environment:
17
+      DATABASE_URL: 'postgres://fluxfail:@fluxfail-postgres/fluxfail'
18
+      SMTP_URL: 'smtp://fakesmtp:1025'
19
+      NODE_TLS_REJECT_UNAUTHORIZED: '0'
20
+      JWT_SECRET: oofohQu1oef9Eid1aigahF9ahzahceiH
21
+    links:
22
+      - db
23
+      - fakesmtp
24
+    ports:
25
+      - '127.0.0.1:3000:3000'
26
+    depends_on:
27
+      - db
28
+      - fakesmtp
29
+
30
+  fakesmtp:
31
+    image: fluxfail/fakesmtp
32
+    container_name: fakesmtp
33
+    ports:
34
+      - '127.0.0.1:1025:1025'
35
+      - '127.0.0.1:1080:1080'

+ 183
- 0
lang/en.json View File

@@ -0,0 +1,183 @@
1
+{
2
+  "vehicle": {
3
+    "none": "-",
4
+    "bus": "Bus",
5
+    "train": "Train",
6
+    "subway": "Subway",
7
+    "tram": "Tram",
8
+    "lift": "Lift",
9
+    "ship": "Ship",
10
+    "airplane": "Airplane",
11
+    "rocket": "Rocket"
12
+  },
13
+  "generic": {
14
+    "form": {
15
+      "btn": {
16
+        "back": "Back",
17
+        "cancel": "Cancel",
18
+        "change": "Change",
19
+        "delete": "Delete",
20
+        "edit": "Edit",
21
+        "next": "Next",
22
+        "no": "No",
23
+        "now": "Now",
24
+        "reset": "Reset",
25
+        "save": "Save",
26
+        "submit": "Submit",
27
+        "yes": "Yes"
28
+      }
29
+    },
30
+    "missingValue": "???"
31
+  },
32
+  "nav": {
33
+    "home": "Home",
34
+    "account": "Account",
35
+    "login": "Login",
36
+    "stream": "Stream",
37
+    "report": "Report",
38
+    "about": "About"
39
+  },
40
+  "homePage": {
41
+    "btn": {
42
+      "delayStream": "Live Stream"
43
+    },
44
+    "slogan": {
45
+      "long": "together on time",
46
+      "short": "together<br />on time"
47
+    },
48
+    "claim": {
49
+      "long": "tracking delays. not you.<br />open data. open source.",
50
+      "short": "tracking delays.<br />not you.<br />&mdash;<br />open data.<br />open source."
51
+    },
52
+    "reportDelay": "Report a Delay",
53
+    "sections": {
54
+      "idea": {
55
+        "title": "The idea is fairly simple",
56
+        "paragraphs": [
57
+          "Whenever you experience a delay in the publics, report it as a flux.fail.",
58
+          "By doing so, we'll keep each other in the loop and up-to-date, while maintaining an effective public incentive for the transportation providers to improve their reliability."
59
+        ],
60
+        "pitchTitle": "Together on time!",
61
+        "pitchDescription": "Invite your friends to join the flux. The more users reporting delays, the higher the overall coverage for all of us."
62
+      },
63
+      "privacy": {
64
+        "title": "Privacy & Data-Protection",
65
+        "paragraphs": [
66
+          "Publicly disclosing personal location and transportation data to a website on the internet doesn't come without drawbacks.",
67
+          "Frankly, flux.fail takes data-protection very serious. We take all measures necessary in order to make it as difficult as possible to correlate individual reports to any specific individuals."
68
+        ],
69
+        "pitchTitle": "Privacy first!",
70
+        "pitchDescription": "flux.fail protects your personal data, simply by not collecting any of it. We're only interested in delays and don't even keep your cleartext e-mail address. To us you are a random cryptic ID."
71
+      },
72
+      "benefits": {
73
+        "title": "Benefits",
74
+        "paragraphs": [
75
+          "Every reported delay is rewarded with a flux point. The more often a reported delay is acknowledged by other reporters, the more points the report is worth to each of its reporters. However, points are also good indicators for various other report qualities.",
76
+          "Follow the flux.fail delay-stream while new reports fly in. Use flux.fail as a travel assistent and circumvent your delays before running into them. Find better performing lines or even entire routes that will get you to your destination faster."
77
+        ],
78
+        "pitchTitle": "Play to win!",
79
+        "pitchDescription": "Earn points for reporting delays that happen to you anyway while using the real-time stream as a travel assistant to circumvent delays in the first place. What are you waiting for? Together on time."
80
+      }
81
+    }
82
+  },
83
+  "accountPage": {
84
+    "loginForm": {
85
+      "title": "Login",
86
+      "welcome": "Welcome to",
87
+      "checkMail": {
88
+        "mailHint": "We've sent you a login-link.",
89
+        "spamHint": "The very first login mail may take up to 10 minutes to arrive, depending on your mail-provider's spam-protection."
90
+      },
91
+      "form": {
92
+        "label": "E-Mail",
93
+        "description": "Use the same address every time you login!",
94
+        "err": {
95
+          "email": "not a valid e-mail address"
96
+        }
97
+      }
98
+    },
99
+    "myAccount": {
100
+      "title": "Account"
101
+    }
102
+  },
103
+  "aboutPage": {
104
+    "title": "About Us"
105
+  },
106
+  "fluxPage": {
107
+    "report": {
108
+      "title": {
109
+        "new": "New Report",
110
+        "edit": "Edit Report"
111
+      },
112
+      "step": {
113
+        "where": "Where",
114
+        "what": "What",
115
+        "when": "When",
116
+        "details": "Details"
117
+      },
118
+      "form": {
119
+        "label": {
120
+          "country": "Country",
121
+          "city": "City",
122
+          "location": "Location",
123
+          "line": "Line",
124
+          "direction": "Direction",
125
+          "scheduledAt": "Scheduled at",
126
+          "actuallyAt": "Actually at",
127
+          "arrival": "Arrival",
128
+          "departure": "Departure",
129
+          "cancelled": "Cancelled"
130
+        },
131
+        "err": {
132
+          "required": "field is required"
133
+        },
134
+        "submitNew": "Report Flux",
135
+        "submitExisting": "Save Flux"
136
+      }
137
+    },
138
+    "stream": {
139
+      "title": {
140
+        "user": "My Reports",
141
+        "all": "Stream"
142
+      },
143
+      "deleteOptIn": {
144
+        "title": "Delete Delay?",
145
+        "info": "Do you really want to delete this delay?"
146
+      }
147
+    }
148
+  },
149
+  "aboutPage": {
150
+    "title": "About Us",
151
+    "paragraphs": [
152
+      "Many of us use the public transportation system on a daily basis, most of which live in and around big cities. Some of us spend up to several hours just sitting in a bus or a train and commuting to work, every day. We depend on the public transportation to be reliable.",
153
+      "The official statistics of the Deutsche Bahn AG state that 25% of all trains are delayed. However, these numbers are only considering trains that are delayed at least by 30 minutes and that are not canceled entirely. The truth is probably that far over 50% of all trains don't arrive or depart on time. The numbers for other transportations providers vary but are in many cases equally disturbing.",
154
+      "We, the people, are left helpless against huge corporations that are more interested in cutting costs and increasing revenue than in providing the bare minimum service quality of simply being on time, at least most of the time.",
155
+      "flux.fail was created to combat this problem. By crowd-sourcing the data-aggregation for the real delay statistics to the commuters and neutrally publishing the numbers on the internet without censorship, we can first inform each other and as a result hopefully create an effective public insentive for the transportation providers to improve their reliability.",
156
+      "Our team consists of a bunch of open-source and open-data enthusiasts who are generally interested in mobility. We take privacy and data-protection very serious, we are financially independent and we are not subject to acquisition. Our mission is to help improve mobility."
157
+    ]
158
+  },
159
+  "imprintPage": {
160
+    "title": "Imprint",
161
+    "company": {
162
+      "name": "flux.fail UG (haftungsbeschränkt) & Co. Betriebs KG",
163
+      "street": "Glogauerstrasse 21",
164
+      "city": "10999 Berlin"
165
+    },
166
+    "registration": {
167
+      "at": "Registered at the local district court of Berlin-Charlottenburg.",
168
+      "id": "HRB200262 B"
169
+    },
170
+    "representatives": {
171
+      "gossip": "Authorized Representatives:",
172
+      "authorized": [
173
+        {
174
+          "name": "Brian Wiborg",
175
+          "mail": "info@flux.fail"
176
+        }
177
+      ]
178
+    }
179
+  },
180
+  "privacy": {
181
+    "dataProtectionStatement": "Data Protection- & Privacy-Statement"
182
+  }
183
+}

+ 0
- 7
layouts/README.md View File

@@ -1,7 +0,0 @@
1
-# LAYOUTS
2
-
3
-**This directory is not required, you can delete it if you don't want to use it.**
4
-
5
-This directory contains your Application Layouts.
6
-
7
-More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/views#layouts).

+ 111
- 27
layouts/default.vue View File

@@ -6,6 +6,7 @@
6 6
       :clipped="clipped"
7 7
       fixed
8 8
       app
9
+      width="300px"
9 10
     >
10 11
       <v-list>
11 12
         <v-list-tile
@@ -15,10 +16,11 @@
15 16
           router
16 17
           exact
17 18
         >
18
-          <v-list-tile-action>
19
+          <v-divider v-if="item.spacer" />
20
+          <v-list-tile-action v-if="!item.spacer">
19 21
             <v-icon>{{ item.icon }}</v-icon>
20 22
           </v-list-tile-action>
21
-          <v-list-tile-content>
23
+          <v-list-tile-content v-if="!item.spacer">
22 24
             <v-list-tile-title v-text="item.title" />
23 25
           </v-list-tile-content>
24 26