├── .babelrc ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .prettierrc ├── .travis.yml ├── DEPLOYMENT.md ├── LICENSE.txt ├── README.md ├── docker-compose.yml ├── dynamodb ├── .babelrc ├── .dockerignore ├── .env ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .prettierignore ├── .prettierrc ├── Dockerfile ├── aws-client.js ├── dynamodb-local-metadata.json ├── fixtures │ ├── BackgroundImages.json │ ├── CharityData.json │ ├── InvitedUsers.json │ ├── Missions.json │ ├── ReferralDataLog.json │ ├── UserData.json │ ├── UserEventLog.json │ ├── UserExperiment.json │ ├── UserGroupImpactMetricLog.json │ ├── UserImpactData.json │ ├── UserLevels.json │ ├── UserMissions.json │ ├── UserRevenueLog.json │ ├── UserSearchSettingsLog.json │ ├── UserSwitchSearchPromptLog.json │ ├── UserWidgetsData.json │ ├── VcDonationLog.json │ ├── VideoAdLog.json │ └── WidgetsData.json ├── package.json ├── scripts │ ├── __tests__ │ │ ├── confirmCommand.test.js │ │ ├── getFixtures.test.js │ │ └── getTableInfo.test.js │ ├── confirmCommand.js │ ├── createTables.js │ ├── deleteTables.js │ ├── getFixtures.js │ ├── getTableInfo.js │ ├── loadFixtures.js │ └── promptUserInput.js ├── serverless.yml ├── tables.json └── yarn.lock ├── graphql ├── .env ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierignore ├── .prettierrc ├── __mocks__ │ ├── @sentry │ │ └── node.js │ ├── @upstash │ │ └── redis │ │ │ └── with-fetch.js │ └── jssha.js ├── babel.config.js ├── config.js ├── data │ ├── schema.graphql │ ├── schema.js │ └── types │ │ └── wildfire.js ├── database │ ├── __mocks__ │ │ ├── @growthbook │ │ │ └── growthbook.js │ │ └── databaseClient.js │ ├── __tests__ │ │ ├── tables.test.js │ │ └── test-utils.test.js │ ├── backgroundImages │ │ ├── BackgroundImageModel.js │ │ ├── __mocks__ │ │ │ ├── BackgroundImageModel.js │ │ │ └── getRandomBackgroundImage.js │ │ ├── __tests__ │ │ │ ├── BackgroundImageModel.test.js │ │ │ ├── getBackgroundImages.test.js │ │ │ └── getRandomBackgroundImage.test.js │ │ ├── getBackgroundImages.js │ │ └── getRandomBackgroundImage.js │ ├── base │ │ ├── BaseModel.js │ │ ├── DynamoDBModel.js │ │ ├── Model.js │ │ ├── RedisModel.js │ │ ├── __mocks__ │ │ │ ├── UserModel.query.js │ │ │ └── dynogels-promisified.js │ │ ├── __tests__ │ │ │ ├── BaseModel-authorization.test.js │ │ │ ├── BaseModel-deserialization.test.js │ │ │ ├── BaseModel-properties.test.js │ │ │ ├── BaseModel-queries.test.js │ │ │ ├── BaseModel-register.test.js │ │ │ ├── DynamoDBModel-authorization.test.js │ │ │ ├── DynamoDBModel-properties.test.js │ │ │ ├── DynamoDBModel-queries.test.js │ │ │ ├── DynamoDBModel-register.test.js │ │ │ ├── Model-authorization.test.js │ │ │ ├── Model-deserialization.test.js │ │ │ ├── Model-properties.test.js │ │ │ └── RedisModel-queries.test.js │ │ ├── dynogels-promisified.js │ │ └── test-utils │ │ │ ├── ExampleDynamoDBModel.js │ │ │ ├── ExampleDynamoDBModelRangeKey.js │ │ │ ├── ExampleModel.js │ │ │ ├── ExampleModelRangeKey.js │ │ │ ├── ExampleModelV2.js │ │ │ ├── ExampleModelV2RangeKey.js │ │ │ └── ExampleRedisModel.js │ ├── cause │ │ ├── CauseModel.js │ │ ├── __tests__ │ │ │ ├── CauseModel.test.js │ │ │ ├── causes.test.js │ │ │ ├── getCause.test.js │ │ │ ├── getCauseByUser.test.js │ │ │ ├── getCauseForWildfire.test.js │ │ │ └── getCauses.test.js │ │ ├── causes.js │ │ ├── causes │ │ │ ├── blackEquity │ │ │ │ ├── about.md │ │ │ │ ├── causeData.js │ │ │ │ ├── onboarding.step1.subtitle.md │ │ │ │ ├── onboarding.step1.title.md │ │ │ │ ├── onboarding.step2.subtitle.md │ │ │ │ ├── onboarding.step2.title.md │ │ │ │ ├── onboarding.step3.subtitle.md │ │ │ │ ├── onboarding.step3.title.md │ │ │ │ ├── social.email.about.md │ │ │ │ ├── social.subtitle.md │ │ │ │ └── social.title.md │ │ │ ├── boysAndGirlsClub │ │ │ │ └── causeData.js │ │ │ ├── cats │ │ │ │ ├── about.md │ │ │ │ ├── causeData.js │ │ │ │ ├── impact.claimImpactSubtitle.md │ │ │ │ ├── impact.confirmImpactSubtitle.md │ │ │ │ ├── impact.impactCounterText.md │ │ │ │ ├── impact.impactWalkthroughText.md │ │ │ │ ├── impact.newlyReferredImpactWalkthroughText.md │ │ │ │ ├── impact.referralRewardNotification.md │ │ │ │ ├── impact.referralRewardSubtitle.md │ │ │ │ ├── impact.referralRewardTitle.md │ │ │ │ ├── onboarding.step1.subtitle.md │ │ │ │ ├── onboarding.step1.title.md │ │ │ │ ├── onboarding.step2.subtitle.md │ │ │ │ ├── onboarding.step2.title.md │ │ │ │ ├── onboarding.step3.subtitle.md │ │ │ │ ├── onboarding.step3.title.md │ │ │ │ ├── social.email.about.md │ │ │ │ ├── social.email.faq.md │ │ │ │ ├── social.subtitle.md │ │ │ │ └── social.title.md │ │ │ ├── democracy │ │ │ │ ├── about.md │ │ │ │ ├── causeData.js │ │ │ │ ├── onboarding.step1.subtitle.md │ │ │ │ ├── onboarding.step1.title.md │ │ │ │ ├── onboarding.step2.subtitle.md │ │ │ │ ├── onboarding.step2.title.md │ │ │ │ ├── onboarding.step3.subtitle.md │ │ │ │ ├── onboarding.step3.title.md │ │ │ │ ├── social.email.about.md │ │ │ │ ├── social.subtitle.md │ │ │ │ └── social.title.md │ │ │ ├── disasterRelief │ │ │ │ ├── about.md │ │ │ │ ├── causeData.js │ │ │ │ ├── onboarding.step1.subtitle.md │ │ │ │ ├── onboarding.step1.title.md │ │ │ │ ├── onboarding.step2.subtitle.md │ │ │ │ ├── onboarding.step2.title.md │ │ │ │ ├── onboarding.step3.subtitle.md │ │ │ │ ├── onboarding.step3.title.md │ │ │ │ ├── social.email.about.md │ │ │ │ ├── social.subtitle.md │ │ │ │ └── social.title.md │ │ │ ├── endingHunger │ │ │ │ └── causeData.js │ │ │ ├── endingPoverty │ │ │ │ └── causeData.js │ │ │ ├── gamesForLove │ │ │ │ └── causeData.js │ │ │ ├── globalHealth │ │ │ │ └── causeData.js │ │ │ ├── lgbtq │ │ │ │ └── causeData.js │ │ │ ├── reproductiveHealthCauseData.js │ │ │ ├── teamseas │ │ │ │ ├── about.md │ │ │ │ ├── causeData.js │ │ │ │ ├── impact.claimImpactSubtitle.md │ │ │ │ ├── impact.confirmImpactSubtitle.md │ │ │ │ ├── impact.impactCounterText.md │ │ │ │ ├── impact.impactWalkthroughText.md │ │ │ │ ├── impact.newlyReferredImpactWalkthroughText.md │ │ │ │ ├── impact.referralRewardNotification.md │ │ │ │ ├── impact.referralRewardSubtitle.md │ │ │ │ ├── impact.referralRewardTitle.md │ │ │ │ ├── onboarding.step1.subtitle.md │ │ │ │ ├── onboarding.step1.title.md │ │ │ │ ├── onboarding.step2.subtitle.md │ │ │ │ ├── onboarding.step2.title.md │ │ │ │ ├── onboarding.step3.subtitle.md │ │ │ │ ├── onboarding.step3.title.md │ │ │ │ ├── social.email.about.md │ │ │ │ ├── social.subtitle.md │ │ │ │ └── social.title.md │ │ │ ├── trees │ │ │ │ └── causeData.js │ │ │ └── ukraine │ │ │ │ └── causeData.js │ │ ├── getCause.js │ │ ├── getCauseByUser.js │ │ ├── getCauseForWildfire.js │ │ └── getCauses.js │ ├── charities │ │ ├── CharityModel.js │ │ ├── __tests__ │ │ │ ├── CharityModel.test.js │ │ │ ├── getCharities.test.js │ │ │ └── getCharityForCause.test.js │ │ ├── getCharities.js │ │ └── getCharityForCause.js │ ├── constants.js │ ├── databaseClient.js │ ├── donations │ │ ├── VCDonationByCharityModel.js │ │ ├── VCDonationModel.js │ │ ├── __tests__ │ │ │ ├── VCDonationByCharityModel.test.js │ │ │ ├── VCDonationModel.test.js │ │ │ ├── donateVc.test.js │ │ │ └── getCharityVcReceived.test.js │ │ ├── donateVc.js │ │ └── getCharityVcReceived.js │ ├── experiments │ │ ├── FeatureModel.js │ │ ├── UserExperimentModel.js │ │ ├── __tests__ │ │ │ ├── FeatureModel.test.js │ │ │ ├── UserExperimentModel.test.js │ │ │ ├── createUserExperiment.test.js │ │ │ ├── getFeature.test.js │ │ │ ├── getUserFeature.test.js │ │ │ ├── getUserFeatures.test.js │ │ │ └── growthbookUtils.test.js │ │ ├── createUserExperiment.js │ │ ├── experimentConstants.js │ │ ├── features.js │ │ ├── getFeature.js │ │ ├── getUserFeature.js │ │ ├── getUserFeatures.js │ │ └── growthbookUtils.js │ ├── fieldTypes.js │ ├── globals │ │ ├── __mocks__ │ │ │ ├── createCampaignConfiguration.js │ │ │ └── getCampaign.js │ │ ├── __tests__ │ │ │ ├── createCampaignConfiguration.test.js │ │ │ ├── getCampaign.test.js │ │ │ ├── getCurrentCampaignConfig.test.js │ │ │ └── globals.test.js │ │ ├── createCampaignConfiguration.js │ │ ├── getCampaign.js │ │ ├── getCurrentCampaignConfig.js │ │ └── globals.js │ ├── groupImpact │ │ ├── CauseGroupImpactMetricModel.js │ │ ├── CauseImpactMetricCountModel.js │ │ ├── GroupImpactLeaderboard.js │ │ ├── GroupImpactMetricModel.js │ │ ├── ImpactMetricModel.js │ │ ├── UserGroupImpactMetricLogModel.js │ │ ├── UserGroupImpactMetricModel.js │ │ ├── __tests__ │ │ │ ├── CauseGroupImpactMetricModel.test.js │ │ │ ├── CauseImpactMetricCountModel.test.js │ │ │ ├── GroupImpactLeaderboard.test.js │ │ │ ├── GroupImpactMetricModel.test.js │ │ │ ├── ImpactMetricModel.test.js │ │ │ ├── UserGroupImpactMetricLogModel.test.js │ │ │ ├── UserGroupImpactMetricModel.test.js │ │ │ ├── getCauseImpactMetricCount.test.js │ │ │ ├── getGroupImpactMetricForCause.test.js │ │ │ ├── getNextImpactMetricForCause.test.js │ │ │ ├── impactMetricRepository.test.js │ │ │ ├── impactMetrics.test.js │ │ │ ├── incrementCauseImpactMetricCount.test.js │ │ │ ├── updateGroupImpactMetric.test.js │ │ │ └── updateUserGroupImpactMetric.test.js │ │ ├── getCauseImpactMetricCount.js │ │ ├── getGroupImpactMetricForCause.js │ │ ├── getNextImpactMetricForCause.js │ │ ├── impactMetricData.js │ │ ├── impactMetricRepository.js │ │ ├── impactMetrics.js │ │ ├── incrementCauseImpactMetricCount.js │ │ ├── updateGroupImpactMetric.js │ │ └── updateUserGroupImpactMetric.js │ ├── invitedUsers │ │ ├── InvitedUsersModel.js │ │ ├── __mocks__ │ │ │ └── InvitedUsersModel.js │ │ ├── __tests__ │ │ │ ├── InvitedUsersModel.test.js │ │ │ ├── createInvitedUsers.test.js │ │ │ └── utils.test.js │ │ ├── createInvitedUsers.js │ │ └── utils.js │ ├── logs │ │ ├── UserEventLogModel.js │ │ ├── __tests__ │ │ │ └── UserEventLogModel.test.js │ │ └── logTypes.js │ ├── missions │ │ ├── MissionModel.js │ │ ├── UserMissionModel.js │ │ ├── __tests__ │ │ │ ├── MissionModel.test.js │ │ │ ├── UserMissionModel.test.js │ │ │ ├── completeMission.test.js │ │ │ ├── createMission.test.js │ │ │ ├── getCurrentUserMission.test.js │ │ │ ├── getPastMissions.test.js │ │ │ ├── restartMission.test.js │ │ │ ├── squadInviteResponse.test.js │ │ │ ├── updateMissionNotification.test.js │ │ │ └── utils.test.js │ │ ├── completeMission.js │ │ ├── constants.js │ │ ├── createMission.js │ │ ├── getCurrentUserMission.js │ │ ├── getPastUserMissions.js │ │ ├── hasSeenCompletedMission.js │ │ ├── inviteUserToMission.js │ │ ├── restartMission.js │ │ ├── squadInviteResponse.js │ │ ├── updateMissionNotification.js │ │ └── utils.js │ ├── referrals │ │ ├── ReferralDataModel.js │ │ ├── ReferralLinkClickLogModel.js │ │ ├── __tests__ │ │ │ ├── ReferralDataModel.test.js │ │ │ ├── ReferralLinkClickLogModel.test.js │ │ │ ├── getRecruits.test.js │ │ │ ├── logReferralData.test.js │ │ │ └── logReferralLinkClick.test.js │ │ ├── getRecruits.js │ │ ├── logReferralData.js │ │ └── logReferralLinkClick.js │ ├── search │ │ ├── SearchEngineModel.js │ │ ├── __tests__ │ │ │ ├── SearchEngineModel.test.js │ │ │ ├── getSearchEngine.test.js │ │ │ └── searchEngines.test.js │ │ ├── getSearchEngine.js │ │ ├── searchEngineData.js │ │ └── searchEngines.js │ ├── tables.js │ ├── test-utils.js │ ├── userDataConsent │ │ ├── UserDataConsentModel.js │ │ ├── __tests__ │ │ │ ├── UserDataConsentModel.test.js │ │ │ └── logUserDataConsent.test.js │ │ └── logUserDataConsent.js │ ├── userImpact │ │ ├── UserImpactModel.js │ │ ├── __mocks__ │ │ │ └── UserImpactModel.js │ │ ├── __tests__ │ │ │ ├── UserImpactModel.test.js │ │ │ ├── getUserImpact.test.js │ │ │ ├── getUserImpactAndCause.test.js │ │ │ └── updateImpact.test.js │ │ ├── getUserImpact.js │ │ ├── getUserImpactAndCause.js │ │ └── updateImpact.js │ ├── userLevels │ │ ├── UserLevelModel.js │ │ ├── __tests__ │ │ │ ├── UserLevelModel.test.js │ │ │ └── getNextLevelFor.test.js │ │ └── getNextLevelFor.js │ ├── userRevenue │ │ ├── UserRevenueModel.js │ │ ├── __tests__ │ │ │ ├── UserRevenueModel.test.js │ │ │ ├── decodeAmazonCPM.test.js │ │ │ └── logRevenue.test.js │ │ ├── amazon-cpm-codes-v2.json.enc │ │ ├── amazon-cpm-codes.json │ │ ├── decodeAmazonCPM.js │ │ └── logRevenue.js │ ├── users │ │ ├── UserModel.js │ │ ├── UserSearchLogModel.js │ │ ├── UserSearchSettingsLogModel.js │ │ ├── UserSwitchSearchPromptLogModel.js │ │ ├── UserTabsLogModel.js │ │ ├── __mocks__ │ │ │ └── addVc.js │ │ ├── __tests__ │ │ │ ├── UserModel.test.js │ │ │ ├── UserSearchLogModel.test.js │ │ │ ├── UserSearchSettingsLogModel.test.js │ │ │ ├── UserSwitchSearchPromptLogModel.test.js │ │ │ ├── UserTabsLogModel.test.js │ │ │ ├── addUsersRecruited.test.js │ │ │ ├── addVc.test.js │ │ │ ├── addVcDonatedAllTime.test.js │ │ │ ├── checkSearchRateLimit.test.js │ │ │ ├── constructExperimentActionsType.test.js │ │ │ ├── createSearchEnginePromptLog.test.js │ │ │ ├── createSfacExtensionPromptResponse.test.js │ │ │ ├── createUser.test.js │ │ │ ├── deleteUser.test.js │ │ │ ├── getBackgroundImage.test.js │ │ │ ├── getLandingPagePhrase.test.js │ │ │ ├── getOrCreateTruexId.test.js │ │ │ ├── getSfacActivityState.test.js │ │ │ ├── getShouldShowSFACExtensionPrompt.test.js │ │ │ ├── getShouldShowSfacIcon.test.js │ │ │ ├── getShouldShowYahooPrompt.test.js │ │ │ ├── getUserByUsername.test.js │ │ │ ├── getUserSearchEngine.test.js │ │ │ ├── logEmailVerified.test.js │ │ │ ├── logSearch.test.js │ │ │ ├── logTab.test.js │ │ │ ├── logUserExperimentActions.test.js │ │ │ ├── logUserExperimentGroups.test.js │ │ │ ├── mergeIntoExistingUser.test.js │ │ │ ├── rewardReferringUser.test.js │ │ │ ├── setActiveWidget.test.js │ │ │ ├── setBackgroundColor.test.js │ │ │ ├── setBackgroundImage.test.js │ │ │ ├── setBackgroundImageDaily.test.js │ │ │ ├── setBackgroundImageFromCustomURL.test.js │ │ │ ├── setEmail.test.js │ │ │ ├── setHasSeenSquads.test.js │ │ │ ├── setHasViewedIntroFlow.test.js │ │ │ ├── setUserCause.test.js │ │ │ ├── setUserSearchEngine.test.js │ │ │ ├── setUsername.test.js │ │ │ ├── setV4Enabled.test.js │ │ │ ├── setYahooSearchOptIn.test.js │ │ │ └── user-utils.test.js │ │ ├── addUsersRecruited.js │ │ ├── addVc.js │ │ ├── addVcDonatedAllTime.js │ │ ├── checkSearchRateLimit.js │ │ ├── constructExperimentActionsType.js │ │ ├── createSearchEnginePromptLog.js │ │ ├── createSfacExtensionPromptResponse.js │ │ ├── createUser.js │ │ ├── deleteUser.js │ │ ├── getBackgroundImage.js │ │ ├── getLandingPagePhrase.js │ │ ├── getOrCreateTruexId.js │ │ ├── getSfacActivityState.js │ │ ├── getShouldShowSfacExtensionPrompt.js │ │ ├── getShouldShowSfacIcon.js │ │ ├── getShouldShowYahooPrompt.js │ │ ├── getUserByUsername.js │ │ ├── getUserNotifications.js │ │ ├── getUserSearchEngine.js │ │ ├── logEmailVerified.js │ │ ├── logSearch.js │ │ ├── logTab.js │ │ ├── logUserExperimentActions.js │ │ ├── logUserExperimentGroups.js │ │ ├── mergeIntoExistingUser.js │ │ ├── rewardReferringUser.js │ │ ├── setActiveWidget.js │ │ ├── setBackgroundColor.js │ │ ├── setBackgroundImage.js │ │ ├── setBackgroundImageDaily.js │ │ ├── setBackgroundImageFromCustomURL.js │ │ ├── setEmail.js │ │ ├── setHasSeenSquads.js │ │ ├── setHasViewedIntroFlow.js │ │ ├── setUserCause.js │ │ ├── setUserSearchEngine.js │ │ ├── setUsername.js │ │ ├── setV4Enabled.js │ │ ├── setYahooSearchOptIn.js │ │ └── user-utils.js │ ├── videoAdLog │ │ ├── VideoAdLogModel.js │ │ ├── __tests__ │ │ │ ├── VideoAdLogModel.test.js │ │ │ ├── createVideoAdLog.test.js │ │ │ ├── isVideoAdEligible.test.js │ │ │ └── logVideoAdCompleted.test.js │ │ ├── createVideoAdLog.js │ │ ├── isVideoAdEligible.js │ │ └── logVideoAdCompleted.js │ └── widgets │ │ ├── Widget.js │ │ ├── __mocks__ │ │ └── setUpWidgetsForNewUser.js │ │ ├── __tests__ │ │ ├── constructFullWidget.test.js │ │ ├── getWidget.test.js │ │ ├── getWidgets.test.js │ │ ├── setUpWidgetsForNewUser.test.js │ │ └── updateWidget.test.js │ │ ├── baseWidget │ │ ├── BaseWidgetModel.js │ │ ├── __tests__ │ │ │ ├── BaseWidgetModel.test.js │ │ │ └── getAllBaseWidgets.test.js │ │ └── getAllBaseWidgets.js │ │ ├── constructFullWidget.js │ │ ├── getWidget.js │ │ ├── getWidgets.js │ │ ├── setUpWidgetsForNewUser.js │ │ ├── updateWidget.js │ │ └── userWidget │ │ ├── UserWidgetModel.js │ │ ├── __tests__ │ │ ├── UserWidgetModel.test.js │ │ ├── getUserWidgetsByEnabledState.test.js │ │ ├── updateUserWidgetConfig.test.js │ │ ├── updateUserWidgetData.test.js │ │ ├── updateUserWidgetEnabled.test.js │ │ └── updateUserWidgetVisibility.test.js │ │ ├── getUserWidgetsByEnabledState.js │ │ ├── updateUserWidgetConfig.js │ │ ├── updateUserWidgetData.js │ │ ├── updateUserWidgetEnabled.js │ │ └── updateUserWidgetVisibility.js ├── handler.js ├── integration-tests │ ├── __tests__ │ │ ├── authorization-user.js │ │ └── authorization.test.js │ ├── fixtures │ │ ├── UserWidgets.json │ │ ├── Users.json │ │ └── Widgets.json │ └── utils │ │ ├── __tests__ │ │ └── fixture-utils.test.js │ │ ├── auth-utils.js │ │ ├── aws-client-dynamodb.js │ │ ├── fetch-graphql.js │ │ ├── fixture-utils.js │ │ └── table-utils.js ├── jest.config-e2e.js ├── jest.config.js ├── package.json ├── scripts │ ├── generateConfig.js │ ├── oneOffDBActions.js │ └── updateSchema.js ├── server.js ├── serverless.yml ├── utils │ ├── __mocks__ │ │ └── logger.js │ ├── __tests__ │ │ ├── authorization-helpers.test.js │ │ ├── dev-tools.test.js │ │ ├── error-logging.test.js │ │ ├── exceptions.test.js │ │ ├── experiments.test.js │ │ ├── logger.test.js │ │ ├── permissions-overrides.test.js │ │ ├── redis.test.js │ │ └── utils.test.js │ ├── authorization-helpers.js │ ├── dev-tools.js │ ├── error-logging.js │ ├── exceptions.js │ ├── experiments.js │ ├── logger.js │ ├── permissions-overrides.js │ ├── redis.js │ ├── sentry-logger.js │ └── utils.js └── yarn.lock ├── lambda ├── .env ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .npmignore ├── .prettierignore ├── .prettierrc ├── README.md ├── __mocks__ │ ├── firebase-admin.js │ └── uuid │ │ └── v4.js ├── babel.config.js ├── package.json ├── scripts │ ├── build.js │ └── set-search-sns-stack-policy.mjs ├── search-sns-cloudformation.yml ├── server.js ├── serverless-lambda-edge.yml ├── serverless-lambda.yml ├── setupTests.js ├── src │ ├── __mocks__ │ │ └── yamljs.js │ ├── __tests__ │ │ └── getLambdas.test.js │ ├── firebase-authorizer │ │ ├── __tests__ │ │ │ ├── decrypt-utils.test.js │ │ │ ├── firebase-authorizer.test.js │ │ │ └── initNFA.test.js │ │ ├── decrypt-utils.js │ │ ├── fetch-polyfill.js │ │ ├── firebase-authorizer.js │ │ ├── initNFA.js │ │ └── variables.yml │ ├── getLambdas.js │ ├── homepage-404-lambda-edge │ │ ├── __tests__ │ │ │ └── homepage-404-lambda-edge.test.js │ │ └── homepage-404-lambda-edge.js │ ├── newtab-app-lambda-edge │ │ ├── __tests__ │ │ │ └── newtab-lambda-edge.test.js │ │ └── newtab-app-lambda-edge.js │ ├── search-app-lambda-edge │ │ ├── __tests__ │ │ │ ├── search-app-lambda-edge.test.js │ │ │ └── searchURLByRegion.test.js │ │ ├── search-app-lambda-edge.js │ │ └── searchURLByRegion.js │ ├── search-request-logger │ │ ├── __tests__ │ │ │ └── search-request-logger.test.mjs │ │ ├── search-request-logger.mjs │ │ └── variables.yml │ └── utils │ │ ├── __mocks__ │ │ └── database.js │ │ ├── __tests__ │ │ └── tables.test.js │ │ ├── constants.js │ │ ├── database.js │ │ ├── lambda-arg-utils.js │ │ └── tables.js └── yarn.lock ├── package.json ├── redis ├── .babelrc ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .prettierignore ├── .prettierrc ├── package.json ├── serverless.yml ├── src │ ├── __tests__ │ │ └── handler.test.js │ └── handler.js └── yarn.lock ├── s3 ├── media │ └── img │ │ ├── background-thumbnails │ │ ├── 50413bb866f7495cb70371a2edc8b934.jpg │ │ ├── 71a27d6823244354acb85e0806d0dff1.jpg │ │ ├── cup.jpg │ │ ├── dota2.jpg │ │ ├── invoker.jpg │ │ ├── lake.jpg │ │ ├── puppy.jpg │ │ ├── shadowfiend.jpg │ │ └── slark.jpg │ │ ├── backgrounds │ │ ├── 135b157fdbb24ba39f6dd0ee8da16f2b.jpg │ │ ├── 3acd54614b1d4d7fbce85d965de3de25.jpg │ │ ├── 3d1299fb-3ad5-4405-8cb4-5236bffd1660.jpg │ │ ├── 7a8b04cc-fd88-4af3-bf73-3b6459e7eccb.jpg │ │ ├── 7bd681cf-850d-42eb-9a1f-7b9620bfb82a.jpg │ │ ├── 7c33e7df-fdc6-4f57-bb13-646de15e58bb.jpg │ │ ├── 87fe1385-388c-4c17-98ce-80163a8ac352.jpg │ │ ├── 8907622e-40f7-46ea-92e4-535b9dbdfdfe.jpg │ │ ├── 8a5ca2a1-fa90-42c1-820a-9029303c9ccd.jpg │ │ ├── 8f54a3b0-9788-4a19-a818-efc40950e97d.jpg │ │ ├── 98c444cd-040d-4484-a25a-267194e9de45.jpg │ │ ├── a3e3c0b2-9e16-4d1a-b258-fee638a96df8.jpg │ │ ├── alex-chernenko-To28QYvt5q4-unsplash.jpg │ │ ├── c45ab947-c54c-404e-b4a2-4bf7b68430ad.jpg │ │ ├── cup.jpg │ │ ├── d588d58c-bc0c-48c6-a815-0e4cddbcd397.jpg │ │ ├── dota2.jpg │ │ ├── invoker.jpg │ │ ├── kanashi-MwGMVKRehaM-unsplash.jpg │ │ ├── lake.jpg │ │ ├── malek-dridi-0F7GRXNOG7g-unsplash.jpg │ │ ├── natalia-nikolaieva-8yCWhEGNkX0-unsplash.jpg │ │ ├── puppy.jpg │ │ ├── shadowfiend.jpg │ │ ├── slark.jpg │ │ └── tran-mau-tri-tam--81lVsfM4gQ-unsplash.jpg │ │ └── charities │ │ ├── charity-logos │ │ ├── a21-201901.png │ │ ├── acfusa-201804.png │ │ ├── conservation-international-201804.png │ │ ├── coral-reef-alliance.jpg │ │ ├── educate-201807.jpg │ │ ├── ftdws.jpg │ │ ├── girls-not-brides.png │ │ ├── givedirectly.png │ │ ├── human-rights-watch.jpg │ │ ├── placeholder.png │ │ ├── room-to-read-20210809.png │ │ ├── save-the-children-201804.png │ │ ├── the-bail-project.jpg │ │ ├── united-we-dream.jpg │ │ ├── water-dot-org-2.png │ │ ├── water-dot-org.jpg │ │ └── wwf-201902.png │ │ └── charity-post-donation-images │ │ ├── a21.jpg │ │ ├── acfusa.jpg │ │ ├── against-malaria-foundation.jpg │ │ ├── amhc.jpg │ │ ├── australian-bushfire-relief.jpg │ │ ├── bwhi.jpg │ │ ├── care-india.jpg │ │ ├── ci-20210707.jpg │ │ ├── conservation-international.jpg │ │ ├── cool-earth.png │ │ ├── coral-reef-alliance.jpg │ │ ├── covid-19-solidarity.jpg │ │ ├── direct-relief.jpg │ │ ├── doctors-without-borders.jpg │ │ ├── earthjustice.jpg │ │ ├── educate-201902.jpg │ │ ├── educate.jpg │ │ ├── eji.jpg │ │ ├── friends-with-four-paws.png │ │ ├── ftdws-20210707.jpg │ │ ├── ftdws.jpg │ │ ├── girls-not-brides.jpg │ │ ├── give-directly.jpg │ │ ├── give-directly.svg │ │ ├── givedirectly.jpg │ │ ├── hrw-20210707.jpg │ │ ├── human-rights-watch.jpg │ │ ├── naacp-ldf.jpg │ │ ├── pih.png │ │ ├── rainforest-alliance.jpg │ │ ├── rainn.jpg │ │ ├── room-to-read-20210707.jpg │ │ ├── room-to-read-20230217.jpg │ │ ├── room-to-read.jpg │ │ ├── save-the-children.jpg │ │ ├── sea-turtle-inc.jpg │ │ ├── sierra-club.jpg │ │ ├── stc-20210707.jpg │ │ ├── the-bail-project.jpg │ │ ├── trans-lifeline.jpg │ │ ├── trees-for-the-future.jpg │ │ ├── uhrp.jpg │ │ ├── united-we-dream.jpg │ │ ├── water-20210707.jpg │ │ ├── water-dot-org.jpg │ │ └── wwf.jpg ├── package.json ├── server.js ├── serverless.yml └── yarn.lock ├── scripts ├── __tests__ │ ├── assign-env-vars.test.js │ └── deployHelpers.test.js ├── assign-env-vars.js ├── deploy.js ├── deployHelpers.js ├── log-prefix.js ├── manage-fixtures.js ├── optimize-images.js ├── rm-node-modules.sh ├── runEndToEndTests.js ├── runTests.js └── upload-data-to-ddb.mjs ├── web ├── .env ├── .eslintrc.json ├── .gitignore ├── .prettierignore ├── .prettierrc ├── README.md ├── __mocks__ │ ├── @sentry │ │ ├── browser.js │ │ └── integrations.js │ ├── firebase │ │ ├── app.js │ │ └── auth.js │ ├── react-relay.js │ ├── react-relay │ │ └── compat.js │ ├── tab-ads.js │ └── tab-cmp.js ├── __tests__ │ └── prebidPatches.test.js ├── codecov.yml ├── data │ └── schema.graphql ├── jsconfig.json ├── package.json ├── patches │ └── react-scripts+3.0.1.patch ├── public │ ├── favicon.ico │ ├── index.html │ ├── manifest.json │ ├── search-2019.2.28.14.21.css │ └── search-favicon.png ├── scripts │ ├── build.js │ └── syncToS3.js ├── serverless.yml ├── src │ ├── __tests__ │ │ ├── index.test.js │ │ └── searchQuery.test.js │ ├── e2e-tests │ │ └── __tests__ │ │ │ └── e2e.test.js │ ├── index.css │ ├── index.js │ ├── js │ │ ├── __mocks__ │ │ │ ├── blockadblock.js │ │ │ ├── browser-detect.js │ │ │ └── relay-env.js │ │ ├── __tests__ │ │ │ └── root.test.js │ │ ├── analytics │ │ │ ├── __mocks__ │ │ │ │ ├── facebook-analytics.js │ │ │ │ └── logEvent.js │ │ │ ├── __tests__ │ │ │ │ ├── facebook-analytics.test.js │ │ │ │ ├── google-analytics.test.js │ │ │ │ ├── logEvent.test.js │ │ │ │ └── reddit-analytics.test.js │ │ │ ├── facebook-analytics.js │ │ │ ├── google-analytics.js │ │ │ ├── logEvent.js │ │ │ ├── reddit-analytics.js │ │ │ └── withPageviewTracking.js │ │ ├── assets │ │ │ ├── blackEquity.png │ │ │ ├── cashHand.png │ │ │ ├── cats.png │ │ │ ├── defaultBackground.jpg │ │ │ ├── defaultSeasBackground.jpg │ │ │ ├── democracy.png │ │ │ ├── disasterRelief.png │ │ │ ├── endingHunger.png │ │ │ ├── givedirectly.png │ │ │ ├── globalHealth.png │ │ │ ├── lgbtq.png │ │ │ ├── logos │ │ │ │ ├── favicon.ico │ │ │ │ ├── logo-grey.svg │ │ │ │ ├── logo-white.svg │ │ │ │ ├── logo-with-text-white.svg │ │ │ │ ├── logo-with-text.svg │ │ │ │ ├── logo.svg │ │ │ │ ├── logo32x32.png │ │ │ │ ├── search-favicon.png │ │ │ │ ├── search-logo-with-text.svg │ │ │ │ └── search-logo.svg │ │ │ ├── nopicture.jpg │ │ │ ├── promos │ │ │ │ ├── allexpress.png │ │ │ │ ├── bookshop.png │ │ │ │ ├── glossier.png │ │ │ │ ├── horse.png │ │ │ │ ├── kiwico.png │ │ │ │ ├── lego.png │ │ │ │ ├── lowes.png │ │ │ │ ├── macys.png │ │ │ │ ├── microsoft.png │ │ │ │ ├── oldnavy.png │ │ │ │ ├── samsung.png │ │ │ │ ├── sephora.png │ │ │ │ ├── sonos.png │ │ │ │ ├── thriftbooks.png │ │ │ │ ├── ultra-beauty.png │ │ │ │ ├── walmart.png │ │ │ │ └── zulily.png │ │ │ ├── reproductiveHealth.png │ │ │ ├── seas1.svg │ │ │ ├── trees.png │ │ │ └── ukraine.png │ │ ├── authentication │ │ │ ├── __mocks__ │ │ │ │ ├── firebaseConfig.js │ │ │ │ ├── helpers.js │ │ │ │ └── user.js │ │ │ ├── __tests__ │ │ │ │ ├── helpers.test.js │ │ │ │ └── user.test.js │ │ │ ├── firebaseConfig.js │ │ │ ├── helpers.js │ │ │ └── user.js │ │ ├── components │ │ │ ├── App │ │ │ │ ├── App.js │ │ │ │ ├── NotFoundPage.js │ │ │ │ └── __tests__ │ │ │ │ │ ├── App.moduleLoad.test.js │ │ │ │ │ └── App.test.js │ │ │ ├── Authentication │ │ │ │ ├── Authentication.js │ │ │ │ ├── AuthenticationContainer.js │ │ │ │ ├── AuthenticationView.js │ │ │ │ ├── EnterUsernameForm.js │ │ │ │ ├── FirebaseAuthenticationUI.js │ │ │ │ ├── FirebaseAuthenticationUIAction.js │ │ │ │ ├── MissingEmailMessage.js │ │ │ │ ├── SignInIframeMessage.js │ │ │ │ ├── VerifyEmailMessage.js │ │ │ │ ├── __mocks__ │ │ │ │ │ └── EnterUsernameForm.js │ │ │ │ └── __tests__ │ │ │ │ │ ├── Authentication.test.js │ │ │ │ │ ├── AuthenticationView.test.js │ │ │ │ │ ├── EnterUsernameForm.test.js │ │ │ │ │ ├── FirebaseAuthenticationUI.test.js │ │ │ │ │ ├── FirebaseAuthenticationUIAction.test.js │ │ │ │ │ ├── MissingEmailMessage.test.js │ │ │ │ │ ├── SignInIframeMessage.test.js │ │ │ │ │ ├── VerifyEmailMessage.test.js │ │ │ │ │ └── __snapshots__ │ │ │ │ │ ├── MissingEmailMessage.test.js.snap │ │ │ │ │ └── VerifyEmailMessage.test.js.snap │ │ │ ├── Background │ │ │ │ ├── BackgroundColorPickerComponent.js │ │ │ │ ├── BackgroundColorPickerContainer.js │ │ │ │ ├── BackgroundCustomImagePickerComponent.js │ │ │ │ ├── BackgroundCustomImagePickerContainer.js │ │ │ │ ├── BackgroundImagePickerComponent.js │ │ │ │ ├── BackgroundImagePickerContainer.js │ │ │ │ ├── FadeBackgroundAnimation.js │ │ │ │ └── __tests__ │ │ │ │ │ ├── BackgroundColorPickerComponent.test.js │ │ │ │ │ ├── BackgroundCustomImagePickerComponent.test.js │ │ │ │ │ └── BackgroundImagePickerComponent.test.js │ │ │ ├── Campaign │ │ │ │ ├── CampaignGenericComponent.js │ │ │ │ ├── CampaignGenericView.js │ │ │ │ ├── CampaignWrapper.js │ │ │ │ ├── CountdownClockComponent.js │ │ │ │ ├── MillionRaisedCampaign.js │ │ │ │ ├── MillionRaisedCampaignContainer.js │ │ │ │ ├── TreePlantingCampaignComponent.js │ │ │ │ ├── TreePlantingCampaignContainer.js │ │ │ │ └── __tests__ │ │ │ │ │ ├── CampaignGenericComponent.test.js │ │ │ │ │ ├── CampaignGenericView.test.js │ │ │ │ │ ├── CountdownClockComponent.test.js │ │ │ │ │ └── TreePlantingCampaignComponent.test.js │ │ │ ├── Dashboard │ │ │ │ ├── AssignExperimentGroupsComponent.js │ │ │ │ ├── AssignExperimentGroupsContainer.js │ │ │ │ ├── DashboardComponent.js │ │ │ │ ├── DashboardContainer.js │ │ │ │ ├── DashboardPopover.js │ │ │ │ ├── DashboardView.js │ │ │ │ ├── FirstTabView.js │ │ │ │ ├── HeartsComponent.js │ │ │ │ ├── HeartsContainer.js │ │ │ │ ├── HeartsDropdownComponent.js │ │ │ │ ├── HeartsDropdownContainer.js │ │ │ │ ├── LogAccountCreationComponent.js │ │ │ │ ├── LogAccountCreationContainer.js │ │ │ │ ├── LogTabComponent.js │ │ │ │ ├── LogTabContainer.js │ │ │ │ ├── MaxHeartsDropdownMessageComponent.js │ │ │ │ ├── NewUserTourComponent.js │ │ │ │ ├── NewUserTourContainer.js │ │ │ │ ├── NotificationComponent.js │ │ │ │ ├── NotificationV2.js │ │ │ │ ├── November2023ShopUser.module.css │ │ │ │ ├── PostUninstallView.js │ │ │ │ ├── SettingsButtonComponent.js │ │ │ │ ├── SettingsDropdownComponent.js │ │ │ │ ├── SfacExtensionSellNotification.js │ │ │ │ ├── ShfacExtensionSellNotification.js │ │ │ │ ├── UserBackgroundImageComponent.js │ │ │ │ ├── UserBackgroundImageContainer.js │ │ │ │ ├── UserMenuComponent.js │ │ │ │ ├── UserMenuContainer.js │ │ │ │ ├── VideoEngagementComponent.js │ │ │ │ ├── VideoEngagementContainer.js │ │ │ │ ├── __mocks__ │ │ │ │ │ └── DashboardContainer.js │ │ │ │ └── __tests__ │ │ │ │ │ ├── AssignExperimentGroupsComponent.test.js │ │ │ │ │ ├── DashboardComponent.test.js │ │ │ │ │ ├── DashboardPopover.test.js │ │ │ │ │ ├── DashboardView.test.js │ │ │ │ │ ├── FirstTabView.test.js │ │ │ │ │ ├── HeartsComponent.test.js │ │ │ │ │ ├── HeartsDropdownComponent.test.js │ │ │ │ │ ├── LogAccountCreationComponent.test.js │ │ │ │ │ ├── LogTabComponent.test.js │ │ │ │ │ ├── MaxHeartsDropdownMessageComponent.test.js │ │ │ │ │ ├── NewUserTourComponent.test.js │ │ │ │ │ ├── NotificationComponent.test.js │ │ │ │ │ ├── NotificationV2.test.js │ │ │ │ │ ├── PostUninstallView.test.js │ │ │ │ │ ├── SettingsButtonComponent.test.js │ │ │ │ │ ├── SettingsDropdownComponent.test.js │ │ │ │ │ ├── SfacExtensionSellNotification.test.js │ │ │ │ │ ├── UserBackgroundImageComponent.test.js │ │ │ │ │ └── UserMenuComponent.test.js │ │ │ ├── Demos │ │ │ │ └── DemosPage.js │ │ │ ├── Donate │ │ │ │ ├── CharityComponent.js │ │ │ │ ├── CharityContainer.js │ │ │ │ ├── DonateHeartsControlsComponent.js │ │ │ │ ├── DonateHeartsControlsContainer.js │ │ │ │ ├── SwitchToV4Component.js │ │ │ │ ├── SwitchToV4Container.js │ │ │ │ └── __tests__ │ │ │ │ │ ├── CharityComponent.test.js │ │ │ │ │ ├── DonateHeartsControlsComponent.test.js │ │ │ │ │ └── SwitchToV4Component.test.js │ │ │ ├── General │ │ │ │ ├── BaseContainer.js │ │ │ │ ├── CenteredWidgetsContainer.js │ │ │ │ ├── CircleButton.js │ │ │ │ ├── ConditionalWrapper.js │ │ │ │ ├── EmailField.js │ │ │ │ ├── ErrorBoundary.js │ │ │ │ ├── ErrorMessage.js │ │ │ │ ├── FadeInAnimation.js │ │ │ │ ├── FadeInDashboardAnimation.js │ │ │ │ ├── Fireworks.js │ │ │ │ ├── Footer.js │ │ │ │ ├── FullPageLoader.js │ │ │ │ ├── Link.js │ │ │ │ ├── LinkWithActionBeforeNavigate.js │ │ │ │ ├── Markdown.js │ │ │ │ ├── PaperItem.js │ │ │ │ ├── QueryRendererWithUser.js │ │ │ │ ├── Scrollable.js │ │ │ │ ├── SocialShareComponent.js │ │ │ │ ├── Timeout.js │ │ │ │ ├── UsernameField.js │ │ │ │ ├── WidgetSharedSpace.js │ │ │ │ ├── __mocks__ │ │ │ │ │ ├── ErrorMessage.js │ │ │ │ │ ├── Link.js │ │ │ │ │ ├── LinkWithActionBeforeNavigate.js │ │ │ │ │ ├── QueryRendererWithUser.js │ │ │ │ │ └── withUser.js │ │ │ │ ├── __tests__ │ │ │ │ │ ├── ConditionalWrapper.test.js │ │ │ │ │ ├── ErrorBoundary.test.js │ │ │ │ │ ├── ErrorMessage.test.js │ │ │ │ │ ├── Footer.test.js │ │ │ │ │ ├── FullPageLoader.test.js │ │ │ │ │ ├── Link.test.js │ │ │ │ │ ├── LinkWithActionBeforeNavigate.test.js │ │ │ │ │ ├── PaperItem.test.js │ │ │ │ │ ├── QueryRendererWithUser.test.js │ │ │ │ │ ├── SocialShareComponent.test.js │ │ │ │ │ ├── Timeout.test.js │ │ │ │ │ ├── UsernameField.test.js │ │ │ │ │ └── withUser.test.js │ │ │ │ └── withUser.js │ │ │ ├── Logo │ │ │ │ ├── Logo.js │ │ │ │ └── __tests__ │ │ │ │ │ └── Logo.test.js │ │ │ ├── MoneyRaised │ │ │ │ ├── MoneyRaisedComponent.js │ │ │ │ ├── MoneyRaisedContainer.js │ │ │ │ └── __tests__ │ │ │ │ │ └── MoneyRaisedComponent.test.js │ │ │ ├── Notification │ │ │ │ └── Notification.js │ │ │ ├── Search │ │ │ │ ├── CodeFuelPixel.js │ │ │ │ ├── ComputationSearchResult.js │ │ │ │ ├── ErrorBoundarySearchResults.js │ │ │ │ ├── FakeSearchResults.js │ │ │ │ ├── FirstSearchView.js │ │ │ │ ├── LogSearchAccountCreationComponent.js │ │ │ │ ├── NewsSearchResults.js │ │ │ │ ├── SearchApp.js │ │ │ │ ├── SearchAuthRedirect.js │ │ │ │ ├── SearchHeartsContainer.js │ │ │ │ ├── SearchMenuComponent.js │ │ │ │ ├── SearchMenuContainer.js │ │ │ │ ├── SearchMenuQuery.js │ │ │ │ ├── SearchPageComponent.js │ │ │ │ ├── SearchPostUninstallView.js │ │ │ │ ├── SearchRandomQueryView.js │ │ │ │ ├── SearchResultErrorMessage.js │ │ │ │ ├── SearchResultItem.js │ │ │ │ ├── SearchResults.js │ │ │ │ ├── SearchResultsBing.js │ │ │ │ ├── SearchResultsCodefuel.js │ │ │ │ ├── SearchResultsQueryBing.js │ │ │ │ ├── SearchResultsQueryCodefuel.js │ │ │ │ ├── SearchTextResult.js │ │ │ │ ├── Settings │ │ │ │ │ ├── SearchProfileInviteFriendComponent.js │ │ │ │ │ ├── SearchProfileInviteFriendView.js │ │ │ │ │ ├── SearchSettingsPageComponent.js │ │ │ │ │ └── __tests__ │ │ │ │ │ │ ├── SearchProfileInviteFriendComponent.test.js │ │ │ │ │ │ ├── SearchProfileInviteFriendView.test.js │ │ │ │ │ │ └── SearchSettingsPageComponent.test.js │ │ │ │ ├── TextAdSearchResult.js │ │ │ │ ├── TimeZoneSearchResult.js │ │ │ │ ├── VideoSearchResults.js │ │ │ │ ├── WebPageSearchResult.js │ │ │ │ ├── WikipediaPageComponent.js │ │ │ │ ├── WikipediaQuery.js │ │ │ │ ├── YPAConfiguration.js │ │ │ │ ├── __mocks__ │ │ │ │ │ ├── SearchPageComponent.js │ │ │ │ │ ├── SearchResultItem.js │ │ │ │ │ ├── fetchWikipediaResults.js │ │ │ │ │ └── getBingMarketCode.js │ │ │ │ ├── __tests__ │ │ │ │ │ ├── ComputationSearchResult.test.js │ │ │ │ │ ├── ErrorBoundarySearchResults.test.js │ │ │ │ │ ├── FakeSearchResults.test.js │ │ │ │ │ ├── FirstSearchView.test.js │ │ │ │ │ ├── LogSearchAccountCreationComponent.test.js │ │ │ │ │ ├── NewsSearchResults.test.js │ │ │ │ │ ├── SearchApp.test.js │ │ │ │ │ ├── SearchAuthRedirect.test.js │ │ │ │ │ ├── SearchMenuComponent.test.js │ │ │ │ │ ├── SearchMenuQuery.test.js │ │ │ │ │ ├── SearchPageComponent.test.js │ │ │ │ │ ├── SearchPostUninstallView.test.js │ │ │ │ │ ├── SearchRandomQueryView.test.js │ │ │ │ │ ├── SearchResultErrorMessage.test.js │ │ │ │ │ ├── SearchResultItem.test.js │ │ │ │ │ ├── SearchResults.test.js │ │ │ │ │ ├── SearchResultsBing.test.js │ │ │ │ │ ├── SearchResultsQueryBing.test.js │ │ │ │ │ ├── SearchTextResult.test.js │ │ │ │ │ ├── TextAdSearchResult.test.js │ │ │ │ │ ├── TimeZoneSearchResult.test.js │ │ │ │ │ ├── VideoSearchResults.test.js │ │ │ │ │ ├── WebPageSearchResult.test.js │ │ │ │ │ ├── WikipediaPageComponent.test.js │ │ │ │ │ ├── WikipediaQuery.test.js │ │ │ │ │ ├── YPAConfiguration.test.js │ │ │ │ │ ├── fetchBingSearchResults.test.js │ │ │ │ │ ├── fetchSearchResults.test.js │ │ │ │ │ ├── fetchWikipediaResults.test.js │ │ │ │ │ └── getBingMarketCode.test.js │ │ │ │ ├── fetchBingSearchResults.js │ │ │ │ ├── fetchCodefuelSearchResults.js │ │ │ │ ├── fetchSearchResults.js │ │ │ │ ├── fetchWikipediaResults.js │ │ │ │ ├── getBingMarketCode.js │ │ │ │ ├── getMockBingSearchResults.js │ │ │ │ ├── getMockCodefuelSearchResults.js │ │ │ │ └── searchResultsStyles.js │ │ │ ├── Settings │ │ │ │ ├── Account │ │ │ │ │ ├── AccountComponent.js │ │ │ │ │ ├── AccountContainer.js │ │ │ │ │ ├── AccountView.js │ │ │ │ │ ├── EnterEmailForm.js │ │ │ │ │ └── __tests__ │ │ │ │ │ │ ├── AccountComponent.test.js │ │ │ │ │ │ ├── AccountView.test.js │ │ │ │ │ │ └── EnterEmailForm.test.js │ │ │ │ ├── Background │ │ │ │ │ ├── BackgroundSettingsComponent.js │ │ │ │ │ ├── BackgroundSettingsContainer.js │ │ │ │ │ ├── BackgroundSettingsView.js │ │ │ │ │ └── __tests__ │ │ │ │ │ │ ├── BackgroundSettingsComponent.test.js │ │ │ │ │ │ └── BackgroundSettingsView.test.js │ │ │ │ ├── Profile │ │ │ │ │ ├── InviteFriendComponent.js │ │ │ │ │ ├── InviteFriendContainer.js │ │ │ │ │ ├── ProfileDonateHeartsComponent.js │ │ │ │ │ ├── ProfileDonateHeartsContainer.js │ │ │ │ │ ├── ProfileDonateHeartsView.js │ │ │ │ │ ├── ProfileInviteFriendComponent.js │ │ │ │ │ ├── ProfileInviteFriendContainer.js │ │ │ │ │ ├── ProfileInviteFriendView.js │ │ │ │ │ ├── ProfileStatsComponent.js │ │ │ │ │ ├── ProfileStatsContainer.js │ │ │ │ │ ├── ProfileStatsView.js │ │ │ │ │ ├── StatComponent.js │ │ │ │ │ └── __tests__ │ │ │ │ │ │ ├── InviteFriendComponent.test.js │ │ │ │ │ │ ├── ProfileDonateHeartsComponent.test.js │ │ │ │ │ │ ├── ProfileDonateHeartsView.test.js │ │ │ │ │ │ ├── ProfileInviteFriendComponent.test.js │ │ │ │ │ │ ├── ProfileInviteFriendView.test.js │ │ │ │ │ │ ├── ProfileStatsComponent.test.js │ │ │ │ │ │ ├── ProfileStatsView.test.js │ │ │ │ │ │ └── StatComponent.test.js │ │ │ │ ├── SettingsChildWrapperComponent.js │ │ │ │ ├── SettingsMenuItem.js │ │ │ │ ├── SettingsPageComponent.js │ │ │ │ ├── TabSettingsPageComponent.js │ │ │ │ ├── Widgets │ │ │ │ │ ├── WidgetConfigComponent.js │ │ │ │ │ ├── WidgetConfigContainer.js │ │ │ │ │ ├── WidgetSettingsComponent.js │ │ │ │ │ ├── WidgetSettingsContainer.js │ │ │ │ │ ├── WidgetsSettingsComponent.js │ │ │ │ │ ├── WidgetsSettingsContainer.js │ │ │ │ │ ├── WidgetsSettingsView.js │ │ │ │ │ └── __tests__ │ │ │ │ │ │ ├── WidgetsSettingsComponent.test.js │ │ │ │ │ │ └── WidgetsSettingsView.test.js │ │ │ │ └── __tests__ │ │ │ │ │ ├── SettingsChildWrapperComponent.test.js │ │ │ │ │ ├── SettingsPageComponent.test.js │ │ │ │ │ └── TabSettingsPageComponent.test.js │ │ │ ├── Shop │ │ │ │ ├── ShopComponent.js │ │ │ │ ├── ShopContainer.js │ │ │ │ └── ShopView.js │ │ │ └── Widget │ │ │ │ ├── ActiveWidgetAnimation.js │ │ │ │ ├── EditWidgetChip.js │ │ │ │ ├── EditWidgetChipAnimation.js │ │ │ │ ├── EmptyWidgetMsg.js │ │ │ │ ├── Leaderboard.js │ │ │ │ ├── WidgetComponent.js │ │ │ │ ├── WidgetContainer.js │ │ │ │ ├── WidgetIFrame.js │ │ │ │ ├── WidgetIconComponent.js │ │ │ │ ├── WidgetIconContainer.js │ │ │ │ ├── WidgetPieceWrapper.js │ │ │ │ ├── WidgetScrollSection.js │ │ │ │ ├── Widgets │ │ │ │ ├── Bookmarks │ │ │ │ │ ├── AddBookmarkForm.js │ │ │ │ │ ├── BookmarkChip.js │ │ │ │ │ ├── BookmarksWidgetComponent.js │ │ │ │ │ ├── BookmarksWidgetContainer.js │ │ │ │ │ ├── EditBookmarkWidgetModal.js │ │ │ │ │ └── __tests__ │ │ │ │ │ │ └── BookmarkChip.test.js │ │ │ │ ├── Clock │ │ │ │ │ ├── ClockWidgetComponent.js │ │ │ │ │ ├── ClockWidgetContainer.js │ │ │ │ │ └── __tests__ │ │ │ │ │ │ └── ClockWidgetComponent.test.js │ │ │ │ ├── Notes │ │ │ │ │ ├── AddNoteForm.js │ │ │ │ │ ├── Note.js │ │ │ │ │ ├── NotesHeader.js │ │ │ │ │ ├── NotesWidgetComponent.js │ │ │ │ │ └── NotesWidgetContainer.js │ │ │ │ ├── Search │ │ │ │ │ ├── Search.js │ │ │ │ │ ├── SearchWidgetComponent.js │ │ │ │ │ ├── SearchWidgetContainer.js │ │ │ │ │ └── __tests__ │ │ │ │ │ │ └── Search.test.js │ │ │ │ ├── Todos │ │ │ │ │ ├── AddTodoForm.js │ │ │ │ │ ├── Todo.js │ │ │ │ │ ├── TodosWidgetComponent.js │ │ │ │ │ └── TodosWidgetContainer.js │ │ │ │ └── WidgetPieceWrapper.js │ │ │ │ ├── WidgetsComponent.js │ │ │ │ ├── WidgetsContainer.js │ │ │ │ ├── __tests__ │ │ │ │ └── WidgetsComponent.test.js │ │ │ │ └── widget-utils.js │ │ ├── constants.js │ │ ├── mutations │ │ │ ├── CreateNewUserMutation.js │ │ │ ├── CreateSfacExtensionPromptResponseMutation.js │ │ │ ├── CreateVideoAdLogMutation.js │ │ │ ├── DeleteUserMutation.js │ │ │ ├── DonateVcMutation.js │ │ │ ├── LogEmailVerifiedMutation.js │ │ │ ├── LogReferralLinkClickMutation.js │ │ │ ├── LogSearchMutation.js │ │ │ ├── LogTabMutation.js │ │ │ ├── LogUserExperimentActionsMutation.js │ │ │ ├── LogUserRevenueMutation.js │ │ │ ├── LogVideoAdCompleteMutation.js │ │ │ ├── MergeIntoExistingUserMutation.js │ │ │ ├── SetBackgroundColorMutation.js │ │ │ ├── SetBackgroundCustomImageMutation.js │ │ │ ├── SetBackgroundDailyImageMutation.js │ │ │ ├── SetBackgroundImageMutation.js │ │ │ ├── SetEmailMutation.js │ │ │ ├── SetUserActiveWidgetMutation.js │ │ │ ├── SetUserCauseMutation.js │ │ │ ├── SetUsernameMutation.js │ │ │ ├── SetV4BetaMutation.js │ │ │ ├── UpdateUserExperimentGroupsMutation.js │ │ │ ├── UpdateWidgetConfigMutation.js │ │ │ ├── UpdateWidgetDataMutation.js │ │ │ ├── UpdateWidgetEnabledMutation.js │ │ │ ├── __mocks__ │ │ │ │ ├── CreateNewUserMutation.js │ │ │ │ ├── DonateVcMutation.js │ │ │ │ ├── LogEmailVerifiedMutation.js │ │ │ │ ├── LogReferralLinkClickMutation.js │ │ │ │ ├── LogSearchMutation.js │ │ │ │ ├── LogTabMutation.js │ │ │ │ ├── MergeIntoExistingUserMutation.js │ │ │ │ ├── SetBackgroundDailyImageMutation.js │ │ │ │ ├── SetUsernameMutation.js │ │ │ │ └── SetV4BetaMutation.js │ │ │ └── __tests__ │ │ │ │ ├── CreateSfacExtensionPromptResponseMutation.test.js │ │ │ │ ├── DonateVcMutation.test.js │ │ │ │ ├── LogReferralLinkClickMutation.test.js │ │ │ │ ├── LogSearchMutation.test.js │ │ │ │ ├── LogTabMutation.test.js │ │ │ │ ├── LogUserExperimentActionsMutation.test.js │ │ │ │ ├── LogUserRevenueMutation.test.js │ │ │ │ └── SetV4BetaMutation.test.js │ │ ├── navigation │ │ │ ├── __mocks__ │ │ │ │ ├── navigation.js │ │ │ │ └── utils.js │ │ │ ├── __tests__ │ │ │ │ ├── navigation.test.js │ │ │ │ └── utils.test.js │ │ │ ├── navigation.js │ │ │ └── utils.js │ │ ├── relay-env.js │ │ ├── root.js │ │ ├── serviceWorker.js │ │ ├── theme │ │ │ ├── __tests__ │ │ │ │ └── searchTheme.test.js │ │ │ ├── default.js │ │ │ ├── defaultV1.js │ │ │ └── searchTheme.js │ │ └── utils │ │ │ ├── __mocks__ │ │ │ ├── client-location.js │ │ │ ├── detectAdblocker.js │ │ │ ├── experiments.js │ │ │ ├── extension-messenger.js │ │ │ ├── localstorage-mgr.js │ │ │ ├── search-utils.js │ │ │ ├── utils.js │ │ │ └── v4-beta-opt-in.js │ │ │ ├── __tests__ │ │ │ ├── awaitTimeLimit.test.js │ │ │ ├── browserSupport.test.js │ │ │ ├── detectAdblocker.test.js │ │ │ ├── detectBrowser.test.js │ │ │ ├── experimentFilters.test.js │ │ │ ├── experiments.test.js │ │ │ ├── feature-flags.test.js │ │ │ ├── initializeCMP.test.js │ │ │ ├── local-bkg-settings.test.js │ │ │ ├── local-user-data-mgr.test.js │ │ │ ├── logger.test.js │ │ │ ├── search-utils.test.js │ │ │ ├── switchToV4.test.js │ │ │ ├── test-utils-search.test.js │ │ │ ├── test-utils.test.js │ │ │ ├── useTruex.test.js │ │ │ ├── utils.test.js │ │ │ └── v4-beta-opt-in.test.js │ │ │ ├── awaitTimeLimit.js │ │ │ ├── browserSupport.js │ │ │ ├── detectAdblocker.js │ │ │ ├── detectBrowser.js │ │ │ ├── errors.js │ │ │ ├── experimentFilters.js │ │ │ ├── experiments.js │ │ │ ├── extension-messenger.js │ │ │ ├── feature-flags.js │ │ │ ├── getFeatureValue.js │ │ │ ├── hooks │ │ │ ├── __tests__ │ │ │ │ ├── useBrowserInfo.test.js │ │ │ │ ├── useBrowserName.test.js │ │ │ │ ├── useDoesBrowserSupportSearchExtension.test.js │ │ │ │ └── useDoesBrowserSupportShopExtension.test.js │ │ │ ├── useBrowserInfo.js │ │ │ ├── useBrowserName.js │ │ │ ├── useDoesBrowserSupportSearchExtension.js │ │ │ ├── useDoesBrowserSupportShopExtension.js │ │ │ ├── useInterval.js │ │ │ └── useMoneyRaised.js │ │ │ ├── initializeCMP.js │ │ │ ├── jsdom-shims.js │ │ │ ├── local-bkg-settings.js │ │ │ ├── local-user-data-mgr.js │ │ │ ├── localstorage-mgr.js │ │ │ ├── logger.js │ │ │ ├── search-utils.js │ │ │ ├── switchToV4.js │ │ │ ├── test-utils-search.js │ │ │ ├── test-utils.js │ │ │ ├── truex.js │ │ │ ├── useTrueX.js │ │ │ ├── utils.js │ │ │ ├── v4-beta-opt-in.js │ │ │ └── widgets-utils.js │ ├── logo.svg │ ├── searchQuery.js │ └── setupTests.js └── yarn.lock └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | build 4 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb", "prettier"], 3 | "plugins": ["prettier"], 4 | "rules": { 5 | "prettier/prettier": "error", 6 | "global-require": 0, 7 | "no-console": 0, 8 | "no-underscore-dangle": 0 9 | }, 10 | "parserOptions": { 11 | "ecmaVersion": 2018, 12 | "sourceType": "module" 13 | }, 14 | "env": { 15 | "es6": true 16 | }, 17 | "settings": { 18 | "react": { 19 | "version": "18" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | npm-debug.log 4 | web/jest 5 | .env.local 6 | 7 | # serverless 8 | .serverless 9 | 10 | # yarn 11 | yarn-error.log 12 | 13 | .yalc 14 | yalc.lock 15 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": true, 3 | "jsxBracketSameLine": false, 4 | "useTabs": false, 5 | "semi": false, 6 | "singleQuote": true, 7 | "tabWidth": 2, 8 | "trailingComma": es5, 9 | } 10 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | 2 | version: '2' 3 | services: 4 | 5 | dynamodb: 6 | restart: always 7 | build: 8 | context: ./dynamodb/ 9 | expose: 10 | - "8000" 11 | ports: 12 | - "8000:8000" 13 | volumes: 14 | - ./dynamodb:/dynamodb 15 | - ./dynamodb/node_modules:/dynamodb/node_modules 16 | 17 | dbadmin: 18 | restart: always 19 | image: wheniwork/dynamodb-admin 20 | expose: 21 | - "8001" 22 | ports: 23 | - "8001:8001" 24 | depends_on: 25 | - dynamodb 26 | env_file: 27 | - ./dynamodb/.env 28 | environment: 29 | - DYNAMO_ENDPOINT=dynamodb:8000 30 | -------------------------------------------------------------------------------- /dynamodb/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /dynamodb/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | .DS_Store 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /dynamodb/.env: -------------------------------------------------------------------------------- 1 | DYNAMODB_ENDPOINT=http://localhost:8000 2 | AWS_REGION=us-west-2 3 | AWS_ACCESS_KEY_ID=fakeKey123 4 | AWS_SECRET_ACCESS_KEY=fakeSecretKey456 5 | DB_TABLE_NAME_APPENDIX="" 6 | -------------------------------------------------------------------------------- /dynamodb/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | build 4 | -------------------------------------------------------------------------------- /dynamodb/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb", "prettier"], 3 | "plugins": ["prettier"], 4 | "rules": { 5 | "prettier/prettier": "error", 6 | "global-require": 0, 7 | "no-underscore-dangle": 0 8 | }, 9 | "parserOptions": { 10 | "ecmaVersion": 2018, 11 | "sourceType": "module" 12 | }, 13 | "env": { 14 | "es6": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /dynamodb/.gitignore: -------------------------------------------------------------------------------- 1 | .serverless/ 2 | coverage 3 | .env.local 4 | -------------------------------------------------------------------------------- /dynamodb/.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | build 4 | -------------------------------------------------------------------------------- /dynamodb/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": true, 3 | "jsxBracketSameLine": false, 4 | "useTabs": false, 5 | "semi": false, 6 | "singleQuote": true, 7 | "tabWidth": 2, 8 | "trailingComma": es5, 9 | } 10 | -------------------------------------------------------------------------------- /dynamodb/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:20.04 2 | 3 | RUN mkdir /opt/dynamodb 4 | RUN mkdir /opt/dynamodb/db 5 | 6 | WORKDIR /opt/dynamodb 7 | 8 | RUN apt-get update && apt-get install -y \ 9 | curl \ 10 | default-jre \ 11 | wget 12 | 13 | RUN wget -O /tmp/dynamodb https://s3-us-west-2.amazonaws.com/dynamodb-local/dynamodb_local_latest.tar.gz 14 | RUN tar xfz /tmp/dynamodb 15 | 16 | # Install Node. 17 | RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - \ 18 | && apt-get install -y nodejs 19 | 20 | ENTRYPOINT ["java", "-Djava.library.path=.", "-jar", "/opt/dynamodb/DynamoDBLocal.jar", "-dbPath", "/opt/dynamodb/db", "-sharedDb"] 21 | CMD ["-port", "8000"] 22 | 23 | VOLUME ["/opt/dynamodb/db"] 24 | 25 | EXPOSE 8000 26 | 27 | RUN mkdir /dynamodb 28 | WORKDIR /dynamodb 29 | -------------------------------------------------------------------------------- /dynamodb/aws-client.js: -------------------------------------------------------------------------------- 1 | /* eslint import/no-extraneous-dependencies: 0 */ 2 | 3 | const path = require('path') 4 | const AWS = require('aws-sdk') 5 | 6 | // Load environment variables from .env file. 7 | require('dotenv-extended').load({ 8 | path: path.join(__dirname, '.env.local'), 9 | defaults: path.join(__dirname, '.env'), 10 | }) 11 | 12 | AWS.config.update({ 13 | region: process.env.AWS_REGION, 14 | endpoint: process.env.DYNAMODB_ENDPOINT, 15 | accessKeyId: process.env.AWS_ACCESS_KEY_ID, 16 | secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, 17 | }) 18 | 19 | module.exports = AWS 20 | -------------------------------------------------------------------------------- /dynamodb/dynamodb-local-metadata.json: -------------------------------------------------------------------------------- 1 | {"installationId":"ed69919f-a6b0-40d5-885d-d085dd441233","telemetryEnabled":"true"} -------------------------------------------------------------------------------- /dynamodb/fixtures/InvitedUsers.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inviterId": "cL5KcFKHd9fEU5C9Vstj3g4JAc73", 4 | "invitedEmail": "alec+07@tabforacause.org", 5 | "invitedId": "12341234", 6 | "created": "2020-07-19T03:05:12Z" 7 | }, 8 | { 9 | "inviterId": "cL5KcFKHd9fEU5C9Vstj3g4JAc73", 10 | "invitedEmail": "alec+09@tabforacause.org", 11 | "created": "2020-07-19T03:05:12Z" 12 | }, 13 | { 14 | "inviterId": "shouldNotBeQueried", 15 | "invitedEmail": "alec+09@tabforacause.org", 16 | "created": "2020-07-19T03:05:12Z" 17 | } 18 | ] 19 | -------------------------------------------------------------------------------- /dynamodb/fixtures/ReferralDataLog.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "userId": "pqrstuvwxyzabcd", 4 | "referringUser": "abcdefghijklmno", 5 | "referringChannel": null, 6 | "created": "2017-07-19T03:05:12Z", 7 | "updated": "2017-07-19T03:05:12Z" 8 | }, 9 | { 10 | "userId": "efghijklmnopqrs", 11 | "referringUser": "abcdefghijklmno", 12 | "referringChannel": null, 13 | "created": "2017-07-19T18:41:30Z", 14 | "updated": "2017-07-19T18:41:30Z" 15 | }, 16 | { 17 | "userId": "tuvwxyzabcdefgh", 18 | "referringUser": "abcdefghijklmno", 19 | "referringChannel": null, 20 | "created": "2017-07-23T01:18:11Z", 21 | "updated": "2017-07-23T01:18:11Z" 22 | } 23 | ] 24 | -------------------------------------------------------------------------------- /dynamodb/fixtures/UserEventLog.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "bb5082cc-151a-4a9a-9289-06906670fd4e", 4 | "userId": "abcdefghijklmno", 5 | "type": "SFAC_EXTENSION_PROMPT", 6 | "timestamp": "2017-07-17T20:45:53Z", 7 | "eventData": { 8 | "browser": "chrome", 9 | "switched": true 10 | } 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /dynamodb/fixtures/UserExperiment.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "userId": "abcdefghijklmno", 4 | "experimentId": "test-1", 5 | "variationId": 0, 6 | "variationValueStr": "false", 7 | "timestampAssigned": "2017-07-17T20:45:53Z" 8 | }, 9 | { 10 | "userId": "abcdefghijklmno", 11 | "experimentId": "test-2", 12 | "variationId": 2, 13 | "variationValueStr": "'some value here'", 14 | "timestampAssigned": "2017-07-17T20:45:53Z" 15 | } 16 | ] 17 | -------------------------------------------------------------------------------- /dynamodb/fixtures/UserGroupImpactMetricLog.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "abcdefghijklmno", 4 | "userId": "abcdefghijklmno", 5 | "causeId": "abcd", 6 | "impactMetricId": "12345", 7 | "dollarContribution": 10000, 8 | "tabDollarContribution": 1000, 9 | "searchDollarContribution": 100, 10 | "shopDollarContribuition": 200, 11 | "referralDollarContribution": 8700, 12 | "dateStarted": "2017-07-17T20:45:53Z" 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /dynamodb/fixtures/UserImpactData.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "userId": "abcdefghijklmno", 4 | "charityId": "6ce5ad8e-7dd4-4de5-ba4f-13868e7d212z", 5 | "userImpactMetric": 3, 6 | "pendingUserReferralImpact": 10, 7 | "pendingUserReferralCount": 1, 8 | "visitsUntilNextImpact": 3, 9 | "confirmedImpact": true, 10 | "hasClaimedLatestReward": false 11 | }, 12 | { 13 | "userId": "cL5KcFKHd9fEU5C9Vstj3g4JAc73", 14 | "charityId": "6ce5ad8e-7dd4-4de5-ba4f-13868e7d212z", 15 | "userImpactMetric": 3, 16 | "pendingUserReferralImpact": 10, 17 | "pendingUserReferralCount": 1, 18 | "visitsUntilNextImpact": 3, 19 | "confirmedImpact": true, 20 | "hasClaimedLatestReward": false 21 | } 22 | ] 23 | -------------------------------------------------------------------------------- /dynamodb/fixtures/UserRevenueLog.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /dynamodb/fixtures/UserSearchSettingsLog.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "userId": "abcdefghijklmno", 4 | "previousEngine": "yahoo", 5 | "newEngine": "google", 6 | "timestamp": "2017-07-17T20:45:53Z" 7 | } 8 | ] 9 | -------------------------------------------------------------------------------- /dynamodb/fixtures/UserSwitchSearchPromptLog.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "userId": "abcdefghijklmno", 4 | "searchEnginePrompted": "yahoo", 5 | "switched": true, 6 | "timestamp": "2017-07-17T20:45:53Z" 7 | } 8 | ] 9 | -------------------------------------------------------------------------------- /dynamodb/fixtures/VcDonationLog.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /dynamodb/fixtures/VideoAdLog.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "userId": "abcdefghijklmno", 4 | "timestamp": "2017-07-18T20:45:53Z", 5 | "completed": false, 6 | "id": "99045a67-842f-4266-85ce-153b4a79c571" 7 | }, 8 | { 9 | "userId": "abcdefghijklmno", 10 | "timestamp": "2017-07-17T20:45:53Z", 11 | "completed": true, 12 | "id": "9f2c0624-6410-4753-b196-06bd9ca767f6", 13 | "truexEngagementId": "asdf", 14 | "truexCreativeId": "1234" 15 | } 16 | ] 17 | -------------------------------------------------------------------------------- /dynamodb/scripts/confirmCommand.js: -------------------------------------------------------------------------------- 1 | /* eslint no-console: 0 */ 2 | import prompt from './promptUserInput' 3 | 4 | // Safety check to prevent accidentally running database operations 5 | // against a production DB. 6 | const confirmCommand = callback => { 7 | const databaseEndpoint = process.env.DYNAMODB_ENDPOINT 8 | 9 | // Standard local dev enpoint. 10 | if (databaseEndpoint === 'http://localhost:8000') { 11 | callback() 12 | return 13 | } 14 | 15 | prompt( 16 | `You are running against a database at ${databaseEndpoint}. Are you sure you want to continue? (y/n)\n`, 17 | response => { 18 | if (response !== 'y') { 19 | console.log('Exiting.') 20 | process.exit() 21 | } else { 22 | callback() 23 | } 24 | } 25 | ) 26 | } 27 | 28 | export default confirmCommand 29 | -------------------------------------------------------------------------------- /dynamodb/scripts/deleteTables.js: -------------------------------------------------------------------------------- 1 | /* eslint no-console: 0 */ 2 | import AWS from '../aws-client' 3 | import confirmCommand from './confirmCommand' 4 | import getTableInfo from './getTableInfo' 5 | 6 | const dynamodb = new AWS.DynamoDB() 7 | 8 | const tables = getTableInfo() 9 | 10 | const deleteTable = tableConfig => { 11 | const params = { 12 | TableName: tableConfig.TableName, 13 | } 14 | dynamodb.deleteTable(params, err => { 15 | if (err) { 16 | console.error( 17 | `Unable to delete table "${ 18 | tableConfig.TableName 19 | }". Error JSON: ${JSON.stringify(err, null, 2)}` 20 | ) 21 | } else { 22 | console.log(`Deleted table "${tableConfig.TableName}".`) 23 | } 24 | }) 25 | } 26 | 27 | confirmCommand(() => { 28 | tables.forEach(table => deleteTable(table)) 29 | }) 30 | -------------------------------------------------------------------------------- /dynamodb/scripts/getTableInfo.js: -------------------------------------------------------------------------------- 1 | /* eslint import/no-extraneous-dependencies: 0 */ 2 | import fs from 'fs' 3 | import path from 'path' 4 | import { map } from 'lodash/collection' 5 | import { cloneDeep } from 'lodash/lang' 6 | 7 | const tablesJsonFile = 'tables.json' 8 | const tablesInfoRaw = JSON.parse( 9 | fs.readFileSync(path.join(__dirname, `../${tablesJsonFile}`), 'utf8') 10 | ) 11 | 12 | const getTableInfo = () => { 13 | // Add an appendix to the table name if required. 14 | const tableNameAppendix = process.env.DB_TABLE_NAME_APPENDIX 15 | ? process.env.DB_TABLE_NAME_APPENDIX 16 | : '' 17 | 18 | return map(tablesInfoRaw, tableInfo => { 19 | const newTableInfo = cloneDeep(tableInfo) 20 | newTableInfo.TableName = `${newTableInfo.TableName}${tableNameAppendix}` 21 | return newTableInfo 22 | }) 23 | } 24 | 25 | export default getTableInfo 26 | -------------------------------------------------------------------------------- /dynamodb/scripts/promptUserInput.js: -------------------------------------------------------------------------------- 1 | export default function(question, callback) { 2 | const { stdin, stdout } = process 3 | stdin.resume() 4 | stdout.write(question) 5 | 6 | stdin.once('data', data => { 7 | callback(data.toString().trim()) 8 | }) 9 | } 10 | -------------------------------------------------------------------------------- /graphql/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | schema.json 4 | schema.graphql 5 | build/* 6 | -------------------------------------------------------------------------------- /graphql/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['airbnb', 'prettier'], 4 | plugins: ['prettier'], 5 | rules: { 6 | 'prettier/prettier': 'error', 7 | 'global-require': 0, 8 | 'no-underscore-dangle': 0, 9 | // TODO: reenable these rules after cleaning code: 10 | 'prefer-object-spread': 0, 11 | 'arrow-body-style': 0, 12 | 'no-useless-catch': 0, 13 | 'import/no-useless-path-segments': 0, 14 | }, 15 | parserOptions: { 16 | ecmaVersion: 2021, 17 | sourceType: 'module', 18 | }, 19 | env: { 20 | es6: true, 21 | }, 22 | settings: { 23 | react: { 24 | version: '18', 25 | }, 26 | }, 27 | } 28 | -------------------------------------------------------------------------------- /graphql/.gitignore: -------------------------------------------------------------------------------- 1 | .serverless/ 2 | coverage 3 | build/ 4 | .env.local 5 | amazon-cpm-codes.json 6 | amazon-cpm-codes-v2.json 7 | DBActionHelpers.js 8 | data/schema.json 9 | -------------------------------------------------------------------------------- /graphql/.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | schema.json 4 | schema.graphql 5 | -------------------------------------------------------------------------------- /graphql/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": true, 3 | "jsxBracketSameLine": false, 4 | "useTabs": false, 5 | "semi": false, 6 | "singleQuote": true, 7 | "tabWidth": 2, 8 | "trailingComma": es5, 9 | } 10 | -------------------------------------------------------------------------------- /graphql/__mocks__/@sentry/node.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | const SentryNode = jest.genMockFromModule('@sentry/node') 4 | module.exports = SentryNode 5 | -------------------------------------------------------------------------------- /graphql/__mocks__/jssha.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | const jsSHAInstance = { 4 | getHash: jest.fn(), 5 | update: jest.fn(), 6 | } 7 | 8 | const jsSHAMock = jest.fn(() => jsSHAInstance) 9 | 10 | export default jsSHAMock 11 | -------------------------------------------------------------------------------- /graphql/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@babel/preset-env'], 3 | plugins: [ 4 | [ 5 | '@babel/plugin-transform-runtime', 6 | { 7 | regenerator: true, 8 | }, 9 | ], 10 | [ 11 | 'content-transformer', 12 | { 13 | transformers: [ 14 | // Convert import statements ending with ".md" into strings 15 | { 16 | file: /\.md$/, 17 | format: 'string', 18 | }, 19 | ], 20 | }, 21 | ], 22 | ], 23 | } 24 | -------------------------------------------------------------------------------- /graphql/database/__mocks__/@growthbook/growthbook.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | const { GrowthBook } = jest.requireActual('@growthbook/growthbook') 3 | const mockGrowthBook = jest.genMockFromModule('@growthbook/growthbook') 4 | 5 | mockGrowthBook.GrowthBook = jest.fn(() => new GrowthBook()) 6 | 7 | module.exports = mockGrowthBook 8 | -------------------------------------------------------------------------------- /graphql/database/backgroundImages/getRandomBackgroundImage.js: -------------------------------------------------------------------------------- 1 | import { sample } from 'lodash/collection' 2 | import getBackgroundImages from './getBackgroundImages' 3 | 4 | /** 5 | * Fetch a random background image by category. 6 | * @return {Promise} A promise that resolves 7 | * into a BackgroundImage instance. 8 | */ 9 | 10 | export default async (userContext, category) => { 11 | const images = await getBackgroundImages(userContext, category) 12 | return sample(images) 13 | } 14 | -------------------------------------------------------------------------------- /graphql/database/base/__mocks__/UserModel.query.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | const query = require('dynogels/lib/query').prototype 4 | 5 | // Mock each method, returning `this` so it is chainable. 6 | const mockQuery = {} 7 | Object.keys(query).forEach((method) => { 8 | mockQuery[method] = jest.fn(() => mockQuery) 9 | }) 10 | 11 | // Our custom method. 12 | mockQuery.execute = jest.fn() 13 | 14 | module.exports = jest.fn(() => mockQuery) 15 | -------------------------------------------------------------------------------- /graphql/database/base/__mocks__/dynogels-promisified.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | module.exports = { 4 | define: jest.fn(), 5 | documentClient: jest.fn(), 6 | } 7 | -------------------------------------------------------------------------------- /graphql/database/base/__tests__/DynamoDBModel-properties.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | jest.mock('../../databaseClient') 4 | 5 | describe('DynamoDBModel required properties', () => { 6 | afterEach(() => { 7 | jest.resetModules() 8 | }) 9 | 10 | it('fails if "tableName" property is not set', () => { 11 | const TestModel = require('../test-utils/ExampleDynamoDBModel').default 12 | delete TestModel.tableName 13 | expect(() => { 14 | TestModel.register() 15 | }).toThrow() 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/blackEquity/onboarding.step1.subtitle.md: -------------------------------------------------------------------------------- 1 | Now, every tab you open supports work to uplift Black communities through systemic reform and access to resources. 2 | 3 | Tabbers like you are supporting critical nonprofit work all around the world. Thank you! 4 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/blackEquity/onboarding.step1.title.md: -------------------------------------------------------------------------------- 1 | ### Your tabs are doing great things 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/blackEquity/onboarding.step2.subtitle.md: -------------------------------------------------------------------------------- 1 | Support Black equity even faster with a squad! 2 | 3 | You and your friends can team up to do even more good. 4 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/blackEquity/onboarding.step2.title.md: -------------------------------------------------------------------------------- 1 | ### Do more with your squad 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/blackEquity/onboarding.step3.subtitle.md: -------------------------------------------------------------------------------- 1 | Ads on the new tab page raise money that we give to nonprofits. Most ads aren't good—but these ones are :) 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/blackEquity/onboarding.step3.title.md: -------------------------------------------------------------------------------- 1 | ### It doesn't cost you a thing 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/blackEquity/social.email.about.md: -------------------------------------------------------------------------------- 1 |
Tab for Black Equity turns your web browser into a force for good. With each tab opened, ad revenue supports The Bail Project and Thurgood Marshall College Fund, uplifting the Black community through education, advocacy, and life-changing access to resources.
2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/blackEquity/social.subtitle.md: -------------------------------------------------------------------------------- 1 | ##### Everyone can and _should_ make a difference: invite a friend to join in. 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/blackEquity/social.title.md: -------------------------------------------------------------------------------- 1 | ### **Get a friend on board** 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/cats/impact.claimImpactSubtitle.md: -------------------------------------------------------------------------------- 1 | ##### You did it! You just turned your tab into a treat for a cat. Keep it up! 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/cats/impact.confirmImpactSubtitle.md: -------------------------------------------------------------------------------- 1 | ##### Each time you open a tab, you'll be helping shelter cats get adopted by [providing treats to be used in positive reinforcement training](https://greatergood.org/jackson-galaxy). Ready to get started? 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/cats/impact.impactCounterText.md: -------------------------------------------------------------------------------- 1 | ##### **Your pawsitive impact!** 2 | 3 | This shows how many treats your tabs can provide to help shelter cats get adopted. Every tab you open helps. Keep it up! 4 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/cats/impact.impactWalkthroughText.md: -------------------------------------------------------------------------------- 1 | ##### When you do, you'll give a shelter cat a treat used during positive-reinforcement training. We'll track how many treats you've given on the top of the page: 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/cats/impact.newlyReferredImpactWalkthroughText.md: -------------------------------------------------------------------------------- 1 | ##### Your friend started you off with 5 cat treats, which are crucial to getting shelter cats adopted. Open a new tab now to earn your 6th treat! We'll track how many treats you've given on the top of the page: 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/cats/impact.referralRewardNotification.md: -------------------------------------------------------------------------------- 1 | ##### Congrats! You recruited a friend to help shelter cats just by opening tabs. To celebrate, we'll give a treat to an extra ${pendingUserReferralImpact} cat${isPlural(pendingUserReferralImpact)}. 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/cats/impact.referralRewardSubtitle.md: -------------------------------------------------------------------------------- 1 | ##### Congratulations! You're making a huge impact for these animals in need. Want to help even more cats? Invite a few more friends! 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/cats/impact.referralRewardTitle.md: -------------------------------------------------------------------------------- 1 | #### You just put \${claimedReferralImpact} cats on track for adoption! 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/cats/onboarding.step1.subtitle.md: -------------------------------------------------------------------------------- 1 | Now, every tab you open supports cats in need. 2 | 3 | Tabbers like you are supporting critical nonprofit work all around the world. Your tabs support initiatives that help shelter cats get adopted, including initiatives that [use treats in positive reinforcement training](https://greatergood.org/jackson-galaxy). 4 | 5 | Thank you! 6 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/cats/onboarding.step1.title.md: -------------------------------------------------------------------------------- 1 | ### Your tabs are doing great things 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/cats/onboarding.step2.subtitle.md: -------------------------------------------------------------------------------- 1 | Cats can get adopted up to 3x faster when you join a squad! 2 | 3 | Team up with your friends to help pay for a shelter cat's house training. Training a cat is the best way to help it find a permanent home, and it enriches the cat's day to day life while in the shelter. 4 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/cats/onboarding.step2.title.md: -------------------------------------------------------------------------------- 1 | ### Help more cats with squads 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/cats/onboarding.step3.subtitle.md: -------------------------------------------------------------------------------- 1 | Ads on the new tab page raise money that we give to nonprofits. Most ads aren't good—but these ones are :) 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/cats/onboarding.step3.title.md: -------------------------------------------------------------------------------- 1 | ### It doesn't cost you a thing 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/cats/social.email.about.md: -------------------------------------------------------------------------------- 1 |

