├── .circleci └── config.yml ├── .codeclimate.yml ├── .csslintrc ├── .dockerignore ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_issue_template.yml │ ├── docs_issue_template.yml │ └── feature_request_template.yml ├── PULL_REQUEST_TEMPLATE.md └── config.yml ├── .gitignore ├── .nvmrc ├── .rspec ├── .rubocop.yml ├── .ruby-gemset ├── .ruby-version ├── .vscode └── settings.json ├── CONTRIBUTING.md ├── Dockerfile ├── Gemfile ├── Gemfile.lock ├── LICENSE.txt ├── Procfile ├── Procfile.dev ├── README-AR.md ├── README-BN.md ├── README-CN.md ├── README-ES.md ├── README-FA.md ├── README-FR.md ├── README-HI.md ├── README-ID.md ├── README-IT.md ├── README-KO.md ├── README-LK.md ├── README-PT.md ├── README-TR.md ├── README-VI.md ├── README.md ├── Rakefile ├── app ├── assets │ ├── images │ │ ├── contributors │ │ │ ├── Arun_Kumar.jpg │ │ │ ├── Ghada_AbdulWahab.png │ │ │ ├── Hassan_Ahmed.png │ │ │ ├── Krishanu_Shashwat.jpg │ │ │ ├── Oliver_Ikegah.jpg │ │ │ ├── Olivia_Robinson.jpg │ │ │ ├── Sourabh_Yadav.jpg │ │ │ ├── Zeeshan_Sarwar.png │ │ │ ├── abe_dolinger.jpeg │ │ │ ├── adan_amarillas.jpg │ │ │ ├── alex_fa.jpg │ │ │ ├── alex_manzo.jpg │ │ │ ├── alline_marjorie.jpg │ │ │ ├── angel_quesada.jpg │ │ │ ├── atibhi_agrawal.jpg │ │ │ ├── bee_martinez.jpg │ │ │ ├── ben_pham.jpg │ │ │ ├── britton_jenner.png │ │ │ ├── camila_campos.png │ │ │ ├── carl_summers.jpg │ │ │ ├── chibuzor_efedigbue.jpeg │ │ │ ├── claire_wild.jpg │ │ │ ├── daaimah_tibrey.png │ │ │ ├── darryl_dixon.jpg │ │ │ ├── dena_burd.png │ │ │ ├── desi_rottman.jpg │ │ │ ├── ellen_macpherson.jpg │ │ │ ├── emma_smith.png │ │ │ ├── francisco_perejon.jpeg │ │ │ ├── george_karametas.png │ │ │ ├── gurpreet_gill.jpg │ │ │ ├── haley_mnatzaganian.jpg │ │ │ ├── hnarasaki.jpg │ │ │ ├── ingrid_garcia.jpg │ │ │ ├── irene_li.jpg │ │ │ ├── irmak_aytekin.jpg │ │ │ ├── isabelle_yiu.png │ │ │ ├── jasmine_logan.png │ │ │ ├── jason_alsip.jpg │ │ │ ├── jasper_martin.png │ │ │ ├── jennifer_parsons.jpg │ │ │ ├── jennifer_shen.jpg │ │ │ ├── jennifer_winer.jpg │ │ │ ├── jenny_nam.jpg │ │ │ ├── jenny_nguyen.jpg │ │ │ ├── jeremiah_tabb.jpg │ │ │ ├── john_maguire.png │ │ │ ├── jon_tan.jpg │ │ │ ├── julia_nguyen.jpg │ │ │ ├── julian_macmang.jpg │ │ │ ├── karolina_benitez.jpg │ │ │ ├── kelly_sousa.png │ │ │ ├── lee_mulvey.jpg │ │ │ ├── liliana_velazquez.jpg │ │ │ ├── lisette_ruiz.jpg │ │ │ ├── lucy_yu.jpg │ │ │ ├── manish_sharma.jpeg │ │ │ ├── manish_yadav.jpeg │ │ │ ├── maribel_duran.jpg │ │ │ ├── miguel_navarrete.png │ │ │ ├── mohammad_aakash.jpg │ │ │ ├── muhammad_rahmatullah.jpeg │ │ │ ├── nagma_kapoor.jpg │ │ │ ├── nicholas_lee.jpg │ │ │ ├── nikhil_bhatt.jpg │ │ │ ├── nishiki_liu.jpg │ │ │ ├── pedro_paulino.jpg │ │ │ ├── pierre_monier.jpg │ │ │ ├── prateksha_udhayanan.jpg │ │ │ ├── rebecca_taylor.jpg │ │ │ ├── rizka_luthfiani.jpeg │ │ │ ├── seun_adekunle.jpg │ │ │ ├── sonu_toor.jpg │ │ │ ├── sophie_mcdonald.jpg │ │ │ ├── srishti_gupta.jpg │ │ │ ├── stephanie_warmenhoven.jpg │ │ │ ├── tara_swenson.jpg │ │ │ ├── tara_wilkins.jpg │ │ │ ├── ted_vu.jpg │ │ │ ├── tracy_holmes.png │ │ │ ├── tyler_walker.jpg │ │ │ ├── tyler_welsh.jpg │ │ │ ├── udayan_shevade.png │ │ │ ├── valeria_kolisnyk.jpg │ │ │ ├── varshini_ananta.jpg │ │ │ ├── vilde_vevatne.jpg │ │ │ └── yuri.jpg │ │ ├── documents.svg │ │ ├── favicon.ico │ │ ├── lotus.svg │ │ ├── partners │ │ │ ├── brown_sisters_speak.png │ │ │ ├── dpga.png │ │ │ ├── dpga.svg │ │ │ ├── everybody_has_a_brain.png │ │ │ ├── hacker_hours.png │ │ │ ├── mh_prompt.png │ │ │ ├── osmi.png │ │ │ ├── rails_girls_summer_of_code.png │ │ │ ├── seadc.png │ │ │ └── write_speak_code.png │ │ ├── phone.svg │ │ ├── static_page_bottom_bg.svg │ │ ├── static_page_top_bg.svg │ │ └── welcome.svg │ ├── javascripts │ │ ├── application.js │ │ └── require_recaptcha.js │ └── stylesheets │ │ ├── application.scss │ │ ├── application │ │ ├── allies.scss │ │ ├── contributors.scss │ │ ├── mailer.scss │ │ ├── profile.scss │ │ └── shared.scss │ │ ├── base │ │ ├── _animations.scss │ │ ├── _breakpoints.scss │ │ ├── _colors.scss │ │ ├── _fonts.scss │ │ ├── _margin.scss │ │ ├── _normalize.scss │ │ ├── _padding.scss │ │ ├── _sizes.scss │ │ └── _values.scss │ │ ├── core │ │ ├── alerts.scss │ │ ├── buttons.scss │ │ ├── containers.scss │ │ ├── displays.scss │ │ ├── errors.scss │ │ ├── footer.scss │ │ ├── forms.scss │ │ ├── grids.scss │ │ ├── images.scss │ │ ├── links.scss │ │ ├── margins.scss │ │ ├── notices.scss │ │ └── text.scss │ │ ├── dashboard │ │ ├── dashboard.scss │ │ ├── dashboard_content.scss │ │ ├── dashboard_nav.scss │ │ └── dashboard_section.scss │ │ ├── overrides │ │ └── font_awesome.scss │ │ ├── secret_share │ │ └── secret_share_content.scss │ │ └── static │ │ ├── static.scss │ │ └── static_content.scss ├── controllers │ ├── allies_controller.rb │ ├── application_controller.rb │ ├── care_plan_contacts_controller.rb │ ├── care_plan_controller.rb │ ├── categories_controller.rb │ ├── comments_controller.rb │ ├── concerns │ │ ├── care_plan_contacts_concern.rb │ │ ├── collection_page_setup_concern.rb │ │ ├── moment_templates_concern.rb │ │ ├── moments_concern.rb │ │ ├── page_redirect_concern.rb │ │ ├── pages_concern.rb │ │ ├── shared.rb │ │ ├── shared_basic_concern.rb │ │ └── strategies_concern.rb │ ├── errors_controller.rb │ ├── groups │ │ └── memberships_controller.rb │ ├── groups_controller.rb │ ├── locales_controller.rb │ ├── medications_controller.rb │ ├── meetings │ │ └── google_calendar_event_controller.rb │ ├── meetings_controller.rb │ ├── moment_templates_controller.rb │ ├── moments_controller.rb │ ├── moods_controller.rb │ ├── notifications_controller.rb │ ├── omniauth_callbacks_controller.rb │ ├── pages_controller.rb │ ├── profile_controller.rb │ ├── pusher_controller.rb │ ├── registrations_controller.rb │ ├── reports_controller.rb │ ├── search_controller.rb │ ├── secret_shares_controller.rb │ ├── sessions_controller.rb │ ├── strategies_controller.rb │ └── users │ │ ├── invitations_controller.rb │ │ └── reports_controller.rb ├── helpers │ ├── application_helper.rb │ ├── application_mailer_helper.rb │ ├── assets_helper.rb │ ├── calendar_helper.rb │ ├── categories_helper.rb │ ├── comments_form_helper.rb │ ├── comments_helper.rb │ ├── dashboard_nav_helper.rb │ ├── date_time_helper.rb │ ├── form_helper.rb │ ├── group_notification_helper.rb │ ├── groups_form_helper.rb │ ├── groups_helper.rb │ ├── header_helper.rb │ ├── medication_refill_helper.rb │ ├── medications_form_helper.rb │ ├── medications_helper.rb │ ├── meetings_form_helper.rb │ ├── meetings_helper.rb │ ├── moments_form_helper.rb │ ├── moments_helper.rb │ ├── moments_stats_helper.rb │ ├── moods_helper.rb │ ├── most_focus_helper.rb │ ├── notification_mailer_helper.rb │ ├── notifications_helper.rb │ ├── pages_helper.rb │ ├── profile_helper.rb │ ├── reminder_helper.rb │ ├── reports_helper.rb │ ├── stories_helper.rb │ ├── strategies_form_helper.rb │ ├── strategies_helper.rb │ ├── tags_helper.rb │ ├── users │ │ └── reports_helper.rb │ ├── viewers_helper.rb │ └── visible_helper.rb ├── mailers │ ├── ally_notifications │ │ ├── accepted_ally_request.rb │ │ └── new_ally_request.rb │ ├── application_mailer.rb │ ├── banned_mailer.rb │ ├── custom_devise_mailer.rb │ ├── notification_mailer.rb │ └── report_mailer.rb ├── models │ ├── allyship.rb │ ├── application_record.rb │ ├── care_plan_contact.rb │ ├── category.rb │ ├── comment.rb │ ├── concerns │ │ ├── ally_concern.rb │ │ ├── common_methods.rb │ │ ├── is_visible_concern.rb │ │ └── viewer.rb │ ├── group.rb │ ├── group_member.rb │ ├── medication.rb │ ├── meeting.rb │ ├── meeting_member.rb │ ├── moment.rb │ ├── moment_template.rb │ ├── moments_category.rb │ ├── moments_mood.rb │ ├── moments_strategy.rb │ ├── mood.rb │ ├── notification.rb │ ├── perform_strategy_reminder.rb │ ├── refill_reminder.rb │ ├── report.rb │ ├── strategies_category.rb │ ├── strategy.rb │ ├── support.rb │ ├── take_medication_reminder.rb │ ├── user.rb │ ├── users.rb │ └── users │ │ └── data_request.rb ├── services │ ├── allyship_creator.rb │ ├── allyships │ │ └── alliance_notifier.rb │ ├── calendar_uploader.rb │ ├── cloudinary_service.rb │ ├── comment_notifications_service.rb │ ├── comment_viewers_service.rb │ ├── group_notifier.rb │ ├── leader_updater.rb │ ├── medication_reminders.rb │ ├── medium.rb │ ├── meeting_notifications_service.rb │ ├── meeting_reminders.rb │ ├── moment_keywords.rb │ ├── profile_picture.rb │ ├── recaptcha_service.rb │ ├── resource_recommendations.rb │ ├── strategy_reminders.rb │ └── time_ago.rb ├── uploaders │ └── avatar_uploader.rb ├── views │ ├── allies │ │ └── index.html.erb │ ├── banned_mailer │ │ ├── add_ban_email.html.erb │ │ └── remove_ban_email.html.erb │ ├── care_plan │ │ └── index.html.erb │ ├── categories │ │ ├── _category.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── new.html.erb │ │ └── show.html.erb │ ├── devise │ │ ├── confirmations │ │ │ └── new.html.erb │ │ ├── mailer │ │ │ ├── confirmation_instructions.html.erb │ │ │ ├── reset_password_instructions.html.erb │ │ │ └── unlock_instructions.html.erb │ │ ├── passwords │ │ │ ├── edit.html.erb │ │ │ └── new.html.erb │ │ ├── registrations │ │ │ ├── edit.html.erb │ │ │ └── new.html.erb │ │ ├── sessions │ │ │ └── new.html.erb │ │ ├── shared │ │ │ └── _links.erb │ │ └── unlocks │ │ │ └── new.html.erb │ ├── errors │ │ ├── internal_server_error.html.erb │ │ └── not_found.html.erb │ ├── groups │ │ ├── _info.html.erb │ │ ├── _members.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── new.html.erb │ │ └── show.html.erb │ ├── layouts │ │ ├── application.html.erb │ │ ├── mailer.html.erb │ │ └── mailer.text.erb │ ├── medications │ │ ├── _medication.html.erb │ │ ├── _story_body.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── new.html.erb │ │ └── show.html.erb │ ├── meetings │ │ ├── _story_body.html.erb │ │ ├── edit.html.erb │ │ ├── new.html.erb │ │ └── show.html.erb │ ├── moment_templates │ │ └── index.html.erb │ ├── moments │ │ ├── _moment.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── new.html.erb │ │ └── show.html.erb │ ├── moods │ │ ├── _mood.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── new.html.erb │ │ └── show.html.erb │ ├── notification_mailer │ │ ├── meeting_reminder.html.erb │ │ ├── meeting_reminder.text.erb │ │ ├── notification_email.html.erb │ │ ├── notification_email.text.erb │ │ ├── perform_strategy.html.erb │ │ ├── perform_strategy.text.erb │ │ ├── refill_medication.html.erb │ │ ├── refill_medication.text.erb │ │ ├── take_medication.html.erb │ │ └── take_medication.text.erb │ ├── pages │ │ ├── _contributor_blurb.html.erb │ │ ├── _not_signed_in.html.erb │ │ ├── _signed_in_empty.html.erb │ │ ├── _update_password_modal.html.erb │ │ ├── about.html.erb │ │ ├── admin_dashboard.html.erb │ │ ├── faq.html.erb │ │ ├── home.html.erb │ │ ├── partners.html.erb │ │ ├── press.html.erb │ │ ├── privacy.html.erb │ │ └── resources.html.erb │ ├── profile │ │ └── index.html.erb │ ├── report_mailer │ │ ├── reported_email.html.erb │ │ └── reportee_email.html.erb │ ├── reports │ │ └── new.html.erb │ ├── search │ │ ├── _form.html.erb │ │ ├── _posts.html.erb │ │ └── index.html.erb │ ├── shared │ │ ├── _comments.html.erb │ │ ├── _dashboard_nav_actions.html.erb │ │ ├── _dashboard_nav_links.html.erb │ │ ├── _dashboard_nav_mobile.html.erb │ │ ├── _footer.html.erb │ │ ├── _page_author.html.erb │ │ ├── _page_title.html.erb │ │ └── _stats.html.erb │ ├── stories │ │ ├── _category_or_mood.html.erb │ │ └── _moment_or_strategy.html.erb │ ├── strategies │ │ ├── _strategy.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── new.html.erb │ │ └── show.html.erb │ ├── tag_usage │ │ ├── _index.html.erb │ │ └── _stories.html.erb │ └── users │ │ ├── invitations │ │ ├── edit.html.erb │ │ └── new.html.erb │ │ └── mailer │ │ ├── invitation_instructions.html.erb │ │ └── invitation_instructions.text.erb └── workers │ ├── delete_stale_data_worker.rb │ └── process_data_request_worker.rb ├── bin ├── bundle ├── client.sh ├── install-git-hooks.bash ├── rails ├── rake ├── setup ├── spring ├── start_app ├── update ├── webpack └── webpack-dev-server ├── client ├── .babelrc ├── .eslintignore ├── .eslintrc ├── .flowconfig ├── .gitignore ├── .prettierrc ├── .storybook │ ├── deploy.sh │ ├── main.js │ ├── preview.js │ └── stories.scss ├── .stylelintrc ├── app │ ├── components │ │ ├── Accordion │ │ │ ├── __tests__ │ │ │ │ └── Accordion.spec.jsx │ │ │ └── index.jsx │ │ ├── Avatar │ │ │ ├── Avatar.scss │ │ │ ├── __tests__ │ │ │ │ └── Avatar.spec.jsx │ │ │ └── index.jsx │ │ ├── BaseContainer │ │ │ ├── StoryContainer.jsx │ │ │ ├── __tests__ │ │ │ │ ├── StoryContainer.spec.jsx │ │ │ │ └── index.spec.jsx │ │ │ └── index.jsx │ │ ├── Blockquote │ │ │ ├── Blockquote.scss │ │ │ ├── __tests__ │ │ │ │ └── Blockquote.spec.jsx │ │ │ └── index.jsx │ │ ├── Chart │ │ │ ├── ChartControl.jsx │ │ │ ├── ChartControl.scss │ │ │ ├── __tests__ │ │ │ │ ├── Chart.spec.jsx │ │ │ │ └── ChartControl.spec.jsx │ │ │ └── index.jsx │ │ ├── Form │ │ │ ├── DynamicForm.jsx │ │ │ ├── Form.scss │ │ │ ├── __tests__ │ │ │ │ ├── DynamicForm.spec.jsx │ │ │ │ └── Form.spec.jsx │ │ │ ├── index.jsx │ │ │ └── utils.js │ │ ├── Header │ │ │ ├── Header.scss │ │ │ ├── HeaderProfile.jsx │ │ │ ├── HeaderProfile.scss │ │ │ ├── __tests__ │ │ │ │ ├── Header.spec.jsx │ │ │ │ └── HeaderProfile.spec.jsx │ │ │ ├── index.jsx │ │ │ └── types.js │ │ ├── Input │ │ │ ├── Input.scss │ │ │ ├── InputCheckbox.jsx │ │ │ ├── InputCheckboxGroup.jsx │ │ │ ├── InputDefault.jsx │ │ │ ├── InputError.jsx │ │ │ ├── InputLabel.jsx │ │ │ ├── InputLocation.jsx │ │ │ ├── InputMultiSelect.jsx │ │ │ ├── InputMultiSelect.scss │ │ │ ├── InputPassword.jsx │ │ │ ├── InputPassword.scss │ │ │ ├── InputRadioGroup.jsx │ │ │ ├── InputRadioGroup.scss │ │ │ ├── InputSelect.jsx │ │ │ ├── InputSubmit.jsx │ │ │ ├── InputSwitch.jsx │ │ │ ├── InputSwitch.scss │ │ │ ├── InputTag.jsx │ │ │ ├── InputTag.scss │ │ │ ├── InputTextarea.jsx │ │ │ ├── InputTextarea.scss │ │ │ ├── InputTextareaTemplate.jsx │ │ │ ├── __tests__ │ │ │ │ ├── Input.spec.jsx │ │ │ │ ├── InputCheckbox.spec.jsx │ │ │ │ ├── InputCheckboxGroup.spec.jsx │ │ │ │ ├── InputDefault.spec.jsx │ │ │ │ ├── InputError.spec.jsx │ │ │ │ ├── InputLabel.spec.jsx │ │ │ │ ├── InputLocation.spec.jsx │ │ │ │ ├── InputMultiSelect.spec.jsx │ │ │ │ ├── InputPassword.spec.jsx │ │ │ │ ├── InputRadioGroup.spec.jsx │ │ │ │ ├── InputSelect.spec.jsx │ │ │ │ ├── InputSubmit.spec.jsx │ │ │ │ ├── InputSwitch.spec.jsx │ │ │ │ ├── InputTag.spec.jsx │ │ │ │ ├── InputTextarea.spec.jsx │ │ │ │ └── InputTextareaTemplate.spec.jsx │ │ │ ├── index.jsx │ │ │ └── utils.js │ │ ├── LoadMoreButton │ │ │ ├── LoadMoreButton.scss │ │ │ ├── __tests__ │ │ │ │ └── LoadMoreButton.spec.jsx │ │ │ └── index.jsx │ │ ├── Logo │ │ │ ├── Logo.scss │ │ │ ├── LogoFactory.jsx │ │ │ ├── __tests__ │ │ │ │ └── Logo.spec.jsx │ │ │ └── index.js │ │ ├── Modal │ │ │ ├── Modal.scss │ │ │ ├── __tests__ │ │ │ │ └── Modal.spec.jsx │ │ │ └── index.jsx │ │ ├── OAuthButton │ │ │ ├── OAuthButton.scss │ │ │ ├── __tests__ │ │ │ │ └── OAuthButton.spec.jsx │ │ │ ├── facebookIcon.svg │ │ │ ├── googleIcon.svg │ │ │ └── index.jsx │ │ ├── PageTitle │ │ │ ├── __tests__ │ │ │ │ └── PageTitle.spec.jsx │ │ │ └── index.jsx │ │ ├── Resource │ │ │ ├── Resource.scss │ │ │ ├── __tests__ │ │ │ │ └── Resource.spec.jsx │ │ │ └── index.jsx │ │ ├── SkipToContent │ │ │ ├── SkipToContent.scss │ │ │ ├── __tests__ │ │ │ │ └── SkipToContent.spec.jsx │ │ │ └── index.jsx │ │ ├── Story │ │ │ ├── Story.scss │ │ │ ├── StoryActions.jsx │ │ │ ├── StoryBy.jsx │ │ │ ├── StoryCategories.jsx │ │ │ ├── StoryDate.jsx │ │ │ ├── StoryDraft.jsx │ │ │ ├── StoryMedication.jsx │ │ │ ├── StoryMoods.jsx │ │ │ ├── StoryName.jsx │ │ │ ├── __tests__ │ │ │ │ ├── Story.spec.jsx │ │ │ │ ├── StoryActions.spec.jsx │ │ │ │ ├── StoryBy.spec.jsx │ │ │ │ ├── StoryCategories.spec.jsx │ │ │ │ ├── StoryDate.spec.jsx │ │ │ │ ├── StoryDraft.spec.jsx │ │ │ │ ├── StoryMedication.spec.jsx │ │ │ │ ├── StoryMoods.spec.jsx │ │ │ │ └── StoryName.spec.jsx │ │ │ └── index.jsx │ │ ├── Tag │ │ │ ├── Tag.scss │ │ │ ├── __tests__ │ │ │ │ └── Tag.spec.jsx │ │ │ └── index.jsx │ │ ├── Toast │ │ │ ├── Toast.scss │ │ │ ├── __tests__ │ │ │ │ └── Toast.spec.jsx │ │ │ └── index.jsx │ │ └── Tooltip │ │ │ ├── Tooltip.scss │ │ │ ├── __tests__ │ │ │ └── Tooltip.spec.jsx │ │ │ └── index.jsx │ ├── hooks │ │ ├── index.js │ │ └── useFocusTrap.js │ ├── libs │ │ ├── history.js │ │ ├── i18n │ │ │ ├── __tests__ │ │ │ │ └── i18n.spec.js │ │ │ └── index.js │ │ └── testHelper.js │ ├── mocks │ │ └── InputMocks.jsx │ ├── pages │ │ └── MomentTemplates │ │ │ ├── MomentTemplate.jsx │ │ │ ├── MomentTemplates.scss │ │ │ ├── MomentTemplatesContext.jsx │ │ │ ├── MomentTemplatesForm.jsx │ │ │ ├── __tests__ │ │ │ └── MomentTemplates.spec.jsx │ │ │ └── index.jsx │ ├── startup │ │ ├── registration.js │ │ ├── scrollToTop.js │ │ └── setTimezone.js │ ├── stories │ │ ├── .eslintrc │ │ ├── Accordion.stories.jsx │ │ ├── Avatar.stories.jsx │ │ ├── Blockquote.stories.jsx │ │ ├── Buttons.stories.jsx │ │ ├── Chart.stories.jsx │ │ ├── Colors.stories.jsx │ │ ├── Errors.stories.jsx │ │ ├── Fonts.stories.jsx │ │ ├── Form.stories.jsx │ │ ├── Grids.stories.jsx │ │ ├── Header.stories.jsx │ │ ├── I18n.stories.jsx │ │ ├── Index.stories.jsx │ │ ├── Input.stories.jsx │ │ ├── Logo.stories.jsx │ │ ├── Margins.stories.jsx │ │ ├── Modal.stories.jsx │ │ ├── OAuthButton.stories.jsx │ │ ├── PageTitle.stories.jsx │ │ ├── Resource.stories.jsx │ │ ├── Story.stories.jsx │ │ ├── Tag.stories.jsx │ │ ├── Toast.stories.jsx │ │ └── Tooltip.stories.jsx │ ├── styles │ │ ├── _global.scss │ │ ├── _global_font.scss │ │ └── _legacy.scss │ ├── utils │ │ ├── __tests__ │ │ │ └── index.spec.jsx │ │ └── index.js │ └── widgets │ │ ├── CarePlanContacts │ │ ├── CarePlanContacts.scss │ │ ├── CarePlanContactsContext.jsx │ │ ├── CarePlanContactsForm.jsx │ │ ├── __tests__ │ │ │ └── index.spec.jsx │ │ └── index.jsx │ │ ├── Comments │ │ ├── Comments.scss │ │ ├── __tests__ │ │ │ └── Comments.spec.jsx │ │ └── index.jsx │ │ ├── CrisisPrevention │ │ ├── CrisisPrevention.scss │ │ ├── __tests___ │ │ │ └── index.spec.jsx │ │ └── index.jsx │ │ ├── Notifications │ │ ├── __tests__ │ │ │ └── Notifications.spec.jsx │ │ └── index.jsx │ │ ├── QuickCreate │ │ ├── QuickCreate.scss │ │ ├── __tests__ │ │ │ └── QuickCreate.spec.jsx │ │ └── index.jsx │ │ ├── Resources │ │ ├── Resources.scss │ │ ├── __tests__ │ │ │ └── Resources.spec.jsx │ │ └── index.jsx │ │ └── ToggleLocale │ │ ├── __tests__ │ │ └── ToggleLocale.spec.jsx │ │ └── index.jsx ├── flow │ ├── media.js │ └── stylesheets.js ├── jest │ └── svgTransform.js ├── package.json ├── webpack.config.js └── yarn.lock ├── code_of_conduct.md ├── config.ru ├── config ├── .yamllint ├── application.rb ├── boot.rb ├── cable.yml ├── cloudinary.yml ├── content_security_policy.rb ├── database.yml ├── env │ ├── development.example.env │ └── test.example.env ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── application_controller_renderer.rb │ ├── assets.rb │ ├── backtrace_silencers.rb │ ├── bullet.rb │ ├── carrierwave.rb │ ├── cloudinary.rb │ ├── content_security_policy.rb │ ├── cookies_serializer.rb │ ├── devise.rb │ ├── filter_parameter_logging.rb │ ├── friendly_id.rb │ ├── inflections.rb │ ├── kaminari_config.rb │ ├── mime_types.rb │ ├── new_framework_defaults.rb │ ├── new_framework_defaults_6_0.rb │ ├── new_framework_defaults_7_0.rb │ ├── permissions_policy.rb │ ├── premailer_rails.rb │ ├── pusher.rb │ ├── react_on_rails.rb │ ├── recaptcha.rb │ ├── session_store.rb │ ├── sidekiq.rb │ ├── timeout.rb │ └── wrap_parameters.rb ├── locale.rb ├── locales │ ├── de.yml │ ├── devise.de.yml │ ├── devise.en.yml │ ├── devise.es.yml │ ├── devise.fr.yml │ ├── devise.hi.yml │ ├── devise.id.yml │ ├── devise.it.yml │ ├── devise.ko.yml │ ├── devise.nb.yml │ ├── devise.nl.yml │ ├── devise.pt-BR.yml │ ├── devise.sv.yml │ ├── devise.vi.yml │ ├── devise.zh-CN.yml │ ├── devise_invitable.de.yml │ ├── devise_invitable.en.yml │ ├── devise_invitable.es.yml │ ├── devise_invitable.fr.yml │ ├── devise_invitable.hi.yml │ ├── devise_invitable.id.yml │ ├── devise_invitable.it.yml │ ├── devise_invitable.ko.yml │ ├── devise_invitable.nb.yml │ ├── devise_invitable.nl.yml │ ├── devise_invitable.pt-BR.yml │ ├── devise_invitable.sv.yml │ ├── devise_invitable.vi.yml │ ├── devise_invitable.zh-CN.yml │ ├── en.yml │ ├── es.yml │ ├── fr.yml │ ├── hi.yml │ ├── id.yml │ ├── it.yml │ ├── ko.yml │ ├── nb.yml │ ├── nl.yml │ ├── pt-BR.yml │ ├── sv.yml │ ├── vi.yml │ └── zh-CN.yml ├── puma.rb ├── routes.rb ├── secrets.yml ├── sidekiq.yml ├── sidekiq_schedule.yml ├── spring.rb ├── storage.yml └── webpacker.yml ├── db ├── migrate │ ├── 20150506005730_setup_database.rb │ ├── 20151205193444_add_relations.rb │ ├── 20151205193712_change_column_names_in_allyships.rb │ ├── 20160429191349_create_notifications.rb │ ├── 20160506154041_devise_invitable_add_to_users.rb │ ├── 20160511231156_add_viewers_to_comments.rb │ ├── 20160512174202_set_limits_on_text.rb │ ├── 20160516202831_add_notifications_to_users.rb │ ├── 20160518220139_create_medication_reminders.rb │ ├── 20161127002019_create_strategy_reminders.rb │ ├── 20161127232311_create_friendly_id_slugs.rb │ ├── 20161127233144_add_slug_to_moments.rb │ ├── 20161215060504_add_slug_to_models.rb │ ├── 20170225182017_add_locale_to_users.rb │ ├── 20170724124951_add_access_expires_at_to_user.rb │ ├── 20170724160338_add_refresh_token_to_user.rb │ ├── 20170724205314_add_secret_shares_to_moments.rb │ ├── 20170806232047_change_column_names_in_moments.rb │ ├── 20170825205513_change_comments_to_be_polymorphic.rb │ ├── 20170830075513_add_add_to_google_cal_to_medication.rb │ ├── 20171006164206_add_published_at_field_to_moments.rb │ ├── 20171006164223_add_published_at_field_to_strategies.rb │ ├── 20171010054721_set_default_publication_date.rb │ ├── 20180213160537_add_weekly_dosage_to_medication.rb │ ├── 20180318075504_rename_userid_on_categories.rb │ ├── 20180318080518_rename_groupid_on_group_members.rb │ ├── 20180405152328_rename_userid_on_moods.rb │ ├── 20180405153007_rename_userid_on_strategies.rb │ ├── 20180406232114_rename_userid_on_moments.rb │ ├── 20180716033741_rename_userid_on_meetings.rb │ ├── 20180716035719_rename_userid_on_notifications.rb │ ├── 20180716035735_rename_userid_on_supports.rb │ ├── 20180716035805_rename_userid_on_medications.rb │ ├── 20180716035824_rename_userid_on_meeting_members.rb │ ├── 20180716040119_rename_userid_on_group_members.rb │ ├── 20180724031319_rename_meetingid_on_meeting_members.rb │ ├── 20180908064825_add_banned_to_users.rb │ ├── 20181030143627_create_password_histories.rb │ ├── 20181103000528_add_reports.rb │ ├── 20181103000904_add_admin_to_users.rb │ ├── 20181119152925_change_refill_column_type.rb │ ├── 20190316160238_add_google_cal_event_id_to_meeting_members.rb │ ├── 20190419023259_replace_empty_name_with_email.rb │ ├── 20190821013432_add_third_party_avatar_to_user.rb │ ├── 20200201005100_add_visible_to_categories.rb │ ├── 20200201005117_add_visible_to_strategies.rb │ ├── 20200208223501_add_visible_to_moods.rb │ ├── 20200219155053_create_moment_moods_join_table.rb │ ├── 20200221153634_drop_moments_mood_column.rb │ ├── 20200224201131_create_moment_categories_join_table.rb │ ├── 20200224205313_drop_moments_category_column.rb │ ├── 20200224231844_create_moment_strategies_join_table.rb │ ├── 20200224234822_drop_moments_strategy_column.rb │ ├── 20200225003308_create_strategy_categories_join_table.rb │ ├── 20200225010346_drop_strategies_category_column.rb │ ├── 20200419063429_add_bookmarked_to_moments.rb │ ├── 20200419063936_add_bookmarked_to_strategies.rb │ ├── 20200506222107_add_resource_recommendations_to_moments.rb │ ├── 20200509110917_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.active_storage.rb │ ├── 20200513052758_add_user_id_index_to_password_histories.rb │ ├── 20200527035711_create_care_plan_contacts.rb │ ├── 20201008170831_create_users_data_requests.rb │ ├── 20201009185200_drop_password_histories_table.rb │ ├── 20201019231628_add_lockable_attributes_to_user.rb │ ├── 20210328174852_create_moment_templates.rb │ ├── 20211005010735_add_confirmable_to_devise.rb │ ├── 20220204015546_add_session_token_to_users.rb │ ├── 20221025214531_add_service_name_to_active_storage_blobs.active_storage.rb │ ├── 20221025214532_create_active_storage_variant_records.active_storage.rb │ └── 20221025214533_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb ├── schema.rb └── seeds.rb ├── doc ├── controllers_brief.svg ├── controllers_complete.svg ├── erd.pdf ├── models_brief.svg ├── models_complete.svg └── pages │ ├── blurbs.json │ ├── contributors.json │ ├── partners.json │ ├── press.json │ └── resources.json ├── docker-compose.test.yml ├── docker-compose.yml ├── docker-entrypoint.sh ├── git-hooks ├── pre-commit └── pre-push ├── lib ├── access_token.rb ├── tasks │ ├── cleaner.rake │ ├── scheduler.rake │ └── slugs.rake └── url_helper.rb ├── package.json ├── public ├── 422.html ├── favicon.ico ├── logo@2x.png ├── logo_512.png ├── manifest.json ├── robots.txt └── wiki_images │ ├── macos-installation-map.png │ ├── new-accounts-settings.png │ ├── new-allies-page.png │ ├── new-categories-page.png │ ├── new-groups-page.png │ ├── new-landing-page.png │ ├── new-medications-form.png │ ├── new-medications-page.png │ ├── new-moments-page.png │ ├── new-moods-page.png │ └── new-strategies-page.png ├── spec ├── compare_locales_support_spec.rb ├── concerns │ ├── ally_concern_spec.rb │ ├── collection_page_setup_concern_spec.rb │ ├── page_redirect_concern_spec.rb │ └── shared_basic_concern_spec.rb ├── factories │ ├── care_plan_contacts.rb │ ├── category.rb │ ├── factories.rb │ ├── moment.rb │ ├── moment_templates.rb │ ├── mood.rb │ ├── notification.rb │ ├── strategy.rb │ ├── support.rb │ ├── user.rb │ └── users │ │ └── data_requests.rb ├── features │ ├── leader_edits_groups_spec.rb │ ├── persisting_browser_locale_after_sign_in_spec.rb │ ├── resources_load_more_spec.rb │ ├── toggle_locale_spec.rb │ ├── user_auth_spec.rb │ ├── user_creates_a_care_plan_contact_spec.rb │ ├── user_creates_a_draft_moment_spec.rb │ ├── user_creates_a_draft_strategy_spec.rb │ ├── user_creates_a_medication_spec.rb │ ├── user_creates_a_moment_template_spec.rb │ ├── user_creates_a_published_moment_spec.rb │ ├── user_creates_a_published_strategy_spec.rb │ ├── user_creates_groups_spec.rb │ ├── user_deletes_a_care_plan_contact_spec.rb │ ├── user_deletes_a_moment_template_spec.rb │ ├── user_displays_resources_links_spec.rb │ ├── user_leaves_groups_spec.rb │ ├── user_updates_a_care_plan_contact_spec.rb │ ├── user_updates_a_moment_templates_spec.rb │ ├── user_updates_groups_spec.rb │ ├── user_updates_profile_spec.rb │ ├── user_visits_group_show_spec.rb │ └── user_visits_groups_pages_spec.rb ├── helpers │ ├── access_token_spec.rb │ ├── application_helper_spec.rb │ ├── assets_helper_spec.rb │ ├── calendar_helper_spec.rb │ ├── categories_helper_spec.rb │ ├── comments_form_helper_spec.rb │ ├── comments_helper_spec.rb │ ├── dashboard_nav_helper_spec.rb │ ├── date_time_helper_spec.rb │ ├── form_helper_spec.rb │ ├── groups │ │ ├── form_helper_spec.rb │ │ └── memberships_helper_spec.rb │ ├── groups_form_helper_spec.rb │ ├── groups_helper_spec.rb │ ├── header_helper_spec.rb │ ├── medication_refill_helper_spec.rb │ ├── medications_form_helper_spec.rb │ ├── medications_helper_spec.rb │ ├── meetings_form_helper_spec.rb │ ├── meetings_helper_spec.rb │ ├── moments_form_helper_spec.rb │ ├── moments_helper_spec.rb │ ├── moments_stats_helper_spec.rb │ ├── moods_helper_spec.rb │ ├── most_focus_helper_spec.rb │ ├── notifications_helper_spec.rb │ ├── pages_helper_spec.rb │ ├── reminder_helper_spec.rb │ ├── reports_helper_spec.rb │ ├── shared_examples.rb │ ├── stories_helper_spec.rb │ ├── strategies_form_helper.spec.rb │ ├── strategies_helper_spec.rb │ ├── tags_helper_spec.rb │ ├── viewers_helper_spec.rb │ └── visible_helper_spec.rb ├── mailers │ ├── ally_notifications │ │ ├── accepted_ally_request_spec.rb │ │ └── new_ally_request_spec.rb │ ├── application_mailer_spec.rb │ ├── banned_mailer_spec.rb │ ├── custom_devise_mailer_spec.rb │ ├── notification_mailer_spec.rb │ ├── previews │ │ └── notification_mailer_preview.rb │ └── report_mailer_spec.rb ├── models │ ├── allyship_spec.rb │ ├── care_plan_contact_spec.rb │ ├── category_spec.rb │ ├── comment_spec.rb │ ├── group_member_spec.rb │ ├── group_notifier_spec.rb │ ├── group_spec.rb │ ├── medication_spec.rb │ ├── meeting_member_spec.rb │ ├── meeting_spec.rb │ ├── moment_spec.rb │ ├── moment_template_spec.rb │ ├── mood_spec.rb │ ├── notification_spec.rb │ ├── perform_strategy_reminder_spec.rb │ ├── refill_reminder_spec.rb │ ├── report_spec.rb │ ├── strategy_spec.rb │ ├── support_spec.rb │ ├── take_medication_reminder_spec.rb │ ├── user_spec.rb │ └── users │ │ └── data_request_spec.rb ├── requests │ ├── allies_spec.rb │ ├── care_plan_contacts_spec.rb │ ├── care_plan_spec.rb │ ├── categories_spec.rb │ ├── comments_spec.rb │ ├── errors_spec.rb │ ├── groups │ │ └── membership_spec.rb │ ├── groups_spec.rb │ ├── locales_spec.rb │ ├── medications_spec.rb │ ├── meetings │ │ └── google_calendar_event_spec.rb │ ├── meetings_spec.rb │ ├── moment_templates_spec.rb │ ├── moments_spec.rb │ ├── moods_spec.rb │ ├── notifications_spec.rb │ ├── omniauth_callbacks_spec.rb │ ├── pages_spec.rb │ ├── profile_spec.rb │ ├── pusher_spec.rb │ ├── registrations_spec.rb │ ├── reports_spec.rb │ ├── search_spec.rb │ ├── secret_shares_spec.rb │ ├── sessions_spec.rb │ ├── strategies_spec.rb │ └── users │ │ ├── invitation_spec.rb │ │ └── reports_request_spec.rb ├── routing │ └── locale_routing_spec.rb ├── services │ ├── allyship_creator_spec.rb │ ├── allyships │ │ └── alliance_notifier_spec.rb │ ├── calendar_uploader_spec.rb │ ├── cloudinary_service_spec.rb │ ├── comment_notifications_service_spec.rb │ ├── comment_viewers_service_spec.rb │ ├── group_notifier_spec.rb │ ├── leader_updater_spec.rb │ ├── medication_reminders_spec.rb │ ├── medium_spec.rb │ ├── meeting_notifications_service_spec.rb │ ├── meeting_reminders_spec.rb │ ├── moment_keywords_spec.rb │ ├── profile_picture_spec.rb │ ├── recaptcha_service_spec.rb │ ├── resource_recommendations_spec.rb │ ├── strategy_reminders_spec.rb │ └── time_ago_spec.rb ├── spec_helper.rb ├── support │ ├── compare_locales_support.rb │ ├── database_cleaner.rb │ ├── hidden_header_support.rb │ ├── page_transition_support.rb │ ├── scroll_spec_support.rb │ ├── shared_contexts │ │ └── logged_in_user.rb │ ├── shared_examples │ │ └── with_no_logged_in_user.rb │ ├── stub_current_user.rb │ ├── stub_omniauth.rb │ └── textarea_support.rb └── uploads │ └── moment.jpeg └── yarn.lock /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | 3 | plugins: 4 | brakeman: 5 | enabled: true 6 | bundler-audit: 7 | enabled: true 8 | csslint: 9 | enabled: true 10 | duplication: 11 | enabled: true 12 | config: 13 | languages: 14 | ruby: {} 15 | javascript: 16 | mass_threshold: 80 17 | exclude_paths: 18 | - "client/**/__tests__/" 19 | eslint: 20 | enabled: true 21 | channel: "eslint-7" 22 | exclude_paths: 23 | - public/javascripts/ 24 | - vendor/assets/javascripts 25 | - client/flow/ 26 | fixme: 27 | enabled: false 28 | rubocop: 29 | enabled: true 30 | channel: rubocop-1-56-3 31 | ratings: 32 | paths: 33 | - Gemfile.lock 34 | - "**.erb" 35 | - "**.rb" 36 | - "**.css" 37 | - "**.js" 38 | exclude_paths: 39 | - config/ 40 | - db/ 41 | - spec/ 42 | -------------------------------------------------------------------------------- /.csslintrc: -------------------------------------------------------------------------------- 1 | --exclude-exts=.min.css 2 | --ignore=adjoining-classes,box-model,ids,order-alphabetical,unqualified-attributes 3 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # git files 2 | .git 3 | .gitignore 4 | .gitkeep 5 | .keep 6 | git-hooks 7 | 8 | # logfiles and tempfiles 9 | coverage 10 | tmp 11 | log 12 | **/*.log 13 | **/npm-debug.log* 14 | 15 | # OS files 16 | **/*.DS_Store 17 | **/Thumbs.db 18 | 19 | # IDE & editor configuration 20 | .vs* 21 | .idea 22 | .tags 23 | **/*.swp 24 | 25 | # managed dependencies 26 | **/node_modules 27 | **/v8-compile-cache-*/ 28 | 29 | # generated JavaScript 30 | public/javascripts 31 | public/webpack 32 | public/webpack-test 33 | 34 | # documentation for project contributors 35 | *.md 36 | 37 | # configuration that doesn't need to be in the image 38 | .circleci 39 | .codeclimate.yml 40 | .dockerignore 41 | .github 42 | Dockerfile 43 | docker-compose.yml 44 | docker-compose.*.yml 45 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.rb] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/*{.,-}min.js 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | @ifmeorg/rgsoc-mentors 2 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 20.17.0 -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /.ruby-gemset: -------------------------------------------------------------------------------- 1 | ifme 2 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.1.4 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "javascript.validate.enable": false 3 | } -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | # Procfile 2 | web: bundle exec puma -C config/puma.rb -------------------------------------------------------------------------------- /Procfile.dev: -------------------------------------------------------------------------------- 1 | web: bash -c "rm -rf ./tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" 2 | client: sh -c 'bin/client.sh' 3 | -------------------------------------------------------------------------------- /app/assets/images/contributors/Arun_Kumar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/Arun_Kumar.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/Ghada_AbdulWahab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/Ghada_AbdulWahab.png -------------------------------------------------------------------------------- /app/assets/images/contributors/Hassan_Ahmed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/Hassan_Ahmed.png -------------------------------------------------------------------------------- /app/assets/images/contributors/Krishanu_Shashwat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/Krishanu_Shashwat.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/Oliver_Ikegah.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/Oliver_Ikegah.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/Olivia_Robinson.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/Olivia_Robinson.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/Sourabh_Yadav.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/Sourabh_Yadav.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/Zeeshan_Sarwar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/Zeeshan_Sarwar.png -------------------------------------------------------------------------------- /app/assets/images/contributors/abe_dolinger.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/abe_dolinger.jpeg -------------------------------------------------------------------------------- /app/assets/images/contributors/adan_amarillas.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/adan_amarillas.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/alex_fa.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/alex_fa.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/alex_manzo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/alex_manzo.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/alline_marjorie.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/alline_marjorie.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/angel_quesada.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/angel_quesada.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/atibhi_agrawal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/atibhi_agrawal.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/bee_martinez.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/bee_martinez.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/ben_pham.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/ben_pham.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/britton_jenner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/britton_jenner.png -------------------------------------------------------------------------------- /app/assets/images/contributors/camila_campos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/camila_campos.png -------------------------------------------------------------------------------- /app/assets/images/contributors/carl_summers.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/carl_summers.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/chibuzor_efedigbue.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/chibuzor_efedigbue.jpeg -------------------------------------------------------------------------------- /app/assets/images/contributors/claire_wild.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/claire_wild.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/daaimah_tibrey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/daaimah_tibrey.png -------------------------------------------------------------------------------- /app/assets/images/contributors/darryl_dixon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/darryl_dixon.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/dena_burd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/dena_burd.png -------------------------------------------------------------------------------- /app/assets/images/contributors/desi_rottman.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/desi_rottman.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/ellen_macpherson.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/ellen_macpherson.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/emma_smith.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/emma_smith.png -------------------------------------------------------------------------------- /app/assets/images/contributors/francisco_perejon.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/francisco_perejon.jpeg -------------------------------------------------------------------------------- /app/assets/images/contributors/george_karametas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/george_karametas.png -------------------------------------------------------------------------------- /app/assets/images/contributors/gurpreet_gill.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/gurpreet_gill.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/haley_mnatzaganian.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/haley_mnatzaganian.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/hnarasaki.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/hnarasaki.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/ingrid_garcia.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/ingrid_garcia.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/irene_li.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/irene_li.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/irmak_aytekin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/irmak_aytekin.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/isabelle_yiu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/isabelle_yiu.png -------------------------------------------------------------------------------- /app/assets/images/contributors/jasmine_logan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/jasmine_logan.png -------------------------------------------------------------------------------- /app/assets/images/contributors/jason_alsip.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/jason_alsip.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/jasper_martin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/jasper_martin.png -------------------------------------------------------------------------------- /app/assets/images/contributors/jennifer_parsons.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/jennifer_parsons.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/jennifer_shen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/jennifer_shen.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/jennifer_winer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/jennifer_winer.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/jenny_nam.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/jenny_nam.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/jenny_nguyen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/jenny_nguyen.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/jeremiah_tabb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/jeremiah_tabb.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/john_maguire.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/john_maguire.png -------------------------------------------------------------------------------- /app/assets/images/contributors/jon_tan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/jon_tan.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/julia_nguyen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/julia_nguyen.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/julian_macmang.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/julian_macmang.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/karolina_benitez.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/karolina_benitez.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/kelly_sousa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/kelly_sousa.png -------------------------------------------------------------------------------- /app/assets/images/contributors/lee_mulvey.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/lee_mulvey.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/liliana_velazquez.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/liliana_velazquez.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/lisette_ruiz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/lisette_ruiz.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/lucy_yu.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/lucy_yu.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/manish_sharma.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/manish_sharma.jpeg -------------------------------------------------------------------------------- /app/assets/images/contributors/manish_yadav.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/manish_yadav.jpeg -------------------------------------------------------------------------------- /app/assets/images/contributors/maribel_duran.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/maribel_duran.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/miguel_navarrete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/miguel_navarrete.png -------------------------------------------------------------------------------- /app/assets/images/contributors/mohammad_aakash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/mohammad_aakash.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/muhammad_rahmatullah.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/muhammad_rahmatullah.jpeg -------------------------------------------------------------------------------- /app/assets/images/contributors/nagma_kapoor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/nagma_kapoor.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/nicholas_lee.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/nicholas_lee.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/nikhil_bhatt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/nikhil_bhatt.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/nishiki_liu.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/nishiki_liu.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/pedro_paulino.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/pedro_paulino.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/pierre_monier.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/pierre_monier.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/prateksha_udhayanan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/prateksha_udhayanan.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/rebecca_taylor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/rebecca_taylor.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/rizka_luthfiani.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/rizka_luthfiani.jpeg -------------------------------------------------------------------------------- /app/assets/images/contributors/seun_adekunle.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/seun_adekunle.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/sonu_toor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/sonu_toor.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/sophie_mcdonald.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/sophie_mcdonald.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/srishti_gupta.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/srishti_gupta.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/stephanie_warmenhoven.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/stephanie_warmenhoven.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/tara_swenson.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/tara_swenson.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/tara_wilkins.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/tara_wilkins.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/ted_vu.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/ted_vu.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/tracy_holmes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/tracy_holmes.png -------------------------------------------------------------------------------- /app/assets/images/contributors/tyler_walker.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/tyler_walker.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/tyler_welsh.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/tyler_welsh.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/udayan_shevade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/udayan_shevade.png -------------------------------------------------------------------------------- /app/assets/images/contributors/valeria_kolisnyk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/valeria_kolisnyk.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/varshini_ananta.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/varshini_ananta.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/vilde_vevatne.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/vilde_vevatne.jpg -------------------------------------------------------------------------------- /app/assets/images/contributors/yuri.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/contributors/yuri.jpg -------------------------------------------------------------------------------- /app/assets/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/favicon.ico -------------------------------------------------------------------------------- /app/assets/images/partners/brown_sisters_speak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/partners/brown_sisters_speak.png -------------------------------------------------------------------------------- /app/assets/images/partners/dpga.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/partners/dpga.png -------------------------------------------------------------------------------- /app/assets/images/partners/everybody_has_a_brain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/partners/everybody_has_a_brain.png -------------------------------------------------------------------------------- /app/assets/images/partners/hacker_hours.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/partners/hacker_hours.png -------------------------------------------------------------------------------- /app/assets/images/partners/mh_prompt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/partners/mh_prompt.png -------------------------------------------------------------------------------- /app/assets/images/partners/osmi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/partners/osmi.png -------------------------------------------------------------------------------- /app/assets/images/partners/rails_girls_summer_of_code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/partners/rails_girls_summer_of_code.png -------------------------------------------------------------------------------- /app/assets/images/partners/seadc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/partners/seadc.png -------------------------------------------------------------------------------- /app/assets/images/partners/write_speak_code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/app/assets/images/partners/write_speak_code.png -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. 9 | // 10 | // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require jquery 14 | //= require jquery_ujs 15 | //= require turbolinks 16 | //= require_tree . 17 | -------------------------------------------------------------------------------- /app/assets/javascripts/require_recaptcha.js: -------------------------------------------------------------------------------- 1 | function recaptcha_verified () { 2 | document.querySelector('.recaptcha_submit').removeAttribute('disabled') 3 | } 4 | 5 | function recaptcha_expired () { 6 | document.querySelector('.recaptcha_submit').setAttribute('disabled', 'disabled') 7 | } 8 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application/allies.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the Allies controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | 5 | /* Allies */ 6 | 7 | .ally { 8 | text-align: center; 9 | 10 | a { 11 | @include standardTransition(); 12 | } 13 | 14 | > a:first-of-type { 15 | @include setMargin($size-0, $size-0, $size-20, $size-0); 16 | 17 | display: block; 18 | color: $mulberry; 19 | font-weight: bold; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application/profile.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the Profile controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | 5 | /* Profiles */ 6 | 7 | .profile { 8 | display: flex; 9 | flex-direction: row; 10 | align-items: center; 11 | 12 | &Info { 13 | margin-left: $size-20; 14 | 15 | > div { 16 | @include setMargin($size-0, $size-0, $size-10, $size-0); 17 | } 18 | 19 | > div:last-of-type { 20 | margin: $size-0; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/assets/stylesheets/base/_animations.scss: -------------------------------------------------------------------------------- 1 | @mixin fadeIn($duration) { 2 | -webkit-animation-duration: $duration; 3 | animation-duration: $duration; 4 | -webkit-animation-fill-mode: both; 5 | animation-fill-mode: both; 6 | -webkit-animation-name: fadein; 7 | animation-name: fadeIn; 8 | 9 | @-webkit-keyframes fadeIn { 10 | 0% { opacity: 0; } 11 | 100% { opacity: 1; } 12 | } 13 | 14 | @keyframes fadeIn { 15 | 0% { opacity: 0; } 16 | 100% { opacity: 1; } 17 | } 18 | } 19 | 20 | @mixin linearTransition($duration) { 21 | transition: $duration; 22 | transition-timing-function: linear; 23 | } 24 | 25 | @mixin standardTransition() { 26 | transition: all 0.2s ease-in-out; 27 | } 28 | -------------------------------------------------------------------------------- /app/assets/stylesheets/base/_breakpoints.scss: -------------------------------------------------------------------------------- 1 | $x-small: 480px; 2 | $small: 640px; 3 | $medium: 1024px; 4 | $large: 1440px; 5 | $x-large: 1920px; 6 | -------------------------------------------------------------------------------- /app/assets/stylesheets/base/_fonts.scss: -------------------------------------------------------------------------------- 1 | @mixin setFontSize($font-size) { 2 | font-size: $font-size; 3 | 4 | @media screen and (max-width: $medium) { 5 | font-size: $font-size - $size-2; 6 | } 7 | } 8 | 9 | $font-family: 'Lato', sans-serif; 10 | 11 | $font-weight-100: 100; 12 | $font-weight-200: 200; 13 | $font-weight-300: 300; 14 | $font-weight-400: 400; 15 | -------------------------------------------------------------------------------- /app/assets/stylesheets/base/_margin.scss: -------------------------------------------------------------------------------- 1 | @mixin setMargin($top, $right, $bottom, $left) { 2 | margin: $top $right $bottom $left; 3 | 4 | @media screen and (max-width: $medium) { 5 | $newTop: if($top == 'auto', $top, calc($top / 2)); 6 | $newRight: if($right == 'auto', $right, calc($right / 2)); 7 | $newBottom: if($bottom == 'auto', $bottom, calc($bottom / 2)); 8 | $newLeft: if($left == 'auto', $left, calc($left / 2)); 9 | 10 | margin: $newTop $newRight $newBottom $newLeft; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/assets/stylesheets/base/_padding.scss: -------------------------------------------------------------------------------- 1 | @mixin setPadding($top, $right, $bottom, $left) { 2 | padding: $top $right $bottom $left; 3 | 4 | @media screen and (max-width: $medium) { 5 | $newTop: if($top == 'auto', $top, calc($top / 2)); 6 | $newRight: if($right == 'auto', $right, calc($right / 2)); 7 | $newBottom: if($bottom == 'auto', $bottom, calc($bottom / 2)); 8 | $newLeft: if($left == 'auto', $left, calc($left / 2)); 9 | 10 | padding: $newTop $newRight $newBottom $newLeft; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/assets/stylesheets/base/_sizes.scss: -------------------------------------------------------------------------------- 1 | $size-0: 0; 2 | $size-1: 1px; 3 | $size-2: 2px; 4 | $size-4: 4px; 5 | $size-6: 6px; 6 | $size-8: 8px; 7 | $size-10: 10px; 8 | $size-12: 12px; 9 | $size-14: 14px; 10 | $size-16: 16px; 11 | $size-18: 18px; 12 | $size-20: 20px; 13 | $size-22: 22px; 14 | $size-24: 24px; 15 | $size-26: 26px; 16 | $size-28: 28px; 17 | $size-30: 30px; 18 | $size-32: 32px; 19 | $size-34: 34px; 20 | $size-35: 35px; 21 | $size-36: 36px; 22 | $size-38: 38px; 23 | $size-40: 40px; 24 | $size-42: 42px; 25 | $size-44: 44px; 26 | $size-46: 46px; 27 | $size-48: 48px; 28 | $size-50: 50px; 29 | $size-60: 60px; 30 | $size-70: 70px; 31 | -------------------------------------------------------------------------------- /app/assets/stylesheets/base/_values.scss: -------------------------------------------------------------------------------- 1 | $z-index-front: 9000; 2 | $nav-height: 86px; 3 | -------------------------------------------------------------------------------- /app/assets/stylesheets/core/alerts.scss: -------------------------------------------------------------------------------- 1 | .alert { 2 | background: $carmine; 3 | color: $white; 4 | padding: $size-10; 5 | text-align: center; 6 | border-radius: $size-4; 7 | 8 | &Text { 9 | color: $carmine; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/assets/stylesheets/core/containers.scss: -------------------------------------------------------------------------------- 1 | .center { 2 | text-align: center; 3 | 4 | &Margin { 5 | margin: 0 auto; 6 | } 7 | } 8 | 9 | .fullWidth { 10 | display: block; 11 | width: 100%; 12 | margin: 0; 13 | } 14 | 15 | .imageContainer { 16 | img { 17 | max-width: 100%; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/assets/stylesheets/core/displays.scss: -------------------------------------------------------------------------------- 1 | .displayNone { 2 | display: none; 3 | } 4 | 5 | .hideOnMobile { 6 | @media screen and (max-width: $small) { 7 | display: none; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /app/assets/stylesheets/core/forms.scss: -------------------------------------------------------------------------------- 1 | .label, 2 | label { 3 | @include setMargin($size-0, $size-0, $size-10, $size-0); 4 | @include setFontSize($size-16); 5 | @include linearTransition(0.25s); 6 | 7 | letter-spacing: 0.095em; 8 | text-transform: uppercase; 9 | font-weight: $font-weight-400; 10 | display: inherit; 11 | width: auto; 12 | } 13 | 14 | .label i, 15 | .sidebar_label i { 16 | opacity: 0.5; 17 | } 18 | -------------------------------------------------------------------------------- /app/assets/stylesheets/core/links.scss: -------------------------------------------------------------------------------- 1 | @mixin staticContentFakeLink() { 2 | @include standardTransition(); 3 | 4 | text-decoration: none; 5 | color: $cornflower; 6 | 7 | &:hover, 8 | &:focus { 9 | opacity: 0.5; 10 | } 11 | } 12 | 13 | @mixin staticContentLink() { 14 | a { 15 | @include staticContentFakeLink(); 16 | } 17 | } 18 | 19 | @mixin dashboardSectionFakeLink() { 20 | text-decoration: none; 21 | color: $blumine; 22 | 23 | &:hover, 24 | &:focus { 25 | opacity: 0.5; 26 | } 27 | } 28 | 29 | @mixin dashboardSectionLink() { 30 | a { 31 | @include dashboardSectionFakeLink(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/assets/stylesheets/core/notices.scss: -------------------------------------------------------------------------------- 1 | .notice { 2 | background: $limeade; 3 | color: $white; 4 | padding: $size-10; 5 | text-align: center; 6 | border-radius: $size-4; 7 | } 8 | -------------------------------------------------------------------------------- /app/assets/stylesheets/core/text.scss: -------------------------------------------------------------------------------- 1 | @mixin overflowText() { 2 | overflow: hidden; 3 | text-overflow: ellipsis; 4 | white-space: nowrap; 5 | } 6 | 7 | .subtle { 8 | color: $eggplant; 9 | } 10 | 11 | p { 12 | word-wrap: break-word; 13 | } 14 | 15 | .moment p { 16 | margin: 0; 17 | } 18 | 19 | .purpleYay { 20 | @include setFontSize($size-30); 21 | 22 | color: $purple-yay; 23 | text-shadow: $size-0 $size-0 $size-4 $purple-yay; 24 | } 25 | -------------------------------------------------------------------------------- /app/assets/stylesheets/dashboard/dashboard.scss: -------------------------------------------------------------------------------- 1 | .dashboard { 2 | background: $mulberry-key-lime; 3 | } 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/dashboard/dashboard_content.scss: -------------------------------------------------------------------------------- 1 | .dashboardContent { 2 | width: 100%; 3 | box-sizing: border-box; 4 | padding: $size-0 25px 50px 25px; 5 | display: flex; 6 | flex-direction: row; 7 | margin-top: $nav-height; 8 | 9 | @media screen and (max-width: $medium) { 10 | flex-direction: column; 11 | padding: 0; 12 | margin-top: 0; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/assets/stylesheets/overrides/font_awesome.scss: -------------------------------------------------------------------------------- 1 | .fa-inline { 2 | @include setMargin($size-0, $size-10, $size-0, $size-0); 3 | } 4 | 5 | .fa-left { 6 | @include setMargin($size-0, $size-0, $size-0, $size-20); 7 | } 8 | -------------------------------------------------------------------------------- /app/assets/stylesheets/secret_share/secret_share_content.scss: -------------------------------------------------------------------------------- 1 | .secretShareContent { 2 | width: 100%; 3 | box-sizing: border-box; 4 | padding: 50px 25px; 5 | background: $white-80; 6 | margin-top: $nav-height; 7 | 8 | @media screen and (max-width: $medium) { 9 | margin-top: 0; 10 | } 11 | 12 | @include dashboardSectionStyles(); 13 | } 14 | -------------------------------------------------------------------------------- /app/assets/stylesheets/static/static.scss: -------------------------------------------------------------------------------- 1 | .static { 2 | background-image: image-url('/assets/static_page_top_bg.svg'), image-url('/assets/static_page_bottom_bg.svg'), $mulberry-key-lime; 3 | background-size: 100% auto; 4 | background-position: center top, center bottom; 5 | background-repeat: no-repeat, no-repeat; 6 | } 7 | -------------------------------------------------------------------------------- /app/controllers/care_plan_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | class CarePlanController < ApplicationController 3 | def index 4 | @bookmarked_strategies = current_user.strategies.where(bookmarked: true) 5 | @contacts = current_user.care_plan_contacts.order('LOWER(name)') 6 | @bookmarked_moments = current_user.moments.where(bookmarked: true) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/controllers/concerns/care_plan_contacts_concern.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module CarePlanContactsConcern 3 | extend ActiveSupport::Concern 4 | 5 | def create_response_object(care_plan_contact) 6 | return unless care_plan_contact.save! 7 | 8 | { id: care_plan_contact.id, 9 | name: care_plan_contact.name, phone: care_plan_contact.phone } 10 | end 11 | 12 | def update_response_object(care_plan_contact) 13 | return unless care_plan_contact.update!(care_plan_contact_params) 14 | 15 | { id: care_plan_contact.id, 16 | name: care_plan_contact.name, phone: care_plan_contact.phone } 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/controllers/concerns/moment_templates_concern.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module MomentTemplatesConcern 3 | extend ActiveSupport::Concern 4 | 5 | def create_response_object(moment_template) 6 | return unless moment_template.save! 7 | 8 | { id: moment_template.id, 9 | name: moment_template.name, description: moment_template.description } 10 | end 11 | 12 | def update_response_object(moment_template) 13 | return unless moment_template.update!(moment_template_params) 14 | 15 | { id: moment_template.id, 16 | name: moment_template.name, description: moment_template.description } 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/controllers/concerns/moments_concern.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module MomentsConcern 3 | extend ActiveSupport::Concern 4 | 5 | included do 6 | helper_method :publishing?, :saving_as_draft?, 7 | :empty_array_for 8 | end 9 | 10 | def publishing? 11 | params[:publishing] == '1' 12 | end 13 | 14 | def saving_as_draft? 15 | !publishing? 16 | end 17 | 18 | def empty_array_for(*symbols) 19 | symbols.each do |symbol| 20 | @moment[symbol] = [] if moment_params[symbol].nil? && @moment.has_attribute?(symbol) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/controllers/concerns/page_redirect_concern.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module PageRedirectConcern 3 | extend ActiveSupport::Concern 4 | 5 | included do 6 | helper_method :if_not_signed_in, :if_not_admin, :redirect_to_path 7 | end 8 | 9 | def if_not_signed_in 10 | return if user_signed_in? 11 | 12 | redirect_to_path(new_user_session_path) 13 | end 14 | 15 | def if_not_admin 16 | return redirect_to_path(new_user_session_path) unless user_signed_in? 17 | 18 | return if current_user.admin? 19 | 20 | redirect_to_path('/') 21 | end 22 | 23 | def redirect_to_path(path) 24 | respond_to do |format| 25 | format.html { redirect_to path } 26 | format.json { head :no_content } 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /app/controllers/concerns/shared_basic_concern.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module SharedBasicConcern 3 | extend ActiveSupport::Concern 4 | include Shared 5 | 6 | def shared_quick_create_basic(model_class, params) 7 | model_name = model_class.name.downcase 8 | model_object = model_class.new( 9 | user_id: current_user.id, 10 | name: params[model_name][:name], 11 | description: params[model_name][:description] 12 | ) 13 | shared_quick_create(model_object) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/controllers/errors_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ErrorsController < ApplicationController 4 | skip_before_action :if_not_signed_in 5 | 6 | def not_found 7 | render status: :not_found 8 | end 9 | 10 | def internal_server_error 11 | render status: :internal_server_error 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/controllers/locales_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class LocalesController < ApplicationController 4 | skip_before_action :if_not_signed_in 5 | 6 | def set_initial_locale 7 | if current_user 8 | current_user.update!(locale: params[:locale]) 9 | else 10 | cookies['locale'] = params[:locale] 11 | end 12 | 13 | redirect_to root_path 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/controllers/registrations_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | class RegistrationsController < Devise::RegistrationsController 3 | protected 4 | 5 | def after_update_path_for(_resource) 6 | edit_user_registration_path 7 | end 8 | 9 | def update_resource(resource, params) 10 | if oauth_provider?(current_user.provider) 11 | params.delete('current_password') 12 | resource.update_without_password(params) 13 | else 14 | cookies.delete(:pwned) 15 | resource.update_with_password(params) 16 | end 17 | end 18 | 19 | def oauth_provider?(provider) 20 | %w[google_oauth2 facebook].include? provider 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /app/helpers/calendar_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module CalendarHelper 4 | def new_cal_refill_reminder_needed?(medication) 5 | (medication.add_to_google_cal && medication.refill && 6 | (medication.add_to_google_cal_changed? || medication.refill_changed?)) 7 | .present? 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/helpers/dashboard_nav_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module DashboardNavHelper 4 | include ApplicationHelper 5 | 6 | def dashboard_nav_link_to(label, url, html_options = {}) 7 | environment = html_options[:method] ? { method: html_options[:method] } : {} 8 | active_class = active?(url, environment) ? 'dashboardNavLinksActive' : '' 9 | html_options[:class] = active_class 10 | link_to(label, url, html_options) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/helpers/date_time_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module DateTimeHelper 4 | def format_date(date_str) 5 | begin 6 | date_formatted = date_str.to_date 7 | rescue ArgumentError, NoMethodError 8 | date_formatted = Date.strptime(date_str, '%m/%d/%Y') 9 | end 10 | I18n.l(date_formatted, format: :long) 11 | end 12 | 13 | def format_time(time_str) 14 | I18n.l(Time.zone.parse(time_str), format: '%I:%M %p') 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/helpers/medication_refill_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module MedicationRefillHelper 4 | include CalendarHelper 5 | 6 | def save_refill_to_google_calendar(medication) 7 | upload_to_calendar(medication) 8 | true 9 | rescue Google::Apis::ClientError 10 | return_to_sign_in 11 | false 12 | rescue Google::Apis::ServerError 13 | redirect_to_medication(medication) 14 | false 15 | end 16 | 17 | private 18 | 19 | def upload_to_calendar(medication) 20 | return unless current_user.oauth_enabled? && 21 | new_cal_refill_reminder_needed?(medication) 22 | 23 | summary = t('medications.event_summary', name: medication.name) 24 | 25 | CalendarUploader 26 | .new(current_user.access_token) 27 | .upload_event(summary, medication.refill) 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /app/helpers/profile_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ProfileHelper 4 | include ApplicationHelper 5 | include StoriesHelper 6 | include MomentsHelper 7 | 8 | def data_json 9 | { 10 | data: moments_or_strategy_props(@stories), 11 | lastPage: @stories.last_page? 12 | } 13 | end 14 | 15 | def setup_stories 16 | @profile = if current_user.uid == params[:uid] 17 | current_user 18 | else 19 | User.find_by(uid: params[:uid]) 20 | end 21 | 22 | return unless @profile == current_user || 23 | current_user.mutual_allies?(@profile) 24 | 25 | @stories = Kaminari.paginate_array(get_stories(@profile)) 26 | .page(params[:page]) 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /app/helpers/reports_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module ReportsHelper 3 | include FormHelper 4 | 5 | def new_report_props(uid, comment_id = nil) 6 | new_form_props( 7 | report_form_inputs, 8 | reports_path(uid:, comment_id:) 9 | ) 10 | end 11 | 12 | private 13 | 14 | def report_form_inputs 15 | [ 16 | { 17 | id: 'report_reasons', 18 | type: 'textarea', 19 | name: 'report[reasons]', 20 | label: t('reports.reasons') 21 | }.merge(value: nil, required: true, dark: true) 22 | ] 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/helpers/visible_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module VisibleHelper 3 | def get_visible(visible) 4 | return t('shared.stats.visible_in_stats') if visible 5 | 6 | t('shared.stats.not_visible_in_stats') 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/mailers/ally_notifications/accepted_ally_request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module AllyNotifications 4 | # Class helper to build accepted ally request message 5 | class AcceptedAllyRequest 6 | def initialize(recipient, data) 7 | @recipient = recipient 8 | @data = data 9 | end 10 | 11 | def to 12 | @recipient.email 13 | end 14 | 15 | def subject 16 | I18n.t('mailers.accepted_ally_request.subject', ally_name: @data[:user]) 17 | end 18 | 19 | def message 20 | I18n.t('mailers.accepted_ally_request.message', ally_name: @data[:user]) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/mailers/ally_notifications/new_ally_request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module AllyNotifications 4 | # Class helper to build new ally request message 5 | class NewAllyRequest 6 | include ::UrlHelper 7 | 8 | def initialize(recipient, data) 9 | @recipient = recipient 10 | @data = data 11 | end 12 | 13 | def to 14 | @recipient.email 15 | end 16 | 17 | def subject 18 | I18n.t('mailers.new_ally_request.subject', ally_name: @data[:user]) 19 | end 20 | 21 | def message 22 | I18n.t('mailers.new_ally_request.message', allies_url:) 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/mailers/banned_mailer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | class BannedMailer < ApplicationMailer 3 | default from: ENV.fetch('SMTP_FROM', nil) 4 | 5 | def add_ban_email(recipient) 6 | @recipient = recipient 7 | mail( 8 | to: @recipient.email, 9 | subject: t('notifications.mailer.add_ban_subject') 10 | ) 11 | end 12 | 13 | def remove_ban_email(recipient) 14 | @recipient = recipient 15 | mail( 16 | to: @recipient.email, 17 | subject: t('notifications.mailer.remove_ban_subject') 18 | ) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/mailers/custom_devise_mailer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | class CustomDeviseMailer < Devise::Mailer 3 | before_action :load_logo_inline 4 | 5 | protected 6 | 7 | def load_logo_inline 8 | attachments.inline['logo@2x.png'] = File.read('./public/logo@2x.png') 9 | end 10 | 11 | def subject_for(key) 12 | return super unless key.to_s == 'invitation_instructions' 13 | 14 | if @resource.invited_by&.name 15 | I18n.t( 16 | 'devise.mailer.invitation_instructions.subject', 17 | name: @resource.invited_by&.name 18 | ) 19 | else 20 | I18n.t('devise.mailer.invitation_instructions.subject_nameless') 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/mailers/report_mailer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | class ReportMailer < ApplicationMailer 3 | default from: ENV.fetch('SMTP_FROM', nil) 4 | 5 | def reported_email(recipient, reportee) 6 | @recipient = recipient 7 | @reportee = reportee 8 | mail( 9 | to: @recipient.email, 10 | subject: t('notifications.mailer.reported_user_subject') 11 | ) 12 | end 13 | 14 | def reportee_email(recipient) 15 | @recipient = recipient 16 | mail( 17 | to: @recipient.email, 18 | subject: t('notifications.mailer.reportee_user_subject') 19 | ) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationRecord < ActiveRecord::Base 4 | self.abstract_class = true 5 | 6 | def self.build_csv_rows(objects) 7 | return [] if objects.blank? 8 | 9 | klass = objects.klass 10 | data = [["#{klass.name.underscore}_info"]] 11 | attributes = if klass.const_defined?(:USER_DATA_ATTRIBUTES) 12 | klass.const_get(:USER_DATA_ATTRIBUTES) 13 | else 14 | klass.column_names 15 | end 16 | data << attributes 17 | objects.each do |object| 18 | data << attributes.map { |attribute| object.send(attribute.to_sym) } 19 | end 20 | data 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /app/models/care_plan_contact.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # == Schema Information 3 | # 4 | # Table name: care_plan_contacts 5 | # 6 | # id :bigint not null, primary key 7 | # name :string 8 | # phone :string 9 | # user_id :integer 10 | # created_at :datetime 11 | # updated_at :datetime 12 | # 13 | 14 | class CarePlanContact < ApplicationRecord 15 | USER_DATA_ATTRIBUTES = %w[ 16 | id 17 | name 18 | phone 19 | created_at 20 | updated_at 21 | ].map!(&:freeze).freeze 22 | 23 | validates :user_id, :name, presence: true 24 | belongs_to :user 25 | end 26 | -------------------------------------------------------------------------------- /app/models/concerns/is_visible_concern.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module IsVisibleConcern 3 | extend ActiveSupport::Concern 4 | 5 | included do 6 | scope :is_visible, -> { where(visible: true) } 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/models/concerns/viewer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Viewer 4 | extend ActiveSupport::Concern 5 | 6 | def viewer?(user) 7 | id = user&.id || user 8 | viewers.include?(id) 9 | end 10 | 11 | class_methods do 12 | def destroy_viewer(user_id, viewer_id) 13 | where(user_id:).find_each do |instance| 14 | viewers = instance.viewers 15 | 16 | if viewers.include?(viewer_id) 17 | viewers.delete(viewer_id) 18 | update(instance.id, viewers:) 19 | end 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/models/moment_template.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # == Schema Information 3 | # 4 | # Table name: moment_templates 5 | # 6 | # id :bigint not null, primary key 7 | # name :string 8 | # description :text 9 | # slug :string 10 | # user_id :bigint 11 | # created_at :datetime not null 12 | # updated_at :datetime not null 13 | # 14 | 15 | class MomentTemplate < ApplicationRecord 16 | extend FriendlyId 17 | 18 | friendly_id :name 19 | validates :user_id, :name, :description, presence: true 20 | belongs_to :user 21 | 22 | USER_DATA_ATTRIBUTES = %w[ 23 | id 24 | name 25 | description 26 | created_at 27 | updated_at 28 | slug 29 | ].map!(&:freeze).freeze 30 | end 31 | -------------------------------------------------------------------------------- /app/models/moments_category.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # == Schema Information 3 | # 4 | # Table name: moments_categories 5 | # 6 | # id :bigint not null, primary key 7 | # moment_id :integer 8 | # category_id :integer 9 | # 10 | 11 | class MomentsCategory < ApplicationRecord 12 | belongs_to :moment 13 | belongs_to :category 14 | 15 | validates :moment_id, uniqueness: { scope: :category_id } 16 | end 17 | -------------------------------------------------------------------------------- /app/models/moments_mood.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # == Schema Information 3 | # 4 | # Table name: moments_moods 5 | # 6 | # id :bigint not null, primary key 7 | # moment_id :integer 8 | # mood_id :integer 9 | # 10 | 11 | class MomentsMood < ApplicationRecord 12 | belongs_to :moment 13 | belongs_to :mood 14 | 15 | validates :moment_id, uniqueness: { scope: :mood_id } 16 | end 17 | -------------------------------------------------------------------------------- /app/models/moments_strategy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # == Schema Information 3 | # 4 | # Table name: moments_strategies 5 | # 6 | # id :bigint not null, primary key 7 | # moment_id :integer 8 | # strategy_id :integer 9 | # 10 | 11 | class MomentsStrategy < ApplicationRecord 12 | belongs_to :moment 13 | belongs_to :strategy 14 | 15 | validates :moment_id, uniqueness: { scope: :strategy_id } 16 | end 17 | -------------------------------------------------------------------------------- /app/models/perform_strategy_reminder.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # == Schema Information 4 | # 5 | # Table name: perform_strategy_reminders 6 | # 7 | # id :integer not null, primary key 8 | # strategy_id :integer not null 9 | # active :boolean not null 10 | # created_at :datetime 11 | # updated_at :datetime 12 | # 13 | 14 | class PerformStrategyReminder < ApplicationRecord 15 | belongs_to :strategy 16 | validates :active, inclusion: { in: [true, false] } 17 | scope :active, -> { where(active: true) } 18 | 19 | def name 20 | I18n.t('common.daily_reminder') 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /app/models/refill_reminder.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # == Schema Information 4 | # 5 | # Table name: refill_reminders 6 | # 7 | # id :integer not null, primary key 8 | # medication_id :integer not null 9 | # active :boolean not null 10 | # created_at :datetime 11 | # updated_at :datetime 12 | # 13 | 14 | class RefillReminder < ApplicationRecord 15 | belongs_to :medication 16 | validates :active, inclusion: { in: [true, false] } 17 | scope :active, -> { where(active: true) } 18 | 19 | def name 20 | I18n.t('medications.refill_reminder') 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /app/models/strategies_category.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # == Schema Information 3 | # 4 | # Table name: strategies_categories 5 | # 6 | # id :bigint not null, primary key 7 | # strategy_id :integer 8 | # category_id :integer 9 | # 10 | 11 | class StrategiesCategory < ApplicationRecord 12 | belongs_to :strategy 13 | belongs_to :category 14 | 15 | validates :strategy_id, uniqueness: { scope: :category_id } 16 | end 17 | -------------------------------------------------------------------------------- /app/models/support.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # == Schema Information 3 | # 4 | # Table name: supports 5 | # 6 | # id :bigint not null, primary key 7 | # user_id :integer 8 | # support_type :string 9 | # support_ids :text 10 | # created_at :datetime 11 | # updated_at :datetime 12 | # 13 | 14 | class Support < ApplicationRecord 15 | validates :user_id, :support_type, :support_ids, presence: true 16 | serialize :support_ids, Array 17 | validates :support_type, inclusion: %w[category mood moment strategy] 18 | before_save :array_data 19 | 20 | def array_data 21 | return unless support_ids.is_a?(Array) 22 | 23 | self.support_ids = support_ids.collect(&:to_i) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/models/take_medication_reminder.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # == Schema Information 4 | # 5 | # Table name: take_medication_reminders 6 | # 7 | # id :integer not null, primary key 8 | # medication_id :integer not null 9 | # active :boolean not null 10 | # created_at :datetime 11 | # updated_at :datetime 12 | # 13 | 14 | class TakeMedicationReminder < ApplicationRecord 15 | belongs_to :medication 16 | validates :active, inclusion: { in: [true, false] } 17 | scope :active, -> { where(active: true) } 18 | scope :for_day, lambda { |day = Time.current.wday| 19 | joins(:medication).where('? = any(medications.weekly_dosage)', day) 20 | } 21 | 22 | def name 23 | I18n.t('common.daily_reminder') 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/models/users.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Users 3 | def self.table_name_prefix 4 | 'users_' 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/services/comment_notifications_service.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CommentNotificationsService 4 | attr_reader :comment_id, :model_name 5 | 6 | def initialize(comment_id:, model_name:) 7 | @comment_id = comment_id 8 | @model_name = model_name 9 | end 10 | 11 | def self.remove(**args) 12 | new(**args).remove 13 | end 14 | 15 | def remove 16 | Comment.find(comment_id).destroy 17 | public_uniqueid = "comment_on_#{model_name}_#{comment_id}" 18 | Notification.where(uniqueid: public_uniqueid).destroy_all 19 | private_uniqueid = "comment_on_#{model_name}_private_#{comment_id}" 20 | Notification.where(uniqueid: private_uniqueid).destroy_all 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /app/services/medication_reminders.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class MedicationReminders 4 | def send_take_medication_reminder_emails 5 | TakeMedicationReminder.active.for_day.each do |reminder| 6 | NotificationMailer.take_medication(reminder).deliver_now 7 | end 8 | end 9 | 10 | def send_refill_reminder_emails 11 | ready_for_refill.each do |reminder| 12 | NotificationMailer.refill_medication(reminder).deliver_now 13 | end 14 | end 15 | 16 | private 17 | 18 | def ready_for_refill 19 | RefillReminder.active 20 | .joins(:medication) 21 | .where('medications.refill': one_week_from_now_as_string) 22 | end 23 | 24 | def one_week_from_now_as_string 25 | 1.week.from_now.strftime('%m/%d/%Y') 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /app/services/medium.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'open-uri' 4 | require 'json' 5 | 6 | class Medium 7 | def posts 8 | content_hash['items'] 9 | end 10 | 11 | private 12 | 13 | def content_hash 14 | JSON.parse(content) 15 | end 16 | 17 | def content 18 | content = '' 19 | URI.open('https://api.rss2json.com/v1/api.json?rss_url=https://medium.com/feed/ifme') do |file| 20 | file.each_line { |line| content += line } 21 | end 22 | content 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/services/meeting_reminders.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Public: Used for sending reminders regarding upcoming meetings 4 | class MeetingReminders 5 | def send_meeting_reminder_emails 6 | meetings_tomorrow.each do |meeting| 7 | meeting.members.each do |member| 8 | NotificationMailer.meeting_reminder(meeting, member).deliver_now 9 | end 10 | end 11 | end 12 | 13 | private 14 | 15 | def meetings_tomorrow 16 | tomorrow_as_string = 1.day.from_now.strftime('%m/%d/%Y') 17 | Meeting.where(date: tomorrow_as_string) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/services/recaptcha_service.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | class RecaptchaService 3 | MIN_ATTEMPTS_FOR_RECAPTCHA = 3 4 | 5 | def initialize(user) 6 | @user = user 7 | end 8 | 9 | def self.recaptcha_configured? 10 | Recaptcha.configuration.site_key.present? && 11 | Recaptcha.configuration.secret_key.present? 12 | end 13 | 14 | def recaptcha_required_for_login? 15 | return false unless self.class.recaptcha_configured? 16 | 17 | failed_attempts = @user.failed_attempts.to_i 18 | failed_attempts >= MIN_ATTEMPTS_FOR_RECAPTCHA 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/services/strategy_reminders.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class StrategyReminders 4 | def send_perform_strategy_reminder_emails 5 | PerformStrategyReminder.active.each do |reminder| 6 | NotificationMailer.perform_strategy(reminder).deliver_now 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/views/banned_mailer/add_ban_email.html.erb: -------------------------------------------------------------------------------- 1 | <%= sanitize(t('notifications.mailer.add_ban_body', code_of_conduct_link: link_to(t('navigation.code_of_conduct'), 'https://www.contributor-covenant.org/'))) %> -------------------------------------------------------------------------------- /app/views/banned_mailer/remove_ban_email.html.erb: -------------------------------------------------------------------------------- 1 | <%= sanitize(t('notifications.mailer.remove_ban_body', code_of_conduct_link: link_to(t('navigation.code_of_conduct'), 'https://www.contributor-covenant.org/'))) %> -------------------------------------------------------------------------------- /app/views/categories/_category.html.erb: -------------------------------------------------------------------------------- 1 | <%= render partial: '/stories/category_or_mood', locals: { item: category } %> 2 | -------------------------------------------------------------------------------- /app/views/categories/edit.html.erb: -------------------------------------------------------------------------------- 1 | <% title "#{t('common.actions.edit_instance', instance: @category.name)}" %> 2 | <%= react_component('Form', props: edit_category_props) %> 3 | -------------------------------------------------------------------------------- /app/views/categories/new.html.erb: -------------------------------------------------------------------------------- 1 | <% title "#{t('categories.new')}" %> 2 | <%= react_component('Form', props: new_category_props) %> 3 | -------------------------------------------------------------------------------- /app/views/categories/show.html.erb: -------------------------------------------------------------------------------- 1 | <% title @category.name %> 2 | <% if !@category.description.blank? && @category.description.length > 0 %> 3 | <%= sanitize(@category.description) %> 4 | <% else %> 5 |
6 | <%= t('warnings.no_description') %> 7 |
8 |
9 | <%= t('warnings.yes_description') %> 10 |
11 | <% end %> 12 |
13 |
14 |
<%= t('common.actions.plural') %>
15 | <%= react_component('StoryActions', props: element_visibility_based_props(@category))%> 16 |
17 |
18 | <%= render partial: '/tag_usage/index' %> 19 | -------------------------------------------------------------------------------- /app/views/devise/confirmations/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %> 2 | <%= render "devise/shared/error_messages", resource: resource %> 3 | 4 | 5 | <%= react_component 'Input', props: { 6 | type: 'email', 7 | name: 'user[email]', 8 | id: 'user_email', 9 | label: t('common.form.email'), 10 | light: true, 11 | required: true 12 | } %> 13 | 14 | <%= f.submit t('devise.confirmations.resend_confirmation'), class: 'buttonGhostM marginTop' %> 15 | <% end %> 16 | 17 | <%= render "devise/shared/links" %> 18 | -------------------------------------------------------------------------------- /app/views/devise/mailer/confirmation_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

