diff --git a/assets/images/signin-github.png b/assets/images/signin-github.png new file mode 100644 index 0000000..20449f2 Binary files /dev/null and b/assets/images/signin-github.png differ diff --git a/assets/images/signin-google.png b/assets/images/signin-google.png new file mode 100644 index 0000000..c84ef29 Binary files /dev/null and b/assets/images/signin-google.png differ diff --git a/elm.js b/elm.js index aaca5e5..ccf92a2 100644 --- a/elm.js +++ b/elm.js @@ -4357,6 +4357,181 @@ function _Browser_load(url) } + +// SEND REQUEST + +var _Http_toTask = F3(function(router, toTask, request) +{ + return _Scheduler_binding(function(callback) + { + function done(response) { + callback(toTask(request.expect.a(response))); + } + + var xhr = new XMLHttpRequest(); + xhr.addEventListener('error', function() { done($elm$http$Http$NetworkError_); }); + xhr.addEventListener('timeout', function() { done($elm$http$Http$Timeout_); }); + xhr.addEventListener('load', function() { done(_Http_toResponse(request.expect.b, xhr)); }); + $elm$core$Maybe$isJust(request.tracker) && _Http_track(router, xhr, request.tracker.a); + + try { + xhr.open(request.method, request.url, true); + } catch (e) { + return done($elm$http$Http$BadUrl_(request.url)); + } + + _Http_configureRequest(xhr, request); + + request.body.a && xhr.setRequestHeader('Content-Type', request.body.a); + xhr.send(request.body.b); + + return function() { xhr.c = true; xhr.abort(); }; + }); +}); + + +// CONFIGURE + +function _Http_configureRequest(xhr, request) +{ + for (var headers = request.headers; headers.b; headers = headers.b) // WHILE_CONS + { + xhr.setRequestHeader(headers.a.a, headers.a.b); + } + xhr.timeout = request.timeout.a || 0; + xhr.responseType = request.expect.d; + xhr.withCredentials = request.allowCookiesFromOtherDomains; +} + + +// RESPONSES + +function _Http_toResponse(toBody, xhr) +{ + return A2( + 200 <= xhr.status && xhr.status < 300 ? $elm$http$Http$GoodStatus_ : $elm$http$Http$BadStatus_, + _Http_toMetadata(xhr), + toBody(xhr.response) + ); +} + + +// METADATA + +function _Http_toMetadata(xhr) +{ + return { + url: xhr.responseURL, + statusCode: xhr.status, + statusText: xhr.statusText, + headers: _Http_parseHeaders(xhr.getAllResponseHeaders()) + }; +} + + +// HEADERS + +function _Http_parseHeaders(rawHeaders) +{ + if (!rawHeaders) + { + return $elm$core$Dict$empty; + } + + var headers = $elm$core$Dict$empty; + var headerPairs = rawHeaders.split('\r\n'); + for (var i = headerPairs.length; i--; ) + { + var headerPair = headerPairs[i]; + var index = headerPair.indexOf(': '); + if (index > 0) + { + var key = headerPair.substring(0, index); + var value = headerPair.substring(index + 2); + + headers = A3($elm$core$Dict$update, key, function(oldValue) { + return $elm$core$Maybe$Just($elm$core$Maybe$isJust(oldValue) + ? value + ', ' + oldValue.a + : value + ); + }, headers); + } + } + return headers; +} + + +// EXPECT + +var _Http_expect = F3(function(type, toBody, toValue) +{ + return { + $: 0, + d: type, + b: toBody, + a: toValue + }; +}); + +var _Http_mapExpect = F2(function(func, expect) +{ + return { + $: 0, + d: expect.d, + b: expect.b, + a: function(x) { return func(expect.a(x)); } + }; +}); + +function _Http_toDataView(arrayBuffer) +{ + return new DataView(arrayBuffer); +} + + +// BODY and PARTS + +var _Http_emptyBody = { $: 0 }; +var _Http_pair = F2(function(a, b) { return { $: 0, a: a, b: b }; }); + +function _Http_toFormData(parts) +{ + for (var formData = new FormData(); parts.b; parts = parts.b) // WHILE_CONS + { + var part = parts.a; + formData.append(part.a, part.b); + } + return formData; +} + +var _Http_bytesToBlob = F2(function(mime, bytes) +{ + return new Blob([bytes], { type: mime }); +}); + + +// PROGRESS + +function _Http_track(router, xhr, tracker) +{ + // TODO check out lengthComputable on loadstart event + + xhr.upload.addEventListener('progress', function(event) { + if (xhr.c) { return; } + _Scheduler_rawSpawn(A2($elm$core$Platform$sendToSelf, router, _Utils_Tuple2(tracker, $elm$http$Http$Sending({ + sent: event.loaded, + size: event.total + })))); + }); + xhr.addEventListener('progress', function(event) { + if (xhr.c) { return; } + _Scheduler_rawSpawn(A2($elm$core$Platform$sendToSelf, router, _Utils_Tuple2(tracker, $elm$http$Http$Receiving({ + received: event.loaded, + size: event.lengthComputable ? $elm$core$Maybe$Just(event.total) : $elm$core$Maybe$Nothing + })))); + }); +} + function _Url_percentEncode(string) { return encodeURIComponent(string); @@ -5167,79 +5342,77 @@ var $elm$core$Task$perform = F2( A2($elm$core$Task$map, toMessage, task))); }); var $elm$browser$Browser$application = _Browser_application; -var $author$project$Main$Model = F2( - function (key, page) { - return {key: key, page: page}; +var $author$project$Main$Model = F4( + function (key, page, authUrls, token) { + return {authUrls: authUrls, key: key, page: page, token: token}; }); var $author$project$Main$NotFound = {$: 'NotFound'}; -var $elm$core$Platform$Cmd$batch = _Platform_batch; -var $elm$core$Platform$Cmd$none = $elm$core$Platform$Cmd$batch(_List_Nil); -var $elm$url$Url$Parser$State = F5( - function (visited, unvisited, params, frag, value) { - return {frag: frag, params: params, unvisited: unvisited, value: value, visited: visited}; +var $author$project$Main$GotAuthUrls = function (a) { + return {$: 'GotAuthUrls', a: a}; +}; +var $elm$json$Json$Decode$field = _Json_decodeField; +var $elm$json$Json$Decode$list = _Json_decodeList; +var $author$project$Main$Url = F2( + function (url, typeUrl) { + return {typeUrl: typeUrl, url: url}; }); -var $elm$url$Url$Parser$getFirstMatch = function (states) { - getFirstMatch: - while (true) { - if (!states.b) { - return $elm$core$Maybe$Nothing; - } else { - var state = states.a; - var rest = states.b; - var _v1 = state.unvisited; - if (!_v1.b) { - return $elm$core$Maybe$Just(state.value); - } else { - if ((_v1.a === '') && (!_v1.b.b)) { - return $elm$core$Maybe$Just(state.value); - } else { - var $temp$states = rest; - states = $temp$states; - continue getFirstMatch; - } - } +var $elm$json$Json$Decode$string = _Json_decodeString; +var $author$project$Main$Github = {$: 'Github'}; +var $author$project$Main$Google = {$: 'Google'}; +var $elm$json$Json$Decode$andThen = _Json_andThen; +var $elm$json$Json$Decode$fail = _Json_fail; +var $author$project$Main$urlTypeDecoder = A2( + $elm$json$Json$Decode$andThen, + function (str) { + switch (str) { + case 'google': + return $elm$json$Json$Decode$succeed($author$project$Main$Google); + case 'github': + return $elm$json$Json$Decode$succeed($author$project$Main$Github); + default: + return $elm$json$Json$Decode$fail('unkown type url'); } - } + }, + $elm$json$Json$Decode$string); +var $author$project$Main$urlDecoder = A3( + $elm$json$Json$Decode$map2, + $author$project$Main$Url, + A2($elm$json$Json$Decode$field, 'url', $elm$json$Json$Decode$string), + A2($elm$json$Json$Decode$field, 'type', $author$project$Main$urlTypeDecoder)); +var $author$project$Main$authUrlsDecoder = A2( + $elm$json$Json$Decode$field, + 'data', + $elm$json$Json$Decode$list($author$project$Main$urlDecoder)); +var $elm$json$Json$Decode$decodeString = _Json_runOnString; +var $elm$http$Http$BadStatus_ = F2( + function (a, b) { + return {$: 'BadStatus_', a: a, b: b}; + }); +var $elm$http$Http$BadUrl_ = function (a) { + return {$: 'BadUrl_', a: a}; }; -var $elm$url$Url$Parser$removeFinalEmpty = function (segments) { - if (!segments.b) { - return _List_Nil; - } else { - if ((segments.a === '') && (!segments.b.b)) { - return _List_Nil; - } else { - var segment = segments.a; - var rest = segments.b; - return A2( - $elm$core$List$cons, - segment, - $elm$url$Url$Parser$removeFinalEmpty(rest)); - } - } +var $elm$http$Http$GoodStatus_ = F2( + function (a, b) { + return {$: 'GoodStatus_', a: a, b: b}; + }); +var $elm$http$Http$NetworkError_ = {$: 'NetworkError_'}; +var $elm$http$Http$Receiving = function (a) { + return {$: 'Receiving', a: a}; }; -var $elm$url$Url$Parser$preparePath = function (path) { - var _v0 = A2($elm$core$String$split, '/', path); - if (_v0.b && (_v0.a === '')) { - var segments = _v0.b; - return $elm$url$Url$Parser$removeFinalEmpty(segments); +var $elm$http$Http$Sending = function (a) { + return {$: 'Sending', a: a}; +}; +var $elm$http$Http$Timeout_ = {$: 'Timeout_'}; +var $elm$core$Dict$RBEmpty_elm_builtin = {$: 'RBEmpty_elm_builtin'}; +var $elm$core$Dict$empty = $elm$core$Dict$RBEmpty_elm_builtin; +var $elm$core$Maybe$isJust = function (maybe) { + if (maybe.$ === 'Just') { + return true; } else { - var segments = _v0; - return $elm$url$Url$Parser$removeFinalEmpty(segments); + return false; } }; -var $elm$url$Url$Parser$addToParametersHelp = F2( - function (value, maybeList) { - if (maybeList.$ === 'Nothing') { - return $elm$core$Maybe$Just( - _List_fromArray( - [value])); - } else { - var list = maybeList.a; - return $elm$core$Maybe$Just( - A2($elm$core$List$cons, value, list)); - } - }); -var $elm$url$Url$percentDecode = _Url_percentDecode; +var $elm$core$Platform$sendToSelf = _Platform_sendToSelf; var $elm$core$Basics$compare = _Utils_compare; var $elm$core$Dict$get = F2( function (targetKey, dict) { @@ -5277,7 +5450,6 @@ var $elm$core$Dict$RBNode_elm_builtin = F5( function (a, b, c, d, e) { return {$: 'RBNode_elm_builtin', a: a, b: b, c: c, d: d, e: e}; }); -var $elm$core$Dict$RBEmpty_elm_builtin = {$: 'RBEmpty_elm_builtin'}; var $elm$core$Dict$Red = {$: 'Red'}; var $elm$core$Dict$balance = F5( function (color, key, value, left, right) { @@ -5754,6 +5926,323 @@ var $elm$core$Dict$update = F3( return A2($elm$core$Dict$remove, targetKey, dictionary); } }); +var $elm$core$Basics$composeR = F3( + function (f, g, x) { + return g( + f(x)); + }); +var $elm$http$Http$expectStringResponse = F2( + function (toMsg, toResult) { + return A3( + _Http_expect, + '', + $elm$core$Basics$identity, + A2($elm$core$Basics$composeR, toResult, toMsg)); + }); +var $elm$core$Result$mapError = F2( + function (f, result) { + if (result.$ === 'Ok') { + var v = result.a; + return $elm$core$Result$Ok(v); + } else { + var e = result.a; + return $elm$core$Result$Err( + f(e)); + } + }); +var $elm$http$Http$BadBody = function (a) { + return {$: 'BadBody', a: a}; +}; +var $elm$http$Http$BadStatus = function (a) { + return {$: 'BadStatus', a: a}; +}; +var $elm$http$Http$BadUrl = function (a) { + return {$: 'BadUrl', a: a}; +}; +var $elm$http$Http$NetworkError = {$: 'NetworkError'}; +var $elm$http$Http$Timeout = {$: 'Timeout'}; +var $elm$http$Http$resolve = F2( + function (toResult, response) { + switch (response.$) { + case 'BadUrl_': + var url = response.a; + return $elm$core$Result$Err( + $elm$http$Http$BadUrl(url)); + case 'Timeout_': + return $elm$core$Result$Err($elm$http$Http$Timeout); + case 'NetworkError_': + return $elm$core$Result$Err($elm$http$Http$NetworkError); + case 'BadStatus_': + var metadata = response.a; + return $elm$core$Result$Err( + $elm$http$Http$BadStatus(metadata.statusCode)); + default: + var body = response.b; + return A2( + $elm$core$Result$mapError, + $elm$http$Http$BadBody, + toResult(body)); + } + }); +var $elm$http$Http$expectJson = F2( + function (toMsg, decoder) { + return A2( + $elm$http$Http$expectStringResponse, + toMsg, + $elm$http$Http$resolve( + function (string) { + return A2( + $elm$core$Result$mapError, + $elm$json$Json$Decode$errorToString, + A2($elm$json$Json$Decode$decodeString, decoder, string)); + })); + }); +var $elm$http$Http$emptyBody = _Http_emptyBody; +var $elm$http$Http$Request = function (a) { + return {$: 'Request', a: a}; +}; +var $elm$http$Http$State = F2( + function (reqs, subs) { + return {reqs: reqs, subs: subs}; + }); +var $elm$http$Http$init = $elm$core$Task$succeed( + A2($elm$http$Http$State, $elm$core$Dict$empty, _List_Nil)); +var $elm$core$Process$kill = _Scheduler_kill; +var $elm$core$Process$spawn = _Scheduler_spawn; +var $elm$http$Http$updateReqs = F3( + function (router, cmds, reqs) { + updateReqs: + while (true) { + if (!cmds.b) { + return $elm$core$Task$succeed(reqs); + } else { + var cmd = cmds.a; + var otherCmds = cmds.b; + if (cmd.$ === 'Cancel') { + var tracker = cmd.a; + var _v2 = A2($elm$core$Dict$get, tracker, reqs); + if (_v2.$ === 'Nothing') { + var $temp$router = router, + $temp$cmds = otherCmds, + $temp$reqs = reqs; + router = $temp$router; + cmds = $temp$cmds; + reqs = $temp$reqs; + continue updateReqs; + } else { + var pid = _v2.a; + return A2( + $elm$core$Task$andThen, + function (_v3) { + return A3( + $elm$http$Http$updateReqs, + router, + otherCmds, + A2($elm$core$Dict$remove, tracker, reqs)); + }, + $elm$core$Process$kill(pid)); + } + } else { + var req = cmd.a; + return A2( + $elm$core$Task$andThen, + function (pid) { + var _v4 = req.tracker; + if (_v4.$ === 'Nothing') { + return A3($elm$http$Http$updateReqs, router, otherCmds, reqs); + } else { + var tracker = _v4.a; + return A3( + $elm$http$Http$updateReqs, + router, + otherCmds, + A3($elm$core$Dict$insert, tracker, pid, reqs)); + } + }, + $elm$core$Process$spawn( + A3( + _Http_toTask, + router, + $elm$core$Platform$sendToApp(router), + req))); + } + } + } + }); +var $elm$http$Http$onEffects = F4( + function (router, cmds, subs, state) { + return A2( + $elm$core$Task$andThen, + function (reqs) { + return $elm$core$Task$succeed( + A2($elm$http$Http$State, reqs, subs)); + }, + A3($elm$http$Http$updateReqs, router, cmds, state.reqs)); + }); +var $elm$core$List$maybeCons = F3( + function (f, mx, xs) { + var _v0 = f(mx); + if (_v0.$ === 'Just') { + var x = _v0.a; + return A2($elm$core$List$cons, x, xs); + } else { + return xs; + } + }); +var $elm$core$List$filterMap = F2( + function (f, xs) { + return A3( + $elm$core$List$foldr, + $elm$core$List$maybeCons(f), + _List_Nil, + xs); + }); +var $elm$http$Http$maybeSend = F4( + function (router, desiredTracker, progress, _v0) { + var actualTracker = _v0.a; + var toMsg = _v0.b; + return _Utils_eq(desiredTracker, actualTracker) ? $elm$core$Maybe$Just( + A2( + $elm$core$Platform$sendToApp, + router, + toMsg(progress))) : $elm$core$Maybe$Nothing; + }); +var $elm$http$Http$onSelfMsg = F3( + function (router, _v0, state) { + var tracker = _v0.a; + var progress = _v0.b; + return A2( + $elm$core$Task$andThen, + function (_v1) { + return $elm$core$Task$succeed(state); + }, + $elm$core$Task$sequence( + A2( + $elm$core$List$filterMap, + A3($elm$http$Http$maybeSend, router, tracker, progress), + state.subs))); + }); +var $elm$http$Http$Cancel = function (a) { + return {$: 'Cancel', a: a}; +}; +var $elm$http$Http$cmdMap = F2( + function (func, cmd) { + if (cmd.$ === 'Cancel') { + var tracker = cmd.a; + return $elm$http$Http$Cancel(tracker); + } else { + var r = cmd.a; + return $elm$http$Http$Request( + { + allowCookiesFromOtherDomains: r.allowCookiesFromOtherDomains, + body: r.body, + expect: A2(_Http_mapExpect, func, r.expect), + headers: r.headers, + method: r.method, + timeout: r.timeout, + tracker: r.tracker, + url: r.url + }); + } + }); +var $elm$http$Http$MySub = F2( + function (a, b) { + return {$: 'MySub', a: a, b: b}; + }); +var $elm$http$Http$subMap = F2( + function (func, _v0) { + var tracker = _v0.a; + var toMsg = _v0.b; + return A2( + $elm$http$Http$MySub, + tracker, + A2($elm$core$Basics$composeR, toMsg, func)); + }); +_Platform_effectManagers['Http'] = _Platform_createManager($elm$http$Http$init, $elm$http$Http$onEffects, $elm$http$Http$onSelfMsg, $elm$http$Http$cmdMap, $elm$http$Http$subMap); +var $elm$http$Http$command = _Platform_leaf('Http'); +var $elm$http$Http$subscription = _Platform_leaf('Http'); +var $elm$http$Http$request = function (r) { + return $elm$http$Http$command( + $elm$http$Http$Request( + {allowCookiesFromOtherDomains: false, body: r.body, expect: r.expect, headers: r.headers, method: r.method, timeout: r.timeout, tracker: r.tracker, url: r.url})); +}; +var $elm$http$Http$get = function (r) { + return $elm$http$Http$request( + {body: $elm$http$Http$emptyBody, expect: r.expect, headers: _List_Nil, method: 'GET', timeout: $elm$core$Maybe$Nothing, tracker: $elm$core$Maybe$Nothing, url: r.url}); +}; +var $author$project$Main$getAuthUrls = $elm$http$Http$get( + { + expect: A2($elm$http$Http$expectJson, $author$project$Main$GotAuthUrls, $author$project$Main$authUrlsDecoder), + url: 'https://appapispike.herokuapp.com/api/auth/urls' + }); +var $elm$core$Platform$Cmd$batch = _Platform_batch; +var $elm$core$Platform$Cmd$none = $elm$core$Platform$Cmd$batch(_List_Nil); +var $elm$url$Url$Parser$State = F5( + function (visited, unvisited, params, frag, value) { + return {frag: frag, params: params, unvisited: unvisited, value: value, visited: visited}; + }); +var $elm$url$Url$Parser$getFirstMatch = function (states) { + getFirstMatch: + while (true) { + if (!states.b) { + return $elm$core$Maybe$Nothing; + } else { + var state = states.a; + var rest = states.b; + var _v1 = state.unvisited; + if (!_v1.b) { + return $elm$core$Maybe$Just(state.value); + } else { + if ((_v1.a === '') && (!_v1.b.b)) { + return $elm$core$Maybe$Just(state.value); + } else { + var $temp$states = rest; + states = $temp$states; + continue getFirstMatch; + } + } + } + } +}; +var $elm$url$Url$Parser$removeFinalEmpty = function (segments) { + if (!segments.b) { + return _List_Nil; + } else { + if ((segments.a === '') && (!segments.b.b)) { + return _List_Nil; + } else { + var segment = segments.a; + var rest = segments.b; + return A2( + $elm$core$List$cons, + segment, + $elm$url$Url$Parser$removeFinalEmpty(rest)); + } + } +}; +var $elm$url$Url$Parser$preparePath = function (path) { + var _v0 = A2($elm$core$String$split, '/', path); + if (_v0.b && (_v0.a === '')) { + var segments = _v0.b; + return $elm$url$Url$Parser$removeFinalEmpty(segments); + } else { + var segments = _v0; + return $elm$url$Url$Parser$removeFinalEmpty(segments); + } +}; +var $elm$url$Url$Parser$addToParametersHelp = F2( + function (value, maybeList) { + if (maybeList.$ === 'Nothing') { + return $elm$core$Maybe$Just( + _List_fromArray( + [value])); + } else { + var list = maybeList.a; + return $elm$core$Maybe$Just( + A2($elm$core$List$cons, value, list)); + } + }); +var $elm$url$Url$percentDecode = _Url_percentDecode; var $elm$url$Url$Parser$addParam = F2( function (segment, dict) { var _v0 = A2($elm$core$String$split, '=', segment); @@ -5782,7 +6271,6 @@ var $elm$url$Url$Parser$addParam = F2( return dict; } }); -var $elm$core$Dict$empty = $elm$core$Dict$RBEmpty_elm_builtin; var $elm$url$Url$Parser$prepareQuery = function (maybeQuery) { if (maybeQuery.$ === 'Nothing') { return $elm$core$Dict$empty; @@ -5808,7 +6296,10 @@ var $elm$url$Url$Parser$parse = F2( url.fragment, $elm$core$Basics$identity))); }); -var $author$project$Main$Auth = {$: 'Auth'}; +var $elm$browser$Browser$Navigation$pushUrl = _Browser_pushUrl; +var $author$project$Main$Auth = function (a) { + return {$: 'Auth', a: a}; +}; var $author$project$Main$Home = {$: 'Home'}; var $elm$url$Url$Parser$Parser = function (a) { return {$: 'Parser', a: a}; @@ -5873,6 +6364,47 @@ var $elm$url$Url$Parser$oneOf = function (parsers) { parsers); }); }; +var $elm$url$Url$Parser$query = function (_v0) { + var queryParser = _v0.a; + return $elm$url$Url$Parser$Parser( + function (_v1) { + var visited = _v1.visited; + var unvisited = _v1.unvisited; + var params = _v1.params; + var frag = _v1.frag; + var value = _v1.value; + return _List_fromArray( + [ + A5( + $elm$url$Url$Parser$State, + visited, + unvisited, + params, + frag, + value( + queryParser(params))) + ]); + }); +}; +var $elm$url$Url$Parser$slash = F2( + function (_v0, _v1) { + var parseBefore = _v0.a; + var parseAfter = _v1.a; + return $elm$url$Url$Parser$Parser( + function (state) { + return A2( + $elm$core$List$concatMap, + parseAfter, + parseBefore(state)); + }); + }); +var $elm$url$Url$Parser$questionMark = F2( + function (parser, queryParser) { + return A2( + $elm$url$Url$Parser$slash, + parser, + $elm$url$Url$Parser$query(queryParser)); + }); var $elm$url$Url$Parser$s = function (str) { return $elm$url$Url$Parser$Parser( function (_v0) { @@ -5899,6 +6431,42 @@ var $elm$url$Url$Parser$s = function (str) { } }); }; +var $elm$url$Url$Parser$Internal$Parser = function (a) { + return {$: 'Parser', a: a}; +}; +var $elm$core$Maybe$withDefault = F2( + function (_default, maybe) { + if (maybe.$ === 'Just') { + var value = maybe.a; + return value; + } else { + return _default; + } + }); +var $elm$url$Url$Parser$Query$custom = F2( + function (key, func) { + return $elm$url$Url$Parser$Internal$Parser( + function (dict) { + return func( + A2( + $elm$core$Maybe$withDefault, + _List_Nil, + A2($elm$core$Dict$get, key, dict))); + }); + }); +var $elm$url$Url$Parser$Query$string = function (key) { + return A2( + $elm$url$Url$Parser$Query$custom, + key, + function (stringList) { + if (stringList.b && (!stringList.b.b)) { + var str = stringList.a; + return $elm$core$Maybe$Just(str); + } else { + return $elm$core$Maybe$Nothing; + } + }); +}; var $elm$url$Url$Parser$top = $elm$url$Url$Parser$Parser( function (state) { return _List_fromArray( @@ -5911,18 +6479,39 @@ var $author$project$Main$routeParser = $elm$url$Url$Parser$oneOf( A2( $elm$url$Url$Parser$map, $author$project$Main$Auth, - $elm$url$Url$Parser$s('auth')) + A2( + $elm$url$Url$Parser$questionMark, + $elm$url$Url$Parser$s('auth'), + $elm$url$Url$Parser$Query$string('jwt'))) ])); var $author$project$Main$parseUrl = F2( function (url, model) { var _v0 = A2($elm$url$Url$Parser$parse, $author$project$Main$routeParser, url); if (_v0.$ === 'Just') { var page = _v0.a; - return _Utils_Tuple2( - _Utils_update( - model, - {page: page}), - $elm$core$Platform$Cmd$none); + if (page.$ === 'Auth') { + if (page.a.$ === 'Nothing') { + var _v2 = page.a; + return _Utils_Tuple2( + _Utils_update( + model, + {page: page}), + $author$project$Main$getAuthUrls); + } else { + var jwt = page.a.a; + return _Utils_Tuple2( + _Utils_update( + model, + {token: jwt}), + A2($elm$browser$Browser$Navigation$pushUrl, model.key, '/')); + } + } else { + return _Utils_Tuple2( + _Utils_update( + model, + {page: page}), + $elm$core$Platform$Cmd$none); + } } else { return _Utils_Tuple2( _Utils_update( @@ -5936,7 +6525,7 @@ var $author$project$Main$init = F3( return A2( $author$project$Main$parseUrl, url, - A2($author$project$Main$Model, key, $author$project$Main$NotFound)); + A4($author$project$Main$Model, key, $author$project$Main$NotFound, _List_Nil, '')); }); var $elm$core$Platform$Sub$batch = _Platform_batch; var $elm$core$Platform$Sub$none = $elm$core$Platform$Sub$batch(_List_Nil); @@ -5944,7 +6533,6 @@ var $author$project$Main$subscriptions = function (_v0) { return $elm$core$Platform$Sub$none; }; var $elm$browser$Browser$Navigation$load = _Browser_load; -var $elm$browser$Browser$Navigation$pushUrl = _Browser_pushUrl; var $elm$url$Url$addPort = F2( function (maybePort, starter) { if (maybePort.$ === 'Nothing') { @@ -5991,25 +6579,38 @@ var $elm$url$Url$toString = function (url) { }; var $author$project$Main$update = F2( function (msg, model) { - if (msg.$ === 'LinkClicked') { - var link = msg.a; - if (link.$ === 'Internal') { - var urlRequested = link.a; - return _Utils_Tuple2( - model, - A2( - $elm$browser$Browser$Navigation$pushUrl, - model.key, - $elm$url$Url$toString(urlRequested))); - } else { - var href = link.a; - return _Utils_Tuple2( - model, - $elm$browser$Browser$Navigation$load(href)); - } - } else { - var url = msg.a; - return A2($author$project$Main$parseUrl, url, model); + switch (msg.$) { + case 'LinkClicked': + var link = msg.a; + if (link.$ === 'Internal') { + var urlRequested = link.a; + return _Utils_Tuple2( + model, + A2( + $elm$browser$Browser$Navigation$pushUrl, + model.key, + $elm$url$Url$toString(urlRequested))); + } else { + var href = link.a; + return _Utils_Tuple2( + model, + $elm$browser$Browser$Navigation$load(href)); + } + case 'UrlChanged': + var url = msg.a; + return A2($author$project$Main$parseUrl, url, model); + default: + var result = msg.a; + if (result.$ === 'Ok') { + var urls = result.a; + return _Utils_Tuple2( + _Utils_update( + model, + {authUrls: urls}), + $elm$core$Platform$Cmd$none); + } else { + return _Utils_Tuple2(model, $elm$core$Platform$Cmd$none); + } } }); var $elm$html$Html$a = _VirtualDom_node('a'); @@ -6022,6 +6623,7 @@ var $elm$html$Html$Attributes$stringProperty = F2( $elm$json$Json$Encode$string(string)); }); var $elm$html$Html$Attributes$class = $elm$html$Html$Attributes$stringProperty('className'); +var $elm$html$Html$div = _VirtualDom_node('div'); var $elm$html$Html$h1 = _VirtualDom_node('h1'); var $elm$html$Html$Attributes$href = function (url) { return A2( @@ -6037,6 +6639,8 @@ var $author$project$Asset$image = function (filename) { return $author$project$Asset$Image('/assets/images/' + filename); }; var $author$project$Asset$logo = $author$project$Asset$image('dwyl.png'); +var $author$project$Asset$signinGithub = $author$project$Asset$image('signin-github.png'); +var $author$project$Asset$signinGoogle = $author$project$Asset$image('signin-google.png'); var $elm$html$Html$Attributes$src = function (url) { return A2( $elm$html$Html$Attributes$stringProperty, @@ -6047,6 +6651,40 @@ var $author$project$Asset$src = function (_v0) { var url = _v0.a; return $elm$html$Html$Attributes$src(url); }; +var $author$project$Main$showAuthUrl = function (url) { + var imgSrc = function () { + var _v0 = url.typeUrl; + if (_v0.$ === 'Google') { + return $author$project$Asset$src($author$project$Asset$signinGoogle); + } else { + return $author$project$Asset$src($author$project$Asset$signinGithub); + } + }(); + return A2( + $elm$html$Html$div, + _List_fromArray( + [ + $elm$html$Html$Attributes$class('tc pa2') + ]), + _List_fromArray( + [ + A2( + $elm$html$Html$a, + _List_fromArray( + [ + $elm$html$Html$Attributes$href(url.url) + ]), + _List_fromArray( + [ + A2( + $elm$html$Html$img, + _List_fromArray( + [imgSrc]), + _List_Nil) + ])) + ])); +}; +var $elm$html$Html$span = _VirtualDom_node('span'); var $elm$virtual_dom$VirtualDom$text = _VirtualDom_text; var $elm$html$Html$text = $elm$virtual_dom$VirtualDom$text; var $author$project$Main$view = function (model) { @@ -6084,7 +6722,7 @@ var $author$project$Main$view = function (model) { [ $elm$html$Html$text('Dwyl application') ])), - A2( + $elm$core$String$isEmpty(model.token) ? A2( $elm$html$Html$a, _List_fromArray( [ @@ -6094,6 +6732,15 @@ var $author$project$Main$view = function (model) { _List_fromArray( [ $elm$html$Html$text('login/signup') + ])) : A2( + $elm$html$Html$span, + _List_fromArray( + [ + $elm$html$Html$Attributes$class('tc db') + ]), + _List_fromArray( + [ + $elm$html$Html$text('logged in with token: ' + model.token) ])) ]); case 'Auth': @@ -6117,15 +6764,14 @@ var $author$project$Main$view = function (model) { _List_Nil) ])), A2( - $elm$html$Html$h1, - _List_fromArray( - [ - $elm$html$Html$Attributes$class('tc') - ]), - _List_fromArray( - [ - $elm$html$Html$text('login page') - ])) + $elm$html$Html$div, + _List_Nil, + A2( + $elm$core$List$map, + function (url) { + return $author$project$Main$showAuthUrl(url); + }, + model.authUrls)) ]); default: return _List_fromArray( diff --git a/elm.json b/elm.json index c069c75..019388f 100644 --- a/elm.json +++ b/elm.json @@ -9,10 +9,13 @@ "elm/browser": "1.0.2", "elm/core": "1.0.4", "elm/html": "1.0.0", + "elm/http": "2.0.0", + "elm/json": "1.1.3", "elm/url": "1.0.0" }, "indirect": { - "elm/json": "1.1.3", + "elm/bytes": "1.0.8", + "elm/file": "1.0.5", "elm/time": "1.0.0", "elm/virtual-dom": "1.0.2" } diff --git a/src/Asset.elm b/src/Asset.elm index ad6bf60..e1674c6 100644 --- a/src/Asset.elm +++ b/src/Asset.elm @@ -1,4 +1,4 @@ -module Asset exposing (Image, logo, src) +module Asset exposing (Image, logo, signinGithub, signinGoogle, src) import Html exposing (Attribute) import Html.Attributes as Attr @@ -13,6 +13,16 @@ logo = image "dwyl.png" +signinGoogle : Image +signinGoogle = + image "signin-google.png" + + +signinGithub : Image +signinGithub = + image "signin-github.png" + + image : String -> Image image filename = Image ("/assets/images/" ++ filename) diff --git a/src/Main.elm b/src/Main.elm index 96c4c4b..9f2cb71 100644 --- a/src/Main.elm +++ b/src/Main.elm @@ -5,8 +5,11 @@ import Browser import Browser.Navigation as Nav import Html exposing (..) import Html.Attributes exposing (..) +import Http +import Json.Decode as JD import Url -import Url.Parser as Parser +import Url.Parser as Parser exposing (()) +import Url.Parser.Query as Query main : Program () Model Msg @@ -27,19 +30,21 @@ main = type Page = Home - | Auth + | Auth (Maybe String) | NotFound type alias Model = { key : Nav.Key , page : Page + , authUrls : List Url + , token : String } init : () -> Url.Url -> Nav.Key -> ( Model, Cmd Msg ) init _ url key = - parseUrl url (Model key NotFound) + parseUrl url (Model key NotFound [] "") @@ -49,6 +54,7 @@ init _ url key = type Msg = UrlChanged Url.Url | LinkClicked Browser.UrlRequest + | GotAuthUrls (Result Http.Error (List Url)) update : Msg -> Model -> ( Model, Cmd Msg ) @@ -65,12 +71,20 @@ update msg model = UrlChanged url -> parseUrl url model + GotAuthUrls result -> + case result of + Ok urls -> + ( { model | authUrls = urls }, Cmd.none ) + + Err _ -> + ( model, Cmd.none ) + routeParser : Parser.Parser (Page -> a) a routeParser = Parser.oneOf [ Parser.map Home Parser.top - , Parser.map Auth (Parser.s "auth") + , Parser.map Auth (Parser.s "auth" Query.string "jwt") ] @@ -78,7 +92,16 @@ parseUrl : Url.Url -> Model -> ( Model, Cmd Msg ) parseUrl url model = case Parser.parse routeParser url of Just page -> - ( { model | page = page }, Cmd.none ) + case page of + Auth Nothing -> + ( { model | page = page }, getAuthUrls ) + + Auth (Just jwt) -> + ( { model | token = jwt }, Nav.pushUrl model.key "/" ) + + -- redirect to home page + _ -> + ( { model | page = page }, Cmd.none ) Nothing -> ( { model | page = NotFound }, Cmd.none ) @@ -97,12 +120,16 @@ view model = Home -> [ a [ href "/" ] [ img [ Asset.src Asset.logo, class "center db pt2" ] [] ] , h1 [ class "tc" ] [ text "Dwyl application" ] - , a [ href "/auth", class "tc db" ] [ text "login/signup" ] + , if String.isEmpty model.token then + a [ href "/auth", class "tc db" ] [ text "login/signup" ] + + else + span [ class "tc db" ] [ text <| "logged in with token: " ++ model.token ] ] - Auth -> + Auth _ -> [ a [ href "/" ] [ img [ Asset.src Asset.logo, class "center db pt2" ] [] ] - , h1 [ class "tc" ] [ text "login page" ] + , div [] <| List.map (\url -> showAuthUrl url) model.authUrls ] NotFound -> @@ -110,3 +137,72 @@ view model = , h1 [ class "tc" ] [ text "page not found" ] ] } + + + +-- OAuth urls +-- convert oauth login urls (github, google) to Elm Url type + + +type alias Url = + { url : String + , typeUrl : TypeUrl + } + + +type TypeUrl + = Google + | Github + + +getAuthUrls : Cmd Msg +getAuthUrls = + Http.get + { url = "https://appapispike.herokuapp.com/api/auth/urls" + , expect = Http.expectJson GotAuthUrls authUrlsDecoder + } + + +authUrlsDecoder : JD.Decoder (List Url) +authUrlsDecoder = + JD.field "data" (JD.list urlDecoder) + + +urlDecoder : JD.Decoder Url +urlDecoder = + JD.map2 Url + (JD.field "url" JD.string) + (JD.field "type" urlTypeDecoder) + + +urlTypeDecoder : JD.Decoder TypeUrl +urlTypeDecoder = + JD.string + |> JD.andThen + (\str -> + case str of + "google" -> + JD.succeed Google + + "github" -> + JD.succeed Github + + _ -> + JD.fail "unkown type url" + ) + + +showAuthUrl : Url -> Html Msg +showAuthUrl url = + let + imgSrc = + case url.typeUrl of + Google -> + Asset.src Asset.signinGoogle + + Github -> + Asset.src Asset.signinGithub + in + div [ class "tc pa2" ] + [ a [ href url.url ] [ img [ imgSrc ] [] ] + ] diff --git a/tests/MainTests.elm b/tests/MainTests.elm index fc9c467..d52507d 100644 --- a/tests/MainTests.elm +++ b/tests/MainTests.elm @@ -44,7 +44,24 @@ suite = Maybe.withDefault defaultUrl (Url.fromString "http://locahost/auth") in Parser.parse routeParser url - |> Expect.equal (Just Auth) + |> Expect.equal (Just (Auth Nothing)) + , test "Test auth page with jwt" <| + \_ -> + let + defaultUrl = + { protocol = Url.Https + , host = "dwyl.com" + , port_ = Just 443 + , path = "/" + , query = Nothing + , fragment = Nothing + } + + url = + Maybe.withDefault defaultUrl (Url.fromString "http://locahost/auth?jwt=aaa.bbb.ccc") + in + Parser.parse routeParser url + |> Expect.equal (Just (Auth (Just "aaa.bbb.ccc"))) , test "Test 404 page" <| \_ -> let