diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..08ef39f --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +.github/ +README.md +lefthook.yml \ No newline at end of file diff --git a/.env.example b/.env.example index 925f74f..f3adba5 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,4 @@ -VITE_BASHAWAY_BE_URL=https://api.staging.bashaway.sliitfoss.org +VITE_CORE_BE_URL= VITE_FIREBASE_CONFIG= VITE_AZURE_UPLOAD_SAS_TOKEN= VITE_AZURE_DOWNLOAD_SAS_TOKEN= diff --git a/.github/workflows/add-labels.yml b/.github/workflows/add-labels.yml new file mode 100644 index 0000000..5f847c9 --- /dev/null +++ b/.github/workflows/add-labels.yml @@ -0,0 +1,19 @@ +name: Add Labels + +on: + issues: + types: opened + +jobs: + add_labels: + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v3 + + - name: add labels + uses: actions-ecosystem/action-add-labels@v1 + with: + labels: | + Event Portal + Team/Dev :man_technologist: diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..8de795e --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,63 @@ +on: + workflow_call: + inputs: + tag: + required: true + type: string + secrets: + GCP_SA_KEY : + required: true + # FRONTEND_URL: + # required: true + # SERVER_URL: + # required: true + # FIREBASE_CONFIG: + # required: true + +jobs: + build-push: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v2 + with: + push: true + tags: ${{ inputs.tag }} + # build-args: | + # PUBLIC_FRONTEND_URL=${{ secrets.FRONTEND_URL }} + # PUBLIC_SERVER_URL=${{ secrets.SERVER_URL }} + # PUBLIC_FIREBASE_CONFIG=${{ secrets.FIREBASE_CONFIG }} + + cloud-run-deploy: + needs: build-push + runs-on: ubuntu-latest + permissions: + contents: 'read' + id-token: 'write' + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Authenticate with Google Cloud + uses: google-github-actions/auth@v1 + with: + credentials_json: '${{ secrets.GCP_SA_KEY }}' + + - name: Deploy to Cloud Run + uses: google-github-actions/deploy-cloudrun@v1 + with: + service: techevents-web + image: ${{ inputs.tag }} \ No newline at end of file diff --git a/.github/workflows/dev-deploy.yml b/.github/workflows/dev-deploy.yml new file mode 100644 index 0000000..5ce8228 --- /dev/null +++ b/.github/workflows/dev-deploy.yml @@ -0,0 +1,19 @@ +name: Dev Release +run-name: Dev Build and deploy + +on: + workflow_dispatch: + push: + branches: + - main +jobs: + build-and-deploy: + name: Dev + uses: ./.github/workflows/deploy.yml + with: + tag: ghcr.io/${{ github.repository }}:${{ github.sha }}-${{ github.run_attempt }}-dev + secrets: + GCP_SA_KEY : ${{ secrets.DEV_GCP_SA_KEY }} + # FRONTEND_URL: ${{ secrets.DEV_FRONTEND_URL }} + # SERVER_URL: ${{ secrets.DEV_SERVER_URL }} + # FIREBASE_CONFIG: ${{ secrets.DEV_FIREBASE_CONFIG }} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1aee42e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,42 @@ +# Stage 1: Build the React application +FROM node:16-alpine as build + +ARG VITE_APP_ENV +ARG VITE_CORE_BE_URL=https://techevents-api-dev-6yykz7rdnq-uc.a.run.app +ARG VITE_FIREBASE_CONFIG +ARG VITE_AZURE_UPLOAD_SAS_TOKEN +ARG VITE_AZURE_DOWNLOAD_SAS_TOKEN +ARG VITE_AZURE_GENERIC_UPLOAD_SAS_TOKEN +ARG VITE_AZURE_STORAGE_ACCOUNT +ARG VITE_AZURE_GENERIC_STORAGE_ACCOUNT +ARG VITE_AZURE_STORAGE_CONTAINER +ARG VITE_SENTRY_DSN +ARG SENTRY_ORG +ARG SENTRY_PROJECT_NAME + +RUN npm install -g pnpm + +WORKDIR /app + +COPY package*.json pnpm-lock.yaml ./ + +COPY patches /app/patches + +RUN pnpm install --ignore-scripts + +COPY . . + +RUN pnpm run build + +# Stage 2: Serve the React application from Nginx +FROM nginx:1.19.0-alpine + +COPY --from=build /app/dist /usr/share/nginx/html + +# Copy the Nginx configuration file +COPY /nginx/nginx.conf /etc/nginx/conf.d/default.conf + +# Expose port 80 +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/index.html b/index.html index e369759..bdba01e 100644 --- a/index.html +++ b/index.html @@ -10,22 +10,22 @@ as="style" /> - - - - + + + + - - + + - + - - - + + + - Bashaway | 2023 + Techevents.lk diff --git a/nginx/nginx.conf b/nginx/nginx.conf new file mode 100644 index 0000000..21fdd18 --- /dev/null +++ b/nginx/nginx.conf @@ -0,0 +1,9 @@ +server { + listen 80; + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ $uri.html =404; + } +} \ No newline at end of file diff --git a/package.json b/package.json index f0017ef..33e4af1 100644 --- a/package.json +++ b/package.json @@ -6,14 +6,13 @@ "build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 vite build", "format": "prettier --write --cache \"**/*.{js,jsx,ts,tsx,md,mdx}\"", "lint": "eslint . --ext js,jsx,mdx --ignore-path .gitignore --fix --cache --report-unused-disable-directives", - "preview": "vite preview", - "prepare": "lefthook install" + "preview": "vite preview" }, "dependencies": { "@azure/storage-blob": "12.15.0", "@reduxjs/toolkit": "1.9.5", "@sentry/react": "7.69.0", - "@sliit-foss/bashaway-ui": "0.10.3", + "@sliit-foss/bashaway-ui": "0.12.2", "async-mutex": "^0.4.0", "firebase": "10.2.0", "framer-motion": "10.14.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a5b6deb..19b0840 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,8 +20,8 @@ dependencies: specifier: 7.69.0 version: 7.69.0(react@18.0.0) '@sliit-foss/bashaway-ui': - specifier: 0.10.3 - version: 0.10.3(@babel/core@7.22.9)(@types/react-dom@18.0.11)(@types/react@18.0.28)(react-native@0.72.4)(redux@4.2.1)(tailwindcss@3.3.2) + specifier: 0.12.2 + version: 0.12.2(@babel/core@7.22.9)(@types/react-dom@18.0.11)(@types/react@18.0.28)(react-native@0.72.4)(redux@4.2.1)(tailwindcss@3.3.2) async-mutex: specifier: ^0.4.0 version: 0.4.0 @@ -3879,8 +3879,8 @@ packages: '@sinonjs/commons': 3.0.0 dev: false - /@sliit-foss/bashaway-ui@0.10.3(@babel/core@7.22.9)(@types/react-dom@18.0.11)(@types/react@18.0.28)(react-native@0.72.4)(redux@4.2.1)(tailwindcss@3.3.2): - resolution: {integrity: sha512-7hI44+c+mdJUqLPNi8t/WY5ivlmpMSiARs/FFHF8Wkf4PUcJl3xkEjoo54oXr21fS+LNzRm/AWi/qTLuOk3FwA==} + /@sliit-foss/bashaway-ui@0.12.2(@babel/core@7.22.9)(@types/react-dom@18.0.11)(@types/react@18.0.28)(react-native@0.72.4)(redux@4.2.1)(tailwindcss@3.3.2): + resolution: {integrity: sha512-mL4vrXKfPUXvCDweo4tQTiej7Lf9G3hecAKBBTL/JhMw6ptlg7wfooW1JtL1DAc8mKMvBoBoOfGo3UOTPy1x0g==} dependencies: '@radix-ui/react-accordion': 1.1.2(@types/react-dom@18.0.11)(@types/react@18.0.28)(react-dom@18.0.0)(react@18.0.0) '@radix-ui/react-alert-dialog': 1.0.4(@types/react-dom@18.0.11)(@types/react@18.0.28)(react-dom@18.0.0)(react@18.0.0) @@ -6681,6 +6681,7 @@ packages: /loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true dependencies: js-tokens: 4.0.0 diff --git a/public/assets/images/cover.png b/public/assets/images/cover.png new file mode 100644 index 0000000..35d88b2 Binary files /dev/null and b/public/assets/images/cover.png differ diff --git a/public/assets/images/gdg-logo.webp b/public/assets/images/gdg-logo.webp new file mode 100644 index 0000000..efc24af Binary files /dev/null and b/public/assets/images/gdg-logo.webp differ diff --git a/public/assets/images/gdglk-logo.png b/public/assets/images/gdglk-logo.png new file mode 100644 index 0000000..4b23012 Binary files /dev/null and b/public/assets/images/gdglk-logo.png differ diff --git a/src/app.jsx b/src/app.jsx index 5914e97..77e78e9 100644 --- a/src/app.jsx +++ b/src/app.jsx @@ -20,7 +20,7 @@ const App = () => {
{ - const { data: { data: settings } = {}, isLoading } = useGetSettingsQuery(); - +const ActionButtons = ({ loading = false, challenge, className, buttonClassName }) => { const navigate = useNavigate(); return (
diff --git a/src/components/question-details/index.jsx b/src/components/challenge-details/index.jsx similarity index 100% rename from src/components/question-details/index.jsx rename to src/components/challenge-details/index.jsx diff --git a/src/components/home/question/index.jsx b/src/components/challenges/index.jsx similarity index 70% rename from src/components/home/question/index.jsx rename to src/components/challenges/index.jsx index 746971a..d60efcf 100644 --- a/src/components/home/question/index.jsx +++ b/src/components/challenges/index.jsx @@ -8,26 +8,26 @@ import { challengeColor } from "@/utils"; import { Tooltip, TooltipContent, TooltipTrigger } from "@sliit-foss/bashaway-ui/components"; import { Body3, Footnote } from "@sliit-foss/bashaway-ui/typography"; -export { default as QuestionGridSkeleton } from "./skeleton"; +export { default as ChallengeGridSkeleton } from "./skeleton"; -export const Question = ({ question }) => { - const cardStyles = useMemo(() => challengeColor(question), [question]); +export const Challenge = ({ challenge }) => { + const cardStyles = useMemo(() => challengeColor(challenge), [challenge]); const cleanedDescription = useMemo(() => { - if (question.description) { - return question.description + if (challenge.description) { + return challenge.description .replace(/^#+\s.*$/gm, "") .trim() .split("\n")[0]; } return ""; - }, [question.description]); + }, [challenge.description]); - const SubmitIcon = question.submitted ? CheckCircle2 : XCircle; + const SubmitIcon = challenge.submitted ? CheckCircle2 : XCircle; return (
{ )} >
- {question.name} + {challenge.name}
@@ -45,20 +45,20 @@ export const Question = ({ question }) => { - {question.submitted ? "Submitted" : "Not Submitted"} + {challenge.submitted ? "Submitted" : "Not Submitted"}
{cleanedDescription}
- {startCase(question.difficulty.toLowerCase())} - {question.max_score}PT - {question.constraints?.length && {question.constraints?.join(", ")}} + {startCase(challenge.difficulty.toLowerCase())} + {challenge.max_score}PT + {challenge.constraints?.length && {challenge.constraints?.join(", ")}}
); }; -export default Question; +export default Challenge; diff --git a/src/components/home/question/skeleton.jsx b/src/components/challenges/skeleton.jsx similarity index 93% rename from src/components/home/question/skeleton.jsx rename to src/components/challenges/skeleton.jsx index 98c4968..57704a2 100644 --- a/src/components/home/question/skeleton.jsx +++ b/src/components/challenges/skeleton.jsx @@ -1,6 +1,6 @@ import { Skeleton } from "@sliit-foss/bashaway-ui/components"; -const QuestionGridSkeleton = ({ className }) => { +const ChallengeGridSkeleton = ({ className }) => { return (
{Array.from({ length: 6 }).map((_, i) => ( @@ -32,4 +32,4 @@ const QuestionGridSkeleton = ({ className }) => { ); }; -export default QuestionGridSkeleton; +export default ChallengeGridSkeleton; diff --git a/src/components/event-details/faq.jsx b/src/components/event-details/faq.jsx new file mode 100644 index 0000000..0f74e2e --- /dev/null +++ b/src/components/event-details/faq.jsx @@ -0,0 +1,30 @@ +import { useRef, useState } from "react"; +import { ChevronDown } from "lucide-react"; +import { faqs } from "@/constants/faqs"; + +export default function FAQ() { + const [closed, setClosed] = useState(true); + const pRef = useRef(null); + + return ( +
+ {faqs.map((faq, index) => ( +
+
{faq.question}
+
+

{faq.answer}

+
+
+ ))} + +
+ +
+
+ ); +} diff --git a/src/components/event-details/index.js b/src/components/event-details/index.js new file mode 100644 index 0000000..a401d3c --- /dev/null +++ b/src/components/event-details/index.js @@ -0,0 +1 @@ +export { default as FAQ } from "./faq"; diff --git a/src/components/home/event/index.jsx b/src/components/home/event/index.jsx new file mode 100644 index 0000000..4528efa --- /dev/null +++ b/src/components/home/event/index.jsx @@ -0,0 +1,53 @@ +import { useMemo } from "react"; +import { CheckCircle2, XCircle } from "lucide-react"; +import { default as ReactMarkdown } from "react-markdown"; +import { Link } from "react-router-dom"; +import { default as moment } from "moment-timezone"; +import { twMerge } from "tailwind-merge"; +import { eventColor } from "@/utils"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@sliit-foss/bashaway-ui/components"; +import { Body3, Footnote } from "@sliit-foss/bashaway-ui/typography"; + +export { default as EventGridSkeleton } from "./skeleton"; + +export const Event = ({ event }) => { + const cardStyles = useMemo(() => eventColor(event), [event]); + + const SubmitIcon = event.ticket?.utilized ? CheckCircle2 : XCircle; + + return ( + +
+
+ {event.name} +
+ + + + + + {event.ticket ? (event.ticket.utilized ? "Attended" : "Not Attended") : "Not Registered"} + + +
+
+ {event.description} +
+ {moment(event.event_date).format("Do MMM @ hh:mm A")} + {event.settings.payments.enabled ? "PAID" : "FREE"} + {event.tags?.length && {event.tags?.join(", ")}} +
+
+ + ); +}; + +export default Event; diff --git a/src/components/home/event/skeleton.jsx b/src/components/home/event/skeleton.jsx new file mode 100644 index 0000000..ddf7530 --- /dev/null +++ b/src/components/home/event/skeleton.jsx @@ -0,0 +1,38 @@ +import { Skeleton } from "@sliit-foss/bashaway-ui/components"; + +const EventGridSkeleton = ({ className }) => { + return ( +
+ {Array.from({ length: 6 }).map((_, i) => ( +
+ +
+ +
+ +
+
+ +
+ + +
+
+
+ ))} +
+ ); +}; + +export default EventGridSkeleton; diff --git a/src/components/home/index.jsx b/src/components/home/index.jsx index ff78fd2..6bcf8f1 100644 --- a/src/components/home/index.jsx +++ b/src/components/home/index.jsx @@ -1 +1 @@ -export * from "./question"; +export * from "./event"; diff --git a/src/components/identification-form.jsx b/src/components/identification-form.jsx index 2182c44..a9d1501 100644 --- a/src/components/identification-form.jsx +++ b/src/components/identification-form.jsx @@ -4,7 +4,7 @@ import { useSelector } from "react-redux"; import { uploadIdCard } from "@/services"; import { store } from "@/store"; import { authApi, useAuthUserQuery, useUpdateProfileMutation } from "@/store/api"; -import { toggleIdentificationForm } from "@/store/reducers/ui/global"; +import { toggleSurveyForm } from "@/store/reducers/ui/event"; import { Accordion, AccordionContent, @@ -23,10 +23,10 @@ import { } from "@sliit-foss/bashaway-ui/components"; import { gender, mealPreference } from "@sliit-foss/bashaway-ui/constants"; -const close = () => store.dispatch(toggleIdentificationForm(false)); +const close = () => store.dispatch(toggleSurveyForm(false)); const IdentificationForm = () => { - const open = useSelector((store) => store.ui.global.showIdentificationForm); + const open = useSelector((store) => store.ui.event.showSurveyForm); const { data: { data: team } = {} } = useAuthUserQuery(); diff --git a/src/components/index.jsx b/src/components/index.jsx index a7cb54f..c006677 100644 --- a/src/components/index.jsx +++ b/src/components/index.jsx @@ -1,6 +1,7 @@ export * from "./home"; export * from "./layout"; -export * from "./question-details"; +export * from "./challenges"; +export * from "./challenge-details"; export * from "./register"; export * from "./submissions"; diff --git a/src/components/layout/footer.jsx b/src/components/layout/footer.jsx index 3668133..395a7e9 100644 --- a/src/components/layout/footer.jsx +++ b/src/components/layout/footer.jsx @@ -1,16 +1,15 @@ import { AiFillFacebook, AiFillInstagram, AiFillLinkedin, AiFillYoutube, AiOutlineTwitter } from "react-icons/ai"; import { twMerge } from "tailwind-merge"; -import { facebook, instagram, linkedIn, repositoryLink, sliitFossMainWebsite, twitter, youTube } from "@/constants"; -import { Bashaway, FOSS } from "@sliit-foss/bashaway-ui/icons"; +import { cocLink, facebook, gdgPageLink, instagram, linkedIn, twitter, youTube } from "@/constants"; const usefulLinks = [ { name: "Visit Us", - url: sliitFossMainWebsite + url: gdgPageLink }, { - name: "Source Code", - url: repositoryLink + name: "Code of Conduct", + url: cocLink } ]; @@ -26,11 +25,16 @@ const Footer = ({ className }) => { >
- -

- Welcome to the SLIIT FOSS Community. We're a group of volunteers who believe in the usage of Free and - Open Source Software (FOSS) -

+
+

+ techevents.lk +

+

+ Powered By +

+ GDGLK Logo +
+
CONNECT WITH US
-
{usefulLinks.map((link, index) => { return ( diff --git a/src/components/layout/header.jsx b/src/components/layout/header.jsx index 7543327..71e08d2 100644 --- a/src/components/layout/header.jsx +++ b/src/components/layout/header.jsx @@ -1,156 +1,17 @@ -import { useState } from "react"; -import { RiWhatsappFill } from "react-icons/ri"; -import { RxCross1, RxHamburgerMenu } from "react-icons/rx"; -import { useDispatch } from "react-redux"; -import { Link, useLocation, useNavigate } from "react-router-dom"; -import { twMerge } from "tailwind-merge"; -import { leaderboardURL, ruleLink, whatsappLink } from "@/constants"; -import { useBreakpoint } from "@/hooks"; -import { whitelistedPaths } from "@/hooks/auth"; -import { BashawayPortal } from "@/icons"; -import { authApi, useAuthUserQuery, useLogoutMutation } from "@/store/api"; -import { AnimatedSwitcher, Button, Skeleton } from "@sliit-foss/bashaway-ui/components"; -import { Bashaway, FOSS, Link as LinkIcon, Times } from "@sliit-foss/bashaway-ui/icons"; - -const internalNavLinks = [ - { - name: "Home", - path: "/" - }, - { - name: "Profile", - path: "/profile" - } -]; - -const mobileNavIconStyles = - "block xl:hidden absolute right-8 lg:right-24 h-[1.65rem] w-[1.65rem] cursor-pointer hover:text-black/70 transition-all duration-medium"; - -const buttonStyles = "mt-1.5 xl:mt-0 px-8 xl:px-[1.15rem] pb-2.5 xl:pb-[0.4rem] min-w-[12rem] xl:min-w-[6rem]"; - -const Header = ({ className }) => { - const [mobileNavOpen, setMobileNavOpen] = useState(false); - - const breakpoints = useBreakpoint(); - - const navigate = useNavigate(); - - const location = useLocation(); - - const dispatch = useDispatch(); - - const [logout] = useLogoutMutation(); - - const { data, isLoading, isError } = useAuthUserQuery(); - - const onNavItemClick = (section) => { - if (!breakpoints["xl"]) setMobileNavOpen(false); - navigate(section.path); - }; - - const onLoginOrRegisterClick = () => { - navigate(location.pathname === "/login" ? "/register" : "/login"); - if (!breakpoints["xl"]) setMobileNavOpen(false); - }; - - const onLogoutClick = async () => { - logout(); - localStorage.clear(); - dispatch(authApi.util.resetApiState()); - if (!breakpoints["xl"]) setMobileNavOpen(false); - navigate("/login"); - }; +import { Link } from "react-router-dom"; +const Header = () => { return ( -
-
); diff --git a/src/constants/faqs.js b/src/constants/faqs.js new file mode 100644 index 0000000..8f413ed --- /dev/null +++ b/src/constants/faqs.js @@ -0,0 +1,59 @@ +export const faqs = [ + { + question: "What is GDG DevFest Sri Lanka?", + answer: + "GDG DevFest Sri Lanka is a community-organized developer conference that is part of the global Google Developer Groups (GDG) DevFest series. It brings together developers, designers, and technology enthusiasts to learn, share, and network. DevFest Sri Lanka typically features a variety of technical sessions, workshops, and keynote presentations on cutting-edge technologies, platforms, and development practices." + }, + { + question: "Where will GDG DevFest Sri Lanka 2023 be held?", + answer: "At Bishop’s College Auditorium, Colombo 03" + }, + { + question: "Is there a selection process for those who paid for the ticket?", + answer: + "No, there is no selection process. If you have completed the registration, including the payment for the ticket, you are eligible to attend the event." + }, + { + question: + "When will participants who have paid for Google I/O Extended Sri Lanka 2023 tickets receive their official tickets?", + answer: + "Participants who have purchased tickets for Google I/O Extended Sri Lanka 2023 will receive their official event tickets via email from GDG Sri Lanka three days prior to the event." + }, + { + question: "What are the benefits I can get from attending this event?", + answer: + "By attending this event, you will have the opportunity to acquire extensive knowledge of Google technologies from experts, network with Googlers, GDEs, and industry experts, and most importantly, have a lot of fun!" + }, + { + question: "Is this event beneficial for beginners in the IT industry?", + answer: + "Absolutely! This event offers valuable benefits for individuals of all levels, from beginners just starting their journey to experts with extensive experience." + }, + { + question: "Has the registration for this event been closed?", + answer: + "Yes, registration for this event is now closed as all the available slots have been filled at a rapid pace. We sincerely appreciate your tremendous enthusiasm and interest in participating." + }, + { + question: + "If I have registered and obtained a ticket for the event but am unable to attend on the event day, can I transfer my ticket to someone else?", + answer: "Unfortunately, you cannot transfer your ticket to someone else." + }, + { + question: + "Is it still possible for someone who has registered but has not yet paid for the ticket to make the payment now?", + answer: + "Unfortunately, it is not possible to make the payment for the ticket now as all the slots have been sold out." + }, + { + question: + "Can I purchase the Google I/O Extended Sri Lanka 2023 participant t-shirt without being a participant in the event?", + answer: + "Unfortunately, the participant t-shirts for the event are exclusively provided to registered participants who possess a valid ticket, and they are not available for individual sale." + }, + { + question: "Is it possible to receive a refund for the payment I made for the event ticket?", + answer: + "Unfortunately, event tickets are non-refundable as stated in the ticket guidelines on the EventsHere platform." + } +]; diff --git a/src/constants/hyperlinks.js b/src/constants/hyperlinks.js index 0eac5af..a845f96 100644 --- a/src/constants/hyperlinks.js +++ b/src/constants/hyperlinks.js @@ -1,11 +1,10 @@ -export const githubOrgLink = "https://github.com/sliit-foss"; -export const repositoryLink = "https://github.com/sliit-foss/bashaway-event-portal"; -export const scorekeeperRepositoryLink = "https://github.com/sliit-foss/scorekeeper"; -export const sliitFossMainWebsite = "https://sliitfoss.org"; -export const ruleLink = "https://bashaway.sliitfoss.org#rules"; -export const whatsappLink = "https://chat.whatsapp.com/JPJYJr4gRiTEMLpWmH1QXh"; +export const githubOrgLink = "https://github.com/gdgsrilanka"; +export const scorekeeperRepositoryLink = "https://github.com/gdgsrilanka/scorekeeper"; +export const gdgPageLink = "https://gdg.community.dev/gdg-sri-lanka"; +export const cocLink = "https://developers.google.com/events/gdd-europe/code-of-conduct"; +export const visitUsLink = "https://gdgsrilanka.org"; -export let leaderboardURL = "https://leaderboard.bashaway.sliitfoss.org"; +export let leaderboardURL = "https://leaderboard.techevents.gdgsrilanka.org"; if (import.meta.env.VITE_APP_ENV !== "production") { leaderboardURL = leaderboardURL.replace("leaderboard.", `leaderboard.${import.meta.env.VITE_APP_ENV}.`); diff --git a/src/constants/socials.js b/src/constants/socials.js index 855bd0a..bd35689 100644 --- a/src/constants/socials.js +++ b/src/constants/socials.js @@ -1,6 +1,6 @@ -export const facebook = "https://www.facebook.com/sliitfoss"; -export const twitter = "https://twitter.com/fosssliit"; -export const instagram = "https://www.instagram.com/sliitfoss/"; -export const linkedIn = "https://www.linkedin.com/company/sliit-foss-community"; -export const youTube = "https://youtube.com/@SLIITFOSSCommunity"; -export const github = "https://github.com/sliit-foss"; +export const facebook = "https://www.facebook.com/GDGSriLanka"; +export const twitter = "https://twitter.com/GDGLK"; +export const instagram = "https://www.instagram.com/gdgsrilanka"; +export const linkedIn = "https://www.linkedin.com/company/gdglk"; +export const youTube = "https://www.youtube.com/c/GDGSriLankaChapter"; +export const github = "https://github.com/gdgsrilanka"; diff --git a/src/filters/question.js b/src/filters/challenge.js similarity index 89% rename from src/filters/question.js rename to src/filters/challenge.js index c440a5d..2162be7 100644 --- a/src/filters/question.js +++ b/src/filters/challenge.js @@ -1,4 +1,4 @@ -export const questionFilters = [ +export const challengeFilters = [ { key: "name", label: "Name" @@ -27,7 +27,7 @@ export const questionFilters = [ } ]; -export const questionSorts = [ +export const challengeSorts = [ { key: "max_score", label: "Sort by points", diff --git a/src/filters/event.js b/src/filters/event.js new file mode 100644 index 0000000..bc6bf49 --- /dev/null +++ b/src/filters/event.js @@ -0,0 +1,47 @@ +export const eventFilters = [ + { + key: "name", + label: "Name" + }, + { + key: "availability", + label: "Availability", + options: [ + { + key: "true", + label: "Open for registration" + }, + { + key: "false", + label: "Closed" + } + ] + }, + { + key: "settings.automatic_approval", + label: "Entry process", + options: [ + { + key: "true", + label: "Manual approval" + }, + { + key: "false", + label: "Direct" + } + ] + } +]; + +export const eventSorts = [ + { + key: "settings.payments.ticket_cost", + label: "Sort by ticket price", + direction: 0 + }, + { + key: "event_date", + label: "Sort by event date", + direction: -1 + } +]; diff --git a/src/filters/index.js b/src/filters/index.js index 8c82c39..9ccaef8 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -1,2 +1,3 @@ -export * from "./question"; +export * from "./event"; +export * from "./challenge"; export * from "./submission"; diff --git a/src/hooks/auth.jsx b/src/hooks/auth.jsx index 4fc8831..798db6a 100644 --- a/src/hooks/auth.jsx +++ b/src/hooks/auth.jsx @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; import { useLocation, useNavigate } from "react-router-dom"; -export const whitelistedPaths = ["login", "register", "forgot-password", "reset-password"]; +export const whitelistedPaths = ["", "login", "register", "forgot-password", "reset-password", "events"]; const useAuth = () => { const navigate = useNavigate(); diff --git a/src/hooks/identification.jsx b/src/hooks/identification.jsx deleted file mode 100644 index 020fbdd..0000000 --- a/src/hooks/identification.jsx +++ /dev/null @@ -1,28 +0,0 @@ -import { useEffect } from "react"; -import { useSelector } from "react-redux"; -import { store } from "@/store"; -import { useAuthUserQuery, useGetSettingsQuery } from "@/store/api"; -import { toggleIdentificationForm } from "@/store/reducers/ui/global"; - -const useIdentification = () => { - const { data: { data: settings } = {} } = useGetSettingsQuery(); - const { data: { data: team } = {} } = useAuthUserQuery(); - - const open = useSelector((store) => store.ui.global.showIdentificationForm); - - useEffect(() => { - if ( - team && - team.role === "GROUP" && - settings?.round_breakpoint && - new Date() > new Date(settings?.round_breakpoint) && - !team.eliminated && - team.members.find((member) => !member.nic) && - !open - ) { - store.dispatch(toggleIdentificationForm(true)); - } - }, [settings, team]); -}; - -export default useIdentification; diff --git a/src/hooks/index.jsx b/src/hooks/index.jsx index 002c660..5742a3f 100644 --- a/src/hooks/index.jsx +++ b/src/hooks/index.jsx @@ -1,5 +1,4 @@ export { default as useAuth } from "./auth"; export { default as useBreakpoint } from "./breakpoint"; export { default as useEffectOnce } from "./effect-once"; -export { default as useIdentification } from "./identification"; export { default as useTitle } from "./title"; diff --git a/src/pages/404.jsx b/src/pages/404.jsx index 240e42b..22fddb7 100644 --- a/src/pages/404.jsx +++ b/src/pages/404.jsx @@ -3,7 +3,7 @@ import { useTitle } from "@/hooks"; import { Button } from "@sliit-foss/bashaway-ui/components"; const NotFound = () => { - useTitle("404 | Bashaway"); + useTitle("404 | Tech Events"); return (
{ }); }; - useTitle("Reset Password | Bashaway"); + useTitle("Reset Password | Tech Events"); return (
diff --git a/src/pages/auth/login.jsx b/src/pages/auth/login.jsx index 7641da8..bcd5d0a 100644 --- a/src/pages/auth/login.jsx +++ b/src/pages/auth/login.jsx @@ -1,6 +1,5 @@ import { Link, useNavigate } from "react-router-dom"; import { useTitle } from "@/hooks"; -import { tracker } from "@/services"; import { store } from "@/store"; import { authApi, useLoginMutation } from "@/store/api"; import { Button, Input } from "@sliit-foss/bashaway-ui/components"; @@ -13,7 +12,6 @@ const Login = () => { const handleLogin = async (e) => { e.preventDefault(); - tracker.event("login", { team_email: e.target.email.value }); await login({ email: e.target.email.value, password: e.target.password.value @@ -25,7 +23,7 @@ const Login = () => { }); }; - useTitle("Login | Bashaway"); + useTitle("Login | Tech Events"); return (
diff --git a/src/pages/auth/register.jsx b/src/pages/auth/register.jsx index 5cd64cd..c4a72f6 100644 --- a/src/pages/auth/register.jsx +++ b/src/pages/auth/register.jsx @@ -74,7 +74,7 @@ const Register = () => { } }; - useTitle("Register | Bashaway"); + useTitle("Register | Tech Events"); return ( <> diff --git a/src/pages/auth/reset-password.jsx b/src/pages/auth/reset-password.jsx index 15f7d11..0611a85 100644 --- a/src/pages/auth/reset-password.jsx +++ b/src/pages/auth/reset-password.jsx @@ -26,7 +26,7 @@ const ResetPassword = () => { }); }; - useTitle("Reset Password | Bashaway"); + useTitle("Reset Password | Tech Events"); return (
diff --git a/src/pages/question-details.jsx b/src/pages/challenge-details.jsx similarity index 67% rename from src/pages/question-details.jsx rename to src/pages/challenge-details.jsx index c66997a..f3d1a55 100644 --- a/src/pages/question-details.jsx +++ b/src/pages/challenge-details.jsx @@ -1,5 +1,4 @@ import { useMemo, useState } from "react"; -import { useEffect } from "react"; import { CheckCircle2 } from "lucide-react"; import { default as ReactMarkdown } from "react-markdown"; import { useSelector } from "react-redux"; @@ -7,29 +6,29 @@ import { useParams } from "react-router-dom"; import { default as isEmpty } from "lodash/isEmpty"; import { default as startCase } from "lodash/startCase"; import { twMerge } from "tailwind-merge"; -import { ActionButtons } from "@/components/question-details"; +import { ActionButtons } from "@/components/challenge-details"; import { useEffectOnce, useTitle } from "@/hooks"; -import { tracker, uploadSubmission } from "@/services"; +import { uploadSubmission } from "@/services"; import { store } from "@/store"; import { - selectQuestionById, + selectEventById, submissionApi, - updateQuestionStateById, + updateEventStateById, useAddSubmissionMutation, - useGetQuestionByIdQuery + useGetChallengeByIdQuery } from "@/store/api"; import { challengeColor } from "@/utils"; import { AnimatedSwitcher, Badge, BreadCrumbs, Skeleton, toast } from "@sliit-foss/bashaway-ui/components"; import { Body3, Footnote } from "@sliit-foss/bashaway-ui/typography"; -export default function QuestionDetails() { - const { id } = useParams(); +export default function ChallengeDetails() { + const { challenge_id: challengeId } = useParams(); const [uploading, setUploading] = useState(false); - const questionFromStore = useSelector(selectQuestionById(id)); + const challengeFromStore = useSelector(selectEventById(challengeId)); - const { data: { data: question = questionFromStore } = {}, isSuccess } = useGetQuestionByIdQuery(id); + const { data: { data: challenge = challengeFromStore } = {} } = useGetChallengeByIdQuery(challengeId); const [addSubmission, { isLoading }] = useAddSubmissionMutation(); @@ -43,12 +42,12 @@ export default function QuestionDetails() { if (!["zip"].includes(file.name?.split(".").pop())) return toast({ variant: "destructive", title: "Submission should be a zip archive" }); const url = await uploadSubmission(file); - await addSubmission({ question: id, link: url }) + await addSubmission({ challenge: challengeId, link: url }) .unwrap() .then(() => { toast({ title: "Submission added successfully" }); - updateQuestionStateById(store, id, { - total_submissions: !question.submitted ? question.total_submissions + 1 : question.total_submissions, + updateEventStateById(store, challengeId, { + total_submissions: !challenge.submitted ? challenge.total_submissions + 1 : challenge.total_submissions, submitted: true }); store.dispatch(submissionApi.util.resetApiState()); @@ -62,46 +61,37 @@ export default function QuestionDetails() { } }; - useTitle("Challenge | Bashaway"); + useTitle("Challenge | Tech Events"); - const cardStyles = useMemo(() => challengeColor(question), [question]); + const cardStyles = useMemo(() => challengeColor(challenge), [challenge]); useEffectOnce(() => { window.scroll({ top: 0, behavior: "smooth" }); }); - useEffect(() => { - if (isSuccess && question?._id) - tracker.event("challenge_view", { - challenge_id: question._id, - challenge_name: question.name, - challenge_difficulty: question.difficulty - }); - }, [question, isSuccess]); - return ( <>
- {!isEmpty(question) ? question?.total_submissions : }{" "} - {question?.total_submissions === 1 ? "team " : "teams "} + {!isEmpty(challenge) ? challenge?.total_submissions : }{" "} + {challenge?.total_submissions === 1 ? "team " : "teams "} submitted - {question?.name} - {question?.description} + {challenge?.name} + {challenge?.description}
- {startCase(question?.difficulty?.toLowerCase())} - {question?.max_score}PT - {question?.constraints?.length && {question?.constraints?.join(", ")}} + {startCase(challenge?.difficulty?.toLowerCase())} + {challenge?.max_score}PT + {challenge?.constraints?.length && {challenge?.constraints?.join(", ")}}
- +
} alternateComponent={ @@ -120,7 +110,7 @@ export default function QuestionDetails() { count={3} shade="dark" /> - + } /> diff --git a/src/pages/challenges.jsx b/src/pages/challenges.jsx new file mode 100644 index 0000000..6414dbc --- /dev/null +++ b/src/pages/challenges.jsx @@ -0,0 +1,77 @@ +import { useState } from "react"; +import { default as Lottie } from "react-lottie"; +import { twMerge } from "tailwind-merge"; +import { Challenge, ChallengeGridSkeleton } from "@/components/challenges"; +import { challengeFilters, challengeSorts } from "@/filters"; +import { useBreakpoint, useTitle } from "@/hooks"; +import { useGetChallengesQuery } from "@/store/api"; +import { AnimatedSwitcher, Filters, NoRecords, Pagination, Sorts } from "@sliit-foss/bashaway-ui/components"; +import { computeFilterQuery, computeSortQuery } from "@sliit-foss/bashaway-ui/utils"; +import { default as teamwork } from "../../public/assets/animations/teamwork.json"; + +const gridStyles = "w-full h-full grid grid-cols-1 lg:grid-cols-2 justify-start items-center gap-5"; + +const Challenges = () => { + const [page, setPage] = useState(1); + const [filters, setFilters] = useState(computeFilterQuery(challengeFilters)); + const [sorts, setSorts] = useState(computeSortQuery(challengeSorts)); + + const { data: challenges, isFetching, isError } = useGetChallengesQuery({ filters, sorts, page }); + + const { xs, sm } = useBreakpoint(); + + useTitle("Home | Tech Events"); + + return ( + <> +
+ + +
+
+ } + className={twMerge( + "grow flex flex-col", + !challenges?.data?.docs?.length && "justify-center pointer-events-none" + )} + alternateComponent={ + challenges?.data?.docs?.length ? ( +
+ {challenges?.data?.docs?.map((challenge) => ( + + ))} +
+ ) : ( + <> + + + + ) + } + /> +
+ setPage(newPage)} + totalPages={challenges?.data?.totalPages} + /> +
+
+ + ); +}; + +export default Challenges; diff --git a/src/pages/change-password.jsx b/src/pages/change-password.jsx index 4c1df4d..c022e5a 100644 --- a/src/pages/change-password.jsx +++ b/src/pages/change-password.jsx @@ -23,7 +23,7 @@ const ChangePassword = () => { }); }; - useTitle("Change Password | Bashaway"); + useTitle("Change Password | Tech Events"); return (
diff --git a/src/pages/event-details.jsx b/src/pages/event-details.jsx new file mode 100644 index 0000000..acde329 --- /dev/null +++ b/src/pages/event-details.jsx @@ -0,0 +1,117 @@ +import { useSelector } from "react-redux"; +import { useParams } from "react-router-dom"; +import { default as isEmpty } from "lodash/isEmpty"; +import { FAQ } from "@/components/event-details"; +import { useEffectOnce, useTitle } from "@/hooks"; +import { selectEventById, useGetEventByIdQuery } from "@/store/api"; +import { AnimatedSwitcher, Skeleton } from "@sliit-foss/bashaway-ui/components"; + +export default function EventDetails() { + const { event_id: eventId } = useParams(); + + const eventFromStore = useSelector(selectEventById(eventId)); + + const { data: { data: event = eventFromStore } = {} } = useGetEventByIdQuery(eventId); + + useTitle(`${event?.name ?? "Event"} | Tech Events`); + + useEffectOnce(() => { + window.scroll({ top: 0, behavior: "smooth" }); + }); + + return ( + <> +
+ +
+ +
+
+
+

DevFest 2023

+

Sri Lanka’s Largest Developer Event in 2023

+
+
+ + 09 Dec 2023 + + + 09:00AM + + + Informatics Institute of Technology, 10 Trelawney Pl + + + 09 Dec 2023 + + + 09 Dec 2023 + + + Musaeus College Auditorium, Colombo 07 + +
+
+
+ +
+
GDG Sri Lanka
+
+
+

This event includes

+
+ 👍🏻 Direct interaction with the instructor + 🖥 Access on mobile and web + 🎥 Session recording after the workshop + ⌛️ 1 hour live session +
+
+
+

About the event

+
+

+ DevFest Sri Lanka is Google’s annual developer conference in the country. Does this sound + interesting to you? Do you believe in helping others as you grow? Having trouble in debugging is + something that many people face. So, do you prefer to learn from professionals? Then you are just + at the right place. +

+
+
+
+
+

Frequently Asked Questions

+ Find all your answers related to this event. +
+
+ +
+
+
+
+
+
+
+ +
+
+
GDG Sri Lanka
+ @GDGLK +
+
+

GDG Sri Lanka is a community driven by technology enthusiasts, it’s Google supported.

+
+
+
+ } + alternateComponent={ + + } + /> +
+ + ); +} diff --git a/src/pages/home.jsx b/src/pages/home.jsx index 401a624..b7d7983 100644 --- a/src/pages/home.jsx +++ b/src/pages/home.jsx @@ -1,10 +1,10 @@ import { useState } from "react"; import { default as Lottie } from "react-lottie"; import { twMerge } from "tailwind-merge"; -import { Question, QuestionGridSkeleton } from "@/components/home"; -import { questionFilters, questionSorts } from "@/filters"; +import { Event, EventGridSkeleton } from "@/components/home"; +import { eventFilters, eventSorts } from "@/filters"; import { useBreakpoint, useTitle } from "@/hooks"; -import { useGetQuestionsQuery } from "@/store/api"; +import { useGetEventsQuery } from "@/store/api"; import { AnimatedSwitcher, Filters, NoRecords, Pagination, Sorts } from "@sliit-foss/bashaway-ui/components"; import { computeFilterQuery, computeSortQuery } from "@sliit-foss/bashaway-ui/utils"; import { default as teamwork } from "../../public/assets/animations/teamwork.json"; @@ -13,34 +13,31 @@ const gridStyles = "w-full h-full grid grid-cols-1 lg:grid-cols-2 justify-start const Home = () => { const [page, setPage] = useState(1); - const [filters, setFilters] = useState(computeFilterQuery(questionFilters)); - const [sorts, setSorts] = useState(computeSortQuery(questionSorts)); + const [filters, setFilters] = useState(computeFilterQuery(eventFilters)); + const [sorts, setSorts] = useState(computeSortQuery(eventSorts)); - const { data: questions, isFetching, isError } = useGetQuestionsQuery({ filters, sorts, page }); + const { data: events, isFetching, isError } = useGetEventsQuery({ filters, sorts, page }); const { xs, sm } = useBreakpoint(); - useTitle("Home | Bashaway"); + useTitle("Home | Tech Events"); return ( <>
- - + *:nth-child(1)]:w-full" }} /> +
} - className={twMerge( - "grow flex flex-col", - !questions?.data?.docs?.length && "justify-center pointer-events-none" - )} + component={} + className={twMerge("grow flex flex-col", !events?.data?.docs?.length && "justify-center pointer-events-none")} alternateComponent={ - questions?.data?.docs?.length ? ( + events?.data?.docs?.length ? (
- {questions?.data?.docs?.map((question) => ( - + {events?.data?.docs?.map((event) => ( + ))}
) : ( @@ -57,7 +54,7 @@ const Home = () => { height={200} width={sm ? 400 : xs ? 300 : 250} /> - + ) } @@ -66,7 +63,7 @@ const Home = () => { setPage(newPage)} - totalPages={questions?.data?.totalPages} + totalPages={events?.data?.totalPages} />
diff --git a/src/pages/index.jsx b/src/pages/index.jsx index f614845..c8feaa7 100644 --- a/src/pages/index.jsx +++ b/src/pages/index.jsx @@ -2,7 +2,9 @@ export { default as NotFound } from "./404"; export { default as ChangePassword } from "./change-password"; export { default as Home } from "./home"; export { default as Profile } from "./profile"; -export { default as QuestionDetails } from "./question-details"; +export { default as Challenges } from "./challenges"; +export { default as ChallengeDetails } from "./challenge-details"; export { default as Submissions } from "./submissions"; +export { default as EventDetails } from "./event-details"; export * from "./auth"; diff --git a/src/pages/profile.jsx b/src/pages/profile.jsx index dcffa18..bca5047 100644 --- a/src/pages/profile.jsx +++ b/src/pages/profile.jsx @@ -6,7 +6,7 @@ import { BreadCrumbs, Button } from "@sliit-foss/bashaway-ui/components"; const Profile = () => { const { data: { data: team } = {}, isLoading } = useAuthUserQuery(); - useTitle("Profile | Bashaway"); + useTitle("Profile | Tech Events"); useEffectOnce(() => { window.scroll({ top: 0, behavior: "smooth" }); diff --git a/src/pages/submissions.jsx b/src/pages/submissions.jsx index c915048..95e33e4 100644 --- a/src/pages/submissions.jsx +++ b/src/pages/submissions.jsx @@ -17,7 +17,7 @@ import { Body2 } from "@sliit-foss/bashaway-ui/typography"; import { computeFilterQuery, computeSortQuery } from "@sliit-foss/bashaway-ui/utils"; const Submissions = () => { - const { id: questionId } = useParams(); + const { challenge_id: challengeId } = useParams(); const [page, setPage] = useState(1); const [filters, setFilters] = useState(computeFilterQuery(submissionFilters)); @@ -29,9 +29,9 @@ const Submissions = () => { data: submissions, isFetching, isError - } = useGetMySubmissionsQuery({ filters: `filter[question]=${questionId}&${filters}`, sorts, page }); + } = useGetMySubmissionsQuery({ filters: `filter[challenge]=${challengeId}&${filters}`, sorts, page }); - useTitle("Submissions | Bashaway"); + useTitle("Submissions | Tech Events"); return ( <> @@ -40,7 +40,7 @@ const Submissions = () => { "Home", { label: "Challenge", - path: `/questions/${questionId}` + path: `/challenges/${challengeId}` }, "Submissions" ]} diff --git a/src/routes/index.jsx b/src/routes/index.jsx index 590d0b0..c2552d1 100644 --- a/src/routes/index.jsx +++ b/src/routes/index.jsx @@ -1,14 +1,15 @@ import { Route, Routes, useLocation } from "react-router-dom"; import { AnimatePresence } from "framer-motion"; -import { useIdentification } from "@/hooks"; import { + ChallengeDetails, + Challenges, ChangePassword, + EventDetails, ForgotPassword, Home, Login, NotFound, Profile, - QuestionDetails, Register, ResetPassword, Submissions @@ -16,15 +17,16 @@ import { const AnimatedRoutes = () => { const location = useLocation(); - useIdentification(); return ( } /> } /> } /> - } /> - } /> + } /> + } /> + } /> + } /> } /> } /> } /> diff --git a/src/services/firebase/analytics.js b/src/services/firebase/analytics.js index 1b79d8c..1d0b9c2 100644 --- a/src/services/firebase/analytics.js +++ b/src/services/firebase/analytics.js @@ -9,8 +9,8 @@ export const track = (name, params) => { setUserId(analytics, user?._id || "Anonymous"); logEvent(analytics, name, { ...params, - team_name: user?.name || "N/A", - team_email: params.team_email ?? user?.email ?? "N/A", + name: user?.name || "N/A", + email: user?.email ?? "N/A", user_agent: navigator.userAgent, platform: navigator.platform }); diff --git a/src/services/index.js b/src/services/index.js index c9b024f..f957427 100644 --- a/src/services/index.js +++ b/src/services/index.js @@ -1,3 +1,3 @@ export * from "./azure"; -export * from "./firebase"; +// export * from "./firebase"; export * from "./sentry"; diff --git a/src/store/api/base/index.js b/src/store/api/base/index.js index e480b69..99a806f 100644 --- a/src/store/api/base/index.js +++ b/src/store/api/base/index.js @@ -5,11 +5,12 @@ import { toast } from "@sliit-foss/bashaway-ui"; import { mutationHelper } from "./mutation-helper"; export * from "./mutation-helper"; +export * from "./query-helper"; const mutex = new Mutex(); const baseQuery = fetchBaseQuery({ - baseUrl: import.meta.env.VITE_BASHAWAY_BE_URL, + baseUrl: import.meta.env.VITE_CORE_BE_URL, prepareHeaders: (headers) => { headers.set("authorization", `Bearer ${localStorage.getItem("access_token")}`); const user = authUser(); diff --git a/src/store/api/base/query-helper.js b/src/store/api/base/query-helper.js new file mode 100644 index 0000000..4a5e167 --- /dev/null +++ b/src/store/api/base/query-helper.js @@ -0,0 +1,37 @@ +import { createSelector } from "@reduxjs/toolkit"; + +export const selectEntityById = (api, endpoint, id) => + createSelector( + (store) => store[api].queries, + (queries) => + Object.values(queries) + .filter((q) => q.endpointName === endpoint) + ?.sort((a, b) => b?.fulfilledTimeStamp - a.fulfilledTimeStamp)?.[0] + ?.data?.data?.docs?.filter((q) => q?._id === id)?.[0] + ); + +export const updateEntityStateById = (store, api, apiName, endpoints, id, payload) => { + Object.values(store.getState()[apiName].queries).forEach(({ endpointName, originalArgs, data }) => { + if (endpointName === endpoints[0]) { + store.dispatch( + api.util.upsertQueryData(endpoints[0], originalArgs, { + ...data, + data: { + ...data.data, + docs: data?.data?.docs?.map((q) => { + if (q?._id === id) return { ...q, ...payload }; + return q; + }) + } + }) + ); + } else if (endpointName === endpoints[1] && originalArgs === id) { + store.dispatch( + api.util.upsertQueryData(endpoints[1], originalArgs, { + ...data, + data: { ...data.data, ...payload } + }) + ); + } + }); +}; diff --git a/src/store/api/challenge.js b/src/store/api/challenge.js new file mode 100644 index 0000000..bd84d38 --- /dev/null +++ b/src/store/api/challenge.js @@ -0,0 +1,22 @@ +import { createApi } from "@reduxjs/toolkit/query/react"; +import baseQuery, { selectEntityById, updateEntityStateById } from "./base"; + +export const challengeApi = createApi({ + reducerPath: "challengeApi", + baseQuery, + endpoints: (builder) => ({ + getChallenges: builder.query({ + query: ({ filters, sorts, page }) => `/api/challenges?${filters}&${sorts}&page=${page}&limit=6` + }), + getChallengeById: builder.query({ + query: (id) => `/api/challenges/${id}` + }) + }) +}); + +export const selectChallengeById = (id) => selectEntityById("challengeApi", "getChallenges", id); + +export const updateChallengeStateById = (store, id, payload) => + updateEntityStateById(store, challengeApi, "challengeApi", ["getChallenges", "getChallengeById"], id, payload); + +export const { useGetChallengesQuery, useLazyGetChallengesQuery, useGetChallengeByIdQuery } = challengeApi; diff --git a/src/store/api/event.js b/src/store/api/event.js new file mode 100644 index 0000000..a062fe3 --- /dev/null +++ b/src/store/api/event.js @@ -0,0 +1,22 @@ +import { createApi } from "@reduxjs/toolkit/query/react"; +import baseQuery, { selectEntityById, updateEntityStateById } from "./base"; + +export const eventApi = createApi({ + reducerPath: "eventApi", + baseQuery, + endpoints: (builder) => ({ + getEvents: builder.query({ + query: ({ filters, sorts, page }) => `/api/events?${filters}&${sorts}&page=${page}&limit=6` + }), + getEventById: builder.query({ + query: (id) => `/api/events/${id}` + }) + }) +}); + +export const selectEventById = (id) => selectEntityById("eventApi", "getEvents", id); + +export const updateEventStateById = (store, id, payload) => + updateEntityStateById(store, eventApi, "eventApi", ["getEvents", "getEventById"], id, payload); + +export const { useGetEventsQuery, useLazyGetEventsQuery, useGetEventByIdQuery } = eventApi; diff --git a/src/store/api/index.js b/src/store/api/index.js index 141ed50..c9098ab 100644 --- a/src/store/api/index.js +++ b/src/store/api/index.js @@ -1,5 +1,6 @@ export * from "./auth"; -export * from "./question"; +export * from "./challenge"; +export * from "./event"; export * from "./setting"; export * from "./submission"; export * from "./user"; diff --git a/src/store/api/question.js b/src/store/api/question.js deleted file mode 100644 index fe1104b..0000000 --- a/src/store/api/question.js +++ /dev/null @@ -1,54 +0,0 @@ -import { createApi } from "@reduxjs/toolkit/query/react"; -import { createSelector } from "@reduxjs/toolkit"; -import baseQuery from "./base"; - -export const questionApi = createApi({ - reducerPath: "questionApi", - baseQuery, - endpoints: (builder) => ({ - getQuestions: builder.query({ - query: ({ filters, sorts, page }) => `/api/questions?${filters}&${sorts}&page=${page}&limit=6` - }), - getQuestionById: builder.query({ - query: (id) => `/api/questions/${id}` - }) - }) -}); - -export const selectQuestionById = (id) => - createSelector( - ({ questionApi }) => questionApi.queries, - (queries) => - Object.values(queries) - .filter((q) => q.endpointName === "getQuestions") - ?.sort((a, b) => b?.fulfilledTimeStamp - a.fulfilledTimeStamp)?.[0] - ?.data?.data?.docs?.filter((q) => q?._id === id)?.[0] - ); - -export const updateQuestionStateById = (store, id, payload) => { - Object.values(store.getState().questionApi.queries).forEach(({ endpointName, originalArgs, data }) => { - if (endpointName === "getQuestions") { - store.dispatch( - questionApi.util.upsertQueryData("getQuestions", originalArgs, { - ...data, - data: { - ...data.data, - docs: data?.data?.docs?.map((q) => { - if (q?._id === id) return { ...q, ...payload }; - return q; - }) - } - }) - ); - } else if (endpointName === "getQuestionById" && originalArgs === id) { - store.dispatch( - questionApi.util.upsertQueryData("getQuestionById", originalArgs, { - ...data, - data: { ...data.data, ...payload } - }) - ); - } - }); -}; - -export const { useGetQuestionsQuery, useLazyGetQuestionsQuery, useGetQuestionByIdQuery } = questionApi; diff --git a/src/store/index.js b/src/store/index.js index af6443f..36d5a3c 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1,5 +1,5 @@ import { combineReducers, configureStore } from "@reduxjs/toolkit"; -import { authApi, questionApi, settingApi, submissionApi, userApi } from "./api"; +import { authApi, challengeApi, eventApi, settingApi, submissionApi, userApi } from "./api"; import { default as rootReducer } from "./reducers"; export function makeStore() { @@ -8,7 +8,8 @@ export function makeStore() { reducer: combineReducers({ ...rootReducer, [authApi.reducerPath]: authApi.reducer, - [questionApi.reducerPath]: questionApi.reducer, + [eventApi.reducerPath]: eventApi.reducer, + [challengeApi.reducerPath]: challengeApi.reducer, [settingApi.reducerPath]: settingApi.reducer, [submissionApi.reducerPath]: submissionApi.reducer, [userApi.reducerPath]: userApi.reducer @@ -16,7 +17,8 @@ export function makeStore() { middleware: (getDefaultMiddleware) => { const middleware = getDefaultMiddleware({ serializableCheck: false }) .concat(authApi.middleware) - .concat(questionApi.middleware) + .concat(eventApi.middleware) + .concat(challengeApi.middleware) .concat(settingApi.middleware) .concat(submissionApi.middleware) .concat(userApi.middleware); diff --git a/src/store/reducers/ui/event.js b/src/store/reducers/ui/event.js new file mode 100644 index 0000000..e2c3551 --- /dev/null +++ b/src/store/reducers/ui/event.js @@ -0,0 +1,19 @@ +import { createSlice } from "@reduxjs/toolkit"; + +const initialState = { + showSurveyForm: false +}; + +export const slice = createSlice({ + name: "event", + initialState, + reducers: { + toggleSurveyForm(state, action) { + state.showSurveyForm = action.payload; + } + } +}); + +export const { toggleSurveyForm } = slice.actions; + +export default slice.reducer; diff --git a/src/store/reducers/ui/global.js b/src/store/reducers/ui/global.js index 42fc1e3..1cb9888 100644 --- a/src/store/reducers/ui/global.js +++ b/src/store/reducers/ui/global.js @@ -1,8 +1,7 @@ import { createSlice } from "@reduxjs/toolkit"; const initialState = { - showLoader: false, - showIdentificationForm: false + showLoader: false }; export const slice = createSlice({ @@ -11,13 +10,10 @@ export const slice = createSlice({ reducers: { toggleLoader(state, action) { state.showLoader = action.payload; - }, - toggleIdentificationForm(state, action) { - state.showIdentificationForm = action.payload; } } }); -export const { toggleLoader, toggleIdentificationForm } = slice.actions; +export const { toggleLoader } = slice.actions; export default slice.reducer; diff --git a/src/store/reducers/ui/index.js b/src/store/reducers/ui/index.js index 4566d10..b201398 100644 --- a/src/store/reducers/ui/index.js +++ b/src/store/reducers/ui/index.js @@ -1,8 +1,10 @@ import { combineReducers } from "@reduxjs/toolkit"; +import event from "./event"; import global from "./global"; import profile from "./profile"; export default combineReducers({ + event, global, profile }); diff --git a/src/utils/colors.js b/src/utils/colors.js index 0f15fec..f7c7a1f 100644 --- a/src/utils/colors.js +++ b/src/utils/colors.js @@ -1,9 +1,16 @@ -export const challengeColor = (question) => { - if (question?.difficulty === "HARD" || question?.difficulty === "EXTREME") { +export const challengeColor = (challenge) => { + if (challenge?.difficulty === "HARD" || challenge?.difficulty === "EXTREME") { return "card-red"; - } else if (question?.difficulty === "MEDIUM") { + } else if (challenge?.difficulty === "MEDIUM") { return "card-blue"; } else { return "card-green"; } }; + +export const eventColor = (event) => { + if (event.settings.payments.enabled) { + return "card-blue"; + } + return "card-green"; +}; diff --git a/src/utils/image.js b/src/utils/image.js new file mode 100644 index 0000000..6aff23d --- /dev/null +++ b/src/utils/image.js @@ -0,0 +1,22 @@ +import resolveConfig from "tailwindcss/resolveConfig"; +import tailwindConfig from "../../tailwind.config.js"; + +const config = resolveConfig(tailwindConfig); + +const pixelValue = (value) => { + if (typeof value === "string") { + return Number(value.replace("px", "")); + } + return value; +}; + +const screenSizes = Object.entries(config.theme.screens).toSorted((a, b) => pixelValue(b[1]) - pixelValue(a[1])); + +export const getBreakpointImage = (urls = {}) => { + for (const [breakpoint, pixels] of screenSizes) { + if (window.matchMedia(`(min-width: ${pixels})`).matches) { + return Object.entries(urls).find(([key]) => key === breakpoint)?.[1] ?? urls.default; + } + } + return urls.default; +}; diff --git a/src/utils/index.js b/src/utils/index.js index f765a33..026e694 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -1,4 +1,5 @@ export * from "./colors"; +export * from "./image"; export * from "./jwt"; export const getRegexPatternFromKey = (key) => {