Browse Source

v5.0.2 99% complete, rft, needs segment stuff added, pre-deploy

master
Your Name 3 years ago
parent
commit
81a12607a7
51 changed files with 2590 additions and 434 deletions
  1. +3
    -1
      .eslintrc.js
  2. +4
    -4
      1st.js
  3. +96
    -0
      assets/css/main.css
  4. BIN
      assets/images/carriers/alaska-air-transit.png
  5. BIN
      assets/images/carriers/alaska-seaplanes.png
  6. BIN
      assets/images/carriers/bering-air.png
  7. BIN
      assets/images/carriers/copper-valley-air-service.png
  8. BIN
      assets/images/carriers/denaina-air-taxi.png
  9. BIN
      assets/images/carriers/everts-air.png
  10. BIN
      assets/images/carriers/grant-aviation.png
  11. BIN
      assets/images/carriers/island-air-express.png
  12. BIN
      assets/images/carriers/island-air-service.png
  13. BIN
      assets/images/carriers/k-bay-air.png
  14. BIN
      assets/images/carriers/katmai-air.png
  15. BIN
      assets/images/carriers/midnight-air.png
  16. BIN
      assets/images/carriers/rambler-air.png
  17. BIN
      assets/images/carriers/ravn-alaska.png
  18. BIN
      assets/images/carriers/reeve-air.png
  19. BIN
      assets/images/carriers/ryan-air.png
  20. BIN
      assets/images/carriers/smokey-bay-air.png
  21. BIN
      assets/images/carriers/taquan-air.png
  22. BIN
      assets/images/carriers/warbelows-air.png
  23. BIN
      assets/images/carriers/wrangell-mountain-air.png
  24. BIN
      assets/images/carriers/wright-air-service.png
  25. BIN
      assets/images/carriers/yute-commuter-service.png
  26. BIN
      assets/images/orgs/1-million-cups.png
  27. BIN
      assets/images/orgs/aaca.png
  28. BIN
      assets/images/orgs/alaska-tia.png
  29. BIN
      assets/images/orgs/alaska-travel-info.png
  30. BIN
      assets/images/orgs/travel-alaska.png
  31. +72
    -11
      components/AirportPicker.vue
  32. +126
    -14
      components/Calendar.vue
  33. +163
    -0
      components/ChosenFlights.vue
  34. +14
    -52
      components/DatePicker.vue
  35. +30
    -0
      components/Deselect.vue
  36. +24
    -4
      components/FlightsFlyout.vue
  37. +21
    -6
      components/Logo.vue
  38. +91
    -64
      components/Map.vue
  39. +29
    -0
      components/OpenIndicator.vue
  40. +93
    -0
      components/TheHeader.vue
  41. +592
    -0
      components/TheSideBar.vue
  42. +18
    -0
      layouts/default.vue
  43. +15
    -0
      middleware/redirects.js
  44. +14
    -5
      nuxt.config.js
  45. +226
    -0
      pages/dates.vue
  46. +323
    -0
      pages/flights copy.vue
  47. +421
    -77
      pages/flights.vue
  48. +103
    -190
      pages/go.vue
  49. +92
    -0
      pages/index copy.vue
  50. +17
    -5
      pages/index.vue
  51. +3
    -1
      stylelint.config.js

+ 3
- 1
.eslintrc.js View File

@ -16,5 +16,7 @@ module.exports = {
plugins: [ plugins: [
], ],
// add your custom rules here // add your custom rules here
rules: {}
rules: {
}
} }

+ 4
- 4
1st.js View File

