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/App.tsx b/src/App.tsx
index 26bbb1fa..ad28d96a 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -18,10 +18,10 @@ import { heIL as heILmui } from '@mui/x-date-pickers/locales'
import { ThemeProvider, createTheme } from '@mui/material'
import { AdapterMoment } from '@mui/x-date-pickers/AdapterMoment'
import { LocalizationProvider } from '@mui/x-date-pickers'
-
-import { usePages } from './routes'
import { EasterEgg } from './pages/EasterEgg/EasterEgg'
+import { usePages } from './routes'
import MainLayout from './layout'
+import { Envelope } from './pages/EasterEgg/Envelope'
const theme = createTheme(
{
@@ -105,7 +105,11 @@ const App = () => {
const RoutedApp = () => (
-
+
+
+
+
+
)
export default RoutedApp
diff --git a/src/layout/sidebar/menu/Menu.tsx b/src/layout/sidebar/menu/Menu.tsx
index 83f28f34..d55ce3a7 100644
--- a/src/layout/sidebar/menu/Menu.tsx
+++ b/src/layout/sidebar/menu/Menu.tsx
@@ -6,6 +6,7 @@ import { usePages } 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,18 +24,12 @@ function getItem(
}
const MainMenu = () => {
- const { t, i18n } = useTranslation()
+ const { t } = useTranslation()
const pages = usePages()
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,
@@ -60,7 +55,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 4f1ed3c1..70a96954 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 (
+
+
+
+ )
+}