🎉 Add new onboarding flow.
|
@ -16,7 +16,6 @@
|
|||
[app.loggers.audit :as audit]
|
||||
[app.media :as media]
|
||||
[app.metrics :as mtx]
|
||||
[app.rpc.mutations.projects :as projects]
|
||||
[app.rpc.mutations.teams :as teams]
|
||||
[app.rpc.queries.profile :as profile]
|
||||
[app.setup.initial-data :as sid]
|
||||
|
@ -256,28 +255,15 @@
|
|||
:code :email-already-exists
|
||||
:cause e)))))))
|
||||
|
||||
|
||||
(defn create-profile-relations
|
||||
[conn profile]
|
||||
(let [team (teams/create-team conn {:profile-id (:id profile)
|
||||
:name "Default"
|
||||
:is-default true})
|
||||
project (projects/create-project conn {:profile-id (:id profile)
|
||||
:team-id (:id team)
|
||||
:name "Drafts"
|
||||
:is-default true})
|
||||
params {:team-id (:id team)
|
||||
:profile-id (:id profile)
|
||||
:project-id (:id project)
|
||||
:role :owner}]
|
||||
|
||||
(teams/create-team-role conn params)
|
||||
(projects/create-project-role conn params)
|
||||
|
||||
(let [team (teams/create-team conn {:profile-id (:id profile)
|
||||
:name "Default"
|
||||
:is-default true})]
|
||||
(-> profile
|
||||
(profile/strip-private-attrs)
|
||||
(assoc :default-team-id (:id team))
|
||||
(assoc :default-project-id (:id project)))))
|
||||
(assoc :default-project-id (:default-project-id team)))))
|
||||
|
||||
;; --- MUTATION: Login
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
;; --- Mutation: Create Team
|
||||
|
||||
(declare create-team)
|
||||
(declare create-team-entry)
|
||||
(declare create-team-role)
|
||||
(declare create-team-default-project)
|
||||
|
||||
|
@ -42,15 +43,21 @@
|
|||
(sv/defmethod ::create-team
|
||||
[{:keys [pool] :as cfg} params]
|
||||
(db/with-atomic [conn pool]
|
||||
(let [team (create-team conn params)
|
||||
params (assoc params
|
||||
:team-id (:id team)
|
||||
:role :owner)]
|
||||
(create-team-role conn params)
|
||||
(create-team-default-project conn params)
|
||||
team)))
|
||||
(create-team conn params)))
|
||||
|
||||
(defn create-team
|
||||
"This is a complete team creation process, it creates the team
|
||||
object and all related objects (default role and default project)."
|
||||
[conn params]
|
||||
(let [team (create-team-entry conn params)
|
||||
params (assoc params
|
||||
:team-id (:id team)
|
||||
:role :owner)
|
||||
project (create-team-default-project conn params)]
|
||||
(create-team-role conn params)
|
||||
(assoc team :default-project-id (:id project))))
|
||||
|
||||
(defn- create-team-entry
|
||||
[conn {:keys [id name is-default] :as params}]
|
||||
(let [id (or id (uuid/next))
|
||||
is-default (if (boolean? is-default) is-default false)]
|
||||
|
@ -59,23 +66,24 @@
|
|||
:name name
|
||||
:is-default is-default})))
|
||||
|
||||
(defn create-team-role
|
||||
(defn- create-team-role
|
||||
[conn {:keys [team-id profile-id role] :as params}]
|
||||
(let [params {:team-id team-id
|
||||
:profile-id profile-id}]
|
||||
(->> (perms/assign-role-flags params role)
|
||||
(db/insert! conn :team-profile-rel))))
|
||||
|
||||
(defn create-team-default-project
|
||||
(defn- create-team-default-project
|
||||
[conn {:keys [team-id profile-id] :as params}]
|
||||
(let [project {:id (uuid/next)
|
||||
:team-id team-id
|
||||
:name "Drafts"
|
||||
:is-default true}]
|
||||
(projects/create-project conn project)
|
||||
:is-default true}
|
||||
project (projects/create-project conn project)]
|
||||
(projects/create-project-role conn {:project-id (:id project)
|
||||
:profile-id profile-id
|
||||
:role :owner})))
|
||||
:role :owner})
|
||||
project))
|
||||
|
||||
;; --- Mutation: Update Team
|
||||
|
||||
|
@ -293,28 +301,18 @@
|
|||
|
||||
;; --- Mutation: Invite Member
|
||||
|
||||
(declare create-team-invitation)
|
||||
|
||||
(s/def ::email ::us/email)
|
||||
(s/def ::invite-team-member
|
||||
(s/keys :req-un [::profile-id ::team-id ::email ::role]))
|
||||
|
||||
(sv/defmethod ::invite-team-member
|
||||
[{:keys [pool tokens] :as cfg} {:keys [profile-id team-id email role] :as params}]
|
||||
[{:keys [pool] :as cfg} {:keys [profile-id team-id email role] :as params}]
|
||||
(db/with-atomic [conn pool]
|
||||
(let [perms (teams/get-permissions conn profile-id team-id)
|
||||
profile (db/get-by-id conn :profile profile-id)
|
||||
member (profile/retrieve-profile-data-by-email conn email)
|
||||
team (db/get-by-id conn :team team-id)
|
||||
itoken (tokens :generate
|
||||
{:iss :team-invitation
|
||||
:exp (dt/in-future "48h")
|
||||
:profile-id (:id profile)
|
||||
:role role
|
||||
:team-id team-id
|
||||
:member-email (:email member email)
|
||||
:member-id (:id member)})
|
||||
ptoken (tokens :generate-predefined
|
||||
{:iss :profile-identity
|
||||
:profile-id (:id profile)})]
|
||||
team (db/get-by-id conn :team team-id)]
|
||||
|
||||
(when-not (:is-admin perms)
|
||||
(ex/raise :type :validation
|
||||
|
@ -326,24 +324,71 @@
|
|||
:code :profile-is-muted
|
||||
:hint "looks like the profile has reported repeatedly as spam or has permanent bounces"))
|
||||
|
||||
(when (and member (not (eml/allow-send-emails? conn member)))
|
||||
(ex/raise :type :validation
|
||||
:code :member-is-muted
|
||||
:hint "looks like the profile has reported repeatedly as spam or has permanent bounces"))
|
||||
|
||||
;; Secondly check if the invited member email is part of the
|
||||
;; global spam/bounce report.
|
||||
(when (eml/has-bounce-reports? conn email)
|
||||
(ex/raise :type :validation
|
||||
:code :email-has-permanent-bounces
|
||||
:hint "looks like the email you invite has been repeatedly reported as spam or permanent bounce"))
|
||||
|
||||
(eml/send! {::eml/conn conn
|
||||
::eml/factory eml/invite-to-team
|
||||
:public-uri (:public-uri cfg)
|
||||
:to email
|
||||
:invited-by (:fullname profile)
|
||||
:team (:name team)
|
||||
:token itoken
|
||||
:extra-data ptoken})
|
||||
(create-team-invitation
|
||||
(assoc cfg
|
||||
:email email
|
||||
:conn conn
|
||||
:team team
|
||||
:profile profile
|
||||
:role role))
|
||||
nil)))
|
||||
|
||||
(defn- create-team-invitation
|
||||
[{:keys [conn tokens team profile role email] :as cfg}]
|
||||
(let [member (profile/retrieve-profile-data-by-email conn email)
|
||||
itoken (tokens :generate
|
||||
{:iss :team-invitation
|
||||
:exp (dt/in-future "48h")
|
||||
:profile-id (:id profile)
|
||||
:role role
|
||||
:team-id (:id team)
|
||||
:member-email (:email member email)
|
||||
:member-id (:id member)})
|
||||
ptoken (tokens :generate-predefined
|
||||
{:iss :profile-identity
|
||||
:profile-id (:id profile)})]
|
||||
|
||||
(when (and member (not (eml/allow-send-emails? conn member)))
|
||||
(ex/raise :type :validation
|
||||
:code :member-is-muted
|
||||
:hint "looks like the profile has reported repeatedly as spam or has permanent bounces"))
|
||||
|
||||
;; Secondly check if the invited member email is part of the
|
||||
;; global spam/bounce report.
|
||||
(when (eml/has-bounce-reports? conn email)
|
||||
(ex/raise :type :validation
|
||||
:code :email-has-permanent-bounces
|
||||
:hint "looks like the email you invite has been repeatedly reported as spam or permanent bounce"))
|
||||
|
||||
(eml/send! {::eml/conn conn
|
||||
::eml/factory eml/invite-to-team
|
||||
:public-uri (:public-uri cfg)
|
||||
:to email
|
||||
:invited-by (:fullname profile)
|
||||
:team (:name team)
|
||||
:token itoken
|
||||
:extra-data ptoken})))
|
||||
|
||||
|
||||
;; --- Mutation: Create Team & Invite Members
|
||||
|
||||
(s/def ::emails ::us/set-of-emails)
|
||||
(s/def ::create-team-and-invite-members
|
||||
(s/and ::create-team (s/keys :req-un [::emails ::role])))
|
||||
|
||||
(sv/defmethod ::create-team-and-invite-members
|
||||
[{:keys [pool] :as cfg} {:keys [profile-id emails role] :as params}]
|
||||
(db/with-atomic [conn pool]
|
||||
(let [team (create-team conn params)
|
||||
profile (db/get-by-id conn :profile profile-id)]
|
||||
|
||||
;; Create invitations for all provided emails.
|
||||
(doseq [email emails]
|
||||
(create-team-invitation
|
||||
(assoc cfg
|
||||
:conn conn
|
||||
:team team
|
||||
:profile profile
|
||||
:email email
|
||||
:role role)))
|
||||
team)))
|
||||
|
|
|
@ -79,12 +79,14 @@
|
|||
where f.project_id = p.id
|
||||
and deleted_at is null) as count
|
||||
from project as p
|
||||
inner join team as t on (t.id = p.team_id)
|
||||
left join team_project_profile_rel as tpp
|
||||
on (tpp.project_id = p.id and
|
||||
tpp.team_id = p.team_id and
|
||||
tpp.profile_id = ?)
|
||||
where p.team_id = ?
|
||||
and p.deleted_at is null
|
||||
and t.deleted_at is null
|
||||
order by p.modified_at desc")
|
||||
|
||||
(defn retrieve-projects
|
||||
|
@ -108,26 +110,26 @@
|
|||
(def sql:all-projects
|
||||
"select p1.*, t.name as team_name, t.is_default as is_default_team
|
||||
from project as p1
|
||||
inner join team as t
|
||||
on t.id = p1.team_id
|
||||
inner join team as t on (t.id = p1.team_id)
|
||||
where t.id in (select team_id
|
||||
from team_profile_rel as tpr
|
||||
where tpr.profile_id = ?
|
||||
and (tpr.can_edit = true or
|
||||
tpr.is_owner = true or
|
||||
tpr.is_admin = true))
|
||||
and t.deleted_at is null
|
||||
and p1.deleted_at is null
|
||||
union
|
||||
select p2.*, t.name as team_name, t.is_default as is_default_team
|
||||
from project as p2
|
||||
inner join team as t
|
||||
on t.id = p2.team_id
|
||||
inner join team as t on (t.id = p2.team_id)
|
||||
where p2.id in (select project_id
|
||||
from project_profile_rel as ppr
|
||||
where ppr.profile_id = ?
|
||||
and (ppr.can_edit = true or
|
||||
ppr.is_owner = true or
|
||||
ppr.is_admin = true))
|
||||
and t.deleted_at is null
|
||||
and p2.deleted_at is null
|
||||
order by team_name, name;")
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
|
||||
(t/is (nil? (:error out)))
|
||||
(let [result (:result out)]
|
||||
(t/is (= 1 (count result)))
|
||||
(t/is (= 2 (count result)))
|
||||
(t/is project-id (get-in result [0 :id]))
|
||||
(t/is (= "test project" (get-in result [0 :name])))))
|
||||
|
||||
|
@ -55,15 +55,15 @@
|
|||
|
||||
(t/is (nil? (:error out)))
|
||||
(let [result (:result out)]
|
||||
(t/is (= 2 (count result)))
|
||||
(t/is (= 3 (count result)))
|
||||
(t/is (not= project-id (get-in result [0 :id])))
|
||||
(t/is (= "Drafts" (get-in result [0 :name])))
|
||||
(t/is (= "Default" (get-in result [0 :team-name])))
|
||||
(t/is (= true (get-in result [0 :is-default-team])))
|
||||
(t/is project-id (get-in result [1 :id]))
|
||||
(t/is (= "test project" (get-in result [1 :name])))
|
||||
(t/is (= "team1" (get-in result [1 :team-name])))
|
||||
(t/is (= false (get-in result [1 :is-default-team])))))
|
||||
(t/is project-id (get-in result [2 :id]))
|
||||
(t/is (= "test project" (get-in result [2 :name])))
|
||||
(t/is (= "team1" (get-in result [2 :team-name])))
|
||||
(t/is (= false (get-in result [2 :is-default-team])))))
|
||||
|
||||
;; rename project
|
||||
(let [data {::th/type :rename-project
|
||||
|
@ -95,7 +95,7 @@
|
|||
(t/is (nil? (:error out)))
|
||||
(t/is (nil? (:result out))))
|
||||
|
||||
;; query a list of projects after delete"
|
||||
;; query a list of projects after delete
|
||||
(let [data {::th/type :projects
|
||||
:team-id (:id team)
|
||||
:profile-id (:id profile)}
|
||||
|
@ -103,7 +103,7 @@
|
|||
;; (th/print-result! out)
|
||||
(t/is (nil? (:error out)))
|
||||
(let [result (:result out)]
|
||||
(t/is (= 0 (count result)))))
|
||||
(t/is (= 1 (count result)))))
|
||||
))
|
||||
|
||||
(t/deftest permissions-checks-create-project
|
||||
|
|
|
@ -130,7 +130,7 @@
|
|||
(let [result (task {:max-age (dt/duration {:minutes 1})})]
|
||||
(t/is (nil? result)))
|
||||
|
||||
;; query the list of projects of a after hard deletion
|
||||
;; query the list of projects after hard deletion
|
||||
(let [data {::th/type :projects
|
||||
:team-id (:id team)
|
||||
:profile-id (:id profile1)}
|
||||
|
|
|
@ -126,7 +126,8 @@
|
|||
:password "123123"
|
||||
:is-demo false}
|
||||
params)]
|
||||
(->> (#'profile/create-profile conn params)
|
||||
(->> params
|
||||
(#'profile/create-profile conn)
|
||||
(#'profile/create-profile-relations conn)))))
|
||||
|
||||
(defn create-project*
|
||||
|
@ -159,15 +160,10 @@
|
|||
([i params] (create-team* *pool* i params))
|
||||
([conn i {:keys [profile-id] :as params}]
|
||||
(us/assert uuid? profile-id)
|
||||
(let [id (mk-uuid "team" i)
|
||||
team (#'teams/create-team conn {:id id
|
||||
:profile-id profile-id
|
||||
:name (str "team" i)})]
|
||||
(#'teams/create-team-role conn
|
||||
{:team-id id
|
||||
:profile-id profile-id
|
||||
:role :owner})
|
||||
team)))
|
||||
(let [id (mk-uuid "team" i)]
|
||||
(teams/create-team conn {:id id
|
||||
:profile-id profile-id
|
||||
:name (str "team" i)}))))
|
||||
|
||||
(defn create-file-media-object*
|
||||
([params] (create-file-media-object* *pool* params))
|
||||
|
@ -350,3 +346,11 @@
|
|||
(defn reset-mock!
|
||||
[m]
|
||||
(reset! m @(mk/make-mock {})))
|
||||
|
||||
(defn pause
|
||||
[]
|
||||
(let [^java.io.Console cnsl (System/console)]
|
||||
(println "[waiting RETURN]")
|
||||
(.readLine cnsl)
|
||||
nil))
|
||||
|
||||
|
|
1
frontend/resources/images/on-solo-hover.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns:xlink="http://www.w3.org/1999/xlink" width="24.47353642154394" xmlns="http://www.w3.org/2000/svg" id="screenshot" version="1.1" viewBox="10.276508697997087 -10.666460420698968 24.47353642154394 45.833972589785844" height="45.833972589785844" style="-webkit-print-color-adjust: exact;"><g id="shape-68c61310-2040-11ec-9082-1597698bcafa" width="133" height="70" fill="none"><g id="shape-68c61311-2040-11ec-9082-1597698bcafa"><path d="M34.15111509349208,-6.042576477324019C32.728545198285246,-6.330271155262835,31.92939323677365,-3.376021339875024,31.573755956590503,-2.4511143742279273C31.03134089353898,-4.792719923592813,32.95730466396617,-10.268928805500309,31.43334627304739,-10.431571776022338C29.909377991244583,-10.594250374600506,28.25067219538414,-7.913146659964696,27.49821990591863,-6.170389430908017C26.74573283532027,-4.427587759031667,25.891378375591557,-1.7644073640558418,25.713212854031553,-2.2272258928269366C25.535081233357232,-2.6900095615483224,27.15263561710435,-6.757552580227184,25.941143250794084,-7.1652527066157745C24.72968451746874,-7.572893837298125,23.954342086512952,-5.090038220419956,23.25052490786311,-3.268314742871553C21.417817942427064,1.4754114884185583,10.659070475242515,0.4358613513782075,10.659070475242515,0.4358613513782075L10.492289692115264,34.903869918353394C10.950455754343238,34.90895547471155,28.014759237585167,30.129601494621056,32.24939430527593,19.872789654186818C35.25119864070166,13.145361698193483,32.406657583041124,6.514941655593702,33.309134176787666,1.1371823352346837C33.78537439733827,-1.58885192629441,35.57368117762144,-5.754847357929975,34.15111509349208,-6.042576477324019ZZ" style="fill: rgb(255, 255, 255); fill-opacity: 1;"/></g><g id="shape-68c61312-2040-11ec-9082-1597698bcafa"><path d="M22.85523475939408,17.21695965203071C22.427431361919844,16.01479714589459,23.05111296555242,14.69085378511636,24.24829081777807,14.259819026323385C25.445434386402667,13.828783886987821,26.762725789724755,14.45390600122937,27.19052918719808,15.656068507365035C27.618298301069444,16.85823063295993,26.994616697437777,18.18217399373725,25.79747312881318,18.61320913307327C24.60029527658753,19.044243891866245,23.283003873265443,18.41912177762515,22.85523475939408,17.21695965203071ZZ" style="stroke-width: 1; stroke: rgb(49, 239, 184); stroke-opacity: 1;"/></g><g id="shape-68c61313-2040-11ec-9082-1597698bcafa"><path d="M17.110277419337763,8.332660542501344C14.855684713780647,9.317767677909615,11.627251872240777,12.863450146366631,13.47442592751213,18.054309980863763C15.321633118238424,23.24527363442894,19.819776888222805,23.267580687635473,22.247438584220617,22.768891367604738" style="stroke-width: 1; stroke: rgb(49, 239, 184); stroke-opacity: 1;"/></g></g></svg>
|
After Width: | Height: | Size: 2.7 KiB |
1
frontend/resources/images/on-solo.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns:xlink="http://www.w3.org/1999/xlink" width="24.47353642154485" xmlns="http://www.w3.org/2000/svg" id="screenshot" version="1.1" viewBox="10.276508697995268 -10.66646042069624 24.47353642154485 45.833972589782206" height="45.833972589782206" style="-webkit-print-color-adjust: exact;"><g id="shape-b0db2ad1-203d-11ec-9082-1597698bcafa" width="133" height="70" fill="none"><g id="shape-b0db2ad2-203d-11ec-9082-1597698bcafa"><path d="M34.15111509349208,-6.042576477324474C32.728545198285246,-6.3302711552623805,31.92939323677365,-3.376021339874569,31.573755956590503,-2.4511143742274726C31.03134089353898,-4.792719923592813,32.95730466396617,-10.268928805500764,31.43334627304739,-10.431571776021883C29.909377991244583,-10.59425037460096,28.25067219538414,-7.913146659964696,27.49821990591863,-6.170389430908472C26.74573283532027,-4.427587759031667,25.891378375591557,-1.7644073640558418,25.713212854031553,-2.2272258928269366C25.535081233357232,-2.6900095615483224,27.15263561710435,-6.757552580227639,25.941143250794084,-7.165252706616229C24.72968451746874,-7.57289383729767,23.954342086512952,-5.090038220419956,23.25052490786311,-3.268314742871553C21.417817942427064,1.4754114884181035,10.659070475242515,0.4358613513777527,10.659070475242515,0.4358613513777527L10.492289692115264,34.90386991835294C10.950455754343238,34.90895547471155,28.014759237585167,30.129601494621056,32.24939430527593,19.872789654186818C35.25119864070166,13.145361698193483,32.406657583041124,6.514941655594157,33.309134176787666,1.1371823352346837C33.78537439733827,-1.58885192629441,35.57368117762144,-5.754847357929975,34.15111509349208,-6.042576477324474ZZ" style="fill: rgb(250, 181, 245);"/></g><g id="shape-b0db51e0-203d-11ec-9082-1597698bcafa"><path d="M22.85523475939408,17.21695965203071C22.427431361919844,16.01479714589459,23.05111296555242,14.690853785115905,24.24829081777807,14.259819026323385C25.445434386402667,13.828783886987367,26.762725789724755,14.45390600122937,27.19052918719808,15.65606850736549C27.618298301069444,16.85823063295993,26.994616697437777,18.182173993736797,25.79747312881318,18.613209133072814C24.60029527658753,19.044243891866245,23.283003873265443,18.41912177762515,22.85523475939408,17.21695965203071ZZ" style="stroke-width: 1; stroke: white;"/></g><g id="shape-b0db51e1-203d-11ec-9082-1597698bcafa"><path d="M17.110277419337763,8.33266054250089C14.855684713780647,9.317767677909615,11.627251872240777,12.863450146366631,13.47442592751213,18.054309980863763C15.321633118238424,23.245273634429395,19.819776888222805,23.267580687635927,22.247438584220617,22.768891367604738" style="stroke-width: 1; stroke: white;"/></g></g></svg>
|
After Width: | Height: | Size: 2.6 KiB |
1
frontend/resources/images/on-teamup-hover.svg
Normal file
After Width: | Height: | Size: 11 KiB |
1
frontend/resources/images/on-teamup.svg
Normal file
After Width: | Height: | Size: 11 KiB |
1
frontend/resources/images/ph-file.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns:xlink="http://www.w3.org/1999/xlink" width="206.95680443620125" xmlns="http://www.w3.org/2000/svg" id="screenshot" version="1.1" viewBox="-1.9573043775739962 -2.7988168413976156 206.95680443620125 158.15679986166066" height="158.15679986166066" style="-webkit-print-color-adjust: exact;"><g id="shape-575c9093-25c2-11ec-9877-05429cda5971"><g id="shape-575c9094-25c2-11ec-9877-05429cda5971"><defs><mask id="outer-stroke-ddd0b3b0-3bbe-11ec-880d-a73a7db30a35" x="-1.9573043775739962" y="-2.7988168413976156" width="206.95680443620125" height="158.15679986166066" maskUnits="userSpaceOnUse"><use xlink:href="#stroke-shape-ddd0b3b0-3bbe-11ec-880d-a73a7db30a35" style="fill: none; stroke: white; stroke-width: 4;"/><use xlink:href="#stroke-shape-ddd0b3b0-3bbe-11ec-880d-a73a7db30a35" style="fill: black;"/></mask></defs><g class="outer-stroke-shape"><defs><rect width="201" height="152" x="1" id="stroke-shape-ddd0b3b0-3bbe-11ec-880d-a73a7db30a35" transform="matrix(0.9999999978661108,-1.745329248269963e-8,1.7453292541199837e-7,1.0000000006480119,-0.000013047912574393195,0.0000017222602934907627)" ry="3" rx="3" y="0" data-style="fill:none;stroke-width:2;stroke:#E3E3E3;stroke-opacity:1;stroke-dasharray:"/></defs><use xlink:href="#stroke-shape-ddd0b3b0-3bbe-11ec-880d-a73a7db30a35" mask="url(#outer-stroke-ddd0b3b0-3bbe-11ec-880d-a73a7db30a35)" style="stroke-width: 4; stroke: rgb(227, 227, 227); stroke-opacity: 1; fill: none;"/><use xlink:href="#stroke-shape-ddd0b3b0-3bbe-11ec-880d-a73a7db30a35" style="fill: none;"/></g></g><g id="shape-575c9095-25c2-11ec-9877-05429cda5971"><path d="M0,91 h202 a0,0 0 0 1 0,0 v59 a3,3 0 0 1 -3,3 h-196 a3,3 0 0 1 -3,-3 v-59 a0,0 0 0 1 0,0 z" x="0" y="91" transform="matrix(1.0000000024760176,-1.0471975568525078e-7,3.490658523073093e-8,1.0000000103484785,-0.000004508681172410434,0.000009314180957176177)" width="202" height="62" style="fill: none; stroke-width: 2; stroke: rgb(227, 227, 227); stroke-opacity: 1;"/></g><g id="shape-575c9096-25c2-11ec-9877-05429cda5971"><rect rx="3" ry="3" x="10" y="103" transform="matrix(0.9999999995870766,-4.810081334728633e-16,-4.810081268554315e-16,1.000000030940994,3.1795153176972235e-8,-0.0000034653912877047333)" width="134" height="18" style="fill: rgb(227, 227, 227); fill-opacity: 1;"/></g><g id="shape-575c9097-25c2-11ec-9877-05429cda5971"><rect rx="3" ry="3" x="10" y="131" transform="matrix(1.0000000022017677,6.948317137937551e-23,6.617444834426139e-23,1.0000000667740845,-2.234794180822064e-7,-0.000009114662532283546)" width="183" height="11" style="fill: rgb(227, 227, 227); fill-opacity: 1;"/></g></g></svg>
|
After Width: | Height: | Size: 2.5 KiB |
1
frontend/resources/images/ph-left.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns:xlink="http://www.w3.org/1999/xlink" width="81.5877720738863" xmlns="http://www.w3.org/2000/svg" id="screenshot" version="1.1" viewBox="-0.43500565160593396 0.14985215967590193 81.5877720738863 128.8502825397186" height="128.8502825397186" style="-webkit-print-color-adjust: exact;"><g id="shape-305b39c3-25c6-11ec-9877-05429cda5971" width="90" height="140" fill="none"><g id="shape-305b39c4-25c6-11ec-9877-05429cda5971"><path d="M18.180074555850297,34.13571661820788C16.328443710368447,37.802933917027985,15.363633970810042,41.85364142381468,15.363541785663983,45.96187246179579C15.363449600517924,49.407753224720636,16.04230101673238,52.81989422415609,17.360917347203213,56.00341606115035C18.679625862816465,59.187030083290665,20.612379636906553,62.07970778419531,23.049017419827578,64.51634556711724C25.485655202748603,66.95298335003963,28.378332903652336,68.88573712412926,31.561946925794473,70.20444563974297C34.74556094793297,71.52306197021062,38.157794132515846,72.20182120128084,41.603674895442964,72.20182120128084C48.56282376312447,72.20172901613478,55.23693615892262,69.43709648322647,60.15777926017472,64.51616119682512C65.07871454658016,59.59522591042378,67.84316270919953,52.92102132947775,67.8430705240462,45.96187246179579C67.84316270919953,41.85373360896074,66.87872171022173,37.802933917027985,65.027275235032,34.13571661820788L18.180074555850297,34.13571661820788ZZ" style="fill: rgb(227, 227, 227); fill-opacity: 1;"/></g><g id="shape-305b39c5-25c6-11ec-9877-05429cda5971"><path d="M40.358622311596264,76.81790020937888C29.539497004501754,76.81799239452494,19.163413509784732,80.76582127821257,11.513152601448382,87.79300278375013C3.862873256086459,94.81972336355693,-0.4349826275210944,104.35074562355658,-0.43500383010177757,114.28922622951995C-0.4349070356984157,119.34558149560962,0.6793145450319571,124.34939122839569,2.840650607504358,129.00013185144462L77.87714712656452,129.00013185144462C80.03842787794929,124.34939122839569,81.15276192455713,119.34558149560962,81.15276192455713,114.28922622951995C81.15276192455713,109.36838312826512,80.09761074177368,104.49547630300549,78.04750527664146,99.94890489510135C75.99739981150924,95.40325533865871,72.99244060259662,91.27234675472573,69.20436857717868,87.79281841345801C65.41629655176075,84.31329007218983,60.919228567337996,81.55317461144296,55.96990025592095,79.67010863112546C51.02047975936148,77.78704265080796,45.715777709559916,76.81790020937888,40.358622311596264,76.81790020937888ZZ" style="fill: rgb(227, 227, 227); fill-opacity: 1;"/></g><g id="shape-305b39c6-25c6-11ec-9877-05429cda5971"><path d="M22.63741074228892,55.77654841648564C22.63741074228892,55.77654841648564,28.038261899520876,66.05546876716517,39.71092947477882,65.88123884095012C51.38359705003313,65.70700891473462,57.30695361562539,56.473468121347196,57.30695361562539,56.473468121347196" style="stroke-width: 2; stroke: white;"/></g><g id="shape-305b39c7-25c6-11ec-9877-05429cda5971"><path d="M33.141447219852125,53.60300704066367C36.667529059923254,53.60300704066367,39.52591388646397,50.77716357071449,39.52591388646397,47.29136663950885C39.52591388646397,43.805569708302755,36.667529059923254,40.97972623835358,33.141447219852125,40.97972623835358C29.615457564927056,40.97972623835358,26.75698055324392,43.805569708302755,26.75698055324392,47.29136663950885C26.75698055324392,50.77716357071449,29.615457564927056,53.60300704066367,33.141447219852125,53.60300704066367ZZ" style="fill: rgb(227, 227, 227); fill-opacity: 1; stroke-width: 2; stroke: white;"/></g><g id="shape-305b39c8-25c6-11ec-9877-05429cda5971"><path d="M52.22921139562823,5.9902455968044706C47.897615748537646,2.9522656685448965,42.82503589672706,1.0223696339853632,37.493231413951435,0.383799439228369C32.16142693117581,-0.2547744429339218,26.74730111289682,0.4191478335183092,21.76469396372704,2.341603604932061L64.9989743951628,32.66471824116161C65.42634473269572,27.547336408323645,64.47748302341643,22.41059569479512,62.24254634026147,17.742063338540447C60.007609657106514,13.073623167432288,56.56080704272608,9.028299273180437,52.22921139562823,5.9902455968044706ZZ" style="fill: rgb(227, 227, 227); fill-opacity: 1;"/></g></g></svg>
|
After Width: | Height: | Size: 4.1 KiB |
1
frontend/resources/images/ph-right.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns:xlink="http://www.w3.org/1999/xlink" width="102.12322708597458" xmlns="http://www.w3.org/2000/svg" id="screenshot" version="1.1" viewBox="-0.2830548330293823 0.00013327541864782688 102.12322708597458 54.02153523420793" height="54.02153523420793" style="-webkit-print-color-adjust: exact;"><g id="shape-305b39ca-25c6-11ec-9877-05429cda5971" width="133" height="70" fill="none"><g id="shape-305b39cb-25c6-11ec-9877-05429cda5971"><path d="M92.06001883709541,52.780900512351764C92.66931429164106,49.56773051235177,86.02942338254798,47.840623239624165,83.94800520073477,47.06227823962445C89.18168701891409,45.781289603260575,101.50092247346038,49.98474823962442,101.82747470073446,46.54621233053331C102.1541065189158,43.10765323962414,96.1049138370945,39.43658051235161,92.18095065527814,37.78441233053354C88.2568870189134,36.13216687598788,82.26786429164349,34.273371421442334,83.30053701891302,33.860271421442576C84.33313247346086,33.4472486941695,93.48809610982426,36.99089869416957,94.37155520073429,34.2504214214423C95.25488292800583,31.510021421442616,89.671905200732,29.82478051235148,85.57220065527872,28.284194148715414C74.89666429164208,24.27258051235185,76.95853247345804,0.00013505780589184724,76.95853247345804,0.00013505780589184724L-0.28305389017623384,0.486489603260452C-0.28305389017623384,1.519162330533618,10.851173382550769,39.85655778507862,33.94041883709724,49.143364148715136C49.09020065527875,55.740090512351344,63.87718701891572,49.16363278507879,75.95035520072997,51.06297323962417C82.07081883709907,52.068059603260735,91.45064610982081,55.99406278507877,92.06001883709541,52.780900512351764ZZ" style="fill: rgb(227, 227, 227); fill-opacity: 1;"/></g><g id="shape-305b39cc-25c6-11ec-9877-05429cda5971"><path d="M39.658059746187064,27.905789603260928C42.341277928004274,26.911598694169697,45.32354156436486,28.28403960326068,46.31920065527811,30.971275966897338C47.314859746184084,33.658435057805946,45.946823382550065,36.64278505780612,43.263605200732854,37.6369759668969C40.580387018912006,38.631089603260534,37.59812338255142,37.258648694170006,36.60246429163817,34.57148960326049C35.6068052007322,31.884253239624286,36.974841564369854,28.89990323962411,39.658059746187064,27.905789603260928ZZ" style="stroke-width: 2; stroke: white;"/></g><g id="shape-305b39cd-25c6-11ec-9877-05429cda5971"><path d="M59.423496109822736,14.73643051235149C57.15994610982307,9.680012330533373,49.1343233825537,2.492953239624512,37.54835974618618,6.785685057806404C25.962164291642694,11.078494148715436,26.02405974618887,21.21628960326052,27.201927928006626,26.674912330533516" style="stroke-width: 2; stroke: white;"/></g></g></svg>
|
After Width: | Height: | Size: 2.6 KiB |
|
@ -335,6 +335,20 @@
|
|||
padding: 3rem;
|
||||
justify-content: center;
|
||||
|
||||
&.drafts {
|
||||
background-image: url("/images/ph-left.svg"), url("/images/ph-right.svg");
|
||||
background-position: 15% bottom, 85% top;
|
||||
background-repeat: no-repeat;
|
||||
.text {
|
||||
p {
|
||||
max-width: 360px;
|
||||
text-align: center;
|
||||
font-size: $fs16;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
svg {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
|
@ -346,5 +360,10 @@
|
|||
color: $color-gray-30;
|
||||
font-size: $fs16;
|
||||
}
|
||||
|
||||
img.ph-files {
|
||||
height: 150px;
|
||||
margin-right: calc(100% - 148px);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 448px;
|
||||
background-color: $color-dashboard;
|
||||
background-color: $color-white;
|
||||
|
||||
.modal-header {
|
||||
align-items: center;
|
||||
|
@ -705,7 +705,7 @@
|
|||
background-color: $color-white;
|
||||
box-shadow: 0 10px 10px rgba(0,0,0,.2);
|
||||
display: flex;
|
||||
min-height: 370px;
|
||||
min-height: 420px;
|
||||
flex-direction: row;
|
||||
font-family: "sourcesanspro", sans-serif;
|
||||
min-width: 620px;
|
||||
|
@ -824,21 +824,93 @@
|
|||
}
|
||||
|
||||
&.final {
|
||||
// TODO: Juan revisa TODA esta parte
|
||||
|
||||
padding: $size-5 0 0 0;
|
||||
flex-direction: column;
|
||||
|
||||
.modal-top {
|
||||
padding-top: 40px;
|
||||
color: $color-gray-60;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
h1 {
|
||||
font-family: 'worksans', sans-serif;
|
||||
font-weight: 700;
|
||||
font-size: 27px;
|
||||
margin-bottom: $size-3;
|
||||
}
|
||||
p {
|
||||
font-family: 'worksans', sans-serif;
|
||||
font-weight: 500;
|
||||
font-size: $fs18;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.modal-columns {
|
||||
display: flex;
|
||||
margin: 17px;
|
||||
|
||||
.modal-left {
|
||||
background-image: url("/images/on-solo.svg");
|
||||
background-position: left top;
|
||||
background-size: 11%;
|
||||
}
|
||||
|
||||
.modal-left:hover {
|
||||
background-image: url("/images/on-solo-hover.svg");
|
||||
background-size: 15%;
|
||||
}
|
||||
|
||||
.modal-right {
|
||||
background-image: url("/images/on-teamup.svg");
|
||||
background-position: right top;
|
||||
background-size: 28%;
|
||||
}
|
||||
|
||||
.modal-right:hover {
|
||||
background-image: url("/images/on-teamup-hover.svg");
|
||||
background-size: 32%;
|
||||
}
|
||||
|
||||
.modal-right,
|
||||
.modal-left {
|
||||
background-repeat: no-repeat;
|
||||
border-radius: $br-medium;
|
||||
transition: all ease .3s;
|
||||
&:hover {
|
||||
background-color: $color-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-left {
|
||||
margin-right: 35px;
|
||||
}
|
||||
|
||||
.modal-left,
|
||||
.modal-right {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: $color-white;
|
||||
color: $color-black;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
overflow: visible;
|
||||
padding: $size-6 40px;
|
||||
// overflow: visible;
|
||||
// padding: $size-6 40px;
|
||||
text-align: center;
|
||||
|
||||
border: 1px solid $color-gray-10;
|
||||
border-radius: 2px;
|
||||
min-height: 180px;
|
||||
width: 233px;
|
||||
cursor: pointer;
|
||||
|
||||
h2 {
|
||||
font-weight: 900;
|
||||
font-weight: 700;
|
||||
margin-bottom: $size-5;
|
||||
font-size: $fs24;
|
||||
}
|
||||
|
@ -847,12 +919,6 @@
|
|||
font-size: $fs14;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
margin-bottom: 0;
|
||||
margin-top: auto;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
img {
|
||||
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.25);
|
||||
border-radius: $br-medium;
|
||||
|
@ -861,26 +927,6 @@
|
|||
width: 150px;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-left {
|
||||
border-right: 1px solid $color-gray-10;
|
||||
|
||||
form {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: auto;
|
||||
|
||||
.custom-input {
|
||||
margin-bottom: $size-4;
|
||||
|
||||
input {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -899,3 +945,193 @@
|
|||
.relnotes .onboarding {
|
||||
height: 420px;
|
||||
}
|
||||
|
||||
.onboarding-templates {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 348px;
|
||||
height: 100vh;
|
||||
|
||||
.modal-close-button {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
margin-right: 13px;
|
||||
margin-top: 13px;
|
||||
svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
height: unset;
|
||||
border-radius: unset;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
border: 0px;
|
||||
padding: 0px 25px;
|
||||
background-color: $color-white;
|
||||
flex-grow: 1;
|
||||
|
||||
p, h3 {
|
||||
color: $color-gray-60;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: $fs18;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: $fs16;
|
||||
}
|
||||
|
||||
|
||||
.templates {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 8%;
|
||||
}
|
||||
|
||||
.template-item {
|
||||
width: 275px;
|
||||
border: 1px solid $color-gray-10;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: left;
|
||||
border-radius: $br-small;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.template-item-content {
|
||||
// height: 144px;
|
||||
flex-grow: 1;
|
||||
|
||||
img {
|
||||
border-radius: $br-small $br-small 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.template-item-title {
|
||||
padding: 6px 12px;
|
||||
height: 64px;
|
||||
border-top: 1px solid $color-gray-10;
|
||||
|
||||
.label {
|
||||
color: $color-black;
|
||||
padding: 0px 4px;
|
||||
font-size: $fs16;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.action {
|
||||
color: $color-primary-dark;
|
||||
cursor: pointer;
|
||||
font-size: $fs14;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: $size-2;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.onboarding-team {
|
||||
display: flex;
|
||||
min-width: 620px;
|
||||
min-height: 420px;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 408px;
|
||||
|
||||
color: $color-gray-60;
|
||||
h2 {
|
||||
font-weight: 700;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: center;
|
||||
font-size: $fs18;
|
||||
}
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: $size-6;
|
||||
|
||||
.buttons {
|
||||
margin-top: 30px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
> *:not(:last-child) {
|
||||
margin-right: 13px;
|
||||
}
|
||||
|
||||
input { margin-bottom: unset; }
|
||||
input[type=submit] {
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
width: 117px;
|
||||
}
|
||||
}
|
||||
|
||||
.team-row {
|
||||
.custom-input {
|
||||
width: 459px;
|
||||
}
|
||||
}
|
||||
|
||||
.invite-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
> *:not(:last-child) {
|
||||
margin-right: 13px;
|
||||
}
|
||||
|
||||
.custom-input {
|
||||
width: 321px;
|
||||
}
|
||||
|
||||
.custom-select {
|
||||
width: 118px;
|
||||
}
|
||||
}
|
||||
|
||||
.skip-action {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 15px;
|
||||
.action {
|
||||
color: $color-primary-dark;
|
||||
font-weight: 500;
|
||||
font-size: $fs16;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -300,6 +300,28 @@
|
|||
(rx/map team-created)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
;; --- EVENT: create-team-with-invitations
|
||||
|
||||
;; NOTE: right now, it only handles a single email, in a near future
|
||||
;; this will be changed to the ability to specify multiple emails.
|
||||
|
||||
(defn create-team-with-invitations
|
||||
[{:keys [name email role] :as params}]
|
||||
(us/assert string? name)
|
||||
(ptk/reify ::create-team-with-invitations
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(let [{:keys [on-success on-error]
|
||||
:or {on-success identity
|
||||
on-error rx/throw}} (meta params)
|
||||
params {:name name
|
||||
:emails #{email}
|
||||
:role role}]
|
||||
(->> (rp/mutation! :create-team-and-invite-members params)
|
||||
(rx/tap on-success)
|
||||
(rx/map team-created)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
;; --- EVENT: update-team
|
||||
|
||||
(defn update-team
|
||||
|
|
|
@ -61,12 +61,6 @@
|
|||
(def dashboard-search-result
|
||||
(l/derived :dashboard-search-result st/state))
|
||||
|
||||
(def dashboard-team
|
||||
(l/derived (fn [state]
|
||||
(let [team-id (:current-team-id state)]
|
||||
(get-in state [:teams team-id])))
|
||||
st/state))
|
||||
|
||||
(def dashboard-team-stats
|
||||
(l/derived :dashboard-team-stats st/state))
|
||||
|
||||
|
|
|
@ -72,7 +72,10 @@
|
|||
:dashboard-team-settings)
|
||||
[:*
|
||||
#_[:div.modal-wrapper
|
||||
[:& app.main.ui.onboarding/release-notes-modal {:version "1.8"}]]
|
||||
#_[:& app.main.ui.onboarding/onboarding-templates-modal]
|
||||
#_[:& app.main.ui.onboarding/onboarding-modal]
|
||||
#_[:& app.main.ui.onboarding/onboarding-team-modal]
|
||||
]
|
||||
[:& dashboard {:route route}]]
|
||||
|
||||
:viewer
|
||||
|
|
|
@ -104,11 +104,11 @@
|
|||
(when (and (:onboarding-viewed props)
|
||||
(not= version (:main @cf/version))
|
||||
(not= "0.0" (:main @cf/version)))
|
||||
(tm/schedule 1000 #(st/emit! (modal/show {:type :release-notes :version (:main @cf/version)})))))))
|
||||
(tm/schedule 1000 #(st/emit! (modal/show {:type :release-notes
|
||||
:version (:main @cf/version)})))))))
|
||||
|
||||
[:& (mf/provider ctx/current-team-id) {:value team-id}
|
||||
[:& (mf/provider ctx/current-project-id) {:value project-id}
|
||||
|
||||
;; NOTE: dashboard events and other related functions assumes
|
||||
;; that the team is a implicit context variable that is
|
||||
;; available using react context or accessing
|
||||
|
|
|
@ -120,6 +120,6 @@
|
|||
[:*
|
||||
[:& header {:team team :project project}]
|
||||
[:section.dashboard-container
|
||||
[:& grid {:project-id (:id project)
|
||||
[:& grid {:project project
|
||||
:files files}]]]))
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
[app.main.ui.dashboard.file-menu :refer [file-menu]]
|
||||
[app.main.ui.dashboard.import :refer [use-import-file]]
|
||||
[app.main.ui.dashboard.inline-edition :refer [inline-edition]]
|
||||
[app.main.ui.dashboard.placeholder :refer [empty-placeholder loading-placeholder]]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.worker :as wrk]
|
||||
[app.util.dom :as dom]
|
||||
|
@ -195,24 +196,10 @@
|
|||
:on-edit on-edit
|
||||
:on-menu-close on-menu-close}])]]]))
|
||||
|
||||
(mf/defc empty-placeholder
|
||||
[{:keys [dragging?] :as props}]
|
||||
(if-not dragging?
|
||||
[:div.grid-empty-placeholder
|
||||
[:div.icon i/file-html]
|
||||
[:div.text (tr "dashboard.empty-files")]]
|
||||
[:div.grid-row.no-wrap
|
||||
[:div.grid-item]]))
|
||||
|
||||
(mf/defc loading-placeholder
|
||||
[]
|
||||
[:div.grid-empty-placeholder
|
||||
[:div.icon i/loader]
|
||||
[:div.text (tr "dashboard.loading-files")]])
|
||||
|
||||
(mf/defc grid
|
||||
[{:keys [files project-id] :as props}]
|
||||
(let [dragging? (mf/use-state false)
|
||||
[{:keys [files project] :as props}]
|
||||
(let [dragging? (mf/use-state false)
|
||||
project-id (:id project)
|
||||
|
||||
on-finish-import
|
||||
(mf/use-callback
|
||||
|
@ -272,7 +259,7 @@
|
|||
:navigate? true}])]
|
||||
|
||||
:else
|
||||
[:& empty-placeholder])]))
|
||||
[:& empty-placeholder {:default? (:is-default project)}])]))
|
||||
|
||||
(mf/defc line-grid-row
|
||||
[{:keys [files selected-files on-load-more dragging?] :as props}]
|
||||
|
@ -330,8 +317,11 @@
|
|||
(tr "dashboard.show-all-files")]])]))
|
||||
|
||||
(mf/defc line-grid
|
||||
[{:keys [project-id team-id files on-load-more] :as props}]
|
||||
[{:keys [project team files on-load-more] :as props}]
|
||||
(let [dragging? (mf/use-state false)
|
||||
project-id (:id project)
|
||||
team-id (:id team)
|
||||
|
||||
selected-files (mf/deref refs/dashboard-selected-files)
|
||||
selected-project (mf/deref refs/dashboard-selected-project)
|
||||
|
||||
|
@ -413,5 +403,6 @@
|
|||
:dragging? @dragging?}]
|
||||
|
||||
:else
|
||||
[:& empty-placeholder {:dragging? @dragging?}])]))
|
||||
[:& empty-placeholder {:dragging? @dragging?
|
||||
:default? (:is-default project)}])]))
|
||||
|
||||
|
|
34
frontend/src/app/main/ui/dashboard/placeholder.cljs
Normal file
|
@ -0,0 +1,34 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) UXBOX Labs SL
|
||||
|
||||
(ns app.main.ui.dashboard.placeholder
|
||||
(:require
|
||||
[app.main.ui.icons :as i]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(mf/defc empty-placeholder
|
||||
[{:keys [dragging? default?] :as props}]
|
||||
(cond
|
||||
(true? dragging?)
|
||||
[:div.grid-row.no-wrap
|
||||
[:div.grid-item]]
|
||||
|
||||
(true? default?)
|
||||
[:div.grid-empty-placeholder.drafts
|
||||
[:div.text
|
||||
[:& i18n/tr-html {:label "dashboard.empty-placeholder-drafts"}]]]
|
||||
|
||||
:else
|
||||
[:div.grid-empty-placeholder
|
||||
[:img.ph-files {:src "images/ph-file.svg"}]]))
|
||||
|
||||
(mf/defc loading-placeholder
|
||||
[]
|
||||
[:div.grid-empty-placeholder
|
||||
[:div.icon i/loader]
|
||||
[:div.text (tr "dashboard.loading-files")]])
|
||||
|
|
@ -73,7 +73,6 @@
|
|||
:accept-label (tr "modals.delete-project-confirm.accept")
|
||||
:on-accept delete-fn}))
|
||||
|
||||
|
||||
file-input (mf/use-ref nil)
|
||||
|
||||
on-import-files
|
||||
|
|
|
@ -33,10 +33,8 @@
|
|||
(tr "dashboard.new-project")]]))
|
||||
|
||||
(mf/defc project-item
|
||||
[{:keys [project first? files] :as props}]
|
||||
[{:keys [project first? team files] :as props}]
|
||||
(let [locale (mf/deref i18n/locale)
|
||||
|
||||
team-id (:team-id project)
|
||||
file-count (or (:count project) 0)
|
||||
|
||||
dstate (mf/deref refs/dashboard-local)
|
||||
|
@ -145,9 +143,8 @@
|
|||
i/actions]]
|
||||
|
||||
[:& line-grid
|
||||
{:project-id (:id project)
|
||||
:project project
|
||||
:team-id team-id
|
||||
{:project project
|
||||
:team team
|
||||
:on-load-more on-nav
|
||||
:files files}]]))
|
||||
|
||||
|
@ -186,7 +183,8 @@
|
|||
(filterv #(= id (:project-id %)))
|
||||
(sort-by :modified-at #(compare %2 %1))))]
|
||||
[:& project-item {:project project
|
||||
:files files
|
||||
:team team
|
||||
:files files
|
||||
:first? (= project (first projects))
|
||||
:key (:id project)}]))]])))
|
||||
|
||||
|
|
|
@ -72,8 +72,9 @@
|
|||
#(doseq [key keys]
|
||||
(events/unlistenByKey key)))))
|
||||
|
||||
[:div.modal-wrapper {:ref wrapper-ref}
|
||||
(mf/element (get components (:type data)) (:props data))]))
|
||||
(when-let [component (get components (:type data))]
|
||||
[:div.modal-wrapper {:ref wrapper-ref}
|
||||
(mf/element component (:props data))])))
|
||||
|
||||
|
||||
(def modal-ref
|
||||
|
|
|
@ -12,8 +12,10 @@
|
|||
[app.main.data.messages :as dm]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.users :as du]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.forms :as fm]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.ui.releases.common :as rc]
|
||||
[app.main.ui.releases.v1-4]
|
||||
[app.main.ui.releases.v1-5]
|
||||
|
@ -21,10 +23,13 @@
|
|||
[app.main.ui.releases.v1-7]
|
||||
[app.main.ui.releases.v1-8]
|
||||
[app.main.ui.releases.v1-9]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.http :as http]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.object :as obj]
|
||||
[app.util.router :as rt]
|
||||
[app.util.timers :as tm]
|
||||
[beicon.core :as rx]
|
||||
[cljs.spec.alpha :as s]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
|
@ -159,7 +164,7 @@
|
|||
skip
|
||||
(mf/use-callback
|
||||
(st/emitf (modal/hide)
|
||||
(modal/show {:type :onboarding-team})
|
||||
(modal/show {:type :onboarding-choice})
|
||||
(du/mark-onboarding-as-viewed)))]
|
||||
|
||||
(mf/use-layout-effect
|
||||
|
@ -187,57 +192,232 @@
|
|||
(s/def ::team-form
|
||||
(s/keys :req-un [::name]))
|
||||
|
||||
(mf/defc onboarding-choice-modal
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :onboarding-choice}
|
||||
[]
|
||||
(let [;; When user choices the option of `fly solo`, we proceed to show
|
||||
;; the onboarding templates modal.
|
||||
on-fly-solo
|
||||
(fn []
|
||||
(tm/schedule 400 #(st/emit! (modal/show {:type :onboarding-templates}))))
|
||||
|
||||
;; When user choices the option of `team up`, we proceed to show
|
||||
;; the team creation modal.
|
||||
on-team-up
|
||||
(fn []
|
||||
(st/emit! (modal/show {:type :onboarding-team})))
|
||||
]
|
||||
|
||||
[:div.modal-overlay
|
||||
[:div.modal-container.onboarding.final.animated.fadeInUp
|
||||
[:div.modal-top
|
||||
[:h1 (tr "onboarding.welcome.title")]
|
||||
[:p (tr "onboarding.welcome.desc3")]]
|
||||
[:div.modal-columns
|
||||
[:div.modal-left
|
||||
[:div.content-button {:on-click on-fly-solo}
|
||||
[:h2 (tr "onboarding.choice.fly-solo")]
|
||||
[:p (tr "onboarding.choice.fly-solo-desc")]]]
|
||||
[:div.modal-right
|
||||
[:div.content-button {:on-click on-team-up}
|
||||
[:h2 (tr "onboarding.choice.team-up")]
|
||||
[:p (tr "onboarding.choice.team-up-desc")]]]]
|
||||
[:img.deco {:src "images/deco-left.png" :border "0"}]
|
||||
[:img.deco.right {:src "images/deco-right.png" :border "0"}]]]))
|
||||
|
||||
(mf/defc onboarding-team-modal
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :onboarding-team}
|
||||
[]
|
||||
(let [close (mf/use-fn (st/emitf (modal/hide)))
|
||||
form (fm/use-form :spec ::team-form
|
||||
(let [form (fm/use-form :spec ::team-form
|
||||
:initial {})
|
||||
on-submit
|
||||
(mf/use-callback
|
||||
(fn [form _]
|
||||
(let [tname (get-in @form [:clean-data :name])]
|
||||
(st/emit! (modal/show {:type :onboarding-team-invitations :name tname})))))]
|
||||
|
||||
[:div.modal-overlay
|
||||
[:div.modal-container.onboarding-team
|
||||
[:div.title
|
||||
[:h2 (tr "onboarding.choice.team-up")]
|
||||
[:p (tr "onboarding.choice.team-up-desc")]]
|
||||
|
||||
[:& fm/form {:form form
|
||||
:on-submit on-submit}
|
||||
|
||||
[:div.team-row
|
||||
[:& fm/input {:type "text"
|
||||
:name :name
|
||||
:label (tr "onboarding.team-input-placeholder")}]]
|
||||
|
||||
[:div.buttons
|
||||
[:button.btn-secondary.btn-large
|
||||
{:on-click #(st/emit! (modal/show {:type :onboarding-choice}))}
|
||||
(tr "labels.cancel")]
|
||||
[:& fm/submit-button
|
||||
{:label (tr "labels.next")}]]]
|
||||
|
||||
[:img.deco {:src "images/deco-left.png" :border "0"}]
|
||||
[:img.deco.right {:src "images/deco-right.png" :border "0"}]]]))
|
||||
|
||||
(defn get-available-roles
|
||||
[]
|
||||
[{:value "editor" :label (tr "labels.editor")}
|
||||
{:value "admin" :label (tr "labels.admin")}])
|
||||
|
||||
(s/def ::email ::us/email)
|
||||
(s/def ::role ::us/keyword)
|
||||
(s/def ::invite-form
|
||||
(s/keys :req-un [::role ::email]))
|
||||
|
||||
;; This is the final step of team creation, consists in provide a
|
||||
;; shortcut for invite users.
|
||||
|
||||
(mf/defc onboarding-team-invitations-modal
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :onboarding-team-invitations}
|
||||
[{:keys [name] :as props}]
|
||||
(let [initial (mf/use-memo (constantly
|
||||
{:role "editor"
|
||||
:name name}))
|
||||
form (fm/use-form :spec ::invite-form
|
||||
:initial initial)
|
||||
|
||||
roles (mf/use-memo #(get-available-roles))
|
||||
|
||||
on-success
|
||||
(mf/use-callback
|
||||
(fn [_form response]
|
||||
(st/emit! (modal/hide)
|
||||
(rt/nav :dashboard-projects {:team-id (:id response)}))))
|
||||
(let [project-id (:default-project-id response)
|
||||
team-id (:id response)]
|
||||
(st/emit!
|
||||
(modal/hide)
|
||||
(rt/nav :dashboard-projects {:team-id team-id}))
|
||||
(tm/schedule 400 #(st/emit!
|
||||
(modal/show {:type :onboarding-templates
|
||||
:project-id project-id}))))))
|
||||
|
||||
on-error
|
||||
(mf/use-callback
|
||||
(fn [_form _response]
|
||||
(st/emit! (dm/error "Error on creating team."))))
|
||||
|
||||
on-submit
|
||||
;; The SKIP branch only creates the team, without invitations
|
||||
on-skip
|
||||
(mf/use-callback
|
||||
(fn [form _event]
|
||||
(fn [_]
|
||||
(let [mdata {:on-success (partial on-success form)
|
||||
:on-error (partial on-error form)}
|
||||
params {:name (get-in @form [:clean-data :name])}]
|
||||
(st/emit! (dd/create-team (with-meta params mdata))))))]
|
||||
params {:name name}]
|
||||
(st/emit! (dd/create-team (with-meta params mdata))))))
|
||||
|
||||
;; The SUBMIT branch creates the team with the invitations
|
||||
on-submit
|
||||
(mf/use-callback
|
||||
(fn [form _]
|
||||
(let [mdata {:on-success (partial on-success form)
|
||||
:on-error (partial on-error form)}
|
||||
params (:clean-data @form)]
|
||||
(st/emit! (dd/create-team-with-invitations (with-meta params mdata))))))]
|
||||
|
||||
[:div.modal-overlay
|
||||
[:div.modal-container.onboarding.final.animated.fadeInUp
|
||||
[:div.modal-left
|
||||
[:img {:src "images/onboarding-team.jpg" :border "0" :alt (tr "onboarding.team.create.title")}]
|
||||
[:h2 (tr "onboarding.team.create.title")]
|
||||
[:p (tr "onboarding.team.create.desc1")]
|
||||
[:div.modal-container.onboarding-team
|
||||
[:div.title
|
||||
[:h2 (tr "onboarding.choice.team-up")]
|
||||
[:p (tr "onboarding.choice.team-up-desc")]]
|
||||
|
||||
[:& fm/form {:form form
|
||||
:on-submit on-submit}
|
||||
[:& fm/input {:type "text"
|
||||
:name :name
|
||||
:label (tr "onboarding.team.create.input-placeholder")}]
|
||||
[:& fm/form {:form form
|
||||
:on-submit on-submit}
|
||||
|
||||
[:div.invite-row
|
||||
[:& fm/input {:name :email
|
||||
:label (tr "labels.email")}]
|
||||
[:& fm/select {:name :role
|
||||
:options roles}]]
|
||||
|
||||
[:div.buttons
|
||||
[:button.btn-secondary.btn-large
|
||||
{:on-click #(st/emit! (modal/show {:type :onboarding-choice}))}
|
||||
(tr "labels.cancel")]
|
||||
[:& fm/submit-button
|
||||
{:label (tr "onboarding.team.create.button")}]]]
|
||||
|
||||
[:div.modal-right
|
||||
[:img {:src "images/onboarding-start.jpg" :border "0" :alt (tr "onboarding.team.start.title")}]
|
||||
[:h2 (tr "onboarding.team.start.title")]
|
||||
[:p (tr "onboarding.team.start.desc1")]
|
||||
[:button.btn-primary.btn-large {:on-click close} (tr "onboarding.team.start.button")]]
|
||||
|
||||
|
||||
{:label (tr "labels.create")}]]
|
||||
[:div.skip-action
|
||||
{:on-click on-skip}
|
||||
[:div.action "Skip and invite later"]]]
|
||||
[:img.deco {:src "images/deco-left.png" :border "0"}]
|
||||
[:img.deco.right {:src "images/deco-right.png" :border "0"}]]]))
|
||||
|
||||
(mf/defc template-item
|
||||
[{:keys [name path image project-id]}]
|
||||
(let [downloading? (mf/use-state false)
|
||||
link (str (assoc cf/public-uri :path path))
|
||||
|
||||
on-finish-import
|
||||
(fn []
|
||||
(st/emit! (dd/fetch-recent-files)))
|
||||
|
||||
open-import-modal
|
||||
(fn [file]
|
||||
(st/emit! (modal/show
|
||||
{:type :import
|
||||
:project-id project-id
|
||||
:files [file]
|
||||
:on-finish-import on-finish-import})))
|
||||
on-click
|
||||
(fn []
|
||||
(reset! downloading? true)
|
||||
(->> (http/send! {:method :get :uri link :response-type :blob :mode :no-cors})
|
||||
(rx/subs (fn [{:keys [body] :as response}]
|
||||
(open-import-modal {:name name :uri (dom/create-uri body)}))
|
||||
(fn [error]
|
||||
(js/console.log "error" error))
|
||||
(fn []
|
||||
(reset! downloading? false)))))
|
||||
]
|
||||
|
||||
[:div.template-item
|
||||
[:div.template-item-content
|
||||
[:img {:src image}]]
|
||||
[:div.template-item-title
|
||||
[:div.label name]
|
||||
(if @downloading?
|
||||
[:div.action "Fetching..."]
|
||||
[:div.action {:on-click on-click} "+ Add to drafts"])]]))
|
||||
|
||||
(mf/defc onboarding-templates-modal
|
||||
{::mf/wrap-props false
|
||||
::mf/register modal/components
|
||||
::mf/register-as :onboarding-templates}
|
||||
;; NOTE: the project usually comes empty, it only comes fullfilled
|
||||
;; when a user creates a new team just after signup.
|
||||
[{:keys [project-id] :as props}]
|
||||
(let [close-fn (mf/use-callback #(st/emit! (modal/hide)))
|
||||
profile (mf/deref refs/profile)
|
||||
project-id (or project-id (:default-project-id profile))]
|
||||
[:div.modal-overlay
|
||||
[:div.modal-container.onboarding-templates
|
||||
[:div.modal-header
|
||||
[:div.modal-close-button
|
||||
{:on-click close-fn} i/close]]
|
||||
|
||||
[:div.modal-content
|
||||
[:h3 (tr "onboarding.templates.title")]
|
||||
[:p (tr "onboarding.templates.subtitle")]
|
||||
|
||||
[:div.templates
|
||||
[:& template-item
|
||||
{:path "/github/penpot-files/Penpot-Design-system.penpot"
|
||||
:image "https://penpot.app/images/libraries/cover-ds-penpot.jpg"
|
||||
:name "Penpot Design System"
|
||||
:project-id project-id}]
|
||||
[:& template-item
|
||||
{:path "/github/penpot-files/Material-Design-Kit.penpot"
|
||||
:image "https://penpot.app/images/libraries/cover-material.jpg"
|
||||
:name "Material Design Kit"
|
||||
:project-id project-id}]]]]]))
|
||||
|
||||
|
||||
;;; --- RELEASE NOTES MODAL
|
||||
|
||||
|
|
|
@ -169,7 +169,7 @@
|
|||
(rx/tap #(do (swap! current inc)
|
||||
(progress! context
|
||||
:upload-data @current total))))))
|
||||
|
||||
|
||||
(rx/map first)
|
||||
(rx/tap #(reset! revn (:revn %)))
|
||||
(rx/ignore))
|
||||
|
|
|
@ -250,6 +250,12 @@ msgstr "Duplicate %s files"
|
|||
msgid "dashboard.empty-files"
|
||||
msgstr "You still have no files here"
|
||||
|
||||
|
||||
#: src/app/main/ui/dashboard/grid.cljs
|
||||
#, markdown
|
||||
msgid "dashboard.empty-placeholder-drafts"
|
||||
msgstr "Oh no! You have no files jet!</br> If you want to try with some templates go to [templates.penpot.app](https://penpot.app/libraries-templates.html)"
|
||||
|
||||
msgid "dashboard.export-frames"
|
||||
msgstr "Export artboards to PDF..."
|
||||
|
||||
|
@ -1533,6 +1539,13 @@ msgstr "Profile saved successfully!"
|
|||
msgid "notifications.validation-email-sent"
|
||||
msgstr "Verification email sent to %s. Check your email!"
|
||||
|
||||
|
||||
msgid "onboarding.templates.title"
|
||||
msgstr "Start designing"
|
||||
|
||||
msgid "onboarding.templates.subtitle"
|
||||
msgid "Here are some templates."
|
||||
|
||||
msgid "onboarding.contrib.alt"
|
||||
msgstr "Open Source"
|
||||
|
||||
|
@ -1606,31 +1619,27 @@ msgstr ""
|
|||
msgid "onboarding.slide.3.title"
|
||||
msgstr "One shared source of truth"
|
||||
|
||||
msgid "onboarding.team.create.button"
|
||||
msgstr "Create a team"
|
||||
msgid "onboarding.choice.fly-solo"
|
||||
msgstr "Fly solo"
|
||||
|
||||
msgid "onboarding.team.create.desc1"
|
||||
msgstr ""
|
||||
"Are you working with someone? Create a team to work together on projects "
|
||||
"and share design assets."
|
||||
msgid "onboarding.choice.fly-solo-desc"
|
||||
msgstr "Jump away into Penpot and start designing by your own."
|
||||
|
||||
msgid "onboarding.team.create.input-placeholder"
|
||||
msgid "onboarding.choice.team-up"
|
||||
msgstr "Team up"
|
||||
|
||||
msgid "onboarding.team.skip-and-invite-later"
|
||||
msgstr "Skip and invite later"
|
||||
|
||||
msgid "onboarding.choice.team-up-desc"
|
||||
msgstr "Are you working with someone? Create a team and invite people to work together on projects and share design assets."
|
||||
|
||||
msgid "labels.next"
|
||||
msgstr "Next"
|
||||
|
||||
msgid "onboarding.team-input-placeholder"
|
||||
msgstr "Enter new team name"
|
||||
|
||||
msgid "onboarding.team.create.title"
|
||||
msgstr "Create team"
|
||||
|
||||
msgid "onboarding.team.start.button"
|
||||
msgstr "Start right away"
|
||||
|
||||
msgid "onboarding.team.start.desc1"
|
||||
msgstr ""
|
||||
"Jump right away into Penpot and start designing by your own. You will still "
|
||||
"have the chance to create teams later."
|
||||
|
||||
msgid "onboarding.team.start.title"
|
||||
msgstr "Start designing"
|
||||
|
||||
msgid "onboarding.welcome.alt"
|
||||
msgstr "Penpot"
|
||||
|
||||
|
@ -1642,6 +1651,9 @@ msgstr ""
|
|||
"Penpot is still at development stage and there will be constant updates. We "
|
||||
"hope you enjoy the first stable version."
|
||||
|
||||
msgid "onboarding.welcome.desc3"
|
||||
msgstr "How do you want to start?"
|
||||
|
||||
msgid "onboarding.welcome.title"
|
||||
msgstr "Welcome to Penpot!"
|
||||
|
||||
|
@ -3187,4 +3199,5 @@ msgid "workspace.updates.update"
|
|||
msgstr "Update"
|
||||
|
||||
msgid "workspace.viewport.click-to-close-path"
|
||||
msgstr "Click to close the path"
|
||||
msgstr "Click to close the path"
|
||||
|
||||
|
|