34 | sent 35 | 36 | 37 | +{donation?.amount?.toLocaleString("US")} 38 | 39 | 40 | {getTimeDifference(String(donation?.createdAt ?? "")) ?? "few mins ago"} 41 |
42 |No donations yet.
47 | )} 48 | 49 | {donations?.length! > 0 && ( 50 | 56 | )} 57 |├── .github ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── add-contributors.yml │ ├── assign.yml │ ├── codeql.yml │ ├── delete-merged-branch.yml │ ├── dependency-review.yml │ ├── label-issues.yml │ ├── pre-commit.yml │ ├── stale.yml │ └── triage.yml ├── .gitignore ├── .husky └── pre-commit ├── .vscode ├── extensions.json └── settings.json ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── biome.json ├── components.json ├── components ├── 2fa │ ├── FirstStep.tsx │ ├── RecoveryCode.tsx │ └── index.ts ├── Protect.tsx ├── about-page │ ├── ContactForm.tsx │ ├── OurAchievements.tsx │ ├── OurPurpose.tsx │ ├── OurTeam.tsx │ └── index.ts ├── campaign-analytics │ ├── Card.tsx │ ├── DonationChart.tsx │ ├── DonationStatsPanel.tsx │ ├── DonationTable.tsx │ ├── MapDisplay.tsx │ ├── SingleCountryDonor.tsx │ ├── SingleTopDonor.tsx │ ├── TopDonators.tsx │ ├── WorldMap.tsx │ └── index.ts ├── campaigns-page │ ├── SuccessStories.tsx │ └── index.ts ├── common │ ├── Avatar.tsx │ ├── CampaignCard.tsx │ ├── CloudflareTurnstile.tsx │ ├── CustomDialog.tsx │ ├── Footer.tsx │ ├── FormErrorMessage.tsx │ ├── Header.tsx │ ├── Heading.tsx │ ├── Loader.tsx │ ├── LogoBanner.tsx │ ├── OtpInputDisplay.tsx │ ├── PageMetaData.tsx │ ├── Pagination.tsx │ ├── SingleCampaignProgress.tsx │ ├── Spinner.tsx │ ├── Success.tsx │ ├── TestimonialCard.tsx │ ├── campaign-icons │ │ ├── ArrowSpinIcon.tsx │ │ ├── ChooseDonation.svg │ │ ├── CrossIcon.tsx │ │ ├── DonorIcon.tsx │ │ ├── DummyAvatar.tsx │ │ ├── MoneyIcon.tsx │ │ ├── SelectFundraiser.svg │ │ ├── ShareIcon.tsx │ │ ├── SubmitDonation.svg │ │ ├── TickIcon.tsx │ │ ├── index.ts │ │ ├── whatsapp.svg │ │ └── x-icon.svg │ ├── dashboardIcons │ │ ├── AnalyticsIcon.tsx │ │ ├── ArrowDown.tsx │ │ ├── BankIcon.tsx │ │ ├── BookmarkIcon.tsx │ │ ├── CampaignIcon.tsx │ │ ├── Chat.tsx │ │ ├── ClockIcon.tsx │ │ ├── DashboardIcon.tsx │ │ ├── EyeIcon.tsx │ │ ├── LocationIcon.tsx │ │ ├── Logo.tsx │ │ ├── MegaphoneIcon.tsx │ │ ├── Menu.tsx │ │ ├── Notification.tsx │ │ ├── PlusIcon.tsx │ │ ├── SettingsIcon.tsx │ │ ├── Star.tsx │ │ ├── UpdatesIcon.tsx │ │ ├── UserIcon.tsx │ │ └── index.ts │ ├── index.ts │ ├── landingPage │ │ ├── CampaignCategories.tsx │ │ ├── FAQ.tsx │ │ ├── Hero.tsx │ │ ├── HowItWorks.tsx │ │ ├── SuccessStories.tsx │ │ ├── UrgentFundraisers.tsx │ │ └── index.ts │ └── svg.tsx ├── create-campaign │ ├── CampaignCarousel.tsx │ ├── CampaignOutlook.tsx │ ├── DonationFlowDialog.tsx │ ├── DonorSection.tsx │ ├── DropZoneInput.tsx │ ├── FormActionButton.tsx │ ├── FormErrorMessage.tsx │ ├── FormSteps │ │ ├── StepOne.tsx │ │ ├── StepThree.tsx │ │ └── StepTwo.tsx │ ├── ImagePreview.tsx │ ├── ShareCampaignDialog.tsx │ ├── StepTracker │ │ ├── StepDetails.tsx │ │ └── StepTracker.tsx │ ├── TipTapEditor │ │ ├── TipTapEditor.tsx │ │ ├── TipTapToolBar.tsx │ │ └── index.ts │ └── index.ts ├── dashboard │ ├── SummaryCard.tsx │ ├── index.ts │ └── settings │ │ ├── Account.tsx │ │ ├── Billing.tsx │ │ ├── Notification.tsx │ │ └── index.ts ├── explore-campaign │ ├── CampaignCategoryCard.tsx │ ├── NoCampaigns.tsx │ └── index.ts └── ui │ ├── accordion.tsx │ ├── button.tsx │ ├── card.tsx │ ├── carousel │ ├── carousel.tsx │ ├── carousel.types.ts │ ├── carouselStoreContext.tsx │ ├── index.ts │ └── useCarouselOptions.ts │ ├── checkbox.tsx │ ├── date-picker │ ├── DateRangePicker.tsx │ ├── calender.tsx │ ├── date-picker.tsx │ ├── index.ts │ └── popover.tsx │ ├── dialog.tsx │ ├── drop-zone.tsx │ ├── dropdown-menu.tsx │ ├── index.ts │ ├── input.tsx │ ├── progressbar.tsx │ ├── select.tsx │ ├── sonner.tsx │ ├── switch.tsx │ ├── table.tsx │ ├── tabs.tsx │ └── toggle.tsx ├── interfaces ├── ApiResponses.ts ├── Campaign.ts ├── Donation.ts ├── FormInputs.ts ├── Layouts.ts ├── SvgProps.ts ├── WithPageLayout.ts └── index.ts ├── layouts ├── AuthPagesLayout.tsx ├── AuthenticatedUserLayout.tsx ├── BaseLayout.tsx └── index.ts ├── lib ├── constants.ts ├── helpers │ ├── callAbegApi.ts │ ├── callApi.ts │ ├── campaign │ │ ├── constants.ts │ │ ├── generateExcerpt.ts │ │ ├── getDateFromString.ts │ │ ├── getEditorExtensions.ts │ │ ├── index.ts │ │ ├── validateFiles.ts │ │ └── validateTagValue.ts │ ├── checkIsDeviceMobileOrTablet.ts │ ├── cn.ts │ ├── create-fetcher │ │ ├── create-fetcher.ts │ │ ├── index.ts │ │ ├── types.ts │ │ └── utils.ts │ ├── getDaysLeft.ts │ ├── getTimeDiff.ts │ ├── omitKeys.ts │ └── parseJSON.ts ├── hooks │ ├── createCustomContext.ts │ ├── index.ts │ ├── useAnimationInterval.ts │ ├── useCallbackRef.ts │ ├── useCloudflareTurnstile.tsx │ ├── useCopyToClipboard.ts │ ├── useDragScroll.ts │ ├── useElementList │ │ ├── For.tsx │ │ └── useElementList.ts │ ├── usePaginate.ts │ ├── usePagination.ts │ ├── useShareCampaign.ts │ ├── useSlot.ts │ ├── useToggle.ts │ └── useWatchInput.ts ├── index.ts ├── type-helpers │ ├── assert.ts │ ├── global.ts │ ├── index.ts │ ├── polymorphism-helper.ts │ └── typeof.ts └── validators │ └── validateWithZod.ts ├── lint-staged.config.js ├── next.config.js ├── package-lock.json ├── package.json ├── pages ├── 2fa │ ├── app.tsx │ ├── authenticate.tsx │ └── index.tsx ├── 404.tsx ├── 500.tsx ├── _app.tsx ├── _document.tsx ├── about │ └── index.tsx ├── api │ └── hello.ts ├── c │ ├── [shortId].tsx │ ├── bookmarks.tsx │ ├── campaigns.tsx │ ├── create.tsx │ ├── index.tsx │ ├── overview.tsx │ ├── preview.tsx │ └── settings.tsx ├── explore │ ├── [categoryId].tsx │ └── index.tsx ├── forgot-password │ └── index.tsx ├── get-started.tsx ├── how-it-works │ └── index.tsx ├── index.tsx ├── reset-password │ ├── index.tsx │ └── success.tsx ├── reveal.tsx ├── signin │ └── index.tsx ├── signup │ ├── index.tsx │ └── verification.tsx └── verify-email │ ├── index.tsx │ └── success.tsx ├── postcss.config.js ├── public ├── assets │ ├── icons │ │ ├── auth │ │ │ ├── arrow-down.svg │ │ │ ├── auth-padlock.svg │ │ │ ├── eye.svg │ │ │ ├── notification-bing.svg │ │ │ └── slashEye.svg │ │ ├── dashboard │ │ │ └── userIcon.svg │ │ └── shared │ │ │ ├── copy.svg │ │ │ ├── google.png │ │ │ └── twitter.png │ ├── images │ │ ├── about-page │ │ │ ├── MagicPattern.png │ │ │ ├── aboutHero.png │ │ │ ├── image-pattern.png │ │ │ ├── index.ts │ │ │ ├── jane.png │ │ │ ├── john-one.png │ │ │ ├── john-two.png │ │ │ ├── our-achievements1.svg │ │ │ ├── our-achievements2.svg │ │ │ ├── our-achievements3.svg │ │ │ ├── our-achievements4.svg │ │ │ └── who-we-are-image.png │ │ ├── auth │ │ │ ├── auth-bg-contourss.png │ │ │ └── auth-bg-jar.svg │ │ ├── campaign-category │ │ │ ├── arrow-down-small.svg │ │ │ ├── arrow-left.svg │ │ │ ├── arrow-right.svg │ │ │ ├── hero-circle.svg │ │ │ ├── hero-half-moon.svg │ │ │ ├── index.ts │ │ │ └── search-icon.svg │ │ ├── dashboard │ │ │ ├── dashboardBg.png │ │ │ ├── dashboardImage.png │ │ │ ├── dummyCardImg.svg │ │ │ └── userIcon.svg │ │ ├── error-pages │ │ │ ├── 404-image.svg │ │ │ ├── 404.svg │ │ │ ├── 500-image.png │ │ │ └── 500.svg │ │ ├── how-it-works │ │ │ ├── how-it-work-hero.png │ │ │ ├── how-it-works-splash.png │ │ │ └── index.ts │ │ ├── landing-page │ │ │ ├── Facebook.svg │ │ │ ├── Instagram.svg │ │ │ ├── LinkedIn.svg │ │ │ ├── MagicPattern.svg │ │ │ ├── Twitter.svg │ │ │ ├── YouTube.svg │ │ │ ├── avatar1.svg │ │ │ ├── avatar2.svg │ │ │ ├── avatar3.svg │ │ │ ├── background.png │ │ │ ├── campaign-hero.svg │ │ │ ├── charity.png │ │ │ ├── close-circle.svg │ │ │ ├── contours.png │ │ │ ├── create-campaign-image1.png │ │ │ ├── create-campaign-image2.png │ │ │ ├── create-campaign-image3.png │ │ │ ├── crowd-fund.svg │ │ │ ├── dropbox.svg │ │ │ ├── envato.svg │ │ │ ├── global-community.svg │ │ │ ├── google.svg │ │ │ ├── happy-people.png │ │ │ ├── hero.svg │ │ │ ├── index.ts │ │ │ ├── join-us.png │ │ │ ├── joinUs.svg │ │ │ ├── menu.svg │ │ │ ├── netflix.svg │ │ │ ├── senville.svg │ │ │ ├── stories-about-us.png │ │ │ ├── support.png │ │ │ ├── testimonial-image1.png │ │ │ ├── testimonial-image2.png │ │ │ ├── testimonial-image3.png │ │ │ └── timecamp.svg │ │ └── shared │ │ │ ├── bg-contours.png │ │ │ ├── contours-old.png │ │ │ ├── hero-background.svg │ │ │ └── logo.svg │ ├── lottie │ │ └── success.json │ └── worldmap.json ├── favicon.ico ├── hero.jpg ├── next.svg └── vercel.svg ├── store ├── index.ts ├── store-types.ts ├── useCampaignStore.ts ├── useFormStore.ts └── useSession.ts ├── styles └── globals.css ├── tailwind.config.ts ├── todo.txt └── tsconfig.json /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## NOTE: 2 | 3 | Type .take as a comment under an issue to automatically assign it to yourself 4 | 5 | ## Issue Type 6 | 7 | - [ ] Bug Report 8 | - [ ] Feature Request 9 | - [ ] Enhancement 10 | - [ ] Other (Please specify) 11 | 12 | ## Description 13 | 14 | [Provide a brief description of the issue or feature request.] 15 | 16 | ## Details 17 | 18 | [Provide more details about the issue or feature request. If it's a bug, include any error messages, unexpected behavior, or other relevant information. If it's a feature request or enhancement, explain why it would be valuable.] 19 | 20 | ## Reproduction Steps (For Bug Reports) 21 | 22 | 1. [Step 1] 23 | 2. [Step 2] 24 | 3. [Step 3] 25 | 26 | ## Expected Behavior (For Bug Reports) 27 | 28 | [Describe what you expected to happen.] 29 | 30 | ## Actual Behavior (For Bug Reports) 31 | 32 | [Describe what actually happened.] 33 | 34 | ## Additional Information 35 | 36 | [Include any additional information that may help in resolving the issue, such as screenshots, logs, or any related links.] 37 | 38 | ## Would you like to contribute to fixing this issue? 39 | 40 | - [ ] Yes 41 | - [ ] No 42 | 43 | ## Related Pull Requests (if any) 44 | 45 | [Link to any related pull requests, if applicable.] 46 | 47 | ## Failure Logs 48 | 49 | Please include any relevant log snippets or files here. 50 | 51 | **Note:** Please make sure to follow the project's code of conduct and contribution guidelines when creating an issue. Thank you for contributing to our open-source project! 52 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | Please include a summary of the change and which issue is fixed (if applicable). 4 | 5 | ## Related Issue 6 | 7 | - Fixes # (issue number) 8 | 9 | ## Contribution Guidelines 10 | 11 | Before submitting this pull request, please review our [Contribution Guidelines](https://github.com/abeg-help/frontend/blob/dev/CONTRIBUTING.md) to understand how to contribute to this project. 12 | 13 | ## Checklist 14 | 15 | - [ ] I have reviewed the Contribution Guidelines linked above. 16 | - [ ] I have tested my changes thoroughly and ensured that all existing tests pass. 17 | - [ ] I have provided clear and concise commit messages. 18 | - [ ] I have updated the project's documentation as necessary. 19 | 20 | ## Screenshots (if applicable) 21 | 22 | 23 | 24 | ## Additional context (if needed) 25 | 26 | 27 | -------------------------------------------------------------------------------- /.github/workflows/add-contributors.yml: -------------------------------------------------------------------------------- 1 | name: Add contributors 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - closed 7 | 8 | jobs: 9 | contrib-readme-job: 10 | if: github.event.pull_request.merged == true 11 | runs-on: ubuntu-latest 12 | name: A job to automate contrib in readme 13 | steps: 14 | - name: Contribute List 15 | uses: akhilmhdh/contributors-readme-action@v2.3.6 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} 18 | -------------------------------------------------------------------------------- /.github/workflows/assign.yml: -------------------------------------------------------------------------------- 1 | # .github/workflows/take.yml 2 | name: Assign issue to contributor 3 | on: 4 | issue_comment: 5 | 6 | jobs: 7 | assign: 8 | name: Take an issue 9 | runs-on: ubuntu-latest 10 | permissions: 11 | issues: write 12 | steps: 13 | - name: take the issue 14 | uses: bdougie/take-action@main 15 | with: 16 | message: Thanks for taking this issue! Let us know if you have any questions! 17 | trigger: .take 18 | token: ${{ secrets.GITHUB_TOKEN }} 19 | -------------------------------------------------------------------------------- /.github/workflows/delete-merged-branch.yml: -------------------------------------------------------------------------------- 1 | name: Delete Merged Branch 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - closed 7 | 8 | jobs: 9 | delete_branch: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Check if pull request was merged 14 | if: github.event.pull_request.merged == true 15 | run: | 16 | source_branch=$(jq --raw-output .pull_request.head.ref "$GITHUB_EVENT_PATH") 17 | base_branch=$(jq --raw-output .pull_request.base.ref "$GITHUB_EVENT_PATH") 18 | 19 | if [[ $source_branch != "main" && $source_branch != "dev" ]]; then 20 | echo "Deleting branch: $source_branch" 21 | git push origin --delete "$source_branch" 22 | else 23 | echo "Branch $source_branch is not deleted since it's 'main' or 'dev'." 24 | fi 25 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | # Dependency Review Action 2 | # 3 | # This Action will scan dependency manifest files that change as part of a Pull Request, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging. 4 | # 5 | # Source repository: https://github.com/actions/dependency-review-action 6 | # Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement 7 | name: "Dependency Review" 8 | on: [pull_request] 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | dependency-review: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: "Checkout Repository" 18 | uses: actions/checkout@v3 19 | - name: "Dependency Review" 20 | uses: actions/dependency-review-action@v3 21 | -------------------------------------------------------------------------------- /.github/workflows/label-issues.yml: -------------------------------------------------------------------------------- 1 | name: Label issues 2 | on: 3 | issues: 4 | types: 5 | - reopened 6 | - opened 7 | jobs: 8 | label_issues: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | issues: write 12 | steps: 13 | - uses: actions/github-script@v6 14 | with: 15 | script: | 16 | github.rest.issues.addLabels({ 17 | issue_number: context.issue.number, 18 | owner: context.repo.owner, 19 | repo: context.repo.repo, 20 | labels: ["triage"] 21 | }) 22 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - dev 8 | pull_request: 9 | branches: 10 | - main 11 | - dev 12 | 13 | jobs: 14 | ci-checks: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@v2 20 | 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v2 23 | with: 24 | node-version: 16 25 | 26 | - name: Install dependencies 27 | run: npm ci 28 | 29 | - name: Run ESLint 30 | run: npm run check-types 31 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | # This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time. 2 | # 3 | # You can adjust the behavior by modifying this file. 4 | # For more information, see: 5 | # https://github.com/actions/stale 6 | name: Mark stale issues and pull requests 7 | 8 | on: 9 | schedule: 10 | - cron: "32 17 * * *" 11 | 12 | jobs: 13 | stale: 14 | runs-on: ubuntu-latest 15 | permissions: 16 | issues: write 17 | pull-requests: write 18 | 19 | steps: 20 | - uses: actions/stale@v5 21 | with: 22 | repo-token: ${{ secrets.GITHUB_TOKEN }} 23 | stale-issue-message: "Stale issue message" 24 | stale-pr-message: "Stale pull request message" 25 | stale-issue-label: "no-issue-activity" 26 | stale-pr-label: "no-pr-activity" 27 | -------------------------------------------------------------------------------- /.github/workflows/triage.yml: -------------------------------------------------------------------------------- 1 | name: "Assign issues with .take" 2 | 3 | on: 4 | issue_comment: 5 | types: 6 | - created 7 | - edited 8 | 9 | jobs: 10 | take-issue: 11 | name: Disable take issue 12 | runs-on: ubuntu-latest 13 | timeout-minutes: 10 14 | steps: 15 | - name: take an issue 16 | uses: bdougie/take-action@main 17 | with: 18 | issueCurrentlyAssignedMessage: Thanks for being interested in this issue. It looks like this ticket is already assigned to someone else. 19 | token: ${{ secrets.GITHUB_TOKEN }} 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # env files 4 | .env.local 5 | .env.development.local 6 | .env.test.local 7 | .env.production.local 8 | .env 9 | 10 | # dependencies 11 | /node_modules 12 | /.pnp 13 | .pnp.js 14 | # pnpm lock file 15 | pnpm-lock.yaml 16 | # testing 17 | /coverage 18 | 19 | # next.js 20 | /.next/ 21 | /out/ 22 | 23 | # production 24 | /build 25 | 26 | # misc 27 | .DS_Store 28 | .npmrc 29 | *.pem 30 | 31 | # debug 32 | npm-debug.log* 33 | yarn-debug.log* 34 | yarn-error.log* 35 | 36 | # local env files 37 | .env*.local 38 | .env 39 | 40 | # vercel 41 | .vercel 42 | 43 | # typescript 44 | *.tsbuildinfo 45 | next-env.d.ts 46 | node_modules 47 | .idea 48 | /.idea/ -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "yzhang.markdown-all-in-one", // Markdown All in One 4 | "christian-kohler.path-intellisense", // Path Intellisense, 5 | "eamodio.gitlens", // GitLens — Git supercharged 6 | "wix.vscode-import-cost", // Import Cost, 7 | "formulahendry.auto-rename-tag", // Auto Rename Tag 8 | "streetsidesoftware.code-spell-checker" // Code Spell Checker 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "Abeg", 4 | "abeghelpme", 5 | "clsx", 6 | "Didn", 7 | "hookform", 8 | "SEOCONFIG", 9 | "signin", 10 | "signout", 11 | "signup", 12 | "sonner", 13 | "zxcvbn" 14 | ], 15 | 16 | "editor.codeActionsOnSave": { 17 | "source.addMissingImports": "never" 18 | }, 19 | 20 | "tailwindCSS.rootFontSize": 16, 21 | "typescript.tsdk": "node_modules\\typescript\\lib" 22 | } 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | All Rights Reserved 2 | 3 | Copyright (c) [2023] 4 | 5 | THE CONTENTS OF THIS PROJECT ARE PROPRIETARY AND CONFIDENTIAL. 6 | UNAUTHORIZED COPYING, TRANSFERRING OR REPRODUCTION OF THE CONTENTS OF THIS PROJECT, VIA ANY MEDIUM IS STRICTLY PROHIBITED. 7 | 8 | The receipt or possession of the source code and/or any parts thereof does not convey or imply any right to use them 9 | for any purpose other than the purpose for which they were provided to you. 10 | 11 | The software is provided "AS IS", without warranty of any kind, express or implied, including but not limited to 12 | the warranties of merchantability, fitness for a particular purpose and non infringement. 13 | In no event shall the authors or copyright holders be liable for any claim, damages or other liability, 14 | whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software 15 | or the use or other dealings in the software. 16 | 17 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the software. 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
"Abeg, a man of many talents and skills. He is the best in all things."
5 |23 | We’ll now ask for a login code whenever you want to log in so that 24 | we're sure it's you 25 |
26 |27 | Make sure to secure your recovery key in safe place . Without these, you 28 | may not be able to disable 2FA your account if you lose access to your 29 | phone or can’t log in using your security method. 30 |
31 |46 | Keep your recovery key in a secure place as it is the only code that 47 | can be used to disable your 2FA 48 |
49 |50 | {achievement.title} 51 |
52 |Meet the team
46 |{dev.position}
63 | 64 |Total {title}
14 |{amount}
17 |{analytics}
18 |Total donors so far
9 |10 | Real time report 11 |
12 |10.8K
17 |{country}
61 |{progress}%
67 |{name}
17 |{type}
18 |${donated}
23 |Target: ${target}
24 |40 | Top Donors 41 |
42 |= 2 && result <= 3 37 | ? "text-yellow-500" 38 | : "text-green-500" 39 | } text-sm`} 40 | > 41 | Password strength: 42 | 43 | {result < 2 44 | ? "Weak" 45 | : result >= 2 && result <= 3 46 | ? "Medium" 47 | : "Strong"} 48 |
49 |{errorMsg}
; 53 | } 54 | ); 55 | 56 | export default FormErrorMessage; 57 | -------------------------------------------------------------------------------- /components/common/Heading.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib"; 2 | import type { PolymorphicProps } from "@/lib/type-helpers/polymorphism-helper"; 3 | 4 | type HeadingElements = keyof typeof semanticHeadings; 5 | 6 | type HeadingProps{message}
12 |{description}
38 |18 | See the list of campaign categories in Abeghelp.me and get started to 19 | empower your cause and that of others 20 |
21 |34 | sent 35 | 36 | 37 | +{donation?.amount?.toLocaleString("US")} 38 | 39 | 40 | {getTimeDifference(String(donation?.createdAt ?? "")) ?? "few mins ago"} 41 |
42 |No donations yet.
47 | )} 48 | 49 | {donations?.length! > 0 && ( 50 | 56 | )} 57 |Click to select files, or Drag {`'n'`} Drop
52 | 53 |Support files; PDF, JPG, CSV
54 | 55 |Not more than 5mb
56 |errorParagraphRef.current?.classList.remove(animationClass)} 49 | > 50 | *{message} 51 |
52 | ); 53 | } 54 | 55 | export default FormErrorMessage; 56 | -------------------------------------------------------------------------------- /components/create-campaign/StepTracker/StepDetails.tsx: -------------------------------------------------------------------------------- 1 | import { TickIcon } from "@/components/common/campaign-icons"; 2 | import { cn } from "@/lib"; 3 | import type { FormStore } from "@/store"; 4 | import Heading from "../../common/Heading"; 5 | 6 | type StepIndicatorProps = { 7 | step: FormStore["currentStep"]; 8 | disabled?: boolean; 9 | isCompleted?: boolean; 10 | }; 11 | 12 | type StepInfoProps = Pick{description}
55 |10 | 58 | 59 | total {title} 60 | 61 |
62 |12 | Try other campaign categories above 13 |
14 |= NextPage& { 5 | PageLayout?: React.ComponentType<{ children: ReactNode }>; 6 | }; 7 | -------------------------------------------------------------------------------- /interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export type { ApiResponse, User } from "./ApiResponses"; 2 | export type { 3 | ForgotPasswordProps, 4 | LoginProps, 5 | ResetPasswordProps, 6 | SignUpProps, 7 | ContactUsProps, 8 | UpdateProfileProps, 9 | UpdatePasswordsProps, 10 | CardDetailsProps, 11 | AddAccountDetailsProps, 12 | DonationDetailsProps, 13 | } from "./FormInputs"; 14 | export type { AuthLayoutProps, BaseLayoutProps } from "./Layouts"; 15 | export type { WithPageLayout } from "./WithPageLayout"; 16 | export type { FillSvgProps } from "./SvgProps"; 17 | export type { AllCampaignCategories } from "./Campaign"; 18 | -------------------------------------------------------------------------------- /layouts/AuthPagesLayout.tsx: -------------------------------------------------------------------------------- 1 | import { LogoBanner, PageMetaData } from "@/components/common"; 2 | import type { AuthLayoutProps } from "@/interfaces"; 3 | 4 | export const AuthPagesLayout = ({ 5 | children, 6 | title, 7 | content, 8 | greeting, 9 | heading, 10 | withHeader, 11 | contentClass, 12 | hasSuccess, 13 | }: AuthLayoutProps) => { 14 | return ( 15 |16 | 40 | ); 41 | }; 42 | -------------------------------------------------------------------------------- /layouts/BaseLayout.tsx: -------------------------------------------------------------------------------- 1 | import { Footer, Header } from "@/components/common"; 2 | import type { BaseLayoutProps } from "@/interfaces"; 3 | 4 | export const BaseLayout = ({ children }: BaseLayoutProps) => { 5 | return ( 6 | <> 7 |17 | 18 | {!hasSuccess ? ( 19 |38 | 39 |25 |34 | ) : ( 35 |26 | {withHeader && ( 27 | 28 |31 | )} 32 | {children} 33 |{heading}
29 |{greeting}
30 |{children}36 | )} 37 |8 | {/* */} 15 | {children} 16 | 17 | > 18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /layouts/index.ts: -------------------------------------------------------------------------------- 1 | export { AuthPagesLayout } from "./AuthPagesLayout"; 2 | export { BaseLayout } from "./BaseLayout"; 3 | export { AuthenticatedUserLayout } from "./AuthenticatedUserLayout"; 4 | -------------------------------------------------------------------------------- /lib/constants.ts: -------------------------------------------------------------------------------- 1 | export const isServer = 2 | typeof window === "undefined" || typeof document === "undefined"; 3 | 4 | export const isBrowser = !isServer; 5 | -------------------------------------------------------------------------------- /lib/helpers/callAbegApi.ts: -------------------------------------------------------------------------------- 1 | import { assertENV } from "@/lib/type-helpers/assert"; 2 | import { useInitSession } from "@/store/useSession"; 3 | import Router from "next/router"; 4 | import { toast } from "sonner"; 5 | import { type ErrorResponseParam, createFetcher } from "./create-fetcher"; 6 | 7 | const BACKEND_API = assertENV(process.env.NEXT_PUBLIC_BACKEND_URL, { 8 | message: "Please add the NEXT_PUBLIC_BACKEND_API variable to your .env file", 9 | }); 10 | 11 | const callAbegApi = createFetcher({ 12 | baseURL: BACKEND_API, 13 | timeout: 60000, // Set timeout to 60 seconds 14 | credentials: "include", 15 | 16 | headers: { 17 | "x-referer": process.env.NEXT_PUBLIC_BASE_URL ?? "https://www.abeghelp.me", 18 | }, 19 | 20 | onResponseError: (response: ErrorResponseParam<{ type?: string; email?: string }>) => { 21 | if (response.status === 401) { 22 | useInitSession.getState().actions.clearSession(); 23 | } 24 | 25 | if (response.status === 429) { 26 | toast.error("Too many requests!", { 27 | description: response.errorData.message, 28 | }); 29 | } 30 | 31 | if (response.status === 403 && response.errorData.message === "2FA verification is required") { 32 | toast.error(response.errorData.message, { 33 | description: "Please authenticate to continue", 34 | }); 35 | 36 | void Router.push({ 37 | pathname: "/2fa/authenticate", 38 | query: { 39 | type: response.errorData.error?.type, 40 | email: response.errorData.error?.email, 41 | }, 42 | }); 43 | } 44 | 45 | if (response.status === 500) { 46 | toast.error("Internal server Error!", { 47 | description: response.errorData.message, 48 | }); 49 | 50 | void Router.push("/500"); 51 | } 52 | }, 53 | }); 54 | 55 | export { callAbegApi }; 56 | -------------------------------------------------------------------------------- /lib/helpers/campaign/constants.ts: -------------------------------------------------------------------------------- 1 | import type { Writeable } from "@/lib/type-helpers"; 2 | import { add } from "date-fns"; 3 | 4 | const $targetCountries = ["Nigeria", "Ghana", "Mali", "Liberia", "Cameroon", "Gambia"] as const; 5 | 6 | export const targetCountries = $targetCountries as Writeable ; 7 | 8 | export const DATE_TODAY = new Date(); 9 | 10 | export const DATE_NEXT_TOMORROW = add(DATE_TODAY, { days: 2 }); 11 | -------------------------------------------------------------------------------- /lib/helpers/campaign/generateExcerpt.ts: -------------------------------------------------------------------------------- 1 | const generateExcerpt = (story: string | undefined) => { 2 | if (!story) return ""; 3 | 4 | // Take the first 200 characters of the campaign story 5 | const first200Chars = story.slice(0, 200); 6 | 7 | // Find the last punctuation mark within the first 200 characters 8 | const lastPunctuationIndex = Math.max( 9 | first200Chars.lastIndexOf("."), 10 | first200Chars.lastIndexOf("!"), 11 | first200Chars.lastIndexOf("?") 12 | ); 13 | 14 | // If a punctuation mark is found, use the substring from the start of the story to the last punctuation mark(inclusive) as the excerpt 15 | // If no punctuation mark is found, use the first 200 characters as the excerpt 16 | const excerpt = 17 | lastPunctuationIndex !== -1 18 | ? first200Chars.slice(0, lastPunctuationIndex + 1) 19 | : first200Chars; 20 | 21 | return excerpt; 22 | }; 23 | 24 | export { generateExcerpt }; 25 | -------------------------------------------------------------------------------- /lib/helpers/campaign/getDateFromString.ts: -------------------------------------------------------------------------------- 1 | import { DATE_TODAY } from "@/lib/helpers/campaign/constants"; 2 | 3 | /* eslint-disable no-console */ 4 | const getDateFromString = (dateString: string, fallback = DATE_TODAY) => { 5 | if (dateString === "") { 6 | console.info( 7 | `%cGetDateFromString: Value is an empty string, returning fallback date`, 8 | "color: yellow; font-weight: 500; font-size: 12px;" 9 | ); 10 | return fallback; 11 | } 12 | 13 | const date = new Date(dateString); 14 | 15 | if (Number.isNaN(date.getTime())) { 16 | console.info( 17 | `%cGetDateFromString: Invalid date string '${dateString}', returning fallback date`, 18 | "color: yellow; font-weight: 500; font-size: 12px;" 19 | ); 20 | console.trace("Invalid date string"); 21 | return fallback; 22 | } 23 | 24 | return date; 25 | }; 26 | 27 | export { getDateFromString }; 28 | -------------------------------------------------------------------------------- /lib/helpers/campaign/getEditorExtensions.ts: -------------------------------------------------------------------------------- 1 | import Placeholder from "@tiptap/extension-placeholder"; 2 | import StarterKit from "@tiptap/starter-kit"; 3 | 4 | const getEditorExtensions = (placeholder = "") => [ 5 | StarterKit.configure({ 6 | bulletList: { 7 | HTMLAttributes: { 8 | class: "list-disc px-4", 9 | }, 10 | }, 11 | 12 | orderedList: { 13 | HTMLAttributes: { 14 | class: "list-decimal marker:font-semibold px-3.5", 15 | }, 16 | }, 17 | 18 | dropcursor: { 19 | class: "text-pink-600", 20 | }, 21 | }), 22 | 23 | Placeholder.configure({ placeholder }), 24 | ]; 25 | 26 | export { getEditorExtensions }; 27 | -------------------------------------------------------------------------------- /lib/helpers/campaign/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./constants"; 2 | export { getDateFromString } from "./getDateFromString"; 3 | export { getEditorExtensions } from "./getEditorExtensions"; 4 | export { validateFiles } from "./validateFiles"; 5 | export { validateTagValue } from "./validateTagValue"; 6 | -------------------------------------------------------------------------------- /lib/helpers/campaign/validateTagValue.ts: -------------------------------------------------------------------------------- 1 | import { toast } from "sonner"; 2 | 3 | const validateTagValue = (tagsArray: string[], newTag: string | undefined) => { 4 | if (!newTag) return; 5 | 6 | if (newTag.length < 3) { 7 | toast.error("Error", { 8 | description: "Tag must be at least 3 characters long", 9 | }); 10 | 11 | return; 12 | } 13 | 14 | if (tagsArray.length >= 5) { 15 | toast.error("Error", { 16 | description: "Cannot add more than 5 tags", 17 | }); 18 | 19 | return; 20 | } 21 | 22 | return newTag; 23 | }; 24 | 25 | export { validateTagValue }; 26 | -------------------------------------------------------------------------------- /lib/helpers/checkIsDeviceMobileOrTablet.ts: -------------------------------------------------------------------------------- 1 | type DeviceCheckReturnType = { isMobileOrTablet: boolean }; 2 | 3 | const checkIsDeviceMobileOrTablet = (): DeviceCheckReturnType => { 4 | const deviceHasMouse = window.matchMedia("(pointer:fine)").matches; 5 | const deviceHasNoMouse = window.matchMedia("(pointer:coarse)").matches; 6 | 7 | switch (true) { 8 | case deviceHasMouse: { 9 | return { isMobileOrTablet: false }; 10 | } 11 | 12 | case deviceHasNoMouse: { 13 | return { isMobileOrTablet: true }; 14 | } 15 | 16 | case "ontouchstart" in window && "maxTouchPoints" in navigator: { 17 | return { isMobileOrTablet: navigator.maxTouchPoints > 0 }; 18 | } 19 | 20 | case "userAgentData" in navigator && 21 | (navigator.userAgentData as { mobile: boolean }).mobile: { 22 | return { isMobileOrTablet: true }; 23 | } 24 | 25 | default: { 26 | const mobileDeviceRegex = 27 | /android|webos|iphone|ipad|ipod|blackberry|mobi|iemobile|opera mini/i; 28 | 29 | return { isMobileOrTablet: mobileDeviceRegex.test(navigator.userAgent) }; 30 | } 31 | } 32 | }; 33 | 34 | export { checkIsDeviceMobileOrTablet }; 35 | -------------------------------------------------------------------------------- /lib/helpers/cn.ts: -------------------------------------------------------------------------------- 1 | import clsx, { type ClassValue } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | const cn = (...classNames: ClassValue[]): string => twMerge(clsx(classNames)); 5 | 6 | export { cn }; 7 | -------------------------------------------------------------------------------- /lib/helpers/create-fetcher/index.ts: -------------------------------------------------------------------------------- 1 | export { createFetcher } from "./create-fetcher"; 2 | export * from "./types"; 3 | -------------------------------------------------------------------------------- /lib/helpers/create-fetcher/utils.ts: -------------------------------------------------------------------------------- 1 | export const getResponseData = (response: Response) => { 2 | return response.json() as Promise ; 3 | }; 4 | 5 | export const getDaysLeftForDonationCarousel = (days: number) => { 6 | return days < 0 ? 0 : days ?? 0; 7 | }; 8 | -------------------------------------------------------------------------------- /lib/helpers/getDaysLeft.ts: -------------------------------------------------------------------------------- 1 | export const getDaysLeft = (deadlineTime: any) => { 2 | const deadline: any = new Date(deadlineTime); 3 | const currentTime: any = new Date(); 4 | const deadlineTimeFinal = deadline.getTime() - currentTime.getTime(); 5 | const final = Math.floor(deadlineTimeFinal / (1000 * 60 * 60 * 24)); 6 | return final; 7 | }; 8 | -------------------------------------------------------------------------------- /lib/helpers/getTimeDiff.ts: -------------------------------------------------------------------------------- 1 | export const getTimeDifference = (time: string): string => { 2 | const createdDate = new Date(time); 3 | const now = new Date(); 4 | const diffInSeconds = Math.floor((now.getTime() - createdDate.getTime()) / 1000); 5 | 6 | const intervals = [ 7 | { label: "year", seconds: 31536000 }, 8 | { label: "month", seconds: 2592000 }, 9 | { label: "day", seconds: 86400 }, 10 | { label: "hour", seconds: 3600 }, 11 | { label: "min", seconds: 60 }, 12 | { label: "sec", seconds: 1 }, 13 | ]; 14 | 15 | for (const interval of intervals) { 16 | const count = Math.floor(diffInSeconds / interval.seconds); 17 | if (count > 0) { 18 | return `${count} ${interval.label}${count !== 1 ? "s" : ""} ago`; 19 | } 20 | } 21 | 22 | return "just now"; 23 | }; 24 | -------------------------------------------------------------------------------- /lib/helpers/omitKeys.ts: -------------------------------------------------------------------------------- 1 | import type { Prettify, Writeable } from "../type-helpers"; 2 | 3 | type PrettifyOmitResult< 4 | TObject extends Record , 5 | TOmitArray extends Array , 6 | > = Prettify >>; 7 | 8 | export const omitKeys = < 9 | const TObject extends Record , 10 | const TOmitArray extends Array , 11 | >( 12 | initialObject: TObject, 13 | keysToOmit: TOmitArray 14 | ) => { 15 | const arrayFromFilteredObject = Object.entries(initialObject).filter( 16 | ([key]) => !keysToOmit.includes(key) 17 | ); 18 | 19 | const updatedObject = Object.fromEntries(arrayFromFilteredObject); 20 | 21 | return updatedObject as PrettifyOmitResult ; 22 | }; 23 | -------------------------------------------------------------------------------- /lib/helpers/parseJSON.ts: -------------------------------------------------------------------------------- 1 | const parseJSON = (value: string | undefined | null) => { 2 | if (typeof value !== "string" || value === "") { 3 | return null; 4 | } 5 | 6 | return JSON.parse(value) as TResult; 7 | }; 8 | 9 | export { parseJSON }; 10 | -------------------------------------------------------------------------------- /lib/hooks/createCustomContext.ts: -------------------------------------------------------------------------------- 1 | import { createContext, useContext } from "react"; 2 | 3 | export class ContextError extends Error { 4 | override name = "ContextError"; 5 | } 6 | 7 | export const getErrorMessage = (hook: string, provider: string) => { 8 | return `${hook} returned "null". Did you forget to wrap the necessary components within ${provider}?`; 9 | }; 10 | 11 | export type CustomContextOptions = { 12 | name?: string; 13 | hookName?: string; 14 | providerName?: string; 15 | errorMessage?: string; 16 | strict?: TStrict; 17 | defaultValue?: TDefaultContextValue | null; 18 | }; 19 | 20 | type UseCustomContextResult = TStrict extends true 21 | ? TContextValue 22 | : TContextValue | null; 23 | 24 | const createCustomContext = ( 25 | options: CustomContextOptions = {} 26 | ) => { 27 | const { 28 | name = "Unnamed Context", 29 | hookName = "Unnamed Context hook", 30 | providerName = "Unnamed Provider", 31 | strict = true, 32 | errorMessage, 33 | defaultValue = null, 34 | } = options; 35 | 36 | const Context = createContext (defaultValue); 37 | 38 | Context.displayName = name; 39 | 40 | const useCustomContext = (): UseCustomContextResult => { 41 | const contextValue = useContext(Context); 42 | 43 | if (strict && contextValue === null) { 44 | throw new ContextError(errorMessage ?? getErrorMessage(hookName, providerName)); 45 | } 46 | 47 | return contextValue as UseCustomContextResult ; 48 | }; 49 | 50 | return [Context.Provider, useCustomContext] as const; 51 | }; 52 | 53 | export { createCustomContext }; 54 | -------------------------------------------------------------------------------- /lib/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export { createCustomContext } from "./createCustomContext"; 2 | export { useCloudflareTurnstile } from "./useCloudflareTurnstile"; 3 | export { useCopyToClipboard } from "./useCopyToClipboard"; 4 | export { useDragScroll } from "./useDragScroll"; 5 | export { useElementList } from "./useElementList/useElementList"; 6 | export { usePaginate } from "./usePaginate"; 7 | export { useShareCampaign } from "./useShareCampaign"; 8 | export { useToggle } from "./useToggle"; 9 | -------------------------------------------------------------------------------- /lib/hooks/useAnimationInterval.ts: -------------------------------------------------------------------------------- 1 | import { useCallbackRef } from "@/lib/hooks/useCallbackRef"; 2 | import { assertDefined } from "@/lib/type-helpers/assert"; 3 | import { useEffect, useRef } from "react"; 4 | 5 | type AnimationOptions = { 6 | callbackFn: () => void; 7 | intervalDuration: number | null; 8 | }; 9 | 10 | const useAnimationInterval = (options: AnimationOptions) => { 11 | const { callbackFn, intervalDuration } = options; 12 | 13 | const startTimeStampRef = useRef (null); 14 | const animationFrameId = useRef (null); 15 | 16 | // prettier-ignore 17 | const smoothAnimation = useCallbackRef((timeStamp: DOMHighResTimeStamp) => { 18 | if (startTimeStampRef.current === null) { 19 | startTimeStampRef.current = Math.floor(timeStamp); 20 | } 21 | 22 | const elapsedTime = Math.floor(timeStamp - startTimeStampRef.current); 23 | 24 | if (elapsedTime >= assertDefined(intervalDuration)) { 25 | callbackFn(); 26 | startTimeStampRef.current = null; // == Reset the starting time stamp 27 | } 28 | 29 | animationFrameId.current = requestAnimationFrame(smoothAnimation); 30 | }); 31 | 32 | const onAnimationStart = useCallbackRef( 33 | () => (animationFrameId.current = requestAnimationFrame(smoothAnimation)) 34 | ); 35 | 36 | const onAnimationStop = useCallbackRef(() => { 37 | if (animationFrameId.current) { 38 | cancelAnimationFrame(animationFrameId.current); 39 | } 40 | startTimeStampRef.current = null; 41 | animationFrameId.current = null; 42 | }); 43 | 44 | useEffect( 45 | function toggleAnimationByInterval() { 46 | if (intervalDuration === null) return; 47 | 48 | onAnimationStart(); 49 | 50 | return () => onAnimationStop(); 51 | }, 52 | 53 | [intervalDuration, onAnimationStart, onAnimationStop] 54 | ); 55 | 56 | return { animationFrameId: animationFrameId.current, onAnimationStop }; 57 | }; 58 | 59 | export { useAnimationInterval }; 60 | -------------------------------------------------------------------------------- /lib/hooks/useCallbackRef.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useLayoutEffect, useRef } from "react"; 2 | import { isServer } from "../constants"; 3 | import type { CallbackFn } from "../type-helpers/global"; 4 | 5 | const useIsoMorphicLayoutEffect = isServer ? useEffect : useLayoutEffect; 6 | 7 | const useCallbackRef = (callbackFn: CallbackFn ) => { 8 | const callbackRef = useRef(callbackFn); 9 | 10 | useIsoMorphicLayoutEffect(() => { 11 | // == Doing this instead updating it directly during render phase, cuz according to Dan Abramov, render should be pure 12 | callbackRef.current = callbackFn; 13 | }, [callbackFn]); 14 | 15 | const savedCallback = useCallback((...params: TParams[]) => callbackRef.current(...params), []); 16 | 17 | return savedCallback; 18 | }; 19 | 20 | export { useCallbackRef }; 21 | -------------------------------------------------------------------------------- /lib/hooks/useCloudflareTurnstile.tsx: -------------------------------------------------------------------------------- 1 | import type { TurnstileInstance } from "@marsidev/react-turnstile"; 2 | import { useRef, useState } from "react"; 3 | import { toast } from "sonner"; 4 | 5 | const useCloudflareTurnstile = () => { 6 | const [botStatus, setBotStatus] = useState<"success" | "error" | "idle">( 7 | "idle" 8 | ); 9 | const cfTurnStile = useRef (null); 10 | 11 | const handleBotStatus = (status: "success" | "error" | "idle") => 12 | setBotStatus(status); 13 | 14 | const checkBotStatus = () => { 15 | if (botStatus !== "success") { 16 | toast.error("Error", { 17 | description: "Please complete the bot verification", 18 | }); 19 | return; 20 | } 21 | return "success"; 22 | }; 23 | 24 | return { 25 | botStatus, 26 | cfTurnStile, 27 | handleBotStatus, 28 | checkBotStatus, 29 | }; 30 | }; 31 | export { useCloudflareTurnstile }; 32 | -------------------------------------------------------------------------------- /lib/hooks/useCopyToClipboard.ts: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | const oldSchoolCopy = (text: string) => { 4 | const tempTextArea = document.createElement("textarea"); 5 | tempTextArea.value = text; 6 | document.body.append(tempTextArea); 7 | tempTextArea.select(); 8 | document.execCommand("copy"); 9 | tempTextArea.remove(); 10 | }; 11 | 12 | const useCopyToClipboard = () => { 13 | const [state, setState] = useState(""); 14 | 15 | const copyToClipboard = (value: string) => { 16 | const handleCopy = async () => { 17 | try { 18 | if (!navigator?.clipboard?.writeText) { 19 | throw new Error("writeText not supported"); 20 | } 21 | 22 | await navigator.clipboard.writeText(value); 23 | setState(value); 24 | } catch (error) { 25 | console.warn(error); 26 | 27 | oldSchoolCopy(value); 28 | setState(value); 29 | } 30 | }; 31 | 32 | void handleCopy(); 33 | }; 34 | 35 | return { copiedValue: state, copyToClipboard }; 36 | }; 37 | 38 | export { useCopyToClipboard }; 39 | -------------------------------------------------------------------------------- /lib/hooks/useElementList/For.tsx: -------------------------------------------------------------------------------- 1 | import type { ForwardedRefType, PolymorphicPropsWithRef } from "@/lib/type-helpers"; 2 | import { forwardRef } from "react"; 3 | 4 | // prettier-ignore 5 | type RenderPropFn = ( 6 | item: TArrayItem, 7 | index: number, 8 | array: TArrayItem[] 9 | ) => React.ReactNode; 10 | 11 | export type EachProp = { each: TArrayItem[] }; 12 | 13 | export type ForRenderProps = 14 | | { 15 | children: RenderPropFn ; 16 | render?: "Hey, Sorry but since your're currently using the children prop, the render prop is now redundant"; 17 | } 18 | | { 19 | children?: "Hey, Sorry but since your're currently using the render prop, so the children prop is now redundant"; 20 | render: RenderPropFn ; 21 | }; 22 | 23 | type ForProps = ForRenderProps & EachProp ; 24 | 25 | function ForBase (props: ForProps ) { 26 | const { each, render, children } = props; 27 | 28 | // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition 29 | if (each == null) { 30 | return []; 31 | } 32 | 33 | const JSXElementList = each.map((...params) => { 34 | const coercedParams = params as Parameters >; 35 | 36 | if (typeof children === "function") { 37 | return children(...coercedParams); 38 | } 39 | 40 | return render(...coercedParams); 41 | }); 42 | 43 | return JSXElementList; 44 | } 45 | 46 | function ForList ( 47 | props: PolymorphicPropsWithRef & { className?: string }>, 48 | ref: ForwardedRefType 49 | ) { 50 | const { each, render, children, as: ListContainer = "ul", className, ...restOfListProps } = props; 51 | 52 | return ( 53 | 54 | 56 | ); 57 | } 58 | 59 | const For = { 60 | Base: ForBase, 61 | List: forwardRef(ForList) as typeof ForList, 62 | }; 63 | 64 | export default For; 65 | -------------------------------------------------------------------------------- /lib/hooks/useElementList/useElementList.ts: -------------------------------------------------------------------------------- 1 | import For from "./For"; 2 | 3 | function useElementList(type: "base"): [typeof For.Base]; 4 | function useElementList(type?: "withWrapper"): [typeof For.List]; 5 | 6 | function useElementList(type = "withWrapper") { 7 | return type === "withWrapper" ? [For.List] : [For.Base]; 8 | } 9 | 10 | export { useElementList }; 11 | -------------------------------------------------------------------------------- /lib/hooks/useShareCampaign.ts: -------------------------------------------------------------------------------- 1 | import { useCallback } from "react"; 2 | import { toast } from "sonner"; 3 | import { useCallbackRef } from "./useCallbackRef"; 4 | import { useCopyToClipboard } from "./useCopyToClipboard"; 5 | 6 | const useShareCampaign = () => { 7 | const { copyToClipboard } = useCopyToClipboard(); 8 | 9 | const generateTweet = useCallback( 10 | (title: string, url: string, tags: string[]) => { 11 | const queryParams = new URLSearchParams({ 12 | text: `Abeg help donate to my campaign: 13 | ${title} 14 | ${tags.length > 0 && tags.join(", ")}`, 15 | url, 16 | via: "abeghelpme", 17 | }); 18 | 19 | return `https://twitter.com/intent/tweet?${queryParams.toString()}`; 20 | }, 21 | [] 22 | ); 23 | 24 | const generateWhatsAppMessage = useCallback((title: string, url: string) => { 25 | const queryParams = new URLSearchParams({ 26 | text: `Abeg help donate to my campaign:\n${title}\n${url}`, 27 | }); 28 | 29 | return `https://wa.me/?${queryParams.toString()}`; 30 | }, []); 31 | 32 | const handleShareLink = useCallbackRef((url: string) => () => { 33 | copyToClipboard(url); 34 | 35 | toast.success("Campaign link copied to clipboard!", { 36 | duration: 1500, 37 | }); 38 | }); 39 | 40 | return { generateTweet, generateWhatsAppMessage, handleShareLink }; 41 | }; 42 | 43 | export { useShareCampaign }; 44 | -------------------------------------------------------------------------------- /lib/hooks/useToggle.ts: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { useCallbackRef } from "./useCallbackRef"; 3 | 4 | type InitialStateType = boolean | (() => boolean); 5 | 6 | const useToggle = (initialValue: InitialStateType = false) => { 7 | const [value, setValue] = useState(initialValue); 8 | 9 | const toggle = useCallbackRef()} /> 55 | (booleanValue?: TValue) => { 10 | if (typeof booleanValue === "boolean") { 11 | setValue(booleanValue); 12 | return; 13 | } 14 | 15 | setValue((prev) => !prev); 16 | }); 17 | 18 | return [value, toggle] as const; 19 | }; 20 | 21 | export { useToggle }; 22 | -------------------------------------------------------------------------------- /lib/hooks/useWatchInput.ts: -------------------------------------------------------------------------------- 1 | import type { SignUpProps } from "@/interfaces"; 2 | import { type Control, useWatch } from "react-hook-form"; 3 | 4 | type watchInputTypes = { 5 | control: Control ; 6 | inputType: keyof SignUpProps; 7 | }; 8 | const useWatchInput = ({ control, inputType }: watchInputTypes) => { 9 | const result = useWatch({ control, name: inputType, defaultValue: "" }); 10 | return result; 11 | }; 12 | 13 | export default useWatchInput; 14 | -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | checkPasswordStrength, 3 | zodValidator, 4 | type ForgotPasswordType, 5 | type LoginType, 6 | type ResetPasswordType, 7 | type SignUpType, 8 | type ContactUsType, 9 | type UpdateProfileType, 10 | type UpdatePasswordsType, 11 | type CardDetailsType, 12 | type AddAccountDetailsType, 13 | type DonationDetailsType, 14 | } from "./validators/validateWithZod"; 15 | 16 | export { callApi } from "./helpers/callApi"; 17 | export { cn } from "./helpers/cn"; 18 | export { parseJSON } from "./helpers/parseJSON"; 19 | export { omitKeys } from "./helpers/omitKeys"; 20 | export { getDaysLeft } from "./helpers/getDaysLeft"; 21 | -------------------------------------------------------------------------------- /lib/type-helpers/assert.ts: -------------------------------------------------------------------------------- 1 | export const assertENV = ( 2 | variable: string | undefined, 3 | options?: { message: string } 4 | ) => { 5 | const { message = "Required Environment variable is missing or undefined" } = 6 | options ?? {}; 7 | 8 | if (!variable) { 9 | throw new Error(message); 10 | } 11 | 12 | return variable; 13 | }; 14 | 15 | export const assertDefined = (value: T) => { 16 | if (value == null) { 17 | throw new Error(`The value passed is not defined!`); 18 | } 19 | 20 | return value; 21 | }; 22 | -------------------------------------------------------------------------------- /lib/type-helpers/global.ts: -------------------------------------------------------------------------------- 1 | export type ForwardedRefType = 2 | TComponent extends React.ElementType 3 | ? React.ForwardedRef > 4 | : React.ForwardedRef ; 5 | 6 | export type InferProps = 7 | TComponent extends React.ElementType 8 | ? React.ComponentPropsWithoutRef 9 | : React.HTMLAttributes ; 10 | 11 | export type CallbackFn = (...params: TParams[]) => TResult; 12 | 13 | export type Writeable = { -readonly [P in keyof T]: T[P] }; 14 | 15 | // == The intersection with "{}" or "unknown" or "NonNullable " 16 | // == is necessary to make it work as expected due to quirks in the TS compiler 17 | export type Prettify = { 18 | [key in keyof TObject]: TObject[key]; 19 | } & NonNullable ; 20 | 21 | export type PrettyOmit = Prettify >; 22 | -------------------------------------------------------------------------------- /lib/type-helpers/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./global"; 2 | export * from "./polymorphism-helper"; 3 | export * from "./typeof"; 4 | -------------------------------------------------------------------------------- /lib/type-helpers/polymorphism-helper.ts: -------------------------------------------------------------------------------- 1 | export type AsProp = { 2 | as?: TElement; 3 | }; 4 | 5 | type PropsWithOptionalAs< 6 | TElement extends React.ElementType, 7 | TProps, 8 | > = "as" extends keyof TProps ? TProps : TProps & AsProp ; 9 | 10 | // == Get all other primitive element props by Omit the result of MergedProps from React.ComponentPropsWithoutRef 11 | type InferOtherProps = Omit< 12 | React.ComponentPropsWithoutRef , 13 | // == Removing children and className as well to give components control over these props 14 | keyof PropsWithOptionalAs | "className" | "children" 15 | >; 16 | 17 | // == Polymorphic props helper 18 | export type PolymorphicProps< 19 | TElement extends React.ElementType, 20 | TProps extends Record = AsProp , 21 | > = PropsWithOptionalAs & InferOtherProps ; 22 | 23 | type RefProp = { 24 | ref?: React.ComponentPropsWithRef ["ref"]; 25 | }; 26 | 27 | // == For components with the Ref Prop 28 | export type PolymorphicPropsWithRef< 29 | TElement extends React.ElementType, 30 | TProps extends Record = AsProp , 31 | > = PolymorphicProps & RefProp ; 32 | -------------------------------------------------------------------------------- /lib/type-helpers/typeof.ts: -------------------------------------------------------------------------------- 1 | export const isArray = (value: unknown): value is unknown[] => Array.isArray(value); 2 | 3 | export const isFormData = (value: unknown): value is FormData => value instanceof FormData; 4 | 5 | export const isObject = (value: unknown): value is Record => { 6 | const isRegularObject = typeof value === "object" && value !== null; 7 | 8 | return !isFormData(value) && !isArray(value) && isRegularObject; 9 | }; 10 | 11 | /* eslint-disable @typescript-eslint/no-explicit-any */ 12 | // == `Any` is required here so that one can pass custom function type without type errors 13 | export const isFunction = any>( 14 | value: unknown 15 | ): value is TFunction => { 16 | return typeof value === "function"; 17 | }; 18 | -------------------------------------------------------------------------------- /lint-staged.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "*.{js,jsx,ts,tsx,json,yaml}": [ 3 | "biome check --apply --no-errors-on-unmatched", 4 | ], 5 | "**/*.ts?(x)": () => "npm run check-types", 6 | }; 7 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | 3 | const nextConfig = { 4 | reactStrictMode: true, 5 | experimental: { 6 | scrollRestoration: true, 7 | }, 8 | images: { 9 | remotePatterns: [ 10 | { 11 | protocol: "http", 12 | hostname: "static.abeghelp.me", 13 | port: "", 14 | pathname: "/**", 15 | }, 16 | { 17 | protocol: "https", 18 | hostname: "picsum.photos", 19 | port: "", 20 | pathname: "/**", 21 | }, 22 | { 23 | protocol: "https", 24 | hostname: "loremflickr.com", 25 | port: "", 26 | pathname: "/**", 27 | }, 28 | ], 29 | }, 30 | }; 31 | 32 | module.exports = nextConfig; 33 | -------------------------------------------------------------------------------- /pages/2fa/app.tsx: -------------------------------------------------------------------------------- 1 | import { FirstStep, RecoveryCode } from "@/components/2fa"; 2 | import { useRef, useState } from "react"; 3 | 4 | const Authenticator = () => { 5 | const [step, setStep] = useState(1); 6 | const recoveryCode = useRef (null); 7 | return ( 8 | 9 | {step === 1 ? ( 10 |15 | ); 16 | }; 17 | export default Authenticator; 18 | Authenticator.protect = true; 19 | -------------------------------------------------------------------------------- /pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import { Head, Html, Main, NextScript } from "next/document"; 2 | 3 | export default function Document() { 4 | return ( 5 | 6 | 7 | 11 | 12 | 13 | 14 |11 | ) : ( 12 | 13 | )} 14 | 15 | 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /pages/api/hello.ts: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | import type { NextApiRequest, NextApiResponse } from "next"; 3 | 4 | type Data = { 5 | name: string; 6 | }; 7 | 8 | export default function handler( 9 | req: NextApiRequest, 10 | res: NextApiResponse 11 | ) { 12 | res.status(200).json({ name: "John Doe" }); 13 | } 14 | -------------------------------------------------------------------------------- /pages/reset-password/success.tsx: -------------------------------------------------------------------------------- 1 | import { Success } from "@/components/common"; 2 | import { AuthPagesLayout } from "@/layouts"; 3 | import Link from "next/link"; 4 | 5 | const ResetPasswordSuccessPage = () => { 6 | return ( 7 | 13 | 22 | ); 23 | }; 24 | 25 | export default ResetPasswordSuccessPage; 26 | ResetPasswordSuccessPage.protect = true; 27 | -------------------------------------------------------------------------------- /pages/verify-email/index.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui"; 2 | import { AuthPagesLayout } from "@/layouts"; 3 | import { callApi } from "@/lib"; 4 | import { useRouter } from "next/router"; 5 | import { useEffect, useState } from "react"; 6 | import { toast } from "sonner"; 7 | 8 | const VerificationPage = () => { 9 | const router = useRouter(); 10 | const [token, setToken] = useState(""); 11 | const [loading, setLoading] = useState(false); 12 | 13 | useEffect(() => { 14 | setToken(router.query.token as string); 15 | }, [router]); 16 | 17 | const verifyEmail = async () => { 18 | setLoading(true); 19 | if (!token) { 20 | setLoading(false); 21 | return toast.error("Request Failed", { 22 | description: "No data provided", 23 | }); 24 | } 25 | const { data, error } = await callApi("/auth/verify-email", { 26 | token, 27 | }); 28 | 29 | if (error) { 30 | setLoading(false); 31 | toast.error(error.status, { 32 | description: error.message, 33 | }); 34 | } else { 35 | setLoading(false); 36 | toast.success("Success", { 37 | description: (data as { message: string }).message, 38 | }); 39 | setTimeout(() => { 40 | void router.push("/verify-email/success"); 41 | }, 500); 42 | } 43 | }; 44 | 45 | return ( 46 |14 | 18 | Sign in to continue 19 | 20 | 21 |53 | 71 | ); 72 | }; 73 | export default VerificationPage; 74 | 75 | VerificationPage.protect = true; 76 | -------------------------------------------------------------------------------- /pages/verify-email/success.tsx: -------------------------------------------------------------------------------- 1 | import { Success } from "@/components/common"; 2 | import { AuthPagesLayout } from "@/layouts"; 3 | import Link from "next/link"; 4 | 5 | const VerifyEmailSuccessPage = () => { 6 | return ( 7 |54 |70 |Verify your email
55 |56 |69 |57 | Please click on the button below to verify your email. 58 |
59 | 68 |13 | 22 | ); 23 | }; 24 | 25 | export default VerifyEmailSuccessPage; 26 | 27 | VerifyEmailSuccessPage.protect = true; 28 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /public/assets/icons/auth/arrow-down.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /public/assets/icons/auth/auth-padlock.svg: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /public/assets/icons/auth/eye.svg: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /public/assets/icons/auth/notification-bing.svg: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /public/assets/icons/auth/slashEye.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /public/assets/icons/shared/copy.svg: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /public/assets/icons/shared/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abeghelpme/frontend/8e66b2f07a633d244a8a5aa35d11fe07f608bfca/public/assets/icons/shared/google.png -------------------------------------------------------------------------------- /public/assets/icons/shared/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abeghelpme/frontend/8e66b2f07a633d244a8a5aa35d11fe07f608bfca/public/assets/icons/shared/twitter.png -------------------------------------------------------------------------------- /public/assets/images/about-page/MagicPattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abeghelpme/frontend/8e66b2f07a633d244a8a5aa35d11fe07f608bfca/public/assets/images/about-page/MagicPattern.png -------------------------------------------------------------------------------- /public/assets/images/about-page/aboutHero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abeghelpme/frontend/8e66b2f07a633d244a8a5aa35d11fe07f608bfca/public/assets/images/about-page/aboutHero.png -------------------------------------------------------------------------------- /public/assets/images/about-page/image-pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abeghelpme/frontend/8e66b2f07a633d244a8a5aa35d11fe07f608bfca/public/assets/images/about-page/image-pattern.png -------------------------------------------------------------------------------- /public/assets/images/about-page/index.ts: -------------------------------------------------------------------------------- 1 | export { default as aboutHero } from "@/public/assets/images/about-page/aboutHero.png"; 2 | export { default as MagicPattern } from "@/public/assets/images/about-page/MagicPattern.png"; 3 | export { default as whoWeAre } from "@/public/assets/images/about-page/who-we-are-image.png"; 4 | export { default as ourAchievements1 } from "@/public/assets/images/about-page/our-achievements1.svg"; 5 | export { default as ourAchievements2 } from "@/public/assets/images/about-page/our-achievements2.svg"; 6 | export { default as ourAchievements3 } from "@/public/assets/images/about-page/our-achievements3.svg"; 7 | export { default as ourAchievements4 } from "@/public/assets/images/about-page/our-achievements4.svg"; 8 | export { default as ImagePattern } from "@/public/assets/images/about-page/image-pattern.png"; 9 | export { default as JaneDoe } from "@/public/assets/images/about-page/jane.png"; 10 | export { default as JohnDoe1 } from "@/public/assets/images/about-page/john-one.png"; 11 | export { default as JohnDoe2 } from "@/public/assets/images/about-page/john-two.png"; 12 | -------------------------------------------------------------------------------- /public/assets/images/about-page/jane.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abeghelpme/frontend/8e66b2f07a633d244a8a5aa35d11fe07f608bfca/public/assets/images/about-page/jane.png -------------------------------------------------------------------------------- /public/assets/images/about-page/john-one.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abeghelpme/frontend/8e66b2f07a633d244a8a5aa35d11fe07f608bfca/public/assets/images/about-page/john-one.png -------------------------------------------------------------------------------- /public/assets/images/about-page/john-two.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abeghelpme/frontend/8e66b2f07a633d244a8a5aa35d11fe07f608bfca/public/assets/images/about-page/john-two.png -------------------------------------------------------------------------------- /public/assets/images/about-page/our-achievements1.svg: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /public/assets/images/about-page/our-achievements2.svg: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /public/assets/images/about-page/our-achievements3.svg: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /public/assets/images/about-page/our-achievements4.svg: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /public/assets/images/about-page/who-we-are-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abeghelpme/frontend/8e66b2f07a633d244a8a5aa35d11fe07f608bfca/public/assets/images/about-page/who-we-are-image.png -------------------------------------------------------------------------------- /public/assets/images/auth/auth-bg-contourss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abeghelpme/frontend/8e66b2f07a633d244a8a5aa35d11fe07f608bfca/public/assets/images/auth/auth-bg-contourss.png -------------------------------------------------------------------------------- /public/assets/images/campaign-category/arrow-down-small.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /public/assets/images/campaign-category/arrow-left.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /public/assets/images/campaign-category/arrow-right.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /public/assets/images/campaign-category/hero-circle.svg: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /public/assets/images/campaign-category/hero-half-moon.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /public/assets/images/campaign-category/index.ts: -------------------------------------------------------------------------------- 1 | export { default as arrowDown } from "@/public/assets/images/campaign-category/arrow-down-small.svg"; 2 | export { default as arrowLeft } from "@/public/assets/images/campaign-category/arrow-left.svg"; 3 | export { default as arrowRight } from "@/public/assets/images/campaign-category/arrow-right.svg"; 4 | export { default as heroCircle } from "@/public/assets/images/campaign-category/hero-circle.svg"; 5 | export { default as heroHalfMoon } from "@/public/assets/images/campaign-category/hero-half-moon.svg"; 6 | export { default as searchIcon } from "@/public/assets/images/campaign-category/search-icon.svg"; 7 | -------------------------------------------------------------------------------- /public/assets/images/campaign-category/search-icon.svg: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /public/assets/images/dashboard/dashboardBg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abeghelpme/frontend/8e66b2f07a633d244a8a5aa35d11fe07f608bfca/public/assets/images/dashboard/dashboardBg.png -------------------------------------------------------------------------------- /public/assets/images/dashboard/dashboardImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abeghelpme/frontend/8e66b2f07a633d244a8a5aa35d11fe07f608bfca/public/assets/images/dashboard/dashboardImage.png -------------------------------------------------------------------------------- /public/assets/images/dashboard/userIcon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/images/error-pages/500-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abeghelpme/frontend/8e66b2f07a633d244a8a5aa35d11fe07f608bfca/public/assets/images/error-pages/500-image.png -------------------------------------------------------------------------------- /public/assets/images/how-it-works/how-it-work-hero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abeghelpme/frontend/8e66b2f07a633d244a8a5aa35d11fe07f608bfca/public/assets/images/how-it-works/how-it-work-hero.png -------------------------------------------------------------------------------- /public/assets/images/how-it-works/how-it-works-splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abeghelpme/frontend/8e66b2f07a633d244a8a5aa35d11fe07f608bfca/public/assets/images/how-it-works/how-it-works-splash.png -------------------------------------------------------------------------------- /public/assets/images/how-it-works/index.ts: -------------------------------------------------------------------------------- 1 | export { default as howItWorkHero } from "@/public/assets/images/how-it-works/how-it-work-hero.png"; 2 | export { default as howItWorksSplash } from "@/public/assets/images/how-it-works/how-it-works-splash.png"; 3 | -------------------------------------------------------------------------------- /public/assets/images/landing-page/Facebook.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /public/assets/images/landing-page/Instagram.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /public/assets/images/landing-page/LinkedIn.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /public/assets/images/landing-page/YouTube.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /public/assets/images/landing-page/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abeghelpme/frontend/8e66b2f07a633d244a8a5aa35d11fe07f608bfca/public/assets/images/landing-page/background.png -------------------------------------------------------------------------------- /public/assets/images/landing-page/charity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abeghelpme/frontend/8e66b2f07a633d244a8a5aa35d11fe07f608bfca/public/assets/images/landing-page/charity.png -------------------------------------------------------------------------------- /public/assets/images/landing-page/close-circle.svg: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /public/assets/images/landing-page/contours.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abeghelpme/frontend/8e66b2f07a633d244a8a5aa35d11fe07f608bfca/public/assets/images/landing-page/contours.png -------------------------------------------------------------------------------- /public/assets/images/landing-page/create-campaign-image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abeghelpme/frontend/8e66b2f07a633d244a8a5aa35d11fe07f608bfca/public/assets/images/landing-page/create-campaign-image1.png -------------------------------------------------------------------------------- /public/assets/images/landing-page/create-campaign-image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abeghelpme/frontend/8e66b2f07a633d244a8a5aa35d11fe07f608bfca/public/assets/images/landing-page/create-campaign-image2.png -------------------------------------------------------------------------------- /public/assets/images/landing-page/create-campaign-image3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abeghelpme/frontend/8e66b2f07a633d244a8a5aa35d11fe07f608bfca/public/assets/images/landing-page/create-campaign-image3.png -------------------------------------------------------------------------------- /public/assets/images/landing-page/crowd-fund.svg: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /public/assets/images/landing-page/global-community.svg: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /public/assets/images/landing-page/happy-people.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abeghelpme/frontend/8e66b2f07a633d244a8a5aa35d11fe07f608bfca/public/assets/images/landing-page/happy-people.png -------------------------------------------------------------------------------- /public/assets/images/landing-page/join-us.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abeghelpme/frontend/8e66b2f07a633d244a8a5aa35d11fe07f608bfca/public/assets/images/landing-page/join-us.png -------------------------------------------------------------------------------- /public/assets/images/landing-page/menu.svg: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /public/assets/images/landing-page/netflix.svg: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /public/assets/images/landing-page/stories-about-us.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abeghelpme/frontend/8e66b2f07a633d244a8a5aa35d11fe07f608bfca/public/assets/images/landing-page/stories-about-us.png -------------------------------------------------------------------------------- /public/assets/images/landing-page/support.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abeghelpme/frontend/8e66b2f07a633d244a8a5aa35d11fe07f608bfca/public/assets/images/landing-page/support.png -------------------------------------------------------------------------------- /public/assets/images/landing-page/testimonial-image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abeghelpme/frontend/8e66b2f07a633d244a8a5aa35d11fe07f608bfca/public/assets/images/landing-page/testimonial-image1.png -------------------------------------------------------------------------------- /public/assets/images/landing-page/testimonial-image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abeghelpme/frontend/8e66b2f07a633d244a8a5aa35d11fe07f608bfca/public/assets/images/landing-page/testimonial-image2.png -------------------------------------------------------------------------------- /public/assets/images/landing-page/testimonial-image3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abeghelpme/frontend/8e66b2f07a633d244a8a5aa35d11fe07f608bfca/public/assets/images/landing-page/testimonial-image3.png -------------------------------------------------------------------------------- /public/assets/images/shared/bg-contours.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abeghelpme/frontend/8e66b2f07a633d244a8a5aa35d11fe07f608bfca/public/assets/images/shared/bg-contours.png -------------------------------------------------------------------------------- /public/assets/images/shared/contours-old.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abeghelpme/frontend/8e66b2f07a633d244a8a5aa35d11fe07f608bfca/public/assets/images/shared/contours-old.png -------------------------------------------------------------------------------- /public/assets/images/shared/logo.svg: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abeghelpme/frontend/8e66b2f07a633d244a8a5aa35d11fe07f608bfca/public/favicon.ico -------------------------------------------------------------------------------- /public/hero.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abeghelpme/frontend/8e66b2f07a633d244a8a5aa35d11fe07f608bfca/public/hero.jpg -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /store/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./store-types"; 2 | export * from "./useCampaignStore"; 3 | export { useSession } from "./useSession"; 4 | export * from "./useFormStore"; 5 | -------------------------------------------------------------------------------- /store/store-types.ts: -------------------------------------------------------------------------------- 1 | export type SelectorFn14 | 18 | Back to sign in 19 | 20 | 21 |= (state: TStore) => TResult; 2 | -------------------------------------------------------------------------------- /store/useCampaignStore.ts: -------------------------------------------------------------------------------- 1 | import type { ApiResponse } from "@/interfaces"; 2 | import type { Campaign } from "@/interfaces/Campaign"; 3 | import { callApi } from "@/lib"; 4 | import { create } from "zustand"; 5 | import { useShallow } from "zustand/react/shallow"; 6 | import type { SelectorFn } from "./store-types"; 7 | 8 | type CampaignStore = { 9 | loading: boolean; 10 | campaigns: Campaign[]; 11 | allUserCampaigns: Campaign[]; 12 | categories: Array<{ _id: string; name: string }>; 13 | 14 | actions: { 15 | addCampaign: (newCampaign: Campaign) => void; 16 | initializeCampaigns: (campaigns: Campaign[]) => void; 17 | initializeAllUserCampaigns: (userId: string | undefined) => Promise ; 18 | initializeCategories: () => Promise ; 19 | }; 20 | }; 21 | 22 | const initialState = { 23 | loading: true, 24 | campaigns: [], 25 | allUserCampaigns: [], 26 | categories: [], 27 | } satisfies Omit ; 28 | 29 | export const useInitCampaignStore = create ()((set, get) => ({ 30 | ...initialState, 31 | 32 | actions: { 33 | addCampaign: (newCampaign) => { 34 | const { campaigns: oldCampaigns } = get(); 35 | 36 | set({ 37 | campaigns: Array.isArray(oldCampaigns) ? [...oldCampaigns, newCampaign] : [newCampaign], 38 | }); 39 | }, 40 | 41 | initializeCampaigns: (campaigns) => set({ campaigns }), 42 | 43 | initializeAllUserCampaigns: async (userId) => { 44 | if (!userId) return; 45 | 46 | const { data, error } = await callApi >(`/campaign/user/${userId}`); 47 | 48 | if (error || !data?.data) return; 49 | 50 | set({ allUserCampaigns: data.data }); 51 | }, 52 | 53 | initializeCategories: async () => { 54 | const { data, error } = 55 | await callApi >("/campaign/categories"); 56 | 57 | if (error || !data?.data) return; 58 | 59 | set({ categories: data.data }); 60 | }, 61 | } satisfies CampaignStore["actions"], 62 | })); 63 | 64 | export const useCampaignStore = (selector: SelectorFn ) => { 65 | const state = useInitCampaignStore(useShallow(selector)); 66 | 67 | return state; 68 | }; 69 | -------------------------------------------------------------------------------- /store/useSession.ts: -------------------------------------------------------------------------------- 1 | import type { ApiResponse, User } from "@/interfaces"; 2 | import type { SessionData } from "@/interfaces/ApiResponses"; 3 | import { callApi } from "@/lib"; 4 | import { create } from "zustand"; 5 | import { useShallow } from "zustand/react/shallow"; 6 | import type { SelectorFn } from "./store-types"; 7 | import { useInitCampaignStore } from "./useCampaignStore"; 8 | 9 | type Session = { 10 | isFirstMount: boolean; 11 | loading: boolean; 12 | user: User | null; 13 | 14 | actions: { 15 | clearSession: () => void; 16 | updateUser: (data: User) => void; 17 | getSession: (isInitialLoad?: boolean) => Promise ; 18 | }; 19 | }; 20 | 21 | const initialState = { 22 | loading: true, 23 | user: null, 24 | isFirstMount: false, 25 | }; 26 | 27 | export const useInitSession = create ()((set, get) => ({ 28 | ...initialState, 29 | 30 | actions: { 31 | getSession: async (isInitialLoad) => { 32 | if (typeof isInitialLoad === "boolean") { 33 | set({ isFirstMount: true }); 34 | } 35 | 36 | const { data } = await callApi >("/auth/session"); 37 | useInitCampaignStore.getState().actions.initializeCampaigns(data?.data?.campaigns ?? []); 38 | void useInitCampaignStore.getState().actions.initializeCategories(); 39 | 40 | set({ 41 | ...(data?.data && { user: data.data.user }), 42 | loading: false, 43 | }); 44 | }, 45 | 46 | updateUser: (data) => set({ user: data }), 47 | 48 | clearSession: () => { 49 | set((state) => ({ 50 | ...initialState, 51 | loading: false, 52 | isFirstMount: state.isFirstMount, 53 | })); 54 | 55 | const currentPageUrl = window.location.pathname; 56 | 57 | if (currentPageUrl !== "/signin" && currentPageUrl !== "/signup" && !get().isFirstMount) { 58 | window.location.replace("/signin?unauthorized=true"); 59 | } 60 | }, 61 | } satisfies Session["actions"], 62 | })); 63 | 64 | export const useSession = (selector: SelectorFn ) => { 65 | const state = useInitSession(useShallow(selector)); 66 | 67 | return state; 68 | }; 69 | -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | 6 | 7 | body:has(.authenticatedUserLayoutMain[data-isopen="true"]) { 8 | overflow: hidden; 9 | } 10 | 11 | #__next { 12 | min-height: 100svh; 13 | height: 100%; 14 | display: flex; 15 | flex-direction: column; 16 | justify-content: space-between; 17 | } 18 | 19 | input[type="number"] { 20 | -webkit-appearance: textfield; 21 | appearance: textfield; 22 | } 23 | 24 | 25 | 26 | input[type="number"] { 27 | -moz-appearance: textfield; 28 | appearance: textfield; 29 | } 30 | 31 | input::-webkit-outer-spin-button, 32 | input::-webkit-inner-spin-button { 33 | -webkit-appearance: none; 34 | margin: 0; 35 | } 36 | 37 | input:-webkit-autofill, 38 | input:-webkit-autofill:focus { 39 | transition: 40 | background-color 600000s 0s, 41 | color 600000s 0s; 42 | } 43 | 44 | input[data-autocompleted] { 45 | background-color: transparent !important; 46 | } 47 | -------------------------------------------------------------------------------- /todo.txt: -------------------------------------------------------------------------------- 1 | // These are observations and things needed to be done 2 | 3 | 18-June-2024 4 | 2. search is not working for campaign explore by category id 5 | 3. fix the preview for campaign and also do the rejection status show 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "allowSyntheticDefaultImports": true, 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "baseUrl": "./", 14 | "moduleResolution": "node", 15 | "noImplicitAny": true, 16 | "resolveJsonModule": true, 17 | "noErrorTruncation": true, // Disables error and type truncation 18 | "verbatimModuleSyntax": true, // Automatically add "type" modifier to type import statements 19 | "isolatedModules": true, 20 | "incremental": true, 21 | "jsx": "preserve", 22 | "paths": { 23 | "@/*": ["./*"] 24 | } 25 | }, 26 | "include": [ 27 | "next-env.d.ts", 28 | "**/*.ts", 29 | "**/*.tsx", 30 | ".eslintrc.js", 31 | "next.config.js", 32 | "postcss.config.js", 33 | "prettier.config.js", 34 | "lint-staged.config.js" 35 | ], 36 | "exclude": ["node_modules"] 37 | } 38 | --------------------------------------------------------------------------------