<%= t('salutation', name: @email) %>

2 | 3 |

<%= t('devise.mailer.confirmation.link_instructions') %>:

4 | 5 |

<%= link_to t('devise.mailer.confirmation.link_text'), confirmation_url(@resource, confirmation_token: @token) %>

6 | -------------------------------------------------------------------------------- /app/views/devise/mailer/reset_password_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

<%= t('salutation', name: @email) %>

2 | 3 |

<%= t('devise.mailer.confirmation.link_instructions') %>

4 | 5 |

<%= link_to t('devise.mailer.confirmation.link_text'), edit_password_url(@resource, reset_password_token: @token) %>

6 | 7 |

<%= t('devise.mailer.reset_password.ignore') %>

8 |

<%= t('devise.mailer.reset_password.info') %>

9 | -------------------------------------------------------------------------------- /app/views/devise/mailer/unlock_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

<%= t('salutation', name: @email) %>

2 | 3 |

<%=t('devise.mailer.unlock.info') %>

4 | 5 |

<%=t('devise.mailer.unlock.link_instructions') %>

6 | 7 |

<%= link_to t('devise.mailer.unlock.link_text'), unlock_url(@resource, unlock_token: @token) %>

8 | -------------------------------------------------------------------------------- /app/views/devise/passwords/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %> 2 | <%= render "devise/shared/error_messages", resource: resource %> 3 | 4 | 5 | <%= react_component 'Input', props: { 6 | type: 'email', 7 | name: 'user[email]', 8 | id: 'user_email', 9 | label: t('common.form.email'), 10 | light: true, 11 | required: true 12 | } %> 13 | 14 | <%= f.submit t('devise.passwords.new.send_instructions'), class: 'buttonGhostM marginTop' %> 15 | <% end %> 16 | 17 | <%= render 'devise/shared/links' %> 18 | -------------------------------------------------------------------------------- /app/views/devise/unlocks/new.html.erb: -------------------------------------------------------------------------------- 1 |