@ -99,7 +99,7 @@ fetchMapData().then(mapData => {
} }
}); });
console.log(mapDataFiltered); // => 'Page not found'
// console.log(mapDataFiltered); // => 'Page not found'
mapDataFiltered.forEach((record) => { mapDataFiltered.forEach((record) => {
@ -117,7 +117,7 @@ fetchMapData().then(mapData => {
}) })
}); });
//console.log(mapData[0].Airport_IATA);
// console.log(mapData[0].Airport_IATA);
L.tileLayer(apiUrl, { L.tileLayer(apiUrl, {
attribution: 'FlyLocal | Map data &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>', attribution: 'FlyLocal | Map data &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>',
@ -136,7 +136,7 @@ map.addLayer(markers);
map.on('popupopen', function (e) { map.on('popupopen', function (e) {
var thisMarker = e.popup._source; var thisMarker = e.popup._source;
console.log(thisMarker);
// console.log(thisMarker);
var origButton = document.getElementById("origBtn"), var origButton = document.getElementById("origBtn"),
destButton = document.getElementById("destBtn"); destButton = document.getElementById("destBtn");
@ -155,7 +155,7 @@ map.on('popupopen', function (e) {
} }
}); });
console.log(mapDataFiltered); // => 'Page not found'
// console.log(mapDataFiltered); // => 'Page not found'
map.removeLayer(markers); map.removeLayer(markers);

+ 96
- 0
assets/css/main.css View File

@ -0,0 +1,96 @@
html,
body,
#__nuxt,
#__layout,
.page-container,
main {
height: 100%;
}
html {
font-size: min(3vmin, 18px);
}
header {
position: fixed;
top: 0;
width: 100vw;
z-index: 403;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
padding: 1rem 1rem 0.75rem 1rem;
pointer-events: none;
}
.white-bkg header {
background-color: white;
}
header button {
pointer-events: all;
}
.header__col:nth-child(2) {
display: flex;
justify-content: center;
align-items: flex-start;
}
.header__col:nth-child(3) {
text-align: right;
}
main {
display: flex;
flex-direction: column;
}
.nav {
flex: 0 0 auto;
background: white;
display: flex;
flex-direction: column;
justify-content: flex-end;
}
:root {
--m-s: 0.1s;
--h-s: 0.3s;
--d: calc(var(--h-s) - var(--m-s));
}
.nav--hide {
transition: margin-top var(--m-s) linear var(--h-s), max-height var(--h-s) linear 0s;
margin-top: 0;
max-height: 0;
}
.nav--show {
margin-top: 3.5rem;
max-height: 250px;
transition: margin-top var(--m-s) linear 0s, max-height var(--h-s) linear var(--m-s);
}
.btn--nav-open {
padding: 1rem;
display: block;
}
.flyout-container {
z-index: 1001;
position: fixed;
bottom: 0;
display: flex;
justify-content: center;
width: 100vw;
transition: all 0.3s;
pointer-events: none;
}
.flyout-container.flyout-container--hide {
transform: translateY(200px);
}
.flyout-container.flyout-container--show {
transform: translateY(0);
}

BIN
assets/images/carriers/alaska-air-transit.png View File

Before After
Width: 257  |  Height: 177  |  Size: 50 KiB

BIN
assets/images/carriers/alaska-seaplanes.png View File

Before After
Width: 247  |  Height: 104  |  Size: 34 KiB

BIN
assets/images/carriers/bering-air.png View File

Before After
Width: 505  |  Height: 132  |  Size: 27 KiB

BIN
assets/images/carriers/copper-valley-air-service.png View File

Before After
Width: 550  |  Height: 432  |  Size: 204 KiB

BIN
assets/images/carriers/denaina-air-taxi.png View File

Before After
Width: 650  |  Height: 384  |  Size: 54 KiB

BIN
assets/images/carriers/everts-air.png View File

Before After
Width: 439  |  Height: 198  |  Size: 67 KiB

BIN
assets/images/carriers/grant-aviation.png View File

Before After
Width: 300  |  Height: 245  |  Size: 39 KiB

BIN
assets/images/carriers/island-air-express.png View File

Before After
Width: 333  |  Height: 121  |  Size: 23 KiB

BIN
assets/images/carriers/island-air-service.png View File

Before After
Width: 200  |  Height: 200  |  Size: 41 KiB

BIN
assets/images/carriers/k-bay-air.png View File

Before After
Width: 180  |  Height: 180  |  Size: 111 KiB

BIN
assets/images/carriers/katmai-air.png View File

Before After
Width: 502  |  Height: 186  |  Size: 31 KiB

BIN
assets/images/carriers/midnight-air.png View File

Before After
Width: 602  |  Height: 105  |  Size: 13 KiB

BIN
assets/images/carriers/rambler-air.png View File

Before After

BIN
assets/images/carriers/ravn-alaska.png View File

Before After
Width: 384  |  Height: 146  |  Size: 10 KiB

BIN
assets/images/carriers/reeve-air.png View File

Before After
Width: 1000  |  Height: 309  |  Size: 42 KiB

BIN
assets/images/carriers/ryan-air.png View File

Before After
Width: 198  |  Height: 198  |  Size: 16 KiB

BIN
assets/images/carriers/smokey-bay-air.png View File

Before After
Width: 187  |  Height: 111  |  Size: 23 KiB

BIN
assets/images/carriers/taquan-air.png View File

Before After
Width: 439  |  Height: 198  |  Size: 49 KiB

BIN
assets/images/carriers/warbelows-air.png View File

Before After
Width: 1050  |  Height: 300  |  Size: 45 KiB

BIN
assets/images/carriers/wrangell-mountain-air.png View File

Before After
Width: 450  |  Height: 269  |  Size: 94 KiB

BIN
assets/images/carriers/wright-air-service.png View File

Before After
Width: 1024  |  Height: 522  |  Size: 147 KiB

BIN
assets/images/carriers/yute-commuter-service.png View File

Before After
Width: 992  |  Height: 831  |  Size: 222 KiB

BIN
assets/images/orgs/1-million-cups.png View File

Before After
Width: 384  |  Height: 203  |  Size: 26 KiB

BIN
assets/images/orgs/aaca.png View File

Before After
Width: 384  |  Height: 142  |  Size: 91 KiB

BIN
assets/images/orgs/alaska-tia.png View File

Before After
Width: 384  |  Height: 196  |  Size: 27 KiB

BIN
assets/images/orgs/alaska-travel-info.png View File

Before After
Width: 512  |  Height: 104  |  Size: 19 KiB

BIN
assets/images/orgs/travel-alaska.png View File

Before After
Width: 384  |  Height: 195  |  Size: 28 KiB

+ 72
- 11
components/AirportPicker.vue View File

@ -3,12 +3,15 @@
<!-- <pre style="position: fixed; z-index: 500; right: 0; top: 7rem; font-size: 0.6rem; color: orange;"> <!-- <pre style="position: fixed; z-index: 500; right: 0; top: 7rem; font-size: 0.6rem; color: orange;">
{{ selectedAirport }} {{ selectedAirport }}
</pre> --> </pre> -->
<label for="">
{{ leg }}
</label>
<v-select <v-select
:value="selectedAirportComp" :value="selectedAirportComp"
label="iata" label="iata"
:options="airports" :options="airports"
:filter-by="filterBy" :filter-by="filterBy"
placeholder="stuff"
:components="{OpenIndicator, Deselect}"
@input="changeAirport" @input="changeAirport"
> >
<template #no-options="{ search, searching }"> <template #no-options="{ search, searching }">
@ -56,12 +59,26 @@
</div> </div>
</div> </div>
</template> </template>
<template #search="{ events, attributes }">
<input
:placeholder="placeholder"
type="search"
class="vs__search"
v-bind="attributes"
v-on="events"
>
</template>
</v-select> </v-select>
</div> </div>
</template> </template>
<script> <script>
import OpenIndicator from './OpenIndicator.vue'
import Deselect from './Deselect.vue'
export default { export default {
// ClearButton,
props: { props: {
airports: { airports: {
type: [Array], type: [Array],
@ -83,18 +100,26 @@ export default {
search: '' search: ''
} }
} }
}
},
data () {
return {
mountains: [],
filterBy: (option, label, search) => {
const temp = search.toLowerCase()
return option.search.toLowerCase().includes(temp)
},
leg: {
type: [String],
default () {
return ''
} }
} }
}, },
data: () => ({
OpenIndicator,
mountains: [],
filterBy: (option, label, search) => {
const temp = search.toLowerCase()
return option.search.toLowerCase().includes(temp)
}
}),
computed: { computed: {
Deselect () {
return this.selectedAirportComp.iata ? Deselect : ''
},
selectedAirportComp () { selectedAirportComp () {
return { return {
iata: this.selectedAirport.iata, iata: this.selectedAirport.iata,
@ -107,6 +132,9 @@ export default {
search: this.selectedAirport.search, search: this.selectedAirport.search,
label: this.selectedAirport.label label: this.selectedAirport.label
} }
},
placeholder () {
return (this.selectedAirport.iata) ? '' : 'town, airport, or iata'
} }
}, },
methods: { methods: {
@ -128,17 +156,30 @@ html {
.picker-wrap { .picker-wrap {
margin: 1rem; margin: 1rem;
position: relative;
} }
.picker-wrap:last-of-type { .picker-wrap:last-of-type {
margin-top: 0; margin-top: 0;
} }
.vs__search {
.picker-wrap .vs__search {
font-size: 3rem !important; font-size: 3rem !important;
padding: 0.5rem; padding: 0.5rem;
} }
.vs__search::placeholder {
color: #bbb;
}
.vs__dropdown-toggle {
background: #eee;
border: 0;
border-radius: 3rem;
padding-right: 1rem;
padding-left: 1rem;
}
.picker-item { .picker-item {
padding: 0.5rem; padding: 0.5rem;
display: flex; display: flex;
@ -151,11 +192,31 @@ html {
} }
.picker-item__muni { .picker-item__muni {
color: blue;
color: #007fff;
} }
.picker-item__iata { .picker-item__iata {
font-size: 3rem; font-size: 3rem;
margin-right: 1rem; margin-right: 1rem;
} }
.vs--single .vs__selected {
position: absolute;
}
</style>
<style scoped>
label {
display: block;
color: black;
position: absolute;
z-index: 1;
text-transform: uppercase;
font-weight: 300;
top: 0.3rem;
left: 2rem;
letter-spacing: 0.13rem;
font-size: 0.8rem;
}
</style> </style>

+ 126
- 14
components/Calendar.vue View File

@ -1,6 +1,6 @@
<template> <template>
<div>
<h1>
<div class="calendar-page">
<h1 class="calendar__month">
{{ new Date(date).toLocaleString("default", { month: "long" }) }} {{ new Date(date).toLocaleString("default", { month: "long" }) }}
</h1> </h1>
<ul class="calendar"> <ul class="calendar">
@ -14,22 +14,64 @@
<li <li
v-for="index in getDayOfWeekOffset(date)" v-for="index in getDayOfWeekOffset(date)"
:key="index + 'o'" :key="index + 'o'"
class="calendar__day"
>
<span class="day__date--blank" />
</li>
class="calendar__day calendar__day--blank"
/>
<li <li
v-for="day in getDaysInMonth(date)" v-for="day in getDaysInMonth(date)"
:key="day.toString()" :key="day.toString()"
class="calendar__day" class="calendar__day"
> >
<span class="day__date"> {{ day.getDate() }} </span> <span class="day__date"> {{ day.getDate() }} </span>
<button
v-if="selectedDays.find( sched => sched['num'] == day.getDay() )"
@click="book(day)"
<NuxtLink
v-if="
selectedDows.find((sched) =>
sched.DowsList.find((dow) => dow.num == day.getDay())
) && day > new Date()
"
class="btn btn--primary btn--day"
:to="
'/flights/' +
$route.params.o +
'/' +
$route.params.d +
'/' +
day.toLocaleDateString('en-CA')
"
> >
book
</button>
<span class="day__date"> {{ day.getDate() }} </span>
</NuxtLink>
<span
v-if="
selectedDows.find((sched) =>
sched.DowsList.find((dow) => dow.num == day.getDay())
) &&
day > new Date() &&
selectedDows.filter((sched) =>
sched.DowsList.find((dow) => dow.num == day.getDay())
).length == 1
"
class="badge badge--day badge--count"
>
{{ scheduleTime(selectedDows[0]) }}
</span>
<span
v-if="
selectedDows.find((sched) =>
sched.DowsList.find((dow) => dow.num == day.getDay())
) &&
day > new Date() &&
selectedDows.filter((sched) =>
sched.DowsList.find((dow) => dow.num == day.getDay())
).length > 1
"
class="badge badge--day badge--count"
>
{{
selectedDows.filter((sched) =>
sched.DowsList.find((dow) => dow.num == day.getDay())
).length + "x"
}}
</span>
</li> </li>
</ul> </ul>
</div> </div>
@ -44,7 +86,7 @@ export default {
return new Date() return new Date()
} }
}, },
selectedDays: {
selectedDows: {
type: [Array], type: [Array],
default () { default () {
return [] return []
@ -73,16 +115,86 @@ export default {
}, },
book (thisDate) { book (thisDate) {
this.$emit('book', thisDate) this.$emit('book', thisDate)
},
scheduleTime (dow) {
// console.log(dow)
const thisDate = new Date(dow.DepartureTimeFormatted)
const hours = thisDate.getHours() + Math.round(thisDate.getMinutes() / 60)
return ((hours > 12) ? (hours - 12) + 'pm' : hours + 'am')
} }
} }
} }
</script> </script>
<style> <style>
.calendar-page {
width: min(90vmin, 400px);
}
.calendar { .calendar {
display: grid; display: grid;
width: 300px;
border-radius: 3rem;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr; grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr 1fr 1fr 1fr 1fr;
grid-template-rows: 0.5fr 1fr 1fr 1fr 1fr 1fr 1fr;
gap: 0.75rem;
}
.day__date {
font-size: 1.4rem;
}
.btn.btn--day {
position: absolute;
left: 0;
width: 100%;
height: 100%;
top: 0;
background: #007fff;
color: white;
border-radius: 999px;
display: flex;
align-items: center;
justify-content: center;
}
.calendar__day {
position: relative;
aspect-ratio: 1;
display: flex;
align-items: center;
justify-content: center;
border-radius: 999px;
background: #eee;
}
.calendar__day--dow {
background: transparent;
aspect-ratio: unset;
color: gray;
font-size: 1.2rem;
font-weight: 300;
}
.calendar__day--blank {
background: white;
}
h1.calendar__month {
text-align: center;
font-size: 2rem;
margin: 0.5rem 0;
font-weight: 700;
}
.badge.badge--day {
position: absolute;
background: hotpink;
border: white 0.2rem solid;
border-radius: 999px;
color: white;
padding: 0 0.4em;
bottom: -0.25rem;
right: -0.25rem;
font-size: 0.7rem;
} }
</style> </style>

+ 163
- 0
components/ChosenFlights.vue View File

@ -0,0 +1,163 @@
<template>
<div class="chosen-wrapper">
<div class="chosen-cols">
<div class="chosen-col">
<label for="" class="chosen-text">
{{ selectedOrig.municipality }}
</label>
<h1 class="chosen-text">
{{ selectedOrig.iata }}
</h1>
<span v-if="departure" class="date-badge">
{{ departure }} {{ (departureTime ? '@' + departureTime : '') }}
</span>
</div>
<div class="chosen-col">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-arrow-right-circle"
width="100"
height="100"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="#007fff"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M18 15l3 -3l-3 -3" />
<circle cx="5" cy="12" r="2" />
<path d="M7 12h14" />
</svg>
</div>
<div class="chosen-col">
<label for="" class="chosen-text">
{{ selectedDest.municipality }}
</label>
<h1 class="chosen-text">
{{ selectedDest.iata }}
</h1>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
selectedOrig: {
type: [Object],
default () {
return {}
}
},
selectedDest: {
type: [Object],
default () {
return {}
}
},
departure: {
type: [String],
default () {
return ''
}
},
departureTime: {
type: [String],
default () {
return ''
}
}
}
}
</script>
<style>
.chosen-wrapper {
display: flex;
flex-direction: column;
align-items: center;
}
.chosen-cols {
display: flex;
justify-content: center;
background-color: #003c79;
border-radius: 1rem;
padding: 0.5rem 2rem;
margin-bottom: 2rem;
position: relative;
max-width: 450px;
--knockout-size: 0.75rem;
}
.chosen-col:nth-of-type(2) {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.chosen-col:nth-of-type(2) svg {
margin-top: -0.9rem;
width: 3.5rem;
height: 3.5rem;
}
.chosen-col:nth-of-type(2)::before {
content: "";
height: var(--knockout-size);
width: var(--knockout-size);
background-color: white;
border-radius: 0 0 999px 999px;
display: block;
position: absolute;
top: 0;
}
.chosen-col:nth-of-type(2)::after {
content: "";
height: var(--knockout-size);
width: var(--knockout-size);
background-color: white;
border-radius: 999px 999px 0 0;
display: block;
position: absolute;
bottom: 0;
}
.flyout .chosen-col:nth-of-type(2)::before,
.flyout .chosen-col:nth-of-type(2)::after {
background-color: #007fff;
}
label.chosen-text {
color: #007fff;
line-height: 0.8;
font-size: 0.8rem;
font-weight: 400;
}
.header__col:nth-child(3) label.chosen-text {
text-align: right;
}
h1.chosen-text {
font-size: 3rem;
line-height: 0.8;
font-weight: 500;
color: #eee;
}
.date-badge {
background: white;
border-radius: 999px;
padding: 0 0.5em;
line-height: 1;
display: inline-block;
font-size: 0.8rem;
}
</style>

+ 14
- 52
components/DatePicker.vue View File

@ -1,50 +1,14 @@
<template> <template>
<div class="calendar-wrapper">
<div>
<label for=""> Local Airline </label>
<h1>
{{ selectedSchedule.Carrier_Name }}
</h1>
</div>
<div>
<label for=""> Departs </label>
<div>
{{ selectedSchedule.Departure_TimeL }}
</div>
</div>
<div>
<label for=""> Arrives </label>
<div>
{{ selectedSchedule.Arrival_TimeL }}
</div>
</div>
<div>
<label for=""> Available Until </label>
<div>
{{ selectedSchedule.Effective_End }}
</div>
</div>
<div>
<label for="">How Many Adults?</label>
<v-select
v-model="numAdult"
:options="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"
<div class="">
<div class="calendar-wrapper">
<Calendar
v-for="date in startingDates"
:key="date.toString()"
:date="new Date(date)"
:selected-dows="selectedDows"
@book="book"
/> />
</div> </div>
<div>
<label for="">How Many Children?</label>
<v-select
v-model="numChild"
:options="[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"
/>
</div>
<Calendar
v-for="date in startingDates"
:key="date.toString()"
:date="new Date(date)"
:selected-days="selectedDays"
@book="book"
/>
</div> </div>
</template> </template>
@ -57,13 +21,7 @@ export default {
return new Date() return new Date()
} }
}, },
selectedSchedule: {
type: [Object],
default () {
return {}
}
},
selectedDays: {
selectedDows: {
type: [Array], type: [Array],
default () { default () {
return [] return []
@ -100,6 +58,10 @@ export default {
<style> <style>
.calendar-wrapper { .calendar-wrapper {
color: black;
display: flex;
flex-wrap: wrap;
gap: 3rem;
justify-content: center;
margin-top: 3rem;
} }
</style> </style>

+ 30
- 0
components/Deselect.vue View File

@ -0,0 +1,30 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-x"
width="60"
height="60"
viewBox="0 0 24 24"
stroke-width="2"
stroke="#bbbbbb"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
</template>
<script>
export default {
}
</script>
<style scoped>
svg {
max-height: 3.2rem;
}
</style>

+ 24
- 4
components/FlightsFlyout.vue View File

@ -1,11 +1,12 @@
<template> <template>
<div class="flyout flyout--book"> <div class="flyout flyout--book">
<ChosenFlights :selected-orig="selectedOrig" :selected-dest="selectedDest" />
<NuxtLink <NuxtLink
:to="'/flights/' + selectedOrig.iata + '/' + selectedDest.iata"
:class="['btn btn--primary btn--large',{'btn--empty': !selectedOrig.iata || !selectedDest.iata}]"
:to="'/dates/' + selectedOrig.iata + '/' + selectedDest.iata"
:class="['btn btn--primary',{'btn--empty': !selectedOrig.iata || !selectedDest.iata}]"
> >
<span :class="['btn__content']"> <span :class="['btn__content']">
see fights from <strong>{{ selectedOrig.municipality }}</strong> to <strong>{{ selectedDest.municipality }}</strong>
see fights
</span> </span>
</NuxtLink> </NuxtLink>
</div> </div>
@ -32,10 +33,23 @@ export default {
<style> <style>
.flyout { .flyout {
background: white;
background: #007fff;
border-radius: 3rem 3rem 0 0; border-radius: 3rem 3rem 0 0;
padding: 3rem; padding: 3rem;
pointer-events: all; pointer-events: all;
width: clamp(450px, 50vw, 400px);
display: flex;
flex-direction: column;
}
.flyout .btn.btn--primary {
background: white;
color: black;
}
.flyout .btn.btn--primary:hover {
background: #00ca00;
color: #fff;
} }
.btn__content { .btn__content {
@ -49,4 +63,10 @@ export default {
.btn.btn--large { .btn.btn--large {
font-size: 2rem; font-size: 2rem;
} }
/* @media only screen and (max-width: 768px) {
.btn.btn--large {
font-size: 1rem;
}
} */
</style> </style>

+ 21
- 6
components/Logo.vue View File

@ -6,16 +6,16 @@
<style> <style>
#flylocal-logo { #flylocal-logo {
height: 4rem;
height: clamp(20px, 4rem, 50px);
transition: all 0.3s;
} }
.cls-1,
.cls-2 {
fill: #2f80ed;
.cls-1 {
fill: #9cb7f3;
} }
.cls-1 {
opacity: 0.57;
.cls-2 {
fill: #2f80ed;
} }
.cls-3 { .cls-3 {
@ -23,7 +23,22 @@
transition: all 0.3s; transition: all 0.3s;
} }
.nav-is-showing #flylocal-logo {
height: 3rem;
}
.nav-is-showing .cls-3 { .nav-is-showing .cls-3 {
fill: #000; fill: #000;
} }
.white-bkg .cls-3 {
fill: #000;
}
/* @media only screen and (max-width: 768px) {
#flylocal-logo {
height: 1.5rem;
}
} */
</style> </style>

+ 91
- 64
components/Map.vue View File

@ -120,7 +120,8 @@
</l-marker> </l-marker>
<l-polyline v-if="selectedOrigNorm.key && selectedDestNorm.key" :lat-lngs="[selectedOrigNorm['lat-lng'],selectedDestNorm['lat-lng']]" color="#007fff" /> <l-polyline v-if="selectedOrigNorm.key && selectedDestNorm.key" :lat-lngs="[selectedOrigNorm['lat-lng'],selectedDestNorm['lat-lng']]" color="#007fff" />
<!-- <l-tile-layer v-bind="tileLayer" :url="mapBoxUrl" /> --> <!-- <l-tile-layer v-bind="tileLayer" :url="mapBoxUrl" /> -->
<l-tile-layer :url="osmUrl" :attribution="osmAttribution" />
<!-- <l-tile-layer :url="osmUrl" :attribution="osmAttribution" /> -->
<l-tile-layer v-bind="tileLayer" />
</l-map> </l-map>
</client-only> </client-only>
</div> </div>
@ -181,7 +182,10 @@ export default {
osmUrl: 'http://{s}.tile.osm.org/{z}/{x}/{y}.png', osmUrl: 'http://{s}.tile.osm.org/{z}/{x}/{y}.png',
osmAttribution: osmAttribution:
'© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors', '© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors',
mapBoxUrl: 'https://api.mapbox.com/styles/v1/flylocal/ckt9x8aho0igb18mmm9ks2fsv/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoiZmx5bG9jYWwiLCJhIjoiY2t0OHUxZXB6MTVueTJ4cGVwOHRuc2s2NyJ9.YF9frLvISHfOuT7nqs3TNg'
mapBoxUrl: 'https://api.mapbox.com/styles/v1/flylocal/ckt9x8aho0igb18mmm9ks2fsv/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoiZmx5bG9jYWwiLCJhIjoiY2t0OHUxZXB6MTVueTJ4cGVwOHRuc2s2NyJ9.YF9frLvISHfOuT7nqs3TNg',
mapBinder: {
}
} }
}, },
computed: { computed: {
@ -206,59 +210,66 @@ export default {
}, },
tileLayer () { tileLayer () {
return { return {
// url: this.mapBoxApiUrl,
url: 'https://api.mapbox.com/styles/v1/flylocal/ckt9x8aho0igb18mmm9ks2fsv/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoiZmx5bG9jYWwiLCJhIjoiY2t0OHUxZXB6MTVueTJ4cGVwOHRuc2s2NyJ9.YF9frLvISHfOuT7nqs3TNg',
maxZoom: 18, maxZoom: 18,
// id: 'flylocal/ckt9x8aho0igb18mmm9ks2fsv',
tileSize: 512,
id: 'flylocal/ckt9x8aho0igb18mmm9ks2fsv',
tileSize: 256,
zoomOffset: -1, zoomOffset: -1,
accessToken: 'your.mapbox.access.token',
token: 'pk.eyJ1IjoiZmx5bG9jYWwiLCJhIjoiY2t0OHUxZXB6MTVueTJ4cGVwOHRuc2s2NyJ9.YF9frLvISHfOuT7nqs3TNg',
attribution: this.mapBoxAttribution attribution: this.mapBoxAttribution
} }
} }
}, },
updated () { updated () {
if (this.$refs.flMap && this.$refs.flMap.mapObject) {
/* selected none */
if (!this.selectedOrigNorm.key && !this.selectedDestNorm.key && this.airportsOrig.length > 0) {
const o = this.airportsOrig.map((m) => { return [m.lat, m.long] })
// console.log('origins:')
// console.log(o)
if (o) {
this.$refs.flMap.mapObject.fitBounds([...o], { padding: [50, 70] })
}
}
/* selected org */
if (this.selectedOrigNorm.key && !this.selectedDestNorm.key) {
const so = [this.selectedOrig.lat, this.selectedOrig.long]
const d = this.airportsDest.map((m) => { return [m.lat, m.long] })
// console.log('so:')
// console.log(so)
// console.log('dests:')
// console.log(d)
if (so && d) {
this.$refs.flMap.mapObject.fitBounds([so, ...d], { padding: [50, 70] })
}
}
/* selected both */
if (this.selectedOrigNorm.key && this.selectedDestNorm.key) {
const so = [this.selectedOrig.lat, this.selectedOrig.long]
const sd = [this.selectedDest.lat, this.selectedDest.long]
// console.log('so:')
// console.log(so)
// console.log('sd:')
// console.log(sd)
if (so && sd) {
this.$refs.flMap.mapObject.fitBounds([so, sd], { padding: [50, 70] })
}
}
}
this.setBounds()
// const o = this.airportsOrig.map((m) => { return [m.lat, m.long] }) // const o = this.airportsOrig.map((m) => { return [m.lat, m.long] })
// const d = this.airportsDest.map((m) => { return [m.lat, m.long] }) // const d = this.airportsDest.map((m) => { return [m.lat, m.long] })
// const so = [this.selectedOrig.lat, this.selectedOrig.long] // const so = [this.selectedOrig.lat, this.selectedOrig.long]
// const sd = [this.selectedDest.lat, this.selectedDest.long] // const sd = [this.selectedDest.lat, this.selectedDest.long]
// this.$refs.flMap.mapObject.fitBounds([...o, ...d, ...so, ...sd]) // this.$refs.flMap.mapObject.fitBounds([...o, ...d, ...so, ...sd])
// this.$refs.flMap.mapObject.fitBounds([[40, -73], [5, -55]]) // this.$refs.flMap.mapObject.fitBounds([[40, -73], [5, -55]])
},
methods: {
resize () {
this.$refs.flMap.mapObject.invalidateSize()
},
setBounds () {
if (this.$refs.flMap && this.$refs.flMap.mapObject) {
/* selected none */
if (!this.selectedOrigNorm.key && !this.selectedDestNorm.key && this.airportsOrig.length > 0) {
const o = this.airportsOrig.map((m) => { return [m.lat, m.long] })
// console.log('origins:')
// console.log(o)
if (o) {
this.$refs.flMap.mapObject.fitBounds([...o], { padding: [50, 70] })
}
}
/* selected org */
if (this.selectedOrigNorm.key && !this.selectedDestNorm.key) {
const so = [this.selectedOrig.lat, this.selectedOrig.long]
const d = this.airportsDest.map((m) => { return [m.lat, m.long] })
// console.log('so:')
// console.log(so)
// console.log('dests:')
// console.log(d)
if (so && d) {
this.$refs.flMap.mapObject.fitBounds([so, ...d], { padding: [50, 70] })
}
}
/* selected both */
if (this.selectedOrigNorm.key && this.selectedDestNorm.key) {
const so = [this.selectedOrig.lat, this.selectedOrig.long]
const sd = [this.selectedDest.lat, this.selectedDest.long]
// console.log('so:')
// console.log(so)
// console.log('sd:')
// console.log(sd)
if (so && sd) {
this.$refs.flMap.mapObject.fitBounds([so, sd], { padding: [50, 70] })
}
}
}
}
} }
} }
</script> </script>
@ -298,6 +309,10 @@ export default {
gap: 0.5em; gap: 0.5em;
} }
.leaflet-container {
font-size: 1rem;
}
.leaflet-popup { .leaflet-popup {
top: 0 !important; top: 0 !important;
left: 0 !important; left: 0 !important;
@ -368,6 +383,25 @@ export default {
color: white; color: white;
} }
/* .marker-cluster {
background-clip: padding-box;
border-radius: 20px;
} */
.marker-cluster div {
/* width: 30px;
height: 30px;
margin-left: 5px;
margin-top: 5px;
text-align: center;
border-radius: 15px;
font: 12px "Helvetica Neue", Arial, Helvetica, sans-serif; */
}
/* .marker-cluster span {
line-height: 30px;
} */
#map-wrap .marker-cluster-small { #map-wrap .marker-cluster-small {
background-color: rgba(47, 151, 255, 0.15); background-color: rgba(47, 151, 255, 0.15);
} }
@ -392,31 +426,24 @@ export default {
background-color: rgba(0, 127, 255, 0.9); background-color: rgba(0, 127, 255, 0.9);
} }
/* .marker-cluster {
background-clip: padding-box;
border-radius: 20px;
}
.marker-cluster div {
width: 30px;
height: 30px;
margin-left: 5px;
margin-top: 5px;
text-align: center;
border-radius: 15px;
font: 12px "Helvetica Neue", Arial, Helvetica, sans-serif;
}
.marker-cluster span {
line-height: 30px;
} */
.btn { .btn {
border-radius: 999px; border-radius: 999px;
padding: 0.4em 0.8em;
font-size: 1rem;
padding: 0.5em 0.8em;
font-size: min(1rem, 18px);
white-space: nowrap; white-space: nowrap;
transition: 0.2s all; transition: 0.2s all;
display: flex;
align-items: center;
justify-content: space-around;
gap: 0.25rem;
line-height: 1;
min-height: 1em;
}
.btn svg {
height: min(1rem, 18px);
display: inline-block;
aspect-ratio: 1;
} }
.btn.btn--primary { .btn.btn--primary {
@ -425,7 +452,7 @@ export default {
} }
.btn.btn--primary:hover { .btn.btn--primary:hover {
background: #72b9ff;
background: #00ca00;
color: #fff; color: #fff;
} }

+ 29
- 0
components/OpenIndicator.vue View File

@ -0,0 +1,29 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-chevron-down"
width="60"
height="60"
viewBox="0 0 24 24"
stroke-width="2"
stroke="#bbbbbb"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<polyline points="6 9 12 15 18 9" fill="none" />
</svg>
</template>
<script>
export default {
}
</script>
<style scoped>
svg {
max-height: 3.2rem;
}
</style>

+ 93
- 0
components/TheHeader.vue View File

@ -0,0 +1,93 @@
<template>
<header>
<div class="header__col">
<Logo />
<div v-if="!selectedOrig.iata" class="logo-blurb">
from one of <strong>{{ airportsOrig.length }}</strong>
<span style="color: white;">local</span> airports
</div>
<div
v-if="
selectedOrig.iata && !selectedDest.iata && airports_dest.length > 1
"
class="logo-blurb"
>
to one of <strong>{{ airportsDest.length }}</strong>
{{ getCompliment }} destinations
</div>
</div>
<div class="header__col">
<div v-show="!isPickerVisible">
<button class="btn btn--primary" @click="showPicker">
tap the map or search 🔎
</button>
</div>
<div v-show="isPickerVisible">
<button class="btn btn--primary" @click="showPicker">
hide search <svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-arrow-bar-to-up"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="3"
stroke="#ffffff"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<line x1="12" y1="10" x2="12" y2="20" />
<line x1="12" y1="10" x2="16" y2="14" />
<line x1="12" y1="10" x2="8" y2="14" />
<line x1="4" y1="4" x2="20" y2="4" />
</svg>
</button>
</div>
</div>
<div class="header__col" />
</header>
</template>
<script>
export default {
props: {
selectedOrig: {
type: [Object],
default () {
return {}
}
},
selectedDest: {
type: [Object],
default () {
return {}
}
},
airportsOrig: {
type: [Array],
default () {
return []
}
},
airportsDest: {
type: [Array],
default () {
return []
}
},
isPickerVisible: {
type: [Boolean],
default () {
return null
}
}
}
}
</script>
<style>
.asdf {
color: red;
}
</style>

+ 592
- 0
components/TheSideBar.vue View File

@ -0,0 +1,592 @@
<template>
<div>
<button class="btn btn--primary sidebar-button" @click="showSidebar">
<svg
v-if="!isSidebarVisible"
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-menu"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="3"
stroke="#ffffff"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<line x1="4" y1="8" x2="20" y2="8" />
<line x1="4" y1="16" x2="20" y2="16" />
</svg>
<svg
v-if="isSidebarVisible"
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-arrow-bar-to-right"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="3"
stroke="#ffffff"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<line x1="14" y1="12" x2="4" y2="12" />
<line x1="14" y1="12" x2="10" y2="16" />
<line x1="14" y1="12" x2="10" y2="8" />
<line x1="20" y1="4" x2="20" y2="20" />
</svg>
</button>
<aside
:class="[
'sidebar-container',
{ 'sidebar-container--hide': !isSidebarVisible },
{ 'sidebar-container--show': isSidebarVisible },
]"
>
<div class="sidebar">
<div class="sidebar-content">
<h1 class="h1--about">
What is FlyLocal Alaska?
</h1>
<div class="img-divider">
<img
class="img--about"
src="https://openmoji.org/data/black/svg/1F3D4.svg"
alt=""
srcset=""
>
<img
class="img--about"
src="https://openmoji.org/data/black/svg/1F4C5.svg"
alt=""
srcset=""
>
<img
class="img--about"
src="https://openmoji.org/data/black/svg/1F601.svg"
alt=""
srcset=""
>
</div>
<p class="text--about">
<strong>FlyLocal-Alaska</strong> is an easy way to view the
schedules for your favorite Alaskan airlines - all in one place.
</p>
<h1 class="h1--about">
Why FlyLocal in Alaska?
</h1>
<div class="img-divider">
<img
class="img--about"
src="https://openmoji.org/data/black/svg/1F6AB.svg"
alt=""
srcset=""
>
<img
class="img--about"
src="https://openmoji.org/data/black/svg/1F697.svg"
alt=""
srcset=""
>
<img
class="img--about"
src="https://openmoji.org/data/black/svg/1F6E3.svg"
alt=""
srcset=""
>
</div>
<p class="text--about">
<strong>80% of Alaskan communities are not connected to the road
system.</strong>
Therefore, they rely heavily on local airlines for transportation.
</p>
<div class="img-divider">
<img
class="img--about"
src="https://openmoji.org/data/black/svg/1F6A3.svg"
alt=""
srcset=""
>
<img
class="img--about"
src="https://openmoji.org/data/black/svg/1F3D5.svg"
alt=""
srcset=""
>
<img
class="img--about"
src="https://openmoji.org/data/black/svg/1F43B.svg"
alt=""
srcset=""
>
</div>
<p class="text--about">
<strong>See new places.</strong> There are ~400 public airports in
Alaska, but only 20 (or 5%) with service from major US carriers.
</p>
<div class="img-divider">
<img
class="img--about"
src="https://openmoji.org/data/black/svg/1F6E9.svg"
alt=""
srcset=""
>
<img
class="img--about"
src="https://openmoji.org/data/black/svg/1F681.svg"
alt=""
srcset=""
>
<img
class="img--about"
src="https://openmoji.org/data/black/svg/1F6F8.svg"
alt=""
srcset=""
>
</div>
<p class="text--about">
Enjoy a completely <strong>unique flying experience</strong>. Have
you ever landed on a lake in a seaplane? Now is your chance to check
it off the bucket-list.
</p>
<h1 class="h1--about">
Up-to-date Travel Info
</h1>
<ul class="about-carriers list-lrg">
<li>
<a href="https://covid19.alaska.gov/travelers/">
<img
class="img--carrier-logo"
:src="require(`@/assets/images/orgs/alaska-travel-info.png`)"
alt="Alaska Travel Info"
>
</a>
</li>
</ul>
<h1 class="h1--about">
Explore Alaska on these great local airlines
</h1>
<ul class="about-carriers">
<li v-for="carrier in carriers" :key="carrier.id">
<a :href="carrier.fields.BookingLink" target="_blank">
<img
class="img--carrier-logo"
:src="require(`@/assets/images/carriers/${carrier.fields.Slug}.png`)"
:alt="carrier.fields.Name"
>
</a>
</li>
</ul>
<h1 class="h1--about">
Proud Member of
</h1>
<ul class="about-carriers list-med">
<li>
<a href="https://alaskaaircarriers.org">
<img
class="img--carrier-logo"
:src="require(`@/assets/images/orgs/aaca.png`)"
alt="Alaska Air Carriers Association"
>
</a>
</li>
<li>
<a href="https://alaskatia.org">
<img
class="img--carrier-logo"
:src="require(`@/assets/images/orgs/alaska-tia.png`)"
alt="Alaska Travel Industry Association"
>
</a>
</li>
</ul>
<h1 class="h1--about">
Featured on
</h1>
<ul class="about-carriers list-med">
<li>
<a href="https://www.1millioncups.com/anchorage/presentations/flylocal-alaska-35304">
<img
class="img--carrier-logo"
:src="require(`@/assets/images/orgs/1-million-cups.png`)"
alt="1 Million Cups"
>
</a>
</li>
<li>
<a href="https://www.travelalaska.com/offers/FlyLocal/Plane-Statewide.aspx">
<img
class="img--carrier-logo"
style="background: black; border-radius: 0.5rem; padding-left: 0.4rem;"
:src="require(`@/assets/images/orgs/travel-alaska.png`)"
alt="Travel Alaska"
>
</a>
</li>
</ul>
<div class="company-footer">
&nbsp; FlyLocal v5.0.2 &nbsp;&nbsp;𐄙&nbsp;&nbsp; ©2021 FlyLocal LLC &nbsp;
</div>
</div>
<ul class="contact">
<li class="contact-item">
<a href="https://twitter.com/fly_local">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-brand-twitter"
width="40"
height="40"
viewBox="0 0 24 24"
stroke-width="2"
stroke="#007fff"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path
d="M22 4.01c-1 .49 -1.98 .689 -3 .99c-1.121 -1.265 -2.783 -1.335 -4.38 -.737s-2.643 2.06 -2.62 3.737v1c-3.245 .083 -6.135 -1.395 -8 -4c0 0 -4.182 7.433 4 11c-1.872 1.247 -3.739 2.088 -6 2c3.308 1.803 6.913 2.423 10.034 1.517c3.58 -1.04 6.522 -3.723 7.651 -7.742a13.84 13.84 0 0 0 .497 -3.753c-.002 -.249 1.51 -2.772 1.818 -4.013z"
/>
</svg>
<span>@fly_local</span>
</a>
</li>
<li class="contact-item">
<a href="https://facebook.com/fly_local">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-brand-facebook"
width="40"
height="40"
viewBox="0 0 24 24"
stroke-width="2"
stroke="#007fff"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path
d="M7 10v4h3v7h4v-7h3l1 -4h-4v-2a1 1 0 0 1 1 -1h3v-4h-3a5 5 0 0 0 -5 5v2h-3"
/>
</svg>
<span>@fly_local</span>
</a>
</li>
<li class="contact-item">
<a href="https://instagram.com/fly_local">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-brand-instagram"
width="40"
height="40"
viewBox="0 0 24 24"
stroke-width="2"
stroke="#007fff"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<rect x="4" y="4" width="16" height="16" rx="4" />
<circle cx="12" cy="12" r="3" />
<line x1="16.5" y1="7.5" x2="16.5" y2="7.501" />
</svg>
<span>@fly_local</span>
</a>
</li>
<li class="contact-item">
<a href="mailto:team@iflylocal.com">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-user"
width="40"
height="40"
viewBox="0 0 24 24"
stroke-width="2"
stroke="#007fff"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<circle cx="12" cy="7" r="4" />
<path d="M6 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2" />
</svg>
<span>contact us</span>
</a>
</li>
<li class="contact-item">
<a href="mailto:partners@iflylocal.com">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-briefcase"
width="40"
height="40"
viewBox="0 0 24 24"
stroke-width="2"
stroke="#007fff"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<rect x="3" y="7" width="18" height="13" rx="2" />
<path d="M8 7v-2a2 2 0 0 1 2 -2h4a2 2 0 0 1 2 2v2" />
<line x1="12" y1="12" x2="12" y2="12.01" />
<path d="M3 13a20 20 0 0 0 18 0" />
</svg>
<span>partner with us</span>
</a>
</li>
</ul>
</div>
</aside>
<div
:class="[
'sidebar-blockui',
{ 'sidebar-blockui--show': isSidebarVisible },
]"
/>
</div>
</template>
<script>
export default {
data: () => ({
isSidebarVisible: false,
carriers: []
}),
async fetch () {
const filterFormula = 'AND(NOT(Status="Inactive"),FIND("Alaska",Region)>0)'
const sortFormula = '&sort[0][field]=Name&sort[0][direction]=asc'
let offset
let data = []
while (true) {
const offsetParam = offset ? `&offset=${offset}` : ''
const res = await fetch(`https://api.airtable.com/v0/appiQwfVZixRgRICe/Carriers_Alaska?filterByFormula=${filterFormula}${sortFormula}${offsetParam}`, {
method: 'GET',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: 'Bearer keyJ2ht64ZSN57AG1'
}
})
const json = await res.json()
offset = await json.offset
data = [...data, ...await json.records]
// await console.log(data.length)
if (await !offset) { break } // Were done let's stop this thing
}
this.carriers = [...data]
},
methods: {
showSidebar () {
this.isSidebarVisible = !this.isSidebarVisible
}
}
}
</script>
<style>
:root {
--sidebar-width: 50ch;
}
.sidebar-container {
z-index: 1002;
position: fixed;
top: 0;
right: 0;
display: flex;
justify-content: flex-start;
height: 100vh;
transition: all 0.3s;
pointer-events: none;
}
.sidebar-container.sidebar-container--hide {
transform: translateX(calc(var(--sidebar-width) + 6rem));
}
.sidebar-container.sidebar-container--show {
transform: translateX(0);
}
.sidebar {
background: white;
padding: 3rem 0 3rem 3rem;
border-radius: 3rem 0 0 3rem;
pointer-events: all;
display: flex;
flex-direction: column;
align-items: flex-end;
}
.sidebar-content {
overflow: auto;
max-width: var(--sidebar-width);
height: calc(100vh - 11rem);
padding-right: 3rem;
display: flex;
flex-direction: column;
align-items: center;
}
.img--about {
height: min(3rem, 60px);
display: inline-block;
}
div.img-divider {
display: flex;
justify-content: space-around;
padding: 0 25%;
}
.img--carrier-logo {
max-width: 6rem;
max-height: 6rem;
}
.list-med .img--carrier-logo {
max-width: 9rem;
max-height: 9rem;
}
.list-lrg .img--carrier-logo {
max-width: 15rem;
max-height: 15rem;
}
h1.h1--about {
font-weight: 200;
text-transform: uppercase;
color: #007fff;
font-size: min(2.7rem, 50px);
line-height: 1;
text-align: center;
margin-bottom: 1rem;
}
.text--about {
margin-top: 1rem;
margin-bottom: 1rem;
max-width: 40ch;
font-size: min(1rem, 18px);
}
.text--about:nth-of-type(1),
.text--about:nth-of-type(4) {
margin-bottom: 5rem;
}
.about-carriers {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 2rem;
align-items: center;
justify-content: space-around;
margin-bottom: 5rem;
}
.about-carriers li {
filter: saturate(0%);
transition: 0.3s all;
position: relative;
}
/* .about-carriers li::after {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: red;
content: '';
} */
.about-carriers li:hover {
filter: saturate(100%);
transform: scale(1.05);
}
.sidebar-button {
position: fixed;
top: 1rem;
right: 1rem;
z-index: 1003;
}
.sidebar-blockui {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
opacity: 0;
filter: blur(0.5);
background-color: #007fff;
transition: all 0.3s;
pointer-events: none;
z-index: 1001;
}
.sidebar-blockui.sidebar-blockui--show {
opacity: 0.8;
pointer-events: all;
}
ul.contact {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 1rem;
flex-wrap: wrap;
margin: 3rem 3rem 0 0;
max-width: var(--sidebar-width);
}
li.contact-item a span {
color: #007fff;
transition: all 0.3s;
}
ul.contact li.contact-item a {
display: flex;
flex-direction: row;
align-items: center;
transition: all 0.3s;
}
ul.contact li.contact-item a:hover span {
color: #00ca00;
}
ul.contact li.contact-item a svg {
max-height: 2rem;
max-width: 2rem;
stroke-width: 1;
transition: all 0.3s;
}
ul.contact li.contact-item a:hover svg {
stroke: #00ca00;
}
.company-footer {
font-family: monospace;
font-size: 0.8rem;
text-transform: uppercase;
color: gray;
}
</style>

