├── .nvmrc ├── packages ├── ui │ ├── src │ │ ├── styles │ │ │ └── globals.css │ │ ├── index.ts │ │ ├── hooks │ │ │ ├── use-platform.ts │ │ │ └── use-mobile.ts │ │ ├── lib │ │ │ └── utils.ts │ │ ├── checkbox-check.svg │ │ ├── skeleton.tsx │ │ └── collapsible.tsx │ ├── tailwind.config.js │ ├── custom.d.ts │ ├── tsconfig.json │ └── components.json ├── database │ ├── .gitignore │ ├── prisma │ │ ├── migrations │ │ │ ├── 20240309043111_remove_poll_vote_index │ │ │ │ └── migration.sql │ │ │ ├── 20240224011353_remove_legacy_user_preferences │ │ │ │ └── migration.sql │ │ │ ├── 20240127064213_add_image_field │ │ │ │ └── migration.sql │ │ │ ├── 20240901171230_participant_locale │ │ │ │ └── migration.sql │ │ │ ├── 20250804130713_add_space_image_column │ │ │ │ └── migration.sql │ │ │ ├── 20250804152225_allow_user_multiple_subscriptions │ │ │ │ └── migration.sql │ │ │ ├── 20230303142641_add_participant_email │ │ │ │ └── migration.sql │ │ │ ├── 20250711140850_add_time_zone_column │ │ │ │ └── migration.sql │ │ │ ├── 20251021160519_add_last_login_method │ │ │ │ └── migration.sql │ │ │ ├── 20240315104329_index_votes_by_poll │ │ │ │ └── migration.sql │ │ │ ├── 20251107120000_allow_multiple_space_subscriptions │ │ │ │ └── migration.sql │ │ │ ├── 20231118134458_add_account_user_index │ │ │ │ └── migration.sql │ │ │ ├── 20240309051319_add_option_vote_index │ │ │ │ └── migration.sql │ │ │ ├── 20251027170830_anonymous_user │ │ │ │ └── migration.sql │ │ │ ├── 20230118192253_bring_back_comment_index │ │ │ │ └── migration.sql │ │ │ ├── 20220330085423_add_notifications_column │ │ │ │ └── migration.sql │ │ │ ├── 20230721163042_hide_participants │ │ │ │ └── migration.sql │ │ │ ├── 20231027074632_nextauth_ci_identifiers │ │ │ │ └── migration.sql │ │ │ ├── migration_lock.toml │ │ │ ├── 20220520115326_add_touch_column │ │ │ │ └── migration.sql │ │ │ ├── 20230915170216_add_required_email │ │ │ │ └── migration.sql │ │ │ ├── 20250217082042_add_subscription_fields │ │ │ │ └── migration.sql │ │ │ ├── 20250221104854_add_cancel_at_period_end │ │ │ │ └── migration.sql │ │ │ ├── 20240221084400_unset_invalid_timezones │ │ │ │ └── migration.sql │ │ │ ├── 20250729143210_add_space_member_last_selected_at │ │ │ │ └── migration.sql │ │ │ ├── 20220408120721_legacy_column │ │ │ │ └── migration.sql │ │ │ ├── 20220522165453_add_deleted_at_column │ │ │ │ └── migration.sql │ │ │ ├── 20240704085250_soft_delete_participants │ │ │ │ └── migration.sql │ │ │ ├── 20250724140012_add_subscription_columns │ │ │ │ └── migration.sql │ │ │ ├── 20250522093338_add_user_role │ │ │ │ └── migration.sql │ │ │ ├── 20220511113020_add_if_need_be │ │ │ │ └── migration.sql │ │ │ ├── 20250302163530_add_user_ban_fields │ │ │ │ └── migration.sql │ │ │ ├── 20230318113511_remove_unecessary_unique_indexes │ │ │ │ └── migration.sql │ │ │ ├── 20250523161200_remove_invitee_constraints │ │ │ │ └── migration.sql │ │ │ ├── 20221026220835_add_indexes │ │ │ │ └── migration.sql │ │ │ ├── 20250402100733_remove_closed_column │ │ │ │ └── migration.sql │ │ │ ├── 20230327105647_remove_author_name │ │ │ │ └── migration.sql │ │ │ ├── 20240317095541_remove_legacy_start_column │ │ │ │ └── migration.sql │ │ │ ├── 20230615163229_remove_selected_option_id │ │ │ │ └── migration.sql │ │ │ ├── 20220412115744_cascade_delete_votes │ │ │ │ └── migration.sql │ │ │ ├── 20230615111329_events │ │ │ │ └── migration.sql │ │ │ ├── 20250526175615_remove_stripe_customer_id │ │ │ │ └── migration.sql │ │ │ ├── 20230725112615_add_poll_config │ │ │ │ └── migration.sql │ │ │ ├── 20220412115407_cascade_delete_participants │ │ │ │ └── migration.sql │ │ │ ├── 20250715153525_update_space_member_indexes │ │ │ │ └── migration.sql │ │ │ ├── 20231205080854_fix_finalized_poll_status │ │ │ │ └── migration.sql │ │ │ ├── 20250730115902_subscription_item_id_non_nullable │ │ │ │ └── migration.sql │ │ │ ├── 20230118200546_add_unqiue_participant │ │ │ │ └── migration.sql │ │ │ ├── 20250729144904_drop_user_active_space_id │ │ │ │ └── migration.sql │ │ │ ├── 20251021132911_add_better_auth_admin_fields │ │ │ │ └── migration.sql │ │ │ ├── 20250614062854_remove_deprecated_event_model │ │ │ │ └── migration.sql │ │ │ ├── 20251110113931_non_nullable_space_id_susbcription │ │ │ │ └── migration.sql │ │ │ ├── 20251107131500_add_space_tier │ │ │ │ └── migration.sql │ │ │ ├── 20220506105524_sessions_update │ │ │ │ └── migration.sql │ │ │ ├── 20251202151504_better_auth_joins │ │ │ │ └── migration.sql │ │ │ ├── 20250710102819_add_active_space_to_user │ │ │ │ └── migration.sql │ │ │ ├── 20250602144531_add_instance_settings │ │ │ │ └── migration.sql │ │ │ ├── 20230117103853_update_indexes │ │ │ │ └── migration.sql │ │ │ ├── 20230704153346_user_payments │ │ │ │ └── migration.sql │ │ │ ├── 20250616140359_space_members_migration │ │ │ │ └── migration.sql │ │ │ ├── 20250222172325_add_payment_method_table │ │ │ │ └── migration.sql │ │ │ ├── 20250901121851_add_ics_columns │ │ │ │ └── migration.sql │ │ │ ├── 20250614110551_create_spaces │ │ │ │ └── migration.sql │ │ │ ├── 20220512093441_add_no_votes │ │ │ │ └── migration.sql │ │ │ ├── 20250226173250_remove_paddle_subscriptions │ │ │ │ └── migration.sql │ │ │ ├── 20250602181052_add_instance_settings_constraints │ │ │ │ └── migration.sql │ │ │ ├── 20231117153753_add_nextauth_provider_accounts │ │ │ │ └── migration.sql │ │ │ ├── 20250731100421_update_subscription_interval_enum │ │ │ │ └── migration.sql │ │ │ ├── 20220627191901_remove_user_relation │ │ │ │ └── migration.sql │ │ │ └── 20250614115818_migrate_events_to_spaces │ │ │ │ └── migration.sql │ │ ├── schema.prisma │ │ ├── seed │ │ │ └── utils.ts │ │ ├── models │ │ │ └── instance-settings.prisma │ │ └── seed.ts │ ├── tsconfig.json │ ├── index.ts │ └── package.json ├── emails │ ├── .gitignore │ ├── locales │ │ ├── th │ │ │ └── emails.json │ │ └── vi │ │ │ └── emails.json │ ├── src │ │ ├── index.ts │ │ ├── previews │ │ │ ├── static │ │ │ │ └── logo.png │ │ │ ├── register.tsx │ │ │ ├── license-key.tsx │ │ │ ├── new-poll.tsx │ │ │ ├── new-participant-confirmation.tsx │ │ │ ├── space-invite.tsx │ │ │ ├── abandoned-checkout.tsx │ │ │ ├── new-participant.tsx │ │ │ ├── change-email-request.tsx │ │ │ ├── event-canceled.tsx │ │ │ ├── new-comment.tsx │ │ │ ├── finalized-participant.tsx │ │ │ └── finalized-host.tsx │ │ ├── components │ │ │ └── email-context.tsx │ │ └── types.ts │ ├── tsconfig.json │ └── i18next.d.ts ├── tailwind-config │ ├── tailwind.config.d.ts │ └── package.json ├── posthog │ ├── src │ │ ├── server.ts │ │ ├── constants.ts │ │ └── utils.ts │ ├── tsconfig.json │ └── package.json ├── dayjs │ └── package.json ├── utils │ ├── src │ │ ├── sleep.ts │ │ └── nanoid.ts │ ├── tsconfig.json │ ├── vitest.config.mts │ └── package.json ├── languages │ ├── tsconfig.json │ ├── src │ │ ├── index.ts │ │ └── languages.ts │ └── package.json ├── billing │ ├── tsconfig.json │ └── src │ │ ├── index.ts │ │ ├── pricing.ts │ │ └── lib │ │ └── get-pricing.ts └── tsconfig │ ├── next.json │ ├── package.json │ ├── node.json │ └── react.json ├── apps ├── web │ ├── public │ │ ├── locales │ │ │ ├── nb │ │ │ │ └── app.json │ │ │ └── th │ │ │ │ └── app.json │ │ ├── logo.png │ │ ├── favicon.ico │ │ ├── robots.txt │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon-96x96.png │ │ ├── mstile-70x70.png │ │ ├── og-image-1200.png │ │ ├── favicon-128x128.png │ │ ├── favicon-196x196.png │ │ ├── mstile-144x144.png │ │ ├── mstile-150x150.png │ │ ├── mstile-310x150.png │ │ ├── mstile-310x310.png │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon-57x57.png │ │ ├── apple-touch-icon-60x60.png │ │ ├── apple-touch-icon-72x72.png │ │ ├── apple-touch-icon-76x76.png │ │ ├── images │ │ │ └── rallly-logo-mark.png │ │ ├── static │ │ │ ├── fonts │ │ │ │ ├── inter-bold.ttf │ │ │ │ └── inter-regular.ttf │ │ │ ├── microsoft.svg │ │ │ ├── yahoo.svg │ │ │ └── google.svg │ │ ├── apple-touch-icon-114x114.png │ │ ├── apple-touch-icon-120x120.png │ │ ├── apple-touch-icon-144x144.png │ │ ├── apple-touch-icon-152x152.png │ │ ├── apple-touch-icon-167x167.png │ │ ├── apple-touch-icon-180x180.png │ │ └── manifest.json │ ├── src │ │ ├── components │ │ │ ├── poll │ │ │ │ ├── guest-alert.tsx │ │ │ │ ├── types.ts │ │ │ │ ├── you-avatar.tsx │ │ │ │ ├── responsive-results.tsx │ │ │ │ └── poll-footer.tsx │ │ │ ├── steps │ │ │ │ ├── index.ts │ │ │ │ └── step.tsx │ │ │ ├── image-upload │ │ │ │ ├── index.ts │ │ │ │ └── types.ts │ │ │ ├── modal │ │ │ │ └── index.ts │ │ │ ├── discussion │ │ │ │ └── index.ts │ │ │ ├── forms │ │ │ │ ├── poll-options-form │ │ │ │ │ ├── month-calendar │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── utils.ts │ │ │ │ │ └── types.ts │ │ │ │ ├── index.ts │ │ │ │ └── types.ts │ │ │ ├── relative-date.tsx │ │ │ ├── container.tsx │ │ │ ├── random-gradient-bar.tsx │ │ │ ├── pro-badge.tsx │ │ │ ├── spinner.tsx │ │ │ ├── trans.tsx │ │ │ ├── use-required-context.ts │ │ │ ├── logo.tsx │ │ │ ├── maintenance.tsx │ │ │ ├── login-link.tsx │ │ │ ├── skeleton.tsx │ │ │ └── stacked-list.tsx │ │ ├── app │ │ │ ├── [locale] │ │ │ │ ├── types.ts │ │ │ │ ├── (optional-space) │ │ │ │ │ ├── new │ │ │ │ │ │ ├── loading.tsx │ │ │ │ │ │ └── back-button.tsx │ │ │ │ │ └── poll │ │ │ │ │ │ └── [urlId] │ │ │ │ │ │ ├── page.tsx │ │ │ │ │ │ └── loading.tsx │ │ │ │ ├── [...notFound] │ │ │ │ │ └── page.tsx │ │ │ │ ├── admin │ │ │ │ │ └── [adminUrlId] │ │ │ │ │ │ └── types.ts │ │ │ │ ├── p │ │ │ │ │ └── [participantUrlId] │ │ │ │ │ │ └── types.ts │ │ │ │ ├── (space) │ │ │ │ │ ├── settings │ │ │ │ │ │ ├── loading.tsx │ │ │ │ │ │ ├── members │ │ │ │ │ │ │ └── schema.ts │ │ │ │ │ │ └── preferences │ │ │ │ │ │ │ └── actions.ts │ │ │ │ │ └── (dashboard) │ │ │ │ │ │ ├── events │ │ │ │ │ │ └── types.ts │ │ │ │ │ │ ├── loading.tsx │ │ │ │ │ │ └── components │ │ │ │ │ │ └── space-sidebar-provider.tsx │ │ │ │ ├── invite │ │ │ │ │ └── [urlId] │ │ │ │ │ │ ├── loading.tsx │ │ │ │ │ │ └── providers.tsx │ │ │ │ ├── (auth) │ │ │ │ │ ├── login │ │ │ │ │ │ ├── loading.tsx │ │ │ │ │ │ └── components │ │ │ │ │ │ │ └── or-divider.tsx │ │ │ │ │ └── register │ │ │ │ │ │ ├── loading.tsx │ │ │ │ │ │ └── actions.ts │ │ │ │ ├── control-panel │ │ │ │ │ ├── loading.tsx │ │ │ │ │ ├── users │ │ │ │ │ │ └── user-search-input.tsx │ │ │ │ │ ├── nav-item.tsx │ │ │ │ │ └── control-panel-sidebar-provider.tsx │ │ │ │ └── admin-setup │ │ │ │ │ └── make-me-admin-button.tsx │ │ │ ├── api │ │ │ │ ├── stripe │ │ │ │ │ ├── webhook │ │ │ │ │ │ └── handlers │ │ │ │ │ │ │ ├── checkout │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ │ ├── customer │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ └── deleted.ts │ │ │ │ │ │ │ ├── payment-method │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ ├── detached.ts │ │ │ │ │ │ │ └── updated.ts │ │ │ │ │ │ │ └── customer-subscription │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── portal │ │ │ │ │ │ ├── route.ts │ │ │ │ │ │ └── payment-methods │ │ │ │ │ │ └── route.ts │ │ │ │ ├── auth │ │ │ │ │ ├── [...nextauth] │ │ │ │ │ │ └── route.ts │ │ │ │ │ └── invalid-session │ │ │ │ │ │ └── route.ts │ │ │ │ ├── better-auth │ │ │ │ │ └── [...all] │ │ │ │ │ │ └── route.ts │ │ │ │ └── status │ │ │ │ │ └── route.ts │ │ │ └── components │ │ │ │ ├── logo-link.tsx │ │ │ │ └── full-logo-link.tsx │ │ ├── features │ │ │ ├── navigation │ │ │ │ └── command-menu │ │ │ │ │ └── index.ts │ │ │ ├── instance-settings │ │ │ │ ├── constants.ts │ │ │ │ ├── schema.ts │ │ │ │ ├── client.tsx │ │ │ │ └── queries.ts │ │ │ ├── password │ │ │ │ ├── types.ts │ │ │ │ └── schema.ts │ │ │ ├── calendars │ │ │ │ ├── constants.ts │ │ │ │ ├── services │ │ │ │ │ └── types.ts │ │ │ │ ├── service.ts │ │ │ │ └── components │ │ │ │ │ └── calendar-provider-icon.tsx │ │ │ ├── feedback │ │ │ │ ├── constants.ts │ │ │ │ └── schema.ts │ │ │ ├── space │ │ │ │ ├── member │ │ │ │ │ ├── schema.ts │ │ │ │ │ └── types.ts │ │ │ │ ├── types.ts │ │ │ │ ├── schema.ts │ │ │ │ ├── components │ │ │ │ │ └── space-role.tsx │ │ │ │ └── mutations.ts │ │ │ ├── poll │ │ │ │ ├── schema.ts │ │ │ │ └── query.ts │ │ │ ├── quick-create │ │ │ │ ├── constants.ts │ │ │ │ ├── index.ts │ │ │ │ ├── components │ │ │ │ │ └── relative-date.tsx │ │ │ │ └── quick-create-button.tsx │ │ │ ├── billing │ │ │ │ ├── constants.ts │ │ │ │ └── schema.ts │ │ │ ├── scheduled-event │ │ │ │ └── schema.ts │ │ │ ├── licensing │ │ │ │ ├── server.ts │ │ │ │ ├── helpers │ │ │ │ │ ├── check-license-key.ts │ │ │ │ │ ├── calculate-checksum.ts │ │ │ │ │ └── generate-license-key.ts │ │ │ │ └── actions.ts │ │ │ ├── setup │ │ │ │ └── utils.ts │ │ │ ├── analytics │ │ │ │ └── posthog.ts │ │ │ ├── subscription │ │ │ │ └── schema.ts │ │ │ ├── credentials │ │ │ │ └── schema.ts │ │ │ └── user │ │ │ │ ├── schema.ts │ │ │ │ └── queries.ts │ │ ├── lib │ │ │ ├── locale │ │ │ │ └── constants.ts │ │ │ ├── storage │ │ │ │ ├── index.ts │ │ │ │ ├── constants.ts │ │ │ │ └── s3.ts │ │ │ ├── timezone │ │ │ │ └── index.ts │ │ │ ├── rate-limit │ │ │ │ └── constants.ts │ │ │ ├── kv.ts │ │ │ ├── feature-flags │ │ │ │ ├── server.ts │ │ │ │ └── types.ts │ │ │ ├── oauth │ │ │ │ └── client.tsx │ │ │ └── errors.ts │ │ ├── i18n │ │ │ ├── types.ts │ │ │ ├── server │ │ │ │ └── get-locale.ts │ │ │ ├── server.ts │ │ │ └── settings.ts │ │ ├── utils │ │ │ ├── constants.ts │ │ │ ├── selectors.ts │ │ │ ├── is-initial-admin.ts │ │ │ ├── permissions.ts │ │ │ ├── cookies.ts │ │ │ ├── next.ts │ │ │ ├── session │ │ │ │ └── session-config.ts │ │ │ ├── get-value-by-path.ts │ │ │ ├── get-registration-enabled.ts │ │ │ ├── grouped-time-zone.ts │ │ │ ├── try-catch.ts │ │ │ └── api-auth.ts │ │ ├── contexts │ │ │ ├── role.tsx │ │ │ ├── environment.tsx │ │ │ └── poll.tsx │ │ ├── trpc │ │ │ ├── context.ts │ │ │ ├── client.ts │ │ │ ├── routers │ │ │ │ ├── auth.ts │ │ │ │ └── index.ts │ │ │ ├── types.ts │ │ │ └── client │ │ │ │ └── types.ts │ │ ├── test │ │ │ └── setup.ts │ │ ├── auth │ │ │ └── providers │ │ │ │ └── guest.ts │ │ ├── instrumentation.ts │ │ └── types.ts │ ├── tailwind.config.js │ ├── postcss.config.js │ ├── i18n.config.js │ ├── .gitignore │ ├── next-env.d.ts │ ├── tests │ │ ├── edit-options-page.ts │ │ ├── i18n.spec.ts │ │ └── invite-page.ts │ ├── declarations │ │ └── i18next.d.ts │ ├── .env.test │ ├── vercel.json │ ├── tsconfig.json │ ├── vitest.config.mts │ └── i18next-scanner.config.js ├── landing │ ├── public │ │ ├── locales │ │ │ ├── fa │ │ │ │ ├── blog.json │ │ │ │ ├── pricing.json │ │ │ │ ├── home.json │ │ │ │ └── common.json │ │ │ ├── nb │ │ │ │ ├── blog.json │ │ │ │ ├── home.json │ │ │ │ ├── common.json │ │ │ │ └── pricing.json │ │ │ ├── nn │ │ │ │ ├── blog.json │ │ │ │ ├── home.json │ │ │ │ ├── common.json │ │ │ │ └── pricing.json │ │ │ ├── th │ │ │ │ ├── home.json │ │ │ │ ├── pricing.json │ │ │ │ └── blog.json │ │ │ ├── vi │ │ │ │ ├── blog.json │ │ │ │ ├── pricing.json │ │ │ │ ├── home.json │ │ │ │ └── common.json │ │ │ ├── zh │ │ │ │ └── blog.json │ │ │ ├── ja │ │ │ │ └── blog.json │ │ │ ├── ko │ │ │ │ └── blog.json │ │ │ ├── zh-Hant │ │ │ │ └── blog.json │ │ │ ├── hr │ │ │ │ └── blog.json │ │ │ ├── en │ │ │ │ └── blog.json │ │ │ ├── it │ │ │ │ └── blog.json │ │ │ ├── ru │ │ │ │ └── blog.json │ │ │ ├── cs │ │ │ │ └── blog.json │ │ │ ├── da │ │ │ │ └── blog.json │ │ │ ├── de │ │ │ │ └── blog.json │ │ │ ├── sk │ │ │ │ └── blog.json │ │ │ ├── nl │ │ │ │ └── blog.json │ │ │ ├── no │ │ │ │ └── blog.json │ │ │ ├── sv │ │ │ │ └── blog.json │ │ │ ├── ca │ │ │ │ └── blog.json │ │ │ ├── es │ │ │ │ └── blog.json │ │ │ ├── eu │ │ │ │ └── blog.json │ │ │ ├── fi │ │ │ │ └── blog.json │ │ │ ├── fr │ │ │ │ └── blog.json │ │ │ ├── hu │ │ │ │ └── blog.json │ │ │ ├── pl │ │ │ │ └── blog.json │ │ │ ├── pt-BR │ │ │ │ └── blog.json │ │ │ ├── pt │ │ │ │ └── blog.json │ │ │ └── tr │ │ │ │ └── blog.json │ │ ├── logo.png │ │ ├── favicon.ico │ │ ├── mstile-70x70.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon-96x96.png │ │ ├── mstile-144x144.png │ │ ├── mstile-150x150.png │ │ ├── mstile-310x150.png │ │ ├── mstile-310x310.png │ │ ├── favicon-128x128.png │ │ ├── favicon-196x196.png │ │ ├── static │ │ │ ├── images │ │ │ │ ├── eric.png │ │ │ │ ├── hero.png │ │ │ │ ├── google.png │ │ │ │ ├── shortcut.gif │ │ │ │ ├── splash.png │ │ │ │ ├── animations.gif │ │ │ │ ├── hero-shot.png │ │ │ │ ├── luke-vella.jpg │ │ │ │ ├── share-demo.gif │ │ │ │ ├── device-data.png │ │ │ │ ├── grouped-times.png │ │ │ │ ├── mobile-demo.gif │ │ │ │ ├── twitter-card.png │ │ │ │ ├── timeslots-demo.gif │ │ │ │ ├── touchable-area.png │ │ │ │ ├── rallly-pro-launch │ │ │ │ │ ├── email.png │ │ │ │ │ ├── billing-page.png │ │ │ │ │ └── finalize-page.png │ │ │ │ ├── introducing-rallly-3-0 │ │ │ │ │ ├── navigation.png │ │ │ │ │ ├── polls-page.png │ │ │ │ │ ├── poll-status.png │ │ │ │ │ └── updated-scoring.png │ │ │ │ ├── introducing-rallly-pro │ │ │ │ │ ├── finalize-poll.gif │ │ │ │ │ ├── rallly-pro-splash.png │ │ │ │ │ └── finalize-poll-demo.gif │ │ │ │ └── pcmag-logo.svg │ │ │ └── fonts │ │ │ │ ├── inter-bold.ttf │ │ │ │ └── inter-regular.ttf │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon-57x57.png │ │ ├── apple-touch-icon-60x60.png │ │ ├── apple-touch-icon-72x72.png │ │ ├── apple-touch-icon-76x76.png │ │ ├── apple-touch-icon-114x114.png │ │ ├── apple-touch-icon-120x120.png │ │ ├── apple-touch-icon-144x144.png │ │ ├── apple-touch-icon-152x152.png │ │ ├── apple-touch-icon-167x167.png │ │ ├── apple-touch-icon-180x180.png │ │ ├── robots.txt │ │ └── .well-known │ │ │ └── microsoft-identity-association.json │ ├── .gitignore │ ├── src │ │ ├── i18n │ │ │ ├── client │ │ │ │ ├── trans.tsx │ │ │ │ ├── use-translation.ts │ │ │ │ └── link.tsx │ │ │ ├── server.ts │ │ │ └── settings.ts │ │ ├── app │ │ │ └── [locale] │ │ │ │ ├── opengraph-image.png │ │ │ │ ├── (main) │ │ │ │ ├── [...notFound] │ │ │ │ │ └── page.tsx │ │ │ │ ├── blog │ │ │ │ │ └── layout.tsx │ │ │ │ └── nav-link.tsx │ │ │ │ └── not-found.tsx │ │ ├── fonts │ │ │ ├── sans.ts │ │ │ └── handwritten.ts │ │ ├── lib │ │ │ ├── linkToApp.ts │ │ │ └── data.ts │ │ ├── middleware.ts │ │ ├── assets │ │ │ ├── twitter.svg │ │ │ ├── linkedin.svg │ │ │ └── github.svg │ │ ├── components │ │ │ ├── blog │ │ │ │ ├── date-formatter.tsx │ │ │ │ ├── post-body.tsx │ │ │ │ └── post-header.tsx │ │ │ ├── login-button.tsx │ │ │ └── sign-up-button.tsx │ │ └── types.ts │ ├── postcss.config.js │ ├── tailwind.config.js │ ├── next-env.d.ts │ ├── vercel.json │ ├── tsconfig.json │ └── declarations │ │ └── i18next.d.ts └── docs │ ├── favicon.png │ ├── images │ ├── home.jpeg │ ├── unused │ │ ├── support.png │ │ ├── polls-page.png │ │ └── response │ │ │ ├── editing-response.png │ │ │ ├── editing-response-1.png │ │ │ ├── editing-response-2.png │ │ │ ├── submitting-response-1.png │ │ │ └── submitting-response-2.png │ ├── workflow │ │ ├── review.png │ │ ├── voting.png │ │ ├── finalize.png │ │ ├── finalized.png │ │ ├── invite-link.png │ │ ├── month-view.png │ │ ├── week-view.png │ │ └── new-poll-page.png │ ├── guide │ │ ├── voting-submitted.png │ │ ├── voting-in-progress.png │ │ └── voting-submitting.png │ ├── contribute │ │ ├── crowdin-project.png │ │ ├── edit-documentation.png │ │ └── icu-message-format.png │ └── self-hosting │ │ └── control-panel.jpeg │ ├── package.json │ ├── administrators │ └── deleting-a-meeting-poll.mdx │ └── contact │ └── support.mdx ├── .npmrc ├── vitest.workspace.ts ├── assets └── images │ └── splash.png ├── pnpm-workspace.yaml ├── .github ├── FUNDING.yml ├── actions │ ├── pnpm-install │ │ └── action.yml │ └── setup-node │ │ └── action.yml ├── ISSUE_TEMPLATE │ └── ---feature-request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── release.yml ├── scripts ├── docker-start.sh └── inject-version.js ├── .dockerignore ├── crowdin.yml └── .gitignore /.nvmrc: -------------------------------------------------------------------------------- 1 | 20 -------------------------------------------------------------------------------- /packages/ui/src/styles/globals.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/web/public/locales/nb/app.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /packages/database/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /apps/landing/public/locales/fa/blog.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /apps/landing/public/locales/nb/blog.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /apps/landing/public/locales/nb/home.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /apps/landing/public/locales/nn/blog.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /apps/landing/public/locales/nn/home.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /apps/landing/public/locales/th/home.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /apps/landing/public/locales/vi/blog.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /apps/web/src/components/poll/guest-alert.tsx: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/emails/.gitignore: -------------------------------------------------------------------------------- 1 | .react-email 2 | /out -------------------------------------------------------------------------------- /packages/emails/locales/th/emails.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /packages/emails/locales/vi/emails.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /apps/landing/public/locales/fa/pricing.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /apps/landing/public/locales/nb/common.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /apps/landing/public/locales/nb/pricing.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /apps/landing/public/locales/nn/common.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /apps/landing/public/locales/vi/pricing.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /packages/ui/src/index.ts: -------------------------------------------------------------------------------- 1 | export { cn } from "./lib/utils"; 2 | -------------------------------------------------------------------------------- /apps/web/src/components/steps/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./steps"; 2 | -------------------------------------------------------------------------------- /apps/landing/public/locales/th/pricing.json: -------------------------------------------------------------------------------- 1 | { 2 | "pricing": "แผนราคา" 3 | } 4 | -------------------------------------------------------------------------------- /apps/web/src/components/image-upload/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./image-upload"; 2 | -------------------------------------------------------------------------------- /apps/web/src/components/modal/index.ts: -------------------------------------------------------------------------------- 1 | export { useModal } from "./use-modal"; 2 | -------------------------------------------------------------------------------- /apps/web/src/components/discussion/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./discussion"; 2 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/types.ts: -------------------------------------------------------------------------------- 1 | export interface Params { 2 | locale: string; 3 | } 4 | -------------------------------------------------------------------------------- /apps/web/src/features/navigation/command-menu/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./command-menu"; 2 | -------------------------------------------------------------------------------- /apps/web/src/lib/locale/constants.ts: -------------------------------------------------------------------------------- 1 | export const LOCALE_COOKIE_NAME = "rallly_locale"; 2 | -------------------------------------------------------------------------------- /packages/tailwind-config/tailwind.config.d.ts: -------------------------------------------------------------------------------- 1 | declare module "@rallly/tailwind-config"; 2 | -------------------------------------------------------------------------------- /apps/docs/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/docs/favicon.png -------------------------------------------------------------------------------- /apps/landing/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | # playwright 4 | /playwright-report 5 | /test-results -------------------------------------------------------------------------------- /apps/web/src/lib/storage/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./actions"; 2 | export * from "./constants"; 3 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | public-hoist-pattern[]=*import-in-the-middle* 2 | public-hoist-pattern[]=*require-in-the-middle* -------------------------------------------------------------------------------- /apps/landing/public/locales/nn/pricing.json: -------------------------------------------------------------------------------- 1 | { 2 | "monthlyBillingDescription": "per månad" 3 | } 4 | -------------------------------------------------------------------------------- /apps/landing/src/i18n/client/trans.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | export { Trans } from "react-i18next"; 3 | -------------------------------------------------------------------------------- /apps/web/src/lib/timezone/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./client/context"; 2 | export * from "./utils"; 3 | -------------------------------------------------------------------------------- /packages/posthog/src/server.ts: -------------------------------------------------------------------------------- 1 | import { PostHog } from "posthog-node"; 2 | 3 | export { PostHog }; 4 | -------------------------------------------------------------------------------- /packages/ui/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require("@rallly/tailwind-config/tailwind.config"); 2 | -------------------------------------------------------------------------------- /vitest.workspace.ts: -------------------------------------------------------------------------------- 1 | export default ["packages/*/vitest.config.mts", "apps/*/vitest.config.mts"]; 2 | -------------------------------------------------------------------------------- /apps/docs/images/home.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/docs/images/home.jpeg -------------------------------------------------------------------------------- /apps/landing/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ["tailwindcss", "autoprefixer"], 3 | }; 4 | -------------------------------------------------------------------------------- /apps/web/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/web/public/logo.png -------------------------------------------------------------------------------- /assets/images/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/assets/images/splash.png -------------------------------------------------------------------------------- /packages/dayjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dayjs", 3 | "version": "0.0.0", 4 | "private": true 5 | } -------------------------------------------------------------------------------- /apps/landing/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/logo.png -------------------------------------------------------------------------------- /apps/web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/web/public/favicon.ico -------------------------------------------------------------------------------- /apps/web/src/features/instance-settings/constants.ts: -------------------------------------------------------------------------------- 1 | export const instanceSettingsTag = "instance-settings"; 2 | -------------------------------------------------------------------------------- /apps/web/src/lib/rate-limit/constants.ts: -------------------------------------------------------------------------------- 1 | export const isRateLimitEnabled = !!process.env.KV_REST_API_URL; 2 | -------------------------------------------------------------------------------- /packages/posthog/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const POSTHOG_BOOTSTAP_DATA_COOKIE_NAME = "posthog_bootstrap_data"; 2 | -------------------------------------------------------------------------------- /apps/landing/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/favicon.ico -------------------------------------------------------------------------------- /apps/web/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /api 3 | Disallow: /locales 4 | Allow: /api/og-image-poll 5 | -------------------------------------------------------------------------------- /apps/landing/src/i18n/client/use-translation.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | export { useTranslation } from "react-i18next"; 4 | -------------------------------------------------------------------------------- /apps/web/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/web/public/favicon-16x16.png -------------------------------------------------------------------------------- /apps/web/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/web/public/favicon-32x32.png -------------------------------------------------------------------------------- /apps/web/public/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/web/public/favicon-96x96.png -------------------------------------------------------------------------------- /apps/web/public/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/web/public/mstile-70x70.png -------------------------------------------------------------------------------- /apps/web/public/og-image-1200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/web/public/og-image-1200.png -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/(optional-space)/new/loading.tsx: -------------------------------------------------------------------------------- 1 | export default function Loading() { 2 | return null; 3 | } 4 | -------------------------------------------------------------------------------- /apps/web/src/components/forms/poll-options-form/month-calendar/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./month-calendar"; 2 | -------------------------------------------------------------------------------- /apps/web/src/features/password/types.ts: -------------------------------------------------------------------------------- 1 | export type PasswordQuality = "veryWeak" | "weak" | "fair" | "good" | "strong"; 2 | -------------------------------------------------------------------------------- /apps/docs/images/unused/support.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/docs/images/unused/support.png -------------------------------------------------------------------------------- /apps/docs/images/workflow/review.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/docs/images/workflow/review.png -------------------------------------------------------------------------------- /apps/docs/images/workflow/voting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/docs/images/workflow/voting.png -------------------------------------------------------------------------------- /apps/landing/public/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/mstile-70x70.png -------------------------------------------------------------------------------- /apps/web/public/favicon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/web/public/favicon-128x128.png -------------------------------------------------------------------------------- /apps/web/public/favicon-196x196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/web/public/favicon-196x196.png -------------------------------------------------------------------------------- /apps/web/public/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/web/public/mstile-144x144.png -------------------------------------------------------------------------------- /apps/web/public/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/web/public/mstile-150x150.png -------------------------------------------------------------------------------- /apps/web/public/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/web/public/mstile-310x150.png -------------------------------------------------------------------------------- /apps/web/public/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/web/public/mstile-310x310.png -------------------------------------------------------------------------------- /apps/web/src/app/api/stripe/webhook/handlers/checkout/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./completed"; 2 | export * from "./expired"; 3 | -------------------------------------------------------------------------------- /apps/web/src/app/api/stripe/webhook/handlers/customer/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./created"; 2 | export * from "./deleted"; 3 | -------------------------------------------------------------------------------- /apps/web/src/features/calendars/constants.ts: -------------------------------------------------------------------------------- 1 | export const isCalendarsEnabled = process.env.CALENDARS_ENABLED === "true"; 2 | -------------------------------------------------------------------------------- /apps/web/tailwind.config.js: -------------------------------------------------------------------------------- 1 | const sharedConfig = require("@rallly/tailwind-config"); 2 | 3 | module.exports = sharedConfig; 4 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - apps/* 3 | - packages/* 4 | 5 | onlyBuiltDependencies: 6 | - '@prisma/client' 7 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [lukevella] 4 | custom: paypal.me/ralllyco 5 | -------------------------------------------------------------------------------- /apps/docs/images/unused/polls-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/docs/images/unused/polls-page.png -------------------------------------------------------------------------------- /apps/docs/images/workflow/finalize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/docs/images/workflow/finalize.png -------------------------------------------------------------------------------- /apps/landing/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/favicon-16x16.png -------------------------------------------------------------------------------- /apps/landing/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/favicon-32x32.png -------------------------------------------------------------------------------- /apps/landing/public/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/favicon-96x96.png -------------------------------------------------------------------------------- /apps/landing/public/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/mstile-144x144.png -------------------------------------------------------------------------------- /apps/landing/public/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/mstile-150x150.png -------------------------------------------------------------------------------- /apps/landing/public/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/mstile-310x150.png -------------------------------------------------------------------------------- /apps/landing/public/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/mstile-310x310.png -------------------------------------------------------------------------------- /apps/web/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /apps/docs/images/workflow/finalized.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/docs/images/workflow/finalized.png -------------------------------------------------------------------------------- /apps/docs/images/workflow/invite-link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/docs/images/workflow/invite-link.png -------------------------------------------------------------------------------- /apps/docs/images/workflow/month-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/docs/images/workflow/month-view.png -------------------------------------------------------------------------------- /apps/docs/images/workflow/week-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/docs/images/workflow/week-view.png -------------------------------------------------------------------------------- /apps/landing/public/favicon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/favicon-128x128.png -------------------------------------------------------------------------------- /apps/landing/public/favicon-196x196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/favicon-196x196.png -------------------------------------------------------------------------------- /apps/web/src/components/forms/poll-options-form/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./poll-options-form"; 2 | export * from "./types"; 3 | -------------------------------------------------------------------------------- /apps/web/src/i18n/types.ts: -------------------------------------------------------------------------------- 1 | import type app from "../../public/locales/en/app.json"; 2 | 3 | export type TxKeyPath = keyof typeof app; 4 | -------------------------------------------------------------------------------- /packages/utils/src/sleep.ts: -------------------------------------------------------------------------------- 1 | export function sleep(ms: number) { 2 | return new Promise((resolve) => setTimeout(resolve, ms)); 3 | } 4 | -------------------------------------------------------------------------------- /apps/docs/images/guide/voting-submitted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/docs/images/guide/voting-submitted.png -------------------------------------------------------------------------------- /apps/docs/images/workflow/new-poll-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/docs/images/workflow/new-poll-page.png -------------------------------------------------------------------------------- /apps/landing/public/static/images/eric.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/static/images/eric.png -------------------------------------------------------------------------------- /apps/landing/public/static/images/hero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/static/images/hero.png -------------------------------------------------------------------------------- /apps/web/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/web/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /apps/web/public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/web/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /apps/web/public/apple-touch-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/web/public/apple-touch-icon-57x57.png -------------------------------------------------------------------------------- /apps/web/public/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/web/public/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /apps/web/public/apple-touch-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/web/public/apple-touch-icon-72x72.png -------------------------------------------------------------------------------- /apps/web/public/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/web/public/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /apps/web/public/images/rallly-logo-mark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/web/public/images/rallly-logo-mark.png -------------------------------------------------------------------------------- /apps/web/public/static/fonts/inter-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/web/public/static/fonts/inter-bold.ttf -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20240309043111_remove_poll_vote_index/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropIndex 2 | DROP INDEX "votes_poll_id_idx"; 3 | -------------------------------------------------------------------------------- /packages/emails/src/index.ts: -------------------------------------------------------------------------------- 1 | export type { SupportedEmailProviders } from "./send-email"; 2 | export { EmailClient } from "./send-email"; 3 | -------------------------------------------------------------------------------- /apps/docs/images/guide/voting-in-progress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/docs/images/guide/voting-in-progress.png -------------------------------------------------------------------------------- /apps/docs/images/guide/voting-submitting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/docs/images/guide/voting-submitting.png -------------------------------------------------------------------------------- /apps/landing/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /apps/landing/public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /apps/landing/public/apple-touch-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/apple-touch-icon-57x57.png -------------------------------------------------------------------------------- /apps/landing/public/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /apps/landing/public/apple-touch-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/apple-touch-icon-72x72.png -------------------------------------------------------------------------------- /apps/landing/public/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /apps/landing/public/static/images/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/static/images/google.png -------------------------------------------------------------------------------- /apps/landing/public/static/images/shortcut.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/static/images/shortcut.gif -------------------------------------------------------------------------------- /apps/landing/public/static/images/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/static/images/splash.png -------------------------------------------------------------------------------- /apps/landing/tailwind.config.js: -------------------------------------------------------------------------------- 1 | const sharedConfig = require("@rallly/tailwind-config/tailwind.config"); 2 | 3 | module.exports = sharedConfig; 4 | -------------------------------------------------------------------------------- /apps/web/public/apple-touch-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/web/public/apple-touch-icon-114x114.png -------------------------------------------------------------------------------- /apps/web/public/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/web/public/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /apps/web/public/apple-touch-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/web/public/apple-touch-icon-144x144.png -------------------------------------------------------------------------------- /apps/web/public/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/web/public/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /apps/web/public/apple-touch-icon-167x167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/web/public/apple-touch-icon-167x167.png -------------------------------------------------------------------------------- /apps/web/public/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/web/public/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /apps/web/public/static/fonts/inter-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/web/public/static/fonts/inter-regular.ttf -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20240224011353_remove_legacy_user_preferences/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropTable 2 | DROP TABLE "user_preferences"; 3 | -------------------------------------------------------------------------------- /packages/emails/src/previews/static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/packages/emails/src/previews/static/logo.png -------------------------------------------------------------------------------- /apps/docs/images/contribute/crowdin-project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/docs/images/contribute/crowdin-project.png -------------------------------------------------------------------------------- /apps/docs/images/self-hosting/control-panel.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/docs/images/self-hosting/control-panel.jpeg -------------------------------------------------------------------------------- /apps/landing/public/apple-touch-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/apple-touch-icon-114x114.png -------------------------------------------------------------------------------- /apps/landing/public/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /apps/landing/public/apple-touch-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/apple-touch-icon-144x144.png -------------------------------------------------------------------------------- /apps/landing/public/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /apps/landing/public/apple-touch-icon-167x167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/apple-touch-icon-167x167.png -------------------------------------------------------------------------------- /apps/landing/public/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /apps/landing/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /api 3 | Disallow: /locales 4 | Allow: /api/og-image 5 | Sitemap: https://rallly.co/sitemap.xml -------------------------------------------------------------------------------- /apps/landing/public/static/fonts/inter-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/static/fonts/inter-bold.ttf -------------------------------------------------------------------------------- /apps/landing/public/static/images/animations.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/static/images/animations.gif -------------------------------------------------------------------------------- /apps/landing/public/static/images/hero-shot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/static/images/hero-shot.png -------------------------------------------------------------------------------- /apps/landing/public/static/images/luke-vella.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/static/images/luke-vella.jpg -------------------------------------------------------------------------------- /apps/landing/public/static/images/share-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/static/images/share-demo.gif -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20240127064213_add_image_field/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "users" ADD COLUMN "image" TEXT; 3 | -------------------------------------------------------------------------------- /apps/docs/images/contribute/edit-documentation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/docs/images/contribute/edit-documentation.png -------------------------------------------------------------------------------- /apps/docs/images/contribute/icu-message-format.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/docs/images/contribute/icu-message-format.png -------------------------------------------------------------------------------- /apps/landing/public/static/fonts/inter-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/static/fonts/inter-regular.ttf -------------------------------------------------------------------------------- /apps/landing/public/static/images/device-data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/static/images/device-data.png -------------------------------------------------------------------------------- /apps/landing/public/static/images/grouped-times.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/static/images/grouped-times.png -------------------------------------------------------------------------------- /apps/landing/public/static/images/mobile-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/static/images/mobile-demo.gif -------------------------------------------------------------------------------- /apps/landing/public/static/images/twitter-card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/static/images/twitter-card.png -------------------------------------------------------------------------------- /apps/landing/src/app/[locale]/opengraph-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/src/app/[locale]/opengraph-image.png -------------------------------------------------------------------------------- /apps/web/src/features/feedback/constants.ts: -------------------------------------------------------------------------------- 1 | import { isSelfHosted } from "@/utils/constants"; 2 | 3 | export const isFeedbackEnabled = !isSelfHosted; 4 | -------------------------------------------------------------------------------- /apps/web/src/lib/kv.ts: -------------------------------------------------------------------------------- 1 | import { kv } from "@vercel/kv"; 2 | 3 | const isKvEnabled = !!process.env.KV_REST_API_URL; 4 | 5 | export { kv, isKvEnabled }; 6 | -------------------------------------------------------------------------------- /apps/docs/images/unused/response/editing-response.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/docs/images/unused/response/editing-response.png -------------------------------------------------------------------------------- /apps/landing/public/locales/zh/blog.json: -------------------------------------------------------------------------------- 1 | { 2 | "recentPosts": "最新文章", 3 | "blogTitle": "Rallly - 博客", 4 | "blogDescription": "有关 Rallly 的新闻、更新和公告。" 5 | } 6 | -------------------------------------------------------------------------------- /apps/landing/public/static/images/timeslots-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/static/images/timeslots-demo.gif -------------------------------------------------------------------------------- /apps/landing/public/static/images/touchable-area.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/static/images/touchable-area.png -------------------------------------------------------------------------------- /apps/web/src/app/api/stripe/webhook/handlers/payment-method/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./attached"; 2 | export * from "./detached"; 3 | export * from "./updated"; 4 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20240901171230_participant_locale/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "participants" ADD COLUMN "locale" TEXT; 3 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20250804130713_add_space_image_column/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "spaces" ADD COLUMN "image" TEXT; 3 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20250804152225_allow_user_multiple_subscriptions/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropIndex 2 | DROP INDEX "subscriptions_user_id_key"; 3 | -------------------------------------------------------------------------------- /packages/languages/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@rallly/tsconfig/node.json", 3 | "include": ["src/**/*.ts"], 4 | "exclude": ["node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/ui/src/hooks/use-platform.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | export function usePlatform() { 4 | return { isMac: navigator.userAgent.includes("Mac") }; 5 | } 6 | -------------------------------------------------------------------------------- /apps/docs/images/unused/response/editing-response-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/docs/images/unused/response/editing-response-1.png -------------------------------------------------------------------------------- /apps/docs/images/unused/response/editing-response-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/docs/images/unused/response/editing-response-2.png -------------------------------------------------------------------------------- /apps/web/src/app/api/stripe/webhook/handlers/customer-subscription/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./created"; 2 | export * from "./deleted"; 3 | export * from "./updated"; 4 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20230303142641_add_participant_email/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "participants" ADD COLUMN "email" TEXT; 3 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20250711140850_add_time_zone_column/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "participants" ADD COLUMN "time_zone" TEXT; 3 | -------------------------------------------------------------------------------- /packages/database/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@rallly/tsconfig/next.json", 3 | "include": ["**/*.ts", "**/*.tsx"], 4 | "exclude": ["node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/ui/custom.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.svg" { 2 | const content: React.FunctionComponent>; 3 | export default content; 4 | } 5 | -------------------------------------------------------------------------------- /apps/docs/images/unused/response/submitting-response-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/docs/images/unused/response/submitting-response-1.png -------------------------------------------------------------------------------- /apps/docs/images/unused/response/submitting-response-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/docs/images/unused/response/submitting-response-2.png -------------------------------------------------------------------------------- /apps/landing/public/locales/ja/blog.json: -------------------------------------------------------------------------------- 1 | { 2 | "recentPosts": "最近の投稿", 3 | "blogTitle": "Rallly - ブログ", 4 | "blogDescription": "Ralllyに関するニュース・アップデート・お知らせ。" 5 | } 6 | -------------------------------------------------------------------------------- /apps/landing/public/locales/ko/blog.json: -------------------------------------------------------------------------------- 1 | { 2 | "recentPosts": "최근 게시물", 3 | "blogTitle": "Rallly - 블로그", 4 | "blogDescription": "Rallly에 관한 뉴스, 업데이트 및 공지사항." 5 | } 6 | -------------------------------------------------------------------------------- /apps/landing/public/locales/zh-Hant/blog.json: -------------------------------------------------------------------------------- 1 | { 2 | "recentPosts": "最新貼文", 3 | "blogTitle": "Rallly - 部落格", 4 | "blogDescription": "關於 Rallly 的新聞、更新與公告。" 5 | } 6 | -------------------------------------------------------------------------------- /apps/landing/src/fonts/sans.ts: -------------------------------------------------------------------------------- 1 | import { Inter } from "next/font/google"; 2 | 3 | export const sans = Inter({ 4 | subsets: ["latin"], 5 | display: "swap", 6 | }); 7 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/[...notFound]/page.tsx: -------------------------------------------------------------------------------- 1 | import { notFound } from "next/navigation"; 2 | 3 | export default function CatchAllPage() { 4 | notFound(); 5 | } 6 | -------------------------------------------------------------------------------- /apps/web/src/app/api/auth/[...nextauth]/route.ts: -------------------------------------------------------------------------------- 1 | import { handlers } from "@/next-auth"; 2 | 3 | export const GET = handlers.GET; 4 | export const POST = handlers.POST; 5 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20251021160519_add_last_login_method/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "users" ADD COLUMN "last_login_method" TEXT; 3 | -------------------------------------------------------------------------------- /apps/landing/public/static/images/rallly-pro-launch/email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/static/images/rallly-pro-launch/email.png -------------------------------------------------------------------------------- /packages/billing/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@rallly/tsconfig/next.json", 3 | "include": ["**/*.ts", "**/*.tsx", "**/*.js"], 4 | "exclude": ["node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20240315104329_index_votes_by_poll/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateIndex 2 | CREATE INDEX "votes_poll_id_idx" ON "votes" USING HASH ("poll_id"); 3 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20251107120000_allow_multiple_space_subscriptions/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropIndex 2 | DROP INDEX IF EXISTS "subscriptions_space_id_key"; 3 | 4 | -------------------------------------------------------------------------------- /apps/landing/src/app/[locale]/(main)/[...notFound]/page.tsx: -------------------------------------------------------------------------------- 1 | import { notFound } from "next/navigation"; 2 | 3 | export default function CatchAllPage() { 4 | notFound(); 5 | } 6 | -------------------------------------------------------------------------------- /apps/landing/src/lib/linkToApp.ts: -------------------------------------------------------------------------------- 1 | export const linkToApp = (path = "") => { 2 | const url = new URL(path, process.env.NEXT_PUBLIC_APP_BASE_URL); 3 | return url.href; 4 | }; 5 | -------------------------------------------------------------------------------- /apps/web/src/utils/constants.ts: -------------------------------------------------------------------------------- 1 | export const isSelfHosted = process.env.NEXT_PUBLIC_SELF_HOSTED === "true"; 2 | 3 | export const appVersion = process.env.NEXT_PUBLIC_APP_VERSION; 4 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20231118134458_add_account_user_index/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateIndex 2 | CREATE INDEX "Account_userId_idx" ON "Account" USING HASH ("userId"); 3 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20240309051319_add_option_vote_index/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateIndex 2 | CREATE INDEX "votes_option_id_idx" ON "votes" USING HASH ("option_id"); 3 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20251027170830_anonymous_user/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "users" ADD COLUMN "anonymous" BOOLEAN NOT NULL DEFAULT false; 3 | -------------------------------------------------------------------------------- /apps/landing/public/locales/hr/blog.json: -------------------------------------------------------------------------------- 1 | { 2 | "recentPosts": "Nedavne objave", 3 | "blogTitle": "Rallly - Blog", 4 | "blogDescription": "Novosti i obavijesti o Rallly." 5 | } 6 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/(optional-space)/poll/[urlId]/page.tsx: -------------------------------------------------------------------------------- 1 | import { AdminPage } from "./admin-page"; 2 | 3 | export default function Page() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /packages/billing/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./lib/stripe"; 2 | export * from "./pricing"; 3 | 4 | export const PLAN_NAMES = { 5 | HOBBY: "Hobby", 6 | PRO: "Pro", 7 | } as const; 8 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20230118192253_bring_back_comment_index/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateIndex 2 | CREATE INDEX "comments_user_id_idx" ON "comments" USING HASH ("user_id"); 3 | -------------------------------------------------------------------------------- /apps/landing/public/locales/th/blog.json: -------------------------------------------------------------------------------- 1 | { 2 | "recentPosts": "โพสต์ล่าสุด", 3 | "blogTitle": "Rallly - บล็อก", 4 | "blogDescription": "ข่าว, อัปเดต และ ประกาศ เกี่ยวกับ Rallly" 5 | } 6 | -------------------------------------------------------------------------------- /apps/landing/public/static/images/rallly-pro-launch/billing-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/static/images/rallly-pro-launch/billing-page.png -------------------------------------------------------------------------------- /apps/web/i18n.config.js: -------------------------------------------------------------------------------- 1 | const languages = require("@rallly/languages/languages.json"); 2 | 3 | module.exports = { 4 | defaultLocale: "en", 5 | locales: Object.keys(languages), 6 | }; 7 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/admin/[adminUrlId]/types.ts: -------------------------------------------------------------------------------- 1 | import type { Params } from "@/app/[locale]/types"; 2 | 3 | export interface PParams extends Params { 4 | adminUrlId: string; 5 | } 6 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20220330085423_add_notifications_column/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Poll" ADD COLUMN "notifications" BOOLEAN NOT NULL DEFAULT false; 3 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20230721163042_hide_participants/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "polls" ADD COLUMN "hide_participants" BOOLEAN NOT NULL DEFAULT false; 3 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20231027074632_nextauth_ci_identifiers/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "verification_tokens" ALTER COLUMN "identifier" SET DATA TYPE CITEXT; 3 | -------------------------------------------------------------------------------- /packages/posthog/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@rallly/tsconfig/next.json", 3 | "include": [ 4 | "**/*.ts", 5 | "**/*.tsx", 6 | ], 7 | "exclude": ["node_modules"] 8 | } 9 | -------------------------------------------------------------------------------- /apps/landing/public/locales/en/blog.json: -------------------------------------------------------------------------------- 1 | { 2 | "recentPosts": "Recent Posts", 3 | "blogTitle": "Rallly - Blog", 4 | "blogDescription": "News, updates and announcement about Rallly." 5 | } 6 | -------------------------------------------------------------------------------- /apps/landing/public/locales/it/blog.json: -------------------------------------------------------------------------------- 1 | { 2 | "recentPosts": "Post Recenti", 3 | "blogTitle": "Rallly - Blog", 4 | "blogDescription": "Notizie, aggiornamenti e annunci su Rallly." 5 | } 6 | -------------------------------------------------------------------------------- /apps/landing/public/locales/ru/blog.json: -------------------------------------------------------------------------------- 1 | { 2 | "recentPosts": "Последние сообщения", 3 | "blogTitle": "Rallly - Блог", 4 | "blogDescription": "Новости, обновления и анонсы о Rallly." 5 | } 6 | -------------------------------------------------------------------------------- /apps/landing/public/static/images/introducing-rallly-3-0/navigation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/static/images/introducing-rallly-3-0/navigation.png -------------------------------------------------------------------------------- /apps/landing/public/static/images/introducing-rallly-3-0/polls-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/static/images/introducing-rallly-3-0/polls-page.png -------------------------------------------------------------------------------- /apps/landing/public/static/images/rallly-pro-launch/finalize-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/static/images/rallly-pro-launch/finalize-page.png -------------------------------------------------------------------------------- /apps/landing/src/fonts/handwritten.ts: -------------------------------------------------------------------------------- 1 | import { Playpen_Sans } from "next/font/google"; 2 | 3 | export const handwritten = Playpen_Sans({ 4 | subsets: ["latin"], 5 | display: "swap", 6 | }); 7 | -------------------------------------------------------------------------------- /apps/web/src/app/api/stripe/portal/route.ts: -------------------------------------------------------------------------------- 1 | import { createStripePortalSessionHandler } from "./helpers/create-portal-session"; 2 | 3 | export const GET = createStripePortalSessionHandler(); 4 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (e.g., Git) 3 | provider = "postgresql" 4 | -------------------------------------------------------------------------------- /apps/landing/public/locales/cs/blog.json: -------------------------------------------------------------------------------- 1 | { 2 | "recentPosts": "Poslední příspěvky", 3 | "blogTitle": "Rallly - Blog", 4 | "blogDescription": "Novinky, aktualizace a oznámení o Rallly." 5 | } 6 | -------------------------------------------------------------------------------- /apps/landing/public/locales/da/blog.json: -------------------------------------------------------------------------------- 1 | { 2 | "recentPosts": "Seneste indlæg", 3 | "blogTitle": "Rallly - Blog", 4 | "blogDescription": "Nyheder, opdateringer og annoncering om Rallly." 5 | } 6 | -------------------------------------------------------------------------------- /apps/landing/public/locales/de/blog.json: -------------------------------------------------------------------------------- 1 | { 2 | "recentPosts": "Neueste Beiträge", 3 | "blogTitle": "Rallly - Blog", 4 | "blogDescription": "News, Updates und Ankündigung über Rallly." 5 | } 6 | -------------------------------------------------------------------------------- /apps/landing/public/locales/sk/blog.json: -------------------------------------------------------------------------------- 1 | { 2 | "recentPosts": "Nedávne príspevky", 3 | "blogTitle": "Rallly - Blog", 4 | "blogDescription": "Novinky, aktualizácie a oznámenia o Rallly." 5 | } 6 | -------------------------------------------------------------------------------- /apps/landing/public/static/images/introducing-rallly-3-0/poll-status.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/static/images/introducing-rallly-3-0/poll-status.png -------------------------------------------------------------------------------- /apps/web/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | # Sentry 4 | .sentryclirc 5 | 6 | # playwright 7 | /playwright-report 8 | /test-results 9 | # Sentry Config File 10 | .env.sentry-build-plugin 11 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/p/[participantUrlId]/types.ts: -------------------------------------------------------------------------------- 1 | import type { Params } from "@/app/[locale]/types"; 2 | 3 | export interface PParams extends Params { 4 | participantUrlId: string; 5 | } 6 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20220520115326_add_touch_column/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "polls" ADD COLUMN "touched_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP; 3 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20230915170216_add_required_email/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "polls" ADD COLUMN "require_participant_email" BOOLEAN NOT NULL DEFAULT false; 3 | -------------------------------------------------------------------------------- /apps/landing/public/.well-known/microsoft-identity-association.json: -------------------------------------------------------------------------------- 1 | { 2 | "associatedApplications": [ 3 | { 4 | "applicationId": "8a0e84e2-4719-4a04-ab6d-30a95a6371d7" 5 | } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /apps/landing/public/locales/nl/blog.json: -------------------------------------------------------------------------------- 1 | { 2 | "recentPosts": "Recente berichten", 3 | "blogTitle": "Rallly - Blog", 4 | "blogDescription": "Nieuws, updates en aankondigingen over Rallly." 5 | } 6 | -------------------------------------------------------------------------------- /apps/landing/public/locales/no/blog.json: -------------------------------------------------------------------------------- 1 | { 2 | "recentPosts": "Nylige innlegg", 3 | "blogTitle": "Rallly - Blogg", 4 | "blogDescription": "Nyheter, oppdateringer og kunngjøringer om Rallly." 5 | } 6 | -------------------------------------------------------------------------------- /apps/landing/public/locales/sv/blog.json: -------------------------------------------------------------------------------- 1 | { 2 | "recentPosts": "Senaste inläggen", 3 | "blogTitle": "Rallly - Blogg", 4 | "blogDescription": "Nyheter, uppdateringar och meddelanden om Rallly." 5 | } 6 | -------------------------------------------------------------------------------- /apps/landing/public/static/images/introducing-rallly-3-0/updated-scoring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/static/images/introducing-rallly-3-0/updated-scoring.png -------------------------------------------------------------------------------- /apps/landing/public/static/images/introducing-rallly-pro/finalize-poll.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/static/images/introducing-rallly-pro/finalize-poll.gif -------------------------------------------------------------------------------- /apps/web/src/components/relative-date.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import dayjs from "dayjs"; 3 | 4 | export function RelativeDate({ date }: { date: Date }) { 5 | return <>{dayjs(date).fromNow()}; 6 | } 7 | -------------------------------------------------------------------------------- /apps/web/src/features/space/member/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const memberRoleSchema = z.enum(["member", "admin"]); 4 | export type MemberRole = z.infer; 5 | -------------------------------------------------------------------------------- /apps/web/src/lib/storage/constants.ts: -------------------------------------------------------------------------------- 1 | import { env } from "@/env"; 2 | 3 | export const isStorageEnabled = Boolean( 4 | env.S3_BUCKET_NAME && env.S3_ACCESS_KEY_ID && env.S3_SECRET_ACCESS_KEY, 5 | ); 6 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20250217082042_add_subscription_fields/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "subscriptions" ADD COLUMN "amount" INTEGER, 3 | ADD COLUMN "status" TEXT; 4 | -------------------------------------------------------------------------------- /apps/landing/public/locales/ca/blog.json: -------------------------------------------------------------------------------- 1 | { 2 | "recentPosts": "Publicacions recents", 3 | "blogTitle": "Rallly - Blog", 4 | "blogDescription": "Notícies, actualitzacions i anuncis sobre Rallly." 5 | } 6 | -------------------------------------------------------------------------------- /apps/landing/public/locales/es/blog.json: -------------------------------------------------------------------------------- 1 | { 2 | "recentPosts": "Posts recientes", 3 | "blogTitle": "Rallly - Blog", 4 | "blogDescription": "Noticias, actualizaciones y anuncios acerca de Rallly." 5 | } 6 | -------------------------------------------------------------------------------- /apps/landing/public/locales/eu/blog.json: -------------------------------------------------------------------------------- 1 | { 2 | "recentPosts": "Azken mezuak", 3 | "blogTitle": "Rallly - Bloga", 4 | "blogDescription": "Ralllyri buruzko albisteak, eguneratzeak eta iragarkia." 5 | } 6 | -------------------------------------------------------------------------------- /apps/landing/public/locales/fi/blog.json: -------------------------------------------------------------------------------- 1 | { 2 | "recentPosts": "Viimeisimmät julkaisut", 3 | "blogTitle": "Rallly - Blogi", 4 | "blogDescription": "Uutisia, päivityksiä ja tiedotteita Ralllysta." 5 | } 6 | -------------------------------------------------------------------------------- /apps/landing/public/locales/fr/blog.json: -------------------------------------------------------------------------------- 1 | { 2 | "recentPosts": "Publications récentes", 3 | "blogTitle": "Rallly - Blog", 4 | "blogDescription": "Actualités, mises à jour et annonces sur Rallly." 5 | } 6 | -------------------------------------------------------------------------------- /apps/landing/public/locales/hu/blog.json: -------------------------------------------------------------------------------- 1 | { 2 | "recentPosts": "Legutóbbi bejegyzések", 3 | "blogTitle": "Rallly - Blog", 4 | "blogDescription": "Hírek, frissítések és bejelentések a Rallly-ról." 5 | } 6 | -------------------------------------------------------------------------------- /apps/landing/public/locales/pl/blog.json: -------------------------------------------------------------------------------- 1 | { 2 | "recentPosts": "Ostatnie posty", 3 | "blogTitle": "Rallly - Blog", 4 | "blogDescription": "Wiadomości, aktualizacje i ogłoszenia dotyczące Rallly." 5 | } 6 | -------------------------------------------------------------------------------- /apps/landing/public/locales/pt-BR/blog.json: -------------------------------------------------------------------------------- 1 | { 2 | "recentPosts": "Publicações Recentes", 3 | "blogTitle": "Rallly - Blog", 4 | "blogDescription": "Notícias, atualizações e avisos sobre Rallly." 5 | } 6 | -------------------------------------------------------------------------------- /apps/landing/public/locales/pt/blog.json: -------------------------------------------------------------------------------- 1 | { 2 | "recentPosts": "Publicações recentes", 3 | "blogTitle": "Rallly - Blogue", 4 | "blogDescription": "Notícias, atualizações e anúncios sobre Rallly." 5 | } 6 | -------------------------------------------------------------------------------- /apps/landing/public/locales/tr/blog.json: -------------------------------------------------------------------------------- 1 | { 2 | "recentPosts": "Son Gönderiler", 3 | "blogTitle": "Rallly - Blog", 4 | "blogDescription": "Rallly hakkında, haberler, güncellemeler ve duyurular." 5 | } 6 | -------------------------------------------------------------------------------- /apps/landing/public/locales/vi/home.json: -------------------------------------------------------------------------------- 1 | { 2 | "noLoginRequired": "Không cần đăng nhập", 3 | "new": "Thêm", 4 | "metaDescription": "Tạo bình chọn và bầu giờ hoặc ngày phù hợp nhất với cả nhóm." 5 | } 6 | -------------------------------------------------------------------------------- /apps/landing/public/static/images/introducing-rallly-pro/rallly-pro-splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/static/images/introducing-rallly-pro/rallly-pro-splash.png -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20250221104854_add_cancel_at_period_end/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "subscriptions" ADD COLUMN "cancel_at_period_end" BOOLEAN NOT NULL DEFAULT false; 3 | -------------------------------------------------------------------------------- /packages/emails/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@rallly/tsconfig/next.json", 3 | "files": ["i18next.d.ts"], 4 | "include": ["**/*.ts", "**/*.tsx"], 5 | "exclude": ["node_modules", ".react-email"] 6 | } 7 | -------------------------------------------------------------------------------- /apps/landing/public/static/images/introducing-rallly-pro/finalize-poll-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code/app-rallly-agpl/main/apps/landing/public/static/images/introducing-rallly-pro/finalize-poll-demo.gif -------------------------------------------------------------------------------- /apps/web/src/app/api/better-auth/[...all]/route.ts: -------------------------------------------------------------------------------- 1 | import { toNextJsHandler } from "better-auth/next-js"; 2 | import { authLib } from "@/lib/auth"; 3 | 4 | export const { POST, GET } = toNextJsHandler(authLib); 5 | -------------------------------------------------------------------------------- /apps/web/src/features/poll/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const pollStatusSchema = z.enum(["live", "paused", "finalized"]); 4 | 5 | export type PollStatus = z.infer; 6 | -------------------------------------------------------------------------------- /apps/web/src/features/quick-create/constants.ts: -------------------------------------------------------------------------------- 1 | import { isSelfHosted } from "@/utils/constants"; 2 | 3 | export const isQuickCreateEnabled = 4 | !isSelfHosted && process.env.QUICK_CREATE_ENABLED === "true"; 5 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20240221084400_unset_invalid_timezones/migration.sql: -------------------------------------------------------------------------------- 1 | -- Unset non-geographic time zones 2 | UPDATE users SET time_zone = NULL WHERE time_zone NOT LIKE '%/%' OR time_zone LIKE 'Etc/%'; 3 | -------------------------------------------------------------------------------- /packages/ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@rallly/tsconfig/next.json", 3 | "compilerOptions": { 4 | "baseUrl": "." 5 | }, 6 | "include": ["**/*.ts", "**/*.tsx"], 7 | "exclude": ["node_modules"] 8 | } 9 | -------------------------------------------------------------------------------- /apps/web/src/features/quick-create/index.ts: -------------------------------------------------------------------------------- 1 | export { isQuickCreateEnabled } from "./constants"; 2 | export { QuickCreateButton } from "./quick-create-button"; 3 | export { QuickCreateWidget } from "./quick-create-widget"; 4 | -------------------------------------------------------------------------------- /packages/billing/src/pricing.ts: -------------------------------------------------------------------------------- 1 | export const pricingData = { 2 | monthly: { 3 | amount: 700, 4 | currency: "usd", 5 | }, 6 | yearly: { 7 | amount: 5600, 8 | currency: "usd", 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /apps/web/src/app/api/stripe/portal/payment-methods/route.ts: -------------------------------------------------------------------------------- 1 | import { createStripePortalSessionHandler } from "../helpers/create-portal-session"; 2 | 3 | export const GET = createStripePortalSessionHandler("/payment-methods"); 4 | -------------------------------------------------------------------------------- /apps/web/src/features/feedback/schema.ts: -------------------------------------------------------------------------------- 1 | import z from "zod"; 2 | 3 | export const feedbackSchema = z.object({ 4 | content: z.string().min(10).max(10000), 5 | }); 6 | 7 | export type Feedback = z.infer; 8 | -------------------------------------------------------------------------------- /apps/web/src/utils/selectors.ts: -------------------------------------------------------------------------------- 1 | export const getPortal = () => { 2 | const el = document.getElementById("portal"); 3 | if (el === null) { 4 | throw new Error("Portal element not found"); 5 | } 6 | return el; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20250729143210_add_space_member_last_selected_at/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "space_members" ADD COLUMN "last_selected_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP; 3 | -------------------------------------------------------------------------------- /packages/utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": ["vitest/globals", "node"], 4 | "lib": ["es2020", "dom"] 5 | }, 6 | "extends": "@rallly/tsconfig/node.json", 7 | "include": ["**/*.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/(space)/settings/loading.tsx: -------------------------------------------------------------------------------- 1 | import { RouterLoadingIndicator } from "@/components/router-loading-indicator"; 2 | 3 | export default async function Loading() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /apps/web/src/contexts/role.tsx: -------------------------------------------------------------------------------- 1 | import { usePathname } from "next/navigation"; 2 | 3 | export const useRole = () => { 4 | const pathname = usePathname(); 5 | return pathname?.includes("/poll") ? "admin" : "participant"; 6 | }; 7 | -------------------------------------------------------------------------------- /apps/web/src/utils/is-initial-admin.ts: -------------------------------------------------------------------------------- 1 | import "server-only"; 2 | 3 | export function isInitialAdmin(email: string) { 4 | return ( 5 | process.env.INITIAL_ADMIN_EMAIL && email === process.env.INITIAL_ADMIN_EMAIL 6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20220408120721_legacy_column/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Poll" ADD COLUMN "closed" BOOLEAN NOT NULL DEFAULT false, 3 | ADD COLUMN "legacy" BOOLEAN NOT NULL DEFAULT false; 4 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20220522165453_add_deleted_at_column/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "polls" ADD COLUMN "deleted_at" TIMESTAMP(3); 3 | 4 | UPDATE "polls" SET "deleted_at" = now() WHERE "deleted" = true; -------------------------------------------------------------------------------- /scripts/docker-start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | export DIRECT_DATABASE_URL=$DATABASE_URL 5 | export AUTH_URL=$NEXT_PUBLIC_BASE_URL 6 | 7 | pnpm prisma migrate deploy --schema=./prisma/schema.prisma 8 | node apps/web/server.js 9 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20240704085250_soft_delete_participants/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "participants" ADD COLUMN "deleted" BOOLEAN NOT NULL DEFAULT false, 3 | ADD COLUMN "deleted_at" TIMESTAMP(3); 4 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20250724140012_add_subscription_columns/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "subscriptions" ADD COLUMN "quantity" INTEGER NOT NULL DEFAULT 1, 3 | ADD COLUMN "subscription_item_id" TEXT; 4 | -------------------------------------------------------------------------------- /apps/web/src/utils/permissions.ts: -------------------------------------------------------------------------------- 1 | export function isOwner( 2 | resource: { userId?: string | null; guestId?: string | null }, 3 | user: { id: string }, 4 | ) { 5 | return resource.userId === user.id || resource.guestId === user.id; 6 | } 7 | -------------------------------------------------------------------------------- /packages/tsconfig/next.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Next.js", 4 | "extends": "@tsconfig/next/tsconfig.json", 5 | "compilerOptions": { 6 | "verbatimModuleSyntax": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/tsconfig/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rallly/tsconfig", 3 | "version": "0.0.0", 4 | "private": true, 5 | "devDependencies": { 6 | "@tsconfig/next": "^2.0.1", 7 | "@tsconfig/strictest": "^2.0.2" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/ui/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import type { ClassValue } from "clsx"; 2 | import clsx from "clsx"; 3 | import { twMerge } from "tailwind-merge"; 4 | 5 | export function cn(...inputs: ClassValue[]) { 6 | return twMerge(clsx(inputs)); 7 | } 8 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/(space)/(dashboard)/events/types.ts: -------------------------------------------------------------------------------- 1 | export type ScheduledEvent = { 2 | id: string; 3 | title: string; 4 | start: Date; 5 | duration: number; 6 | timeZone: string | null; 7 | participants: { name: string }[]; 8 | }; 9 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20250522093338_add_user_role/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateEnum 2 | CREATE TYPE "user_role" AS ENUM ('admin', 'user'); 3 | 4 | -- AlterTable 5 | ALTER TABLE "users" ADD COLUMN "role" "user_role" NOT NULL DEFAULT 'user'; 6 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20220511113020_add_if_need_be/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateEnum 2 | CREATE TYPE "VoteType" AS ENUM ('yes', 'no', 'ifNeedBe'); 3 | 4 | -- AlterTable 5 | ALTER TABLE "Vote" ADD COLUMN "type" "VoteType" NOT NULL DEFAULT E'yes'; 6 | -------------------------------------------------------------------------------- /apps/web/src/features/billing/constants.ts: -------------------------------------------------------------------------------- 1 | import { isSelfHosted } from "@/utils/constants"; 2 | 3 | export const isBillingEnabled = !isSelfHosted; 4 | 5 | // Re-export plan names from the shared billing package 6 | export { PLAN_NAMES } from "@rallly/billing"; 7 | -------------------------------------------------------------------------------- /apps/web/src/features/instance-settings/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const instanceSettingsSchema = z.object({ 4 | disableUserRegistration: z.boolean(), 5 | }); 6 | 7 | export type InstanceSettings = z.infer; 8 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20250302163530_add_user_ban_fields/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "users" ADD COLUMN "ban_reason" TEXT, 3 | ADD COLUMN "banned" BOOLEAN NOT NULL DEFAULT false, 4 | ADD COLUMN "banned_at" TIMESTAMP(3); 5 | -------------------------------------------------------------------------------- /.github/actions/pnpm-install/action.yml: -------------------------------------------------------------------------------- 1 | name: "Pnpm Install" 2 | description: "Runs pnpm install with --frozen-lockfile" 3 | runs: 4 | using: "composite" 5 | steps: 6 | - name: Run pnpm install 7 | run: pnpm install --frozen-lockfile 8 | shell: bash 9 | -------------------------------------------------------------------------------- /apps/web/src/features/scheduled-event/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const statusSchema = z.enum([ 4 | "upcoming", 5 | "unconfirmed", 6 | "past", 7 | "canceled", 8 | ]); 9 | 10 | export type Status = z.infer; 11 | -------------------------------------------------------------------------------- /packages/utils/vitest.config.mts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitest/config"; 2 | 3 | export default defineConfig({ 4 | test: { 5 | globals: true, 6 | include: ["**/*.test.ts"], 7 | exclude: ["**/node_modules/**", "**/*.spec.ts"], 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /apps/web/src/features/licensing/server.ts: -------------------------------------------------------------------------------- 1 | import { env } from "@/env"; 2 | import { LicenseManager } from "./lib/licensing-manager"; 3 | 4 | export const licenseManager = new LicenseManager({ 5 | apiUrl: env.LICENSE_API_URL, 6 | authToken: env.LICENSE_API_AUTH_TOKEN, 7 | }); 8 | -------------------------------------------------------------------------------- /apps/web/src/features/space/types.ts: -------------------------------------------------------------------------------- 1 | import type { MemberRole } from "@/features/space/schema"; 2 | 3 | export type SpaceDTO = { 4 | id: string; 5 | name: string; 6 | ownerId: string; 7 | tier: "hobby" | "pro"; 8 | role: MemberRole; 9 | image?: string; 10 | }; 11 | -------------------------------------------------------------------------------- /apps/web/src/lib/feature-flags/server.ts: -------------------------------------------------------------------------------- 1 | import "server-only"; 2 | import type { Feature } from "@/lib/feature-flags/types"; 3 | import { featureFlagConfig } from "./config"; 4 | 5 | export function isFeatureEnabled(feature: Feature) { 6 | return featureFlagConfig[feature]; 7 | } 8 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20230318113511_remove_unecessary_unique_indexes/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropIndex 2 | DROP INDEX "Comment_id_pollId_key"; 3 | 4 | -- DropIndex 5 | DROP INDEX "comments_id_poll_id_key"; 6 | 7 | -- DropIndex 8 | DROP INDEX "participants_id_poll_id_key"; 9 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20250523161200_remove_invitee_constraints/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropIndex 2 | DROP INDEX "scheduled_event_invites_scheduled_event_id_invitee_email_key"; 3 | 4 | -- DropIndex 5 | DROP INDEX "scheduled_event_invites_scheduled_event_id_invitee_id_key"; 6 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20221026220835_add_indexes/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateIndex 2 | CREATE INDEX "participants_poll_id_idx" ON "participants" USING HASH ("poll_id"); 3 | 4 | -- CreateIndex 5 | CREATE INDEX "votes_participant_id_idx" ON "votes" USING HASH ("participant_id"); 6 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/invite/[urlId]/loading.tsx: -------------------------------------------------------------------------------- 1 | import { Spinner } from "@/components/spinner"; 2 | 3 | export default function Loading() { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /apps/web/src/trpc/context.ts: -------------------------------------------------------------------------------- 1 | type User = { 2 | id: string; 3 | isGuest: boolean; 4 | locale?: string; 5 | image?: string; 6 | isLegacyGuest: boolean; 7 | }; 8 | 9 | export type TRPCContext = { 10 | user?: User; 11 | locale?: string; 12 | identifier?: string; 13 | }; 14 | -------------------------------------------------------------------------------- /apps/landing/public/locales/fa/home.json: -------------------------------------------------------------------------------- 1 | { 2 | "startUsOnGithub": "به ما در گیت‌هاب ستاره دهید", 3 | "noLoginRequired": "بدون نیاز به ورود", 4 | "metaDescription": "با ایجاد نظرسنجی بهترین روز و وقت را پیدا کنید! جایگزینی رایگان برای Doodle", 5 | "getStarted": "همین حالا شروع کنید" 6 | } 7 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20250402100733_remove_closed_column/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `closed` on the `polls` table. All the data in the column will be lost. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "polls" DROP COLUMN "closed"; 9 | -------------------------------------------------------------------------------- /apps/web/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | // NOTE: This file should not be edited 6 | // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. 7 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/(optional-space)/poll/[urlId]/loading.tsx: -------------------------------------------------------------------------------- 1 | import { Spinner } from "@/components/spinner"; 2 | 3 | export default function Loading() { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /apps/web/src/lib/feature-flags/types.ts: -------------------------------------------------------------------------------- 1 | export interface FeatureFlagConfig { 2 | storage: boolean; 3 | billing: boolean; 4 | feedback: boolean; 5 | emailLogin: boolean; 6 | registration: boolean; 7 | calendars: boolean; 8 | } 9 | 10 | export type Feature = keyof FeatureFlagConfig; 11 | -------------------------------------------------------------------------------- /packages/emails/src/previews/register.tsx: -------------------------------------------------------------------------------- 1 | import { previewEmailContext } from "../components/email-context"; 2 | import { RegisterEmail } from "../templates/register"; 3 | 4 | export default function RegisterEmailPreview() { 5 | return ; 6 | } 7 | -------------------------------------------------------------------------------- /apps/landing/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | // NOTE: This file should not be edited 6 | // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. 7 | -------------------------------------------------------------------------------- /apps/web/tests/edit-options-page.ts: -------------------------------------------------------------------------------- 1 | import type { Page } from "@playwright/test"; 2 | 3 | export class EditOptionsPage { 4 | constructor(public readonly page: Page) {} 5 | 6 | async switchToSpecifyTimes() { 7 | await this.page.click("[data-testid='specify-times-switch']"); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20230327105647_remove_author_name/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `author_name` on the `polls` table. All the data in the column will be lost. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "polls" DROP COLUMN "author_name"; 9 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20240317095541_remove_legacy_start_column/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `start` on the `options` table. All the data in the column will be lost. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "options" DROP COLUMN "start"; 9 | -------------------------------------------------------------------------------- /apps/web/src/utils/cookies.ts: -------------------------------------------------------------------------------- 1 | import Cookies from "js-cookie"; 2 | 3 | export function setCookie(key: string, value: string) { 4 | Cookies.set(key, value); 5 | } 6 | 7 | export function popCookie(key: string) { 8 | const value = Cookies.get(key); 9 | Cookies.remove(key); 10 | return value; 11 | } 12 | -------------------------------------------------------------------------------- /packages/languages/src/index.ts: -------------------------------------------------------------------------------- 1 | import { languages } from "./languages"; 2 | 3 | export type SupportedLocale = keyof typeof languages; 4 | 5 | export const supportedLngs = Object.keys(languages); 6 | 7 | export const defaultLocale = "en"; 8 | 9 | export default languages; 10 | 11 | export { languages }; 12 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/(auth)/login/loading.tsx: -------------------------------------------------------------------------------- 1 | import { Spinner } from "@/components/spinner"; 2 | 3 | export default function Loading() { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/(auth)/register/loading.tsx: -------------------------------------------------------------------------------- 1 | import { Spinner } from "@/components/spinner"; 2 | 3 | export default function Loading() { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /apps/web/src/features/space/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const memberRoleSchema = z.enum(["member", "admin"]); 4 | export type MemberRole = z.infer; 5 | 6 | export const spaceTierSchema = z.enum(["hobby", "pro"]); 7 | export type SpaceTier = z.infer; 8 | -------------------------------------------------------------------------------- /apps/web/src/components/container.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@rallly/ui"; 2 | 3 | export const Container = ({ 4 | children, 5 | className, 6 | }: React.PropsWithChildren<{ className?: string }>) => { 7 | return ( 8 |
{children}
9 | ); 10 | }; 11 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20230615163229_remove_selected_option_id/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `selected_option_id` on the `polls` table. All the data in the column will be lost. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "polls" DROP COLUMN "selected_option_id"; 9 | -------------------------------------------------------------------------------- /packages/emails/i18next.d.ts: -------------------------------------------------------------------------------- 1 | import "i18next"; 2 | 3 | import type emails from "./locales/en/emails.json"; 4 | 5 | declare module "i18next" { 6 | interface CustomTypeOptions { 7 | defaultNS: "emails"; 8 | resources: { 9 | emails: typeof emails; 10 | }; 11 | returnNull: false; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /apps/web/src/features/setup/utils.ts: -------------------------------------------------------------------------------- 1 | import { prisma } from "@rallly/database"; 2 | 3 | export const userHasSpaces = async (userId: string): Promise => { 4 | const spaceCount = await prisma.spaceMember.count({ 5 | where: { 6 | userId: userId, 7 | }, 8 | }); 9 | return spaceCount > 0; 10 | }; 11 | -------------------------------------------------------------------------------- /apps/web/src/features/space/member/types.ts: -------------------------------------------------------------------------------- 1 | import type { MemberRole } from "@/features/space/schema"; 2 | 3 | export type MemberDTO = { 4 | id: string; 5 | name: string; 6 | email: string; 7 | userId: string; 8 | spaceId: string; 9 | image?: string; 10 | role: MemberRole; 11 | isOwner: boolean; 12 | }; 13 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20220412115744_cascade_delete_votes/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropForeignKey 2 | ALTER TABLE "Vote" DROP CONSTRAINT "Vote_pollId_fkey"; 3 | 4 | -- AddForeignKey 5 | ALTER TABLE "Vote" ADD CONSTRAINT "Vote_pollId_fkey" FOREIGN KEY ("pollId") REFERENCES "Poll"("urlId") ON DELETE CASCADE ON UPDATE CASCADE; 6 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20230615111329_events/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - Added the required column `option_id` to the `events` table without a default value. This is not possible if the table is not empty. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "events" ADD COLUMN "option_id" TEXT NOT NULL; 9 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20250526175615_remove_stripe_customer_id/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `stripe_customer_id` on the `licenses` table. All the data in the column will be lost. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "licenses" DROP COLUMN "stripe_customer_id"; 9 | -------------------------------------------------------------------------------- /packages/ui/src/checkbox-check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /apps/web/src/lib/oauth/client.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | export function connect(provider: string) { 4 | const url = new URL( 5 | `/api/integrations/auth/${provider}`, 6 | window.location.origin, 7 | ); 8 | url.searchParams.set("redirect", window.location.pathname); 9 | window.location.href = url.toString(); 10 | } 11 | -------------------------------------------------------------------------------- /packages/database/prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | datasource db { 2 | provider = "postgresql" 3 | url = env("DATABASE_URL") 4 | directUrl = env("DIRECT_DATABASE_URL") 5 | } 6 | 7 | generator client { 8 | provider = "prisma-client-js" 9 | binaryTargets = ["native"] 10 | previewFeatures = ["relationJoins"] 11 | } 12 | -------------------------------------------------------------------------------- /scripts/inject-version.js: -------------------------------------------------------------------------------- 1 | const { execSync } = require("node:child_process"); 2 | const packageJson = require("../package.json"); 3 | 4 | const version = packageJson.version; 5 | const gitHash = execSync("git rev-parse --short HEAD").toString().trim(); 6 | const versionWithHash = `${version}-${gitHash}`; 7 | 8 | console.log(versionWithHash); 9 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .git 3 | .github 4 | .vscode 5 | tsconfig.tsbuildinfo 6 | .env 7 | .sentryclirc 8 | sentry.properties 9 | Dockerfile 10 | docker-compose.yml 11 | /docs 12 | README.md 13 | node_modules 14 | 15 | # Build outputs 16 | apps/*/.next 17 | apps/*/dist 18 | packages/*/dist 19 | 20 | # Turbo prune output 21 | out/ 22 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/(space)/settings/members/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const searchParamsSchema = z.object({ 4 | page: z.coerce.number().min(1).default(1), 5 | pageSize: z.coerce.number().min(1).default(10), 6 | q: z.string().optional(), 7 | role: z.enum(["all", "member", "admin"]).optional().catch("all"), 8 | }); 9 | -------------------------------------------------------------------------------- /apps/web/src/components/forms/poll-options-form/utils.ts: -------------------------------------------------------------------------------- 1 | import dayjs from "dayjs"; 2 | 3 | export const formatDateWithoutTz = (date: Date): string => { 4 | return dayjs(date).format("YYYY-MM-DDTHH:mm:ss"); 5 | }; 6 | 7 | export const formatDateWithoutTime = (date: Date): string => { 8 | return dayjs(date).format("YYYY-MM-DD"); 9 | }; 10 | -------------------------------------------------------------------------------- /apps/web/src/components/forms/index.ts: -------------------------------------------------------------------------------- 1 | export type { PollDetailsData } from "./poll-details-form"; 2 | export { PollDetailsForm } from "./poll-details-form"; 3 | export type { PollOptionsData } from "./poll-options-form/poll-options-form"; 4 | export { default as PollOptionsForm } from "./poll-options-form/poll-options-form"; 5 | export * from "./types"; 6 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20230725112615_add_poll_config/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateEnum 2 | CREATE TYPE "participant_visibility" AS ENUM ('full', 'scoresOnly', 'limited'); 3 | 4 | -- AlterTable 5 | ALTER TABLE "polls" ADD COLUMN "disable_comments" BOOLEAN NOT NULL DEFAULT false, 6 | ADD COLUMN "hide_scores" BOOLEAN NOT NULL DEFAULT false; 7 | -------------------------------------------------------------------------------- /apps/landing/src/middleware.ts: -------------------------------------------------------------------------------- 1 | import type { NextRequest } from "next/server"; 2 | import { i18nMiddleware } from "@/i18n/middleware"; 3 | 4 | export async function middleware(req: NextRequest) { 5 | return i18nMiddleware(req); 6 | } 7 | 8 | export const config = { 9 | matcher: ["/((?!api|_next/static|_next/image|static|poll|.*\\.).*)"], 10 | }; 11 | -------------------------------------------------------------------------------- /apps/web/src/components/random-gradient-bar.tsx: -------------------------------------------------------------------------------- 1 | import { generateGradient } from "@/utils/color-hash"; 2 | 3 | export function RandomGradientBar({ seed }: { seed?: string }) { 4 | return ( 5 |
9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /apps/web/src/utils/next.ts: -------------------------------------------------------------------------------- 1 | import type { NextApiHandler } from "next"; 2 | 3 | export function composeApiHandlers(...fns: NextApiHandler[]): NextApiHandler { 4 | return async (req, res) => { 5 | for (const fn of fns) { 6 | await fn(req, res); 7 | if (res.writableEnded) { 8 | return; 9 | } 10 | } 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /packages/ui/src/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "./lib/utils"; 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return ( 8 |
12 | ); 13 | } 14 | 15 | export { Skeleton }; 16 | -------------------------------------------------------------------------------- /apps/landing/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://openapi.vercel.sh/vercel.json", 3 | "installCommand": "pnpm install", 4 | "buildCommand": "cd ../.. && pnpm db:generate && pnpm build:landing", 5 | "outputDirectory": ".next", 6 | "rewrites": [ 7 | { "source": "/poll/:path*", "destination": "https://app.rallly.co/invite/:path*" } 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20220412115407_cascade_delete_participants/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropForeignKey 2 | ALTER TABLE "Participant" DROP CONSTRAINT "Participant_pollId_fkey"; 3 | 4 | -- AddForeignKey 5 | ALTER TABLE "Participant" ADD CONSTRAINT "Participant_pollId_fkey" FOREIGN KEY ("pollId") REFERENCES "Poll"("urlId") ON DELETE CASCADE ON UPDATE CASCADE; 6 | -------------------------------------------------------------------------------- /apps/web/src/app/components/logo-link.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import Logo from "@/assets/logo-mark.svg"; 3 | 4 | export function LogoLink() { 5 | return ( 6 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /apps/web/src/components/pro-badge.tsx: -------------------------------------------------------------------------------- 1 | import { Badge } from "@rallly/ui/badge"; 2 | 3 | import { PLAN_NAMES } from "@/features/billing/constants"; 4 | 5 | export const ProBadge = ({ className }: { className?: string }) => { 6 | return ( 7 | 8 | {PLAN_NAMES.PRO} 9 | 10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /apps/web/src/test/setup.ts: -------------------------------------------------------------------------------- 1 | import "@testing-library/jest-dom/vitest"; 2 | import { vi } from "vitest"; 3 | 4 | // Mock next-themes to avoid SSR issues 5 | vi.mock("next-themes", () => ({ 6 | useTheme: () => ({ 7 | theme: "light", 8 | setTheme: vi.fn(), 9 | }), 10 | ThemeProvider: ({ children }: { children: React.ReactNode }) => children, 11 | })); 12 | -------------------------------------------------------------------------------- /packages/database/prisma/seed/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Generates a random integer between floor and max (inclusive). 3 | * @param max The maximum value. 4 | * @param floor The minimum value (default: 0). 5 | * @returns A random integer. 6 | */ 7 | export const randInt = (max = 1, floor = 0): number => { 8 | return Math.round(Math.random() * max) + floor; 9 | }; 10 | -------------------------------------------------------------------------------- /apps/web/public/static/microsoft.svg: -------------------------------------------------------------------------------- 1 | MS-SymbolLockup -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/control-panel/loading.tsx: -------------------------------------------------------------------------------- 1 | import { PageSkeleton } from "@/app/components/page-layout"; 2 | import { RouterLoadingIndicator } from "@/components/router-loading-indicator"; 3 | 4 | export default async function Loading() { 5 | return ( 6 | <> 7 | 8 | 9 | 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /apps/web/src/app/components/full-logo-link.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import Logo from "@/assets/logo.svg"; 3 | 4 | export function FullLogoLink() { 5 | return ( 6 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20250715153525_update_space_member_indexes/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropIndex 2 | DROP INDEX "space_members_space_id_idx"; 3 | 4 | -- CreateIndex 5 | CREATE INDEX "space_members_space_id_idx" ON "space_members" USING HASH ("space_id"); 6 | 7 | -- CreateIndex 8 | CREATE INDEX "space_members_user_id_idx" ON "space_members" USING HASH ("user_id"); 9 | -------------------------------------------------------------------------------- /apps/landing/src/app/[locale]/(main)/blog/layout.tsx: -------------------------------------------------------------------------------- 1 | export default function BlogLayout({ 2 | children, 3 | }: { 4 | children: React.ReactNode; 5 | }) { 6 | return ( 7 |
8 |
9 |
{children}
10 |
11 |
12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/(space)/(dashboard)/loading.tsx: -------------------------------------------------------------------------------- 1 | import { PageSkeleton } from "@/app/components/page-layout"; 2 | import { RouterLoadingIndicator } from "@/components/router-loading-indicator"; 3 | 4 | export default async function Loading() { 5 | return ( 6 | <> 7 | 8 | 9 | 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /apps/web/src/features/billing/schema.ts: -------------------------------------------------------------------------------- 1 | import z from "zod"; 2 | 3 | export const billingIntervalSchema = z.enum(["month", "year"]); 4 | export type BillingInterval = z.infer; 5 | 6 | export const customerMetadataSchema = z.object({ 7 | userId: z.string(), 8 | }); 9 | export type CustomerMetadata = z.infer; 10 | -------------------------------------------------------------------------------- /apps/web/src/utils/session/session-config.ts: -------------------------------------------------------------------------------- 1 | import { absoluteUrl } from "@rallly/utils/absolute-url"; 2 | 3 | export const sessionConfig = { 4 | password: process.env.SECRET_PASSWORD ?? "", 5 | cookieName: "rallly-session", 6 | cookieOptions: { 7 | secure: absoluteUrl().startsWith("https://") ?? false, 8 | }, 9 | ttl: 60 * 60 * 24 * 30, // 30 days 10 | }; 11 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20231205080854_fix_finalized_poll_status/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Fixes an issue in the previous migration where paused polls were not being 3 | set to finalized if they had an event_id. 4 | */ 5 | -- Set "status" to "finalized" if "event_id" is not null 6 | UPDATE "polls" 7 | SET "status" = 'finalized'::poll_status 8 | WHERE "event_id" IS NOT NULL; 9 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20250730115902_subscription_item_id_non_nullable/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - Made the column `subscription_item_id` on table `subscriptions` required. This step will fail if there are existing NULL values in that column. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "subscriptions" ALTER COLUMN "subscription_item_id" SET NOT NULL; 9 | -------------------------------------------------------------------------------- /apps/web/src/contexts/environment.tsx: -------------------------------------------------------------------------------- 1 | export const isSelfHosted = process.env.NEXT_PUBLIC_SELF_HOSTED === "true"; 2 | 3 | export const IfSelfHosted = ({ children }: React.PropsWithChildren) => { 4 | return isSelfHosted ? children : null; 5 | }; 6 | 7 | export const IfCloudHosted = ({ children }: React.PropsWithChildren) => { 8 | return isSelfHosted ? null : children; 9 | }; 10 | -------------------------------------------------------------------------------- /apps/web/src/components/spinner.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@rallly/ui"; 2 | import { Loader2Icon } from "lucide-react"; 3 | 4 | export const Spinner = (props: { className?: string }) => { 5 | return ( 6 | 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20230118200546_add_unqiue_participant/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - A unique constraint covering the columns `[id,poll_id]` on the table `participants` will be added. If there are existing duplicate values, this will fail. 5 | 6 | */ 7 | -- CreateIndex 8 | CREATE UNIQUE INDEX "participants_id_poll_id_key" ON "participants"("id", "poll_id"); 9 | -------------------------------------------------------------------------------- /apps/web/public/locales/th/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "login": "ลงชื่อเข้าใช้", 3 | "common_language": "ภาษา", 4 | "common_support": "ช่วยเหลือ", 5 | "errors_notFoundTitle": "404 ไม่พบสิ่งที่ค้นหา", 6 | "errors_notFoundDescription": "เราไม่พบหน้าที่คุณค้นหา", 7 | "errors_goToHome": "ไปยังหน้าหลัก", 8 | "language": "ภาษา", 9 | "support": "ช่วยเหลือ", 10 | "pricing": "แผนราคา" 11 | } 12 | -------------------------------------------------------------------------------- /apps/web/src/components/poll/types.ts: -------------------------------------------------------------------------------- 1 | import type { VoteType } from "@rallly/database"; 2 | 3 | export interface ParticipantForm { 4 | votes: Array< 5 | | { 6 | optionId: string; 7 | type?: VoteType; 8 | } 9 | | undefined 10 | >; 11 | } 12 | 13 | export interface ParticipantFormSubmitted { 14 | votes: Array<{ optionId: string; type: VoteType }>; 15 | } 16 | -------------------------------------------------------------------------------- /apps/web/src/components/poll/you-avatar.tsx: -------------------------------------------------------------------------------- 1 | import { Icon } from "@rallly/ui/icon"; 2 | import { UserIcon } from "lucide-react"; 3 | 4 | export function YouAvatar() { 5 | return ( 6 |
7 | 8 | 9 | 10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /packages/utils/src/nanoid.ts: -------------------------------------------------------------------------------- 1 | import { customAlphabet } from "nanoid"; 2 | 3 | export const nanoid = customAlphabet( 4 | "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 5 | 12, 6 | ); 7 | 8 | export const randomid = customAlphabet( 9 | "0123456789abcdefghijklmnopqrstuvwxyz", 10 | 12, 11 | ); 12 | 13 | export const generateOtp = customAlphabet("0123456789", 6); 14 | -------------------------------------------------------------------------------- /apps/docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rallly/docs", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "mint dev", 7 | "build": "mint build" 8 | }, 9 | "devDependencies": { 10 | "@rallly/tsconfig": "workspace:*" 11 | }, 12 | "dependencies": { 13 | "mint": "^4.2.245", 14 | "react": "^19.1.4", 15 | "react-dom": "^19.1.4" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/(auth)/register/actions.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { cookies } from "next/headers"; 4 | 5 | export async function setToken(token: string) { 6 | (await cookies()).set("registration-token", token, { 7 | httpOnly: true, 8 | secure: process.env.NEXT_PUBLIC_BASE_URL?.startsWith("https://"), 9 | sameSite: "lax", 10 | maxAge: 15 * 60, 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /apps/web/src/components/trans.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { Trans as BaseTrans, useTranslation } from "react-i18next"; 3 | 4 | import type { TxKeyPath } from "../i18n/types"; 5 | 6 | export const Trans = ( 7 | props: React.ComponentProps & { i18nKey: TxKeyPath }, 8 | ) => { 9 | const { t } = useTranslation(); 10 | return ; 11 | }; 12 | -------------------------------------------------------------------------------- /apps/web/src/i18n/server/get-locale.ts: -------------------------------------------------------------------------------- 1 | import { defaultLocale } from "@rallly/languages"; 2 | import { headers } from "next/headers"; 3 | 4 | export async function getLocale() { 5 | const headersList = await headers(); 6 | const localeFromHeader = headersList.get("x-locale"); 7 | 8 | if (!localeFromHeader) { 9 | return defaultLocale; 10 | } 11 | return localeFromHeader; 12 | } 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F914 Feature request" 3 | about: Please use discussions instead of issues for feature requests 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **DO NOT OPEN AN ISSUE FOR FEATURE REQUESTS** 11 | 12 | Please use Discussions ☝️ instead and start a new idea discussion if it doesn't exist already. 13 | 14 | Thank you :) 15 | -------------------------------------------------------------------------------- /apps/web/src/features/analytics/posthog.ts: -------------------------------------------------------------------------------- 1 | import { PostHog } from "@rallly/posthog/server"; 2 | import { env } from "@/env"; 3 | 4 | export function PostHogClient() { 5 | if (!env.NEXT_PUBLIC_POSTHOG_API_KEY) return null; 6 | 7 | return new PostHog(env.NEXT_PUBLIC_POSTHOG_API_KEY, { 8 | host: env.NEXT_PUBLIC_POSTHOG_API_HOST, 9 | flushAt: 20, 10 | flushInterval: 10000, 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /packages/ui/src/collapsible.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"; 4 | 5 | const Collapsible = CollapsiblePrimitive.Root; 6 | 7 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger; 8 | 9 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent; 10 | 11 | export { Collapsible, CollapsibleContent, CollapsibleTrigger }; 12 | -------------------------------------------------------------------------------- /apps/web/src/auth/providers/guest.ts: -------------------------------------------------------------------------------- 1 | import { randomid } from "@rallly/utils/nanoid"; 2 | import CredentialsProvider from "next-auth/providers/credentials"; 3 | 4 | export const GuestProvider = CredentialsProvider({ 5 | id: "guest", 6 | name: "Guest", 7 | credentials: {}, 8 | async authorize() { 9 | return { 10 | id: `user-${randomid()}`, 11 | email: null, 12 | }; 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /apps/web/src/components/steps/step.tsx: -------------------------------------------------------------------------------- 1 | import type React from "react"; 2 | 3 | export interface StepProps { 4 | title: string; 5 | children?: React.ReactNode; 6 | } 7 | 8 | const Step: React.FunctionComponent = ({ title, children }) => { 9 | return ( 10 |
11 |
{title}
12 |
{children}
13 |
14 | ); 15 | }; 16 | 17 | export default Step; 18 | -------------------------------------------------------------------------------- /apps/web/src/instrumentation.ts: -------------------------------------------------------------------------------- 1 | import * as Sentry from "@sentry/nextjs"; 2 | 3 | export const onRequestError = Sentry.captureRequestError; 4 | 5 | export async function register() { 6 | if (process.env.NEXT_RUNTIME === "nodejs") { 7 | await import("../sentry.server.config"); 8 | } 9 | 10 | if (process.env.NEXT_RUNTIME === "edge") { 11 | await import("../sentry.edge.config"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /apps/web/src/trpc/client.ts: -------------------------------------------------------------------------------- 1 | import { createTRPCReact } from "@trpc/react-query"; 2 | 3 | import type { AppRouter } from "@/trpc/routers"; 4 | 5 | export const trpc = createTRPCReact({ 6 | overrides: { 7 | useMutation: { 8 | async onSuccess(opts) { 9 | await opts.originalFn(); 10 | await opts.queryClient.invalidateQueries(); 11 | }, 12 | }, 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /apps/landing/src/assets/twitter.svg: -------------------------------------------------------------------------------- 1 | 4 | 6 | -------------------------------------------------------------------------------- /apps/web/src/features/licensing/helpers/check-license-key.ts: -------------------------------------------------------------------------------- 1 | import { calculateChecksum } from "./calculate-checksum"; 2 | 3 | export function checkLicenseKey(key: string): boolean { 4 | const parts = key.split("-"); 5 | if (parts.length !== 6) return false; 6 | const checksum = parts[5]; 7 | const licenseBody = parts.slice(0, 5).join("-"); 8 | return calculateChecksum(licenseBody) === checksum; 9 | } 10 | -------------------------------------------------------------------------------- /apps/landing/src/app/[locale]/not-found.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import ErrorPage from "@/components/error-page"; 4 | import { useTranslation } from "@/i18n/client/use-translation"; 5 | 6 | export default function Page() { 7 | const { t } = useTranslation("common"); 8 | return ( 9 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /apps/web/declarations/i18next.d.ts: -------------------------------------------------------------------------------- 1 | import "i18next"; 2 | 3 | import type emails from "@rallly/emails/locales/emails.json"; 4 | 5 | import type app from "../public/locales/en/app.json"; 6 | 7 | declare module "i18next" { 8 | interface CustomTypeOptions { 9 | defaultNS: "app"; 10 | returnNull: false; 11 | resources: { 12 | app: typeof app; 13 | emails: typeof emails; 14 | }; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /apps/web/src/features/quick-create/components/relative-date.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import dayjs from "dayjs"; 3 | 4 | import { Trans } from "@/components/trans"; 5 | 6 | export function RelativeDate({ date }: { date: Date }) { 7 | return ( 8 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20250729144904_drop_user_active_space_id/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `active_space_id` on the `users` table. All the data in the column will be lost. 5 | 6 | */ 7 | -- DropForeignKey 8 | ALTER TABLE "users" DROP CONSTRAINT "users_active_space_id_fkey"; 9 | 10 | -- AlterTable 11 | ALTER TABLE "users" DROP COLUMN "active_space_id"; 12 | -------------------------------------------------------------------------------- /apps/landing/src/components/blog/date-formatter.tsx: -------------------------------------------------------------------------------- 1 | import dayjs from "dayjs"; 2 | import localizedFormat from "dayjs/plugin/localizedFormat"; 3 | 4 | dayjs.extend(localizedFormat); 5 | 6 | type Props = { 7 | dateString: string; 8 | }; 9 | 10 | const DateFormatter = ({ dateString }: Props) => { 11 | return ; 12 | }; 13 | 14 | export default DateFormatter; 15 | -------------------------------------------------------------------------------- /packages/emails/src/previews/license-key.tsx: -------------------------------------------------------------------------------- 1 | import { previewEmailContext } from "../components/email-context"; 2 | import { LicenseKeyEmail } from "../templates/license-key"; 3 | 4 | export default function LicenseKeyPreview() { 5 | return ( 6 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /apps/docs/administrators/deleting-a-meeting-poll.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Delete a Poll" 3 | --- 4 | 5 | 6 | Deleting a poll will delete all data related to that poll such as options, 7 | participants and votes. This action cannot be undone. 8 | 9 | 10 | To delete a meeting poll, from the admin page: 11 | 12 | 1. Click **Manage** 13 | 1. Select **Delete poll** from the dropdown menu 14 | 1. Click **Delete poll** 15 | -------------------------------------------------------------------------------- /apps/landing/src/assets/linkedin.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/database/prisma/models/instance-settings.prisma: -------------------------------------------------------------------------------- 1 | model InstanceSettings { 2 | id Int @id @default(1) 3 | // Authentication & Security 4 | disableUserRegistration Boolean @default(false) @map("disable_user_registration") 5 | 6 | createdAt DateTime @default(now()) @map("created_at") 7 | updatedAt DateTime @default(now()) @updatedAt @map("updated_at") 8 | 9 | @@map("instance_settings") 10 | } 11 | -------------------------------------------------------------------------------- /apps/web/.env.test: -------------------------------------------------------------------------------- 1 | PORT=3002 2 | NEXT_PUBLIC_BASE_URL=http://localhost:3002 3 | AUTH_URL=$NEXT_PUBLIC_BASE_URL 4 | SECRET_PASSWORD=abcdef1234567890abcdef1234567890 5 | DATABASE_URL=postgres://postgres:postgres@localhost:5450/rallly 6 | SUPPORT_EMAIL=support@rallly.co 7 | SMTP_HOST=0.0.0.0 8 | SMTP_PORT=1025 9 | QUICK_CREATE_ENABLED=true 10 | CRON_SECRET=1234567890abcdef1234567890abcdef1234 11 | INITIAL_ADMIN_EMAIL=initial.admin@rallly.co 12 | -------------------------------------------------------------------------------- /apps/landing/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@rallly/tsconfig/next.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "paths": { 6 | "@/*": ["src/*"], 7 | "~/*": ["public/*"] 8 | }, 9 | "checkJs": false, 10 | "strictNullChecks": true, 11 | "target": "ES2017" 12 | }, 13 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 14 | "exclude": ["node_modules", ".next"] 15 | } 16 | -------------------------------------------------------------------------------- /apps/web/src/contexts/poll.tsx: -------------------------------------------------------------------------------- 1 | import { useParams } from "next/navigation"; 2 | 3 | import { trpc } from "@/trpc/client"; 4 | 5 | export const usePoll = () => { 6 | const params = useParams<{ urlId: string }>(); 7 | const pollQuery = trpc.polls.get.useQuery({ urlId: params?.urlId as string }); 8 | 9 | if (!pollQuery.data) { 10 | throw new Error("Expected poll to be prefetched"); 11 | } 12 | 13 | return pollQuery.data; 14 | }; 15 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20251021132911_add_better_auth_admin_fields/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "sessions" ADD COLUMN "impersonated_by" TEXT; 3 | 4 | -- AlterTable 5 | ALTER TABLE "users" ADD COLUMN "ban_expires" TIMESTAMP(3); 6 | 7 | -- AddForeignKey 8 | ALTER TABLE "sessions" ADD CONSTRAINT "sessions_impersonated_by_fkey" FOREIGN KEY ("impersonated_by") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE; 9 | -------------------------------------------------------------------------------- /packages/languages/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rallly/languages", 3 | "version": "0.0.0", 4 | "private": true, 5 | "exports": { 6 | ".": "./src/index.ts", 7 | "./*": "./src/*.ts" 8 | }, 9 | "dependencies": { 10 | "@formatjs/intl-localematcher": "^0.6.0", 11 | "negotiator": "^1.0.0" 12 | }, 13 | "devDependencies": { 14 | "@rallly/tsconfig": "workspace:*", 15 | "@types/negotiator": "^0.6.3" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /apps/web/src/components/use-required-context.ts: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export const useRequiredContext = ( 4 | context: React.Context, 5 | errorMessage?: string, 6 | ) => { 7 | const contextValue = React.useContext(context); 8 | if (contextValue === null) { 9 | throw new Error( 10 | errorMessage ?? `Missing context provider: ${context.displayName}`, 11 | ); 12 | } 13 | return contextValue; 14 | }; 15 | -------------------------------------------------------------------------------- /apps/web/src/features/poll/query.ts: -------------------------------------------------------------------------------- 1 | import { prisma } from "@rallly/database"; 2 | 3 | export const hasPollAdminAccess = async (pollId: string, userId: string) => { 4 | const poll = await prisma.poll.findFirst({ 5 | where: { 6 | id: pollId, 7 | OR: [{ userId: userId }, { space: { members: { some: { userId } } } }], 8 | }, 9 | select: { 10 | id: true, 11 | }, 12 | }); 13 | 14 | return poll !== null; 15 | }; 16 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/control-panel/users/user-search-input.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { SearchInput } from "@/app/components/search-input"; 4 | import { useTranslation } from "@/i18n/client"; 5 | 6 | export function UserSearchInput() { 7 | const { t } = useTranslation(); 8 | return ( 9 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /apps/web/src/features/space/components/space-role.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Trans } from "@/components/trans"; 4 | import type { MemberRole } from "@/features/space/schema"; 5 | 6 | export const SpaceRole = ({ role }: { role: MemberRole }) => { 7 | switch (role) { 8 | case "admin": 9 | return ; 10 | case "member": 11 | return ; 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /packages/tsconfig/node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Node.js", 4 | "compilerOptions": { 5 | "lib": ["es2020"], 6 | "module": "NodeNext", 7 | "target": "es2020", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "moduleResolution": "NodeNext", 13 | "noUncheckedIndexedAccess": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/ui/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "src/styles/globals.css", 9 | "baseColor": "gray", 10 | "cssVariables": false, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@rallly/ui", 15 | "ui": "src", 16 | "utils": "@rallly/ui" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /apps/landing/src/lib/data.ts: -------------------------------------------------------------------------------- 1 | import "server-only"; 2 | 3 | import { prisma } from "@rallly/database"; 4 | import { unstable_cache } from "next/cache"; 5 | 6 | export const getUserCount = unstable_cache( 7 | async () => { 8 | const userCount = await prisma.user.count({ 9 | where: { 10 | isAnonymous: false, 11 | }, 12 | }); 13 | return userCount; 14 | }, 15 | ["user-count"], 16 | { 17 | revalidate: false, 18 | }, 19 | ); 20 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20250614062854_remove_deprecated_event_model/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the `events` table. If the table is not empty, all the data it contains will be lost. 5 | 6 | */ 7 | -- DropForeignKey 8 | ALTER TABLE "events" DROP CONSTRAINT "events_user_id_fkey"; 9 | 10 | -- DropForeignKey 11 | ALTER TABLE "polls" DROP CONSTRAINT "polls_event_id_fkey"; 12 | 13 | -- DropTable 14 | DROP TABLE "events"; 15 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20251110113931_non_nullable_space_id_susbcription/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropForeignKey 2 | ALTER TABLE "subscriptions" DROP CONSTRAINT "subscriptions_space_id_fkey"; 3 | 4 | -- AlterTable 5 | ALTER TABLE "subscriptions" ALTER COLUMN "space_id" SET NOT NULL; 6 | 7 | -- AddForeignKey 8 | ALTER TABLE "subscriptions" ADD CONSTRAINT "subscriptions_space_id_fkey" FOREIGN KEY ("space_id") REFERENCES "spaces"("id") ON DELETE CASCADE ON UPDATE CASCADE; 9 | -------------------------------------------------------------------------------- /apps/landing/src/types.ts: -------------------------------------------------------------------------------- 1 | import type React from "react"; 2 | 3 | import type { JSX } from "react"; 4 | 5 | export type ReactTag = keyof JSX.IntrinsicElements; 6 | 7 | export type PropsOf = TTag extends React.ElementType 8 | ? React.ComponentProps 9 | : never; 10 | 11 | export type Post = { 12 | slug: string; 13 | title: string; 14 | date: string; 15 | coverImage?: string; 16 | excerpt: string; 17 | content: string; 18 | }; 19 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/(auth)/login/components/or-divider.tsx: -------------------------------------------------------------------------------- 1 | import { getTranslation } from "@/i18n/server"; 2 | 3 | export async function OrDivider() { 4 | const { t } = await getTranslation(); 5 | return ( 6 |
7 |
8 |
{t("or")}
9 |
10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /apps/web/src/utils/get-value-by-path.ts: -------------------------------------------------------------------------------- 1 | export function getValueByPath>( 2 | obj: O, 3 | path: string, 4 | ): unknown { 5 | const pathArray = path.split("."); 6 | // biome-ignore lint/suspicious/noExplicitAny: Fix this later 7 | let curr: any = obj; 8 | for (const part of pathArray) { 9 | if (curr[part] === undefined) { 10 | return undefined; 11 | } 12 | curr = curr[part]; 13 | } 14 | return curr; 15 | } 16 | -------------------------------------------------------------------------------- /packages/emails/src/previews/new-poll.tsx: -------------------------------------------------------------------------------- 1 | import { previewEmailContext } from "../components/email-context"; 2 | import NewPollEmail from "../templates/new-poll"; 3 | 4 | export default function NewPollPreview() { 5 | return ( 6 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /packages/languages/src/languages.ts: -------------------------------------------------------------------------------- 1 | export const languages = { 2 | cs: "Česky", 3 | da: "Dansk", 4 | de: "Deutsch", 5 | en: "English", 6 | "en-GB": "English (UK)", 7 | es: "Español", 8 | fr: "Français", 9 | it: "Italiano", 10 | hu: "Magyar", 11 | nl: "Nederlands", 12 | no: "Norsk", 13 | pl: "Polski", 14 | pt: "Português", 15 | "pt-BR": "Português - Brasil", 16 | ru: "Русский", 17 | fi: "Suomi", 18 | sv: "Svenska", 19 | zh: "简体中文", 20 | }; 21 | -------------------------------------------------------------------------------- /packages/tailwind-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rallly/tailwind-config", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "tailwind.config.js", 6 | "types": "tailwind.config.d.ts", 7 | "dependencies": { 8 | "tailwindcss": "^3.4.17" 9 | }, 10 | "devDependencies": { 11 | "@tailwindcss/typography": "^0.5.13", 12 | "autoprefixer": "^10.4.13", 13 | "tailwind-scrollbar": "^3.0.4", 14 | "tailwindcss-animate": "^1.0.5" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rallly/utils", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "test:unit": "vitest run", 7 | "type-check": "tsc --noEmit" 8 | }, 9 | "exports": { 10 | "./*": "./src/*.ts" 11 | }, 12 | "dependencies": { 13 | "nanoid": "^5.0.9" 14 | }, 15 | "devDependencies": { 16 | "@rallly/tsconfig": "workspace:*", 17 | "@types/node": "^20.19.0", 18 | "vitest": "^4.0.16" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/invite/[urlId]/providers.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { LegacyPollContextProvider } from "@/components/poll/poll-context-provider"; 4 | import { VisibilityProvider } from "@/components/visibility"; 5 | 6 | export default function Providers({ children }: { children: React.ReactNode }) { 7 | return ( 8 | 9 | {children} 10 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /apps/web/src/features/calendars/services/types.ts: -------------------------------------------------------------------------------- 1 | export interface CalendarService { 2 | listCalendars: () => Promise; 3 | } 4 | 5 | export type CalendarInfo = { 6 | id: string; 7 | name: string; 8 | timeZone?: string; 9 | isPrimary: boolean; 10 | isSelected: boolean; 11 | isDeleted: boolean; 12 | isWritable: boolean; 13 | // biome-ignore lint/suspicious/noExplicitAny: any is used to store the raw data from the provider 14 | _rawData: any; 15 | }; 16 | -------------------------------------------------------------------------------- /apps/web/src/utils/get-registration-enabled.ts: -------------------------------------------------------------------------------- 1 | import "server-only"; 2 | 3 | import { getInstanceSettings } from "@/features/instance-settings/queries"; 4 | import { isFeatureEnabled } from "@/lib/feature-flags/server"; 5 | 6 | export async function getRegistrationEnabled() { 7 | if (!isFeatureEnabled("registration")) { 8 | return false; 9 | } 10 | const instanceSettings = await getInstanceSettings(); 11 | 12 | return !instanceSettings.disableUserRegistration; 13 | } 14 | -------------------------------------------------------------------------------- /apps/landing/src/components/blog/post-body.tsx: -------------------------------------------------------------------------------- 1 | import markdownStyles from "./markdown-styles.module.css"; 2 | 3 | type Props = { 4 | content: string; 5 | }; 6 | 7 | const PostBody = ({ content }: Props) => { 8 | return ( 9 |
14 | ); 15 | }; 16 | 17 | export default PostBody; 18 | -------------------------------------------------------------------------------- /apps/web/src/app/api/stripe/webhook/handlers/payment-method/detached.ts: -------------------------------------------------------------------------------- 1 | import type { Stripe } from "@rallly/billing"; 2 | import { prisma } from "@rallly/database"; 3 | 4 | export async function onPaymentMethodDetached(event: Stripe.Event) { 5 | const paymentMethod = event.data.object as Stripe.PaymentMethod; 6 | 7 | // Delete the payment method from our database 8 | await prisma.paymentMethod.delete({ 9 | where: { 10 | id: paymentMethod.id, 11 | }, 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | **Replace me with a summary of the change and which issue is fixed. Please also include relevant motivation and context.** 4 | 5 | ## Checklist 6 | 7 | Please check off all the following items with an "x" in the boxes before requesting a review. 8 | 9 | - [ ] I have performed a self-review of my code 10 | - [ ] My code follows the code style of this project 11 | - [ ] I have commented my code, particularly in hard-to-understand areas 12 | -------------------------------------------------------------------------------- /packages/emails/src/previews/new-participant-confirmation.tsx: -------------------------------------------------------------------------------- 1 | import { previewEmailContext } from "../components/email-context"; 2 | import { NewParticipantConfirmationEmail } from "../templates/new-participant-confirmation"; 3 | 4 | export default function NewParticipantConfirmationPreview() { 5 | return ( 6 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /packages/emails/src/previews/space-invite.tsx: -------------------------------------------------------------------------------- 1 | import { previewEmailContext } from "../components/email-context"; 2 | import { SpaceInviteEmail } from "../templates/space-invite"; 3 | 4 | export default function SpaceInvitePreview() { 5 | return ( 6 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /packages/emails/src/previews/abandoned-checkout.tsx: -------------------------------------------------------------------------------- 1 | import { previewEmailContext } from "../components/email-context"; 2 | import { AbandonedCheckoutEmail } from "../templates/abandoned-checkout"; 3 | 4 | export default function AbandonedCheckoutEmailPreview() { 5 | return ( 6 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /apps/web/src/features/licensing/helpers/calculate-checksum.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Calculate a checksum for a string 3 | * @param str The string to calculate the checksum for 4 | * @returns The checksum 5 | */ 6 | export function calculateChecksum(str: string): string { 7 | // Simple checksum: sum char codes, mod 100000, base36 8 | let sum = 0; 9 | for (let i = 0; i < str.length; i++) { 10 | sum += str.charCodeAt(i); 11 | } 12 | return (sum % 100000).toString(36).toUpperCase().padStart(5, "0"); 13 | } 14 | -------------------------------------------------------------------------------- /apps/web/src/components/poll/responsive-results.tsx: -------------------------------------------------------------------------------- 1 | import { createBreakpoint } from "react-use"; 2 | 3 | import DesktopPoll from "@/components/poll/desktop-poll"; 4 | import MobilePoll from "@/components/poll/mobile-poll"; 5 | 6 | const useBreakpoint = createBreakpoint({ list: 320, table: 640 }); 7 | 8 | export function ResponsiveResults() { 9 | const breakpoint = useBreakpoint(); 10 | const PollComponent = breakpoint === "table" ? DesktopPoll : MobilePoll; 11 | 12 | return ; 13 | } 14 | -------------------------------------------------------------------------------- /apps/web/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://openapi.vercel.sh/vercel.json", 3 | "installCommand": "pnpm install", 4 | "buildCommand": "cd ../.. && pnpm db:generate && pnpm build:web && pnpm db:deploy", 5 | "outputDirectory": ".next", 6 | "crons": [ 7 | { 8 | "path": "/api/house-keeping/delete-inactive-polls", 9 | "schedule": "0 6 * * *" 10 | }, 11 | { 12 | "path": "/api/house-keeping/remove-deleted-polls", 13 | "schedule": "30 6 * * *" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /packages/emails/src/previews/new-participant.tsx: -------------------------------------------------------------------------------- 1 | import { previewEmailContext } from "../components/email-context"; 2 | import { NewParticipantEmail } from "../templates/new-participant"; 3 | 4 | export default function NewParticipantPreview() { 5 | return ( 6 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /apps/web/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Rallly", 3 | "name": "Rallly", 4 | "icons": [ 5 | { 6 | "src": "android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "start_url": "/", 17 | "scope": "/", 18 | "display": "standalone", 19 | "background_color": "#F3F4F6", 20 | "theme_color": "#4F46E5" 21 | } 22 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/(space)/settings/preferences/actions.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { prisma } from "@rallly/database"; 4 | 5 | import { getUserIdIfLoggedIn } from "@/lib/auth"; 6 | 7 | export async function updateLocale(locale: string) { 8 | const userId = await getUserIdIfLoggedIn(); 9 | if (!userId) { 10 | throw new Error("User not found"); 11 | } 12 | await prisma.user.update({ 13 | where: { 14 | id: userId, 15 | }, 16 | data: { 17 | locale, 18 | }, 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /apps/web/src/app/api/stripe/webhook/handlers/customer/deleted.ts: -------------------------------------------------------------------------------- 1 | import type { Stripe } from "@rallly/billing"; 2 | import { prisma } from "@rallly/database"; 3 | 4 | export async function onCustomerDeleted(event: Stripe.Event) { 5 | const customer = event.data.object as Stripe.Customer; 6 | 7 | // Find and update the user with this customerId 8 | await prisma.user.updateMany({ 9 | where: { 10 | customerId: customer.id, 11 | }, 12 | data: { 13 | customerId: null, 14 | }, 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /packages/billing/src/lib/get-pricing.ts: -------------------------------------------------------------------------------- 1 | import { stripe } from ".."; 2 | 3 | export async function getPricing() { 4 | const prices = await stripe.prices.list({ 5 | lookup_keys: ["pro-monthly", "pro-yearly"], 6 | }); 7 | 8 | const [monthly, yearly] = prices.data; 9 | 10 | return { 11 | monthly: { 12 | currency: monthly.currency, 13 | price: monthly.unit_amount, 14 | }, 15 | yearly: { 16 | currency: yearly.currency, 17 | price: yearly.unit_amount, 18 | }, 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /packages/emails/src/previews/change-email-request.tsx: -------------------------------------------------------------------------------- 1 | import { previewEmailContext } from "../components/email-context"; 2 | import { ChangeEmailRequest } from "../templates/change-email-request"; 3 | 4 | export default function ChangeEmailRequestPreview() { 5 | return ( 6 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /apps/web/src/i18n/server.ts: -------------------------------------------------------------------------------- 1 | import { defaultNS } from "@/i18n/settings"; 2 | 3 | import { initI18next } from "./i18n"; 4 | import { getLocale } from "./server/get-locale"; 5 | 6 | export async function getTranslation(localeOverride?: string) { 7 | let locale = localeOverride; 8 | 9 | if (!locale) { 10 | locale = await getLocale(); 11 | } 12 | 13 | const { i18n } = await initI18next({ 14 | lng: locale, 15 | }); 16 | return { 17 | t: i18n.getFixedT(locale, defaultNS), 18 | i18n, 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /apps/web/src/trpc/routers/auth.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | import { decryptToken } from "@/utils/session"; 4 | import { publicProcedure, router } from "../trpc"; 5 | 6 | export const auth = router({ 7 | getUserPermission: publicProcedure 8 | .input(z.object({ token: z.string() })) 9 | .query(async ({ input }) => { 10 | const res = await decryptToken<{ userId: string }>(input.token); 11 | 12 | if (!res) { 13 | return null; 14 | } 15 | 16 | return res; 17 | }), 18 | }); 19 | -------------------------------------------------------------------------------- /apps/web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@rallly/tsconfig/next.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "paths": { 6 | "@/*": ["src/*"] 7 | }, 8 | "strictNullChecks": true, 9 | "types": ["vitest/globals"], 10 | "target": "ES2017" 11 | }, 12 | "include": [ 13 | "**/*.ts", 14 | "**/*.tsx", 15 | "**/*.js", 16 | ".next/types/**/*.ts", 17 | "vitest.config.mts" 18 | ], 19 | "exclude": ["node_modules", ".next/**/*", "playwright-report", "test-results"] 20 | } 21 | -------------------------------------------------------------------------------- /packages/emails/src/previews/event-canceled.tsx: -------------------------------------------------------------------------------- 1 | import { previewEmailContext } from "../components/email-context"; 2 | import { EventCanceledEmail } from "../templates/event-canceled"; 3 | 4 | export default function EventCanceledPreview() { 5 | return ( 6 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /packages/emails/src/previews/new-comment.tsx: -------------------------------------------------------------------------------- 1 | import { previewEmailContext } from "../components/email-context"; 2 | import { NewCommentEmail } from "../templates/new-comment"; 3 | 4 | function NewCommentEmailPreview() { 5 | return ( 6 | 13 | ); 14 | } 15 | 16 | export default NewCommentEmailPreview; 17 | -------------------------------------------------------------------------------- /apps/web/src/utils/grouped-time-zone.ts: -------------------------------------------------------------------------------- 1 | import { parseIanaTimezone } from "@/utils/date-time-utils"; 2 | import { supportedTimeZones } from "@/utils/supported-time-zones"; 3 | 4 | export const groupedTimeZones = supportedTimeZones.reduce( 5 | (acc, tz) => { 6 | const { region, city } = parseIanaTimezone(tz); 7 | if (!acc[region]) { 8 | acc[region] = []; 9 | } 10 | acc[region].push({ timezone: tz, city }); 11 | return acc; 12 | }, 13 | {} as Record, 14 | ); 15 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20251107131500_add_space_tier/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateEnum 2 | CREATE TYPE "space_tiers" AS ENUM ('hobby', 'pro'); 3 | 4 | -- AlterTable 5 | ALTER TABLE "spaces" ADD COLUMN "tier" "space_tiers" NOT NULL DEFAULT 'hobby'; 6 | 7 | -- Populate new column based on active subscriptions 8 | UPDATE "spaces" 9 | SET "tier" = 'pro' 10 | WHERE EXISTS ( 11 | SELECT 1 12 | FROM "subscriptions" 13 | WHERE "subscriptions"."space_id" = "spaces"."id" 14 | AND "subscriptions"."active" = true 15 | ); 16 | 17 | -------------------------------------------------------------------------------- /apps/web/src/features/subscription/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const subscriptionCheckoutMetadataSchema = z.object({ 4 | userId: z.string(), 5 | spaceId: z.string().optional(), 6 | }); 7 | 8 | export type SubscriptionCheckoutMetadata = z.infer< 9 | typeof subscriptionCheckoutMetadataSchema 10 | >; 11 | 12 | export const subscriptionMetadataSchema = z.object({ 13 | userId: z.string(), 14 | spaceId: z.string(), 15 | }); 16 | 17 | export type SubscriptionMetadata = z.infer; 18 | -------------------------------------------------------------------------------- /apps/web/src/features/credentials/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const credentialTypeSchema = z.enum(["oauth"]); 4 | export type CredentialType = z.infer; 5 | 6 | export const oauthCredentialsSchema = z.object({ 7 | accessToken: z.string(), 8 | refreshToken: z.string().optional(), 9 | expiresAt: z.string().optional(), 10 | scopes: z.array(z.string()), 11 | }); 12 | 13 | export type OAuthCredentials = z.infer; 14 | export type Credentials = OAuthCredentials; 15 | -------------------------------------------------------------------------------- /apps/web/src/features/user/schema.ts: -------------------------------------------------------------------------------- 1 | import type { TimeFormat } from "@prisma/client"; 2 | import z from "zod"; 3 | 4 | export const userRoleSchema = z.enum(["admin", "user"]); 5 | 6 | export type UserRole = z.infer; 7 | 8 | export type UserDTO = { 9 | id: string; 10 | name: string; 11 | image?: string; 12 | email: string; 13 | role: UserRole; 14 | isGuest: boolean; 15 | timeZone?: string; 16 | timeFormat?: TimeFormat; 17 | locale?: string; 18 | weekStart?: number; 19 | customerId?: string; 20 | }; 21 | -------------------------------------------------------------------------------- /apps/web/src/i18n/settings.ts: -------------------------------------------------------------------------------- 1 | import allLanguages from "@rallly/languages"; 2 | import type { InitOptions, Namespace } from "i18next"; 3 | 4 | export const fallbackLng = "en"; 5 | export const languages = Object.keys(allLanguages); 6 | export const defaultNS = "app"; 7 | 8 | export function getOptions( 9 | lng = fallbackLng, 10 | ns: Namespace = defaultNS, 11 | ): InitOptions { 12 | return { 13 | supportedLngs: languages, 14 | fallbackLng, 15 | lng, 16 | fallbackNS: defaultNS, 17 | defaultNS, 18 | ns, 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20220506105524_sessions_update/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `verificationCode` on the `Poll` table. All the data in the column will be lost. 5 | 6 | */ 7 | -- DropIndex 8 | DROP INDEX "Poll_urlId_verificationCode_key"; 9 | 10 | -- AlterTable 11 | ALTER TABLE "Comment" ADD COLUMN "guestId" TEXT; 12 | 13 | -- AlterTable 14 | ALTER TABLE "Participant" ADD COLUMN "guestId" TEXT; 15 | 16 | -- AlterTable 17 | ALTER TABLE "Poll" DROP COLUMN "verificationCode"; 18 | -------------------------------------------------------------------------------- /apps/landing/src/assets/github.svg: -------------------------------------------------------------------------------- 1 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /apps/web/vitest.config.mts: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | import { defineConfig } from "vitest/config"; 3 | import react from "@vitejs/plugin-react"; 4 | 5 | export default defineConfig({ 6 | plugins: [react()], 7 | test: { 8 | globals: true, 9 | environment: "jsdom", 10 | include: ["**/*.test.{ts,tsx}"], 11 | exclude: ["**/node_modules/**", "**/*.spec.ts"], 12 | setupFiles: ["./src/test/setup.ts"], 13 | css: true, 14 | }, 15 | resolve: { 16 | alias: { 17 | "@": path.resolve(__dirname, "./src"), 18 | }, 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /packages/emails/src/components/email-context.tsx: -------------------------------------------------------------------------------- 1 | import { i18nDefaultConfig, i18nInstance } from "../i18n"; 2 | import type { EmailContext } from "../types"; 3 | 4 | i18nInstance.init({ 5 | ...i18nDefaultConfig, 6 | initImmediate: true, 7 | }); 8 | 9 | export const previewEmailContext: EmailContext = { 10 | logoUrl: "https://d39ixtfgglw55o.cloudfront.net/images/rallly-logo-mark.png", 11 | baseUrl: "https://rallly.co", 12 | domain: "rallly.co", 13 | supportEmail: "support@rallly.co", 14 | i18n: i18nInstance, 15 | t: i18nInstance.getFixedT("en"), 16 | }; 17 | -------------------------------------------------------------------------------- /packages/tsconfig/react.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Next.js", 4 | "compilerOptions": { 5 | "module": "ESNext", 6 | "moduleResolution": "Bundler", 7 | "noPropertyAccessFromIndexSignature": false, 8 | "exactOptionalPropertyTypes": false, 9 | "noUncheckedIndexedAccess": false, 10 | "noImplicitReturns": false, 11 | "verbatimModuleSyntax": true, 12 | "skipLibCheck": true, 13 | "strictNullChecks": true, 14 | "lib": ["dom", "dom.iterable", "esnext"], 15 | "jsx": "react-jsx" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20251202151504_better_auth_joins/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - A unique constraint covering the columns `[identifier]` on the table `verifications` will be added. If there are existing duplicate values, this will fail. 5 | 6 | */ 7 | -- CreateIndex 8 | CREATE INDEX "accounts_user_id_idx" ON "accounts"("user_id"); 9 | 10 | -- CreateIndex 11 | CREATE UNIQUE INDEX "verifications_identifier_key" ON "verifications"("identifier"); 12 | 13 | -- CreateIndex 14 | CREATE INDEX "verifications_identifier_idx" ON "verifications"("identifier"); 15 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20250710102819_add_active_space_to_user/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "users" ADD COLUMN "active_space_id" TEXT; 3 | 4 | -- AddForeignKey 5 | ALTER TABLE "users" ADD CONSTRAINT "users_active_space_id_fkey" FOREIGN KEY ("active_space_id") REFERENCES "spaces"("id") ON DELETE SET NULL ON UPDATE CASCADE; 6 | 7 | -- Set active_space_id to each user's default space (where they are the owner) 8 | UPDATE "users" u 9 | SET "active_space_id" = ( 10 | SELECT s.id 11 | FROM "spaces" s 12 | WHERE s."owner_id" = u.id 13 | LIMIT 1 14 | ); 15 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20250602144531_add_instance_settings/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "instance_settings" ( 3 | "id" INTEGER NOT NULL DEFAULT 1, 4 | "disable_user_registration" BOOLEAN NOT NULL DEFAULT false, 5 | "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 6 | "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 7 | 8 | CONSTRAINT "instance_settings_pkey" PRIMARY KEY ("id") 9 | ); 10 | 11 | -- Create default instance settings 12 | INSERT INTO "instance_settings" ("id", "disable_user_registration") VALUES (1, false); 13 | -------------------------------------------------------------------------------- /packages/emails/src/previews/finalized-participant.tsx: -------------------------------------------------------------------------------- 1 | import { previewEmailContext } from "../components/email-context"; 2 | import { FinalizeParticipantEmail } from "../templates/finalized-participant"; 3 | 4 | export default function FinalizedParticipantPreview() { 5 | return ( 6 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /apps/web/public/static/yahoo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/web/src/trpc/types.ts: -------------------------------------------------------------------------------- 1 | export type RegistrationTokenPayload = { 2 | name: string; 3 | email: string; 4 | locale?: string; 5 | timeZone?: string; 6 | code: string; 7 | }; 8 | 9 | export type DisableNotificationsPayload = { 10 | pollId: string; 11 | watcherId: number; 12 | }; 13 | 14 | export type RegisteredUserSession = { 15 | isGuest: false; 16 | id: string; 17 | name: string; 18 | email: string; 19 | }; 20 | 21 | export type GuestUserSession = { 22 | isGuest: true; 23 | id: string; 24 | }; 25 | 26 | export type UserSession = GuestUserSession | RegisteredUserSession; 27 | -------------------------------------------------------------------------------- /crowdin.yml: -------------------------------------------------------------------------------- 1 | project_id_env: CROWDIN_PROJECT_ID 2 | api_token_env: CROWDIN_PERSONAL_TOKEN 3 | pull_request_title: "🌐 New Crowdin updates" 4 | commit_message: "[ci skip]" 5 | 6 | files: 7 | - source: /apps/web/public/locales/en/*.json 8 | translation: /apps/web/public/locales/%two_letters_code%/%original_file_name% 9 | - source: /apps/landing/public/locales/en/*.json 10 | translation: /apps/landing/public/locales/%two_letters_code%/%original_file_name% 11 | - source: /packages/emails/locales/en/*.json 12 | translation: /packages/emails/locales/%two_letters_code%/%original_file_name% 13 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20230117103853_update_indexes/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropIndex 2 | DROP INDEX "Participant_id_pollId_key"; 3 | 4 | -- DropIndex 5 | DROP INDEX "participants_id_poll_id_key"; 6 | 7 | -- CreateIndex 8 | CREATE INDEX "comments_poll_id_idx" ON "comments" USING HASH ("poll_id"); 9 | 10 | -- CreateIndex 11 | CREATE INDEX "options_poll_id_idx" ON "options" USING HASH ("poll_id"); 12 | 13 | -- CreateIndex 14 | CREATE INDEX "polls_user_id_idx" ON "polls" USING HASH ("user_id"); 15 | 16 | -- CreateIndex 17 | CREATE INDEX "votes_poll_id_idx" ON "votes" USING HASH ("poll_id"); 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | .next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | 25 | # local env files 26 | .env 27 | .env*.local 28 | 29 | # ts 30 | tsconfig.tsbuildinfo 31 | 32 | # Turbo 33 | .turbo 34 | 35 | # Jetbrains IDE 36 | .idea 37 | 38 | # Playwright 39 | playwright-report 40 | test-results 41 | .kiro 42 | .vercel 43 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20230704153346_user_payments/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateEnum 2 | CREATE TYPE "subscription_status" AS ENUM ('active', 'paused', 'deleted', 'trialing', 'past_due'); 3 | 4 | -- CreateTable 5 | CREATE TABLE "user_payment_data" ( 6 | "user_id" TEXT NOT NULL, 7 | "subscription_id" TEXT NOT NULL, 8 | "plan_id" TEXT NOT NULL, 9 | "end_date" TIMESTAMP(3) NOT NULL, 10 | "status" "subscription_status" NOT NULL, 11 | "update_url" TEXT NOT NULL, 12 | "cancel_url" TEXT NOT NULL, 13 | 14 | CONSTRAINT "user_payment_data_pkey" PRIMARY KEY ("user_id") 15 | ); 16 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20250616140359_space_members_migration/migration.sql: -------------------------------------------------------------------------------- 1 | -- Create space members with OWNER role for existing spaces 2 | INSERT INTO "space_members" ("id", "space_id", "user_id", "created_at", "updated_at", "role") 3 | SELECT 4 | gen_random_uuid(), 5 | id as space_id, 6 | owner_id as user_id, 7 | NOW() as created_at, 8 | NOW() as updated_at, 9 | 'OWNER' as role 10 | FROM "spaces" 11 | WHERE NOT EXISTS ( 12 | SELECT 1 FROM "space_members" 13 | WHERE "space_members"."space_id" = "spaces"."id" 14 | AND "space_members"."user_id" = "spaces"."owner_id" 15 | ); -------------------------------------------------------------------------------- /apps/landing/declarations/i18next.d.ts: -------------------------------------------------------------------------------- 1 | import "i18next"; 2 | 3 | import type blog from "../public/locales/en/blog.json"; 4 | import type common from "../public/locales/en/common.json"; 5 | import type home from "../public/locales/en/home.json"; 6 | import type pricing from "../public/locales/en/pricing.json"; 7 | 8 | declare module "i18next" { 9 | interface CustomTypeOptions { 10 | defaultNS: "common"; 11 | resources: { 12 | common: typeof common; 13 | home: typeof home; 14 | pricing: typeof pricing; 15 | blog: typeof blog; 16 | }; 17 | returnNull: false; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /apps/web/src/utils/try-catch.ts: -------------------------------------------------------------------------------- 1 | // Types for the result object with discriminated union 2 | type Success = { 3 | data: T; 4 | error: null; 5 | }; 6 | 7 | type Failure = { 8 | data: null; 9 | error: E; 10 | }; 11 | 12 | export type Result = Success | Failure; 13 | 14 | // Main wrapper function 15 | export async function tryCatch( 16 | promise: Promise, 17 | ): Promise> { 18 | try { 19 | const data = await promise; 20 | return { data, error: null }; 21 | } catch (error) { 22 | return { data: null, error: error as E }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /apps/landing/src/components/blog/post-header.tsx: -------------------------------------------------------------------------------- 1 | import DateFormatter from "./date-formatter"; 2 | 3 | type Props = { 4 | title: string; 5 | date: string; 6 | }; 7 | 8 | const PostHeader = ({ title, date }: Props) => { 9 | return ( 10 |
11 |

12 | {title} 13 |

14 |
15 | 16 |
17 |
18 | ); 19 | }; 20 | 21 | export default PostHeader; 22 | -------------------------------------------------------------------------------- /packages/database/prisma/seed.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client"; 2 | import { seedPolls } from "./seed/polls"; 3 | import { seedScheduledEvents } from "./seed/scheduled-events"; 4 | import { seedUsers } from "./seed/users"; 5 | 6 | const prisma = new PrismaClient(); 7 | 8 | async function main() { 9 | const users = await seedUsers(); 10 | 11 | for (const user of users) { 12 | await seedPolls(user.id); 13 | await seedScheduledEvents(user.id); 14 | } 15 | } 16 | 17 | main() 18 | .catch((e) => console.error(e)) 19 | .finally(async () => { 20 | await prisma.$disconnect(); 21 | }); 22 | -------------------------------------------------------------------------------- /apps/web/src/components/logo.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | 3 | const sizes = { 4 | sm: { 5 | width: 140, 6 | height: 22, 7 | }, 8 | md: { 9 | width: 150, 10 | height: 30, 11 | }, 12 | }; 13 | 14 | export const Logo = ({ 15 | className, 16 | size = "md", 17 | }: { 18 | className?: string; 19 | size?: keyof typeof sizes; 20 | }) => { 21 | return ( 22 | Rallly 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /apps/web/src/features/password/schema.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { z } from "zod"; 3 | import { 4 | calculatePasswordStrength, 5 | passwordQualityThresholds, 6 | } from "@/features/password/utils"; 7 | import { useTranslation } from "@/i18n/client"; 8 | 9 | export function usePasswordValidationSchema() { 10 | const { t } = useTranslation(); 11 | return z.string().refine( 12 | (password) => 13 | calculatePasswordStrength(password) >= passwordQualityThresholds.good, 14 | t("passwordTooWeak", { 15 | defaultValue: "Password is too weak. Please use a stronger password.", 16 | }), 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20250222172325_add_payment_method_table/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "payment_methods" ( 3 | "id" TEXT NOT NULL, 4 | "user_id" TEXT NOT NULL, 5 | "type" TEXT NOT NULL, 6 | "data" JSONB NOT NULL, 7 | "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 8 | "updated_at" TIMESTAMP(3) NOT NULL, 9 | 10 | CONSTRAINT "payment_methods_pkey" PRIMARY KEY ("id") 11 | ); 12 | 13 | -- AddForeignKey 14 | ALTER TABLE "payment_methods" ADD CONSTRAINT "payment_methods_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; 15 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20250901121851_add_ics_columns/migration.sql: -------------------------------------------------------------------------------- 1 | -- Step 1: Add sequence column and optional uid column 2 | ALTER TABLE "scheduled_events" ADD COLUMN "sequence" INTEGER NOT NULL DEFAULT 0; 3 | ALTER TABLE "scheduled_events" ADD COLUMN "uid" TEXT; 4 | 5 | -- Step 2: Populate uid column for existing rows 6 | UPDATE "scheduled_events" SET "uid" = "id" || '@rallly.co' WHERE "uid" IS NULL; 7 | 8 | -- Step 3: Make uid column non-nullable and add unique constraint 9 | ALTER TABLE "scheduled_events" ALTER COLUMN "uid" SET NOT NULL; 10 | CREATE UNIQUE INDEX "scheduled_events_uid_key" ON "scheduled_events"("uid"); 11 | -------------------------------------------------------------------------------- /.github/actions/setup-node/action.yml: -------------------------------------------------------------------------------- 1 | name: "Setup Node.js" 2 | description: "Sets up a consistent Node.js environment" 3 | inputs: 4 | node-version: 5 | description: "Node.js version" 6 | required: true 7 | default: "20" 8 | cache: 9 | description: "Package manager for caching" 10 | required: false 11 | default: "pnpm" 12 | runs: 13 | using: "composite" 14 | steps: 15 | - name: Setup pnpm 16 | uses: pnpm/action-setup@v4 17 | 18 | - name: Setup Node.js 19 | uses: actions/setup-node@v4 20 | with: 21 | node-version: ${{ inputs.node-version }} 22 | cache: ${{ inputs.cache }} 23 | -------------------------------------------------------------------------------- /apps/web/src/lib/errors.ts: -------------------------------------------------------------------------------- 1 | export type AppErrorCode = 2 | | "UNAUTHORIZED" 3 | | "NOT_FOUND" 4 | | "FORBIDDEN" 5 | | "INTERNAL_SERVER_ERROR" 6 | | "PAYMENT_REQUIRED" 7 | | "PAYLOAD_TOO_LARGE" 8 | | "SERVICE_UNAVAILABLE" 9 | | "TOO_MANY_REQUESTS"; 10 | 11 | export class AppError extends Error { 12 | code: AppErrorCode; 13 | constructor({ 14 | code, 15 | message, 16 | cause, 17 | }: { 18 | code: AppErrorCode; 19 | message: string; 20 | cause?: unknown; 21 | }) { 22 | super(`[${code}]: ${message}`); 23 | this.name = "AppError"; 24 | this.code = code; 25 | this.cause = cause; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /apps/web/tests/i18n.spec.ts: -------------------------------------------------------------------------------- 1 | import test, { expect } from "@playwright/test"; 2 | 3 | test("should show correct language if supported", async ({ browser }) => { 4 | const context = await browser.newContext({ locale: "de" }); 5 | const page = await context.newPage(); 6 | await page.goto("/new"); 7 | await expect(page.locator("text=Titel")).toBeVisible(); 8 | }); 9 | 10 | test("should default to english", async ({ browser }) => { 11 | const context = await browser.newContext({ locale: "mt" }); 12 | const page = await context.newPage(); 13 | await page.goto("/new"); 14 | await expect(page.locator("text=Title")).toBeVisible(); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20250614110551_create_spaces/migration.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO spaces (id, name, owner_id, created_at, updated_at) 2 | SELECT gen_random_uuid(), 'Personal', id, NOW(), NOW() 3 | FROM users 4 | WHERE NOT EXISTS ( 5 | SELECT 1 FROM spaces WHERE spaces.owner_id = users.id 6 | ) ON CONFLICT DO NOTHING; 7 | 8 | -- Set space_id for polls 9 | UPDATE polls 10 | SET space_id = spaces.id 11 | FROM spaces 12 | WHERE polls.user_id = spaces.owner_id AND polls.space_id IS NULL; 13 | 14 | UPDATE subscriptions 15 | SET space_id = spaces.id 16 | FROM spaces 17 | WHERE subscriptions.user_id = spaces.owner_id AND subscriptions.space_id IS NULL; -------------------------------------------------------------------------------- /apps/web/src/components/forms/poll-options-form/types.ts: -------------------------------------------------------------------------------- 1 | export type DateOption = { 2 | type: "date"; 3 | date: string; 4 | }; 5 | 6 | export type TimeOption = { 7 | type: "timeSlot"; 8 | start: string; 9 | duration: number; 10 | end: string; 11 | }; 12 | 13 | export type DateTimeOption = DateOption | TimeOption; 14 | 15 | export interface DateTimePickerProps { 16 | title?: string; 17 | options: DateTimeOption[]; 18 | date?: Date; 19 | onNavigate: (date: Date) => void; 20 | onChange: (options: DateTimeOption[]) => void; 21 | duration: number; 22 | onChangeDuration: (duration: number) => void; 23 | scrollToTime?: Date; 24 | } 25 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20220512093441_add_no_votes/migration.sql: -------------------------------------------------------------------------------- 1 | -- Previously we only stored "yes" and "ifNeedBe" votes and assumed missing votes are "no" 2 | -- Since we want to differentiate between "no" and did not vote yet we want to include "no" votes 3 | -- in this table 4 | INSERT INTO votes (id, poll_id, participant_id, option_id, type) 5 | SELECT nanoid(), poll_id, participant_id, option_id, 'no' 6 | FROM ( 7 | SELECT o.poll_id, p.id participant_id, o.id option_id 8 | FROM options o 9 | JOIN participants p ON o.poll_id = p.poll_id 10 | EXCEPT 11 | SELECT poll_id, participant_id, option_id 12 | FROM votes 13 | ) AS missing; 14 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20250226173250_remove_paddle_subscriptions/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropTable 2 | DROP TABLE "user_payment_data"; 3 | 4 | -- AlterEnum 5 | BEGIN; 6 | CREATE TYPE "subscription_status_new" AS ENUM ('incomplete', 'incomplete_expired', 'active', 'paused', 'trialing', 'past_due', 'canceled', 'unpaid'); 7 | ALTER TABLE "subscriptions" ALTER COLUMN "status" TYPE "subscription_status_new" USING ("status"::text::"subscription_status_new"); 8 | ALTER TYPE "subscription_status" RENAME TO "subscription_status_old"; 9 | ALTER TYPE "subscription_status_new" RENAME TO "subscription_status"; 10 | DROP TYPE "subscription_status_old"; 11 | COMMIT; -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20250602181052_add_instance_settings_constraints/migration.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "instance_settings" 2 | ADD CONSTRAINT instance_settings_singleton CHECK (id = 1); 3 | 4 | CREATE OR REPLACE FUNCTION prevent_delete_instance_settings() 5 | RETURNS TRIGGER AS $$ 6 | BEGIN 7 | IF OLD.id = 1 THEN 8 | RAISE EXCEPTION 'Deleting the instance_settings record (id=1) is not permitted.'; 9 | END IF; 10 | RETURN OLD; 11 | END; 12 | $$ LANGUAGE plpgsql; 13 | 14 | CREATE TRIGGER trg_prevent_instance_settings_deletion 15 | BEFORE DELETE ON instance_settings 16 | FOR EACH ROW EXECUTE FUNCTION prevent_delete_instance_settings(); 17 | -------------------------------------------------------------------------------- /apps/web/src/app/api/auth/invalid-session/route.ts: -------------------------------------------------------------------------------- 1 | import type { NextRequest } from "next/server"; 2 | import { NextResponse } from "next/server"; 3 | import auth from "@/lib/auth"; 4 | import { signOut as legacySignOut } from "@/next-auth"; 5 | 6 | export async function GET(req: NextRequest) { 7 | try { 8 | await Promise.all([ 9 | legacySignOut({ 10 | redirect: false, 11 | }), 12 | auth.api.signOut({ 13 | headers: req.headers, 14 | }), 15 | ]); 16 | } catch (error) { 17 | console.error("FAILED TO SIGN OUT", error); 18 | } 19 | 20 | return NextResponse.redirect(new URL("/login", req.url)); 21 | } 22 | -------------------------------------------------------------------------------- /apps/web/src/components/maintenance.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | 3 | const Maintenance: React.FunctionComponent = () => { 4 | return ( 5 |
6 | 7 | Down for maintenance - Be right back 8 | 9 |
10 |
11 | The site is currently down for some maintenance and will be back 12 | shortly… 13 |
14 |
15 |
16 | ); 17 | }; 18 | 19 | export default Maintenance; 20 | -------------------------------------------------------------------------------- /apps/web/src/features/user/queries.ts: -------------------------------------------------------------------------------- 1 | import { prisma } from "@rallly/database"; 2 | 3 | export const getUserCount = async () => { 4 | return await prisma.user.count(); 5 | }; 6 | 7 | export const getUserHasPassword = async (userId: string) => { 8 | const account = await prisma.account.findFirst({ 9 | where: { 10 | userId, 11 | provider: "credential", 12 | }, 13 | }); 14 | return !!account; 15 | }; 16 | 17 | export const getUserHasNoAccounts = async (userId: string) => { 18 | const accountCount = await prisma.account.count({ 19 | where: { 20 | userId, 21 | }, 22 | }); 23 | return accountCount === 0; 24 | }; 25 | -------------------------------------------------------------------------------- /packages/emails/src/previews/finalized-host.tsx: -------------------------------------------------------------------------------- 1 | import { previewEmailContext } from "../components/email-context"; 2 | import { FinalizeHostEmail } from "../templates/finalized-host"; 3 | 4 | export default function FinalizedHostPreview() { 5 | return ( 6 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /apps/landing/public/locales/fa/common.json: -------------------------------------------------------------------------------- 1 | { 2 | "login": "ورود", 3 | "links": "پیوندها", 4 | "blog": "وبلاگ", 5 | "discussions": "بحث‌وگفتگو", 6 | "footerCredit": "ساخته‌شده توسط @imlukevella", 7 | "footerSponsor": "این پروژه توسط کاربران تأمین مالی می‌شود. لطفا با حمایت مالی از آن پشتیبانی کنید.", 8 | "language": "زبان", 9 | "poweredBy": "قدرت گرفته از", 10 | "privacyPolicy": "سیاست رازداری", 11 | "support": "پشتیبانی", 12 | "volunteerTranslator": "کمک به ترجمه‌ی این سایت", 13 | "notFoundTitle": "خطای ۴۰۴ - پیدا نشد", 14 | "notFoundDescription": "متأسفانه صفحه مورد نظر شما یافت نشد.", 15 | "goToHome": "رفتن به خانه" 16 | } 17 | -------------------------------------------------------------------------------- /apps/landing/src/i18n/server.ts: -------------------------------------------------------------------------------- 1 | import type { Namespace } from "i18next"; 2 | import { defaultNS, fallbackLng } from "@/i18n/settings"; 3 | import { i18next } from "./i18next"; 4 | 5 | export async function getTranslation( 6 | locale: string = fallbackLng, 7 | ns: string | string[] = defaultNS, 8 | ) { 9 | const lng = locale; 10 | if (lng && i18next.resolvedLanguage !== lng) { 11 | await i18next.changeLanguage(lng); 12 | } 13 | 14 | if (ns && !i18next.hasLoadedNamespace(ns)) { 15 | await i18next.loadNamespaces(ns); 16 | } 17 | return { 18 | t: i18next.getFixedT(lng), 19 | i18n: i18next, 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/(space)/(dashboard)/components/space-sidebar-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { SidebarProvider } from "@rallly/ui/sidebar"; 4 | import { useLocalStorage } from "react-use"; 5 | 6 | export function SpaceSidebarProvider({ 7 | children, 8 | }: { 9 | children: React.ReactNode; 10 | }) { 11 | const [value, setValue] = useLocalStorage("sidebar_state", "expanded"); 12 | return ( 13 | { 16 | setValue(open ? "expanded" : "collapsed"); 17 | }} 18 | > 19 | {children} 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /apps/web/src/components/login-link.tsx: -------------------------------------------------------------------------------- 1 | import type { LinkProps } from "next/link"; 2 | import Link from "next/link"; 3 | import { usePathname } from "next/navigation"; 4 | import React from "react"; 5 | 6 | export const LoginLink = React.forwardRef< 7 | HTMLAnchorElement, 8 | React.PropsWithChildren & { className?: string }> 9 | >(function LoginLink({ children, ...props }, ref) { 10 | const pathname = usePathname() ?? "/"; 11 | return ( 12 | 17 | {children} 18 | 19 | ); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/database/index.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client"; 2 | 3 | export type * from "@prisma/client"; 4 | 5 | const prismaClientSingleton = () => { 6 | return new PrismaClient(); 7 | }; 8 | 9 | export type ExtendedPrismaClient = ReturnType; 10 | 11 | // biome-ignore lint/suspicious/noShadowRestrictedNames: Fix this later 12 | declare const globalThis: { 13 | prismaGlobal: ExtendedPrismaClient; 14 | } & typeof global; 15 | 16 | const prisma = globalThis.prismaGlobal ?? prismaClientSingleton(); 17 | 18 | export { prisma }; 19 | 20 | if (process.env.NODE_ENV !== "production") globalThis.prismaGlobal = prisma; 21 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - "v*" 5 | 6 | name: Create Release 7 | 8 | jobs: 9 | build: 10 | name: Create Release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v4 15 | 16 | - name: Trigger deploy to production 17 | run: | 18 | curl -X POST -d {} ${{ secrets.DEPLOY_HOOK }} 19 | 20 | - name: Create Release 21 | uses: ncipollo/release-action@v1 22 | with: 23 | token: ${{ secrets.GITHUB_TOKEN }} 24 | draft: true 25 | generateReleaseNotes: true 26 | makeLatest: true 27 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/control-panel/nav-item.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { SidebarMenuButton, SidebarMenuItem } from "@rallly/ui/sidebar"; 4 | import Link from "next/link"; 5 | import { usePathname } from "next/navigation"; 6 | 7 | export function NavItem({ 8 | href, 9 | children, 10 | }: { 11 | href: string; 12 | children: React.ReactNode; 13 | }) { 14 | const pathname = usePathname(); 15 | const isActive = pathname === href; 16 | 17 | return ( 18 | 19 | 20 | {children} 21 | 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /apps/web/src/components/forms/types.ts: -------------------------------------------------------------------------------- 1 | import type { PollSettingsFormData } from "@/components/forms/poll-settings"; 2 | 3 | import type { PollDetailsData } from "./poll-details-form"; 4 | import type { PollOptionsData } from "./poll-options-form/poll-options-form"; 5 | 6 | export type NewEventData = PollDetailsData & 7 | PollOptionsData & 8 | PollSettingsFormData; 9 | 10 | // biome-ignore lint/suspicious/noExplicitAny: Fix this later 11 | export interface PollFormProps> { 12 | onSubmit?: (data: T) => void; 13 | onChange?: (data: Partial) => void; 14 | defaultValues?: Partial; 15 | name?: string; 16 | className?: string; 17 | } 18 | -------------------------------------------------------------------------------- /apps/web/src/features/calendars/service.ts: -------------------------------------------------------------------------------- 1 | import { GoogleCalendarService } from "@/features/calendars/services/google-calendar"; 2 | 3 | type CalendarServiceProvider = { 4 | provider: string; 5 | credentials: unknown; 6 | }; 7 | 8 | export const createCalendarService = async ( 9 | service: CalendarServiceProvider, 10 | ) => { 11 | switch (service.provider) { 12 | case "google": 13 | return new GoogleCalendarService({ 14 | credentials: GoogleCalendarService.credentialsSchema.parse( 15 | service.credentials, 16 | ), 17 | }); 18 | default: 19 | throw new Error(`Unsupported provider: ${service.provider}`); 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /apps/web/src/lib/storage/s3.ts: -------------------------------------------------------------------------------- 1 | import { S3Client } from "@aws-sdk/client-s3"; 2 | 3 | import { env } from "@/env"; 4 | 5 | export function getS3Client() { 6 | if ( 7 | !env.S3_BUCKET_NAME || 8 | !env.S3_ACCESS_KEY_ID || 9 | !env.S3_SECRET_ACCESS_KEY 10 | ) { 11 | return null; 12 | } 13 | 14 | const s3Client = new S3Client({ 15 | region: env.S3_REGION, 16 | endpoint: env.S3_ENDPOINT, 17 | credentials: { 18 | accessKeyId: env.S3_ACCESS_KEY_ID, 19 | secretAccessKey: env.S3_SECRET_ACCESS_KEY, 20 | }, 21 | // S3 compatible storage requires path style 22 | forcePathStyle: true, 23 | }); 24 | 25 | return s3Client; 26 | } 27 | -------------------------------------------------------------------------------- /packages/posthog/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rallly/posthog", 3 | "version": "0.0.0", 4 | "private": true, 5 | "exports": { 6 | "./server": "./src/server.ts", 7 | "./client": "./src/client.tsx", 8 | "./utils": "./src/utils.ts" 9 | }, 10 | "scripts": { 11 | "type-check": "tsc --noEmit" 12 | }, 13 | "dependencies": { 14 | "js-cookie": "^3.0.1", 15 | "posthog-js": "^1.290.0", 16 | "posthog-node": "^5.11.2", 17 | "react": "^19.1.4" 18 | }, 19 | "devDependencies": { 20 | "@rallly/tsconfig": "workspace:*", 21 | "@types/js-cookie": "^3.0.1", 22 | "@types/node": "^20.19.0", 23 | "@types/react": "19.1.2" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /apps/landing/src/i18n/settings.ts: -------------------------------------------------------------------------------- 1 | import allLanguages from "@rallly/languages"; 2 | import type { InitOptions, Namespace } from "i18next"; 3 | 4 | export const fallbackLng = "en"; 5 | export const languages = Object.keys(allLanguages); 6 | export const defaultNS = "common"; 7 | export const cookieName = "i18next"; 8 | export const headerName = "x-i18next-current-language"; 9 | 10 | export function getOptions( 11 | lng = fallbackLng, 12 | ns: Namespace = defaultNS, 13 | ): InitOptions { 14 | return { 15 | // debug: true, 16 | supportedLngs: languages, 17 | fallbackLng, 18 | lng, 19 | fallbackNS: defaultNS, 20 | defaultNS, 21 | ns, 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /apps/web/src/features/licensing/actions.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { prisma } from "@rallly/database"; 4 | import { adminActionClient } from "@/lib/safe-action/server"; 5 | 6 | export const removeInstanceLicenseAction = adminActionClient 7 | .metadata({ 8 | actionName: "remove_instance_license", 9 | }) 10 | .action(async () => { 11 | try { 12 | await prisma.instanceLicense.deleteMany(); 13 | } catch (_error) { 14 | return { 15 | success: false, 16 | message: "Failed to delete license", 17 | }; 18 | } 19 | 20 | return { 21 | success: true, 22 | message: "License deleted successfully", 23 | }; 24 | }); 25 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/(optional-space)/new/back-button.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Button } from "@rallly/ui/button"; 4 | import { Icon } from "@rallly/ui/icon"; 5 | import { ArrowLeftIcon } from "lucide-react"; 6 | import { useRouter } from "next/navigation"; 7 | 8 | import { Trans } from "@/components/trans"; 9 | 10 | export function BackButton() { 11 | const router = useRouter(); 12 | 13 | return ( 14 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /apps/landing/src/app/[locale]/(main)/nav-link.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { cn } from "@rallly/ui"; 4 | import { Button } from "@rallly/ui/button"; 5 | import { usePathname } from "next/navigation"; 6 | import { LinkBase } from "@/i18n/client/link"; 7 | 8 | export const NavLink = ({ 9 | className, 10 | ...props 11 | }: React.ComponentProps) => { 12 | const pathname = usePathname(); 13 | const isActive = pathname === props.href; 14 | return ( 15 | 22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /apps/web/src/components/image-upload/types.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const allowedMimeTypes = z.enum(["image/jpeg", "image/png"]); 4 | export type AllowedMimeType = z.infer; 5 | 6 | export interface ImageUploadControlProps { 7 | keyPrefix: "avatars" | "spaces"; 8 | onUploadSuccess: (imageKey: string) => Promise | void; 9 | onRemoveSuccess: () => Promise | void; 10 | hasCurrentImage?: boolean; 11 | } 12 | 13 | export interface ImageUploadPreviewProps { 14 | className?: string; 15 | children?: React.ReactNode; 16 | } 17 | 18 | export interface ImageUploadProps { 19 | className?: string; 20 | children?: React.ReactNode; 21 | } 22 | -------------------------------------------------------------------------------- /apps/web/src/components/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@rallly/ui"; 2 | import type React from "react"; 3 | 4 | export const Skeleton = (props: { 5 | className?: string; 6 | children?: React.ReactNode; 7 | }) => { 8 | return ( 9 |
12 | {props.children} 13 |
14 | ); 15 | }; 16 | 17 | export function SkeletonCard({ 18 | children, 19 | className, 20 | }: { 21 | children?: React.ReactNode; 22 | className?: string; 23 | }) { 24 | return ( 25 |
26 | {children} 27 |
28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/control-panel/control-panel-sidebar-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { SidebarProvider } from "@rallly/ui/sidebar"; 4 | import { useLocalStorage } from "react-use"; 5 | 6 | export function ControlPanelSidebarProvider({ 7 | children, 8 | }: { 9 | children: React.ReactNode; 10 | }) { 11 | const [value, setValue] = useLocalStorage( 12 | "control-panel-sidebar_state", 13 | "expanded", 14 | ); 15 | return ( 16 | { 19 | setValue(open ? "expanded" : "collapsed"); 20 | }} 21 | > 22 | {children} 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /apps/web/src/features/instance-settings/client.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React from "react"; 4 | import type { InstanceSettings } from "./schema"; 5 | 6 | const InstanceSettingsContext = React.createContext({ 7 | disableUserRegistration: false, 8 | }); 9 | 10 | export function InstanceSettingsProvider({ 11 | value, 12 | children, 13 | }: { 14 | value: InstanceSettings; 15 | children: React.ReactNode; 16 | }) { 17 | return ( 18 | 19 | {children} 20 | 21 | ); 22 | } 23 | 24 | export const useInstanceSettings = () => 25 | React.useContext(InstanceSettingsContext); 26 | -------------------------------------------------------------------------------- /apps/web/tests/invite-page.ts: -------------------------------------------------------------------------------- 1 | import type { Page } from "@playwright/test"; 2 | 3 | export class InvitePage { 4 | constructor(public readonly page: Page) {} 5 | 6 | async addParticipant(name: string, email?: string) { 7 | const page = this.page; 8 | 9 | await page.locator("data-testid=vote-selector >> nth=0").click(); 10 | await page.locator("data-testid=vote-selector >> nth=2").click(); 11 | await page.click("button >> text='Continue'"); 12 | 13 | await page.type('[placeholder="Jessie Smith"]', name); 14 | if (email) { 15 | await page.type('[placeholder="jessie.smith@example.com"]', email); 16 | } 17 | 18 | await page.click("text='Submit'"); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20231117153753_add_nextauth_provider_accounts/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "Account" ( 3 | "id" TEXT NOT NULL, 4 | "userId" TEXT NOT NULL, 5 | "type" TEXT NOT NULL, 6 | "provider" TEXT NOT NULL, 7 | "providerAccountId" TEXT NOT NULL, 8 | "refresh_token" TEXT, 9 | "access_token" TEXT, 10 | "expires_at" INTEGER, 11 | "token_type" TEXT, 12 | "scope" TEXT, 13 | "id_token" TEXT, 14 | "session_state" TEXT, 15 | 16 | CONSTRAINT "Account_pkey" PRIMARY KEY ("id") 17 | ); 18 | 19 | -- CreateIndex 20 | CREATE UNIQUE INDEX "Account_provider_providerAccountId_key" ON "Account"("provider", "providerAccountId"); 21 | -------------------------------------------------------------------------------- /packages/ui/src/hooks/use-mobile.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | const MOBILE_BREAKPOINT = 768; 4 | 5 | export function useIsMobile() { 6 | const [isMobile, setIsMobile] = React.useState( 7 | undefined, 8 | ); 9 | 10 | React.useEffect(() => { 11 | const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`); 12 | const onChange = () => { 13 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); 14 | }; 15 | mql.addEventListener("change", onChange); 16 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); 17 | return () => mql.removeEventListener("change", onChange); 18 | }, []); 19 | 20 | return !!isMobile; 21 | } 22 | -------------------------------------------------------------------------------- /apps/landing/public/locales/vi/common.json: -------------------------------------------------------------------------------- 1 | { 2 | "login": "Đăng nhập", 3 | "links": "Liên kết", 4 | "blog": "Blog", 5 | "discussions": "Thảo luận", 6 | "footerCredit": "Làm bởi imlukevella", 7 | "footerSponsor": "Dự án này do người dùng tài trợ. Vui lòng xem xét hỗ trợ nó bằng cách đóng góp.", 8 | "language": "Ngôn ngữ", 9 | "poweredBy": "Chạy trên nền tảng", 10 | "privacyPolicy": "Chính sách bảo mật", 11 | "support": "Hỗ trợ", 12 | "volunteerTranslator": "Giúp dịch trang này", 13 | "notFoundTitle": "Không tìm thấy trang 404", 14 | "notFoundDescription": "Chúng tôi không thấy trang bạn đang tìm.", 15 | "goToHome": "Về trang chủ", 16 | "getStarted": "Bắt đầu" 17 | } 18 | -------------------------------------------------------------------------------- /apps/landing/public/static/images/pcmag-logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/admin-setup/make-me-admin-button.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Button } from "@rallly/ui/button"; 4 | import { Trans } from "@/components/trans"; 5 | import { useSafeAction } from "@/lib/safe-action/client"; 6 | import { makeMeAdminAction } from "./actions"; 7 | 8 | export function MakeMeAdminButton() { 9 | const makeMeAdmin = useSafeAction(makeMeAdminAction); 10 | return ( 11 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /apps/web/src/utils/api-auth.ts: -------------------------------------------------------------------------------- 1 | import { headers } from "next/headers"; 2 | import { NextResponse } from "next/server"; 3 | 4 | /** 5 | * Checks if the request is authorized using the cron secret for Vercel cron jobs 6 | * @returns A NextResponse with 401 status if unauthorized, or null if authorized 7 | */ 8 | export async function checkApiAuthorization() { 9 | const headersList = await headers(); 10 | const authorization = headersList.get("authorization"); 11 | 12 | if (authorization !== `Bearer ${process.env.CRON_SECRET}`) { 13 | return NextResponse.json( 14 | { success: false, message: "Unauthorized" }, 15 | { status: 401 }, 16 | ); 17 | } 18 | 19 | return null; 20 | } 21 | -------------------------------------------------------------------------------- /apps/web/i18next-scanner.config.js: -------------------------------------------------------------------------------- 1 | const typescriptTransform = require("i18next-scanner-typescript"); 2 | 3 | module.exports = { 4 | input: ["src/**/*.{ts,tsx}", "!src/next-auth*.ts"], 5 | options: { 6 | nsSeparator: false, 7 | defaultNs: "app", 8 | defaultValue: "__STRING_NOT_TRANSLATED__", 9 | lngs: ["en"], 10 | ns: ["app"], 11 | plural: false, 12 | removeUnusedKeys: true, 13 | func: { 14 | list: ["t"], 15 | }, 16 | resource: { 17 | loadPath: "public/locales/{{lng}}/{{ns}}.json", 18 | savePath: "public/locales/{{lng}}/{{ns}}.json", 19 | }, 20 | }, 21 | format: "json", 22 | fallbackLng: "en", 23 | transform: typescriptTransform(), 24 | }; 25 | -------------------------------------------------------------------------------- /apps/web/src/components/poll/poll-footer.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | 3 | import { Trans } from "@/components/trans"; 4 | 5 | export function PollFooter() { 6 | return ( 7 |
8 | 18 | ), 19 | }} 20 | /> 21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /apps/web/src/types.ts: -------------------------------------------------------------------------------- 1 | import type React from "react"; 2 | import type { JSX } from "react"; 3 | 4 | export type ReactTag = keyof JSX.IntrinsicElements; 5 | 6 | export type PropsOf = TTag extends React.ElementType 7 | ? React.ComponentProps 8 | : never; 9 | 10 | export type PropsWithClassName> = 11 | React.PropsWithChildren & { 12 | className?: string; 13 | }; 14 | 15 | // biome-ignore lint/complexity/noBannedTypes: Fix this later 16 | export type IconComponent = {}> = 17 | React.ComponentType< 18 | React.PropsWithChildren & { 19 | className?: string; 20 | } 21 | >; 22 | -------------------------------------------------------------------------------- /apps/landing/src/components/login-button.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { usePostHog } from "@rallly/posthog/client"; 4 | import { Button } from "@rallly/ui/button"; 5 | import Link from "next/link"; 6 | import { Trans } from "@/i18n/client/trans"; 7 | import { linkToApp } from "@/lib/linkToApp"; 8 | 9 | export const LoginButton = () => { 10 | const posthog = usePostHog(); 11 | return ( 12 | 23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /apps/web/src/features/calendars/components/calendar-provider-icon.tsx: -------------------------------------------------------------------------------- 1 | import GoogleCalendarIcon from "@/features/calendars/assets/google-calendar.svg"; 2 | import OutlookIcon from "@/features/calendars/assets/outlook.svg"; 3 | 4 | export function CalendarProviderIcon({ 5 | provider, 6 | size, 7 | }: { 8 | provider: string; 9 | size: number; 10 | }) { 11 | switch (provider) { 12 | case "google": 13 | return ( 14 | 15 | ); 16 | case "microsoft": 17 | return ( 18 | 19 | ); 20 | default: 21 | return null; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /apps/docs/contact/support.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Support 3 | description: Get in touch with us if you need help with anything 4 | --- 5 | 6 | If you have any questions, feedback or suggestions, please get in touch. We'd love to hear from you! 7 | 8 | 9 | 14 | Send an email 15 | 16 | 21 | Join Discord 22 | 23 | 24 | 25 | 26 | Check out the [FAQ](/faq) page to see if your question has already been 27 | answered. 28 | 29 | -------------------------------------------------------------------------------- /apps/landing/src/components/sign-up-button.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { usePostHog } from "@rallly/posthog/client"; 4 | import { Button } from "@rallly/ui/button"; 5 | import Link from "next/link"; 6 | import { Trans } from "@/i18n/client/trans"; 7 | import { linkToApp } from "@/lib/linkToApp"; 8 | 9 | export function SignUpButton() { 10 | const posthog = usePostHog(); 11 | return ( 12 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /apps/web/src/trpc/client/types.ts: -------------------------------------------------------------------------------- 1 | import type { PollStatus, VoteType } from "@rallly/database"; 2 | 3 | export type GetPollApiResponse = { 4 | id: string; 5 | title: string; 6 | location: string | null; 7 | description: string | null; 8 | options: { id: string; startTime: Date; duration: number }[]; 9 | user: { 10 | id: string; 11 | name: string; 12 | email: string | null; 13 | image: string | null; 14 | banned: boolean; 15 | } | null; 16 | timeZone: string | null; 17 | adminUrlId: string; 18 | status: PollStatus; 19 | participantUrlId: string; 20 | createdAt: Date; 21 | deleted: boolean; 22 | }; 23 | 24 | export type Vote = { 25 | optionId: string; 26 | type: VoteType; 27 | }; 28 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20250731100421_update_subscription_interval_enum/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - The values [day,week] on the enum `subscription_interval` will be removed. If these variants are still used in the database, this will fail. 5 | 6 | */ 7 | -- AlterEnum 8 | BEGIN; 9 | CREATE TYPE "subscription_interval_new" AS ENUM ('month', 'year'); 10 | ALTER TABLE "subscriptions" ALTER COLUMN "interval" TYPE "subscription_interval_new" USING ("interval"::text::"subscription_interval_new"); 11 | ALTER TYPE "subscription_interval" RENAME TO "subscription_interval_old"; 12 | ALTER TYPE "subscription_interval_new" RENAME TO "subscription_interval"; 13 | DROP TYPE "subscription_interval_old"; 14 | COMMIT; 15 | -------------------------------------------------------------------------------- /packages/emails/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { TFunction } from "i18next"; 2 | 3 | import type { I18nInstance } from "./i18n"; 4 | import type { EmailTemplates } from "./templates"; 5 | 6 | export type EmailContext = { 7 | logoUrl: string; 8 | baseUrl: string; 9 | domain: string; 10 | supportEmail: string; 11 | i18n: I18nInstance; 12 | t: TFunction; 13 | }; 14 | 15 | export type TemplateName = keyof EmailTemplates; 16 | 17 | export type TemplateProps = Omit< 18 | React.ComponentProps, 19 | "ctx" 20 | >; 21 | 22 | export type TemplateComponent = EmailTemplates[T] & { 23 | getSubject?: (props: TemplateProps, ctx: EmailContext) => string; 24 | }; 25 | -------------------------------------------------------------------------------- /apps/web/src/features/instance-settings/queries.ts: -------------------------------------------------------------------------------- 1 | import "server-only"; 2 | 3 | import { prisma } from "@rallly/database"; 4 | import { unstable_cache } from "next/cache"; 5 | import { instanceSettingsTag } from "./constants"; 6 | 7 | export const getInstanceSettings = unstable_cache( 8 | async () => { 9 | const instanceSettings = await prisma.instanceSettings.findUnique({ 10 | where: { 11 | id: 1, 12 | }, 13 | select: { 14 | disableUserRegistration: true, 15 | }, 16 | }); 17 | 18 | return { 19 | disableUserRegistration: 20 | instanceSettings?.disableUserRegistration ?? false, 21 | }; 22 | }, 23 | [], 24 | { 25 | tags: [instanceSettingsTag], 26 | }, 27 | ); 28 | -------------------------------------------------------------------------------- /apps/web/src/features/licensing/helpers/generate-license-key.ts: -------------------------------------------------------------------------------- 1 | import { customAlphabet } from "nanoid"; 2 | import { calculateChecksum } from "./calculate-checksum"; 3 | 4 | const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 5 | const generate = customAlphabet(alphabet, 4); 6 | 7 | /** 8 | * Generate user friendly licenses 9 | * eg. RLYV4-ABCD-1234-ABCD-1234-XXXX 10 | */ 11 | export const generateLicenseKey = ({ version }: { version?: number }) => { 12 | let license = `RLYV${version ?? "X"}-`; 13 | for (let i = 0; i < 4; i++) { 14 | license += generate(); 15 | if (i < 3) { 16 | license += "-"; 17 | } 18 | } 19 | const checksum = calculateChecksum(license); 20 | return `${license}-${checksum}`; 21 | }; 22 | -------------------------------------------------------------------------------- /apps/landing/src/i18n/client/link.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { defaultLocale } from "@rallly/languages"; 4 | import Link from "next/link"; 5 | import { useTranslation } from "@/i18n/client/use-translation"; 6 | 7 | export const LinkBase = ({ 8 | href, 9 | children, 10 | className, 11 | }: { 12 | href: string; 13 | children?: React.ReactNode; 14 | className?: string; 15 | }) => { 16 | const { i18n } = useTranslation(); 17 | const locale = 18 | i18n.resolvedLanguage === defaultLocale ? "" : `/${i18n.resolvedLanguage}`; 19 | const newHref = href.startsWith("/") ? `${locale}${href}` : href; 20 | 21 | return ( 22 | 23 | {children} 24 | 25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /apps/web/src/app/api/stripe/webhook/handlers/payment-method/updated.ts: -------------------------------------------------------------------------------- 1 | import type { Stripe } from "@rallly/billing"; 2 | import type { Prisma } from "@rallly/database"; 3 | import { prisma } from "@rallly/database"; 4 | 5 | export async function onPaymentMethodUpdated(event: Stripe.Event) { 6 | const paymentMethod = event.data.object as Stripe.PaymentMethod; 7 | 8 | if (!paymentMethod.customer) { 9 | return; 10 | } 11 | 12 | // Update the payment method data in our database 13 | await prisma.paymentMethod.update({ 14 | where: { 15 | id: paymentMethod.id, 16 | }, 17 | data: { 18 | type: paymentMethod.type, 19 | data: paymentMethod[paymentMethod.type] as Prisma.JsonObject, 20 | }, 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /apps/web/public/static/google.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/web/src/components/stacked-list.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@rallly/ui"; 2 | 3 | export function StackedList({ 4 | children, 5 | className, 6 | }: { 7 | children: React.ReactNode; 8 | className?: string; 9 | }) { 10 | return ( 11 |
    12 | {children} 13 |
14 | ); 15 | } 16 | 17 | export function StackedListItem({ 18 | children, 19 | className, 20 | }: { 21 | children: React.ReactNode; 22 | className?: string; 23 | }) { 24 | return ( 25 |
  • 31 | {children} 32 |
  • 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20220627191901_remove_user_relation/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `guest_id` on the `comments` table. All the data in the column will be lost. 5 | - You are about to drop the column `guest_id` on the `participants` table. All the data in the column will be lost. 6 | 7 | */ 8 | -- Set user_id to guest_id 9 | UPDATE "comments" 10 | SET "user_id" = "guest_id" 11 | WHERE "user_id" IS NULL; 12 | 13 | -- AlterTable 14 | ALTER TABLE "comments" DROP COLUMN "guest_id"; 15 | 16 | -- Set user_id to guest_id 17 | UPDATE "participants" 18 | SET "user_id" = "guest_id" 19 | WHERE "user_id" IS NULL; 20 | 21 | -- AlterTable 22 | ALTER TABLE "participants" DROP COLUMN "guest_id"; 23 | -------------------------------------------------------------------------------- /apps/web/src/features/space/mutations.ts: -------------------------------------------------------------------------------- 1 | import type { SpaceTier } from "@rallly/database"; 2 | import { prisma } from "@rallly/database"; 3 | import { isSelfHosted } from "@/utils/constants"; 4 | 5 | export async function createSpace({ 6 | name = "Personal", 7 | ownerId, 8 | tier = isSelfHosted ? "pro" : "hobby", 9 | }: { 10 | name?: string; 11 | ownerId: string; 12 | tier?: SpaceTier; 13 | }) { 14 | const space = await prisma.space.create({ 15 | data: { 16 | name, 17 | ownerId, 18 | tier, 19 | members: { 20 | create: { 21 | userId: ownerId, 22 | role: "ADMIN", 23 | lastSelectedAt: new Date(), 24 | }, 25 | }, 26 | }, 27 | }); 28 | 29 | return space; 30 | } 31 | -------------------------------------------------------------------------------- /packages/database/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rallly/database", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "db:generate": "prisma generate", 7 | "db:push": "prisma db push --skip-generate", 8 | "db:deploy": "prisma migrate deploy", 9 | "db:migrate": "prisma migrate dev", 10 | "db:seed": "tsx prisma/seed.ts", 11 | "type-check": "tsc --pretty --noEmit" 12 | }, 13 | "exports": "./index.ts", 14 | "dependencies": { 15 | "@prisma/client": "^6.8.2", 16 | "dayjs": "^1.11.13" 17 | }, 18 | "devDependencies": { 19 | "@faker-js/faker": "^7.6.0", 20 | "@rallly/tsconfig": "workspace:*", 21 | "@types/node": "^20.19.0", 22 | "prisma": "^6.8.2", 23 | "tsx": "^4.6.2" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /apps/web/src/app/api/status/route.ts: -------------------------------------------------------------------------------- 1 | import { prisma } from "@rallly/database"; 2 | import { NextResponse } from "next/server"; 3 | 4 | async function getDatabaseStatus() { 5 | try { 6 | await prisma.$connect(); 7 | return "connected"; 8 | } catch { 9 | return "disconnected"; 10 | } 11 | } 12 | 13 | export const GET = async () => { 14 | const database = await getDatabaseStatus(); 15 | const version = process.env.NEXT_PUBLIC_APP_VERSION || "unknown"; 16 | const environment = process.env.NODE_ENV; 17 | const timestamp = new Date().toISOString(); 18 | 19 | const status = { 20 | status: "ok", 21 | timestamp, 22 | version, 23 | environment, 24 | database, 25 | }; 26 | 27 | return NextResponse.json(status); 28 | }; 29 | -------------------------------------------------------------------------------- /apps/web/src/features/quick-create/quick-create-button.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@rallly/ui/button"; 2 | import { Icon } from "@rallly/ui/icon"; 3 | import { ZapIcon } from "lucide-react"; 4 | import Link from "next/link"; 5 | import { Trans } from "react-i18next/TransWithoutContext"; 6 | 7 | import { getTranslation } from "@/i18n/server"; 8 | 9 | export async function QuickCreateButton() { 10 | const { t } = await getTranslation(); 11 | return ( 12 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20250614115818_migrate_events_to_spaces/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - Added the required column `space_id` to the `scheduled_events` table without a default value. This is not possible if the table is not empty. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "scheduled_events" ADD COLUMN "space_id" TEXT; 9 | 10 | -- AddForeignKey 11 | ALTER TABLE "scheduled_events" ADD CONSTRAINT "scheduled_events_space_id_fkey" FOREIGN KEY ("space_id") REFERENCES "spaces"("id") ON DELETE CASCADE ON UPDATE CASCADE; 12 | 13 | UPDATE "scheduled_events" SET "space_id" = (SELECT "id" FROM "spaces" WHERE "owner_id" = "scheduled_events"."user_id" LIMIT 1); 14 | 15 | ALTER TABLE "scheduled_events" ALTER COLUMN "space_id" SET NOT NULL; 16 | -------------------------------------------------------------------------------- /apps/web/src/trpc/routers/index.ts: -------------------------------------------------------------------------------- 1 | import dayjs from "dayjs"; 2 | import timezone from "dayjs/plugin/timezone"; 3 | import toArray from "dayjs/plugin/toArray"; 4 | import utc from "dayjs/plugin/utc"; 5 | 6 | import { mergeRouters, router } from "../trpc"; 7 | import { auth } from "./auth"; 8 | import { calendars } from "./calendars"; 9 | import { events } from "./events"; 10 | import { polls } from "./polls"; 11 | import { user } from "./user"; 12 | 13 | dayjs.extend(toArray); // used for creating ics 14 | dayjs.extend(timezone); 15 | dayjs.extend(utc); 16 | 17 | export const appRouter = mergeRouters( 18 | router({ 19 | auth, 20 | events, 21 | polls, 22 | user, 23 | calendars, 24 | }), 25 | ); 26 | 27 | export type AppRouter = typeof appRouter; 28 | -------------------------------------------------------------------------------- /packages/posthog/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { POSTHOG_BOOTSTAP_DATA_COOKIE_NAME } from "./constants"; 2 | 3 | const posthogApiKey = process.env.NEXT_PUBLIC_POSTHOG_API_KEY; 4 | 5 | export function getPosthogBootstrapCookie(bootstrapData: { 6 | distinctID?: string; 7 | }) { 8 | if (!posthogApiKey) { 9 | return; 10 | } 11 | 12 | const domain = process.env.NEXT_PUBLIC_ROOT_DOMAIN 13 | ? `.${process.env.NEXT_PUBLIC_ROOT_DOMAIN}` 14 | : undefined; 15 | 16 | return { 17 | name: POSTHOG_BOOTSTAP_DATA_COOKIE_NAME, 18 | value: JSON.stringify(bootstrapData), 19 | httpOnly: false, 20 | secure: process.env.NEXT_PUBLIC_BASE_URL?.startsWith("https://") ?? false, 21 | sameSite: "lax" as const, 22 | path: "/", 23 | domain, 24 | }; 25 | } 26 | --------------------------------------------------------------------------------