Resend unlock instructions

2 | 3 | <%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %> 4 | <%= render "devise/shared/error_messages", resource: resource %> 5 | 6 | <%= react_component 'Input', props: { 7 | type: 'email', 8 | name: 'user[email]', 9 | id: 'user_email', 10 | label: t('common.form.email'), 11 | light: true, 12 | required: true 13 | } %> 14 | 15 | <%= f.submit t('devise.unlock.resend'), class: 'buttonGhostM marginTop' %> 16 | <% end %> 17 | 18 | <%= render 'devise/shared/links' %> 19 | -------------------------------------------------------------------------------- /app/views/errors/internal_server_error.html.erb: -------------------------------------------------------------------------------- 1 | <% title 'Error' %> 2 |
3 |
4 | <%= t('errors.internal_server_error.header_message') %> 5 |
6 | 7 |
8 | <%= link_to t('errors.home_page'), root_url %> 9 |
10 |
11 | -------------------------------------------------------------------------------- /app/views/errors/not_found.html.erb: -------------------------------------------------------------------------------- 1 | <% title 'Error' %> 2 |
3 |
4 | <%= t('errors.not_found.header_message') %> 5 |
6 | 7 |
8 | <%= link_to t('errors.home_page'), root_url %> 9 |
10 |
11 | -------------------------------------------------------------------------------- /app/views/groups/_members.html.erb: -------------------------------------------------------------------------------- 1 | <% group.members.each do |member| %> 2 |
3 | <%= link_to group.leaders.include?(member) ? "#{member.name} #{t('groups.index.leader')}" : member.name, profile_index_path(uid: member.uid) %> 4 | <% if member != current_user && 5 | group.instance_of?(Group) && 6 | group.leaders.include?(current_user) %> 7 | <%= kick_member_link(group, member) %> 8 | <% end %> 9 |
10 | <% end %> 11 | -------------------------------------------------------------------------------- /app/views/groups/edit.html.erb: -------------------------------------------------------------------------------- 1 | <% title "#{t('common.actions.edit_instance', instance: @group.name)}" %> 2 | <%= react_component('Form', props: edit_group_props) %> 3 | -------------------------------------------------------------------------------- /app/views/groups/new.html.erb: -------------------------------------------------------------------------------- 1 | <% title t('groups.new') %> 2 | <%= react_component('Form', props: new_group_props) %> 3 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | 3 | --- 4 | 5 | <%= t('layouts.mailer.html.app_description', 6 | app_name: link_to(t('app_name'), root_url) 7 | ).html_safe %> 8 | 9 | <%= t('layouts.mailer.html.no_reply', 10 | email: link_to(t('email'), "mailto:join.ifme@gmail.com") 11 | ).html_safe %> 12 | 13 | <%= sanitize t('shared.footer.licence_subtitle', 14 | licence: link_to(t('shared.footer.licence'), 'https://github.com/ifmeorg/ifme/blob/main/LICENSE.txt', target: 'blank')) 15 | %> 16 | -------------------------------------------------------------------------------- /app/views/medications/_medication.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= react_component('Story', 3 | html_options: html_options, 4 | props: present_medication(medication)) %> 5 |
6 | -------------------------------------------------------------------------------- /app/views/medications/edit.html.erb: -------------------------------------------------------------------------------- 1 | <% title "#{t('common.actions.edit_instance', instance: @medication.name)}" %> 2 | <%= react_component('Form', props: edit_medication_props) %> 3 | -------------------------------------------------------------------------------- /app/views/medications/new.html.erb: -------------------------------------------------------------------------------- 1 | <% title "#{t('medications.new')}" %> 2 | <%= react_component('Form', props: new_medication_props) %> 3 | -------------------------------------------------------------------------------- /app/views/meetings/_story_body.html.erb: -------------------------------------------------------------------------------- 1 |
2 | 3 | <%= t('common.form.location') %>: 4 | 5 | 6 | <% if meeting.location.match(/^https?:\/\/|www\./) %> 7 | <%= link_to meeting.location, meeting.location %> 8 | <% else %> 9 | <%= link_to meeting.location, 10 | "https://www.google.com/maps/place/" + meeting.location %> 11 | <% end %> 12 |
13 |
14 | 15 | <%= t('common.date') %>: 16 | 17 | <%= format_date(meeting.date) %> 18 |
19 |
20 | 21 | <%= t('meetings.info.meeting_time') %>: 22 | 23 | <%= format_time(meeting.time) %> 24 |
25 |
26 | 27 | <%= get_meeting_members(meeting) %> 28 | 29 |
30 | -------------------------------------------------------------------------------- /app/views/meetings/edit.html.erb: -------------------------------------------------------------------------------- 1 | <% title "#{t('common.actions.edit_instance', instance: @meeting.name)}" %> 2 | <%= react_component('Form', props: edit_meeting_props(@meeting)) %> 3 | -------------------------------------------------------------------------------- /app/views/meetings/new.html.erb: -------------------------------------------------------------------------------- 1 | <% title "#{t('meetings.new')}" %> 2 | <%= react_component('Form', props: new_meeting_props(@group)) %> 3 | -------------------------------------------------------------------------------- /app/views/moment_templates/index.html.erb: -------------------------------------------------------------------------------- 1 | <%= react_component('MomentTemplates', html_options: {}, props: { 2 | templates: @templates 3 | }) %> 4 | -------------------------------------------------------------------------------- /app/views/moments/_moment.html.erb: -------------------------------------------------------------------------------- 1 | <%= render partial: '/stories/moment_or_strategy', locals: { item: moment } %> 2 | -------------------------------------------------------------------------------- /app/views/moments/edit.html.erb: -------------------------------------------------------------------------------- 1 | <% title "#{t('common.actions.edit_instance', instance: @moment.name)}" %> 2 | <%= react_component('Form', props: edit_moment_props) %> 3 | -------------------------------------------------------------------------------- /app/views/moments/new.html.erb: -------------------------------------------------------------------------------- 1 | <% title "#{t('moments.new')}" %> 2 | <%= react_component('Form', props: new_moment_props) %> 3 | -------------------------------------------------------------------------------- /app/views/moods/_mood.html.erb: -------------------------------------------------------------------------------- 1 | <%= render partial: '/stories/category_or_mood', locals: { item: mood } %> 2 | -------------------------------------------------------------------------------- /app/views/moods/edit.html.erb: -------------------------------------------------------------------------------- 1 | <% title "#{t('common.actions.edit_instance', instance: @mood.name)}" %> 2 | <%= react_component('Form', props: edit_mood_props) %> 3 | -------------------------------------------------------------------------------- /app/views/moods/new.html.erb: -------------------------------------------------------------------------------- 1 | <% title "#{t('moods.new')}" %> 2 | <%= react_component('Form', props: new_mood_props) %> 3 | -------------------------------------------------------------------------------- /app/views/moods/show.html.erb: -------------------------------------------------------------------------------- 1 | <% title @mood.name %> 2 | <% if @mood.description.present? && @mood.description.length > 0 %> 3 | <%= sanitize(@mood.description) %> 4 | <% else %> 5 |
6 | <%= t('warnings.no_description') %> 7 |
8 |
9 | <%= t('warnings.yes_description') %> 10 |
11 | <% end %> 12 |
13 |
14 |
<%= t('common.actions.plural') %>
15 | <%= react_component('StoryActions', props: element_visibility_based_props(@mood)) %> 16 |
17 |
18 | <%= render partial: '/tag_usage/index' %> 19 | -------------------------------------------------------------------------------- /app/views/notification_mailer/meeting_reminder.html.erb: -------------------------------------------------------------------------------- 1 |