+ 18
- 0
layouts/default.vue View File

@ -0,0 +1,18 @@
<template>
<div style="height: 100%;">
<Nuxt />
<TheSideBar />
</div>
</template>
<script>
export default {
}
</script>
<style>
.asdf {
color: red;
}
</style>

+ 15
- 0
middleware/redirects.js View File

@ -0,0 +1,15 @@
export default function (req, res, next) {
const redirects = [
{
from: '/',
to: '/go'
}
]
const redirect = redirects.find(r => r.from === req.url)
if (redirect) {
res.writeHead(301, { Location: redirect.to })
res.end()
} else {
next()
}
}

+ 14
- 5
nuxt.config.js View File

@ -13,6 +13,15 @@ export default {
] ]
}, },
server: {
host: '192.168.178.100',
port: 3010
},
serverMiddleware: [
'~/middleware/redirects.js'
],
// Global CSS: https://go.nuxtjs.dev/config-css // Global CSS: https://go.nuxtjs.dev/config-css
css: [ css: [
'~assets/css/main.css' '~assets/css/main.css'
@ -40,13 +49,13 @@ export default {
component: resolve(__dirname, 'pages/go.vue') component: resolve(__dirname, 'pages/go.vue')
}, },
{ {
name: 'flights-orig-dest',
path: '/flights/:o/:d',
component: resolve(__dirname, 'pages/flights.vue')
name: 'dates-orig-dest',
path: '/dates/:o/:d',
component: resolve(__dirname, 'pages/dates.vue')
}, },
{ {
name: 'flights-orig',
path: '/flights/:o',
name: 'flights-orig-dest-departure',
path: '/flights/:o/:d/:departure',
component: resolve(__dirname, 'pages/flights.vue') component: resolve(__dirname, 'pages/flights.vue')
} }
) )

