24 |
{question}
25 |
26 |
27 | {answer.length > textLength && shouldAnswerTruncate ? (
28 | See more')
35 | }}
36 | />
37 | ) : (
38 |
43 | )}
44 |
45 |
46 |
47 |
48 | );
49 | };
50 |
51 | export default FaqItem;
52 |
--------------------------------------------------------------------------------
/components/Faq/FaqItem/FaqItem.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../../styles/partials/colors';
2 |
3 | .FaqItem {
4 | &__question {
5 | color: $primary-color;
6 | font-size: 1.15rem;
7 | line-height: 28px;
8 | margin-bottom: 6px;
9 | }
10 | &__answer {
11 | display: block;
12 | }
13 | }
14 |
15 | .seeMore {
16 | color: $grey-color;
17 | }
--------------------------------------------------------------------------------
/components/Faq/FaqList/FaqList.js:
--------------------------------------------------------------------------------
1 | // Wrapper component for FAQ List
2 | import FaqItem from '../FaqItem/FaqItem';
3 | import './FaqList.scss';
4 |
5 | const FaqList = ({ faqs = [] }) => {
6 | return (
7 |
8 | {faqs.map((faq, index) => (
9 |
10 | ))}
11 |
12 | );
13 | };
14 |
15 | export default FaqList;
16 |
--------------------------------------------------------------------------------
/components/Faq/FaqList/FaqList.scss:
--------------------------------------------------------------------------------
1 | .FaqList {
2 | list-style: none;
3 | margin: 0;
4 | padding: 0;
5 | }
--------------------------------------------------------------------------------
/components/Layout/AppHeader/AppHeader.js:
--------------------------------------------------------------------------------
1 | import './AppHeader.module.scss';
2 |
3 | const AppHeader = props => {
4 | return (
5 |
6 | {props.children}
7 |
8 | );
9 | };
10 |
11 | export default AppHeader;
12 |
--------------------------------------------------------------------------------
/components/Layout/AppHeader/AppHeader.module.scss:
--------------------------------------------------------------------------------
1 | .AppHeader {
2 | height: 54px;
3 | display: flex;
4 | align-items: center;
5 | justify-content: space-between;
6 |
7 | z-index: 100;
8 | }
--------------------------------------------------------------------------------
/components/Layout/Layout.js:
--------------------------------------------------------------------------------
1 | import './Layout.module.scss';
2 | import Navigation from '../Navigation/Navigation';
3 |
4 | const Layout = props => {
5 | const { children, shouldHideBottomNav = false, ...other } = props;
6 |
7 | return (
8 |
9 | {/*
10 |

11 |
*/}
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | {children}
20 |
21 |
22 |
23 |
24 | {!shouldHideBottomNav && (
25 |
26 |
27 |
28 | )}
29 |
51 |
52 | );
53 | };
54 |
55 | export default Layout;
56 |
--------------------------------------------------------------------------------
/components/Layout/Layout.module.scss:
--------------------------------------------------------------------------------
1 | .Layout {
2 | &__wrapper {
3 | position: relative;
4 | padding-bottom: 80px;
5 | }
6 | }
7 |
8 | @media (min-width: 1200px) {
9 | .Layout {
10 | &__desktopLogo {
11 | img {
12 | display: block;
13 | margin: 0 auto;
14 | width: auto;
15 | height: 52px;
16 | }
17 | }
18 | &__wrapper {
19 | padding-bottom: 10px;
20 | }
21 | &__sideNav {
22 | position: relative;
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/components/Location/TownshipModal.js:
--------------------------------------------------------------------------------
1 | // TODO: Refactor this component to be able to use as a reusable collapse in the future
2 | import { useEffect, useState } from 'react';
3 | import { AiOutlineLoading } from 'react-icons/ai';
4 | import Collapsible from 'react-collapsible';
5 | import Modal from '../Common/Modal/Modal';
6 |
7 | import './locationCollapse.scss';
8 | import useAPI from '../../hooks/useAPI';
9 |
10 | const TownshipModal = props => {
11 | const { isModalOpen, setModalOpen, onClickTownship } = props;
12 | const [stateRegions, setStateRegions] = useState([]);
13 | const [townships, setTownships] = useState([]);
14 | const [, fetchData] = useAPI();
15 |
16 | async function fetchStateRegions() {
17 | const response = await fetchData('/api/locations', {
18 | type: 'state_regions'
19 | });
20 |
21 | setStateRegions(response.data);
22 | }
23 |
24 | useEffect(() => {
25 | if (isModalOpen) {
26 | fetchStateRegions();
27 | }
28 | }, [isModalOpen]);
29 |
30 | async function fetchTownships(stateRegion) {
31 | // Use cached values if it is already loaded
32 | const townshipsLoaded =
33 | townships.findIndex(({ stateRegion: sr }) => sr === stateRegion) > -1;
34 |
35 | if (townshipsLoaded) return;
36 |
37 | const { data } = await fetchData('/api/locations', {
38 | type: 'townships',
39 | state_region: stateRegion
40 | });
41 |
42 | const clonedTownships = [...townships];
43 |
44 | clonedTownships.push({
45 | stateRegion,
46 | townships: data
47 | });
48 |
49 | setTownships(clonedTownships);
50 | }
51 |
52 | function renderTownships(stateRegion) {
53 | const containedTownships = townships.find(
54 | ({ stateRegion: sr }) => sr === stateRegion
55 | );
56 |
57 | if (containedTownships) {
58 | return containedTownships.townships.map(township => (
59 |
onClickTownship(stateRegion, township)}
63 | >
64 | {township}
65 |
66 | ));
67 | }
68 | return
;
69 | }
70 |
71 | return (
72 |
73 |
setModalOpen(false)}
77 | >
78 | မြို့နယ်ရွေးပါ
79 | {stateRegions.map((stateRegion, srIndex) => (
80 | fetchTownships(stateRegion)}
85 | >
86 | {renderTownships(stateRegion)}
87 |
88 | ))}
89 |
90 |
91 | );
92 | };
93 |
94 | export default TownshipModal;
95 |
--------------------------------------------------------------------------------
/components/Location/WardVillageModal.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import Modal from '../Common/Modal/Modal';
3 | import useAPI from '../../hooks/useAPI';
4 |
5 | const WardVillageModal = props => {
6 | const {
7 | isModalOpen,
8 | setModalOpen,
9 | stateRegion,
10 | township,
11 | onClickWardVillage
12 | } = props;
13 |
14 | const [wardVillages, setWardVillages] = useState([]);
15 | const [, fetchData] = useAPI();
16 |
17 | async function fetchWardVillage() {
18 | const { data } = await fetchData('/api/locations', {
19 | type: 'wards',
20 | state_region: stateRegion,
21 | township
22 | });
23 |
24 | setWardVillages(data);
25 | }
26 |
27 | useEffect(() => {
28 | if (stateRegion && township) {
29 | fetchWardVillage();
30 | }
31 | }, [stateRegion, township]);
32 |
33 | return (
34 |
setModalOpen(false)}
38 | >
39 | ရပ်ကွက်/ကျေးရွာအုပ်စု ရွေးပါ
40 | {township}
41 | {wardVillages.map(ward => (
42 | onClickWardVillage(ward)}
46 | >
47 | {ward}
48 |
49 | ))}
50 |
51 | );
52 | };
53 |
54 | export default WardVillageModal;
55 |
--------------------------------------------------------------------------------
/components/Location/locationCollapse.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/partials/colors';
2 |
3 | %header {
4 | color: $primary-color;
5 | font-weight: 500;
6 | }
7 |
8 | .location-ward-village-header {
9 | @extend %header;
10 | margin: 12px 0;
11 | }
12 |
13 | .location-child {
14 | padding: 5px 15px;
15 | margin: 5px 0;
16 | }
17 |
18 | .Collapsible {
19 | margin: 12px 0;
20 | cursor: pointer;
21 | }
22 |
23 | .Collapsible__trigger {
24 | @extend %header;
25 | }
26 |
27 | .location-modal {
28 | position: absolute;
29 | top: 40px;
30 | left: 40px;
31 | right: 40px;
32 | bottom: 40px;
33 | border: 1px solid rgb(204, 204, 204);
34 | background: rgb(255, 255, 255);
35 | overflow: auto;
36 | border-radius: 4px;
37 | outline: none;
38 | padding: 20px;
39 | }
40 |
41 | @media (min-width: 1200px) {
42 | .location-modal {
43 | top: 50%;
44 | left: 50%;
45 | transform: translateX(-50%) translateY(-50%);
46 | max-width: 380px;
47 | max-height: 80%;
48 | }
49 | }
--------------------------------------------------------------------------------
/components/Navigation/Navigation.js:
--------------------------------------------------------------------------------
1 | import { useRouter } from 'next/router';
2 |
3 | import Link from '../Common/Link/Link';
4 | import PeopleIcon from '../Common/Icons/people';
5 | import FlagIcon from '../Common/Icons/flag';
6 | import CheckboxIcon from '../Common/Icons/checkbox';
7 | import LightBulbIcon from '../Common/Icons/lightbulb';
8 | import NewsIcon from '../Common/Icons/news';
9 | import ActivePeopleIcon from '../Common/Icons/activePeople';
10 | import ActiveFlagIcon from '../Common/Icons/activeFlag';
11 | import ActiveLightBulbIcon from '../Common/Icons/activeLightBulb';
12 | import ActiveCheckboxIcon from '../Common/Icons/activeCheckbox';
13 | import ActiveNewsIcon from '../Common/Icons/activeNews';
14 |
15 | import './Navigation.module.scss';
16 |
17 | const Navigation = () => {
18 | const router = useRouter();
19 | const currentPath = router.pathname;
20 |
21 | const NavComponent = ({ link, activeIcon, inActiveIcon, text }) => {
22 | const isSamePath =
23 | currentPath.split('/').indexOf(link.replace('/', '')) > -1;
24 |
25 | if (isSamePath) {
26 | return (
27 |
28 | {activeIcon}
29 | {text}
30 |
31 | );
32 | }
33 | return (
34 |
35 | {inActiveIcon}
36 | {text}
37 |
38 | );
39 | };
40 |
41 | return (
42 |
109 | );
110 | };
111 |
112 | export default Navigation;
113 |
--------------------------------------------------------------------------------
/components/Navigation/Navigation.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/partials/colors';
2 |
3 | .Navigation {
4 | font-size: 0.85rem;
5 | padding-top: 12px;
6 | padding-bottom: 12px;
7 | padding-left: 5px;
8 | padding-right: 5px;
9 | width: 100%;
10 | position: fixed;
11 | left: 0;
12 | bottom: 0;
13 | border-top: 1px solid rgba($color: #000000, $alpha: 0.1);
14 | background: #ffffff;
15 | &__mVoterLogo {
16 | display: none;
17 | }
18 | ul {
19 | display: flex;
20 | justify-content: space-around;
21 | align-items: center;
22 | list-style: none;
23 | margin: 0;
24 | padding: 0;
25 | }
26 | li {
27 | position: relative;
28 | width: 100%;
29 | height: 48px;
30 | color: #999999;
31 | margin: 0;
32 | padding: 0;
33 | font-size: .8rem;
34 | text-align: center;
35 | &:hover {
36 | cursor: pointer;
37 | }
38 | .active {
39 | color: $primary-color;
40 | }
41 | .text {
42 | position: absolute;
43 | bottom: 0;
44 | left: 50%;
45 | transform: translateX(-50%);
46 | width: 100%;
47 | }
48 | }
49 | }
50 |
51 | @media (min-width: 1200px) {
52 | %linkDivRules {
53 | width: 100%;
54 | display: flex;
55 | height: auto;
56 | svg {
57 | width: 30px;
58 | }
59 | }
60 | .Navigation {
61 | width: 255px;
62 | display: flex;
63 | align-content: center;
64 | margin: 0 auto;
65 | height: 100%;
66 | border-top: none;
67 | padding-top: 2px;
68 | left: unset;
69 | &__mVoterLogo {
70 | display: block;
71 | img {
72 | display: block;
73 | margin: 0 auto;
74 | width: auto;
75 | height: 64px;
76 | }
77 | }
78 | ul {
79 | display: inline-block;
80 | // position: fixed;
81 | margin: 0 auto;
82 | }
83 | li {
84 | width: 180px;
85 | text-align: left;
86 | display: flex;
87 | align-items: center;
88 | transition: color 0.2s linear;
89 | div.active {
90 | width: 100%;
91 | display: flex;
92 | padding: 10px;
93 | height: auto;
94 | background-color: $primary-color;
95 | border-radius: 5px;
96 | svg {
97 | g {
98 | fill: #ffffff;
99 | }
100 | }
101 | }
102 | span.active {
103 | color: #ffffff;
104 | }
105 | > div {
106 | @extend %linkDivRules;
107 | }
108 | div.inactive {
109 | width: 100%;
110 | display: flex;
111 | padding: 10px;
112 | &:hover {
113 | color: rgba($color: $primary-color, $alpha: 0.85);
114 | g {
115 | fill: rgba($color: $primary-color, $alpha: 0.85);
116 | }
117 | }
118 | }
119 | .text {
120 | position: relative;
121 | display: inline-block;
122 | font-size: 1rem;
123 | }
124 | }
125 | }
126 | }
--------------------------------------------------------------------------------
/components/News/NewsItem/NewsItem.js:
--------------------------------------------------------------------------------
1 | import { formatPublishDateToMMLocale } from '../../../utils/textFormatter';
2 | import './NewsItem.scss';
3 |
4 | const Article = props => {
5 | const {
6 | news: {
7 | attributes: { title, summary, published_date: publishedDate, url }
8 | }
9 | } = props;
10 |
11 | return (
12 |
18 |
19 |
20 |
{title}
21 |
{summary} ...
22 |
23 | {formatPublishDateToMMLocale(publishedDate)}
24 |
25 |
26 |
27 |

28 |
29 |
30 |
31 | );
32 | };
33 |
34 | export default Article;
35 |
--------------------------------------------------------------------------------
/components/News/NewsItem/NewsItem.scss:
--------------------------------------------------------------------------------
1 | @import '../../../styles/partials/fontsizes';
2 | @import '../../../styles/partials/colors';
3 |
4 | .Article {
5 | width: 100%;
6 | display: flex;
7 | justify-content: space-between;
8 | h1 {
9 | line-height: 28px;
10 | }
11 | &__heading {
12 | font-weight: 700;
13 | font-size: $medium;
14 | }
15 | &__date {
16 | color: $grey-color;
17 | }
18 | &__newsGroup {
19 | margin-right: 10px;
20 | }
21 | &__summary {
22 | margin-bottom: 5px;
23 | }
24 | &__UECLogo {
25 | >img {
26 | width: auto;
27 | height: 65px;
28 | }
29 | }
30 | }
31 |
32 | .article-link {
33 | text-decoration: none;
34 | color: inherit;
35 | }
36 |
--------------------------------------------------------------------------------
/components/News/NewsList/NewsList.js:
--------------------------------------------------------------------------------
1 | import NewsItem from '../NewsItem/NewsItem';
2 |
3 | import './NewsList.scss';
4 |
5 | const NewsList = ({ news = [] }) => {
6 | return (
7 |
8 | {news.map(newsItem => (
9 |
10 | ))}
11 |
12 | );
13 | };
14 |
15 | export default NewsList;
16 |
--------------------------------------------------------------------------------
/components/News/NewsList/NewsList.scss:
--------------------------------------------------------------------------------
1 | .NewsList {
2 | list-style: none;
3 | margin: 0;
4 | padding: 0;
5 | }
--------------------------------------------------------------------------------
/components/Parties/PartyItem/PartyItem.js:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 | import Card from '../../Common/Card/Card';
3 |
4 | import './PartyItem.module.scss';
5 |
6 | const PartyCard = props => {
7 | const {
8 | party: {
9 | id,
10 | attributes: { seal_image: sealImage, name_burmese: nameBurmese, region }
11 | }
12 | } = props;
13 |
14 | const sealImageStyle = {
15 | width: 64,
16 | height: 64,
17 | backgroundImage: `url("${sealImage}")`
18 | };
19 |
20 | return (
21 |
34 | );
35 | };
36 |
37 | export default PartyCard;
38 |
--------------------------------------------------------------------------------
/components/Parties/PartyItem/PartyItem.module.scss:
--------------------------------------------------------------------------------
1 | .PartyItem {
2 | width: 100%;
3 | &__Card {
4 | display: flex;
5 | align-items: center;
6 | }
7 | &__image {
8 | background-position: center center;
9 | background-repeat: no-repeat;
10 | background-size: contain;
11 | }
12 | &__Description {
13 | margin-left: 10px;
14 | .name {
15 | margin-bottom: 5px;
16 | }
17 | }
18 | }
19 |
20 | .constituency {
21 | font-size: 0.9rem;
22 | color: rgba($color: #000000, $alpha: 0.5);
23 | }
24 |
25 | @media (min-width: 1200px) {
26 | .PartyItem {
27 | &__Card {
28 | padding: 0 15px;
29 | border: 1px solid rgba($color: #000000, $alpha: 0.1);
30 | }
31 | &__Description {
32 | .name {
33 | line-height: 25px;
34 | margin-bottom: 0;
35 | }
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/components/Parties/PartyList/PartyList.js:
--------------------------------------------------------------------------------
1 | import PartyItem from '../PartyItem/PartyItem';
2 | import './PartyList.scss';
3 |
4 | const PartyList = ({ parties = [] }) => {
5 | return (
6 |
7 |
8 | {parties.map((party, index) => (
9 | -
10 |
11 |
12 | ))}
13 |
14 |
15 | );
16 | };
17 |
18 | export default PartyList;
19 |
--------------------------------------------------------------------------------
/components/Parties/PartyList/PartyList.scss:
--------------------------------------------------------------------------------
1 | .PartyList {
2 | list-style: none;
3 | margin: 0;
4 | padding: 0;
5 | overflow: hidden;
6 | }
7 |
8 | @media (min-width: 1200px) {
9 | .PartyList {
10 | &__itemWrapper {
11 | padding-top: 5px;
12 | &:nth-of-type(2n+1) {
13 | padding-right: 5px;
14 | }
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/components/Search/SearchPage.js:
--------------------------------------------------------------------------------
1 | import ReactGA from 'react-ga';
2 | import { useRouter } from 'next/router';
3 | import InfiniteScroll from 'react-infinite-scroll-component';
4 | import { AiOutlineLoading } from 'react-icons/ai';
5 | import React, { useRef, useCallback, useState, useEffect } from 'react';
6 | import Head from 'next/head';
7 | import Layout from '../Layout/Layout';
8 | import Button from '../Common/Button/Button';
9 | import AppHeader from '../Layout/AppHeader/AppHeader';
10 | import { debounce } from '../../utils/helpers';
11 |
12 | import './SearchPage.scss';
13 | import useAPI from '../../hooks/useAPI';
14 |
15 | const SearchPage = props => {
16 | const {
17 | type = 'candidates',
18 | endpoint,
19 | children,
20 | inputPlaceholder = 'ရှာဖွေလိုသော အမည်ကို ရိုက်ထည့်ပါ',
21 | emptyPlaceholder = '',
22 | notFoundPlaceholder = ''
23 | } = props;
24 |
25 | const router = useRouter();
26 | const [list, setList] = useState(null);
27 | const [page, setPage] = useState(1);
28 | const [searchString, setSearchString] = useState('');
29 | const [hasMore, setHasMore] = useState(true);
30 | const [loading, fetchData] = useAPI();
31 | const searchInputRef = useRef(null);
32 |
33 | useEffect(() => {
34 | ReactGA.pageview(window.location.pathname);
35 | searchInputRef.current.focus();
36 | }, []);
37 |
38 | async function apiCall(value) {
39 | const arr = list ?? [];
40 |
41 | if (page === 1) {
42 | setList(null);
43 | }
44 |
45 | const { data } = await fetchData(`/api/${endpoint}`, {
46 | page,
47 | query: value || searchString,
48 | item_per_page: 25
49 | });
50 |
51 | if (data.length === 0) {
52 | setHasMore(false);
53 | } else {
54 | setHasMore(true);
55 | }
56 |
57 | setPage(page + 1);
58 |
59 | return setList(arr.concat(data));
60 | }
61 |
62 | const debouncedCall = useCallback(
63 | debounce(value => apiCall(value), 500),
64 | []
65 | );
66 |
67 | function loadMoreData() {
68 | apiCall();
69 | }
70 |
71 | function onChangeSearch(e) {
72 | const {
73 | target: { value }
74 | } = e;
75 | setSearchString(value);
76 | debouncedCall(value);
77 | }
78 |
79 | return (
80 |
81 |
82 | mVoter 2020
83 |
84 |
85 |
90 |
91 | search
92 |
100 |
101 |
102 |
103 |
104 |
105 | {!list && !loading && (
106 |
107 | {emptyPlaceholder}
108 |
109 | )}
110 | {list && list.length === 0 && !loading && (
111 |
112 | {notFoundPlaceholder}
113 |
114 | )}
115 | {loading && (
116 |
117 | )}
118 |
123 | {React.Children.map(children, child =>
124 | React.cloneElement(child, { [type]: list ?? [] })
125 | )}
126 |
127 |
128 |
129 |
130 |
131 | );
132 | };
133 |
134 | export default SearchPage;
135 |
--------------------------------------------------------------------------------
/components/Search/SearchPage.scss:
--------------------------------------------------------------------------------
1 | .search-input-group {
2 | width: 90%;
3 | display: flex;
4 | align-items: center;
5 | > i {
6 | color: rgba($color: #000000, $alpha: 0.5);
7 | display: inline-block;
8 | margin-right: 5px;
9 | }
10 | }
11 |
12 | .search-input {
13 | border-top: 0;
14 | border-left: 0;
15 | border-right: 0;
16 | float: right;
17 | border-bottom: 1px solid rgba($color: #000000, $alpha: 0.25);
18 | outline: 0;
19 | width: 100%;
20 | padding-bottom: 5px;
21 | }
22 |
23 | .search-page-loader {
24 | font-size: 2rem;
25 | display: block;
26 | margin: 0 auto;
27 | }
28 |
--------------------------------------------------------------------------------
/context/AuthProvider.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import nookies, { parseCookies } from 'nookies';
3 | import { createContext, useContext, useEffect, useState } from 'react';
4 |
5 | const AuthContext = createContext({
6 | token: null,
7 | updateToken: () => {}
8 | });
9 |
10 | export function useAuthContext() {
11 | return useContext(AuthContext);
12 | }
13 |
14 | const AuthProvider = props => {
15 | const { children } = props;
16 |
17 | const [token, setToken] = useState(null);
18 |
19 | useEffect(() => {
20 | // TODO: Handle invalid logic
21 | async function fetchToken() {
22 | // Check token first
23 | const cookies = parseCookies();
24 |
25 | if (cookies.token) {
26 | setToken(cookies.token);
27 | return;
28 | }
29 |
30 | const {
31 | data: { token: apiToken }
32 | } = await axios.get('/api/auth', {
33 | method: 'POST'
34 | });
35 |
36 | nookies.set(null, 'token', apiToken, {
37 | path: '/'
38 | });
39 |
40 | if (apiToken) {
41 | setToken(apiToken);
42 | }
43 | }
44 |
45 | fetchToken();
46 | }, []);
47 |
48 | function updateToken(apiToken) {
49 | nookies.set(null, 'token', apiToken, {
50 | path: '/'
51 | });
52 | setToken(apiToken);
53 | }
54 |
55 | return (
56 |
57 | {children}
58 |
59 | );
60 | };
61 |
62 | export default AuthProvider;
63 |
--------------------------------------------------------------------------------
/gateway/api.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | import axios from 'axios';
3 | import { fetchToken } from '../pages/api/auth';
4 |
5 | class MaePaySohAPI {
6 | constructor(token) {
7 | const axiosInstance = axios.create({
8 | baseURL: process.env.BASE_URL,
9 | timeout: 10000
10 | });
11 |
12 | axiosInstance.interceptors.request.use(
13 | async config => {
14 | if (token) {
15 | // eslint-disable-next-line no-param-reassign
16 | config.headers['api-token'] = token;
17 | }
18 |
19 | return config;
20 | },
21 | error => {
22 | return Promise.reject(error);
23 | }
24 | );
25 |
26 | axiosInstance.interceptors.response.use(
27 | response => {
28 | return response;
29 | },
30 | // eslint-disable-next-line func-names
31 | async function(error) {
32 | if (error.response.status !== 401) {
33 | return Promise.reject(error);
34 | }
35 |
36 | const { config } = error;
37 |
38 | if (error.response.status === 401 && !error.response.retry) {
39 | // Token key not authorized for use
40 | const apiToken = await fetchToken(); // This is signed
41 | config.headers['api-token'] = apiToken;
42 |
43 | const response = await axios.request(config);
44 |
45 | response.retry = true;
46 | response.data.token = apiToken;
47 |
48 | return Promise.resolve(response);
49 | }
50 | return Promise.reject(error);
51 | }
52 | );
53 |
54 | this.api = axiosInstance;
55 | }
56 |
57 | getConstituencies(query) {
58 | return this.api.get('/constituencies', {
59 | params: {
60 | query
61 | }
62 | });
63 | }
64 |
65 | getCandidateList(constituencyId) {
66 | return this.api
67 | .get('/candidates', {
68 | params: {
69 | constituency_id: constituencyId
70 | }
71 | })
72 | .catch(console.error);
73 | }
74 |
75 | searchCandidates({ query, page = 1, item_per_page }) {
76 | return this.api
77 | .get('/candidates', {
78 | params: {
79 | query,
80 | page,
81 | item_per_page
82 | }
83 | })
84 | .catch(console.error);
85 | }
86 |
87 | getCandidateById(id) {
88 | return this.api.get(`/candidates/${id}`).catch(console.error);
89 | }
90 |
91 | getFaqs({ page, name, category }) {
92 | return this.api
93 | .get('/faqs', {
94 | params: {
95 | page,
96 | name,
97 | category
98 | }
99 | })
100 | .catch(console.error);
101 | }
102 |
103 | getFaqById(id) {
104 | return this.api.get(`/faqs/${id}`).catch(console.error);
105 | }
106 |
107 | searchFaqs({ query, page = 1 }) {
108 | return this.api.get('/faqs', {
109 | params: {
110 | query,
111 | page
112 | }
113 | });
114 | }
115 |
116 | getStateRegions() {
117 | return this.api.get('/locality/state_regions').catch(console.error);
118 | }
119 |
120 | getTownships(stateRegion) {
121 | return this.api
122 | .get('/locality/townships', {
123 | params: {
124 | state_region: stateRegion
125 | }
126 | })
127 | .catch(console.error);
128 | }
129 |
130 | getWards(stateRegion, township) {
131 | return this.api
132 | .get('/locality/wards', {
133 | params: {
134 | state_region: stateRegion,
135 | township
136 | }
137 | })
138 | .catch(console.error);
139 | }
140 |
141 | getWardDetails(stateRegion, township, ward) {
142 | return this.api
143 | .get('/locality/details', {
144 | params: {
145 | state_region: stateRegion,
146 | township,
147 | ward
148 | }
149 | })
150 | .catch(console.error);
151 | }
152 |
153 | getNews({ page, itemPerPage = 10 }) {
154 | return this.api
155 | .get('/news', {
156 | params: {
157 | page,
158 | item_per_page: itemPerPage
159 | }
160 | })
161 | .catch(console.error);
162 | }
163 |
164 | searchNews({ query, page = 1 }) {
165 | return this.api.get('/news', {
166 | params: {
167 | query,
168 | page
169 | }
170 | });
171 | }
172 |
173 | getParties({ page, item_per_page }) {
174 | return this.api.get('/parties', {
175 | params: {
176 | page,
177 | item_per_page
178 | }
179 | });
180 | }
181 |
182 | getPartyById(id) {
183 | return this.api.get(`/parties/${id}`).catch(console.error);
184 | }
185 |
186 | searchParties({ query, page = 1, item_per_page }) {
187 | return this.api.get('/parties', {
188 | params: {
189 | query,
190 | page,
191 | item_per_page
192 | }
193 | });
194 | }
195 |
196 | getBallots(category = 'normal') {
197 | return this.api
198 | .get('ballots', {
199 | params: {
200 | category
201 | }
202 | })
203 | .catch(console.error);
204 | }
205 | }
206 |
207 | module.exports = MaePaySohAPI;
208 |
--------------------------------------------------------------------------------
/hooks/useAPI.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { useState } from 'react';
3 | import { useAuthContext } from '../context/AuthProvider';
4 |
5 | function useAPI() {
6 | const { updateToken } = useAuthContext();
7 | const [loading, setLoading] = useState(false);
8 |
9 | async function fetchData(url, query) {
10 | try {
11 | setLoading(true);
12 |
13 | const { data } = await axios.get(url, {
14 | params: { ...query },
15 | withCredentials: true
16 | });
17 |
18 | // This is where we handle custom token
19 | if (data.token) {
20 | updateToken(data.token);
21 | }
22 |
23 | setLoading(false);
24 | return data;
25 | } catch (error) {
26 | setLoading(false);
27 | return error;
28 | }
29 | }
30 |
31 | return [loading, fetchData];
32 | }
33 |
34 | export default useAPI;
35 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | const withSass = require('@zeit/next-sass');
2 | const withCSS = require('@zeit/next-css');
3 |
4 | module.exports = {
5 | env: {
6 | GA_TRACKING_ID: process.env.GA_TRACKING_ID
7 | },
8 | devIndicators: {
9 | autoPrerender: false
10 | },
11 | ...withCSS(withSass())
12 | };
13 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mvoter-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "NODE_ENV=development next dev -p 3009",
7 | "build": "rm -rf ./build && NODE_ENV=production next build",
8 | "start": "next start -p 8080",
9 | "deploy": "eslint . && npm run build && gcloud app deploy --silent",
10 | "test": "jest --watch",
11 | "lint": "eslint .",
12 | "lint:fix": "eslint . --fix"
13 | },
14 | "dependencies": {
15 | "@zeit/next-css": "^1.0.1",
16 | "@zeit/next-sass": "^1.0.1",
17 | "axios": "^0.19.2",
18 | "bootstrap": "^4.5.1",
19 | "express": "^4.17.1",
20 | "html-to-text": "^5.1.1",
21 | "moment": "^2.27.0",
22 | "myanmar-numbers": "^2.1.3",
23 | "next": "9.3.4",
24 | "node-gyp": "^7.0.0",
25 | "node-sass": "^4.14.1",
26 | "nookies": "^2.4.0",
27 | "normalize.css": "^8.0.1",
28 | "react": "16.13.1",
29 | "react-collapsible": "^2.8.0",
30 | "react-dom": "16.13.1",
31 | "react-ga": "^3.1.2",
32 | "react-icons": "^3.10.0",
33 | "react-infinite-scroll-component": "^5.0.5",
34 | "react-modal": "^3.11.2",
35 | "react-select": "^3.1.0",
36 | "react-slick": "^0.27.10",
37 | "react-transition-group": "^4.4.1",
38 | "slick-carousel": "^1.8.1",
39 | "uuid": "^8.3.0"
40 | },
41 | "devDependencies": {
42 | "babel-eslint": "^9.0.0",
43 | "babel-jest": "^26.3.0",
44 | "eslint": "^6.8.0",
45 | "eslint-config-airbnb": "^18.2.0",
46 | "eslint-config-nextjs": "^1.0.6",
47 | "eslint-config-prettier": "^4.3.0",
48 | "eslint-config-standard": "^14.1.1",
49 | "eslint-plugin-html": "^5.0.5",
50 | "eslint-plugin-import": "^2.22.1",
51 | "eslint-plugin-jsx-a11y": "^6.3.1",
52 | "eslint-plugin-node": "^11.1.0",
53 | "eslint-plugin-prettier": "^3.1.4",
54 | "eslint-plugin-promise": "^4.2.1",
55 | "eslint-plugin-react": "^7.21.3",
56 | "eslint-plugin-react-hooks": "^1.7.0",
57 | "eslint-plugin-standard": "^4.0.1",
58 | "jest": "^26.4.2",
59 | "prettier": "^1.19.1"
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/pages/_app.js:
--------------------------------------------------------------------------------
1 | import ReactGA from 'react-ga';
2 | import Head from 'next/head';
3 | import AuthProvider from '../context/AuthProvider';
4 | import 'bootstrap/dist/css/bootstrap-reboot.css';
5 | import 'bootstrap/dist/css/bootstrap-grid.css';
6 | import '../styles/base.scss';
7 | import '../styles/helpers.scss';
8 |
9 | ReactGA.initialize(process.env.GA_TRACKING_ID);
10 |
11 | function mVoterApp({ Component, pageProps }) {
12 | return (
13 | <>
14 |
15 |
20 |
26 |
32 |
33 |
34 |
35 |
36 |
37 |
41 |
42 |
43 |
44 |
45 | >
46 | );
47 | }
48 |
49 | export default mVoterApp;
50 |
--------------------------------------------------------------------------------
/pages/about/about.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/partials/colors';
2 |
3 | .About {
4 | h1, p {
5 | margin: 0;
6 | }
7 | p {
8 | line-height: 1.5rem;
9 | }
10 | .mVoterLogo {
11 | width: auto;
12 | height: 150px;
13 | }
14 | .PopStack {
15 | width: 150px;
16 | height: auto;
17 | display: block;
18 | margin-top: 10px;
19 | margin-left: auto;
20 | margin-right: auto;
21 | }
22 | img {
23 | width: 100%;
24 | height: auto;
25 | }
26 | img.large-logo {
27 | width: 100%;
28 | height: auto;
29 | }
30 | .suggestive-links {
31 | margin-top: 10px;
32 | margin-bottom: 5px;
33 | i {
34 | display: inline-block;
35 | margin-left: 8px;
36 | margin-right: 8px;
37 | padding: 8px;
38 | font-size: 1.35rem;
39 | color: white;
40 | background-color: $primary-color;
41 | border-radius: 100%;
42 | }
43 | }
44 | }
45 |
46 | @media (min-width: 1200px) {
47 | .About {
48 | max-width: 85%;
49 | .PopStack {
50 | width: auto;
51 | height: 180px;
52 | }
53 | img.large-logo {
54 | width: auto;
55 | height: 170px;
56 | }
57 | img.step-logo {
58 | height: 130px;
59 | }
60 | img.eu-logo {
61 | height: 120px;
62 | }
63 | }
64 | }
--------------------------------------------------------------------------------
/pages/about/index.js:
--------------------------------------------------------------------------------
1 | import { useRouter } from 'next/router';
2 | import Head from 'next/head';
3 | import Layout from '../../components/Layout/Layout';
4 | import AppHeader from '../../components/Layout/AppHeader/AppHeader';
5 |
6 | import './about.module.scss';
7 | import Button from '../../components/Common/Button/Button';
8 |
9 | const About = () => {
10 | const router = useRouter();
11 |
12 | return (
13 |
14 |
15 | About | mVoter 2020
16 |
17 |
18 |
23 |
24 |
25 |
26 |
27 |

32 |
mVoter
33 |
34 | Award-winning voter education app for Myanmar elections since
35 | 2015.
36 |
37 |
38 |
Proudly and voluntarily presented by
39 |
40 |
41 |

46 |
47 |
48 | Supported by
49 |
50 |
51 |
52 |

53 |
54 |
55 |
66 |
67 |
78 |
79 |
80 |
မေးမြန်းအကြံပြုလိုပါက ဆက်သွယ်ရန်
81 |
96 |
97 | ©2015-2020 Team PopStack. All rights reserved.
98 |
99 |
100 |
101 |
102 | );
103 | };
104 |
105 | export default About;
106 |
--------------------------------------------------------------------------------
/pages/api/auth.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | const authAPI = axios.create({
4 | baseURL: process.env.BASE_URL,
5 | headers: {
6 | 'api-key': process.env.API_KEY
7 | }
8 | });
9 |
10 | export async function fetchToken() {
11 | // This is rather a side effect
12 | const response = await authAPI.post('/authenticate');
13 | const { token: apiToken } = response.data;
14 | return apiToken;
15 | }
16 |
17 | export default async function auth(req, res) {
18 | try {
19 | const token = await fetchToken();
20 | // Set token inside HTTP Cookie so we can deal with SSR endpoints
21 | return res.status(200).send({ token });
22 | } catch (error) {
23 | return res.status(500).send('Internal server error');
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/pages/api/ballots.js:
--------------------------------------------------------------------------------
1 | import MaePaySohAPI from '../../gateway/api';
2 |
3 | export default async function(req, res) {
4 | try {
5 | const { category = 'normal' } = req.query;
6 |
7 | const api = new MaePaySohAPI(req.cookies.token);
8 |
9 | const response = await api.getBallots(category);
10 |
11 | return res.status(200).send(response.data);
12 | } catch (error) {
13 | return res.status(500).send('Internal server error');
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/pages/api/candidates.js:
--------------------------------------------------------------------------------
1 | import MaePaySohAPI from '../../gateway/api';
2 |
3 | export default async function(req, res) {
4 | try {
5 | const { constituency_id: constituencyId } = req.query;
6 |
7 | const api = new MaePaySohAPI(req.cookies.token);
8 | const response = await api.getCandidateList(constituencyId);
9 | const { data } = response.data;
10 |
11 | return res.status(200).send({
12 | // pre-sort the data here before Frontend
13 | token: response.data.token,
14 | data: data.sort(
15 | (a, b) => a.attributes.ballot_order - b.attributes.ballot_order
16 | )
17 | });
18 | } catch (error) {
19 | return res.status(500).send('Internal server error');
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/pages/api/constituencies.js:
--------------------------------------------------------------------------------
1 | import MaePaySohAPI from '../../gateway/api';
2 |
3 | export default async function(req, res) {
4 | try {
5 | const { query } = req.query;
6 |
7 | const api = new MaePaySohAPI(req.cookies.token);
8 |
9 | const response = await api.getConstituencies(query);
10 |
11 | return res.status(200).send(response.data);
12 | } catch (error) {
13 | return res.status(500).send('Internal server error');
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/pages/api/faqs.js:
--------------------------------------------------------------------------------
1 | import MaePaySohAPI from '../../gateway/api';
2 |
3 | export default async function(req, res) {
4 | try {
5 | const { page, category } = req.query;
6 |
7 | const api = new MaePaySohAPI(req.cookies.token);
8 | const response = await api.getFaqs({ page, category });
9 |
10 | return res.status(200).send(response.data);
11 | } catch (error) {
12 | return res.status(500).send('Internal server error');
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/pages/api/locations.js:
--------------------------------------------------------------------------------
1 | import MaePaySohAPI from '../../gateway/api';
2 |
3 | // eslint-disable-next-line func-names
4 | export default async function(req, res) {
5 | try {
6 | const { type = 'state_regions', state_region, township, ward } = req.query;
7 |
8 | const api = new MaePaySohAPI(req.cookies.token);
9 | let response;
10 |
11 | if (type === 'state_regions') {
12 | response = await api.getStateRegions();
13 | } else if (type === 'townships') {
14 | response = await api.getTownships(state_region);
15 | } else if (type === 'wards') {
16 | response = await api.getWards(state_region, township);
17 | } else if (type === 'details') {
18 | response = await api.getWardDetails(state_region, township, ward);
19 | } else {
20 | throw new Error('Location type not provided.');
21 | }
22 |
23 | return res.status(200).send(response.data);
24 | } catch (error) {
25 | return res.status(500).send('Internal server error');
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/pages/api/news.js:
--------------------------------------------------------------------------------
1 | import MaePaySohAPI from '../../gateway/api';
2 |
3 | export default async function(req, res) {
4 | try {
5 | const { page } = req.query;
6 |
7 | const api = new MaePaySohAPI(req.cookies.token);
8 |
9 | const response = await api.getNews({ page });
10 |
11 | return res.status(200).send(response.data);
12 | } catch (error) {
13 | return res.status(500).send('Internal server error');
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/pages/api/parties.js:
--------------------------------------------------------------------------------
1 | import MaePaySohAPI from '../../gateway/api';
2 |
3 | export default async function(req, res) {
4 | try {
5 | const { page, item_per_page = 25 } = req.query;
6 |
7 | const api = new MaePaySohAPI(req.cookies.token);
8 | const response = await api.getParties({
9 | page,
10 | item_per_page
11 | });
12 |
13 | return res.status(200).send(response.data);
14 | } catch (error) {
15 | // eslint-disable-next-line no-console
16 | console.error(error);
17 | return res.status(500).send('Internal server error');
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/pages/api/searchCandidates.js:
--------------------------------------------------------------------------------
1 | import MaePaySohAPI from '../../gateway/api';
2 |
3 | export default async function(req, res) {
4 | try {
5 | const { query, page, item_per_page = 25 } = req.query;
6 |
7 | const api = new MaePaySohAPI(req.cookies.token);
8 |
9 | const response = await api.searchCandidates({ query, page, item_per_page });
10 |
11 | return res.status(200).send(response.data);
12 | } catch (error) {
13 | // eslint-disable-next-line no-console
14 | console.error(error);
15 | return res.status(500).send('Internal server error');
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/pages/api/searchFaqs.js:
--------------------------------------------------------------------------------
1 | import MaePaySohAPI from '../../gateway/api';
2 |
3 | export default async function(req, res) {
4 | try {
5 | const { query, page, item_per_page = 25 } = req.query;
6 |
7 | const api = new MaePaySohAPI(req.cookies.token);
8 |
9 | const response = await api.searchFaqs({ query, page, item_per_page });
10 |
11 | return res.status(200).send(response.data);
12 | } catch (error) {
13 | // eslint-disable-next-line no-console
14 | console.error(error);
15 | return res.status(500).send('Internal server error');
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/pages/api/searchNews.js:
--------------------------------------------------------------------------------
1 | import MaePaySohAPI from '../../gateway/api';
2 |
3 | export default async function(req, res) {
4 | try {
5 | const { query, page, item_per_page = 25 } = req.query;
6 |
7 | const api = new MaePaySohAPI(req.cookies.token);
8 | const response = await api.searchNews({ query, page, item_per_page });
9 |
10 | return res.status(200).send(response.data);
11 | } catch (error) {
12 | return res.status(500).send('Internal server error');
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/pages/api/searchParties.js:
--------------------------------------------------------------------------------
1 | import MaePaySohAPI from '../../gateway/api';
2 |
3 | export default async function(req, res) {
4 | try {
5 | const { query, page, item_per_page = 25 } = req.query;
6 |
7 | const api = new MaePaySohAPI(req.cookies.token);
8 | const response = await api.searchParties({ query, page, item_per_page });
9 |
10 | return res.status(200).send(response.data);
11 | } catch (error) {
12 | return res.status(500).send('Internal server error');
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/pages/candidates/candidate.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/partials/colors';
2 |
3 | .Candidate {
4 | line-height: 25px;
5 | &__imageWrapper {
6 | width: 130px;
7 | height: 130px;
8 | margin: 0 auto;
9 | position: relative;
10 | margin-bottom: 20px;
11 | }
12 | &__infoHeaderWrapper {
13 | text-align: center;
14 | }
15 | &__image {
16 | border-radius: 100%;
17 | background-position: center center;
18 | background-repeat: no-repeat;
19 | background-size: cover;
20 | width: 100%;
21 | height: 100%;
22 | &.is-winner {
23 | border: 3px solid $primary-color;
24 | }
25 | }
26 | &__winner {
27 | position: absolute;
28 | left: 50%;
29 | color: #ffffff;
30 | transform: translateX(-50%);
31 | bottom: -6px;
32 | background-color: $primary-color;
33 | font-size: 0.65rem;
34 | border-radius: 10px;
35 | z-index: 2;
36 | padding: 0px 8px;
37 | }
38 | &__partyFlag {
39 | display: inline-block;
40 | width: auto;
41 | height: 30px;
42 | margin-bottom: 5px;
43 | }
44 | &__name {
45 | display: inline-block;
46 | margin-left: 6px;
47 | margin-bottom: 10px;
48 | font-size: 1.25rem;
49 | }
50 | &__party {
51 | display: inline-block;
52 | margin-left: 3px;
53 | }
54 | &__partyName {
55 | color: rgba($color: #000000, $alpha: 0.75);
56 | cursor: pointer;
57 | i {
58 | vertical-align: middle;
59 | }
60 | }
61 | &__senate {
62 | font-size: 0.875rem;
63 | }
64 | &__constituency {
65 | margin-top: 10px;
66 | display: inline-block;
67 | background-color: rgba($color: $primary-color, $alpha: 0.25);
68 | border-radius: 100px;
69 | font-size: 0.85rem;
70 | font-weight: 600;
71 | padding: 8px 12px;
72 | }
73 | &__age {
74 | font-size: 1.5rem;
75 | color: $primary-color;
76 | }
77 | &__info {
78 | margin-bottom: 12px;
79 | .col-12 {
80 | margin-top: 5px;
81 | margin-bottom: 5px;
82 | }
83 | }
84 | &__infoLabel {
85 | font-size: 0.875rem;
86 | color: rgba($color: #000000, $alpha: 0.85);
87 | }
88 | .parent-info {
89 | div {
90 | &:first-child {
91 | margin-top: 0;
92 | }
93 | }
94 | }
95 | &__infoAnswer {
96 | font-size: 1rem;
97 | }
98 | }
99 |
100 | .competitors-wrapper {
101 | margin-top: 8px;
102 | margin-bottom: 0px;
103 | }
104 |
105 | .competitors-text {
106 | font-weight: 600;
107 | font-size: 1rem;
108 | margin: 5px 0;
109 | }
110 |
111 | @media (min-width: 1200px) {
112 | .Candidate {
113 | &__infoHeaderWrapper {
114 | text-align: left;
115 | }
116 | &__infoLabel {
117 | color: $primary-color;
118 | }
119 | &__infoAnswer {
120 | word-wrap: break-word;
121 | }
122 | &__partyFlag {
123 | margin-bottom: unset;
124 | }
125 | &__info {
126 | .col-12 {
127 | margin-top: 0;
128 | margin-bottom: 0;
129 | }
130 | .parent-type {
131 | color: $primary-color;
132 | }
133 | }
134 | }
135 | .competitors-wrapper {
136 | margin-top: 16px;
137 | margin-bottom: -6px;
138 | }
139 | .parent-info {
140 | margin-top: 0px;
141 | display: flex;
142 | justify-content: space-between;
143 | &-name {
144 | width: 50%;
145 | }
146 | &-ethnicity, &-religion {
147 | width: 25%;
148 | }
149 | .Candidate__infoLabel {
150 | color: rgba($color: #000000, $alpha: 0.65);
151 | }
152 | div {
153 | &:first-child {
154 | margin-left: 0;
155 | }
156 | margin-left: 5px;
157 | margin-right: 5px;
158 | }
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/pages/candidates/candidates.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/partials/colors';
2 | @import '../../styles/partials/extends';
3 |
4 | .show-location-image {
5 | width: auto;
6 | height: 96px;
7 | display: block;
8 | margin: 0 auto;
9 | }
10 |
11 | .show-location-chooser {
12 | margin-top: 20px;
13 | }
14 |
15 | .show-location-chooser-button {
16 | margin-top: 20px;
17 | background-color: $primary-color;
18 | color: #FFFFFF;
19 | border: 0;
20 | outline: 0;
21 | border-radius: 25px;
22 | padding: 5px 20px 3px 15px;
23 | transition: all 0.2s linear;
24 | font-size: 0.8rem;
25 | i {
26 | font-size: 1.2rem;
27 | }
28 | span {
29 | vertical-align: super;
30 | }
31 | &:hover, &:active {
32 | @extend %button-hover;
33 | }
34 | }
35 |
36 | .Candidates {
37 | @extend %mobile-vert-rules;
38 | }
39 |
40 | .Tabs {
41 | display: flex;
42 | justify-content: space-around;
43 | align-items: center;
44 | position: relative;
45 | padding: 10px;
46 | border-bottom: 1px solid rgba($color: #000000, $alpha: 0.1);
47 | box-shadow: 0px 3px 5px rgba($color: #000000, $alpha: 0.1);
48 | }
49 |
50 | .Tab {
51 | word-break: break-all;
52 | }
53 |
54 | .slider {
55 | position: absolute;
56 | left: 0;
57 | bottom: 0;
58 | // transform: translateX(-50%);
59 | height: 3px;
60 | width: 138px;
61 | background: #0071dd;
62 | }
63 |
64 | .no-data-text {
65 | margin-top: 55%;
66 | color: red;
67 | }
68 |
--------------------------------------------------------------------------------
/pages/candidates/search.js:
--------------------------------------------------------------------------------
1 | import SearchPage from '../../components/Search/SearchPage';
2 | import CandidateList from '../../components/Candidates/CandidateList/CandidateList';
3 |
4 | const CandidateSearch = () => {
5 | // Why pass endpoint you may ask? Answer: I am lazy.
6 | return (
7 |
14 |
15 |
16 | );
17 | };
18 |
19 | export default CandidateSearch;
20 |
--------------------------------------------------------------------------------
/pages/faqs/[faq].js:
--------------------------------------------------------------------------------
1 | import ReactGA from 'react-ga';
2 | import htmlToText from 'html-to-text';
3 | import nookies from 'nookies';
4 | import Head from 'next/head';
5 | import { useEffect } from 'react';
6 | import MaePaySohAPI from '../../gateway/api';
7 | import Layout from '../../components/Layout/Layout';
8 | import { formatFAQCategory } from '../../utils/textFormatter';
9 | import { useAuthContext } from '../../context/AuthProvider';
10 |
11 | import './faq.module.scss';
12 |
13 | const FAQ = props => {
14 | const {
15 | faq: { id, category, question, answer, strippedAnswer },
16 | token
17 | } = props;
18 |
19 | const { updateToken } = useAuthContext();
20 |
21 | useEffect(() => ReactGA.pageview('/faqs/[faq]'), []);
22 | useEffect(() => {
23 | if (token) {
24 | updateToken(token);
25 | }
26 | }, [token]);
27 |
28 | return (
29 |
30 |
31 | {formatFAQCategory(category)} သိမှတ်ဖွယ်ရာ | mVoter 2020
32 |
33 |
34 |
35 |
36 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
52 |
53 |
54 |
58 |
59 |
60 |
61 |
62 |
63 |
{question}
64 |
69 |
70 |
71 |
72 |
73 | {/* TODO Add more sensible information under this */}
74 |
75 | );
76 | };
77 |
78 | export async function getServerSideProps(context) {
79 | const { params } = context;
80 |
81 | const cookies = nookies.get(context);
82 | const api = new MaePaySohAPI(cookies.token);
83 | const response = await api.getFaqById(params.faq);
84 |
85 | const { data, token } = response.data;
86 |
87 | // Strip HTML characters into one string
88 | const htmlStrippedAnswer = htmlToText.fromString(data.attributes.answer);
89 | data.attributes.strippedAnswer = htmlStrippedAnswer;
90 |
91 | return {
92 | props: {
93 | faq: {
94 | ...data,
95 | ...data.attributes
96 | },
97 | ...(token && { token })
98 | }
99 | };
100 | }
101 |
102 | export default FAQ;
103 |
--------------------------------------------------------------------------------
/pages/faqs/ballots.js:
--------------------------------------------------------------------------------
1 | import ReactGA from 'react-ga';
2 | import { useRouter } from 'next/router';
3 | import { useState, useEffect, useRef } from 'react';
4 | import { AiOutlineLoading } from 'react-icons/ai';
5 | import Head from 'next/head';
6 | import Slider from 'react-slick';
7 | import Select from 'react-select';
8 | import myanmarNumbers from 'myanmar-numbers';
9 | import { customSelectStyle, BALLOT_CATEGORIES } from '../../utils/constants';
10 | import Button from '../../components/Common/Button/Button';
11 | import AppHeader from '../../components/Layout/AppHeader/AppHeader';
12 | import Layout from '../../components/Layout/Layout';
13 |
14 | import './slick.scss';
15 | import './slick-theme.scss';
16 | import './ballots.module.scss';
17 | import useAPI from '../../hooks/useAPI';
18 |
19 | function PrevArrow(props) {
20 | const { onClick, currentSlide } = props;
21 | return (
22 |
37 | );
38 | }
39 |
40 | function NextArrow(props) {
41 | const { onClick, currentSlide, slideCount } = props;
42 | return (
43 |
58 | );
59 | }
60 |
61 | const Ballots = () => {
62 | const router = useRouter();
63 | const [ballots, setBallots] = useState([]);
64 | const [loading, setLoading] = useState(false);
65 | const [currentSlide, setCurrentSlide] = useState(1);
66 | const sliderRef = useRef();
67 | const [, fetchData] = useAPI();
68 |
69 | const settings = {
70 | dots: false,
71 | infinite: false,
72 | speed: 500,
73 | slidesToShow: 1,
74 | slidesToScroll: 1,
75 | count: ballots.length,
76 | prevArrow:
,
77 | nextArrow:
,
78 | afterChange: current => {
79 | setCurrentSlide(current + 1);
80 | }
81 | };
82 |
83 | async function fetchBallots(category = 'normal') {
84 | setLoading(true);
85 |
86 | const { data } = await fetchData('/api/ballots', {
87 | category
88 | });
89 |
90 | setBallots(data);
91 | setLoading(false);
92 | }
93 |
94 | useEffect(() => {
95 | ReactGA.pageview(window.location.pathname);
96 |
97 | fetchBallots();
98 | }, []);
99 |
100 | function onChangeCategory(value) {
101 | sliderRef.current.slickGoTo(0, true); // don't animate
102 | fetchBallots(value);
103 | setCurrentSlide(1);
104 | }
105 |
106 | return (
107 |
108 |
109 | မဲနမူနာများ | mVoter 2020
110 |
111 |
112 |
113 |
116 | သိမှတ်ဖွယ်ရာများ
117 |
118 |
119 |
120 |
155 |
156 | );
157 | };
158 |
159 | export default Ballots;
160 |
--------------------------------------------------------------------------------
/pages/faqs/ballots.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/partials/colors';
2 |
3 | .Ballots {
4 | position: relative;
5 | height: 100%;
6 | &__ballot-item {
7 | outline: 0;
8 | text-align: center;
9 | margin-bottom: 0;
10 | img {
11 | display: block;
12 | margin: 0 auto;
13 | width: auto;
14 | height: 380px;
15 | }
16 | }
17 | &__SelectCategory {
18 | margin-bottom: 10px;
19 | }
20 | }
21 |
22 | .valid-color {
23 | color: #349739;
24 | }
25 |
26 | .invalid-color {
27 | color: #cc1e1e;
28 | }
29 |
30 | .ballot-arrow {
31 | background: $primary-color;
32 | padding: 1px;
33 | border-radius: 50%;
34 | border: 0;
35 | color: #ffffff;
36 | &:disabled {
37 | cursor: not-allowed;
38 | background: rgba($color: #000000, $alpha: 0.2);
39 | }
40 | i {
41 | font-size: 1.5rem;
42 | margin-top: 0px;
43 | display: block;
44 | }
45 | }
46 |
47 | .ballot-loader {
48 | position: absolute;
49 | top: 50%;
50 | left: 45%;
51 | transform: translateX(-50%) translateY(-50%);
52 | font-size: 3rem;
53 | }
54 |
55 | @media (min-width: 1200px) {
56 | .Ballots {
57 | &__ballot-item {
58 | margin-top: 15px;
59 | img {
60 | height: 550px;
61 | }
62 | }
63 | }
64 | }
--------------------------------------------------------------------------------
/pages/faqs/faq.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/partials/colors';
2 |
3 | .FAQ {
4 | line-height: 25px;
5 | padding-top: 30px;
6 | &__question {
7 | color: $primary-color;
8 | font-size: 1rem;
9 | font-weight: 400;
10 | margin-bottom: 10px;
11 | }
12 | }
--------------------------------------------------------------------------------
/pages/faqs/faqs.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/partials/extends';
2 | @import '../../styles/partials/colors';
3 |
4 | $ballot-stack-height: 110px;
5 |
6 | #FAQS {
7 | line-height: 25px;
8 | }
9 |
10 | .check-voter-list {
11 | display: flex;
12 | justify-content: center;
13 | align-items: center;
14 | border: 1px solid rgba($color: #000000, $alpha: 0.1);
15 | border-radius: 8px;
16 | padding: 8px 10px;
17 | i {
18 | font-size: 3rem;
19 | }
20 | }
21 |
22 | .ballot-stack, .unfair-law {
23 | padding: 8px 10px;
24 | margin-bottom: 10px;
25 | border-radius: 10px;
26 | border: 1px solid rgba($color: #000000, $alpha: 0.1);
27 | cursor: pointer;
28 | }
29 |
30 | .unfair-law {
31 | display: flex;
32 | align-items: center;
33 | &-text {
34 | color: #2e2e2e;
35 | margin-left: 15px;
36 | }
37 | }
38 |
39 | .ballot-stack-picture {
40 | display: block;
41 | margin: 0 auto;
42 | height: $ballot-stack-height;
43 | }
44 |
45 | .prohibition {
46 | img {
47 | width: 100%;
48 | height: auto;
49 | }
50 | div {
51 | margin-top: 3px;
52 | text-align: center;
53 | font-size: 0.85rem;
54 | }
55 | }
56 |
57 | .category-select {
58 | width: 100%;
59 | background: transparent;
60 | // position: relative;
61 | // -webkit-appearance: none;
62 | // -moz-appearance: none;
63 | }
64 |
65 | .FAQS {
66 | @extend %mobile-vert-rules;
67 | }
68 |
69 | @media (min-width: 1200px) {
70 | .ballot-stack {
71 | margin-bottom: 0;
72 | }
73 | .unfair-law {
74 | &-text {
75 | margin-left: 5px;
76 | }
77 | }
78 | }
--------------------------------------------------------------------------------
/pages/faqs/index.js:
--------------------------------------------------------------------------------
1 | import ReactGA from 'react-ga';
2 | import { useEffect, useState } from 'react';
3 | import { useRouter } from 'next/router';
4 | import Select from 'react-select';
5 | import Head from 'next/head';
6 | import Link from 'next/link';
7 | import InfiniteScroll from 'react-infinite-scroll-component';
8 | import { customSelectStyle, FAQ_CATEGORY } from '../../utils/constants';
9 | import Layout from '../../components/Layout/Layout';
10 | import AppHeader from '../../components/Layout/AppHeader/AppHeader';
11 | import FaqList from '../../components/Faq/FaqList/FaqList';
12 | import Button from '../../components/Common/Button/Button';
13 | import GavelIcon from '../../components/Common/Icons/gavel';
14 |
15 | import './faqs.module.scss';
16 | import useAPI from '../../hooks/useAPI';
17 |
18 | const FAQs = () => {
19 | const [faqs, setFaqs] = useState([]);
20 | const [totalCount, setTotalCount] = useState(0);
21 | const [page, setPage] = useState(1);
22 | const [faqCategory, setFaqCategory] = useState('voter_list');
23 | const router = useRouter();
24 | const [, fetchData] = useAPI();
25 |
26 | async function fetchFaqs(category = 'voter_list', pageToLoad = 1) {
27 | try {
28 | const data = await fetchData('/api/faqs', {
29 | page: pageToLoad,
30 | category
31 | });
32 |
33 | return data;
34 | } catch (error) {
35 | return error;
36 | }
37 | }
38 |
39 | useEffect(() => {
40 | ReactGA.pageview(window.location.pathname);
41 |
42 | fetchFaqs()
43 | .then(result => {
44 | setFaqs(result.data);
45 | setTotalCount(result.pagination.total);
46 | })
47 | .catch(error => error);
48 | }, []);
49 |
50 | function onChangeCategory(category) {
51 | // Clear FAQs first
52 | fetchFaqs(category)
53 | .then(result => {
54 | // Reset to defaults
55 | setPage(1);
56 | setFaqs([]);
57 | setFaqCategory(category);
58 | return result;
59 | })
60 | .then(result => setFaqs(result.data))
61 | .catch(error => error);
62 | }
63 |
64 | function loadMoreFaqs() {
65 | const nextPage = page + 1;
66 | fetchFaqs(faqCategory, nextPage)
67 | .then(result => setFaqs(faqs.concat(result.data)))
68 | .then(() => setPage(nextPage))
69 | .catch(error => error);
70 | }
71 |
72 | return (
73 |
74 |
75 | သိမှတ်ဖွယ်ရာများ | mVoter 2020
76 |
77 |
78 | သိမှတ်ဖွယ်ရာများ
79 |
95 |
96 |
97 |
98 |
99 |
114 |
115 |
116 |
117 |
118 | {faqCategory === 'voter_list' && (
119 |
120 |
121 |
router.push('/faqs/ballots')}
124 | >
125 |
126 |

131 |
132 |
ပယ်မဲ၊ ခိုင်လုံမဲ နမူနာများ
133 |
134 |
135 |
136 |
137 |
138 |

142 |
143 | Selfie
144 | မရိုက်ရ
145 |
146 |
147 |
148 |

149 |
150 | ဓာတ်ပုံ
151 | မရိုက်ရ
152 |
153 |
154 |
155 |

156 |
157 | ဗီဒီယို
158 | မရိုက်ရ
159 |
160 |
161 |
162 |

166 |
167 | အသံ
168 | မသွင်းရ
169 |
170 |
171 |
172 |
173 |
174 | )}
175 | {faqCategory === 'voter_list' && (
176 |
177 |
178 |
179 |
180 |
181 | how_to_reg
182 |
183 | မဲစာရင်းစစ်ရန်
184 |
185 |
186 |
187 |
188 | )}
189 | {faqCategory === 'candidate' && (
190 |
210 | )}
211 |
212 |
217 |
218 |
219 |
220 |
221 | );
222 | };
223 |
224 | export default FAQs;
225 |
--------------------------------------------------------------------------------
/pages/faqs/search.js:
--------------------------------------------------------------------------------
1 | import SearchPage from '../../components/Search/SearchPage';
2 | import FaqList from '../../components/Faq/FaqList/FaqList';
3 |
4 | const FaqSearch = () => {
5 | // Why pass endpoint you may ask? Answer: I am lazy.
6 | return (
7 |
14 |
15 |
16 | );
17 | };
18 |
19 | export default FaqSearch;
20 |
--------------------------------------------------------------------------------
/pages/faqs/slick-theme.scss:
--------------------------------------------------------------------------------
1 | @charset 'UTF-8';
2 | /* Slider */
3 | .slick-loading .slick-list
4 | {
5 | // background: #fff url('./ajax-loader.gif') center center no-repeat;
6 | }
7 | /* Arrows */
8 | .slick-prev,
9 | .slick-next
10 | {
11 | font-size: 0;
12 | line-height: 0;
13 |
14 | position: absolute;
15 | top: 50%;
16 |
17 | display: block;
18 |
19 | width: 20px;
20 | height: 20px;
21 | padding: 0;
22 | -webkit-transform: translate(0, -50%);
23 | -ms-transform: translate(0, -50%);
24 | transform: translate(0, -50%);
25 |
26 | cursor: pointer;
27 |
28 | color: transparent;
29 | border: none;
30 | outline: none;
31 | background: transparent;
32 | }
33 | .slick-prev:hover,
34 | .slick-prev:focus,
35 | .slick-next:hover,
36 | .slick-next:focus
37 | {
38 | color: transparent;
39 | outline: none;
40 | background: transparent;
41 | }
42 | .slick-prev:hover:before,
43 | .slick-prev:focus:before,
44 | .slick-next:hover:before,
45 | .slick-next:focus:before
46 | {
47 | opacity: 1;
48 | }
49 | .slick-prev.slick-disabled:before,
50 | .slick-next.slick-disabled:before
51 | {
52 | opacity: .25;
53 | }
54 |
55 | .slick-prev:before,
56 | .slick-next:before
57 | {
58 | font-family: 'slick';
59 | font-size: 20px;
60 | line-height: 1;
61 |
62 | opacity: .75;
63 | color: white;
64 |
65 | -webkit-font-smoothing: antialiased;
66 | -moz-osx-font-smoothing: grayscale;
67 | }
68 |
69 | .slick-prev
70 | {
71 | left: -25px;
72 | }
73 | [dir='rtl'] .slick-prev
74 | {
75 | right: -25px;
76 | left: auto;
77 | }
78 | .slick-prev:before
79 | {
80 | content: '←';
81 | }
82 | [dir='rtl'] .slick-prev:before
83 | {
84 | content: '→';
85 | }
86 |
87 | .slick-next
88 | {
89 | right: -25px;
90 | }
91 | [dir='rtl'] .slick-next
92 | {
93 | right: auto;
94 | left: -25px;
95 | }
96 | .slick-next:before
97 | {
98 | content: '→';
99 | }
100 | [dir='rtl'] .slick-next:before
101 | {
102 | content: '←';
103 | }
104 |
105 | /* Dots */
106 | .slick-dotted.slick-slider
107 | {
108 | margin-bottom: 30px;
109 | }
110 |
111 | .slick-dots
112 | {
113 | position: absolute;
114 | bottom: -25px;
115 |
116 | display: block;
117 |
118 | width: 100%;
119 | padding: 0;
120 | margin: 0;
121 |
122 | list-style: none;
123 |
124 | text-align: center;
125 | }
126 | .slick-dots li
127 | {
128 | position: relative;
129 |
130 | display: inline-block;
131 |
132 | width: 20px;
133 | height: 20px;
134 | margin: 0 5px;
135 | padding: 0;
136 |
137 | cursor: pointer;
138 | }
139 | .slick-dots li button
140 | {
141 | font-size: 0;
142 | line-height: 0;
143 |
144 | display: block;
145 |
146 | width: 20px;
147 | height: 20px;
148 | padding: 5px;
149 |
150 | cursor: pointer;
151 |
152 | color: transparent;
153 | border: 0;
154 | outline: none;
155 | background: transparent;
156 | }
157 | .slick-dots li button:hover,
158 | .slick-dots li button:focus
159 | {
160 | outline: none;
161 | }
162 | .slick-dots li button:hover:before,
163 | .slick-dots li button:focus:before
164 | {
165 | opacity: 1;
166 | }
167 | .slick-dots li button:before
168 | {
169 | font-family: 'slick';
170 | font-size: 6px;
171 | line-height: 20px;
172 |
173 | position: absolute;
174 | top: 0;
175 | left: 0;
176 |
177 | width: 20px;
178 | height: 20px;
179 |
180 | content: '•';
181 | text-align: center;
182 |
183 | opacity: .25;
184 | color: black;
185 |
186 | -webkit-font-smoothing: antialiased;
187 | -moz-osx-font-smoothing: grayscale;
188 | }
189 | .slick-dots li.slick-active button:before
190 | {
191 | opacity: .75;
192 | color: black;
193 | }
--------------------------------------------------------------------------------
/pages/faqs/slick.scss:
--------------------------------------------------------------------------------
1 | /* Slider */
2 | .slick-slider
3 | {
4 | position: relative;
5 | display: block;
6 | box-sizing: border-box;
7 |
8 | -webkit-user-select: none;
9 | -moz-user-select: none;
10 | -ms-user-select: none;
11 | user-select: none;
12 |
13 | -webkit-touch-callout: none;
14 | -khtml-user-select: none;
15 | -ms-touch-action: pan-y;
16 | touch-action: pan-y;
17 | -webkit-tap-highlight-color: transparent;
18 | }
19 |
20 | .slick-list
21 | {
22 | position: relative;
23 |
24 | display: block;
25 | overflow: hidden;
26 |
27 | margin: 0;
28 | padding: 0;
29 | }
30 | .slick-list:focus
31 | {
32 | outline: none;
33 | }
34 | .slick-list.dragging
35 | {
36 | cursor: pointer;
37 | cursor: hand;
38 | }
39 |
40 | .slick-slider .slick-track,
41 | .slick-slider .slick-list
42 | {
43 | -webkit-transform: translate3d(0, 0, 0);
44 | -moz-transform: translate3d(0, 0, 0);
45 | -ms-transform: translate3d(0, 0, 0);
46 | -o-transform: translate3d(0, 0, 0);
47 | transform: translate3d(0, 0, 0);
48 | }
49 |
50 | .slick-track
51 | {
52 | position: relative;
53 | top: 0;
54 | left: 0;
55 |
56 | display: block;
57 | margin-left: auto;
58 | margin-right: auto;
59 | }
60 | .slick-track:before,
61 | .slick-track:after
62 | {
63 | display: table;
64 |
65 | content: '';
66 | }
67 | .slick-track:after
68 | {
69 | clear: both;
70 | }
71 | .slick-loading .slick-track
72 | {
73 | visibility: hidden;
74 | }
75 |
76 | .slick-slide
77 | {
78 | display: none;
79 | float: left;
80 |
81 | height: 100%;
82 | min-height: 1px;
83 | }
84 | [dir='rtl'] .slick-slide
85 | {
86 | float: right;
87 | }
88 | .slick-slide img
89 | {
90 | display: block;
91 | }
92 | .slick-slide.slick-loading img
93 | {
94 | display: none;
95 | }
96 | .slick-slide.dragging img
97 | {
98 | pointer-events: none;
99 | }
100 | .slick-initialized .slick-slide
101 | {
102 | display: block;
103 | }
104 | .slick-loading .slick-slide
105 | {
106 | visibility: hidden;
107 | }
108 | .slick-vertical .slick-slide
109 | {
110 | display: block;
111 |
112 | height: auto;
113 |
114 | border: 1px solid transparent;
115 | }
116 | .slick-arrow.slick-hidden {
117 | display: none;
118 | }
--------------------------------------------------------------------------------
/pages/how_to_vote/HowToVote.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/partials/colors';
2 | @import '../../styles/partials/extends';
3 |
4 | .check-voter-list-button {
5 | display: block;
6 | margin: 0 auto;
7 | width: 100%;
8 | border: 0;
9 | border-radius: 10px;
10 | background: $primary-color;
11 | color: #ffffff;
12 | padding: 10px 30px;
13 | transition: background 0.2s linear;
14 | &:hover {
15 | background: rgba($color: $primary-color, $alpha: 0.85);
16 | }
17 | }
18 |
19 | .HowToVote {
20 | @extend %mobile-vert-rules;
21 | padding-bottom: 20px;
22 | &__announcement {
23 | padding: 15px;
24 | border-radius: 12px;
25 | background-color: rgba($color: $primary-color, $alpha: 0.25);
26 | .title, .date, .day {
27 | display: inline-block;
28 | }
29 | .title {
30 | font-weight: 700;
31 | margin-bottom: 14px;
32 | }
33 | .date {
34 | font-weight: 700;
35 | color: $primary-color;
36 | font-size: 1.4rem;
37 | }
38 | .day {
39 | color: $grey-color;
40 | font-size: 1.1rem;
41 | margin-bottom: 12px;
42 | }
43 | }
44 | &__votingTime {
45 | display: flex;
46 | justify-content: center;
47 | .opening-time, .closing-time {
48 | margin: 5px 10px;
49 | .pre {
50 | line-height: 28px;
51 | font-size: 0.8rem;
52 | font-weight: 700;
53 | }
54 | }
55 | }
56 | .instruction-list {
57 | position: relative;
58 | // &::after {
59 | // content: '';
60 | // display: inline-block;
61 | // position: absolute;
62 | // top: 0;
63 | // left: 10px;
64 | // width: 2px;
65 | // height: 100%;
66 | // background-color: $primary-color;
67 | // }
68 | }
69 | .title {
70 | font-weight: 700;
71 | margin-top: 2px;
72 | margin-bottom: 3px;
73 | }
74 | .instruction {
75 | padding-left: 30px;
76 | position: relative;
77 | line-height: 28px;
78 | margin: 5px 0;
79 | &:last-child::before {
80 | content: '';
81 | display: none;
82 | }
83 | &::before {
84 | content: '';
85 | display: inline-block;
86 | position: absolute;
87 | top: 20px;
88 | left: 10px;
89 | width: 2px;
90 | height: 100%;
91 | background-color: $primary-color;
92 | }
93 | &::after {
94 | content: '';
95 | display: inline-block;
96 |
97 | position: absolute;
98 | border-radius: 100%;
99 | top: 9px;
100 | left: 5px;
101 | width: 12px;
102 | height: 12px;
103 | background-color: $primary-color;
104 | }
105 | }
106 | }
107 |
108 | @media (min-width: 1200px) {
109 | .check-voter-list-button {
110 | width: unset;
111 | }
112 | }
--------------------------------------------------------------------------------
/pages/how_to_vote/index.js:
--------------------------------------------------------------------------------
1 | import ReactGA from 'react-ga';
2 | import { useEffect } from 'react';
3 | import Link from 'next/link';
4 | import Head from 'next/head';
5 | import dynamic from 'next/dynamic';
6 |
7 | import Layout from '../../components/Layout/Layout';
8 | import Clock6AM from '../../components/Common/Icons/clock6AM';
9 | import Clock4PM from '../../components/Common/Icons/clock4PM';
10 | import AppHeader from '../../components/Layout/AppHeader/AppHeader';
11 |
12 | import './HowToVote.module.scss';
13 |
14 | const ElectionCountdown = dynamic(
15 | () => import('../../components/Countdown/ElectionCountdown'),
16 | { ssr: false }
17 | );
18 |
19 | export default function howToVote() {
20 | useEffect(() => {
21 | ReactGA.pageview(window.location.pathname);
22 | }, []);
23 |
24 | return (
25 |
26 |
27 | မဲပေးနည်း | mVoter 2020
28 |
29 |
30 | မဲပေးနည်း
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | (၂၀၂၀) ပြည့်နှစ် အထွေထွေရွေးကောက်ပွဲနေ့
39 |
40 |
41 |
နိုဝင်ဘာလ (၈) ရက်
42 |
43 |
တနင်္ဂနွေနေ့
44 |
45 |
46 |
47 |
48 |
49 | မဲရုံဖွင့်ချိန်
50 |
51 |
52 | နံနက်(၆)နာရီ
53 |
54 |
55 |
56 |
57 | မဲရုံပိတ်ချိန်
58 |
59 |
60 | ညနေ(၄)နာရီ
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
ရွေးကောက်ပွဲနေ့တွင်
80 |
81 |
82 | ရပ်ကွက်/ကျေးရွာအုပ်စု အတွင်းရှိ မိမိ မဲပေးရမည့် မဲရုံသို့သွားပါ။
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | ပြည်သူ့လွှတ်တော် ကိုယ်စားလှယ် ရွေးချယ်ရန်
91 |
92 |
93 |
94 | ပြည်သူ့လွှတ်တော် ကိုယ်စားလှယ် ဆန္ဒမဲအတွက် မဲစာရင်းစစ်သူထံ
95 | သွားရောက်၍ မှတ်ပုံတင်ပြသပါ။
96 |
97 |
98 | မဲစာရင်းတွင် မိမိအမည်ပါလျှင် မဲလက်မှတ် ထုတ်ပေးသူထံမှ မဲလက်မှတ်
99 | ရယူပါ။
100 |
101 |
102 | လျှို့ဝှက်ဆန္ဒမဲပေးခန်းသို့ သွား၍ မိမိနှစ်သက်ရာ
103 | ကိုယ်စားလှယ်လောင်း၏ အကွက်တွင် တံဆိပ်တုံးနှိပ်ပါ။
104 |
105 |
106 | ပြည်သူ့လွှတ်တော် ဆန္ဒမဲပုံး ရှိရာသို့သွား၍ မဲပုံးထဲသို့
107 | မဲလက်မှတ်ကိုထည့်ပါ။
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 | အမျိုးသားလွှတ်တော် ကိုယ်စားလှယ် ရွေးချယ်ရန်
116 |
117 |
118 | အမျိုးသားလွှတ်တော် ကိုယ်စားလှယ် ဆန္ဒမဲအတွက် မဲစာရင်းစစ်သူထံ
119 | သွားရောက်၍ မှတ်ပုံတင်ပြသပါ။
120 |
121 |
122 | မဲစာရင်းတွင် မိမိအမည်ပါလျှင် မဲလက်မှတ် ထုတ်ပေးသူထံမှ မဲလက်မှတ်
123 | ရယူပါ။
124 |
125 |
126 | လျှို့ဝှက်ဆန္ဒမဲပေးခန်းသို့ သွား၍ မိမိနှစ်သက်ရာ
127 | ကိုယ်စားလှယ်လောင်း၏ အကွက်တွင် တံဆိပ်တုံးနှိပ်ပါ။
128 |
129 |
130 | အမျိုးသားလွှတ်တော် ဆန္ဒမဲပုံး ရှိရာသို့သွား၍ မဲပုံးထဲသို့
131 | မဲလက်မှတ်ကိုထည့်ပါ။
132 |
133 |
134 |
135 |
136 |
137 |
138 | တိုင်းဒေသကြီး/ပြည်နယ် လွှတ်တော် ကိုယ်စားလှယ် ရွေးချယ်ရန်
139 |
140 |
141 | တိုင်းဒေသကြီး/ပြည်နယ် လွှတ်တော် ကိုယ်စားလှယ် ဆန္ဒမဲ အတွက်
142 | မဲစာရင်းစစ်သူထံ သွားရောက်၍ မှတ်ပုံတင်ပြသပါ။
143 |
144 |
145 | မဲစာရင်းတွင် မိမိအမည်ပါလျှင် မဲလက်မှတ် ထုတ်ပေးသူထံမှ မဲလက်မှတ်
146 | ရယူပါ။
147 |
148 |
149 | လျှို့ဝှက်ဆန္ဒမဲပေးခန်းသို့ သွား၍ မိမိနှစ်သက်ရာ
150 | ကိုယ်စားလှယ်လောင်း၏ အကွက်တွင် တံဆိပ်တုံးနှိပ်ပါ။
151 |
152 |
153 | တိုင်းဒေသကြီး/ပြည်နယ် လွှတ်တော် ဆန္ဒမဲပုံး ရှိရာသို့သွား၍
154 | မဲပုံးထဲသို့ မဲလက်မှတ်ကိုထည့်ပါ။
155 |
156 |
157 |
158 |
159 |
160 |
161 | တိုင်းရင်းသားလူမျိုး ကိုယ်စားလှယ် ရွေးချယ်ရန်ရှိပါက
162 |
163 |
164 | တိုင်းရင်းသားလူမျိုး ကိုယ်စားလှယ် ဆန္ဒမဲအတွက် မဲစာရင်းစစ်သူထံ
165 | သွားရောက်၍ မှတ်ပုံတင်ပြသပါ။
166 |
167 |
168 | မဲစာရင်းတွင် မိမိအမည်ပါလျှင် မဲလက်မှတ် ထုတ်ပေးသူထံမှ မဲလက်မှတ်
169 | ရယူပါ။
170 |
171 |
172 | လျှို့ဝှက်ဆန္ဒမဲပေးခန်းသို့ သွား၍ မိမိနှစ်သက်ရာ
173 | ကိုယ်စားလှယ်လောင်း၏ အကွက်တွင် တံဆိပ်တုံးနှိပ်ပါ။
174 |
175 |
176 | တိုင်းရင်းသားလူမျိုး ဆန္ဒမဲပုံး ရှိရာသို့သွား၍ မဲပုံးထဲသို့
177 | မဲလက်မှတ်ကိုထည့်ပါ။
178 |
179 |
180 |
181 |
182 |
183 |
အထက်ပါ အဆင့်များ လုပ်ဆောင်ပြီးလျှင်
184 |
185 | မဲပေးပြီးကြောင်း မင်တို့မည့် မဲရုံအဖွဲ့ဝင်ထံ သွားရောက်၍ မိမိ၏
186 | လက်ဝဲဘက် လက်သန်းတွင် မင်တို့ပါ။
187 |
188 |
189 | မဲရုံအတွင်းမှ ထွက်နိုင်ပါပြီ။ သင် နိုင်ငံသားတစ်ယောက်၏ တာဝန်
190 | ကျေပွန်ခဲ့ပြီ ဖြစ်သည်။
191 |
192 |
193 |
194 |
195 |
196 | );
197 | }
198 |
--------------------------------------------------------------------------------
/pages/how_to_vote/voter_list.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { useRouter } from 'next/router';
3 | import ReactGA from 'react-ga';
4 | import Head from 'next/head';
5 | import Layout from '../../components/Layout/Layout';
6 | import AppHeader from '../../components/Layout/AppHeader/AppHeader';
7 | import { VOTER_LIST_LINKS } from '../../utils/constants';
8 |
9 | import './voter_list.module.scss';
10 |
11 | const VoterListPage = () => {
12 | const router = useRouter();
13 |
14 | useEffect(() => {
15 | ReactGA.pageview(window.location.pathname);
16 | }, []);
17 |
18 | return (
19 |
20 |
21 | မဲစာရင်းစစ်ရန် | mVoter 2020
22 |
23 |
24 |
32 |
33 |
34 |
35 | မိမိ အမည်ပါ၊ မပါ မဲစာရင်းစစ်ရန် ရွေးကောက်ပွဲကော်မရှင်မှ တရားဝင် Web
36 | Application Link များအား အသုံးပြုစစ်ဆေးနိုင်ပါတယ်။
37 |
38 |
39 |
40 |
41 | တိုင်းဒေသကြီး/ပြည်နယ် |
42 | စစ်ဆေးရန် App Link |
43 |
44 |
45 |
46 | {VOTER_LIST_LINKS.map(({ region, url }, index) => (
47 |
48 | {region} |
49 |
50 |
51 | {url}
52 |
53 | |
54 |
55 | ))}
56 |
57 |
58 |
59 |
60 | );
61 | };
62 |
63 | export default VoterListPage;
64 |
--------------------------------------------------------------------------------
/pages/how_to_vote/voter_list.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/partials/colors';
2 |
3 | .VoterList {
4 | &__table {
5 | width: 100%;
6 | th, tr, td {
7 | padding: 5px 10px;
8 | border: 1px solid rgba($color: #000000, $alpha: 0.85);
9 | }
10 | }
11 | }
12 |
13 | @media (min-width: 1200px) {
14 | .VoterList {
15 | &__table {
16 | th, tr, td {
17 | padding: 10px;
18 | }
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import Head from 'next/head';
3 | import { useRouter } from 'next/router';
4 | import Layout from '../components/Layout/Layout';
5 |
6 | const Home = () => {
7 | const router = useRouter();
8 |
9 | useEffect(() => {
10 | router.push('/candidates');
11 | }, []);
12 |
13 | return (
14 |
15 |
16 |
mVoter 2020 Web Application
17 |
18 |
19 |
23 |
24 |
25 |
26 |
30 |
31 |
32 |
33 |
34 |
35 |
39 |
40 |
41 |
42 |
43 | );
44 | };
45 |
46 | export default Home;
47 |
--------------------------------------------------------------------------------
/pages/location/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import Head from 'next/head';
3 | import { useRouter } from 'next/router';
4 |
5 | import Link from 'next/link';
6 | import Layout from '../../components/Layout/Layout';
7 | import ActivePeopleIcon from '../../components/Common/Icons/activePeople';
8 | import Button from '../../components/Common/Button/Button';
9 | import AppHeader from '../../components/Layout/AppHeader/AppHeader';
10 | import TownshipModal from '../../components/Location/TownshipModal';
11 | import WardVillageModal from '../../components/Location/WardVillageModal';
12 |
13 | import './location.module.scss';
14 | import { hasFullLocation } from '../../utils/helpers';
15 | import { LOCALSTORAGE_KEYS } from '../../utils/constants';
16 |
17 | const Location = () => {
18 | const [townshipModalOpen, setTownshipModalOpen] = useState(false);
19 | const [wardVillageModalOpen, setWardVillageModalOpen] = useState(false);
20 | const [stateRegion, setStateRegion] = useState();
21 | const [township, setTownship] = useState();
22 | const [wardVillage, setWardVillage] = useState();
23 | const [isAppStart, setIsAppStart] = useState(true);
24 | const router = useRouter();
25 |
26 | useEffect(() => {
27 | if (hasFullLocation()) {
28 | setIsAppStart(false);
29 | setStateRegion(localStorage.getItem(LOCALSTORAGE_KEYS.STATE_REGION));
30 | setTownship(localStorage.getItem(LOCALSTORAGE_KEYS.TOWNSHIP));
31 | setWardVillage(localStorage.getItem(LOCALSTORAGE_KEYS.WARD_VILLAGE));
32 | }
33 | }, []);
34 |
35 | function onClickDone() {
36 | localStorage.setItem(LOCALSTORAGE_KEYS.STATE_REGION, stateRegion);
37 | localStorage.setItem(LOCALSTORAGE_KEYS.TOWNSHIP, township);
38 | localStorage.setItem(LOCALSTORAGE_KEYS.WARD_VILLAGE, wardVillage);
39 | router.push('/candidates');
40 | }
41 |
42 | function onClickChooseTownship() {
43 | setTownshipModalOpen(true);
44 | }
45 |
46 | function onClickChooseWardVillage() {
47 | setWardVillageModalOpen(true);
48 | }
49 |
50 | function onClickTownship(chosenStateRegion, chosenTownship) {
51 | // Set localStorage here
52 | setStateRegion(chosenStateRegion);
53 | setTownship(chosenTownship);
54 | setWardVillage(null);
55 | setTownshipModalOpen(false);
56 | }
57 |
58 | function onClickWardVillage(chosenWardVillage) {
59 | setWardVillage(chosenWardVillage);
60 | setWardVillageModalOpen(false);
61 | }
62 |
63 | return (
64 |
65 |
66 | မဲဆန္ဒနယ်ရွေးရန် | mVoter 2020
67 |
68 | {!isAppStart && (
69 |
70 |
71 |
76 | ကိုယ်စားလှယ်လောင်းများ
77 |
78 |
79 | )}
80 |
81 |
82 |
83 |
84 |
{ActivePeopleIcon}
85 |
86 | မိမိ မဲပေးရွေးချယ်ရမည့် လွှတ်တော်ကိုယ်စားလှယ််လောင်းများကို
87 | သိရှိရန် နေထိုင်ရာအရပ်ကို ရွေးပါ
88 |
89 |
90 |
102 |
115 |
116 |
117 |
118 |
119 |
127 |
128 |
129 |
130 |
131 |
136 |
143 |
144 | );
145 | };
146 |
147 | export default Location;
148 |
--------------------------------------------------------------------------------
/pages/location/location.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/partials/colors';
2 | @import '../../styles/partials/extends';
3 |
4 | .locationSelector {
5 | // display: inline-block;
6 | display: flex;
7 | justify-content: center;
8 | align-items: center;
9 | margin: 0 auto;
10 | padding: 10px 0px 10px 5px;
11 | text-align: center;
12 | border: 0;
13 | background: none;
14 | &:disabled {
15 | span {
16 | color: rgba($color: #000000, $alpha: 0.25) !important;
17 | }
18 | }
19 | span {
20 | color: $primary-color;
21 | margin-right: 4px;
22 | font-weight: 700;
23 | font-size: 1.3rem;
24 | }
25 | svg {
26 | float: right;
27 | }
28 | }
29 |
30 | .people-icon {
31 | margin-bottom: 8px;
32 | >svg {
33 | width: 135px;
34 | height: 78px;
35 | }
36 | }
37 |
38 | .Location {
39 | height: calc(100vh - 180px);
40 | &__wrapper {
41 | position: absolute;
42 | top: 50%;
43 | transform: translateY(-50%);
44 | }
45 | &__done {
46 | margin-top: 30px;
47 | border: 0;
48 | background-color: $primary-color;
49 | color: $white-color;
50 | border-radius: 25px;
51 | padding: 5px 50px;
52 | vertical-align: middle;
53 | transition: all 0.2s linear;
54 | &:hover, &:active {
55 | @extend %button-hover;
56 | }
57 | &:disabled {
58 | cursor: not-allowed !important;
59 | background-color: rgba($color: #000000, $alpha: 0.25);
60 | }
61 | i {
62 | margin-top: 3px;
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/pages/news/index.js:
--------------------------------------------------------------------------------
1 | import ReactGA from 'react-ga';
2 | import { useState, useEffect } from 'react';
3 | import InfiniteScroll from 'react-infinite-scroll-component';
4 | import Head from 'next/head';
5 | import Link from 'next/link';
6 |
7 | import Layout from '../../components/Layout/Layout';
8 | import AppHeader from '../../components/Layout/AppHeader/AppHeader';
9 | import Button from '../../components/Common/Button/Button';
10 | import NewsList from '../../components/News/NewsList/NewsList';
11 |
12 | import './news.module.scss';
13 | import useAPI from '../../hooks/useAPI';
14 |
15 | const News = () => {
16 | const [news, setNews] = useState([]);
17 | const [page, setPage] = useState(1);
18 | const [, fetchData] = useAPI();
19 |
20 | async function fetchNews(pageToLoad = 1) {
21 | try {
22 | const { data } = await fetchData('/api/news', {
23 | page: pageToLoad
24 | });
25 |
26 | return setNews(news.concat(data));
27 | } catch (error) {
28 | return error;
29 | }
30 | }
31 |
32 | useEffect(() => {
33 | ReactGA.pageview(window.location.pathname);
34 |
35 | fetchNews();
36 | }, []);
37 |
38 | function loadMoreNews() {
39 | const nextPage = page + 1;
40 | fetchNews(nextPage);
41 | setPage(nextPage);
42 | }
43 |
44 | return (
45 |
46 |
47 | သတင်းများ | mVoter 2020
48 |
49 |
50 | သတင်းများ
51 |
60 |
61 |
62 |
63 |
64 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | );
76 | };
77 |
78 | export default News;
79 |
--------------------------------------------------------------------------------
/pages/news/news.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/partials/extends';
2 |
3 | .News {
4 | @extend %mobile-vert-rules;
5 | }
--------------------------------------------------------------------------------
/pages/news/search.js:
--------------------------------------------------------------------------------
1 | import SearchPage from '../../components/Search/SearchPage';
2 | import NewsList from '../../components/News/NewsList/NewsList';
3 |
4 | const NewsSearch = () => {
5 | return (
6 |
13 |
14 |
15 | );
16 | };
17 |
18 | export default NewsSearch;
19 |
--------------------------------------------------------------------------------
/pages/parties/[party].js:
--------------------------------------------------------------------------------
1 | import ReactGA from 'react-ga';
2 | import { useEffect } from 'react';
3 | import { useRouter } from 'next/router';
4 | import nookies from 'nookies';
5 | import Head from 'next/head';
6 | import myanmarNumbers from 'myanmar-numbers';
7 | import Layout from '../../components/Layout/Layout';
8 | import AppHeader from '../../components/Layout/AppHeader/AppHeader';
9 | import MaePaySohAPI from '../../gateway/api';
10 | import Button from '../../components/Common/Button/Button';
11 | import { useAuthContext } from '../../context/AuthProvider';
12 |
13 | import './party.module.scss';
14 |
15 | const Party = props => {
16 | const {
17 | party: {
18 | id,
19 | flag_image: flagImage,
20 | seal_image: sealImage,
21 | abbreviation,
22 | name_burmese: nameBurmese,
23 | name_english: nameEnglish,
24 | policy,
25 | region,
26 | leaders_and_chairmen: leadership,
27 | member_count: memberCount,
28 | headquarter_address: headquarterAddress,
29 | contacts,
30 | establishment_application_date: establishmentApplicationDate,
31 | establishment_approval_date: establishmentApprovalDate,
32 | registration_application_date: registrationApplicationDate,
33 | registration_approved_date: registrationApprovedDate
34 | },
35 | token = null
36 | } = props;
37 |
38 | const router = useRouter();
39 | const { updateToken } = useAuthContext(token);
40 |
41 | useEffect(() => ReactGA.pageview('/parties/[party]'), []);
42 | useEffect(() => {
43 | if (token) {
44 | updateToken(token);
45 | }
46 | }, [token]);
47 |
48 | return (
49 |
50 |
51 | {nameBurmese} | mVoter 2020
52 |
53 |
54 |
55 |
59 |
60 |
61 |
65 |
66 |
67 |
71 |
72 |
76 |
77 |
78 |
79 |
82 |
83 |
84 |
85 |
86 |

91 |
92 |
93 |
{nameBurmese}
94 |
{nameEnglish}
95 | {abbreviation && (
96 |
{abbreviation}
97 | )}
98 |
{region}
99 |
105 | ပါတီ မူဝါဒ
106 |
107 |
108 |
109 |
110 |
111 |
112 |

113 |
114 |
ပါတီအမှတ်စဥ်
115 |
116 | {myanmarNumbers(id, 'my')}
117 |
118 |
119 |
120 |
121 | {leadership.length > 0 && (
122 | <>
123 |
124 | ပါတီဥက္ကဋ္ဌနှင့် ဗဟိုအလုပ်အမှုဆောင်များ
125 |
126 |
127 | {leadership.join('၊ ')}
128 |
129 | >
130 | )}
131 |
132 |
133 |
လျှောက်ထားစဥ် ပါတီအင်အား
134 |
135 | {myanmarNumbers(memberCount, 'my')}
136 |
137 |
138 |
139 |
ပါတီရုံးချုပ်
140 |
{headquarterAddress}
141 |
142 |
143 |
ဆက်သွယ်ရန်
144 |
{contacts.join('၊ ')}
145 |
146 |
147 | {establishmentApplicationDate && (
148 |
149 |
150 | {establishmentApplicationDate}
151 |
152 |
153 | ပါတီ တည်ထောင်ခွင့်လျှောက်ထားသည်
154 |
155 |
156 | )}
157 | {establishmentApprovalDate && (
158 |
159 |
160 | {establishmentApprovalDate}
161 |
162 |
163 | ပါတီ တည်ထောင်ခွင့် ရရှိသည်
164 |
165 |
166 | )}
167 | {registrationApplicationDate && (
168 |
169 |
170 | {registrationApplicationDate}
171 |
172 |
173 | ပါတီအဖြစ် မှတ်ပုံတင်ခွင့် လျှောက်ထားသည်
174 |
175 |
176 | )}
177 | {registrationApprovedDate && (
178 |
179 |
180 | {registrationApprovedDate}
181 |
182 |
183 | ပါတီအဖြစ် မှတ်ပုံတင်ခွင့် ရရှိသည်
184 |
185 |
186 | )}
187 |
188 |
189 |
190 |
191 |
192 | );
193 | };
194 |
195 | export async function getServerSideProps(context) {
196 | const { params } = context;
197 |
198 | const cookies = nookies.get(context);
199 | const api = new MaePaySohAPI(cookies.token);
200 | const response = await api.getPartyById(params.party);
201 |
202 | const { data, token } = response.data;
203 | // expand everything inside data attributes to primary object
204 | return {
205 | props: {
206 | party: {
207 | ...data,
208 | ...data.attributes
209 | },
210 | ...(token && { token })
211 | }
212 | };
213 | }
214 |
215 | export default Party;
216 |
--------------------------------------------------------------------------------
/pages/parties/index.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import ReactGA from 'react-ga';
3 | import Head from 'next/head';
4 | import Link from 'next/link';
5 | import InfiniteScroll from 'react-infinite-scroll-component';
6 |
7 | import useAPI from '../../hooks/useAPI';
8 | import Layout from '../../components/Layout/Layout';
9 | import AppHeader from '../../components/Layout/AppHeader/AppHeader';
10 | import Button from '../../components/Common/Button/Button';
11 | import PartyList from '../../components/Parties/PartyList/PartyList';
12 |
13 | import './parties.module.scss';
14 |
15 | const Parties = () => {
16 | // Inject AJAX call on first load
17 | const [parties, setParties] = useState([]);
18 | const [totalCount, setTotalCount] = useState(0);
19 | const [page, setPage] = useState(1);
20 | const [, fetchData] = useAPI();
21 |
22 | async function fetchParties(pageToLoad = 1) {
23 | try {
24 | const data = await fetchData('/api/parties', {
25 | page: pageToLoad,
26 | item_per_page: 25
27 | });
28 |
29 | return data;
30 | } catch (error) {
31 | return error;
32 | }
33 | }
34 |
35 | useEffect(() => {
36 | // initial load
37 | ReactGA.pageview(window.location.pathname);
38 |
39 | fetchParties()
40 | .then(result => {
41 | setParties(result.data);
42 | setTotalCount(result.pagination.total);
43 | })
44 | .catch(error => error);
45 | }, []);
46 |
47 | async function loadMoreParties() {
48 | const nextPage = page + 1;
49 | fetchParties(nextPage)
50 | .then(result => setParties(parties.concat(result.data)))
51 | .then(() => setPage(nextPage))
52 | .catch(error => error);
53 | }
54 |
55 | return (
56 |
57 |
58 | ပါတီများ | mVoter 2020
59 |
60 |
61 | ပါတီများ
62 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | flag
78 |
79 |
80 | (၂၀၂၀) ခုနှစ် အထွေထွေ ရွေးကောက်ပွဲတွင် ဝင်ရောက်ယှဥ်ပြိုင်မည့်
81 | နိုင်ငံရေးပါတီ (၉၁) ခု
82 |
83 |
84 |
85 |
86 |
91 |
92 |
93 |
94 |
95 | );
96 | };
97 |
98 | export default Parties;
99 |
--------------------------------------------------------------------------------
/pages/parties/parties.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/partials/colors';
2 | @import '../../styles/partials/extends';
3 |
4 | .Parties {
5 | @extend %mobile-vert-rules;
6 | &__Container {
7 | min-height: 100vh;
8 | }
9 | &__Wrapper {
10 | padding: 5px 10px;
11 | }
12 | &__infoHeader {
13 | display: flex;
14 | padding: 10px 15px;
15 | border-radius: 6px;
16 | background-color: rgba($color: $primary-color, $alpha: 0.25);
17 | font-weight: 600;
18 | font-size: 0.875rem;
19 | .icon-blk {
20 | position: relative;
21 | width: 30px;
22 | height: 20px;
23 | margin-top: 4px;
24 | margin-right: 8px;
25 | background-color: $primary-color;
26 | border-radius: 100%;
27 | .flag {
28 | position: absolute;
29 | left: 50%;
30 | top: 50%;
31 | transform: translateX(-50%) translateY(-50%);
32 | font-size: 1rem;
33 | color: #ffffff;
34 | }
35 | }
36 | p {
37 | margin: 0;
38 | }
39 | }
40 | &__buttonGroup {
41 | color: $primary-color;
42 | }
43 | ul {
44 | overflow: hidden;
45 | }
46 | }
47 |
48 | @media (min-width: 1200px) {
49 | .Parties {
50 | &__infoHeader {
51 | .icon-blk {
52 | height: 30px;
53 | margin-top: 0;
54 | }
55 | }
56 | }
57 | }
--------------------------------------------------------------------------------
/pages/parties/party.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/partials/colors';
2 |
3 | .Party {
4 | line-height: 25px;
5 | &__headInfo {
6 | display: flex;
7 | justify-content: center;
8 | text-align: center;
9 | padding-top: 5px;
10 | margin-bottom: 12px;
11 | }
12 | &__seal {
13 | width: 150px;
14 | height: auto;
15 | display: block;
16 | margin-left: auto;
17 | margin-right: auto;
18 | }
19 | &__flag {
20 | display: inline-block;
21 | width: auto;
22 | height: 40px;
23 | background-position: center center;
24 | background-repeat: no-repeat;
25 | background-size: contain;
26 | }
27 | &__title {
28 | font-size: 1.25rem;
29 | }
30 | &__engTitle {
31 | font-size: 1.25rem;
32 | font-weight: 400;
33 | margin: 0;
34 | }
35 | &__abbreviation {
36 | font-size: 1.5rem;
37 | font-weight: 600;
38 | }
39 | &__partyPolicy {
40 | background-color: $primary-color;
41 | color: #ffffff;
42 | font-size: 0.85rem;
43 | border-radius: 30px;
44 | padding: 5px 10px;
45 | box-shadow: 0px 2px 4px rgba($color: $primary-color, $alpha: 0.25);
46 | transition: all 0.2s linear;
47 | &:hover {
48 | opacity: 0.95;
49 | box-shadow: 0px 2px 6px rgba($color: $primary-color, $alpha: 1);
50 | }
51 | }
52 | &__region {
53 | font-size: 0.875rem;
54 | }
55 | &__info {
56 | margin-bottom: 12px;
57 | }
58 | &__flagInfo {
59 | display: flex;
60 | align-items: center;
61 | }
62 | &__infoLabel {
63 | font-size: 0.875rem;
64 | color: rgba($color: #000000, $alpha: 0.85);
65 | }
66 | &__infoAnswer {
67 | font-size: 1rem;
68 | }
69 | &__timeline {
70 | background: rgba($color: $primary-color, $alpha: 0.25);
71 | padding: 5px;
72 | .timeline-item {
73 | margin: 5px 0;
74 | .date {
75 | font-weight: 600;
76 | font-size: 1.15rem;
77 | }
78 | .time {
79 | padding-left: 0;
80 | }
81 | &:last-child { // Tricky hack
82 | .description {
83 | &::before {
84 | display: none;
85 | }
86 | }
87 | }
88 | .description {
89 | position: relative;
90 | &::before {
91 | content: '';
92 | display: inline-block;
93 | position: absolute;
94 | top: 14px;
95 | left: 0px;
96 | width: 2px;
97 | height: 100%;
98 | background-color: $primary-color;
99 | }
100 | &::after {
101 | content: '';
102 | display: inline-block;
103 | position: absolute;
104 | border-radius: 100%;
105 | top: 8px;
106 | left: -5px;
107 | width: 12px;
108 | height: 12px;
109 | background-color: $primary-color;
110 | }
111 | }
112 | }
113 | }
114 | }
115 |
116 | @media (min-width: 1200px) {
117 | .Party {
118 | &__headInfo {
119 | text-align: left;
120 | margin-bottom: 15px;
121 | }
122 | &__infoLabel {
123 | color: $primary-color;
124 | }
125 | &__timeline {
126 | padding: 5px 0px 15px 0px;
127 | }
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/pages/parties/search.js:
--------------------------------------------------------------------------------
1 | import SearchPage from '../../components/Search/SearchPage';
2 | import PartyList from '../../components/Parties/PartyList/PartyList';
3 |
4 | const FaqSearch = () => {
5 | // Why pass endpoint you may ask? Answer: I am lazy.
6 | return (
7 |
14 |
15 |
16 | );
17 | };
18 |
19 | export default FaqSearch;
20 |
--------------------------------------------------------------------------------
/public/UEC.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PopStackHack/mVoterWeb/71a6bf3da114a0688689dc93658576c8c806afe5/public/UEC.png
--------------------------------------------------------------------------------
/public/about/TAFVLogo_RGB.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PopStackHack/mVoterWeb/71a6bf3da114a0688689dc93658576c8c806afe5/public/about/TAFVLogo_RGB.jpg
--------------------------------------------------------------------------------
/public/about/UEC_logo_mm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PopStackHack/mVoterWeb/71a6bf3da114a0688689dc93658576c8c806afe5/public/about/UEC_logo_mm.png
--------------------------------------------------------------------------------
/public/about/eu_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PopStackHack/mVoterWeb/71a6bf3da114a0688689dc93658576c8c806afe5/public/about/eu_logo.png
--------------------------------------------------------------------------------
/public/about/eu_logo.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PopStackHack/mVoterWeb/71a6bf3da114a0688689dc93658576c8c806afe5/public/about/eu_logo.webp
--------------------------------------------------------------------------------
/public/about/idea_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PopStackHack/mVoterWeb/71a6bf3da114a0688689dc93658576c8c806afe5/public/about/idea_logo.png
--------------------------------------------------------------------------------
/public/about/idea_logo.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PopStackHack/mVoterWeb/71a6bf3da114a0688689dc93658576c8c806afe5/public/about/idea_logo.webp
--------------------------------------------------------------------------------
/public/about/maepaysoh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PopStackHack/mVoterWeb/71a6bf3da114a0688689dc93658576c8c806afe5/public/about/maepaysoh.png
--------------------------------------------------------------------------------
/public/about/mvoter2020_new_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PopStackHack/mVoterWeb/71a6bf3da114a0688689dc93658576c8c806afe5/public/about/mvoter2020_new_logo.png
--------------------------------------------------------------------------------
/public/about/popstack_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PopStackHack/mVoterWeb/71a6bf3da114a0688689dc93658576c8c806afe5/public/about/popstack_logo.png
--------------------------------------------------------------------------------
/public/about/popstack_logo.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PopStackHack/mVoterWeb/71a6bf3da114a0688689dc93658576c8c806afe5/public/about/popstack_logo.webp
--------------------------------------------------------------------------------
/public/about/step_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PopStackHack/mVoterWeb/71a6bf3da114a0688689dc93658576c8c806afe5/public/about/step_logo.png
--------------------------------------------------------------------------------
/public/about/step_logo.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PopStackHack/mVoterWeb/71a6bf3da114a0688689dc93658576c8c806afe5/public/about/step_logo.webp
--------------------------------------------------------------------------------
/public/about/taf_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PopStackHack/mVoterWeb/71a6bf3da114a0688689dc93658576c8c806afe5/public/about/taf_logo.png
--------------------------------------------------------------------------------
/public/about/taf_logo.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PopStackHack/mVoterWeb/71a6bf3da114a0688689dc93658576c8c806afe5/public/about/taf_logo.webp
--------------------------------------------------------------------------------
/public/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PopStackHack/mVoterWeb/71a6bf3da114a0688689dc93658576c8c806afe5/public/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PopStackHack/mVoterWeb/71a6bf3da114a0688689dc93658576c8c806afe5/public/android-chrome-512x512.png
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PopStackHack/mVoterWeb/71a6bf3da114a0688689dc93658576c8c806afe5/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/ballot_stack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PopStackHack/mVoterWeb/71a6bf3da114a0688689dc93658576c8c806afe5/public/ballot_stack.png
--------------------------------------------------------------------------------
/public/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #da532c
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PopStackHack/mVoterWeb/71a6bf3da114a0688689dc93658576c8c806afe5/public/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PopStackHack/mVoterWeb/71a6bf3da114a0688689dc93658576c8c806afe5/public/favicon-32x32.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PopStackHack/mVoterWeb/71a6bf3da114a0688689dc93658576c8c806afe5/public/favicon.ico
--------------------------------------------------------------------------------
/public/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PopStackHack/mVoterWeb/71a6bf3da114a0688689dc93658576c8c806afe5/public/mstile-150x150.png
--------------------------------------------------------------------------------
/public/mvoter2020-transparent-vertical.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PopStackHack/mVoterWeb/71a6bf3da114a0688689dc93658576c8c806afe5/public/mvoter2020-transparent-vertical.png
--------------------------------------------------------------------------------
/public/prohibition_signs/no_photo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PopStackHack/mVoterWeb/71a6bf3da114a0688689dc93658576c8c806afe5/public/prohibition_signs/no_photo.png
--------------------------------------------------------------------------------
/public/prohibition_signs/no_recording.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PopStackHack/mVoterWeb/71a6bf3da114a0688689dc93658576c8c806afe5/public/prohibition_signs/no_recording.png
--------------------------------------------------------------------------------
/public/prohibition_signs/no_selfie.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PopStackHack/mVoterWeb/71a6bf3da114a0688689dc93658576c8c806afe5/public/prohibition_signs/no_selfie.png
--------------------------------------------------------------------------------
/public/prohibition_signs/no_video.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PopStackHack/mVoterWeb/71a6bf3da114a0688689dc93658576c8c806afe5/public/prohibition_signs/no_video.png
--------------------------------------------------------------------------------
/public/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
46 |
--------------------------------------------------------------------------------
/public/site.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mVoter 2020",
3 | "short_name": "mVoter 2020",
4 | "icons": [
5 | {
6 | "src": "/android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "/android-chrome-512x512.png",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | }
15 | ],
16 | "theme_color": "#ffffff",
17 | "background_color": "#ffffff",
18 | "display": "standalone"
19 | }
20 |
--------------------------------------------------------------------------------
/public/zeit.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/styles/base.scss:
--------------------------------------------------------------------------------
1 | @import '../styles/partials/colors';
2 |
3 | .ReactModal__Overlay {
4 | opacity: 0;
5 | transition: all 200ms ease-in-out;
6 | }
7 |
8 | .ReactModal__Overlay--after-open {
9 | opacity: 1;
10 | }
11 |
12 | .ReactModal__Overlay--before-close {
13 | opacity: 0;
14 | }
15 |
16 | .vert-flex-center {
17 | display: flex;
18 | align-items: center;
19 | }
20 |
21 | .text-bold {
22 | font-weight: 700;
23 | }
24 |
25 | *:disabled {
26 | cursor: not-allowed !important;
27 | }
28 |
29 | a.no-style {
30 | color: inherit !important;
31 | &:hover {
32 | text-decoration: none !important;
33 | }
34 | }
35 |
36 | p {
37 | line-height: 26px;
38 | }
39 |
40 | .cursor-pointer {
41 | cursor: pointer;
42 | }
43 |
44 | .color-primary {
45 | color: $primary-color;
46 | * {
47 | color: $primary-color;
48 | }
49 | }
50 |
51 | .color-white {
52 | color: $white-color;
53 | * {
54 | color: $white-color;
55 | }
56 | }
57 |
58 | .color-danger {
59 | color: $danger-color;
60 | * {
61 | color: $danger-color;
62 | }
63 | }
64 |
65 | .no-padding {
66 | padding: 0 !important;
67 | }
68 |
69 | .no-margin {
70 | margin: 0 !important;
71 | }
72 |
73 | .no-text-decor {
74 | text-decoration: none !important;
75 | }
76 |
77 | .vert-align-middle {
78 | vertical-align: middle;
79 | }
80 |
81 | a, select {
82 | cursor: pointer;
83 | }
84 |
85 | .box-hover {
86 | transition: all 0.2s linear !important;
87 | &:hover, &:active {
88 | border-color: rgba($color: $primary-color, $alpha: 0.5);
89 | // box-shadow: 0px 2px 6px rgba($color: #000000, $alpha: 0.2);
90 | }
91 | }
92 |
93 | .loader {
94 | color: $primary-color;
95 | animation: spin 0.7s linear infinite;
96 | }
97 |
98 | @keyframes spin {
99 | 0% {
100 | transform: rotate(0deg);
101 | }
102 | 100% {
103 | transform: rotate(360deg)
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/styles/normalize.scss:
--------------------------------------------------------------------------------
1 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
2 |
3 | /* Document
4 | ========================================================================== */
5 |
6 | /**
7 | * 1. Correct the line height in all browsers.
8 | * 2. Prevent adjustments of font size after orientation changes in iOS.
9 | */
10 |
11 | html {
12 | line-height: 1.15; /* 1 */
13 | -webkit-text-size-adjust: 100%; /* 2 */
14 | }
15 |
16 | /* Sections
17 | ========================================================================== */
18 |
19 | /**
20 | * Remove the margin in all browsers.
21 | */
22 |
23 | body {
24 | margin: 0;
25 | }
26 |
27 | /**
28 | * Render the `main` element consistently in IE.
29 | */
30 |
31 | main {
32 | display: block;
33 | }
34 |
35 | /**
36 | * Correct the font size and margin on `h1` elements within `section` and
37 | * `article` contexts in Chrome, Firefox, and Safari.
38 | */
39 |
40 | h1 {
41 | font-size: 2em;
42 | margin: 0.67em 0;
43 | }
44 |
45 | /* Grouping content
46 | ========================================================================== */
47 |
48 | /**
49 | * 1. Add the correct box sizing in Firefox.
50 | * 2. Show the overflow in Edge and IE.
51 | */
52 |
53 | hr {
54 | box-sizing: content-box; /* 1 */
55 | height: 0; /* 1 */
56 | overflow: visible; /* 2 */
57 | }
58 |
59 | /**
60 | * 1. Correct the inheritance and scaling of font size in all browsers.
61 | * 2. Correct the odd `em` font sizing in all browsers.
62 | */
63 |
64 | pre {
65 | font-family: monospace, monospace; /* 1 */
66 | font-size: 1em; /* 2 */
67 | }
68 |
69 | /* Text-level semantics
70 | ========================================================================== */
71 |
72 | /**
73 | * Remove the gray background on active links in IE 10.
74 | */
75 |
76 | a {
77 | background-color: transparent;
78 | }
79 |
80 | /**
81 | * 1. Remove the bottom border in Chrome 57-
82 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
83 | */
84 |
85 | abbr[title] {
86 | border-bottom: none; /* 1 */
87 | text-decoration: underline; /* 2 */
88 | text-decoration: underline dotted; /* 2 */
89 | }
90 |
91 | /**
92 | * Add the correct font weight in Chrome, Edge, and Safari.
93 | */
94 |
95 | b,
96 | strong {
97 | font-weight: bolder;
98 | }
99 |
100 | /**
101 | * 1. Correct the inheritance and scaling of font size in all browsers.
102 | * 2. Correct the odd `em` font sizing in all browsers.
103 | */
104 |
105 | code,
106 | kbd,
107 | samp {
108 | font-family: monospace, monospace; /* 1 */
109 | font-size: 1em; /* 2 */
110 | }
111 |
112 | /**
113 | * Add the correct font size in all browsers.
114 | */
115 |
116 | small {
117 | font-size: 80%;
118 | }
119 |
120 | /**
121 | * Prevent `sub` and `sup` elements from affecting the line height in
122 | * all browsers.
123 | */
124 |
125 | sub,
126 | sup {
127 | font-size: 75%;
128 | line-height: 0;
129 | position: relative;
130 | vertical-align: baseline;
131 | }
132 |
133 | sub {
134 | bottom: -0.25em;
135 | }
136 |
137 | sup {
138 | top: -0.5em;
139 | }
140 |
141 | /* Embedded content
142 | ========================================================================== */
143 |
144 | /**
145 | * Remove the border on images inside links in IE 10.
146 | */
147 |
148 | img {
149 | border-style: none;
150 | }
151 |
152 | /* Forms
153 | ========================================================================== */
154 |
155 | /**
156 | * 1. Change the font styles in all browsers.
157 | * 2. Remove the margin in Firefox and Safari.
158 | */
159 |
160 | button,
161 | input,
162 | optgroup,
163 | select,
164 | textarea {
165 | font-family: inherit; /* 1 */
166 | font-size: 100%; /* 1 */
167 | line-height: 1.15; /* 1 */
168 | margin: 0; /* 2 */
169 | }
170 |
171 | /**
172 | * Show the overflow in IE.
173 | * 1. Show the overflow in Edge.
174 | */
175 |
176 | button,
177 | input { /* 1 */
178 | overflow: visible;
179 | }
180 |
181 | /**
182 | * Remove the inheritance of text transform in Edge, Firefox, and IE.
183 | * 1. Remove the inheritance of text transform in Firefox.
184 | */
185 |
186 | button,
187 | select { /* 1 */
188 | text-transform: none;
189 | }
190 |
191 | /**
192 | * Correct the inability to style clickable types in iOS and Safari.
193 | */
194 |
195 | button,
196 | [type="button"],
197 | [type="reset"],
198 | [type="submit"] {
199 | -webkit-appearance: button;
200 | }
201 |
202 | /**
203 | * Remove the inner border and padding in Firefox.
204 | */
205 |
206 | button::-moz-focus-inner,
207 | [type="button"]::-moz-focus-inner,
208 | [type="reset"]::-moz-focus-inner,
209 | [type="submit"]::-moz-focus-inner {
210 | border-style: none;
211 | padding: 0;
212 | }
213 |
214 | /**
215 | * Restore the focus styles unset by the previous rule.
216 | */
217 |
218 | button:-moz-focusring,
219 | [type="button"]:-moz-focusring,
220 | [type="reset"]:-moz-focusring,
221 | [type="submit"]:-moz-focusring {
222 | outline: 1px dotted ButtonText;
223 | }
224 |
225 | /**
226 | * Correct the padding in Firefox.
227 | */
228 |
229 | fieldset {
230 | padding: 0.35em 0.75em 0.625em;
231 | }
232 |
233 | /**
234 | * 1. Correct the text wrapping in Edge and IE.
235 | * 2. Correct the color inheritance from `fieldset` elements in IE.
236 | * 3. Remove the padding so developers are not caught out when they zero out
237 | * `fieldset` elements in all browsers.
238 | */
239 |
240 | legend {
241 | box-sizing: border-box; /* 1 */
242 | color: inherit; /* 2 */
243 | display: table; /* 1 */
244 | max-width: 100%; /* 1 */
245 | padding: 0; /* 3 */
246 | white-space: normal; /* 1 */
247 | }
248 |
249 | /**
250 | * Add the correct vertical alignment in Chrome, Firefox, and Opera.
251 | */
252 |
253 | progress {
254 | vertical-align: baseline;
255 | }
256 |
257 | /**
258 | * Remove the default vertical scrollbar in IE 10+.
259 | */
260 |
261 | textarea {
262 | overflow: auto;
263 | }
264 |
265 | /**
266 | * 1. Add the correct box sizing in IE 10.
267 | * 2. Remove the padding in IE 10.
268 | */
269 |
270 | [type="checkbox"],
271 | [type="radio"] {
272 | box-sizing: border-box; /* 1 */
273 | padding: 0; /* 2 */
274 | }
275 |
276 | /**
277 | * Correct the cursor style of increment and decrement buttons in Chrome.
278 | */
279 |
280 | [type="number"]::-webkit-inner-spin-button,
281 | [type="number"]::-webkit-outer-spin-button {
282 | height: auto;
283 | }
284 |
285 | /**
286 | * 1. Correct the odd appearance in Chrome and Safari.
287 | * 2. Correct the outline style in Safari.
288 | */
289 |
290 | [type="search"] {
291 | -webkit-appearance: textfield; /* 1 */
292 | outline-offset: -2px; /* 2 */
293 | }
294 |
295 | /**
296 | * Remove the inner padding in Chrome and Safari on macOS.
297 | */
298 |
299 | [type="search"]::-webkit-search-decoration {
300 | -webkit-appearance: none;
301 | }
302 |
303 | /**
304 | * 1. Correct the inability to style clickable types in iOS and Safari.
305 | * 2. Change font properties to `inherit` in Safari.
306 | */
307 |
308 | ::-webkit-file-upload-button {
309 | -webkit-appearance: button; /* 1 */
310 | font: inherit; /* 2 */
311 | }
312 |
313 | /* Interactive
314 | ========================================================================== */
315 |
316 | /*
317 | * Add the correct display in Edge, IE 10+, and Firefox.
318 | */
319 |
320 | details {
321 | display: block;
322 | }
323 |
324 | /*
325 | * Add the correct display in all browsers.
326 | */
327 |
328 | summary {
329 | display: list-item;
330 | }
331 |
332 | /* Misc
333 | ========================================================================== */
334 |
335 | /**
336 | * Add the correct display in IE 10+.
337 | */
338 |
339 | template {
340 | display: none;
341 | }
342 |
343 | /**
344 | * Add the correct display in IE 10.
345 | */
346 |
347 | [hidden] {
348 | display: none;
349 | }
--------------------------------------------------------------------------------
/styles/partials/_colors.scss:
--------------------------------------------------------------------------------
1 | $primary-color: #2071DA;
2 | $white-color: #FFFFFF;
3 | $grey-color: rgba($color: #000000, $alpha: 0.5);
4 | $danger-color: #942d2d;
--------------------------------------------------------------------------------
/styles/partials/_extends.scss:
--------------------------------------------------------------------------------
1 | @import './colors';
2 |
3 | %mobile-vert-rules {
4 | overflow-x: hidden;
5 | }
6 |
7 | %button-hover {
8 | background-color: rgba($primary-color, 0.85);
9 | }
--------------------------------------------------------------------------------
/styles/partials/_fontsizes.scss:
--------------------------------------------------------------------------------
1 | $big: 1.5rem;
2 | $medium: 1.1rem;
3 | $normal: 1rem;
4 | $small: 0.8rem;
--------------------------------------------------------------------------------
/utils/constants.js:
--------------------------------------------------------------------------------
1 | export const FAQ_CATEGORY = Object.freeze({
2 | voter_list: 'မဲဆန္ဒရှင်စာရင်း',
3 | candidate: 'ကိုယ်စားလှယ်လောင်း',
4 | international_observer: 'စောင့်ကြည့်လေ့လာခြင်း',
5 | diplomatic: 'သံတမန်ရေးရာ',
6 | conflict_resolution: 'အငြင်းပွားမှုများဖြေရှင်းခြင်း',
7 | mediation_committees: 'စေ့စပ်ညှိနှိုင်းရေးကော်မတီများ'
8 | });
9 |
10 | export const HOUSES = Object.freeze({
11 | amyota: 'အမျိုးသားလွှတ်တော်',
12 | pyithu: 'ပြည်ထောင်စုလွှတ်တော်',
13 | state: 'တိုင်း/ပြည်နယ်လွှတ်တော်'
14 | });
15 |
16 | export const BALLOT_CATEGORIES = Object.freeze([
17 | { label: 'မဲနမူနာများ', value: 'normal' },
18 | { label: 'ကြိုတင်မဲနမူနာများ', value: 'advanced' }
19 | ]);
20 |
21 | export const customSelectStyle = Object.freeze({
22 | option: styles => ({
23 | ...styles,
24 | cursor: 'pointer'
25 | }),
26 | control: styles => ({
27 | ...styles,
28 | cursor: 'pointer'
29 | })
30 | });
31 |
32 | export const VOTER_LIST_LINKS = [
33 | {
34 | region: 'တိုင်းဒေသကြီး/ပြည်နယ် အားလုံး',
35 | url: 'https://findyourpollingstation.uec.gov.mm'
36 | },
37 | { region: 'ကချင်ပြည်နယ်', url: 'http://bit.ly/kachin-voters' },
38 | { region: 'ကယားပြည်နယ်', url: 'http://bit.ly/kayah-voters' },
39 | { region: 'ကရင်ပြည်နယ်', url: 'http://bit.ly/kayin-voters' },
40 | { region: 'ချင်းပြည်နယ်', url: 'http://bit.ly/chin-voters' },
41 | { region: 'စစ်ကိုင်းတိုင်းဒေသကြီး', url: 'http://bit.ly/sagaing-voters' },
42 | { region: 'တနင်္သာရီတိုင်းဒေသကြီး', url: 'http://bit.ly/tanintharyi-voters' },
43 | { region: 'ပဲခူးတိုင်းဒေသကြီး', url: 'http://bit.ly/bago-voters' },
44 | { region: 'မကွေးတိုင်းဒေသကြီး', url: 'http://bit.ly/magway-voters' },
45 | { region: 'မန္တလေးတိုင်းဒေသကြီး', url: 'http://bit.ly/mandalay-voters' },
46 | { region: 'မွန်ပြည်နယ်', url: 'http://bit.ly/mon-voters' },
47 | { region: 'ရခိုင်ပြည်နယ်', url: 'http://bit.ly/rakhine-voters' },
48 | { region: 'ရန်ကုန်တိုင်းဒေသကြီး', url: 'http://bit.ly/yangon-voters' },
49 | { region: 'ရှမ်းပြည်နယ်', url: 'http://bit.ly/shan-voters' },
50 | { region: 'ဧရာဝတီတိုင်းဒေသကြီး', url: 'http://bit.ly/ayarwaddy-voters' },
51 | { region: 'ပြည်ထောင်စုနယ်မြေ', url: 'http://bit.ly/naypyitaw-voters' }
52 | ];
53 |
54 | export const LOCALSTORAGE_KEYS = {
55 | STATE_REGION: 'stateRegion',
56 | TOWNSHIP: 'township',
57 | WARD_VILLAGE: 'wardVillage',
58 | CANDIDATE_PROMPT_DISMISS: 'candidatePromptDismiss'
59 | };
60 |
--------------------------------------------------------------------------------
/utils/helpers.js:
--------------------------------------------------------------------------------
1 | import moment from 'moment';
2 | import { LOCALSTORAGE_KEYS } from './constants';
3 |
4 | export function hasFullLocation() {
5 | return (
6 | localStorage.getItem(LOCALSTORAGE_KEYS.STATE_REGION) &&
7 | localStorage.getItem(LOCALSTORAGE_KEYS.TOWNSHIP) &&
8 | localStorage.getItem(LOCALSTORAGE_KEYS.WARD_VILLAGE)
9 | );
10 | }
11 |
12 | // https://chrisboakes.com/how-a-javascript-debounce-function-works/ <- Code taken from this article
13 | export function debounce(callback, wait) {
14 | let timeout;
15 | return (...args) => {
16 | const context = this;
17 | clearTimeout(timeout);
18 | timeout = setTimeout(() => callback.apply(context, args), wait);
19 | };
20 | }
21 |
22 | export function handleCountdown(cb) {
23 | const electionDay = moment('2020-11-08').utcOffset('+0630');
24 | const nowEpoch = moment().unix() * 1000;
25 |
26 | const electionDayStartEpoch =
27 | electionDay
28 | .clone()
29 | .startOf('day')
30 | .unix() * 1000;
31 |
32 | const pollingStationOpenEpoch =
33 | electionDay
34 | .clone()
35 | .set({ hours: 6 })
36 | .unix() * 1000;
37 |
38 | const pollingStationCloseEpoch =
39 | electionDay
40 | .clone()
41 | .set({ hours: 16 })
42 | .unix() * 1000;
43 |
44 | // Check if it's still day diff >= 1
45 | const dayDiff = Math.ceil(
46 | moment.duration(moment(electionDayStartEpoch).diff(nowEpoch)).asDays()
47 | );
48 |
49 | if (dayDiff >= 1) {
50 | return cb(dayDiff, 'day');
51 | }
52 |
53 | // Election over
54 | if (dayDiff >= 0 && nowEpoch > pollingStationCloseEpoch) {
55 | return cb(null, 'over');
56 | }
57 |
58 | setInterval(() => {
59 | const currentIntervalEpoch = moment().unix() * 1000;
60 | // Check if pollion station has closed
61 | if (currentIntervalEpoch > pollingStationCloseEpoch) {
62 | return cb(null, 'over');
63 | }
64 |
65 | // Check if current interval epoch has passed election day
66 | // To show မဲရုံများဖွင့်ချိန်
67 | if (
68 | currentIntervalEpoch > electionDayStartEpoch &&
69 | currentIntervalEpoch < pollingStationOpenEpoch
70 | ) {
71 | const diffTime = pollingStationOpenEpoch - currentIntervalEpoch;
72 | const duration = moment.duration(diffTime, 'milliseconds');
73 | const cd = moment.duration(duration - 1000, 'milliseconds');
74 |
75 | const hours = cd.hours() < 10 ? `0${cd.hours()}` : cd.hours();
76 | const minutes = cd.minutes() < 10 ? `0${cd.minutes()}` : cd.minutes();
77 | const seconds = cd.seconds() < 10 ? `0${cd.seconds()}` : cd.seconds();
78 |
79 | return cb(`${hours}-${minutes}-${seconds}`, 'start');
80 | }
81 |
82 | // Check if pollion station has opened
83 | // To show မဲရုံများပိတ်ချိန်
84 | if (
85 | currentIntervalEpoch > electionDayStartEpoch &&
86 | currentIntervalEpoch > pollingStationOpenEpoch &&
87 | currentIntervalEpoch < pollingStationCloseEpoch
88 | ) {
89 | const diffTime = pollingStationCloseEpoch - currentIntervalEpoch;
90 | const duration = moment.duration(diffTime, 'milliseconds');
91 | const cd = moment.duration(duration - 1000, 'milliseconds');
92 |
93 | const hours = cd.hours() < 10 ? `0${cd.hours()}` : cd.hours();
94 | const minutes = cd.minutes() < 10 ? `0${cd.minutes()}` : cd.minutes();
95 | const seconds = cd.seconds() < 10 ? `0${cd.seconds()}` : cd.seconds();
96 |
97 | return cb(`${hours}-${minutes}-${seconds}`, 'close');
98 | }
99 |
100 | return true;
101 | }, 1000);
102 |
103 | return true;
104 | }
105 |
--------------------------------------------------------------------------------
/utils/textFormatter.js:
--------------------------------------------------------------------------------
1 | import myanmarNumbers from 'myanmar-numbers';
2 | import moment from 'moment';
3 | import { HOUSES, FAQ_CATEGORY } from './constants';
4 |
5 | export function formatHouse(house) {
6 | return HOUSES[house];
7 | }
8 |
9 | export function formatConstituency(stateRegion, constituencyName) {
10 | return `${stateRegion} ${constituencyName
11 | .split(' ')
12 | .slice(1)
13 | .join('')}`;
14 | }
15 |
16 | export function formatFAQCategory(category) {
17 | return FAQ_CATEGORY[category];
18 | }
19 |
20 | // Nasty format function to quickly achieve our MM locale result
21 | // dateStr will be in YYYY-MM-DD format
22 | export function formatPublishDateToMMLocale(dateStr) {
23 | const publishedTime = moment(dateStr);
24 | const date = dateStr.split('-')[2]; // It has zero paddings so we cheat.
25 | const month = publishedTime.month(); // This is to access MM Locale months below
26 | const year = publishedTime.year();
27 | // Text data taken from https://gist.github.com/eimg/3f115b39fbc6c48976e6cc7af1b8ddf9
28 | const months = 'ဇန်နဝါရီ_ဖေဖော်ဝါရီ_မတ်_ဧပြီ_မေ_ဇွန်_ဇူလိုင်_သြဂုတ်_စက်တင်ဘာ_အောက်တိုဘာ_နိုဝင်ဘာ_ဒီဇင်ဘာ'.split(
29 | '_'
30 | );
31 | return `${myanmarNumbers(date, 'my')} ${months[month]} ${myanmarNumbers(
32 | year,
33 | 'my'
34 | )}`;
35 | }
36 |
37 | export function formatBirthDay(birthday) {
38 | if (!birthday) return '-';
39 | const strArr = birthday.split('-');
40 | return `${myanmarNumbers(strArr[2], 'my')}၊ ${myanmarNumbers(
41 | strArr[1],
42 | 'my'
43 | )}၊ ${myanmarNumbers(strArr[0], 'my')}`;
44 | }
45 |
--------------------------------------------------------------------------------