Skip to content

Commit 896c14a

Browse files
committed
Added log
1 parent 3c4c145 commit 896c14a

13 files changed

Lines changed: 344 additions & 60 deletions

File tree

.tool-versions

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
elixir 1.16.3-otp-26
2-
erlang 26.2.5.9
1+
elixir 1.19.5-otp-28
2+
erlang 28.3.1

FEATURES.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Features
2+
3+
- Adding a new todo (optionally shows a desktop notification)
4+
- Marking a todo as completed
5+
- Deleting a todo
6+
- Viewing the completed log
7+
8+
# The Completed Log
9+
10+
- The completed log should be displayed in a list
11+
- The completed log should be sorted by the date it was completed and show the day separator
12+

assets/css/todo.scss

Lines changed: 113 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,54 @@ body.ios {
1616
padding: 1em;
1717
}
1818

19+
.header {
20+
display: flex;
21+
flex-wrap: wrap;
22+
align-items: center;
23+
gap: 0.5em;
24+
25+
h2 {
26+
margin: 0;
27+
flex: 1;
28+
line-height: 1;
29+
display: flex;
30+
align-items: center;
31+
}
32+
33+
form {
34+
display: flex;
35+
align-items: center;
36+
gap: 0.2em;
37+
}
38+
39+
padding-bottom: 1em;
40+
}
41+
42+
.header-actions {
43+
display: flex;
44+
align-items: center;
45+
}
46+
47+
.icon-btn {
48+
display: inline-flex;
49+
align-items: center;
50+
justify-content: center;
51+
background: rgba(255, 255, 255, 0.2);
52+
color: white;
53+
border: 1px solid rgba(255, 255, 255, 0.4);
54+
border-radius: 5px;
55+
padding: 0.35em 0.5em;
56+
font-size: 1.2em;
57+
cursor: pointer;
58+
line-height: 1;
59+
min-height: 2em;
60+
box-sizing: border-box;
61+
62+
&:hover {
63+
background: rgba(255, 255, 255, 0.3);
64+
}
65+
}
66+
1967
h2 {
2068
text-align: left;
2169
color: white;
@@ -32,7 +80,7 @@ input {
3280
font-size: 16px;
3381
}
3482

35-
button {
83+
form button {
3684
padding: 10px;
3785
width: 25%;
3886
border-radius: 5px;
@@ -50,7 +98,7 @@ button {
5098
form {
5199
display: flex;
52100
justify-content: stretch;
53-
padding-bottom: 2em;
101+
align-items: center;
54102
}
55103

56104
ul {
@@ -98,4 +146,67 @@ li {
98146
background-color: #f44336;
99147
color: white;
100148
}
149+
}
150+
151+
.log-view {
152+
h3 {
153+
color: white;
154+
margin-top: 0.5em;
155+
}
156+
}
157+
158+
.back-btn {
159+
background: #555;
160+
color: white;
161+
border: 1px solid #666;
162+
border-radius: 5px;
163+
padding: 8px 12px;
164+
cursor: pointer;
165+
font-size: 14px;
166+
margin-bottom: 1em;
167+
168+
&:hover {
169+
background: #666;
170+
}
171+
}
172+
173+
.log-list {
174+
list-style: none;
175+
padding: 0;
176+
margin: 0;
177+
}
178+
179+
.log-day-separator {
180+
color: white;
181+
font-size: 0.95em;
182+
font-weight: 600;
183+
padding: 0.6em 12px 0.3em;
184+
margin-top: 0.75em;
185+
cursor: default;
186+
background: transparent;
187+
list-style: none;
188+
189+
&:first-child {
190+
margin-top: 0;
191+
}
192+
}
193+
194+
.log-entry {
195+
display: flex;
196+
justify-content: space-between;
197+
align-items: center;
198+
padding: 10px 12px;
199+
background: #f9f9f9;
200+
border-radius: 5px;
201+
margin: 0.3em 0;
202+
cursor: default;
203+
204+
.log-text {
205+
flex: 1;
206+
}
207+
.log-date {
208+
color: #777;
209+
font-size: 0.9em;
210+
margin-left: 1em;
211+
}
101212
}

assets/js/app.js

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,34 @@ import "phoenix_html"
1111
import {Socket} from "phoenix"
1212
import {LiveSocket} from "phoenix_live_view"
1313

14-
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
15-
let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}})
14+
let csrfToken = (function() {
15+
let meta = document.querySelector("meta[name='csrf-token']")
16+
if (!meta) {
17+
console.error("[LiveView] Missing meta[name='csrf-token'] – check your root layout. Clicks will not work.")
18+
return ""
19+
}
20+
let token = meta.getAttribute("content")
21+
if (token == null || token === "") {
22+
console.error("[LiveView] csrf-token meta is empty. Clicks may not work.")
23+
return ""
24+
}
25+
return token
26+
})()
1627

17-
// connect if there are any LiveViews on the page
18-
liveSocket.connect()
28+
let Hooks = {}
29+
try {
30+
let liveSocket = new LiveSocket("/live", Socket, {
31+
params: {_csrf_token: csrfToken},
32+
hooks: Hooks
33+
})
1934

20-
// expose liveSocket on window for web console debug logs and latency simulation:
21-
// >> liveSocket.enableDebug()
22-
// >> liveSocket.enableLatencySim(1000)
23-
window.liveSocket = liveSocket
35+
// connect if there are any LiveViews on the page
36+
liveSocket.connect()
37+
38+
// expose liveSocket on window for web console debug logs and latency simulation:
39+
// >> liveSocket.enableDebug()
40+
// >> liveSocket.enableLatencySim(1000)
41+
window.liveSocket = liveSocket
42+
} catch (err) {
43+
console.error("[LiveView] Failed to start:", err)
44+
}

config/config.exs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,7 @@ config :logger, :console,
2828

2929
# Configures the endpoint
3030
config :todo_app, TodoWeb.Endpoint,
31-
# url: [host: "localhost", port: 10_000 + :rand.uniform(45_000)],
32-
# because of the iOS rebind - this is now a fixed port, but randomly selected
33-
http: [ip: {127, 0, 0, 1}, port: 10_000 + :rand.uniform(45_000)],
31+
http: [ip: {127, 0, 0, 1}, port: 30_979],
3432
adapter: Bandit.PhoenixAdapter,
3533
render_errors: [
3634
formats: [html: TodoWeb.ErrorHTML, json: TodoWeb.ErrorJSON],

config/dev.exs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ config :todo_app, TodoWeb.Endpoint,
1010
debug_errors: true,
1111
code_reloader: true,
1212
check_origin: false,
13+
force_watchers: true,
1314
watchers: [
1415
esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]},
1516
sass:

lib/todo_app/repo.ex

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,13 @@ defmodule TodoApp.Repo do
99
status TEXT
1010
)
1111
""")
12+
13+
Ecto.Adapters.SQL.query!(__MODULE__, """
14+
CREATE TABLE IF NOT EXISTS completed_tasks_log (
15+
id INTEGER PRIMARY KEY AUTOINCREMENT,
16+
text TEXT NOT NULL,
17+
completed_at TEXT NOT NULL
18+
)
19+
""")
1220
end
1321
end

lib/todo_app/todo.ex

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ defmodule TodoApp.Todo do
1414
end
1515

1616
@topic "todos"
17+
@completed_log_topic "completed_tasks_log"
18+
1719
def toggle_todo(id) do
1820
todo = Repo.get(__MODULE__, id)
1921

@@ -23,12 +25,28 @@ defmodule TodoApp.Todo do
2325
"done" -> "todo"
2426
end
2527

28+
if status == "done" do
29+
log_completed_task(todo.text)
30+
end
31+
2632
change(todo, %{status: status})
2733
|> Repo.update()
2834

2935
Phoenix.PubSub.broadcast(TodoApp.PubSub, @topic, :changed)
3036
end
3137

38+
defp log_completed_task(text) do
39+
completed_at = DateTime.utc_now() |> DateTime.to_iso8601()
40+
41+
Ecto.Adapters.SQL.query!(
42+
Repo,
43+
"INSERT INTO completed_tasks_log (text, completed_at) VALUES (?, ?)",
44+
[text, completed_at]
45+
)
46+
47+
Phoenix.PubSub.broadcast(TodoApp.PubSub, @completed_log_topic, :completed_log_changed)
48+
end
49+
3250
def drop_todo(id) do
3351
Repo.get(__MODULE__, id)
3452
|> Repo.delete()
@@ -44,11 +62,30 @@ defmodule TodoApp.Todo do
4462
end
4563

4664
def all_todos() do
47-
order_by(__MODULE__, asc: :id)
65+
order_by(__MODULE__, desc: :id)
4866
|> Repo.all()
4967
end
5068

5169
def subscribe() do
5270
Phoenix.PubSub.subscribe(TodoApp.PubSub, @topic)
5371
end
72+
73+
def list_completed_log() do
74+
{:ok, result} =
75+
Ecto.Adapters.SQL.query(
76+
Repo,
77+
"SELECT id, text, completed_at FROM completed_tasks_log ORDER BY completed_at DESC",
78+
[]
79+
)
80+
81+
rows = result.rows || []
82+
83+
Enum.map(rows, fn [id, text, completed_at] ->
84+
%{id: id, text: text, completed_at: completed_at}
85+
end)
86+
end
87+
88+
def subscribe_completed_log() do
89+
Phoenix.PubSub.subscribe(TodoApp.PubSub, @completed_log_topic)
90+
end
5491
end

lib/todo_web/endpoint.ex

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ defmodule TodoWeb.Endpoint do
4747
plug Plug.Head
4848

4949
plug Plug.Session, @session_options
50-
plug Desktop.Auth
50+
51+
if not code_reloading? do
52+
plug Desktop.Auth
53+
end
54+
5155
plug TodoWeb.Router
5256
end

0 commit comments

Comments
 (0)