2 | <%= t('salutation', name: @member.name) %> 3 |

4 |

5 | <%= t('meetings.reminder_mailer.body', meeting_name: @meeting.name, time: @meeting.time, location: @meeting.location) %> 6 |

7 | -------------------------------------------------------------------------------- /app/views/notification_mailer/meeting_reminder.text.erb: -------------------------------------------------------------------------------- 1 | <%= t('salutation', name: @member.name) %> 2 | 3 | <%= t('meetings.reminder_mailer.body', meeting_name: @meeting.name, time: @meeting.time, location: @meeting.location) %> 4 | -------------------------------------------------------------------------------- /app/views/notification_mailer/notification_email.html.erb: -------------------------------------------------------------------------------- 1 |

2 | <%= t('salutation', name: @recipient.name) %> 3 |

4 | 5 | <%= sanitize(@message) %> 6 | -------------------------------------------------------------------------------- /app/views/notification_mailer/notification_email.text.erb: -------------------------------------------------------------------------------- 1 | <%= t('salutation', name: @recipient.name) %> 2 | <%= sanitize(@message) %> 3 | -------------------------------------------------------------------------------- /app/views/notification_mailer/perform_strategy.html.erb: -------------------------------------------------------------------------------- 1 |

2 | <%= t('salutation', name: @user.name) %> 3 |

