Skip to content
Merged
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
4 changes: 2 additions & 2 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ jobs:
- uses: actions/setup-node@v2
with:
node-version: '14.x'
- name: Install dependencies
run: npm install
- name: Install Playwright
run: npx playwright install --with-deps
- name: Install dependencies
run: npm install
- name: Run Playwright tests
run: npx playwright test
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,5 @@ While developing, run `npx mix watch` to compile assets as you edit them. When y

| Version | Date | Changes |
| ------- | ----------- | ------------------------------------------------------------------------- |
| 1.4.1 | Sep 5, 2022 | Google Sheet support<br/>Better error handling<br/>Mobile layout tweaks |
| 1.4 | Sep 4, 2022 | Replaced `moment-timezone` with `luxon`<br/>Restyled in-progress meetings |
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "tsml-ui",
"version": "1.4",
"version": "1.4.1",
"private": false,
"license": "MIT",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion public/app.js

Large diffs are not rendered by default.

80 changes: 80 additions & 0 deletions public/data/invalid.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
[
{
"slug": "monday-7pm",
"name": "Monday 7pm",
"day": "1",
"time": "19:00",
"end_time": "21:00",
"group": "Online Group",
"types": ["O", "M"],
"conference_url": "https://zoom.us/d/1234567"
},
{
"slug": "saturday-8pm",
"name": "Saturday 8pm",
"day": "6",
"time": "20:00",
"end_time": "21:00",
"group": "Online Group",
"types": ["O", "SM"],
"conference_url": "https://zoom.us/d/1234567"
},
{
"slug": "saturday-10am",
"name": "Saturday 10am",
"day": "6",
"time": "10:00",
"end_time": "11:00",
"group": "Online Group",
"types": ["O", "SM"],
"conference_url": "https://zoom.us/d/1234567"
},
{
"slug": "saturday-7pm",
"name": "Saturday 7pm",
"day": "6",
"time": "19:00",
"end_time": "21:00",
"group": "Online Group",
"types": ["O", "SM"],
"conference_url": "https://zoom.us/d/1234567"
},
{
"slug": "appointment",
"name": "Appt",
"group": "Online Group",
"types": ["O", "SM"],
"conference_url": "https://zoom.us/d/1234567"
},
{
"slug": "monday-7am",
"name": "Monday 7am",
"day": "1",
"time": "07:00",
"group": "Online Group",
"types": ["O", "D"],
"conference_url": "https://zoom.us/d/1234567"
},
{
"slug": "sunday-8pm",
"name": "Sunday 8pm",
"day": "7",
"time": "20:00",
"end_time": "21:00",
"group": "Online Group",
"types": ["O", "SM"],
"conference_url": "https://zoom.us/d/1234567"
},
{
"slug": "sunday-8am",
"name": "Sunday 8am",
"day": "7",
"time": "08:00",
"end_time": "12:00",
"group": "Online Group",
"types": ["O", "SM"],
"conference_url": "https://zoom.us/d/1234567"
}
]

invalid text
93 changes: 52 additions & 41 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,41 +21,28 @@ <h1>TSML UI</h1>
interactive recovery meeting finder that can be embedded on any
website.
</p>
<p>
Setup instructions follow. More information is available on the
<a href="https://github.com/code4recovery/tsml-ui" target="_blank"
>project page on GitHub</a
>, including advanced configuration options. To get help, please
<a
href="https://github.com/code4recovery/tsml-ui/issues"
target="_blank"
>file an issue</a
>.
</p>
<section class="my-5">
<h4>12 Step Meeting List</h4>
<h2 class="h4">12 Step Meeting List</h2>
<p>
Go to Dashboard > Meetings > Import & Settings > Settings and
change <code>User Interface Display</code> &rarr;
<code>TSML UI</code>
If you are using the <a href="https://wordpress.org/plugins/12-step-meeting-list/" target="_blank">12
Step Meeting List plugin</a> for WordPress, setup is simple. Go to Dashboard > Meetings > Import &
Settings > Settings and change <code>User Interface Display</code> &rarr; <code>TSML UI</code>
</p>
<!--
<div
style="
position: relative;
padding-bottom: 69.76744186046511%;
height: 0;
"
>
<iframe
src="https://www.loom.com/embed/7b080b54d4674be0959277f148b7b87f"
frameborder="0"
webkitallowfullscreen
mozallowfullscreen
allowfullscreen
style="
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
"
></iframe>
</div>
-->
</section>

<section class="my-5">
<h4>Custom JSON feed</h4>
<h2 class="h4">Custom JSON feed</h2>
<p>
If you can generate a
<a href="https://github.com/code4recovery/spec" target="_blank"
Expand Down Expand Up @@ -91,7 +78,42 @@ <h4>Custom JSON feed</h4>
</section>

<section class="my-5">
<h4>Google Sheet</h4>
<h2 class="h4">Google Sheet (direct)</h2>
<p>
You can connect TSML UI directly to a Google Sheet. To do this,
just use your Google Sheet URL as the
<code>data-src</code> attribute, get a Google Sheets API Key
(<a
href="https://developers.google.com/sheets/api/guides/authorizing#APIKey"
target="_blank"
>here are instructions</a
>), and enter it as the <code>data-google</code> attribute.
</p>
<pre class="rounded bg-dark text-light p-3 overflow-hidden">
&lt;div
id="tsml-ui"
data-src="https://docs.google.com/spreadsheets/d/your.google.sheet.id/edit#gid=more.stuff.here"
data-google="your.google.api.key"
data-mapbox="pk.your.mapbox.access.token.goes.here"
data-timezone="America/Los_Angeles"
&gt;&lt;/div&gt;
&lt;script src="https://tsml-ui.code4recovery.org/app.js" async&gt;&lt;/script&gt;</pre
>
<!-- <button class="btn btn-outline-primary mb-3" href="" disabled>Demo coming soon</button> -->
<p>Some caveats about connecting directly to Google Sheets:
<ol>
<li>Although it is free, Google imposes <a href="https://developers.google.com/sheets/api/limits" target="_blank">rate limits on its Sheets API</a>, and if that limit is exceeded, your users may see a 429 (too many requests) error when accessing the meeting finder.</li>
<li>Depending on how your sheet is set up, the request from Google can be pretty bulky, and page performance may vary.</li>
<li>Users may see incomplete data if they request the page while data is being entered.</li>
<li>Some users may have privacy-oriented content blockers that block their access to Google, which will cause an error.</li>
<li>If you choose to enter personally identifiable information (such as meeting contact information) this will be exposed to users who know how to look for it.</li>
</ol>
</p>
<p>The Sheets Importer option below mitigates these issues and does not require you to have a Google API key.</p>
</section>

<section class="my-5">
<h2 class="h4">Google Sheet (via Sheet Importer)</h2>
<p>
You can generate a JSON feed from a Google Sheet using the
<a href="https://sheets.code4recovery.org/" target="_blank"
Expand All @@ -113,17 +135,6 @@ <h4>Google Sheet</h4>
>
</section>

<p class="mb-4">
More information is available on the
<a href="https://github.com/code4recovery/tsml-ui" target="_blank"
>project page on GitHub</a
>, including advanced configuration options. To get help, please
<a
href="https://github.com/code4recovery/tsml-ui/issues"
target="_blank"
>file an issue</a
>.
</p>
<p class="text-center">
<a href="https://code4recovery.org" target="_blank">
<img
Expand Down
2 changes: 1 addition & 1 deletion public/style.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion public/tests/analytics.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<body>
<div
id="tsml-ui"
data-src="https://airtable-json.aasfmarin.org/"
data-src="https://sheets.code4recovery.org/storage/aasfmarin.json"
data-mapbox="pk.eyJ1Ijoiam9zaHJlaXNuZXIiLCJhIjoiY2tvYXA0YnZxMGRldDJxbzdta25uNGphdiJ9.eay-UKgIT99ALmdw08xBPw"
data-timezone="America/Los_Angeles"
></div>
Expand Down
4 changes: 2 additions & 2 deletions public/tests/cache.html → public/tests/invalid.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
/>
<meta name="description" content="React JS recovery meeting finder demo" />
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
<title>Cache Test Demo</title>
<title>Meetings</title>
</head>

<body>
<div
id="tsml-ui"
data-src="https://sheets.code4recovery.org/storage/1_zuHJBCNxYmxaXxX5-qozyzBNySrhcQcocVQpl4dTMM.json"
data-src="/data/invalid.json"
data-mapbox="pk.eyJ1Ijoiam9zaHJlaXNuZXIiLCJhIjoiY2tvYXA0YnZxMGRldDJxbzdta25uNGphdiJ9.eay-UKgIT99ALmdw08xBPw"
data-timezone="America/Los_Angeles"
></div>
Expand Down
24 changes: 24 additions & 0 deletions public/tests/puget-sound.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<meta name="description" content="React JS recovery meeting finder demo" />
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
<title>Meetings</title>
</head>

