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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "kanttiinit-web",
"version": "11.0.3",
"version": "12.0.0",
"description": "Kanttiinit.fi web client.",
"main": "index.js",
"engines": {
Expand Down
38 changes: 27 additions & 11 deletions src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,34 +12,50 @@ import {
type Update,
} from './types';

const coursesCache = new Map<string, CourseType[]>();

const coursesCacheKey = (
restaurantId: number | string,
dateStr: string,
lang: string,
) => `${restaurantId}-${dateStr}-${lang}`;

export const getCourses = async (
restaurantId: number,
day: Date,
lang: Lang,
): Promise<CourseType[]> => {
const dateStr = format(day, 'y-MM-dd');
const key = coursesCacheKey(restaurantId, dateStr, lang);
if (coursesCache.has(key)) {
return coursesCache.get(key) as CourseType[];
}
const restaurant = await http.get(
`/restaurants/${restaurantId}/menu?day=${format(
day,
'y-MM-dd',
)}&lang=${lang}`,
`/restaurants/${restaurantId}/menu?day=${dateStr}&lang=${lang}`,
);
if (!restaurant.menus.length) {
return [];
} else {
return restaurant.menus[0].courses;
}
const courses: CourseType[] = restaurant.menus.length
? restaurant.menus[0].courses
: [];
coursesCache.set(key, courses);
return courses;
};

export const getMenus = (
export const getMenus = async (
restaurantIds: number[],
days: Date[],
lang: string,
): Promise<MenuType> => {
return http.get(
const result: MenuType = await http.get(
`/menus?lang=${lang}&restaurants=${restaurantIds.join(
',',
)}&days=${days.map(day => format(day, 'y-MM-dd')).join(',')}`,
);
for (const [restaurantId, dates] of Object.entries(result)) {
for (const [dateStr, courses] of Object.entries(dates)) {
coursesCache.set(coursesCacheKey(restaurantId, dateStr, lang), courses);
}
}
return result;
};

export const sendFeedback = (message: string, email: string) =>
Expand Down
90 changes: 56 additions & 34 deletions src/components/AreaSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { css, styled } from 'solid-styled-components';
import { FilledStarIcon, WalkIcon } from '../icons';
import { computedState, resources, setState, state } from '../state';
import type allTranslations from '../translations';
import Button from './Button';

const iconStyles = css`
margin-right: 0.5ch;
Expand Down Expand Up @@ -37,64 +36,82 @@ const specialAreas: SpecialArea[] = [
},
];

const AreaWrapper = styled.div`
width: 50%;
box-sizing: border-box;
`;

const AreaButton = styled(Button)<{ selected: boolean }>`
background-color: ${props =>
props.selected ? 'var(--gray6)' : 'transparent'};
color: inherit;
const AreaButton = styled.button<{ selected: boolean }>`
width: 100%;
padding: 1em 0.5em;
border-radius: 4px;
font-weight: inherit;
text-align: center;
outline: none;
color: ${props => (props.selected ? 'var(--accent_color)' : 'var(--gray1)')};
border-radius: var(--radius-sm);
padding: 0.6em 0.75em;
display: flex;
align-items: center;
justify-content: space-between;
text-align: left;
font-size: 0.875rem;
font-family: inherit;
font-weight: ${props => (props.selected ? '500' : 'inherit')};
background: ${props => (props.selected ? 'var(--bg-interactive)' : 'transparent')};
color: ${props => (props.selected ? 'var(--accent_color)' : 'var(--text-primary)')};
border: none;
cursor: pointer;
transition: background 0.1s, color 0.1s;

&:hover {
background: var(--bg-interactive);
}

&:hover,
&:focus {
background: var(--gray6);
outline: 2px solid var(--accent_color);
outline-offset: -2px;
}
`;

const Checkmark = styled.span`
color: var(--accent_color);
font-size: 0.9rem;
font-weight: 600;
`;

const Divider = styled.hr`
border: none;
border-top: 1px solid var(--border-subtle);
margin: 4px 8px;
`;

const Area = (props: {
area: { icon?: JSX.Element; label: JSX.Element; id: number };
selectedAreaId: number;
selectArea: (id: number) => void;
}) => (
<AreaWrapper>
}) => {
const selected = () => props.selectedAreaId === props.area.id;
return (
<AreaButton
onKeyDown={(e: KeyboardEvent) =>
e.key === 'Enter' && props.selectArea(props.area.id)
}
onMouseUp={() => props.selectArea(props.area.id)}
selected={props.selectedAreaId === props.area.id}
selected={selected()}
>
{props.area.icon && (
<div style={{ 'margin-right': '4px', display: 'inline-block' }}>
{props.area.icon}
</div>
)}
{props.area.label}
<span>
{props.area.icon && (
<span style={{ 'margin-right': '4px', display: 'inline-block' }}>
{props.area.icon}
</span>
)}
{props.area.label}
</span>
{selected() && <Checkmark>✓</Checkmark>}
</AreaButton>
</AreaWrapper>
);
);
};

interface Props {
onAreaSelected?: () => void;
}

const Container = styled.menu`
margin: 0;
padding: 0;
padding: 4px 0;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
flex-direction: column;
gap: 2px;
user-select: none;
`;

Expand Down Expand Up @@ -123,7 +140,12 @@ export default function AreaSelector(props: Props) {
/>
)}
</For>
<For each={areas()?.sort((a, b) => (a.name > b.name ? -1 : 1))}>
<Divider />
<For
each={areas()
?.slice()
.sort((a, b) => (a.name > b.name ? 1 : -1))}
>
{area => (
<Area
selectedAreaId={state.preferences.selectedArea}
Expand Down
22 changes: 13 additions & 9 deletions src/components/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,33 +9,37 @@ interface ButtonProps {
const Button = styled.button<ButtonProps>`
border: none;
padding: 0.8em 1.2em;
border-radius: 0.4em;
border-radius: var(--radius-md);
font-family: inherit;
font-size: 0.8rem;
display: inline-block;
text-transform: uppercase;
min-width: 4rem;
background: ${props =>
props.color === 'secondary' || props.secondary
? 'var(--gray3)'
? 'var(--text-muted)'
: 'var(--accent_color)'};
text-align: center;
color: var(--gray6);
outline: none;
font-weight: 500;
transition: transform 0.1s;
color: white;
font-weight: 600;
letter-spacing: 0.01em;
transition: transform 0.1s, box-shadow 0.1s;
opacity: 0.95;

&:hover {
opacity: 1;
transform: translateY(-1px);
box-shadow: var(--shadow-sm);
}

&:active {
transform: scale(0.98);
transform: translateY(0) scale(0.98);
box-shadow: none;
}

&:focus {
color: var(--gray6);
color: white;
outline: 2px solid var(--accent_color);
outline-offset: 2px;
}

&:disabled {
Expand Down
6 changes: 3 additions & 3 deletions src/components/ChangeLog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ import PageContainer from './PageContainer';
const UpdateWrapper = styled.div`
margin-bottom: 0.5em;
display: flex;
color: var(--gray2);
color: var(--text-secondary);
padding: 0.5em;
border-radius: 0.2em;
cursor: pointer;

&:hover {
background: var(--gray5);
background: var(--bg-interactive);
}

&:last-child {
Expand All @@ -47,7 +47,7 @@ const PublishedAt = styled.p`
text-transform: uppercase;
font-weight: 500;
margin: 0 0 0;
color: var(--gray2) !important;
color: var(--text-secondary) !important;
`;

const ArrowDownIcon = styled(CaretDownIcon)<{ isVisible: boolean }>`
Expand Down
14 changes: 11 additions & 3 deletions src/components/Contact.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { createSignal } from 'solid-js';
import { computedState } from '../state';
import { useFeedback } from '../utils';
import Button from './Button';
Expand All @@ -6,6 +7,7 @@ import PageContainer from './PageContainer';

const Contact = () => {
const [feedback, send] = useFeedback();
const [acknowledged, setAcknowledged] = createSignal(false);

const onSubmit = (e: any) => {
e.preventDefault();
Expand All @@ -17,6 +19,15 @@ const Contact = () => {
<PageContainer title={computedState.translations().contact}>
{feedback.sent ? (
computedState.translations().thanksForFeedback
) : !acknowledged() ? (
<>
<p style={{ 'line-height': '1.6', color: 'var(--text-secondary)' }}>
{computedState.translations().tosShort}
</p>
<Button onClick={() => setAcknowledged(true)}>
{computedState.translations().continueButton}
</Button>
</>
) : (
<form onSubmit={onSubmit}>
<Input
Expand All @@ -34,9 +45,6 @@ const Contact = () => {
label={computedState.translations().message}
rows={10}
/>
<p>
<i>{computedState.translations().tosShort}</i>
</p>
<Button disabled={feedback.sending} type="submit">
{feedback.sending
? computedState.translations().sending
Expand Down
8 changes: 4 additions & 4 deletions src/components/CourseList/Course.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Property from './Property';

const CourseTitle = styled.h2<{ highlight: boolean; dimmed: boolean }>`
flex: 1;
color: var(--gray1);
color: var(--text-primary);
margin: 0;
font-size: inherit;
font-weight: inherit;
Expand All @@ -22,14 +22,14 @@ const CourseTitle = styled.h2<{ highlight: boolean; dimmed: boolean }>`
`
: ''}

${props => (props.dimmed ? 'color: var(--gray4);' : '')}
${props => (props.dimmed ? 'color: var(--text-disabled);' : '')}
`;

const PropertyContainer = styled.span`
font-size: 0.7rem;
font-weight: 300;
display: inline;
color: var(--gray2);
color: var(--text-secondary);
`;

const FavoriteIcon = styled(HeartFilledIcon)`
Expand All @@ -46,7 +46,7 @@ const CourseWrapper = styled.li<{
font-size: 0.9rem;

&:not(:last-child) {
border-bottom: 1px solid var(--gray6);
border-bottom: 1px solid var(--border-subtle);
}

${props => (props.favorite ? 'color: var(--hearty);' : '')}
Expand Down
5 changes: 3 additions & 2 deletions src/components/CourseList/CourseList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const getCourseGroup = (course: CourseType) => {

interface Props {
courses: CourseType[];
loading?: boolean;
class?: string;
}

Expand All @@ -38,7 +39,7 @@ const Group = styled.ul`

const GroupTitle = styled.h1`
display: block;
color: var(--gray2);
color: var(--text-secondary);
margin: 0 0 0.2rem 0;
font-size: 0.8em;
font-weight: 500;
Expand Down Expand Up @@ -86,7 +87,7 @@ const CourseList = (props: Props) => {

return (
<Container {...props}>
{!props.courses.length && (
{!props.loading && !props.courses.length && (
<EmptyText>{computedState.translations().noMenu}</EmptyText>
)}
<For each={courseGroups()}>
Expand Down
2 changes: 1 addition & 1 deletion src/components/CourseList/Property.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const Container = styled(Tooltip)<{ dimmed: boolean; highlighted: boolean }>`
`
: ''}

${props => (props.dimmed ? 'color: var(--gray4);' : '')}
${props => (props.dimmed ? 'color: var(--text-disabled);' : '')}

&:hover {
color: var(--accent_color);
Expand Down
Loading