4 |

5 | <%= t('strategies.reminder_mailer.body', name: @model.name) %> 6 |

7 | -------------------------------------------------------------------------------- /app/views/notification_mailer/perform_strategy.text.erb: -------------------------------------------------------------------------------- 1 | <%= t('salutation', name: @user.name) %> 2 | <%= t('strategies.reminder_mailer.body', name: @model.name) %> 3 | -------------------------------------------------------------------------------- /app/views/notification_mailer/refill_medication.html.erb: -------------------------------------------------------------------------------- 1 |

2 | <%= t('salutation', name: @user.name) %> 3 |

4 |

5 | <%= t('medications.refill_mailer.body', 6 | name: @model.name, refill: @model.refill) %> 7 |

8 | -------------------------------------------------------------------------------- /app/views/notification_mailer/refill_medication.text.erb: -------------------------------------------------------------------------------- 1 | <%= t('salutation', name: @user.name) %> 2 | <%= t('medications.refill_mailer.body', 3 | name: @model.name, refill: @model.refill) %> 4 | -------------------------------------------------------------------------------- /app/views/notification_mailer/take_medication.html.erb: -------------------------------------------------------------------------------- 1 |

2 | <%= t('salutation', name: @user.name) %> 3 |

4 |

5 | <%= t('medications.reminder_mailer.body', name: @model.name) %> 6 |

7 | -------------------------------------------------------------------------------- /app/views/notification_mailer/take_medication.text.erb: -------------------------------------------------------------------------------- 1 | <%= t('salutation', name: @user.name) %> 2 | <%= t('medications.reminder_mailer.body', name: @model.name) %> 3 | -------------------------------------------------------------------------------- /app/views/pages/_update_password_modal.html.erb: -------------------------------------------------------------------------------- 1 | <% if user_signed_in? && cookies[:pwned] %> 2 | <%= react_component('Modal', props: { 3 | title: t('pages.home.update_pwd_modal.title'), 4 | body: t('pages.home.update_pwd_modal.body', update_password_link: link_to(t('pages.home.update_pwd_modal.update_link'), edit_user_registration_path)), 5 | open: true 6 | }) %> 7 | <% end %> 8 | -------------------------------------------------------------------------------- /app/views/pages/home.html.erb: -------------------------------------------------------------------------------- 1 | <% if user_signed_in? && @stories.any? %> 2 | <% title t('pages.home.stories') %> 3 | <% @page_new = "New Moment" %> 4 | <% page_new new_moment_path %> 5 | <% elsif user_signed_in? %> 6 | <% title t('pages.home.signed_in_empty.main_message_one', name: current_user.name) %> 7 | <% end %> 8 | 9 | <% if !user_signed_in? %> 10 | <%= render partial: 'not_signed_in' %> 11 | <% elsif @stories.any? %> 12 | <%= react_component('BaseContainer', props: { 13 | container: 'StoryContainer', 14 | data: moments_or_strategy_props(@stories), 15 | fetchUrl: home_data_path, 16 | lastPage: @stories.last_page? 17 | }) %> 18 | <% else %> 19 | <%= render partial: 'signed_in_empty' %> 20 | <% end %> 21 | 22 | <%= render partial: 'update_password_modal' %> 23 | -------------------------------------------------------------------------------- /app/views/pages/partners.html.erb: -------------------------------------------------------------------------------- 1 | <% title t('navigation.partners') %> 2 |
3 | <%= raw t('pages.partners.description', 4 | email: link_to(t('email'), "mailto:#{t('email')}") 5 | ) %> 6 |
7 | <%= print_partners(@organizations) %> 8 | -------------------------------------------------------------------------------- /app/views/pages/press.html.erb: -------------------------------------------------------------------------------- 1 | <% title t('navigation.press') %> 2 | 3 |
4 | <% @press.each do |p| %> 5 |
6 | <%= react_component('Resource', props: { 7 | external: true, 8 | title: p['link_name'], 9 | link: p['link'], 10 | author: p['author'] 11 | }) %> 12 |
13 | <% end %> 14 |
15 | -------------------------------------------------------------------------------- /app/views/pages/resources.html.erb: -------------------------------------------------------------------------------- 1 | <% title t('navigation.resources') %> 2 | <%= react_component('Resources', props: { resources: @resources, keywords: @keywords }) %> 3 | -------------------------------------------------------------------------------- /app/views/report_mailer/reported_email.html.erb: -------------------------------------------------------------------------------- 1 | <%= t('notifications.mailer.reported_user_body', reported_name: @reportee.name) %> 2 | -------------------------------------------------------------------------------- /app/views/report_mailer/reportee_email.html.erb: -------------------------------------------------------------------------------- 1 | <%= t('notifications.mailer.reportee_user_body') %> 2 | -------------------------------------------------------------------------------- /app/views/reports/new.html.erb: -------------------------------------------------------------------------------- 1 | <% title "#{t('reports.new', name: User.find_by(uid: params[:uid]).name)}" %> 2 | <%= react_component('Form', props: new_report_props(params[:uid], params[:comment_id])) %> 3 | -------------------------------------------------------------------------------- /app/views/search/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for(:search, url: search_index_path, html: { method: :get }) do |f| %> 2 | 14 | <% end %> 15 | -------------------------------------------------------------------------------- /app/views/shared/_comments.html.erb: -------------------------------------------------------------------------------- 1 | <%= react_component('Comments', props: { 2 | comments: @comments, 3 | formProps: comment_form_props( 4 | local_assigns[:commentable], 5 | local_assigns[:commentable].class.name 6 | ) 7 | }) %> 8 | -------------------------------------------------------------------------------- /app/views/shared/_dashboard_nav_actions.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= link_to(t('shared.header.signout'), destroy_user_session_path, method: :delete, class: 'buttonGhostS') %> 3 |
-------------------------------------------------------------------------------- /app/views/shared/_dashboard_nav_links.html.erb: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /app/views/shared/_dashboard_nav_mobile.html.erb: -------------------------------------------------------------------------------- 1 | <%= render partial: "shared/dashboard_nav_links" %> 2 | <%= render partial: "shared/dashboard_nav_actions" %> 3 | -------------------------------------------------------------------------------- /app/views/shared/_page_author.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= ProfilePicture.fetch(local_assigns[:author].avatar.url, small: true) %> 3 | <%= link_to local_assigns[:author].name, profile_index_path(uid: local_assigns[:author].uid) %> 4 |
5 | -------------------------------------------------------------------------------- /app/views/stories/_category_or_mood.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= react_component('Story', 3 | html_options: html_options, 4 | props: present_category_or_mood(local_assigns[:item])) %> 5 |
6 | -------------------------------------------------------------------------------- /app/views/stories/_moment_or_strategy.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= react_component('Story', 3 | html_options: html_options, 4 | props: present_moment_or_strategy(local_assigns[:item])) %> 5 |
6 | -------------------------------------------------------------------------------- /app/views/strategies/_strategy.html.erb: -------------------------------------------------------------------------------- 1 | <%= render partial: '/stories/moment_or_strategy', locals: { item: strategy } %> 2 | -------------------------------------------------------------------------------- /app/views/strategies/edit.html.erb: -------------------------------------------------------------------------------- 1 | <% title "#{t('common.actions.edit_instance', instance: @strategy.name)}" %> 2 | <%= react_component('Form', props: edit_strategy_props(@strategy, @viewers)) %> 3 | -------------------------------------------------------------------------------- /app/views/strategies/new.html.erb: -------------------------------------------------------------------------------- 1 | <% title "#{t('strategies.new')}" %> 2 | <%= react_component('Form', props: new_strategy_props(@strategy, @viewers)) %> -------------------------------------------------------------------------------- /app/views/tag_usage/_index.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <% if @moments&.any? %> 3 |

<%= t('moments.tagged_moments') %> (<%= @total_moments %>)

4 | <%= render partial: '/tag_usage/stories', locals: { data: @moments, data_type: 'moment' } %> 5 | <% end %> 6 | <% if !@strategies.nil? && @strategies.any? %> 7 |

<%= t('strategies.tagged_strategies') %> (<%= @total_strategies %>)

