From 61a680ac9f79b16d86512adb9d220243c1781fef Mon Sep 17 00:00:00 2001 From: BrunoMartins11 Date: Sat, 17 Oct 2020 15:42:55 +0100 Subject: [PATCH 1/5] Update develop branch on CI (#4) --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 2e45264..9a1f51b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -2,7 +2,7 @@ on: push: pull_request: branches: - - main + - dev jobs: test: From fbc3ecc144c06ad594c3351bd67df85f377ce115 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Vila=C3=A7a?= Date: Wed, 21 Oct 2020 20:05:38 +0100 Subject: [PATCH 2/5] Add bulma basic css struct (#5) * Add bulma basic css struct * Update tests * Add impl to live functions * Fix footer text --- assets/css/app.scss | 94 +--------------- assets/css/footer.scss | 15 +++ assets/css/phoenix.css | 101 ------------------ assets/css/phoenix.scss | 94 ++++++++++++++++ assets/js/app.js | 3 +- assets/js/nav-burger.js | 18 ++++ assets/package-lock.json | 5 + assets/package.json | 5 +- lib/infin_web/live/page_live.ex | 34 ++---- lib/infin_web/live/page_live.html.leex | 56 ++-------- .../templates/layout/_footer.html.eex | 6 ++ .../templates/layout/_navbar.html.eex | 26 +++++ .../templates/layout/_user_menu.html.eex | 19 ++-- lib/infin_web/templates/layout/root.html.leex | 26 ++--- .../user_registration_controller_test.exs | 2 +- .../user_session_controller_test.exs | 2 +- test/infin_web/live/page_live_test.exs | 4 +- 17 files changed, 211 insertions(+), 299 deletions(-) create mode 100644 assets/css/footer.scss delete mode 100644 assets/css/phoenix.css create mode 100644 assets/css/phoenix.scss create mode 100644 assets/js/nav-burger.js create mode 100644 lib/infin_web/templates/layout/_footer.html.eex create mode 100644 lib/infin_web/templates/layout/_navbar.html.eex diff --git a/assets/css/app.scss b/assets/css/app.scss index f7775db..ec0b3ba 100644 --- a/assets/css/app.scss +++ b/assets/css/app.scss @@ -1,91 +1,7 @@ -/* This file is for your main application css. */ -@import "./phoenix.css"; -@import "../node_modules/nprogress/nprogress.css"; - -/* LiveView specific classes for your customizations */ -.phx-no-feedback.invalid-feedback, -.phx-no-feedback .invalid-feedback { - display: none; -} - -.phx-click-loading { - opacity: 0.5; - transition: opacity 1s ease-out; -} - -.phx-disconnected{ - cursor: wait; -} -.phx-disconnected *{ - pointer-events: none; -} - -.phx-modal { - opacity: 1!important; - position: fixed; - z-index: 1; - left: 0; - top: 0; - width: 100%; - height: 100%; - overflow: auto; - background-color: rgb(0,0,0); - background-color: rgba(0,0,0,0.4); -} +@import './footer.scss'; -.phx-modal-content { - background-color: #fefefe; - margin: 15% auto; - padding: 20px; - border: 1px solid #888; - width: 80%; -} +@import 'bulma'; -.phx-modal-close { - color: #aaa; - float: right; - font-size: 28px; - font-weight: bold; -} - -.phx-modal-close:hover, -.phx-modal-close:focus { - color: black; - text-decoration: none; - cursor: pointer; -} - - -/* Alerts and form errors */ -.alert { - padding: 15px; - margin-bottom: 20px; - border: 1px solid transparent; - border-radius: 4px; -} -.alert-info { - color: #31708f; - background-color: #d9edf7; - border-color: #bce8f1; -} -.alert-warning { - color: #8a6d3b; - background-color: #fcf8e3; - border-color: #faebcc; -} -.alert-danger { - color: #a94442; - background-color: #f2dede; - border-color: #ebccd1; -} -.alert p { - margin-bottom: 0; -} -.alert:empty { - display: none; -} -.invalid-feedback { - color: #a94442; - display: block; - margin: -1rem 0 2rem; -} +/* This file is for your main application css. */ +@import "../node_modules/nprogress/nprogress.css"; +@import './phoenix.scss'; diff --git a/assets/css/footer.scss b/assets/css/footer.scss new file mode 100644 index 0000000..7a65dff --- /dev/null +++ b/assets/css/footer.scss @@ -0,0 +1,15 @@ +#page-container { + position: relative; + min-height: 100vh; +} + +#content-wrap { + padding-bottom: 2.5rem; +} + +#footer { + position: absolute; + bottom: 0; + width: 100%; + height: 2.5rem; +} diff --git a/assets/css/phoenix.css b/assets/css/phoenix.css deleted file mode 100644 index 3767b31..0000000 --- a/assets/css/phoenix.css +++ /dev/null @@ -1,101 +0,0 @@ -/* Includes some default style for the starter application. - * This can be safely deleted to start fresh. - */ - -/* Milligram v1.3.0 https://milligram.github.io - * Copyright (c) 2017 CJ Patoilo Licensed under the MIT license - */ - -*,*:after,*:before{box-sizing:inherit}html{box-sizing:border-box;font-size:62.5%}body{color:#000000;font-family:'Helvetica', 'Arial', sans-serif;font-size:1.6em;font-weight:300;line-height:1.6}blockquote{border-left:0.3rem solid #d1d1d1;margin-left:0;margin-right:0;padding:1rem 1.5rem}blockquote *:last-child{margin-bottom:0}.button,button,input[type='button'],input[type='reset'],input[type='submit']{background-color:#0069d9;border:0.1rem solid #0069d9;border-radius:.4rem;color:#fff;cursor:pointer;display:inline-block;font-size:1.1rem;font-weight:700;height:3.8rem;letter-spacing:.1rem;line-height:3.8rem;padding:0 3.0rem;text-align:center;text-decoration:none;text-transform:uppercase;white-space:nowrap}.button:focus,.button:hover,button:focus,button:hover,input[type='button']:focus,input[type='button']:hover,input[type='reset']:focus,input[type='reset']:hover,input[type='submit']:focus,input[type='submit']:hover{background-color:#606c76;border-color:#606c76;color:#fff;outline:0}.button[disabled],button[disabled],input[type='button'][disabled],input[type='reset'][disabled],input[type='submit'][disabled]{cursor:default;opacity:.5}.button[disabled]:focus,.button[disabled]:hover,button[disabled]:focus,button[disabled]:hover,input[type='button'][disabled]:focus,input[type='button'][disabled]:hover,input[type='reset'][disabled]:focus,input[type='reset'][disabled]:hover,input[type='submit'][disabled]:focus,input[type='submit'][disabled]:hover{background-color:#0069d9;border-color:#0069d9}.button.button-outline,button.button-outline,input[type='button'].button-outline,input[type='reset'].button-outline,input[type='submit'].button-outline{background-color:transparent;color:#0069d9}.button.button-outline:focus,.button.button-outline:hover,button.button-outline:focus,button.button-outline:hover,input[type='button'].button-outline:focus,input[type='button'].button-outline:hover,input[type='reset'].button-outline:focus,input[type='reset'].button-outline:hover,input[type='submit'].button-outline:focus,input[type='submit'].button-outline:hover{background-color:transparent;border-color:#606c76;color:#606c76}.button.button-outline[disabled]:focus,.button.button-outline[disabled]:hover,button.button-outline[disabled]:focus,button.button-outline[disabled]:hover,input[type='button'].button-outline[disabled]:focus,input[type='button'].button-outline[disabled]:hover,input[type='reset'].button-outline[disabled]:focus,input[type='reset'].button-outline[disabled]:hover,input[type='submit'].button-outline[disabled]:focus,input[type='submit'].button-outline[disabled]:hover{border-color:inherit;color:#0069d9}.button.button-clear,button.button-clear,input[type='button'].button-clear,input[type='reset'].button-clear,input[type='submit'].button-clear{background-color:transparent;border-color:transparent;color:#0069d9}.button.button-clear:focus,.button.button-clear:hover,button.button-clear:focus,button.button-clear:hover,input[type='button'].button-clear:focus,input[type='button'].button-clear:hover,input[type='reset'].button-clear:focus,input[type='reset'].button-clear:hover,input[type='submit'].button-clear:focus,input[type='submit'].button-clear:hover{background-color:transparent;border-color:transparent;color:#606c76}.button.button-clear[disabled]:focus,.button.button-clear[disabled]:hover,button.button-clear[disabled]:focus,button.button-clear[disabled]:hover,input[type='button'].button-clear[disabled]:focus,input[type='button'].button-clear[disabled]:hover,input[type='reset'].button-clear[disabled]:focus,input[type='reset'].button-clear[disabled]:hover,input[type='submit'].button-clear[disabled]:focus,input[type='submit'].button-clear[disabled]:hover{color:#0069d9}code{background:#f4f5f6;border-radius:.4rem;font-size:86%;margin:0 .2rem;padding:.2rem .5rem;white-space:nowrap}pre{background:#f4f5f6;border-left:0.3rem solid #0069d9;overflow-y:hidden}pre>code{border-radius:0;display:block;padding:1rem 1.5rem;white-space:pre}hr{border:0;border-top:0.1rem solid #f4f5f6;margin:3.0rem 0}input[type='email'],input[type='number'],input[type='password'],input[type='search'],input[type='tel'],input[type='text'],input[type='url'],textarea,select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:transparent;border:0.1rem solid #d1d1d1;border-radius:.4rem;box-shadow:none;box-sizing:inherit;height:3.8rem;padding:.6rem 1.0rem;width:100%}input[type='email']:focus,input[type='number']:focus,input[type='password']:focus,input[type='search']:focus,input[type='tel']:focus,input[type='text']:focus,input[type='url']:focus,textarea:focus,select:focus{border-color:#0069d9;outline:0}select{background:url('data:image/svg+xml;utf8,') center right no-repeat;padding-right:3.0rem}select:focus{background-image:url('data:image/svg+xml;utf8,')}textarea{min-height:6.5rem}label,legend{display:block;font-size:1.6rem;font-weight:700;margin-bottom:.5rem}fieldset{border-width:0;padding:0}input[type='checkbox'],input[type='radio']{display:inline}.label-inline{display:inline-block;font-weight:normal;margin-left:.5rem}.row{display:flex;flex-direction:column;padding:0;width:100%}.row.row-no-padding{padding:0}.row.row-no-padding>.column{padding:0}.row.row-wrap{flex-wrap:wrap}.row.row-top{align-items:flex-start}.row.row-bottom{align-items:flex-end}.row.row-center{align-items:center}.row.row-stretch{align-items:stretch}.row.row-baseline{align-items:baseline}.row .column{display:block;flex:1 1 auto;margin-left:0;max-width:100%;width:100%}.row .column.column-offset-10{margin-left:10%}.row .column.column-offset-20{margin-left:20%}.row .column.column-offset-25{margin-left:25%}.row .column.column-offset-33,.row .column.column-offset-34{margin-left:33.3333%}.row .column.column-offset-50{margin-left:50%}.row .column.column-offset-66,.row .column.column-offset-67{margin-left:66.6666%}.row .column.column-offset-75{margin-left:75%}.row .column.column-offset-80{margin-left:80%}.row .column.column-offset-90{margin-left:90%}.row .column.column-10{flex:0 0 10%;max-width:10%}.row .column.column-20{flex:0 0 20%;max-width:20%}.row .column.column-25{flex:0 0 25%;max-width:25%}.row .column.column-33,.row .column.column-34{flex:0 0 33.3333%;max-width:33.3333%}.row .column.column-40{flex:0 0 40%;max-width:40%}.row .column.column-50{flex:0 0 50%;max-width:50%}.row .column.column-60{flex:0 0 60%;max-width:60%}.row .column.column-66,.row .column.column-67{flex:0 0 66.6666%;max-width:66.6666%}.row .column.column-75{flex:0 0 75%;max-width:75%}.row .column.column-80{flex:0 0 80%;max-width:80%}.row .column.column-90{flex:0 0 90%;max-width:90%}.row .column .column-top{align-self:flex-start}.row .column .column-bottom{align-self:flex-end}.row .column .column-center{-ms-grid-row-align:center;align-self:center}@media (min-width: 40rem){.row{flex-direction:row;margin-left:-1.0rem;width:calc(100% + 2.0rem)}.row .column{margin-bottom:inherit;padding:0 1.0rem}}a{color:#0069d9;text-decoration:none}a:focus,a:hover{color:#606c76}dl,ol,ul{list-style:none;margin-top:0;padding-left:0}dl dl,dl ol,dl ul,ol dl,ol ol,ol ul,ul dl,ul ol,ul ul{font-size:90%;margin:1.5rem 0 1.5rem 3.0rem}ol{list-style:decimal inside}ul{list-style:circle inside}.button,button,dd,dt,li{margin-bottom:1.0rem}fieldset,input,select,textarea{margin-bottom:1.5rem}blockquote,dl,figure,form,ol,p,pre,table,ul{margin-bottom:2.5rem}table{border-spacing:0;width:100%}td,th{border-bottom:0.1rem solid #e1e1e1;padding:1.2rem 1.5rem;text-align:left}td:first-child,th:first-child{padding-left:0}td:last-child,th:last-child{padding-right:0}b,strong{font-weight:bold}p{margin-top:0}h1,h2,h3,h4,h5,h6{font-weight:300;letter-spacing:-.1rem;margin-bottom:2.0rem;margin-top:0}h1{font-size:4.6rem;line-height:1.2}h2{font-size:3.6rem;line-height:1.25}h3{font-size:2.8rem;line-height:1.3}h4{font-size:2.2rem;letter-spacing:-.08rem;line-height:1.35}h5{font-size:1.8rem;letter-spacing:-.05rem;line-height:1.5}h6{font-size:1.6rem;letter-spacing:0;line-height:1.4}img{max-width:100%}.clearfix:after{clear:both;content:' ';display:table}.float-left{float:left}.float-right{float:right} - -/* General style */ -h1{font-size: 3.6rem; line-height: 1.25} -h2{font-size: 2.8rem; line-height: 1.3} -h3{font-size: 2.2rem; letter-spacing: -.08rem; line-height: 1.35} -h4{font-size: 1.8rem; letter-spacing: -.05rem; line-height: 1.5} -h5{font-size: 1.6rem; letter-spacing: 0; line-height: 1.4} -h6{font-size: 1.4rem; letter-spacing: 0; line-height: 1.2} -pre{padding: 1em;} - -.container{ - margin: 0 auto; - max-width: 80.0rem; - padding: 0 2.0rem; - position: relative; - width: 100% -} -select { - width: auto; -} - -/* Phoenix promo and logo */ -.phx-hero { - text-align: center; - border-bottom: 1px solid #e3e3e3; - background: #eee; - border-radius: 6px; - padding: 3em 3em 1em; - margin-bottom: 3rem; - font-weight: 200; - font-size: 120%; -} -.phx-hero input { - background: #ffffff; -} -.phx-logo { - min-width: 300px; - margin: 1rem; - display: block; -} -.phx-logo img { - width: auto; - display: block; -} - -/* Headers */ -header { - width: 100%; - background: #fdfdfd; - border-bottom: 1px solid #eaeaea; - margin-bottom: 2rem; -} -header section { - align-items: center; - display: flex; - flex-direction: column; - justify-content: space-between; -} -header section :first-child { - order: 2; -} -header section :last-child { - order: 1; -} -header nav ul, -header nav li { - margin: 0; - padding: 0; - display: block; - text-align: right; - white-space: nowrap; -} -header nav ul { - margin: 1rem; - margin-top: 0; -} -header nav a { - display: block; -} - -@media (min-width: 40.0rem) { /* Small devices (landscape phones, 576px and up) */ - header section { - flex-direction: row; - } - header nav ul { - margin: 1rem; - } - .phx-logo { - flex-basis: 527px; - margin: 2rem 1rem; - } -} diff --git a/assets/css/phoenix.scss b/assets/css/phoenix.scss new file mode 100644 index 0000000..7c2d94a --- /dev/null +++ b/assets/css/phoenix.scss @@ -0,0 +1,94 @@ +/* LiveView specific classes for your customizations */ +.phx-no-feedback.invalid-feedback, +.phx-no-feedback .invalid-feedback { + display: none; +} + +.phx-click-loading { + opacity: 0.5; + transition: opacity 1s ease-out; +} + +.phx-disconnected { + cursor: wait; +} + +.phx-disconnected * { + pointer-events: none; +} + +.phx-modal { + opacity: 1 !important; + position: fixed; + z-index: 1; + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + background-color: rgb(0, 0, 0); + background-color: rgba(0, 0, 0, 0.4); +} + +.phx-modal-content { + background-color: #fefefe; + margin: 15% auto; + padding: 20px; + border: 1px solid #888; + width: 80%; +} + +.phx-modal-close { + color: #aaa; + float: right; + font-size: 28px; + font-weight: bold; +} + +.phx-modal-close:hover, +.phx-modal-close:focus { + color: black; + text-decoration: none; + cursor: pointer; +} + + +/* Alerts and form errors */ +.alert { + padding: 15px; + margin-bottom: 20px; + border: 1px solid transparent; + border-radius: 4px; +} + +.alert-info { + color: #31708f; + background-color: #d9edf7; + border-color: #bce8f1; +} + +.alert-warning { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #faebcc; +} + +.alert-danger { + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; +} + +.alert p { + margin-bottom: 0; +} + +.alert:empty { + display: none; +} + +.invalid-feedback { + color: #a94442; + display: block; + margin: -1rem 0 2rem; +} diff --git a/assets/js/app.js b/assets/js/app.js index dfc0f97..9719f8a 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -3,6 +3,8 @@ // its own CSS file. import "../css/app.scss" +import "./nav-burger" + // webpack automatically bundles all modules in your // entry points. Those entry points can be configured // in "webpack.config.js". @@ -32,4 +34,3 @@ liveSocket.connect() // >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session // >> liveSocket.disableLatencySim() window.liveSocket = liveSocket - diff --git a/assets/js/nav-burger.js b/assets/js/nav-burger.js new file mode 100644 index 0000000..63e4e43 --- /dev/null +++ b/assets/js/nav-burger.js @@ -0,0 +1,18 @@ +const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0); + +// Check if there are any navbar burgers +if ($navbarBurgers.length > 0) { + // Add a click event on each of them + $navbarBurgers.forEach( el => { + el.addEventListener('click', () => { + // Get the target from the "data-target" attribute + const target = el.dataset.target; + const $target = document.getElementById(target); + + // Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu" + el.classList.toggle('is-active'); + $target.classList.toggle('is-active'); + }); + }); +} + diff --git a/assets/package-lock.json b/assets/package-lock.json index 87f37dc..1215cb9 100644 --- a/assets/package-lock.json +++ b/assets/package-lock.json @@ -1808,6 +1808,11 @@ "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", "dev": true }, + "bulma": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/bulma/-/bulma-0.9.1.tgz", + "integrity": "sha512-LSF69OumXg2HSKl2+rN0/OEXJy7WFEb681wtBlNS/ulJYR27J3rORHibdXZ6GVb/vyUzzYK/Arjyh56wjbFedA==" + }, "cacache": { "version": "12.0.4", "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", diff --git a/assets/package.json b/assets/package.json index aa41dee..3a529d6 100644 --- a/assets/package.json +++ b/assets/package.json @@ -7,10 +7,11 @@ "watch": "webpack --mode development --watch" }, "dependencies": { + "bulma": "^0.9.1", + "nprogress": "^0.2.0", "phoenix": "file:../deps/phoenix", "phoenix_html": "file:../deps/phoenix_html", - "phoenix_live_view": "file:../deps/phoenix_live_view", - "nprogress": "^0.2.0" + "phoenix_live_view": "file:../deps/phoenix_live_view" }, "devDependencies": { "@babel/core": "^7.0.0", diff --git a/lib/infin_web/live/page_live.ex b/lib/infin_web/live/page_live.ex index fef4cb6..1ab836a 100644 --- a/lib/infin_web/live/page_live.ex +++ b/lib/infin_web/live/page_live.ex @@ -3,37 +3,19 @@ defmodule InfinWeb.PageLive do @impl true def mount(_params, _session, socket) do - {:ok, assign(socket, query: "", results: %{})} - end + if connected?(socket), do: Process.send_after(self(), :update, 1000) - @impl true - def handle_event("suggest", %{"q" => query}, socket) do - {:noreply, assign(socket, results: search(query), query: query)} + {:ok, assign(socket, time: DateTime.utc_now)} end @impl true - def handle_event("search", %{"q" => query}, socket) do - case search(query) do - %{^query => vsn} -> - {:noreply, redirect(socket, external: "https://hexdocs.pm/#{query}/#{vsn}")} - - _ -> - {:noreply, - socket - |> put_flash(:error, "No dependencies found matching \"#{query}\"") - |> assign(results: %{}, query: query)} - end + def handle_info(:update, socket) do + Process.send_after(self(), :update, 1000) + {:noreply, assign(socket, time: DateTime.utc_now)} end - defp search(query) do - if not InfinWeb.Endpoint.config(:code_reloader) do - raise "action disabled when not in development" - end - - for {app, desc, vsn} <- Application.started_applications(), - app = to_string(app), - String.starts_with?(app, query) and not List.starts_with?(desc, ~c"ERTS"), - into: %{}, - do: {app, vsn} + @impl true + def handle_event("update", _value, socket) do + {:noreply, assign(socket, time: DateTime.utc_now)} end end diff --git a/lib/infin_web/live/page_live.html.leex b/lib/infin_web/live/page_live.html.leex index 52509c2..e5882ba 100644 --- a/lib/infin_web/live/page_live.html.leex +++ b/lib/infin_web/live/page_live.html.leex @@ -1,48 +1,8 @@ -
-

<%= gettext "Welcome to %{name}!", name: "Phoenix" %>

-

Peace of mind from prototype to production

- -
- - - <%= for {app, _vsn} <- @results do %> - - <% end %> - - -
-
- -
- - -
+
+
+ <%= @time %> +
+
+ <%= link "Update", to: "#", "phx-click": "update", class: "button is-primary" %> +
+
diff --git a/lib/infin_web/templates/layout/_footer.html.eex b/lib/infin_web/templates/layout/_footer.html.eex new file mode 100644 index 0000000..7ee0cf9 --- /dev/null +++ b/lib/infin_web/templates/layout/_footer.html.eex @@ -0,0 +1,6 @@ +
+
+

Infin by Infinions.

+

Manage money, not spreadsheets.

+
+
diff --git a/lib/infin_web/templates/layout/_navbar.html.eex b/lib/infin_web/templates/layout/_navbar.html.eex new file mode 100644 index 0000000..75db494 --- /dev/null +++ b/lib/infin_web/templates/layout/_navbar.html.eex @@ -0,0 +1,26 @@ + diff --git a/lib/infin_web/templates/layout/_user_menu.html.eex b/lib/infin_web/templates/layout/_user_menu.html.eex index f281cfc..4cb5b69 100644 --- a/lib/infin_web/templates/layout/_user_menu.html.eex +++ b/lib/infin_web/templates/layout/_user_menu.html.eex @@ -1,10 +1,9 @@ - +
+ <%= if @current_user do %> + <%= link "#{@current_user.email}", to: Routes.user_settings_path(@conn, :edit), class: "button is-primary" %> + <%= link "Log out", to: Routes.user_session_path(@conn, :delete), method: :delete, class: "button is-light" %> + <% else %> + <%= link "Register", to: Routes.user_registration_path(@conn, :new), class: "button is-primary" %> + <%= link "Log in", to: Routes.user_session_path(@conn, :new), class: "button is-light" %> + <% end %> +
diff --git a/lib/infin_web/templates/layout/root.html.leex b/lib/infin_web/templates/layout/root.html.leex index 565ca6a..b32cbf9 100644 --- a/lib/infin_web/templates/layout/root.html.leex +++ b/lib/infin_web/templates/layout/root.html.leex @@ -5,27 +5,17 @@ <%= csrf_meta_tag() %> - <%= live_title_tag assigns[:page_title] || "Infin", suffix: " · Phoenix Framework" %> + <%= live_title_tag assigns[:page_title] || "Infin", suffix: " · Manage money, not spreadsheets" %> "/> -
-
- - -
-
- <%= @inner_content %> +
+
+ <%= render "_navbar.html", assigns %> + <%= @inner_content %> +
+ <%= render "_footer.html", assigns %> +
diff --git a/test/infin_web/controllers/user_registration_controller_test.exs b/test/infin_web/controllers/user_registration_controller_test.exs index 540e46b..2def322 100644 --- a/test/infin_web/controllers/user_registration_controller_test.exs +++ b/test/infin_web/controllers/user_registration_controller_test.exs @@ -35,7 +35,7 @@ defmodule InfinWeb.UserRegistrationControllerTest do conn = get(conn, "/") response = html_response(conn, 200) assert response =~ email - assert response =~ "Settings" + assert response =~ "#{email}" assert response =~ "Log out" end diff --git a/test/infin_web/controllers/user_session_controller_test.exs b/test/infin_web/controllers/user_session_controller_test.exs index 777c6db..d7aff26 100644 --- a/test/infin_web/controllers/user_session_controller_test.exs +++ b/test/infin_web/controllers/user_session_controller_test.exs @@ -36,7 +36,7 @@ defmodule InfinWeb.UserSessionControllerTest do conn = get(conn, "/") response = html_response(conn, 200) assert response =~ user.email - assert response =~ "Settings" + assert response =~ "#{user.email}" assert response =~ "Log out" end diff --git a/test/infin_web/live/page_live_test.exs b/test/infin_web/live/page_live_test.exs index 921b547..e39ed82 100644 --- a/test/infin_web/live/page_live_test.exs +++ b/test/infin_web/live/page_live_test.exs @@ -5,7 +5,7 @@ defmodule InfinWeb.PageLiveTest do test "disconnected and connected render", %{conn: conn} do {:ok, page_live, disconnected_html} = live(conn, "/") - assert disconnected_html =~ "Welcome to Phoenix!" - assert render(page_live) =~ "Welcome to Phoenix!" + assert disconnected_html =~ "Update" + assert render(page_live) =~ "Update" end end From c1a2568482d298d04860f26b08d1e99ffec25ea7 Mon Sep 17 00:00:00 2001 From: Eduardo Jorge Date: Sat, 24 Oct 2020 00:55:07 +0100 Subject: [PATCH 3/5] Add companies (#6) --- lib/infin/accounts.ex | 13 +- lib/infin/accounts/user.ex | 35 ++++- lib/infin/companies.ex | 120 ++++++++++++++++++ lib/infin/companies/company.ex | 20 +++ .../controllers/company_controller.ex | 31 +++++ .../user_registration_controller.ex | 2 +- lib/infin_web/router.ex | 7 + lib/infin_web/templates/company/edit.html.eex | 4 + lib/infin_web/templates/company/form.html.eex | 15 +++ lib/infin_web/templates/company/show.html.eex | 12 ++ .../templates/layout/_user_menu.html.eex | 3 + .../templates/user_registration/new.html.eex | 9 +- lib/infin_web/views/company_view.ex | 3 + .../20201021135554_create_companies.exs | 14 ++ .../20201021142319_users_add_company.exs | 9 ++ priv/repo/seeds.exs | 4 +- test/infin/accounts_test.exs | 5 +- test/infin/companies_test.exs | 71 +++++++++++ .../controllers/company_controller_test.exs | 54 ++++++++ .../user_registration_controller_test.exs | 5 +- test/support/fixtures/accounts_fixtures.ex | 4 +- 21 files changed, 429 insertions(+), 11 deletions(-) create mode 100644 lib/infin/companies.ex create mode 100644 lib/infin/companies/company.ex create mode 100644 lib/infin_web/controllers/company_controller.ex create mode 100644 lib/infin_web/templates/company/edit.html.eex create mode 100644 lib/infin_web/templates/company/form.html.eex create mode 100644 lib/infin_web/templates/company/show.html.eex create mode 100644 lib/infin_web/views/company_view.ex create mode 100644 priv/repo/migrations/20201021135554_create_companies.exs create mode 100644 priv/repo/migrations/20201021142319_users_add_company.exs create mode 100644 test/infin/companies_test.exs create mode 100644 test/infin_web/controllers/company_controller_test.exs diff --git a/lib/infin/accounts.ex b/lib/infin/accounts.ex index 2dc6c6c..c8bfa46 100644 --- a/lib/infin/accounts.ex +++ b/lib/infin/accounts.ex @@ -183,6 +183,15 @@ defmodule Infin.Accounts do User.password_changeset(user, attrs) end + @doc """ + Returns an `%Ecto.Changeset{}` for changing the user company. + + """ + def change_user_company(user, attrs) do + User.company_changeset(user, attrs) + |> Repo.update!() + end + @doc """ Updates the user password. @@ -227,7 +236,9 @@ defmodule Infin.Accounts do """ def get_user_by_session_token(token) do {:ok, query} = UserToken.verify_session_token_query(token) - Repo.one(query) + query + |> Repo.one() + |> Repo.preload(:company) end @doc """ diff --git a/lib/infin/accounts/user.ex b/lib/infin/accounts/user.ex index f9d5fea..cdf1055 100644 --- a/lib/infin/accounts/user.ex +++ b/lib/infin/accounts/user.ex @@ -8,6 +8,7 @@ defmodule Infin.Accounts.User do field :password, :string, virtual: true field :hashed_password, :string field :confirmed_at, :naive_datetime + belongs_to :company, Infin.Companies.Company timestamps() end @@ -22,12 +23,35 @@ defmodule Infin.Accounts.User do """ def registration_changeset(user, attrs) do user - |> cast(attrs, [:email, :password]) + |> cast(to_company_form(attrs), [:email, :password]) |> validate_confirmation(:password, message: "does not match password") + |> cast_assoc(:company, required: true) |> validate_email() |> validate_password() end + defp to_company_form(%{"nif" => nif, "name" => name} = attrs), do: Map.put(attrs, "company", %{ "nif" => nif, "name" => name }) + defp to_company_form(%{nif: nif, name: name} = attrs), do: Map.put(attrs, :company, %{ nif: nif, name: name }) + defp to_company_form(%{} = attrs), do: attrs + + def copy_company_errors(%Ecto.Changeset{} = changeset) do + company = changeset.changes.company + + cond do + company -> + Enum.reduce(company.errors, changeset, fn err, acc -> + key = Kernel.elem(err, 0) + pre_message = Kernel.elem(err, 1) + message = Kernel.elem(pre_message, 0) + + Ecto.Changeset.add_error(acc, key, message) + end) + + true -> + changeset + end + end + defp validate_email(changeset) do changeset |> validate_required([:email]) @@ -80,6 +104,15 @@ defmodule Infin.Accounts.User do |> validate_password() end + @doc """ + A user changeset for changing the company. + """ + def company_changeset(user, attrs) do + user + |> cast(attrs, [:company_id]) + |> validate_required([:company_id]) + end + @doc """ Confirms the account by setting `confirmed_at`. """ diff --git a/lib/infin/companies.ex b/lib/infin/companies.ex new file mode 100644 index 0000000..bf57cdb --- /dev/null +++ b/lib/infin/companies.ex @@ -0,0 +1,120 @@ +defmodule Infin.Companies do + @moduledoc """ + The Companies context. + """ + + import Ecto.Query, warn: false + alias Infin.Repo + + alias Infin.Companies.Company + + @doc """ + Returns the list of companies. + + ## Examples + + iex> list_companies() + [%Company{}, ...] + + """ + def list_companies do + Repo.all(Company) + end + + @doc """ + Gets a single company. + + Raises `Ecto.NoResultsError` if the Company does not exist. + + ## Examples + + iex> get_company!(123) + %Company{} + + iex> get_company!(456) + ** (Ecto.NoResultsError) + + """ + def get_company!(id), do: Repo.get!(Company, id) + + @doc """ + Gets a single company. + + Raises `Ecto.NoResultsError` if the Company does not exist. + + ## Examples + + iex> get_company_by_nif!(123) + %Company{} + + iex> get_company_by_nif!(456) + ** (Ecto.NoResultsError) + + """ + def get_company_by_nif!(nif), do: Repo.get_by!(Company, nif: nif) + + @doc """ + Creates a company. + + ## Examples + + iex> create_company(%{field: value}) + {:ok, %Company{}} + + iex> create_company(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_company(attrs \\ %{}) do + %Company{} + |> Company.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a company. + + ## Examples + + iex> update_company(company, %{field: new_value}) + {:ok, %Company{}} + + iex> update_company(company, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_company(%Company{} = company, attrs) do + company + |> Company.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a company. + + ## Examples + + iex> delete_company(company) + {:ok, %Company{}} + + iex> delete_company(company) + {:error, %Ecto.Changeset{}} + + """ + def delete_company(%Company{} = company) do + Repo.delete(company) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking company changes. + + ## Examples + + iex> change_company(company) + %Ecto.Changeset{data: %Company{}} + + """ + def change_company(%Company{} = company, attrs \\ %{}) do + Company.changeset(company, attrs) + end +end diff --git a/lib/infin/companies/company.ex b/lib/infin/companies/company.ex new file mode 100644 index 0000000..8df1bcb --- /dev/null +++ b/lib/infin/companies/company.ex @@ -0,0 +1,20 @@ +defmodule Infin.Companies.Company do + use Ecto.Schema + import Ecto.Changeset + + schema "companies" do + field :name, :string, null: false + field :nif, :string, null: false + has_many :users, Infin.Accounts.User + + timestamps() + end + + @doc false + def changeset(company, attrs) do + company + |> cast(attrs, [:name, :nif]) + |> validate_required([:name, :nif]) + |> unique_constraint(:nif) + end +end diff --git a/lib/infin_web/controllers/company_controller.ex b/lib/infin_web/controllers/company_controller.ex new file mode 100644 index 0000000..c09c666 --- /dev/null +++ b/lib/infin_web/controllers/company_controller.ex @@ -0,0 +1,31 @@ +defmodule InfinWeb.CompanyController do + use InfinWeb, :controller + + alias Infin.Companies + + def show(conn, _params, current_user_company) do + render(conn, "show.html", company: current_user_company) + end + + def edit(conn, _params, current_user_company) do + changeset = Companies.change_company(current_user_company) + render(conn, "edit.html", company: current_user_company, changeset: changeset) + end + + def update(conn, %{"company" => company_params}, current_user_company) do + case Companies.update_company(current_user_company, company_params) do + {:ok, company} -> + conn + |> put_flash(:info, "Company updated successfully.") + |> redirect(to: Routes.company_path(conn, :show, company)) + + {:error, %Ecto.Changeset{} = changeset} -> + render(conn, "edit.html", company: current_user_company, changeset: changeset) + end + end + + def action(conn, _) do + args = [conn, conn.params, conn.assigns.current_user.company] + apply(__MODULE__, action_name(conn), args) + end +end diff --git a/lib/infin_web/controllers/user_registration_controller.ex b/lib/infin_web/controllers/user_registration_controller.ex index c108b04..dcbd82d 100644 --- a/lib/infin_web/controllers/user_registration_controller.ex +++ b/lib/infin_web/controllers/user_registration_controller.ex @@ -24,7 +24,7 @@ defmodule InfinWeb.UserRegistrationController do |> UserAuth.log_in_user(user) {:error, %Ecto.Changeset{} = changeset} -> - render(conn, "new.html", changeset: changeset) + render(conn, "new.html", changeset: User.copy_company_errors(changeset)) end end end diff --git a/lib/infin_web/router.ex b/lib/infin_web/router.ex index 5716b9f..b049579 100644 --- a/lib/infin_web/router.ex +++ b/lib/infin_web/router.ex @@ -76,4 +76,11 @@ defmodule InfinWeb.Router do post "/users/confirm", UserConfirmationController, :create get "/users/confirm/:token", UserConfirmationController, :confirm end + + + scope "/manage", InfinWeb do + pipe_through [:browser, :require_authenticated_user] + + resources "/companies", CompanyController, only: [:show, :edit, :update] + end end diff --git a/lib/infin_web/templates/company/edit.html.eex b/lib/infin_web/templates/company/edit.html.eex new file mode 100644 index 0000000..76b0c83 --- /dev/null +++ b/lib/infin_web/templates/company/edit.html.eex @@ -0,0 +1,4 @@ +

Edit Company

+ +<%= render "form.html", Map.put(assigns, :action, Routes.company_path(@conn, :update, @company)) %> + diff --git a/lib/infin_web/templates/company/form.html.eex b/lib/infin_web/templates/company/form.html.eex new file mode 100644 index 0000000..e43e401 --- /dev/null +++ b/lib/infin_web/templates/company/form.html.eex @@ -0,0 +1,15 @@ +<%= form_for @changeset, @action, fn f -> %> + <%= if @changeset.action do %> +
+

Oops, something went wrong! Please check the errors below.

+
+ <% end %> + + <%= label f, :name %> + <%= text_input f, :name %> + <%= error_tag f, :name %> + +
+ <%= submit "Save" %> +
+<% end %> diff --git a/lib/infin_web/templates/company/show.html.eex b/lib/infin_web/templates/company/show.html.eex new file mode 100644 index 0000000..1ed70b7 --- /dev/null +++ b/lib/infin_web/templates/company/show.html.eex @@ -0,0 +1,12 @@ +

Show Company

+ +
    + +
  • + Name: + <%= @company.name %> +
  • + +
+ +<%= link "Edit", to: Routes.company_path(@conn, :edit, @company) %> diff --git a/lib/infin_web/templates/layout/_user_menu.html.eex b/lib/infin_web/templates/layout/_user_menu.html.eex index 4cb5b69..b3447a4 100644 --- a/lib/infin_web/templates/layout/_user_menu.html.eex +++ b/lib/infin_web/templates/layout/_user_menu.html.eex @@ -1,6 +1,9 @@
<%= if @current_user do %> <%= link "#{@current_user.email}", to: Routes.user_settings_path(@conn, :edit), class: "button is-primary" %> + <%= if @current_user.company do %> + <%= link "My company", to: Routes.company_path(@conn, :show, @current_user.company), class: "button is-primary" %> + <%= end %> <%= link "Log out", to: Routes.user_session_path(@conn, :delete), method: :delete, class: "button is-light" %> <% else %> <%= link "Register", to: Routes.user_registration_path(@conn, :new), class: "button is-primary" %> diff --git a/lib/infin_web/templates/user_registration/new.html.eex b/lib/infin_web/templates/user_registration/new.html.eex index 019fd3b..fb60982 100644 --- a/lib/infin_web/templates/user_registration/new.html.eex +++ b/lib/infin_web/templates/user_registration/new.html.eex @@ -11,6 +11,14 @@ <%= email_input f, :email, required: true %> <%= error_tag f, :email %> + <%= label f, :company_name %> + <%= text_input f, :name, required: true %> + <%= error_tag f, :name %> + + <%= label f, :company_nif %> + <%= text_input f, :nif, required: true %> + <%= error_tag f, :nif %> + <%= label f, :password %> <%= password_input f, :password, required: true %> <%= error_tag f, :password %> @@ -18,7 +26,6 @@ <%= label f, :password_confirmation %> <%= password_input f, :password_confirmation, required: true %> <%= error_tag f, :password_confirmation %> -
<%= submit "Register" %>
diff --git a/lib/infin_web/views/company_view.ex b/lib/infin_web/views/company_view.ex new file mode 100644 index 0000000..5bb2bf4 --- /dev/null +++ b/lib/infin_web/views/company_view.ex @@ -0,0 +1,3 @@ +defmodule InfinWeb.CompanyView do + use InfinWeb, :view +end diff --git a/priv/repo/migrations/20201021135554_create_companies.exs b/priv/repo/migrations/20201021135554_create_companies.exs new file mode 100644 index 0000000..2e0d0e4 --- /dev/null +++ b/priv/repo/migrations/20201021135554_create_companies.exs @@ -0,0 +1,14 @@ +defmodule Infin.Repo.Migrations.CreateCompanies do + use Ecto.Migration + + def change do + create table(:companies) do + add :name, :string + add :nif, :string + + timestamps() + end + + create unique_index(:companies, [:nif]) + end +end diff --git a/priv/repo/migrations/20201021142319_users_add_company.exs b/priv/repo/migrations/20201021142319_users_add_company.exs new file mode 100644 index 0000000..f65b7f4 --- /dev/null +++ b/priv/repo/migrations/20201021142319_users_add_company.exs @@ -0,0 +1,9 @@ +defmodule Infin.Repo.Migrations.UsersAddCompany do + use Ecto.Migration + + def change do + alter table("users") do + add :company_id, references(:companies) + end + end +end diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index c8c5e90..3b810c2 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -14,10 +14,10 @@ alias Infin.Repo Repo.insert!( %User{} - |> User.registration_changeset(%{email: "test@mail.com", password: "Qwerty1234567891"}) + |> User.registration_changeset(%{"email" => "test1@mail.com", "password" => "Qwerty1234567891", "name" => "Infin", "nif" => "1234"}) ) Repo.insert!( %User{} - |> User.registration_changeset(%{email: "test2@mail.com", password: "Qwerty1234567892"}) + |> User.registration_changeset(%{"email" => "test2@mail.com", "password" => "Qwerty1234567892", "name" => "NotInfin", "nif" => "6123"}) ) diff --git a/test/infin/accounts_test.exs b/test/infin/accounts_test.exs index 981b5b9..2b71d48 100644 --- a/test/infin/accounts_test.exs +++ b/test/infin/accounts_test.exs @@ -86,18 +86,19 @@ defmodule Infin.AccountsTest do test "registers users with a hashed password" do email = unique_user_email() - {:ok, user} = Accounts.register_user(%{email: email, password: valid_user_password(), password_confirmation: valid_user_password()}) + {:ok, user} = Accounts.register_user(%{email: email, password: valid_user_password(), password_confirmation: valid_user_password(), name: "#{System.unique_integer()}", nif: "#{System.unique_integer()}"}) assert user.email == email assert is_binary(user.hashed_password) assert is_nil(user.confirmed_at) assert is_nil(user.password) + refute is_nil(user.company) end end describe "change_user_registration/2" do test "returns a changeset" do assert %Ecto.Changeset{} = changeset = Accounts.change_user_registration(%User{}) - assert changeset.required == [:password, :email] + assert changeset.required == [:password, :email, :company] end end diff --git a/test/infin/companies_test.exs b/test/infin/companies_test.exs new file mode 100644 index 0000000..a6acd0e --- /dev/null +++ b/test/infin/companies_test.exs @@ -0,0 +1,71 @@ +defmodule Infin.CompaniesTest do + use Infin.DataCase + + alias Infin.Companies + + describe "companies" do + alias Infin.Companies.Company + + @valid_attrs %{name: "some name", nif: "1234"} + @update_attrs %{name: "some updated name", nif: "123"} + @invalid_attrs %{nif: nil, name: nil} + + def company_fixture(attrs \\ %{}) do + {:ok, company} = + attrs + |> Enum.into(@valid_attrs) + |> Companies.create_company() + + company + end + + test "list_companies/0 returns all companies" do + company = company_fixture() + assert Companies.list_companies() == [company] + end + + test "get_company!/1 returns the company with given id" do + company = company_fixture() + assert Companies.get_company!(company.id) == company + end + + test "get_company_by_nif!/1 returns the company with given nif" do + company = company_fixture() + assert Companies.get_company_by_nif!(company.nif) == company + end + + test "create_company/1 with valid data creates a company" do + assert {:ok, %Company{} = company} = Companies.create_company(@valid_attrs) + assert company.name == "some name" + assert company.nif == "1234" + end + + test "create_company/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Companies.create_company(@invalid_attrs) + end + + test "update_company/2 with valid data updates the company" do + company = company_fixture() + assert {:ok, %Company{} = company} = Companies.update_company(company, @update_attrs) + assert company.name == "some updated name" + assert company.nif == "123" + end + + test "update_company/2 with invalid data returns error changeset" do + company = company_fixture() + assert {:error, %Ecto.Changeset{}} = Companies.update_company(company, @invalid_attrs) + assert company == Companies.get_company!(company.id) + end + + test "delete_company/1 deletes the company" do + company = company_fixture() + assert {:ok, %Company{}} = Companies.delete_company(company) + assert_raise Ecto.NoResultsError, fn -> Companies.get_company!(company.id) end + end + + test "change_company/1 returns a company changeset" do + company = company_fixture() + assert %Ecto.Changeset{} = Companies.change_company(company) + end + end +end diff --git a/test/infin_web/controllers/company_controller_test.exs b/test/infin_web/controllers/company_controller_test.exs new file mode 100644 index 0000000..f1c8308 --- /dev/null +++ b/test/infin_web/controllers/company_controller_test.exs @@ -0,0 +1,54 @@ +defmodule InfinWeb.CompanyControllerTest do + use InfinWeb.ConnCase, async: true + + alias Infin.Companies + alias Infin.Accounts + + setup :register_and_log_in_user + + @create_attrs %{name: "some name", nif: "1234"} + @update_attrs %{name: "some updated name"} + @invalid_attrs %{nif: nil} + + def fixture(:company) do + {:ok, company} = Companies.create_company(@create_attrs) + company + end + + describe "edit company" do + setup [:create_company] + + test "renders form for editing chosen company", %{conn: conn, company: company, user: user} do + Accounts.change_user_company(user, %{company_id: company.id}) + + conn = get(conn, Routes.company_path(conn, :edit, company)) + assert html_response(conn, 200) =~ "Edit Company" + end + end + + describe "update company" do + setup [:create_company] + + test "redirects when data is valid", %{conn: conn, company: company, user: user} do + Accounts.change_user_company(user, %{company_id: company.id}) + + conn = put(conn, Routes.company_path(conn, :update, company), company: @update_attrs) + assert redirected_to(conn) == Routes.company_path(conn, :show, company) + + conn = get(conn, Routes.company_path(conn, :show, company)) + assert html_response(conn, 200) =~ "some updated name" + end + + test "renders errors when data is invalid", %{conn: conn, company: company, user: user} do + Accounts.change_user_company(user, %{company_id: company.id}) + + conn = put(conn, Routes.company_path(conn, :update, company), company: @invalid_attrs) + assert html_response(conn, 200) =~ "Edit Company" + end + end + + defp create_company(_) do + company = fixture(:company) + %{company: company} + end +end diff --git a/test/infin_web/controllers/user_registration_controller_test.exs b/test/infin_web/controllers/user_registration_controller_test.exs index 2def322..17a785e 100644 --- a/test/infin_web/controllers/user_registration_controller_test.exs +++ b/test/infin_web/controllers/user_registration_controller_test.exs @@ -25,9 +25,10 @@ defmodule InfinWeb.UserRegistrationControllerTest do conn = post(conn, Routes.user_registration_path(conn, :create), %{ - "user" => %{"email" => email, "password" => valid_user_password(), "password_confirmation" => valid_user_password()} + "user" => %{"email" => email, "password" => valid_user_password(), "password_confirmation" => valid_user_password(), "nif" => "#{System.unique_integer()}", "name" => "#{System.unique_integer()}"} }) + assert get_session(conn, :user_token) assert redirected_to(conn) =~ "/" @@ -42,7 +43,7 @@ defmodule InfinWeb.UserRegistrationControllerTest do test "render errors for invalid data", %{conn: conn} do conn = post(conn, Routes.user_registration_path(conn, :create), %{ - "user" => %{"email" => "with spaces", "password" => "too short", "password_confirmation" => "does not match"} + "user" => %{"email" => "with spaces", "password" => "too short", "password_confirmation" => "does not match", "nif" => "valid", "name" => "valid"} }) response = html_response(conn, 200) diff --git a/test/support/fixtures/accounts_fixtures.ex b/test/support/fixtures/accounts_fixtures.ex index ad11656..a84b187 100644 --- a/test/support/fixtures/accounts_fixtures.ex +++ b/test/support/fixtures/accounts_fixtures.ex @@ -12,7 +12,9 @@ defmodule Infin.AccountsFixtures do attrs |> Enum.into(%{ email: unique_user_email(), - password: valid_user_password() + password: valid_user_password(), + name: "#{System.unique_integer()}", + nif: "#{System.unique_integer()}" }) |> Infin.Accounts.register_user() From ab0d4b4f8bd2381322d167c7b7f7d03b9f2e884d Mon Sep 17 00:00:00 2001 From: Eduardo Jorge Date: Sat, 24 Oct 2020 16:00:40 +0100 Subject: [PATCH 4/5] Add key check (#7) Add key check to user registration changeset error handling --- lib/infin/accounts/user.ex | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/infin/accounts/user.ex b/lib/infin/accounts/user.ex index cdf1055..114e9af 100644 --- a/lib/infin/accounts/user.ex +++ b/lib/infin/accounts/user.ex @@ -30,15 +30,19 @@ defmodule Infin.Accounts.User do |> validate_password() end - defp to_company_form(%{"nif" => nif, "name" => name} = attrs), do: Map.put(attrs, "company", %{ "nif" => nif, "name" => name }) - defp to_company_form(%{nif: nif, name: name} = attrs), do: Map.put(attrs, :company, %{ nif: nif, name: name }) + defp to_company_form(%{"nif" => nif, "name" => name} = attrs), + do: Map.put(attrs, "company", %{"nif" => nif, "name" => name}) + + defp to_company_form(%{nif: nif, name: name} = attrs), + do: Map.put(attrs, :company, %{nif: nif, name: name}) + defp to_company_form(%{} = attrs), do: attrs def copy_company_errors(%Ecto.Changeset{} = changeset) do - company = changeset.changes.company - cond do - company -> + Map.has_key?(changeset.changes, :company) -> + company = changeset.changes.company + Enum.reduce(company.errors, changeset, fn err, acc -> key = Kernel.elem(err, 0) pre_message = Kernel.elem(err, 1) From 5a224aac5bda0a30786ba1c2449e95bd4f9ac3dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Vila=C3=A7a?= Date: Sat, 24 Oct 2020 16:58:09 +0100 Subject: [PATCH 5/5] Update user and company forms (#8) * Add forms * Add css to settings page * Add css to forgot password page * Edit company change page --- assets/css/footer.scss | 6 +- assets/css/phoenix.scss | 4 + .../controllers/company_controller.ex | 8 +- lib/infin_web/router.ex | 2 +- lib/infin_web/templates/company/form.html.eex | 38 +++++- lib/infin_web/templates/company/show.html.eex | 13 +- .../templates/layout/_navbar.html.eex | 2 +- .../templates/layout/_user_menu.html.eex | 4 +- lib/infin_web/templates/layout/root.html.leex | 5 +- .../templates/user_confirmation/new.html.eex | 9 +- .../templates/user_registration/new.html.eex | 124 ++++++++++++++---- .../user_reset_password/edit.html.eex | 57 ++++++-- .../user_reset_password/new.html.eex | 28 ++-- .../templates/user_session/new.html.eex | 63 ++++++--- .../templates/user_settings/edit.html.eex | 114 ++++++++++++---- lib/infin_web/views/error_helpers.ex | 11 +- .../controllers/company_controller_test.exs | 6 +- .../user_registration_controller_test.exs | 10 +- .../user_reset_password_controller_test.exs | 8 +- .../user_session_controller_test.exs | 8 +- .../user_settings_controller_test.exs | 8 +- 21 files changed, 390 insertions(+), 138 deletions(-) diff --git a/assets/css/footer.scss b/assets/css/footer.scss index 7a65dff..ee6c5a5 100644 --- a/assets/css/footer.scss +++ b/assets/css/footer.scss @@ -4,12 +4,14 @@ } #content-wrap { - padding-bottom: 2.5rem; + padding-bottom: 6rem; } #footer { position: absolute; bottom: 0; width: 100%; - height: 2.5rem; + height: 6rem; + padding-top: 1rem; + padding-bottom: 1rem; } diff --git a/assets/css/phoenix.scss b/assets/css/phoenix.scss index 7c2d94a..6fd5f4d 100644 --- a/assets/css/phoenix.scss +++ b/assets/css/phoenix.scss @@ -92,3 +92,7 @@ display: block; margin: -1rem 0 2rem; } + +.bd-lead { + padding: 1.5rem; +} diff --git a/lib/infin_web/controllers/company_controller.ex b/lib/infin_web/controllers/company_controller.ex index c09c666..e4517f9 100644 --- a/lib/infin_web/controllers/company_controller.ex +++ b/lib/infin_web/controllers/company_controller.ex @@ -4,12 +4,8 @@ defmodule InfinWeb.CompanyController do alias Infin.Companies def show(conn, _params, current_user_company) do - render(conn, "show.html", company: current_user_company) - end - - def edit(conn, _params, current_user_company) do changeset = Companies.change_company(current_user_company) - render(conn, "edit.html", company: current_user_company, changeset: changeset) + render(conn, "show.html", company: current_user_company, changeset: changeset) end def update(conn, %{"company" => company_params}, current_user_company) do @@ -20,7 +16,7 @@ defmodule InfinWeb.CompanyController do |> redirect(to: Routes.company_path(conn, :show, company)) {:error, %Ecto.Changeset{} = changeset} -> - render(conn, "edit.html", company: current_user_company, changeset: changeset) + render(conn, "show.html", company: current_user_company, changeset: changeset) end end diff --git a/lib/infin_web/router.ex b/lib/infin_web/router.ex index b049579..2664a80 100644 --- a/lib/infin_web/router.ex +++ b/lib/infin_web/router.ex @@ -81,6 +81,6 @@ defmodule InfinWeb.Router do scope "/manage", InfinWeb do pipe_through [:browser, :require_authenticated_user] - resources "/companies", CompanyController, only: [:show, :edit, :update] + resources "/companies", CompanyController, only: [:show, :update] end end diff --git a/lib/infin_web/templates/company/form.html.eex b/lib/infin_web/templates/company/form.html.eex index e43e401..3ffdaa5 100644 --- a/lib/infin_web/templates/company/form.html.eex +++ b/lib/infin_web/templates/company/form.html.eex @@ -5,11 +5,39 @@
<% end %> - <%= label f, :name %> - <%= text_input f, :name %> - <%= error_tag f, :name %> +
+ <%= label f, :name, "Name", class: "label" %> +
+ + + + <%= if has_error(f, :name) do %> + <%= text_input f, :name, required: true, class: "input is-danger" %> + + + + <% else %> + <%= text_input f, :name, required: true, class: "input" %> + <% end %> + <%= error_tag f, :name %> +
+
-
- <%= submit "Save" %> +
+
+ <%= label f, :company_nif, "Tax Id", class: "label" %> +
+ + + + <%= text_input f, :nif, required: true, class: "input" %> +
+
+
+ +
+
+ <%= submit "Update", class: "button is-primary" %> +
<% end %> diff --git a/lib/infin_web/templates/company/show.html.eex b/lib/infin_web/templates/company/show.html.eex index 1ed70b7..f2ff823 100644 --- a/lib/infin_web/templates/company/show.html.eex +++ b/lib/infin_web/templates/company/show.html.eex @@ -1,12 +1,3 @@ -

Show Company

+

<%= @company.name %> Settings

-
    - -
  • - Name: - <%= @company.name %> -
  • - -
- -<%= link "Edit", to: Routes.company_path(@conn, :edit, @company) %> +<%= render "form.html", Map.put(assigns, :action, Routes.company_path(@conn, :update, @company)) %> diff --git a/lib/infin_web/templates/layout/_navbar.html.eex b/lib/infin_web/templates/layout/_navbar.html.eex index 75db494..bbe698c 100644 --- a/lib/infin_web/templates/layout/_navbar.html.eex +++ b/lib/infin_web/templates/layout/_navbar.html.eex @@ -1,4 +1,4 @@ -