Skip to content

Commit

Permalink
Merge pull request #1098 from concord-consortium/186428490-add-projec…
Browse files Browse the repository at this point in the history
…t-project-admin-ui

feat: Added UI for showing/removing admins of a project [PT-186428490]
  • Loading branch information
dougmartin authored Nov 21, 2023
2 parents d95313d + 3357e61 commit 26cbd53
Show file tree
Hide file tree
Showing 11 changed files with 408 additions and 152 deletions.
132 changes: 93 additions & 39 deletions app/assets/javascripts/lara-typescript.js
Original file line number Diff line number Diff line change
Expand Up @@ -124633,17 +124633,21 @@ var ProjectSettingsForm = function (_a) {
project = _g[0],
setProject = _g[1];

var _h = (0, react_1.useState)(false),
projectLoaded = _h[0],
setProjectLoaded = _h[1];
var _h = (0, react_1.useState)([]),
admins = _h[0],
setAdmins = _h[1];

var _j = (0, react_1.useState)(false),
isNewProject = _j[0],
setIsNewProject = _j[1];
projectLoaded = _j[0],
setProjectLoaded = _j[1];

var _k = (0, react_1.useState)(false),
projectSaved = _k[0],
setProjectSaved = _k[1];
isNewProject = _k[0],
setIsNewProject = _k[1];

var _l = (0, react_1.useState)(false),
projectSaved = _l[0],
setProjectSaved = _l[1];

(0, react_1.useEffect)(function () {
if (id) {
Expand Down Expand Up @@ -124685,6 +124689,7 @@ var ProjectSettingsForm = function (_a) {
delete data.project.created_at;
delete data.project.updated_at;
setProject((0, convert_keys_1.snakeToCamelCaseKeys)(data.project));
setAdmins(data.admins || []);
setProjectLoaded(true);
setPageTitle("Edit " + data.project.title);
return [2
Expand Down Expand Up @@ -124741,11 +124746,9 @@ var ProjectSettingsForm = function (_a) {
var handleCollaboratorsImageUrlChange = handleTextInputChange("collaboratorsImageUrl");
var handleContactEmailChange = handleTextInputChange("contactEmail");
var handleCopyrightImageUrlChange = handleTextInputChange("copyrightImageUrl");
var handleFooterChange = handleTextareaChange("footer");
var handleFundersImageUrlChange = handleTextInputChange("fundersImageUrl");
var handleKeyChange = handleTextInputChange("projectKey");
var handleLogoApChange = handleTextInputChange("logoAp");
var handleLogoLaraChange = handleTextInputChange("logoLara");
var handleTitleChange = handleTextInputChange("title");
var handleUrlChange = handleTextInputChange("url");
var handleAboutChange = handleSlateRteChange("about", setAboutValue);
Expand All @@ -124760,6 +124763,9 @@ var ProjectSettingsForm = function (_a) {
case 0:
apiUrl = id ? "/api/v1/projects/" + id : "/api/v1/projects";
projectData = (0, convert_keys_1.camelToSnakeCaseKeys)(project);
projectData.admin_ids = admins.map(function (a) {
return a.id;
});
return [4
/*yield*/
, fetch(apiUrl, {
Expand Down Expand Up @@ -124804,6 +124810,55 @@ var ProjectSettingsForm = function (_a) {
});
};

var handleRemoveAdmin = function (admin) {
return function (e) {
var title = project.title.trim().length > 0 ? project.title : "this project";

if (confirm("Are you sure you want to remove " + admin.email + " as a Project Admin of " + title + "?")) {
setAdmins(function (prev) {
return prev.filter(function (a) {
return a.id !== admin.id;
});
});
}
};
};

var renderProjectAdmins = function () {
if (!id) {
return null;
}

var renderList = function () {
if (!projectLoaded) {
return React.createElement("div", {
className: "emphasis"
}, "Loading the admin list ...");
}

if (admins.length === 0) {
return React.createElement("div", {
className: "emphasis"
}, "There are no project admins assigned to this project. Please contact a site admin to add project admins to this project.");
}

return React.createElement("div", null, React.createElement("table", null, React.createElement("tbody", null, admins.map(function (admin) {
return React.createElement("tr", {
key: admin.id
}, React.createElement("td", null, admin.email), React.createElement("td", null, React.createElement("button", {
title: "Remove this project admin from the project",
onClick: handleRemoveAdmin(admin)
}, "DELETE")));
}))), React.createElement("div", {
className: "emphasis"
}, "Please contact a site admin to add additional project admins to this project."));
};

return React.createElement("div", {
className: "projectAdmins"
}, React.createElement("label", null, "Project Admins"), renderList());
};

if (isNewProject && projectSaved) {
return null;
}
Expand All @@ -124816,11 +124871,16 @@ var ProjectSettingsForm = function (_a) {
href: "/"
}, "Home"), " "), React.createElement("li", null, "\u00A0/ ", React.createElement("a", {
href: "/projects"
}, "Projects"), " "), React.createElement("li", null, "\u00A0/ ", pageTitle)))), React.createElement("h1", {
className: "title"
}, pageTitle), alertMessage && React.createElement("div", {
}, "Projects"), " "), React.createElement("li", null, "\u00A0/ ", pageTitle)))), React.createElement("div", {
className: "titleContainer"
}, React.createElement("h1", null, pageTitle), React.createElement("button", {
className: "save-button",
onClick: handleSaveProject
}, "Save")), alertMessage && React.createElement("div", {
className: "alertMessage"
}, alertMessage), React.createElement("dl", null, React.createElement("dt", null, React.createElement("label", {
}, alertMessage), React.createElement("div", {
className: "splitForm"
}, React.createElement("dl", null, React.createElement("dt", null, React.createElement("label", {
htmlFor: "project-title"
}, "Title")), React.createElement("dd", null, React.createElement("input", {
id: "project-title",
Expand All @@ -124840,19 +124900,8 @@ var ProjectSettingsForm = function (_a) {
})), React.createElement("dd", {
className: "inputNote"
}, "The project key is used across sites to synchronise project information. It must be a unique value."), React.createElement("dt", null, React.createElement("label", {
htmlFor: "project-logo-lara"
}, "LARA Runtime Logo URL")), React.createElement("dd", {
className: "hasNote"
}, React.createElement("input", {
id: "project-logo-lara",
name: "project[logo_lara]",
defaultValue: project.logoLara,
onChange: handleLogoLaraChange
})), React.createElement("dd", {
className: "inputNote"
}, "Image should be 228 pixels wide by 70 pixels high. If left blank, the Concord Consortium logo will be used by default."), React.createElement("dt", null, React.createElement("label", {
htmlFor: "project-logo-ap"
}, "Activity Player Logo URL")), React.createElement("dd", {
}, "Activity Header Logo URL")), React.createElement("dd", {
className: "hasNote"
}, React.createElement("input", {
id: "project-logo-ap",
Expand All @@ -124872,18 +124921,7 @@ var ProjectSettingsForm = function (_a) {
onChange: handleUrlChange
})), React.createElement("dd", {
className: "inputNote"
}, "When logo image is clicked, this is URL will launch in a new browser tab."), React.createElement("dt", null, React.createElement("label", {
htmlFor: "project-url"
}, "LARA Runtime Footer")), React.createElement("dd", {
className: "hasNote"
}, React.createElement("textarea", {
id: "project-footer",
name: "project[footer]",
defaultValue: project.footer,
onChange: handleFooterChange
})), React.createElement("dd", {
className: "inputNote"
}, "Raw HTML can be entered into this legacy field.")), React.createElement("h2", null, "Activity Player Footer"), React.createElement("dl", null, React.createElement("dt", null, React.createElement("label", null, "Copyright/Attribution Text")), React.createElement("dd", null, React.createElement("div", {
}, "When logo image is clicked, this is URL will launch in a new browser tab.")), renderProjectAdmins()), React.createElement("h2", null, "Activity Homepage Footer"), React.createElement("dl", null, React.createElement("dt", null, React.createElement("label", null, "Copyright/Attribution Text")), React.createElement("dd", null, React.createElement("div", {
className: "slateContainer"
}, React.createElement(slate_editor_1.SlateContainer, {
value: copyrightValue,
Expand Down Expand Up @@ -124943,8 +124981,24 @@ var ProjectSettingsForm = function (_a) {
onChange: handleContactEmailChange
})), React.createElement("dd", {
className: "inputNote"
}, "Provide a valid email address for users to contact your project team. When this is provided, your contact email will be displayed in the footer.")), React.createElement("button", {
id: "save-button",
}, "Provide a valid email address for users to contact your project team. When this is provided, your contact email will be displayed in the footer.")), React.createElement("h2", null, "Legacy LARA Fields"), React.createElement("dl", null, React.createElement("dd", {
className: "inputNote"
}, "These fields are no longer used but are shown here in case you need to see or copy their values."), React.createElement("dt", null, React.createElement("label", {
htmlFor: "project-logo-lara"
}, "LARA Runtime Logo URL")), React.createElement("dd", null, React.createElement("input", {
id: "project-logo-lara",
name: "project[logo_lara]",
defaultValue: project.logoLara,
disabled: true
})), React.createElement("dt", null, React.createElement("label", {
htmlFor: "project-url"
}, "LARA Runtime Footer")), React.createElement("dd", null, React.createElement("textarea", {
id: "project-footer",
name: "project[footer]",
defaultValue: project.footer,
disabled: true
}))), React.createElement("button", {
className: "save-button",
onClick: handleSaveProject
}, "Save"));
};
Expand Down
52 changes: 48 additions & 4 deletions app/assets/stylesheets/lara-typescript.css
Original file line number Diff line number Diff line change
Expand Up @@ -4097,13 +4097,57 @@ button.bigButton svg {
.projectSettingsForm {
padding-bottom: 20px;
}
.projectSettingsForm .titleContainer {
padding: 1% 0;
display: flex;
justify-content: space-between;
align-items: center;
}
.projectSettingsForm .titleContainer h1, .projectSettingsForm .titleContainer button {
margin: 0;
}
.projectSettingsForm h2 {
color: var(--teal);
font-family: var(--font-family-bold);
font-size: 20px;
line-height: 1.2;
margin: 30px 0 20px;
margin: 20px 0;
padding: 0;
}
.projectSettingsForm .splitForm {
display: flex;
gap: 20px;
}
.projectSettingsForm .splitForm dl {
flex-grow: 1;
width: 100%;
}
.projectSettingsForm .splitForm .projectAdmins {
flex-grow: 1;
width: 25%;
}
.projectSettingsForm .splitForm .projectAdmins label {
font-size: 16px;
font-weight: 900;
}
.projectSettingsForm .splitForm .projectAdmins table {
margin: 10px 0;
width: 100%;
border-spacing: 10px;
}
.projectSettingsForm .splitForm .projectAdmins table button {
background: transparent;
border: none;
color: var(--dark-gray);
display: inline-block;
font-size: 12px;
margin: 0;
padding: 0;
text-transform: uppercase;
}
.projectSettingsForm .splitForm .projectAdmins .emphasis {
margin: 10px 0;
font-style: italic;
}
.projectSettingsForm .slateContainer {
border: solid 1.5px var(--med-gray);
Expand Down Expand Up @@ -4142,16 +4186,16 @@ button.bigButton svg {
border-radius: 4px;
font-family: var(--font-family-default);
font-size: 16px;
padding: 5px 10px 7px;
width: calc(50% - 23px);
padding: 5px 10px;
width: calc(100% - 23px);
-webkit-appearance: none;
}
.projectSettingsForm textarea {
height: 200px;
width: calc(100% - 23px);
}
.projectSettingsForm dl {
margin: 0 0 20px;
margin: 0;
padding: 0;
}
.projectSettingsForm dl dt {
Expand Down
5 changes: 5 additions & 0 deletions app/assets/stylesheets/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -1227,6 +1227,11 @@ body.admin.edit, body.admin.new {
padding: 7px;
}
}
input[type=checkbox] {
vertical-align: middle;
position: relative;
bottom: 1px;
}
div.actions {
padding: 7px;
input {
Expand Down
16 changes: 13 additions & 3 deletions app/controllers/api/v1/projects_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ def index
# GET /api/v1/projects/1.json
def show
begin
@project = Project.find(params[:id])
@project = Project.includes(:admins).find(params[:id])
authorize! :manage, @project
render json: {project: @project}, status: 200
render json: {project: @project, admins: admin_json(@project)}, status: 200
rescue ActiveRecord::RecordNotFound
render json: {error: "Project not found"}, status: 404
end
Expand All @@ -36,8 +36,12 @@ def update
@updated_project_hash = params[:project]
@project = Project.find(@updated_project_hash[:id]);
authorize! :update, @project

# remove any extra admin ids in the update that are not in the original, to ensure we can only remove and not add project admins
@updated_project_hash[:admin_ids] = (@updated_project_hash[:admin_ids] || []).select { |id| @project.admin_ids.include?(id.to_i) }

if @project.update_attributes(@updated_project_hash)
render json: {success: true, project: @project}, status: :ok
render json: {success: true, project: @project, admins: admin_json(@project)}, status: :ok
else
render json: @project.errors, status: :unprocessable_entity
end
Expand All @@ -54,4 +58,10 @@ def destroy
end
end

private

def admin_json(project)
project.admins.map { |a| {id: a.id, email: a.email} }
end

end
2 changes: 1 addition & 1 deletion app/models/project.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ class Project < ActiveRecord::Base
DefaultKey = 'default-project'

attr_accessible :footer, :logo_lara, :logo_ap, :title, :url, :about, :project_key, :copyright,
:copyright_image_url, :collaborators, :funders_image_url, :collaborators_image_url, :contact_email
:copyright_image_url, :collaborators, :funders_image_url, :collaborators_image_url, :contact_email, :admin_ids
validates :project_key, uniqueness: true
has_many :sequences
has_many :lightweight_activities
Expand Down
2 changes: 1 addition & 1 deletion app/views/admin/users/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<div>
<%= f.check_box :is_admin %>
<label class="normal" for="user_is_admin">
Admin
Site Admin
</label>
</div>
<div>
Expand Down
Loading

0 comments on commit 26cbd53

Please sign in to comment.