Compare commits
4 commits
24642d7c67
...
61796cf985
Author | SHA1 | Date | |
---|---|---|---|
61796cf985 | |||
dceef941c7 | |||
68d60c120d | |||
6b787297bb |
14 changed files with 95 additions and 184 deletions
16
README.md
16
README.md
|
@ -6,19 +6,19 @@ Written in Elixir & Phoenix LiveView, with Bootstrap v5.
|
|||
|
||||
## TODO
|
||||
|
||||
- [ ] Ability to edit shifts?
|
||||
- [ ] Proper modal to delete shifts?
|
||||
- [ ] Allow all-day items for notes, or require hours even for sick days?
|
||||
- [ ] Implement proper shift/template/assign tests (views etc)
|
||||
- [X] ~~*Proper modal to delete shifts?*~~ [2022-08-14]
|
||||
- [ ] Update tests, which are probably all way out of date. But I also don't care that much for this project...
|
||||
|
||||
## Deploying
|
||||
|
||||
The below notes are old; I'm using a docker build to deploy this now. Will document when I have time.
|
||||
|
||||
### New versions
|
||||
|
||||
When improvements are made, we can update the deployed version like so:
|
||||
|
||||
```shell
|
||||
cd /opt/shift73k
|
||||
cd ${SHIFT73K_BASE_DIR}
|
||||
# update from master
|
||||
/usr/bin/git pull 73k master
|
||||
# fetch prod deps & compile
|
||||
|
@ -27,10 +27,10 @@ MIX_ENV=prod /usr/bin/mix compile
|
|||
# perform any migrations
|
||||
MIX_ENV=prod /usr/bin/mix ecto.migrate
|
||||
# update node packages via package-lock.json
|
||||
/usr/bin/npm --prefix /opt/shift73k/assets/ ci
|
||||
/usr/bin/npm --prefix ./assets/ ci
|
||||
# rebuild static assets:
|
||||
rm -rf /opt/shift73k/priv/static/*
|
||||
/usr/bin/npm --prefix /opt/shift73k/assets/ run deploy
|
||||
rm -rf ./priv/static/*
|
||||
/usr/bin/npm --prefix ./assets/ run build
|
||||
MIX_ENV=prod /usr/bin/mix phx.digest
|
||||
# rebuild release
|
||||
MIX_ENV=prod /usr/bin/mix release --overwrite
|
||||
|
|
12
assets/package-lock.json
generated
12
assets/package-lock.json
generated
|
@ -57,9 +57,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "18.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.2.tgz",
|
||||
"integrity": "sha512-ce7MIiaYWCFv6A83oEultwhBXb22fxwNOQf5DIxWA4WXvDQ7K+L0fbWl/YOfCzlR5B/uFkSnVBhPcOfOECcWvA==",
|
||||
"version": "18.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.3.tgz",
|
||||
"integrity": "sha512-LJgzOEwWuMTBxHzgBR/fhhBOWrvBjvO+zPteUgbbuQi80rYIZHrk1mNbRUqPZqSLP2H7Rwt1EFLL/tNLD1Xx/w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/phoenix": {
|
||||
|
@ -1070,9 +1070,9 @@
|
|||
"peer": true
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "18.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.2.tgz",
|
||||
"integrity": "sha512-ce7MIiaYWCFv6A83oEultwhBXb22fxwNOQf5DIxWA4WXvDQ7K+L0fbWl/YOfCzlR5B/uFkSnVBhPcOfOECcWvA==",
|
||||
"version": "18.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.3.tgz",
|
||||
"integrity": "sha512-LJgzOEwWuMTBxHzgBR/fhhBOWrvBjvO+zPteUgbbuQi80rYIZHrk1mNbRUqPZqSLP2H7Rwt1EFLL/tNLD1Xx/w==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/phoenix": {
|
||||
|
|
|
@ -1,83 +0,0 @@
|
|||
import Config
|
||||
|
||||
# config/runtime.exs is executed for all environments, including
|
||||
# during releases. It is executed after compilation and before the
|
||||
# system starts, so it is typically used to load production configuration
|
||||
# and secrets from environment variables or elsewhere. Do not define
|
||||
# any compile-time configuration in here, as it won't be applied.
|
||||
# The block below contains prod specific runtime configuration.
|
||||
|
||||
# ## Using releases
|
||||
#
|
||||
# If you use `mix release`, you need to explicitly enable the server
|
||||
# by passing the PHX_SERVER=true when you start it:
|
||||
#
|
||||
# PHX_SERVER=true bin/shift73k start
|
||||
#
|
||||
# Alternatively, you can use `mix phx.gen.release` to generate a `bin/server`
|
||||
# script that automatically sets the env var above.
|
||||
if System.get_env("PHX_SERVER") do
|
||||
config :shift73k, Shift73kWeb.Endpoint, server: true
|
||||
end
|
||||
|
||||
if config_env() == :prod do
|
||||
database_url =
|
||||
System.get_env("DATABASE_URL") ||
|
||||
raise """
|
||||
environment variable DATABASE_URL is missing.
|
||||
For example: ecto://USER:PASS@HOST/DATABASE
|
||||
"""
|
||||
|
||||
maybe_ipv6 = if System.get_env("ECTO_IPV6"), do: [:inet6], else: []
|
||||
|
||||
config :shift73k, Shift73k.Repo,
|
||||
# ssl: true,
|
||||
url: database_url,
|
||||
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"),
|
||||
socket_options: maybe_ipv6
|
||||
|
||||
# The secret key base is used to sign/encrypt cookies and other secrets.
|
||||
# A default value is used in config/dev.exs and config/test.exs but you
|
||||
# want to use a different value for prod and you most likely don't want
|
||||
# to check this value into version control, so we use an environment
|
||||
# variable instead.
|
||||
secret_key_base =
|
||||
System.get_env("SECRET_KEY_BASE") ||
|
||||
raise """
|
||||
environment variable SECRET_KEY_BASE is missing.
|
||||
You can generate one by calling: mix phx.gen.secret
|
||||
"""
|
||||
|
||||
host = System.get_env("PHX_HOST") || "example.com"
|
||||
port = String.to_integer(System.get_env("PORT") || "4000")
|
||||
|
||||
config :shift73k, Shift73kWeb.Endpoint,
|
||||
url: [host: host, port: 443, scheme: "https"],
|
||||
http: [
|
||||
# Enable IPv6 and bind on all interfaces.
|
||||
# Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access.
|
||||
# See the documentation on https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html
|
||||
# for details about using IPv6 vs IPv4 and loopback vs public addresses.
|
||||
ip: {0, 0, 0, 0, 0, 0, 0, 0},
|
||||
port: port
|
||||
],
|
||||
secret_key_base: secret_key_base
|
||||
|
||||
# ## Configuring the mailer
|
||||
#
|
||||
# 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 :shift73k, Shift73k.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
|
||||
#
|
||||
# See https://hexdocs.pm/swoosh/Swoosh.html#module-installation for details.
|
||||
end
|
|
@ -105,9 +105,6 @@ defmodule Shift73kWeb do
|
|||
# Import basic rendering functionality (render, render_layout, etc)
|
||||
import Phoenix.View
|
||||
|
||||
# Import SVG Icon helper
|
||||
import Shift73kWeb.IconHelpers
|
||||
|
||||
import Shift73kWeb.ErrorHelpers
|
||||
import Shift73kWeb.Gettext
|
||||
alias Shift73kWeb.Router.Helpers, as: Routes
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
defmodule Shift73kWeb.LiveHelpers do
|
||||
import Phoenix.LiveView
|
||||
import Phoenix.LiveView.Helpers
|
||||
|
||||
alias Shift73k.Accounts
|
||||
alias Shift73k.Accounts.User
|
||||
|
@ -19,27 +18,6 @@ defmodule Shift73kWeb.LiveHelpers do
|
|||
"""
|
||||
def live_okreply(socket), do: {:ok, socket}
|
||||
|
||||
@doc """
|
||||
Renders a component inside the `Shift73kWeb.ModalComponent` component.
|
||||
|
||||
The rendered modal receives a `:return_to` option to properly update
|
||||
the URL when the modal is closed.
|
||||
|
||||
## Examples
|
||||
|
||||
<%= live_modal @socket, Shift73kWeb.PropertyLive.FormComponent,
|
||||
id: @property.id || :new,
|
||||
action: @live_action,
|
||||
property: @property,
|
||||
return_to: Routes.property_index_path(@socket, :index) %>
|
||||
"""
|
||||
def live_modal(socket, component, opts) do
|
||||
modal_opts = [id: :modal, component: component, opts: opts]
|
||||
# dirty little workaround for elixir complaining about socket being unused
|
||||
_socket = socket
|
||||
live_component(socket, Shift73kWeb.ModalComponent, modal_opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Loads default assigns for liveviews
|
||||
"""
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
<%= if @delete_days_shifts do %>
|
||||
<%= live_modal @socket, Shift73kWeb.ShiftAssignLive.DeleteComponent,
|
||||
<.live_component
|
||||
module={Shift73kWeb.ModalComponent}
|
||||
id="modal"
|
||||
component={Shift73kWeb.ShiftAssignLive.DeleteComponent}
|
||||
opts={[
|
||||
id: "delete-days-shifts-#{@current_user.id}",
|
||||
title: "Delete Shifts From Selected Days",
|
||||
delete_days_shifts: @delete_days_shifts,
|
||||
current_user: @current_user
|
||||
%>
|
||||
]}
|
||||
/>
|
||||
<% end %>
|
||||
|
||||
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
<%= if @delete_shift do %>
|
||||
<%= live_modal @socket, Shift73kWeb.ShiftLive.DeleteComponent,
|
||||
<.live_component
|
||||
module={Shift73kWeb.ModalComponent}
|
||||
id="modal"
|
||||
component={Shift73kWeb.ShiftLive.DeleteComponent}
|
||||
opts={[
|
||||
id: @delete_shift.id,
|
||||
title: "Delete Shift Template",
|
||||
delete_shift: @delete_shift %>
|
||||
delete_shift: @delete_shift
|
||||
]}
|
||||
/>
|
||||
<% end %>
|
||||
|
||||
|
||||
|
|
|
@ -1,17 +1,29 @@
|
|||
<%= if @live_action in [:new, :edit, :clone] do %>
|
||||
<%= live_modal @socket, Shift73kWeb.ShiftTemplateLive.FormComponent,
|
||||
id: @shift_template.id || :new,
|
||||
title: @page_title,
|
||||
action: @live_action,
|
||||
shift_template: @shift_template,
|
||||
current_user: @current_user %>
|
||||
<.live_component
|
||||
module={Shift73kWeb.ModalComponent}
|
||||
id="modal"
|
||||
component={Shift73kWeb.ShiftTemplateLive.FormComponent}
|
||||
opts={[
|
||||
id: @shift_template.id || :new,
|
||||
title: @page_title,
|
||||
action: @live_action,
|
||||
shift_template: @shift_template,
|
||||
current_user: @current_user
|
||||
]}
|
||||
/>
|
||||
<% end %>
|
||||
|
||||
<%= if @delete_shift_template do %>
|
||||
<%= live_modal @socket, Shift73kWeb.ShiftTemplateLive.DeleteComponent,
|
||||
<.live_component
|
||||
module={Shift73kWeb.ModalComponent}
|
||||
id="modal"
|
||||
component={Shift73kWeb.ShiftTemplateLive.DeleteComponent}
|
||||
opts={[
|
||||
id: @delete_shift_template.id,
|
||||
title: "Delete Shift Template",
|
||||
delete_shift_template: @delete_shift_template %>
|
||||
delete_shift_template: @delete_shift_template
|
||||
]}
|
||||
/>
|
||||
<% end %>
|
||||
|
||||
|
||||
|
|
|
@ -6,10 +6,10 @@
|
|||
</h2>
|
||||
|
||||
<div class="row justify-content-center justify-content-md-start">
|
||||
<%= live_component @socket, Shift73kWeb.UserLive.Settings.Email, id: "email-#{@current_user.id}", current_user: @current_user %>
|
||||
<%= live_component @socket, Shift73kWeb.UserLive.Settings.Password, id: "password-#{@current_user.id}", current_user: @current_user %>
|
||||
<%= live_component @socket, Shift73kWeb.UserLive.Settings.WeekStart, id: "week_start-#{@current_user.id}", current_user: @current_user %>
|
||||
<%= live_component @socket, Shift73kWeb.UserLive.Settings.CalendarUrl, id: "calendar_url-#{@current_user.id}", current_user: @current_user %>
|
||||
<.live_component module={Shift73kWeb.UserLive.Settings.Email} id={"email-#{@current_user.id}"} current_user={@current_user} />
|
||||
<.live_component module={Shift73kWeb.UserLive.Settings.Password} id={"password-#{@current_user.id}"} current_user={@current_user} />
|
||||
<.live_component module={Shift73kWeb.UserLive.Settings.WeekStart} id={"week_start-#{@current_user.id}"} current_user={@current_user} />
|
||||
<.live_component module={Shift73kWeb.UserLive.Settings.CalendarUrl} id={"calendar_url-#{@current_user.id}"} current_user={@current_user} />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
@ -9,9 +9,13 @@ defmodule Shift73kWeb.UserManagement.DeleteComponent do
|
|||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("confirm", %{"id" => id, "email" => email}, socket) do
|
||||
id
|
||||
|> Accounts.get_user()
|
||||
def handle_event("confirm", %{"id" => id, "email" => email} = params, socket) do
|
||||
IO.inspect(params)
|
||||
|
||||
user = Accounts.get_user(id)
|
||||
IO.inspect(user)
|
||||
|
||||
user
|
||||
|> Accounts.delete_user()
|
||||
|> case do
|
||||
{:ok, _} ->
|
||||
|
|
|
@ -1,18 +1,29 @@
|
|||
<%= if @live_action in [:new, :edit] do %>
|
||||
<%= live_modal @socket, Shift73kWeb.UserManagement.FormComponent,
|
||||
<.live_component
|
||||
module={Shift73kWeb.ModalComponent}
|
||||
id="modal"
|
||||
component={Shift73kWeb.UserManagement.FormComponent}
|
||||
opts={[
|
||||
id: @user.id || :new,
|
||||
title: @page_title,
|
||||
action: @live_action,
|
||||
user: @user,
|
||||
current_user: @current_user %>
|
||||
current_user: @current_user
|
||||
]}
|
||||
/>
|
||||
<% end %>
|
||||
|
||||
<%= if @delete_user do %>
|
||||
<%= live_modal @socket, Shift73kWeb.UserManagement.DeleteComponent,
|
||||
<.live_component
|
||||
module={Shift73kWeb.ModalComponent}
|
||||
id="modal"
|
||||
component={Shift73kWeb.UserManagement.DeleteComponent}
|
||||
opts={[
|
||||
id: @delete_user.id,
|
||||
title: "Delete User",
|
||||
delete_user: @delete_user
|
||||
%>
|
||||
]}
|
||||
/>
|
||||
<% end %>
|
||||
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
AND:
|
||||
There are no users -- [REGISTER]
|
||||
OR no registration allowed -- [LOG IN] %>
|
||||
<%= else %>
|
||||
<% else %>
|
||||
|
||||
<%= if !Repo.exists?(User) || allow_registration() do %>
|
||||
<%= link nav_link_opts(@conn, to: Routes.user_registration_path(@conn, :new), class: "btn btn-outline-light") do %>
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
defmodule Shift73kWeb.IconHelpers do
|
||||
@moduledoc """
|
||||
Generate SVG sprite use tags for SVG icons
|
||||
"""
|
||||
|
||||
use Phoenix.HTML
|
||||
alias Shift73kWeb.Router.Helpers, as: Routes
|
||||
|
||||
def icon_div(conn, name, div_opts \\ [], svg_opts \\ []) do
|
||||
content_tag(:div, tag_opts(name, div_opts)) do
|
||||
icon_svg(conn, name, svg_opts)
|
||||
end
|
||||
end
|
||||
|
||||
def icon_svg(conn, name, opts \\ []) do
|
||||
opts = aria_hidden?(opts)
|
||||
|
||||
content_tag(:svg, tag_opts(name, opts)) do
|
||||
~E"""
|
||||
<%= if title = Keyword.get(opts, :aria_label), do: content_tag(:title, title) %>
|
||||
<%= tag(:use, "xlink:href": Routes.static_path(conn, "/images/icons.svg##{name}")) %>
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
||||
defp tag_opts(name, opts) do
|
||||
Keyword.update(opts, :class, name, fn c -> "#{c} #{name}" end)
|
||||
end
|
||||
|
||||
defp aria_hidden?(opts) do
|
||||
case Keyword.get(opts, :aria_hidden) do
|
||||
"false" -> Keyword.drop(opts, [:aria_hidden])
|
||||
false -> Keyword.drop(opts, [:aria_hidden])
|
||||
"true" -> opts
|
||||
_ -> Keyword.put(opts, :aria_hidden, "true")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,19 @@
|
|||
defmodule Shift73k.Repo.Migrations.FixShiftsUserIdReference do
|
||||
use Ecto.Migration
|
||||
|
||||
def up do
|
||||
execute("ALTER TABLE shifts DROP CONSTRAINT shifts_user_id_fkey")
|
||||
|
||||
alter table(:shifts) do
|
||||
modify :user_id, references(:users, on_delete: :delete_all, type: :binary_id)
|
||||
end
|
||||
end
|
||||
|
||||
def down do
|
||||
execute("ALTER TABLE shifts DROP CONSTRAINT shifts_user_id_fkey")
|
||||
|
||||
alter table(:shifts) do
|
||||
modify :user_id, references(:users, on_delete: :nothing, type: :binary_id)
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue