Skip to content

Commit b10bb9c

Browse files
authored
Merge pull request #1101 from OpenSourceFellows/clean-readme-patch
docs: fix typo and header capitalization in README
2 parents c186d01 + 54eff41 commit b10bb9c

3 files changed

Lines changed: 452 additions & 1 deletion

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Amplify
22

3-
Amplify is an open-source app created for users to take the initiative in being part of an actionable step in the efforts to protect against climate change. The user is able to choose a climate campaign, then using their zip code, they will be able to select a representative of their choice. The user then donates to have their letter sent out by Amplify
3+
Amplify is an open-source app created for users to take the initiative in being part of an actionable step in the efforts to protect against climate change. The user is able to choose a climate campaign, then using their zip code, they will be able to select a representative of their choice. The user then donates to have their letter sent out by Amplify.
44

55

66
## Table of Contents
Lines changed: 360 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,360 @@
1+
# Front-end API Wrapper Implementation Plan
2+
3+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4+
5+
**Goal:** Create `src/api/index.js` — a class-based axios wrapper with a custom `APIError`, following the existing `PaymentPresenter` pattern — and migrate the two v1 routes as working examples.
6+
7+
**Architecture:** An `API` class takes a resource path and optional version string, builds a base URL, and exposes `get/post/put/delete` methods that return `response.data` on success and throw `APIError` on failure. Two existing call sites (`store/index.js:loadLetterTemplate` and `LetterLoad.vue:renderLetter`) are refactored to use the new class.
8+
9+
**Tech Stack:** Vue 2, Vuex 3, axios 0.27, Jest 28, Babel (`@vue/cli-plugin-babel/preset`)
10+
11+
---
12+
13+
## File Map
14+
15+
| File | Action | Responsibility |
16+
|------|--------|----------------|
17+
| `src/api/index.js` | Create | `APIError` class + `API` class |
18+
| `src/api/__tests__/api.test.js` | Create | Unit tests (Jest, mocked axios) |
19+
| `src/store/index.js` | Modify (lines 1, 123–139) | Replace axios with `API` in `loadLetterTemplate` |
20+
| `src/components/LetterLoad.vue` | Modify (lines 53, 132–137) | Replace axios with `API` in `renderLetter` |
21+
22+
---
23+
24+
## Task 1: Bootstrap the test file and watch it fail
25+
26+
**Files:**
27+
- Create: `src/api/__tests__/api.test.js`
28+
29+
- [ ] **Step 1: Create the test directory and file**
30+
31+
```bash
32+
mkdir -p src/api/__tests__
33+
```
34+
35+
- [ ] **Step 2: Write the test file**
36+
37+
Save to `src/api/__tests__/api.test.js`:
38+
39+
```js
40+
import axios from 'axios'
41+
import { API, APIError } from '../index'
42+
43+
jest.mock('axios')
44+
45+
afterEach(() => {
46+
jest.clearAllMocks()
47+
})
48+
49+
describe('API — URL construction', () => {
50+
test('builds base URL with version', () => {
51+
const api = new API('/campaigns', 'v1')
52+
expect(api.baseUrl).toBe('/api/v1/campaigns')
53+
})
54+
55+
test('builds base URL without version', () => {
56+
const api = new API('/representatives')
57+
expect(api.baseUrl).toBe('/api/representatives')
58+
})
59+
})
60+
61+
describe('API — get()', () => {
62+
test('returns response.data on success', async () => {
63+
const mockData = { id: 1, name: 'test' }
64+
axios.get.mockResolvedValue({ data: mockData })
65+
66+
const api = new API('/campaigns')
67+
const result = await api.get()
68+
expect(result).toEqual(mockData)
69+
})
70+
71+
test('throws APIError on failure', async () => {
72+
axios.get.mockRejectedValue({ message: 'Not found', response: { status: 404 } })
73+
74+
const api = new API('/campaigns')
75+
await expect(api.get()).rejects.toThrow(APIError)
76+
})
77+
78+
test('APIError carries HTTP status', async () => {
79+
axios.get.mockRejectedValue({ message: 'Not found', response: { status: 404 } })
80+
81+
const api = new API('/campaigns')
82+
await expect(api.get()).rejects.toMatchObject({ status: 404 })
83+
})
84+
})
85+
86+
describe('API — post()', () => {
87+
test('returns response.data on success', async () => {
88+
const mockData = { letter: '<p>Hello</p>' }
89+
axios.post.mockResolvedValue({ data: mockData })
90+
91+
const api = new API('/letter_templates', 'v1')
92+
const result = await api.post('/render', { templateId: 1 })
93+
expect(result).toEqual(mockData)
94+
})
95+
96+
test('throws APIError on failure', async () => {
97+
axios.post.mockRejectedValue({ message: 'Server error', response: { status: 500 } })
98+
99+
const api = new API('/letter_templates', 'v1')
100+
await expect(api.post('/render', {})).rejects.toThrow(APIError)
101+
})
102+
})
103+
```
104+
105+
- [ ] **Step 3: Run tests — confirm they fail with "Cannot find module '../index'"**
106+
107+
```bash
108+
npx jest src/api/__tests__/api.test.js --no-coverage
109+
```
110+
111+
Expected output contains: `Cannot find module '../index'`
112+
113+
---
114+
115+
## Task 2: Implement `src/api/index.js`
116+
117+
**Files:**
118+
- Create: `src/api/index.js`
119+
120+
- [ ] **Step 1: Create the file**
121+
122+
Save to `src/api/index.js`:
123+
124+
```js
125+
import axios from 'axios'
126+
127+
export class APIError extends Error {
128+
constructor(message, status) {
129+
super(message)
130+
this.name = 'APIError'
131+
this.status = status
132+
}
133+
}
134+
135+
export class API {
136+
constructor(path, version) {
137+
this.baseUrl = version ? `/api/${version}${path}` : `/api${path}`
138+
}
139+
140+
async get(endpoint = '', params = {}) {
141+
try {
142+
const res = await axios.get(this.baseUrl + endpoint, { params })
143+
return res.data
144+
} catch (e) {
145+
throw new APIError(e.message, e.response?.status)
146+
}
147+
}
148+
149+
async post(endpoint = '', data = {}) {
150+
try {
151+
const res = await axios.post(this.baseUrl + endpoint, data)
152+
return res.data
153+
} catch (e) {
154+
throw new APIError(e.message, e.response?.status)
155+
}
156+
}
157+
158+
async put(endpoint = '', data = {}) {
159+
try {
160+
const res = await axios.put(this.baseUrl + endpoint, data)
161+
return res.data
162+
} catch (e) {
163+
throw new APIError(e.message, e.response?.status)
164+
}
165+
}
166+
167+
async delete(endpoint = '') {
168+
try {
169+
const res = await axios.delete(this.baseUrl + endpoint)
170+
return res.data
171+
} catch (e) {
172+
throw new APIError(e.message, e.response?.status)
173+
}
174+
}
175+
}
176+
```
177+
178+
- [ ] **Step 2: Run tests — confirm all pass**
179+
180+
```bash
181+
npx jest src/api/__tests__/api.test.js --no-coverage
182+
```
183+
184+
Expected output: `Tests: 7 passed, 7 total`
185+
186+
- [ ] **Step 3: Commit**
187+
188+
```bash
189+
git add src/api/index.js src/api/__tests__/api.test.js
190+
git commit -m "feat: add API wrapper class and APIError"
191+
```
192+
193+
---
194+
195+
## Task 3: Refactor `loadLetterTemplate` in `src/store/index.js`
196+
197+
**Files:**
198+
- Modify: `src/store/index.js`
199+
200+
The current action (lines 123–139) calls `axios.get` directly and uses the full URL string:
201+
202+
```js
203+
// CURRENT — remove this
204+
import axios from 'axios'
205+
...
206+
async loadLetterTemplate({ state, commit }) {
207+
const templateUrl = `/api/v1/letter_templates/${state.campaign.letterTemplateId}`
208+
try {
209+
const res = await axios.get(templateUrl)
210+
const { letterTemplate } = res.data
211+
console.log(letterTemplate)
212+
commit('setGenericValue', { key: 'letterTemplate', value: letterTemplate })
213+
} catch (e) {
214+
alert(e)
215+
}
216+
}
217+
```
218+
219+
- [ ] **Step 1: Add the API import at line 1 of `src/store/index.js`**
220+
221+
Replace:
222+
```js
223+
import axios from 'axios'
224+
```
225+
With:
226+
```js
227+
import { API } from '@/api'
228+
```
229+
230+
- [ ] **Step 2: Replace `loadLetterTemplate` action (lines 123–139)**
231+
232+
Replace:
233+
```js
234+
async loadLetterTemplate({ state, commit }) {
235+
const templateUrl = `/api/v1/letter_templates/${state.campaign.letterTemplateId}`
236+
237+
try {
238+
const res = await axios.get(templateUrl)
239+
240+
const { letterTemplate } = res.data
241+
console.log(letterTemplate)
242+
243+
commit('setGenericValue', {
244+
key: 'letterTemplate',
245+
value: letterTemplate
246+
})
247+
} catch (e) {
248+
alert(e)
249+
}
250+
}
251+
```
252+
253+
With:
254+
```js
255+
async loadLetterTemplate({ state, commit }) {
256+
const letterTemplatesApi = new API('/letter_templates', 'v1')
257+
258+
try {
259+
const data = await letterTemplatesApi.get(`/${state.campaign.letterTemplateId}`)
260+
261+
commit('setGenericValue', {
262+
key: 'letterTemplate',
263+
value: data.letterTemplate
264+
})
265+
} catch (e) {
266+
alert(e.message)
267+
}
268+
}
269+
```
270+
271+
- [ ] **Step 3: Run the full test suite to confirm nothing broke**
272+
273+
```bash
274+
npx jest --no-coverage
275+
```
276+
277+
Expected: all previously passing tests still pass.
278+
279+
- [ ] **Step 4: Commit**
280+
281+
```bash
282+
git add src/store/index.js
283+
git commit -m "refactor: use API wrapper in loadLetterTemplate store action"
284+
```
285+
286+
---
287+
288+
## Task 4: Refactor `renderLetter` in `src/components/LetterLoad.vue`
289+
290+
**Files:**
291+
- Modify: `src/components/LetterLoad.vue`
292+
293+
The current method (lines 53, 132–137) imports axios directly:
294+
295+
```js
296+
// CURRENT — to be replaced
297+
import axios from 'axios'
298+
...
299+
renderLetter() {
300+
axios.post('/api/v1/letter_templates/render', { mergeVariables: { ...this.userSelections, representativeName: this.selectedRep.name, firstName: '<Your name here>', lastName: '' }, templateId: this.letterTemplate.id })
301+
.then((res) => {
302+
this.letterBody = res.data.letter
303+
})
304+
}
305+
```
306+
307+
- [ ] **Step 1: Replace the `axios` import in the `<script>` block**
308+
309+
Replace:
310+
```js
311+
import axios from 'axios'
312+
```
313+
With:
314+
```js
315+
import { API } from '@/api'
316+
```
317+
318+
- [ ] **Step 2: Replace the `renderLetter` method**
319+
320+
Replace:
321+
```js
322+
renderLetter() {
323+
axios.post('/api/v1/letter_templates/render', { mergeVariables: { ...this.userSelections, representativeName: this.selectedRep.name, firstName: '<Your name here>', lastName: '' }, templateId: this.letterTemplate.id })
324+
.then((res) => {
325+
this.letterBody = res.data.letter
326+
})
327+
}
328+
```
329+
330+
With:
331+
```js
332+
async renderLetter() {
333+
const letterTemplatesApi = new API('/letter_templates', 'v1')
334+
const data = await letterTemplatesApi.post('/render', {
335+
mergeVariables: {
336+
...this.userSelections,
337+
representativeName: this.selectedRep.name,
338+
firstName: '<Your name here>',
339+
lastName: ''
340+
},
341+
templateId: this.letterTemplate.id
342+
})
343+
this.letterBody = data.letter
344+
}
345+
```
346+
347+
- [ ] **Step 3: Run the full test suite**
348+
349+
```bash
350+
npx jest --no-coverage
351+
```
352+
353+
Expected: all previously passing tests still pass.
354+
355+
- [ ] **Step 4: Commit**
356+
357+
```bash
358+
git add src/components/LetterLoad.vue
359+
git commit -m "refactor: use API wrapper in LetterLoad renderLetter"
360+
```

0 commit comments

Comments
 (0)