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 public/app.js

Large diffs are not rendered by default.

23 changes: 16 additions & 7 deletions src/components/Alert.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getIndexByKey, formatString as i18n } from '../helpers';
import { useNavigate } from 'react-router-dom';
import { formatUrl, getIndexByKey, formatString as i18n } from '../helpers';
import { useData, useError, useFilter, useInput, useSettings } from '../hooks';
import { alertCss, errorCss } from '../styles';

Expand All @@ -8,7 +9,8 @@ export default function Alert() {
const { indexes } = useData();
const { error } = useError();
const { alert } = useFilter();
const { input, setInput } = useInput();
const { input } = useInput();
const navigate = useNavigate();
const { settings, strings } = useSettings();
return error ? (
<div css={errorCss}>{error}</div>
Expand All @@ -17,7 +19,9 @@ export default function Alert() {
<div css={alertCss}>{alert}</div>
{alert === strings.no_results && input.search && (
<Button
onClick={() => setInput(input => ({ ...input, search: '' }))}
onClick={() =>
navigate(formatUrl({ ...input, search: '' }, settings))
}
text={i18n(strings.remove, { filter: input.search })}
icon="close"
/>
Expand All @@ -29,10 +33,15 @@ export default function Alert() {
key={value}
icon="close"
onClick={() => {
setInput(input => ({
...input,
[filter]: input[filter].filter(item => item !== value),
}));
navigate(
formatUrl(
{
...input,
[filter]: input[filter].filter(item => item !== value),
},
settings
)
);
}}
text={i18n(strings.remove, {
filter: getIndexByKey(indexes[filter], value)?.name,
Expand Down
23 changes: 11 additions & 12 deletions src/components/Controls.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { FormEvent, MouseEvent, useEffect, useRef, useState } from 'react';

import { analyticsEvent } from '../helpers';
import { useNavigate } from 'react-router-dom';

import { analyticsEvent, formatUrl } from '../helpers';
import { useData, useInput, useSettings } from '../hooks';
import {
controlsCss,
Expand All @@ -18,9 +20,10 @@ import Dropdown from './Dropdown';

export default function Controls() {
const { capabilities, meetings, slugs } = useData();
const navigate = useNavigate();
const { settings, strings } = useSettings();
const [dropdown, setDropdown] = useState<string>();
const { input, setInput } = useInput();
const { input } = useInput();
const [search, setSearch] = useState(input.search);
const searchInput = useRef<HTMLInputElement>(null);

Expand Down Expand Up @@ -78,7 +81,7 @@ export default function Controls() {

if (value === input.search) return;

setInput(input => ({ ...input, search: value }));
navigate(formatUrl({ ...input, search: value }, settings));
}, [searchInput.current?.value]);

// update search when global state changes
Expand Down Expand Up @@ -106,7 +109,7 @@ export default function Controls() {
});
}

setInput(input => ({ ...input, search }));
navigate(formatUrl({ ...input, search }, settings));
};

// set search mode dropdown and reset distances
Expand All @@ -127,7 +130,7 @@ export default function Controls() {
// focus after waiting for disabled to clear
setTimeout(() => searchInput.current?.focus(), 100);

setInput(input => ({
const newInput = {
...input,
distance:
mode === 'search'
Expand All @@ -136,13 +139,9 @@ export default function Controls() {
mode,
region: mode === 'search' ? input.region : [],
search,
}));
};
};

// toggle list/map view
const setView = (e: MouseEvent, view: 'table' | 'map') => {
e.preventDefault();
setInput(input => ({ ...input, view }));
navigate(formatUrl(newInput, settings));
};

return !slugs.length ? null : (
Expand Down Expand Up @@ -224,7 +223,7 @@ export default function Controls() {
css={index ? controlsGroupLastCss : controlsGroupFirstCss}
data-active={input.view === view}
key={view}
onClick={e => setView(e, view)}
onClick={() => navigate(formatUrl({ ...input, view }, settings))}
type="button"
>
{strings.views[view]}
Expand Down
20 changes: 12 additions & 8 deletions src/components/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
useState,
} from 'react';

import { getIndexByKey, formatString as i18n } from '../helpers';
import { useNavigate } from 'react-router-dom';
import { formatUrl, getIndexByKey, formatString as i18n } from '../helpers';
import { type Data, useData, useInput, useSettings } from '../hooks';
import { dropdownButtonCss, dropdownCss } from '../styles';
import type { Index } from '../types';
Expand All @@ -23,8 +24,9 @@ export default function Dropdown({
setDropdown: Dispatch<SetStateAction<string | undefined>>;
}) {
const { indexes } = useData();
const { strings } = useSettings();
const { input, setInput, waitingForInput } = useInput();
const navigate = useNavigate();
const { settings, strings } = useSettings();
const { input, waitingForInput } = useInput();
const options = indexes[filter];
const values =
filter === 'distance'
Expand Down Expand Up @@ -54,10 +56,12 @@ export default function Dropdown({
e.preventDefault();

if (filter === 'distance') {
setInput(input => ({
...input,
distance: value ? parseInt(value) : undefined,
}));
navigate(
formatUrl(
{ ...input, distance: value ? parseInt(value) : undefined },
settings
)
);
} else {
// add or remove from filters
let currentValues = input[filter] as string[];
Expand All @@ -80,7 +84,7 @@ export default function Dropdown({
// Remove the filter from search params if no value is provided
currentValues = [];
}
setInput(input => ({ ...input, [filter]: currentValues }));
navigate(formatUrl({ ...input, [filter]: currentValues }, settings));
}
};

Expand Down
14 changes: 5 additions & 9 deletions src/components/Link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import { formatUrl } from '../helpers';
import { useFilter, useInput, useSettings } from '../hooks';
import type { Meeting } from '../types';

import { Link as RouterLink } from 'react-router-dom';

export default function Link({ meeting }: { meeting: Meeting }) {
const { meeting: thisMeeting } = useFilter();
const { settings, strings } = useSettings();
const { input, setInput } = useInput();
const { input } = useInput();

const flags =
settings.flags
Expand All @@ -27,15 +29,9 @@ export default function Link({ meeting }: { meeting: Meeting }) {

return (
<>
<a
href={formatUrl({ ...input, meeting: meeting.slug }, settings)}
onClick={e => {
e.preventDefault();
setInput(input => ({ ...input, meeting: meeting.slug }));
}}
>
<RouterLink to={formatUrl({ ...input, meeting: meeting.slug }, settings)}>
{meeting.name}
</a>
</RouterLink>
{flags && <small>{flags}</small>}
</>
);
Expand Down
13 changes: 4 additions & 9 deletions src/components/Meeting.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react';

import { DateTime, Info } from 'luxon';
import { Link as RouterLink } from 'react-router-dom';

import {
formatDirectionsUrl,
Expand Down Expand Up @@ -29,7 +30,7 @@ import type { Meeting as MeetingType } from '../types';

export default function Meeting({ meeting }: { meeting: MeetingType }) {
const { settings, strings } = useSettings();
const { input, setInput } = useInput();
const { input } = useInput();

// open types
const [define, setDefine] = useState<string | undefined>();
Expand Down Expand Up @@ -258,15 +259,9 @@ export default function Meeting({ meeting }: { meeting: MeetingType }) {
</h1>
<div css={meetingBackCss}>
<Icon icon="back" />
<a
href={formatUrl({ ...input, meeting: undefined }, settings)}
onClick={e => {
e.preventDefault();
setInput(input => ({ ...input, meeting: undefined }));
}}
>
<RouterLink to={formatUrl({ ...input, meeting: undefined }, settings)}>
{strings.back_to_meetings}
</a>
</RouterLink>
</div>
<div css={meetingColumnsCss}>
<div>
Expand Down
11 changes: 8 additions & 3 deletions src/components/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import { useState } from 'react';

import InfiniteScroll from 'react-infinite-scroller';

import { formatString as i18n } from '../helpers';
import { useNavigate } from 'react-router-dom';

import { formatUrl, formatString as i18n } from '../helpers';
import { useData, useError, useFilter, useInput, useSettings } from '../hooks';
import {
tableChicletCss,
Expand All @@ -20,7 +22,8 @@ export default function Table() {
const { error } = useError();
const { filteredSlugs, inProgress } = useFilter();
const { settings, strings } = useSettings();
const { latitude, longitude, setInput } = useInput();
const { input, latitude, longitude } = useInput();
const navigate = useNavigate();
const meetingsPerPage = 10;
const supported_columns = [
'address',
Expand Down Expand Up @@ -130,7 +133,9 @@ export default function Table() {
const meeting = meetings[slug];
return (
<tr
onClick={() => setInput(input => ({ ...input, meeting: meeting.slug }))}
onClick={() =>
navigate(formatUrl({ ...input, meeting: meeting.slug }, settings))
}
>
{columns.map((column, index) => (
<td className={`tsml-${column}`} key={index}>
Expand Down
2 changes: 1 addition & 1 deletion src/helpers/format-feedback-email.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export function formatFeedbackEmail({
strings: Translation;
}) {
// remove extra query params from meeting URL
const meetingUrl = formatUrl({ meeting: meeting.slug }, settings);
const meetingUrl = formatUrl({ meeting: meeting.slug }, settings, true);

// build message
const lines = [
Expand Down
17 changes: 13 additions & 4 deletions src/helpers/format-url.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
// format an internal link with correct query params
export function formatUrl(
input: Partial<TSMLReactConfig['defaults']>,
settings: TSMLReactConfig
settings: TSMLReactConfig,
includeDomain = false
) {
const query = {};

// distance, region, time, type, and weekday
// region, time, type, and weekday
settings.filters
.filter(filter => typeof input[filter] !== 'undefined')
.filter(filter => input[filter]?.length)
Expand All @@ -14,6 +15,12 @@ export function formatUrl(
query[filter] = input[filter].join('/');
});

// distance
if (typeof input.distance !== 'undefined') {
// @ts-expect-error TODO
query.distance = input.distance.toString();
}

// meeting, mode, search, view
settings.params
.filter(param => typeof input[param] !== 'undefined')
Expand All @@ -30,7 +37,9 @@ export function formatUrl(
.replace(/%20/g, '+')
.replace(/%2C/g, ',');

const [path] = window.location.href.split('?');
const base = includeDomain ? `${window.location.origin}` : '';

const [path] = window.location.pathname.split('?');

return `${path}${queryString.length ? `?${queryString}` : ''}`;
return `${base}${path}${queryString.length ? `?${queryString}` : ''}`;
}
33 changes: 6 additions & 27 deletions src/hooks/input.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {
import {
createContext,
PropsWithChildren,
useContext,
Expand All @@ -22,13 +22,12 @@ const InputContext = createContext<
input: TSMLReactConfig['defaults'];
latitude?: number;
longitude?: number;
setInput: React.Dispatch<React.SetStateAction<TSMLReactConfig['defaults']>>;
} & Coordinates
>({ input: defaults.defaults, setInput: () => {}, waitingForInput: false });
>({ input: defaults.defaults, waitingForInput: false });

export const InputProvider = ({ children }: PropsWithChildren) => {
const { setError } = useError();
const [searchParams, setSearchParams] = useSearchParams();
const [searchParams] = useSearchParams();
const { settings, strings } = useSettings();

const [input, setInput] = useState<TSMLReactConfig['defaults']>(
Expand All @@ -39,7 +38,7 @@ export const InputProvider = ({ children }: PropsWithChildren) => {
waitingForInput: input.mode !== 'search',
});

// detect initial input from URL search params
// detect input from URL search params
useEffect(() => {
const mode =
searchParams.get('mode') === 'location'
Expand Down Expand Up @@ -80,27 +79,7 @@ export const InputProvider = ({ children }: PropsWithChildren) => {
view,
weekday,
}));
}, []);

// update URL search params when input changes
useEffect(() => {
if (input === defaults.defaults) return;
const params = {
distance: input.distance,
meeting: input.meeting,
mode: input.mode == defaults.defaults.mode ? '' : input.mode,
region: input.region.join('/'),
search: input.search,
time: input.time.join('/'),
type: input.type.join('/'),
view: input.view === defaults.defaults.view ? '' : input.view,
weekday: input.weekday.join('/'),
};
const filteredParams = Object.fromEntries(
Object.entries(params).filter(([, value]) => value)
) as { [key: string]: string };
setSearchParams(filteredParams);
}, [input]);
}, [searchParams]);

// handle geocoding or geolocation requests
useEffect(() => {
Expand Down Expand Up @@ -166,7 +145,7 @@ export const InputProvider = ({ children }: PropsWithChildren) => {
}, [input.mode, input.search]);

return (
<InputContext.Provider value={{ input, setInput, ...coordinates }}>
<InputContext.Provider value={{ input, ...coordinates }}>
{children}
</InputContext.Provider>
);
Expand Down
Loading