+ 226
- 0
pages/dates.vue View File

@ -0,0 +1,226 @@
<template>
<div class="white-bkg">
<header>
<div class="header__col">
<button class="btn btn--primary" @click="goBack">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-chevrons-left"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="3"
stroke="#ffffff"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<polyline points="11 7 6 12 11 17" />
<polyline points="17 7 12 12 17 17" />
</svg> back
</button>
</div>
<div class="header__col">
<Logo />
</div>
<div class="header__col" />
</header>
<main class="main-with-header">
<ChosenFlights
:selected-orig="selectedOrig"
:selected-dest="selectedDest"
style="margin: 0 1rem;"
/>
<!-- <pre>
{{ schedules }}
</pre> -->
<DatePicker
:starting-date="startingDate"
:selected-dows="selectedDows"
/>
</main>
</div>
</template>
<script>
export default {
data () {
return {
schedules: [],
startingDate: new Date(),
selectedSchedule: {},
selectedDays: [],
selectedOrig: {},
selectedDest: {}
}
},
async fetch () {
// console.log(this.$route.params.o)
const today = new Date().toLocaleDateString('en-CA')
// function addDays (date, days) {
// const result = new Date(date)
// result.setDate(result.getDate() + days)
// return result
// }
// const tomorrow = addDays(new Date(), 1).toLocaleDateString('en-CA')
const scheduleFetchFilterFormula = `AND(Is_Current="Yes",Origin_IATA="${this.$route.params.o}",Destination_IATA="${this.$route.params.d}",Effective_End>"${today}")`
const scheduleFetchSort = '&sort[0][field]=DepartureTimeFormatted&sort[0][direction]=asc'
const response = await fetch(`https://api.airtable.com/v0/appiQwfVZixRgRICe/Schedule?filterByFormula=${scheduleFetchFilterFormula}${scheduleFetchSort}`, {
method: 'GET',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: 'Bearer keyJ2ht64ZSN57AG1'
}
})
const data = await response.json()
// console.log(data)
this.schedules = [...data.records]
},
computed: {
selectedDows () {
const mappedDows = this.schedules.map((schedule) => {
// console.log(schedule.fields)
return {
Flight_Number: schedule.fields.Flight_Number,
DowsList: this.daysList(schedule.fields.Frequency_Convert),
Effective_Start: schedule.fields.Effective_Start,
Effective_End: schedule.fields.Effective_End,
DepartureTimeFormatted: schedule.fields.DepartureTimeFormatted
}
})
return mappedDows
}
},
created () {
// console.log(this.$route.path)
if (this.$route && this.$route.params && this.$route.params.o) {
this.fetchSingleAirport(this.$route.params.o, true)
}
if (this.$route && this.$route.params && this.$route.params.d) {
this.fetchSingleAirport(this.$route.params.d, false)
}
},
methods: {
daysList (days) {
const list = days.split(', ').map((day) => {
const name = day.toLowerCase().slice(0, -1)
let num
switch (name) {
case 'su':
num = 0
break
case 'mo':
num = 1
break
case 'tu':
num = 2
break
case 'we':
num = 3
break
case 'th':
num = 4
break
case 'fr':
num = 5
break
case 'sa':
num = 6
break
default:
break
}
return {
name,
num
}
})
return list.sort(function (a, b) {
return a.num - b.num
})
},
goBack () {
this.$router.go(-1)
},
book (schedule) {
if (schedule.fields.IsExpedia[0] === 'true') {
this.startingDate = new Date()
this.selectedSchedule = schedule.fields
this.selectedDays = this.daysList(schedule.fields.Frequency_Convert)
} else {
window.open(schedule.fields['BookingLink (from Carriers_Alaska)'][0])
}
},
clearSelectedSchedule () {
console.log('clear')
this.startingDate = ''
this.selectedSchedule = {}
},
async fetchSingleAirport (iata, isOrig) {
const airportFetchFilterFormula =
isOrig
? `AND(Is_Origin=1,{IsCurrent-AsOrigin}="Yes",Airport_IATA="${iata}")`
: `AND(Is_Destination=1,{IsCurrent-AsDest}="Yes",Airport_IATA="${iata}")`
const response = await fetch(`https://api.airtable.com/v0/appiQwfVZixRgRICe/Airports_IATA?filterByFormula=${airportFetchFilterFormula}`, {
method: 'GET',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: 'Bearer keyJ2ht64ZSN57AG1'
}
})
const mapData = await response.json()
const thisAirport = {
iata: mapData.records[0].fields.Airport_IATA,
lat: mapData.records[0].fields.Latitude_Deg,
long: mapData.records[0].fields.Longitude_Deg,
icon: mapData.records[0].fields.Icon_Url,
name: mapData.records[0].fields.Airport_Name,
municipality: mapData.records[0].fields.Municipality,
type: mapData.records[0].fields.Type,
search: mapData.records[0].fields.Search_Field
}
switch (isOrig) {
case true:
this.selectedOrig = { ...thisAirport }
break
case false:
this.selectedDest = { ...thisAirport }
break
default:
break
}
}
}
}
</script>
<style lang="scss">
.main-with-header {
margin-top: clamp(20px + 1.75rem, 4rem + 1.75rem, 50px + 1.75rem);
}
</style>

