-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
## Description - New .html page for the RI calculator - Card-based presentation of incentives, rather than the table like in the IRA calculator. This follows the initial designs. No utility incentives yet, only state + federal. Utility selection will come in a followup. If you enter a non-RI zip code, the API gives the error "we don't yet have state-level coverage in <state>". This isn't exactly what we want -- it should say "that zip/address is not in Rhode Island". This will require an API change to add a "state" parameter. ## Test Plan Enter a RI zip code and make sure that RI incentives come up. **Note** this new page will not be available in the Vercel preview. I haven't configured the Parcel build to include `rhode-island.html` yet, because I don't want the page to be publicly available when this lands. ## Next Steps Follow-up PRs that add the utility and project fields.
- Loading branch information
Showing
6 changed files
with
514 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
export type IncentiveType = 'tax_credit' | 'pos_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 interface Incentive { | ||
type: IncentiveType; | ||
authority_type: AuthorityType; | ||
authority_name: string | null; | ||
program: string; | ||
item: string; | ||
item_url: string; | ||
amount: Amount; | ||
start_date: number; | ||
end_date: number; | ||
|
||
eligible: boolean; | ||
} | ||
|
||
export interface APIResponse { | ||
pos_rebate_incentives: Incentive[]; | ||
tax_credit_incentives: Incentive[]; | ||
} |
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,77 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="utf-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1" /> | ||
<meta | ||
name="description" | ||
content="Demo Rewiring America calculator widget." | ||
/> | ||
<meta name="robots" content="noindex" /> | ||
<title>Rewiring America Incentives Calculator - Rhode Island</title> | ||
<script type="module" src="./state-calculator.ts"></script> | ||
<link rel="stylesheet" type="text/css" href="./rewiring-fonts.css" /> | ||
<style> | ||
html, | ||
body { | ||
margin: 0; | ||
padding: 0; | ||
background: white; | ||
color: #222; | ||
box-sizing: border-box; | ||
} | ||
|
||
main { | ||
font-smoothing: antialiased; | ||
-webkit-font-smoothing: antialiased; | ||
font-family: 'GT America', system-ui, -apple-system, BlinkMacSystemFont, | ||
'Segoe UI', Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', | ||
'Helvetica Neue', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', | ||
'Segoe UI Symbol', 'Noto Color Emoji'; | ||
font-size: 16px; | ||
line-height: 28px; | ||
font-weight: 400; | ||
margin: 32px auto; | ||
max-width: 1024px; | ||
padding: 32px; | ||
} | ||
|
||
h1, | ||
h2, | ||
h3, | ||
p { | ||
padding: 0; | ||
} | ||
|
||
/* Extra small devices */ | ||
@media only screen and (max-width: 600px) { | ||
main { | ||
padding: 0; | ||
} | ||
|
||
h1, | ||
h2, | ||
h3, | ||
p { | ||
padding: 32px; | ||
} | ||
} | ||
</style> | ||
</head> | ||
|
||
<body> | ||
<main> | ||
<h1>Rewiring America Incentives Calculator</h1> | ||
<h2>Rhode Island</h2> | ||
<rewiring-america-state-calculator | ||
api-key="{{ apiKey }}" | ||
state="RI" | ||
zip="02807" | ||
household-income="80000" | ||
household-size="1" | ||
tax-filing="single" | ||
owner-status="homeowner" | ||
></rewiring-america-state-calculator> | ||
</main> | ||
</body> | ||
</html> |
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,181 @@ | ||
import { LitElement, html, nothing } from 'lit'; | ||
import { customElement, property } from 'lit/decorators.js'; | ||
import { Task, initialState } from '@lit-labs/task'; | ||
import { baseStyles, gridStyles } from './styles'; | ||
import { formTemplate, formStyles } from './calculator-form'; | ||
import { FilingStatus, OwnerStatus } from './calculator-types'; | ||
import { CALCULATOR_FOOTER } from './calculator-footer'; | ||
import { fetchApi } from './api/fetch'; | ||
import { | ||
stateIncentivesTemplate, | ||
stateIncentivesStyles, | ||
cardStyles, | ||
} from './state-incentive-details'; | ||
|
||
const loadingTemplate = () => html` | ||
<div class="card card-content">Loading...</div> | ||
`; | ||
|
||
const errorTemplate = (error: unknown) => html` | ||
<div class="card card-content"> | ||
${typeof error === 'object' && error && 'message' in error && error.message | ||
? error.message | ||
: 'Error loading incentives.'} | ||
</div> | ||
`; | ||
|
||
const DEFAULT_CALCULATOR_API_HOST: string = 'https://api.rewiringamerica.org'; | ||
|
||
@customElement('rewiring-america-state-calculator') | ||
export class RewiringAmericaStateCalculator extends LitElement { | ||
static override styles = [ | ||
baseStyles, | ||
cardStyles, | ||
gridStyles, | ||
...formStyles, | ||
stateIncentivesStyles, | ||
]; | ||
|
||
/* supported properties to control showing/hiding of each card in the widget */ | ||
|
||
@property({ type: Boolean, attribute: 'hide-form' }) | ||
hideForm: boolean = false; | ||
|
||
@property({ type: Boolean, attribute: 'hide-details' }) | ||
hideDetails: boolean = false; | ||
|
||
/* supported properties to control which API path and key is used to load the calculator results */ | ||
|
||
@property({ type: String, attribute: 'api-key' }) | ||
apiKey: string = ''; | ||
|
||
@property({ type: String, attribute: 'api-host' }) | ||
apiHost: string = DEFAULT_CALCULATOR_API_HOST; | ||
|
||
/** | ||
* Property to customize the calculator for a particular state. Must be the | ||
* two-letter code, uppercase (example: "NY"). | ||
* | ||
* Currently the only customization is to display the name of the state. | ||
* TODO: Have a nice error message if you enter a zip/address outside this | ||
* state, if it's defined. | ||
*/ | ||
@property({ type: String, attribute: 'state' }) | ||
state: string = ''; | ||
|
||
/* supported properties to allow pre-filling the form */ | ||
|
||
@property({ type: String, attribute: 'zip' }) | ||
zip: string = ''; | ||
|
||
@property({ type: String, attribute: 'owner-status' }) | ||
ownerStatus: OwnerStatus = 'homeowner'; | ||
|
||
@property({ type: String, attribute: 'household-income' }) | ||
householdIncome: string = '0'; | ||
|
||
@property({ type: String, attribute: 'tax-filing' }) | ||
taxFiling: FilingStatus = 'single'; | ||
|
||
@property({ type: String, attribute: 'household-size' }) | ||
householdSize: string = '1'; | ||
|
||
submit(e: SubmitEvent) { | ||
e.preventDefault(); | ||
const formData = new FormData(e.target as HTMLFormElement); | ||
this.zip = (formData.get('zip') as string) || ''; | ||
this.ownerStatus = (formData.get('owner_status') as OwnerStatus) || ''; | ||
this.householdIncome = (formData.get('household_income') as string) || ''; | ||
this.taxFiling = (formData.get('tax_filing') as FilingStatus) || ''; | ||
this.householdSize = (formData.get('household_size') as string) || ''; | ||
} | ||
|
||
get hideResult() { | ||
return !( | ||
this.zip && | ||
this.ownerStatus && | ||
this.taxFiling && | ||
this.householdIncome && | ||
this.householdSize | ||
); | ||
} | ||
|
||
private _task = new Task(this, { | ||
// this array of parameters is generated by the `args` function below | ||
// it's formatted with snake_case to make it really easy to throw into URLSearchParams | ||
task: async ([ | ||
zip, | ||
owner_status, | ||
household_income, | ||
tax_filing, | ||
household_size, | ||
]) => { | ||
if (this.hideResult) { | ||
// this is a special response type provided by Task to keep it in the INITIAL state | ||
return initialState; | ||
} | ||
const query = new URLSearchParams({ | ||
'location[zip]': zip, | ||
owner_status, | ||
household_income, | ||
tax_filing, | ||
household_size, | ||
}); | ||
query.append('authority_types', 'federal'); | ||
query.append('authority_types', 'state'); | ||
|
||
return await fetchApi( | ||
this.apiKey, | ||
this.apiHost, | ||
'/api/v1/calculator', | ||
query, | ||
); | ||
}, | ||
// if the args array changes then the task will run again | ||
args: () => [ | ||
this.zip, | ||
this.ownerStatus, | ||
this.householdIncome, | ||
this.taxFiling, | ||
this.householdSize, | ||
], | ||
}); | ||
|
||
override render() { | ||
return html` | ||
<div class="calculator"> | ||
<div class="card card-content"> | ||
<h1>Your household info</h1> | ||
${this.hideForm | ||
? nothing | ||
: formTemplate( | ||
[ | ||
this.zip, | ||
this.ownerStatus, | ||
this.householdIncome, | ||
this.taxFiling, | ||
this.householdSize, | ||
], | ||
(event: SubmitEvent) => this.submit(event), | ||
)} | ||
</div> | ||
${this.hideResult | ||
? nothing | ||
: html` | ||
${this._task.render({ | ||
pending: loadingTemplate, | ||
complete: results => stateIncentivesTemplate(results), | ||
error: errorTemplate, | ||
})} | ||
`} | ||
${CALCULATOR_FOOTER} | ||
</div> | ||
`; | ||
} | ||
} | ||
|
||
declare global { | ||
interface HTMLElementTagNameMap { | ||
'rewiring-america-state-calculator': RewiringAmericaStateCalculator; | ||
} | ||
} |
Oops, something went wrong.