-
Notifications
You must be signed in to change notification settings - Fork 755
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Build endpoints and UI for Moment Templates
- Loading branch information
1 parent
4c9987b
commit 8fc802b
Showing
28 changed files
with
836 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# frozen_string_literal: true | ||
module MomentTemplatesConcern | ||
extend ActiveSupport::Concern | ||
|
||
def create_response_object(moment_template) | ||
return unless moment_template.save! | ||
|
||
{ id: moment_template.id, | ||
name: moment_template.name, description: moment_template.description } | ||
end | ||
|
||
def update_response_object(moment_template) | ||
return unless moment_template.update!(moment_template_params) | ||
|
||
{ id: moment_template.id, | ||
name: moment_template.name, description: moment_template.description } | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
# frozen_string_literal: true | ||
class MomentTemplatesController < ApplicationController | ||
include MomentTemplatesConcern | ||
|
||
def index | ||
@templates = current_user.moment_templates.order('LOWER(name)') | ||
end | ||
|
||
def create | ||
moment_template = MomentTemplate.new( | ||
moment_template_params.merge(user_id: current_user.id) | ||
) | ||
render json: create_response_object(moment_template) | ||
end | ||
|
||
def update | ||
moment_template = MomentTemplate.find_by(id: params[:id]) | ||
render json: update_response_object(moment_template) | ||
end | ||
|
||
def destroy | ||
moment_template = MomentTemplate.find_by(id: params[:id]) | ||
moment_template&.destroy | ||
redirect_to_path(moment_templates_path) | ||
end | ||
|
||
private | ||
|
||
def moment_template_params | ||
params.require(:moment_template).permit(:name, :description) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
<%= react_component('MomentTemplates', html_options: {}, props: { | ||
templates: @templates | ||
}) %> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,37 +1,57 @@ | ||
<% if !not_signed_in_root_path? %> | ||
<div class="pageTitle"> | ||
<% if sign_in_path? %> | ||
<div class="pageTitle"> | ||
<%= t('layouts.application.signin') %> | ||
</div> | ||
<% elsif join_path? %> | ||
<%= t('layouts.application.create_account') %> | ||
<div class="pageTitle"> | ||
<%= t('layouts.application.create_account') %> | ||
</div> | ||
<% elsif forgot_password_path? %> | ||
<%= t('account.forgot_password') %> | ||
<div class="pageTitle"> | ||
<%= t('account.forgot_password') %> | ||
</div> | ||
<% elsif update_account_path? %> | ||
<%= t('layouts.application.update_account') %> | ||
<div class="pageTitle"> | ||
<%= t('layouts.application.update_account') %> | ||
</div> | ||
<% elsif send_ally_invitation_path? %> | ||
<%= t('devise.invitations.new.header') %> | ||
<div class="pageTitle"> | ||
<%= t('devise.invitations.new.header') %> | ||
</div> | ||
<% elsif ally_accept_invitation_path? %> | ||
<%= t('devise.invitations.edit.header') %> | ||
<div class="pageTitle"> | ||
<%= t('devise.invitations.edit.header') %> | ||
</div> | ||
<% elsif reset_password_path? %> | ||
<%= t('layouts.title.reset_password') %> | ||
<div class="pageTitle"> | ||
<%= t('layouts.title.reset_password') %> | ||
</div> | ||
<% elsif new_user_confirmation_path? %> | ||
<%= t('devise.confirmations.resend_confirmation') %> | ||
<% elsif yield(:page_new).present? && @page_new %> | ||
<div class="pageTitleLeft"> | ||
<%= yield(:title) %> | ||
<div class="pageTitle"> | ||
<%= t('devise.confirmations.resend_confirmation') %> | ||
</div> | ||
<div class="pageTitleRight"> | ||
<%= link_to @page_new, yield(:page_new), class: 'buttonM' %> | ||
<% elsif yield(:page_new).present? && @page_new %> | ||
<div class="pageTitle"> | ||
<div> | ||
<%= yield(:title) %> | ||
</div> | ||
<div class="pageTitleRight"> | ||
<%= link_to @page_new, yield(:page_new), class: 'buttonM' %> | ||
</div> | ||
</div> | ||
<% elsif @page_author.present? %> | ||
<div class="pageTitleLeft"> | ||
<%= yield(:title) %> | ||
<div class="pageTitle"> | ||
<div> | ||
<%= yield(:title) %> | ||
</div> | ||
<div class="pageTitleRight"> | ||
<%= render partial: '/shared/page_author', locals: { author: @page_author } %> | ||
</div> | ||
</div> | ||
<div class="pageTitleRight"> | ||
<%= render partial: '/shared/page_author', locals: { author: @page_author } %> | ||
<% elsif yield(:title).present? %> | ||
<div class="pageTitle"> | ||
<%= yield(:title) %> | ||
</div> | ||
<% else %> | ||
<%= yield(:title) %> | ||
<% end %> | ||
</div> | ||
<% end %> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
48 changes: 48 additions & 0 deletions
48
client/app/components/PageTitle/__tests__/PageTitle.spec.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
// @flow | ||
import React from 'react'; | ||
import { render, screen } from '@testing-library/react'; | ||
import { PageTitle } from 'components/PageTitle'; | ||
|
||
const TITLE = 'title'; | ||
const SUBTITLE = 'subtitle'; | ||
const CTA_TEXT = 'cta'; | ||
const INSTRUCTIONS = 'instructions'; | ||
|
||
describe('PageTitle', () => { | ||
it('renders with title and subtitle', () => { | ||
render(<PageTitle title={TITLE} subtitle={SUBTITLE} />); | ||
expect(screen.getByText(TITLE)).toBeInTheDocument(); | ||
expect(screen.getByText(SUBTITLE)).toBeInTheDocument(); | ||
expect(window.document.title).toEqual(`if-me.org | ${TITLE}`); | ||
}); | ||
|
||
it('renders with title, subtitle, and cta', () => { | ||
render( | ||
<PageTitle | ||
title={TITLE} | ||
subtitle={SUBTITLE} | ||
cta={<button type="button">{CTA_TEXT}</button>} | ||
/>, | ||
); | ||
expect(screen.getByText(TITLE)).toBeInTheDocument(); | ||
expect(screen.getByText(SUBTITLE)).toBeInTheDocument(); | ||
expect(window.document.title).toEqual(`if-me.org | ${TITLE}`); | ||
expect(screen.getByText(CTA_TEXT)).toBeInTheDocument(); | ||
}); | ||
|
||
it('renders with title, subtitle, cta, and instructions', () => { | ||
render( | ||
<PageTitle | ||
title={TITLE} | ||
subtitle={SUBTITLE} | ||
cta={<button type="button">{CTA_TEXT}</button>} | ||
instructions={INSTRUCTIONS} | ||
/>, | ||
); | ||
expect(screen.getByText(TITLE)).toBeInTheDocument(); | ||
expect(screen.getByText(SUBTITLE)).toBeInTheDocument(); | ||
expect(window.document.title).toEqual(`if-me.org | ${TITLE}`); | ||
expect(screen.getByText(CTA_TEXT)).toBeInTheDocument(); | ||
expect(screen.getByText(INSTRUCTIONS)).toBeInTheDocument(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// @flow | ||
import React, { useEffect } from 'react'; | ||
import { I18n } from 'libs/i18n'; | ||
import globalCSS from 'styles/_global.scss'; | ||
|
||
type Props = { | ||
title: string, | ||
subtitle: string, | ||
cta?: any, | ||
instructions?: any, | ||
}; | ||
|
||
export const PageTitle = ({ | ||
title, subtitle, cta, instructions, | ||
}: Props) => { | ||
useEffect(() => { | ||
window.document.title = `${I18n.t('app_name')} | ${title}`; | ||
}); | ||
|
||
return ( | ||
<> | ||
<div className={globalCSS.pageTitle}> | ||
<div>{title}</div> | ||
{cta && <div className={globalCSS.pageTitleRight}>{cta}</div>} | ||
</div> | ||
<div className={globalCSS.subtitle}>{subtitle}</div> | ||
{instructions} | ||
</> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
@import '~styles/_global.scss'; | ||
|
||
.newTemplate { | ||
@media screen and (max-width: $small) { | ||
width: 100%; | ||
} | ||
|
||
button { | ||
font-weight: bold; | ||
|
||
@media screen and (max-width: $small) { | ||
width: inherit; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
// @flow | ||
import { createContext } from 'react'; | ||
|
||
const TemplatesContext = createContext<Object>({}); | ||
|
||
export default TemplatesContext; |
107 changes: 107 additions & 0 deletions
107
client/app/pages/MomentTemplates/MomentTemplatesForm.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
// @flow | ||
import React, { useState } from 'react'; | ||
import { I18n } from 'libs/i18n'; | ||
import { Utils } from 'utils'; | ||
import DynamicForm from 'components/Form/DynamicForm'; | ||
import TemplatesContext from './MomentTemplatesContext'; | ||
import css from './MomentTemplates.scss'; | ||
|
||
export type Template = { | ||
id: number, | ||
name: string, | ||
description: string, | ||
}; | ||
|
||
type Response = { | ||
error?: string, | ||
data?: Template, | ||
}; | ||
|
||
export type Props = { | ||
template?: Template, | ||
}; | ||
|
||
export const MomentTemplatesForm = ({ template }: Props) => { | ||
const [error, setError] = useState(); | ||
const action = `/moment_templates/${ | ||
template ? `update?id=${template.id}` : 'create' | ||
}`; | ||
|
||
const onSubmit = (response: Response, context: Object) => { | ||
const { | ||
templates, | ||
setTemplates, | ||
setEditableTemplate, | ||
setOpenModal, | ||
setModalKey, | ||
} = context; | ||
if (response.error) { | ||
setError(response.error); | ||
} else { | ||
let newTemplates = [...templates]; | ||
if (template) { | ||
newTemplates = newTemplates.filter((c) => c.id !== template.id); | ||
} | ||
newTemplates.push(response.data); | ||
setTemplates(newTemplates.sort((a, b) => a.name.localeCompare(b.name))); | ||
setOpenModal(false); | ||
setModalKey(Utils.randomString()); | ||
setEditableTemplate(null); | ||
} | ||
}; | ||
|
||
return ( | ||
<TemplatesContext.Consumer> | ||
{(context) => ( | ||
<> | ||
<DynamicForm | ||
type={template ? 'patch' : 'post'} | ||
formProps={{ | ||
action, | ||
inputs: [ | ||
{ | ||
id: 'moment_template_name', | ||
name: 'moment_template[name]', | ||
type: 'text', | ||
label: I18n.t('common.name'), | ||
dark: true, | ||
required: true, | ||
value: template && template.name, | ||
}, | ||
{ | ||
id: 'moment_template_description', | ||
name: 'moment_template[description]', | ||
type: 'textarea', | ||
label: I18n.t('common.form.description'), | ||
dark: true, | ||
required: true, | ||
value: template && template.description, | ||
}, | ||
{ | ||
dark: true, | ||
id: 'submit', | ||
name: 'commit', | ||
type: 'submit', | ||
value: I18n.t('common.actions.submit'), | ||
}, | ||
], | ||
}} | ||
onSubmit={(response) => onSubmit(response, context)} | ||
/> | ||
{error && ( | ||
<div | ||
className={`${css.errorField} ${css.smallMarginTop}`} | ||
role="alert" | ||
> | ||
{error} | ||
</div> | ||
)} | ||
</> | ||
)} | ||
</TemplatesContext.Consumer> | ||
); | ||
}; | ||
|
||
export default ({ template }: Props) => ( | ||
<MomentTemplatesForm template={template} /> | ||
); |
Oops, something went wrong.