+ 323
- 0
pages/flights copy.vue View File

@ -0,0 +1,323 @@
<template>
<main>
<div>
<button class="btn btn--primary" style="margin-left: 2rem; margin-top: 2rem;" @click="goBack">
👈 back
</button>
<h1 style="text-align: center; font-size: 2.5rem;">
{{ selectedOrig.municipality }} 🛩 {{ selectedDest.municipality }}
</h1>
</div>
<ul class="schedule-grid">
<li
v-for="schedule in schedules"
:key="schedule.fields.Record_ID"
class="grid__row"
>
<pre>
{{ schedule.fields.Flight_Number }}
</pre>
<div class="grid__field">
<label> Local Airline </label>
<div class="field__content">
{{ schedule.fields.Carrier_Name }}
</div>
</div>
<div class="grid__field">
<label> Days of the Week </label>
<div class="field__content">
<div class="dow-grid">
<div
v-for="day in daysList(schedule.fields.Frequency_Convert)"
:key="day.name"
:class="['dow-grid__day', 'day--' + day.name]"
>
<span> {{ day.name }} </span>
</div>
</div>
</div>
</div>
<div class="grid__field">
<label> Departing </label>
<div class="field__content">
{{ schedule.fields.Departure_TimeL }}
</div>
</div>
<div class="grid__field">
<label> Arriving </label>
<div class="field__content">
{{ schedule.fields.Arrival_TimeL }}
</div>
</div>
<div class="grid__field">
<label> Available Until </label>
<div class="field__content">
{{ schedule.fields.Effective_End }}
</div>
</div>
<div class="grid__field">
<div class="field__content">
{{ schedule.fields.Price_USD }}
</div>
<button class="btn btn--primary" @click="book(schedule)">
{{ schedule.fields.IsExpedia[0] === 'true' ? 'book it!' : 'see flights' }}
</button>
</div>
</li>
</ul>
</main>
</template>
<script>
export default {
data () {
return {
schedules: [],
startingDate: '',
selectedSchedule: {},
selectedDays: [],
selectedOrig: {},
selectedDest: {}
}
},
async fetch () {
// console.log(this.$route.params.o)
const today = new Date().toLocaleDateString('en-CA')
// function addDays (date, days) {
// const result = new Date(date)
// result.setDate(result.getDate() + days)
// return result
// }
// const tomorrow = addDays(new Date(), 1).toLocaleDateString('en-CA')
const scheduleFetchFilterFormula = `AND(Is_Current="Yes",Origin_IATA="${this.$route.params.o}",Destination_IATA="${this.$route.params.d}",Effective_End>"${today}",SEARCH("${new Date(this.$route.params.departure).getDay()}",Frequency)>0)`
// console.log(scheduleFetchFilterFormula)
const scheduleFetchSort = '&sort[0][field]=DepartureTimeFormatted&sort[0][direction]=asc'
const response = await fetch(`https://api.airtable.com/v0/appiQwfVZixRgRICe/Schedule?filterByFormula=${scheduleFetchFilterFormula}${scheduleFetchSort}`, {
method: 'GET',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: 'Bearer keyJ2ht64ZSN57AG1'
}
})
const data = await response.json()
// console.log(data)
this.schedules = [...data.records]
},
computed: {
},
created () {
// console.log(this.$route.path)
if (this.$route && this.$route.params && this.$route.params.o) {
this.fetchSingleAirport(this.$route.params.o, true)
}
if (this.$route && this.$route.params && this.$route.params.d) {
this.fetchSingleAirport(this.$route.params.d, false)
}
},
methods: {
daysList (days) {
const list = days.split(', ').map((day) => {
const name = day.toLowerCase().slice(0, -1)
let num
switch (name) {
case 'su':
num = 0
break
case 'mo':
num = 1
break
case 'tu':
num = 2
break
case 'we':
num = 3
break
case 'th':
num = 4
break
case 'fr':
num = 5
break
case 'sa':
num = 6
break
default:
break
}
return {
name,
num
}
})
return list.sort(function (a, b) {
return a.num - b.num
})
},
goBack () {
this.$router.go(-1)
},
book (schedule) {
if (schedule.fields.IsExpedia[0] === 'true') {
this.startingDate = new Date()
this.selectedSchedule = schedule.fields
this.selectedDays = this.daysList(schedule.fields.Frequency_Convert)
} else {
window.open(schedule.fields['BookingLink (from Carriers_Alaska)'][0])
}
},
clearSelectedSchedule () {
console.log('clear')
this.startingDate = ''
this.selectedSchedule = {}
},
async fetchSingleAirport (iata, isOrig) {
const airportFetchFilterFormula =
isOrig
? `AND(Is_Origin=1,{IsCurrent-AsOrigin}="Yes",Airport_IATA="${iata}")`
: `AND(Is_Destination=1,{IsCurrent-AsDest}="Yes",Airport_IATA="${iata}")`
const response = await fetch(`https://api.airtable.com/v0/appiQwfVZixRgRICe/Airports_IATA?filterByFormula=${airportFetchFilterFormula}`, {
method: 'GET',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: 'Bearer keyJ2ht64ZSN57AG1'
}
})
const mapData = await response.json()
const thisAirport = {
iata: mapData.records[0].fields.Airport_IATA,
lat: mapData.records[0].fields.Latitude_Deg,
long: mapData.records[0].fields.Longitude_Deg,
icon: mapData.records[0].fields.Icon_Url,
name: mapData.records[0].fields.Airport_Name,
municipality: mapData.records[0].fields.Municipality,
type: mapData.records[0].fields.Type,
search: mapData.records[0].fields.Search_Field
}
switch (isOrig) {
case true:
this.selectedOrig = { ...thisAirport }
break
case false:
this.selectedDest = { ...thisAirport }
break
default:
break
}
}
}
}
</script>
<style lang="scss">
.schedule-grid {
display: flex;
flex-direction: column;
gap: 1rem;
margin: 2rem;
}
.grid__row {
display: grid;
grid-template-columns: 2fr 2fr 1fr 1fr 2fr 1fr;
grid-template-rows: auto;
gap: 1rem;
padding: 1rem;
}
@media only screen and (max-width: 768px) {
.grid__row {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: auto;
}
}
.grid__row:nth-child(2n) {
background: #eee;
}
// .grid__field {
// }
label {
font-size: 0.75rem;
font-weight: 700;
color: gray;
display: block;
}
.field__content {
font-size: 1.5rem;
}
.dow-grid {
width: 200px;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
gap: 0.5rem;
}
.dow-grid__day {
font-size: 0.8rem;
border: 1px solid #ccc !important;
display: flex;
align-items: center;
justify-content: center;
border-radius: 3px !important;
text-transform: capitalize;
}
.day--su {
grid-column: 1 / span 1;
}
.day--mo {
grid-column: 2 / span 1;
}
.day--tu {
grid-column: 3 / span 1;
}
.day--we {
grid-column: 4 / span 1;
}
.day--th {
grid-column: 5 / span 1;
}
.day--fr {
grid-column: 6 / span 1;
}
.day--sa {
grid-column: 7 / span 1;
}
</style>

