| @ -0,0 +1,28 @@ | |||
| { | |||
| "type": "application", | |||
| "source-directories": [ | |||
| "src/assets/scripts/elm" | |||
| ], | |||
| "elm-version": "0.19.0", | |||
| "dependencies": { | |||
| "direct": { | |||
| "arowM/elm-form-decoder": "1.2.0", | |||
| "elm/browser": "1.0.1", | |||
| "elm/core": "1.0.2", | |||
| "elm/html": "1.0.0", | |||
| "elm/http": "2.0.0", | |||
| "elm/json": "1.1.3", | |||
| "elm/url": "1.0.0" | |||
| }, | |||
| "indirect": { | |||
| "elm/bytes": "1.0.8", | |||
| "elm/file": "1.0.5", | |||
| "elm/time": "1.0.0", | |||
| "elm/virtual-dom": "1.0.2" | |||
| } | |||
| }, | |||
| "test-dependencies": { | |||
| "direct": {}, | |||
| "indirect": {} | |||
| } | |||
| } | |||
| @ -0,0 +1,156 @@ | |||
| port module Main exposing (Model, Msg(..), init, main, update, view) | |||
| import Browser exposing (Document, UrlRequest(..)) | |||
| import Browser.Navigation as Nav exposing (Key) | |||
| import Html exposing (Html, a, br, div, h1, img, text) | |||
| import Html.Attributes exposing (class, href, id, src) | |||
| import Html.Events exposing (onClick) | |||
| import Pages.Counselors as Counselors | |||
| import Session exposing (User) | |||
| import Url exposing (Url) | |||
| import Url.Parser as Parser exposing (Parser, map, oneOf, s, top) | |||
| ---- MODEL ---- | |||
| type alias Model = | |||
| { navKey : Key, page : Page, user : Maybe User } | |||
| type Page | |||
| = Dashboard | |||
| | Counselors Counselors.Model | |||
| init : Maybe User -> Url -> Key -> ( Model, Cmd Msg ) | |||
| init user url key = | |||
| ( { navKey = key | |||
| , page = whichPage url | |||
| , user = user | |||
| } | |||
| , Cmd.none | |||
| ) | |||
| routes : Parser (Page -> Page) Page | |||
| routes = | |||
| oneOf | |||
| [ map Dashboard top | |||
| , map (Counselors Counselors.init) (s "counselors") | |||
| ] | |||
| whichPage : Url -> Page | |||
| whichPage url = | |||
| Maybe.withDefault Dashboard (Parser.parse routes url) | |||
| ---- UPDATE ---- | |||
| type Msg | |||
| = ClickedLink UrlRequest | |||
| | ChangedUrl Url | |||
| | CounselorsMsg Counselors.Msg | |||
| | Logout | |||
| | NoOp | |||
| update : Msg -> Model -> ( Model, Cmd Msg ) | |||
| update msg model = | |||
| case ( msg, model.page ) of | |||
| ( ClickedLink req, _ ) -> | |||
| case req of | |||
| Internal url -> | |||
| ( model | |||
| , Nav.pushUrl model.navKey (Url.toString url) | |||
| ) | |||
| External href -> | |||
| ( model, Nav.load href ) | |||
| ( ChangedUrl url, _ ) -> | |||
| ( { model | page = whichPage url }, Cmd.none ) | |||
| ( CounselorsMsg cMsg, Counselors cModel ) -> | |||
| let | |||
| ( cModel_, cCmd ) = | |||
| Counselors.update cMsg cModel | |||
| in | |||
| ( { model | |||
| | page = Counselors cModel_ | |||
| } | |||
| , Cmd.map CounselorsMsg cCmd | |||
| ) | |||
| ( Logout, _ ) -> | |||
| ( { model | user = Nothing }, logout () ) | |||
| _ -> | |||
| ( model, Cmd.none ) | |||
| ---- VIEW ---- | |||
| view : Model -> Document Msg | |||
| view model = | |||
| let | |||
| content = | |||
| case model.page of | |||
| Counselors cModel -> | |||
| Html.map CounselorsMsg Counselors.view | |||
| Dashboard -> | |||
| mainHtml | |||
| in | |||
| { title = "InteroCare Admin" | |||
| , body = | |||
| [ div [] | |||
| [ img [ src "/ALFBLogo.png" ] [] | |||
| , div [ id "firebaseui-auth-container", class "hidden" ] [] | |||
| , content | |||
| ] | |||
| ] | |||
| } | |||
| mainHtml : Html Msg | |||
| mainHtml = | |||
| div | |||
| [] | |||
| [ div [ id "firebaseui-auth-container" ] [] | |||
| , h1 | |||
| [] | |||
| [ text "Dashboard" ] | |||
| , a [ href "/counselors" ] [ text "Counselors" ] | |||
| , br [] [] | |||
| , a [ href "/", onClick Logout ] [ text "Logout" ] | |||
| ] | |||
| ---- PROGRAM ---- | |||
| main : Program (Maybe User) Model Msg | |||
| main = | |||
| Browser.application | |||
| { view = view | |||
| , init = init | |||
| , update = update | |||
| , subscriptions = always Sub.none | |||
| , onUrlRequest = ClickedLink | |||
| , onUrlChange = ChangedUrl | |||
| } | |||
| -- Ports | |||
| port logout : () -> Cmd msg | |||
| @ -0,0 +1,264 @@ | |||
| module Pages.Counselors exposing (Model, Msg, init, update, view) | |||
| import Debug exposing (log) | |||
| import Form.Decoder as Decoder exposing (Decoder) | |||
| import Html | |||
| exposing | |||
| ( Html | |||
| , a | |||
| , br | |||
| , button | |||
| , div | |||
| , fieldset | |||
| , form | |||
| , h1 | |||
| , input | |||
| , label | |||
| , legend | |||
| , p | |||
| , span | |||
| , text | |||
| ) | |||
| import Html.Attributes | |||
| exposing | |||
| ( action | |||
| , class | |||
| , for | |||
| , href | |||
| , method | |||
| , name | |||
| , placeholder | |||
| , type_ | |||
| , value | |||
| ) | |||
| import Html.Events exposing (onClick, onInput, onSubmit) | |||
| import Http | |||
| import Json.Encode as E | |||
| -- MODEL | |||
| type alias Model = | |||
| { form : NewCounselorForm, counselor : Maybe NewCounselor } | |||
| type alias NewCounselorForm = | |||
| { firstName : String | |||
| , lastName : String | |||
| , email : String | |||
| } | |||
| type Msg | |||
| = NewCounselorSubmitted (Result Http.Error ()) | |||
| | NewCounselorFormSubmitted | |||
| | EmailCharEntered String | |||
| | FirstNameCharEntered String | |||
| | LastNameCharEntered String | |||
| type alias NewCounselor = | |||
| { firstName : String | |||
| , lastName : String | |||
| , email : String | |||
| } | |||
| -- UPDATE & INIT | |||
| update : Msg -> Model -> ( Model, Cmd Msg ) | |||
| update msg model = | |||
| case msg of | |||
| NewCounselorSubmitted _ -> | |||
| ( model, Cmd.none ) | |||
| NewCounselorFormSubmitted -> | |||
| let | |||
| ( newCounselor, _ ) = | |||
| case Decoder.run form model.form of | |||
| Ok newCounselor_ -> | |||
| ( Just newCounselor_, Nothing ) | |||
| Err _ -> | |||
| ( Nothing, Nothing ) | |||
| in | |||
| ( { model | counselor = newCounselor }, Cmd.none ) | |||
| FirstNameCharEntered char -> | |||
| let | |||
| cForm : NewCounselorForm | |||
| cForm = | |||
| model.form | |||
| form_ : NewCounselorForm | |||
| form_ = | |||
| { cForm | firstName = String.trim char } | |||
| in | |||
| ( { model | form = form_ }, Cmd.none ) | |||
| LastNameCharEntered char -> | |||
| let | |||
| cForm : NewCounselorForm | |||
| cForm = | |||
| model.form | |||
| form_ : NewCounselorForm | |||
| form_ = | |||
| { cForm | lastName = String.trim char } | |||
| in | |||
| ( { model | form = form_ }, Cmd.none ) | |||
| EmailCharEntered char -> | |||
| let | |||
| cForm : NewCounselorForm | |||
| cForm = | |||
| model.form | |||
| form_ : NewCounselorForm | |||
| form_ = | |||
| { cForm | email = String.trim char } | |||
| in | |||
| ( { model | form = form_ }, Cmd.none ) | |||
| init : Model | |||
| init = | |||
| { form = NewCounselorForm "" "" "", counselor = Nothing } | |||
| -- VIEW | |||
| view : Html Msg | |||
| view = | |||
| div | |||
| [] | |||
| [ h1 [] [ text "Counselors" ] | |||
| , a [ href "/" ] [ text "Dashboard" ] | |||
| , br [] [] | |||
| , Html.form | |||
| [ class "pure-form pure-form-aligned" | |||
| , onSubmit NewCounselorFormSubmitted | |||
| ] | |||
| [ legend | |||
| [] | |||
| [ text "Invite a Counselor" | |||
| , fieldset [] | |||
| [ div | |||
| [ class "pure-control-group" ] | |||
| [ label [ for "first_name" ] [ text "First Name" ] | |||
| , input | |||
| [ type_ "text", placeholder "First Name", name "first_name", onInput FirstNameCharEntered ] | |||
| [] | |||
| ] | |||
| , div | |||
| [ class "pure-control-group" ] | |||
| [ label [ for "last_name" ] [ text "Last Name" ] | |||
| , input | |||
| [ type_ "text", name "last_name", placeholder "Last Name", onInput LastNameCharEntered ] | |||
| [] | |||
| ] | |||
| , div | |||
| [ class "pure-control-group" ] | |||
| [ label [ for "email" ] [ text "Email" ] | |||
| , input | |||
| [ type_ "text", name "email", placeholder "Email", onInput EmailCharEntered ] | |||
| [] | |||
| , div | |||
| [ class "pure-controls" ] | |||
| [ input | |||
| [ type_ "submit" | |||
| , class "pure-button pure-button-primary" | |||
| , value "Submit" | |||
| ] | |||
| [] | |||
| ] | |||
| ] | |||
| ] | |||
| ] | |||
| ] | |||
| ] | |||
| -- Form Handling | |||
| type FormError | |||
| = FirstNameRequired | |||
| | LastNameRequired | |||
| | EmailRequired | |||
| | InvalidEmail | |||
| firstName : Decoder String FormError String | |||
| firstName = | |||
| Decoder.identity | |||
| |> Decoder.assert (Decoder.minLength FirstNameRequired 1) | |||
| lastName : Decoder String FormError String | |||
| lastName = | |||
| Decoder.identity | |||
| |> Decoder.assert (Decoder.minLength LastNameRequired 1) | |||
| email : Decoder String FormError String | |||
| email = | |||
| Decoder.identity | |||
| |> Decoder.assert (Decoder.minLength EmailRequired 1) | |||
| firstName_ : Decoder NewCounselorForm FormError String | |||
| firstName_ = | |||
| Decoder.lift .firstName firstName | |||
| lastName_ : Decoder NewCounselorForm FormError String | |||
| lastName_ = | |||
| Decoder.lift .lastName lastName | |||
| email_ : Decoder NewCounselorForm FormError String | |||
| email_ = | |||
| Decoder.lift .email email | |||
| form : Decoder NewCounselorForm FormError NewCounselor | |||
| form = | |||
| Decoder.top NewCounselor | |||
| |> Decoder.field firstName_ | |||
| |> Decoder.field lastName_ | |||
| |> Decoder.field email_ | |||
| toNewCounselor : NewCounselorForm -> Result (List FormError) NewCounselor | |||
| toNewCounselor theForm = | |||
| Ok <| NewCounselor "" "" "" | |||
| -- API | |||
| encodeNewCounselor : NewCounselor -> E.Value | |||
| encodeNewCounselor counselor = | |||
| E.object | |||
| [ ( "firstName", E.string counselor.firstName ) | |||
| , ( "lastName", E.string counselor.lastName ) | |||
| , ( "email", E.string counselor.email ) | |||
| ] | |||
| submitNewCounselor : NewCounselor -> Cmd Msg | |||
| submitNewCounselor counselor = | |||
| Http.post | |||
| { url = "/counselors" | |||
| , body = Http.jsonBody (encodeNewCounselor counselor) | |||
| , expect = Http.expectWhatever NewCounselorSubmitted | |||
| } | |||
| @ -0,0 +1,5 @@ | |||
| module Pages.Login exposing (Model) | |||
| type alias Model = | |||
| {} | |||
| @ -0,0 +1,9 @@ | |||
| module Session exposing (User) | |||
| type alias User = | |||
| { email : String | |||
| , photoUrl : String | |||
| , orgId : Maybe String | |||
| , accessToken : String | |||
| } | |||
| @ -0,0 +1,9 @@ | |||
| module.exports = { | |||
| apiKey: "AIzaSyDaihAI-PZdy0lNSsIhzciKWWy2tYOIUVE", | |||
| authDomain: "dev-interocare.firebaseapp.com", | |||
| databaseURL: "https://dev-interocare.firebaseio.com", | |||
| projectId: "dev-interocare", | |||
| storageBucket: "dev-interocare.appspot.com", | |||
| messagingSenderId: "820163062176", | |||
| appId: "1:820163062176:web:33ea3278ea9166900c1cc5" | |||
| }; | |||
| @ -0,0 +1,62 @@ | |||
| import { Elm } from './Main.elm'; | |||
| export default (function () { | |||
| var firebaseConfig = require('./firebaseConfig'); | |||
| const firebase = require('firebase/app'); | |||
| // Initialize Firebase | |||
| firebase.initializeApp(firebaseConfig); | |||
| const firebaseui = require('firebaseui'); | |||
| const ui = new firebaseui.auth.AuthUI(firebase.auth()); | |||
| firebase.auth().onAuthStateChanged(function (user) { | |||
| if (user) { | |||
| Promise.all([user.getIdToken(), user.getIdTokenResult()]).then(([accessToken, idToken]) => { | |||
| const app = Elm.Main.init({ | |||
| flags: { | |||
| email: user.email, | |||
| photoUrl: user.photoURL, | |||
| orgId: idToken.claims.orgId || null, | |||
| accessToken: accessToken | |||
| } | |||
| }); | |||
| app.ports.logout.subscribe(() => { | |||
| firebase.auth().signOut().then(() => { | |||
| window.location.reload(); | |||
| }); | |||
| }); | |||
| }); | |||
| } else { | |||
| initLoginForm(); | |||
| } | |||
| }); | |||
| const initLoginForm = () => { | |||
| const uiConfig = { | |||
| callbacks: { | |||
| signInSuccessWithAuthResult: function (authResult, redirectUrl) { | |||
| // User successfully signed in. | |||
| // Return type determines whether we continue the redirect automatically | |||
| // or whether we leave that to developer to handle. | |||
| return true; | |||
| } | |||
| }, | |||
| // Will use popup for IDP Providers sign-in flow instead of the default, redirect. | |||
| // signInFlow: 'popup', | |||
| signInSuccessUrl: window.location.href, | |||
| signInOptions: [ | |||
| // Leave the lines as is for the providers you want to offer your users. | |||
| firebase.auth.GoogleAuthProvider.PROVIDER_ID | |||
| ], | |||
| // Terms of service url. | |||
| tosUrl: '<your-tos-url>', | |||
| // Privacy policy url. | |||
| privacyPolicyUrl: '<your-privacy-policy-url>' | |||
| }; | |||
| ui.start('#firebaseui-auth-container', uiConfig); | |||
| } | |||
| }()); | |||
| @ -1,17 +1,3 @@ | |||
| import '../styles/index.scss'; | |||
| import './masonry'; | |||
| import './charts'; | |||
| import './popover'; | |||
| import './scrollbar'; | |||
| import './search'; | |||
| import './sidebar'; | |||
| import './skycons'; | |||
| import './vectorMaps'; | |||
| import './chat'; | |||
| import './datatable'; | |||
| import './datepicker'; | |||
| import './email'; | |||
| import './fullcalendar'; | |||
| import './googleMaps'; | |||
| import './utils'; | |||
| import './elm'; | |||
| @ -0,0 +1,8 @@ | |||
| module.exports = { | |||
| test: /\.elm$/, | |||
| exclude: [/elm-stuff/, /node_modules/], | |||
| use: { | |||
| loader: 'elm-webpack-loader', | |||
| options: {} | |||
| } | |||
| }; | |||