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 }) => ( - -
-
-
-
-
-
-
-
- busFavicon -
-
-
-
-
-
-
-
-
-
-
-
-) +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 ( + +
+
+
+
+
+
+
+
+ busFavicon +
+
+
+
+
+
+
+
+
+
+
+
+ ) +} 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 ( + + + + ) +}