Tab for Cats turns your web browser into a paradise with daily cat background photos. On top of that, you’ll be helping shelter cats get adopted every time you open a new tab, for free.

2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/cats/social.email.faq.md: -------------------------------------------------------------------------------- 1 | We've partnered with Greater Good's The Jackson Galaxy Project to ensure we make a big impact for these amazing animals! 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/cats/social.subtitle.md: -------------------------------------------------------------------------------- 1 | ##### Save more cats! When a friend signs up, you'll each earn 5 additional treats to help a shelter cat get adopted. 😺 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/cats/social.title.md: -------------------------------------------------------------------------------- 1 | ### **Share Tab for Cats with your friends** 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/democracy/about.md: -------------------------------------------------------------------------------- 1 | **VoteAmerica** works to strengthen American democracy by removing barriers to voting. The end result is a larger and more diverse electorate – one that is better prepared to hold lawmakers accountable. They have a three-prong approach: 2 | 3 | 1. **Software Development**: They build easy-to-use software that helps people navigate needlessly complicated voting processes. 4 | 2. **Voter Registration and Mobilization**: They run large-scale voter registration and mobilization campaigns. They are goal-oriented and tactic-agnostic, which means they’ll abandon any tactic that stops producing results. 5 | 3. **Impact Litigation**: They engage in impact litigation against states that pass voter suppression laws. They recently won lawsuits against Kansas and Georgia. 6 | 7 | Learn more at [VoteAmerica](https://about.voteamerica.com/). 8 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/democracy/onboarding.step1.subtitle.md: -------------------------------------------------------------------------------- 1 | Now, every tab you open supports VoteAmerica in their effort to strengthen our democracy. 2 | 3 | Tabbers like you are supporting critical nonprofit work all around the world. Thank you! 4 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/democracy/onboarding.step1.title.md: -------------------------------------------------------------------------------- 1 | ### Your tabs are doing great things 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/democracy/onboarding.step2.subtitle.md: -------------------------------------------------------------------------------- 1 | Help support democracy by getting your friends to join. You and your friends can team up to do even more good. 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/democracy/onboarding.step2.title.md: -------------------------------------------------------------------------------- 1 | ### Have a larger impact with friends 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/democracy/onboarding.step3.subtitle.md: -------------------------------------------------------------------------------- 1 | Ads on the new tab page raise money that we give to nonprofits. Most ads aren't good—but these ones are :) 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/democracy/onboarding.step3.title.md: -------------------------------------------------------------------------------- 1 | ### It doesn't cost you a thing 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/democracy/social.email.about.md: -------------------------------------------------------------------------------- 1 |
Tab for Democracy turns your web browser into a force for good. With each tab opened, ad revenue supports VoteAmerica, strengthening our democracy via increased voter engagement.
2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/democracy/social.subtitle.md: -------------------------------------------------------------------------------- 1 | ##### Everyone can and _should_ make a difference: invite a friend to join in. 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/democracy/social.title.md: -------------------------------------------------------------------------------- 1 | ### **Get a friend on board** 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/disasterRelief/about.md: -------------------------------------------------------------------------------- 1 | ### About Tab for Disaster Relief 2 | 3 | **Tab for Disaster Relief** supports [Direct Relief](https://www.directrelief.org/about/) and [World Central Kitchen](https://wck.org/relief) 4 | 5 | Direct Relief: Direct Relief is a humanitarian aid organization, active in all 50 states and more than 80 countries, with a mission to improve the health and lives of people affected by poverty or emergencies – without regard to politics, religion, or ability to pay. Learn more about their guiding principles here: https://www.directrelief.org/about/principles/ 6 | 7 | Wold Central Kitchen: WCK responds to natural disasters, man-made crises, and humanitarian emergencies around the world. We're a team of food first responders, mobilizing with the urgency of now to get meals to the people who need them most. 8 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/disasterRelief/onboarding.step1.subtitle.md: -------------------------------------------------------------------------------- 1 | Now, every tab you open supports World Central Kitchen and Direct Relief in their effort to respond to natural disasters. 2 | 3 | Tabbers like you are supporting critical nonprofit work all around the world. Thank you! 4 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/disasterRelief/onboarding.step1.title.md: -------------------------------------------------------------------------------- 1 | ### Your tabs are doing great things 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/disasterRelief/onboarding.step2.subtitle.md: -------------------------------------------------------------------------------- 1 | Help provide disaster relief by getting your friends to join. 2 | 3 | You and your friends can team up to do even more good. 4 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/disasterRelief/onboarding.step2.title.md: -------------------------------------------------------------------------------- 1 | ### Have a larger impact with friends 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/disasterRelief/onboarding.step3.subtitle.md: -------------------------------------------------------------------------------- 1 | Ads on the new tab page raise money that we give to nonprofits. Most ads aren't good—but these ones are :) 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/disasterRelief/onboarding.step3.title.md: -------------------------------------------------------------------------------- 1 | ### It doesn't cost you a thing 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/disasterRelief/social.email.about.md: -------------------------------------------------------------------------------- 1 |
Tab for Disaster Relief turns your web browser into a force for good. With each tab opened, ad revenue supports Direct Relief and World Central Kitchen, responding to disasters with food and medical care.
2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/disasterRelief/social.subtitle.md: -------------------------------------------------------------------------------- 1 | ##### Everyone can and _should_ make a difference: invite a friend to join in. 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/disasterRelief/social.title.md: -------------------------------------------------------------------------------- 1 | ### **Get a friend on board** 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/teamseas/impact.claimImpactSubtitle.md: -------------------------------------------------------------------------------- 1 | ##### You did it! You just turned your tabs into removing even more trash from rivers and oceans. Keep it up! 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/teamseas/impact.confirmImpactSubtitle.md: -------------------------------------------------------------------------------- 1 | ##### Each time you open a tab, you'll be restoring the environment by [removing trash from our oceans and rivers](https://teamseas.org/). Ready to get started? 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/teamseas/impact.impactCounterText.md: -------------------------------------------------------------------------------- 1 | ##### **Your #teamseas impact!** 2 | 3 | This shows how many plastic water bottles' worth of trash you've helped remove from our seas. Nice work! 4 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/teamseas/impact.impactWalkthroughText.md: -------------------------------------------------------------------------------- 1 | ##### When you do, you'll remove a plastic water bottle's worth of trash from a river or ocean. We'll track how many plastic bottles' of trash you've helped clean up on the top of the page: 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/teamseas/impact.newlyReferredImpactWalkthroughText.md: -------------------------------------------------------------------------------- 1 | ##### Your friend gave you a boost: you've already removed 5 plastic water bottles' worth of trash from our rivers and oceans! Open a new tab now to clean up your 6th water bottle. We'll track how many water bottles' worth of trash you've helped clean up on the top of the page: 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/teamseas/impact.referralRewardNotification.md: -------------------------------------------------------------------------------- 1 | ##### Congrats! You recruited a friend to support #teamseas. To celebrate, we'll remove an extra ${pendingUserReferralImpact} water bottle${isPlural(pendingUserReferralImpact)} of trash from our oceans and rivers. 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/teamseas/impact.referralRewardSubtitle.md: -------------------------------------------------------------------------------- 1 | ##### Congratulations! You're making a huge impact on our oceans and rivers. Want to help our seas even more? Invite a few more friends! 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/teamseas/impact.referralRewardTitle.md: -------------------------------------------------------------------------------- 1 | ##### **You just cleaned up \${claimedReferralImpact} water bottles' worth of trash from our oceans and rivers** 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/teamseas/onboarding.step1.subtitle.md: -------------------------------------------------------------------------------- 1 | Now, every tab you open supports removing trash from our seas via #teamseas. 2 | 3 | Tabbers like you are supporting critical nonprofit work all around the world. Thank you! 4 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/teamseas/onboarding.step1.title.md: -------------------------------------------------------------------------------- 1 | ### Your tabs are doing great things 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/teamseas/onboarding.step2.subtitle.md: -------------------------------------------------------------------------------- 1 | Clean our seas even faster with a squad! 2 | 3 | You and your friends can team up to remove trash more quickly, getting us closer to the #teamseas goal of removing 30 million pounds of trash from our seas. 4 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/teamseas/onboarding.step2.title.md: -------------------------------------------------------------------------------- 1 | ### Do more with your squad 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/teamseas/onboarding.step3.subtitle.md: -------------------------------------------------------------------------------- 1 | Ads on the new tab page raise money that we give to nonprofits. Most ads aren't good—but these ones are :) 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/teamseas/onboarding.step3.title.md: -------------------------------------------------------------------------------- 1 | ### It doesn't cost you a thing 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/teamseas/social.email.about.md: -------------------------------------------------------------------------------- 1 |