+ 421
- 77
pages/flights.vue View File

@ -1,67 +1,178 @@
<template> <template>
<main>
<ul class="schedule-grid">
<li
v-for="schedule in schedules"
:key="schedule.fields.Record_ID"
class="grid__row"
>
<div class="grid__field">
<label> Local Airline </label>
<div class="field__content">
{{ schedule.fields.Carrier_Name }}
</div>
</div>
<div class="grid__field">
<label> Days of the Week </label>
<div class="field__content">
<div class="dow-grid">
<div
v-for="day in daysList(schedule.fields.Frequency_Convert)"
:key="day.name"
:class="['dow-grid__day', 'day--' + day.name]"
<div class="white-bkg">
<header>
<div class="header__col">
<button class="btn btn--primary" @click="goBack">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-chevrons-left"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="3"
stroke="#ffffff"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<polyline points="11 7 6 12 11 17" />
<polyline points="17 7 12 12 17 17" />
</svg>
back
</button>
</div>
<div class="header__col">
<Logo />
</div>
<div class="header__col" />
</header>
<main class="main-with-header">
<ChosenFlights
:selected-orig="selectedOrig"
:selected-dest="selectedDest"
:departure="formattedDate($route.params.departure)"
:departure-time="selectedSchedule && selectedSchedule.fields && selectedSchedule.fields.Departure_TimeL"
style="margin: 0 1rem;"
/>
<div class="schedule-wrapper">
<ul class="schedule-grid">
<li
v-for="schedule in schedules"
:key="schedule.fields.Record_ID"
:class="[
'grid__row',
{ 'grid__row--selected': selectedSchedule.id == schedule.id },
]"
>
<div class="row__check">
<label class="radio radio--sched" :for="schedule.id">
<input :id="schedule.id" type="radio" name="schedules">
<span @click="selectedSchedule = { ...schedule }">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-check"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="3"
stroke="#ffffff"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M5 12l5 5l10 -10" />
</svg>
</span>
</label>
</div>
<div class="row__dep">
<label class="text--muni">
{{ selectedOrig.municipality }}
</label>
<h1 class="text--time">
{{ schedule.fields.Departure_TimeL }}
</h1>
</div>
<div class="row__trip">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-arrow-right-circle"
width="100"
height="100"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="#007fff"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
> >
<span> {{ day.name }} </span>
</div>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M18 15l3 -3l-3 -3" />
<circle cx="5" cy="12" r="2" />
<path d="M7 12h14" />
</svg>
</div> </div>
</div>
</div>
<div class="grid__field">
<label> Departing </label>
<div class="field__content">
{{ schedule.fields.Departure_TimeL }}
</div>
</div>
<div class="grid__field">
<label> Arriving </label>
<div class="field__content">
{{ schedule.fields.Arrival_TimeL }}
</div>
</div>
<div class="grid__field">
<label> Available Until </label>
<div class="field__content">
{{ schedule.fields.Effective_End }}
</div>
</div>
<div class="grid__field">
<div class="field__content">
{{ schedule.fields.Price_USD }}
</div>
<button class="btn btn--primary" @click="book(schedule)">
{{ schedule.fields.IsExpedia[0] === 'true' ? 'book it!' : 'see flights' }}
<div class="row__arr">
<label class="text--muni">
{{ selectedDest.municipality }}
</label>
<h1 class="text--time">
{{ schedule.fields.Arrival_TimeL }}
</h1>
</div>
<div class="row__info">
<h2 class="text--carrier">
{{ schedule.fields.Carrier_Name }}
</h2>
<span v-if="schedule.fields.Flight_Number" class="text--flight-num">
Flight No. {{ schedule.fields.Flight_Number }}
</span>
<span class="text--dow">
{{ schedule.fields.Frequency_Convert }}
</span>
<span class="badge--avail">
Available until
<strong>{{ formattedDate(schedule.fields.Effective_End) }}</strong>
</span>
</div>
</li>
</ul>
</div>
<div
:class="[
'flyout-container',
{ 'flyout-container--hide': !selectedSchedule.id },
{ 'flyout-container--show': selectedSchedule.id },
]"
>
<div class="flyout">
<button
v-if="
selectedSchedule &&
selectedSchedule.fields &&
selectedSchedule.fields.IsExpedia &&
selectedSchedule.fields.IsExpedia[0] !== 'true'
"
class="btn btn--primary"
@click="goToCarrier"
>
see flights
</button> </button>
<div
v-if="
selectedSchedule &&
selectedSchedule.fields &&
selectedSchedule.fields.IsExpedia &&
selectedSchedule.fields.IsExpedia[0] === 'true'
"
class="book-container"
>
<div>
<label for="">How Many Adults?</label>
<v-select
v-model="numAdult"
:options="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"
auto
/>
</div>
<div>
<label for="">How Many Children?</label>
<v-select
v-model="numChild"
:options="[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"
:menu-props="{ top: true, offsetY: true }"
/>
</div>
<button class="btn btn--primary" @click="book">
book now!
</button>
</div>
</div> </div>
</li>
</ul>
<div v-if="startingDate">
<DatePicker
:starting-date="startingDate"
:selected-schedule="selectedSchedule"
:selected-days="selectedDays"
/>
</div>
</main>
</div>
</main>
</div>
</template> </template>
<script> <script>
@ -71,12 +182,28 @@ export default {
schedules: [], schedules: [],
startingDate: '', startingDate: '',
selectedSchedule: {}, selectedSchedule: {},
selectedDays: []
selectedDays: [],
selectedOrig: {},
selectedDest: {},
numAdult: 1,
numChild: 0
} }
}, },
async fetch () { async fetch () {
// console.log(this.$route.params.o) // console.log(this.$route.params.o)
const scheduleFetchFilterFormula = `AND(Is_Current="Yes",AND(Origin_IATA="${this.$route.params.o}",Destination_IATA="${this.$route.params.d}"))`
const today = new Date().toLocaleDateString('en-CA')
// function addDays (date, days) {
// const result = new Date(date)
// result.setDate(result.getDate() + days)
// return result
// }
// const tomorrow = addDays(new Date(), 1).toLocaleDateString('en-CA')
const scheduleFetchFilterFormula = `AND(Is_Current="Yes",Origin_IATA="${this.$route.params.o}",Destination_IATA="${this.$route.params.d}",Effective_End>"${today}",SEARCH("${new Date(this.$route.params.departure).getDay()}",Frequency)>0)`
// console.log(scheduleFetchFilterFormula)
const scheduleFetchSort = '&sort[0][field]=DepartureTimeFormatted&sort[0][direction]=asc' const scheduleFetchSort = '&sort[0][field]=DepartureTimeFormatted&sort[0][direction]=asc'
const response = await fetch(`https://api.airtable.com/v0/appiQwfVZixRgRICe/Schedule?filterByFormula=${scheduleFetchFilterFormula}${scheduleFetchSort}`, { const response = await fetch(`https://api.airtable.com/v0/appiQwfVZixRgRICe/Schedule?filterByFormula=${scheduleFetchFilterFormula}${scheduleFetchSort}`, {
@ -93,10 +220,23 @@ export default {
this.schedules = [...data.records] this.schedules = [...data.records]
}, },
computed: {
created () {
// console.log(this.$route.path)
if (this.$route && this.$route.params && this.$route.params.o) {
this.fetchSingleAirport(this.$route.params.o, true)
}
if (this.$route && this.$route.params && this.$route.params.d) {
this.fetchSingleAirport(this.$route.params.d, false)
}
}, },
methods: { methods: {
formattedDate (date) {
const thisDate = new Date(date)
const month = thisDate.toLocaleString('en-us', { month: 'short' })
const year = ((thisDate.getFullYear() !== new Date().getFullYear()) ? ', ' + thisDate.getFullYear() : '')
return month + ' ' + thisDate.getDate() + year
},
daysList (days) { daysList (days) {
const list = days.split(', ').map((day) => { const list = days.split(', ').map((day) => {
const name = day.toLowerCase().slice(0, -1) const name = day.toLowerCase().slice(0, -1)
@ -144,13 +284,69 @@ export default {
return a.num - b.num return a.num - b.num
}) })
}, },
book (schedule) {
if (schedule.fields.IsExpedia[0] === 'true') {
this.startingDate = new Date()
this.selectedSchedule = schedule.fields
this.selectedDays = this.daysList(schedule.fields.Frequency_Convert)
} else {
window.open(schedule.fields['BookingLink (from Carriers_Alaska)'][0])
goBack () {
this.$router.go(-1)
},
// book2 (schedule) {
// if (schedule.fields.IsExpedia[0] === 'true') {
// this.startingDate = new Date()
// this.selectedSchedule = schedule.fields
// this.selectedDays = this.daysList(schedule.fields.Frequency_Convert)
// } else {
// window.open(schedule.fields['BookingLink (from Carriers_Alaska)'][0])
// }
// },
book () {
// add tracker
window.open(`https://www.anrdoezrs.net/links/100449149/type/am/https://www.expedia.com/go/flight/search/oneway/${this.$route.params.departure}/${this.$route.params.departure}?langid=1033&FromAirport=${this.selectedSchedule.fields.Origin_IATA}&FromTime=${this.selectedSchedule.fields.Departure_TimeL}&ToTime=${this.selectedSchedule.fields.Arrival_TimeL}&ToAirport=${this.selectedSchedule.fields.Destination_IATA}&Class=3&NumAdult=${this.numAdult}&NumChild=${this.numChild}`)
},
goToCarrier () {
// add tracker
window.open(this.selectedSchedule.fields['BookingLink (from Carriers_Alaska)'][0])
},
clearSelectedSchedule () {
// console.log('clear')
this.startingDate = ''
this.selectedSchedule = {}
},
async fetchSingleAirport (iata, isOrig) {
const airportFetchFilterFormula =
isOrig
? `AND(Is_Origin=1,{IsCurrent-AsOrigin}="Yes",Airport_IATA="${iata}")`
: `AND(Is_Destination=1,{IsCurrent-AsDest}="Yes",Airport_IATA="${iata}")`
const response = await fetch(`https://api.airtable.com/v0/appiQwfVZixRgRICe/Airports_IATA?filterByFormula=${airportFetchFilterFormula}`, {
method: 'GET',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: 'Bearer keyJ2ht64ZSN57AG1'
}
})
const mapData = await response.json()
const thisAirport = {
iata: mapData.records[0].fields.Airport_IATA,
lat: mapData.records[0].fields.Latitude_Deg,
long: mapData.records[0].fields.Longitude_Deg,
icon: mapData.records[0].fields.Icon_Url,
name: mapData.records[0].fields.Airport_Name,
municipality: mapData.records[0].fields.Municipality,
type: mapData.records[0].fields.Type,
search: mapData.records[0].fields.Search_Field
}
switch (isOrig) {
case true:
this.selectedOrig = { ...thisAirport }
break
case false:
this.selectedDest = { ...thisAirport }
break
default:
break
} }
} }
} }
@ -158,27 +354,37 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
.schedule-wrapper {
display: flex;
flex-direction: column;
align-items: center;
}
.schedule-grid { .schedule-grid {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 1rem; gap: 1rem;
margin: 2rem 2rem 2rem 2rem;
} }
.grid__row { .grid__row {
display: grid;
grid-template-columns: 2fr 2fr 1fr 1fr 2fr 1fr;
grid-template-rows: auto;
display: flex;
flex-direction: row;
align-items: center;
gap: 1rem; gap: 1rem;
padding: 1rem 1rem 1rem 4rem;
flex-wrap: wrap;
border-radius: 2rem;
} }
// .grid__field {
// }
.grid__row:nth-child(odd) {
background: #eee;
}
label { label {
font-size: 0.5rem;
font-size: 0.75rem;
font-weight: 700; font-weight: 700;
color: gray;
color: white;
display: block; display: block;
} }
@ -230,4 +436,142 @@ label {
.day--sa { .day--sa {
grid-column: 7 / span 1; grid-column: 7 / span 1;
} }
.row__trip {
display: flex;
align-items: center;
}
.row__trip svg {
margin-bottom: -0.5rem;
width: 3.5rem;
height: 3.5rem;
margin-left: -1rem;
margin-right: -1rem;
}
.shape--trip {
height: 0.4rem;
width: 2rem;
background: #007fff;
display: block;
margin-top: 1rem;
position: relative;
}
.shape--trip::before {
height: 0.8rem;
width: 0.8rem;
background: #fff;
border: 0.02rem solid #007fff;
border-radius: 999px;
content: "";
position: absolute;
top: -0.15rem;
left: -0.5rem;
}
.shape--trip::after {
height: 0.8rem;
width: 0.8rem;
background: #007fff;
border: 0.02rem solid #007fff;
border-radius: 999px;
content: "";
position: absolute;
top: -0.15rem;
right: -0.5rem;
}
h1 {
font-size: 3rem;
line-height: 0.85;
}
h2 {
font-size: 2rem;
line-height: 1;
}
label.text--muni {
color: #007fff;
line-height: 1;
padding-left: 0.3rem;
}
span.text--flight-num {
font-weight: 700;
margin-right: 0.5rem;
}
span.text--dow {
font-weight: 300;
letter-spacing: -0.1em;
}
span.badge--avail {
border-radius: 999px;
border: 1px solid gray;
color: gray;
padding: 0.2em 0.8em;
line-height: 1;
font-size: 0.8rem;
display: block;
width: fit-content;
}
.radio span {
border-radius: 999px;
border: 3px solid #007fff;
background-color: white;
height: 2rem;
width: 2rem;
display: flex;
position: relative;
transition: all 0.3s;
align-items: center;
justify-content: center;
}
.radio span svg {
transition: all 0.3s;
stroke: #eee;
}
.radio span:hover {
background: #007fff;
}
.radio input:checked + span {
background: #007fff;
}
.radio input:checked + span svg {
stroke: white;
}
.radio input {
display: none;
}
.row__info {
min-width: fit-content;
}
.row__check {
margin-left: -3rem;
}
.book-container {
display: flex;
flex-direction: column;
justify-content: stretch;
gap: 1rem;
}
.book-container label {
text-transform: uppercase;
font-size: 1rem;
font-weight: 300;
}
</style> </style>

+ 103
- 190
pages/go.vue View File

@ -2,34 +2,59 @@
<div :class="['page-container', { 'nav-is-showing': isPickerVisible }]"> <div :class="['page-container', { 'nav-is-showing': isPickerVisible }]">
<header> <header>
<div class="header__col"> <div class="header__col">
<Logo />
<div v-if="!selectedOrig.iata" class="logo-blurb">
from one of {{ airports_orig.length }} airports
</div>
<div v-if="selectedOrig.iata && !selectedDest.iata && airports_dest.length > 1" class="logo-blurb">
to one of {{ airports_dest.length }} beautiful destinations
</div>
</div>
<div class="header__col">
<div v-show="!isPickerVisible">
<template v-if="!isPickerVisible">
<button class="btn btn--primary" @click="showPicker"> <button class="btn btn--primary" @click="showPicker">
tap the map or search 🔎
tap the map or <svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-search"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="3"
stroke="#ffffff"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<circle cx="10" cy="10" r="7" />
<line x1="21" y1="21" x2="15" y2="15" />
</svg>
</button> </button>
</div>
<div v-show="isPickerVisible">
</template>
<template v-if="isPickerVisible">
<button class="btn btn--primary" @click="showPicker"> <button class="btn btn--primary" @click="showPicker">
hide search
hide search <svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-arrow-bar-to-up"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="3"
stroke="#ffffff"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<line x1="12" y1="10" x2="12" y2="20" />
<line x1="12" y1="10" x2="16" y2="14" />
<line x1="12" y1="10" x2="8" y2="14" />
<line x1="4" y1="4" x2="20" y2="4" />
</svg>
</button> </button>
</div>
</template>
</div> </div>
<div class="header__col"> <div class="header__col">
<button
class="btn btn--primary"
@click="showSidebar"
>
</button>
<Logo />
<div v-if="!selectedOrig.iata && !isPickerVisible" class="logo-blurb">
from one of <strong>{{ airports_orig.length }}</strong> <span style="color: white;">local</span> airports
</div>
<div v-if="selectedOrig.iata && !selectedDest.iata && airports_dest.length > 1 && !isPickerVisible" class="logo-blurb">
to one of <strong>{{ airports_dest.length }}</strong> {{ getCompliment }} destinations
</div>
</div> </div>
<div class="header__col" />
</header> </header>
<!-- <pre style="position: fixed; z-index: 500; right: 0; top: 2rem; font-size: 0.6rem; color: gray;"> <!-- <pre style="position: fixed; z-index: 500; right: 0; top: 2rem; font-size: 0.6rem; color: gray;">
{{ selectedOrig }} {{ selectedOrig }}
@ -40,6 +65,7 @@
<AirportPicker <AirportPicker
:airports="airports_orig" :airports="airports_orig"
:selected-airport="selectedOrig" :selected-airport="selectedOrig"
leg="start here ►"
@select-airport="makeOrigin" @select-airport="makeOrigin"
@deselect-airport="clearOrigin" @deselect-airport="clearOrigin"
/> />
@ -47,11 +73,13 @@
v-show="selectedOrig.iata" v-show="selectedOrig.iata"
:airports="airports_dest" :airports="airports_dest"
:selected-airport="selectedDest" :selected-airport="selectedDest"
leg="land here ■"
@select-airport="makeDestination" @select-airport="makeDestination"
@deselect-airport="clearDestination" @deselect-airport="clearDestination"
/> />
</nav> </nav>
<Map <Map
ref="mapComponent"
:airports-orig="airports_orig" :airports-orig="airports_orig"
:airports-dest="airports_dest" :airports-dest="airports_dest"
:selected-orig="selectedOrig" :selected-orig="selectedOrig"
@ -61,15 +89,9 @@
@clear-origin="clearOrigin" @clear-origin="clearOrigin"
@clear-destination="clearDestination" @clear-destination="clearDestination"
/> />
<aside
:class="['sidebar-container',{ 'sidebar-container--hide': !isSidebarVisible },{ 'sidebar-container--show': isSidebarVisible }]"
>
<div class="sidebar">
sidebar info
</div>
</aside>
<div :class="['flyout-container',{ 'flyout-container--hide': !selectedOrig.iata || !selectedDest.iata },{ 'flyout-container--show': selectedOrig.iata && selectedDest.iata }]"> <div :class="['flyout-container',{ 'flyout-container--hide': !selectedOrig.iata || !selectedDest.iata },{ 'flyout-container--show': selectedOrig.iata && selectedDest.iata }]">
<FlightsFlyout <FlightsFlyout
v-if="selectedOrig.iata && selectedDest.iata"
:selected-orig="selectedOrig" :selected-orig="selectedOrig"
:selected-dest="selectedDest" :selected-dest="selectedDest"
/> />
@ -140,44 +162,30 @@ export default {
} }
}, },
async fetch () { async fetch () {
this.mountains = await fetch(
'https://api.nuxtjs.dev/mountains'
).then(res => res.json())
let response2 = {}
let mapData2 = {}
const response = await fetch(`https://api.airtable.com/v0/appiQwfVZixRgRICe/Airports_IATA?filterByFormula=${this.airportFetch_filterFormula}${this.airportFetch_fields_string}${this.airportFetch_sort}`, {
method: 'GET',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: 'Bearer keyJ2ht64ZSN57AG1'
}
})
let offset
let data = []
if (await response.offset) {
response2 = fetch(`https://api.airtable.com/v0/appiQwfVZixRgRICe/Airports_IATA?filterByFormula=${this.airportFetch_filterFormula}&fields[]=Airport_IATA&fields[]=Icon_Url&fields[]=Latitude_Deg&fields[]=Longitude_Deg&sort[0][field]=Airport_IATA&sort[0][direction]=asc&offset=${response.offset}`, {
while (true) {
const offsetParam = offset ? `&offset=${offset}` : ''
const res = await fetch(`https://api.airtable.com/v0/appiQwfVZixRgRICe/Airports_IATA?filterByFormula=${this.airportFetch_filterFormula}${this.airportFetch_fields_string}${this.airportFetch_sort}${offsetParam}`, {
method: 'GET', method: 'GET',
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded', 'Content-Type': 'application/x-www-form-urlencoded',
Authorization: 'Bearer keyJ2ht64ZSN57AG1' Authorization: 'Bearer keyJ2ht64ZSN57AG1'
} }
}) })
mapData2 = await response2.json()
}
const mapData = await response.json()
const r1 = mapData.records
const r2 = mapData2.records
const json = await res.json()
offset = await json.offset
data = [...data, ...await json.records]
await console.log(data.length)
console.log(r2)
// const mapDataTotal = [...r1, ...r2]
if (await !offset) { break } // Were done let's stop this thing
}
const mapDataTotal = [...r1]
// console.log('Look ma! I waited!') // Won't run till the while is done
this.airports_orig = mapDataTotal.map((record) => {
this.airports_orig = data.map((record) => {
return { return {
iata: record.fields.Airport_IATA, iata: record.fields.Airport_IATA,
lat: record.fields.Latitude_Deg, lat: record.fields.Latitude_Deg,
@ -197,6 +205,12 @@ export default {
return '&fields[]=' + field return '&fields[]=' + field
}) })
return array.join('') return array.join('')
},
getCompliment () {
const arr = [
'beautiful', 'gorgeous', 'breathtaking', 'wild', 'adventurous', 'amazing', 'majestic', 'uncharted', 'unpredictable', 'pristine', 'untamed'
]
return arr[Math.floor(Math.random() * arr.length)]
} }
}, },
created () { created () {
@ -233,6 +247,8 @@ export default {
makeDestination (airport) { makeDestination (airport) {
this.selectedDest = { ...airport } this.selectedDest = { ...airport }
this.isPickerVisible = false
history.pushState( history.pushState(
{}, {},
null, null,
@ -259,20 +275,30 @@ export default {
async fetchDestinations (iata) { async fetchDestinations (iata) {
const airportFetchDestfilterFormula = `AND(Is_Destination=1,{IsCurrent-AsDest}="Yes",(FIND("${iata}", Associated_Origin_Search)>0))` const airportFetchDestfilterFormula = `AND(Is_Destination=1,{IsCurrent-AsDest}="Yes",(FIND("${iata}", Associated_Origin_Search)>0))`
const response = await fetch(`https://api.airtable.com/v0/appiQwfVZixRgRICe/Airports_IATA?filterByFormula=${airportFetchDestfilterFormula}${this.airportFetch_fields_string}${this.airportFetch_sort}`, {
method: 'GET',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: 'Bearer keyJ2ht64ZSN57AG1'
}
})
const mapData = await response.json()
let offset
let data = []
while (true) {
const offsetParam = offset ? `&offset=${offset}` : ''
const res = await fetch(`https://api.airtable.com/v0/appiQwfVZixRgRICe/Airports_IATA?filterByFormula=${airportFetchDestfilterFormula}${this.airportFetch_fields_string}${this.airportFetch_sort}${offsetParam}`, {
method: 'GET',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: 'Bearer keyJ2ht64ZSN57AG1'
}
})
const json = await res.json()
offset = await json.offset
data = [...data, ...await json.records]
// await console.log(data.length)
if (await !offset) { break } // Were done let's stop this thing
}
// console.log('mapData.records length: ' + mapData.records.length)
// console.log('this.airports_dest length (before): ' + this.airports_dest.length)
// console.log('Look ma! I waited!') // Won't run till the while is done
this.airports_dest = mapData.records.map((record) => {
this.airports_dest = data.map((record) => {
return { return {
iata: record.fields.Airport_IATA, iata: record.fields.Airport_IATA,
lat: record.fields.Latitude_Deg, lat: record.fields.Latitude_Deg,
@ -284,10 +310,6 @@ export default {
search: record.fields.Search_Field search: record.fields.Search_Field
} }
}) })
// this.$refs.flMap.mapObject.fitBounds(this.markers.map((m) => { return [m.lat, m.lng] }))
// console.log('this.airports_dest length (after)' + this.airports_dest.length)
}, },
async fetchSingleAirport (iata, isOrig) { async fetchSingleAirport (iata, isOrig) {
const airportFetchFilterFormula = const airportFetchFilterFormula =
@ -331,7 +353,11 @@ export default {
} }
}, },
showPicker () { showPicker () {
this.isPickerVisible = !this.isPickerVisible
const vm = this
vm.isPickerVisible = !vm.isPickerVisible
setTimeout(function () {
vm.$refs.mapComponent.resize()
}, 500)
}, },
showSidebar () { showSidebar () {
this.isSidebarVisible = !this.isSidebarVisible this.isSidebarVisible = !this.isSidebarVisible
@ -341,127 +367,14 @@ export default {
</script> </script>
<style> <style>
html,
body,
#__nuxt,
#__layout,
.page-container,
main {
height: 100%;
}
header {
position: fixed;
top: 0;
width: 100vw;
z-index: 403;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
padding: 1rem;
}
.header__col:nth-child(2) {
text-align: center;
}
.header__col:nth-child(3) {
text-align: right;
}
.logo-blurb { .logo-blurb {
position: relative;
left: 4.5rem;
position: absolute;
left: min(400px, 47%);
font-size: clamp(10px, 1rem, 18px);
color: white; color: white;
top: -0.8rem;
}
main {
display: flex;
flex-direction: column;
/* grid-template-rows: min-content 1fr; */
top: clamp(20px + 0.25rem, 4rem + 0.25rem, 50px + 0.25rem);
white-space: nowrap;
} }
.nav {
flex: 0 0 auto;
/* position: fixed;
top: 0;
padding-top: 4rem;
width: 100vw;
z-index: 401; */
background: white;
display: flex;
flex-direction: column;
justify-content: flex-end;
}
:root {
--m-s: 0.1s;
--h-s: 0.3s;
--d: calc(var(--h-s) - var(--m-s));
}
.nav--hide {
transition: margin-top var(--m-s) linear var(--h-s), max-height var(--h-s) linear 0s;
margin-top: 0;
max-height: 0;
}
.nav--show {
margin-top: 3.5rem;
max-height: 250px;
transition: margin-top var(--m-s) linear 0s, max-height var(--h-s) linear var(--m-s);
}
.btn--nav-open {
padding: 1rem;
display: block;
}
.flyout-container {
z-index: 401;
position: fixed;
bottom: 0;
display: flex;
justify-content: center;
width: 100vw;
transition: all 0.3s;
pointer-events: none;
}
.flyout-container.flyout-container--hide {
transform: translateY(200px);
}
.flyout-container.flyout-container--show {
transform: translateY(0);
}
.sidebar-container {
z-index: 402;
position: fixed;
top: 0;
right: 0;
display: flex;
justify-content: flex-start;
height: 100vh;
transition: all 0.3s;
pointer-events: none;
}
.sidebar-container.sidebar-container--hide {
transform: translateX(200px);
}
.sidebar-container.sidebar-container--show {
transform: translateX(0);
}
.sidebar {
background: white;
padding: 3rem;
border-radius: 3rem 0 0 3rem;
pointer-events: all;
}
</style> </style>

+ 92
- 0
pages/index copy.vue View File

@ -0,0 +1,92 @@
<template>
<main class="special">
<div class="section">
<NuxtLink to="/go" class="btn btn--primary">
go to the site
</NuxtLink>
<br><br>
<h1 style="font-size: 2rem;">
"what is this site, really?"
</h1>
<br>
<pre style="white-space: pre-wrap;">
Last week, I was mulling over FlyLocal.io's speed issues.
As an exercise, I made a list of all the things that the FlyLocal website does.
- [1] get data from a server three times
- [2] display data on a map, twice
- [3] display data on a page, once
I let it ruminate that, by design, all the heavy lifting, all the processing, is already being done by Airtable.
From a technical perspective, the site is really just a *viewer* of our product, and our product is Airtable data.
***
Then I thought about all the previous iterations of the site.
It occurred to me that all the fancy stuff I'd done in Bubble v1 and pre-Bubble was for a business model that no longer existed.
There was nothing fancy about this *new* model. No forms, no calculations. Get data, display data.
And it occurred me that all the time-consuming stuff I'd done with Bubble v2 over the last year was really about me learning Bubble and figuring out how to get it to do the stuff that I already knew that websites could do: Get data, display data.
And really, there was nothing all that time-consuming about getting data and displaying data, especially in 2021.
***
So... I decided to take a couple days and make a website. And I'm pleasantly surprised with how quickly it all came together.
Hand-coded from the ground up on a modern, popular web-dev framework, it's lean, snappy, mobile-first, it has an elegant backend, and it checks all of my boxes.
It still needs some css TLC, but all the major dev is complete.
</pre>
<NuxtLink to="/go" class="btn btn--primary">
v5, for your consideration
</NuxtLink>
</div>
</main>
</template>
<script>
export default {
mounted () {
const body = document.querySelector('body')
body.classList.add('special')
}
}
</script>
<style>
main.special {
color: #888 !important;
display: flex;
align-items: center;
padding: 3rem;
background: #262626;
overflow-y: scroll;
}
.section {
max-width: 60ch;
border-radius: 2rem;
border: 1px solid #888;
background: rgba(255, 255, 255, 0.05);
padding: 3rem;
}
.btn {
border-radius: 999px;
padding: 0.4em 0.8em;
font-size: 1rem;
white-space: nowrap;
transition: 0.2s all;
}
.btn.btn--primary {
background: #007fff;
color: #fff;
}
</style>

+ 17
- 5
pages/index.vue View File

@ -1,13 +1,25 @@
<template> <template>
<main>
<h1>Home page</h1>
<NuxtLink to="/go">
Go!
<main class="empty-page white-bkg">
<NuxtLogo />
<NuxtLink to="/go" class="btn btn--primary">
go to the site
</NuxtLink> </NuxtLink>
<a href="https://nuxtjs.org">External Link to another page</a>
</main> </main>
</template> </template>
<script> <script>
export default {} export default {}
</script> </script>
<style scoped>
html {
background-color: black;
}
.empty-page {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
</style>

+ 3
- 1
stylelint.config.js View File

@ -4,5 +4,7 @@ module.exports = {
], ],
// add your custom config here // add your custom config here
// https://stylelint.io/user-guide/configuration // https://stylelint.io/user-guide/configuration
rules: {}
rules: {
// 'length-zero-no-unit': ['fix']
}
} }

Loading…
Cancel
Save