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 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 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)
|
- 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)
|
## 2.15.0 (Unreleased)
|
||||||
|
|
||||||
|
|||||||
@ -314,6 +314,25 @@
|
|||||||
(climit/invoke! generate-thumbnail file))]
|
(climit/invoke! generate-thumbnail file))]
|
||||||
(sto/put-object! storage params)))
|
(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
|
;; --- MUTATION: Request Email Change
|
||||||
|
|
||||||
(declare ^:private request-email-change!)
|
(declare ^:private request-email-change!)
|
||||||
|
|||||||
@ -125,7 +125,20 @@
|
|||||||
out (th/command! data)]
|
out (th/command! data)]
|
||||||
|
|
||||||
;; (th/print-result! out)
|
;; (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
|
(t/deftest profile-deletion-1
|
||||||
(let [prof (th/create-profile* 1)
|
(let [prof (th/create-profile* 1)
|
||||||
|
|||||||
@ -348,6 +348,23 @@
|
|||||||
(rx/map (constantly (refresh-profile)))
|
(rx/map (constantly (refresh-profile)))
|
||||||
(rx/catch on-error))))))
|
(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
|
(defn fetch-file-comments-users
|
||||||
[{:keys [team-id]}]
|
[{:keys [team-id]}]
|
||||||
(assert (uuid? team-id) "expected a valid uuid for `team-id`")
|
(assert (uuid? team-id) "expected a valid uuid for `team-id`")
|
||||||
|
|||||||
@ -16,6 +16,7 @@
|
|||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.main.ui.components.file-uploader :refer [file-uploader]]
|
[app.main.ui.components.file-uploader :refer [file-uploader]]
|
||||||
[app.main.ui.components.forms :as fm]
|
[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.dom :as dom]
|
||||||
[app.util.i18n :as i18n :refer [tr]]
|
[app.util.i18n :as i18n :refer [tr]]
|
||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
@ -92,6 +93,7 @@
|
|||||||
[]
|
[]
|
||||||
(let [input-ref (mf/use-ref nil)
|
(let [input-ref (mf/use-ref nil)
|
||||||
profile (mf/deref refs/profile)
|
profile (mf/deref refs/profile)
|
||||||
|
has-photo? (some? (:photo-id profile))
|
||||||
|
|
||||||
photo
|
photo
|
||||||
(mf/with-memo [profile]
|
(mf/with-memo [profile]
|
||||||
@ -103,13 +105,32 @@
|
|||||||
|
|
||||||
on-file-selected
|
on-file-selected
|
||||||
(fn [file]
|
(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)}
|
[:form {:class (stl/css :avatar-form)}
|
||||||
[:div {:class (stl/css :image-change-field)}
|
[:div {:class (stl/css :image-change-field)}
|
||||||
[:span {:class (stl/css :update-overlay)
|
[:span {:class (stl/css :update-overlay)
|
||||||
:on-click on-image-click} (tr "labels.update")]
|
:on-click on-image-click} (tr "labels.update")]
|
||||||
[:img {:src photo}]
|
[: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"
|
[:& file-uploader {:accept "image/jpeg,image/png"
|
||||||
:multi false
|
:multi false
|
||||||
:ref input-ref
|
:ref input-ref
|
||||||
|
|||||||
@ -280,6 +280,31 @@ form.avatar-form {
|
|||||||
z-index: $z-index-modal;
|
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"] {
|
input[type="file"] {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -294,6 +319,10 @@ form.avatar-form {
|
|||||||
.update-overlay {
|
.update-overlay {
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.delete-overlay {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2594,6 +2594,12 @@ msgstr "Delete %s files"
|
|||||||
msgid "labels.deleted"
|
msgid "labels.deleted"
|
||||||
msgstr "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
|
#: src/app/main/ui/onboarding/questions.cljs:86
|
||||||
msgid "labels.developer"
|
msgid "labels.developer"
|
||||||
msgstr "Development"
|
msgstr "Development"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user