<body>
<div
id="tsml-ui"
data-src="https://docs.google.com/spreadsheets/d/13W4lBuRWKpnHNOC_3wTXI5anLVOkyvMmyn4Wvqx1z3c/edit#gid=2133070274"
data-mapbox="pk.eyJ1Ijoiam9zaHJlaXNuZXIiLCJhIjoiY2tvYXA0YnZxMGRldDJxbzdta25uNGphdiJ9.eay-UKgIT99ALmdw08xBPw"
data-google="AIzaSyCS9M8Dqk5cMFqA7xvUrQEzT1u5IvcbT7c"
data-timezone="America/Los_Angeles"
></div>
<script src="/app.js" async></script>
</body>
</html>
2 changes: 2 additions & 0 deletions src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ if (element) {
json: element.getAttribute('data-src') || element.getAttribute('src'),
mapbox:
element.getAttribute('data-mapbox') || element.getAttribute('mapbox'),
google:
element.getAttribute('data-google') || element.getAttribute('google'),
timezone:
element.getAttribute('data-timezone') || tsml_react_config?.timezone,
}}
Expand Down
16 changes: 9 additions & 7 deletions src/components/Alert.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React from 'react';
import { fireEvent, render, screen } from '@testing-library/react';
import Alert from './Alert';
import { strings } from '../helpers';

//TODO: These types can be much better once AppState types are defined.

Expand All @@ -10,21 +12,21 @@ describe('<Alert />', () => {
});

it('works with error state', () => {
render(<Alert state={{ error: 'bad_data' }} setState={jest.fn()} />);

const reloadSpy = jest.spyOn(location, 'reload');
render(
<Alert
state={{ error: 'an error was encountered loading the data' }}
setState={jest.fn()}
/>
);

const text = /an error was encountered loading the data/i;
const button = screen.getByText(/reload/i);

expect(screen.getByText(text)).toBeInTheDocument();
fireEvent.click(button);
expect(reloadSpy).toHaveBeenCalled();
});

it('works with clearing filters with no results', async () => {
const mockState = {
alert: 'no_results',
alert: strings.no_results,
input: {
distance: [],
region: ['foo'],
Expand Down
17 changes: 4 additions & 13 deletions src/components/Alert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,11 @@ type AlertProps = {

export default function Alert({ state, setState }: AlertProps) {
return state.error ? (
<div className="d-flex flex-column gap-3">
<div className="alert alert-danger text-center m-0">
{strings.alerts[state.error]}
</div>
{state.error === 'bad_data' && (
<Button onClick={() => location.reload()} text="Reload" />
)}
</div>
<div className="alert alert-danger text-center m-0">{state.error}</div>
) : state.alert ? (
<div className="d-flex flex-column gap-3">
<div className="alert alert-warning text-center m-0">
{strings.alerts[state.alert]}
</div>
{state.alert === 'no_results' && state.input.search && (
<div className="alert alert-warning text-center m-0">{state.alert}</div>
{state.alert === strings.no_results && state.input.search && (
<Button
onClick={() => {
state.input.search = '';
Expand All @@ -35,7 +26,7 @@ export default function Alert({ state, setState }: AlertProps) {
icon="close"
/>
)}
{state.alert === 'no_results' &&
{state.alert === strings.no_results &&
settings.filters.map(filter =>
state.input[filter].map(value => (
<Button
Expand Down
10 changes: 5 additions & 5 deletions src/components/Controls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,8 @@ export default function Controls({ state, setState, mapbox }: ControlsProps) {

return (
!!Object.keys(state.meetings).length && (
<div className="row d-print-none controls">
<div className="col-sm-6 col-lg mb-3">
<div className="controls d-print-none gx-3 gx-md-4 gy-3 row">
<div className="col-6 col-lg">
<div className="position-relative">
<form className="input-group" onSubmit={locationSearch}>
<input
Expand Down Expand Up @@ -201,7 +201,7 @@ export default function Controls({ state, setState, mapbox }: ControlsProps) {
</div>
</div>
{filters.map((filter, index) => (
<div className="col-sm-6 col-lg mb-3" key={filter}>
<div className="col-6 col-lg" key={filter}>
<Dropdown
defaultValue={strings[`${filter}_any` as keyof typeof strings]}
end={!canShowViews && index === filters.length - 1}
Expand All @@ -214,13 +214,13 @@ export default function Controls({ state, setState, mapbox }: ControlsProps) {
</div>
))}
{canShowViews && (
<div className="col-sm-6 col-lg mb-3">
<div className="col-6 col-lg">
<div className="btn-group h-100 w-100" role="group">
{views.map(view => (
<button
aria-label={strings.views[view]}
className={cx(
'btn btn-outline-secondary d-flex align-items-center justify-content-center w-100',
'align-items-center btn btn-outline-secondary d-flex justify-content-center w-100',
{
active: state.input.view === view,
}
Expand Down
Loading