tangled
alpha
login
or
join now
yoten.app
/
yoten
17
fork
atom
Yōten: A social tracker for your language learning journey built on the atproto.
17
fork
atom
overview
issues
pulls
pipelines
feat: add notification pages / partials
brookjeynes.dev
6 months ago
9c8f8954
7a892a74
verified
This commit was signed with the committer's
known signature
.
brookjeynes.dev
SSH Key Fingerprint:
SHA256:N3n3PCBSiXfS6EHlmGdx+LMEruJMj6FS2hqaXyfsw0s=
+139
-22
6 changed files
expand all
collapse all
unified
split
internal
server
views
notifications.templ
partials
header.templ
notification-feed.templ
notification.templ
partials.go
views.go
+40
internal/server/views/notifications.templ
···
1
1
+
package views
2
2
+
3
3
+
import (
4
4
+
"yoten.app/internal/server/views/layouts"
5
5
+
"yoten.app/internal/server/views/partials"
6
6
+
)
7
7
+
8
8
+
templ NotificationsPage(params NotificationsPageParams) {
9
9
+
@layouts.Base(layouts.BaseParams{Title: "notifications"}) {
10
10
+
@partials.Header(partials.HeaderProps{User: params.User})
11
11
+
<div class="container mx-auto px-4 py-8 max-w-4xl">
12
12
+
<div class="flex items-center justify-between mb-8">
13
13
+
<div>
14
14
+
<h1 class="text-3xl font-bold">Notifications</h1>
15
15
+
<p class="text-text-muted mt-1">Stay updated with your learning journey</p>
16
16
+
</div>
17
17
+
<div class="flex items-center space-x-2">
18
18
+
if params.User.UnreadNotificationCount > 0 {
19
19
+
<button
20
20
+
class="btn btn-muted"
21
21
+
type="button"
22
22
+
id="mark-all-button"
23
23
+
hx-disabled-elt="#mark-all-button"
24
24
+
hx-post="/notification/mark-all-read"
25
25
+
>
26
26
+
<i class="w-4 h-4" data-lucide="check"></i>
27
27
+
<span>Mark all read</span>
28
28
+
<i class="w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" data-lucide="loader-circle"></i>
29
29
+
</button>
30
30
+
}
31
31
+
</div>
32
32
+
</div>
33
33
+
<div id="study-feed" hx-trigger="load" hx-get="/notification/feed" class="flex flex-col gap-6">
34
34
+
<div class="flex justify-center py-4">
35
35
+
<i data-lucide="loader-circle" class="w-6 h-6 animate-spin text-text-muted"></i>
36
36
+
</div>
37
37
+
</div>
38
38
+
</div>
39
39
+
}
40
40
+
}
+13
-22
internal/server/views/partials/header.templ
···
8
8
<a class="text-xl font-bold" href="/">Yōten <span class="text-sm font-normal italic">alpha</span></a>
9
9
if params.User != nil {
10
10
<div class="flex gap-4">
11
11
-
<div class="flex gap-2 items-center">
12
12
-
<a
13
13
-
href="/profile/activities"
14
14
-
class="text-sm hover:underline"
15
15
-
>
11
11
+
<nav class="flex gap-2 items-center">
12
12
+
<a href="/profile/activities" class="text-sm hover:underline">
16
13
Activities
17
14
</a>
18
18
-
<a
19
19
-
href="/profile/resources"
20
20
-
class="text-sm hover:underline"
21
21
-
>
15
15
+
<a href="/profile/resources" class="text-sm hover:underline">
22
16
Resources
23
17
</a>
24
24
-
<a
25
25
-
href="/stats"
26
26
-
class="text-sm hover:underline"
27
27
-
>
18
18
+
<a href="/stats" class="text-sm hover:underline">
28
19
Stats
29
20
</a>
30
30
-
</div>
21
21
+
<a href="/profile/notifications" class="relative">
22
22
+
<i class="w-5 h-5" data-lucide="bell"></i>
23
23
+
if params.User.UnreadNotificationCount > 0 {
24
24
+
<span class="absolute top-0 right-0 block h-2 w-2 rounded-full bg-red-500"></span>
25
25
+
}
26
26
+
</a>
27
27
+
</nav>
31
28
<details class="relative inline-block text-left">
32
29
<summary class="cursor-pointer list-none">
33
30
if params.User.BskyProfile.Avatar == "" {
···
48
45
<i class="w-4 h-4" data-lucide="user"></i>
49
46
Profile
50
47
</a>
51
51
-
<a
52
52
-
href="/friends"
53
53
-
class="flex items-center px-4 py-2 text-sm hover:bg-bg gap-2"
54
54
-
>
48
48
+
<a href="/friends" class="flex items-center px-4 py-2 text-sm hover:bg-bg gap-2">
55
49
<i class="w-4 h-4" data-lucide="users"></i>
56
50
Friends
57
51
</a>
58
58
-
<a
59
59
-
href="/profile/edit"
60
60
-
class="flex items-center px-4 py-2 text-sm hover:bg-bg gap-2"
61
61
-
>
52
52
+
<a href="/profile/edit" class="flex items-center px-4 py-2 text-sm hover:bg-bg gap-2">
62
53
<i class="w-4 h-4" data-lucide="settings"></i>
63
54
Settings
64
55
</a>
+35
internal/server/views/partials/notification-feed.templ
···
1
1
+
package partials
2
2
+
3
3
+
import "fmt"
4
4
+
5
5
+
templ NotificationFeed(params NotificationFeedProps) {
6
6
+
<div x-init="lucide.createIcons()">
7
7
+
<div id="notification-feed" class="flex flex-col gap-6">
8
8
+
for _, notification := range params.Feed {
9
9
+
@Notification(NotificationProps{
10
10
+
Notification: notification,
11
11
+
})
12
12
+
}
13
13
+
if params.NextPage > 0 {
14
14
+
<div
15
15
+
id="next-notification-segment"
16
16
+
hx-get={ templ.SafeURL(fmt.Sprintf("/notification/feed?page=%d", params.NextPage)) }
17
17
+
hx-trigger="revealed"
18
18
+
hx-swap="outerHTML"
19
19
+
>
20
20
+
<div class="flex justify-center py-4">
21
21
+
<i data-lucide="loader-circle" class="w-6 h-6 animate-spin text-text-muted"></i>
22
22
+
</div>
23
23
+
</div>
24
24
+
}
25
25
+
</div>
26
26
+
if len(params.Feed) == 0 {
27
27
+
<div class="flex flex-col gap-6">
28
28
+
<div class="text-center py-12">
29
29
+
<p class="text-text text-lg">All caught up!</p>
30
30
+
<p class="text-text-muted">You have no new notifications.</p>
31
31
+
</div>
32
32
+
</div>
33
33
+
}
34
34
+
</div>
35
35
+
}
+33
internal/server/views/partials/notification.templ
···
1
1
+
package partials
2
2
+
3
3
+
import "yoten.app/internal/db"
4
4
+
5
5
+
templ Notification(params NotificationProps) {
6
6
+
<div
7
7
+
id={ params.Notification.ID }
8
8
+
class={ "card", templ.KV("border border-primary", params.Notification.State==db.NotificationStateUnread) }
9
9
+
>
10
10
+
switch (params.Notification.Type) {
11
11
+
case db.NotificationTypeFollow:
12
12
+
<div>
13
13
+
<h1 class="font-semibold">New Follower</h1>
14
14
+
<p class="text-sm mt-1">
15
15
+
<a class="hover:underline" href={ templ.SafeURL("/" + params.Notification.ActorDid) }>
16
16
+
{ params.Notification.ActorDid }
17
17
+
</a> started following you
18
18
+
</p>
19
19
+
</div>
20
20
+
case db.NotificationTypeReaction:
21
21
+
<div>
22
22
+
<h1 class="font-semibold">New Reaction</h1>
23
23
+
<p class="text-sm mt-1">
24
24
+
<a class="hover:underline" href={ templ.SafeURL("/" + params.Notification.ActorDid) }>
25
25
+
@{ params.Notification.ActorBskyHandle }
26
26
+
</a> reacted to your study session
27
27
+
</p>
28
28
+
</div>
29
29
+
default:
30
30
+
}
31
31
+
<p class="text-xs text-text-muted">{ params.Notification.CreatedAt.Format("02/01/2006") }</p>
32
32
+
</div>
33
33
+
}
+11
internal/server/views/partials/partials.go
···
207
207
Title string
208
208
Segments []db.DonutChartSegment
209
209
}
210
210
+
211
211
+
type NotificationProps struct {
212
212
+
Notification db.NotificationWithBskyHandle
213
213
+
}
214
214
+
215
215
+
type NotificationFeedProps struct {
216
216
+
// The current logged in user
217
217
+
User *types.User
218
218
+
Feed []db.NotificationWithBskyHandle
219
219
+
NextPage int
220
220
+
}
+7
internal/server/views/views.go
···
118
118
InputOutputPercentage int
119
119
LanguageSummaryChartSegments []db.DonutChartSegment
120
120
}
121
121
+
122
122
+
type NotificationsPageParams struct {
123
123
+
// The current logged in user.
124
124
+
User *types.User
125
125
+
Notifications []db.NotificationWithBskyHandle
126
126
+
ActiveTab string
127
127
+
}