Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion _config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ defaults:
body: Email message (remember to include placeholders for name)
siteTitle: Defund12.org
permalink: pretty
subtitle: Email and mail government officials and council members to reallocate egregious
subtitle: Email, call, and mail government officials and council members to reallocate egregious
police budgets towards education, social services, and dismantling racial injustice.
meta: Email government officials and council members to reallocate police budgets
towards education, social services, and dismantling racial injustice.
Expand Down
2 changes: 1 addition & 1 deletion _emails/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ When adding templates, please ensure you are using UTF-8 encoding. Please do not
| --------------- | -------- | ------ | ----------------------------------------------------------------------------------- |
| title | yes | String | City, State (for CMS tracking purposes) |
| layout | yes | String | Layout for markdown file (must be `email`) |
| permalink | yes | String | Path to url on the site (e.g., /nyc |
| permalink | yes | String | Path to url on the site (e.g., /nyc) |
| name | yes | String | Name of the email that will be displayed to users |
| city | yes | String | City the email template is for |
| state | yes | String | State (abbreviated) the email template is for (e.g., NY) |
Expand Down
59 changes: 59 additions & 0 deletions _phoneScripts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Notice for template implementers

When adding templates, please ensure you are using UTF-8 encoding. Please do not use UTF-8-BOM or "ANSI", as this may cause problems when rendering the template.

## Fields

### Call Script
| Key | Required | Type | Description |
| --------------- | -------- | ------ | ----------------------------------------------------------------------------------------- |
| title | yes | String | City, State (for CMS tracking purposes) |
| layout | yes | String | Layout for markdown file (must be `phone`) |
| permalink | yes | String | Path to url on the site (e.g., /nyc) |
| name | yes | String | Name of the call script that will be displayed to users |
| city | yes | String | City the call script is for |
| state | yes | String | State (abbreviated) the call script is for (e.g., NY) |
| body | yes | String | The call script text. Prefix with `|-` and indent underneath (e.g., see below) |
| contacts | yes | Array | Contact information for officials to call with this script using the Contact fields below |
| expiration_date | no | String | YYYY-MM-DD format date that the call script should expire from the site |
| organization | no | String | Name of the organization that curated the call script (if it exists) |
| redirect_from | no | Array | Links that should redirect to the permalink above |

### Contact
| Key | Required | Type | Description |
| ------ | -------- | ------ | --------------------------------------------------------------------- |
| name | yes | String | The official's name including title, e.g. Board Supervisor First Last |
| number | yes | String | The official's phone number, formatted like e.g. (555) 555-5555 |

## File naming conventions

Please use underscores for spaces when creating new markdown files and directories. For example:

```
_phone/us/california/san_francisco.md
_phone/us/new_york/hudson_valley.md
```

## Example template

```
---
title: <city, state_initials>
layout: phone
permalink: /<link_address>
name: <name>
city: <city>
state: <state_initials>
contacts:
- name: <name>
number: <phone number>
- name: <name>
number: <phone number>
body: |-
<body>
expiration_date: <YYYY-MM-DD>
organization: <organization>
redirect_from:
- /<redirect_link_address>
---
```
22 changes: 22 additions & 0 deletions _phoneScripts/us/california/san_francisco.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
title: San Francisco, California
permalink: "/phone/sanfrancisco"
name: Call to the Mayor, Board of Supervisors, and Elected Officers
city: San Francisco
state: CA
layout: phone
contacts:
- name: "Rep"
number: "(555) 555-5555"
- name: "Noth"
number: "(555) 555-5555"
body: |-
My name is [YOUR NAME], and I am a resident of San Francisco. This past week, our nation has been gripped by protests calling for rapid and meaningful change with regard to police behavior, an end to racism and anti-Blackness, and immediate reform in how Black people are treated in America. Our city has been at the forefront of much of this action. Accordingly, it has come to my attention that the budget for 2021 is being decided as these protests continue.

SFPD has been a waste of our resources. Last year, the SFPD budget was $611,701,869, the majority of which comes from the San Francisco general fund. While we've been spending extraordinary amounts on policing, we have not seen improvements to safety, homelessness, mental health, or affordability in our city. Instead, we see wasteful and harmful actions of our police.

I call on you to slash the SFPD budget and instead use those extraordinary resources towards solving homelessness, which is felt most by our Black neighbors and veterans. We implore you to give every member of our community experiencing homelessness a place to call home and the treatment they need.

We can be a beacon for other cities to follow if only we have the courage to change.
---

15 changes: 15 additions & 0 deletions _sass/2-layout/_phone.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

.phoneScriptContentSection {
@include highlightedSection;
padding: 8px 0px;

.phoneScriptContent {
div {
margin-top: $x_3;
}
}

>.container {
@include siteWidth;
}
}
8 changes: 8 additions & 0 deletions gatsby-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ module.exports = {
ignore: ["README.md"],
},
},
{
resolve: "gatsby-source-filesystem",
options: {
name: "phoneScripts",
path: `${__dirname}/_phoneScripts/`,
ignore: ["README.md"],
},
},
{
resolve: "gatsby-source-filesystem",
options: {
Expand Down
11 changes: 8 additions & 3 deletions gatsby-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const createPages: GatsbyNode["createPages"] = async ({
const { createPage, createRedirect } = actions;

/** Shared logic between creating email and letter pages */
async function createEmailOrLetterTemplatePage({
async function createTemplatePages({
layout,
component,
}: {
Expand Down Expand Up @@ -57,13 +57,18 @@ export const createPages: GatsbyNode["createPages"] = async ({
);
}

await createEmailOrLetterTemplatePage({
await createTemplatePages({
layout: "email",
component: require.resolve("./src/components/email/Email.tsx"),
});

await createEmailOrLetterTemplatePage({
await createTemplatePages({
layout: "letter",
component: require.resolve("./src/components/letter/Letter.tsx"),
});

await createTemplatePages({
layout: "phone",
component: require.resolve("./src/components/phone/Phone.tsx"),
});
};
159 changes: 159 additions & 0 deletions src/components/phone/Phone.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { graphql, PageProps } from "gatsby";
import React from "react";
import {
PhoneScriptProps,
PhoneScriptConfig,
OptionalLayoutProps,
} from "../../types/PropTypes";
import { PhoneScriptData, PhoneScriptContact } from "../../types/TemplateData";
import * as queryString from "query-string";
import Layout from "../common/Layout";
import { DefundUtils } from "../../DefundUtils";
import PhoneList from "../template-list/PhoneList";

/**
* The @link {PhoneScript} component state.
*/
class PhoneScriptState {
bodyCopied = false;
}

/**
* A rendered phone script, containing links to copy the script text or link.
*/
export default class PhoneScript extends React.Component<
PageProps<PhoneScriptProps>,
PhoneScriptState
> {
siteConfig: PhoneScriptConfig;
phoneScriptData: PhoneScriptData;
layoutProps: OptionalLayoutProps;
/**
* Initialize the component and its state
* @param {PageProps<PhoneScriptConfig>} props
*/
constructor(props: PageProps<PhoneScriptProps>) {
super(props);
this.siteConfig = this.props.data.siteConfig;
this.phoneScriptData = this.props.data.markdownRemark.frontmatter;
this.layoutProps = {
pageTitle: `Defund12 in ${this.phoneScriptData.city}, ${this.phoneScriptData.state}`,
meta: `View a phone script to call ${this.phoneScriptData.city}, ${this.phoneScriptData.state} officials`,
metaQueryString: queryString.stringify({
state: this.phoneScriptData.state,
city: this.phoneScriptData.city,
}),
layout: "phone",
};
this.state = new PhoneScriptState();
}

/**
* Updates the component state to reflect which element was clicked and
* copies the selected element to the user's clipboard.
* @param {Pick<EmailState, K>} statePatch an object containing
* the state update to apply
* @param {string} copy the text to copy to the user's clipboard.
*/
handleClipboardCopy<K extends keyof PhoneScriptState>(
statePatch: Pick<PhoneScriptState, K>,
copy: string
): void {
this.setState(statePatch);
DefundUtils.copyToClipboard(copy);
}

/**
* Renders the phone script's contact list as list items.
* @return {React.ReactNode}
*/
renderContacts(): React.ReactNode {
return this.phoneScriptData.contacts.map((contact: PhoneScriptContact) => (
<li>
{contact.name}, {contact.number}
</li>
));
}

/**
* React render method.
* @return {React.ReactNode} the rendered component
*/
render(): React.ReactNode {
return (
<Layout {...this.layoutProps}>
<section className="emailPageHeader">
<h2>{this.phoneScriptData.name}</h2>
<b>
{this.phoneScriptData.city}, {this.phoneScriptData.state}
</b>
<div className="buttons">
<a
onClick={() =>
DefundUtils.copyToClipboard(
this.phoneScriptData.permalink,
true
)
}
>
Copy link
</a>
</div>
<ul>{this.renderContacts()}</ul>
</section>

<article className="phoneScriptContentSection">
<div className="container">
<div className="phoneScriptContent">
<div>
<span>Message:</span>
<span
className="copyToClipboard"
onClick={() =>
this.handleClipboardCopy(
{ bodyCopied: true },
this.phoneScriptData.body
)
}
>
{this.state.bodyCopied ? "✅(copied)" : "🔗"}
</span>
<span
dangerouslySetInnerHTML={{
__html: DefundUtils.markdownToHTML(
this.phoneScriptData.body
),
}}
></span>
</div>
</div>
</div>
</article>

<PhoneList />
</Layout>
);
}
}

export const pageQuery = graphql`
query($permalink: String!) {
markdownRemark(
frontmatter: { permalink: { eq: $permalink }, layout: { eq: "phone" } }
) {
frontmatter {
body
city
country
date
name
state
permalink
contacts {
name
number
}
}
}
}
`;
9 changes: 9 additions & 0 deletions src/components/template-list/PhoneList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import TemplateList from "./TemplateList";
import React from "react";

/** List of all call scripts
* @return {React.ReactElement} the rendered list
*/
export default function PhoneList(): React.ReactElement {
return <TemplateList layout="phone" />;
}
32 changes: 32 additions & 0 deletions src/pages/phoneScripts.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from "react";
import { PageProps } from "gatsby";
import Layout from "../components/common/Layout";
import { SiteProps } from "../types/PropTypes";
import PhoneList from "../components/template-list/PhoneList";

/**
* Contains another list just like the homepage, this time with call scripts.
*/
export default class PhoneScripts extends React.Component<
PageProps<SiteProps>
> {
/**
* Initialize the component.
* @param {PageProps<SiteProps>} props
*/
constructor(props: PageProps<SiteProps>) {
super(props);
}

/**
* React render method.
* @return {React.ReactNode} the rendered component
*/
render(): React.ReactNode {
return (
<Layout>
<PhoneList />
</Layout>
);
}
}
20 changes: 20 additions & 0 deletions src/types/PropTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
EmailData,
SharedTemplateMetadata,
LetterData,
PhoneScriptData,
} from "./TemplateData";

// {Page query result properties}
Expand Down Expand Up @@ -48,6 +49,25 @@ export interface LetterProps {
siteConfig: LetterConfig;
}

export type PhoneScriptConfig = Pick<
SiteConfig,
| "siteTitle"
| "meta"
| "logoUrl"
| "faviconUrl"
| "phonePageHeader"
| "googleApiKey"
>;

export type PhonePageConfig = Pick<SiteConfig, "phonePageHeader">;

export interface PhoneScriptProps {
markdownRemark: {
frontmatter: PhoneScriptData;
};
siteConfig: PhoneScriptConfig;
}

export interface OptionalLayoutProps {
pageTitle?: string;
meta?: string;
Expand Down
Loading