diff --git a/.gitignore b/.gitignore index 0de619a..5bac89f 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,5 @@ send_it-*.tar npm-debug.log /assets/node_modules/ +setup_env.sh +customers_master_list.csv diff --git a/assets/css/app.css b/assets/css/app.css index e69de29..5b7fc93 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -0,0 +1,315 @@ +@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,400;0,700;1,400;1,700&family=IBM+Plex+Sans:ital,wght@0,200;0,400;0,700;1,200;1,400;1,700&family=IBM+Plex+Serif:ital,wght@0,400;0,700;1,400;1,700&display=swap'); +@import url("./normalize.css"); + +:root { + /* --blue: #0064ff;*/ + --grey: #d8d8d8; + --grey-dark: #1f1d1c; + --red: #ce2b2b; + --blue: #3ab0ed; + --magenta: #9b62fd; + --white: #ffffff; + --black: #000000; + + --cyan: #1ee3cf; + --yellow: #eed811; + --pink: #eb0871; +} + +html { + font-size: 100%; +} + +body { + background: rgb(44, 43, 54); + background: radial-gradient(circle, rgba(44, 43, 54, 1) 25%, rgba(41, 41, 44, 1) 100%); + font-family: 'IBM Plex Sans', sans-serif; + font-weight: 400; + line-height: 1.3; + color: var(--white); +} + +p { + margin: 0; + font-size: 1rem; +} + +a { + color: var(--magenta); + cursor: pointer; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0; + font-weight: 700; + line-height: 1; +} + +h1 { + margin-top: 0; + font-size: 1.802rem; +} + +h2 { + font-size: 1.602rem; +} + +h3 { + font-size: 1.424rem; +} + +h4 { + font-size: 1.266rem; +} + +h5 { + font-size: 1.125rem; +} + +h6 { + font-size: 1rem; +} + +small { + font-size: 0.889rem; +} + +main { + max-width: 1024px; + margin: 0 auto; + padding: 1rem; +} + +.table-default { + border-collapse: collapse; + width: 100%; + text-align: left; + margin: 1rem 0; + + & thead { + border-bottom: 3px solid var(--magenta); + } + + & th, + td { + padding: 0.5rem 1rem; + } + + & td.is-link:hover { + cursor: pointer; + } + + & tr:nth-child(2n) { + background-color: var(--grey-dark); + } + + & tbody tr:hover { + color: white; + background-color: var(--magenta); + + & a { + color: white; + } + } + + & .table-actions { + display: flex; + gap: 1rem; + flex-wrap: wrap; + } +} + +@media screen and (max-width:600px) { + .table-default { + & tr { + display: grid; + grid-template-columns: 1fr + } + + & tfoot th { + text-align: unset + } + } +} + + +/* Forms */ +label { + font: inherit; + line-height: inherit; + display: inline-block; +} + +input[type=datetime-local] { + background-color: inherit; + color: inherit; + outline: none; + line-height: 1.75; + padding: 0.25rem 0.5rem; + font-size: 1.25em; + font-family: "IBM Plex Mono", monospace; + margin: 0; + width: 100%; + border: 3px solid #414141; +} + +input, +textarea { + caret-color: var(--magenta); + background-color: inherit; + color: inherit; + outline: none; + line-height: 1.75; + padding: 0.25rem 0.5rem; + font-size: 1.25em; + font-family: "IBM Plex Mono", monospace; + margin: 0; + width: 100%; + resize: vertical; + border: 3px solid #414141; +} + +textarea { + min-height: 12rem; +} + +input:focus, +input[type=datetime-local]:focus, +textarea:focus, +textarea:focus { + border-color: var(--magenta); +} + +button { + color: white; + background-color: var(--magenta); + font-weight: bold; + border: none; + padding: 0.25rem 0.5rem; + margin-bottom: 1rem; + font-size: 1.25em; + cursor: pointer; +} + +.form-default { + display: flex; + gap: 1rem; + flex-direction: column; + + .form-field { + display: flex; + gap: 0.25rem; + flex-direction: column; + + .checkbox { + display: flex; + gap: 0.5rem; + align-items: center; + align-self: flex-start; + } + } +} + +input[type="radio"], +input[type="checkbox"] { + width: 2rem; + height: 2rem; + vertical-align: middle; + accent-color: var(--magenta); +} + + +.flex-column { + display: flex; + gap: 1rem; + flex-direction: column; +} + +.header-default { + display: flex; + gap: 1rem; + justify-content: space-between; +} + + +.site-nav { + padding: 1rem; + display: flex; + gap: 1rem; + justify-content: space-between; + align-items: baseline; + + .nav-list { + list-style-type: none; + display: flex; + gap: 1rem; + justify-content: space-between; + align-items: baseline; + } +} + +.modal-wrapper, +.modal-bg { + position: fixed; + top: 0; + right: 0; + left: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + background: rgba(0, 0, 0, 0.7); + z-index: 9999; +} + +.modal { + box-sizing: border-box; + overflow-y: scroll; + + background: var(--grey-dark); + margin: auto; + max-height: 100%; + padding: 1rem; + width: calc(72ch + 48px); + border: 0.5rem solid var(--magenta); + + .header { + display: grid; + grid-template-columns: 2fr 1fr; + } + + .modal-close { + justify-self: end; + } + + .modal-container { + display: flex; + flex-direction: column; + } +} + +.error { + color: var(--red); +} + +.banner { + max-width: 1024px; + margin: 0 auto; + background-color: var(--grey-dark); + padding: 2rem; + border-radius: 1rem; + + & h2 { + font-size: 3em; + margin-bottom: 1rem; + } + + & p { + font-size: 1.75em; + } +} \ No newline at end of file diff --git a/assets/css/normalize.css b/assets/css/normalize.css new file mode 100644 index 0000000..f555f14 --- /dev/null +++ b/assets/css/normalize.css @@ -0,0 +1,227 @@ +/*! modern-normalize v3.0.1 | MIT License | https://github.com/sindresorhus/modern-normalize */ + +/* +Document +======== +*/ + +/** +Use a better box model (opinionated). +*/ + +*, +::before, +::after { + box-sizing: border-box; +} + +html { + /* Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3) */ + font-family: + system-ui, + 'Segoe UI', + Roboto, + Helvetica, + Arial, + sans-serif, + 'Apple Color Emoji', + 'Segoe UI Emoji'; + line-height: 1.15; + /* 1. Correct the line height in all browsers. */ + -webkit-text-size-adjust: 100%; + /* 2. Prevent adjustments of font size after orientation changes in iOS. */ + tab-size: 4; + /* 3. Use a more readable tab size (opinionated). */ +} + +/* +Sections +======== +*/ + +body { + margin: 0; + /* Remove the margin in all browsers. */ +} + +/* +Text-level semantics +==================== +*/ + +/** +Add the correct font weight in Chrome and Safari. +*/ + +b, +strong { + font-weight: bolder; +} + +/** +1. Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3) +2. Correct the odd 'em' font sizing in all browsers. +*/ + +code, +kbd, +samp, +pre { + font-family: + ui-monospace, + SFMono-Regular, + Consolas, + 'Liberation Mono', + Menlo, + monospace; + /* 1 */ + font-size: 1em; + /* 2 */ +} + +/** +Add the correct font size in all browsers. +*/ + +small { + font-size: 80%; +} + +/** +Prevent 'sub' and 'sup' elements from affecting the line height in all browsers. +*/ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* +Tabular data +============ +*/ + +/** +Correct table border color inheritance in Chrome and Safari. (https://issues.chromium.org/issues/40615503, https://bugs.webkit.org/show_bug.cgi?id=195016) +*/ + +table { + border-color: currentcolor; +} + +/* +Forms +===== +*/ + +/** +1. Change the font styles in all browsers. +2. Remove the margin in Firefox and Safari. +*/ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + /* 1 */ + font-size: 100%; + /* 1 */ + line-height: 1.15; + /* 1 */ + margin: 0; + /* 2 */ +} + +/** +Correct the inability to style clickable types in iOS and Safari. +*/ + +button, +[type='button'], +[type='reset'], +[type='submit'] { + -webkit-appearance: button; +} + +/** +Remove the padding so developers are not caught out when they zero out 'fieldset' elements in all browsers. +*/ + +legend { + padding: 0; +} + +/** +Add the correct vertical alignment in Chrome and Firefox. +*/ + +progress { + vertical-align: baseline; +} + +/** +Correct the cursor style of increment and decrement buttons in Safari. +*/ + +::-webkit-inner-spin-button, +::-webkit-outer-spin-button { + height: auto; +} + +/** +1. Correct the odd appearance in Chrome and Safari. +2. Correct the outline style in Safari. +*/ + +[type='search'] { + -webkit-appearance: textfield; + /* 1 */ + outline-offset: -2px; + /* 2 */ +} + +/** +Remove the inner padding in Chrome and Safari on macOS. +*/ + +::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** +1. Correct the inability to style clickable types in iOS and Safari. +2. Change font properties to 'inherit' in Safari. +*/ + +::-webkit-file-upload-button { + -webkit-appearance: button; + /* 1 */ + font: inherit; + /* 2 */ +} + +/* +Interactive +=========== +*/ + +/* +Add the correct display in Chrome and Safari. +*/ + +summary { + display: list-item; +} \ No newline at end of file diff --git a/config/runtime.exs b/config/runtime.exs index a0fe2cf..96fc7b6 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -102,16 +102,15 @@ if config_env() == :prod do # In production you need to configure the mailer to use a different adapter. # Also, you may need to configure the Swoosh API client of your choice if you # are not using SMTP. Here is an example of the configuration: - # - # config :send_it, SendIt.Mailer, - # adapter: Swoosh.Adapters.Mailgun, - # api_key: System.get_env("MAILGUN_API_KEY"), - # domain: System.get_env("MAILGUN_DOMAIN") + config :send_it, SendIt.Mailer, + adapter: Swoosh.Adapters.Mailgun, + api_key: System.get_env("MAILGUN_API_KEY"), + domain: System.get_env("MAILGUN_DOMAIN") + # # For this example you need include a HTTP client required by Swoosh API client. # Swoosh supports Hackney and Finch out of the box: - # - # config :swoosh, :api_client, Swoosh.ApiClient.Hackney + config :swoosh, api_client: Swoosh.ApiClient.Finch, finch_name: SendIt.Finch # # See https://hexdocs.pm/swoosh/Swoosh.html#module-installation for details. end diff --git a/lib/send_it/marketing.ex b/lib/send_it/marketing.ex index 7d8e281..6c1882c 100644 --- a/lib/send_it/marketing.ex +++ b/lib/send_it/marketing.ex @@ -21,6 +21,10 @@ defmodule SendIt.Marketing do Repo.all(Contact) end + def list_subscribed_contacts do + Repo.all(from c in Contact, where: [subscribed: true]) + end + @doc """ Gets a single contact. @@ -37,6 +41,8 @@ defmodule SendIt.Marketing do """ def get_contact!(id), do: Repo.get!(Contact, id) + def get_contact_by_email!(email), do: Repo.get_by!(Contact, email: email) + @doc """ Creates a contact. @@ -73,6 +79,18 @@ defmodule SendIt.Marketing do |> Repo.update() end + def unsubscribe(email) do + case get_contact_by_email!(email) do + %Contact{} = contact -> + contact + |> Contact.changeset(%{subscribed: false}) + |> Repo.update() + + {:error, reason} -> + {:error, reason} + end + end + @doc """ Deletes a contact. @@ -208,7 +226,7 @@ defmodule SendIt.Marketing do """ def change_message(%Message{} = message, attrs \\ %{}) do - contacts = list_contacts() + contacts = list_subscribed_contacts() message |> Message.changeset(attrs) diff --git a/lib/send_it_web/components/core_components.ex b/lib/send_it_web/components/core_components.ex index 7944d2e..d976319 100644 --- a/lib/send_it_web/components/core_components.ex +++ b/lib/send_it_web/components/core_components.ex @@ -47,41 +47,32 @@ defmodule SendItWeb.CoreComponents do phx-mounted={@show && show_modal(@id)} phx-remove={hide_modal(@id)} data-cancel={JS.exec(@on_cancel, "phx-remove")} - class="hidden" + class="modal-wrapper" > -