Skip to content

Commit

Permalink
New state-specific calculator design (#20)
Browse files Browse the repository at this point in the history
## Description

This implements most of the spec'd design. There are some significant
pieces still missing. They're tracked in Asana, but just as a summary:

- Multiple selection of projects.

- Adding authority logos to the bottom of the page.

- Handling the case of entering a location outside RI.

- Adding an optional email field for people to join the mailing list.

In addition, there are some more pieces missing before this frontend
is ready to replace the existing IRA calculator wholesale:

- Spanish localization

- Analytics

- Appearance customizability via CSS

### Guide to new code

- New .html page for the RI calculator, which pretty much just has the
  embed. It's included in the Parcel targets, which means it will be
  built and deployed. It's not ready for the public yet, so it uses a
  meta tag to disable search engine indexing.

- `state-calculator.ts` is the custom HTML element. This contains all
  the state management.

- `state-incentive-details.ts` is the view layer.

  - A couple of sub-parts are factored out: `utility-selector.ts` and
    `icon-tab-bar.ts`.

- `calculator-types-v1.ts` corresponds to the shape of the API
  responses. This is manually maintained right now, not generated
  from the API in any way; eventually it'd be good to change that.

- `projects.ts` has icons and labels for the various projects.

## Test Plan

- Enter a RI zip code. 02807 for Block Island, 02861 for Providence
  (RI Energy only), 02814 for Pascoag (RIE and PUD available).

- Fill in the rest of the form, click Calculate. Look at results. In
  the 02814 case, switch to the other utility and make sure the
  incentives reload.

- Click the pill buttons to switch projects.

- Click the "Learn more" / "Visit site" buttons.

Resize the window to look at the three possible layouts. (1, 2, or 3
columns of incentive cards.)
  • Loading branch information
oyamauchi authored Sep 26, 2023
1 parent 73283f9 commit 0f32d67
Show file tree
Hide file tree
Showing 13 changed files with 1,546 additions and 100 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,10 @@
"default": {
"source": [
"src/calculator.ts",
"src/state-calculator.ts",
"src/rewiring-fonts.css",
"src/index.html"
"src/index.html",
"src/rhode-island.html"
],
"outputFormat": "global",
"context": "browser",
Expand Down
63 changes: 63 additions & 0 deletions src/api/calculator-types-v1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
export type IncentiveType =
| 'tax_credit'
| 'pos_rebate'
| 'rebate'
| 'account_credit'
| 'performance_rebate';
export type AuthorityType = 'federal' | 'state' | 'utility';

export type AmountType = 'dollar_amount' | 'percent' | 'dollars_per_unit';
export interface Amount {
type: AmountType;
number: number;
maximum?: number;
representative?: number;
unit?: string;
}

export type ItemType =
| 'battery_storage_installation'
| 'efficiency_rebates'
| 'electric_panel'
| 'electric_stove'
| 'electric_vehicle_charger'
| 'electric_wiring'
| 'geothermal_heating_installation'
| 'heat_pump_air_conditioner_heater'
| 'heat_pump_clothes_dryer'
| 'heat_pump_water_heater'
| 'new_electric_vehicle'
| 'rooftop_solar_installation'
| 'used_electric_vehicle'
| 'weatherization';

export interface Item {
type: ItemType;
name: string;
url: string;
}

export interface Incentive {
type: IncentiveType;
authority_type: AuthorityType;
authority_name: string | null;
program: string;
program_url?: string;
item: Item;
amount: Amount;
start_date: number;
end_date: number;
short_description?: string;

eligible: boolean;
}

export interface APIResponse {
savings: {
tax_credit: number;
pos_rebate: number;
rebate: number;
account_credit: number;
};
incentives: Incentive[];
}
236 changes: 137 additions & 99 deletions src/calculator-form.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { html, css } from 'lit';
import { html, css, nothing } from 'lit';
import { downIcon, questionIcon } from './icons';
import { select, selectStyles, OptionParam } from './select';
import { inputStyles } from './styles/input';
import './currency-input';
import '@shoelace-style/shoelace/dist/themes/light.css';
import '@shoelace-style/shoelace/dist/components/tooltip/tooltip.js';
import { PROJECTS } from './projects';

const buttonStyles = css`
button {
button.primary {
appearance: none;
font-family: inherit;
font-size: 16px;
Expand All @@ -24,7 +25,7 @@ const buttonStyles = css`
width: 100%;
}
button:hover {
button.primary:hover {
background-color: var(--ra-embed-primary-button-background-hover-color);
}
Expand Down Expand Up @@ -74,110 +75,147 @@ const HOUSEHOLD_SIZE_OPTIONS: OptionParam[] = [1, 2, 3, 4, 5, 6, 7, 8].map(
);

export const formTemplate = (
[zip, ownerStatus, householdIncome, taxFiling, householdSize]: Array<string>,
[
project,
zip,
ownerStatus,
householdIncome,
taxFiling,
householdSize,
]: Array<string>,
showProjectField: boolean,
onSubmit: (e: SubmitEvent) => void,
) => html`
<form @submit=${onSubmit}>
<div class="grid-3-2">
<div>
<label for="zip">
Zip
gridClass: string = 'grid-3-2',
) => {
const projectField = showProjectField
? html`<div>
<label for="project">
Project you're most interested in
<sl-tooltip
content="Your zip code helps determine the amount of discounts and tax credits you qualify for."
hoist
>${questionIcon(18, 18)}</sl-tooltip
><br />
<input
tabindex="0"
id="zip"
placeholder="12345"
name="zip"
required
type="text"
value="${zip}"
minlength="5"
maxlength="5"
inputmode="numeric"
pattern="[0-9]{5}"
/>
</label>
</div>
<div>
<label for="owner_status">
Homeowners Status
<sl-tooltip
content="Homeowners and renters qualify for different incentives."
hoist
>${questionIcon(18, 18)}</sl-tooltip
><br />
${select({
id: 'owner_status',
required: true,
options: OWNER_STATUS_OPTIONS,
currentValue: ownerStatus,
tabIndex: 0,
})}
</label>
</div>
<div>
<label for="household_income">
Household Income
<sl-tooltip
content="Enter your gross income (income before taxes). Include wages and salary plus other forms of income, including pensions, interest, dividends, and rental income. If you are married and file jointly, include your spouse's income"
hoist
>${questionIcon(18, 18)}</sl-tooltip
><br />
<ra-currency-input
id="household_income"
placeholder="$60,000"
name="household_income"
required
value=${householdIncome}
min="0"
max="100000000"
></ra-currency-input>
</label>
</div>
<div>
<label for="tax_filing">
Tax Filing
<sl-tooltip hoist
><div slot="content">
Select "Head of Household" if you have a child or relative living
with you, and you pay more than half the costs of your home.
Select "Joint" if you file your taxes as a married couple."
</div>
${questionIcon(18, 18)}</sl-tooltip
><br />
${select({
id: 'tax_filing',
required: true,
options: TAX_FILING_OPTIONS,
currentValue: taxFiling,
tabIndex: 0,
})}
</label>
</div>
<div>
<label for="household_size">
Household Size
<sl-tooltip
content="Include anyone you live with who you claim as a dependent on your taxes, and your spouse or partner if you file taxes together."
content="Select the project you're most interested in."
hoist
>${questionIcon(18, 18)}</sl-tooltip
><br />
${select({
id: 'household_size',
id: 'project',
required: true,
options: HOUSEHOLD_SIZE_OPTIONS,
currentValue: householdSize,
options: Object.entries(PROJECTS)
.map(([value, data]) => ({ value, label: data.label }))
.sort((a, b) => a.label.localeCompare(b.label)),
currentValue: project,
tabIndex: 0,
})}
</label>
</div> `
: nothing;

return html`
<form @submit=${onSubmit}>
<div class="${gridClass}">
${projectField}
<div>
<label for="zip">
Zip
<sl-tooltip
content="Your zip code helps determine the amount of discounts and tax credits you qualify for."
hoist
>${questionIcon(18, 18)}</sl-tooltip
><br />
<input
tabindex="0"
id="zip"
placeholder="12345"
name="zip"
required
type="text"
value="${zip}"
minlength="5"
maxlength="5"
inputmode="numeric"
pattern="[0-9]{5}"
/>
</label>
</div>
<div>
<label for="owner_status">
Homeowners Status
<sl-tooltip
content="Homeowners and renters qualify for different incentives."
hoist
>${questionIcon(18, 18)}</sl-tooltip
><br />
${select({
id: 'owner_status',
required: true,
options: OWNER_STATUS_OPTIONS,
currentValue: ownerStatus,
tabIndex: 0,
})}
</label>
</div>
<div>
<label for="household_income">
Household Income
<sl-tooltip
content="Enter your gross income (income before taxes). Include wages and salary plus other forms of income, including pensions, interest, dividends, and rental income. If you are married and file jointly, include your spouse's income"
hoist
>${questionIcon(18, 18)}</sl-tooltip
><br />
<ra-currency-input
id="household_income"
placeholder="$60,000"
name="household_income"
required
value=${householdIncome}
min="0"
max="100000000"
></ra-currency-input>
</label>
</div>
<div>
<label for="tax_filing">
Tax Filing
<sl-tooltip hoist
><div slot="content">
Select "Head of Household" if you have a child or relative
living with you, and you pay more than half the costs of your
home. Select "Joint" if you file your taxes as a married
couple."
</div>
${questionIcon(18, 18)}</sl-tooltip
><br />
${select({
id: 'tax_filing',
required: true,
options: TAX_FILING_OPTIONS,
currentValue: taxFiling,
tabIndex: 0,
})}
</label>
</div>
<div>
<label for="household_size">
Household Size
<sl-tooltip
content="Include anyone you live with who you claim as a dependent on your taxes, and your spouse or partner if you file taxes together."
hoist
>${questionIcon(18, 18)}</sl-tooltip
><br />
${select({
id: 'household_size',
required: true,
options: HOUSEHOLD_SIZE_OPTIONS,
currentValue: householdSize,
tabIndex: 0,
})}
</label>
</div>
<div>
<button class="primary" type="submit">
Calculate! ${downIcon(18, 18)}
</button>
</div>
</div>
<div>
<button type="submit">Calculate! ${downIcon(18, 18)}</button>
</div>
</div>
</form>
`;
</form>
`;
};
3 changes: 3 additions & 0 deletions src/calculator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from './calculator-types';
import { CALCULATOR_FOOTER } from './calculator-footer';
import { fetchApi } from './api/fetch';
import { NO_PROJECT } from './projects';

const loadedTemplate = (
results: ICalculatedIncentiveResults,
Expand Down Expand Up @@ -150,12 +151,14 @@ export class RewiringAmericaCalculator extends LitElement {
? nothing
: formTemplate(
[
NO_PROJECT,
this.zip,
this.ownerStatus,
this.householdIncome,
this.taxFiling,
this.householdSize,
],
false,
(event: SubmitEvent) => this.submit(event),
)}
</div>
Expand Down
Loading

1 comment on commit 0f32d67

@vercel
Copy link

@vercel vercel bot commented on 0f32d67 Sep 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.