mirror of
https://github.com/penpot/penpot.git
synced 2026-04-25 11:18:36 +00:00
🐛 Add ability to delete uploaded profile avatar (#9068)
Fixes #9067. Adds a delete button that appears on hover over an uploaded profile photo; clicking it opens a confirm modal and, on accept, clears the stored photo so the generated fallback avatar is shown again. A new :delete-profile-photo RPC schedules the old storage object for garbage collection and sets photo-id to null. Signed-off-by: moorsecopers99 <patellscott18@gmail.com> Co-authored-by: Andrey Antukh <niwi@niwi.nz>
This commit is contained in:
parent
bb91c06390
commit
95b2d7b083
@ -70,6 +70,7 @@
|
||||
- Fix opacity mixed value [Taiga #13960](https://tree.taiga.io/project/penpot/issue/13960)
|
||||
- Fix gap input throwing an error [Github #8984](https://github.com/penpot/penpot/pull/8984)
|
||||
- Fix copy to be more specific [Taiga #13990](https://tree.taiga.io/project/penpot/issue/13990)
|
||||
- Allow deleting the profile avatar after uploading [Github #9067](https://github.com/penpot/penpot/issues/9067)
|
||||
|
||||
## 2.15.0 (Unreleased)
|
||||
|
||||
|
||||
@ -314,6 +314,25 @@
|
||||
(climit/invoke! generate-thumbnail file))]
|
||||
(sto/put-object! storage params)))
|
||||
|
||||
;; --- MUTATION: Delete Photo
|
||||
|
||||
(sv/defmethod ::delete-profile-photo
|
||||
{::doc/added "2.16"
|
||||
::sm/params [:map]
|
||||
::sm/result :nil
|
||||
::db/transaction true}
|
||||
[{:keys [::db/conn ::sto/storage]} {:keys [::rpc/profile-id]}]
|
||||
(let [profile (get-profile conn profile-id ::db/for-update true)]
|
||||
(when-let [id (:photo-id profile)]
|
||||
(sto/touch-object! storage id))
|
||||
|
||||
(db/update! conn :profile
|
||||
{:photo-id nil}
|
||||
{:id profile-id}
|
||||
{::db/return-keys false})
|
||||
|
||||
nil))
|
||||
|
||||
;; --- MUTATION: Request Email Change
|
||||
|
||||
(declare ^:private request-email-change!)
|
||||
|
||||
@ -125,7 +125,20 @@
|
||||
out (th/command! data)]
|
||||
|
||||
;; (th/print-result! out)
|
||||
(t/is (nil? (:error out)))))))
|
||||
(t/is (nil? (:error out)))))
|
||||
|
||||
(t/testing "delete photo clears photo-id"
|
||||
(let [data {::th/type :delete-profile-photo
|
||||
::rpc/profile-id (:id profile)}
|
||||
out (th/command! data)]
|
||||
(t/is (nil? (:error out)))
|
||||
(t/is (nil? (:result out))))
|
||||
|
||||
(let [data {::th/type :get-profile
|
||||
::rpc/profile-id (:id profile)}
|
||||
out (th/command! data)]
|
||||
(t/is (nil? (:error out)))
|
||||
(t/is (nil? (:photo-id (:result out))))))))
|
||||
|
||||
(t/deftest profile-deletion-1
|
||||
(let [prof (th/create-profile* 1)
|
||||
|
||||
@ -348,6 +348,23 @@
|
||||
(rx/map (constantly (refresh-profile)))
|
||||
(rx/catch on-error))))))
|
||||
|
||||
(def delete-photo
|
||||
(ptk/reify ::delete-photo
|
||||
ev/Event
|
||||
(-data [_] {})
|
||||
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:profile :photo-id] nil))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(->> (rp/cmd! :delete-profile-photo {})
|
||||
(rx/map (constantly (refresh-profile)))
|
||||
(rx/catch (fn [cause]
|
||||
(js/console.error "delete-photo failed" cause)
|
||||
(rx/of (refresh-profile))))))))
|
||||
|
||||
(defn fetch-file-comments-users
|
||||
[{:keys [team-id]}]
|
||||
(assert (uuid? team-id) "expected a valid uuid for `team-id`")
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.file-uploader :refer [file-uploader]]
|
||||
[app.main.ui.components.forms :as fm]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i :refer [icon*]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[rumext.v2 :as mf]))
|
||||
@ -92,6 +93,7 @@
|
||||
[]
|
||||
(let [input-ref (mf/use-ref nil)
|
||||
profile (mf/deref refs/profile)
|
||||
has-photo? (some? (:photo-id profile))
|
||||
|
||||
photo
|
||||
(mf/with-memo [profile]
|
||||
@ -103,13 +105,32 @@
|
||||
|
||||
on-file-selected
|
||||
(fn [file]
|
||||
(st/emit! (du/update-photo file)))]
|
||||
(st/emit! (du/update-photo file)))
|
||||
|
||||
on-delete-click
|
||||
(mf/use-fn
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (modal/show
|
||||
{:type :confirm
|
||||
:title (tr "labels.delete-profile-photo.title")
|
||||
:message (tr "labels.delete-profile-photo.message")
|
||||
:accept-label (tr "labels.delete")
|
||||
:on-accept (fn [_] (st/emit! du/delete-photo))}))))]
|
||||
|
||||
[:form {:class (stl/css :avatar-form)}
|
||||
[:div {:class (stl/css :image-change-field)}
|
||||
[:span {:class (stl/css :update-overlay)
|
||||
:on-click on-image-click} (tr "labels.update")]
|
||||
[:img {:src photo}]
|
||||
(when has-photo?
|
||||
[:button {:type "button"
|
||||
:class (stl/css :delete-overlay)
|
||||
:title (tr "labels.delete")
|
||||
:aria-label (tr "labels.delete")
|
||||
:on-click on-delete-click
|
||||
:data-testid "profile-image-delete"}
|
||||
[:> icon* {:icon-id i/delete :size "m"}]])
|
||||
[:& file-uploader {:accept "image/jpeg,image/png"
|
||||
:multi false
|
||||
:ref input-ref
|
||||
|
||||
@ -280,6 +280,31 @@ form.avatar-form {
|
||||
z-index: $z-index-modal;
|
||||
}
|
||||
|
||||
.delete-overlay {
|
||||
position: absolute;
|
||||
top: $s-4;
|
||||
inset-inline-end: $s-4;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: $s-32;
|
||||
height: $s-32;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
background: var(--color-background-primary);
|
||||
color: var(--color-foreground-primary);
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s ease-in-out;
|
||||
z-index: calc(#{$z-index-modal} + 1);
|
||||
|
||||
&:hover {
|
||||
background: var(--color-background-quaternary);
|
||||
color: var(--color-accent-primary);
|
||||
}
|
||||
}
|
||||
|
||||
input[type="file"] {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@ -294,6 +319,10 @@ form.avatar-form {
|
||||
.update-overlay {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.delete-overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2594,6 +2594,12 @@ msgstr "Delete %s files"
|
||||
msgid "labels.deleted"
|
||||
msgstr "Deleted"
|
||||
|
||||
msgid "labels.delete-profile-photo.title"
|
||||
msgstr "Delete profile photo"
|
||||
|
||||
msgid "labels.delete-profile-photo.message"
|
||||
msgstr "Are you sure you want to delete your profile photo?"
|
||||
|
||||
#: src/app/main/ui/onboarding/questions.cljs:86
|
||||
msgid "labels.developer"
|
||||
msgstr "Development"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user