appview/pages: notifications ui and templating #595

merged
opened by anirudh.fi targeting master from push-xwotmtuuvokm
Changed files
+498 -15
appview
pages
templates
errors
layouts
fragments
notifications
strings
user
+40
appview/pages/pages.go
··· 296 296 return p.execute("user/settings/profile", w, params) 297 297 } 298 298 299 + type NotificationsParams struct { 300 + LoggedInUser *oauth.User 301 + Notifications []*models.NotificationWithEntity 302 + UnreadCount int 303 + HasMore bool 304 + NextOffset int 305 + Limit int 306 + } 307 + 308 + func (p *Pages) Notifications(w io.Writer, params NotificationsParams) error { 309 + return p.execute("notifications/list", w, params) 310 + } 311 + 312 + type NotificationItemParams struct { 313 + Notification *models.Notification 314 + } 315 + 316 + func (p *Pages) NotificationItem(w io.Writer, params NotificationItemParams) error { 317 + return p.executePlain("notifications/fragments/item", w, params) 318 + } 319 + 320 + type NotificationCountParams struct { 321 + Count int 322 + } 323 + 324 + func (p *Pages) NotificationCount(w io.Writer, params NotificationCountParams) error { 325 + return p.executePlain("notifications/fragments/count", w, params) 326 + } 327 + 299 328 type UserKeysSettingsParams struct { 300 329 LoggedInUser *oauth.User 301 330 PubKeys []models.PublicKey ··· 318 347 return p.execute("user/settings/emails", w, params) 319 348 } 320 349 350 + type UserNotificationSettingsParams struct { 351 + LoggedInUser *oauth.User 352 + Preferences *models.NotificationPreferences 353 + Tabs []map[string]any 354 + Tab string 355 + } 356 + 357 + func (p *Pages) UserNotificationSettings(w io.Writer, params UserNotificationSettingsParams) error { 358 + return p.execute("user/settings/notifications", w, params) 359 + } 360 + 321 361 type UpgradeBannerParams struct { 322 362 Registrations []models.Registration 323 363 Spindles []models.Spindle
+4 -11
appview/pages/templates/errors/500.html
··· 5 5 <div class="bg-white dark:bg-gray-800 rounded-lg drop-shadow-sm p-8 max-w-lg mx-auto"> 6 6 <div class="mb-6"> 7 7 <div class="w-16 h-16 mx-auto mb-4 rounded-full bg-red-100 dark:bg-red-900/30 flex items-center justify-center"> 8 - {{ i "alert-triangle" "w-8 h-8 text-red-500 dark:text-red-400" }} 8 + {{ i "triangle-alert" "w-8 h-8 text-red-500 dark:text-red-400" }} 9 9 </div> 10 10 </div> 11 11 ··· 14 14 500 &mdash; internal server error 15 15 </h1> 16 16 <p class="text-gray-600 dark:text-gray-300"> 17 - Something went wrong on our end. We've been notified and are working to fix the issue. 18 - </p> 19 - <div class="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded p-3 text-sm text-yellow-800 dark:text-yellow-200"> 20 - <div class="flex items-center gap-2"> 21 - {{ i "info" "w-4 h-4" }} 22 - <span class="font-medium">we're on it!</span> 23 - </div> 24 - <p class="mt-1">Our team has been automatically notified about this error.</p> 25 - </div> 17 + We encountered an error while processing your request. Please try again later. 18 + </p> 26 19 <div class="flex flex-col sm:flex-row gap-3 justify-center items-center mt-6"> 27 20 <button onclick="location.reload()" class="btn-create gap-2"> 28 21 {{ i "refresh-cw" "w-4 h-4" }} 29 22 try again 30 23 </button> 31 24 <a href="/" class="btn no-underline hover:no-underline gap-2"> 32 - {{ i "home" "w-4 h-4" }} 25 + {{ i "arrow-left" "w-4 h-4" }} 33 26 back to home 34 27 </a> 35 28 </div>
+2 -1
appview/pages/templates/layouts/fragments/topbar.html
··· 10 10 <div id="right-items" class="flex items-center gap-2"> 11 11 {{ with .LoggedInUser }} 12 12 {{ block "newButton" . }} {{ end }} 13 + {{ template "notifications/fragments/bell" }} 13 14 {{ block "dropDown" . }} {{ end }} 14 15 {{ else }} 15 16 <a href="/login">login</a> ··· 44 45 {{ define "dropDown" }} 45 46 <details class="relative inline-block text-left nav-dropdown"> 46 47 <summary 47 - class="cursor-pointer list-none flex items-center" 48 + class="cursor-pointer list-none flex items-center gap-1" 48 49 > 49 50 {{ $user := didOrHandle .Did .Handle }} 50 51 {{ template "user/fragments/picHandle" $user }}
+11
appview/pages/templates/notifications/fragments/bell.html
··· 1 + {{define "notifications/fragments/bell"}} 2 + <div class="relative" 3 + hx-get="/notifications/count" 4 + hx-target="#notification-count" 5 + hx-trigger="load, every 30s"> 6 + <a href="/notifications" class="text-gray-500 dark:text-gray-400 flex gap-1 items-center group ml-4 mr-2"> 7 + {{ i "bell" "w-5 h-5" }} 8 + <span id="notification-count"></span> 9 + </a> 10 + </div> 11 + {{end}}
+7
appview/pages/templates/notifications/fragments/count.html
··· 1 + {{define "notifications/fragments/count"}} 2 + {{if and .Count (gt .Count 0)}} 3 + <span class="absolute -top-1 -right-0.5 min-w-[16px] h-[16px] px-1 bg-red-500 text-white text-xs font-medium rounded-full flex items-center justify-center"> 4 + {{if gt .Count 99}}99+{{else}}{{.Count}}{{end}} 5 + </span> 6 + {{end}} 7 + {{end}}
+212
appview/pages/templates/notifications/fragments/item.html
··· 1 + {{define "notifications/fragments/item"}} 2 + <div class="border border-gray-200 dark:border-gray-700 rounded-sm p-3 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors {{if not .Read}}bg-blue-50 dark:bg-blue-900/20{{end}}"> 3 + {{if .Issue}} 4 + {{template "issueNotification" .}} 5 + {{else if .Pull}} 6 + {{template "pullNotification" .}} 7 + {{else if .Repo}} 8 + {{template "repoNotification" .}} 9 + {{else if eq .Type "followed"}} 10 + {{template "followNotification" .}} 11 + {{else}} 12 + {{template "genericNotification" .}} 13 + {{end}} 14 + </div> 15 + {{end}} 16 + 17 + {{define "issueNotification"}} 18 + {{$url := printf "/%s/%s/issues/%d" (resolve .Repo.Did) .Repo.Name .Issue.IssueId}} 19 + <a 20 + href="{{$url}}" 21 + class="block no-underline hover:no-underline text-inherit -m-3 p-3" 22 + > 23 + <div class="flex items-center justify-between"> 24 + <div class="min-w-0 flex-1"> 25 + <!-- First line: icon + actor action --> 26 + <div class="flex items-center gap-2 text-gray-900 dark:text-white"> 27 + {{if eq .Type "issue_created"}} 28 + <span class="text-green-600 dark:text-green-500"> 29 + {{ i "circle-dot" "w-4 h-4" }} 30 + </span> 31 + {{else if eq .Type "issue_commented"}} 32 + <span class="text-gray-500 dark:text-gray-400"> 33 + {{ i "message-circle" "w-4 h-4" }} 34 + </span> 35 + {{else if eq .Type "issue_closed"}} 36 + <span class="text-gray-500 dark:text-gray-400"> 37 + {{ i "ban" "w-4 h-4" }} 38 + </span> 39 + {{end}} 40 + {{template "user/fragments/picHandle" (resolve .ActorDid)}} 41 + {{if eq .Type "issue_created"}} 42 + <span class="text-gray-500 dark:text-gray-400">opened issue</span> 43 + {{else if eq .Type "issue_commented"}} 44 + <span class="text-gray-500 dark:text-gray-400">commented on issue</span> 45 + {{else if eq .Type "issue_closed"}} 46 + <span class="text-gray-500 dark:text-gray-400">closed issue</span> 47 + {{end}} 48 + {{if not .Read}} 49 + <div class="w-2 h-2 bg-blue-500 rounded-full flex-shrink-0 ml-1"></div> 50 + {{end}} 51 + </div> 52 + 53 + <div class="text-sm text-gray-600 dark:text-gray-400 mt-0.5 ml-6 flex items-center gap-1"> 54 + <span class="text-gray-500 dark:text-gray-400">#{{.Issue.IssueId}}</span> 55 + <span class="text-gray-900 dark:text-white truncate">{{.Issue.Title}}</span> 56 + <span>on</span> 57 + <span class="font-medium text-gray-900 dark:text-white">{{resolve .Repo.Did}}/{{.Repo.Name}}</span> 58 + </div> 59 + </div> 60 + 61 + <div class="text-xs text-gray-500 dark:text-gray-400 flex-shrink-0 ml-2"> 62 + {{ template "repo/fragments/time" .Created }} 63 + </div> 64 + </div> 65 + </a> 66 + {{end}} 67 + 68 + {{define "pullNotification"}} 69 + {{$url := printf "/%s/%s/pulls/%d" (resolve .Repo.Did) .Repo.Name .Pull.PullId}} 70 + <a 71 + href="{{$url}}" 72 + class="block no-underline hover:no-underline text-inherit -m-3 p-3" 73 + > 74 + <div class="flex items-center justify-between"> 75 + <div class="min-w-0 flex-1"> 76 + <div class="flex items-center gap-2 text-gray-900 dark:text-white"> 77 + {{if eq .Type "pull_created"}} 78 + <span class="text-green-600 dark:text-green-500"> 79 + {{ i "git-pull-request-create" "w-4 h-4" }} 80 + </span> 81 + {{else if eq .Type "pull_commented"}} 82 + <span class="text-gray-500 dark:text-gray-400"> 83 + {{ i "message-circle" "w-4 h-4" }} 84 + </span> 85 + {{else if eq .Type "pull_merged"}} 86 + <span class="text-purple-600 dark:text-purple-500"> 87 + {{ i "git-merge" "w-4 h-4" }} 88 + </span> 89 + {{else if eq .Type "pull_closed"}} 90 + <span class="text-red-600 dark:text-red-500"> 91 + {{ i "git-pull-request-closed" "w-4 h-4" }} 92 + </span> 93 + {{end}} 94 + {{template "user/fragments/picHandle" (resolve .ActorDid)}} 95 + {{if eq .Type "pull_created"}} 96 + <span class="text-gray-500 dark:text-gray-400">opened pull request</span> 97 + {{else if eq .Type "pull_commented"}} 98 + <span class="text-gray-500 dark:text-gray-400">commented on pull request</span> 99 + {{else if eq .Type "pull_merged"}} 100 + <span class="text-gray-500 dark:text-gray-400">merged pull request</span> 101 + {{else if eq .Type "pull_closed"}} 102 + <span class="text-gray-500 dark:text-gray-400">closed pull request</span> 103 + {{end}} 104 + {{if not .Read}} 105 + <div class="w-2 h-2 bg-blue-500 rounded-full flex-shrink-0 ml-1"></div> 106 + {{end}} 107 + </div> 108 + 109 + <div class="text-sm text-gray-600 dark:text-gray-400 mt-0.5 ml-6 flex items-center gap-1"> 110 + <span class="text-gray-500 dark:text-gray-400">#{{.Pull.PullId}}</span> 111 + <span class="text-gray-900 dark:text-white truncate">{{.Pull.Title}}</span> 112 + <span>on</span> 113 + <span class="font-medium text-gray-900 dark:text-white">{{resolve .Repo.Did}}/{{.Repo.Name}}</span> 114 + </div> 115 + </div> 116 + 117 + <div class="text-xs text-gray-500 dark:text-gray-400 flex-shrink-0 ml-2"> 118 + {{ template "repo/fragments/time" .Created }} 119 + </div> 120 + </div> 121 + </a> 122 + {{end}} 123 + 124 + {{define "repoNotification"}} 125 + {{$url := printf "/%s/%s" (resolve .Repo.Did) .Repo.Name}} 126 + <a 127 + href="{{$url}}" 128 + class="block no-underline hover:no-underline text-inherit -m-3 p-3" 129 + > 130 + <div class="flex items-center justify-between"> 131 + <div class="flex items-center gap-2 min-w-0 flex-1"> 132 + <span class="text-yellow-500 dark:text-yellow-400"> 133 + {{ i "star" "w-4 h-4" }} 134 + </span> 135 + 136 + <div class="min-w-0 flex-1"> 137 + <!-- Single line for stars: actor action subject --> 138 + <div class="flex items-center gap-1 text-gray-900 dark:text-white"> 139 + {{template "user/fragments/picHandle" (resolve .ActorDid)}} 140 + <span class="text-gray-500 dark:text-gray-400">starred</span> 141 + <span class="font-medium">{{resolve .Repo.Did}}/{{.Repo.Name}}</span> 142 + {{if not .Read}} 143 + <div class="w-2 h-2 bg-blue-500 rounded-full flex-shrink-0 ml-1"></div> 144 + {{end}} 145 + </div> 146 + </div> 147 + </div> 148 + 149 + <div class="text-xs text-gray-500 dark:text-gray-400 flex-shrink-0 ml-2"> 150 + {{ template "repo/fragments/time" .Created }} 151 + </div> 152 + </div> 153 + </a> 154 + {{end}} 155 + 156 + {{define "followNotification"}} 157 + {{$url := printf "/%s" (resolve .ActorDid)}} 158 + <a 159 + href="{{$url}}" 160 + class="block no-underline hover:no-underline text-inherit -m-3 p-3" 161 + > 162 + <div class="flex items-center justify-between"> 163 + <div class="flex items-center gap-2 min-w-0 flex-1"> 164 + <span class="text-blue-600 dark:text-blue-400"> 165 + {{ i "user-plus" "w-4 h-4" }} 166 + </span> 167 + 168 + <div class="min-w-0 flex-1"> 169 + <div class="flex items-center gap-1 text-gray-900 dark:text-white"> 170 + {{template "user/fragments/picHandle" (resolve .ActorDid)}} 171 + <span class="text-gray-500 dark:text-gray-400">followed you</span> 172 + {{if not .Read}} 173 + <div class="w-2 h-2 bg-blue-500 rounded-full flex-shrink-0 ml-1"></div> 174 + {{end}} 175 + </div> 176 + </div> 177 + </div> 178 + 179 + <div class="text-xs text-gray-500 dark:text-gray-400 flex-shrink-0 ml-2"> 180 + {{ template "repo/fragments/time" .Created }} 181 + </div> 182 + </div> 183 + </a> 184 + {{end}} 185 + 186 + {{define "genericNotification"}} 187 + <a 188 + href="#" 189 + class="block no-underline hover:no-underline text-inherit -m-3 p-3" 190 + > 191 + <div class="flex items-center justify-between"> 192 + <div class="flex items-center gap-2 min-w-0 flex-1"> 193 + <span class="{{if not .Read}}text-blue-600 dark:text-blue-400{{else}}text-gray-500 dark:text-gray-400{{end}}"> 194 + {{ i "bell" "w-4 h-4" }} 195 + </span> 196 + 197 + <div class="min-w-0 flex-1"> 198 + <div class="flex items-center gap-1 text-gray-900 dark:text-white"> 199 + <span>New notification</span> 200 + {{if not .Read}} 201 + <div class="w-2 h-2 bg-blue-500 rounded-full flex-shrink-0 ml-1"></div> 202 + {{end}} 203 + </div> 204 + </div> 205 + </div> 206 + 207 + <div class="text-xs text-gray-500 dark:text-gray-400 flex-shrink-0 ml-2"> 208 + {{ template "repo/fragments/time" .Created }} 209 + </div> 210 + </div> 211 + </a> 212 + {{end}}
+46
appview/pages/templates/notifications/list.html
··· 1 + {{ define "title" }}notifications{{ end }} 2 + 3 + {{ define "content" }} 4 + <div class="p-6"> 5 + <div class="flex items-center justify-between mb-4"> 6 + <p class="text-xl font-bold dark:text-white">Notifications</p> 7 + <a href="/settings/notifications" class="flex items-center gap-2"> 8 + {{ i "settings" "w-4 h-4" }} 9 + preferences 10 + </a> 11 + </div> 12 + </div> 13 + 14 + <div class="bg-white dark:bg-gray-800 p-6 rounded relative w-full mx-auto drop-shadow-sm dark:text-white"> 15 + {{if .Notifications}} 16 + <div class="flex flex-col gap-4" id="notifications-list"> 17 + {{range .Notifications}} 18 + {{template "notifications/fragments/item" .}} 19 + {{end}} 20 + </div> 21 + 22 + {{if .HasMore}} 23 + <div class="mt-6 text-center"> 24 + <button 25 + class="btn gap-2 group" 26 + hx-get="/notifications?offset={{.NextOffset}}&limit={{.Limit}}" 27 + hx-target="#notifications-list" 28 + hx-swap="beforeend" 29 + > 30 + {{ i "chevron-down" "w-4 h-4 group-[.htmx-request]:hidden" }} 31 + {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 32 + Load more 33 + </button> 34 + </div> 35 + {{end}} 36 + {{else}} 37 + <div class="text-center py-12"> 38 + <div class="w-16 h-16 mx-auto mb-4 text-gray-300 dark:text-gray-600"> 39 + {{ i "bell-off" "w-16 h-16" }} 40 + </div> 41 + <h3 class="text-lg font-medium text-gray-900 dark:text-white mb-2">No notifications</h3> 42 + <p class="text-gray-600 dark:text-gray-400">When you receive notifications, they'll appear here.</p> 43 + </div> 44 + {{end}} 45 + </div> 46 + {{ end }}
+1 -1
appview/pages/templates/strings/timeline.html
··· 44 44 {{ $stat := .Stats }} 45 45 {{ $resolved := resolve .Did.String }} 46 46 <div class="text-gray-400 pt-4 text-sm font-mono inline-flex items-center gap-2 mt-auto"> 47 - <a href="/strings/{{ $resolved }}" class="flex items-center"> 47 + <a href="/strings/{{ $resolved }}" class="flex items-center gap-1"> 48 48 {{ template "user/fragments/picHandle" $resolved }} 49 49 </a> 50 50 <span class="select-none [&:before]:content-['·']"></span>
+1 -1
appview/pages/templates/user/fragments/picHandle.html
··· 2 2 <img 3 3 src="{{ tinyAvatar . }}" 4 4 alt="" 5 - class="rounded-full h-6 w-6 mr-1 border border-gray-300 dark:border-gray-700" 5 + class="rounded-full h-6 w-6 border border-gray-300 dark:border-gray-700" 6 6 /> 7 7 {{ . | truncateAt30 }} 8 8 {{ end }}
+1 -1
appview/pages/templates/user/fragments/picHandleLink.html
··· 1 1 {{ define "user/fragments/picHandleLink" }} 2 2 {{ $resolved := resolve . }} 3 - <a href="/{{ $resolved }}" class="flex items-center"> 3 + <a href="/{{ $resolved }}" class="flex items-center gap-1"> 4 4 {{ template "user/fragments/picHandle" $resolved }} 5 5 </a> 6 6 {{ end }}
+173
appview/pages/templates/user/settings/notifications.html
··· 1 + {{ define "title" }}{{ .Tab }} settings{{ end }} 2 + 3 + {{ define "content" }} 4 + <div class="p-6"> 5 + <p class="text-xl font-bold dark:text-white">Settings</p> 6 + </div> 7 + <div class="bg-white dark:bg-gray-800 p-6 rounded relative w-full mx-auto drop-shadow-sm dark:text-white"> 8 + <section class="w-full grid grid-cols-1 md:grid-cols-4 gap-6"> 9 + <div class="col-span-1"> 10 + {{ template "user/settings/fragments/sidebar" . }} 11 + </div> 12 + <div class="col-span-1 md:col-span-3 flex flex-col gap-6"> 13 + {{ template "notificationSettings" . }} 14 + </div> 15 + </section> 16 + </div> 17 + {{ end }} 18 + 19 + {{ define "notificationSettings" }} 20 + <div class="grid grid-cols-1 md:grid-cols-3 gap-4 items-center"> 21 + <div class="col-span-1 md:col-span-2"> 22 + <h2 class="text-sm pb-2 uppercase font-bold">Notification Preferences</h2> 23 + <p class="text-gray-500 dark:text-gray-400"> 24 + Choose which notifications you want to receive when activity happens on your repositories and profile. 25 + </p> 26 + </div> 27 + </div> 28 + 29 + <form hx-put="/settings/notifications" hx-swap="none" class="flex flex-col gap-6"> 30 + 31 + <div class="flex flex-col rounded border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700 w-full"> 32 + <div class="flex items-center justify-between p-2"> 33 + <div class="flex items-center gap-2"> 34 + <div class="flex flex-col gap-1"> 35 + <span class="font-bold">Repository starred</span> 36 + <div class="flex text-sm items-center gap-1 text-gray-500 dark:text-gray-400"> 37 + <span>When someone stars your repository.</span> 38 + </div> 39 + </div> 40 + </div> 41 + <label class="flex items-center gap-2"> 42 + <input type="checkbox" name="repo_starred" {{if .Preferences.RepoStarred}}checked{{end}}> 43 + </label> 44 + </div> 45 + 46 + <div class="flex items-center justify-between p-2"> 47 + <div class="flex items-center gap-2"> 48 + <div class="flex flex-col gap-1"> 49 + <span class="font-bold">New issues</span> 50 + <div class="flex text-sm items-center gap-1 text-gray-500 dark:text-gray-400"> 51 + <span>When someone creates an issue on your repository.</span> 52 + </div> 53 + </div> 54 + </div> 55 + <label class="flex items-center gap-2"> 56 + <input type="checkbox" name="issue_created" {{if .Preferences.IssueCreated}}checked{{end}}> 57 + </label> 58 + </div> 59 + 60 + <div class="flex items-center justify-between p-2"> 61 + <div class="flex items-center gap-2"> 62 + <div class="flex flex-col gap-1"> 63 + <span class="font-bold">Issue comments</span> 64 + <div class="flex text-sm items-center gap-1 text-gray-500 dark:text-gray-400"> 65 + <span>When someone comments on an issue you're involved with.</span> 66 + </div> 67 + </div> 68 + </div> 69 + <label class="flex items-center gap-2"> 70 + <input type="checkbox" name="issue_commented" {{if .Preferences.IssueCommented}}checked{{end}}> 71 + </label> 72 + </div> 73 + 74 + <div class="flex items-center justify-between p-2"> 75 + <div class="flex items-center gap-2"> 76 + <div class="flex flex-col gap-1"> 77 + <span class="font-bold">Issue closed</span> 78 + <div class="flex text-sm items-center gap-1 text-gray-500 dark:text-gray-400"> 79 + <span>When an issue on your repository is closed.</span> 80 + </div> 81 + </div> 82 + </div> 83 + <label class="flex items-center gap-2"> 84 + <input type="checkbox" name="issue_closed" {{if .Preferences.IssueClosed}}checked{{end}}> 85 + </label> 86 + </div> 87 + 88 + <div class="flex items-center justify-between p-2"> 89 + <div class="flex items-center gap-2"> 90 + <div class="flex flex-col gap-1"> 91 + <span class="font-bold">New pull requests</span> 92 + <div class="flex text-sm items-center gap-1 text-gray-500 dark:text-gray-400"> 93 + <span>When someone creates a pull request on your repository.</span> 94 + </div> 95 + </div> 96 + </div> 97 + <label class="flex items-center gap-2"> 98 + <input type="checkbox" name="pull_created" {{if .Preferences.PullCreated}}checked{{end}}> 99 + </label> 100 + </div> 101 + 102 + <div class="flex items-center justify-between p-2"> 103 + <div class="flex items-center gap-2"> 104 + <div class="flex flex-col gap-1"> 105 + <span class="font-bold">Pull request comments</span> 106 + <div class="flex text-sm items-center gap-1 text-gray-500 dark:text-gray-400"> 107 + <span>When someone comments on a pull request you're involved with.</span> 108 + </div> 109 + </div> 110 + </div> 111 + <label class="flex items-center gap-2"> 112 + <input type="checkbox" name="pull_commented" {{if .Preferences.PullCommented}}checked{{end}}> 113 + </label> 114 + </div> 115 + 116 + <div class="flex items-center justify-between p-2"> 117 + <div class="flex items-center gap-2"> 118 + <div class="flex flex-col gap-1"> 119 + <span class="font-bold">Pull request merged</span> 120 + <div class="flex text-sm items-center gap-1 text-gray-500 dark:text-gray-400"> 121 + <span>When your pull request is merged.</span> 122 + </div> 123 + </div> 124 + </div> 125 + <label class="flex items-center gap-2"> 126 + <input type="checkbox" name="pull_merged" {{if .Preferences.PullMerged}}checked{{end}}> 127 + </label> 128 + </div> 129 + 130 + <div class="flex items-center justify-between p-2"> 131 + <div class="flex items-center gap-2"> 132 + <div class="flex flex-col gap-1"> 133 + <span class="font-bold">New followers</span> 134 + <div class="flex text-sm items-center gap-1 text-gray-500 dark:text-gray-400"> 135 + <span>When someone follows you.</span> 136 + </div> 137 + </div> 138 + </div> 139 + <label class="flex items-center gap-2"> 140 + <input type="checkbox" name="followed" {{if .Preferences.Followed}}checked{{end}}> 141 + </label> 142 + </div> 143 + 144 + <div class="flex items-center justify-between p-2"> 145 + <div class="flex items-center gap-2"> 146 + <div class="flex flex-col gap-1"> 147 + <span class="font-bold">Email notifications</span> 148 + <div class="flex text-sm items-center gap-1 text-gray-500 dark:text-gray-400"> 149 + <span>Receive notifications via email in addition to in-app notifications.</span> 150 + </div> 151 + </div> 152 + </div> 153 + <label class="flex items-center gap-2"> 154 + <input type="checkbox" name="email_notifications" {{if .Preferences.EmailNotifications}}checked{{end}}> 155 + </label> 156 + </div> 157 + </div> 158 + 159 + <div class="flex justify-end pt-2"> 160 + <button 161 + type="submit" 162 + class="btn-create flex items-center gap-2 group" 163 + > 164 + {{ i "save" "w-4 h-4" }} 165 + save 166 + {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 167 + </button> 168 + </div> 169 + <div id="settings-notifications-success"></div> 170 + 171 + <div id="settings-notifications-error" class="error"></div> 172 + </form> 173 + {{ end }}