Tab for #TeamSeas turns your web browser into a force for good. With each tab opened, ad revenue earned will support Ocean Conservancy and The Ocean Cleanup, working towards #TeamSeas’ goal of removing 30 million pounds of trash from oceans, rivers, and lakes around the world.

2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/teamseas/social.subtitle.md: -------------------------------------------------------------------------------- 1 | ##### Do even more for our seas! Invite a friend to join in. 2 | -------------------------------------------------------------------------------- /graphql/database/cause/causes/teamseas/social.title.md: -------------------------------------------------------------------------------- 1 | ### **Share Tab for #TeamSeas with friends** 2 | -------------------------------------------------------------------------------- /graphql/database/cause/getCause.js: -------------------------------------------------------------------------------- 1 | import { find } from 'lodash/collection' 2 | import causes from './causes' 3 | import { DatabaseItemDoesNotExistException } from '../../utils/exceptions' 4 | 5 | const getCause = async (id) => { 6 | const cause = find(causes, { id }) 7 | if (!cause) { 8 | throw new DatabaseItemDoesNotExistException() 9 | } 10 | return cause 11 | } 12 | 13 | export default getCause 14 | -------------------------------------------------------------------------------- /graphql/database/cause/getCauses.js: -------------------------------------------------------------------------------- 1 | import { isBoolean } from 'lodash/lang' 2 | import causes from './causes' 3 | import { showInternalOnly } from '../../utils/authorization-helpers' 4 | 5 | const getCauses = async (userContext, filters = {}) => { 6 | const { isAvailableToSelect } = filters 7 | // Filter out causes not ready for release yet for external users. 8 | const filteredCauses = causes.filter((cause) => { 9 | // allow internal users to switch to hidden causes 10 | if (isBoolean(isAvailableToSelect)) { 11 | return cause.isAvailableToSelect || showInternalOnly(userContext.email) 12 | } 13 | return true 14 | }) 15 | 16 | return filteredCauses 17 | } 18 | export default getCauses 19 | -------------------------------------------------------------------------------- /graphql/database/charities/getCharityForCause.js: -------------------------------------------------------------------------------- 1 | import CharityModel from './CharityModel' 2 | 3 | /** 4 | * Get all active charities. 5 | * @param {object} userContext - The user authorizer object. 6 | * @param {object} charityIds - The charity IDs for the cause. 7 | * @param {object} charityId -The charity ID for the cause. 8 | * @return {Promise} Returns a promise that resolves into 9 | * an array of Charity objects. 10 | */ 11 | const getCharitiesForCause = async (userContext, charityIds, charityId) => { 12 | try { 13 | if (charityIds) { 14 | return await Promise.all( 15 | charityIds.map((cId) => CharityModel.get(userContext, cId)) 16 | ) 17 | } 18 | return [await CharityModel.get(userContext, charityId)] 19 | } catch (e) { 20 | throw e 21 | } 22 | } 23 | 24 | export default getCharitiesForCause 25 | -------------------------------------------------------------------------------- /graphql/database/databaseClient.js: -------------------------------------------------------------------------------- 1 | import config from '../config' 2 | 3 | const AWS = require('aws-sdk') 4 | 5 | AWS.config.update({ 6 | region: config.AWS_REGION, 7 | endpoint: config.DYNAMODB_ENDPOINT, 8 | }) 9 | 10 | // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html 11 | const dbClient = new AWS.DynamoDB.DocumentClient({ 12 | // allow empty string values by converting them to null 13 | convertEmptyValues: true, 14 | }) 15 | 16 | export default dbClient 17 | -------------------------------------------------------------------------------- /graphql/database/experiments/getFeature.js: -------------------------------------------------------------------------------- 1 | import { 2 | getFeatureWithoutUser, 3 | getConfiguredGrowthbook, 4 | } from './growthbookUtils' 5 | 6 | const getFeature = (featureName) => { 7 | const configuredGrowthbook = getConfiguredGrowthbook({ 8 | userSpecific: false, 9 | }) 10 | const feature = getFeatureWithoutUser(configuredGrowthbook, featureName) 11 | return feature 12 | } 13 | 14 | export default getFeature 15 | -------------------------------------------------------------------------------- /graphql/database/experiments/getUserFeature.js: -------------------------------------------------------------------------------- 1 | import { 2 | getAndLogFeatureForUser, 3 | getConfiguredGrowthbook, 4 | } from './growthbookUtils' 5 | 6 | const getUserFeature = async (userContext, user, featureName) => { 7 | const configuredGrowthbook = getConfiguredGrowthbook(user) 8 | const userFeature = await getAndLogFeatureForUser( 9 | userContext, 10 | user.id, 11 | configuredGrowthbook, 12 | featureName 13 | ) 14 | 15 | return userFeature 16 | } 17 | 18 | export default getUserFeature 19 | -------------------------------------------------------------------------------- /graphql/database/experiments/getUserFeatures.js: -------------------------------------------------------------------------------- 1 | import features from './features' 2 | import { 3 | getAndLogFeatureForUser, 4 | getConfiguredGrowthbook, 5 | } from './growthbookUtils' 6 | 7 | const getUserFeatures = async (userContext, user) => { 8 | const configuredGrowthbook = getConfiguredGrowthbook(user) 9 | const userFeatures = await Promise.all( 10 | Object.keys(features).map((featureName) => 11 | getAndLogFeatureForUser( 12 | userContext, 13 | user.id, 14 | configuredGrowthbook, 15 | featureName 16 | ) 17 | ) 18 | ) 19 | 20 | return userFeatures 21 | } 22 | 23 | export default getUserFeatures 24 | -------------------------------------------------------------------------------- /graphql/database/fieldTypes.js: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import { v4 as uuid } from 'uuid' 3 | 4 | const customTypes = {} 5 | customTypes.uuid = () => 6 | Joi.string() 7 | .guid() 8 | .default(() => uuid(), 'uuid v4') 9 | 10 | // Joi, with some added convenience types. 11 | // https://github.com/hapijs/joi/blob/v10.6.0/API.md 12 | export default Object.assign({}, Joi, customTypes) 13 | -------------------------------------------------------------------------------- /graphql/database/globals/__mocks__/createCampaignConfiguration.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | const actualCreateCampConf = jest.requireActual( 4 | '../createCampaignConfiguration' 5 | ).default 6 | 7 | // Use the real createCampaignConfiguration, but allow us to 8 | // mock it or inspect the input. 9 | export default jest.fn((input) => actualCreateCampConf(input)) 10 | -------------------------------------------------------------------------------- /graphql/database/groupImpact/impactMetrics.js: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import ImpactMetricModel from './ImpactMetricModel' 3 | import impactMetricData from './impactMetricData' 4 | 5 | const impactMetricSchema = Joi.object(ImpactMetricModel.schema) 6 | 7 | // Validate data. 8 | // TODO: this should eventually live in a better ORM. 9 | impactMetricData.forEach((data) => { 10 | // With abortEarly=false, collect all errors before exiting. 11 | // https://github.com/sideway/joi/blob/master/API.md#anyvalidatevalue-options 12 | const validation = impactMetricSchema.validate(data, { abortEarly: false }) 13 | if (validation.error) { 14 | throw validation.error 15 | } 16 | }) 17 | 18 | export default impactMetricData 19 | -------------------------------------------------------------------------------- /graphql/database/missions/constants.js: -------------------------------------------------------------------------------- 1 | // Action for updating missions notification 2 | 3 | export const MISSION_COMPLETE = 'mission_complete' 4 | export const MISSION_STARTED = 'mission_started' 5 | 6 | export const LONGEST_TAB_STREAK_AWARD_NAME = 'Consistent Kitty' 7 | export const MOST_TABS_IN_DAY_AWARD_NAME = 'Hot Paws' 8 | export const MOST_TOTAL_TABS_AWARD_NAME = 'All-Star Fur Ball' 9 | -------------------------------------------------------------------------------- /graphql/database/referrals/logReferralData.js: -------------------------------------------------------------------------------- 1 | import ReferralDataModel from './ReferralDataModel' 2 | 3 | /** 4 | * Add a new referral data log to the DB. 5 | * @param {object} userContext - The user authorizer object. 6 | * @param {string} userId - The referred user id. 7 | * @param {string} referringUserId - The referring user id. 8 | * @param {string} referringChannelId - The referring channel id. 9 | * @return {Promise} A promise that resolve into a referral data log. 10 | */ 11 | export default async ( 12 | userContext, 13 | userId, 14 | referringUserId, 15 | referringChannelId 16 | ) => 17 | ReferralDataModel.create(userContext, { 18 | userId, 19 | referringUser: referringUserId, 20 | referringChannel: referringChannelId, 21 | }) 22 | -------------------------------------------------------------------------------- /graphql/database/referrals/logReferralLinkClick.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment' 2 | import ReferralLinkClickLogModel from './ReferralLinkClickLogModel' 3 | 4 | /** 5 | * Log a user's click on their referral link, presumably to copy 6 | * it and share it with somebody. 7 | * @param {object} userContext - The user authorizer object. 8 | * @param {string} userId - The user id. 9 | * @return {Promise} A promise that resolves into an object 10 | * with a "success" boolean property. 11 | */ 12 | const logReferralLinkClick = async (userContext, userId) => { 13 | try { 14 | await ReferralLinkClickLogModel.create(userContext, { 15 | userId, 16 | timestamp: moment.utc().toISOString(), 17 | }) 18 | } catch (e) { 19 | throw e 20 | } 21 | return { success: true } 22 | } 23 | 24 | export default logReferralLinkClick 25 | -------------------------------------------------------------------------------- /graphql/database/search/getSearchEngine.js: -------------------------------------------------------------------------------- 1 | import { find } from 'lodash/collection' 2 | import searchEngines from './searchEngines' 3 | import { DatabaseItemDoesNotExistException } from '../../utils/exceptions' 4 | 5 | const getSearchEngine = (id) => { 6 | const searchEngine = find(searchEngines, { id }) 7 | if (!searchEngine) { 8 | throw new DatabaseItemDoesNotExistException() 9 | } 10 | return searchEngine 11 | } 12 | 13 | export default getSearchEngine 14 | -------------------------------------------------------------------------------- /graphql/database/search/searchEngines.js: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import SearchEngineModel from './SearchEngineModel' 3 | import { searchEngineData } from './searchEngineData' 4 | 5 | const searchEngineModels = searchEngineData.map( 6 | (engineData) => new SearchEngineModel(engineData) 7 | ) 8 | 9 | // Validate data. 10 | // TODO: this should eventually live in a better ORM. 11 | const SearchEngineSchema = Joi.object(SearchEngineModel.schema) 12 | searchEngineModels.forEach((data) => { 13 | // With abortEarly=false, collect all errors before exiting. 14 | // https://github.com/sideway/joi/blob/master/API.md#anyvalidatevalue-options 15 | const validation = SearchEngineSchema.validate(data, { abortEarly: false }) 16 | if (validation.error) { 17 | throw validation.error 18 | } 19 | }) 20 | 21 | export default searchEngineModels 22 | -------------------------------------------------------------------------------- /graphql/database/userRevenue/amazon-cpm-codes-v2.json.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/graphql/database/userRevenue/amazon-cpm-codes-v2.json.enc -------------------------------------------------------------------------------- /graphql/database/userRevenue/amazon-cpm-codes.json: -------------------------------------------------------------------------------- 1 | { 2 | "foo": "0.55", 3 | "bar": "2.00", 4 | "baz": "11.50" 5 | } 6 | -------------------------------------------------------------------------------- /graphql/database/userRevenue/decodeAmazonCPM.js: -------------------------------------------------------------------------------- 1 | const amazonCPMCodes = require('./amazon-cpm-codes.json') 2 | 3 | /** 4 | * Change an Amazon CPM code into a CPM value 5 | * @param {string} amazonCPMCode - The Amazon CPM code 6 | * @return {number} The $USD value of the CPM 7 | */ 8 | const decodeAmazonCPM = (amazonCPMCode) => { 9 | const cpmStr = amazonCPMCodes[amazonCPMCode] 10 | // If no valid CPM value, return 0.0 11 | if (!cpmStr) { 12 | return 0.0 13 | } 14 | const cpmVal = parseFloat(cpmStr) 15 | if (Number.isNaN(cpmVal)) { 16 | throw new Error( 17 | `Amazon CPM code "${amazonCPMCode}" resolved to a non-numeric value` 18 | ) 19 | } 20 | return cpmVal 21 | } 22 | 23 | export default decodeAmazonCPM 24 | -------------------------------------------------------------------------------- /graphql/database/users/__mocks__/addVc.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | const Promise = require('bluebird') 4 | 5 | const addVc = jest.fn(() => 6 | Promise.resolve({ 7 | id: 'abc-123', 8 | vcCurrent: 4, 9 | vcAllTime: 4, 10 | heartsUntilNextLevel: 1, 11 | }) 12 | ) 13 | 14 | export default addVc 15 | -------------------------------------------------------------------------------- /graphql/database/users/addVcDonatedAllTime.js: -------------------------------------------------------------------------------- 1 | import UserModel from './UserModel' 2 | 3 | /** 4 | * Adds the specified virtual currency to the user's vc donated amount. 5 | * Added `vc` can be negative. 6 | * @param {object} userContext - The user authorizer object. 7 | * @param {string} id - The user id. 8 | * @param {integer} vc - The amount of virtual currency to add to the 9 | * user's balance. 10 | * @return {Promise} A promise that resolves into a User instance. 11 | */ 12 | const addVcDonatedAllTime = async (userContext, userId, vc = 0) => { 13 | try { 14 | const user = await UserModel.update(userContext, { 15 | id: userId, 16 | vcDonatedAllTime: { $add: vc }, 17 | }) 18 | return user 19 | } catch (e) { 20 | throw e 21 | } 22 | } 23 | 24 | export default addVcDonatedAllTime 25 | -------------------------------------------------------------------------------- /graphql/database/users/constructExperimentActionsType.js: -------------------------------------------------------------------------------- 1 | export default (user) => ({ 2 | referralNotification: user.testReferralNotificationAction, 3 | searchIntro: user.testSearchIntroAction, 4 | }) 5 | -------------------------------------------------------------------------------- /graphql/database/users/getLandingPagePhrase.js: -------------------------------------------------------------------------------- 1 | import { V4_SUPPORTING_STATEMENTS } from '../experiments/experimentConstants' 2 | import getUserFeature from '../experiments/getUserFeature' 3 | import UserModel from './UserModel' 4 | 5 | const getLandingPagePhrase = async (userContext, cause) => { 6 | const user = await UserModel.get(userContext, userContext.id) 7 | 8 | const feature = await getUserFeature( 9 | userContext, 10 | user, 11 | V4_SUPPORTING_STATEMENTS 12 | ) 13 | 14 | return feature.variation === 'Experiment' 15 | ? cause.landingPagePhrase 16 | : `Supporting: ${cause.name}` 17 | } 18 | 19 | export default getLandingPagePhrase 20 | -------------------------------------------------------------------------------- /graphql/database/users/getOrCreateTruexId.js: -------------------------------------------------------------------------------- 1 | import { nanoid } from 'nanoid' 2 | import UserModel from './UserModel' 3 | /** 4 | * A resolver for the user's background image. 5 | * @param {Object} userContext - The user authorizer object. 6 | * @param {Object} user.truexId - the user's truexId if it exists 7 | * @param {String} user.id - the user's ID 8 | * @param {Object} user.truexId - the current value of 9 | * the User model's backgroundImage field 10 | * @return {Promise} A promise that resolves into a 11 | * truexId value. 12 | */ 13 | export default async (userContext, { truexId, id }) => 14 | truexId || 15 | ( 16 | await UserModel.update(userContext, { 17 | id, 18 | truexId: nanoid(), 19 | }) 20 | ).truexId 21 | -------------------------------------------------------------------------------- /graphql/database/users/getSfacActivityState.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment' 2 | import { SFAC_ACTIVITY_STATES } from '../constants' 3 | 4 | const getSfacActivityState = async (_userContext, user) => { 5 | if (user.searches === 0) { 6 | return SFAC_ACTIVITY_STATES.NEW 7 | } 8 | 9 | if (!user.maxSearchesDay || !user.maxSearchesDay.recentDay) { 10 | return SFAC_ACTIVITY_STATES.NEW 11 | } 12 | 13 | const minActiveDate = moment.utc().subtract(21, 'days') 14 | const lastSearchDay = new Date(user.maxSearchesDay.recentDay.date) 15 | if (minActiveDate > lastSearchDay) { 16 | return SFAC_ACTIVITY_STATES.INACTIVE 17 | } 18 | 19 | return SFAC_ACTIVITY_STATES.ACTIVE 20 | } 21 | 22 | export default getSfacActivityState 23 | -------------------------------------------------------------------------------- /graphql/database/users/getShouldShowSfacIcon.js: -------------------------------------------------------------------------------- 1 | import { 2 | SFAC_EXISTING_USER_ACTIVITY_ICON, 3 | SFAC_EXTENSION_PROMPT, 4 | } from '../experiments/experimentConstants' 5 | import getUserFeature from '../experiments/getUserFeature' 6 | 7 | const getShouldShowSfacIcon = async (userContext, user) => { 8 | const newUserSFACNotifFeature = await getUserFeature( 9 | userContext, 10 | user, 11 | SFAC_EXTENSION_PROMPT 12 | ) 13 | const existingUserSFACIconFeature = await getUserFeature( 14 | userContext, 15 | user, 16 | SFAC_EXISTING_USER_ACTIVITY_ICON 17 | ) 18 | return ( 19 | newUserSFACNotifFeature.variation === 'Notification' || 20 | existingUserSFACIconFeature.variation === 'Icon' 21 | ) 22 | } 23 | 24 | export default getShouldShowSfacIcon 25 | -------------------------------------------------------------------------------- /graphql/database/users/mergeIntoExistingUser.js: -------------------------------------------------------------------------------- 1 | import UserModel from './UserModel' 2 | 3 | /** 4 | * Merge an existing user into an existing user account, because 5 | * they are owned by the same person. This happens when we create 6 | * an anonymous user account for an existing user who is not 7 | * logged in, but then they log in. 8 | * @param {object} userContext - The user authorizer object. 9 | * @param {string} id - The user id. 10 | * @return {Promise} A promise that resolves into a User instance. 11 | */ 12 | const mergeIntoExistingUser = async (userContext, userId) => { 13 | try { 14 | await UserModel.update(userContext, { 15 | id: userId, 16 | mergedIntoExistingUser: true, 17 | }) 18 | } catch (e) { 19 | throw e 20 | } 21 | return { success: true } 22 | } 23 | 24 | export default mergeIntoExistingUser 25 | -------------------------------------------------------------------------------- /graphql/database/users/setActiveWidget.js: -------------------------------------------------------------------------------- 1 | import UserModel from './UserModel' 2 | 3 | /** 4 | * Set the user's active widget. 5 | * @param {object} userContext - The user authorizer object. 6 | * @param {string} userId - The user id. 7 | * @param {string} widgetId - The widget Id. 8 | * @return {Promise} A promise that resolves into a User instance. 9 | */ 10 | const setActiveWidget = async (userContext, userId, widgetId) => { 11 | try { 12 | const userInstance = await UserModel.update(userContext, { 13 | id: userId, 14 | activeWidget: widgetId, 15 | }) 16 | return userInstance 17 | } catch (e) { 18 | throw e 19 | } 20 | } 21 | 22 | export default setActiveWidget 23 | -------------------------------------------------------------------------------- /graphql/database/users/setBackgroundColor.js: -------------------------------------------------------------------------------- 1 | import UserModel from './UserModel' 2 | import { USER_BACKGROUND_OPTION_COLOR } from '../constants' 3 | 4 | /** 5 | * Set user's background color. 6 | * @param {object} userContext - The user authorizer object. 7 | * @param {string} userId - The user id. 8 | * @param {string} color - The background color. 9 | * @return {Promise} A promise that resolves into a User instance. 10 | */ 11 | const setBackgroundColor = async (userContext, userId, color) => { 12 | try { 13 | const userInstance = await UserModel.update(userContext, { 14 | id: userId, 15 | backgroundColor: color, 16 | backgroundOption: USER_BACKGROUND_OPTION_COLOR, 17 | }) 18 | return userInstance 19 | } catch (e) { 20 | throw e 21 | } 22 | } 23 | 24 | export default setBackgroundColor 25 | -------------------------------------------------------------------------------- /graphql/database/users/setEmail.js: -------------------------------------------------------------------------------- 1 | import UserModel from './UserModel' 2 | 3 | /** 4 | * Set user's email. 5 | * @param {object} userContext - The user authorizer object. 6 | * @param {string} userId - The user id. 7 | * @return {Promise} response - A promise that resolves into a response object 8 | * @return {(Object|null)} response.user - a User instance, or null 9 | */ 10 | const setEmail = async (userContext, userId) => { 11 | try { 12 | const userInstance = await UserModel.update(userContext, { 13 | id: userId, 14 | email: userContext.email, 15 | }) 16 | return { 17 | user: userInstance, 18 | } 19 | } catch (e) { 20 | throw e 21 | } 22 | } 23 | 24 | export default setEmail 25 | -------------------------------------------------------------------------------- /graphql/database/users/setHasSeenSquads.js: -------------------------------------------------------------------------------- 1 | import UserModel from './UserModel' 2 | import logger from '../../utils/logger' 3 | 4 | /** 5 | * Set whether the user has viewed the intro flow in Tab V4 beta app. 6 | * @param {Object} userContext - The user authorizer object. 7 | * @param {String} userId - The user ID. 8 | * @return {Promise} response - A Promise that resolves into a 9 | * User object 10 | */ 11 | const setHasSeenSquads = async (userContext, userId) => { 12 | try { 13 | const user = await UserModel.update(userContext, { 14 | id: userId, 15 | hasSeenSquads: true, 16 | }) 17 | return user 18 | } catch (e) { 19 | logger.error(e) 20 | throw e 21 | } 22 | } 23 | 24 | export default setHasSeenSquads 25 | -------------------------------------------------------------------------------- /graphql/database/users/setHasViewedIntroFlow.js: -------------------------------------------------------------------------------- 1 | import UserModel from './UserModel' 2 | import logger from '../../utils/logger' 3 | 4 | /** 5 | * Set whether the user has viewed the intro flow in Tab V4 beta app. 6 | * @param {Object} userContext - The user authorizer object. 7 | * @param {String} userId - The user ID. 8 | * @param {String} enabled - Whether user has seen intro flow. 9 | * @return {Promise} response - A Promise that resolves into a 10 | * User object 11 | */ 12 | const hasViewedIntroFlow = async (userContext, { userId, enabled }) => { 13 | try { 14 | const user = await UserModel.update(userContext, { 15 | id: userId, 16 | hasViewedIntroFlow: enabled, 17 | }) 18 | return user 19 | } catch (e) { 20 | logger.error(e) 21 | throw e 22 | } 23 | } 24 | 25 | export default hasViewedIntroFlow 26 | -------------------------------------------------------------------------------- /graphql/database/users/setV4Enabled.js: -------------------------------------------------------------------------------- 1 | import UserModel from './UserModel' 2 | import logger from '../../utils/logger' 3 | 4 | /** 5 | * Set whether the user should use the Tab V4 beta app. 6 | * @param {Object} userContext - The user authorizer object. 7 | * @param {String} userId - The user ID. 8 | * @param {String} enabled - Whether the V4 beta is enabled. 9 | * @return {Promise} response - A Promise that resolves into a 10 | * User object 11 | */ 12 | const setV4Enabled = async (userContext, { userId, enabled }) => { 13 | try { 14 | const user = await UserModel.update(userContext, { 15 | id: userId, 16 | v4BetaEnabled: enabled, 17 | }) 18 | return user 19 | } catch (e) { 20 | logger.error(e) 21 | throw e 22 | } 23 | } 24 | 25 | export default setV4Enabled 26 | -------------------------------------------------------------------------------- /graphql/database/videoAdLog/createVideoAdLog.js: -------------------------------------------------------------------------------- 1 | import { v4 as uuid } from 'uuid' 2 | import moment from 'moment' 3 | import VideoAdLogModel from './VideoAdLogModel' 4 | /** 5 | * @param {Object} userContext - The user context. 6 | * @param {string} userId - The user's Id 7 | * @return {Promise} A promise that resolves into an object containing a log id 8 | */ 9 | 10 | export default async (userContext, userId) => 11 | VideoAdLogModel.create(userContext, { 12 | userId, 13 | id: uuid(), 14 | timestamp: moment.utc().toISOString(), 15 | }) 16 | -------------------------------------------------------------------------------- /graphql/database/videoAdLog/isVideoAdEligible.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment' 2 | import VideoAdLogModel from './VideoAdLogModel' 3 | /** 4 | * @param {Object} userContext - The user context. 5 | * @param {string} userId - The user object 6 | * @return {Promise} A promise that resolves into a boolean on whether user is video ad eligible 7 | */ 8 | 9 | export default async (userContext, { id: userId }) => 10 | ( 11 | await VideoAdLogModel.query(userContext, userId) 12 | .where('timestamp') 13 | .between( 14 | moment().subtract(1, 'day').toISOString(), 15 | moment().toISOString() 16 | ) 17 | .filter('completed') 18 | .equals(true) 19 | .execute() 20 | ).length < 3 21 | -------------------------------------------------------------------------------- /graphql/database/widgets/Widget.js: -------------------------------------------------------------------------------- 1 | import { WIDGET } from '../constants' 2 | 3 | /* 4 | * A class that represents the combination of a 5 | * BaseWidget and UserWidget. 6 | */ 7 | class Widget { 8 | static get name() { 9 | return WIDGET 10 | } 11 | } 12 | 13 | export default Widget 14 | -------------------------------------------------------------------------------- /graphql/database/widgets/__mocks__/setUpWidgetsForNewUser.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | export default jest.fn(() => Promise.resolve()) 4 | -------------------------------------------------------------------------------- /graphql/database/widgets/baseWidget/__tests__/BaseWidgetModel.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | import tableNames from '../../../tables' 4 | import BaseWidgetModel from '../BaseWidgetModel' 5 | 6 | jest.mock('../../../databaseClient') 7 | 8 | describe('BaseWidgetModel', () => { 9 | it('implements the name property', () => { 10 | expect(BaseWidgetModel.name).toBe('BaseWidget') 11 | }) 12 | 13 | it('implements the hashKey property', () => { 14 | expect(BaseWidgetModel.hashKey).toBe('id') 15 | }) 16 | 17 | it('implements the tableName property', () => { 18 | expect(BaseWidgetModel.tableName).toBe(tableNames.widgets) 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /graphql/database/widgets/baseWidget/getAllBaseWidgets.js: -------------------------------------------------------------------------------- 1 | import { sortBy } from 'lodash/collection' 2 | import BaseWidgetModel from './BaseWidgetModel' 3 | 4 | /** 5 | * Get all widgets. 6 | * @param {object} userContext - The user authorizer object. 7 | * @return {Promise} Returns a promise that resolves into 8 | * an array of Widgets. 9 | */ 10 | const getAllBaseWidgets = async (userContext) => { 11 | try { 12 | const widgets = await BaseWidgetModel.getAll(userContext) 13 | widgets.forEach((widget, i) => { 14 | widgets[i].widgetId = widgets[i].id 15 | widgets[i].settings = JSON.stringify(widgets[i].settings) 16 | }) 17 | const sortedWidgets = sortBy(widgets, (obj) => obj.position) 18 | return sortedWidgets 19 | } catch (e) { 20 | throw e 21 | } 22 | } 23 | 24 | export default getAllBaseWidgets 25 | -------------------------------------------------------------------------------- /graphql/database/widgets/constructFullWidget.js: -------------------------------------------------------------------------------- 1 | import Widget from './Widget' 2 | 3 | /** 4 | * Merge the user widget with the widget data to create an object 5 | * with all the widget information. 6 | * @param {Object} userWidget 7 | * @param {Object} widget 8 | * @return {Object} Returns an instance of UserWidget 9 | * with all the widget information. 10 | */ 11 | const constructFullWidget = (userWidget, widget) => 12 | Object.assign(new Widget(), userWidget, widget, { 13 | id: userWidget.widgetId, 14 | data: JSON.stringify(userWidget.data), 15 | config: JSON.stringify(userWidget.config), 16 | settings: JSON.stringify(widget.settings), 17 | }) 18 | 19 | export default constructFullWidget 20 | -------------------------------------------------------------------------------- /graphql/database/widgets/userWidget/getUserWidgetsByEnabledState.js: -------------------------------------------------------------------------------- 1 | import { filter } from 'lodash/collection' 2 | import UserWidgetModel from './UserWidgetModel' 3 | 4 | /** 5 | * Fetch the widgets for a user by enabled state. 6 | * @param {object} userContext - The user authorizer object. 7 | * @param {string} userId - The user id. 8 | * @param {boolean} enabled - Wether to fetch all enabled or disabled widgets. 9 | * @return {Promise} Returns a promise that resolves into a 10 | * list of user widgets. 11 | */ 12 | const getUserWidgetsByEnabledState = (userContext, userId, enabled) => 13 | UserWidgetModel.query(userContext, userId) 14 | .execute() 15 | .then((widgets) => { 16 | const result = filter(widgets, (w) => w.enabled === enabled) 17 | return result 18 | }) 19 | 20 | export default getUserWidgetsByEnabledState 21 | -------------------------------------------------------------------------------- /graphql/database/widgets/userWidget/updateUserWidgetData.js: -------------------------------------------------------------------------------- 1 | import UserWidgetModel from './UserWidgetModel' 2 | 3 | /** 4 | * Update widget data. 5 | * @param {object} userContext - The user authorizer object. 6 | * @param {string} userId - The user id. 7 | * @param {string} widgetId - The widget id. 8 | * @param {Object} data - The new widget data. 9 | * @return {Promise} Returns a promise that resolves into a 10 | * Widget. 11 | */ 12 | export default async (userContext, userId, widgetId, data) => 13 | UserWidgetModel.update(userContext, { 14 | userId, 15 | widgetId, 16 | data, 17 | }) 18 | -------------------------------------------------------------------------------- /graphql/database/widgets/userWidget/updateUserWidgetVisibility.js: -------------------------------------------------------------------------------- 1 | import UserWidgetModel from './UserWidgetModel' 2 | 3 | /** 4 | * Update widget data. 5 | * @param {object} userContext - The user authorizer object. 6 | * @param {string} userId - The user id. 7 | * @param {string} widgetId - The widget id. 8 | * @param {Object} visibility - The new widget visibility. 9 | * @return {Promise} Returns a promise that resolves into a 10 | * Widget. 11 | */ 12 | export default async (userContext, userId, widgetId, visible) => 13 | UserWidgetModel.update(userContext, { 14 | userId, 15 | widgetId, 16 | visible, 17 | }) 18 | -------------------------------------------------------------------------------- /graphql/integration-tests/fixtures/UserWidgets.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "userId": "gqltest1-12ab-12ab-12ab-123abc456def", 4 | "widgetId": "gqltest1-asdf-asdf-asdf-asdfghjklzxc", 5 | "enabled": true, 6 | "visible": true, 7 | "data": { 8 | "bookmarks": [ 9 | { 10 | "name": "Google", 11 | "link": "https://www.google.com/" 12 | } 13 | ] 14 | } 15 | } 16 | ] 17 | -------------------------------------------------------------------------------- /graphql/integration-tests/fixtures/Users.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "gqltest1-12ab-12ab-12ab-123abc456def", 4 | "username": "kevin", 5 | "email": "foo@bar.com", 6 | "vcCurrent": 10, 7 | "vcAllTime": 10, 8 | "level": 1, 9 | "heartsUntilNextLevel": 5 10 | }, 11 | { 12 | "id": "gqltest1-yz89-yz80-yz80-xyz789tuv456", 13 | "username": "susan", 14 | "email": "susan@example.com", 15 | "vcCurrent": 200, 16 | "vcAllTime": 450, 17 | "level": 4, 18 | "heartsUntilNextLevel": 50 19 | } 20 | ] 21 | -------------------------------------------------------------------------------- /graphql/integration-tests/utils/aws-client-dynamodb.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const AWS = require('aws-sdk') 3 | 4 | // Load environment variables from .env file. 5 | require('dotenv-extended').load({ 6 | path: path.join(__dirname, '../../', '.env.local'), 7 | defaults: path.join(__dirname, '../../', '.env'), 8 | }) 9 | 10 | AWS.config.update({ 11 | region: process.env.AWS_REGION, 12 | endpoint: process.env.DYNAMODB_ENDPOINT, 13 | accessKeyId: process.env.AWS_ACCESS_KEY_ID, 14 | secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, 15 | }) 16 | 17 | module.exports = AWS 18 | -------------------------------------------------------------------------------- /graphql/jest.config-e2e.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transform: { 3 | '^.+\\.(js|jsx|ts|tsx)$': '/node_modules/babel-jest', 4 | }, 5 | } 6 | -------------------------------------------------------------------------------- /graphql/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | collectCoverageFrom: [ 3 | 'src/**/*.{js,jsx,ts,tsx}', 4 | '!**/*.d.ts', 5 | '!**/node_modules/**', 6 | '!**/build/**', 7 | '!**/__mocks__/**', 8 | '!**/__tests__/**', 9 | ], 10 | coverageDirectory: '/coverage/', 11 | testPathIgnorePatterns: [ 12 | '/node_modules/', 13 | '/integration-tests/', 14 | '/build/', 15 | ], 16 | transform: { 17 | '^.+\\.(js|jsx|ts|tsx)$': '/node_modules/babel-jest', 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /graphql/scripts/updateSchema.js: -------------------------------------------------------------------------------- 1 | /* eslint no-console: 0 */ 2 | import fs from 'fs' 3 | import path from 'path' 4 | import { validateSchema } from 'graphql' 5 | import { printSchema } from 'graphql/utilities' 6 | 7 | // Print schema for client use. 8 | import { Schema } from '../data/schema' 9 | 10 | function updateSchema() { 11 | console.log('Updating schema...') 12 | const errors = validateSchema(Schema) 13 | if (errors.length) { 14 | console.error('The GraphQL schema is invalid:') 15 | console.error(errors) 16 | return 17 | } 18 | console.log('The GraphQL schema is valid.') 19 | fs.writeFileSync( 20 | path.join(__dirname, '../data/schema.graphql'), 21 | printSchema(Schema) 22 | ) 23 | console.log('Schema updated.') 24 | } 25 | 26 | updateSchema() 27 | -------------------------------------------------------------------------------- /graphql/utils/__mocks__/logger.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | const logger = {} 4 | 5 | logger.log = jest.fn() 6 | logger.debug = jest.fn() 7 | logger.info = jest.fn() 8 | logger.warn = jest.fn() 9 | logger.error = jest.fn() 10 | logger.fatal = jest.fn() 11 | export default logger 12 | 13 | export const logErrorWithId = jest.fn() 14 | -------------------------------------------------------------------------------- /graphql/utils/utils.js: -------------------------------------------------------------------------------- 1 | /* eslint import/prefer-default-export: 0 */ 2 | import moment from 'moment' 3 | 4 | /** 5 | * Determine if an argument is a valid ISO string. 6 | * @param {*} isoString - The argument to validate 7 | * @return {boolean} Whether the value of isoString is a valid ISO string. 8 | */ 9 | export const isValidISOString = (isoString) => 10 | // https://stackoverflow.com/a/40069911/1332513 11 | typeof isoString === 'string' && moment(isoString, moment.ISO_8601).isValid() 12 | -------------------------------------------------------------------------------- /lambda/.env: -------------------------------------------------------------------------------- 1 | LAMBDA_PORT=8001 2 | DYNAMODB_ENDPOINT=http://localhost:8000 3 | AWS_REGION=us-west-2 4 | AWS_ACCESS_KEY_ID=fakeKey123 5 | AWS_SECRET_ACCESS_KEY=fakeSecretKey456 6 | 7 | # Firebase authorizer 8 | LAMBDA_FIREBASE_PROJECT_ID=some-project 9 | LAMBDA_FIREBASE_DATABASE_URL=https://some-project.firebaseio.com 10 | LAMBDA_FIREBASE_CLIENT_EMAIL=example@some-project.iam.gserviceaccount.com 11 | LAMBDA_FIREBASE_PRIVATE_KEY=ABC123 12 | 13 | # Lambda@Edge for /newtab* CloudFront cache behavior. 14 | LAMBDA_TAB_V4_HOST=localhost:4250 # just a placeholder, not used locally 15 | -------------------------------------------------------------------------------- /lambda/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | build 4 | -------------------------------------------------------------------------------- /lambda/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb", "prettier"], 3 | "plugins": ["prettier"], 4 | "rules": { 5 | "prettier/prettier": "error", 6 | "global-require": 0, 7 | "no-underscore-dangle": 0 8 | }, 9 | "parserOptions": { 10 | "ecmaVersion": 2018, 11 | "sourceType": "module" 12 | }, 13 | "env": { 14 | "es6": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lambda/.gitignore: -------------------------------------------------------------------------------- 1 | .serverless/ 2 | coverage 3 | build 4 | .env.local 5 | serverless.yml 6 | **/*.local-only.test.* 7 | -------------------------------------------------------------------------------- /lambda/.npmignore: -------------------------------------------------------------------------------- 1 | # package directories 2 | node_modules 3 | jspm_packages 4 | 5 | # Serverless directories 6 | .serverless -------------------------------------------------------------------------------- /lambda/.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | build 4 | -------------------------------------------------------------------------------- /lambda/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": true, 3 | "jsxBracketSameLine": false, 4 | "useTabs": false, 5 | "semi": false, 6 | "singleQuote": true, 7 | "tabWidth": 2, 8 | "trailingComma": es5, 9 | } 10 | -------------------------------------------------------------------------------- /lambda/__mocks__/firebase-admin.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | // Note that this mock will be used unless we explicitly 4 | // call `jest.unmock('firebase-admin')`: 5 | // https://facebook.github.io/jest/docs/en/manual-mocks.html 6 | 7 | const firebaseAdmin = jest.genMockFromModule('firebase-admin') 8 | 9 | firebaseAdmin.auth = jest.fn(() => ({ 10 | verifyIdToken: jest.fn(() => Promise.resolve(null)), 11 | })) 12 | 13 | firebaseAdmin.apps = jest.fn(() => []) 14 | 15 | module.exports = firebaseAdmin 16 | -------------------------------------------------------------------------------- /lambda/__mocks__/uuid/v4.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | module.exports = jest.fn(() => '3ea8049d-861f-4a60-a56d-a61cf73e8b16') 4 | -------------------------------------------------------------------------------- /lambda/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = api => { 2 | const isTest = api.env('test') 3 | return { 4 | presets: [ 5 | [ 6 | '@babel/preset-env', 7 | { 8 | targets: { 9 | node: '14', 10 | }, 11 | }, 12 | ], 13 | ], 14 | plugins: [ 15 | 'babel-plugin-lodash', 16 | // Do not use transform-inline-environment-variables during 17 | // Jest tests. 18 | ...(isTest 19 | ? [] 20 | : [ 21 | [ 22 | 'transform-inline-environment-variables', 23 | { 24 | include: ['LAMBDA_TAB_V4_HOST'], 25 | }, 26 | ], 27 | ]), 28 | ], 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lambda/scripts/build.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | const path = require('path') 3 | const { spawnSync } = require('child_process') 4 | 5 | // Load environment variables from .env file. 6 | // https://github.com/keithmorris/node-dotenv-extended 7 | require('dotenv-extended').load({ 8 | path: path.join(__dirname, '../', '.env.local'), 9 | defaults: path.join(__dirname, '../', '.env'), 10 | }) 11 | 12 | process.env.NODE_ENV = 'production' 13 | 14 | spawnSync('babel', [ 15 | '--out-dir=build', 16 | "--ignore='build,**/coverage,**/__mocks__,**/__tests__", 17 | './src/', 18 | ]) 19 | -------------------------------------------------------------------------------- /lambda/setupTests.js: -------------------------------------------------------------------------------- 1 | /* eslint import/no-extraneous-dependencies: 0 */ 2 | 3 | import 'regenerator-runtime/runtime' 4 | -------------------------------------------------------------------------------- /lambda/src/__mocks__/yamljs.js: -------------------------------------------------------------------------------- 1 | /* global jest */ 2 | 3 | const YAMLMock = jest.genMockFromModule('yamljs') 4 | 5 | let mockYAML = Object.create(null) 6 | 7 | // Tests can use this function to add mock loaded YAML. 8 | // newMockYAML should be an object containing file names 9 | // for keys and objects or arrays reqpresenting the parsed 10 | // YAML loaded from that file. 11 | function __setMockYAML(newMockYAML) { 12 | mockYAML = newMockYAML 13 | } 14 | 15 | function __getMockYAML() { 16 | return mockYAML 17 | } 18 | 19 | // A custom version of YAML.load. 20 | function load(filePath) { 21 | return mockYAML[filePath] || {} 22 | } 23 | 24 | YAMLMock.__setMockYAML = __setMockYAML 25 | YAMLMock.__getMockYAML = __getMockYAML 26 | YAMLMock.load = load 27 | module.exports = YAMLMock 28 | -------------------------------------------------------------------------------- /lambda/src/firebase-authorizer/__tests__/initNFA.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | import { init } from 'next-firebase-auth' 3 | import initNFA from '../initNFA' 4 | 5 | jest.mock('next-firebase-auth') 6 | 7 | afterEach(() => { 8 | jest.clearAllMocks() 9 | }) 10 | 11 | const getMockArgs = () => ({ 12 | firebaseProjectId: 'some-project-id', 13 | firebasePrivateKey: 'fake-super-secret-key', 14 | firebaseClientEmail: 'some-client-email', 15 | firebaseDatabaseURL: 'some-database-url', 16 | firebasePublicAPIKey: 'some-public-api-key', 17 | cookieKeys: ['fake-key-1', 'another-fake-key'], 18 | }) 19 | 20 | describe('initNFA', () => { 21 | it('calls next-firebase-auth init', () => { 22 | expect.assertions() 23 | const input = getMockArgs() 24 | initNFA(input) 25 | expect(init).toHaveBeenCalled() 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /lambda/src/firebase-authorizer/fetch-polyfill.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | /* globals globalThis */ 3 | 4 | // A global `fetch`` is expected for use in `next-firebase-auth`: 5 | // https://github.com/node-fetch/node-fetch#providing-global-access 6 | import fetch, { 7 | Blob, 8 | blobFrom, 9 | blobFromSync, 10 | File, 11 | fileFrom, 12 | fileFromSync, 13 | FormData, 14 | Headers, 15 | Request, 16 | Response, 17 | } from 'node-fetch' 18 | 19 | if (!globalThis.fetch) { 20 | globalThis.fetch = fetch 21 | globalThis.Headers = Headers 22 | globalThis.Request = Request 23 | globalThis.Response = Response 24 | } 25 | -------------------------------------------------------------------------------- /lambda/src/homepage-404-lambda-edge/homepage-404-lambda-edge.js: -------------------------------------------------------------------------------- 1 | // Used in CloudFront origin response for tab-homepage. 2 | 3 | import { get } from 'lodash/object' 4 | 5 | const notFoundPaths = ['/404', '/404/', '/404.html'] 6 | 7 | exports.handler = (event, context, callback) => { 8 | const uri = get(event, 'Records[0].cf.request.uri', '') 9 | const { response } = event.Records[0].cf 10 | if (notFoundPaths.includes(uri)) { 11 | response.status = '404' 12 | } 13 | callback(null, response) 14 | } 15 | -------------------------------------------------------------------------------- /lambda/src/utils/__mocks__/database.js: -------------------------------------------------------------------------------- 1 | // Mock our database. 2 | const getFn = params => { 3 | if (params.Key.id === 'abc123') { 4 | return Promise.resolve({ 5 | Item: { 6 | username: 'fake_user', 7 | id: 'abc123', 8 | vcCurrent: 350, 9 | }, 10 | }) 11 | } 12 | return Promise.reject(new Error()) 13 | } 14 | 15 | module.exports = { 16 | get: getFn, 17 | set: () => Promise.reject(new Error()), 18 | } 19 | -------------------------------------------------------------------------------- /lambda/src/utils/constants.js: -------------------------------------------------------------------------------- 1 | export const AUTH_COOKIE_NAME = 'TabAuth.AuthUserTokens' 2 | export const AUTH_SIG_COOKIE_NAME = 'TabAuth.AuthUserTokens.sig' 3 | -------------------------------------------------------------------------------- /lambda/src/utils/database.js: -------------------------------------------------------------------------------- 1 | import { DynamoDB } from '@aws-sdk/client-dynamodb' 2 | import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb' 3 | 4 | const dynamoDb = new DynamoDB({ 5 | region: process.env.AWS_REGION, 6 | endpoint: process.env.DYNAMODB_ENDPOINT, 7 | // TODO: remove and rely on IAM role permissions. 8 | accessKeyId: process.env.AWS_ACCESS_KEY_ID, 9 | secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, 10 | }) 11 | const documentClient = DynamoDBDocumentClient.from(dynamoDb) 12 | 13 | const database = {} 14 | 15 | database.put = params => documentClient.put(params).promise() 16 | 17 | database.get = params => documentClient.get(params).promise() 18 | 19 | export default database 20 | -------------------------------------------------------------------------------- /lambda/src/utils/tables.js: -------------------------------------------------------------------------------- 1 | import { mapValues } from 'lodash/object' 2 | 3 | const tableNameAppendix = process.env.DB_TABLE_NAME_APPENDIX 4 | ? process.env.DB_TABLE_NAME_APPENDIX 5 | : '' 6 | 7 | const tables = { 8 | users: 'Users', 9 | userLevels: 'UserLevels', 10 | charities: 'Charities', 11 | vcDonationLog: 'VcDonationLog', 12 | vcDonationByCharity: 'VcDonationByCharity', 13 | userRevenueLog: 'UserRevenueLog', 14 | userDataConsentLog: 'UserDataConsentLog', 15 | backgroundImages: 'BackgroundImages', 16 | widgets: 'Widgets', 17 | userWidgets: 'UserWidgets', 18 | userTabsLog: 'UserTabsLog', 19 | userSearchLog: 'UserSearchLog', 20 | referralDataLog: 'ReferralDataLog', 21 | referralLinkClickLog: 'ReferralLinkClickLog', 22 | } 23 | 24 | export default mapValues(tables, name => `${name}${tableNameAppendix}`) 25 | -------------------------------------------------------------------------------- /redis/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env" 4 | ], 5 | presets: [ 6 | ['@babel/preset-env', { targets: { node: 'current' } }], 7 | ], 8 | } 9 | -------------------------------------------------------------------------------- /redis/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | build 4 | -------------------------------------------------------------------------------- /redis/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb/base", "prettier"], 3 | "plugins": ["prettier"], 4 | "rules": { 5 | "prettier/prettier": "error", 6 | "global-require": 0, 7 | "no-underscore-dangle": 0 8 | }, 9 | "parserOptions": { 10 | "ecmaVersion": 2018, 11 | "sourceType": "module" 12 | }, 13 | "env": { 14 | "es6": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /redis/.gitignore: -------------------------------------------------------------------------------- 1 | .serverless/ 2 | coverage 3 | build 4 | .env.local 5 | -------------------------------------------------------------------------------- /redis/.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | build 4 | .serverless 5 | -------------------------------------------------------------------------------- /redis/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": true, 3 | "jsxBracketSameLine": false, 4 | "useTabs": false, 5 | "semi": false, 6 | "singleQuote": true, 7 | "tabWidth": 2, 8 | "trailingComma": es5, 9 | } 10 | -------------------------------------------------------------------------------- /s3/media/img/background-thumbnails/50413bb866f7495cb70371a2edc8b934.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/background-thumbnails/50413bb866f7495cb70371a2edc8b934.jpg -------------------------------------------------------------------------------- /s3/media/img/background-thumbnails/71a27d6823244354acb85e0806d0dff1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/background-thumbnails/71a27d6823244354acb85e0806d0dff1.jpg -------------------------------------------------------------------------------- /s3/media/img/background-thumbnails/cup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/background-thumbnails/cup.jpg -------------------------------------------------------------------------------- /s3/media/img/background-thumbnails/dota2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/background-thumbnails/dota2.jpg -------------------------------------------------------------------------------- /s3/media/img/background-thumbnails/invoker.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/background-thumbnails/invoker.jpg -------------------------------------------------------------------------------- /s3/media/img/background-thumbnails/lake.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/background-thumbnails/lake.jpg -------------------------------------------------------------------------------- /s3/media/img/background-thumbnails/puppy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/background-thumbnails/puppy.jpg -------------------------------------------------------------------------------- /s3/media/img/background-thumbnails/shadowfiend.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/background-thumbnails/shadowfiend.jpg -------------------------------------------------------------------------------- /s3/media/img/background-thumbnails/slark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/background-thumbnails/slark.jpg -------------------------------------------------------------------------------- /s3/media/img/backgrounds/135b157fdbb24ba39f6dd0ee8da16f2b.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/backgrounds/135b157fdbb24ba39f6dd0ee8da16f2b.jpg -------------------------------------------------------------------------------- /s3/media/img/backgrounds/3acd54614b1d4d7fbce85d965de3de25.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/backgrounds/3acd54614b1d4d7fbce85d965de3de25.jpg -------------------------------------------------------------------------------- /s3/media/img/backgrounds/3d1299fb-3ad5-4405-8cb4-5236bffd1660.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/backgrounds/3d1299fb-3ad5-4405-8cb4-5236bffd1660.jpg -------------------------------------------------------------------------------- /s3/media/img/backgrounds/7a8b04cc-fd88-4af3-bf73-3b6459e7eccb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/backgrounds/7a8b04cc-fd88-4af3-bf73-3b6459e7eccb.jpg -------------------------------------------------------------------------------- /s3/media/img/backgrounds/7bd681cf-850d-42eb-9a1f-7b9620bfb82a.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/backgrounds/7bd681cf-850d-42eb-9a1f-7b9620bfb82a.jpg -------------------------------------------------------------------------------- /s3/media/img/backgrounds/7c33e7df-fdc6-4f57-bb13-646de15e58bb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/backgrounds/7c33e7df-fdc6-4f57-bb13-646de15e58bb.jpg -------------------------------------------------------------------------------- /s3/media/img/backgrounds/87fe1385-388c-4c17-98ce-80163a8ac352.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/backgrounds/87fe1385-388c-4c17-98ce-80163a8ac352.jpg -------------------------------------------------------------------------------- /s3/media/img/backgrounds/8907622e-40f7-46ea-92e4-535b9dbdfdfe.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/backgrounds/8907622e-40f7-46ea-92e4-535b9dbdfdfe.jpg -------------------------------------------------------------------------------- /s3/media/img/backgrounds/8a5ca2a1-fa90-42c1-820a-9029303c9ccd.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/backgrounds/8a5ca2a1-fa90-42c1-820a-9029303c9ccd.jpg -------------------------------------------------------------------------------- /s3/media/img/backgrounds/8f54a3b0-9788-4a19-a818-efc40950e97d.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/backgrounds/8f54a3b0-9788-4a19-a818-efc40950e97d.jpg -------------------------------------------------------------------------------- /s3/media/img/backgrounds/98c444cd-040d-4484-a25a-267194e9de45.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/backgrounds/98c444cd-040d-4484-a25a-267194e9de45.jpg -------------------------------------------------------------------------------- /s3/media/img/backgrounds/a3e3c0b2-9e16-4d1a-b258-fee638a96df8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/backgrounds/a3e3c0b2-9e16-4d1a-b258-fee638a96df8.jpg -------------------------------------------------------------------------------- /s3/media/img/backgrounds/alex-chernenko-To28QYvt5q4-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/backgrounds/alex-chernenko-To28QYvt5q4-unsplash.jpg -------------------------------------------------------------------------------- /s3/media/img/backgrounds/c45ab947-c54c-404e-b4a2-4bf7b68430ad.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/backgrounds/c45ab947-c54c-404e-b4a2-4bf7b68430ad.jpg -------------------------------------------------------------------------------- /s3/media/img/backgrounds/cup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/backgrounds/cup.jpg -------------------------------------------------------------------------------- /s3/media/img/backgrounds/d588d58c-bc0c-48c6-a815-0e4cddbcd397.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/backgrounds/d588d58c-bc0c-48c6-a815-0e4cddbcd397.jpg -------------------------------------------------------------------------------- /s3/media/img/backgrounds/dota2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/backgrounds/dota2.jpg -------------------------------------------------------------------------------- /s3/media/img/backgrounds/invoker.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/backgrounds/invoker.jpg -------------------------------------------------------------------------------- /s3/media/img/backgrounds/kanashi-MwGMVKRehaM-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/backgrounds/kanashi-MwGMVKRehaM-unsplash.jpg -------------------------------------------------------------------------------- /s3/media/img/backgrounds/lake.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/backgrounds/lake.jpg -------------------------------------------------------------------------------- /s3/media/img/backgrounds/malek-dridi-0F7GRXNOG7g-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/backgrounds/malek-dridi-0F7GRXNOG7g-unsplash.jpg -------------------------------------------------------------------------------- /s3/media/img/backgrounds/natalia-nikolaieva-8yCWhEGNkX0-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/backgrounds/natalia-nikolaieva-8yCWhEGNkX0-unsplash.jpg -------------------------------------------------------------------------------- /s3/media/img/backgrounds/puppy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/backgrounds/puppy.jpg -------------------------------------------------------------------------------- /s3/media/img/backgrounds/shadowfiend.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/backgrounds/shadowfiend.jpg -------------------------------------------------------------------------------- /s3/media/img/backgrounds/slark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/backgrounds/slark.jpg -------------------------------------------------------------------------------- /s3/media/img/backgrounds/tran-mau-tri-tam--81lVsfM4gQ-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/backgrounds/tran-mau-tri-tam--81lVsfM4gQ-unsplash.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-logos/a21-201901.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-logos/a21-201901.png -------------------------------------------------------------------------------- /s3/media/img/charities/charity-logos/acfusa-201804.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-logos/acfusa-201804.png -------------------------------------------------------------------------------- /s3/media/img/charities/charity-logos/conservation-international-201804.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-logos/conservation-international-201804.png -------------------------------------------------------------------------------- /s3/media/img/charities/charity-logos/coral-reef-alliance.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-logos/coral-reef-alliance.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-logos/educate-201807.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-logos/educate-201807.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-logos/ftdws.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-logos/ftdws.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-logos/girls-not-brides.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-logos/girls-not-brides.png -------------------------------------------------------------------------------- /s3/media/img/charities/charity-logos/givedirectly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-logos/givedirectly.png -------------------------------------------------------------------------------- /s3/media/img/charities/charity-logos/human-rights-watch.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-logos/human-rights-watch.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-logos/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-logos/placeholder.png -------------------------------------------------------------------------------- /s3/media/img/charities/charity-logos/room-to-read-20210809.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-logos/room-to-read-20210809.png -------------------------------------------------------------------------------- /s3/media/img/charities/charity-logos/save-the-children-201804.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-logos/save-the-children-201804.png -------------------------------------------------------------------------------- /s3/media/img/charities/charity-logos/the-bail-project.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-logos/the-bail-project.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-logos/united-we-dream.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-logos/united-we-dream.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-logos/water-dot-org-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-logos/water-dot-org-2.png -------------------------------------------------------------------------------- /s3/media/img/charities/charity-logos/water-dot-org.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-logos/water-dot-org.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-logos/wwf-201902.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-logos/wwf-201902.png -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/a21.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/a21.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/acfusa.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/acfusa.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/against-malaria-foundation.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/against-malaria-foundation.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/amhc.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/amhc.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/australian-bushfire-relief.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/australian-bushfire-relief.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/bwhi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/bwhi.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/care-india.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/care-india.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/ci-20210707.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/ci-20210707.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/conservation-international.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/conservation-international.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/cool-earth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/cool-earth.png -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/coral-reef-alliance.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/coral-reef-alliance.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/covid-19-solidarity.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/covid-19-solidarity.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/direct-relief.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/direct-relief.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/doctors-without-borders.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/doctors-without-borders.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/earthjustice.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/earthjustice.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/educate-201902.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/educate-201902.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/educate.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/educate.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/eji.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/eji.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/friends-with-four-paws.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/friends-with-four-paws.png -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/ftdws-20210707.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/ftdws-20210707.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/ftdws.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/ftdws.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/girls-not-brides.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/girls-not-brides.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/give-directly.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/give-directly.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/givedirectly.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/givedirectly.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/hrw-20210707.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/hrw-20210707.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/human-rights-watch.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/human-rights-watch.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/naacp-ldf.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/naacp-ldf.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/pih.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/pih.png -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/rainforest-alliance.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/rainforest-alliance.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/rainn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/rainn.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/room-to-read-20210707.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/room-to-read-20210707.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/room-to-read-20230217.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/room-to-read-20230217.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/room-to-read.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/room-to-read.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/save-the-children.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/save-the-children.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/sea-turtle-inc.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/sea-turtle-inc.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/sierra-club.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/sierra-club.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/stc-20210707.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/stc-20210707.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/the-bail-project.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/the-bail-project.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/trans-lifeline.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/trans-lifeline.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/trees-for-the-future.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/trees-for-the-future.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/uhrp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/uhrp.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/united-we-dream.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/united-we-dream.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/water-20210707.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/water-20210707.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/water-dot-org.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/water-dot-org.jpg -------------------------------------------------------------------------------- /s3/media/img/charities/charity-post-donation-images/wwf.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/s3/media/img/charities/charity-post-donation-images/wwf.jpg -------------------------------------------------------------------------------- /s3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tab-dev-s3", 3 | "version": "0.1.0", 4 | "description": "S3 dev server for Tab for a Cause.", 5 | "private": true, 6 | "dependencies": { 7 | "express": "^4.17.3" 8 | }, 9 | "devDependencies": { 10 | "serverless": "^1.65.0" 11 | }, 12 | "scripts": { 13 | "start": "node ./server.js", 14 | "deploy": "serverless deploy -v" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/gladly-team/tab.git" 19 | }, 20 | "author": "Gladly Team", 21 | "bugs": { 22 | "url": "https://github.com/gladly-team/tab/issues" 23 | }, 24 | "homepage": "https://github.com/gladly-team/tab#readme" 25 | } 26 | -------------------------------------------------------------------------------- /s3/server.js: -------------------------------------------------------------------------------- 1 | var express = require('express') 2 | var app = express() 3 | 4 | app.use(express.static('media')) 5 | 6 | app.listen(9000) 7 | -------------------------------------------------------------------------------- /scripts/rm-node-modules.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x 3 | rm -rf ./node_modules/ 4 | rm -rf ./graphql/node_modules/ 5 | rm -rf ./lambda/node_modules/ 6 | rm -rf ./web/node_modules/ 7 | -------------------------------------------------------------------------------- /scripts/runEndToEndTests.js: -------------------------------------------------------------------------------- 1 | // Used to test in CI. 2 | 3 | import { spawn } from 'child_process' 4 | import assignEnvVars from './assign-env-vars' 5 | 6 | // Set env vars. 7 | assignEnvVars('test') 8 | 9 | const tests = spawn('yarn', ['run', 'test:integration'], { stdio: 'inherit' }) 10 | 11 | // Exit this process with the child process code. 12 | tests.on('exit', (code) => process.exit(code)) 13 | -------------------------------------------------------------------------------- /scripts/runTests.js: -------------------------------------------------------------------------------- 1 | // Used to test in CI. 2 | 3 | import { spawn } from 'child_process' 4 | import assignEnvVars from './assign-env-vars' 5 | 6 | // Set env vars. 7 | const requireAllEnvVarsSet = true 8 | assignEnvVars('test', requireAllEnvVarsSet) 9 | 10 | const tests = spawn('yarn', ['run', 'test'], { stdio: 'inherit' }) 11 | 12 | // Exit this process with the child process code. 13 | tests.on('exit', (code) => process.exit(code)) 14 | -------------------------------------------------------------------------------- /web/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "react-app", 3 | "plugins": ["prettier"], 4 | "rules": { 5 | "prettier/prettier": "error" 6 | }, 7 | "settings": { 8 | "react": { 9 | "version": "16.8.6" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | build 4 | build-temp 5 | .DS_Store 6 | .env.local 7 | npm-debug.log 8 | 9 | # https://github.com/whitecolor/yalc#keep-it-out-of-git 10 | .yalc 11 | yalc.lock 12 | 13 | __generated__/ 14 | ssl/ 15 | -------------------------------------------------------------------------------- /web/.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | build 4 | data 5 | __generated__ 6 | src/js/ads/prebid/prebid.js 7 | -------------------------------------------------------------------------------- /web/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": true, 3 | "jsxBracketSameLine": false, 4 | "useTabs": false, 5 | "semi": false, 6 | "singleQuote": true, 7 | "tabWidth": 2, 8 | "trailingComma": es5, 9 | } 10 | -------------------------------------------------------------------------------- /web/__mocks__/@sentry/browser.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | const SentryBrowser = jest.genMockFromModule('@sentry/browser') 4 | module.exports = SentryBrowser 5 | -------------------------------------------------------------------------------- /web/__mocks__/@sentry/integrations.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | const SentryIntegrations = jest.genMockFromModule('@sentry/integrations') 4 | module.exports = SentryIntegrations 5 | -------------------------------------------------------------------------------- /web/__mocks__/firebase/auth.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | // Mock for 'firebase/app' handles all the mock methods. 4 | module.exports = jest.fn() 5 | -------------------------------------------------------------------------------- /web/__mocks__/react-relay/compat.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | import RelayMock from '../react-relay' 3 | 4 | // Just use our react-relay mock. 5 | module.exports = RelayMock 6 | -------------------------------------------------------------------------------- /web/__mocks__/tab-cmp.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | const mockTabCMP = jest.genMockFromModule('tab-cmp') 4 | module.exports = mockTabCMP 5 | -------------------------------------------------------------------------------- /web/codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - src/js/ads/prebid/prebid.js 3 | -------------------------------------------------------------------------------- /web/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "src" 4 | }, 5 | "include": ["src"] 6 | } 7 | -------------------------------------------------------------------------------- /web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/public/favicon.ico -------------------------------------------------------------------------------- /web/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Tab", 3 | "name": "Tab for a Cause", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#9d4ba3", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /web/public/search-2019.2.28.14.21.css: -------------------------------------------------------------------------------- 1 | /* Hide the arrow button on ads. */ 2 | .ypaAdCtaButtonImageDiv { 3 | display: none !important; 4 | } 5 | /* Hide the empty image div. */ 6 | .ypaAdImageDiv { 7 | display: none !important; 8 | } 9 | /* Disable keyword bolding in results. */ 10 | b { 11 | font-weight: normal !important; 12 | } 13 | /* Add a bottom border to the ads div. */ 14 | .ypaAdSS .ypaAdUnit { 15 | border-bottom: 1px solid rgb(228, 228, 228); 16 | } 17 | -------------------------------------------------------------------------------- /web/public/search-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/public/search-favicon.png -------------------------------------------------------------------------------- /web/src/js/__mocks__/blockadblock.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | export default jest.fn() 4 | -------------------------------------------------------------------------------- /web/src/js/__mocks__/browser-detect.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | // https://www.npmjs.com/package/browser-detect 4 | const detectBrowser = jest.fn(() => ({ 5 | name: 'chrome', 6 | version: '58.0.3029', 7 | versionNumber: 58.03029, 8 | mobile: false, 9 | os: 'Windows NT 10.0', 10 | })) 11 | 12 | export default detectBrowser 13 | -------------------------------------------------------------------------------- /web/src/js/__mocks__/relay-env.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | export default { 4 | network: {}, 5 | store: { 6 | create: jest.fn(), 7 | delete: jest.fn(), 8 | get: jest.fn(), 9 | getRoot: jest.fn(), 10 | getRootField: jest.fn(), 11 | getPluralRootField: jest.fn(), 12 | }, 13 | } 14 | -------------------------------------------------------------------------------- /web/src/js/analytics/__mocks__/facebook-analytics.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | module.exports = jest.fn() 4 | -------------------------------------------------------------------------------- /web/src/js/analytics/__mocks__/logEvent.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | jest.mock('js/analytics/facebook-analytics') 4 | 5 | const logEventMock = jest.genMockFromModule('../logEvent') 6 | module.exports = logEventMock 7 | -------------------------------------------------------------------------------- /web/src/js/analytics/facebook-analytics.js: -------------------------------------------------------------------------------- 1 | import logger from 'js/utils/logger' 2 | 3 | const facebookAnalytics = (...args) => { 4 | const DEBUG = false 5 | 6 | const fbq = window.fbq 7 | if (!fbq) { 8 | logger.error('Facebook analytics are not available on `window.fbq`.') 9 | } 10 | try { 11 | if (DEBUG) { 12 | console.log('Logging Facebook event with args:', args) 13 | } 14 | fbq.apply(this, args) 15 | } catch (e) { 16 | logger.error(e) 17 | } 18 | } 19 | 20 | export default facebookAnalytics 21 | -------------------------------------------------------------------------------- /web/src/js/analytics/google-analytics.js: -------------------------------------------------------------------------------- 1 | import logger from 'js/utils/logger' 2 | 3 | const googleAnalytics = (...args) => { 4 | const { gtag } = window 5 | try { 6 | gtag.apply(this, args) 7 | } catch (e) { 8 | logger.error(e) 9 | } 10 | } 11 | 12 | export default googleAnalytics 13 | -------------------------------------------------------------------------------- /web/src/js/analytics/reddit-analytics.js: -------------------------------------------------------------------------------- 1 | import logger from 'js/utils/logger' 2 | 3 | // https://www.reddithelp.com/en/categories/advertising/creating-ads/installing-reddit-conversion-pixel 4 | const redditAnalytics = (...args) => { 5 | const DEBUG = false 6 | 7 | const rdt = window.rdt 8 | if (!rdt) { 9 | logger.error('Reddit analytics are not available on `window.rdt`.') 10 | } 11 | try { 12 | if (DEBUG) { 13 | console.log('Logging Reddit event with args:', args) 14 | } 15 | rdt.apply(this, args) 16 | } catch (e) { 17 | logger.error(e) 18 | } 19 | } 20 | 21 | export default redditAnalytics 22 | -------------------------------------------------------------------------------- /web/src/js/assets/blackEquity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/src/js/assets/blackEquity.png -------------------------------------------------------------------------------- /web/src/js/assets/cashHand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/src/js/assets/cashHand.png -------------------------------------------------------------------------------- /web/src/js/assets/cats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/src/js/assets/cats.png -------------------------------------------------------------------------------- /web/src/js/assets/defaultBackground.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/src/js/assets/defaultBackground.jpg -------------------------------------------------------------------------------- /web/src/js/assets/defaultSeasBackground.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/src/js/assets/defaultSeasBackground.jpg -------------------------------------------------------------------------------- /web/src/js/assets/democracy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/src/js/assets/democracy.png -------------------------------------------------------------------------------- /web/src/js/assets/disasterRelief.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/src/js/assets/disasterRelief.png -------------------------------------------------------------------------------- /web/src/js/assets/endingHunger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/src/js/assets/endingHunger.png -------------------------------------------------------------------------------- /web/src/js/assets/givedirectly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/src/js/assets/givedirectly.png -------------------------------------------------------------------------------- /web/src/js/assets/globalHealth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/src/js/assets/globalHealth.png -------------------------------------------------------------------------------- /web/src/js/assets/lgbtq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/src/js/assets/lgbtq.png -------------------------------------------------------------------------------- /web/src/js/assets/logos/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/src/js/assets/logos/favicon.ico -------------------------------------------------------------------------------- /web/src/js/assets/logos/logo32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/src/js/assets/logos/logo32x32.png -------------------------------------------------------------------------------- /web/src/js/assets/logos/search-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/src/js/assets/logos/search-favicon.png -------------------------------------------------------------------------------- /web/src/js/assets/nopicture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/src/js/assets/nopicture.jpg -------------------------------------------------------------------------------- /web/src/js/assets/promos/allexpress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/src/js/assets/promos/allexpress.png -------------------------------------------------------------------------------- /web/src/js/assets/promos/bookshop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/src/js/assets/promos/bookshop.png -------------------------------------------------------------------------------- /web/src/js/assets/promos/glossier.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/src/js/assets/promos/glossier.png -------------------------------------------------------------------------------- /web/src/js/assets/promos/horse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/src/js/assets/promos/horse.png -------------------------------------------------------------------------------- /web/src/js/assets/promos/kiwico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/src/js/assets/promos/kiwico.png -------------------------------------------------------------------------------- /web/src/js/assets/promos/lego.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/src/js/assets/promos/lego.png -------------------------------------------------------------------------------- /web/src/js/assets/promos/lowes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/src/js/assets/promos/lowes.png -------------------------------------------------------------------------------- /web/src/js/assets/promos/macys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/src/js/assets/promos/macys.png -------------------------------------------------------------------------------- /web/src/js/assets/promos/microsoft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/src/js/assets/promos/microsoft.png -------------------------------------------------------------------------------- /web/src/js/assets/promos/oldnavy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/src/js/assets/promos/oldnavy.png -------------------------------------------------------------------------------- /web/src/js/assets/promos/samsung.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/src/js/assets/promos/samsung.png -------------------------------------------------------------------------------- /web/src/js/assets/promos/sephora.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/src/js/assets/promos/sephora.png -------------------------------------------------------------------------------- /web/src/js/assets/promos/sonos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/src/js/assets/promos/sonos.png -------------------------------------------------------------------------------- /web/src/js/assets/promos/thriftbooks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/src/js/assets/promos/thriftbooks.png -------------------------------------------------------------------------------- /web/src/js/assets/promos/ultra-beauty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/src/js/assets/promos/ultra-beauty.png -------------------------------------------------------------------------------- /web/src/js/assets/promos/walmart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/src/js/assets/promos/walmart.png -------------------------------------------------------------------------------- /web/src/js/assets/promos/zulily.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/src/js/assets/promos/zulily.png -------------------------------------------------------------------------------- /web/src/js/assets/reproductiveHealth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/src/js/assets/reproductiveHealth.png -------------------------------------------------------------------------------- /web/src/js/assets/trees.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/src/js/assets/trees.png -------------------------------------------------------------------------------- /web/src/js/assets/ukraine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/src/js/assets/ukraine.png -------------------------------------------------------------------------------- /web/src/js/authentication/__mocks__/firebaseConfig.js: -------------------------------------------------------------------------------- 1 | import firebase from 'firebase/app' 2 | import 'firebase/auth' 3 | 4 | const config = { 5 | apiKey: 'abc123', 6 | authDomain: 'localhost', 7 | databaseURL: 'foo', 8 | projectId: 'xyz', 9 | } 10 | 11 | export const initializeFirebase = () => { 12 | firebase.initializeApp(config) 13 | } 14 | -------------------------------------------------------------------------------- /web/src/js/authentication/__mocks__/helpers.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | const mockAuthHelpers = jest.genMockFromModule('../helpers') 4 | module.exports = mockAuthHelpers 5 | -------------------------------------------------------------------------------- /web/src/js/authentication/firebaseConfig.js: -------------------------------------------------------------------------------- 1 | import firebase from 'firebase/app' 2 | import 'firebase/auth' 3 | 4 | const config = { 5 | apiKey: process.env.REACT_APP_FIREBASE_API_KEY, 6 | authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN, 7 | databaseURL: process.env.REACT_APP_FIREBASE_DATABASE_URL, 8 | projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID, 9 | } 10 | 11 | export const initializeFirebase = () => { 12 | firebase.initializeApp(config) 13 | } 14 | -------------------------------------------------------------------------------- /web/src/js/components/App/NotFoundPage.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import { externalRedirect } from 'js/navigation/utils' 3 | 4 | const NotFound = () => { 5 | useEffect(() => { 6 | // Rely on the 404 page from another app behind the same Cloudfront 7 | // distribution. 8 | externalRedirect('/404/') 9 | }) 10 | return null 11 | } 12 | 13 | export default NotFound 14 | -------------------------------------------------------------------------------- /web/src/js/components/Authentication/AuthenticationContainer.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { createFragmentContainer } from 'react-relay' 3 | 4 | import Authentication from 'js/components/Authentication/Authentication' 5 | 6 | export default createFragmentContainer(Authentication, { 7 | user: graphql` 8 | fragment AuthenticationContainer_user on User { 9 | id 10 | email 11 | username 12 | v4BetaEnabled 13 | ...AssignExperimentGroupsContainer_user 14 | } 15 | `, 16 | }) 17 | -------------------------------------------------------------------------------- /web/src/js/components/Authentication/FirebaseAuthenticationUIAction.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { checkIfEmailVerified } from 'js/authentication/helpers' 3 | 4 | class FirebaseAuthenticationUIAction extends React.Component { 5 | componentDidMount() { 6 | // See if the user verified their email address so that we can 7 | // log the verification. It would be better to user a cloud 8 | // function for this, or at least an official callback from the 9 | // Firebase SDK, but Firebase does not yet support one. See: 10 | // https://stackoverflow.com/q/43503377 11 | checkIfEmailVerified() 12 | } 13 | 14 | render() { 15 | return null 16 | } 17 | } 18 | 19 | FirebaseAuthenticationUIAction.propTypes = {} 20 | FirebaseAuthenticationUIAction.defaultProps = {} 21 | 22 | export default FirebaseAuthenticationUIAction 23 | -------------------------------------------------------------------------------- /web/src/js/components/Authentication/__mocks__/EnterUsernameForm.js: -------------------------------------------------------------------------------- 1 | import { createMockReactComponent } from 'js/utils/test-utils' 2 | 3 | export default createMockReactComponent('EnterUsernameForm') 4 | -------------------------------------------------------------------------------- /web/src/js/components/Background/BackgroundColorPickerContainer.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { createFragmentContainer } from 'react-relay' 3 | 4 | import BackgroundColorPicker from 'js/components/Background/BackgroundColorPickerComponent' 5 | 6 | export default createFragmentContainer(BackgroundColorPicker, { 7 | user: graphql` 8 | fragment BackgroundColorPickerContainer_user on User { 9 | backgroundColor 10 | } 11 | `, 12 | }) 13 | -------------------------------------------------------------------------------- /web/src/js/components/Background/BackgroundCustomImagePickerContainer.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { createFragmentContainer } from 'react-relay' 3 | 4 | import BackgroundCustomImagePicker from 'js/components/Background/BackgroundCustomImagePickerComponent' 5 | 6 | export default createFragmentContainer(BackgroundCustomImagePicker, { 7 | user: graphql` 8 | fragment BackgroundCustomImagePickerContainer_user on User { 9 | customImage 10 | } 11 | `, 12 | }) 13 | -------------------------------------------------------------------------------- /web/src/js/components/Background/BackgroundImagePickerContainer.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { createFragmentContainer } from 'react-relay' 3 | 4 | import BackgroundImagePicker from 'js/components/Background/BackgroundImagePickerComponent' 5 | 6 | export default createFragmentContainer(BackgroundImagePicker, { 7 | user: graphql` 8 | fragment BackgroundImagePickerContainer_user on User { 9 | backgroundImage { 10 | id 11 | imageURL 12 | } 13 | } 14 | `, 15 | app: graphql` 16 | fragment BackgroundImagePickerContainer_app on App { 17 | backgroundImages(first: 50) { 18 | edges { 19 | node { 20 | id 21 | name 22 | imageURL 23 | thumbnailURL 24 | } 25 | } 26 | } 27 | } 28 | `, 29 | }) 30 | -------------------------------------------------------------------------------- /web/src/js/components/Campaign/MillionRaisedCampaignContainer.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { createFragmentContainer } from 'react-relay' 3 | import MillionRaisedCampaign from 'js/components/Campaign/MillionRaisedCampaign' 4 | 5 | export default createFragmentContainer(MillionRaisedCampaign, { 6 | app: graphql` 7 | fragment MillionRaisedCampaignContainer_app on App { 8 | moneyRaised 9 | dollarsPerDayRate 10 | } 11 | `, 12 | user: graphql` 13 | fragment MillionRaisedCampaignContainer_user on User { 14 | ...InviteFriendContainer_user 15 | } 16 | `, 17 | }) 18 | -------------------------------------------------------------------------------- /web/src/js/components/Dashboard/AssignExperimentGroupsContainer.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { createFragmentContainer } from 'react-relay' 3 | 4 | import AssignExperimentGroups from 'js/components/Dashboard/AssignExperimentGroupsComponent' 5 | 6 | export default createFragmentContainer(AssignExperimentGroups, { 7 | user: graphql` 8 | fragment AssignExperimentGroupsContainer_user on User { 9 | id 10 | joined 11 | numUsersRecruited 12 | } 13 | `, 14 | }) 15 | -------------------------------------------------------------------------------- /web/src/js/components/Dashboard/HeartsContainer.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { createFragmentContainer } from 'react-relay' 3 | 4 | import Hearts from 'js/components/Dashboard/HeartsComponent' 5 | 6 | export default createFragmentContainer(Hearts, { 7 | user: graphql` 8 | fragment HeartsContainer_user on User { 9 | vcCurrent 10 | tabsToday 11 | } 12 | `, 13 | }) 14 | -------------------------------------------------------------------------------- /web/src/js/components/Dashboard/HeartsDropdownContainer.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { createFragmentContainer } from 'react-relay' 3 | 4 | import HeartsDropdown from 'js/components/Dashboard/HeartsDropdownComponent' 5 | 6 | export default createFragmentContainer(HeartsDropdown, { 7 | app: graphql` 8 | fragment HeartsDropdownContainer_app on App { 9 | referralVcReward 10 | } 11 | `, 12 | user: graphql` 13 | fragment HeartsDropdownContainer_user on User { 14 | id 15 | vcCurrent 16 | level 17 | heartsUntilNextLevel 18 | vcDonatedAllTime 19 | numUsersRecruited 20 | tabsToday 21 | } 22 | `, 23 | }) 24 | -------------------------------------------------------------------------------- /web/src/js/components/Dashboard/LogAccountCreationContainer.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { createFragmentContainer } from 'react-relay' 3 | 4 | import LogAccountCreation from 'js/components/Dashboard/LogAccountCreationComponent' 5 | 6 | export default createFragmentContainer(LogAccountCreation, { 7 | user: graphql` 8 | fragment LogAccountCreationContainer_user on User { 9 | userId 10 | tabs 11 | } 12 | `, 13 | }) 14 | -------------------------------------------------------------------------------- /web/src/js/components/Dashboard/LogTabContainer.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { createFragmentContainer } from 'react-relay' 3 | 4 | import LogTab from 'js/components/Dashboard/LogTabComponent' 5 | 6 | export default createFragmentContainer(LogTab, { 7 | user: graphql` 8 | fragment LogTabContainer_user on User { 9 | id 10 | } 11 | `, 12 | }) 13 | -------------------------------------------------------------------------------- /web/src/js/components/Dashboard/NewUserTourContainer.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { createFragmentContainer } from 'react-relay' 3 | 4 | import NewUserTour from 'js/components/Dashboard/NewUserTourComponent' 5 | 6 | // If this needs to fetch substantially more data 7 | // than we currently do on the new tab page, we 8 | // may want to give it its own QueryRenderer so 9 | // that we don't fetch unneeded data on every tab. 10 | export default createFragmentContainer(NewUserTour, { 11 | user: graphql` 12 | fragment NewUserTourContainer_user on User { 13 | ...InviteFriendContainer_user 14 | } 15 | `, 16 | }) 17 | -------------------------------------------------------------------------------- /web/src/js/components/Dashboard/November2023ShopUser.module.css: -------------------------------------------------------------------------------- 1 | .hoverable { 2 | border: 1px solid transparent; 3 | transition: 0.3s; 4 | width: 100px; 5 | height: 100px; 6 | } 7 | 8 | .hoverable:hover { 9 | width: 100px; 10 | height: 100px; 11 | border: 3px solid #ccc; 12 | margin-bottom: -5px; 13 | } 14 | -------------------------------------------------------------------------------- /web/src/js/components/Dashboard/PostUninstallView.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { postUninstallSurveyURL } from 'js/navigation/navigation' 3 | import { goTo } from 'js/navigation/navigation' 4 | 5 | // The view the extensions open immediately after the 6 | // user uninstalls the extension. 7 | class PostUninstallView extends React.Component { 8 | componentDidMount() { 9 | goTo(postUninstallSurveyURL) 10 | } 11 | 12 | render() { 13 | return 14 | } 15 | } 16 | 17 | export default PostUninstallView 18 | -------------------------------------------------------------------------------- /web/src/js/components/Dashboard/UserBackgroundImageContainer.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { createFragmentContainer } from 'react-relay' 3 | 4 | import UserBackgroundImage from 'js/components/Dashboard/UserBackgroundImageComponent' 5 | 6 | export default createFragmentContainer(UserBackgroundImage, { 7 | user: graphql` 8 | fragment UserBackgroundImageContainer_user on User { 9 | id 10 | backgroundOption 11 | customImage 12 | backgroundColor 13 | backgroundImage { 14 | imageURL 15 | timestamp 16 | } 17 | } 18 | `, 19 | }) 20 | -------------------------------------------------------------------------------- /web/src/js/components/Dashboard/UserMenuContainer.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { createFragmentContainer } from 'react-relay' 3 | 4 | import UserMenu from 'js/components/Dashboard/UserMenuComponent' 5 | 6 | export default createFragmentContainer(UserMenu, { 7 | app: graphql` 8 | fragment UserMenuContainer_app on App { 9 | ...MoneyRaisedContainer_app 10 | ...HeartsDropdownContainer_app 11 | } 12 | `, 13 | user: graphql` 14 | fragment UserMenuContainer_user on User { 15 | ...HeartsContainer_user 16 | ...HeartsDropdownContainer_user 17 | ...VideoEngagementContainer_user 18 | email 19 | } 20 | `, 21 | }) 22 | -------------------------------------------------------------------------------- /web/src/js/components/Dashboard/VideoEngagementContainer.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { createFragmentContainer } from 'react-relay' 3 | 4 | import VideoEngagement from 'js/components/Dashboard/VideoEngagementComponent' 5 | 6 | export default createFragmentContainer(VideoEngagement, { 7 | user: graphql` 8 | fragment VideoEngagementContainer_user on User { 9 | id 10 | truexId 11 | videoAdEligible 12 | } 13 | `, 14 | }) 15 | -------------------------------------------------------------------------------- /web/src/js/components/Dashboard/__mocks__/DashboardContainer.js: -------------------------------------------------------------------------------- 1 | import { createMockReactComponent } from 'js/utils/test-utils' 2 | 3 | export default createMockReactComponent('DashboardContainer') 4 | -------------------------------------------------------------------------------- /web/src/js/components/Donate/CharityContainer.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { createFragmentContainer } from 'react-relay' 3 | 4 | import Charity from 'js/components/Donate/CharityComponent' 5 | 6 | export default createFragmentContainer(Charity, { 7 | charity: graphql` 8 | fragment CharityContainer_charity on Charity { 9 | id 10 | description 11 | logo 12 | name 13 | website 14 | ...DonateHeartsControlsContainer_charity 15 | } 16 | `, 17 | user: graphql` 18 | fragment CharityContainer_user on User { 19 | id 20 | vcCurrent 21 | ...DonateHeartsControlsContainer_user 22 | } 23 | `, 24 | }) 25 | -------------------------------------------------------------------------------- /web/src/js/components/Donate/DonateHeartsControlsContainer.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { createFragmentContainer } from 'react-relay' 3 | 4 | import DonateHeartsControlsComponent from 'js/components/Donate/DonateHeartsControlsComponent' 5 | 6 | export default createFragmentContainer(DonateHeartsControlsComponent, { 7 | charity: graphql` 8 | fragment DonateHeartsControlsContainer_charity on Charity { 9 | id 10 | image 11 | imageCaption 12 | impact 13 | name 14 | website 15 | } 16 | `, 17 | user: graphql` 18 | fragment DonateHeartsControlsContainer_user on User { 19 | id 20 | vcCurrent 21 | } 22 | `, 23 | }) 24 | -------------------------------------------------------------------------------- /web/src/js/components/Donate/SwitchToV4Container.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { createFragmentContainer } from 'react-relay' 3 | 4 | import SwitchToCats from 'js/components/Donate/SwitchToV4Component' 5 | 6 | export default createFragmentContainer(SwitchToCats, { 7 | user: graphql` 8 | fragment SwitchToV4Container_user on User { 9 | id 10 | } 11 | `, 12 | }) 13 | -------------------------------------------------------------------------------- /web/src/js/components/General/BaseContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | class BaseContainer extends React.Component { 4 | render() { 5 | const root = { 6 | position: 'absolute', 7 | top: 0, 8 | right: 0, 9 | bottom: 0, 10 | left: 0, 11 | margin: 0, 12 | padding: 0, 13 | border: 'none', 14 | } 15 | 16 | return
{this.props.children}
17 | } 18 | } 19 | 20 | export default BaseContainer 21 | -------------------------------------------------------------------------------- /web/src/js/components/General/CenteredWidgetsContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | class CenteredWidgetsContainer extends React.Component { 4 | render() { 5 | const root = { 6 | display: 'flex', 7 | alignItems: 'center', 8 | justifyContent: 'center', 9 | textAlign: 'center', 10 | position: 'absolute', 11 | top: 0, 12 | right: 0, 13 | bottom: 0, 14 | left: 0, 15 | overflow: 'hidden', 16 | boxSizing: 'border-box', 17 | pointerEvents: 'none', 18 | } 19 | 20 | return
{this.props.children}
21 | } 22 | } 23 | 24 | export default CenteredWidgetsContainer 25 | -------------------------------------------------------------------------------- /web/src/js/components/General/ConditionalWrapper.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | 3 | const ConditionalWrapper = ({ condition, wrapper, children }) => 4 | condition ? wrapper(children) : children 5 | 6 | ConditionalWrapper.propTypes = { 7 | condition: PropTypes.bool.isRequired, 8 | wrapper: PropTypes.func.isRequired, 9 | children: PropTypes.object.isRequired, 10 | } 11 | 12 | export default ConditionalWrapper 13 | -------------------------------------------------------------------------------- /web/src/js/components/General/WidgetSharedSpace.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | class WidgetSharedSpace extends React.Component { 5 | render() { 6 | const main = { 7 | position: 'absolute', 8 | zIndex: 1, 9 | top: 40, 10 | left: 5, 11 | backgroundColor: 'transparent', 12 | width: 300, 13 | overflowX: 'hidden', 14 | overflowY: 'hidden', 15 | } 16 | 17 | return ( 18 |
19 | {this.props.children} 20 |
21 | ) 22 | } 23 | } 24 | 25 | WidgetSharedSpace.propTypes = { 26 | containerStyle: PropTypes.object, 27 | } 28 | 29 | WidgetSharedSpace.defaultProps = { 30 | containerStyle: {}, 31 | } 32 | 33 | export default WidgetSharedSpace 34 | -------------------------------------------------------------------------------- /web/src/js/components/General/__mocks__/ErrorMessage.js: -------------------------------------------------------------------------------- 1 | import { createMockReactComponent } from 'js/utils/test-utils' 2 | 3 | export default createMockReactComponent('ErrorMessage') 4 | -------------------------------------------------------------------------------- /web/src/js/components/General/__mocks__/Link.js: -------------------------------------------------------------------------------- 1 | import { createMockReactComponent } from 'js/utils/test-utils' 2 | 3 | export default createMockReactComponent('Link') 4 | -------------------------------------------------------------------------------- /web/src/js/components/General/__mocks__/LinkWithActionBeforeNavigate.js: -------------------------------------------------------------------------------- 1 | import { createMockReactComponent } from 'js/utils/test-utils' 2 | 3 | export default createMockReactComponent('LinkWithActionBeforeNavigate') 4 | -------------------------------------------------------------------------------- /web/src/js/components/General/__mocks__/QueryRendererWithUser.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | // Use our react-relay QR mock for now. 4 | import { QueryRenderer } from 'react-relay' 5 | jest.mock('react-relay') 6 | 7 | export default QueryRenderer 8 | -------------------------------------------------------------------------------- /web/src/js/components/General/__mocks__/withUser.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | import React from 'react' 3 | 4 | var mockAuthUser = { 5 | id: 'abc123xyz456', 6 | email: 'foo@example.com', 7 | username: 'example', 8 | isAnonymous: false, 9 | emailVerified: true, 10 | } 11 | 12 | export const __setMockAuthUser = newMockAuthUser => { 13 | mockAuthUser = newMockAuthUser 14 | } 15 | 16 | export const __mockWithUserWrappedFunction = jest.fn( 17 | ChildComponent => props => ( 18 | 19 | ) 20 | ) 21 | 22 | export default jest.fn(options => __mockWithUserWrappedFunction) 23 | -------------------------------------------------------------------------------- /web/src/js/components/MoneyRaised/MoneyRaisedContainer.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { createFragmentContainer } from 'react-relay' 3 | 4 | import MoneyRaised from 'js/components/MoneyRaised/MoneyRaisedComponent' 5 | 6 | export default createFragmentContainer(MoneyRaised, { 7 | app: graphql` 8 | fragment MoneyRaisedContainer_app on App { 9 | moneyRaised 10 | dollarsPerDayRate 11 | } 12 | `, 13 | }) 14 | -------------------------------------------------------------------------------- /web/src/js/components/Search/CodeFuelPixel.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | // Impression tracking for search result items. 5 | const CodeFuelPixel = ({ url }) => { 6 | // eslint-disable-next-line jsx-a11y/alt-text 7 | return 8 | } 9 | CodeFuelPixel.propTypes = { 10 | url: PropTypes.string, 11 | } 12 | CodeFuelPixel.defaultProps = { 13 | url: null, 14 | } 15 | 16 | export default CodeFuelPixel 17 | -------------------------------------------------------------------------------- /web/src/js/components/Search/SearchAuthRedirect.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { replaceUrl, loginURL } from 'js/navigation/navigation' 3 | import { SEARCH_APP } from 'js/constants' 4 | 5 | // A convenience route to redirect /search/auth/ to 6 | // /newtab/auth/?app=search. 7 | class SearchAuthRedirect extends React.Component { 8 | componentDidMount() { 9 | replaceUrl(loginURL, { app: SEARCH_APP }) 10 | } 11 | 12 | render() { 13 | return null 14 | } 15 | } 16 | 17 | export default SearchAuthRedirect 18 | -------------------------------------------------------------------------------- /web/src/js/components/Search/SearchHeartsContainer.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { createFragmentContainer } from 'react-relay' 3 | 4 | import Hearts from 'js/components/Dashboard/HeartsComponent' 5 | 6 | export default createFragmentContainer(Hearts, { 7 | app: graphql` 8 | fragment SearchHeartsContainer_app on App { 9 | ...HeartsDropdownContainer_app 10 | } 11 | `, 12 | user: graphql` 13 | fragment SearchHeartsContainer_user on User { 14 | vcCurrent 15 | searchRateLimit { 16 | limitReached 17 | reason 18 | } 19 | ...HeartsDropdownContainer_user 20 | } 21 | `, 22 | }) 23 | -------------------------------------------------------------------------------- /web/src/js/components/Search/SearchMenuContainer.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { createFragmentContainer } from 'react-relay' 3 | 4 | import SearchMenu from 'js/components/Search/SearchMenuComponent' 5 | 6 | export default createFragmentContainer(SearchMenu, { 7 | app: graphql` 8 | fragment SearchMenuContainer_app on App { 9 | ...SearchHeartsContainer_app 10 | ...MoneyRaisedContainer_app 11 | } 12 | `, 13 | user: graphql` 14 | fragment SearchMenuContainer_user on User { 15 | id 16 | searches 17 | vcDonatedAllTime 18 | ...SearchHeartsContainer_user 19 | } 20 | `, 21 | }) 22 | -------------------------------------------------------------------------------- /web/src/js/components/Search/SearchPostUninstallView.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { searchPostUninstallSurveyURL } from 'js/navigation/navigation' 3 | import { goTo } from 'js/navigation/navigation' 4 | 5 | // The view the search extension opens immediately after the 6 | // user uninstalls the extension. 7 | class SearchPostUninstallView extends React.Component { 8 | componentDidMount() { 9 | goTo(searchPostUninstallSurveyURL) 10 | } 11 | 12 | render() { 13 | return 14 | } 15 | } 16 | 17 | export default SearchPostUninstallView 18 | -------------------------------------------------------------------------------- /web/src/js/components/Search/SearchRandomQueryView.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { goTo, searchBaseURL } from 'js/navigation/navigation' 3 | 4 | // The browser extension opens this page when the user 5 | // clicks on the browser extension icon. We should not 6 | // prepopulate any search queries. 7 | class SearchRandomQueryView extends React.Component { 8 | componentDidMount() { 9 | goTo(searchBaseURL) 10 | } 11 | 12 | render() { 13 | return 14 | } 15 | } 16 | 17 | export default SearchRandomQueryView 18 | -------------------------------------------------------------------------------- /web/src/js/components/Search/__mocks__/SearchPageComponent.js: -------------------------------------------------------------------------------- 1 | import { createMockReactComponent } from 'js/utils/test-utils' 2 | 3 | export default createMockReactComponent('SearchPageComponent') 4 | -------------------------------------------------------------------------------- /web/src/js/components/Search/__mocks__/SearchResultItem.js: -------------------------------------------------------------------------------- 1 | import { createMockReactComponent } from 'js/utils/test-utils' 2 | 3 | export default createMockReactComponent('SearchResultItem') 4 | -------------------------------------------------------------------------------- /web/src/js/components/Search/__mocks__/fetchWikipediaResults.js: -------------------------------------------------------------------------------- 1 | export default jest.fn(() => Promise.resolve({})) 2 | -------------------------------------------------------------------------------- /web/src/js/components/Search/__mocks__/getBingMarketCode.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | export default jest.fn(() => Promise.resolve('en-US')) 4 | -------------------------------------------------------------------------------- /web/src/js/components/Search/__tests__/FakeSearchResults.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | import React from 'react' 4 | import { shallow } from 'enzyme' 5 | 6 | const getMockProps = () => ({ 7 | query: 'tacos', 8 | }) 9 | 10 | afterEach(() => { 11 | jest.clearAllMocks() 12 | }) 13 | 14 | describe('SearchResults component', () => { 15 | it('renders without error', () => { 16 | const FakeSearchResults = require('js/components/Search/FakeSearchResults') 17 | .default 18 | const mockProps = getMockProps() 19 | shallow() 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /web/src/js/components/Search/searchResultsStyles.js: -------------------------------------------------------------------------------- 1 | export const linkColor = '#1a0dab' 2 | export const linkColorVisited = '#670199' 3 | -------------------------------------------------------------------------------- /web/src/js/components/Settings/Account/AccountContainer.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { createFragmentContainer } from 'react-relay' 3 | 4 | import Account from 'js/components/Settings/Account/AccountComponent' 5 | 6 | export default createFragmentContainer(Account, { 7 | user: graphql` 8 | fragment AccountContainer_user on User { 9 | id 10 | email 11 | username 12 | } 13 | `, 14 | }) 15 | -------------------------------------------------------------------------------- /web/src/js/components/Settings/Profile/InviteFriendContainer.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { createFragmentContainer } from 'react-relay' 3 | 4 | import InviteFriend from 'js/components/Settings/Profile/InviteFriendComponent' 5 | 6 | export default createFragmentContainer(InviteFriend, { 7 | user: graphql` 8 | fragment InviteFriendContainer_user on User { 9 | id 10 | username 11 | } 12 | `, 13 | }) 14 | -------------------------------------------------------------------------------- /web/src/js/components/Settings/Profile/ProfileDonateHeartsContainer.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { createFragmentContainer } from 'react-relay' 3 | 4 | import ProfileDonateHearts from 'js/components/Settings/Profile/ProfileDonateHeartsComponent' 5 | 6 | export default createFragmentContainer(ProfileDonateHearts, { 7 | app: graphql` 8 | fragment ProfileDonateHeartsContainer_app on App { 9 | charities(first: 20, filters: { isPermanentPartner: true }) { 10 | edges { 11 | node { 12 | id 13 | ...CharityContainer_charity 14 | } 15 | } 16 | } 17 | } 18 | `, 19 | user: graphql` 20 | fragment ProfileDonateHeartsContainer_user on User { 21 | ...CharityContainer_user 22 | ...SwitchToV4Container_user 23 | } 24 | `, 25 | }) 26 | -------------------------------------------------------------------------------- /web/src/js/components/Settings/Profile/ProfileInviteFriendContainer.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { createFragmentContainer } from 'react-relay' 3 | 4 | import ProfileInviteFriend from 'js/components/Settings/Profile/ProfileInviteFriendComponent' 5 | 6 | export default createFragmentContainer(ProfileInviteFriend, { 7 | app: graphql` 8 | fragment ProfileInviteFriendContainer_app on App { 9 | referralVcReward 10 | } 11 | `, 12 | user: graphql` 13 | fragment ProfileInviteFriendContainer_user on User { 14 | numUsersRecruited 15 | ...InviteFriendContainer_user 16 | } 17 | `, 18 | }) 19 | -------------------------------------------------------------------------------- /web/src/js/components/Settings/Profile/ProfileStatsContainer.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { createFragmentContainer } from 'react-relay' 3 | 4 | import ProfileStats from 'js/components/Settings/Profile/ProfileStatsComponent' 5 | 6 | export default createFragmentContainer(ProfileStats, { 7 | user: graphql` 8 | fragment ProfileStatsContainer_user on User { 9 | id 10 | username 11 | heartsUntilNextLevel 12 | joined 13 | level 14 | maxTabsDay { 15 | date 16 | numTabs 17 | } 18 | numUsersRecruited 19 | searches 20 | tabs 21 | vcDonatedAllTime 22 | } 23 | `, 24 | }) 25 | -------------------------------------------------------------------------------- /web/src/js/components/Settings/SettingsChildWrapperComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | const SettingsChildWrapper = props => { 5 | const { children } = props 6 | return ( 7 |
13 | {children} 14 |
15 | ) 16 | } 17 | 18 | SettingsChildWrapper.propTypes = { 19 | children: PropTypes.element, 20 | } 21 | 22 | SettingsChildWrapper.defaultProps = {} 23 | 24 | export default SettingsChildWrapper 25 | -------------------------------------------------------------------------------- /web/src/js/components/Settings/Widgets/WidgetConfigContainer.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gladly-team/tab/1962d7cc9dfee8e019195bc2be12e43563c24591/web/src/js/components/Settings/Widgets/WidgetConfigContainer.js -------------------------------------------------------------------------------- /web/src/js/components/Settings/Widgets/WidgetSettingsContainer.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { createFragmentContainer } from 'react-relay' 3 | 4 | import WidgetSettings from 'js/components/Settings/Widgets/WidgetSettingsComponent' 5 | 6 | export default createFragmentContainer(WidgetSettings, { 7 | widget: graphql` 8 | fragment WidgetSettingsContainer_widget on Widget { 9 | id 10 | name 11 | enabled 12 | config 13 | settings 14 | } 15 | `, 16 | user: graphql` 17 | fragment WidgetSettingsContainer_user on User { 18 | id 19 | } 20 | `, 21 | appWidget: graphql` 22 | fragment WidgetSettingsContainer_appWidget on Widget { 23 | id 24 | name 25 | type 26 | settings 27 | } 28 | `, 29 | }) 30 | -------------------------------------------------------------------------------- /web/src/js/components/Shop/ShopContainer.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { createFragmentContainer } from 'react-relay' 3 | 4 | import ShopComponent from 'js/components/Shop/ShopComponent' 5 | 6 | export default createFragmentContainer(ShopComponent, { 7 | user: graphql` 8 | fragment ShopContainer_user on User { 9 | id 10 | userId 11 | causeId 12 | ...SwitchToV4Container_user 13 | } 14 | `, 15 | }) 16 | -------------------------------------------------------------------------------- /web/src/js/components/Widget/EditWidgetChipAnimation.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { CSSTransition, TransitionGroup } from 'react-transition-group' 3 | 4 | class EditWidgetChipAnimation extends React.Component { 5 | render() { 6 | return ( 7 | 8 | {React.Children.map(this.props.children, (item, i) => { 9 | return item ? ( 10 | 19 | {item} 20 | 21 | ) : null 22 | })} 23 | 24 | ) 25 | } 26 | } 27 | 28 | export default EditWidgetChipAnimation 29 | -------------------------------------------------------------------------------- /web/src/js/components/Widget/EmptyWidgetMsg.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | class EmptyWidgetMsg extends React.Component { 4 | render() { 5 | return null 6 | } 7 | } 8 | 9 | export default EmptyWidgetMsg 10 | -------------------------------------------------------------------------------- /web/src/js/components/Widget/WidgetIconContainer.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { createFragmentContainer } from 'react-relay' 3 | 4 | import WidgetIcon from 'js/components/Widget/WidgetIconComponent' 5 | 6 | export default createFragmentContainer(WidgetIcon, { 7 | widget: graphql` 8 | fragment WidgetIconContainer_widget on Widget { 9 | id 10 | type 11 | visible 12 | } 13 | `, 14 | }) 15 | -------------------------------------------------------------------------------- /web/src/js/components/Widget/WidgetPieceWrapper.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | // import PropTypes from 'prop-types' 3 | 4 | class WidgetPieceWrapper extends React.Component { 5 | render() { 6 | return {this.props.children} 7 | } 8 | } 9 | 10 | WidgetPieceWrapper.propTypes = {} 11 | 12 | WidgetPieceWrapper.defaultProps = {} 13 | 14 | export default WidgetPieceWrapper 15 | -------------------------------------------------------------------------------- /web/src/js/components/Widget/Widgets/Bookmarks/BookmarksWidgetContainer.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { createFragmentContainer } from 'react-relay' 3 | 4 | import BookmarksWidget from 'js/components/Widget/Widgets/Bookmarks/BookmarksWidgetComponent' 5 | 6 | export default createFragmentContainer(BookmarksWidget, { 7 | widget: graphql` 8 | fragment BookmarksWidgetContainer_widget on Widget { 9 | id 10 | name 11 | enabled 12 | visible 13 | data 14 | type 15 | } 16 | `, 17 | user: graphql` 18 | fragment BookmarksWidgetContainer_user on User { 19 | id 20 | } 21 | `, 22 | }) 23 | -------------------------------------------------------------------------------- /web/src/js/components/Widget/Widgets/Clock/ClockWidgetContainer.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { createFragmentContainer } from 'react-relay' 3 | 4 | import ClockWidget from 'js/components/Widget/Widgets/Clock/ClockWidgetComponent' 5 | 6 | export default createFragmentContainer(ClockWidget, { 7 | widget: graphql` 8 | fragment ClockWidgetContainer_widget on Widget { 9 | id 10 | name 11 | enabled 12 | config 13 | settings 14 | type 15 | } 16 | `, 17 | user: graphql` 18 | fragment ClockWidgetContainer_user on User { 19 | id 20 | } 21 | `, 22 | }) 23 | -------------------------------------------------------------------------------- /web/src/js/components/Widget/Widgets/Notes/AddNoteForm.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import EditWidgetChip from 'js/components/Widget/EditWidgetChip' 4 | 5 | class AddNoteForm extends React.Component { 6 | create() { 7 | this.props.addNote('') 8 | } 9 | 10 | render() { 11 | return ( 12 | 16 | ) 17 | } 18 | } 19 | 20 | AddNoteForm.propTypes = { 21 | addNote: PropTypes.func.isRequired, 22 | } 23 | 24 | AddNoteForm.defaultProps = {} 25 | 26 | export default AddNoteForm 27 | -------------------------------------------------------------------------------- /web/src/js/components/Widget/Widgets/Notes/NotesWidgetContainer.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { createFragmentContainer } from 'react-relay' 3 | 4 | import NotesWidget from 'js/components/Widget/Widgets/Notes/NotesWidgetComponent' 5 | 6 | export default createFragmentContainer(NotesWidget, { 7 | widget: graphql` 8 | fragment NotesWidgetContainer_widget on Widget { 9 | id 10 | name 11 | enabled 12 | visible 13 | data 14 | type 15 | } 16 | `, 17 | user: graphql` 18 | fragment NotesWidgetContainer_user on User { 19 | id 20 | } 21 | `, 22 | }) 23 | -------------------------------------------------------------------------------- /web/src/js/components/Widget/Widgets/Search/SearchWidgetContainer.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { createFragmentContainer } from 'react-relay' 3 | 4 | import SearchWidget from 'js/components/Widget/Widgets/Search/SearchWidgetComponent' 5 | 6 | export default createFragmentContainer(SearchWidget, { 7 | widget: graphql` 8 | fragment SearchWidgetContainer_widget on Widget { 9 | id 10 | name 11 | enabled 12 | data 13 | config 14 | settings 15 | type 16 | } 17 | `, 18 | user: graphql` 19 | fragment SearchWidgetContainer_user on User { 20 | id 21 | } 22 | `, 23 | }) 24 | -------------------------------------------------------------------------------- /web/src/js/components/Widget/Widgets/Todos/TodosWidgetContainer.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { createFragmentContainer } from 'react-relay' 3 | 4 | import TodosWidget from 'js/components/Widget/Widgets/Todos/TodosWidgetComponent' 5 | 6 | export default createFragmentContainer(TodosWidget, { 7 | widget: graphql` 8 | fragment TodosWidgetContainer_widget on Widget { 9 | id 10 | name 11 | enabled 12 | visible 13 | data 14 | type 15 | } 16 | `, 17 | user: graphql` 18 | fragment TodosWidgetContainer_user on User { 19 | id 20 | } 21 | `, 22 | }) 23 | -------------------------------------------------------------------------------- /web/src/js/components/Widget/Widgets/WidgetPieceWrapper.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | // import PropTypes from 'prop-types' 3 | 4 | class WidgetPieceWrapper extends React.Component { 5 | render() { 6 | return {this.props.children} 7 | } 8 | } 9 | 10 | WidgetPieceWrapper.propTypes = {} 11 | 12 | WidgetPieceWrapper.defaultProps = {} 13 | 14 | export default WidgetPieceWrapper 15 | -------------------------------------------------------------------------------- /web/src/js/components/Widget/WidgetsContainer.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { createFragmentContainer } from 'react-relay' 3 | 4 | import Widgets from 'js/components/Widget/WidgetsComponent' 5 | 6 | export default createFragmentContainer(Widgets, { 7 | user: graphql` 8 | fragment WidgetsContainer_user on User { 9 | id 10 | activeWidget 11 | ...WidgetContainer_user 12 | widgets(first: 20, enabled: true) { 13 | edges { 14 | node { 15 | id 16 | type 17 | ...WidgetContainer_widget 18 | ...WidgetIconContainer_widget 19 | } 20 | } 21 | } 22 | } 23 | `, 24 | }) 25 | -------------------------------------------------------------------------------- /web/src/js/mutations/CreateVideoAdLogMutation.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import commitMutation from 'relay-commit-mutation-promise' 3 | import environment from 'js/relay-env' 4 | 5 | const mutation = graphql` 6 | mutation CreateVideoAdLogMutation($input: CreateVideoAdLogInput!) { 7 | createVideoAdLog(input: $input) { 8 | VideoAdLog { 9 | id 10 | } 11 | } 12 | } 13 | ` 14 | 15 | export default userId => { 16 | return commitMutation(environment, { 17 | mutation, 18 | variables: { 19 | input: userId, 20 | }, 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /web/src/js/mutations/DeleteUserMutation.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { commitMutation } from 'react-relay' 3 | 4 | const mutation = graphql` 5 | mutation DeleteUserMutation($input: DeleteUserInput!) { 6 | deleteUser(input: $input) { 7 | success 8 | } 9 | } 10 | ` 11 | 12 | function commit( 13 | environment, 14 | userId, 15 | onCompleted = () => {}, 16 | onError = () => {} 17 | ) { 18 | return commitMutation(environment, { 19 | mutation, 20 | variables: { 21 | input: { 22 | userId, 23 | }, 24 | }, 25 | onCompleted: response => { 26 | onCompleted(response) 27 | }, 28 | onError: err => { 29 | onError(err) 30 | }, 31 | }) 32 | } 33 | 34 | export default commit 35 | -------------------------------------------------------------------------------- /web/src/js/mutations/LogEmailVerifiedMutation.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { commitMutation } from 'react-relay' 3 | 4 | const mutation = graphql` 5 | mutation LogEmailVerifiedMutation($input: LogEmailVerifiedMutationInput!) { 6 | logEmailVerified(input: $input) { 7 | user { 8 | id 9 | } 10 | } 11 | } 12 | ` 13 | 14 | function commit( 15 | environment, 16 | userId, 17 | onCompleted = () => {}, 18 | onError = () => {} 19 | ) { 20 | return commitMutation(environment, { 21 | mutation, 22 | variables: { 23 | input: { userId }, 24 | }, 25 | onCompleted, 26 | onError, 27 | }) 28 | } 29 | 30 | export default commit 31 | -------------------------------------------------------------------------------- /web/src/js/mutations/LogReferralLinkClickMutation.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import commitMutation from 'relay-commit-mutation-promise' 3 | import environment from 'js/relay-env' 4 | 5 | const mutation = graphql` 6 | mutation LogReferralLinkClickMutation($input: LogReferralLinkClickInput!) { 7 | logReferralLinkClick(input: $input) { 8 | success 9 | } 10 | } 11 | ` 12 | 13 | export default input => { 14 | return commitMutation(environment, { 15 | mutation, 16 | variables: { 17 | input: input, 18 | }, 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /web/src/js/mutations/LogSearchMutation.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import commitMutation from 'relay-commit-mutation-promise' 3 | import environment from 'js/relay-env' 4 | 5 | const mutation = graphql` 6 | mutation LogSearchMutation($input: LogSearchInput!) { 7 | logSearch(input: $input) { 8 | user { 9 | id 10 | heartsUntilNextLevel 11 | level 12 | searches 13 | searchRateLimit { 14 | limitReached 15 | reason 16 | } 17 | searchesToday 18 | vcCurrent 19 | vcAllTime 20 | } 21 | } 22 | } 23 | ` 24 | 25 | export default input => { 26 | return commitMutation(environment, { 27 | mutation, 28 | variables: { 29 | input: input, 30 | }, 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /web/src/js/mutations/LogTabMutation.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import commitMutation from 'relay-commit-mutation-promise' 3 | import environment from 'js/relay-env' 4 | 5 | const mutation = graphql` 6 | mutation LogTabMutation($input: LogTabInput!) { 7 | logTab(input: $input) { 8 | user { 9 | id 10 | heartsUntilNextLevel 11 | level 12 | tabs 13 | tabsToday 14 | vcCurrent 15 | } 16 | } 17 | } 18 | ` 19 | 20 | export default input => { 21 | return commitMutation(environment, { 22 | mutation, 23 | variables: { 24 | input: input, 25 | }, 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /web/src/js/mutations/LogUserExperimentActionsMutation.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import commitMutation from 'relay-commit-mutation-promise' 3 | import environment from 'js/relay-env' 4 | 5 | const mutation = graphql` 6 | mutation LogUserExperimentActionsMutation( 7 | $input: LogUserExperimentActionsInput! 8 | ) { 9 | logUserExperimentActions(input: $input) { 10 | user { 11 | id 12 | } 13 | } 14 | } 15 | ` 16 | 17 | export default input => { 18 | return commitMutation(environment, { 19 | mutation, 20 | variables: { 21 | input: input, 22 | }, 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /web/src/js/mutations/LogUserRevenueMutation.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import commitMutation from 'relay-commit-mutation-promise' 3 | import environment from 'js/relay-env' 4 | 5 | const mutation = graphql` 6 | mutation LogUserRevenueMutation($input: LogUserRevenueInput!) { 7 | logUserRevenue(input: $input) { 8 | success 9 | } 10 | } 11 | ` 12 | 13 | export default input => { 14 | return commitMutation(environment, { 15 | mutation, 16 | variables: { 17 | input: input, 18 | }, 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /web/src/js/mutations/LogVideoAdCompleteMutation.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import commitMutation from 'relay-commit-mutation-promise' 3 | import environment from 'js/relay-env' 4 | 5 | const mutation = graphql` 6 | mutation LogVideoAdCompleteMutation($input: LogVideoAdCompleteInput!) { 7 | logVideoAdComplete(input: $input) { 8 | success 9 | user { 10 | vcCurrent 11 | videoAdEligible 12 | } 13 | } 14 | } 15 | ` 16 | 17 | export default input => { 18 | return commitMutation(environment, { 19 | mutation, 20 | variables: { 21 | input: input, 22 | }, 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /web/src/js/mutations/MergeIntoExistingUserMutation.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { commitMutation } from 'react-relay' 3 | 4 | const mutation = graphql` 5 | mutation MergeIntoExistingUserMutation($input: MergeIntoExistingUserInput!) { 6 | mergeIntoExistingUser(input: $input) { 7 | success 8 | } 9 | } 10 | ` 11 | 12 | function commit( 13 | environment, 14 | userId, 15 | onCompleted = () => {}, 16 | onError = () => {} 17 | ) { 18 | return commitMutation(environment, { 19 | mutation, 20 | variables: { 21 | input: { userId }, 22 | }, 23 | onCompleted, 24 | onError, 25 | }) 26 | } 27 | 28 | export default commit 29 | -------------------------------------------------------------------------------- /web/src/js/mutations/SetBackgroundColorMutation.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { commitMutation } from 'react-relay' 3 | 4 | const mutation = graphql` 5 | mutation SetBackgroundColorMutation($input: SetUserBkgColorInput!) { 6 | setUserBkgColor(input: $input) { 7 | user { 8 | backgroundOption 9 | backgroundColor 10 | } 11 | } 12 | } 13 | ` 14 | 15 | function commit( 16 | environment, 17 | user, 18 | color, 19 | onCompleted = () => {}, 20 | onError = () => {} 21 | ) { 22 | const userId = user.id 23 | 24 | return commitMutation(environment, { 25 | mutation, 26 | variables: { 27 | input: { userId, color }, 28 | }, 29 | onCompleted, 30 | onError, 31 | }) 32 | } 33 | 34 | export default { commit } 35 | -------------------------------------------------------------------------------- /web/src/js/mutations/SetBackgroundCustomImageMutation.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { commitMutation } from 'react-relay' 3 | 4 | const mutation = graphql` 5 | mutation SetBackgroundCustomImageMutation( 6 | $input: SetUserBkgCustomImageInput! 7 | ) { 8 | setUserBkgCustomImage(input: $input) { 9 | user { 10 | backgroundOption 11 | customImage 12 | } 13 | } 14 | } 15 | ` 16 | 17 | function commit( 18 | environment, 19 | user, 20 | image, 21 | onCompleted = () => {}, 22 | onError = () => {} 23 | ) { 24 | const userId = user.id 25 | 26 | return commitMutation(environment, { 27 | mutation, 28 | variables: { 29 | input: { userId, image }, 30 | }, 31 | onCompleted, 32 | onError, 33 | }) 34 | } 35 | 36 | export default { commit } 37 | -------------------------------------------------------------------------------- /web/src/js/mutations/SetEmailMutation.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { commitMutation } from 'react-relay' 3 | 4 | const mutation = graphql` 5 | mutation SetEmailMutation($input: SetEmailInput!) { 6 | setEmail(input: $input) { 7 | user { 8 | email 9 | } 10 | } 11 | } 12 | ` 13 | 14 | function commit( 15 | environment, 16 | userId, 17 | email, 18 | onCompleted = () => {}, 19 | onError = () => {} 20 | ) { 21 | return commitMutation(environment, { 22 | mutation, 23 | variables: { 24 | input: { userId, email }, 25 | }, 26 | onCompleted, 27 | onError, 28 | }) 29 | } 30 | 31 | export default commit 32 | -------------------------------------------------------------------------------- /web/src/js/mutations/SetUserActiveWidgetMutation.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { commitMutation } from 'react-relay' 3 | 4 | const mutation = graphql` 5 | mutation SetUserActiveWidgetMutation($input: SetUserActiveWidgetInput!) { 6 | setUserActiveWidget(input: $input) { 7 | user { 8 | activeWidget 9 | } 10 | } 11 | } 12 | ` 13 | 14 | function commit( 15 | environment, 16 | user, 17 | widget, 18 | onCompleted = () => {}, 19 | onError = () => {} 20 | ) { 21 | const userId = user.id 22 | const widgetId = widget.id 23 | 24 | return commitMutation(environment, { 25 | mutation, 26 | variables: { 27 | input: { userId, widgetId }, 28 | }, 29 | onCompleted, 30 | onError, 31 | }) 32 | } 33 | 34 | export default { commit } 35 | -------------------------------------------------------------------------------- /web/src/js/mutations/SetUserCauseMutation.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import commitMutation from 'relay-commit-mutation-promise' 3 | 4 | const mutation = graphql` 5 | mutation SetUserCauseMutation($input: SetUserCauseInput!) { 6 | setUserCause(input: $input) { 7 | user { 8 | id 9 | cause { 10 | id 11 | } 12 | } 13 | } 14 | } 15 | ` 16 | 17 | function commit( 18 | environment, 19 | userId, 20 | causeId, 21 | onCompleted = () => {}, 22 | onError = () => {} 23 | ) { 24 | return commitMutation(environment, { 25 | mutation, 26 | variables: { 27 | input: { userId, causeId }, 28 | }, 29 | onCompleted, 30 | onError, 31 | }) 32 | } 33 | 34 | export default commit 35 | -------------------------------------------------------------------------------- /web/src/js/mutations/SetUsernameMutation.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { commitMutation } from 'react-relay' 3 | 4 | const mutation = graphql` 5 | mutation SetUsernameMutation($input: SetUsernameInput!) { 6 | setUsername(input: $input) { 7 | user { 8 | username 9 | } 10 | errors { 11 | code 12 | message 13 | } 14 | } 15 | } 16 | ` 17 | 18 | function commit( 19 | environment, 20 | userId, 21 | username, 22 | onCompleted = () => {}, 23 | onError = () => {} 24 | ) { 25 | return commitMutation(environment, { 26 | mutation, 27 | variables: { 28 | input: { userId, username }, 29 | }, 30 | onCompleted, 31 | onError, 32 | }) 33 | } 34 | 35 | export default commit 36 | -------------------------------------------------------------------------------- /web/src/js/mutations/SetV4BetaMutation.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import commitMutation from 'relay-commit-mutation-promise' 3 | import environment from 'js/relay-env' 4 | 5 | const mutation = graphql` 6 | mutation SetV4BetaMutation($input: SetV4BetaInput!) { 7 | setV4Beta(input: $input) { 8 | user { 9 | v4BetaEnabled 10 | } 11 | } 12 | } 13 | ` 14 | 15 | export default input => { 16 | return commitMutation(environment, { 17 | mutation, 18 | variables: { 19 | input: input, 20 | }, 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /web/src/js/mutations/UpdateUserExperimentGroupsMutation.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { commitMutation } from 'react-relay' 3 | 4 | const mutation = graphql` 5 | mutation UpdateUserExperimentGroupsMutation( 6 | $input: UpdateUserExperimentGroupsInput! 7 | ) { 8 | updateUserExperimentGroups(input: $input) { 9 | user { 10 | id 11 | } 12 | } 13 | } 14 | ` 15 | 16 | function commit( 17 | environment, 18 | { userId, experimentGroups }, 19 | onCompleted = () => {}, 20 | onError = () => {} 21 | ) { 22 | return commitMutation(environment, { 23 | mutation, 24 | variables: { 25 | input: { 26 | userId, 27 | experimentGroups, 28 | }, 29 | }, 30 | onCompleted, 31 | onError, 32 | }) 33 | } 34 | 35 | export default commit 36 | -------------------------------------------------------------------------------- /web/src/js/mutations/UpdateWidgetConfigMutation.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { commitMutation } from 'react-relay' 3 | 4 | const mutation = graphql` 5 | mutation UpdateWidgetConfigMutation($input: UpdateWidgetConfigInput!) { 6 | updateWidgetConfig(input: $input) { 7 | widget { 8 | config 9 | } 10 | } 11 | } 12 | ` 13 | 14 | function commit( 15 | environment, 16 | user, 17 | widget, 18 | config, 19 | onCompleted = () => {}, 20 | onError = () => {} 21 | ) { 22 | const userId = user.id 23 | const widgetId = widget.id 24 | 25 | return commitMutation(environment, { 26 | mutation, 27 | variables: { 28 | input: { userId, widgetId, config }, 29 | }, 30 | onCompleted, 31 | onError, 32 | }) 33 | } 34 | 35 | export default { commit } 36 | -------------------------------------------------------------------------------- /web/src/js/mutations/UpdateWidgetDataMutation.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { commitMutation } from 'react-relay' 3 | 4 | const mutation = graphql` 5 | mutation UpdateWidgetDataMutation($input: UpdateWidgetDataInput!) { 6 | updateWidgetData(input: $input) { 7 | widget { 8 | data 9 | } 10 | } 11 | } 12 | ` 13 | 14 | function commit( 15 | environment, 16 | user, 17 | widget, 18 | data, 19 | onCompleted = () => {}, 20 | onError = () => {} 21 | ) { 22 | const userId = user.id 23 | const widgetId = widget.id 24 | 25 | return commitMutation(environment, { 26 | mutation, 27 | variables: { 28 | input: { userId, widgetId, data }, 29 | }, 30 | onCompleted, 31 | onError, 32 | }) 33 | } 34 | 35 | export default { commit } 36 | -------------------------------------------------------------------------------- /web/src/js/mutations/UpdateWidgetEnabledMutation.js: -------------------------------------------------------------------------------- 1 | import graphql from 'babel-plugin-relay/macro' 2 | import { commitMutation } from 'react-relay' 3 | 4 | const mutation = graphql` 5 | mutation UpdateWidgetEnabledMutation($input: UpdateWidgetEnabledInput!) { 6 | updateWidgetEnabled(input: $input) { 7 | widget { 8 | enabled 9 | } 10 | } 11 | } 12 | ` 13 | 14 | function commit( 15 | environment, 16 | user, 17 | widget, 18 | enabled, 19 | onCompleted = () => {}, 20 | onError = () => {} 21 | ) { 22 | const userId = user.id 23 | const widgetId = widget.id 24 | 25 | return commitMutation(environment, { 26 | mutation, 27 | variables: { 28 | input: { userId, widgetId, enabled }, 29 | }, 30 | onCompleted, 31 | onError, 32 | }) 33 | } 34 | 35 | export default { commit } 36 | -------------------------------------------------------------------------------- /web/src/js/mutations/__mocks__/CreateNewUserMutation.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | var onCompletedCallback = () => {} 4 | var onErrorCallback = () => {} 5 | 6 | // Mock Relay data returns 7 | export const __runOnCompleted = (response = null) => { 8 | onCompletedCallback(response) 9 | } 10 | 11 | export const __runOnError = response => { 12 | onErrorCallback(response) 13 | } 14 | 15 | export default jest.fn( 16 | (environment, userId, email, referralData, onCompleted, onError) => { 17 | onCompletedCallback = onCompleted 18 | onErrorCallback = onError 19 | } 20 | ) 21 | -------------------------------------------------------------------------------- /web/src/js/mutations/__mocks__/DonateVcMutation.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | export default jest.fn(() => Promise.resolve({})) 4 | -------------------------------------------------------------------------------- /web/src/js/mutations/__mocks__/LogReferralLinkClickMutation.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | export default jest.fn(() => Promise.resolve({})) 4 | -------------------------------------------------------------------------------- /web/src/js/mutations/__mocks__/LogSearchMutation.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | export default jest.fn(() => Promise.resolve({})) 4 | -------------------------------------------------------------------------------- /web/src/js/mutations/__mocks__/LogTabMutation.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | export default jest.fn(() => Promise.resolve({})) 4 | -------------------------------------------------------------------------------- /web/src/js/mutations/__mocks__/SetBackgroundDailyImageMutation.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | var onCompletedCallback = () => {} 4 | var onErrorCallback = () => {} 5 | 6 | // Mock Relay data returns 7 | export const __runOnCompleted = response => { 8 | onCompletedCallback(response) 9 | } 10 | 11 | export const __runOnError = response => { 12 | onErrorCallback(response) 13 | } 14 | 15 | export default jest.fn((environment, userId, onCompleted, onError) => { 16 | onCompletedCallback = onCompleted 17 | onErrorCallback = onError 18 | }) 19 | -------------------------------------------------------------------------------- /web/src/js/mutations/__mocks__/SetUsernameMutation.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | var onCompletedCallback = () => {} 4 | var onErrorCallback = () => {} 5 | 6 | var usernameArg = null 7 | 8 | // Mock Relay data returns 9 | export const __runOnCompleted = (response = null) => { 10 | // Default response for this mutation's mock 11 | if (!response) { 12 | response = { 13 | setUsername: { 14 | user: usernameArg, 15 | errors: null, 16 | }, 17 | } 18 | } 19 | onCompletedCallback(response) 20 | } 21 | 22 | export const __runOnError = response => { 23 | onErrorCallback(response) 24 | } 25 | 26 | export default jest.fn( 27 | (environment, userId, username, onCompleted, onError) => { 28 | onCompletedCallback = onCompleted 29 | onErrorCallback = onError 30 | usernameArg = username 31 | } 32 | ) 33 | -------------------------------------------------------------------------------- /web/src/js/mutations/__mocks__/SetV4BetaMutation.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | module.exports = jest.fn(() => Promise.resolve({})) 3 | -------------------------------------------------------------------------------- /web/src/js/navigation/__mocks__/utils.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | const navigationUtilsMock = jest.genMockFromModule('js/navigation/utils') 4 | const actualModule = jest.requireActual('js/navigation/utils') 5 | 6 | navigationUtilsMock.isAbsoluteURL = actualModule.isAbsoluteURL 7 | module.exports = navigationUtilsMock 8 | -------------------------------------------------------------------------------- /web/src/js/theme/searchTheme.js: -------------------------------------------------------------------------------- 1 | import tabTheme from 'js/theme/defaultV1' 2 | 3 | const primaryMainColor = '#00b597' 4 | const primaryContrastTextColor = '#fff' 5 | 6 | export default { 7 | ...tabTheme, 8 | palette: { 9 | ...tabTheme.palette, 10 | primary: { 11 | main: primaryMainColor, 12 | contrastText: primaryContrastTextColor, 13 | }, 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /web/src/js/utils/__mocks__/client-location.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | export const getCountry = jest.fn(async () => 'US') 4 | 5 | export const isInEuropeanUnion = jest.fn(async () => false) 6 | -------------------------------------------------------------------------------- /web/src/js/utils/__mocks__/detectAdblocker.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | export default jest.fn().mockResolvedValue(false) 4 | -------------------------------------------------------------------------------- /web/src/js/utils/__mocks__/experiments.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | // Only mock some specific functions. 4 | const experimentsActual = require.requireActual('js/utils/experiments') 5 | experimentsActual.getUserExperimentGroup = jest.fn(() => 'none') 6 | experimentsActual.assignUserToTestGroups = jest.fn() 7 | experimentsActual.getUserTestGroupsForMutation = jest.fn(() => {}) 8 | module.exports = experimentsActual 9 | -------------------------------------------------------------------------------- /web/src/js/utils/__mocks__/extension-messenger.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | const extensionMessenger = jest.genMockFromModule('../extension-messenger') 4 | 5 | module.exports = extensionMessenger 6 | -------------------------------------------------------------------------------- /web/src/js/utils/__mocks__/localstorage-mgr.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | import { isNil } from 'lodash/lang' 3 | 4 | var mockStorage = {} 5 | 6 | export const __mockClear = () => { 7 | mockStorage = {} 8 | } 9 | 10 | export default { 11 | getItem: jest.fn(key => { 12 | return mockStorage[key] 13 | }), 14 | setItem: jest.fn((key, val) => { 15 | if (!isNil(val)) { 16 | mockStorage[key] = String(val) 17 | } 18 | }), 19 | removeItem: jest.fn((key, val) => { 20 | delete mockStorage[key] 21 | }), 22 | clear: () => { 23 | __mockClear() 24 | }, 25 | } 26 | -------------------------------------------------------------------------------- /web/src/js/utils/__mocks__/search-utils.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | // Only mock some specific functions. 4 | const searchUtilsActual = require.requireActual('js/utils/search-utils') 5 | searchUtilsActual.isReactSnapClient = jest.fn(() => false) 6 | searchUtilsActual.getSearchProvider = jest.fn(() => 'yahoo') 7 | searchUtilsActual.showBingPagination = jest.fn(() => false) 8 | searchUtilsActual.getSearchResultCountPerPage = jest.fn(() => 10) 9 | searchUtilsActual.isSearchExtensionInstalled = jest.fn(() => false) 10 | module.exports = searchUtilsActual 11 | -------------------------------------------------------------------------------- /web/src/js/utils/__mocks__/utils.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | const webUtilsMock = jest.genMockFromModule('js/utils/utils') 4 | const actualWebUtils = jest.requireActual('js/utils/utils') 5 | 6 | webUtilsMock.isInIframe = jest.fn(() => false) 7 | webUtilsMock.getUrlParameters = jest.fn(() => { 8 | return {} 9 | }) 10 | webUtilsMock.parseUrlSearchString = actualWebUtils.parseUrlSearchString 11 | webUtilsMock.validateAppName = actualWebUtils.validateAppName 12 | module.exports = webUtilsMock 13 | -------------------------------------------------------------------------------- /web/src/js/utils/__mocks__/v4-beta-opt-in.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | module.exports = jest.fn(() => Promise.resolve()) 3 | -------------------------------------------------------------------------------- /web/src/js/utils/detectAdblocker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Determines whether the user has an ad blocker enabled. 3 | * @return {Promise} Resolves to true if an ad blocker 4 | * is enabled and false if not. 5 | */ 6 | export default () => { 7 | return new Promise((resolve, reject) => { 8 | const onAdBlockDetected = () => { 9 | resolve(true) 10 | } 11 | const onNoAdBlockDetected = () => { 12 | resolve(false) 13 | } 14 | 15 | require('blockadblock') 16 | const adblockerDetection = window.blockAdBlock 17 | 18 | // Audit in case an ad blocker blocks the "blockadblock" script. 19 | if (typeof adblockerDetection === 'undefined') { 20 | onAdBlockDetected() 21 | } else { 22 | adblockerDetection.onDetected(onAdBlockDetected) 23 | adblockerDetection.onNotDetected(onNoAdBlockDetected) 24 | } 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /web/src/js/utils/errors.js: -------------------------------------------------------------------------------- 1 | /* eslint max-classes-per-file: 0 */ 2 | 3 | class ExtendableError extends Error { 4 | constructor(message) { 5 | super(message) 6 | this.name = this.constructor.name 7 | if (typeof Error.captureStackTrace === 'function') { 8 | Error.captureStackTrace(this, this.constructor) 9 | } else { 10 | this.stack = new Error(message).stack 11 | } 12 | } 13 | } 14 | 15 | const AWAIT_TIMED_OUT = 'AWAIT_TIMED_OUT' 16 | class AwaitedPromiseTimeout extends ExtendableError { 17 | constructor() { 18 | super('Awaited promise timed out.') 19 | this.code = AWAIT_TIMED_OUT 20 | } 21 | 22 | static get code() { 23 | return AWAIT_TIMED_OUT 24 | } 25 | } 26 | 27 | // eslint-disable-next-line import/prefer-default-export 28 | export { AwaitedPromiseTimeout } 29 | -------------------------------------------------------------------------------- /web/src/js/utils/extension-messenger.js: -------------------------------------------------------------------------------- 1 | const POST_MSG_TYPE_BACKGROUND_SETTINGS = 'background-settings' 2 | 3 | export const postMessage = (type, data) => { 4 | // Important: messages sent are NOT private. If we want 5 | // private messages, we need to postMessage specifically 6 | // to each of our extensions' domains. 7 | window.top.postMessage({ type: type, data: data }, '*') 8 | } 9 | 10 | export const postBackgroundSettings = settings => { 11 | postMessage(POST_MSG_TYPE_BACKGROUND_SETTINGS, settings) 12 | } 13 | -------------------------------------------------------------------------------- /web/src/js/utils/getFeatureValue.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/prefer-default-export */ 2 | 3 | const getFeatureValue = (features, experimentName) => { 4 | const desiredFeature = features.find( 5 | feature => feature.featureName === experimentName 6 | ) 7 | return desiredFeature && desiredFeature.variation 8 | } 9 | 10 | export default getFeatureValue 11 | -------------------------------------------------------------------------------- /web/src/js/utils/hooks/useBrowserName.js: -------------------------------------------------------------------------------- 1 | import useBrowserInfo from './useBrowserInfo' 2 | import { simplifyBrowserName } from '../browserSupport' 3 | 4 | /** 5 | * Return the user's browser name, simplified to a short list of browsers 6 | * we care about. 7 | * @param {String|undefined} userAgent - An optional user agent string to 8 | * support determining the browser on the server side. 9 | * @return {String|undefined} The browser name. Undefined if it has not yet 10 | * determined the browser (e.g., on first render without a user agent). 11 | */ 12 | const useBrowserName = ({ userAgent } = {}) => { 13 | let browserName 14 | const browserInfo = useBrowserInfo({ userAgent }) || {} 15 | if (browserInfo.name) { 16 | browserName = simplifyBrowserName(browserInfo.name) 17 | } 18 | return browserName 19 | } 20 | 21 | export default useBrowserName 22 | -------------------------------------------------------------------------------- /web/src/js/utils/hooks/useInterval.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react' 2 | 3 | // https://overreacted.io/making-setinterval-declarative-with-react-hooks/ 4 | function useInterval(callback, delay) { 5 | const savedCallback = useRef() 6 | 7 | // Remember the latest callback. 8 | useEffect(() => { 9 | savedCallback.current = callback 10 | }, [callback]) 11 | 12 | // Set up the interval. 13 | useEffect(() => { 14 | function tick() { 15 | savedCallback.current() 16 | } 17 | if (delay !== null) { 18 | const id = setInterval(tick, delay) 19 | return () => clearInterval(id) 20 | } 21 | return () => {} 22 | }, [delay]) 23 | } 24 | 25 | export default useInterval 26 | -------------------------------------------------------------------------------- /web/src/js/utils/initializeCMP.js: -------------------------------------------------------------------------------- 1 | import tabCMP from 'tab-cmp' 2 | import logger from 'js/utils/logger' 3 | import tabLogoWithText from 'js/assets/logos/logo-with-text.svg' 4 | 5 | const initializeCMP = async () => { 6 | // Disable the CMP in the test environment. It currently breaks 7 | // acceptance tests. 8 | if (process.env.REACT_APP_CMP_ENABLED === 'true') { 9 | await tabCMP.initializeCMP({ 10 | // Debugging can be enabled with URL param tabCMPDebug=true. 11 | debug: false, 12 | displayPersistentConsentLink: false, 13 | onError: err => { 14 | logger.error(err) 15 | }, 16 | primaryButtonColor: '#9d4ba3', 17 | publisherName: 'Tab for a Cause', 18 | publisherLogo: tabLogoWithText, 19 | }) 20 | } 21 | } 22 | 23 | export default initializeCMP 24 | -------------------------------------------------------------------------------- /web/src/js/utils/jsdom-shims.js: -------------------------------------------------------------------------------- 1 | // Workaround for getting element width and height. 2 | // https://github.com/tmpvar/jsdom/issues/135#issuecomment-68191941 3 | Object.defineProperties(window.HTMLElement.prototype, { 4 | offsetLeft: { 5 | get: function() { 6 | return parseFloat(window.getComputedStyle(this).marginLeft) || 0 7 | }, 8 | }, 9 | offsetTop: { 10 | get: function() { 11 | return parseFloat(window.getComputedStyle(this).marginTop) || 0 12 | }, 13 | }, 14 | offsetHeight: { 15 | get: function() { 16 | return parseFloat(window.getComputedStyle(this).height) || 0 17 | }, 18 | }, 19 | offsetWidth: { 20 | get: function() { 21 | return parseFloat(window.getComputedStyle(this).width) || 0 22 | }, 23 | }, 24 | }) 25 | -------------------------------------------------------------------------------- /web/src/js/utils/localstorage-mgr.js: -------------------------------------------------------------------------------- 1 | /* global localStorage */ 2 | 3 | const localStorageMgr = {} 4 | 5 | localStorageMgr.setItem = function(key, value) { 6 | try { 7 | localStorage.setItem(key, value) 8 | } catch (e) { 9 | console.log('localStorage not supported. ', e) 10 | } 11 | } 12 | 13 | localStorageMgr.getItem = function(key) { 14 | try { 15 | var value = localStorage.getItem(key) 16 | return value 17 | } catch (e) { 18 | console.log('localStorage not supported. ', e) 19 | return null 20 | } 21 | } 22 | 23 | localStorageMgr.removeItem = function(key) { 24 | try { 25 | localStorage.removeItem(key) 26 | } catch (e) { 27 | console.log('localStorage not supported. ', e) 28 | } 29 | } 30 | 31 | export default localStorageMgr 32 | -------------------------------------------------------------------------------- /web/src/js/utils/widgets-utils.js: -------------------------------------------------------------------------------- 1 | function getWidgetConfig(config, settings) { 2 | const configuration = {} 3 | var value 4 | var field 5 | for (var index in settings) { 6 | field = settings[index].field 7 | if (!config || !(field in config)) { 8 | value = settings[index].defaultValue 9 | } else { 10 | value = config[field] 11 | } 12 | configuration[field] = value 13 | } 14 | 15 | return configuration 16 | } 17 | 18 | export { getWidgetConfig } 19 | --------------------------------------------------------------------------------