diff --git a/README.md b/README.md
index 506e3d61..6c7cf39e 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,28 @@
# Open bus ranking app
## Welcome!
-This is the official repository of the open bus (תחב"צ פתוחה) project - also known as "ShameBus".
+This is the official repository of the open bus (תחב"צ פתוחה / דאטאבוס) project - also known as "ShameBus".
[link to the project](https://open-bus-map-search.hasadna.org.il/dashboard)
-- While in the site, type "storybook" (in lowercase) to see a secret 😉
Please feel free to submit pull requests and contribute to the project (see the "contribution" section).
## View video (Hebrew language):
[![video (hebrew) about the project](https://img.youtube.com/vi/6H6jkJCVhgk/0.jpg)](https://www.youtube.com/watch?v=6H6jkJCVhgk)
+# Easter eggs
+We've hidden a couple of fun surprises in our web app, just for you. Discovering them is as easy as typing a few magic words on your keyboard.
+
+## How to Find the Easter Eggs
+1. Open our [web app](https://open-bus-map-search.hasadna.org.il/dashboard)
+2. **Unleash the Magic Words:**
+ To reveal the hidden gems, use your keyboard to type the following commands:
+
+ - **Type "storybook":**
+ Watch the magic unfold as you type "storybook" on your keyboard. You might just stumble upon our Storybook, a treasure trove of UI components showcasing the beauty and functionality of our app.
+ - **Type "english":**
+ Feel like switching up the language? Type "english" and see the language toggle in action. Our app is multilingual, and you can experience it by triggering this secret command.
+
+
## deployments
[![Netlify Status](https://api.netlify.com/api/v1/badges/d3ef62c2-b5bb-48ac-8299-71e5bd22b211/deploy-status)](https://app.netlify.com/sites/open-bus/deploys)
diff --git a/src/api/useVehicleLocations.ts b/src/api/useVehicleLocations.ts
index 98508c9e..18c81262 100644
--- a/src/api/useVehicleLocations.ts
+++ b/src/api/useVehicleLocations.ts
@@ -139,7 +139,7 @@ function getLocations({
operatorRef?: number
onUpdate: (locations: VehicleLocation[] | { finished: true }) => void // the observer will be called every time with all the locations that were loaded
}) {
- const key = `${formatTime(from)}-${formatTime(to)}`
+ const key = `${formatTime(from)}-${formatTime(to)}-${operatorRef}-${lineRef}`
if (!loadedLocations.has(key)) {
loadedLocations.set(key, new LocationObservable({ from, to, lineRef, operatorRef }))
}
diff --git a/src/layout/index.tsx b/src/layout/index.tsx
index 333d5f17..9ce78566 100644
--- a/src/layout/index.tsx
+++ b/src/layout/index.tsx
@@ -6,6 +6,8 @@ import LayoutContext from './LayoutContext'
import { Outlet } from 'react-router-dom'
import { Suspense } from 'react'
import Preloader from 'src/shared/Preloader'
+import { EasterEgg } from 'src/pages/EasterEgg/EasterEgg'
+import { Envelope } from 'src/pages/EasterEgg/Envelope'
const { Content } = Layout
@@ -32,6 +34,11 @@ export function MainLayout() {
}>
+
+
+
+
+
diff --git a/src/layout/sidebar/menu/Menu.tsx b/src/layout/sidebar/menu/Menu.tsx
index 8d01c962..4dbbe2f1 100644
--- a/src/layout/sidebar/menu/Menu.tsx
+++ b/src/layout/sidebar/menu/Menu.tsx
@@ -6,6 +6,7 @@ import { PAGES } from 'src/routes'
import type { MenuProps } from 'antd'
import { Menu } from 'antd'
+import { LanguageToggle } from 'src/pages/EasterEgg/LanguageToggle'
type MenuItem = Required['items'][number]
function getItem(
@@ -23,17 +24,11 @@ function getItem(
}
const MainMenu = () => {
- const { t, i18n } = useTranslation()
+ const { t } = useTranslation()
const items: MenuItem[] = PAGES.map((itm) => {
return getItem({t(itm.label)}, itm.path, itm.icon)
})
- const [currentLanguage, setCurrentLanguage] = useState('en')
- const handleChangeLanguage = () => {
- const newLanguage = currentLanguage === 'en' ? 'he' : 'en'
- setCurrentLanguage(newLanguage)
- i18n.changeLanguage(newLanguage)
- }
const location = useLocation()
const [current, setCurrent] = useState(
location.pathname === '/' || location.pathname === '' ? '/dashboard' : location.pathname,
@@ -59,7 +54,7 @@ const MainMenu = () => {
mode="inline"
items={items}
/>
- {null && }
+ {}
>
)
}
diff --git a/src/locale/en.json b/src/locale/en.json
index f565208d..6ab7696c 100644
--- a/src/locale/en.json
+++ b/src/locale/en.json
@@ -70,6 +70,7 @@
"migdal_company": "\"A tower in the community\"",
"and_smaller_donors": "And other small contributions from my friends and fans of the workshop.",
"github_link": "Go to GitHub",
+ "Change Language":"שנה שפה",
"bug_title": "Title/Summary",
"bug_title_message": "Please enter a title/summary!",
"bug_description": "Description",
diff --git a/src/locale/he.json b/src/locale/he.json
index 1cd2ab74..221a83f3 100644
--- a/src/locale/he.json
+++ b/src/locale/he.json
@@ -70,6 +70,7 @@
"and_smaller_donors": "ותרומות קטנות נוספות של ידידי ואוהדי הסדנא.",
"gaps_patterns_page_title": "דפוסי נסיעות שלא יצאו",
"github_link": "למעבר אל GitHub",
+ "Change Language":"Change Language",
"bug_title": "כותרת/סיכום",
"bug_title_message": "אנא הזן כותרת/סיכום!",
"bug_description": "תיאור",
diff --git a/src/pages/EasterEgg/EasterEgg.tsx b/src/pages/EasterEgg/EasterEgg.tsx
index 366109a8..8d1ea84b 100644
--- a/src/pages/EasterEgg/EasterEgg.tsx
+++ b/src/pages/EasterEgg/EasterEgg.tsx
@@ -1,233 +1,9 @@
-import { useState } from 'react'
-import styled from 'styled-components'
+import { createContext, useState } from 'react'
import useKonami from 'use-konami'
-const colors = {
- primaryColor300: '#7ec1ff', // the prev color '#e95f55',
- primaryColor400: '#1890ff', // the prev color '#e15349' ,
- primaryColor500: '#317fc8', // the prev color '#cb5a5e',
- primaryColor600: '#136fc5', // the prev color '#cf4a43',
-}
-const EnvelopeWrapper = styled.div`
- .letter-image {
- position: absolute;
- top: 50%;
- left: 50%;
- width: 200px;
- height: 200px;
- -webkit-transform: translate(-50%, -50%);
- -moz-transform: translate(-50%, -50%);
- transform: translate(-50%, -50%);
- cursor: pointer;
- direction: ltr;
- z-index: 999;
- opacity: 1;
- animation: fadeIn 1s ease-in-out;
-
- &.fade-out {
- opacity: 0;
- animation: fadeOut 1s ease-in-out;
- }
- }
-
- @keyframes fadeIn {
- 0% {
- opacity: 0;
- }
- 100% {
- opacity: 1;
- }
- }
-
- @keyframes fadeOut {
- 0% {
- opacity: 1;
- }
- 100% {
- opacity: 0;
- }
- }
-
- .animated-mail {
- position: absolute;
- height: 150px;
- width: 200px;
- -webkit-transition: 0.4s;
- -moz-transition: 0.4s;
- transition: 0.4s;
-
- .body {
- position: absolute;
- bottom: 0;
- width: 0;
- height: 0;
- border-style: solid;
- border-width: 0 0 100px 200px;
- border-color: transparent transparent ${colors.primaryColor300} transparent;
- z-index: 2;
- }
-
- .top-fold {
- position: absolute;
- top: 50px;
- width: 0;
- height: 0;
- border-style: solid;
- border-width: 50px 100px 0 100px;
- -webkit-transform-origin: 50% 0%;
- -webkit-transition: transform 0.4s 0.4s, z-index 0.2s 0.4s;
- -moz-transform-origin: 50% 0%;
- -moz-transition: transform 0.4s 0.4s, z-index 0.2s 0.4s;
- transform-origin: 50% 0%;
- transition: transform 0.4s 0.4s, z-index 0.2s 0.4s;
- border-color: ${colors.primaryColor600} transparent transparent transparent;
- z-index: 2;
- }
-
- .back-fold {
- position: absolute;
- bottom: 0;
- width: 200px;
- height: 100px;
- background: ${colors.primaryColor600};
- z-index: 0;
- }
-
- .left-fold {
- position: absolute;
- bottom: 0;
- width: 0;
- height: 0;
- border-style: solid;
- border-width: 50px 0 50px 100px;
- border-color: transparent transparent transparent ${colors.primaryColor400};
- z-index: 2;
- }
-
- .letter {
- left: 20px;
- bottom: 0px;
- position: absolute;
- width: 160px;
- height: 60px;
- background: white;
- z-index: 1;
- overflow: hidden;
- -webkit-transition: 0.4s 0.2s;
- -moz-transition: 0.4s 0.2s;
- transition: 0.4s 0.2s;
-
- .letter-border {
- height: 10px;
- width: 100%;
- background: repeating-linear-gradient(
- -45deg,
- ${colors.primaryColor500},
- ${colors.primaryColor500} 8px,
- transparent 8px,
- transparent 18px
- );
- }
-
- .letter-title {
- margin-top: 10px;
- margin-left: 5px;
- height: 10px;
- width: 40%;
- background: ${colors.primaryColor500};
- }
- .letter-context {
- margin-top: 10px;
- margin-left: 5px;
- height: 10px;
- width: 20%;
- background: ${colors.primaryColor500};
- }
- .letter-favicon {
- display: flex;
- justify-content: center;
- }
- .letter-stamp {
- margin-top: 30px;
- margin-left: 120px;
- border-radius: 100%;
- height: 30px;
- width: 30px;
- background: ${colors.primaryColor500};
- opacity: 0.3;
- }
- }
- }
-
- .shadow {
- position: absolute;
- top: 200px;
- left: 50%;
- width: 400px;
- height: 30px;
- transition: 0.4s;
- transform: translateX(-50%);
- -webkit-transition: 0.4s;
- -webkit-transform: translateX(-50%);
- -moz-transition: 0.4s;
- -moz-transform: translateX(-50%);
-
- border-radius: 100%;
- background: radial-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0), rgba(0, 0, 0, 0));
- }
-
- .letter-image:hover {
- .animated-mail {
- transform: translateY(50px);
- -webkit-transform: translateY(50px);
- -moz-transform: translateY(50px);
- }
-
- .animated-mail .top-fold {
- transition: transform 0.4s, z-index 0.2s;
- transform: rotateX(180deg);
- -webkit-transition: transform 0.4s, z-index 0.2s;
- -webkit-transform: rotateX(180deg);
- -moz-transition: transform 0.4s, z-index 0.2s;
- -moz-transform: rotateX(180deg);
- z-index: 0;
- }
-
- .animated-mail .letter {
- height: 180px;
- }
-
- .shadow {
- width: 250px;
- }
- }
-`
-const Envelope = ({ fade }: { fade: boolean }) => (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-)
+export const FadeContext = createContext(false)
-export function EasterEgg() {
+export function EasterEgg({ children, code }: { children?: React.ReactNode; code: string }) {
const [show, setShow] = useState(false)
const [fade, setFade] = useState(false)
useKonami({
@@ -241,13 +17,7 @@ export function EasterEgg() {
setFade(false)
}, 10000)
},
- sequence: 'storybook'.split(''),
+ sequence: code.split(''),
})
- return (
- show && (
-
-
-
- )
- )
+ return show && {children}
}
diff --git a/src/pages/EasterEgg/Envelope.tsx b/src/pages/EasterEgg/Envelope.tsx
new file mode 100644
index 00000000..dca0f799
--- /dev/null
+++ b/src/pages/EasterEgg/Envelope.tsx
@@ -0,0 +1,233 @@
+import { useContext } from 'react'
+import styled from 'styled-components'
+import { FadeContext } from './EasterEgg'
+
+const colors = {
+ primaryColor300: '#7ec1ff',
+ primaryColor400: '#1890ff',
+ primaryColor500: '#317fc8',
+ primaryColor600: '#136fc5',
+}
+
+const EnvelopeWrapper = styled.div`
+ .letter-image {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 200px;
+ height: 200px;
+ -webkit-transform: translate(-50%, -50%);
+ -moz-transform: translate(-50%, -50%);
+ transform: translate(-50%, -50%);
+ cursor: pointer;
+ direction: ltr;
+ z-index: 999;
+ opacity: 1;
+ animation: fadeIn 1s ease-in-out;
+
+ &.fade-out {
+ opacity: 0;
+ animation: fadeOut 1s ease-in-out;
+ }
+ }
+
+ @keyframes fadeIn {
+ 0% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+ }
+
+ @keyframes fadeOut {
+ 0% {
+ opacity: 1;
+ }
+ 100% {
+ opacity: 0;
+ }
+ }
+
+ .animated-mail {
+ position: absolute;
+ height: 150px;
+ width: 200px;
+ -webkit-transition: 0.4s;
+ -moz-transition: 0.4s;
+ transition: 0.4s;
+
+ .body {
+ position: absolute;
+ bottom: 0;
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-width: 0 0 100px 200px;
+ border-color: transparent transparent ${colors.primaryColor300} transparent;
+ z-index: 2;
+ }
+
+ .top-fold {
+ position: absolute;
+ top: 50px;
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-width: 50px 100px 0 100px;
+ -webkit-transform-origin: 50% 0%;
+ -webkit-transition: transform 0.4s 0.4s, z-index 0.2s 0.4s;
+ -moz-transform-origin: 50% 0%;
+ -moz-transition: transform 0.4s 0.4s, z-index 0.2s 0.4s;
+ transform-origin: 50% 0%;
+ transition: transform 0.4s 0.4s, z-index 0.2s 0.4s;
+ border-color: ${colors.primaryColor600} transparent transparent transparent;
+ z-index: 2;
+ }
+
+ .back-fold {
+ position: absolute;
+ bottom: 0;
+ width: 200px;
+ height: 100px;
+ background: ${colors.primaryColor600};
+ z-index: 0;
+ }
+
+ .left-fold {
+ position: absolute;
+ bottom: 0;
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-width: 50px 0 50px 100px;
+ border-color: transparent transparent transparent ${colors.primaryColor400};
+ z-index: 2;
+ }
+
+ .letter {
+ left: 20px;
+ bottom: 0px;
+ position: absolute;
+ width: 160px;
+ height: 60px;
+ background: white;
+ z-index: 1;
+ overflow: hidden;
+ -webkit-transition: 0.4s 0.2s;
+ -moz-transition: 0.4s 0.2s;
+ transition: 0.4s 0.2s;
+
+ .letter-border {
+ height: 10px;
+ width: 100%;
+ background: repeating-linear-gradient(
+ -45deg,
+ ${colors.primaryColor500},
+ ${colors.primaryColor500} 8px,
+ transparent 8px,
+ transparent 18px
+ );
+ }
+
+ .letter-title {
+ margin-top: 10px;
+ margin-left: 5px;
+ height: 10px;
+ width: 40%;
+ background: ${colors.primaryColor500};
+ }
+ .letter-context {
+ margin-top: 10px;
+ margin-left: 5px;
+ height: 10px;
+ width: 20%;
+ background: ${colors.primaryColor500};
+ }
+ .letter-favicon {
+ display: flex;
+ justify-content: center;
+ }
+ .letter-stamp {
+ margin-top: 30px;
+ margin-left: 120px;
+ border-radius: 100%;
+ height: 30px;
+ width: 30px;
+ background: ${colors.primaryColor500};
+ opacity: 0.3;
+ }
+ }
+ }
+
+ .shadow {
+ position: absolute;
+ top: 200px;
+ left: 50%;
+ width: 400px;
+ height: 30px;
+ transition: 0.4s;
+ transform: translateX(-50%);
+ -webkit-transition: 0.4s;
+ -webkit-transform: translateX(-50%);
+ -moz-transition: 0.4s;
+ -moz-transform: translateX(-50%);
+
+ border-radius: 100%;
+ background: radial-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0), rgba(0, 0, 0, 0));
+ }
+
+ .letter-image:hover {
+ .animated-mail {
+ transform: translateY(50px);
+ -webkit-transform: translateY(50px);
+ -moz-transform: translateY(50px);
+ }
+
+ .animated-mail .top-fold {
+ transition: transform 0.4s, z-index 0.2s;
+ transform: rotateX(180deg);
+ -webkit-transition: transform 0.4s, z-index 0.2s;
+ -webkit-transform: rotateX(180deg);
+ -moz-transition: transform 0.4s, z-index 0.2s;
+ -moz-transform: rotateX(180deg);
+ z-index: 0;
+ }
+
+ .animated-mail .letter {
+ height: 180px;
+ }
+
+ .shadow {
+ width: 250px;
+ }
+ }
+`
+
+export const Envelope = () => {
+ const fade = useContext(FadeContext)
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/pages/EasterEgg/LanguageToggle.tsx b/src/pages/EasterEgg/LanguageToggle.tsx
new file mode 100644
index 00000000..a629dc9b
--- /dev/null
+++ b/src/pages/EasterEgg/LanguageToggle.tsx
@@ -0,0 +1,24 @@
+import { useReducer } from 'react'
+import { useTranslation } from 'react-i18next'
+import { EasterEgg } from './EasterEgg'
+import { Button } from '@mui/material'
+
+export const LanguageToggle = () => {
+ const { t, i18n } = useTranslation()
+ const [, handleChangeLanguage] = useReducer((state: string) => {
+ const newLanguage = { he: 'en', en: 'he' }[state]
+ i18n.changeLanguage(newLanguage)
+ return newLanguage!
+ }, 'he')
+
+ return (
+
+
+
+ )
+}
diff --git a/src/pages/dashboard/WorstLinesChart/LineHbarChart/HbarChart/HbarChart.stories.tsx b/src/pages/dashboard/WorstLinesChart/LineHbarChart/HbarChart/HbarChart.stories.tsx
new file mode 100644
index 00000000..14d3a236
--- /dev/null
+++ b/src/pages/dashboard/WorstLinesChart/LineHbarChart/HbarChart/HbarChart.stories.tsx
@@ -0,0 +1,52 @@
+import type { Meta, StoryObj } from '@storybook/react'
+import { HbarChart, Entry } from './HbarChart'
+
+const meta = {
+ title: 'Components/MapLayers/HbarChart',
+ component: HbarChart,
+ tags: ['map', 'tooltip', 'autodocs'],
+} satisfies Meta
+
+export default meta
+
+type Story = StoryObj
+
+const operatorsPT: Entry[] = [
+ {
+ name: 'כפיר',
+ total: 1883,
+ actual: 0,
+ color: '#5840c0',
+ },
+ {
+ name: 'Unknown',
+ total: 2318,
+ actual: 0,
+ color: '#2a443e',
+ },
+ {
+ name: 'כרמלית',
+ total: 998,
+ actual: 0,
+ color: '#5cbcec',
+ },
+ {
+ name: 'כבל אקספרס',
+ total: 12824,
+ actual: 0,
+ color: '#0d2b58',
+ },
+ {
+ name: 'מועצה אזורית גולן',
+ total: 1992,
+ actual: 1506,
+ color: '#957476',
+ },
+]
+
+export const Chart: Story = {
+ args: {
+ entries: operatorsPT,
+ complement: false,
+ },
+}
diff --git a/src/pages/dashboard/WorstLinesChart/LineHbarChart/HbarChart/HbarChart.tsx b/src/pages/dashboard/WorstLinesChart/LineHbarChart/HbarChart/HbarChart.tsx
index 54e71a35..b283a22f 100644
--- a/src/pages/dashboard/WorstLinesChart/LineHbarChart/HbarChart/HbarChart.tsx
+++ b/src/pages/dashboard/WorstLinesChart/LineHbarChart/HbarChart/HbarChart.tsx
@@ -3,7 +3,7 @@ import { TEXTS } from 'src/resources/texts'
import './HbarChart.scss'
import { Tooltip } from '@mui/material'
-type Entry = { name: string; total: number; actual: number; color?: string }
+export type Entry = { name: string; total: number; actual: number; color?: string }
const numberFormatter = new Intl.NumberFormat('he-IL')
export function HbarChart({