Monorepo for Tangled

appview/state: add the ability to remove your avatar

Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.org>

authored by

Anirudh Oppiliappan and committed by tangled.org a120ed6b b217e460

+97 -12
+19 -10
appview/pages/templates/user/fragments/editAvatar.html
··· 6 6 hx-swap="none" 7 7 class="flex flex-col gap-2"> 8 8 <label for="avatar-file" class="uppercase p-0"> 9 - Upload avatar 9 + Upload or Remove Avatar 10 10 </label> 11 - <p class="text-sm text-gray-500 dark:text-gray-400">Select an image (PNG or JPEG, max 1MB)</p> 11 + <p class="text-sm text-gray-500 dark:text-gray-400">Upload a new image (PNG or JPEG, max 1MB) or remove your current avatar.</p> 12 12 <input 13 13 type="file" 14 14 id="avatar-file" ··· 23 23 dark:file:bg-gray-700 dark:file:text-gray-300 24 24 hover:file:bg-gray-200 dark:hover:file:bg-gray-600" /> 25 25 <div id="avatar-error" class="text-red-500 dark:text-red-400 text-sm min-h-5"></div> 26 - <div class="flex gap-2 pt-2"> 26 + <div class="flex flex-col gap-2 pt-2"> 27 + <button type="submit" class="btn w-full flex items-center justify-center gap-2"> 28 + <span class="inline-flex gap-2 items-center">{{ i "upload" "size-4" }} upload</span> 29 + <span id="spinner" class="group"> 30 + {{ i "loader-circle" "ml-2 w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 31 + </span> 32 + </button> 33 + <button 34 + type="button" 35 + hx-delete="/profile/avatar" 36 + hx-confirm="Are you sure you want to remove your profile picture?" 37 + hx-swap="none" 38 + class="btn w-full flex items-center justify-center gap-2"> 39 + {{ i "trash-2" "size-4" }} 40 + remove avatar 41 + </button> 27 42 <button 28 43 id="cancel-avatar-btn" 29 44 type="button" 30 45 popovertarget="avatar-upload-modal" 31 46 popovertargetaction="hide" 32 - class="btn w-1/2 flex items-center gap-2 text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300"> 47 + class="btn w-full flex items-center justify-center gap-2 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"> 33 48 {{ i "x" "size-4" }} 34 49 cancel 35 - </button> 36 - <button type="submit" class="btn w-1/2 flex items-center"> 37 - <span class="inline-flex gap-2 items-center">{{ i "upload" "size-4" }} upload</span> 38 - <span id="spinner" class="group"> 39 - {{ i "loader-circle" "ml-2 w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 40 - </span> 41 50 </button> 42 51 </div> 43 52 </form>
+77 -2
appview/state/profile.go
··· 840 840 return 841 841 } 842 842 843 - w.Header().Set("HX-Redirect", r.Header.Get("Referer")) 844 - w.WriteHeader(http.StatusOK) 843 + s.pages.HxRedirect(w, r.Header.Get("Referer")) 844 + } 845 + 846 + func (s *State) RemoveProfileAvatar(w http.ResponseWriter, r *http.Request) { 847 + l := s.logger.With("handler", "RemoveProfileAvatar") 848 + user := s.oauth.GetUser(r) 849 + l = l.With("did", user.Did) 850 + 851 + client, err := s.oauth.AuthorizedClient(r) 852 + if err != nil { 853 + l.Error("failed to get PDS client", "err", err) 854 + s.pages.Notice(w, "avatar-error", "Failed to connect to your PDS") 855 + return 856 + } 857 + 858 + getRecordResp, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.ActorProfileNSID, user.Did, "self") 859 + if err != nil { 860 + l.Error("failed to get current profile record", "err", err) 861 + s.pages.Notice(w, "avatar-error", "Failed to get current profile from your PDS") 862 + return 863 + } 864 + 865 + var profileRecord *tangled.ActorProfile 866 + if getRecordResp.Value != nil { 867 + if val, ok := getRecordResp.Value.Val.(*tangled.ActorProfile); ok { 868 + profileRecord = val 869 + } else { 870 + l.Warn("profile record type assertion failed") 871 + profileRecord = &tangled.ActorProfile{} 872 + } 873 + } else { 874 + l.Warn("no existing profile record") 875 + profileRecord = &tangled.ActorProfile{} 876 + } 877 + 878 + profileRecord.Avatar = nil 879 + 880 + _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 881 + Collection: tangled.ActorProfileNSID, 882 + Repo: user.Did, 883 + Rkey: "self", 884 + Record: &lexutil.LexiconTypeDecoder{Val: profileRecord}, 885 + SwapRecord: getRecordResp.Cid, 886 + }) 887 + 888 + if err != nil { 889 + l.Error("failed to update profile record", "err", err) 890 + s.pages.Notice(w, "avatar-error", "Failed to remove avatar from your PDS") 891 + return 892 + } 893 + 894 + l.Info("successfully removed avatar from PDS") 895 + 896 + profile, err := db.GetProfile(s.db, user.Did) 897 + if err != nil { 898 + l.Warn("getting profile data from DB", "err", err) 899 + profile = &models.Profile{Did: user.Did} 900 + } 901 + profile.Avatar = "" 902 + 903 + tx, err := s.db.BeginTx(r.Context(), nil) 904 + if err != nil { 905 + l.Error("failed to start transaction", "err", err) 906 + s.pages.HxRefresh(w) 907 + w.WriteHeader(http.StatusOK) 908 + return 909 + } 910 + 911 + err = db.UpsertProfile(tx, profile) 912 + if err != nil { 913 + l.Error("failed to update profile in DB", "err", err) 914 + s.pages.HxRefresh(w) 915 + w.WriteHeader(http.StatusOK) 916 + return 917 + } 918 + 919 + s.pages.HxRedirect(w, r.Header.Get("Referer")) 845 920 }
+1
appview/state/router.go
··· 166 166 r.Post("/bio", s.UpdateProfileBio) 167 167 r.Post("/pins", s.UpdateProfilePins) 168 168 r.Post("/avatar", s.UploadProfileAvatar) 169 + r.Delete("/avatar", s.RemoveProfileAvatar) 169 170 }) 170 171 171 172 r.Mount("/settings", s.SettingsRouter())