8 | <%= render partial: '/tag_usage/stories', locals: { data: @strategies, data_type: 'strategy' } %> 9 | <% end %> 10 |
11 | -------------------------------------------------------------------------------- /app/views/tag_usage/_stories.html.erb: -------------------------------------------------------------------------------- 1 | <%= react_component('BaseContainer', props: { 2 | container: 'StoryContainer', 3 | data: moments_or_strategy_props(local_assigns[:data]), 4 | fetchUrl: local_assigns[:data_type] == 'moment' ? tagged_moments_path(category_id: @category&.id, mood_id: @mood&.id, strategy_id: @strategy&.id) : tagged_strategies_path(category_id: @category&.id), 5 | lastPage: local_assigns[:data].last_page? 6 | }) %> 7 | -------------------------------------------------------------------------------- /app/views/users/invitations/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for resource, as: resource_name, url: invitation_path(resource_name), html: { id: 'invitations', method: :post } do |f| %> 2 | <%= render "devise/shared/error_messages", resource: resource %> 3 | <%= react_component 'Input', props: { 4 | type: 'text', 5 | name: 'user[email]', 6 | id: 'user_email', 7 | label: t('common.form.email'), 8 | placeholder: t('allies.form.placeholder'), 9 | dark: true, 10 | required: true 11 | } %> 12 |
13 | <%= f.submit t('devise.invitations.new.submit_button'), class: 'buttonDarkM' %> 14 |
15 | <% end %> 16 | -------------------------------------------------------------------------------- /app/views/users/mailer/invitation_instructions.html.erb: -------------------------------------------------------------------------------- 1 | <%= t("devise.mailer.invitation_instructions.hello", email: @resource.email) %> 2 |
3 |
4 | <%= t("devise.mailer.invitation_instructions.someone_invited_you", name: @resource.invited_by&.name, url: root_url) %> 5 |
6 |
7 | <%= link_to t("devise.mailer.invitation_instructions.accept"), accept_invitation_url(@resource, :invitation_token => @token) %> 8 | 9 | <% if @resource.invitation_due_at %> 10 |
11 |
12 | <%= t("devise.mailer.invitation_instructions.accept_until", due_date: @resource.invitation_due_at.strftime('%A %B %d, %Y at %I:%M %p')) %> 13 | <% end %> 14 | 15 |
16 |
17 | <%= t("devise.mailer.invitation_instructions.ignore").html_safe %> 18 | -------------------------------------------------------------------------------- /app/views/users/mailer/invitation_instructions.text.erb: -------------------------------------------------------------------------------- 1 | <%= t("devise.mailer.invitation_instructions.hello", email: @resource.email) %> 2 | 3 | <%= t("devise.mailer.invitation_instructions.someone_invited_you", name: @resource.invited_by&.name, url: root_url) %> 4 | 5 | <%= accept_invitation_url(@resource, :invitation_token => @token) %> 6 | 7 | <% if @resource.invitation_due_at %> 8 | <%= t("devise.mailer.invitation_instructions.accept_until", due_date: @resource.invitation_due_at.strftime('%A %B %d, %Y at %I:%M %p')) %> 9 | <% end %> 10 | 11 | <%= strip_tags t("devise.mailer.invitation_instructions.ignore") %> 12 | -------------------------------------------------------------------------------- /app/workers/delete_stale_data_worker.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | class DeleteStaleDataWorker 3 | include Sidekiq::Worker 4 | 5 | ACTIVE_DURATION = 30.days 6 | 7 | def perform 8 | Users::DataRequest 9 | .where('updated_at < ?', (Time.current - ACTIVE_DURATION)) 10 | .where(status_id: Users::DataRequest::STATUS[:success]) 11 | .each do |dr| 12 | File.delete(dr.file_path) if dr.file_path.present? && File.exist?(dr.file_path) 13 | dr.update(status_id: Users::DataRequest::STATUS[:deleted]) 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/workers/process_data_request_worker.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | class ProcessDataRequestWorker 3 | include Sidekiq::Worker 4 | sidekiq_options queue: 'critical' 5 | 6 | def perform(request_id) 7 | data_request = Users::DataRequest.find_by(request_id:) 8 | return if data_request.blank? || 9 | data_request.status_id == Users::DataRequest::STATUS[:success] 10 | 11 | data_request.create_csv 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /bin/client.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This script is run from Procfile.dev (which is run from bin/start_app 4 | # 5 | # Make sure we're in the source directory by checking for the existence 6 | # of ./public (note that we're checking the relative path, not the absolute). 7 | # 8 | # bin/start_app should change to the source directory, so 9 | # this should never happen unless something very unusual is wrong 10 | 11 | if [ ! -d public ]; then 12 | { 13 | echo "Error in setup" 14 | exit $? 15 | } 16 | fi 17 | 18 | rails db:migrate RAILS_ENV=development 19 | rm -rf public/webpack/development/* || true && \ 20 | cd client && \ 21 | bundle exec rake react_on_rails:locale && \ 22 | yarn install && \ 23 | yarn build:development && \ 24 | 25 | exit 0; 26 | -------------------------------------------------------------------------------- /bin/install-git-hooks.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | FILE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 4 | 5 | for f in ${FILE_DIR}/../git-hooks/*; do 6 | filename=$(basename ${f}) 7 | ln -sf ${f} ${FILE_DIR}/../.git/hooks/${filename} 8 | done 9 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path("../config/application", __dir__) 3 | require_relative "../config/boot" 4 | require "rails/commands" 5 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative "../config/boot" 3 | require "rake" 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This file loads spring without using Bundler, in order to be fast 4 | # It gets overwritten when you run the `spring binstub` command 5 | 6 | unless defined?(Spring) 7 | require 'rubygems' 8 | require 'bundler' 9 | 10 | if match = Bundler.default_lockfile.read.match(/^GEM$.*?^ spring \((.*?)\)$.*?^$/m) 11 | ENV['GEM_PATH'] = ([Bundler.bundle_path.to_s] + Gem.path).join(File::PATH_SEPARATOR) 12 | ENV['GEM_HOME'] = '' 13 | Gem.paths = ENV 14 | 15 | gem 'spring', match[1] 16 | require 'spring/binstub' 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /bin/start_app: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | require 'fileutils' 4 | include FileUtils 5 | 6 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 7 | 8 | chdir APP_ROOT do 9 | puts "\n== Installing gems ==" 10 | system('bundle install') 11 | puts "\n== Installing npm packages ==" 12 | system('cd client/ && yarn install && cd ..') 13 | puts "\n== Starting application server (foreman) ==" 14 | system('foreman start -f Procfile.dev') 15 | end 16 | -------------------------------------------------------------------------------- /bin/webpack: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" 4 | ENV["NODE_ENV"] ||= "development" 5 | 6 | require "pathname" 7 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 8 | Pathname.new(__FILE__).realpath) 9 | 10 | require "bundler/setup" 11 | 12 | require "webpacker" 13 | require "webpacker/webpack_runner" 14 | 15 | APP_ROOT = File.expand_path("..", __dir__) 16 | Dir.chdir(APP_ROOT) do 17 | Webpacker::WebpackRunner.run(ARGV) 18 | end 19 | -------------------------------------------------------------------------------- /bin/webpack-dev-server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" 4 | ENV["NODE_ENV"] ||= "development" 5 | 6 | require "pathname" 7 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 8 | Pathname.new(__FILE__).realpath) 9 | 10 | require "bundler/setup" 11 | 12 | require "webpacker" 13 | require "webpacker/dev_server_runner" 14 | 15 | APP_ROOT = File.expand_path("..", __dir__) 16 | Dir.chdir(APP_ROOT) do 17 | Webpacker::DevServerRunner.run(ARGV) 18 | end 19 | -------------------------------------------------------------------------------- /client/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/env", { 5 | "useBuiltIns": "usage", 6 | "modules": "commonjs", 7 | "corejs": "3" 8 | } 9 | ], 10 | "@babel/preset-flow", 11 | "@babel/preset-react" 12 | ], 13 | "plugins": [ 14 | "@babel/plugin-proposal-class-properties", 15 | "@babel/plugin-syntax-dynamic-import", 16 | "@babel/plugin-transform-modules-commonjs", 17 | "@babel/plugin-proposal-private-methods", 18 | "@babel/plugin-proposal-private-property-in-object" 19 | ], 20 | "env": { 21 | "test": { 22 | "plugins": [ 23 | "babel-plugin-transform-es2015-modules-commonjs" 24 | ] 25 | }, 26 | "development": { 27 | "plugins": [ 28 | "flow-react-proptypes" 29 | ] 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /client/.eslintignore: -------------------------------------------------------------------------------- 1 | flow/ 2 | flow-typed/ 3 | default.js 4 | translations.js 5 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | flow-typed/ 3 | translations.js 4 | default.js 5 | /coverage 6 | /storybook-static 7 | /.out*/* 8 | /.public*/* 9 | /app/libs/i18n/*.json 10 | -------------------------------------------------------------------------------- /client/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "singleQuote": true 4 | } -------------------------------------------------------------------------------- /client/.storybook/preview.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies, import/no-unresolved, import/extensions */ 2 | import React from 'react'; 3 | import './stories.scss'; 4 | 5 | export const parameters = { 6 | backgrounds: { 7 | default: 'light-grey', 8 | values: [ 9 | { name: 'light-grey', value: '#D3D3D3' }, 10 | { name: 'grey', value: '#808080' }, 11 | { name: 'white', value: '#FFFFFF' }, 12 | { name: 'mulberry', value: '#6D0839' }, 13 | ], 14 | }, 15 | viewMode: 'docs', 16 | }; 17 | -------------------------------------------------------------------------------- /client/.storybook/stories.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/global.scss"; 2 | 3 | /* This file is to override Storybook default styles */ 4 | 5 | body { 6 | /* Needed to get Storybook Backgrounds Addon to work */ 7 | background-color: transparent !important; 8 | height: auto !important; 9 | /* Add padding to story page to give it some space against the edges */ 10 | padding: 20px !important; 11 | } 12 | -------------------------------------------------------------------------------- /client/.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-standard", 3 | "ignoreFiles": ["../node_modules/**/*.scss", "./node_modules/**/*.scss", "./app/styles/_global_font.scss"], 4 | "rules": { 5 | "at-rule-no-unknown": null, 6 | "no-descending-specificity": null 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /client/app/components/Avatar/Avatar.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/_global.scss"; 2 | 3 | .avatar { 4 | @include setFontSize($size-20); 5 | 6 | display: inline-block; 7 | text-align: center; 8 | } 9 | 10 | .image { 11 | border-radius: 50%; 12 | overflow: hidden; 13 | object-fit: cover; 14 | background: $mulberry-key-lime; 15 | height: 100px; 16 | width: 100px; 17 | margin: 0 auto; 18 | } 19 | 20 | .large { 21 | @include setFontSize($size-22); 22 | 23 | .image { 24 | height: 150px; 25 | width: 150px; 26 | } 27 | } 28 | 29 | .small { 30 | @include setFontSize($size-18); 31 | 32 | .image { 33 | height: 50px; 34 | width: 50px; 35 | } 36 | } 37 | 38 | .name { 39 | margin-top: 0.5em; 40 | } 41 | -------------------------------------------------------------------------------- /client/app/components/BaseContainer/__tests__/StoryContainer.spec.jsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react'; 3 | import { render } from '@testing-library/react'; 4 | import StoryContainer from 'components/BaseContainer/StoryContainer'; 5 | 6 | describe('StoryContainer', () => { 7 | it('renders correctly', () => { 8 | const { container } = render(); 9 | expect(container.firstChild).not.toBeNull(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /client/app/components/Blockquote/Blockquote.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/_global.scss"; 2 | 3 | .text { 4 | @include setFontSize($size-24); 5 | 6 | display: inline-block; 7 | position: relative; 8 | background-color: rgba(255, 255, 255, 0.1); 9 | font-style: italic; 10 | text-align: center; 11 | align-content: center; 12 | text-shadow: $size-0 $size-2 4px rgba(0, 0, 0, 0.25); 13 | width: 100%; 14 | padding: $size-50; 15 | color: $white; 16 | 17 | @media screen and (max-width: $medium) { 18 | padding: $size-20; 19 | } 20 | 21 | .author { 22 | @include setMargin($size-10, $size-0, $size-0, $size-0); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /client/app/components/Blockquote/index.jsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react'; 3 | import type { Node } from 'react'; 4 | import css from './Blockquote.scss'; 5 | 6 | export type Props = { 7 | text?: string, 8 | author?: string, 9 | }; 10 | 11 | export const Blockquote = (props: Props): Node => { 12 | const { text, author } = props; 13 | const textClassNames = `${css.text}`; 14 | const authorClassNames = `${css.author}`; 15 | return ( 16 |
17 |

18 | {text} 19 |

20 |
{author}
21 |
22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /client/app/components/Chart/ChartControl.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/_global.scss"; 2 | 3 | .chartControl { 4 | > div:first-of-type { 5 | @include setMargin($size-0, $size-0, $size-20, $size-0); 6 | 7 | line-height: 2.5; 8 | } 9 | 10 | button { 11 | @include setMargin($size-0, $size-8, $size-0, $size-0); 12 | 13 | &:last-of-type { 14 | @include setMargin($size-0, $size-0, $size-0, $size-0); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /client/app/components/Chart/index.jsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | /* eslint react/jsx-props-no-spreading: 0 */ 3 | import { Chart as ChartJS } from 'chart.js'; 4 | import React from 'react'; 5 | import type { Node } from 'react'; 6 | import { AreaChart, LineChart } from 'react-chartkick'; 7 | 8 | ChartJS.defaults.global.defaultFontFamily = 'Lato'; 9 | 10 | type chartShape = { 11 | xtitle?: string, 12 | ytitle?: string, 13 | data?: Object | any[], 14 | chartType: 'Line' | 'Area', 15 | }; 16 | 17 | const colorSchemes = ['#6D0839', '#66118', '#7F503F', '#775577', '#CCAADD']; 18 | 19 | export function Chart({ chartType, ...props }: chartShape): Node { 20 | return chartType === 'Line' ? ( 21 | 22 | ) : ( 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /client/app/components/Form/Form.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/_global.scss"; 2 | 3 | .form { 4 | > div { 5 | @include setMargin($size-0, $size-0, $size-20, $size-0); 6 | 7 | &:last-of-type { 8 | @include setMargin($size-0, $size-0, $size-0, $size-0); 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /client/app/components/Header/types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export type Link = { 4 | name: string, 5 | url: string, 6 | active?: boolean, 7 | dataMethod?: string, 8 | hideInMobile?: boolean, 9 | }; 10 | 11 | export type Profile = { 12 | avatar?: string, 13 | name: string, 14 | profile: Link, 15 | account: Link, 16 | notifications: { 17 | plural: string, 18 | none: string, 19 | clear: string, 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /client/app/components/Input/InputPassword.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/_global.scss"; 2 | 3 | .password { 4 | display: flex; 5 | flex-direction: row; 6 | 7 | input { 8 | border-radius: $size-4 $size-0 $size-0 $size-4 !important; 9 | } 10 | 11 | button { 12 | @include setFontSize($size-20); 13 | 14 | background-color: $mulberry; 15 | border: 0; 16 | color: $white; 17 | border-radius: $size-0 $size-4 $size-4 $size-0; 18 | width: 70px; 19 | box-shadow: $size-0 $size-2 $size-10 $black-10; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /client/app/components/Input/InputRadioGroup.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/_global.scss"; 2 | 3 | .wrapper { 4 | display: flex; 5 | flex-direction: row; 6 | 7 | .content { 8 | margin-right: $size-16; 9 | } 10 | 11 | input { 12 | margin-right: $size-6; 13 | margin-left: $size-6; 14 | } 15 | 16 | label { 17 | all: unset; 18 | color: $mulberry-90; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /client/app/components/Input/__tests__/InputLabel.spec.jsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { render, screen } from '@testing-library/react'; 3 | import React from 'react'; 4 | import { InputLabel } from 'components/Input/InputLabel'; 5 | 6 | const label = 'Some Label'; 7 | const info = 'Some Info'; 8 | 9 | describe('InputLabel', () => { 10 | it('renders correctly', () => { 11 | render( 12 | , 13 | ); 14 | expect(screen.getByText(label)).toBeInTheDocument(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /client/app/components/LoadMoreButton/LoadMoreButton.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/_global.scss"; 2 | 3 | .container { 4 | text-align: center; 5 | } 6 | -------------------------------------------------------------------------------- /client/app/components/LoadMoreButton/index.jsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react'; 3 | import type { Node } from 'react'; 4 | import { I18n } from 'libs/i18n'; 5 | import css from './LoadMoreButton.scss'; 6 | 7 | export type Props = { 8 | onClick?: () => void, 9 | }; 10 | 11 | const LoadMoreButton = ({ onClick }: Props): Node => ( 12 |
13 | 20 |
21 | ); 22 | 23 | export { LoadMoreButton }; 24 | -------------------------------------------------------------------------------- /client/app/components/Logo/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { LogoFactory } from './LogoFactory'; 3 | 4 | export const Logo: any = LogoFactory(); 5 | Logo.displayName = 'Logo'; 6 | 7 | export const LogoSolid: any = LogoFactory('solid'); 8 | LogoSolid.displayName = 'LogoSolid'; 9 | -------------------------------------------------------------------------------- /client/app/components/OAuthButton/facebookIcon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/app/components/OAuthButton/googleIcon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/app/components/SkipToContent/SkipToContent.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/_global.scss"; 2 | 3 | .skipToContent { 4 | // accessibility hidden element 5 | height: 1px; 6 | left: -10000px; 7 | overflow: hidden; 8 | position: absolute; 9 | top: auto; 10 | width: 1px; 11 | // styling 12 | align-items: center; 13 | background: $cornflower; 14 | color: $carmine; 15 | display: flex; 16 | flex-direction: row; 17 | flex-wrap: wrap; 18 | justify-content: space-evenly; 19 | line-height: 3.5; 20 | margin-top: $size-4; 21 | 22 | &:focus { 23 | height: auto; 24 | width: auto; 25 | position: static; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /client/app/components/SkipToContent/__tests__/SkipToContent.spec.jsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react'; 3 | import { render } from '@testing-library/react'; 4 | import SkipToContent from 'components/SkipToContent'; 5 | 6 | describe('SkipToContent', () => { 7 | it('renders correctly', () => { 8 | let wrapper; 9 | expect(() => { 10 | wrapper = render(); 11 | }).not.toThrow(); 12 | expect(wrapper).not.toBeNull(); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /client/app/components/SkipToContent/index.jsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react'; 3 | import type { Node } from 'react'; 4 | 5 | import { I18n } from 'libs/i18n'; 6 | import css from './SkipToContent.scss'; 7 | 8 | export type Props = { 9 | id: string, 10 | }; 11 | 12 | const SkipToContent = ({ id }: Props): Node => ( 13 | 14 | {I18n.t('navigation.skip_to_main_content')} 15 | 16 | ); 17 | 18 | export default SkipToContent; 19 | -------------------------------------------------------------------------------- /client/app/components/Story/StoryBy.jsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react'; 3 | import type { Node } from 'react'; 4 | import { Utils } from 'utils'; 5 | import { Avatar } from 'components/Avatar'; 6 | import css from './Story.scss'; 7 | 8 | export type Props = { 9 | avatar?: string, 10 | author: any, 11 | }; 12 | 13 | export const StoryBy = (props: Props): Node => { 14 | const { avatar, author } = props; 15 | return ( 16 |
17 | 18 |
{Utils.renderContent(author)}
19 |
20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /client/app/components/Story/StoryCategories.jsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react'; 3 | import type { Node } from 'react'; 4 | import { Tag } from 'components/Tag'; 5 | import css from './Story.scss'; 6 | 7 | export type Category = { 8 | name: string, 9 | slug: string, 10 | }; 11 | 12 | export type Props = { 13 | categories: Category[], 14 | }; 15 | 16 | export const StoryCategories = (props: Props): Node => { 17 | const { categories } = props; 18 | return ( 19 |
20 | {categories.map((value) => ( 21 | 22 | ))} 23 |
24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /client/app/components/Story/StoryDate.jsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react'; 3 | import type { Node } from 'react'; 4 | import css from './Story.scss'; 5 | 6 | export type Props = { 7 | date: string, 8 | }; 9 | 10 | export const StoryDate = (props: Props): Node => { 11 | const { date } = props; 12 | return
{date}
; 13 | }; 14 | -------------------------------------------------------------------------------- /client/app/components/Story/StoryDraft.jsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react'; 3 | import type { Node } from 'react'; 4 | import { Tag } from 'components/Tag'; 5 | import css from './Story.scss'; 6 | 7 | export type Props = { 8 | draft: string, 9 | }; 10 | 11 | export const StoryDraft = (props: Props): Node => { 12 | const { draft } = props; 13 | return ( 14 |
15 | 16 |
17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /client/app/components/Story/StoryMoods.jsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react'; 3 | import type { Node } from 'react'; 4 | import { Tag } from 'components/Tag'; 5 | import css from './Story.scss'; 6 | 7 | export type Mood = { 8 | name: string, 9 | slug: string, 10 | }; 11 | 12 | export type Props = { 13 | moods: Mood[], 14 | }; 15 | 16 | export const StoryMoods = (props: Props): Node => { 17 | const { moods } = props; 18 | return ( 19 |
20 | {moods.map((value) => ( 21 | 22 | ))} 23 |
24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /client/app/components/Story/StoryName.jsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react'; 3 | import type { Node } from 'react'; 4 | import css from './Story.scss'; 5 | 6 | export type Props = { 7 | name: string, 8 | link: ?string, 9 | onClick?: Function, 10 | }; 11 | 12 | export const StoryName = ({ name, link, onClick }: Props): Node => { 13 | if (link) { 14 | return ( 15 | 16 | {name} 17 | 18 | ); 19 | } 20 | if (onClick) { 21 | return ( 22 | 25 | ); 26 | } 27 | return {name}; 28 | }; 29 | -------------------------------------------------------------------------------- /client/app/components/Story/__tests__/StoryBy.spec.jsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { render, screen } from '@testing-library/react'; 3 | import React from 'react'; 4 | import { StoryBy } from 'components/Story/StoryBy'; 5 | 6 | describe('StoryBy', () => { 7 | const { getByText } = screen; 8 | 9 | describe('has no options', () => { 10 | it('renders correctly', () => { 11 | render(); 12 | expect(getByText('Some author')).toBeInTheDocument(); 13 | }); 14 | }); 15 | describe('has all options', () => { 16 | it('renders correctly', () => { 17 | render(); 18 | expect(getByText('Some author')).toBeInTheDocument(); 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /client/app/components/Story/__tests__/StoryCategories.spec.jsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { render, screen } from '@testing-library/react'; 3 | import React from 'react'; 4 | import { StoryCategories } from 'components/Story/StoryCategories'; 5 | 6 | describe('StoryCategories', () => { 7 | const { getByText } = screen; 8 | 9 | it('renders correctly', () => { 10 | render( 11 | , 12 | ); 13 | expect(getByText('Family')).toBeInTheDocument(); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /client/app/components/Story/__tests__/StoryDate.spec.jsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { render, screen } from '@testing-library/react'; 3 | import React from 'react'; 4 | import { StoryDate } from 'components/Story/StoryDate'; 5 | 6 | describe('StoryDate', () => { 7 | const { getByText } = screen; 8 | 9 | it('renders correctly', () => { 10 | render(); 11 | expect(getByText('Created 2 Days ago')).toBeInTheDocument(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /client/app/components/Story/__tests__/StoryDraft.spec.jsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { render, screen } from '@testing-library/react'; 3 | import React from 'react'; 4 | import { StoryDraft } from 'components/Story/StoryDraft'; 5 | 6 | describe('StoryDraft', () => { 7 | const { getByText } = screen; 8 | 9 | it('renders correctly', () => { 10 | render(); 11 | expect(getByText('DRAFT')).toBeInTheDocument(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /client/app/components/Story/__tests__/StoryMoods.spec.jsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { render, screen } from '@testing-library/react'; 3 | import React from 'react'; 4 | import { StoryMoods } from 'components/Story/StoryMoods'; 5 | 6 | describe('StoryMoods', () => { 7 | const { getByText } = screen; 8 | 9 | it('renders correctly', () => { 10 | render(); 11 | expect(getByText('Nervous')).toBeInTheDocument(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /client/app/components/Story/__tests__/StoryName.spec.jsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { render, screen } from '@testing-library/react'; 3 | import React from 'react'; 4 | import { StoryName } from 'components/Story/StoryName'; 5 | 6 | describe('StoryName', () => { 7 | const { getByText } = screen; 8 | 9 | it('renders correctly', () => { 10 | render(); 11 | expect(getByText('Real Moment')).toBeInTheDocument(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /client/app/components/Toast/Toast.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/_global.scss"; 2 | 3 | .toast { 4 | display: flex; 5 | justify-content: space-between; 6 | width: fit-content; 7 | margin: 0 auto; 8 | 9 | button { 10 | background: transparent; 11 | color: $white; 12 | opacity: 1; 13 | border: none; 14 | 15 | &:hover { 16 | border: none; 17 | opacity: 0.8; 18 | } 19 | } 20 | } 21 | 22 | .toastElementHidden { 23 | visibility: hidden; 24 | } 25 | 26 | .toastElementVisible { 27 | visibility: visible; 28 | } 29 | -------------------------------------------------------------------------------- /client/app/hooks/index.js: -------------------------------------------------------------------------------- 1 | export { useFocusTrap } from './useFocusTrap'; 2 | -------------------------------------------------------------------------------- /client/app/libs/history.js: -------------------------------------------------------------------------------- 1 | import { createBrowserHistory } from 'history'; 2 | 3 | export default createBrowserHistory(); 4 | -------------------------------------------------------------------------------- /client/app/pages/MomentTemplates/MomentTemplates.scss: -------------------------------------------------------------------------------- 1 | @import '~styles/_global.scss'; 2 | 3 | .newTemplate { 4 | @media screen and (max-width: $small) { 5 | width: 100%; 6 | } 7 | 8 | button { 9 | font-weight: bold; 10 | 11 | @media screen and (max-width: $small) { 12 | width: inherit; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /client/app/pages/MomentTemplates/MomentTemplatesContext.jsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { createContext } from 'react'; 3 | 4 | const TemplatesContext: Object = createContext({}); 5 | 6 | export default TemplatesContext; 7 | -------------------------------------------------------------------------------- /client/app/startup/scrollToTop.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | export const scrollToTop = () => { 3 | window.addEventListener('beforeunload', () => { 4 | window.scrollTo(0, 0); 5 | }); 6 | }; 7 | -------------------------------------------------------------------------------- /client/app/startup/setTimezone.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import * as jstz from 'jstimezonedetect'; 3 | import Cookies from 'js-cookie'; 4 | 5 | Cookies.set('timezone', jstz.determine().name(), { secure: true }); 6 | -------------------------------------------------------------------------------- /client/app/stories/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "import/no-extraneous-dependencies": ["error", {"devDependencies": true }] 4 | } 5 | } 6 | 7 | -------------------------------------------------------------------------------- /client/app/stories/Blockquote.stories.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/jsx-props-no-spreading */ 2 | import React from 'react'; 3 | import { Blockquote } from 'components/Blockquote'; 4 | 5 | export default { 6 | title: 'Components/Blockquote', 7 | component: Blockquote, 8 | }; 9 | 10 | const Template = (args) =>
; 11 | 12 | export const Default = Template.bind({}); 13 | 14 | Default.args = { 15 | text: 16 | "It's not just all in your head, it's all around you. We can heal together.", 17 | author: '❤️', 18 | }; 19 | Default.parameters = { 20 | backgrounds: { default: 'mulberry' }, 21 | }; 22 | -------------------------------------------------------------------------------- /client/app/stories/Errors.stories.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import React from 'react'; 3 | import css from 'styles/_global.scss'; 4 | 5 | export default { 6 | title: 'Style Guide/Errors', 7 | }; 8 | 9 | const Template = (args) => ( 10 |
11 |

errors

12 |
    13 |
  • this
  • 14 |
  • is
  • 15 |
  • an
  • 16 |
  • example
  • 17 |
18 |
19 | ); 20 | 21 | export const Default = Template.bind({}); 22 | -------------------------------------------------------------------------------- /client/app/stories/Fonts.stories.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default { 4 | title: 'Style Guide/Fonts', 5 | }; 6 | 7 | export const Fonts = () => ( 8 | <> 9 |

$font-weight-100

10 |

$font-weight-200

11 |

$font-weight-300

12 |

$font-weight-400

13 | 14 | ); 15 | 16 | Fonts.parameters = { 17 | viewMode: 'story', 18 | backgrounds: { default: 'white' }, 19 | previewTabs: { 20 | 'storybook/docs/panel': { 21 | hidden: true, 22 | }, 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /client/app/stories/I18n.stories.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import { I18n } from 'libs/i18n'; 3 | 4 | export default { 5 | title: 'Libraries/I18n', 6 | }; 7 | 8 | const TranslationWithNoVariableTemplate = (args) => I18n.t('draft'); 9 | 10 | export const TranslationWithNoVariable = TranslationWithNoVariableTemplate.bind( 11 | {}, 12 | ); 13 | 14 | TranslationWithNoVariable.storyName = 'Translation with no variable'; 15 | 16 | const TranslationWithVariableTemplate = (args) => I18n.t('created', { created_at: 'Blah' }); 17 | 18 | export const TranslationWithVariable = TranslationWithVariableTemplate.bind({}); 19 | 20 | TranslationWithVariable.storyName = 'Translation with variable'; 21 | -------------------------------------------------------------------------------- /client/app/stories/Toast.stories.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/jsx-props-no-spreading */ 2 | import React from 'react'; 3 | import { Toast } from 'components/Toast'; 4 | 5 | export default { 6 | title: 'Components/Toast', 7 | component: Toast, 8 | }; 9 | 10 | const Template = (args) => ; 11 | 12 | export const noticeToast = Template.bind({}); 13 | 14 | noticeToast.args = { 15 | notice: 'Login successful.', 16 | appendDashboardClass: true, 17 | }; 18 | noticeToast.storyName = 'Toast Type: Notice'; 19 | 20 | export const alertToast = Template.bind({}); 21 | 22 | alertToast.args = { 23 | alert: 'Login failed.', 24 | }; 25 | alertToast.storyName = 'Toast Type: Alert'; 26 | -------------------------------------------------------------------------------- /client/app/styles/_global.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/_legacy.scss"; 2 | @import "~styles/_global_font.scss"; 3 | 4 | body, 5 | html { 6 | @include setFontSize($size-20); 7 | 8 | margin: 0 auto; 9 | font-family: $font-family; 10 | font-weight: $font-weight-400; 11 | line-height: 1.4; 12 | } 13 | 14 | a { 15 | text-decoration: none; 16 | 17 | &:hover, 18 | &:focus { 19 | opacity: 0.5; 20 | } 21 | } 22 | 23 | @include dashboardSectionStyles(); 24 | -------------------------------------------------------------------------------- /client/app/widgets/CarePlanContacts/CarePlanContacts.scss: -------------------------------------------------------------------------------- 1 | @import '~styles/_global.scss'; 2 | 3 | .newContact { 4 | @media screen and (max-width: $small) { 5 | width: 100%; 6 | } 7 | 8 | button { 9 | font-weight: bold; 10 | 11 | @media screen and (max-width: $small) { 12 | width: inherit; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /client/app/widgets/CarePlanContacts/CarePlanContactsContext.jsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { createContext } from 'react'; 3 | 4 | const ContactsContext: Object = createContext({}); 5 | 6 | export default ContactsContext; 7 | -------------------------------------------------------------------------------- /client/app/widgets/Comments/Comments.scss: -------------------------------------------------------------------------------- 1 | @import '~styles/_global.scss'; 2 | 3 | .comments { 4 | @include setMargin($size-40, $size-0, $size-0, $size-0); 5 | } 6 | 7 | .comment { 8 | @include fadeIn(1.5s); 9 | @include gridItemBoxLight(); 10 | 11 | &:last-of-type { 12 | @include setMargin($size-0, $size-0, $size-0, $size-0); 13 | } 14 | 15 | &Info { 16 | @include setMargin($size-20, $size-0, $size-0, $size-0); 17 | 18 | display: flex; 19 | flex-direction: row; 20 | justify-content: space-between; 21 | align-items: center; 22 | } 23 | 24 | &Content { 25 | @include setMargin($size-0, $size-0, $size-30, $size-0); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /client/app/widgets/CrisisPrevention/CrisisPrevention.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/_global.scss"; 2 | 3 | .heart { 4 | @extend .purpleYay; 5 | 6 | @include setFontSize($size-20); 7 | } 8 | -------------------------------------------------------------------------------- /client/app/widgets/CrisisPrevention/__tests___/index.spec.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import CrisisPrevention from 'widgets/CrisisPrevention'; 4 | 5 | describe('CrisisPrevention', () => { 6 | it('renders the component', () => { 7 | let container; 8 | expect(() => { 9 | container = render(); 10 | }).not.toThrow(); 11 | expect(container).not.toBeNull(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /client/app/widgets/QuickCreate/QuickCreate.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/_global.scss"; 2 | 3 | .modal { 4 | font-size: $size-0; 5 | } 6 | -------------------------------------------------------------------------------- /client/app/widgets/Resources/Resources.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/_global.scss"; 2 | -------------------------------------------------------------------------------- /client/flow/media.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare export default string 3 | -------------------------------------------------------------------------------- /client/flow/stylesheets.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare export default { [key: string]: string } 3 | -------------------------------------------------------------------------------- /client/jest/svgTransform.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | process: () => 'module.exports = {};', 3 | getCacheKey: () => 'svgTransform', 4 | }; 5 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This file is used by Rack-based servers to start the application. 4 | 5 | require 'rack-rewrite' 6 | 7 | use Rack::Rewrite do 8 | r301(/.*/, 'https://www.if-me.org$&', host: 'if-me.org') 9 | end 10 | 11 | require File.expand_path('config/environment', __dir__) 12 | run Rails.application 13 | -------------------------------------------------------------------------------- /config/.yamllint: -------------------------------------------------------------------------------- 1 | # Configuration file for YAML linting (https://github.com/adrienverge/yamllint) 2 | extends: default 3 | 4 | rules: 5 | document-start: disable 6 | line-length: disable 7 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 4 | 5 | require "bundler/setup" # Set up gems listed in the Gemfile. 6 | 7 | if Gem::Specification.find_all_by_name("dotenv").any? 8 | require "dotenv" 9 | environment = ENV["RAILS_ENV"] || :development 10 | dotenv_path = File.join("config", "env", "#{environment}.env") 11 | Dotenv.load(dotenv_path) 12 | end 13 | -------------------------------------------------------------------------------- /config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: async 6 | 7 | production: 8 | adapter: redis 9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> 10 | channel_prefix: ifme_production 11 | -------------------------------------------------------------------------------- /config/cloudinary.yml: -------------------------------------------------------------------------------- 1 | development: 2 | cloud_name: ENV['CLOUDINARY_CLOUD_NAME'] 3 | api_key: ENV['CLOUDINARY_API_KEY'] 4 | api_secret: ENV['CLOUDINARY_API_SECRET'] 5 | enhance_image_tag: true 6 | static_image_support: true 7 | secure: true 8 | test: 9 | cloud_name: ENV['CLOUDINARY_CLOUD_NAME'] 10 | api_key: ENV['CLOUDINARY_API_KEY'] 11 | api_secret: ENV['CLOUDINARY_API_SECRET'] 12 | enhance_image_tag: true 13 | static_image_support: true 14 | secure: true 15 | production: 16 | cloud_name: ENV['CLOUDINARY_CLOUD_NAME'] 17 | api_key: ENV['CLOUDINARY_API_KEY'] 18 | api_secret: ENV['CLOUDINARY_API_SECRET'] 19 | enhance_image_tag: true 20 | static_image_support: true 21 | secure: true 22 | -------------------------------------------------------------------------------- /config/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Rails.application.config.content_security_policy do |policy| 4 | policy.default_src :none 5 | policy.connect_src :self 6 | policy.style_src :unsafe_inline, :self 7 | policy.font_src :self, 'https://cdn.jsdelivr.net' 8 | policy.img_src 'https://maps.gstatic.com', 'https://res.cloudinary.com' 9 | policy.script_src 'self', 'https://js.pusher.com/3.0.0/xhr.min.js', 10 | 'https://js.pusher.com/3.0/pusher.min.js', 11 | 'https://maps.googleapis.com/maps/api/js', 12 | 'https://maps.googleapis.com/maps-api-v3/api/js', 13 | 'https://maps.googleapis.com/maps/api/place/js' 14 | end 15 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | default: &default 2 | adapter: postgresql 3 | allow_concurrency: true 4 | encoding: unicode 5 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 6 | development: 7 | <<: *default 8 | host: <%= ENV['PSQL_HOST'] || 'localhost' %> 9 | database: ifme_development 10 | username: <%= ENV['PSQL_USERNAME'] %> 11 | password: <%= ENV['PSQL_PASSWORD'] %> 12 | min_messages: warning 13 | test: 14 | <<: *default 15 | database: ifme_test 16 | host: <%= ENV['PSQL_HOST'] || 'localhost' %> 17 | username: <%= ENV['PSQL_USERNAME'] %> 18 | password: <%= ENV['PSQL_PASSWORD'] %> 19 | min_messages: error 20 | url: <%= ENV['DATABASE_URL'] %> 21 | production: 22 | <<: *default 23 | database: ifme_production 24 | username: <%= ENV['PSQL_USERNAME'] %> 25 | password: <%= ENV['PSQL_PASSWORD'] %> 26 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Load the Rails application. 4 | require_relative "application" 5 | 6 | # Initialize the Rails application. 7 | Rails.application.initialize! 8 | -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # ActiveSupport::Reloader.to_prepare do 6 | # ApplicationController.renderer.defaults.merge!( 7 | # http_host: 'example.org', 8 | # https: false 9 | # ) 10 | # end 11 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Version of your assets, change this if you want to expire all your assets. 6 | Rails.application.config.assets.version = "1.0" 7 | 8 | # Add additional assets to the asset load path. 9 | # Rails.application.config.assets.paths << Emoji.images_path 10 | 11 | Rails.application.config.assets.precompile += %w( application/mailer.css ) 12 | # Precompile additional assets. 13 | # application.js, application.css, and all non-JS/CSS in the app/assets 14 | # folder are already added. 15 | # Rails.application.config.assets.precompile += %w( admin.js admin.css ) 16 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 6 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 7 | 8 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 9 | # Rails.backtrace_cleaner.remove_silencers! 10 | -------------------------------------------------------------------------------- /config/initializers/bullet.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | unless Rails.env.production? 4 | Rails.application.configure do 5 | config.after_initialize do 6 | Bullet.enable = true 7 | Bullet.bullet_logger = true 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /config/initializers/carrierwave.rb: -------------------------------------------------------------------------------- 1 | if Rails.env.test? or Rails.env.cucumber? 2 | CarrierWave.configure do |config| 3 | config.storage = :file 4 | config.enable_processing = false 5 | end 6 | end -------------------------------------------------------------------------------- /config/initializers/cloudinary.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Cloudinary.config do |config| 4 | config.secure = true 5 | end 6 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Specify a serializer for the signed and encrypted cookie jars. 6 | # Valid options are :json, :marshal, and :hybrid. 7 | Rails.application.config.action_dispatch.cookies_serializer = :json 8 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure parameters to be filtered from the log file. Use this to limit dissemination of 4 | # sensitive information. See the ActiveSupport::ParameterFilter documentation for supported 5 | # notations and behaviors. 6 | Rails.application.config.filter_parameters += [ 7 | :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn 8 | ] 9 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, "\\1en" 8 | # inflect.singular /^(ox)en/i, "\\1" 9 | # inflect.irregular "person", "people" 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym "RESTful" 16 | # end 17 | -------------------------------------------------------------------------------- /config/initializers/kaminari_config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Kaminari.configure do |config| 4 | config.default_per_page = 6 5 | 6 | # Avaliable Configuration: 7 | # 8 | # config.max_per_page = nil 9 | # config.window = 4 10 | # config.outer_window = 0 11 | # config.left = 0 12 | # config.right = 0 13 | # config.page_method_name = :page 14 | # config.param_name = :page 15 | end 16 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Add new mime types for use in respond_to blocks: 6 | # Mime::Type.register "text/richtext", :rtf 7 | -------------------------------------------------------------------------------- /config/initializers/permissions_policy.rb: -------------------------------------------------------------------------------- 1 | # Define an application-wide HTTP permissions policy. For further 2 | # information see https://developers.google.com/web/updates/2018/06/feature-policy 3 | # 4 | # Rails.application.config.permissions_policy do |f| 5 | # f.camera :none 6 | # f.gyroscope :none 7 | # f.microphone :none 8 | # f.usb :none 9 | # f.fullscreen :self 10 | # f.payment :self, "https://secure.example.com" 11 | # end 12 | -------------------------------------------------------------------------------- /config/initializers/premailer_rails.rb: -------------------------------------------------------------------------------- 1 | Premailer::Rails.config.merge! preserve_styles: true, remove_ids: true 2 | -------------------------------------------------------------------------------- /config/initializers/pusher.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # config/initializers/pusher.rb 4 | require 'pusher' 5 | 6 | Pusher.app_id = ENV['PUSHER_APP_ID'] 7 | Pusher.key = ENV['PUSHER_KEY'] 8 | Pusher.secret = ENV['PUSHER_SECRET'] 9 | Pusher.logger = Rails.logger 10 | Pusher.encrypted = true 11 | Pusher.cluster = ENV['PUSHER_CLUSTER'] 12 | -------------------------------------------------------------------------------- /config/initializers/recaptcha.rb: -------------------------------------------------------------------------------- 1 | Recaptcha.configure do |config| 2 | config.site_key = ENV['RECAPTCHA_SITE_KEY'] 3 | config.secret_key = ENV['RECAPTCHA_SECRET_KEY'] 4 | end 5 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | Rails.application.config.session_store :cookie_store, key: '_ifme_session', same_site: :lax -------------------------------------------------------------------------------- /config/initializers/sidekiq.rb: -------------------------------------------------------------------------------- 1 | Sidekiq.configure_client do |config| 2 | config.redis = { url: "redis://#{ENV['REDIS_HOST']}:#{ENV['REDIS_PORT']}/0", namespace: 'ifme' } 3 | end 4 | 5 | Sidekiq.configure_server do |config| 6 | config.redis = { url: "redis://#{ENV['REDIS_HOST']}:#{ENV['REDIS_PORT']}/0", namespace: 'ifme' } 7 | schedule_file = "config/sidekiq_schedule.yml" 8 | 9 | if File.exist?(schedule_file) 10 | Sidekiq::Cron::Job.load_from_hash YAML.load_file(schedule_file) 11 | end 12 | end 13 | 14 | Sidekiq.default_job_options = { :backtrace => true, :unique => :all, :failures => true } 15 | -------------------------------------------------------------------------------- /config/initializers/timeout.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # There is no request timeout mechanism inside of Puma. The Heroku router will 4 | # timeout all requests that exceed 30 seconds. Although an error will be 5 | # returned back to the client, Puma will continue to work on the request as 6 | # there is no way for the router to notify Puma that the request terminated 7 | # early. To avoid clogging processing ability, Rack::Timeout terminates long 8 | # running requests. 9 | if defined?(Rack::Timeout) 10 | Rails.application.config.middleware.insert_before Rack::Runtime, Rack::Timeout, service_timeout: 30 11 | end 12 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # This file contains settings for ActionController::ParamsWrapper which 6 | # is enabled by default. 7 | 8 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 9 | ActiveSupport.on_load(:action_controller) do 10 | wrap_parameters format: [:json] 11 | end 12 | 13 | # To enable root element in JSON for ActiveRecord objects. 14 | # ActiveSupport.on_load(:active_record) do 15 | # self.include_root_in_json = true 16 | # end 17 | -------------------------------------------------------------------------------- /config/locale.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Locale 4 | def self.available_locales 5 | # The available locales to be used by the app. Unit tests are dynamically 6 | # generated from this array. If adding a new locale, be sure to refer to 7 | # any failures when running rspec spec/compare_locales_support_spec.rb 8 | # to see what keys and files are missing that need to be added. 9 | %w[en es de it nb nl pt-BR sv vi fr hi zh-CN ko id] 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rake secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | development: &default 14 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 15 | 16 | test: 17 | <<: *default 18 | # Do not keep production secrets in the repository, 19 | # instead read values from the environment. 20 | production: 21 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 22 | -------------------------------------------------------------------------------- /config/sidekiq.yml: -------------------------------------------------------------------------------- 1 | --- 2 | :concurrency: 1 3 | :logfile: log/sidekiq.log 4 | staging: 5 | :concurrency: 1 6 | production: 7 | :concurrency: 1 8 | :timeout: 10800 9 | :queues: 10 | - critical 11 | - default 12 | - low 13 | -------------------------------------------------------------------------------- /config/sidekiq_schedule.yml: -------------------------------------------------------------------------------- 1 | 2 | delete_stale_csv: 3 | cron: "0 0 0 * * * *" 4 | class: "DeleteStaleDataWorker" 5 | queue: default 6 | 7 | -------------------------------------------------------------------------------- /config/spring.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Spring.watch( 4 | ".ruby-version", 5 | ".rbenv-vars", 6 | "tmp/restart.txt", 7 | "tmp/caching-dev.txt" 8 | ) 9 | -------------------------------------------------------------------------------- /db/migrate/20151205193444_add_relations.rb: -------------------------------------------------------------------------------- 1 | class AddRelations < ActiveRecord::Migration[4.2] 2 | def change 3 | rename_table :allies, :allyships 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20151205193712_change_column_names_in_allyships.rb: -------------------------------------------------------------------------------- 1 | class ChangeColumnNamesInAllyships < ActiveRecord::Migration[4.2] 2 | def change 3 | rename_column :allyships, :userid1, :user_id 4 | rename_column :allyships, :userid2, :ally_id 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20160429191349_create_notifications.rb: -------------------------------------------------------------------------------- 1 | class CreateNotifications < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :notifications do |t| 4 | t.integer :userid 5 | t.string :uniqueid 6 | t.text :data 7 | 8 | t.timestamps null: false 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20160511231156_add_viewers_to_comments.rb: -------------------------------------------------------------------------------- 1 | class AddViewersToComments < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :comments, :viewers, :text 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20160516202831_add_notifications_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddNotificationsToUsers < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :users, :comment_notify, :boolean 4 | add_column :users, :ally_notify, :boolean 5 | add_column :users, :group_notify, :boolean 6 | add_column :users, :meeting_notify, :boolean 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /db/migrate/20160518220139_create_medication_reminders.rb: -------------------------------------------------------------------------------- 1 | class CreateMedicationReminders < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :take_medication_reminders do |t| 4 | t.integer :medication_id, null: false 5 | t.boolean :active, null:false 6 | 7 | t.timestamps 8 | end 9 | 10 | create_table :refill_reminders do |t| 11 | t.integer :medication_id, null: false 12 | t.boolean :active, null:false 13 | 14 | t.timestamps 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /db/migrate/20161127002019_create_strategy_reminders.rb: -------------------------------------------------------------------------------- 1 | class CreateStrategyReminders < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :perform_strategy_reminders do |t| 4 | t.integer :strategy_id, null: false 5 | t.boolean :active, null: false 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20161127232311_create_friendly_id_slugs.rb: -------------------------------------------------------------------------------- 1 | class CreateFriendlyIdSlugs < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :friendly_id_slugs do |t| 4 | t.string :slug, :null => false 5 | t.integer :sluggable_id, :null => false 6 | t.string :sluggable_type, :limit => 50 7 | t.string :scope 8 | t.datetime :created_at 9 | end 10 | add_index :friendly_id_slugs, :sluggable_id 11 | add_index :friendly_id_slugs, [:slug, :sluggable_type] 12 | add_index :friendly_id_slugs, [:slug, :sluggable_type, :scope], :unique => true 13 | add_index :friendly_id_slugs, :sluggable_type 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /db/migrate/20161127233144_add_slug_to_moments.rb: -------------------------------------------------------------------------------- 1 | class AddSlugToMoments < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :moments, :slug, :string 4 | add_index :moments, :slug, unique: true 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20161215060504_add_slug_to_models.rb: -------------------------------------------------------------------------------- 1 | class AddSlugToModels < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :strategies, :slug, :string 4 | add_index :strategies, :slug, unique: true 5 | add_column :moods, :slug, :string 6 | add_index :moods, :slug, unique: true 7 | add_column :categories, :slug, :string 8 | add_index :categories, :slug, unique: true 9 | add_column :groups, :slug, :string 10 | add_index :groups, :slug, unique: true 11 | add_column :meetings, :slug, :string 12 | add_index :meetings, :slug, unique: true 13 | add_column :medications, :slug, :string 14 | add_index :medications, :slug, unique: true 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /db/migrate/20170225182017_add_locale_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddLocaleToUsers < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :users, :locale, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20170724124951_add_access_expires_at_to_user.rb: -------------------------------------------------------------------------------- 1 | class AddAccessExpiresAtToUser < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :users, :access_expires_at, :datetime 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20170724160338_add_refresh_token_to_user.rb: -------------------------------------------------------------------------------- 1 | class AddRefreshTokenToUser < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :users, :refresh_token, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20170724205314_add_secret_shares_to_moments.rb: -------------------------------------------------------------------------------- 1 | class AddSecretSharesToMoments < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :moments, :secret_share_identifier, :uuid 4 | add_column :moments, :secret_share_expires_at, :datetime 5 | add_index :moments, :secret_share_identifier, unique: true 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20170806232047_change_column_names_in_moments.rb: -------------------------------------------------------------------------------- 1 | class ChangeColumnNamesInMoments < ActiveRecord::Migration[4.2] 2 | def change 3 | rename_column :moments, :strategies, :strategy 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20170825205513_change_comments_to_be_polymorphic.rb: -------------------------------------------------------------------------------- 1 | class ChangeCommentsToBePolymorphic < ActiveRecord::Migration[5.0] 2 | def change 3 | rename_column :comments, :comment_type, :commentable_type 4 | rename_column :comments, :commented_on, :commentable_id 5 | 6 | add_index :comments, [:commentable_type, :commentable_id] 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /db/migrate/20170830075513_add_add_to_google_cal_to_medication.rb: -------------------------------------------------------------------------------- 1 | class AddAddToGoogleCalToMedication < ActiveRecord::Migration[5.0] 2 | def change 3 | add_column :medications, :add_to_google_cal, :boolean, :default => false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20171006164206_add_published_at_field_to_moments.rb: -------------------------------------------------------------------------------- 1 | class AddPublishedAtFieldToMoments < ActiveRecord::Migration[5.0] 2 | def change 3 | add_column :moments, :published_at, :datetime 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20171006164223_add_published_at_field_to_strategies.rb: -------------------------------------------------------------------------------- 1 | class AddPublishedAtFieldToStrategies < ActiveRecord::Migration[5.0] 2 | def change 3 | add_column :strategies, :published_at, :datetime 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20171010054721_set_default_publication_date.rb: -------------------------------------------------------------------------------- 1 | class SetDefaultPublicationDate < ActiveRecord::Migration[5.0] 2 | def change 3 | Moment.update_all("published_at=updated_at") 4 | Strategy.update_all("published_at=updated_at") 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20180213160537_add_weekly_dosage_to_medication.rb: -------------------------------------------------------------------------------- 1 | class AddWeeklyDosageToMedication < ActiveRecord::Migration[5.0] 2 | def change 3 | add_column :medications, :weekly_dosage, :integer, array: true, default: [0,1,2,3,4,5,6] 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20180318075504_rename_userid_on_categories.rb: -------------------------------------------------------------------------------- 1 | class RenameUseridOnCategories < ActiveRecord::Migration[5.0] 2 | def change 3 | rename_column(:categories, :userid, :user_id) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20180318080518_rename_groupid_on_group_members.rb: -------------------------------------------------------------------------------- 1 | class RenameGroupidOnGroupMembers < ActiveRecord::Migration[5.0] 2 | def change 3 | rename_column(:group_members, :groupid, :group_id) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20180405152328_rename_userid_on_moods.rb: -------------------------------------------------------------------------------- 1 | class RenameUseridOnMoods < ActiveRecord::Migration[5.0] 2 | def change 3 | rename_column(:moods, :userid, :user_id) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20180405153007_rename_userid_on_strategies.rb: -------------------------------------------------------------------------------- 1 | class RenameUseridOnStrategies < ActiveRecord::Migration[5.0] 2 | def change 3 | rename_column(:strategies, :userid, :user_id) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20180406232114_rename_userid_on_moments.rb: -------------------------------------------------------------------------------- 1 | class RenameUseridOnMoments < ActiveRecord::Migration[5.0] 2 | def change 3 | rename_column(:moments, :userid, :user_id) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20180716033741_rename_userid_on_meetings.rb: -------------------------------------------------------------------------------- 1 | class RenameUseridOnMeetings < ActiveRecord::Migration[5.0] 2 | def change 3 | rename_column(:meetings, :groupid, :group_id) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20180716035719_rename_userid_on_notifications.rb: -------------------------------------------------------------------------------- 1 | class RenameUseridOnNotifications < ActiveRecord::Migration[5.0] 2 | def change 3 | rename_column(:notifications, :userid, :user_id) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20180716035735_rename_userid_on_supports.rb: -------------------------------------------------------------------------------- 1 | class RenameUseridOnSupports < ActiveRecord::Migration[5.0] 2 | def change 3 | rename_column(:supports, :userid, :user_id) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20180716035805_rename_userid_on_medications.rb: -------------------------------------------------------------------------------- 1 | class RenameUseridOnMedications < ActiveRecord::Migration[5.0] 2 | def change 3 | rename_column(:medications, :userid, :user_id) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20180716035824_rename_userid_on_meeting_members.rb: -------------------------------------------------------------------------------- 1 | class RenameUseridOnMeetingMembers < ActiveRecord::Migration[5.0] 2 | def change 3 | rename_column(:meeting_members, :userid, :user_id) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20180716040119_rename_userid_on_group_members.rb: -------------------------------------------------------------------------------- 1 | class RenameUseridOnGroupMembers < ActiveRecord::Migration[5.0] 2 | def change 3 | rename_column(:group_members, :userid, :user_id) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20180724031319_rename_meetingid_on_meeting_members.rb: -------------------------------------------------------------------------------- 1 | class RenameMeetingidOnMeetingMembers < ActiveRecord::Migration[5.0] 2 | def change 3 | rename_column(:meeting_members, :meetingid, :meeting_id) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20180908064825_add_banned_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddBannedToUsers < ActiveRecord::Migration[5.1] 2 | def change 3 | add_column :users, :banned, :boolean, :default => false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20181030143627_create_password_histories.rb: -------------------------------------------------------------------------------- 1 | class CreatePasswordHistories < ActiveRecord::Migration[5.2] 2 | def up 3 | create_table :password_histories do |t| 4 | t.integer :user_id, null: false 5 | t.string :encrypted_password 6 | t.datetime :created_at, null: false 7 | end 8 | end 9 | 10 | # rake db:migrate:down VERSION=20181030143627 11 | def down 12 | drop_table(:password_histories) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /db/migrate/20181103000528_add_reports.rb: -------------------------------------------------------------------------------- 1 | class AddReports < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :reports do |t| 4 | t.integer "reporter_id" 5 | t.integer "reportee_id" 6 | t.text "reasons" 7 | t.datetime "created_at", null: false 8 | t.datetime "updated_at", null: false 9 | t.integer "comment_id" 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20181103000904_add_admin_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddAdminToUsers < ActiveRecord::Migration[5.2] 2 | def change 3 | add_column :users, :admin, :boolean, :default => false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20181119152925_change_refill_column_type.rb: -------------------------------------------------------------------------------- 1 | class ChangeRefillColumnType < ActiveRecord::Migration[5.2] 2 | def up 3 | change_column(:medications, :refill, 'timestamptz USING CAST(refill AS timestamptz)') 4 | end 5 | 6 | def down 7 | change_column(:medications, :refill, :string) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20190316160238_add_google_cal_event_id_to_meeting_members.rb: -------------------------------------------------------------------------------- 1 | class AddGoogleCalEventIdToMeetingMembers < ActiveRecord::Migration[5.2] 2 | def up 3 | add_column(:meeting_members, :google_cal_event_id, :string) 4 | end 5 | 6 | def down 7 | remove_column(:meeting_members, :google_cal_event_id) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20190419023259_replace_empty_name_with_email.rb: -------------------------------------------------------------------------------- 1 | class ReplaceEmptyNameWithEmail < ActiveRecord::Migration[5.2] 2 | def up 3 | User.find_each do |user| 4 | if user.name.blank? && !user.email.blank? 5 | user.name = user.email 6 | user.save! 7 | end 8 | end 9 | end 10 | 11 | def down 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /db/migrate/20190821013432_add_third_party_avatar_to_user.rb: -------------------------------------------------------------------------------- 1 | class AddThirdPartyAvatarToUser < ActiveRecord::Migration[5.2] 2 | def change 3 | add_column :users, :third_party_avatar, :text 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200201005100_add_visible_to_categories.rb: -------------------------------------------------------------------------------- 1 | class AddVisibleToCategories < ActiveRecord::Migration[5.2] 2 | def change 3 | add_column :categories, :visible, :boolean, default: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200201005117_add_visible_to_strategies.rb: -------------------------------------------------------------------------------- 1 | class AddVisibleToStrategies < ActiveRecord::Migration[5.2] 2 | def change 3 | add_column :strategies, :visible, :boolean, default: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200208223501_add_visible_to_moods.rb: -------------------------------------------------------------------------------- 1 | class AddVisibleToMoods < ActiveRecord::Migration[5.2] 2 | def change 3 | add_column :moods, :visible, :boolean, default: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200219155053_create_moment_moods_join_table.rb: -------------------------------------------------------------------------------- 1 | class CreateMomentMoodsJoinTable < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :moments_moods do |t| 4 | t.integer :moment_id 5 | t.integer :mood_id 6 | end 7 | 8 | add_index :moments_moods, [:moment_id, :mood_id], unique: true 9 | add_foreign_key :moments_moods, :moments 10 | add_foreign_key :moments_moods, :moods 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20200221153634_drop_moments_mood_column.rb: -------------------------------------------------------------------------------- 1 | class DropMomentsMoodColumn < ActiveRecord::Migration[5.2] 2 | def change 3 | remove_column :moments, :mood 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200224201131_create_moment_categories_join_table.rb: -------------------------------------------------------------------------------- 1 | class CreateMomentCategoriesJoinTable < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :moments_categories do |t| 4 | t.integer :moment_id 5 | t.integer :category_id 6 | end 7 | 8 | add_index :moments_categories, [:moment_id, :category_id], unique: true 9 | add_foreign_key :moments_categories, :moments 10 | add_foreign_key :moments_categories, :categories 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20200224205313_drop_moments_category_column.rb: -------------------------------------------------------------------------------- 1 | class DropMomentsCategoryColumn < ActiveRecord::Migration[5.2] 2 | def change 3 | remove_column :moments, :category 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200224231844_create_moment_strategies_join_table.rb: -------------------------------------------------------------------------------- 1 | class CreateMomentStrategiesJoinTable < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :moments_strategies do |t| 4 | t.integer :moment_id 5 | t.integer :strategy_id 6 | end 7 | 8 | add_index :moments_strategies, [:moment_id, :strategy_id], unique: true 9 | add_foreign_key :moments_strategies, :moments 10 | add_foreign_key :moments_strategies, :strategies 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20200224234822_drop_moments_strategy_column.rb: -------------------------------------------------------------------------------- 1 | class DropMomentsStrategyColumn < ActiveRecord::Migration[5.2] 2 | def change 3 | remove_column :moments, :strategy 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200225003308_create_strategy_categories_join_table.rb: -------------------------------------------------------------------------------- 1 | class CreateStrategyCategoriesJoinTable < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :strategies_categories do |t| 4 | t.integer :strategy_id 5 | t.integer :category_id 6 | end 7 | 8 | add_index :strategies_categories, [:strategy_id, :category_id], unique: true 9 | add_foreign_key :strategies_categories, :strategies 10 | add_foreign_key :strategies_categories, :categories 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20200225010346_drop_strategies_category_column.rb: -------------------------------------------------------------------------------- 1 | class DropStrategiesCategoryColumn < ActiveRecord::Migration[5.2] 2 | def change 3 | remove_column :strategies, :category 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200419063429_add_bookmarked_to_moments.rb: -------------------------------------------------------------------------------- 1 | class AddBookmarkedToMoments < ActiveRecord::Migration[5.2] 2 | def change 3 | add_column :moments, :bookmarked, :boolean, default: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200419063936_add_bookmarked_to_strategies.rb: -------------------------------------------------------------------------------- 1 | class AddBookmarkedToStrategies < ActiveRecord::Migration[5.2] 2 | def change 3 | add_column :strategies, :bookmarked, :boolean, default: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200506222107_add_resource_recommendations_to_moments.rb: -------------------------------------------------------------------------------- 1 | class AddResourceRecommendationsToMoments < ActiveRecord::Migration[5.2] 2 | def change 3 | add_column :moments, :resource_recommendations, :boolean, default: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200509110917_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.active_storage.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from active_storage (originally 20180723000244) 2 | class AddForeignKeyConstraintToActiveStorageAttachmentsForBlobId < ActiveRecord::Migration[6.0] 3 | def up 4 | return if foreign_key_exists?(:active_storage_attachments, column: :blob_id) 5 | 6 | if table_exists?(:active_storage_blobs) 7 | add_foreign_key :active_storage_attachments, :active_storage_blobs, column: :blob_id 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20200513052758_add_user_id_index_to_password_histories.rb: -------------------------------------------------------------------------------- 1 | class AddUserIdIndexToPasswordHistories < ActiveRecord::Migration[6.0] 2 | def change 3 | add_index :password_histories, [:encrypted_password, :user_id], unique: true 4 | add_foreign_key :password_histories, :users 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20200527035711_create_care_plan_contacts.rb: -------------------------------------------------------------------------------- 1 | class CreateCarePlanContacts < ActiveRecord::Migration[6.0] 2 | def change 3 | create_table :care_plan_contacts do |t| 4 | t.string :name 5 | t.string :phone 6 | t.integer :user_id 7 | t.datetime :created_at 8 | t.datetime :updated_at 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20201009185200_drop_password_histories_table.rb: -------------------------------------------------------------------------------- 1 | class DropPasswordHistoriesTable < ActiveRecord::Migration[6.0] 2 | def up 3 | drop_table(:password_histories) 4 | end 5 | 6 | # rake db:migrate:down VERSION=20201009185200 7 | def down 8 | create_table :password_histories do |t| 9 | t.integer :user_id, null: false 10 | t.string :encrypted_password 11 | t.datetime :created_at, null: false 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /db/migrate/20201019231628_add_lockable_attributes_to_user.rb: -------------------------------------------------------------------------------- 1 | class AddLockableAttributesToUser < ActiveRecord::Migration[6.0] 2 | def change 3 | add_column :users, :failed_attempts, :integer, default: 0, null: false 4 | add_column :users, :locked_at, :datetime 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20210328174852_create_moment_templates.rb: -------------------------------------------------------------------------------- 1 | class CreateMomentTemplates < ActiveRecord::Migration[6.0] 2 | def change 3 | create_table :moment_templates do |t| 4 | t.string :name 5 | t.text :description 6 | t.string :slug 7 | t.index :slug, unique: true 8 | t.references :user, foreign_key: true, null: true 9 | 10 | t.timestamps 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /db/migrate/20211005010735_add_confirmable_to_devise.rb: -------------------------------------------------------------------------------- 1 | class AddConfirmableToDevise < ActiveRecord::Migration[6.0] 2 | def self.up 3 | add_column :users, :confirmation_token, :string 4 | add_column :users, :confirmed_at, :datetime 5 | add_column :users, :confirmation_sent_at, :datetime 6 | add_column :users, :unconfirmed_email, :string 7 | 8 | add_index :users, :confirmation_token, unique: true 9 | end 10 | 11 | def self.down 12 | remove_index :users, :confirmation_token 13 | 14 | remove_column :users, :unconfirmed_email 15 | remove_column :users, :confirmation_sent_at 16 | remove_column :users, :confirmed_at 17 | remove_column :users, :confirmation_token 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /db/migrate/20220204015546_add_session_token_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddSessionTokenToUsers < ActiveRecord::Migration[6.0] 2 | def change 3 | add_column :users, :session_token, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20221025214533_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from active_storage (originally 20211119233751) 2 | class RemoveNotNullOnActiveStorageBlobsChecksum < ActiveRecord::Migration[6.0] 3 | def change 4 | return unless table_exists?(:active_storage_blobs) 5 | 6 | change_column_null(:active_storage_blobs, :checksum, true) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /doc/erd.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/doc/erd.pdf -------------------------------------------------------------------------------- /docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | bundle check || bundle install 5 | 6 | # run only when needed 7 | # bundle exec rake assets:precompile 8 | 9 | exec "$@" 10 | -------------------------------------------------------------------------------- /git-hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e # Stop script if any command returns an error 3 | echo "Running pre-commit hook" 4 | 5 | rubocop 6 | cd client 7 | yarn lint 8 | 9 | cd ../config 10 | yamllint locales/ 11 | -------------------------------------------------------------------------------- /git-hooks/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e # Stop script if any command returns an error 3 | echo "Running pre-push hook" 4 | 5 | # Allows us to read user input below, assigns stdin to keyboard 6 | exec < /dev/tty 7 | 8 | echo "Do you wish to run pre-push tests? (This is recommended)" 9 | select option in "Yes" "No"; do 10 | if [ "$option" = "Yes" ]; then 11 | rspec 12 | cd client 13 | yarn test 14 | exit 0 15 | elif [ "$option" = "No" ]; then 16 | echo "Skipping tests..." 17 | exit 0 18 | else 19 | echo "Please select option (1) or (2)." 20 | fi 21 | done 22 | -------------------------------------------------------------------------------- /lib/access_token.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AccessToken 4 | attr_reader :token 5 | 6 | def initialize(token) 7 | @token = token 8 | end 9 | 10 | def apply!(headers) 11 | headers['Authorization'] = "Bearer #{@token}" 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/tasks/cleaner.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | namespace :cleaner do 4 | desc 'Remove lingering categories on Strategy instances' 5 | task remove_lingering_categories: :environment do 6 | Strategy.all.each do |strategy| 7 | strategy.category.each do |category_id| 8 | next if Category.find_by(id: category_id) 9 | 10 | p "Removing category #{category_id} from strategy: #{strategy.name}" 11 | strategy.category.delete(category_id) 12 | strategy.save 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/tasks/scheduler.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | namespace :scheduler do 4 | desc 'Send taking medication reminders' 5 | task send_take_medication_reminders: :environment do 6 | MedicationReminders.new.send_take_medication_reminder_emails 7 | end 8 | 9 | desc 'Send refill reminders' 10 | task send_refill_reminders: :environment do 11 | MedicationReminders.new.send_refill_reminder_emails 12 | end 13 | 14 | desc 'Send perform strategy reminders' 15 | task send_perform_strategy_reminders: :environment do 16 | StrategyReminders.new.send_perform_strategy_reminder_emails 17 | end 18 | 19 | desc 'Send meeting reminders' 20 | task send_meeting_reminders: :environment do 21 | MeetingReminders.new.send_meeting_reminder_emails 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/tasks/slugs.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | namespace :slugs do 4 | desc 'Add slugs to models' 5 | task slugify: :environment do 6 | Moment.find_each(&:save) 7 | Strategy.find_each(&:save) 8 | Mood.find_each(&:save) 9 | Category.find_each(&:save) 10 | Medication.find_each(&:save) 11 | Group.find_each(&:save) 12 | Meeting.find_each(&:save) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/url_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Helper to be included to create urls 4 | module UrlHelper 5 | extend ActiveSupport::Concern 6 | include Rails.application.routes.url_helpers 7 | 8 | included do 9 | def default_url_options 10 | ActionMailer::Base.default_url_options 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ifme", 3 | "private": true, 4 | "dependencies": {}, 5 | "scripts": { 6 | "postinstall": "cd client && yarn install", 7 | "start": "foreman start -f Procfile.dev" 8 | }, 9 | "engines": { 10 | "node": "20.17.0" 11 | }, 12 | "cacheDirectories": [ 13 | "node_modules", 14 | "client/node_modules" 15 | ], 16 | "browserslist": [ 17 | "defaults" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/public/favicon.ico -------------------------------------------------------------------------------- /public/logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/public/logo@2x.png -------------------------------------------------------------------------------- /public/logo_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/public/logo_512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "if-me.org", 3 | "short_name": "if-me.org", 4 | "start_url": "/", 5 | "theme_color": "#6d0839", 6 | "background_color": "#6d0839", 7 | "display": "fullscreen", 8 | "orientation": "portrait", 9 | "icons": [{ 10 | "src": "./logo_512.png", 11 | "sizes": "512x512", 12 | "type": "image/png" 13 | }] 14 | } -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /public/wiki_images/macos-installation-map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/public/wiki_images/macos-installation-map.png -------------------------------------------------------------------------------- /public/wiki_images/new-accounts-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/public/wiki_images/new-accounts-settings.png -------------------------------------------------------------------------------- /public/wiki_images/new-allies-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/public/wiki_images/new-allies-page.png -------------------------------------------------------------------------------- /public/wiki_images/new-categories-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/public/wiki_images/new-categories-page.png -------------------------------------------------------------------------------- /public/wiki_images/new-groups-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/public/wiki_images/new-groups-page.png -------------------------------------------------------------------------------- /public/wiki_images/new-landing-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/public/wiki_images/new-landing-page.png -------------------------------------------------------------------------------- /public/wiki_images/new-medications-form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/public/wiki_images/new-medications-form.png -------------------------------------------------------------------------------- /public/wiki_images/new-medications-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/public/wiki_images/new-medications-page.png -------------------------------------------------------------------------------- /public/wiki_images/new-moments-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/public/wiki_images/new-moments-page.png -------------------------------------------------------------------------------- /public/wiki_images/new-moods-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/public/wiki_images/new-moods-page.png -------------------------------------------------------------------------------- /public/wiki_images/new-strategies-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/public/wiki_images/new-strategies-page.png -------------------------------------------------------------------------------- /spec/compare_locales_support_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | describe CompareLocalesSupport do 3 | described_class::ENGLISH_FILES.each do |f| 4 | context "when comparing #{f}" do 5 | described_class::NON_ENGLISH_LOCALES.each do |locale| 6 | locale_file = f.sub(/en\.yml$/, "#{locale}.yml") 7 | 8 | context "with #{locale_file}" do 9 | it 'should exist' do 10 | expect(File.exist?(locale_file)).to be true 11 | end 12 | 13 | it 'should have matching keys' do 14 | expect(described_class.compare(f, locale_file)).to be_empty 15 | end 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/factories/care_plan_contacts.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: care_plan_contacts 4 | # 5 | # id :bigint not null, primary key 6 | # name :string 7 | # phone :string 8 | # user_id :integer 9 | # created_at :datetime 10 | # updated_at :datetime 11 | # 12 | 13 | FactoryBot.define do 14 | factory :care_plan_contact do 15 | name { 'Lovely Person' } 16 | phone { "416000000" } 17 | 18 | trait :with_user do 19 | user { create(:user1) } 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/factories/category.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | FactoryBot.define do 3 | factory :category do 4 | name { 'Test Category' } 5 | description { 'Test description category' } 6 | 7 | trait :with_user do 8 | user { create(:user1) } 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/factories/moment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | FactoryBot.define do 3 | factory :moment do 4 | name { 'Test Moment' } 5 | why { 'Test Why' } 6 | fix { 'Test fix' } 7 | comment { true } 8 | 9 | trait :with_secret_share do 10 | secret_share_identifier { SecureRandom.uuid } 11 | secret_share_expires_at { 1.day.from_now } 12 | end 13 | 14 | trait :with_expired_secret_share do 15 | secret_share_identifier { SecureRandom.uuid } 16 | secret_share_expires_at { 1.day.ago } 17 | end 18 | 19 | trait :with_published_at do 20 | published_at { Time.zone.now } 21 | end 22 | 23 | trait :with_user do 24 | user { create(:user1) } 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/factories/moment_templates.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: moment_templates 4 | # 5 | # id :bigint not null, primary key 6 | # name :string 7 | # description :text 8 | # slug :string 9 | # user_id :bigint 10 | # created_at :datetime not null 11 | # updated_at :datetime not null 12 | # 13 | 14 | FactoryBot.define do 15 | factory :moment_template do 16 | name { 'Test Moment Template Name' } 17 | description { 'Test Moment Template Description' } 18 | 19 | trait :with_user do 20 | user { create(:user1) } 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/factories/mood.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | FactoryBot.define do 3 | factory :mood do 4 | name { 'Test Mood' } 5 | description { 'Test Mood' } 6 | visible { true } 7 | 8 | trait :with_user do 9 | user { create(:user1) } 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/factories/notification.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :notification do 5 | data = { 6 | cutoff: false, 7 | user: 'Almond Butters', 8 | comment: 'Hello', 9 | typename: 'typename', 10 | type: 'type_comment_moment', 11 | typeid: 1, 12 | commentable_id: 1, 13 | } 14 | uniqueid { 'MyString' } 15 | data { data.to_json } 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/factories/strategy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | FactoryBot.define do 3 | factory :strategy do 4 | name { 'Test Strategy' } 5 | description { 'Test Description' } 6 | comment { true } 7 | user { create(:user1) } 8 | 9 | after(:create) do |strategy| 10 | create :perform_strategy_reminder, strategy: strategy, active: false 11 | end 12 | 13 | trait :with_daily_reminder do 14 | after(:create) do |strategy| 15 | create :perform_strategy_reminder, strategy: strategy 16 | end 17 | end 18 | 19 | trait :with_published_at do 20 | published_at { Time.zone.now } 21 | end 22 | end 23 | 24 | factory :perform_strategy_reminder do 25 | active { true } 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/factories/support.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # == Schema Information 4 | # 5 | # Table name: supports 6 | # 7 | # id :integer not null, primary key 8 | # user_id :integer 9 | # support_type :string 10 | # support_ids :text 11 | # created_at :datetime 12 | # updated_at :datetime 13 | # 14 | 15 | # Read about factories at https://github.com/thoughtbot/factory_bot 16 | 17 | FactoryBot.define do 18 | factory :support do 19 | user_id { 1 } 20 | support_type { 'MyString' } 21 | support_ids { 'MyString' } 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/features/leader_edits_groups_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | feature 'LeaderEditsGroups', type: :feature do 4 | scenario 'successfully' do 5 | user = create :user 6 | login_as user 7 | group = create :group_with_member, user_id: user.id, leader: true 8 | visit edit_group_path(group) 9 | 10 | expect(page).to have_content("Edit #{group.name}") 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/features/user_auth_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | feature 'UserAuthLogin', type: :feature, js: true do 4 | context 'when user has not confirmable yet and tries to login' do 5 | scenario 'successful logs in' do 6 | user = create :user_oauth, :unconfirmable 7 | login_as user 8 | 9 | visit moments_path 10 | 11 | expect(page.body).to_not have_content("confirm your email") 12 | expect(page).to have_current_path moments_path 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/features/user_creates_a_care_plan_contact_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | feature 'UserCreatesACarePlanContact', type: :feature, js: true do 4 | scenario 'unsuccessful' do 5 | user = create :user 6 | login_as user 7 | visit care_plan_path 8 | click_button('New Contact') 9 | find('#submit').click 10 | expect(page).to have_content 'This field cannot be empty!' 11 | end 12 | 13 | scenario 'successful' do 14 | user = create :user 15 | login_as user 16 | visit care_plan_path 17 | click_button('New Contact') 18 | find('input[aria-label="Name"]').set('Test1 Lastname') 19 | find('input[aria-label="Phone number"]').set('4160000000') 20 | find('#submit').click 21 | expect(page).to have_content 'Test1 Lastname' 22 | expect(page).to have_content '4160000000' 23 | end 24 | end -------------------------------------------------------------------------------- /spec/features/user_creates_groups_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | feature 'UserCreatesGroups', type: :feature, js: true do 4 | scenario 'successful' do 5 | user = create :user 6 | login_as user 7 | visit new_group_path 8 | find('#group_name').set('A Group') 9 | fill_in_textarea('A Group Description', '#group_description') 10 | find('#submit').click 11 | expect(page).to have_content 'A Group Description' 12 | end 13 | 14 | scenario 'unsuccessful' do 15 | user = create :user 16 | login_as user 17 | visit new_group_path 18 | find('#submit').click 19 | expect(page).to have_content 'New Group' 20 | expect(page).to have_css('.labelError') 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/features/user_deletes_a_care_plan_contact_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | feature 'UserDeletesACarePlanContact', type: :feature, js: true do 4 | scenario 'successful' do 5 | user = create :user 6 | create :care_plan_contact, user_id: user.id 7 | login_as user 8 | visit care_plan_path 9 | expect(page).to have_content('Lovely Person') 10 | 11 | within '.story' do 12 | find('a[aria-label="Delete Lovely Person"]').click 13 | end 14 | 15 | page.driver.browser.switch_to.alert.accept 16 | expect(page).to_not have_content('Lovely Person') 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/features/user_deletes_a_moment_template_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | feature 'UserDeletesAMomentTemplate', type: :feature, js: true do 4 | scenario 'successful' do 5 | user = create :user 6 | create :moment_template, user_id: user.id 7 | login_as user 8 | visit moment_templates_path 9 | expect(page).to have_content('Test Moment Template Name') 10 | 11 | within '.story' do 12 | find('a[aria-label="Delete Test Moment Template Name"]').click 13 | end 14 | 15 | page.driver.browser.switch_to.alert.accept 16 | expect(page).to_not have_content('Test Moment Template Name') 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/features/user_displays_resources_links_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | feature 'Display of recommended resource links', type: :feature, js: true do 4 | let(:user) { create :user } 5 | 6 | scenario 'User allows' do 7 | login_as user 8 | moment = create :moment, user_id: user.id, name: 'Teachers', resource_recommendations: 'true' 9 | visit moment_path(moment) 10 | expect(page).to have_content 'Insight Timer' 11 | end 12 | 13 | scenario 'User does not allow' do 14 | login_as user 15 | moment = create :moment, user_id: user.id, name: 'Teachers', resource_recommendations: 'false' 16 | visit moment_path(moment) 17 | expect(page).not_to have_content 'Insight Timer' 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/features/user_visits_groups_pages_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | feature 'UserVisitsGroupsPages', type: :feature, js: true do 4 | feature 'User vists groups page' do 5 | scenario 'successfully' do 6 | user = create :user1 7 | login_as user 8 | group = create :group_with_member, user_id: user.id 9 | ally = create :user2 10 | create :allyships_accepted, user_id: user.id, ally_id: ally.id 11 | available_group = create :group_with_member, user_id: user.id 12 | 13 | visit groups_path 14 | 15 | expect(page).to have_content group.name 16 | expect(page).to have_content available_group.name 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/helpers/access_token_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe AccessToken, type: :helper do 4 | describe '#apply!' do 5 | it 'applies the access token to bearer' do 6 | foo = AccessToken.new('testToken') 7 | bar = { 'Authorization' => '' } 8 | foo.apply!(bar) 9 | 10 | expect(bar['Authorization']).to eq('Bearer testToken') 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/helpers/calendar_helper_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe CalendarHelper do 4 | include CalendarHelper 5 | describe 'new_cal_refill_reminder_needed?' do 6 | let(:user) { FactoryBot.create(:user1) } 7 | let(:medication) { FactoryBot.create(:medication, user_id: user.id) } 8 | 9 | it 'when add_to_google_cal is true without a refill date' do 10 | medication.add_to_google_cal = true 11 | medication.refill = nil 12 | expect(new_cal_refill_reminder_needed?(medication)).to eq(false) 13 | end 14 | 15 | it 'when add_to_google_cal is true with a refill date' do 16 | medication.add_to_google_cal = true 17 | medication.refill = Time.zone.now 18 | expect(new_cal_refill_reminder_needed?(medication)).to eq(true) 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/helpers/groups/memberships_helper_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Specs in this file have access to a helper object that includes 4 | # the Groups::MembershipsHelper. For example: 5 | # 6 | # describe Groups::MembershipsHelper do 7 | # describe "string concat" do 8 | # it "concats two strings with spaces" do 9 | # expect(helper.concat_strings("this","that")).to eq("this that") 10 | # end 11 | # end 12 | # end 13 | # describe Groups::MembershipsHelper, type: :helper do 14 | # pending "add some examples to (or delete) #{__FILE__}" 15 | # end 16 | -------------------------------------------------------------------------------- /spec/helpers/medications_helper_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe MedicationsHelper do 4 | let(:current_user) { create(:user) } 5 | let(:medication) { create(:medication, user: current_user) } 6 | 7 | before do 8 | @medication = medication 9 | end 10 | 11 | describe '#present_medication' do 12 | subject { present_medication(medication) } 13 | it 'returns correct data' do 14 | expect(subject.keys).to include(:name, :link, :actions, :medicationBody) 15 | expect(subject[:link]).to eq(medication_path(medication)) 16 | expect(subject[:name]).to eq(medication[:name]) 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/helpers/most_focus_helper_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative './shared_examples' 3 | 4 | describe MostFocusHelper, type: :controller do 5 | controller(ApplicationController) do 6 | end 7 | 8 | describe '#most_focus' do 9 | it_behaves_like :most_focus, :category 10 | it_behaves_like :most_focus, :mood 11 | it_behaves_like :most_focus, :strategy 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/helpers/visible_helper_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe VisibleHelper do 4 | describe 'get_visible' do 5 | context 'when visible is true' do 6 | it 'returns text' do 7 | expect(get_visible(true)).to eq(t('shared.stats.visible_in_stats')) 8 | end 9 | end 10 | 11 | context 'when visible is false' do 12 | it 'returns text' do 13 | expect(get_visible(false)).to eq(t('shared.stats.not_visible_in_stats')) 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/mailers/application_mailer_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | describe 'ApplicationMailer' do 3 | describe '#load_logo_inline' do 4 | let(:user1) { create(:user1) } 5 | let(:user2) { create(:user2) } 6 | 7 | subject(:email) { ReportMailer.reported_email(user1, user2) } 8 | 9 | it 'adds logo to email attachments' do 10 | email = ReportMailer.reported_email(user1, user2) 11 | email.deliver 12 | expect(email.attachments[0].filename).to eq('logo@2x.png') 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/mailers/previews/notification_mailer_preview.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Preview all emails at http://localhost:3000/rails/mailers/notification_mailer 4 | class NotificationMailerPreview < ActionMailer::Preview 5 | end 6 | -------------------------------------------------------------------------------- /spec/models/perform_strategy_reminder_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # == Schema Information 4 | # 5 | # Table name: perform_strategy_reminders 6 | # 7 | # id :integer not null, primary key 8 | # strategy_id :integer not null 9 | # active :boolean not null 10 | # created_at :datetime 11 | # updated_at :datetime 12 | # 13 | describe PerformStrategyReminder do 14 | context 'with relations' do 15 | it { is_expected.to belong_to :strategy } 16 | end 17 | 18 | context '#name' do 19 | let(:strategy_name) { I18n.t('common.daily_reminder') } 20 | let(:perform_strategy_reminder) { PerformStrategyReminder.new } 21 | 22 | it { expect(perform_strategy_reminder.name).to eq strategy_name } 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/models/refill_reminder_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # == Schema Information 4 | # 5 | # Table name: refill_reminders 6 | # 7 | # id :integer not null, primary key 8 | # medication_id :integer not null 9 | # active :boolean not null 10 | # created_at :datetime 11 | # updated_at :datetime 12 | # 13 | describe RefillReminder do 14 | context 'with relations' do 15 | it { is_expected.to belong_to :medication } 16 | end 17 | 18 | context '#name' do 19 | let(:reminder_name) { I18n.t('medications.refill_reminder') } 20 | let(:refill_reminder) { RefillReminder.new } 21 | 22 | it { expect(refill_reminder.name).to eq reminder_name } 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/requests/errors_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe 'Error', type: :request do 4 | describe 'GET #not_found' do 5 | it 'returns http 404' do 6 | get errors_not_found_path 7 | expect(response).to have_http_status(404) 8 | end 9 | end 10 | 11 | describe 'GET #internal_server_error' do 12 | it 'returns http 500' do 13 | get errors_internal_server_error_path 14 | expect(response).to have_http_status(500) 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/routing/locale_routing_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe 'locale routing', type: :routing do 4 | Rails.configuration.i18n.available_locales.each do |locale| 5 | it 'should route all of our locale shortcuts' do 6 | expect(get: locale.to_s).to be_routable 7 | expect(get: locale.to_s).to route_to( 8 | controller: 'locales', 9 | action: 'set_initial_locale', 10 | locale: locale.to_s 11 | ) 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/services/medium_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe Medium do 4 | subject { Medium.new.posts } 5 | 6 | it 'retrieve Medium posts from API' do 7 | expect(subject.first['title']).to be_truthy 8 | expect(subject.first['link']).to be_truthy 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/support/hidden_header_support.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module HiddenHeaderSupport 4 | def open_header_if_hidden 5 | hamburger_id = '#headerHamburger' 6 | find(hamburger_id).click if has_css?(hamburger_id) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/support/page_transition_support.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module PageTransitionSupport 4 | def change_language(language_code) 5 | within 'select[name=locale]' do 6 | find("option[value=#{language_code}]").click 7 | expect( 8 | find("option[value=#{language_code}]", wait: 10) 9 | ).to be_visible 10 | end 11 | end 12 | end 13 | 14 | RSpec.configure do |config| 15 | config.include PageTransitionSupport, type: :feature 16 | end 17 | -------------------------------------------------------------------------------- /spec/support/shared_contexts/logged_in_user.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | shared_context :logged_in_user do 4 | before do 5 | stub_current_user_with(user) 6 | allow(controller).to receive(:user_signed_in?).and_return(true) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/support/shared_examples/with_no_logged_in_user.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | shared_examples_for :with_no_logged_in_user do 4 | it 'redirects to sign_in page' do 5 | expect(response).to redirect_to new_user_session_path 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/support/stub_current_user.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module StubCurrentUserHelper 4 | def stub_current_user_with(user) 5 | allow(request.env['warden']).to receive(:authenticate!).and_return(user) 6 | allow(controller).to receive(:current_user).and_return(user) 7 | end 8 | 9 | def stub_current_user 10 | build_stubbed(:user1).tap do |user| 11 | stub_current_user_with(user) 12 | end 13 | end 14 | 15 | def create_current_user 16 | create(:user1).tap do |user| 17 | stub_current_user_with(user) 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/support/textarea_support.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module TextareaSupport 4 | def fill_in_textarea(text, locator) 5 | within(locator) do 6 | find('.editorContent', visible: false).send_keys('') 7 | find('.editorContent', visible: false).send_keys(text) 8 | end 9 | end 10 | end 11 | 12 | RSpec.configure do |config| 13 | config.include TextareaSupport, type: :feature 14 | end 15 | -------------------------------------------------------------------------------- /spec/uploads/moment.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifmeorg/ifme/28e4fe5001cdabcc46972a6c96a6c7fbf461fe0f/spec/uploads/moment.jpeg -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | --------------------------------------------------------------------------------