├── .eslintignore
├── Dockerfile
├── .prettierignore
├── static
├── favicon.ico
├── images
│ ├── cms_logo.png
│ └── icons
│ │ ├── icon-72x72.png
│ │ ├── icon-96x96.png
│ │ ├── icon-128x128.png
│ │ ├── icon-144x144.png
│ │ ├── icon-152x152.png
│ │ ├── icon-192x192.png
│ │ ├── icon-384x384.png
│ │ └── icon-512x512.png
└── manifest.json
├── .gitignore
├── .prettierrc
├── components
├── admin
│ ├── useModal.js
│ ├── alertComponent.js
│ └── addRemoveCard.js
├── loadingScreen.js
├── breadCrumbGenerator.js
├── titlebar.js
├── header.js
├── reset-password
│ └── resetForm.js
├── authRequired.js
├── links.js
├── login
│ └── loginForm.js
├── base.js
├── event
│ └── eventForm.js
├── account
│ ├── changePassword.js
│ └── basicSettings.js
├── register
│ └── registerForm.js
├── sidebar.js
└── blog
│ └── addBlog.js
├── pages
├── _app.js
├── index.js
├── _document.js
├── register.js
├── login.js
├── reset-password.js
├── calendar
│ └── create-event.js
├── logout.js
├── attendance
│ ├── stats.js
│ ├── live-report.js
│ ├── dashboard.js
│ └── daily-report.js
├── status-updates
│ ├── stats.js
│ ├── daily-report.js
│ ├── messages.js
│ └── individual-report.js
├── blog
│ └── create-blog.js
├── 404.js
├── account
│ └── settings.js
├── form
│ └── [id]
│ │ ├── index.js
│ │ └── entries.js
├── events
│ └── check-in.js
├── forms
│ └── view-forms.js
└── admin
│ └── manage-users.js
├── .gitlab-ci.yml
├── README.md
├── utils
├── queries.js
├── fileUpload.js
├── mutations.js
└── dataFetch.js
├── .eslintrc
├── next.config.js
├── modules
├── forms
│ ├── formFields.js
│ ├── entryDetails.js
│ └── fieldEditor.js
├── attendance
│ └── components
│ │ ├── ActiveStatusBar.js
│ │ ├── LiveReportCard.js
│ │ ├── Overview.js
│ │ ├── TrendGraph.js
│ │ ├── DailyReportCard.js
│ │ ├── TrendAttendanceGraph.js
│ │ └── Rankings.js
└── statusUpdates
│ └── components
│ ├── DailyStatusReportCard.js
│ ├── Overview.js
│ ├── StatusGraph.js
│ ├── TrendStatusGraph.js
│ └── Ranking.js
├── styles
└── styles.sass
└── package.json
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .next
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:10
2 | EXPOSE 3000
3 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | package-lock.json
2 | .next/
3 | node_modules/
4 |
--------------------------------------------------------------------------------
/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amfoss/web-app/HEAD/static/favicon.ico
--------------------------------------------------------------------------------
/static/images/cms_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amfoss/web-app/HEAD/static/images/cms_logo.png
--------------------------------------------------------------------------------
/static/images/icons/icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amfoss/web-app/HEAD/static/images/icons/icon-72x72.png
--------------------------------------------------------------------------------
/static/images/icons/icon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amfoss/web-app/HEAD/static/images/icons/icon-96x96.png
--------------------------------------------------------------------------------
/static/images/icons/icon-128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amfoss/web-app/HEAD/static/images/icons/icon-128x128.png
--------------------------------------------------------------------------------
/static/images/icons/icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amfoss/web-app/HEAD/static/images/icons/icon-144x144.png
--------------------------------------------------------------------------------
/static/images/icons/icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amfoss/web-app/HEAD/static/images/icons/icon-152x152.png
--------------------------------------------------------------------------------
/static/images/icons/icon-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amfoss/web-app/HEAD/static/images/icons/icon-192x192.png
--------------------------------------------------------------------------------
/static/images/icons/icon-384x384.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amfoss/web-app/HEAD/static/images/icons/icon-384x384.png
--------------------------------------------------------------------------------
/static/images/icons/icon-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amfoss/web-app/HEAD/static/images/icons/icon-512x512.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | node_modules
4 | .idea
5 | .next
6 | out
7 | package-lock.json
8 | public/workbox-*.js
9 | public/workbox-*.js.*
10 | public/sw.js.*
11 | public/sw.js
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "printWidth": 85,
4 | "arrowParens": "always",
5 | "semi": true,
6 | "tabWidth": 2,
7 | "bracketSpacing": true,
8 | "endOfLine": "auto"
9 | }
--------------------------------------------------------------------------------
/components/admin/useModal.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 |
3 | const useModal = () => {
4 | const [show, setShow] = useState(false);
5 |
6 | function toggle() {
7 | setShow(!show);
8 | }
9 |
10 | return {
11 | show,
12 | toggle,
13 | };
14 | };
15 |
16 | export default useModal;
17 |
--------------------------------------------------------------------------------
/pages/_app.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import '../styles/styles.sass';
4 | import 'bootstrap/dist/css/bootstrap.min.css';
5 | import 'react-quill/dist/quill.snow.css';
6 | import 'antd/dist/antd.min.css';
7 | import 'react-markdown-editor-lite/lib/index.css';
8 |
9 | function MyApp({ Component, pageProps }) {
10 | return ;
11 | }
12 |
13 | export default MyApp;
14 |
--------------------------------------------------------------------------------
/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | image: registry.gitlab.com/harshithpabbati/webapp:latest
2 |
3 | cache:
4 | key: ${CI_COMMIT_REF_SLUG}
5 | paths:
6 | - node_modules/
7 | - .next/cache/
8 |
9 | pages:
10 | stage: deploy
11 | script:
12 | - npm install
13 | - npm run export
14 | - rm -rf public
15 | - mv out public
16 |
17 | artifacts:
18 | paths:
19 | - public # mandatory, other folder won't work
20 | only:
21 | - master
22 |
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Base from '../components/base';
3 | import TitleBar from '../components/titlebar';
4 |
5 | const Index = () => {
6 | const routes = [
7 | {
8 | path: '/',
9 | name: 'Home',
10 | },
11 | ];
12 |
13 | return (
14 |
15 |
16 |
17 |
18 |
19 | );
20 | };
21 |
22 | export default Index;
23 |
--------------------------------------------------------------------------------
/components/loadingScreen.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactLoading from 'react-loading';
3 | import PropTypes from 'prop-types';
4 |
5 | const LoadingScreen = ({ text }) => {
6 | return (
7 |
8 |
9 |
{text}
10 |
11 | );
12 | };
13 |
14 | LoadingScreen.propTypes = {
15 | text: PropTypes.string,
16 | };
17 |
18 | export default LoadingScreen;
19 |
--------------------------------------------------------------------------------
/pages/_document.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Document, { Html, Head, Main, NextScript } from 'next/document';
3 |
4 | class MyDocument extends Document {
5 | static async getInitialProps(ctx) {
6 | const initialProps = await Document.getInitialProps(ctx);
7 | return { ...initialProps };
8 | }
9 |
10 | render() {
11 | return (
12 |
13 |
15 |
16 |
17 |
18 |
19 | );
20 | }
21 | }
22 |
23 | export default MyDocument;
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WebApp
2 |
3 | [](https://gitlab.com/amfoss/WebApp/-/commits/master)
4 |
5 | Webapp for FOSS@Amrita
6 |
7 | #### NPM Commands
8 | * ```npm install``` - install all node modules (dependencies) to run the app
9 | * ``npm run dev`` - runs the app in development mode in port 3000
10 | * ```npm run build``` - builds the app for production
11 |
12 | #### Tech Stack
13 | * Next / React / JSX / SASS
14 | * **Linting & Quality:** ESlint (ESlin Airbnb Config), Prettier
15 | * **UI Library:** Ant Design
16 |
--------------------------------------------------------------------------------
/utils/queries.js:
--------------------------------------------------------------------------------
1 | export const clubAttendance = `
2 | query{
3 | clubAttendance{
4 | dailyLog{
5 | avgDuration
6 | date
7 | membersPresent
8 | members{
9 | user{
10 | username
11 | firstName
12 | lastName
13 | }
14 | duration
15 | start
16 | end
17 | }
18 | }
19 | }
20 | }`;
21 |
22 | export const getApplicant = `
23 | query getApplicant($hash: String!){
24 | getApplicant(hash: $hash){
25 | id
26 | name
27 | formData{
28 | key
29 | value
30 | }
31 | }
32 | }
33 | `;
34 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "commonjs": true,
5 | "es6": true,
6 | "node": true
7 | },
8 | "plugins": [
9 | "react",
10 | "react-hooks"
11 | ],
12 | "extends": ["eslint:recommended", "plugin:react/recommended", "plugin:prettier/recommended"],
13 | "parserOptions": {
14 | "sourceType": "module",
15 | "ecmaVersion": 2018
16 | },
17 | "settings": {
18 | "react": {
19 | "version": "detect"
20 | }
21 | },
22 | "rules": {
23 | "linebreak-style": ["error", "unix"],
24 | "react-hooks/rules-of-hooks": "error",
25 | "react-hooks/exhaustive-deps": "warn"
26 | }
27 | }
--------------------------------------------------------------------------------
/components/breadCrumbGenerator.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Link from 'next/link';
3 | import PropTypes from 'prop-types';
4 |
5 | // antd components
6 | import Breadcrumb from 'antd/lib/breadcrumb';
7 |
8 | const BreadCrumbGenerator = ({ routes }) => {
9 | return (
10 |
11 | {routes
12 | ? routes.map((r, i) => (
13 |
14 | {r.name}
15 |
16 | ))
17 | : null}
18 |
19 | );
20 | };
21 |
22 | BreadCrumbGenerator.propTypes = {
23 | routes: PropTypes.array,
24 | };
25 |
26 | export default BreadCrumbGenerator;
27 |
--------------------------------------------------------------------------------
/pages/register.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import RegisterForm from '../components/register/registerForm';
4 | import Header from '../components/header';
5 |
6 | const Register = () => {
7 | return (
8 |
9 |
10 |
20 |
21 | );
22 | };
23 |
24 | export default Register;
25 |
--------------------------------------------------------------------------------
/pages/login.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import LoginForm from '../components/login/loginForm';
4 | import Header from '../components/header';
5 |
6 | const Login = ({ lastLocation }) => {
7 | return (
8 |
9 |
10 |
20 |
21 | );
22 | };
23 |
24 | export default Login;
25 |
--------------------------------------------------------------------------------
/pages/reset-password.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import ResetForm from '../components/reset-password/resetForm';
4 | import Header from '../components/header';
5 |
6 | const ResetPassword = () => {
7 | return (
8 |
9 |
10 |
20 |
21 | );
22 | };
23 |
24 | export default ResetPassword;
25 |
--------------------------------------------------------------------------------
/utils/fileUpload.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import Cookies from 'universal-cookie';
3 |
4 | const cookies = new Cookies();
5 | const API_URL = 'https://api.amfoss.in/';
6 |
7 | export default ({ data }) => {
8 | const token = cookies.get('token');
9 | return axios({
10 | method: 'post',
11 | url: API_URL,
12 | data,
13 | headers: {
14 | Authorization: token ? `JWT ${token}` : null,
15 | },
16 | config: {
17 | headers: {
18 | 'Content-Tranfer-Encoding': 'multipart/form-data',
19 | 'Content-Type': 'application/graphql',
20 | },
21 | },
22 | })
23 | .then(function (response) {
24 | return response.data;
25 | })
26 | .catch(function (response) {
27 | throw response;
28 | });
29 | };
30 |
--------------------------------------------------------------------------------
/pages/calendar/create-event.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import TitleBar from '../../components/titlebar';
4 | import Base from '../../components/base';
5 | import EventForm from '../../components/event/eventForm';
6 |
7 | const CreateEvent = (props) => {
8 | const routes = [
9 | {
10 | path: '/',
11 | name: 'Home',
12 | },
13 | {
14 | path: '/calendar',
15 | name: 'Calendar',
16 | },
17 | {
18 | path: '/calendar/create-event',
19 | name: 'Create Event',
20 | },
21 | ];
22 | return (
23 |
24 |
25 |
26 |
27 | );
28 | };
29 |
30 | export default CreateEvent;
31 |
--------------------------------------------------------------------------------
/pages/logout.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import Cookies from 'universal-cookie';
3 | import { useRouter } from 'next/router';
4 |
5 | import LoadingScreen from '../components/loadingScreen';
6 |
7 | const cookies = new Cookies();
8 |
9 | const LogoutPage = () => {
10 | const router = useRouter();
11 | const [loggedOut, setLoggedOut] = useState(false);
12 |
13 | useEffect(() => {
14 | cookies.remove('token');
15 | cookies.remove('refreshToken');
16 | cookies.remove('username');
17 | cookies.remove('expiry');
18 | if (!loggedOut) {
19 | setLoggedOut(true);
20 | router.push('/login');
21 | }
22 | });
23 |
24 | return ;
25 | };
26 |
27 | export default LogoutPage;
28 |
--------------------------------------------------------------------------------
/pages/attendance/stats.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Base from '../../components/base';
4 | import Overview from '../../modules/attendance/components/Overview';
5 | import TitleBar from '../../components/titlebar';
6 |
7 | const Stats = (props) => {
8 | const routes = [
9 | {
10 | path: '/',
11 | name: 'Home',
12 | },
13 | {
14 | path: '/attendance',
15 | name: 'Attendance',
16 | },
17 | {
18 | path: '/attendance-stats',
19 | name: 'Attendance Statistics',
20 | },
21 | ];
22 | return (
23 |
24 |
25 |
26 |
27 |
28 |
29 | );
30 | };
31 |
32 | export default Stats;
33 |
--------------------------------------------------------------------------------
/pages/status-updates/stats.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Base from '../../components/base';
4 | import Overview from '../../modules/statusUpdates/components/Overview';
5 | import TitleBar from '../../components/titlebar';
6 |
7 | const StatusStats = (props) => {
8 | const routes = [
9 | {
10 | path: '/',
11 | name: 'Home',
12 | },
13 | {
14 | path: '/status',
15 | name: 'Status Update',
16 | },
17 | {
18 | path: '/status-stats',
19 | name: 'Status Update Statistics',
20 | },
21 | ];
22 | return (
23 |
24 |
25 |
26 |
27 |
28 |
29 | );
30 | };
31 |
32 | export default StatusStats;
33 |
--------------------------------------------------------------------------------
/pages/blog/create-blog.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TitleBar from '../../components/titlebar';
3 | import Base from '../../components/base';
4 | import AddBlog from '../../components/blog/addBlog';
5 |
6 | import Card from 'antd/lib/card';
7 |
8 | const CreateBlog = (props) => {
9 | const routes = [
10 | {
11 | path: '/',
12 | name: 'Home',
13 | },
14 | {
15 | path: '/blog',
16 | name: 'Blog',
17 | },
18 | {
19 | path: '/blog/create-blog',
20 | name: 'Create Blog',
21 | },
22 | ];
23 |
24 | return (
25 |
26 |
27 |
32 |
33 | );
34 | };
35 |
36 | export default CreateBlog;
37 |
--------------------------------------------------------------------------------
/pages/404.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Router from 'next/router';
3 |
4 | // antd components
5 | import Result from 'antd/lib/result';
6 | import Button from 'antd/lib/button';
7 |
8 | import Base from '../components/base';
9 |
10 | export default function Page404(props) {
11 | return (
12 |
13 |
17 | Router.push('/')}>
23 | Back Home
24 |
25 | }
26 | />
27 |
28 |
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/utils/mutations.js:
--------------------------------------------------------------------------------
1 | export const TokenAuth = `
2 | mutation TokenAuth($username: String!, $password: String!) {
3 | tokenAuth(username: $username, password: $password) {
4 | token
5 | refreshToken
6 | payload
7 | refreshExpiresIn
8 | }
9 | }`;
10 |
11 | export const CheckIn = `
12 | mutation CheckIn($appID: Int!){
13 | checkIn(appID: $appID){
14 | status
15 | }
16 | }
17 | `;
18 |
19 | export const Register = `mutation ($email: String!, $username: String!, $password: String!){
20 | createUser(email: $email, username: $username, password: $password){
21 | user{
22 | id
23 | }
24 | }
25 | }`;
26 |
27 | export const verifyUserMutation = `mutation ($usernames: [String]){
28 | approveUsers(usernames: $usernames){
29 | status
30 | }
31 | }`;
32 |
33 | export const addToPlatformMutation = `mutation ($usernames: [String], $platform: String!){
34 | addToPlatform(usernames: $usernames, platform: $platform){
35 | status
36 | }
37 | }`;
38 |
--------------------------------------------------------------------------------
/components/titlebar.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | // antd components
5 | import PageHeader from 'antd/lib/page-header';
6 |
7 | import BreadcrumbGenerator from './breadCrumbGenerator';
8 |
9 | const TitleBar = ({
10 | routes,
11 | title,
12 | subTitle,
13 | pageHeaderProps,
14 | pageHeaderContent,
15 | }) => {
16 | return (
17 |
18 |
19 |
20 |
21 |
22 | {pageHeaderContent ? (
23 |
24 |
{pageHeaderContent.children}
25 |
{pageHeaderContent.extra}
26 |
27 | ) : null}
28 |
29 |
30 | );
31 | };
32 |
33 | TitleBar.propTypes = {
34 | routes: PropTypes.array,
35 | title: PropTypes.string,
36 | subTitle: PropTypes.string,
37 | };
38 |
39 | export default TitleBar;
40 |
--------------------------------------------------------------------------------
/utils/dataFetch.js:
--------------------------------------------------------------------------------
1 | import fetch from 'isomorphic-fetch';
2 | import Cookies from 'universal-cookie';
3 |
4 | const cookies = new Cookies();
5 | const API_URL = 'https://api.amfoss.in/';
6 |
7 | export default ({ query, variables }) => {
8 | const body = {
9 | query,
10 | variables,
11 | };
12 |
13 | const token = cookies.get('token');
14 |
15 | const apiConfig = {
16 | method: 'POST',
17 | headers: {
18 | 'Content-Type': 'application/json',
19 | Authorization: token ? `JWT ${token}` : null,
20 | },
21 | body: JSON.stringify(body),
22 | };
23 |
24 | return fetch(API_URL, apiConfig).then(function (response) {
25 | const contentType = response.headers.get('content-type');
26 | if (response.ok) {
27 | if (contentType && contentType.indexOf('application/json') !== -1) {
28 | return response.json().then((json) => json);
29 | }
30 | if (contentType && contentType.indexOf('text') !== -1) {
31 | return response.text().then((text) => text);
32 | }
33 | return response;
34 | }
35 | console.error(
36 | `Response status ${response.status} during dataFetch for url ${response.url}.`
37 | );
38 | throw response;
39 | });
40 | };
41 |
--------------------------------------------------------------------------------
/static/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "amFOSS CMS",
3 | "short_name": "CMS",
4 | "theme_color": "#001529",
5 | "background_color": "#eeeeee",
6 | "display": "standalone",
7 | "Scope": "/",
8 | "start_url": "/",
9 | "icons": [
10 | {
11 | "src": "/static/images/icons/icon-72x72.png",
12 | "sizes": "72x72",
13 | "type": "image/png"
14 | },
15 | {
16 | "src": "/static/images/icons/icon-96x96.png",
17 | "sizes": "96x96",
18 | "type": "image/png"
19 | },
20 | {
21 | "src": "/static/images/icons/icon-128x128.png",
22 | "sizes": "128x128",
23 | "type": "image/png"
24 | },
25 | {
26 | "src": "/static/images/icons/icon-144x144.png",
27 | "sizes": "144x144",
28 | "type": "image/png"
29 | },
30 | {
31 | "src": "/static/images/icons/icon-152x152.png",
32 | "sizes": "152x152",
33 | "type": "image/png"
34 | },
35 | {
36 | "src": "/static/images/icons/icon-192x192.png",
37 | "sizes": "192x192",
38 | "type": "image/png"
39 | },
40 | {
41 | "src": "/static/images/icons/icon-384x384.png",
42 | "sizes": "384x384",
43 | "type": "image/png"
44 | },
45 | {
46 | "src": "/static/images/icons/icon-512x512.png",
47 | "sizes": "512x512",
48 | "type": "image/png"
49 | }
50 | ],
51 | "splash_pages": null
52 | }
53 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | const withPlugins = require('next-compose-plugins');
2 | const withOffline = require('next-offline');
3 | const optimizedImages = require('next-optimized-images');
4 | const path = require('path');
5 |
6 | const nextConfig = {
7 | target: 'serverless',
8 | devIndicators: {
9 | autoPrerender: false,
10 | },
11 | sassOptions: {
12 | includePaths: [path.join(__dirname, 'styles')],
13 | },
14 | productionBrowserSourceMaps: true,
15 | };
16 |
17 | module.exports = withPlugins(
18 | [
19 | [optimizedImages],
20 | [
21 | withOffline({
22 | workboxOpts: {
23 | swDest: 'service-worker.js',
24 | runtimeCaching: [
25 | {
26 | urlPattern: /[.](png|jpg|ico|css)/,
27 | handler: 'CacheFirst',
28 | options: {
29 | cacheName: 'assets-cache',
30 | cacheableResponse: {
31 | statuses: [0, 200],
32 | },
33 | },
34 | },
35 | {
36 | urlPattern: /^https?.*/,
37 | handler: 'NetworkFirst',
38 | options: {
39 | cacheName: 'offlineCache',
40 | expiration: {
41 | maxEntries: 200,
42 | },
43 | },
44 | },
45 | ],
46 | },
47 | }),
48 | ],
49 | ],
50 | nextConfig
51 | );
52 |
--------------------------------------------------------------------------------
/pages/attendance/live-report.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 |
3 | import dataFetch from '../../utils/dataFetch';
4 | import Base from '../../components/base';
5 | import LiveReportCard from '../../modules/attendance/components/LiveReportCard';
6 | import TitleBar from '../../components/titlebar';
7 |
8 | const LiveReport = (props) => {
9 | const [data, setData] = useState([]);
10 | const [isLoaded, setLoaded] = useState(false);
11 |
12 | const query = `query{
13 | liveAttendance{
14 | membersPresent
15 | {
16 | count
17 | members
18 | {
19 | firstName
20 | lastName
21 | username
22 | firstSeenToday
23 | duration
24 | }
25 | }
26 | }
27 | }`;
28 |
29 | const fetchData = async () => dataFetch({ query });
30 |
31 | useEffect(() => {
32 | if (!isLoaded) {
33 | fetchData().then((r) => {
34 | setData(r.data.liveAttendance);
35 | setLoaded(true);
36 | });
37 | }
38 | });
39 |
40 | const routes = [
41 | {
42 | path: '/',
43 | name: 'Home',
44 | },
45 | {
46 | path: '/attendance',
47 | name: 'Attendance',
48 | },
49 | {
50 | path: '/attendance/live-report',
51 | name: 'Live Attendance',
52 | },
53 | ];
54 |
55 | return (
56 |
57 |
62 |
63 |
64 | );
65 | };
66 |
67 | export default LiveReport;
68 |
--------------------------------------------------------------------------------
/modules/forms/formFields.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | // antd components
5 | import Collapse from 'antd/lib/collapse';
6 | import Tag from 'antd/lib/tag';
7 |
8 | import FieldEditor from './fieldEditor';
9 |
10 | const FormFields = ({ data }) => {
11 | const renderField = (f) => (
12 | {f.question}}
15 | >
16 |
17 |
18 | );
19 |
20 | return (
21 |
22 |
23 |
27 | Name Inbuilt
28 |
29 | }
30 | >
31 | Inbuilt Field
32 |
33 |
37 | Phone Inbuilt
38 |
39 | }
40 | >
41 | Inbuilt Field
42 |
43 |
47 | Email Inbuilt
48 |
49 | }
50 | >
51 | Inbuilt Field
52 |
53 | {data.fields.map((f) => renderField(f))}
54 |
55 |
56 | );
57 | };
58 |
59 | FormFields.propTypes = {
60 | data: PropTypes.object,
61 | };
62 |
63 | export default FormFields;
64 |
--------------------------------------------------------------------------------
/styles/styles.sass:
--------------------------------------------------------------------------------
1 | body
2 | margin: 0
3 | a:hover
4 | text-decoration: none
5 |
6 | .page-container
7 | min-height: 100vh
8 |
9 | select
10 | background: white
11 |
12 | .menu-card
13 | background: linear-gradient(to bottom, #8e2de2, #4a00e0)
14 | box-shadow: 3px 1px 10px rgba(0, 0, 0, 0.3)
15 | padding: 3rem
16 | display: block
17 | text-align: center
18 | font-size: calc(1.2rem + 0.8vw)
19 | line-height: 1.2
20 | color: white
21 |
22 | .menu-card:hover
23 | color: white
24 |
25 | .ant-table-body
26 | min-height: 73vh!important
27 | cursor: pointer
28 | overflow: auto
29 |
30 | .loading
31 | background-color: #eeeeee
32 | min-height: 100vh
33 | display: flex
34 | flex-direction: column
35 | align-items: center
36 | justify-content: center
37 |
38 | .confirm-modal-overlay
39 | position: fixed
40 | top: 0
41 | left: 0
42 | z-index: 1050
43 | width: 100vw
44 | height: 100vh
45 | background-color: #000
46 | opacity: .5
47 |
48 | .confirm-modal-wrapper
49 | position: fixed
50 | top: 0
51 | left: 0
52 | z-index: 1050
53 | width: 100%
54 | height: 100%
55 | overflow-x: hidden
56 | overflow-y: auto
57 | outline: 0
58 |
59 | .confirm-modal
60 | z-index: 1050
61 | background: white
62 | position: relative
63 | margin: 18.75rem auto
64 | border-radius: 3px
65 | max-width: 500px
66 | padding: 2.5rem
67 | display: block
68 |
69 | .confirm-modal-header
70 | display: flex
71 |
72 | .confirm-modal-header-button
73 | display: flex
74 | justify-content: flex-end
75 |
76 | .platform-true
77 | color: #52c41a
78 |
79 | .platform-false
80 | color: #eb2f96
81 |
82 | .platform-icon
83 | display: inline-block
84 | vertical-align: middle
85 |
--------------------------------------------------------------------------------
/modules/attendance/components/ActiveStatusBar.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | // antd components
5 | import Alert from 'antd/lib/alert';
6 |
7 | const ActiveStatusBar = ({ lastSeen, membersPresentCount, isInLab }) => {
8 | return membersPresentCount === 0 && !isInLab ? (
9 |
19 | ) : !isInLab && lastSeen !== null ? (
20 | You were last recorded at ${lastSeen.toString()}`}
26 | type="warning"
27 | showIcon
28 | />
29 | ) : lastSeen == null ? (
30 |
38 | ) : (
39 |
46 | );
47 | };
48 |
49 | ActiveStatusBar.propTypes = {
50 | lastSeen: PropTypes.dateTime,
51 | membersPresentCount: PropTypes.int,
52 | isInLab: PropTypes.bool,
53 | };
54 |
55 | export default ActiveStatusBar;
56 |
--------------------------------------------------------------------------------
/pages/account/settings.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 |
3 | // antd components
4 | import Card from 'antd/lib/card';
5 | import Menu from 'antd/lib/menu';
6 |
7 | import Base from '../../components/base';
8 | import TitleBar from '../../components/titlebar';
9 | import BasicSettings from '../../components/account/basicSettings';
10 | import ChangePassword from '../../components/account/changePassword';
11 |
12 | const routes = [
13 | {
14 | path: '/',
15 | name: 'Home',
16 | },
17 | {
18 | path: '/account',
19 | name: 'Account',
20 | },
21 | {
22 | path: '/account/settings',
23 | name: 'Settings',
24 | },
25 | ];
26 | const AccountSettings = (props) => {
27 | const [current, setCurrent] = useState('basic');
28 | return (
29 |
30 |
31 |
32 |
33 |
34 |
35 |
39 |
40 |
50 | {current === 'basic' ? : }
51 |
52 |
53 |
54 |
55 |
56 | );
57 | };
58 |
59 | export default AccountSettings;
60 |
--------------------------------------------------------------------------------
/modules/forms/entryDetails.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const EntryDetails = ({ fields, data }) => {
5 | const getColSize = (a, b) => {
6 | if (a == null && b == null) return 'col-md-3 col-sm-4 col-12';
7 | let value;
8 | if (a == null) value = b.length;
9 | else if (b == null) value = a.length;
10 | else if (a.length > b.length) value = a.length;
11 | else value = b.length;
12 | if (value > 100) return 'col-12';
13 | if (value > 30) return 'col-md-6 col-lg-4';
14 | return 'col-md-4 col-lg-3';
15 | };
16 |
17 | const getItem = (label, value) => (
18 |
19 |
20 |
{label}
21 | {value}
22 |
23 |
24 | );
25 |
26 | return (
27 |
28 |
29 |
30 |
Entry #{data.id}
31 |
{data.name}
32 |
33 | -
34 | Submission Time:{' '}
35 | {new Date(data.submissionTime).toLocaleString()}
36 |
37 | -
38 | Email: {data.email}
39 |
40 | -
41 | Phone Number: {data.phone}
42 |
43 | -
44 | Details: {data.details}
45 |
46 |
47 |
48 |
49 | {fields.map((f) =>
50 | getItem(f.question, data.formData.find((d) => d.key === f.key).value)
51 | )}
52 |
53 | );
54 | };
55 |
56 | EntryDetails.propTypes = {
57 | fields: PropTypes.array,
58 | data: PropTypes.object,
59 | };
60 |
61 | export default EntryDetails;
62 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cms-webapp",
3 | "version": "0.1.0",
4 | "description": "webapp for FOSS@Amrita",
5 | "main": "index.js",
6 | "scripts": {
7 | "dev": "NODE_ENV='DEV' next",
8 | "build": "next build",
9 | "start": "next start",
10 | "purifyCSS": "node ./scripts/purifyCSS.js",
11 | "export": "npm run build && next export",
12 | "lint": "eslint .",
13 | "lint:fix": "eslint --fix .",
14 | "format": "prettier --write \"**/*.+(js|jsx|json)\""
15 | },
16 | "repository": {
17 | "type": "git",
18 | "url": "git://gitlab.com/amfoss/WebApp.git"
19 | },
20 | "keywords": [
21 | "webapp",
22 | "react",
23 | "club",
24 | "management"
25 | ],
26 | "author": "amfoss ",
27 | "license": "GPL-3.0-or-later",
28 | "dependencies": {
29 | "@ant-design/icons": "^4.0.6",
30 | "antd": "^4.1.1",
31 | "axios": "^0.19.2",
32 | "bootstrap": "^4.4.1",
33 | "chart.js": "^2.9.3",
34 | "classnames": "^2.2.6",
35 | "es6-promise": "^4.2.8",
36 | "imagemin-optipng": "^7.1.0",
37 | "isomorphic-fetch": "^2.2.1",
38 | "markdown-it": "^11.0.0",
39 | "moment": "^2.24.0",
40 | "moment-range": "^4.0.2",
41 | "next": "^9.3.5",
42 | "next-compose-plugins": "^2.2.0",
43 | "next-offline": "^5.0.1",
44 | "next-optimized-images": "^2.5.8",
45 | "prop-types": "^15.7.2",
46 | "react": "^16.13.1",
47 | "react-chartjs-2": "^2.9.0",
48 | "react-csv": "^2.0.3",
49 | "react-dom": "^16.13.1",
50 | "react-highlight-words": "^0.16.0",
51 | "react-loading": "^2.0.3",
52 | "react-markdown-editor-lite": "^1.1.5",
53 | "react-qr-reader": "^2.2.1",
54 | "react-quill": "^1.3.5",
55 | "sass": "^1.32.8",
56 | "universal-cookie": "^4.0.3"
57 | },
58 | "devDependencies": {
59 | "eslint": "^6.8.0",
60 | "eslint-config-prettier": "^6.10.1",
61 | "eslint-plugin-prettier": "^3.1.3",
62 | "eslint-plugin-react": "^7.19.0",
63 | "eslint-plugin-react-hooks": "^3.0.0",
64 | "prettier": "^2.0.4"
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/components/header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Head from 'next/head';
3 | import { string } from 'prop-types';
4 |
5 | const content =
6 | 'FOSS@Amrita (also known as amFOSS) is a student community based in Amrita Vishwa Vidyapeetham, Amritapuri' +
7 | ' focused on contributing to Free and Open Source Software and mentoring students to achieve excellence in various ' +
8 | 'fields of Computer Science.';
9 |
10 | const Header = (props) => (
11 |
12 | {props.title} | amFOSS CMS
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | );
40 |
41 | Header.propTypes = {
42 | title: string,
43 | description: string,
44 | keywords: string,
45 | url: string,
46 | ogImage: string,
47 | };
48 |
49 | export default Header;
50 |
--------------------------------------------------------------------------------
/modules/attendance/components/LiveReportCard.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Moment from 'moment';
3 | import { extendMoment } from 'moment-range';
4 | import PropTypes from 'prop-types';
5 |
6 | // antd components
7 | import Card from 'antd/lib/card';
8 | import List from 'antd/lib/list';
9 | import Avatar from 'antd/lib/avatar';
10 | import Badge from 'antd/lib/badge';
11 | import Tabs from 'antd/lib/tabs';
12 |
13 | const { TabPane } = Tabs;
14 | const moment = extendMoment(Moment);
15 |
16 | const LiveReportCard = ({ data, isLoaded }) => {
17 | const membersCard = (members) => (
18 | (
23 |
24 |
25 | }
27 | title={
28 |
29 |
30 | {member.firstName} {member.lastName}
31 |
32 |
33 | }
34 | description={
35 |
36 | Since {moment(member.firstSeenToday).format('HH:mm')}
37 | For {member.duration}
38 |
39 | }
40 | />
41 |
42 |
43 | )}
44 | />
45 | );
46 |
47 | return (
48 |
49 |
50 |
54 | Present Now
55 |
61 |
62 | }
63 | >
64 | {isLoaded ? membersCard(data.membersPresent.members) : null}
65 |
66 |
67 |
68 | );
69 | };
70 |
71 | LiveReportCard.propTypes = {
72 | data: PropTypes.object,
73 | isLoaded: PropTypes.bool,
74 | };
75 |
76 | export default LiveReportCard;
77 |
--------------------------------------------------------------------------------
/modules/statusUpdates/components/DailyStatusReportCard.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | // antd components
5 | import Card from 'antd/lib/card';
6 | import List from 'antd/lib/list';
7 | import Avatar from 'antd/lib/avatar';
8 | import Badge from 'antd/lib/badge';
9 | import Tabs from 'antd/lib/tabs';
10 |
11 | const { TabPane } = Tabs;
12 |
13 | const DailyStatusReportCard = ({ data, isLoaded }) => {
14 | const membersCard = (members) => (
15 | (
20 |
21 |
22 |
31 | }
32 | title={
33 |
34 | {m.member.fullName}
35 |
36 | }
37 | />
38 |
39 |
40 | )}
41 | />
42 | );
43 |
44 | return (
45 |
46 |
47 |
51 | Sent
52 |
56 |
57 | }
58 | >
59 | {isLoaded ? membersCard(data.membersSent, 'sent') : null}
60 |
61 |
66 | Did Not Send
67 |
71 |
72 | }
73 | >
74 | {isLoaded ? membersCard(data.memberDidNotSend, 'didNotSend') : null}
75 |
76 |
77 |
78 | );
79 | };
80 |
81 | DailyStatusReportCard.propTypes = {
82 | data: PropTypes.object,
83 | isLoaded: PropTypes.bool,
84 | };
85 |
86 | export default DailyStatusReportCard;
87 |
--------------------------------------------------------------------------------
/pages/attendance/dashboard.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import Link from 'next/link';
3 | import Cookies from 'universal-cookie';
4 |
5 | import TitleBar from '../../components/titlebar';
6 | import Base from '../../components/base';
7 | import ActiveStatusBar from '../../modules/attendance/components/ActiveStatusBar';
8 | import dataFetch from '../../utils/dataFetch';
9 |
10 | const cookies = new Cookies();
11 |
12 | const AttendanceDashboard = (props) => {
13 | const [isInLab, setIsInLab] = useState(false);
14 | const [lastSeen, setLastSeen] = useState(false);
15 | const [membersPresentCount, setMembersPresentCount] = useState(0);
16 |
17 | const [isLoaded, setLoaded] = useState(false);
18 | const username = cookies.get('username');
19 |
20 | const query = `
21 | query($username: String!){
22 | user(username: $username)
23 | {
24 | isInLab
25 | lastSeenInLab
26 | }
27 | liveAttendance
28 | {
29 | membersPresent
30 | {
31 | count
32 | }
33 | }
34 | }`;
35 |
36 | const fetchData = async (variables) => dataFetch({ query, variables });
37 |
38 | useEffect(() => {
39 | if (!isLoaded) {
40 | fetchData({ username }).then((r) => {
41 | setIsInLab(r.data.user.isInLab);
42 | setLastSeen(r.data.user.lastSeenInLab);
43 | setMembersPresentCount(r.data.liveAttendance.membersPresent.count);
44 | setLoaded(true);
45 | });
46 | }
47 | });
48 |
49 | const routes = [
50 | {
51 | path: '/',
52 | name: 'Home',
53 | },
54 | {
55 | path: '/attendance',
56 | name: 'Attendance',
57 | },
58 | ];
59 |
60 | return (
61 |
62 |
63 |
87 |
88 | );
89 | };
90 |
91 | export default AttendanceDashboard;
92 |
--------------------------------------------------------------------------------
/components/reset-password/resetForm.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import dataFetch from '../../utils/dataFetch';
3 | import Link from 'next/link';
4 |
5 | import Result from 'antd/lib/result';
6 | import Card from "antd/lib/card";
7 | import Form from "antd/lib/form";
8 | import Input from "antd/lib/input";
9 | import { LockOutlined, UserOutlined } from "@ant-design/icons";
10 | import Button from "antd/lib/button";
11 |
12 | const ResetForm = () => {
13 | const [isLoading, setLoaded] = useState(false);
14 | const [error, setErrorText] = useState('');
15 | const [success, setSuccessText] = useState('');
16 |
17 | const query = `
18 | mutation ($email: String!){
19 | resetPassword(email:$email){
20 | status
21 | }
22 | }
23 | `;
24 |
25 | const submitForm = async (variables) => await dataFetch({ query, variables });
26 |
27 | const resetPassword = (values) => {
28 | submitForm(values).then((response) => {
29 | if (Object.prototype.hasOwnProperty.call(response, 'errors')) {
30 | setErrorText(response.errors[0].message);
31 | } else {
32 | setSuccessText(response.data.status);
33 | setErrorText('');
34 | setLoaded(true);
35 | }
36 | });
37 | };
38 |
39 | const onFinishFailed = (errorInfo) => {
40 | console.error(errorInfo);
41 | };
42 |
43 | return !isLoading ? (
44 |
45 |
46 |
58 | }
60 | placeholder="Email"
61 | />
62 |
63 |
64 |
71 |
72 |
73 | Already have an account? Login
74 |
75 | ) : (
76 |
77 | {success !== '' ? (
78 |
79 | ) : error !== '' ? (
80 | {error}
81 | ) : (
82 | Submitting. Please Wait
83 | )}
84 | Back to Login
85 |
86 | );
87 | };
88 |
89 | export default ResetForm;
90 |
--------------------------------------------------------------------------------
/components/authRequired.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Result from 'antd/lib/result';
3 | import Button from 'antd/lib/button';
4 | import Router from 'next/router';
5 | import PropTypes from 'prop-types';
6 |
7 | import LoadingScreen from './loadingScreen';
8 | import Base from './base';
9 |
10 | const AuthRequired = ({
11 | children,
12 | loaded,
13 | isAdmin,
14 | isClubMember,
15 | verificationRequired,
16 | adminRequired,
17 | }) => {
18 | return loaded ? (
19 | adminRequired ? (
20 | isAdmin ? (
21 | children
22 | ) : (
23 |
24 |
28 | Router.push('/')}>
34 | Back Home
35 |
36 | }
37 | />
38 |
39 |
40 | )
41 | ) : isAdmin || isClubMember ? (
42 | children
43 | ) : (
44 |
48 |
52 |
62 | {verificationRequired ? (
63 |
69 | ) : (
70 |
73 | )}
74 |
75 | }
76 | />
77 |
78 |
79 | )
80 | ) : (
81 |
82 | );
83 | };
84 |
85 | AuthRequired.propTypes = {
86 | loaded: PropTypes.bool,
87 | children: PropTypes.node,
88 | isAdmin: PropTypes.bool,
89 | isClubMember: PropTypes.bool,
90 | verificationRequired: PropTypes.bool,
91 | adminRequired: PropTypes.bool,
92 | };
93 |
94 | export default AuthRequired;
95 |
--------------------------------------------------------------------------------
/pages/status-updates/daily-report.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import Moment from 'moment';
3 | import { extendMoment } from 'moment-range';
4 |
5 | // antd components
6 | import DatePicker from 'antd/lib/date-picker';
7 |
8 | import Base from '../../components/base';
9 | import DailyStatusReportCard from '../../modules/statusUpdates/components/DailyStatusReportCard';
10 | import dataFetch from '../../utils/dataFetch';
11 | import TitleBar from '../../components/titlebar';
12 |
13 | const moment = extendMoment(Moment);
14 |
15 | const DailyStatusReport = (props) => {
16 | const [data, setData] = useState('');
17 | const [date, setDate] = useState(
18 | moment().subtract(1, 'days').format('YYYY-MM-DD')
19 | );
20 | const [isLoaded, setLoaded] = useState(false);
21 |
22 | const query = `query($date:Date!){
23 | dailyStatusUpdates(date:$date){
24 | date
25 | membersSent{
26 | member{
27 | username
28 | fullName
29 | statusUpdateCount
30 | profile{
31 | profilePic
32 | }
33 | avatar{
34 | githubUsername
35 | }
36 | }
37 | }
38 | memberDidNotSend{
39 | member{
40 | username
41 | fullName
42 | statusUpdateCount
43 | profile{
44 | profilePic
45 | }
46 | avatar{
47 | githubUsername
48 | }
49 | }
50 | }
51 | }
52 | }`;
53 |
54 | const fetchData = async (variables) => dataFetch({ query, variables });
55 |
56 | useEffect(() => {
57 | if (!isLoaded) {
58 | fetchData({ date }).then((r) => {
59 | setData(r.data.dailyStatusUpdates);
60 | setLoaded(true);
61 | });
62 | }
63 | });
64 |
65 | const routes = [
66 | {
67 | path: '/',
68 | name: 'Home',
69 | },
70 | {
71 | path: '/status-update',
72 | name: 'Status Update',
73 | },
74 | {
75 | path: '/status-update/daily-report',
76 | name: 'Daily Report',
77 | },
78 | ];
79 |
80 | return (
81 |
82 |
87 |
88 |
89 |
90 | {
93 | setLoaded(false);
94 | setDate(e.format('YYYY-MM-DD'));
95 | }}
96 | format="DD-MM-YYYY"
97 | value={moment(date)}
98 | />
99 |
100 |
101 |
102 |
103 |
104 |
105 | );
106 | };
107 |
108 | export default DailyStatusReport;
109 |
--------------------------------------------------------------------------------
/pages/attendance/daily-report.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import Moment from 'moment';
3 | import { extendMoment } from 'moment-range';
4 |
5 | // antd components
6 | import DatePicker from 'antd/lib/date-picker';
7 |
8 | import Base from '../../components/base';
9 | import DailyReportCard from '../../modules/attendance/components/DailyReportCard';
10 | import dataFetch from '../../utils/dataFetch';
11 | import TitleBar from '../../components/titlebar';
12 |
13 | const moment = extendMoment(Moment);
14 |
15 | const DailyReport = (props) => {
16 | const [data, setData] = useState('');
17 | const [date, setDate] = useState(moment().format('YYYY-MM-DD'));
18 | const [isLoaded, setLoaded] = useState(false);
19 |
20 | const query = `query($date: Date!){
21 | dailyAttendance(date: $date)
22 | {
23 | date
24 | membersPresent
25 | {
26 | member
27 | {
28 | username
29 | firstName
30 | lastName
31 | profile{
32 | profilePic
33 | }
34 | }
35 | lastSeen
36 | firstSeen
37 | duration
38 | }
39 | membersAbsent
40 | {
41 | member
42 | {
43 | username
44 | firstName
45 | lastName
46 | profile{
47 | profilePic
48 | }
49 | avatar
50 | {
51 | githubUsername
52 | }
53 | }
54 | lastSeen
55 | }
56 | }
57 | }`;
58 |
59 | const fetchData = async (variables) => dataFetch({ query, variables });
60 |
61 | useEffect(() => {
62 | if (!isLoaded) {
63 | fetchData({ date }).then((r) => {
64 | setData(r.data.dailyAttendance);
65 | setLoaded(true);
66 | });
67 | }
68 | });
69 |
70 | const routes = [
71 | {
72 | path: '/',
73 | name: 'Home',
74 | },
75 | {
76 | path: '/attendance',
77 | name: 'Attendance',
78 | },
79 | {
80 | path: '/attendance/daily-report',
81 | name: 'Daily Report',
82 | },
83 | ];
84 |
85 | return (
86 |
87 |
92 |
93 |
94 |
95 | {
98 | setLoaded(false);
99 | setDate(e.format('YYYY-MM-DD'));
100 | }}
101 | format="DD-MM-YYYY"
102 | value={moment(date)}
103 | />
104 |
105 |
106 |
107 |
108 |
109 |
110 | );
111 | };
112 |
113 | export default DailyReport;
114 |
--------------------------------------------------------------------------------
/modules/statusUpdates/components/Overview.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import Moment from 'moment';
3 | import { extendMoment } from 'moment-range';
4 |
5 | // antd components
6 | import DatePicker from 'antd/lib/date-picker';
7 |
8 | import dataFetch from '../../../utils/dataFetch';
9 | import Rankings from './Ranking';
10 | import TrendStatusGraph from './TrendStatusGraph';
11 | import StatusGraph from './StatusGraph';
12 |
13 | const { RangePicker } = DatePicker;
14 | const moment = extendMoment(Moment);
15 |
16 | const Overview = () => {
17 | const [data, setData] = useState([]);
18 | const [dailyLogData, setDailyLogData] = useState([]);
19 | const [startDate, setStartDate] = useState(new Date());
20 | const [endDate, setEndDate] = useState(new Date());
21 |
22 | const [rangeLoaded, setRangeLoaded] = useState(false);
23 | const [isLoaded, setLoaded] = useState(false);
24 |
25 | const query = `query ($startDate: Date!, $endDate: Date){
26 | clubStatusUpdate(startDate: $startDate, endDate: $endDate){
27 | dailyLog{
28 | date
29 | membersSentCount
30 | }
31 | memberStats{
32 | user{
33 | username
34 | admissionYear
35 | }
36 | statusCount
37 | }
38 | }
39 | }`;
40 |
41 | const fetchData = async (variables) => dataFetch({ query, variables });
42 |
43 | useEffect(() => {
44 | if (!rangeLoaded) {
45 | setStartDate(new Date(moment().subtract(1, 'weeks').format('YYYY-MM-DD')));
46 | setRangeLoaded(true);
47 | }
48 | if (!isLoaded && rangeLoaded) {
49 | const variables = {
50 | startDate: moment(startDate).format('YYYY-MM-DD'),
51 | endDate: moment(endDate).format('YYYY-MM-DD'),
52 | };
53 | fetchData(variables).then((r) => {
54 | setData(r.data.clubStatusUpdate.memberStats);
55 | setDailyLogData(r.data.clubStatusUpdate.dailyLog);
56 | setLoaded(true);
57 | });
58 | }
59 | });
60 |
61 | const handleRangeChange = (obj) => {
62 | if (obj[0] != null && obj[1] != null) {
63 | setStartDate(obj[0]);
64 | setEndDate(obj[1]);
65 | setLoaded(false);
66 | }
67 | };
68 |
69 | return (
70 |
71 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
97 |
98 |
99 |
100 | );
101 | };
102 |
103 | export default Overview;
104 |
--------------------------------------------------------------------------------
/modules/attendance/components/Overview.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import Moment from 'moment';
3 | import { extendMoment } from 'moment-range';
4 |
5 | // antd components
6 | import DatePicker from 'antd/lib/date-picker';
7 |
8 | import dataFetch from '../../../utils/dataFetch';
9 | import Rankings from './Rankings';
10 | import TrendGraph from './TrendGraph';
11 | import TrendAttendanceGraph from './TrendAttendanceGraph';
12 |
13 | const { RangePicker } = DatePicker;
14 | const moment = extendMoment(Moment);
15 |
16 | const Overview = () => {
17 | const [data, setData] = useState([]);
18 | const [memberStats, setMemberStats] = useState([]);
19 | const [startDate, setStartDate] = useState(new Date());
20 | const [endDate, setEndDate] = useState(new Date());
21 |
22 | const [rangeLoaded, setRangeLoaded] = useState(false);
23 | const [isLoaded, setLoaded] = useState(false);
24 |
25 | const query = `query($startDate: Date!, $endDate: Date){
26 | clubAttendance(startDate: $startDate, endDate: $endDate)
27 | {
28 | dailyLog
29 | {
30 | date
31 | membersPresent
32 | avgDuration
33 | }
34 | memberStats{
35 | user{
36 | username
37 | admissionYear
38 | }
39 | presentCount
40 | }
41 | }
42 | }`;
43 |
44 | const fetchData = async (variables) => dataFetch({ query, variables });
45 |
46 | useEffect(() => {
47 | if (!rangeLoaded) {
48 | setStartDate(new Date(moment().subtract(2, 'weeks').format('YYYY-MM-DD')));
49 | setRangeLoaded(true);
50 | }
51 | if (!isLoaded && rangeLoaded) {
52 | const variables = {
53 | startDate: moment(startDate).format('YYYY-MM-DD'),
54 | endDate: moment(endDate).format('YYYY-MM-DD'),
55 | };
56 | fetchData(variables).then((r) => {
57 | setData(r.data.clubAttendance.dailyLog);
58 | setMemberStats(r.data.clubAttendance.memberStats);
59 | setLoaded(true);
60 | });
61 | }
62 | });
63 |
64 | const handleRangeChange = (obj) => {
65 | if (obj[0] != null && obj[1] != null) {
66 | setStartDate(obj[0]);
67 | setEndDate(obj[1]);
68 | setLoaded(false);
69 | }
70 | };
71 |
72 | return (
73 |
74 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
100 |
101 |
102 |
103 | );
104 | };
105 |
106 | export default Overview;
107 |
--------------------------------------------------------------------------------
/components/admin/alertComponent.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { MinusSquareOutlined, PlusSquareOutlined } from '@ant-design/icons';
4 |
5 | import dataFetch from '../../utils/dataFetch';
6 |
7 | const AlertComponent = ({ changedPlatforms, show, toggle, data }) => {
8 | const [result, setResult] = useState(null);
9 |
10 | const query = `
11 | mutation changeUserPlatform($username: String!, $GitLab: Boolean, $GitHub: Boolean, $CloudFlare: Boolean, $Telegram: Boolean){
12 | changeUserPlatform(username: $username, gitlab: $GitLab, github: $GitHub, cloudflare: $CloudFlare, telegram: $Telegram){
13 | status
14 | }
15 | }
16 | `;
17 | const fetchData = async (variables) => dataFetch({ query, variables });
18 |
19 | function getValue(obj, key) {
20 | let value;
21 | for (let i in obj) {
22 | if (!obj.hasOwnProperty(i)) continue;
23 | if (i === key) {
24 | value = obj[i];
25 | }
26 | }
27 | return value;
28 | }
29 |
30 | function submit() {
31 | const variables = { username: data.user };
32 | changedPlatforms.map((platform) => {
33 | variables[`${platform}`] = getValue(data, platform);
34 | });
35 | fetchData(variables).then((r) => {
36 | setResult(r.data.changeUserPlatform.status);
37 | });
38 | }
39 |
40 | return show ? (
41 |
42 |
43 |
44 |
45 | {result ? (
46 |
Successfully Updated
47 | ) : null}
48 |
49 |
Are you sure?
50 |
51 |
52 | You are updating status of {data.user} in the following platforms
53 |
54 | {changedPlatforms.map((platform, index) => (
55 |
60 | {getValue(data, platform) ? (
61 |
62 | ) : (
63 |
64 | )}{' '}
65 | {platform}
66 |
67 | ))}
68 |
69 |
78 |
86 |
87 |
88 |
89 |
90 |
91 | ) : null;
92 | };
93 |
94 | AlertComponent.propTypes = {
95 | changedPlatforms: PropTypes.array,
96 | data: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
97 | show: PropTypes.bool,
98 | toggle: PropTypes.func,
99 | };
100 |
101 | export default AlertComponent;
102 |
--------------------------------------------------------------------------------
/modules/statusUpdates/components/StatusGraph.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import classnames from 'classnames';
3 | import { Line } from 'react-chartjs-2';
4 | import PropTypes from 'prop-types';
5 |
6 | // antd components
7 | import Card from 'antd/lib/card';
8 |
9 | const StatusGraph = ({ isLoaded, dailyLogData }) => {
10 | let options = {
11 | maintainAspectRatio: false,
12 | legend: {
13 | display: false,
14 | },
15 | tooltips: {
16 | backgroundColor: '#f5f5f5',
17 | titleFontColor: '#333',
18 | bodyFontColor: '#666',
19 | bodySpacing: 4,
20 | xPadding: 12,
21 | mode: 'nearest',
22 | intersect: 0,
23 | position: 'nearest',
24 | },
25 | responsive: true,
26 | scales: {
27 | yAxes: [
28 | {
29 | barPercentage: 1.6,
30 | gridLines: {
31 | drawBorder: false,
32 | color: 'rgba(29,140,248,0.0)',
33 | zeroLineColor: 'transparent',
34 | },
35 | ticks: {
36 | suggestedMin: 0,
37 | suggestedMax: 5,
38 | padding: 20,
39 | fontColor: '#9a9a9a',
40 | },
41 | },
42 | ],
43 | xAxes: [
44 | {
45 | barPercentage: 1.6,
46 | gridLines: {
47 | drawBorder: false,
48 | color: 'rgba(29,140,248,0.1)',
49 | zeroLineColor: 'transparent',
50 | },
51 | ticks: {
52 | padding: 20,
53 | fontColor: '#9a9a9a',
54 | },
55 | },
56 | ],
57 | },
58 | };
59 |
60 | const x = [];
61 | const y = [];
62 | dailyLogData.map((r) => {
63 | x.push(r.date);
64 | y.push(r.membersSentCount);
65 | });
66 | let statusGraph = {
67 | data: (canvas) => {
68 | let ctx = canvas.getContext('2d');
69 |
70 | let gradientStroke = ctx.createLinearGradient(0, 2300, 0, 50);
71 |
72 | gradientStroke.addColorStop(1, 'rgba(29,140,248,0.2)');
73 | gradientStroke.addColorStop(0.4, 'rgba(29,140,248,0.0)');
74 | gradientStroke.addColorStop(0, 'rgba(29,140,248,0)'); //blue colors
75 |
76 | return {
77 | labels: x,
78 | datasets: [
79 | {
80 | label: 'Status Graph',
81 | fill: true,
82 | backgroundColor: gradientStroke,
83 | borderColor: '#1f8ef1',
84 | borderWidth: 2,
85 | borderDash: [],
86 | borderDashOffset: 0.0,
87 | pointBackgroundColor: '#1f8ef1',
88 | pointBorderColor: 'rgba(255,255,255,0)',
89 | pointHoverBackgroundColor: '#1f8ef1',
90 | pointBorderWidth: 20,
91 | pointHoverRadius: 4,
92 | pointHoverBorderWidth: 15,
93 | pointRadius: 4,
94 | data: y,
95 | },
96 | ],
97 | };
98 | },
99 | };
100 |
101 | return (
102 |
103 |
104 |
105 |
Status Trends
106 |
107 |
108 |
112 | {isLoaded ? : null}
113 |
114 |
115 | );
116 | };
117 |
118 | StatusGraph.propTypes = {
119 | isLoaded: PropTypes.bool,
120 | dailyLogData: PropTypes.array,
121 | };
122 |
123 | export default StatusGraph;
124 |
--------------------------------------------------------------------------------
/pages/status-updates/messages.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { extendMoment } from 'moment-range';
3 | import Moment from 'moment';
4 |
5 | // antd components
6 | import Card from 'antd/lib/card';
7 | import DatePicker from 'antd/lib/date-picker';
8 | import Avatar from 'antd/lib/avatar';
9 |
10 | import Base from '../../components/base';
11 | import dataFetch from '../../utils/dataFetch';
12 | import TitleBar from '../../components/titlebar';
13 |
14 | const moment = extendMoment(Moment);
15 | const { Meta } = Card;
16 |
17 | const Messages = (props) => {
18 | const [data, setData] = useState([]);
19 | const [date, setDate] = useState(
20 | moment().subtract(1, 'days').format('YYYY-MM-DD')
21 | );
22 | const [isLoaded, setLoaded] = useState(false);
23 |
24 | const query = `
25 | query($date: Date!){
26 | getStatusUpdates(date: $date){
27 | member{
28 | username
29 | fullName
30 | profile{
31 | profilePic
32 | }
33 | avatar{
34 | githubUsername
35 | }
36 | }
37 | timestamp
38 | message
39 | }
40 | }`;
41 |
42 | const fetchData = async (variables) => dataFetch({ query, variables });
43 |
44 | useEffect(() => {
45 | if (!isLoaded) {
46 | fetchData({ date }).then((r) => {
47 | setData(r.data.getStatusUpdates);
48 | setLoaded(true);
49 | });
50 | }
51 | });
52 |
53 | const routes = [
54 | {
55 | path: '/',
56 | name: 'Home',
57 | },
58 | {
59 | path: '/status-updates/dashboard',
60 | name: 'Status Update',
61 | },
62 | {
63 | path: '/status-updates/messages',
64 | name: 'Messages',
65 | },
66 | ];
67 |
68 | return (
69 |
70 |
75 | {
79 | setLoaded(false);
80 | setDate(e.format('YYYY-MM-DD'));
81 | }}
82 | format="DD-MM-YYYY"
83 | value={moment(date)}
84 | />
85 |
86 | {data.length > 0 ? (
87 | data.map((message) => (
88 |
89 |
102 | }
103 | title={message.member.fullName}
104 | />
105 | }
106 | >
107 |
108 |
109 |
110 | ))
111 | ) : (
112 |
113 | Can't find any status updates
114 |
115 | )}
116 |
117 |
118 | );
119 | };
120 |
121 | export default Messages;
122 |
--------------------------------------------------------------------------------
/pages/status-updates/individual-report.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 |
3 | // antd components
4 | import Card from 'antd/lib/card';
5 | import Input from 'antd/lib/input';
6 | import Avatar from 'antd/lib/avatar';
7 |
8 | import dataFetch from '../../utils/dataFetch';
9 | import Base from '../../components/base';
10 | import TitleBar from '../../components/titlebar';
11 |
12 | const { Search } = Input;
13 | const { Meta } = Card;
14 |
15 | const IndividualReport = (props) => {
16 | const [data, setData] = useState([]);
17 | const [username, setUsername] = useState('');
18 | const [isLoaded, setLoaded] = useState(false);
19 |
20 | const query = `
21 | query getMemberStatusUpdates($username: String!){
22 | getMemberStatusUpdates(username:$username){
23 | message
24 | date
25 | member{
26 | fullName
27 | profile{
28 | profilePic
29 | }
30 | avatar{
31 | githubUsername
32 | }
33 | }
34 | }
35 | }`;
36 |
37 | const routes = [
38 | {
39 | path: '/',
40 | name: 'Home',
41 | },
42 | {
43 | path: '/status-updates/individual-report',
44 | name: 'Status Update',
45 | },
46 | ];
47 |
48 | const fetchData = async (variables) => dataFetch({ query, variables });
49 |
50 | const getMemberUpdates = (username) => {
51 | setUsername(username);
52 | const variables = { username };
53 | fetchData(variables).then((r) => {
54 | setData(r.data.getMemberStatusUpdates);
55 | setLoaded(true);
56 | });
57 | };
58 |
59 | return (
60 |
61 |
66 |
67 |
68 | getMemberUpdates(value.toLowerCase())}
71 | style={{ width: 350 }}
72 | enterButton
73 | />
74 |
75 |
76 | {data.length > 0 ? (
77 |
78 | No of status updates: {data.length}
79 |
80 | ) : null}
81 |
82 | {data.length > 0 ? (
83 | data.map((report) => (
84 |
85 |
98 | }
99 | title={report.member.fullName}
100 | />
101 | }
102 | extra={report.date}
103 | >
104 |
105 |
106 |
107 | ))
108 | ) : (
109 |
113 | Type the username to get the report
114 |
115 | )}
116 |
117 | );
118 | };
119 |
120 | export default IndividualReport;
121 |
--------------------------------------------------------------------------------
/pages/form/[id]/index.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import Link from 'next/link';
3 | import { useRouter } from 'next/router';
4 |
5 | // antd components
6 | import Button from 'antd/lib/button';
7 | import Statistic from 'antd/lib/statistic';
8 | import Result from 'antd/lib/result';
9 | import Tabs from 'antd/lib/tabs';
10 |
11 | import dataFetch from '../../../utils/dataFetch';
12 | import Base from '../../../components/base';
13 | import TitleBar from '../../../components/titlebar';
14 | import FormFields from '../../../modules/forms/formFields';
15 |
16 | const FormId = (props) => {
17 | const formID = useRouter().query.id;
18 | const [data, setData] = useState('');
19 | const [isLoaded, setLoaded] = useState(false);
20 |
21 | const query = `query getFormDetails($formID: Int!){
22 | getForm(formID: $formID)
23 | {
24 | name
25 | isActive
26 | allowMultiple
27 | submissionDeadline
28 | applicationLimit
29 | entriesCount
30 | fields
31 | {
32 | question
33 | required
34 | important
35 | type
36 | regex
37 | key
38 | }
39 | }
40 | }`;
41 |
42 | const fetchData = async (variables) => dataFetch({ query, variables });
43 | useEffect(() => {
44 | if (!isLoaded && formID) {
45 | fetchData({ formID }).then((r) => {
46 | setLoaded(true);
47 | setData(r.data.getForm);
48 | });
49 | }
50 | });
51 |
52 | const routes = [
53 | {
54 | path: '/',
55 | name: 'Home',
56 | },
57 | {
58 | path: '/form/view-forms',
59 | name: 'Forms',
60 | },
61 | {
62 | path: `#`,
63 | name: isLoaded ? `${data.name}` : formID,
64 | },
65 | ];
66 |
67 | const formStat = (
68 |
69 |
70 | new Date())
77 | ? 'Active'
78 | : 'Inactive'
79 | }
80 | />
81 |
82 |
83 | );
84 |
85 | const formActions = (
86 |
87 |
88 |
91 |
92 |
95 |
96 | );
97 |
98 | const headerContent = (
99 |
100 |
101 |
{formStat}
102 |
103 | );
104 |
105 | const panes = (
106 |
107 |
108 | We are working on it}
112 | />
113 |
114 |
115 | {isLoaded ? : null}
116 |
117 |
118 | );
119 |
120 | return (
121 |
122 |
134 |
135 | );
136 | };
137 |
138 | export default FormId;
139 |
--------------------------------------------------------------------------------
/components/links.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | DashboardOutlined,
4 | DeploymentUnitOutlined,
5 | ScheduleOutlined,
6 | CalendarOutlined,
7 | FormOutlined,
8 | GlobalOutlined,
9 | UserOutlined,
10 | SettingOutlined,
11 | LogoutOutlined,
12 | ContainerOutlined,
13 | } from '@ant-design/icons';
14 |
15 | export const links = [
16 | {
17 | title: 'Dashboard',
18 | key: '/',
19 | icon: ,
20 | },
21 | {
22 | title: 'Attendance',
23 | key: 'attendance',
24 | icon: ,
25 | items: [
26 | {
27 | key: 'dashboard',
28 | title: 'Dashboard',
29 | },
30 | // {
31 | // key: 'individual-report',
32 | // title: 'Individual Report',
33 | // },
34 | {
35 | key: 'live-report',
36 | title: 'Live Attendance',
37 | },
38 | {
39 | key: 'daily-report',
40 | title: 'Daily Report',
41 | },
42 | {
43 | key: 'stats',
44 | title: 'Stats',
45 | },
46 | ],
47 | },
48 | {
49 | title: 'Status Updates',
50 | key: 'status-updates',
51 | icon: ,
52 | items: [
53 | {
54 | key: 'dashboard',
55 | title: 'Dashboard',
56 | },
57 | {
58 | key: 'individual-report',
59 | title: 'Individual Report',
60 | },
61 | {
62 | key: 'daily-report',
63 | title: 'Daily Report',
64 | },
65 | {
66 | key: 'messages',
67 | title: 'Messages',
68 | },
69 | {
70 | key: 'stats',
71 | title: 'Stats',
72 | },
73 | ],
74 | },
75 | {
76 | title: 'Calendar',
77 | key: 'calendar',
78 | icon: ,
79 | items: [
80 | {
81 | key: 'create-event',
82 | title: 'Create Event',
83 | },
84 | {
85 | key: 'view-calendar',
86 | title: 'View Calendar',
87 | },
88 | ],
89 | },
90 | {
91 | title: 'Forms',
92 | key: 'forms',
93 | icon: ,
94 | items: [
95 | {
96 | key: 'create-forms',
97 | title: 'Create Forms',
98 | },
99 | {
100 | key: 'view-forms',
101 | title: 'View Forms',
102 | },
103 | ],
104 | },
105 | {
106 | title: 'Blog',
107 | key: 'blog',
108 | icon: ,
109 | items: [
110 | {
111 | key: 'create-blog',
112 | title: 'Create Blog',
113 | },
114 | ],
115 | },
116 | {
117 | title: 'Events',
118 | key: 'events',
119 | icon: ,
120 | items: [
121 | {
122 | key: 'check-in',
123 | title: 'QR Scanner',
124 | },
125 | ],
126 | },
127 | {
128 | title: 'Account',
129 | key: 'account',
130 | icon: ,
131 | items: [
132 | // {
133 | // key: 'profile',
134 | // title: 'My Profile',
135 | // },
136 | {
137 | key: 'settings',
138 | title: 'Settings',
139 | },
140 | ],
141 | },
142 | {
143 | title: 'Admin',
144 | key: 'admin',
145 | icon: ,
146 | adminExclusive: true,
147 | items: [
148 | {
149 | key: 'manage-users',
150 | title: 'Manage Users',
151 | },
152 | ],
153 | },
154 | // {
155 | // title: 'Settings',
156 | // key: 'settings',
157 | // icon: ,
158 | // items: [
159 | // {
160 | // key: 'general',
161 | // title: 'General Settings',
162 | // },
163 | // {
164 | // key: 'privacy',
165 | // title: 'Privacy',
166 | // },
167 | // {
168 | // key: 'appearance',
169 | // title: 'Appearance',
170 | // },
171 | // {
172 | // key: 'notifications',
173 | // title: 'Notifications',
174 | // },
175 | // ],
176 | // },
177 | {
178 | title: 'Logout',
179 | key: 'logout',
180 | icon: ,
181 | },
182 | ];
183 |
--------------------------------------------------------------------------------
/pages/events/check-in.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import dynamic from 'next/dynamic';
3 |
4 | // antd components
5 | import Card from 'antd/lib/card';
6 | import Button from 'antd/lib/button';
7 |
8 | import { getApplicant as query } from '../../utils/queries.js';
9 | import { CheckIn as Mutation } from '../../utils/mutations';
10 | import TitleBar from '../../components/titlebar';
11 | import Base from '../../components/base';
12 | import dataFetch from '../../utils/dataFetch';
13 |
14 | const QrReader = dynamic(() => import('react-qr-reader'), { ssr: false });
15 |
16 | const CheckIn = (props) => {
17 | const [data, setData] = useState('');
18 | const [result, setResult] = useState('No Results');
19 | const [status, setStatus] = useState('');
20 | const [errorText, setErrorText] = useState('');
21 | const [submitError, setSubmitError] = useState('');
22 |
23 | const delay = 200;
24 |
25 | function handleScan(scanResult) {
26 | if (scanResult) {
27 | if (result !== scanResult) {
28 | setResult(scanResult);
29 | setStatus('');
30 | setErrorText('');
31 | scanQR();
32 | }
33 | }
34 | }
35 |
36 | const scanQR = async () => {
37 | const variables = { hash: result };
38 | const response = await dataFetch({ query, variables });
39 | if (!Object.prototype.hasOwnProperty.call(response, 'errors')) {
40 | setData(response.data);
41 | } else {
42 | setErrorText(response.errors[0].message);
43 | setData('');
44 | setSubmitError('');
45 | }
46 | };
47 |
48 | const submitQR = async () => {
49 | const variables = { appID: data.getApplicant.id };
50 | const response = await dataFetch({ query: Mutation, variables });
51 | if (!Object.prototype.hasOwnProperty.call(response, 'errors')) {
52 | setStatus(response.data.checkIn.status);
53 | } else {
54 | setSubmitError(response.errors[0].message);
55 | setErrorText('');
56 | }
57 | };
58 |
59 | const routes = [
60 | {
61 | path: '/',
62 | name: 'Home',
63 | },
64 | {
65 | path: '/events',
66 | name: 'Events',
67 | },
68 | {
69 | path: '/check-in',
70 | name: 'Check In',
71 | },
72 | ];
73 |
74 | return (
75 |
76 |
77 |
118 |
119 | );
120 | };
121 |
122 | export default CheckIn;
123 |
--------------------------------------------------------------------------------
/components/login/loginForm.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { useRouter } from 'next/router';
3 | import Cookies from 'universal-cookie';
4 | import { UserOutlined, LockOutlined } from '@ant-design/icons';
5 | import Link from 'next/link';
6 |
7 | // antd components
8 | import Card from 'antd/lib/card';
9 | import Button from 'antd/lib/button';
10 | import Form from 'antd/lib/form';
11 | import Checkbox from 'antd/lib/checkbox';
12 | import Input from 'antd/lib/input';
13 |
14 | import dataFetch from '../../utils/dataFetch';
15 | import { TokenAuth as query } from '../../utils/mutations';
16 |
17 | const cookies = new Cookies();
18 |
19 | const LoginForm = () => {
20 | const router = useRouter();
21 | const [cookiesSet, setCookies] = useState(false);
22 | const [authFail, setAuthFail] = useState(false);
23 | const [loading, setLoading] = useState(false);
24 |
25 | useEffect(() => {
26 | const token = cookies.get('token');
27 | if (token != null) {
28 | setCookies(true);
29 | router.push('/');
30 | }
31 | });
32 |
33 | const login = async (variables) => await dataFetch({ query, variables });
34 |
35 | const onFinish = (values) => {
36 | login(values).then((response) => {
37 | if (!Object.prototype.hasOwnProperty.call(response, 'errors')) {
38 | const tokenMaxAge =
39 | response.data.tokenAuth.payload.exp -
40 | response.data.tokenAuth.payload.origIat;
41 | cookies.set('token', response.data.tokenAuth.token, {
42 | path: '/',
43 | maxAge: tokenMaxAge,
44 | });
45 | cookies.set('refreshToken', response.data.tokenAuth.refreshToken, {
46 | path: '/',
47 | maxAge: response.data.tokenAuth.refreshExpiresIn,
48 | });
49 | cookies.set('username', values.username, { path: '/' });
50 | cookies.set('expiry', response.data.tokenAuth.payload.exp);
51 | setCookies(true);
52 | router.push('/');
53 | } else {
54 | setAuthFail(true);
55 | setLoading(false);
56 | }
57 | });
58 | };
59 |
60 | const errorMessage = (
61 | Please enter vaild credentials
62 | );
63 |
64 | const onFinishFailed = (errorInfo) => {
65 | console.error(errorInfo);
66 | };
67 |
68 | return !loading ? (
69 |
70 |
71 | {authFail ? errorMessage : null}
72 |
85 | }
87 | placeholder="Username"
88 | />
89 |
90 |
95 | }
97 | type="password"
98 | placeholder="Password"
99 | />
100 |
101 |
102 | Remember me
103 |
104 |
105 |
112 |
113 | Forgot Password?
114 |
115 |
116 | Don't have account? Register
117 |
118 |
119 | ) : (
120 | Loading
121 | );
122 | };
123 |
124 | export default LoginForm;
125 |
--------------------------------------------------------------------------------
/components/base.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import Cookies from 'universal-cookie';
3 | import { useRouter } from 'next/router';
4 | import moment from 'moment';
5 | import PropTypes from 'prop-types';
6 |
7 | import Sidebar from '../components/sidebar';
8 | import dataFetch from '../utils/dataFetch';
9 | import Header from './header';
10 | import LoadingScreen from './loadingScreen';
11 | import AuthRequired from './authRequired';
12 |
13 | const cookies = new Cookies();
14 |
15 | const Base = ({
16 | children,
17 | title,
18 | adminRequired = false,
19 | verificationRequired = true,
20 | }) => {
21 | const router = useRouter();
22 | const [loaded, setLoaded] = useState(false);
23 | const [userLoaded, setUserLoaded] = useState(false);
24 | const [data, setData] = useState({});
25 |
26 | const refreshTokenQuery = `
27 | mutation refresh($refresh: String!) {
28 | refreshToken(refreshToken: $refresh) {
29 | token
30 | refreshToken
31 | payload
32 | refreshExpiresIn
33 | }
34 | }
35 | `;
36 | const refreshTokens = async (refreshTokenVariables) =>
37 | dataFetch({ query: refreshTokenQuery, variables: refreshTokenVariables });
38 | const fetchData = async () =>
39 | await dataFetch({ query: `{ isAdmin isClubMember }` });
40 |
41 | useEffect(() => {
42 | const token = cookies.get('token');
43 | const refreshToken = cookies.get('refreshToken');
44 | const expiry = cookies.get('expiry');
45 | const fetchStatus = () => {
46 | if (!userLoaded) {
47 | fetchData().then((r) => {
48 | setData(r.data);
49 | }).finally(() => setUserLoaded(true));
50 | }
51 | };
52 | if (!loaded) {
53 | if (token !== undefined || refreshToken !== undefined) {
54 | fetchStatus();
55 | if (moment().unix() + 50 > expiry) {
56 | const refreshTokenVariables = { refresh: refreshToken };
57 | refreshTokens(refreshTokenVariables).then((response) => {
58 | if (!Object.prototype.hasOwnProperty.call(response, 'errors')) {
59 | const tokenMaxAge =
60 | response.data.refreshToken.payload.exp -
61 | response.data.refreshToken.payload.origIat;
62 | cookies.set('token', response.data.refreshToken.token, {
63 | path: '/',
64 | maxAge: tokenMaxAge,
65 | });
66 | cookies.set('refreshToken', response.data.refreshToken.refreshToken, {
67 | path: '/',
68 | maxAge: response.data.refreshToken.refreshExpiresIn,
69 | });
70 | cookies.set('username', response.data.refreshToken.payload.username, {
71 | path: '/',
72 | maxAge: response.data.refreshToken.refreshExpiresIn,
73 | });
74 | cookies.set('expiry', response.data.refreshToken.payload.exp, {
75 | path: '/',
76 | maxAge: response.data.refreshToken.refreshExpiresIn,
77 | });
78 | } else {
79 | router.push('/logout');
80 | }
81 | });
82 | }
83 | } else {
84 | router.push('/login');
85 | }
86 | setLoaded(true);
87 | }
88 | }, []);
89 |
90 | return loaded ? (
91 | adminRequired || verificationRequired ? (
92 |
99 |
100 |
101 | {children}
102 |
103 |
104 | ) : (
105 |
106 |
107 |
108 | {children}
109 |
110 |
111 | )
112 | ) : (
113 |
114 | );
115 | };
116 |
117 | Base.propTypes = {
118 | title: PropTypes.string,
119 | children: PropTypes.node,
120 | adminRequired: PropTypes.bool,
121 | verificationRequired: PropTypes.bool,
122 | };
123 |
124 | export default Base;
125 |
--------------------------------------------------------------------------------
/modules/attendance/components/TrendGraph.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import classnames from 'classnames';
3 | import { Line } from 'react-chartjs-2';
4 | import Moment from 'moment';
5 | import { extendMoment } from 'moment-range';
6 | import PropTypes from 'prop-types';
7 |
8 | // antd components
9 | import Card from 'antd/lib/card';
10 |
11 | const moment = extendMoment(Moment);
12 |
13 | const TrendGraph = ({ isLoaded, data }) => {
14 | const [type, setType] = useState('attendee');
15 |
16 | const x = [];
17 | const y = [];
18 | data.map((r) => {
19 | x.push(r.date);
20 | y.push(r.membersPresent);
21 | });
22 |
23 | const xdurr = [];
24 | const ydurr = [];
25 | data.map((r) => {
26 | xdurr.push(r.date);
27 | ydurr.push(moment.duration(r.avgDuration).asHours().toFixed(2));
28 | });
29 | let options = {
30 | maintainAspectRatio: false,
31 | legend: {
32 | display: false,
33 | },
34 | tooltips: {
35 | backgroundColor: '#f5f5f5',
36 | titleFontColor: '#333',
37 | bodyFontColor: '#666',
38 | bodySpacing: 4,
39 | xPadding: 12,
40 | mode: 'nearest',
41 | intersect: 0,
42 | position: 'nearest',
43 | },
44 | responsive: true,
45 | scales: {
46 | yAxes: [
47 | {
48 | barPercentage: 1.6,
49 | gridLines: {
50 | drawBorder: false,
51 | color: 'rgba(29,140,248,0.0)',
52 | zeroLineColor: 'transparent',
53 | },
54 | ticks: {
55 | suggestedMin: 0,
56 | suggestedMax: 5,
57 | padding: 20,
58 | fontColor: '#9a9a9a',
59 | },
60 | },
61 | ],
62 | xAxes: [
63 | {
64 | barPercentage: 1.6,
65 | gridLines: {
66 | drawBorder: false,
67 | color: 'rgba(29,140,248,0.1)',
68 | zeroLineColor: 'transparent',
69 | },
70 | ticks: {
71 | padding: 20,
72 | fontColor: '#9a9a9a',
73 | },
74 | },
75 | ],
76 | },
77 | };
78 |
79 | let AttendanceGraph = {
80 | data: (canvas) => {
81 | let ctx = canvas.getContext('2d');
82 |
83 | let gradientStroke = ctx.createLinearGradient(0, 2300, 0, 50);
84 |
85 | gradientStroke.addColorStop(1, 'rgba(29,140,248,0.2)');
86 | gradientStroke.addColorStop(0.4, 'rgba(29,140,248,0.0)');
87 | gradientStroke.addColorStop(0, 'rgba(29,140,248,0)'); //blue colors
88 |
89 | return {
90 | labels: type === 'attendee' ? x : xdurr,
91 | datasets: [
92 | {
93 | label: 'Attendance Graph',
94 | fill: true,
95 | backgroundColor: gradientStroke,
96 | borderColor: '#1f8ef1',
97 | borderWidth: 2,
98 | borderDash: [],
99 | borderDashOffset: 0.0,
100 | pointBackgroundColor: '#1f8ef1',
101 | pointBorderColor: 'rgba(255,255,255,0)',
102 | pointHoverBackgroundColor: '#1f8ef1',
103 | pointBorderWidth: 20,
104 | pointHoverRadius: 4,
105 | pointHoverBorderWidth: 15,
106 | pointRadius: 4,
107 | data: type === 'attendee' ? y : ydurr,
108 | },
109 | ],
110 | };
111 | },
112 | };
113 |
114 | return (
115 |
116 |
117 |
118 |
Attendance Trends
119 |
120 |
121 |
130 |
131 |
132 |
136 | {isLoaded ? : null}
137 |
138 |
139 | );
140 | };
141 |
142 | TrendGraph.propTypes = {
143 | data: PropTypes.object,
144 | isLoaded: PropTypes.bool,
145 | };
146 |
147 | export default TrendGraph;
148 |
--------------------------------------------------------------------------------
/modules/forms/fieldEditor.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | // antd components
5 | import Select from 'antd/lib/select';
6 | import Switch from 'antd/lib/switch';
7 |
8 | const FieldEditor = ({ field }) => {
9 | const fieldTypes = [
10 | {
11 | name: 'Commonly Used',
12 | options: [
13 | { name: 'Email', value: 'email' },
14 | { name: 'Phone', value: 'phone' },
15 | { name: 'Age', value: 'age' },
16 | { name: 'Gender', value: 'gender' },
17 | ],
18 | },
19 | {
20 | name: 'String',
21 | options: [
22 | { name: 'String', value: 'string' },
23 | { name: 'Paragraph', value: 'paragraph' },
24 | { name: 'Email', value: 'email' },
25 | { name: 'Phone', value: 'phone' },
26 | ],
27 | },
28 | {
29 | name: 'Number',
30 | options: [
31 | { name: 'Number', value: 'number' },
32 | { name: 'Integer', value: 'integer' },
33 | { name: 'Whole Number', value: 'wholeNumber' },
34 | ],
35 | },
36 | {
37 | name: 'Select',
38 | options: [
39 | { name: 'Select / Radio', value: 'radio' },
40 | { name: 'Multi-Select / Checkbox', value: 'checkbox' },
41 | ],
42 | },
43 | {
44 | name: 'Upload',
45 | options: [
46 | { name: 'Image Upload', value: 'image', disabled: true },
47 | { name: 'File Upload', value: 'file', disabled: true },
48 | ],
49 | },
50 | {
51 | name: 'Other',
52 | options: [
53 | { name: 'Custom Regex', value: 'regex' },
54 | { name: 'Slot', value: 'slot' },
55 | ],
56 | },
57 | ];
58 |
59 | const renderFieldTypeSelector = (
60 |
61 |
62 |
78 |
79 | );
80 |
81 | return (
82 |
83 |
84 |
85 |
86 |
87 |
88 | You cannot change key of a field, once entries for the form has been
89 | received.
90 |
91 |
92 |
{renderFieldTypeSelector}
93 | {field.type === 'regex' ? (
94 |
95 |
96 |
97 |
98 | ) : null}
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
130 |
131 | );
132 | };
133 |
134 | FieldEditor.propTypes = {
135 | field: PropTypes.object,
136 | };
137 |
138 | export default FieldEditor;
139 |
--------------------------------------------------------------------------------
/pages/forms/view-forms.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import {
3 | CheckCircleTwoTone,
4 | CloseCircleTwoTone,
5 | EyeOutlined,
6 | LinkOutlined,
7 | EditOutlined,
8 | DeleteOutlined,
9 | } from '@ant-design/icons';
10 | import Link from 'next/link';
11 |
12 | // antd components
13 | import Table from 'antd/lib/table';
14 | import Button from 'antd/lib/button';
15 | import Badge from 'antd/lib/badge';
16 |
17 | import dataFetch from '../../utils/dataFetch';
18 | import Base from '../../components/base';
19 | import TitleBar from '../../components/titlebar';
20 |
21 | const ViewForms = (props) => {
22 | const [data, setData] = useState('');
23 | const [isLoaded, setLoaded] = useState(false);
24 |
25 | const query = `query{
26 | viewForms
27 | {
28 | id
29 | name
30 | isActive
31 | allowMultiple
32 | entriesCount
33 | submissionDeadline
34 | applicationLimit
35 | }
36 | }`;
37 |
38 | const fetchData = async () => dataFetch({ query });
39 |
40 | useEffect(() => {
41 | if (!isLoaded) {
42 | fetchData().then((r) => {
43 | setData(r.data.viewForms);
44 | setLoaded(true);
45 | });
46 | }
47 | });
48 |
49 | const routes = [
50 | {
51 | path: '/',
52 | name: 'Home',
53 | },
54 | {
55 | path: '/forms',
56 | name: 'Forms',
57 | },
58 | {
59 | path: '/forms/view-forms',
60 | name: 'View Forms',
61 | },
62 | ];
63 |
64 | const columns = [
65 | {
66 | title: 'Name',
67 | dataIndex: 'name',
68 | key: 'name',
69 | sorter: (a, b) => a.name.localeCompare(b.name),
70 | render: (name, obj) => {name},
71 | },
72 | {
73 | title: 'Entries',
74 | dataIndex: 'entriesCount',
75 | key: 'entriesCount',
76 | render: (entriesCount, obj) => (
77 |
78 |
79 |
80 | ),
81 | },
82 | {
83 | title: 'Active',
84 | dataIndex: 'isActive',
85 | key: 'isActive',
86 | render: (status) => ,
87 | },
88 | {
89 | title: 'Multiple Entries',
90 | dataIndex: 'allowMultiple',
91 | key: 'allowMultiple',
92 | filters: [
93 | {
94 | text: 'Allowed',
95 | value: true,
96 | },
97 | {
98 | text: 'Denied',
99 | value: false,
100 | },
101 | ],
102 | onFilter: (value, record) => record.allowMultiple === value,
103 | render: (status) =>
104 | status ? (
105 |
106 | ) : (
107 |
108 | ),
109 | },
110 | {
111 | title: 'Submission Deadline',
112 | dataIndex: 'submissionDeadline',
113 | key: 'submissionDeadline',
114 | render: (timestamp) =>
115 | timestamp ? (
116 | new Date(timestamp).toLocaleString()
117 | ) : (
118 |
119 | ),
120 | },
121 | {
122 | title: 'Admission Limit',
123 | dataIndex: 'applicationLimit',
124 | key: 'applicationLimit',
125 | },
126 | {
127 | title: 'Actions',
128 | dataIndex: '',
129 | key: 'x',
130 | render: () => (
131 |
132 | } className="m-1" />
133 | } className="m-1" />
134 | } className="m-1" />
135 | } className="m-1" />
136 |
137 | ),
138 | },
139 | ];
140 |
141 | return (
142 |
143 |
148 |
156 |
157 | );
158 | };
159 |
160 | export default ViewForms;
161 |
--------------------------------------------------------------------------------
/modules/attendance/components/DailyReportCard.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Moment from 'moment';
3 | import { extendMoment } from 'moment-range';
4 | import PropTypes from 'prop-types';
5 |
6 | // antd components
7 | import Card from 'antd/lib/card';
8 | import List from 'antd/lib/list';
9 | import Avatar from 'antd/lib/avatar';
10 | import Badge from 'antd/lib/badge';
11 | import Tabs from 'antd/lib/tabs';
12 |
13 | const { TabPane } = Tabs;
14 | const moment = extendMoment(Moment);
15 |
16 | const DailyReportCard = ({ data, isLoaded }) => {
17 | const membersCard = (members, status) => (
18 | (
23 |
24 |
25 |
34 | }
35 | title={
36 |
37 |
38 | {m.member.firstName} {m.member.lastName}
39 |
40 |
41 | }
42 | description={
43 |
44 | {status === 'late'
45 | ? `${moment
46 | .duration(
47 | moment(data.date).set('hour', 17).set('minute', 15) -
48 | moment(m.firstSeen)
49 | )
50 | .asMinutes()}mins late, came to lab at ${moment(
51 | m.firstSeen
52 | ).format('hh:mm')}`
53 | : status === 'present'
54 | ? m.duration
55 | : status === 'absent'
56 | ? m.lastSeen
57 | ? `Last seen ${moment
58 | .duration(moment().diff(m.lastSeen))
59 | .humanize()} ago`
60 | : 'No previous Record'
61 | : null}
62 |
63 | }
64 | />
65 |
66 |
67 | )}
68 | />
69 | );
70 |
71 | const lateMembers = isLoaded
72 | ? data.membersPresent.filter((m) => {
73 | if (
74 | moment(m.firstSeen) > moment(data.date).set('hour', 17).set('minute', 15)
75 | )
76 | return m;
77 | })
78 | : null;
79 |
80 | return (
81 |
82 |
83 |
87 | Present
88 |
92 |
93 | }
94 | >
95 | {isLoaded ? membersCard(data.membersPresent, 'present') : null}
96 |
97 |
102 | Absent
103 |
107 |
108 | }
109 | >
110 | {isLoaded ? membersCard(data.membersAbsent, 'absent') : null}
111 |
112 |
117 | Late To Lab
118 |
122 |
123 | }
124 | >
125 | {isLoaded ? membersCard(lateMembers, 'late') : null}
126 |
127 |
128 |
129 | );
130 | };
131 |
132 | DailyReportCard.propTypes = {
133 | data: PropTypes.object,
134 | isLoaded: PropTypes.bool,
135 | };
136 |
137 | export default DailyReportCard;
138 |
--------------------------------------------------------------------------------
/components/event/eventForm.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { extendMoment } from 'moment-range';
3 | import Moment from 'moment';
4 | import Link from 'next/link';
5 |
6 | // antd components
7 | import DatePicker from 'antd/lib/date-picker';
8 | import Button from 'antd/lib/button';
9 | import Result from 'antd/lib/result';
10 |
11 | import dataFetch from '../../utils/dataFetch';
12 |
13 | const EventForm = () => {
14 | const moment = extendMoment(Moment);
15 | const [isLoading, setLoaded] = useState(false);
16 | const [name, setName] = useState('');
17 | const [details, setDetails] = useState('');
18 | const [startDate, setStartDate] = useState(moment().format('YYYY-MM-DD'));
19 | const [endDate, setEndDate] = useState(moment().format('YYYY-MM-DD'));
20 | const [error, setErrorText] = useState('');
21 | const [success, setSuccessText] = useState('');
22 |
23 | const query = `
24 | mutation createEvent($name: String!, $details: String!, $startDate: Date!, $endDate: Date!){
25 | createEvent(details:$details, endTimestamp:$endDate, startTimestamp: $startDate, name:$name){
26 | id
27 | }
28 | }
29 | `;
30 |
31 | const submitForm = async (variables) => dataFetch({ query, variables });
32 |
33 | const register = () => {
34 | const variables = { name, details, startDate, endDate };
35 | submitForm(variables).then((r) => {
36 | if (Object.prototype.hasOwnProperty.call(r, 'errors')) {
37 | setErrorText(r.errors[0].message);
38 | } else {
39 | setSuccessText(r.data.id);
40 | setErrorText('');
41 | }
42 | });
43 | };
44 | return !isLoading ? (
45 |
116 | ) : (
117 |
118 | {success !== '' ? (
119 |
124 |
125 |
126 | }
127 | />
128 | ) : error !== '' ? (
129 | {error}
130 | ) : (
131 | Submitting. Please Wait
132 | )}
133 |
134 | );
135 | };
136 |
137 | export default EventForm;
138 |
--------------------------------------------------------------------------------
/components/account/changePassword.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import Link from 'next/link';
3 |
4 | // antd components
5 | import Button from 'antd/lib/button';
6 | import Result from 'antd/lib/result';
7 |
8 | import dataFetch from '../../utils/dataFetch';
9 |
10 | const ChangePassword = () => {
11 | const [isLoading, setLoaded] = useState(false);
12 | const [password, setPassword] = useState('');
13 | const [newPassword, setNewPassword] = useState('');
14 | const [verifyPassword, setVerifyPassword] = useState('');
15 | const [error, setErrorText] = useState('');
16 | const [success, setSuccessText] = useState('');
17 |
18 | const query = `
19 | mutation ($password: String!, $newPassword: String!){
20 | changePassword(password:$password, newPassword:$newPassword){
21 | status
22 | }
23 | }
24 | `;
25 |
26 | const submitForm = async (variables) => dataFetch({ query, variables });
27 |
28 | const changePassword = () => {
29 | if (newPassword === verifyPassword) {
30 | const variables = { password, newPassword };
31 | submitForm(variables).then((r) => {
32 | if (Object.prototype.hasOwnProperty.call(r, 'errors')) {
33 | setErrorText(r.errors[0].message);
34 | } else {
35 | setSuccessText(r.data.status);
36 | setErrorText('');
37 | }
38 | });
39 | } else {
40 | setErrorText('Please enter same passwords');
41 | setSuccessText('');
42 | }
43 | };
44 |
45 | return (
46 |
47 |
48 |
Change Password
49 | {!isLoading ? (
50 |
107 | ) : (
108 |
109 | {success !== '' ? (
110 |
115 |
116 |
117 | }
118 | />
119 | ) : error !== '' ? (
120 | {error}
121 | ) : (
122 | Submitting. Please Wait
123 | )}
124 |
125 | )}
126 |
127 |
128 | );
129 | };
130 |
131 | export default ChangePassword;
132 |
--------------------------------------------------------------------------------
/components/register/registerForm.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { useRouter } from 'next/router';
3 | import Cookies from 'universal-cookie';
4 | import { UserOutlined, LockOutlined, MailOutlined } from '@ant-design/icons';
5 |
6 | // antd components
7 | import Card from 'antd/lib/card';
8 | import Button from 'antd/lib/button';
9 | import Form from 'antd/lib/form';
10 | import Input from 'antd/lib/input';
11 |
12 | import dataFetch from '../../utils/dataFetch';
13 | import { TokenAuth as query, Register } from '../../utils/mutations';
14 | import Link from 'next/link';
15 |
16 | const cookies = new Cookies();
17 |
18 | const RegisterForm = () => {
19 | const router = useRouter();
20 | const [cookiesSet, setCookies] = useState(false);
21 | const [authFail, setAuthFail] = useState(false);
22 | const [loading, setLoading] = useState(false);
23 |
24 | useEffect(() => {
25 | const token = cookies.get('token');
26 | if (token != null) {
27 | setCookies(true);
28 | router.push('/');
29 | }
30 | });
31 |
32 | const login = async (variables) => await dataFetch({ query, variables });
33 | const register = async (variables) =>
34 | await dataFetch({ query: Register, variables });
35 |
36 | const onFinish = (values) => {
37 | register(values).then((r) => {
38 | if (!Object.prototype.hasOwnProperty.call(r, 'errors')) {
39 | delete values.email;
40 | login(values).then((response) => {
41 | if (!Object.prototype.hasOwnProperty.call(response, 'errors')) {
42 | const tokenMaxAge =
43 | response.data.tokenAuth.payload.exp -
44 | response.data.tokenAuth.payload.origIat;
45 | cookies.set('token', response.data.tokenAuth.token, {
46 | path: '/',
47 | maxAge: tokenMaxAge,
48 | });
49 | cookies.set('refreshToken', response.data.tokenAuth.refreshToken, {
50 | path: '/',
51 | maxAge: response.data.tokenAuth.refreshExpiresIn,
52 | });
53 | cookies.set('username', values.username, { path: '/' });
54 | cookies.set('expiry', response.data.tokenAuth.payload.exp);
55 | setCookies(true);
56 | router.push('/');
57 | }
58 | });
59 | } else {
60 | setAuthFail(true);
61 | setLoading(false);
62 | }
63 | });
64 | };
65 |
66 | const errorMessage = (
67 |
68 | Sorry, we are not able to register your account, please contact any of the
69 | administrator.
70 |
71 | );
72 |
73 | const onFinishFailed = (errorInfo) => {
74 | console.error(errorInfo);
75 | };
76 |
77 | return !loading ? (
78 |
79 |
80 | {authFail ? errorMessage : null}
81 |
94 | }
96 | placeholder="Email ID"
97 | />
98 |
99 |
104 | }
106 | placeholder="Username"
107 | />
108 |
109 |
114 | }
116 | type="password"
117 | placeholder="Password"
118 | />
119 |
120 |
121 |
128 |
129 |
130 | Already have an account? Login
131 |
132 | ) : (
133 | Loading
134 | );
135 | };
136 |
137 | export default RegisterForm;
138 |
--------------------------------------------------------------------------------
/modules/statusUpdates/components/TrendStatusGraph.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import classnames from 'classnames';
3 | import { Bar } from 'react-chartjs-2';
4 | import PropTypes from 'prop-types';
5 |
6 | // antd components
7 | import Card from 'antd/lib/card';
8 |
9 | const TrendStatusGraph = ({ isLoaded, data }) => {
10 | const x2019 = [];
11 | const y2019 = [];
12 | const x2018 = [];
13 | const y2018 = [];
14 | const x2017 = [];
15 | const y2017 = [];
16 | const x2016 = [];
17 | const y2016 = [];
18 |
19 | data.map((d) => {
20 | if (d.user.admissionYear === 2019) {
21 | x2019.push(d.statusCount);
22 | y2019.push(d.user.username);
23 | } else if (d.user.admissionYear === 2018) {
24 | x2018.push(d.statusCount);
25 | y2018.push(d.user.username);
26 | } else if (d.user.admissionYear === 2017) {
27 | x2017.push(d.statusCount);
28 | y2017.push(d.user.username);
29 | } else {
30 | x2016.push(d.statusCount);
31 | y2016.push(d.user.username);
32 | }
33 | });
34 | const [year, setYear] = useState('2019');
35 | let graph = {
36 | data: (canvas) => {
37 | let ctx = canvas.getContext('2d');
38 |
39 | let gradientStroke = ctx.createLinearGradient(0, 230, 0, 50);
40 |
41 | gradientStroke.addColorStop(1, 'rgba(29,140,248,0.2)');
42 | gradientStroke.addColorStop(0.4, 'rgba(29,140,248,0.0)');
43 | gradientStroke.addColorStop(0, 'rgba(29,140,248,0)');
44 |
45 | return {
46 | labels:
47 | year === '2019'
48 | ? y2019
49 | : year === '2018'
50 | ? y2018
51 | : year === '2017'
52 | ? y2017
53 | : y2016,
54 | datasets: [
55 | {
56 | label: 'Status Updates Sent',
57 | fill: true,
58 | backgroundColor: '#1f8ef1',
59 | hoverBackgroundColor: gradientStroke,
60 | borderColor: '#1f8ef1',
61 | borderWidth: 2,
62 | borderDash: [],
63 | borderDashOffset: 0.0,
64 | data:
65 | year === '2019'
66 | ? x2019
67 | : year === '2018'
68 | ? x2018
69 | : year === '2017'
70 | ? x2017
71 | : x2016,
72 | },
73 | ],
74 | };
75 | },
76 | options: {
77 | maintainAspectRatio: false,
78 | legend: {
79 | display: false,
80 | },
81 | tooltips: {
82 | backgroundColor: '#f5f5f5',
83 | titleFontColor: '#333',
84 | bodyFontColor: '#666',
85 | bodySpacing: 4,
86 | xPadding: 12,
87 | mode: 'nearest',
88 | intersect: 0,
89 | position: 'nearest',
90 | },
91 | responsive: true,
92 | scales: {
93 | yAxes: [
94 | {
95 | gridLines: {
96 | drawBorder: false,
97 | color: 'rgba(29,140,248,0.0)',
98 | zeroLineColor: 'transparent',
99 | },
100 | ticks: {
101 | suggestedMin: 0,
102 | suggestedMax: 5,
103 | padding: 20,
104 | fontColor: '#9a9a9a',
105 | },
106 | },
107 | ],
108 | xAxes: [
109 | {
110 | gridLines: {
111 | drawBorder: false,
112 | color: 'rgba(225,78,202,0.1)',
113 | zeroLineColor: 'transparent',
114 | },
115 | ticks: {
116 | padding: 20,
117 | fontColor: '#9e9e9e',
118 | },
119 | },
120 | ],
121 | },
122 | },
123 | };
124 | return (
125 |
126 |
127 |
128 |
Member Status Update Trends
129 |
130 |
131 |
142 |
143 |
144 |
148 | {isLoaded ? : null}
149 |
150 |
151 | );
152 | };
153 |
154 | TrendStatusGraph.propTypes = {
155 | data: PropTypes.object,
156 | isLoaded: PropTypes.bool,
157 | };
158 |
159 | export default TrendStatusGraph;
160 |
--------------------------------------------------------------------------------
/modules/attendance/components/TrendAttendanceGraph.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import classnames from 'classnames';
3 | import { Bar } from 'react-chartjs-2';
4 | import PropTypes from 'prop-types';
5 |
6 | // antd components
7 | import Card from 'antd/lib/card';
8 |
9 | const TrendAttendanceGraph = ({ isLoaded, data }) => {
10 | const x2019 = [];
11 | const y2019 = [];
12 | const x2018 = [];
13 | const y2018 = [];
14 | const x2017 = [];
15 | const y2017 = [];
16 | const x2016 = [];
17 | const y2016 = [];
18 |
19 | data.map((d) => {
20 | if (d.user.admissionYear === 2019) {
21 | x2019.push(d.presentCount);
22 | y2019.push(d.user.username);
23 | } else if (d.user.admissionYear === 2018) {
24 | x2018.push(d.presentCount);
25 | y2018.push(d.user.username);
26 | } else if (d.user.admissionYear === 2017) {
27 | x2017.push(d.presentCount);
28 | y2017.push(d.user.username);
29 | } else {
30 | x2016.push(d.presentCount);
31 | y2016.push(d.user.username);
32 | }
33 | });
34 | const [year, setYear] = useState('2019');
35 | let graph = {
36 | data: (canvas) => {
37 | let ctx = canvas.getContext('2d');
38 |
39 | let gradientStroke = ctx.createLinearGradient(0, 230, 0, 50);
40 |
41 | gradientStroke.addColorStop(1, 'rgba(29,140,248,0.2)');
42 | gradientStroke.addColorStop(0.4, 'rgba(29,140,248,0.0)');
43 | gradientStroke.addColorStop(0, 'rgba(29,140,248,0)');
44 |
45 | return {
46 | labels:
47 | year === '2019'
48 | ? y2019
49 | : year === '2018'
50 | ? y2018
51 | : year === '2017'
52 | ? y2017
53 | : y2016,
54 | datasets: [
55 | {
56 | label: 'Present Count',
57 | fill: true,
58 | backgroundColor: '#1f8ef1',
59 | hoverBackgroundColor: gradientStroke,
60 | borderColor: '#1f8ef1',
61 | borderWidth: 2,
62 | borderDash: [],
63 | borderDashOffset: 0.0,
64 | data:
65 | year === '2019'
66 | ? x2019
67 | : year === '2018'
68 | ? x2018
69 | : year === '2017'
70 | ? x2017
71 | : x2016,
72 | },
73 | ],
74 | };
75 | },
76 | options: {
77 | maintainAspectRatio: false,
78 | legend: {
79 | display: false,
80 | },
81 | tooltips: {
82 | backgroundColor: '#f5f5f5',
83 | titleFontColor: '#333',
84 | bodyFontColor: '#666',
85 | bodySpacing: 4,
86 | xPadding: 12,
87 | mode: 'nearest',
88 | intersect: 0,
89 | position: 'nearest',
90 | },
91 | responsive: true,
92 | scales: {
93 | yAxes: [
94 | {
95 | gridLines: {
96 | drawBorder: false,
97 | color: 'rgba(29,140,248,0.0)',
98 | zeroLineColor: 'transparent',
99 | },
100 | ticks: {
101 | suggestedMin: 0,
102 | suggestedMax: 5,
103 | padding: 20,
104 | fontColor: '#9a9a9a',
105 | },
106 | },
107 | ],
108 | xAxes: [
109 | {
110 | gridLines: {
111 | drawBorder: false,
112 | color: 'rgba(225,78,202,0.1)',
113 | zeroLineColor: 'transparent',
114 | },
115 | ticks: {
116 | padding: 20,
117 | fontColor: '#9e9e9e',
118 | },
119 | },
120 | ],
121 | },
122 | },
123 | };
124 | return (
125 |
126 |
127 |
128 |
Member Attendance Trends
129 |
130 |
131 |
142 |
143 |
144 |
148 | {isLoaded ? : null}
149 |
150 |
151 | );
152 | };
153 |
154 | TrendAttendanceGraph.propTypes = {
155 | isLoaded: PropTypes.bool,
156 | data: PropTypes.object,
157 | };
158 |
159 | export default TrendAttendanceGraph;
160 |
--------------------------------------------------------------------------------
/components/sidebar.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import Link from 'next/link';
3 | import { MenuOutlined } from '@ant-design/icons';
4 | import PropTypes from 'prop-types';
5 |
6 | // antd components
7 | import Menu from 'antd/lib/menu';
8 | import Layout from 'antd/lib/layout';
9 | import Drawer from 'antd/lib/drawer';
10 |
11 | import { links } from './links';
12 |
13 | const { SubMenu } = Menu;
14 | const { Sider } = Layout;
15 |
16 | function getWindowDimensions() {
17 | const { innerWidth: width, innerHeight: height } = window;
18 | return {
19 | width,
20 | height,
21 | };
22 | }
23 |
24 | function useWindowDimensions() {
25 | const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions());
26 |
27 | useEffect(() => {
28 | function handleResize() {
29 | setWindowDimensions(getWindowDimensions());
30 | }
31 |
32 | window.addEventListener('resize', handleResize);
33 | return () => window.removeEventListener('resize', handleResize);
34 | }, []);
35 |
36 | return windowDimensions;
37 | }
38 |
39 | const Sidebar = ({ selected, children, isAdmin }) => {
40 | const [sidebarVisible, setSidebarVisible] = useState(false);
41 | const { width } = useWindowDimensions();
42 |
43 | let selectedKeys = selected ? selected.slice(1).split('/') : [];
44 | selectedKeys.length > 0
45 | ? (selectedKeys = selectedKeys.map((e, i) => {
46 | if (i !== 0) {
47 | return selectedKeys[i - 1] + '-' + e;
48 | }
49 | return e;
50 | }))
51 | : [];
52 |
53 | const getMenuItem = (link, label, icon, key) => (
54 |
55 |
56 |
57 | {icon} {label}
58 |
59 |
60 |
61 | );
62 |
63 | const menu = (
64 |
109 | );
110 |
111 | const navbar = (
112 |
113 |
114 | {width < 600 ? (
115 |
116 |
117 | setSidebarVisible(true)}
120 | />
121 |
122 |
123 |

128 |
129 |
130 | ) : null}
131 |
132 |
setSidebarVisible(false)}
136 | visible={sidebarVisible}
137 | bodyStyle={{ padding: 0 }}
138 | >
139 | {menu}
140 |
141 |
142 | );
143 |
144 | return width > 600 ? (
145 |
146 |
155 | {menu}
156 |
157 | {children}
158 |
159 | ) : (
160 |
161 | {navbar}
162 |
{children}
163 |
164 | );
165 | };
166 |
167 | Sidebar.propTypes = {
168 | isAdmin: PropTypes.bool,
169 | children: PropTypes.node,
170 | selected: PropTypes.string,
171 | };
172 |
173 | export default Sidebar;
174 |
--------------------------------------------------------------------------------
/modules/statusUpdates/components/Ranking.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState, useRef } from 'react';
2 | import classnames from 'classnames';
3 | import Moment from 'moment';
4 | import { extendMoment } from 'moment-range';
5 | import PropTypes from 'prop-types';
6 |
7 | // antd components
8 | import Card from 'antd/lib/card';
9 | import List from 'antd/lib/list';
10 | import Avatar from 'antd/lib/avatar';
11 | import Tabs from 'antd/lib/tabs';
12 |
13 | import dataFetch from '../../../utils/dataFetch';
14 |
15 | const moment = extendMoment(Moment);
16 | const { TabPane } = Tabs;
17 |
18 | const Rankings = ({ isRangeSet, startDate, endDate }) => {
19 | const [memberData, setMemberData] = useState([]);
20 | const [hasLoaded, setLoaded] = useState(false);
21 |
22 | const prevStart = useRef();
23 | const prevEnd = useRef();
24 |
25 | const query = `query($startDate: Date!, $endDate: Date){
26 | clubStatusUpdate(startDate: $startDate, endDate: $endDate)
27 | {
28 | memberStats
29 | {
30 | user
31 | {
32 | username
33 | firstName
34 | lastName
35 | profile{
36 | profilePic
37 | }
38 | avatar
39 | {
40 | githubUsername
41 | }
42 | }
43 | statusCount
44 | }
45 | }
46 | }`;
47 |
48 | const fetchData = async (variables) => dataFetch({ query, variables });
49 |
50 | const fetchRankings = () => {
51 | const variables = {
52 | startDate: moment(startDate).format('YYYY-MM-DD'),
53 | endDate: moment(endDate).format('YYYY-MM-DD'),
54 | };
55 | fetchData(variables).then((r) => {
56 | setMemberData(r.data.clubStatusUpdate.memberStats);
57 | setLoaded(true);
58 | });
59 | };
60 |
61 | useEffect(() => {
62 | if (
63 | !hasLoaded ||
64 | prevStart.current !== startDate ||
65 | (prevEnd.current !== endDate && isRangeSet)
66 | )
67 | fetchRankings();
68 | prevStart.current = startDate;
69 | prevEnd.current = endDate;
70 | });
71 |
72 | const loadingCards = () => (
73 | (
77 |
80 | 00
81 |
82 | }
83 | >
84 |
90 | }
91 | title={
92 |
97 | Some Person's Name
98 |
99 | }
100 | description={
101 |
106 | {' '}
107 | Some Text is there
108 |
109 | }
110 | />
111 |
112 | )}
113 | />
114 | );
115 |
116 | const memberCard = (members) => (
117 | (
122 | {m.statusCount}}>
123 |
132 | }
133 | title={
134 |
139 | }
140 | />
141 |
142 | )}
143 | />
144 | );
145 |
146 | return (
147 |
148 | Member Rankings
149 |
150 | Top 5} key="1">
151 | {hasLoaded ? memberCard(memberData.slice(0, 5)) : loadingCards()}
152 |
153 | Worst 5} key="2">
154 | {hasLoaded
155 | ? memberCard(memberData.slice().reverse().slice(0, 5))
156 | : loadingCards()}
157 |
158 |
159 |
160 | );
161 | };
162 |
163 | Rankings.propTypes = {
164 | startDate: PropTypes.instanceOf(Date),
165 | endDate: PropTypes.instanceOf(Date),
166 | isRangeSet: PropTypes.bool,
167 | };
168 |
169 | export default Rankings;
170 |
--------------------------------------------------------------------------------
/pages/form/[id]/entries.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { CheckCircleTwoTone, CloseCircleTwoTone } from '@ant-design/icons';
3 | import { CSVLink } from 'react-csv';
4 | import { useRouter } from 'next/router';
5 |
6 | // antd components
7 | import Table from 'antd/lib/table';
8 | import Button from 'antd/lib/button';
9 |
10 | import dataFetch from '../../../utils/dataFetch';
11 | import Base from '../../../components/base';
12 | import TitleBar from '../../../components/titlebar';
13 | import EntryDetails from '../../../modules/forms/entryDetails';
14 |
15 | const Entries = (props) => {
16 | const formID = useRouter().query.id;
17 | const [data, setData] = useState('');
18 | const [fields, setFields] = useState([]);
19 | const [isDataLoaded, setDataLoaded] = useState(false);
20 | const [isLoaded, setLoaded] = useState(false);
21 |
22 | const query = `query viewFormEntries($formID: Int!){
23 | viewEntries(formID: $formID)
24 | {
25 | id
26 | name
27 | submissionTime
28 | phone
29 | email
30 | details
31 | formData
32 | {
33 | key
34 | value
35 | }
36 | }
37 | }`;
38 |
39 | const formFieldsQuery = `query getFromFields($formID: Int!){
40 | getFormFields(formID: $formID)
41 | {
42 | question
43 | key
44 | important
45 | }
46 | }`;
47 |
48 | const fetchFields = async (variables) =>
49 | dataFetch({ query: formFieldsQuery, variables });
50 | const fetchData = async (variables) => dataFetch({ query, variables });
51 |
52 | useEffect(() => {
53 | if (!isLoaded) {
54 | if (!isDataLoaded)
55 | fetchData({ formID }).then((r) => {
56 | setDataLoaded(true);
57 | setData(r.data.viewEntries);
58 | });
59 | if (isDataLoaded && !isLoaded)
60 | fetchFields({ formID }).then((r) => {
61 | setLoaded(true);
62 | setFields(r.data.getFormFields);
63 | });
64 | }
65 | });
66 |
67 | const routes = [
68 | {
69 | path: '/',
70 | name: 'Home',
71 | },
72 | {
73 | path: '/forms/view-forms',
74 | name: 'Forms',
75 | },
76 | {
77 | path: `/form/${formID}`,
78 | name: `${formID}`,
79 | },
80 | {
81 | path: `#`,
82 | name: 'Entries',
83 | },
84 | ];
85 |
86 | const columns = [
87 | {
88 | title: 'Name',
89 | dataIndex: 'name',
90 | key: 'name',
91 | sorter: (a, b) => a.name.localeCompare(b.name),
92 | render: (name) => {name},
93 | },
94 | {
95 | title: 'Submission Time',
96 | dataIndex: 'submissionTime',
97 | key: 'submissionTime',
98 | render: (timestamp) =>
99 | timestamp ? (
100 | new Date(timestamp).toLocaleString()
101 | ) : (
102 |
103 | ),
104 | },
105 | {
106 | title: 'Phone',
107 | dataIndex: 'phone',
108 | key: 'phone',
109 | },
110 | {
111 | title: 'Email',
112 | dataIndex: 'email',
113 | key: 'email',
114 | },
115 | {
116 | title: 'Details',
117 | dataIndex: 'details',
118 | key: 'details',
119 | render: (details) =>
120 | details ? (
121 |
122 | ) : (
123 |
124 | ),
125 | },
126 | ];
127 |
128 | isLoaded && fields.length > 0
129 | ? fields.map((f) => {
130 | f.important
131 | ? columns.push({
132 | title: f.question,
133 | dataIndex: f.key,
134 | key: f.key,
135 | sorter: (a, b) => a.name.localeCompare(b.name),
136 | render: (a, r) => r.formData.find((d) => d.key === f.key).value,
137 | })
138 | : null;
139 | })
140 | : null;
141 |
142 | const getExportData = isLoaded
143 | ? data.map((e) => {
144 | const obj = {
145 | name: e.name,
146 | submissionTime: e.submissionTime,
147 | email: e.email,
148 | phone: e.phone,
149 | };
150 | fields.map((f) => {
151 | obj[f.question] = e.formData.find((d) => d.key === f.key).value;
152 | return null;
153 | });
154 | return obj;
155 | })
156 | : null;
157 |
158 | return (
159 |
160 |
165 |
166 | {isLoaded ? (
167 |
168 |
169 |
170 | ) : null}
171 |
172 |
173 |
}
179 | />
180 |
181 |
182 | );
183 | };
184 |
185 | export default Entries;
186 |
--------------------------------------------------------------------------------
/modules/attendance/components/Rankings.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState, useRef } from 'react';
2 | import classnames from 'classnames';
3 | import Moment from 'moment';
4 | import { extendMoment } from 'moment-range';
5 | import PropTypes from 'prop-types';
6 |
7 | // antd components
8 | import Card from 'antd/lib/card';
9 | import List from 'antd/lib/list';
10 | import Avatar from 'antd/lib/avatar';
11 | import Tabs from 'antd/lib/tabs';
12 |
13 | import dataFetch from '../../../utils/dataFetch';
14 |
15 | const moment = extendMoment(Moment);
16 | const { TabPane } = Tabs;
17 |
18 | const Rankings = ({ isRangeSet, startDate, endDate }) => {
19 | const [memberData, setMemberData] = useState([]);
20 | const [hasLoaded, setLoaded] = useState(false);
21 |
22 | const prevStart = useRef();
23 | const prevEnd = useRef();
24 |
25 | const query = `query($startDate: Date!, $endDate: Date){
26 | clubAttendance(startDate: $startDate, endDate: $endDate)
27 | {
28 | memberStats
29 | {
30 | user
31 | {
32 | username
33 | firstName
34 | lastName
35 | profile{
36 | profilePic
37 | }
38 | avatar
39 | {
40 | githubUsername
41 | }
42 | }
43 | totalDuration
44 | presentCount
45 | avgDuration
46 | }
47 | }
48 | }`;
49 |
50 | const fetchData = async (variables) => dataFetch({ query, variables });
51 |
52 | const fetchRankings = () => {
53 | const variables = {
54 | startDate: moment(startDate).format('YYYY-MM-DD'),
55 | endDate: moment(endDate).format('YYYY-MM-DD'),
56 | };
57 | fetchData(variables).then((r) => {
58 | setMemberData(r.data.clubAttendance.memberStats);
59 | setLoaded(true);
60 | });
61 | };
62 |
63 | useEffect(() => {
64 | if (
65 | !hasLoaded ||
66 | prevStart.current !== startDate ||
67 | (prevEnd.current !== endDate && isRangeSet)
68 | )
69 | fetchRankings();
70 | prevStart.current = startDate;
71 | prevEnd.current = endDate;
72 | });
73 |
74 | const loadingCards = () => (
75 | (
79 |
82 | 00
83 |
84 | }
85 | >
86 |
92 | }
93 | title={
94 |
99 | Some Person's Name
100 |
101 | }
102 | description={
103 |
108 | {' '}
109 | Some Text is there
110 |
111 | }
112 | />
113 |
114 | )}
115 | />
116 | );
117 |
118 | const memberCard = (members) => (
119 | (
124 | {m.presentCount}}>
125 |
134 | }
135 | title={
136 |
141 | }
142 | description={
143 |
144 | Avg: {m.avgDuration} | Total: {m.totalDuration}
145 |
146 | }
147 | />
148 |
149 | )}
150 | />
151 | );
152 |
153 | return (
154 |
155 | Member Rankings
156 |
157 | Top 5} key="1">
158 | {hasLoaded ? memberCard(memberData.slice(0, 5)) : loadingCards()}
159 |
160 | Worst 5} key="2">
161 | {hasLoaded
162 | ? memberCard(memberData.slice().reverse().slice(0, 5))
163 | : loadingCards()}
164 |
165 |
166 |
167 | );
168 | };
169 |
170 | Rankings.propTypes = {
171 | startDate: PropTypes.instanceOf(Date),
172 | endDate: PropTypes.instanceOf(Date),
173 | isRangeSet: PropTypes.bool,
174 | };
175 |
176 | export default Rankings;
177 |
--------------------------------------------------------------------------------
/components/blog/addBlog.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { extendMoment } from 'moment-range';
3 | import Moment from 'moment';
4 |
5 | // antd components
6 | import DatePicker from 'antd/lib/date-picker';
7 | import Result from 'antd/lib/result';
8 | import Upload from 'antd/lib/upload';
9 | import Button from 'antd/lib/button';
10 | import { UploadOutlined } from '@ant-design/icons';
11 | import dynamic from 'next/dynamic';
12 | import MarkdownIt from 'markdown-it';
13 |
14 | const MdEditor = dynamic(() => import('react-markdown-editor-lite'), {
15 | ssr: false,
16 | });
17 |
18 | import fileUpload from '../../utils/fileUpload';
19 | import Link from 'next/link';
20 |
21 | const AddBlog = () => {
22 | const getDate = (options = 'default') => {
23 | let today = new Date();
24 | let dd = String(today.getDate()).padStart(2, '0');
25 | let mm = String(today.getMonth() + 1).padStart(2, '0');
26 | let yyyy = today.getFullYear();
27 | today = yyyy + '/' + mm + '/' + dd;
28 | if (options === 'API') today = yyyy + '-' + mm + '-' + dd;
29 | return today;
30 | };
31 | const dateFormat = 'YYYY/MM/DD';
32 |
33 | const moment = extendMoment(Moment);
34 | const [loading, setLoading] = useState(false);
35 | const [title, setTitle] = useState('');
36 | const [slug, setSlug] = useState('');
37 | const [description, setDescription] = useState('');
38 | const [draft, setDraft] = useState('');
39 | const [cover, setCover] = useState('');
40 | const [date, setDate] = useState(getDate('API'));
41 | const [success, setSuccessText] = useState('');
42 | const [error, setErrorText] = useState('');
43 | const [value, setValue] = useState('');
44 |
45 | const mdParser = new MarkdownIt({
46 | html: true,
47 | linkify: true,
48 | });
49 |
50 | const handleEditorChange = ({ html, text }) => {
51 | setValue(text);
52 | setDescription(html);
53 | };
54 |
55 | const query = `
56 | mutation createBlog($title: String!, $slug: String!, $description: String!, $draft: String!, $date: Date!){
57 | createBlog(title: $title, slug: $slug, description: $description, draft: $draft, date: $date){
58 | id
59 | }
60 | }
61 | `;
62 |
63 | const uploadData = async (data) => await fileUpload(data);
64 | const upload = () => {
65 | const data = new FormData();
66 | data.append('cover', cover);
67 | data.append('query', query);
68 | data.append(
69 | 'variables',
70 | JSON.stringify({ title, slug, description, draft, date })
71 | );
72 | uploadData({ data }).then((r) => {
73 | if (Object.prototype.hasOwnProperty.call(r, 'errors')) {
74 | setErrorText(r.errors[0].message);
75 | setSuccessText('');
76 | } else {
77 | setSuccessText(r.data.id);
78 | setErrorText('');
79 | }
80 | });
81 | };
82 |
83 | const onChange = (dateString) => {
84 | setDate(dateString);
85 | };
86 |
87 | return !loading ? (
88 |
170 | ) : (
171 |
172 | {success !== '' ? (
173 |
179 |
180 |
181 | }
182 | />
183 | ) : error !== '' ? (
184 | {error}
185 | ) : (
186 | Submitting. Please Wait
187 | )}
188 |
189 | );
190 | };
191 |
192 | export default AddBlog;
193 |
--------------------------------------------------------------------------------
/components/admin/addRemoveCard.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | // antd components
5 | import Checkbox from 'antd/lib/checkbox';
6 |
7 | import dataFetch from '../../utils/dataFetch';
8 | import AlertComponent from './alertComponent';
9 | import useModal from './useModal';
10 |
11 | const AddRemoveCard = ({ name, username, showCard, toggleCard }) => {
12 | const [user, setUser] = useState('');
13 | const [gitLabUsername, setGitLabUsername] = useState('');
14 | const [githubUsername, setGitHubUsername] = useState('');
15 | const [telegramID, setTelegramID] = useState('');
16 | const [customEmail, setCustomEmail] = useState('');
17 | const [inGitLab, setInGitLab] = useState(null);
18 | const [inGitHub, setInGitHub] = useState(null);
19 | const [inTelegram, setInTelegram] = useState(null);
20 | const [inCloudFlare, setInCloudflare] = useState(null);
21 | const [changedPlatforms, setChangedPlatforms] = useState([]);
22 | const [loadPlatformData, setLoadPlatformData] = useState(false);
23 | const [userData, setUserData] = useState('');
24 | const { show, toggle } = useModal();
25 |
26 | const userQuery = `
27 | query ($username:String!){
28 | user(username: $username){
29 | profile{
30 | githubUsername
31 | gitlabUsername
32 | customEmail
33 | telegramID
34 | inGitLabGroup
35 | inGitHubGroup
36 | inTelegramGroup
37 | inCloudFlareGroup
38 | }
39 | }
40 | }
41 | `;
42 |
43 | const fetchUserData = async (variables) =>
44 | dataFetch({ query: userQuery, variables });
45 |
46 | useEffect(() => {
47 | if (user !== username) {
48 | setLoadPlatformData(false);
49 | setUser(username);
50 | setInCloudflare(null);
51 | setInTelegram(null);
52 | setInGitHub(null);
53 | setInGitLab(null);
54 | setChangedPlatforms([]);
55 | const variables = { username };
56 | fetchUserData(variables).then((r) => {
57 | setGitLabUsername(r.data.user.profile.gitlabUsername);
58 | setGitHubUsername(r.data.user.profile.githubUsername);
59 | setTelegramID(r.data.user.profile.telegramID);
60 | setCustomEmail(r.data.user.profile.customEmail);
61 | setInCloudflare(r.data.user.profile.inCloudFlareGroup);
62 | setInGitHub(r.data.user.profile.inGitHubGroup);
63 | setInTelegram(r.data.user.profile.inTelegramGroup);
64 | setInGitLab(r.data.user.profile.inGitLabGroup);
65 | setLoadPlatformData(true);
66 | });
67 | }
68 | });
69 |
70 | function handleGitLabChange(event) {
71 | setInGitLab(!inGitLab);
72 | if (changedPlatforms.indexOf(event.target.name) !== -1) {
73 | setChangedPlatforms(changedPlatforms.filter((e) => e !== event.target.name));
74 | } else {
75 | setChangedPlatforms((changedPlatforms) => [
76 | ...changedPlatforms,
77 | event.target.name,
78 | ]);
79 | }
80 | }
81 |
82 | function handleGitHubChange(event) {
83 | setInGitHub(!inGitHub);
84 | if (changedPlatforms.indexOf(event.target.name) !== -1) {
85 | setChangedPlatforms(changedPlatforms.filter((e) => e !== event.target.name));
86 | } else {
87 | setChangedPlatforms((changedPlatforms) => [
88 | ...changedPlatforms,
89 | event.target.name,
90 | ]);
91 | }
92 | }
93 |
94 | function handleTelegramChange(event) {
95 | setInTelegram(!inTelegram);
96 | if (changedPlatforms.indexOf(event.target.name) !== -1) {
97 | setChangedPlatforms(changedPlatforms.filter((e) => e !== event.target.name));
98 | } else {
99 | setChangedPlatforms((changedPlatforms) => [
100 | ...changedPlatforms,
101 | event.target.name,
102 | ]);
103 | }
104 | }
105 |
106 | function handleCloudFlareChange(event) {
107 | setInCloudflare(!inCloudFlare);
108 | if (changedPlatforms.indexOf(event.target.name) !== -1) {
109 | setChangedPlatforms(changedPlatforms.filter((e) => e !== event.target.name));
110 | } else {
111 | setChangedPlatforms((changedPlatforms) => [
112 | ...changedPlatforms,
113 | event.target.name,
114 | ]);
115 | }
116 | }
117 |
118 | function submit() {
119 | const data = {
120 | user: user,
121 | GitLab: inGitLab,
122 | GitHub: inGitHub,
123 | Telegram: inTelegram,
124 | CloudFlare: inCloudFlare,
125 | };
126 | setUserData(data);
127 | toggle();
128 | }
129 |
130 | return showCard ? (
131 |
132 |
133 |
134 |
135 |
136 |
{name}
137 |
138 |
139 | {username ? (
140 | loadPlatformData ? (
141 |
142 |
143 | Name: {username}
144 |
145 |
146 | -
147 | handleGitLabChange(e)}
152 | >
153 | GitLab
154 |
155 |
156 | -
157 | handleGitHubChange(e)}
162 | >
163 | GitHub
164 |
165 |
166 | -
167 | handleTelegramChange(e)}
172 | >
173 | Telegram
174 |
175 |
176 | -
177 | handleCloudFlareChange(e)}
182 | >
183 | CloudFlare
184 |
185 |
186 |
187 |
188 |
197 |
205 |
206 |
207 | ) : (
208 |
Loading
209 | )
210 | ) : null}
211 |
212 |
213 |
214 |
220 |
221 | ) : null;
222 | };
223 |
224 | AddRemoveCard.propTypes = {
225 | name: PropTypes.string,
226 | username: PropTypes.string,
227 | showCard: PropTypes.bool,
228 | toggleCard: PropTypes.func,
229 | };
230 |
231 | export default AddRemoveCard;
232 |
--------------------------------------------------------------------------------
/pages/admin/manage-users.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef, useState } from 'react';
2 |
3 | // antd components
4 | import Table from 'antd/lib/table';
5 | import Input from 'antd/lib/input';
6 | import Button from 'antd/lib/button';
7 | import {
8 | SearchOutlined,
9 | CheckCircleTwoTone,
10 | CloseCircleTwoTone,
11 | } from '@ant-design/icons';
12 | import Highlighter from 'react-highlight-words';
13 |
14 | import Base from '../../components/base';
15 | import TitleBar from '../../components/titlebar';
16 | import dataFetch from '../../utils/dataFetch';
17 | import AddRemoveCard from '../../components/admin/addRemoveCard';
18 | import useModal from '../../components/admin/useModal';
19 | import { verifyUserMutation, addToPlatformMutation } from '../../utils/mutations';
20 |
21 | const routes = [
22 | {
23 | path: '/',
24 | name: 'Home',
25 | },
26 | {
27 | path: '/admin',
28 | name: 'Admin',
29 | },
30 | {
31 | path: '/admin/users',
32 | name: 'Users',
33 | },
34 | ];
35 |
36 | const Users = (props) => {
37 | const [usersData, setUsersData] = useState([]);
38 | const [isUsersLoaded, setUsersLoaded] = useState(false);
39 | const [searchText, setSearchText] = useState('');
40 | const [searchColumn, setSearchColumn] = useState('');
41 | const [username, setUsername] = useState('');
42 | const searchInput = useRef(null);
43 | const [selected, setSelected] = useState({});
44 | const { show, toggle } = useModal();
45 |
46 | const getColumnSearchProps = (dataIndex, fullName = null) => ({
47 | filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => (
48 |
49 | setSelectedKeys(e.target.value ? [e.target.value] : [])}
54 | onPressEnter={() => handleSearch(selectedKeys, confirm, dataIndex)}
55 | style={{ width: 188, marginBottom: 8, display: 'block' }}
56 | />
57 |
66 |
73 |
74 | ),
75 | filterIcon: (filtered) => (
76 |
77 | ),
78 | onFilter: (value, record) =>
79 | record[dataIndex]
80 | ? fullName
81 | ? record[dataIndex][fullName]
82 | .toString()
83 | .toLowerCase()
84 | .includes(value.toLowerCase())
85 | : record[dataIndex].toString().toLowerCase().includes(value.toLowerCase())
86 | : null,
87 | onFilterDropdownVisibleChange: (visible) => {
88 | if (visible) {
89 | setTimeout(() => searchInput.select);
90 | }
91 | },
92 | render: (text) =>
93 | searchColumn === dataIndex ? (
94 |
100 | ) : (
101 | text
102 | ),
103 | });
104 |
105 | const handleSearch = (selectedKeys, confirm, dataIndex) => {
106 | confirm();
107 | setSearchText(selectedKeys[0]);
108 | setSearchColumn(dataIndex);
109 | };
110 |
111 | const handleReset = (clearFilters) => {
112 | clearFilters();
113 | setSearchText('');
114 | };
115 |
116 | const rowSelection = {
117 | onChange: (selectedRowKeys, selectedRows) => {
118 | setSelected(selectedRows);
119 | },
120 | getCheckboxProps: (record) => ({
121 | disabled: record.username === 'amFOSS-Admin', // Column configuration not to be checked
122 | name: record.username,
123 | }),
124 | };
125 |
126 | const verifyUsersFetch = async (variables) =>
127 | dataFetch({ query: verifyUserMutation, variables });
128 |
129 | const addToPlatform = async (variables) =>
130 | dataFetch({ query: addToPlatformMutation, variables });
131 |
132 | const verifyUsers = () => {
133 | let usernames = [];
134 | selected.map((s) => {
135 | usernames.push(s.username);
136 | });
137 | const variables = { usernames };
138 | verifyUsersFetch(variables);
139 | };
140 |
141 | const addToGitLab = () => {
142 | let usernames = [];
143 | selected.map((s) => {
144 | usernames.push(s.username);
145 | });
146 | const variables = { usernames, platform: "gitlab" };
147 | addToPlatform(variables).then(r => alert('Successfully added them!'));
148 | };
149 |
150 | const columns = [
151 | {
152 | title: 'S.NO',
153 | key: 'index',
154 | render: (text, record, index) => index + 1,
155 | width: 70,
156 | },
157 | {
158 | title: 'Name',
159 | dataIndex: 'profile.fullName',
160 | key: 'profile.fullName',
161 | sorter: (a, b) =>
162 | a.profile && b.profile
163 | ? a.profile.fullName.localeCompare(b.profile.fullName)
164 | : null,
165 | ...getColumnSearchProps('profile', 'fullName'),
166 | render: (text, record) => (record.profile ? record.profile.fullName : null),
167 | width: 250,
168 | },
169 | {
170 | title: 'Username',
171 | dataIndex: 'username',
172 | key: 'username',
173 | sorter: (a, b) => a.username.localeCompare(b.username),
174 | ...getColumnSearchProps('username'),
175 | width: 200,
176 | },
177 | {
178 | title: 'GitLab',
179 | dataIndex: 'profile.gitlabUsername',
180 | key: 'profile.gitlabUsername',
181 | render: (text, record) => (record.profile ? record.profile.gitlabUsername : null),
182 | width: 200,
183 | },
184 | {
185 | title: 'Batch',
186 | dataIndex: 'profile.batch',
187 | key: 'profile.batch',
188 | filters: [
189 | { text: '2016', value: '2016' },
190 | { text: '2017', value: '2017' },
191 | { text: '2018', value: '2018' },
192 | { text: '2019', value: '2019' },
193 | { text: '2020', value: '2020' },
194 | ],
195 | onFilter: (value, record) =>
196 | record.profile ? record.profile.batch.indexOf(value) === 0 : null,
197 | render: (text, record) => (record.profile ? record.profile.batch : null),
198 | width: 100,
199 | },
200 | {
201 | title: 'Membership',
202 | dataIndex: 'isVerified',
203 | key: 'isVerified',
204 | render: (record) =>
205 | record ? (
206 |
207 | ) : (
208 |
209 | ),
210 | filters: [
211 | { text: 'Active', value: true },
212 | { text: 'Not Active', value: false },
213 | ],
214 | onFilter: (value, record) => record.isVerified === value,
215 | width: 150,
216 | },
217 | {
218 | title: 'Is Admin',
219 | dataIndex: 'isAdmin',
220 | key: 'isAdmin',
221 | render: (record) =>
222 | record ? (
223 |
224 | ) : (
225 |
226 | ),
227 | filters: [
228 | { text: 'Admins', value: true },
229 | { text: 'Users', value: false },
230 | ],
231 | onFilter: (value, record) => record.isAdmin === value,
232 | width: 150,
233 | },
234 | ];
235 |
236 | const usersQuery = `
237 | {
238 | users{
239 | username
240 | email
241 | isVerified
242 | isAdmin
243 | profile{
244 | fullName
245 | batch
246 | gitlabUsername
247 | }
248 | }
249 | }
250 | `;
251 |
252 | const fetchUsersData = async () => dataFetch({ query: usersQuery });
253 |
254 | useEffect(() => {
255 | if (!isUsersLoaded) {
256 | fetchUsersData().then((r) => {
257 | setUsersLoaded(true);
258 | setUsersData(r.data.users);
259 | });
260 | }
261 | });
262 |
263 | const onSelectRow = (record) => {
264 | if (!record.profile) return;
265 | setUsername(record.username);
266 | toggle();
267 | };
268 |
269 | return (
270 |
271 |
276 |
277 |
278 |
286 |
294 |
295 |
{
301 | return {
302 | onClick: () => {
303 | onSelectRow(record);
304 | },
305 | };
306 | }}
307 | bordered
308 | pagination={false}
309 | loading={!isUsersLoaded}
310 | dataSource={usersData}
311 | columns={columns}
312 | scroll={{ y: 240 }}
313 | rowKey="username"
314 | />
315 |
316 |
322 |
323 | );
324 | };
325 |
326 | export default Users;
327 |
--------------------------------------------------------------------------------
/components/account/basicSettings.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import Cookies from 'universal-cookie';
3 | import Link from 'next/link';
4 | import { UploadOutlined } from '@ant-design/icons';
5 | import dynamic from 'next/dynamic';
6 |
7 | // antd components
8 | import Upload from 'antd/lib/upload';
9 | import Button from 'antd/lib/button';
10 | import Result from 'antd/lib/result';
11 |
12 | import fileUpload from '../../utils/fileUpload';
13 | import dataFetch from '../../utils/dataFetch';
14 |
15 | const cookies = new Cookies();
16 |
17 | const QuillNoSSRWrapper = dynamic(import('react-quill'), {
18 | ssr: false,
19 | loading: () => Loading ...
,
20 | });
21 |
22 | const BasicSettings = () => {
23 | const [isLoading, setLoaded] = useState(false);
24 | const [queried, setQueried] = useState(false);
25 | const [username, setUsername] = useState('');
26 | const [firstName, setFirstName] = useState('');
27 | const [lastName, setLastName] = useState('');
28 | const [email, setEmail] = useState('');
29 | const [phoneNo, setPhoneNo] = useState('');
30 | const [githubUsername, setGithubUsername] = useState('');
31 | const [gitlabUsername, setGitlabUsername] = useState('');
32 | const [twitterUsername, setTwitterUsername] = useState('');
33 | const [telegramUsername, setTelegramUsername] = useState('');
34 | const [roll, setRoll] = useState('');
35 | const [batch, setBatch] = useState(0);
36 | const [about, setAbout] = useState('');
37 | const [profilePic, setProfilePic] = useState('');
38 | const [languages, setLanguages] = useState([]);
39 | const [links, setLinks] = useState('');
40 | const [error, setErrorText] = useState('');
41 | const [dataLoading, setDataLoading] = useState(false);
42 | const [success, setSuccess] = useState('');
43 |
44 | const query = `
45 | query user($username: String!){
46 | user(username:$username){
47 | username
48 | firstName
49 | lastName
50 | email
51 | profile{
52 | profilePic
53 | phone
54 | about
55 | roll
56 | batch
57 | githubUsername
58 | gitlabUsername
59 | telegramUsername
60 | twitterUsername
61 | languages{
62 | name
63 | }
64 | links{
65 | link
66 | portal{
67 | name
68 | }
69 | }
70 | }
71 | }
72 | }
73 | `;
74 |
75 | const updateProfileQuery = `
76 | mutation ($about: String, $batch: Int, $email: String, $firstName: String!, $githubUsername: String, $twitterUsername: String, $telegramUsername: String,$lastName: String, $phoneNo: String, $roll: String, $gitlabUsername: String, $username: String, $languages: [String]){
77 | UpdateProfile(about: $about, batch: $batch, email: $email, firstName: $firstName, githubUsername: $githubUsername, twitterUsername: $twitterUsername, telegramUsername: $telegramUsername, lastName:$lastName, phoneNo:$phoneNo, roll: $roll, gitlabUsername: $gitlabUsername, username: $username, languages: $languages){
78 | id
79 | }
80 | }
81 | `;
82 |
83 | const fetchData = async (variables) => dataFetch({ query, variables });
84 |
85 | const submitForm = async (variables) =>
86 | dataFetch({ query: updateProfileQuery, variables });
87 |
88 | const uploadFile = async (data) => await fileUpload(data);
89 |
90 | useEffect(() => {
91 | if (!queried) {
92 | const usernameCookie = cookies.get('username');
93 | const variables = { username: usernameCookie };
94 | fetchData(variables).then((r) => {
95 | if (!Object.prototype.hasOwnProperty.call(r, 'errors')) {
96 | setUsername(r.data.user.username ? r.data.user.username : '');
97 | setFirstName(r.data.user.firstName ? r.data.user.firstName : '');
98 | setLastName(r.data.user.lastName ? r.data.user.lastName : '');
99 | setEmail(r.data.user.email ? r.data.user.email : '');
100 | setPhoneNo(r.data.user.profile.phone ? r.data.user.profile.phone : '');
101 | setRoll(r.data.user.profile.roll ? r.data.user.profile.roll : '');
102 | setBatch(r.data.user.profile.batch ? r.data.user.profile.batch : null);
103 | setAbout(r.data.user.profile.about ? r.data.user.profile.about : '');
104 | setGithubUsername(
105 | r.data.user.profile.githubUsername
106 | ? r.data.user.profile.githubUsername
107 | : ''
108 | );
109 | setGitlabUsername(
110 | r.data.user.profile.gitlabUsername
111 | ? r.data.user.profile.gitlabUsername
112 | : ''
113 | );
114 | setTelegramUsername(
115 | r.data.user.profile.telegramUsername
116 | ? r.data.user.profile.telegramUsername
117 | : ''
118 | );
119 | setTwitterUsername(
120 | r.data.user.profile.twitterUsername
121 | ? r.data.user.profile.twitterUsername
122 | : ''
123 | );
124 | setProfilePic(
125 | r.data.user.profile.profilePic ? r.data.user.profile.profilePic : ''
126 | );
127 | r.data.user.profile.languages.map((language) =>
128 | setLanguages((languages) => [...languages, language.name])
129 | );
130 | setLoaded(true);
131 | }
132 | });
133 | setQueried(true);
134 | }
135 | });
136 |
137 | const updateProfile = () => {
138 | const variables = {
139 | username,
140 | firstName,
141 | lastName,
142 | email,
143 | gitlabUsername,
144 | roll,
145 | about,
146 | githubUsername,
147 | telegramUsername,
148 | twitterUsername,
149 | batch,
150 | phoneNo,
151 | languages: languages[0] ? languages : [],
152 | };
153 | submitForm(variables).then((r) => {
154 | if (Object.prototype.hasOwnProperty.call(r, 'errors')) {
155 | setErrorText(r.errors[0].message);
156 | } else {
157 | setSuccess(r.data);
158 | setErrorText('');
159 | }
160 | });
161 | };
162 |
163 | const uploadProps = {
164 | name: 'file',
165 | multiple: false,
166 | showUploadList: false,
167 | customRequest: ({ file }) => {
168 | const data = new FormData();
169 | data.append('imageFile', file);
170 | const query = `mutation{
171 | UpdateProfilePic{
172 | fileName
173 | }
174 | }`;
175 | data.append('query', query);
176 | uploadFile({ data }).then((response) =>
177 | setProfilePic(response.data.UpdateProfilePic.fileName)
178 | );
179 | },
180 | };
181 |
182 | return isLoading ? (
183 |
184 | Edit Profile
185 |
374 |
375 | ) : null;
376 | };
377 |
378 | export default BasicSettings;
379 |
--------------------------------------------------------------------------------